Merge "Enable proguard for telephony library."
diff --git a/Android.bp b/Android.bp
index b3cb9dc..8a8ff6a 100644
--- a/Android.bp
+++ b/Android.bp
@@ -105,6 +105,13 @@
         "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",
         "voip-common",
         "ims-common",
         "unsupportedappusage",
diff --git a/OWNERS b/OWNERS
index 003cc3c..5b59a13 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,4 +1,3 @@
-amitmahajan@google.com
 breadley@google.com
 fionaxu@google.com
 jackyu@google.com
@@ -6,11 +5,11 @@
 tgunn@google.com
 jminjie@google.com
 shuoq@google.com
-nazaninb@google.com
 sarahchin@google.com
 xiaotonj@google.com
 huiwang@google.com
 jayachandranc@google.com
 chinmayd@google.com
 amruthr@google.com
+sasindran@google.com
 
diff --git a/jarjar-rules-shared.txt b/jarjar-rules-shared.txt
index 5635b18..4f3f617 100644
--- a/jarjar-rules-shared.txt
+++ b/jarjar-rules-shared.txt
@@ -4,6 +4,7 @@
 rule android.os.Registrant* com.android.internal.telephony.Registrant@1
 rule android.hidl.** android.internal.hidl.@1
 rule android.sysprop.** android.internal.telephony.sysprop.@1
+rule android.util.IndentingPrintWriter* com.android.internal.telephony.AndroidUtilIndentingPrintWriter@1
 rule android.util.LocalLog* com.android.internal.telephony.LocalLog@1
 rule android.util.TimeUtils* com.android.internal.telephony.TimeUtils@1
 rule com.android.internal.os.SomeArgs* com.android.internal.telephony.SomeArgs@1
diff --git a/proto/src/telephony.proto b/proto/src/telephony.proto
index e1ec3a9..0d9ee80 100644
--- a/proto/src/telephony.proto
+++ b/proto/src/telephony.proto
@@ -53,6 +53,9 @@
 
   // The last active subscription info for each slot.
   repeated ActiveSubscriptionInfo last_active_subscription_info = 10;
+
+  // Bandwidth estimator stats
+  optional BandwidthEstimatorStats bandwidth_estimator_stats = 11;
 }
 
 // The time information
@@ -418,6 +421,20 @@
   RAT_NR = 20;
 }
 
+// NR (5G) operation mode
+enum NrMode {
+  // The device is not in a NR network.
+  NR_NONE = 1;
+  // The device is in a NR non-standalone network at non-MMWAVE frequencies.
+  NR_NSA = 2;
+  // The device is in a NR non-standalone network at MMWAVE frequencies.
+  NR_NSA_MMWAVE = 3;
+  // The device is in a NR standalone network at non-MMWAVE frequencies.
+  NR_SA = 4;
+  // The device is in a NR standalone network at MMWAVE frequencies.
+  NR_SA_MMWAVE = 5;
+}
+
 // The information about IMS errors
 // https://cs.corp.google.com/#android/frameworks/base/telephony/java/com/android/ims/ImsReasonInfo.java
 message ImsReasonInfo {
@@ -2700,3 +2717,34 @@
   // Actual monitored rail energy consumed by modem (mAh)
   optional double monitored_rail_energy_consumed_mah = 15;
 }
+
+// Bandwidth estimator stats
+message BandwidthEstimatorStats {
+  // Bandwidth stats of each level
+  message PerLevel {
+    optional uint32 signal_level = 1;
+    // Accumulated bandwidth sample count
+    optional uint32 count = 2;
+    // Average end-to-end bandwidth in kbps
+    optional uint32 avg_bw_kbps = 3;
+    // Normalized error of static BW values in percent
+    optional uint32 static_bw_error_percent = 4;
+    // Normalized error of end-to-end BW estimation in percent
+    optional uint32 bw_est_error_percent = 5;
+  }
+
+  // Bandwidth stats of each RAT
+  message PerRat {
+    // radio access technology
+    optional RadioAccessTechnology rat = 1;
+    // NR (5g) operation mode
+    optional NrMode nr_mode = 2;
+    // bandwidth stats of signal levels
+    repeated PerLevel per_level = 3;
+  }
+
+  // Tx Stats of visited RATs
+  repeated PerRat per_rat_tx = 1;
+  // Rx Stats of visited RATs
+  repeated PerRat per_rat_rx = 2;
+}
diff --git a/src/java/com/android/internal/telephony/BtSmsInterfaceManager.java b/src/java/com/android/internal/telephony/BtSmsInterfaceManager.java
index 7271bf3..40e9a1c 100644
--- a/src/java/com/android/internal/telephony/BtSmsInterfaceManager.java
+++ b/src/java/com/android/internal/telephony/BtSmsInterfaceManager.java
@@ -18,7 +18,9 @@
 package com.android.internal.telephony;
 
 import android.app.PendingIntent;
+import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothMapClient;
 import android.bluetooth.BluetoothProfile;
 import android.content.Context;
 import android.net.Uri;
@@ -40,10 +42,6 @@
      */
     public void sendText(Context context, String destAddr, String text, PendingIntent sentIntent,
             PendingIntent deliveryIntent, SubscriptionInfo info) {
-        /*
-        This is to remove the usage of hidden constant MAP_CLIENT and hidden API
-        BluetoothMapClient.sendMessage(). This code is currently not functional anyway; it will be
-        re-enabled in a later release.
         BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
         if (btAdapter == null) {
             // No bluetooth service on this platform?
@@ -56,10 +54,11 @@
             sendErrorInPendingIntent(sentIntent, SmsManager.RESULT_INVALID_BLUETOOTH_ADDRESS);
             return;
         }
-        btAdapter.getProfileProxy(context.getApplicationContext(),
+        if (btAdapter.getProfileProxy(context.getApplicationContext(),
                 new MapMessageSender(destAddr, text, device, sentIntent, deliveryIntent),
-                BluetoothProfile.MAP_CLIENT);
-        */
+                BluetoothProfile.MAP_CLIENT)) {
+            return;
+        }
         throw new RuntimeException("Can't send message through BluetoothMapClient");
     }
 
@@ -100,7 +99,6 @@
         @Override
         public void onServiceConnected(int profile, BluetoothProfile proxy) {
             Log.d(LOG_TAG, "Service connected");
-            /*
             if (profile != BluetoothProfile.MAP_CLIENT) {
                 return;
             }
@@ -112,8 +110,6 @@
             }
             BluetoothAdapter.getDefaultAdapter()
                     .closeProfileProxy(BluetoothProfile.MAP_CLIENT, mapProfile);
-            */
-            throw new RuntimeException("Can't send message through BluetoothMapClient");
         }
 
         @Override
diff --git a/src/java/com/android/internal/telephony/CarrierInfoManager.java b/src/java/com/android/internal/telephony/CarrierInfoManager.java
index c73214a..00f8c39 100644
--- a/src/java/com/android/internal/telephony/CarrierInfoManager.java
+++ b/src/java/com/android/internal/telephony/CarrierInfoManager.java
@@ -254,7 +254,13 @@
             return;
         }
         mLastAccessResetCarrierKey = now;
-        deleteCarrierInfoForImsiEncryption(context);
+        int[] subIds = context.getSystemService(SubscriptionManager.class)
+                .getSubscriptionIds(mPhoneId);
+        if (subIds == null || subIds.length < 1) {
+            Log.e(LOG_TAG, "Could not reset carrier keys, subscription for mPhoneId=" + mPhoneId);
+            return;
+        }
+        deleteCarrierInfoForImsiEncryption(context, subIds[0]);
         Intent resetIntent = new Intent(TelephonyIntents.ACTION_CARRIER_CERTIFICATE_DOWNLOAD);
         SubscriptionManager.putPhoneIdAndSubIdExtra(resetIntent, mPhoneId);
         context.sendBroadcastAsUser(resetIntent, UserHandle.ALL);
@@ -264,12 +270,12 @@
      * Deletes all the keys for a given Carrier from the device keystore.
      * @param context Context
      */
-    public static void deleteCarrierInfoForImsiEncryption(Context context) {
-        Log.i(LOG_TAG, "deleting carrier key from db");
+    public static void deleteCarrierInfoForImsiEncryption(Context context, int subId) {
+        Log.i(LOG_TAG, "deleting carrier key from db for subId=" + subId);
         String mcc = "";
         String mnc = "";
-        final TelephonyManager telephonyManager =
-                (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+        final TelephonyManager telephonyManager = context.getSystemService(TelephonyManager.class)
+                .createForSubscriptionId(subId);
         String simOperator = telephonyManager.getSimOperator();
         if (!TextUtils.isEmpty(simOperator)) {
             mcc = simOperator.substring(0, 3);
diff --git a/src/java/com/android/internal/telephony/CarrierKeyDownloadManager.java b/src/java/com/android/internal/telephony/CarrierKeyDownloadManager.java
index 96b5ff1..757530e 100644
--- a/src/java/com/android/internal/telephony/CarrierKeyDownloadManager.java
+++ b/src/java/com/android/internal/telephony/CarrierKeyDownloadManager.java
@@ -81,8 +81,6 @@
     // This will define the end date of the window.
     private static final int END_RENEWAL_WINDOW_DAYS = 7;
 
-
-
     /* Intent for downloading the public key */
     private static final String INTENT_KEY_RENEWAL_ALARM_PREFIX =
             "com.android.internal.telephony.carrier_key_download_alarm";
@@ -121,7 +119,7 @@
         mContext = phone.getContext();
         IntentFilter filter = new IntentFilter();
         filter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
-        filter.addAction(INTENT_KEY_RENEWAL_ALARM_PREFIX + mPhone.getPhoneId());
+        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);
@@ -143,18 +141,22 @@
         @Override
         public void onReceive(Context context, Intent intent) {
             String action = intent.getAction();
-            int slotId = mPhone.getPhoneId();
-            if (action.equals(INTENT_KEY_RENEWAL_ALARM_PREFIX + slotId)) {
-                Log.d(LOG_TAG, "Handling key renewal alarm: " + action);
-                sendEmptyMessage(EVENT_ALARM_OR_CONFIG_CHANGE);
+            int slotIndex = SubscriptionManager.getSlotIndex(mPhone.getSubId());
+            int phoneId = mPhone.getPhoneId();
+            if (action.equals(INTENT_KEY_RENEWAL_ALARM_PREFIX)) {
+                int slotIndexExtra = intent.getIntExtra(SubscriptionManager.EXTRA_SLOT_INDEX, -1);
+                if (slotIndexExtra == slotIndex) {
+                    Log.d(LOG_TAG, "Handling key renewal alarm: " + action);
+                    sendEmptyMessage(EVENT_ALARM_OR_CONFIG_CHANGE);
+                }
             } else if (action.equals(TelephonyIntents.ACTION_CARRIER_CERTIFICATE_DOWNLOAD)) {
-                if (slotId == intent.getIntExtra(PhoneConstants.PHONE_KEY,
+                if (phoneId == intent.getIntExtra(PhoneConstants.PHONE_KEY,
                         SubscriptionManager.INVALID_SIM_SLOT_INDEX)) {
                     Log.d(LOG_TAG, "Handling reset intent: " + action);
                     sendEmptyMessage(EVENT_ALARM_OR_CONFIG_CHANGE);
                 }
             } else if (action.equals(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)) {
-                if (slotId == intent.getIntExtra(PhoneConstants.PHONE_KEY,
+                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);
@@ -217,8 +219,9 @@
 
     private void cleanupRenewalAlarms() {
         Log.d(LOG_TAG, "Cleaning up existing renewal alarms");
-        int slotId = mPhone.getPhoneId();
-        Intent intent = new Intent(INTENT_KEY_RENEWAL_ALARM_PREFIX + slotId);
+        int slotIndex = SubscriptionManager.getSlotIndex(mPhone.getSubId());
+        Intent intent = new Intent(INTENT_KEY_RENEWAL_ALARM_PREFIX);
+        intent.putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, slotIndex);
         PendingIntent carrierKeyDownloadIntent = PendingIntent.getBroadcast(mContext, 0, intent,
                 PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
         AlarmManager alarmManager =
@@ -273,12 +276,13 @@
     @VisibleForTesting
     public void resetRenewalAlarm() {
         cleanupRenewalAlarms();
-        int slotId = mPhone.getPhoneId();
+        int slotIndex = SubscriptionManager.getSlotIndex(mPhone.getSubId());
         long minExpirationDate = getExpirationDate();
         Log.d(LOG_TAG, "minExpirationDate: " + new Date(minExpirationDate));
         final AlarmManager alarmManager = (AlarmManager) mContext.getSystemService(
                 Context.ALARM_SERVICE);
-        Intent intent = new Intent(INTENT_KEY_RENEWAL_ALARM_PREFIX + slotId);
+        Intent intent = new Intent(INTENT_KEY_RENEWAL_ALARM_PREFIX);
+        intent.putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, slotIndex);
         PendingIntent carrierKeyDownloadIntent = PendingIntent.getBroadcast(mContext, 0, intent,
                 PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
         alarmManager.set(AlarmManager.RTC_WAKEUP, minExpirationDate, carrierKeyDownloadIntent);
diff --git a/src/java/com/android/internal/telephony/CarrierServiceBindHelper.java b/src/java/com/android/internal/telephony/CarrierServiceBindHelper.java
index 4b4980e..162da24 100644
--- a/src/java/com/android/internal/telephony/CarrierServiceBindHelper.java
+++ b/src/java/com/android/internal/telephony/CarrierServiceBindHelper.java
@@ -71,9 +71,6 @@
     private final PackageChangeReceiver mPackageMonitor = new CarrierServicePackageMonitor();
     private final LocalLog mLocalLog = new LocalLog(100);
 
-    // whether we have successfully bound to the service
-    private boolean mServiceBound = false;
-
     private BroadcastReceiver mUserUnlockedReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -291,7 +288,6 @@
                         (r) -> mHandler.post(r),
                         connection)) {
                     logdWithLocalLog("service bound");
-                    mServiceBound = true;
                     return;
                 }
 
@@ -346,16 +342,13 @@
             carrierPackage = null;
             carrierServiceClass = null;
 
-            // Actually unbind
-            if (mServiceBound) {
-                logdWithLocalLog("Unbinding from carrier app");
-                mServiceBound = false;
+            // Always call unbindService, no matter if bindService succeed.
+            if (connection != null) {
                 mContext.unbindService(connection);
-            } else {
-                logdWithLocalLog("Not bound, skipping unbindService call");
+                logdWithLocalLog("Unbinding from carrier app");
+                connection = null;
+                mUnbindScheduledUptimeMillis = -1;
             }
-            connection = null;
-            mUnbindScheduledUptimeMillis = -1;
         }
 
         private void cancelScheduledUnbind() {
diff --git a/src/java/com/android/internal/telephony/CarrierServiceStateTracker.java b/src/java/com/android/internal/telephony/CarrierServiceStateTracker.java
index c7e34a0..64dc7ec 100644
--- a/src/java/com/android/internal/telephony/CarrierServiceStateTracker.java
+++ b/src/java/com/android/internal/telephony/CarrierServiceStateTracker.java
@@ -32,9 +32,9 @@
 import android.telephony.CarrierConfigManager;
 import android.telephony.RadioAccessFamily;
 import android.telephony.ServiceState;
-import android.telephony.TelephonyCallback;
 import android.telephony.SubscriptionManager;
 import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
+import android.telephony.TelephonyCallback;
 import android.telephony.TelephonyManager;
 import android.telephony.TelephonyManager.NetworkTypeBitMask;
 
diff --git a/src/java/com/android/internal/telephony/CarrierServicesSmsFilter.java b/src/java/com/android/internal/telephony/CarrierServicesSmsFilter.java
index 61c8eae..6bc2450 100644
--- a/src/java/com/android/internal/telephony/CarrierServicesSmsFilter.java
+++ b/src/java/com/android/internal/telephony/CarrierServicesSmsFilter.java
@@ -27,6 +27,7 @@
 import android.service.carrier.CarrierMessagingServiceWrapper;
 import android.service.carrier.CarrierMessagingServiceWrapper.CarrierMessagingCallback;
 import android.service.carrier.MessagePdu;
+import android.telephony.AnomalyReporter;
 import android.util.LocalLog;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -40,10 +41,18 @@
 import java.util.List;
 import java.util.Optional;
 import java.util.Set;
+import java.util.UUID;
+import java.util.stream.Collectors;
 
 /**
  * Filters incoming SMS with carrier services.
- * <p> A new instance must be created for filtering each message.
+ *
+ * <p>A new instance must be created for filtering each message.
+ *
+ * <p>Note that if a carrier services app is unavailable at the time a message is received because
+ * credential-encrypted storage is unavailable and it is not direct-boot aware, and the message ends
+ * up being handled by a filter further down the chain, that message will not be redelivered to the
+ * carrier app once the user unlocks the storage.
  */
 public class CarrierServicesSmsFilter {
     protected static final boolean DBG = true;
@@ -53,6 +62,10 @@
     /** onFilterComplete timeout. */
     public static final int FILTER_COMPLETE_TIMEOUT_MS = 10 * 60 * 1000; //10 minutes
 
+    /** SMS anomaly uuid -- CarrierMessagingService did not respond */
+    private static final UUID sAnomalyNoResponseFromCarrierMessagingService =
+            UUID.fromString("94095e8e-b516-4065-a8be-e05b84071002");
+
     private final Context mContext;
     private final Phone mPhone;
     private final byte[][] mPdus;
@@ -117,7 +130,7 @@
             mFilterAggregator = new FilterAggregator(numPackages);
             //start the timer
             mCallbackTimeoutHandler.sendMessageDelayed(mCallbackTimeoutHandler
-                            .obtainMessage(EVENT_ON_FILTER_COMPLETE_NOT_CALLED),
+                            .obtainMessage(EVENT_ON_FILTER_COMPLETE_NOT_CALLED, mFilterAggregator),
                     FILTER_COMPLETE_TIMEOUT_MS);
             for (String smsFilterPackage : smsFilterPackages) {
                 filterWithPackage(smsFilterPackage, mFilterAggregator);
@@ -239,11 +252,12 @@
         void filterSms(CarrierSmsFilterCallback smsFilterCallback) {
             mSmsFilterCallback = smsFilterCallback;
             if (!mCarrierMessagingServiceWrapper.bindToCarrierMessagingService(
-                    mContext, mPackageName, ()-> onServiceReady())) {
-                loge("CarrierSmsFilter::filterSms: bindService() for failed for " + mPackageName);
-                smsFilterCallback.onFilterComplete(CarrierMessagingService.RECEIVE_OPTIONS_DEFAULT);
+                    mContext, mPackageName, runnable -> runnable.run(), ()-> onServiceReady())) {
+                loge("CarrierSmsFilter::filterSms: bindService() failed for " + mPackageName);
+                smsFilterCallback.onReceiveSmsComplete(
+                        CarrierMessagingService.RECEIVE_OPTIONS_DEFAULT);
             } else {
-                logv("CarrierSmsFilter::filterSms: bindService() for succeeded for "
+                logv("CarrierSmsFilter::filterSms: bindService() succeeded for "
                         + mPackageName);
             }
         }
@@ -255,12 +269,12 @@
         private void onServiceReady() {
             try {
                 log("onServiceReady: calling filterSms on " + mPackageName);
-                mCarrierMessagingServiceWrapper.filterSms(
+                mCarrierMessagingServiceWrapper.receiveSms(
                         new MessagePdu(Arrays.asList(mPdus)), mSmsFormat, mDestPort,
-                        mPhone.getSubId(), mSmsFilterCallback);
+                        mPhone.getSubId(), runnable -> runnable.run(), mSmsFilterCallback);
             } catch (RuntimeException e) {
                 loge("Exception filtering the SMS with " + mPackageName + ": " + e);
-                mSmsFilterCallback.onFilterComplete(
+                mSmsFilterCallback.onReceiveSmsComplete(
                         CarrierMessagingService.RECEIVE_OPTIONS_DEFAULT);
             }
         }
@@ -288,15 +302,15 @@
          * This method should be called only once.
          */
         @Override
-        public void onFilterComplete(int result) {
+        public void onReceiveSmsComplete(int result) {
             log("CarrierSmsFilterCallback::onFilterComplete: Called from " + mPackageName
                     + " with result: " + result);
             // in the case that timeout has already passed and triggered, but the initial callback
             // is run afterwards, we should not follow through
             if (!mIsOnFilterCompleteCalled) {
                 mIsOnFilterCompleteCalled = true;
-                mCarrierMessagingServiceWrapper.disposeConnection(mContext);
-                mFilterAggregator.onFilterComplete(result);
+                mCarrierMessagingServiceWrapper.disconnect();
+                mFilterAggregator.onFilterComplete(result, this);
             }
         }
 
@@ -337,9 +351,10 @@
             mFilterResult = CarrierMessagingService.RECEIVE_OPTIONS_DEFAULT;
         }
 
-        void onFilterComplete(int result) {
+        void onFilterComplete(int result, CarrierSmsFilterCallback callback) {
             synchronized (mFilterLock) {
                 mNumPendingFilters--;
+                mCallbacks.remove(callback);
                 combine(result);
                 if (mNumPendingFilters == 0) {
                     // Calling identity was the CarrierMessagingService in this callback, change it
@@ -387,6 +402,12 @@
                 case EVENT_ON_FILTER_COMPLETE_NOT_CALLED:
                     mLocalLog.log("CarrierServicesSmsFilter: onFilterComplete timeout: not"
                             + " called before " + FILTER_COMPLETE_TIMEOUT_MS + " milliseconds.");
+                    FilterAggregator filterAggregator = (FilterAggregator) msg.obj;
+                    String packages = filterAggregator.mCallbacks.stream()
+                            .map(callback -> callback.mPackageName)
+                            .collect(Collectors.joining(", "));
+                    AnomalyReporter.reportAnomaly(sAnomalyNoResponseFromCarrierMessagingService,
+                            "No response from " + packages);
                     handleFilterCallbacksTimeout();
                     break;
             }
@@ -395,7 +416,7 @@
         private void handleFilterCallbacksTimeout() {
             for (CarrierSmsFilterCallback callback : mFilterAggregator.mCallbacks) {
                 log("handleFilterCallbacksTimeout: calling onFilterComplete");
-                callback.onFilterComplete(CarrierMessagingService.RECEIVE_OPTIONS_DEFAULT);
+                callback.onReceiveSmsComplete(CarrierMessagingService.RECEIVE_OPTIONS_DEFAULT);
             }
         }
     }
diff --git a/src/java/com/android/internal/telephony/CellBroadcastServiceManager.java b/src/java/com/android/internal/telephony/CellBroadcastServiceManager.java
index 703160b..73da797 100644
--- a/src/java/com/android/internal/telephony/CellBroadcastServiceManager.java
+++ b/src/java/com/android/internal/telephony/CellBroadcastServiceManager.java
@@ -325,6 +325,17 @@
         pw.println("CellBroadcastServiceManager:");
         pw.println(" mEnabled=" + mEnabled);
         pw.println(" mCellBroadcastServicePackage=" + mCellBroadcastServicePackage);
+        if (mEnabled) {
+            try {
+                if (sServiceConnection != null && sServiceConnection.mService != null) {
+                    sServiceConnection.mService.dump(fd, args);
+                } else {
+                    pw.println(" sServiceConnection is null");
+                }
+            } catch (RemoteException e) {
+                pw.println(" mService.dump() threw RemoteException e: " + e.toString());
+            }
+        }
         mLocalLog.dump(fd, pw, args);
         pw.flush();
     }
diff --git a/src/java/com/android/internal/telephony/CellularNetworkService.java b/src/java/com/android/internal/telephony/CellularNetworkService.java
index 886438a..b9d5e0e 100644
--- a/src/java/com/android/internal/telephony/CellularNetworkService.java
+++ b/src/java/com/android/internal/telephony/CellularNetworkService.java
@@ -221,16 +221,12 @@
             final int domain = NetworkRegistrationInfo.DOMAIN_CS;
 
             if (result instanceof android.hardware.radio.V1_6.RegStateResult) {
-                return getNetworkRegistrationInfo1_6(
-                        domain,
-                        transportType,
+                return getNetworkRegistrationInfo1_6(domain, transportType,
                         (android.hardware.radio.V1_6.RegStateResult) result);
             }
             // 1.5 at the top so that we can do an "early exit" from the method
             else if (result instanceof android.hardware.radio.V1_5.RegStateResult) {
-                return getNetworkRegistrationInfo(
-                        domain,
-                        transportType,
+                return getNetworkRegistrationInfo(domain, transportType,
                         (android.hardware.radio.V1_5.RegStateResult) result);
             } else if (result instanceof android.hardware.radio.V1_0.VoiceRegStateResult) {
                 android.hardware.radio.V1_0.VoiceRegStateResult voiceRegState =
@@ -245,7 +241,8 @@
                 int defaultRoamingIndicator = voiceRegState.defaultRoamingIndicator;
                 List<Integer> availableServices = getAvailableServices(
                         regState, domain, emergencyOnly);
-                CellIdentity cellIdentity = CellIdentity.create(voiceRegState.cellIdentity);
+                CellIdentity cellIdentity =
+                        RILUtils.convertHalCellIdentity(voiceRegState.cellIdentity);
                 final String rplmn = getPlmnFromCellIdentity(cellIdentity);
 
                 return new NetworkRegistrationInfo(domain, transportType, regState,
@@ -265,7 +262,8 @@
                 int defaultRoamingIndicator = voiceRegState.defaultRoamingIndicator;
                 List<Integer> availableServices = getAvailableServices(
                         regState, domain, emergencyOnly);
-                CellIdentity cellIdentity = CellIdentity.create(voiceRegState.cellIdentity);
+                CellIdentity cellIdentity =
+                        RILUtils.convertHalCellIdentity(voiceRegState.cellIdentity);
                 final String rplmn = getPlmnFromCellIdentity(cellIdentity);
 
                 return new NetworkRegistrationInfo(domain, transportType, regState,
@@ -296,16 +294,12 @@
                             LteVopsSupportInfo.LTE_STATUS_NOT_AVAILABLE);
 
             if (result instanceof android.hardware.radio.V1_6.RegStateResult) {
-                return getNetworkRegistrationInfo1_6(
-                        domain,
-                        transportType,
+                return getNetworkRegistrationInfo1_6(domain, transportType,
                         (android.hardware.radio.V1_6.RegStateResult) result);
             }
             // 1.5 at the top so that we can do an "early exit" from the method
             else if (result instanceof android.hardware.radio.V1_5.RegStateResult) {
-                return getNetworkRegistrationInfo(
-                        domain,
-                        transportType,
+                return getNetworkRegistrationInfo(domain, transportType,
                         (android.hardware.radio.V1_5.RegStateResult) result);
             } else if (result instanceof android.hardware.radio.V1_0.DataRegStateResult) {
                 android.hardware.radio.V1_0.DataRegStateResult dataRegState =
@@ -315,7 +309,7 @@
                 reasonForDenial = dataRegState.reasonDataDenied;
                 emergencyOnly = isEmergencyOnly(dataRegState.regState);
                 maxDataCalls = dataRegState.maxDataCalls;
-                cellIdentity = CellIdentity.create(dataRegState.cellIdentity);
+                cellIdentity = RILUtils.convertHalCellIdentity(dataRegState.cellIdentity);
             } else if (result instanceof android.hardware.radio.V1_2.DataRegStateResult) {
                 android.hardware.radio.V1_2.DataRegStateResult dataRegState =
                         (android.hardware.radio.V1_2.DataRegStateResult) result;
@@ -324,7 +318,7 @@
                 reasonForDenial = dataRegState.reasonDataDenied;
                 emergencyOnly = isEmergencyOnly(dataRegState.regState);
                 maxDataCalls = dataRegState.maxDataCalls;
-                cellIdentity = CellIdentity.create(dataRegState.cellIdentity);
+                cellIdentity = RILUtils.convertHalCellIdentity(dataRegState.cellIdentity);
             } else if (result instanceof android.hardware.radio.V1_4.DataRegStateResult) {
                 android.hardware.radio.V1_4.DataRegStateResult dataRegState =
                         (android.hardware.radio.V1_4.DataRegStateResult) result;
@@ -334,7 +328,7 @@
                 reasonForDenial = dataRegState.base.reasonDataDenied;
                 emergencyOnly = isEmergencyOnly(dataRegState.base.regState);
                 maxDataCalls = dataRegState.base.maxDataCalls;
-                cellIdentity = CellIdentity.create(dataRegState.base.cellIdentity);
+                cellIdentity = RILUtils.convertHalCellIdentity(dataRegState.base.cellIdentity);
                 android.hardware.radio.V1_4.NrIndicators nrIndicators = dataRegState.nrIndicators;
 
                 // Check for lteVopsInfo only if its initialized and RAT is EUTRAN
@@ -379,7 +373,8 @@
             final List<Integer> availableServices = getAvailableServices(
                     regState, domain, isEmergencyOnly);
             final int rejectCause = regResult.reasonForDenial;
-            final CellIdentity cellIdentity = CellIdentity.create(regResult.cellIdentity);
+            final CellIdentity cellIdentity =
+                    RILUtils.convertHalCellIdentity(regResult.cellIdentity);
             final String rplmn = regResult.registeredPlmn;
             final int reasonForDenial = regResult.reasonForDenial;
 
@@ -459,7 +454,8 @@
             final List<Integer> availableServices = getAvailableServices(
                     regState, domain, isEmergencyOnly);
             final int rejectCause = regResult.reasonForDenial;
-            final CellIdentity cellIdentity = CellIdentity.create(regResult.cellIdentity);
+            final CellIdentity cellIdentity =
+                    RILUtils.convertHalCellIdentity(regResult.cellIdentity);
             final String rplmn = regResult.registeredPlmn;
             final int reasonForDenial = regResult.reasonForDenial;
 
diff --git a/src/java/com/android/internal/telephony/CommandsInterface.java b/src/java/com/android/internal/telephony/CommandsInterface.java
index f9039ec..a6ff3fa 100644
--- a/src/java/com/android/internal/telephony/CommandsInterface.java
+++ b/src/java/com/android/internal/telephony/CommandsInterface.java
@@ -2638,7 +2638,6 @@
      */
     default void cancelHandover(Message result, int callId) {};
 
-
     /**
      * Control the data throttling at modem.
      *
@@ -2652,6 +2651,14 @@
     default void setDataThrottling(Message result, WorkSource workSource,
             int dataThrottlingAction, long completionWindowMillis) {};
 
+    /**
+     * Request to get the current slicing configuration including URSP rules and
+     * NSSAIs (configured, allowed and rejected).
+     *
+     * @param result Message that will be sent back to handler.
+     */
+    default void getSlicingConfig(Message result) {};
+
    /**
      * Request the SIM phonebook records of all activated UICC applications
      *
@@ -2704,12 +2711,4 @@
      * @param h Handler to be removed from the registrant list.
      */
      public void unregisterForSimPhonebookRecordsReceived(Handler h);
-
-    /**
-     * Request to get the current slicing configuration including URSP rules and
-     * NSSAIs (configured, allowed and rejected).
-     *
-     * @param result Message that will be sent back to handler.
-     */
-    default void getSlicingConfig(Message result) {};
 }
diff --git a/src/java/com/android/internal/telephony/Connection.java b/src/java/com/android/internal/telephony/Connection.java
index 54cce0b..c60e5df 100644
--- a/src/java/com/android/internal/telephony/Connection.java
+++ b/src/java/com/android/internal/telephony/Connection.java
@@ -92,6 +92,11 @@
          * local device.
          */
         public static final int IS_PULLABLE = 0x00000020;
+
+        /**
+         * For an IMS call, indicates that the peer supports RTT.
+         */
+        public static final int SUPPORTS_RTT_REMOTE = 0x00000040;
     }
 
     /**
@@ -105,6 +110,7 @@
         public void onVideoProviderChanged(
                 android.telecom.Connection.VideoProvider videoProvider);
         public void onAudioQualityChanged(int audioQuality);
+        public void onMediaAttributesChanged();
         public void onConferenceParticipantsChanged(List<ConferenceParticipant> participants);
         public void onCallSubstateChanged(int callSubstate);
         public void onMultipartyStateChanged(boolean isMultiParty);
@@ -151,6 +157,8 @@
         @Override
         public void onAudioQualityChanged(int audioQuality) {}
         @Override
+        public void onMediaAttributesChanged() {}
+        @Override
         public void onConferenceParticipantsChanged(List<ConferenceParticipant> participants) {}
         @Override
         public void onCallSubstateChanged(int callSubstate) {}
@@ -1101,6 +1109,15 @@
     }
 
     /**
+     * Notifies interested parties of changes to the media attributes of the call.
+     */
+    public void notifyMediaAttributesChanged() {
+        for (Listener l: mListeners) {
+            l.onMediaAttributesChanged();
+        }
+    }
+
+    /**
      * Notifies listeners that connection extras has changed.
      * @param extras New connection extras. This Bundle will be cloned to ensure that any concurrent
      * modifications to the extras Bundle do not affect Bundle operations in the onExtrasChanged
diff --git a/src/java/com/android/internal/telephony/DataIndication.java b/src/java/com/android/internal/telephony/DataIndication.java
new file mode 100644
index 0000000..e56cd85
--- /dev/null
+++ b/src/java/com/android/internal/telephony/DataIndication.java
@@ -0,0 +1,107 @@
+/*
+ * 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 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;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_UNTHROTTLE_APN;
+
+import android.hardware.radio.data.IRadioDataIndication;
+import android.os.AsyncResult;
+import android.os.RemoteException;
+import android.telephony.PcoData;
+import android.telephony.data.DataCallResponse;
+
+import com.android.internal.telephony.dataconnection.KeepaliveStatus;
+
+import java.util.ArrayList;
+
+/**
+ * Interface declaring unsolicited radio indications for data APIs.
+ */
+public class DataIndication extends IRadioDataIndication.Stub {
+    private final RIL mRil;
+
+    public DataIndication(RIL ril) {
+        mRil = ril;
+    }
+
+    /**
+     * Indicates data call contexts have changed.
+     * @param indicationType Type of radio indication
+     * @param dcList List of SetupDataCallResult identical to that returned by getDataCallList.
+     *        It is the complete list of current data contexts including new contexts that have
+     *        been activated.
+     */
+    public void dataCallListChanged(int indicationType,
+            android.hardware.radio.data.SetupDataCallResult[] dcList) {
+        mRil.processIndication(indicationType);
+
+        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_DATA_CALL_LIST_CHANGED, dcList);
+        ArrayList<DataCallResponse> response = RILUtils.convertHalDataCallResultList(dcList);
+        mRil.mDataCallListChangedRegistrants.notifyRegistrants(
+                new AsyncResult(null, response, null));
+    }
+
+    /**
+     * Indicates a status update for an ongoing Keepalive session.
+     * @param indicationType Type of radio indication
+     * @param halStatus Status of the ongoing Keepalive session
+     */
+    public void keepaliveStatus(int indicationType,
+            android.hardware.radio.data.KeepaliveStatus halStatus) {
+        mRil.processIndication(indicationType);
+
+        if (RIL.RILJ_LOGD) {
+            mRil.unsljLogRet(RIL_UNSOL_KEEPALIVE_STATUS, "handle=" + halStatus.sessionHandle
+                    + " code=" +  halStatus.code);
+        }
+
+        KeepaliveStatus ks = new KeepaliveStatus(halStatus.sessionHandle, halStatus.code);
+        mRil.mNattKeepaliveStatusRegistrants.notifyRegistrants(new AsyncResult(null, ks, null));
+    }
+
+    /**
+     * Indicates when there is new Carrier PCO data received for a data call.
+     * @param indicationType Type of radio indication
+     * @param pco New PcoData
+     */
+    public void pcoData(int indicationType, android.hardware.radio.data.PcoDataInfo pco) {
+        mRil.processIndication(indicationType);
+
+        PcoData response = new PcoData(pco.cid, pco.bearerProto, pco.pcoId, pco.contents);
+
+        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_PCO_DATA, response);
+
+        mRil.mPcoDataRegistrants.notifyRegistrants(new AsyncResult(null, response, null));
+    }
+
+    /**
+     * Stop throttling calls to setupDataCall for the given APN.
+     * @param indicationType Type of radio indication
+     * @param apn APN to unthrottle
+     * @throws RemoteException
+     */
+    public void unthrottleApn(int indicationType, String apn) throws RemoteException {
+        mRil.processIndication(indicationType);
+
+        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_UNTHROTTLE_APN, apn);
+
+        mRil.mApnUnthrottledRegistrants.notifyRegistrants(new AsyncResult(null, apn, null));
+    }
+}
diff --git a/src/java/com/android/internal/telephony/DataResponse.java b/src/java/com/android/internal/telephony/DataResponse.java
new file mode 100644
index 0000000..3ff9b53
--- /dev/null
+++ b/src/java/com/android/internal/telephony/DataResponse.java
@@ -0,0 +1,229 @@
+/*
+ * 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 android.hardware.radio.RadioError;
+import android.hardware.radio.RadioResponseInfo;
+import android.hardware.radio.data.IRadioDataResponse;
+import android.telephony.data.DataCallResponse;
+import android.telephony.data.NetworkSlicingConfig;
+
+import com.android.internal.telephony.dataconnection.KeepaliveStatus;
+
+import java.util.ArrayList;
+
+/**
+ * Interface declaring response functions to solicited radio requests for data APIs.
+ */
+public class DataResponse extends IRadioDataResponse.Stub {
+    private final RIL mRil;
+
+    public DataResponse(RIL ril) {
+        mRil = ril;
+    }
+
+    /**
+     * Acknowledge the receipt of radio request sent to the vendor. This must be sent only for
+     * radio request which take long time to respond.
+     * For more details, refer https://source.android.com/devices/tech/connect/ril.html
+     * @param serial Serial no. of the request whose acknowledgement is sent.
+     */
+    public void acknowledgeRequest(int serial) {
+        mRil.processRequestAck(serial);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param id The pdu session id allocated
+     */
+    public void allocatePduSessionIdResponse(RadioResponseInfo responseInfo, int id) {
+        RILRequest rr = mRil.processResponse(responseInfo);
+        if (rr != null) {
+            if (responseInfo.error == RadioError.NONE) {
+                RadioResponse.sendMessageResponse(rr.mResult, id);
+            }
+            mRil.processResponseDone(rr, responseInfo, id);
+        }
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void cancelHandoverResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void deactivateDataCallResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param dataCallResultList Response to get data call list as defined by SetupDataCallResult
+     */
+    public void getDataCallListResponse(RadioResponseInfo responseInfo,
+            android.hardware.radio.data.SetupDataCallResult[] dataCallResultList) {
+        RILRequest rr = mRil.processResponse(responseInfo);
+
+        if (rr != null) {
+            ArrayList<DataCallResponse> response =
+                    RILUtils.convertHalDataCallResultList(dataCallResultList);
+            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
+     * @param slicingConfig Current slicing configuration
+     */
+    public void getSlicingConfigResponse(RadioResponseInfo responseInfo,
+                android.hardware.radio.data.SlicingConfig slicingConfig) {
+        RILRequest rr = mRil.processResponse(responseInfo);
+
+        if (rr != null) {
+            NetworkSlicingConfig ret = RILUtils.convertHalSlicingConfig(slicingConfig);
+            if (responseInfo.error == RadioError.NONE) {
+                RadioResponse.sendMessageResponse(rr.mResult, ret);
+            }
+            mRil.processResponseDone(rr, responseInfo, ret);
+        }
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void releasePduSessionIdResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void setDataAllowedResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void setDataProfileResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void setDataThrottlingResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void setInitialAttachApnResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param setupDataCallResult Response to data call setup as defined by SetupDataCallResult
+     */
+    public void setupDataCallResponse(RadioResponseInfo responseInfo,
+            android.hardware.radio.data.SetupDataCallResult setupDataCallResult) {
+        RILRequest rr = mRil.processResponse(responseInfo);
+
+        if (rr != null) {
+            DataCallResponse response = RILUtils.convertHalDataCallResult(setupDataCallResult);
+            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 startHandoverResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param keepaliveStatus status of the keepalive with a handle for the session
+     */
+    public void startKeepaliveResponse(RadioResponseInfo responseInfo,
+            android.hardware.radio.data.KeepaliveStatus keepaliveStatus) {
+
+        RILRequest rr = mRil.processResponse(responseInfo);
+        if (rr == null) return;
+
+        KeepaliveStatus ret = null;
+        try {
+            switch(responseInfo.error) {
+                case RadioError.NONE:
+                    int convertedStatus = RILUtils.convertHalKeepaliveStatusCode(
+                            keepaliveStatus.code);
+                    if (convertedStatus < 0) {
+                        ret = new KeepaliveStatus(KeepaliveStatus.ERROR_UNSUPPORTED);
+                    } else {
+                        ret = new KeepaliveStatus(keepaliveStatus.sessionHandle, convertedStatus);
+                    }
+                    // If responseInfo.error is NONE, response function sends the response message
+                    // even if result is actually an error.
+                    RadioResponse.sendMessageResponse(rr.mResult, ret);
+                    break;
+                case RadioError.REQUEST_NOT_SUPPORTED:
+                    ret = new KeepaliveStatus(KeepaliveStatus.ERROR_UNSUPPORTED);
+                    break;
+                case RadioError.NO_RESOURCES:
+                    ret = new KeepaliveStatus(KeepaliveStatus.ERROR_NO_RESOURCES);
+                    break;
+                default:
+                    ret = new KeepaliveStatus(KeepaliveStatus.ERROR_UNKNOWN);
+                    break;
+            }
+        } finally {
+            // If responseInfo.error != NONE, the processResponseDone sends the response message.
+            mRil.processResponseDone(rr, responseInfo, ret);
+        }
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void stopKeepaliveResponse(RadioResponseInfo responseInfo) {
+        RILRequest rr = mRil.processResponse(responseInfo);
+        if (rr == null) return;
+
+        try {
+            if (responseInfo.error == RadioError.NONE) {
+                RadioResponse.sendMessageResponse(rr.mResult, null);
+            } else {
+                //TODO: Error code translation
+            }
+        } finally {
+            mRil.processResponseDone(rr, responseInfo, null);
+        }
+    }
+}
diff --git a/src/java/com/android/internal/telephony/DeviceStateMonitor.java b/src/java/com/android/internal/telephony/DeviceStateMonitor.java
index 8692fef..3967604 100644
--- a/src/java/com/android/internal/telephony/DeviceStateMonitor.java
+++ b/src/java/com/android/internal/telephony/DeviceStateMonitor.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.telephony;
 
+import static android.app.UiModeManager.PROJECTION_TYPE_AUTOMOTIVE;
 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;
@@ -25,7 +26,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.res.Configuration;
 import android.hardware.display.DisplayManager;
 import android.hardware.radio.V1_5.IndicationFilter;
 import android.net.ConnectivityManager;
@@ -41,9 +41,7 @@
 import android.os.RegistrantList;
 import android.provider.Settings;
 import android.telephony.AccessNetworkConstants.AccessNetworkType;
-import android.telephony.CarrierConfigManager;
 import android.telephony.NetworkRegistrationInfo;
-import android.telephony.SignalThresholdInfo;
 import android.util.LocalLog;
 import android.view.Display;
 
@@ -71,7 +69,7 @@
     protected static final String TAG = DeviceStateMonitor.class.getSimpleName();
 
     static final int EVENT_RIL_CONNECTED                = 0;
-    static final int EVENT_CAR_MODE_CHANGED             = 1;
+    static final int EVENT_AUTOMOTIVE_PROJECTION_STATE_CHANGED = 1;
     @VisibleForTesting
     static final int EVENT_SCREEN_STATE_CHANGED         = 2;
     static final int EVENT_POWER_SAVE_MODE_CHANGED      = 3;
@@ -82,8 +80,6 @@
     @VisibleForTesting
     static final int EVENT_WIFI_CONNECTION_CHANGED      = 7;
     static final int EVENT_UPDATE_ALWAYS_REPORT_SIGNAL_STRENGTH = 8;
-    static final int EVENT_RADIO_ON                     = 9;
-    static final int EVENT_RADIO_OFF_OR_NOT_AVAILABLE   = 10;
 
     private static final int WIFI_UNAVAILABLE = 0;
     private static final int WIFI_AVAILABLE = 1;
@@ -175,17 +171,11 @@
     private boolean mIsWifiConnected;
 
     /**
-     * Car mode is on. True means the device is currently connected to Android Auto. This should be
-     * handled by mIsScreenOn, but the Android Auto display is private and not accessible by
-     * DeviceStateMonitor from DisplayMonitor.
+     * Automotive projection is active. True means the device is currently connected to Android
+     * Auto. This should be handled by mIsScreenOn, but the Android Auto display is private and not
+     * accessible by DeviceStateMonitor from DisplayMonitor.
      */
-    private boolean mIsCarModeOn;
-
-    /**
-     * Radio is on. False means that radio is either off or not available and it is ok to reduce
-     * commands to the radio to avoid unnecessary power consumption.
-     */
-    private boolean mIsRadioOn;
+    private boolean mIsAutomotiveProjectionActive;
 
     /**
      * True indicates we should always enable the signal strength reporting from radio.
@@ -256,14 +246,6 @@
                     msg = obtainMessage(EVENT_TETHERING_STATE_CHANGED);
                     msg.arg1 = isTetheringOn ? 1 : 0;
                     break;
-                case UiModeManager.ACTION_ENTER_CAR_MODE_PRIORITIZED:
-                    msg = obtainMessage(EVENT_CAR_MODE_CHANGED);
-                    msg.arg1 = 1; // car mode on
-                    break;
-                case UiModeManager.ACTION_EXIT_CAR_MODE_PRIORITIZED:
-                    msg = obtainMessage(EVENT_CAR_MODE_CHANGED);
-                    msg.arg1 = 0; // car mode off
-                    break;
                 default:
                     log("Unexpected broadcast intent: " + intent, false);
                     return;
@@ -287,8 +269,7 @@
         mIsPowerSaveOn = isPowerSaveModeOn();
         mIsCharging = isDeviceCharging();
         mIsScreenOn = isScreenOn();
-        mIsRadioOn = isRadioOn();
-        mIsCarModeOn = isCarModeOn();
+        mIsAutomotiveProjectionActive = isAutomotiveProjectionActive();
         // Assuming tethering is always off after boot up.
         mIsTetheringOn = false;
         mIsLowDataExpected = false;
@@ -298,36 +279,41 @@
                 + ", mIsCharging=" + mIsCharging
                 + ", mIsPowerSaveOn=" + mIsPowerSaveOn
                 + ", mIsLowDataExpected=" + mIsLowDataExpected
-                + ", mIsCarModeOn=" + mIsCarModeOn
+                + ", mIsAutomotiveProjectionActive=" + mIsAutomotiveProjectionActive
                 + ", mIsWifiConnected=" + mIsWifiConnected
                 + ", mIsAlwaysSignalStrengthReportingEnabled="
-                + mIsAlwaysSignalStrengthReportingEnabled
-                + ", mIsRadioOn=" + mIsRadioOn, false);
+                + mIsAlwaysSignalStrengthReportingEnabled, false);
 
         final IntentFilter filter = new IntentFilter();
         filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
         filter.addAction(BatteryManager.ACTION_CHARGING);
         filter.addAction(BatteryManager.ACTION_DISCHARGING);
         filter.addAction(TetheringManager.ACTION_TETHER_STATE_CHANGED);
-        filter.addAction(UiModeManager.ACTION_ENTER_CAR_MODE_PRIORITIZED);
-        filter.addAction(UiModeManager.ACTION_EXIT_CAR_MODE_PRIORITIZED);
         mPhone.getContext().registerReceiver(mBroadcastReceiver, filter, null, mPhone);
 
         mPhone.mCi.registerForRilConnected(this, EVENT_RIL_CONNECTED, null);
         mPhone.mCi.registerForAvailable(this, EVENT_RADIO_AVAILABLE, null);
-        mPhone.mCi.registerForOn(this, EVENT_RADIO_ON, null);
-        mPhone.mCi.registerForOffOrNotAvailable(this, EVENT_RADIO_OFF_OR_NOT_AVAILABLE, null);
 
         ConnectivityManager cm = (ConnectivityManager) phone.getContext().getSystemService(
                 Context.CONNECTIVITY_SERVICE);
         cm.registerNetworkCallback(mWifiNetworkRequest, mNetworkCallback);
+
+        UiModeManager umm = (UiModeManager) phone.getContext().getSystemService(
+                Context.UI_MODE_SERVICE);
+        umm.addOnProjectionStateChangedListener(PROJECTION_TYPE_AUTOMOTIVE,
+                phone.getContext().getMainExecutor(),
+                (t, pkgs) -> {
+                    Message msg = obtainMessage(EVENT_AUTOMOTIVE_PROJECTION_STATE_CHANGED);
+                    msg.arg1 = Math.min(pkgs.size(), 1);
+                    sendMessage(msg);
+                });
     }
 
     /**
      * @return True if low data is expected
      */
     private boolean isLowDataExpected() {
-        return (!mIsCharging && !mIsTetheringOn && !mIsScreenOn) || !mIsRadioOn;
+        return !mIsCharging && !mIsTetheringOn && !mIsScreenOn;
     }
 
     /**
@@ -411,14 +397,12 @@
      * @return True if the response update should be enabled.
      */
     public boolean shouldEnableHighPowerConsumptionIndications() {
-        // We should enable indications reports if radio is on and one of the following conditions
-        // is true:
+        // We should enable indications reports if one of the following condition is true.
         // 1. The device is charging.
         // 2. When the screen is on.
         // 3. When the tethering is on.
-        // 4. When car mode (Android Auto) is on.
-        return (mIsCharging || mIsScreenOn || mIsTetheringOn || mIsCarModeOn)
-                && mIsRadioOn;
+        // 4. When automotive projection (Android Auto) is on.
+        return mIsCharging || mIsScreenOn || mIsTetheringOn || mIsAutomotiveProjectionActive;
     }
 
     /**
@@ -469,18 +453,12 @@
             case EVENT_RADIO_AVAILABLE:
                 onReset();
                 break;
-            case EVENT_RADIO_ON:
-                onUpdateDeviceState(msg.what, /* state= */ true);
-                break;
-            case EVENT_RADIO_OFF_OR_NOT_AVAILABLE:
-                onUpdateDeviceState(msg.what, /* state= */ false);
-                break;
             case EVENT_SCREEN_STATE_CHANGED:
             case EVENT_POWER_SAVE_MODE_CHANGED:
             case EVENT_CHARGING_STATE_CHANGED:
             case EVENT_TETHERING_STATE_CHANGED:
             case EVENT_UPDATE_ALWAYS_REPORT_SIGNAL_STRENGTH:
-            case EVENT_CAR_MODE_CHANGED:
+            case EVENT_AUTOMOTIVE_PROJECTION_STATE_CHANGED:
                 onUpdateDeviceState(msg.what, msg.arg1 != 0);
                 break;
             case EVENT_WIFI_CONNECTION_CHANGED:
@@ -510,11 +488,6 @@
                 mIsCharging = state;
                 sendDeviceState(CHARGING_STATE, mIsCharging);
                 break;
-            case EVENT_RADIO_ON:
-            case EVENT_RADIO_OFF_OR_NOT_AVAILABLE:
-                if (mIsRadioOn == state) return;
-                mIsRadioOn = state;
-                break;
             case EVENT_TETHERING_STATE_CHANGED:
                 if (mIsTetheringOn == state) return;
                 mIsTetheringOn = state;
@@ -532,9 +505,9 @@
                 if (mIsAlwaysSignalStrengthReportingEnabled == state) return;
                 mIsAlwaysSignalStrengthReportingEnabled = state;
                 break;
-            case EVENT_CAR_MODE_CHANGED:
-                if (mIsCarModeOn == state) return;
-                mIsCarModeOn = state;
+            case EVENT_AUTOMOTIVE_PROJECTION_STATE_CHANGED:
+                if (mIsAutomotiveProjectionActive == state) return;
+                mIsAutomotiveProjectionActive = state;
                 break;
             default:
                 return;
@@ -615,7 +588,6 @@
         sendDeviceState(LOW_DATA_EXPECTED, mIsLowDataExpected);
         sendDeviceState(POWER_SAVE_MODE, mIsPowerSaveOn);
         setUnsolResponseFilter(mUnsolicitedResponseFilter, true);
-        setSignalStrengthReportingCriteria();
         setLinkCapacityReportingCriteria();
         setCellInfoMinInterval(mCellInfoMinInterval);
     }
@@ -660,36 +632,6 @@
         }
     }
 
-    private void setSignalStrengthReportingCriteria() {
-        mPhone.setSignalStrengthReportingCriteria(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI,
-                AccessNetworkThresholds.GERAN, AccessNetworkType.GERAN, true);
-        mPhone.setSignalStrengthReportingCriteria(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSCP,
-                AccessNetworkThresholds.UTRAN, AccessNetworkType.UTRAN, true);
-        mPhone.setSignalStrengthReportingCriteria(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRP,
-                AccessNetworkThresholds.EUTRAN_RSRP, AccessNetworkType.EUTRAN, true);
-        mPhone.setSignalStrengthReportingCriteria(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI,
-                AccessNetworkThresholds.CDMA2000, AccessNetworkType.CDMA2000, true);
-        if (mPhone.getHalVersion().greaterOrEqual(RIL.RADIO_HAL_VERSION_1_5)) {
-            mPhone.setSignalStrengthReportingCriteria(
-                    SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRQ,
-                    AccessNetworkThresholds.EUTRAN_RSRQ, AccessNetworkType.EUTRAN, false);
-            mPhone.setSignalStrengthReportingCriteria(
-                    SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSNR,
-                    AccessNetworkThresholds.EUTRAN_RSSNR, AccessNetworkType.EUTRAN, true);
-
-            // Defaultly we only need SSRSRP for NGRAN signal criteria reporting
-            mPhone.setSignalStrengthReportingCriteria(
-                    SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRP,
-                    AccessNetworkThresholds.NGRAN_RSRSRP, AccessNetworkType.NGRAN, true);
-            mPhone.setSignalStrengthReportingCriteria(
-                    SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRQ,
-                    AccessNetworkThresholds.NGRAN_RSRSRQ, AccessNetworkType.NGRAN, false);
-            mPhone.setSignalStrengthReportingCriteria(
-                    SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSSINR,
-                    AccessNetworkThresholds.NGRAN_SSSINR, AccessNetworkType.NGRAN, false);
-        }
-    }
-
     private void setLinkCapacityReportingCriteria() {
         mPhone.setLinkCapacityReportingCriteria(LINK_CAPACITY_DOWNLINK_THRESHOLDS,
                 LINK_CAPACITY_UPLINK_THRESHOLDS, AccessNetworkType.GERAN);
@@ -765,22 +707,16 @@
     }
 
     /**
-     * @return True if the radio is on.
+     * @return True if automotive projection (Android Auto) is active.
      */
-    private boolean isRadioOn() {
-        return mPhone.isRadioOn();
-    }
-
-    /**
-     * @return True if car mode (Android Auto) is on.
-     */
-    private boolean isCarModeOn() {
+    private boolean isAutomotiveProjectionActive() {
         final UiModeManager umm = (UiModeManager) mPhone.getContext().getSystemService(
                 Context.UI_MODE_SERVICE);
         if (umm == null) return false;
-        boolean retval = umm.getCurrentModeType() == Configuration.UI_MODE_TYPE_CAR;
-        log("isCarModeOn=" + retval, true);
-        return retval;
+        boolean isAutomotiveProjectionActive = (umm.getActiveProjectionTypes()
+                & PROJECTION_TYPE_AUTOMOTIVE) != 0;
+        log("isAutomotiveProjectionActive=" + isAutomotiveProjectionActive, true);
+        return isAutomotiveProjectionActive;
     }
 
     /**
@@ -831,12 +767,11 @@
         ipw.println("mIsCharging=" + mIsCharging);
         ipw.println("mIsPowerSaveOn=" + mIsPowerSaveOn);
         ipw.println("mIsLowDataExpected=" + mIsLowDataExpected);
-        ipw.println("mIsCarModeOn=" + mIsCarModeOn);
+        ipw.println("mIsAutomotiveProjectionActive=" + mIsAutomotiveProjectionActive);
         ipw.println("mUnsolicitedResponseFilter=" + mUnsolicitedResponseFilter);
         ipw.println("mIsWifiConnected=" + mIsWifiConnected);
         ipw.println("mIsAlwaysSignalStrengthReportingEnabled="
                 + mIsAlwaysSignalStrengthReportingEnabled);
-        ipw.println("mIsRadioOn=" + mIsRadioOn);
         ipw.println("Local logs:");
         ipw.increaseIndent();
         mLocalLog.dump(fd, ipw, args);
@@ -846,115 +781,6 @@
     }
 
     /**
-     * dBm thresholds that correspond to changes in signal strength indications.
-     */
-    private static final class AccessNetworkThresholds {
-
-        /**
-         * List of dBm thresholds for GERAN {@link AccessNetworkType}.
-         *
-         * Calculated from GSM asu level thresholds - TS 27.007 Sec 8.5
-         */
-        public static final int[] GERAN = new int[] {
-            -109,
-            -103,
-            -97,
-            -89,
-        };
-
-        /**
-         * List of default dBm thresholds for UTRAN {@link AccessNetworkType}.
-         *
-         * These thresholds are taken from the WCDMA RSCP defaults in {@link CarrierConfigManager}.
-         * See TS 27.007 Sec 8.69.
-         */
-        public static final int[] UTRAN = new int[] {
-            -114, /* SIGNAL_STRENGTH_POOR */
-            -104, /* SIGNAL_STRENGTH_MODERATE */
-            -94,  /* SIGNAL_STRENGTH_GOOD */
-            -84   /* SIGNAL_STRENGTH_GREAT */
-        };
-
-        /**
-         * List of default dBm RSRP thresholds for EUTRAN {@link AccessNetworkType}.
-         *
-         * These thresholds are taken from the LTE RSRP defaults in {@link CarrierConfigManager}.
-         */
-        public static final int[] EUTRAN_RSRP = new int[] {
-            -128, /* SIGNAL_STRENGTH_POOR */
-            -118, /* SIGNAL_STRENGTH_MODERATE */
-            -108, /* SIGNAL_STRENGTH_GOOD */
-            -98,  /* SIGNAL_STRENGTH_GREAT */
-        };
-
-        /**
-         * List of default dB RSRQ thresholds for EUTRAN {@link AccessNetworkType}.
-         *
-         * These thresholds are taken from the LTE RSRQ defaults in {@link CarrierConfigManager}.
-         */
-        public static final int[] EUTRAN_RSRQ = new int[] {
-            -20,  /* SIGNAL_STRENGTH_POOR */
-            -17,  /* SIGNAL_STRENGTH_MODERATE */
-            -14,  /* SIGNAL_STRENGTH_GOOD */
-            -11   /* SIGNAL_STRENGTH_GREAT */
-        };
-
-        /**
-         * List of default dB RSSNR thresholds for EUTRAN {@link AccessNetworkType}.
-         *
-         * These thresholds are taken from the LTE RSSNR defaults in {@link CarrierConfigManager}.
-         */
-        public static final int[] EUTRAN_RSSNR = new int[] {
-            -3,  /* SIGNAL_STRENGTH_POOR */
-            1,   /* SIGNAL_STRENGTH_MODERATE */
-            5,   /* SIGNAL_STRENGTH_GOOD */
-            13   /* SIGNAL_STRENGTH_GREAT */
-        };
-
-        /**
-         * List of dBm thresholds for CDMA2000 {@link AccessNetworkType}.
-         *
-         * These correspond to EVDO level thresholds.
-         */
-        public static final int[] CDMA2000 = new int[] {
-            -105,
-            -90,
-            -75,
-            -65
-        };
-
-        /**
-         * List of dB thresholds for NGRAN {@link AccessNetworkType} RSRSRP
-         */
-        public static final int[] NGRAN_RSRSRP = new int[] {
-            -110, /* SIGNAL_STRENGTH_POOR */
-            -90, /* SIGNAL_STRENGTH_MODERATE */
-            -80, /* SIGNAL_STRENGTH_GOOD */
-            -65,  /* SIGNAL_STRENGTH_GREAT */
-        };
-
-        /**
-         * List of dB thresholds for NGRAN {@link AccessNetworkType} RSRSRP
-         */
-        public static final int[] NGRAN_RSRSRQ = new int[] {
-            -31, /* SIGNAL_STRENGTH_POOR */
-            -19, /* SIGNAL_STRENGTH_MODERATE */
-            -7, /* SIGNAL_STRENGTH_GOOD */
-            6  /* SIGNAL_STRENGTH_GREAT */
-        };
-
-        /**
-         * List of dB thresholds for NGRAN {@link AccessNetworkType} SSSINR
-         */
-        public static final int[] NGRAN_SSSINR = new int[] {
-            -5, /* SIGNAL_STRENGTH_POOR */
-            5, /* SIGNAL_STRENGTH_MODERATE */
-            15, /* SIGNAL_STRENGTH_GOOD */
-            30  /* SIGNAL_STRENGTH_GREAT */
-        };
-    }
-
-    /**
      * Downlink reporting thresholds in kbps
      *
      * <p>Threshold values taken from FCC Speed Guide when available
diff --git a/src/java/com/android/internal/telephony/GsmCdmaConnection.java b/src/java/com/android/internal/telephony/GsmCdmaConnection.java
index cdf1f42..e9ecb79 100644
--- a/src/java/com/android/internal/telephony/GsmCdmaConnection.java
+++ b/src/java/com/android/internal/telephony/GsmCdmaConnection.java
@@ -559,6 +559,9 @@
             case CallFailCause.USER_ALERTING_NO_ANSWER:
                 return DisconnectCause.TIMED_OUT;
 
+            case CallFailCause.RADIO_OFF:
+                return DisconnectCause.POWER_OFF;
+
             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 7834fe9..c742931 100644
--- a/src/java/com/android/internal/telephony/GsmCdmaPhone.java
+++ b/src/java/com/android/internal/telephony/GsmCdmaPhone.java
@@ -91,6 +91,7 @@
 import com.android.internal.telephony.cdma.CdmaSubscriptionSourceManager;
 import com.android.internal.telephony.dataconnection.DataEnabledSettings;
 import com.android.internal.telephony.dataconnection.DcTracker;
+import com.android.internal.telephony.dataconnection.LinkBandwidthEstimator;
 import com.android.internal.telephony.dataconnection.TransportManager;
 import com.android.internal.telephony.emergency.EmergencyNumberTracker;
 import com.android.internal.telephony.gsm.GsmMmiCode;
@@ -126,6 +127,7 @@
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
+import java.util.function.Consumer;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -201,7 +203,7 @@
     private SIMRecords mSimRecords;
 
     // For non-persisted manual network selection
-    private String mManualNetworkSelectionPlmn = "";
+    private String mManualNetworkSelectionPlmn;
 
     //Common
     // Instance Variables
@@ -257,6 +259,7 @@
     private IccSmsInterfaceManager mIccSmsInterfaceManager;
 
     private boolean mResetModemOnRadioTechnologyChange = false;
+    private boolean mSsOverCdmaSupported = false;
 
     private int mRilVersion;
     private boolean mBroadcastEmergencyCallStateChanges = false;
@@ -305,6 +308,9 @@
                 .makeCarrierSignalAgent(this);
         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);
         mSST = mTelephonyComponentFactory.inject(ServiceStateTracker.class.getName())
                 .makeServiceStateTracker(this, this.mCi);
         mEmergencyNumberTracker = mTelephonyComponentFactory
@@ -351,6 +357,10 @@
         SubscriptionController.getInstance().registerForUiccAppsEnabled(this,
                 EVENT_UICC_APPS_ENABLEMENT_SETTING_CHANGED, null, false);
 
+        mLinkBandwidthEstimator = mTelephonyComponentFactory
+                .inject(LinkBandwidthEstimator.class.getName())
+                .makeLinkBandwidthEstimator(this);
+
         loadTtyMode();
 
         CallManager.getInstance().registerPhone(this);
@@ -444,8 +454,6 @@
     private void initRatSpecific(int precisePhoneType) {
         mPendingMMIs.clear();
         mIccPhoneBookIntManager.updateIccRecords(null);
-        mEsn = null;
-        mMeid = null;
 
         mPrecisePhoneType = precisePhoneType;
         logd("Precise phone type " + mPrecisePhoneType);
@@ -652,6 +660,11 @@
     }
 
     @Override
+    public SignalStrengthController getSignalStrengthController() {
+        return mSignalStrengthController;
+    }
+
+    @Override
     public void updateVoiceMail() {
         if (isPhoneTypeGsm()) {
             int countVoiceMessages = 0;
@@ -1331,8 +1344,8 @@
     }
 
     @Override
-    public Connection dial(String dialString, @NonNull DialArgs dialArgs)
-            throws CallStateException {
+    public Connection dial(String dialString, @NonNull DialArgs dialArgs,
+            Consumer<Phone> chosenPhoneConsumer) throws CallStateException {
         if (!isPhoneTypeGsm() && dialArgs.uusInfo != null) {
             throw new CallStateException("Sending UUS information NOT supported in CDMA!");
         }
@@ -1415,6 +1428,7 @@
                 || useImsForEmergency) {
             try {
                 if (DBG) logd("Trying IMS PS call");
+                chosenPhoneConsumer.accept(imsPhone);
                 return imsPhone.dial(dialString, dialArgs);
             } catch (CallStateException e) {
                 if (DBG) logd("IMS PS call exception " + e +
@@ -1471,6 +1485,7 @@
             mCi.testingEmergencyCall();
         }
 
+        chosenPhoneConsumer.accept(this);
         return dialInternal(dialString, dialArgs);
     }
 
@@ -1707,7 +1722,7 @@
         SharedPreferences.Editor editor = sp.edit();
         setVmSimImsi(getSubscriberId());
         logd("storeVoiceMailNumber: mPrecisePhoneType=" + mPrecisePhoneType + " vmNumber="
-                + number);
+                + Rlog.pii(LOG_TAG, number));
         if (isPhoneTypeGsm()) {
             editor.putString(VM_NUMBER + getPhoneId(), number);
             editor.apply();
@@ -1728,9 +1743,10 @@
                 SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
                 String spName = isPhoneTypeGsm() ? VM_NUMBER : VM_NUMBER_CDMA;
                 number = sp.getString(spName + getPhoneId(), null);
-                logd("getVoiceMailNumber: from " + spName + " number=" + number);
+                logd("getVoiceMailNumber: from " + spName + " number="
+                        + Rlog.pii(LOG_TAG, number));
             } else {
-                logd("getVoiceMailNumber: from IccRecords number=" + number);
+                logd("getVoiceMailNumber: from IccRecords number=" + Rlog.pii(LOG_TAG, number));
             }
         }
         if (!isPhoneTypeGsm() && TextUtils.isEmpty(number)) {
@@ -1908,7 +1924,7 @@
 
     @Override
     public void deleteCarrierInfoForImsiEncryption() {
-        CarrierInfoManager.deleteCarrierInfoForImsiEncryption(mContext);
+        CarrierInfoManager.deleteCarrierInfoForImsiEncryption(mContext, getSubId());
     }
 
     @Override
@@ -2067,7 +2083,7 @@
             mManualNetworkSelectionPlmn = nsm.operatorNumeric;
         } else {
         //on Phone0 in emergency mode (no SIM), or in some races then clear the cache
-            mManualNetworkSelectionPlmn = "";
+            mManualNetworkSelectionPlmn = null;
             Rlog.e(LOG_TAG, "Cannot update network selection due to invalid subId "
                     + subId);
         }
@@ -2205,6 +2221,11 @@
         return false;
     }
 
+    private void updateSsOverCdmaSupported(PersistableBundle b) {
+        if (b == null) return;
+        mSsOverCdmaSupported = b.getBoolean(CarrierConfigManager.KEY_SUPPORT_SS_OVER_CDMA_BOOL);
+    }
+
     @Override
     public boolean useSsOverIms(Message onComplete) {
         boolean isUtEnabled = isUtEnabled();
@@ -2245,8 +2266,16 @@
                 mCi.queryCallForwardStatus(commandInterfaceCFReason, serviceClass, null, resp);
             }
         } else {
-            loge("getCallForwardingOption: not possible in CDMA, just return empty result");
-            AsyncResult.forMessage(onComplete, makeEmptyCallForward(), null);
+            if (!mSsOverCdmaSupported) {
+                // If SS over CDMA is not supported and UT is not at the time, notify the user of
+                // the error and disable the option.
+                AsyncResult.forMessage(onComplete, null,
+                        new CommandException(CommandException.Error.INVALID_STATE,
+                                "Call Forwarding over CDMA unavailable"));
+            } else {
+                loge("getCallForwardingOption: not possible in CDMA, just return empty result");
+                AsyncResult.forMessage(onComplete, makeEmptyCallForward(), null);
+            }
             onComplete.sendToTarget();
         }
     }
@@ -2294,7 +2323,7 @@
                         timerSeconds,
                         resp);
             }
-        } else {
+        } else if (mSsOverCdmaSupported) {
             String formatNumber = GsmCdmaConnection.formatDialString(dialingNumber);
             String cfNumber = CdmaMmiCode.getCallForwardingPrefixAndNumber(
                     commandInterfaceCFAction, commandInterfaceCFReason, formatNumber);
@@ -2306,10 +2335,15 @@
             extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle);
 
             final TelecomManager telecomManager = TelecomManager.from(mContext);
-            telecomManager.placeCall(Uri.parse(PhoneAccount.SCHEME_TEL + cfNumber), extras);
+            telecomManager.placeCall(
+                    Uri.fromParts(PhoneAccount.SCHEME_TEL, cfNumber, null), extras);
 
             AsyncResult.forMessage(onComplete, CommandsInterface.SS_STATUS_UNKNOWN, null);
             onComplete.sendToTarget();
+        } else {
+            loge("setCallForwardingOption: SS over CDMA not supported, can not complete");
+            AsyncResult.forMessage(onComplete, CommandsInterface.SS_STATUS_UNKNOWN, null);
+            onComplete.sendToTarget();
         }
     }
 
@@ -2433,8 +2467,17 @@
             //class parameter in call waiting interrogation  to network
             mCi.queryCallWaiting(CommandsInterface.SERVICE_CLASS_NONE, onComplete);
         } else {
-            int arr[] = {CommandsInterface.SS_STATUS_UNKNOWN, CommandsInterface.SERVICE_CLASS_NONE};
-            AsyncResult.forMessage(onComplete, arr, null);
+            if (!mSsOverCdmaSupported) {
+                // If SS over CDMA is not supported and UT is not at the time, notify the user of
+                // the error and disable the option.
+                AsyncResult.forMessage(onComplete, null,
+                        new CommandException(CommandException.Error.INVALID_STATE,
+                                "Call Waiting over CDMA unavailable"));
+            } else {
+                int[] arr =
+                        {CommandsInterface.SS_STATUS_UNKNOWN, CommandsInterface.SERVICE_CLASS_NONE};
+                AsyncResult.forMessage(onComplete, arr, null);
+            }
             onComplete.sendToTarget();
         }
     }
@@ -2462,7 +2505,7 @@
 
         if (isPhoneTypeGsm()) {
             mCi.setCallWaiting(enable, serviceClass, onComplete);
-        } else {
+        } else if (mSsOverCdmaSupported) {
             String cwPrefix = CdmaMmiCode.getCallWaitingPrefix(enable);
             Rlog.i(LOG_TAG, "setCallWaiting in CDMA : dial for set call waiting" + " prefix= " + cwPrefix);
 
@@ -2471,10 +2514,15 @@
             extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle);
 
             final TelecomManager telecomManager = TelecomManager.from(mContext);
-            telecomManager.placeCall(Uri.parse(PhoneAccount.SCHEME_TEL + cwPrefix), extras);
+            telecomManager.placeCall(
+                    Uri.fromParts(PhoneAccount.SCHEME_TEL, cwPrefix, null), extras);
 
             AsyncResult.forMessage(onComplete, CommandsInterface.SS_STATUS_UNKNOWN, null);
             onComplete.sendToTarget();
+        } else {
+            loge("setCallWaiting: SS over CDMA not supported, can not complete");
+            AsyncResult.forMessage(onComplete, CommandsInterface.SS_STATUS_UNKNOWN, null);
+            onComplete.sendToTarget();
         }
     }
 
@@ -2616,7 +2664,6 @@
      * @param mmi MMI that is done
      */
     public void onMMIDone(MmiCode mmi) {
-
         /* Only notify complete if it's on the pending list.
          * Otherwise, it's already been handled (eg, previously canceled).
          * The exception is cancellation of an incoming USSD-REQUEST, which is
@@ -2624,7 +2671,6 @@
          */
         if (mPendingMMIs.remove(mmi) || (isPhoneTypeGsm() && (mmi.isUssdRequest() ||
                 ((GsmMmiCode)mmi).isSsInfo()))) {
-
             ResultReceiver receiverCallback = mmi.getUssdCallbackReceiver();
             if (receiverCallback != null) {
                 Rlog.i(LOG_TAG, "onMMIDone: invoking callback: " + mmi);
@@ -2665,6 +2711,7 @@
         if (!isPhoneTypeGsm()) {
             loge("onIncomingUSSD: not expected on GSM");
         }
+
         boolean isUssdError;
         boolean isUssdRequest;
         boolean isUssdRelease;
@@ -2692,7 +2739,6 @@
 
         if (found != null) {
             // Complete pending USSD
-
             if (isUssdRelease) {
                 found.onUssdRelease();
             } else if (isUssdError) {
@@ -2712,6 +2758,13 @@
                                                    GsmCdmaPhone.this,
                                                    mUiccApplication.get());
             onNetworkInitiatedUssd(mmi);
+        } else if (isUssdError && !isUssdRelease) {
+            GsmMmiCode mmi;
+            mmi = GsmMmiCode.newNetworkInitiatedUssd(ussdMessage,
+                    true,
+                    GsmCdmaPhone.this,
+                    mUiccApplication.get());
+            mmi.onUssdFinishedError();
         }
     }
 
@@ -2727,6 +2780,10 @@
         Rlog.i(LOG_TAG, "syncClirSetting: " + CLIR_KEY + getSubId() + "=" + clirSetting);
         if (clirSetting >= 0) {
             mCi.setCLIR(clirSetting, null);
+        } else {
+            // if there is no preference set, ensure the CLIR is updated to the default value in
+            // order to ensure that CLIR values in the RIL are not carried over during SIM swap.
+            mCi.setCLIR(CommandsInterface.CLIR_DEFAULT, null);
         }
     }
 
@@ -2914,6 +2971,7 @@
                 updateCdmaRoamingSettingsAfterCarrierConfigChanged(b);
 
                 updateNrSettingsAfterCarrierConfigChanged(b);
+                updateSsOverCdmaSupported(b);
                 loadAllowedNetworksFromSubscriptionDatabase();
                 // Obtain new radio capabilities from the modem, since some are SIM-dependent
                 mCi.getRadioCapability(obtainMessage(EVENT_GET_RADIO_CAPABILITY));
@@ -4059,17 +4117,19 @@
     @Override
     public void setSignalStrengthReportingCriteria(int signalStrengthMeasure,
             int[] systemThresholds, int ran, boolean isEnabledForSystem) {
-        int[] consolidatedThresholds = mSST.getConsolidatedSignalThresholds(
+        int[] consolidatedThresholds = mSignalStrengthController.getConsolidatedSignalThresholds(
                 ran,
                 signalStrengthMeasure,
-                isEnabledForSystem && mSST.shouldHonorSystemThresholds() ? systemThresholds
+                isEnabledForSystem && mSignalStrengthController.shouldHonorSystemThresholds()
+                        ? systemThresholds
                         : new int[]{},
                 REPORTING_HYSTERESIS_DB);
-        boolean isEnabledForAppRequest = mSST.shouldEnableSignalThresholdForAppRequest(
-                ran,
-                signalStrengthMeasure,
-                getSubId(),
-                isDeviceIdle());
+        boolean isEnabledForAppRequest =
+                mSignalStrengthController.shouldEnableSignalThresholdForAppRequest(
+                        ran,
+                        signalStrengthMeasure,
+                        getSubId(),
+                        isDeviceIdle());
         mCi.setSignalStrengthReportingCriteria(
                 new SignalThresholdInfo.Builder()
                         .setRadioAccessNetworkType(ran)
@@ -4604,6 +4664,27 @@
         return Collections.emptyList();
     }
 
+    /**
+     * @return Currently bound data service package names.
+     */
+    public @NonNull List<String> 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;
+    }
+
     private void updateBroadcastEmergencyCallStateChangesAfterCarrierConfigChanged(
             PersistableBundle config) {
         if (config == null) {
@@ -4682,4 +4763,9 @@
                 && imsManager.isNonTtyOrTtyOnVolteEnabled());
         return imsUseEnabled;
     }
+
+    @Override
+    public InboundSmsHandler getInboundSmsHandler(boolean is3gpp2) {
+        return mIccSmsInterfaceManager.getInboundSmsHandler(is3gpp2);
+    }
 }
diff --git a/src/java/com/android/internal/telephony/IccPhoneBookInterfaceManager.java b/src/java/com/android/internal/telephony/IccPhoneBookInterfaceManager.java
index b78d1ab..c6c3d6a 100644
--- a/src/java/com/android/internal/telephony/IccPhoneBookInterfaceManager.java
+++ b/src/java/com/android/internal/telephony/IccPhoneBookInterfaceManager.java
@@ -38,8 +38,8 @@
 import com.android.internal.telephony.uicc.UiccProfile;
 import com.android.telephony.Rlog;
 
-import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
  * IccPhoneBookInterfaceManager to provide an inter-process communication to
@@ -167,6 +167,11 @@
     }
 
     private AdnRecord generateAdnRecordWithNewTagByContentValues(ContentValues values) {
+        return generateAdnRecordWithNewTagByContentValues(0, 0, values);
+    }
+
+    private AdnRecord generateAdnRecordWithNewTagByContentValues(
+            int efId, int recordNumber, ContentValues values) {
         if (values == null) {
             return null;
         }
@@ -177,7 +182,8 @@
         String[] newEmailArray = TextUtils.isEmpty(newEmail)
                 ? null : getEmailStringArray(newEmail);
         String[] newAnrArray = TextUtils.isEmpty(newAnr) ? null : getAnrStringArray(newAnr);
-        return new AdnRecord(newTag, newPhoneNumber, newEmailArray, newAnrArray);
+        return new AdnRecord(
+                efId, recordNumber, newTag, newPhoneNumber, newEmailArray, newAnrArray);
     }
 
     /**
@@ -257,7 +263,6 @@
             throw new SecurityException(
                     "Requires android.permission.WRITE_CONTACTS permission");
         }
-        efid = updateEfForIccType(efid);
         if (DBG) {
             logd("updateAdnRecordsInEfByIndex: efid=" + efid + ", values = " +
                 values + " index=" + index + ", pin2=" + pin2);
@@ -267,7 +272,7 @@
         Request updateRequest = new Request();
         synchronized (updateRequest) {
             Message response = mBaseHandler.obtainMessage(EVENT_UPDATE_DONE, updateRequest);
-            AdnRecord newAdn = generateAdnRecordWithNewTagByContentValues(values);
+            AdnRecord newAdn = generateAdnRecordWithNewTagByContentValues(efid, index, values);
             if (usesPbCache(efid)) {
                 mSimPbRecordCache.updateSimPbAdnByRecordId(index, newAdn, response);
                 waitForResult(updateRequest);
diff --git a/src/java/com/android/internal/telephony/IccSmsInterfaceManager.java b/src/java/com/android/internal/telephony/IccSmsInterfaceManager.java
index d8d092b..d09f8c3 100644
--- a/src/java/com/android/internal/telephony/IccSmsInterfaceManager.java
+++ b/src/java/com/android/internal/telephony/IccSmsInterfaceManager.java
@@ -21,6 +21,7 @@
 import static android.telephony.SmsManager.STATUS_ON_ICC_UNREAD;
 
 import android.Manifest;
+import android.annotation.RequiresPermission;
 import android.app.AppOpsManager;
 import android.app.PendingIntent;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -537,7 +538,7 @@
                     + " text='" + text + "' sentIntent=" + sentIntent + " deliveryIntent="
                     + deliveryIntent + " priority=" + priority + " expectMore=" + expectMore
                     + " validityPeriod=" + validityPeriod + " isForVVM=" + isForVvm
-                    + " id= " +  messageId);
+                    + " " + SmsController.formatCrossStackMessageId(messageId));
         }
         notifyIfOutgoingEmergencySms(destAddr);
         destAddr = filterDestAddress(destAddr);
@@ -743,7 +744,7 @@
             for (String part : parts) {
                 log("sendMultipartTextWithOptions: destAddr=" + destAddr + ", srAddr=" + scAddr
                         + ", part[" + (i++) + "]=" + part
-                        + " id: " + messageId);
+                        + " " + SmsController.formatCrossStackMessageId(messageId));
             }
         }
         notifyIfOutgoingEmergencySms(destAddr);
@@ -1049,8 +1050,9 @@
     /**
      * Reset all cell broadcast ranges. Previously enabled ranges will become invalid after this.
      */
+    @RequiresPermission(android.Manifest.permission.MODIFY_CELL_BROADCASTS)
     public void resetAllCellBroadcastRanges() {
-        mContext.enforceCallingPermission(android.Manifest.permission.RECEIVE_EMERGENCY_BROADCAST,
+        mContext.enforceCallingPermission(android.Manifest.permission.MODIFY_CELL_BROADCASTS,
                 "resetAllCellBroadcastRanges");
         mCdmaBroadcastRangeManager.clearRanges();
         mCellBroadcastRangeManager.clearRanges();
@@ -1495,6 +1497,13 @@
         }
     }
 
+    /**
+     * Get InboundSmsHandler for the phone.
+     */
+    public InboundSmsHandler getInboundSmsHandler(boolean is3gpp2) {
+        return mDispatchersController.getInboundSmsHandler(is3gpp2);
+    }
+
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("Enabled GSM channels: " + mCellBroadcastRangeManager);
         pw.println("Enabled CDMA channels: " + mCdmaBroadcastRangeManager);
diff --git a/src/java/com/android/internal/telephony/InboundSmsHandler.java b/src/java/com/android/internal/telephony/InboundSmsHandler.java
index 38d7047..b5c0f95 100644
--- a/src/java/com/android/internal/telephony/InboundSmsHandler.java
+++ b/src/java/com/android/internal/telephony/InboundSmsHandler.java
@@ -16,6 +16,9 @@
 
 package com.android.internal.telephony;
 
+import static android.os.PowerWhitelistManager.REASON_EVENT_MMS;
+import static android.os.PowerWhitelistManager.REASON_EVENT_SMS;
+import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
 import static android.provider.Telephony.Sms.Intents.RESULT_SMS_DATABASE_ERROR;
 import static android.provider.Telephony.Sms.Intents.RESULT_SMS_DISPATCH_FAILURE;
 import static android.provider.Telephony.Sms.Intents.RESULT_SMS_INVALID_URI;
@@ -183,6 +186,9 @@
     /** Update the sms tracker */
     public static final int EVENT_UPDATE_TRACKER = 8;
 
+    /** 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;
 
@@ -206,8 +212,10 @@
 
     // The notitfication tag used when showing a notification. The combination of notification tag
     // and notification id should be unique within the phone app.
-    private static final String NOTIFICATION_TAG = "InboundSmsHandler";
-    private static final int NOTIFICATION_ID_NEW_MESSAGE = 1;
+    @VisibleForTesting
+    public static final String NOTIFICATION_TAG = "InboundSmsHandler";
+    @VisibleForTesting
+    public static final int NOTIFICATION_ID_NEW_MESSAGE = 1;
 
     /** URI for raw table of SMS provider. */
     protected static final Uri sRawUri = Uri.withAppendedPath(Telephony.Sms.CONTENT_URI, "raw");
@@ -373,6 +381,9 @@
             case EVENT_UPDATE_TRACKER:
                 whatString = "EVENT_UPDATE_TRACKER";
                 break;
+            case EVENT_RECEIVER_TIMEOUT:
+                whatString = "EVENT_RECEIVER_TIMEOUT";
+                break;
             default:
                 whatString = "UNKNOWN EVENT " + what;
         }
@@ -626,6 +637,15 @@
                     deferMessage(msg);
                     return HANDLED;
 
+                case EVENT_RECEIVER_TIMEOUT:
+                    logeWithLocalLog("WaitingState.processMessage: received "
+                            + "EVENT_RECEIVER_TIMEOUT");
+                    if (mLastDeliveredSmsTracker != null) {
+                        mLastDeliveredSmsTracker.getSmsBroadcastReceiver(InboundSmsHandler.this)
+                                .fakeNextAction();
+                    }
+                    return HANDLED;
+
                 case EVENT_BROADCAST_COMPLETE:
                     mLastDeliveredSmsTracker = null;
                     // return to idle after handling all deferred messages
@@ -676,7 +696,7 @@
      * This method is called when a new SMS PDU is injected into application framework.
      * @param ar is the AsyncResult that has the SMS PDU to be injected.
      */
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private void handleInjectSms(AsyncResult ar, boolean isOverIms) {
         int result;
         SmsDispatchersController.SmsInjectionCallback callback = null;
@@ -806,7 +826,7 @@
      * @param smsSource the source of the SMS message
      * @return {@link Intents#RESULT_SMS_HANDLED} if the message was accepted, or an error status
      */
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     protected int dispatchNormalMessage(SmsMessageBase sms, @SmsSource int smsSource) {
         SmsHeader smsHeader = sms.getUserDataHeader();
         InboundSmsTracker tracker;
@@ -987,8 +1007,8 @@
                 log("processMessagePart: all " + messageCount + " segments "
                         + " received. refNumber: " + refNumber, tracker.getMessageId());
             } catch (SQLException e) {
-                loge("processMessagePart: Can't access multipart SMS database, id: "
-                        + tracker.getMessageId(), e);
+                loge("processMessagePart: Can't access multipart SMS database, "
+                        + SmsController.formatCrossStackMessageId(tracker.getMessageId()), e);
                 return false;
             } finally {
                 if (cursor != null) {
@@ -1034,7 +1054,7 @@
             }
         }
 
-        SmsBroadcastReceiver resultReceiver = new SmsBroadcastReceiver(tracker);
+        SmsBroadcastReceiver resultReceiver = tracker.getSmsBroadcastReceiver(this);
 
         if (!mUserManager.isUserUnlocked()) {
             log("processMessagePart: !isUserUnlocked; calling processMessagePartWithUserLocked. "
@@ -1043,7 +1063,8 @@
                     tracker,
                     (isWapPush ? new byte[][] {output.toByteArray()} : pdus),
                     destPort,
-                    resultReceiver);
+                    resultReceiver,
+                    block);
         }
 
         if (isWapPush) {
@@ -1116,7 +1137,7 @@
      * @return true if an ordered broadcast was sent to the carrier app; false otherwise.
      */
     private boolean processMessagePartWithUserLocked(InboundSmsTracker tracker,
-            byte[][] pdus, int destPort, SmsBroadcastReceiver resultReceiver) {
+            byte[][] pdus, int destPort, SmsBroadcastReceiver resultReceiver, boolean block) {
         if (destPort == SmsHeader.PORT_WAP_PUSH && mWapPush.isWapPushForMms(pdus[0], this)) {
             showNewMessageNotification();
             return false;
@@ -1125,14 +1146,15 @@
             // This is a regular SMS - hand it to the carrier or system app for filtering.
             boolean filterInvoked = filterSms(
                     pdus, destPort, tracker, resultReceiver, false /* userUnlocked */,
-                    false /* block */);
+                    block);
             if (filterInvoked) {
                 // filter invoked, wait for it to return the result.
                 return true;
-            } else {
-                // filter not invoked, show the notification and do nothing further.
+            } else if (!block) {
+                // filter not invoked and message not blocked, show the notification and do nothing
+                // further. Even if the message is blocked, we keep it in the database so it can be
+                // reprocessed by filters once credential-encrypted storage is available.
                 showNewMessageNotification();
-                return false;
             }
         }
         return false;
@@ -1281,9 +1303,21 @@
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public void dispatchIntent(Intent intent, String permission, String appOp,
-            Bundle opts, BroadcastReceiver resultReceiver, UserHandle user, int subId) {
+            Bundle opts, SmsBroadcastReceiver resultReceiver, UserHandle user, int subId) {
         intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT);
         final String action = intent.getAction();
+        if (Intents.SMS_DELIVER_ACTION.equals(action)
+                || Intents.SMS_RECEIVED_ACTION.equals(action)
+                || Intents.WAP_PUSH_DELIVER_ACTION.equals(action)
+                || Intents.WAP_PUSH_RECEIVED_ACTION.equals(action)) {
+            // Some intents need to be delivered with high priority:
+            // SMS_DELIVER, SMS_RECEIVED, WAP_PUSH_DELIVER, WAP_PUSH_RECEIVED
+            // In some situations, like after boot up or system under load, normal
+            // intent delivery could take a long time.
+            // This flag should only be set for intents for visible, timely operations
+            // which is true for the intents above.
+            intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+        }
         SubscriptionManager.putPhoneIdAndSubIdExtra(intent, mPhone.getPhoneId());
 
         // override the subId value in the intent with the values from tracker as they can be
@@ -1300,6 +1334,11 @@
             for (UserHandle handle : userHandles) {
                 if (mUserManager.isUserRunning(handle)) {
                     runningUserHandles.add(handle);
+                } else {
+                    if (handle.equals(UserHandle.SYSTEM)) {
+                        logeWithLocalLog("dispatchIntent: SYSTEM user is not running",
+                                resultReceiver.mInboundSmsTracker.getMessageId());
+                    }
                 }
             }
             if (runningUserHandles.isEmpty()) {
@@ -1326,6 +1365,9 @@
                 }
                 // Only pass in the resultReceiver when the user SYSTEM is processed.
                 try {
+                    if (users[i] == UserHandle.SYSTEM.getIdentifier()) {
+                        resultReceiver.setWaitingForIntent(intent);
+                    }
                     mContext.createPackageContextAsUser(mContext.getPackageName(), 0, targetUser)
                             .sendOrderedBroadcast(intent, Activity.RESULT_OK, permission, appOp,
                                     users[i] == UserHandle.SYSTEM.getIdentifier()
@@ -1336,6 +1378,7 @@
             }
         } else {
             try {
+                resultReceiver.setWaitingForIntent(intent);
                 mContext.createPackageContextAsUser(mContext.getPackageName(), 0, user)
                         .sendOrderedBroadcast(intent, Activity.RESULT_OK, permission, appOp,
                                 resultReceiver, getHandler(), null /* initialData */,
@@ -1385,9 +1428,12 @@
             bundle = bopts.toBundle();
         }
         long duration = mPowerWhitelistManager.whitelistAppTemporarilyForEvent(
-                pkgName, PowerWhitelistManager.EVENT_SMS, reason);
+                pkgName, PowerWhitelistManager.EVENT_SMS, REASON_EVENT_SMS, reason);
         if (bopts == null) bopts = BroadcastOptions.makeBasic();
-        bopts.setTemporaryAppWhitelistDuration(duration);
+        bopts.setTemporaryAppAllowlist(duration,
+                TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
+                REASON_EVENT_SMS,
+                "");
         bundle = bopts.toBundle();
 
         return bundle;
@@ -1560,8 +1606,8 @@
                     return Intents.RESULT_SMS_DUPLICATED;   // reject message
                 }
             } catch (SQLException e) {
-                loge("addTrackerToRawTable: Can't access SMS database, id: "
-                        + tracker.getMessageId(), e);
+                loge("addTrackerToRawTable: Can't access SMS database, "
+                        + SmsController.formatCrossStackMessageId(tracker.getMessageId()), e);
                 return RESULT_SMS_DATABASE_ERROR;    // reject message
             }
         } else {
@@ -1592,8 +1638,8 @@
             }
             return Intents.RESULT_SMS_HANDLED;
         } catch (Exception e) {
-            loge("addTrackerToRawTable: error parsing URI for new row: " + newUri + " id: "
-                    + tracker.getMessageId(), e);
+            loge("addTrackerToRawTable: error parsing URI for new row: " + newUri
+                    + " " + SmsController.formatCrossStackMessageId(tracker.getMessageId()), e);
             return RESULT_SMS_INVALID_URI;
         }
     }
@@ -1607,27 +1653,80 @@
         return (PHONE_TYPE_CDMA == activePhone);
     }
 
+    @VisibleForTesting
+    public static int sTimeoutDurationMillis = 10 * 60 * 1000; // 10 minutes
+
     /**
      * Handler for an {@link InboundSmsTracker} broadcast. Deletes PDUs from the raw table and
      * logs the broadcast duration (as an error if the other receivers were especially slow).
      */
-    @VisibleForTesting
     public final class SmsBroadcastReceiver extends BroadcastReceiver {
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
         private final String mDeleteWhere;
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
         private final String[] mDeleteWhereArgs;
-        private long mBroadcastTimeNano;
+        private long mBroadcastTimeMillis;
+        public Intent mWaitingForIntent;
+        private final InboundSmsTracker mInboundSmsTracker;
 
-        SmsBroadcastReceiver(InboundSmsTracker tracker) {
+        /**
+         * This method must be called anytime an ordered broadcast is sent that is expected to be
+         * received by this receiver.
+         */
+        public synchronized void setWaitingForIntent(Intent intent) {
+            mWaitingForIntent = intent;
+            mBroadcastTimeMillis = System.currentTimeMillis();
+            removeMessages(EVENT_RECEIVER_TIMEOUT);
+            sendMessageDelayed(EVENT_RECEIVER_TIMEOUT, sTimeoutDurationMillis);
+        }
+
+        public SmsBroadcastReceiver(InboundSmsTracker tracker) {
             mDeleteWhere = tracker.getDeleteWhere();
             mDeleteWhereArgs = tracker.getDeleteWhereArgs();
-            mBroadcastTimeNano = System.nanoTime();
+            mInboundSmsTracker = tracker;
+        }
+
+        /**
+         * This method is called if the expected intent (mWaitingForIntent) is not received and
+         * the timer for it expires. It fakes the receipt of the intent to unblock the state
+         * machine.
+         */
+        public void fakeNextAction() {
+            if (mWaitingForIntent != null) {
+                logeWithLocalLog("fakeNextAction: " + mWaitingForIntent.getAction(),
+                        mInboundSmsTracker.getMessageId());
+                handleAction(mWaitingForIntent, false);
+            } else {
+                logeWithLocalLog("fakeNextAction: mWaitingForIntent is null",
+                        mInboundSmsTracker.getMessageId());
+            }
         }
 
         @Override
         public void onReceive(Context context, Intent intent) {
+            handleAction(intent, true);
+        }
+
+        private synchronized void handleAction(Intent intent, boolean onReceive) {
             String action = intent.getAction();
+            if (mWaitingForIntent == null || !mWaitingForIntent.getAction().equals(action)) {
+                logeWithLocalLog("handleAction: Received " + action + " when expecting "
+                        + mWaitingForIntent == null ? "none" : mWaitingForIntent.getAction(),
+                        mInboundSmsTracker.getMessageId());
+                return;
+            }
+
+            if (onReceive) {
+                int durationMillis = (int) (System.currentTimeMillis() - mBroadcastTimeMillis);
+                if (durationMillis >= 5000) {
+                    loge("Slow ordered broadcast completion time for " + action + ": "
+                            + durationMillis + " ms");
+                } else if (DBG) {
+                    log("Ordered broadcast completed for " + action + " in: "
+                            + durationMillis + " ms");
+                }
+            }
+
             int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
                     SubscriptionManager.INVALID_SUBSCRIPTION_ID);
             if (action.equals(Intents.SMS_DELIVER_ACTION)) {
@@ -1639,6 +1738,7 @@
                 // All running users will be notified of the received sms.
                 Bundle options = handleSmsWhitelisting(null, false /* bgActivityStartAllowed */);
 
+                setWaitingForIntent(intent);
                 dispatchIntent(intent, android.Manifest.permission.RECEIVE_SMS,
                         AppOpsManager.OPSTR_RECEIVE_SMS,
                         options, this, UserHandle.ALL, subId);
@@ -1651,12 +1751,18 @@
                 long duration = mPowerWhitelistManager.whitelistAppTemporarilyForEvent(
                         mContext.getPackageName(),
                         PowerWhitelistManager.EVENT_MMS,
+                        REASON_EVENT_MMS,
                         "mms-broadcast");
                 BroadcastOptions bopts = BroadcastOptions.makeBasic();
-                bopts.setTemporaryAppWhitelistDuration(duration);
+                bopts.setTemporaryAppAllowlist(duration,
+                        TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
+                        REASON_EVENT_MMS,
+                        "");
                 Bundle options = bopts.toBundle();
 
                 String mimeType = intent.getType();
+
+                setWaitingForIntent(intent);
                 dispatchIntent(intent, WapPushOverSms.getPermissionForType(mimeType),
                         WapPushOverSms.getAppOpsStringPermissionForIntent(mimeType), options, this,
                         UserHandle.SYSTEM, subId);
@@ -1664,28 +1770,24 @@
                 // Now that the intents have been deleted we can clean up the PDU data.
                 if (!Intents.DATA_SMS_RECEIVED_ACTION.equals(action)
                         && !Intents.SMS_RECEIVED_ACTION.equals(action)
-                        && !Intents.DATA_SMS_RECEIVED_ACTION.equals(action)
                         && !Intents.WAP_PUSH_RECEIVED_ACTION.equals(action)) {
                     loge("unexpected BroadcastReceiver action: " + action);
                 }
 
-                int rc = getResultCode();
-                if ((rc != Activity.RESULT_OK) && (rc != Intents.RESULT_SMS_HANDLED)) {
-                    loge("a broadcast receiver set the result code to " + rc
-                            + ", deleting from raw table anyway!");
-                } else if (DBG) {
-                    log("successful broadcast, deleting from raw table.");
+                if (onReceive) {
+                    int rc = getResultCode();
+                    if ((rc != Activity.RESULT_OK) && (rc != Intents.RESULT_SMS_HANDLED)) {
+                        loge("a broadcast receiver set the result code to " + rc
+                                + ", deleting from raw table anyway!");
+                    } else if (DBG) {
+                        log("successful broadcast, deleting from raw table.");
+                    }
                 }
 
                 deleteFromRawTable(mDeleteWhere, mDeleteWhereArgs, MARK_DELETED);
+                mWaitingForIntent = null;
+                removeMessages(EVENT_RECEIVER_TIMEOUT);
                 sendMessage(EVENT_BROADCAST_COMPLETE);
-
-                int durationMillis = (int) ((System.nanoTime() - mBroadcastTimeNano) / 1000000);
-                if (durationMillis >= 5000) {
-                    loge("Slow ordered broadcast completion time: " + durationMillis + " ms");
-                } else if (DBG) {
-                    log("ordered broadcast completed in: " + durationMillis + " ms");
-                }
             }
         }
     }
@@ -1726,7 +1828,7 @@
 
         @Override
         public void onFilterComplete(int result) {
-            log("onFilterComplete: result is " + result, mMessageId);
+            log("onFilterComplete: result is " + result, mTracker.getMessageId());
 
             boolean carrierRequestedDrop =
                     (result & CarrierMessagingService.RECEIVE_OPTIONS_DROP) != 0;
@@ -1745,12 +1847,18 @@
             }
 
             // Now that all filters have been invoked, drop the message if it is blocked.
-            // TODO(b/156910035): Remove mUserUnlocked once we stop showing the new message
-            // notification for blocked numbers.
-            if (mUserUnlocked && mBlock) {
-                log("onFilterComplete: dropping message as the sender is blocked",
-                        mTracker.getMessageId());
-                dropFilteredSms(mTracker, mSmsBroadcastReceiver, mBlock);
+            if (mBlock) {
+                // Only delete the message if the user is unlocked. Otherwise, we should reprocess
+                // the message after unlock so the filter has a chance to run while credential-
+                // encrypted storage is available.
+                if (mUserUnlocked) {
+                    log("onFilterComplete: dropping message as the sender is blocked",
+                            mTracker.getMessageId());
+                    dropFilteredSms(mTracker, mSmsBroadcastReceiver, mBlock);
+                } else {
+                    // Just complete handling of the message without dropping it.
+                    sendMessage(EVENT_BROADCAST_COMPLETE);
+                }
                 return;
             }
 
@@ -1802,7 +1910,7 @@
      */
     protected void logWithLocalLog(String logMsg, long id) {
         log(logMsg, id);
-        mLocalLog.log(logMsg + ", id: " + id);
+        mLocalLog.log(logMsg + ", " + SmsController.formatCrossStackMessageId(id));
     }
 
     /**
@@ -1821,7 +1929,7 @@
      */
     protected void logeWithLocalLog(String logMsg, long id) {
         loge(logMsg, id);
-        mLocalLog.log(logMsg + ", id: " + id);
+        mLocalLog.log(logMsg + ", " + SmsController.formatCrossStackMessageId(id));
     }
 
     /**
@@ -1840,7 +1948,7 @@
      * @param id unique message id
      */
     protected void log(String s, long id) {
-        log(s + ", id: " + id);
+        log(s + ", " + SmsController.formatCrossStackMessageId(id));
     }
 
     /**
@@ -1859,7 +1967,7 @@
      * @param id unique message id
      */
     protected void loge(String s, long id) {
-        loge(s + ", id: " + id);
+        loge(s + ", " + SmsController.formatCrossStackMessageId(id));
     }
 
     /**
diff --git a/src/java/com/android/internal/telephony/InboundSmsTracker.java b/src/java/com/android/internal/telephony/InboundSmsTracker.java
index 9ddee8d..7a73c16 100644
--- a/src/java/com/android/internal/telephony/InboundSmsTracker.java
+++ b/src/java/com/android/internal/telephony/InboundSmsTracker.java
@@ -68,6 +68,8 @@
     private String mDeleteWhere;
     private String[] mDeleteWhereArgs;
 
+    // BroadcastReceiver associated with this tracker
+    private InboundSmsHandler.SmsBroadcastReceiver mSmsBroadcastReceiver;
     /**
      * Copied from SmsMessageBase#getDisplayOriginatingAddress used for blocking messages.
      * DisplayAddress could be email address if this message was from an email gateway, otherwise
@@ -326,9 +328,9 @@
             builder.append(") deleteArgs=(").append(Arrays.toString(mDeleteWhereArgs));
             builder.append(')');
         }
-        builder.append(" id=");
-        builder.append(mMessageId);
-        builder.append('}');
+        builder.append(" ");
+        builder.append(SmsController.formatCrossStackMessageId(mMessageId));
+        builder.append("}");
         return builder.toString();
     }
 
@@ -509,4 +511,16 @@
     public @InboundSmsHandler.SmsSource int getSource() {
         return mSmsSource;
     }
+
+    /**
+     * Get/create the SmsBroadcastReceiver corresponding to the current tracker.
+     */
+    public InboundSmsHandler.SmsBroadcastReceiver getSmsBroadcastReceiver(
+            InboundSmsHandler handler) {
+        // lazy initialization
+        if (mSmsBroadcastReceiver == null) {
+            mSmsBroadcastReceiver = handler.new SmsBroadcastReceiver(this);
+        }
+        return mSmsBroadcastReceiver;
+    }
 }
diff --git a/src/java/com/android/internal/telephony/LinkCapacityEstimate.java b/src/java/com/android/internal/telephony/LinkCapacityEstimate.java
deleted file mode 100644
index 7bca5cd..0000000
--- a/src/java/com/android/internal/telephony/LinkCapacityEstimate.java
+++ /dev/null
@@ -1,100 +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;
-
-/**
- * Link Bandwidth Information from the Radio
- */
-public class LinkCapacityEstimate {
-    /** Any field that is not reported shall be set to INVALID */
-    public static final int INVALID = -1;
-
-    /** LCE is active; Deprecated in HAL 1.2 */
-    public static final int STATUS_ACTIVE = 0;
-
-    /** LCE is suspended; Deprecated in HAL 1.2 */
-    public static final int STATUS_SUSPENDED = 1;
-
-    /** Downlink radio link capacity in kbps. In case of a dual connected network,
-     * this includes capacity of both primary and secondary */
-    public final int downlinkCapacityKbps;
-
-    /** Uplink radio link capacity; added in HAL 1.2. In case of a dual connected network,
-     * this includes capacity of both primary and secondary */
-    public final int uplinkCapacityKbps;
-
-    /** Downlink radio link capacity of secondary network in kbps */
-    public final int secondaryDownlinkCapacityKbps;
-
-    /** Uplink radio link capacity of secondary network in kbps */
-    public final int secondaryUplinkCapacityKbps;
-
-    /** Confidence of the downlink estimate as a percentage [1, 100]; deprecated in HAL 1.2 */
-    public final int confidence;
-
-    /** Status of the LCE; deprecated in HAL 1.2 */
-    public final int status; // either STATUS_ACTIVE, STATUS_SUSPENDED, or INVALID
-
-    /** Constructor matching the estimate in Radio HAL v1.0 */
-    public LinkCapacityEstimate(int downlinkCapacityKbps, int confidence, int status) {
-        this.downlinkCapacityKbps = downlinkCapacityKbps;
-        this.confidence = confidence;
-        this.status = status;
-        this.uplinkCapacityKbps = INVALID;
-        this.secondaryDownlinkCapacityKbps = INVALID;
-        this.secondaryUplinkCapacityKbps = INVALID;
-    }
-
-    /** Constructor matching the estimate in Radio HAL v1.2 */
-    public LinkCapacityEstimate(int downlinkCapacityKbps, int uplinkCapacityKbps) {
-        this.downlinkCapacityKbps = downlinkCapacityKbps;
-        this.uplinkCapacityKbps = uplinkCapacityKbps;
-        this.confidence = INVALID;
-        this.status = INVALID;
-        this.secondaryDownlinkCapacityKbps = INVALID;
-        this.secondaryUplinkCapacityKbps = INVALID;
-    }
-
-    /** Constructor matching the estimate in Radio HAL v1.6 */
-    public LinkCapacityEstimate(int downlinkCapacityKbps, int uplinkCapacityKbps,
-            int secondaryDownlinkCapacityKbps, int secondaryUplinkCapacityKbps) {
-        this.downlinkCapacityKbps = downlinkCapacityKbps;
-        this.uplinkCapacityKbps = uplinkCapacityKbps;
-        this.confidence = INVALID;
-        this.status = INVALID;
-        this.secondaryDownlinkCapacityKbps = secondaryDownlinkCapacityKbps;
-        this.secondaryUplinkCapacityKbps = secondaryUplinkCapacityKbps;
-    }
-
-    @Override
-    public String toString() {
-        return new StringBuilder()
-                .append("{downlinkCapacityKbps=")
-                .append(downlinkCapacityKbps)
-                .append(", uplinkCapacityKbps=")
-                .append(uplinkCapacityKbps)
-                .append(", confidence=")
-                .append(confidence)
-                .append(", status=")
-                .append(status)
-                .append("{secondaryDownlinkCapacityKbps=")
-                .append(secondaryDownlinkCapacityKbps)
-                .append(", secondaryUplinkCapacityKbps=")
-                .append(secondaryUplinkCapacityKbps)
-                .toString();
-    }
-}
diff --git a/src/java/com/android/internal/telephony/LocaleTracker.java b/src/java/com/android/internal/telephony/LocaleTracker.java
index 694330b..285659d 100755
--- a/src/java/com/android/internal/telephony/LocaleTracker.java
+++ b/src/java/com/android/internal/telephony/LocaleTracker.java
@@ -85,10 +85,12 @@
      * <p> This broadcast is not effective on user build.
      *
      * <p>Example: To override the current country <code>
+     * adb root
      * adb shell am broadcast -a com.android.internal.telephony.action.COUNTRY_OVERRIDE
      * --es country us </code>
      *
      * <p> To remove the override <code>
+     * adb root
      * adb shell am broadcast -a com.android.internal.telephony.action.COUNTRY_OVERRIDE
      * --ez reset true</code>
      */
diff --git a/src/java/com/android/internal/telephony/MessagingIndication.java b/src/java/com/android/internal/telephony/MessagingIndication.java
new file mode 100644
index 0000000..528b00a
--- /dev/null
+++ b/src/java/com/android/internal/telephony/MessagingIndication.java
@@ -0,0 +1,180 @@
+/*
+ * 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 com.android.internal.telephony.RILConstants.RIL_UNSOL_CDMA_RUIM_SMS_STORAGE_FULL;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_ON_USSD;
+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;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_RESPONSE_NEW_SMS;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_RESPONSE_NEW_SMS_ON_SIM;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_RESPONSE_NEW_SMS_STATUS_REPORT;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_SIM_SMS_STORAGE_FULL;
+
+import android.hardware.radio.messaging.IRadioMessagingIndication;
+import android.os.AsyncResult;
+import android.telephony.SmsMessage;
+
+import com.android.internal.telephony.uicc.IccUtils;
+
+/**
+ * Interface declaring unsolicited radio indications for messaging APIs.
+ */
+public class MessagingIndication extends IRadioMessagingIndication.Stub {
+    private final RIL mRil;
+
+    public MessagingIndication(RIL ril) {
+        mRil = ril;
+    }
+
+    /**
+     * Indicates when new CDMA SMS is received.
+     * @param indicationType Type of radio indication
+     * @param msg CdmaSmsMessage
+     */
+    public void cdmaNewSms(int indicationType,
+            android.hardware.radio.messaging.CdmaSmsMessage msg) {
+        mRil.processIndication(indicationType);
+
+        if (RIL.RILJ_LOGD) mRil.unsljLog(RIL_UNSOL_RESPONSE_CDMA_NEW_SMS);
+
+        SmsMessage sms = new SmsMessage(RILUtils.convertHalCdmaSmsMessage(msg));
+        if (mRil.mCdmaSmsRegistrant != null) {
+            mRil.mCdmaSmsRegistrant.notifyRegistrant(new AsyncResult(null, sms, null));
+        }
+    }
+
+    /**
+     * Indicates that SMS storage on the RUIM is full. Messages cannot be saved on the RUIM until
+     * space is freed.
+     * @param indicationType Type of radio indication
+     */
+    public void cdmaRuimSmsStorageFull(int indicationType) {
+        mRil.processIndication(indicationType);
+
+        if (RIL.RILJ_LOGD) mRil.unsljLog(RIL_UNSOL_CDMA_RUIM_SMS_STORAGE_FULL);
+
+        if (mRil.mIccSmsFullRegistrant != null) {
+            mRil.mIccSmsFullRegistrant.notifyRegistrant();
+        }
+    }
+
+    /**
+     * Indicates when new Broadcast SMS is received
+     * @param indicationType Type of radio indication
+     * @param data If received from GSM network, "data" is byte array of 88 bytes which indicates
+     *        each page of a CBS Message sent to the MS by the BTS as coded in 3GPP 23.041 Section
+     *        9.4.1.2. If received from UMTS network, "data" is byte array of 90 up to 1252 bytes
+     *        which contain between 1 and 15 CBS Message pages sent as one packet to the MS by the
+     *        BTS as coded in 3GPP 23.041 Section 9.4.2.2
+     */
+    public void newBroadcastSms(int indicationType, byte[] data) {
+        mRil.processIndication(indicationType);
+
+        if (RIL.RILJ_LOGD) {
+            mRil.unsljLogvRet(RIL_UNSOL_RESPONSE_NEW_BROADCAST_SMS,
+                    IccUtils.bytesToHexString(data));
+        }
+
+        if (mRil.mGsmBroadcastSmsRegistrant != null) {
+            mRil.mGsmBroadcastSmsRegistrant.notifyRegistrant(new AsyncResult(null, data, null));
+        }
+    }
+
+    /**
+     * Indicates when new SMS is received.
+     * @param indicationType Type of radio indication
+     * @param pdu PDU of SMS-DELIVER represented as byte array.
+     *        The PDU starts with the SMSC address per TS 27.005 (+CMT:)
+     */
+    public void newSms(int indicationType, byte[] pdu) {
+        mRil.processIndication(indicationType);
+        if (RIL.RILJ_LOGD) mRil.unsljLog(RIL_UNSOL_RESPONSE_NEW_SMS);
+
+        SmsMessageBase smsb = com.android.internal.telephony.gsm.SmsMessage.createFromPdu(pdu);
+        if (mRil.mGsmSmsRegistrant != null) {
+            mRil.mGsmSmsRegistrant.notifyRegistrant(
+                    new AsyncResult(null, smsb == null ? null : new SmsMessage(smsb), null));
+        }
+    }
+
+    /**
+     * Indicates when new SMS has been stored on SIM card.
+     * @param indicationType Type of radio indication
+     * @param recordNumber Record number on the SIM
+     */
+    public void newSmsOnSim(int indicationType, int recordNumber) {
+        mRil.processIndication(indicationType);
+
+        if (RIL.RILJ_LOGD) mRil.unsljLog(RIL_UNSOL_RESPONSE_NEW_SMS_ON_SIM);
+
+        if (mRil.mSmsOnSimRegistrant != null) {
+            mRil.mSmsOnSimRegistrant.notifyRegistrant(new AsyncResult(null, recordNumber, null));
+        }
+    }
+
+    /**
+     * Indicates when new SMS Status Report is received.
+     * @param indicationType Type of radio indication
+     * @param pdu PDU of SMS-STATUS-REPORT represented as byte array.
+     *        The PDU starts with the SMSC address per TS 27.005 (+CMT:)
+     */
+    public void newSmsStatusReport(int indicationType, byte[] pdu) {
+        mRil.processIndication(indicationType);
+
+        if (RIL.RILJ_LOGD) mRil.unsljLog(RIL_UNSOL_RESPONSE_NEW_SMS_STATUS_REPORT);
+
+        if (mRil.mSmsStatusRegistrant != null) {
+            mRil.mSmsStatusRegistrant.notifyRegistrant(new AsyncResult(null, pdu, null));
+        }
+    }
+
+    /**
+     * Indicates when a new USSD message is received. The USSD session is assumed to persist if the
+     * type code is REQUEST, otherwise the current session (if any) is assumed to have terminated.
+     * @param indicationType Type of radio indication
+     * @param ussdModeType USSD type code
+     * @param msg Message string in UTF-8, if applicable
+     */
+    public void onUssd(int indicationType, int ussdModeType, String msg) {
+        mRil.processIndication(indicationType);
+
+        if (RIL.RILJ_LOGD) 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};
+        if (mRil.mUSSDRegistrant != null) {
+            mRil.mUSSDRegistrant.notifyRegistrant(new AsyncResult(null, resp, null));
+        }
+    }
+
+    /**
+     * Indicates that SMS storage on the SIM is full. Sent when the network attempts to deliver a
+     * new SMS message. Messages cannot be saved on the SIM until space is freed. In particular,
+     * incoming Class 2 messages must not be stored.
+     * @param indicationType Type of radio indication
+     */
+    public void simSmsStorageFull(int indicationType) {
+        mRil.processIndication(indicationType);
+
+        if (RIL.RILJ_LOGD) 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
new file mode 100644
index 0000000..10f1690
--- /dev/null
+++ b/src/java/com/android/internal/telephony/MessagingResponse.java
@@ -0,0 +1,290 @@
+/*
+ * 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 android.hardware.radio.RadioError;
+import android.hardware.radio.RadioResponseInfo;
+import android.hardware.radio.messaging.IRadioMessagingResponse;
+
+import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
+
+import java.util.ArrayList;
+
+/**
+ * Interface declaring response functions to solicited radio requests for messaging APIs.
+ */
+public class MessagingResponse extends IRadioMessagingResponse.Stub {
+    private final RIL mRil;
+
+    public MessagingResponse(RIL ril) {
+        mRil = ril;
+    }
+
+    private void responseSms(RadioResponseInfo responseInfo,
+            android.hardware.radio.messaging.SendSmsResult sms) {
+        RILRequest rr = mRil.processResponse(responseInfo);
+
+        if (rr != null) {
+            long messageId = RIL.getOutgoingSmsMessageId(rr.mResult);
+            SmsResponse ret = new SmsResponse(sms.messageRef, sms.ackPDU, sms.errorCode, messageId);
+            if (responseInfo.error == RadioError.NONE) {
+                RadioResponse.sendMessageResponse(rr.mResult, ret);
+            }
+            mRil.processResponseDone(rr, responseInfo, ret);
+        }
+    }
+
+    /**
+     * Acknowledge the receipt of radio request sent to the vendor. This must be sent only for
+     * radio request which take long time to respond.
+     * For more details, refer https://source.android.com/devices/tech/connect/ril.html
+     * @param serial Serial no. of the request whose acknowledgement is sent.
+     */
+    public void acknowledgeRequest(int serial) {
+        mRil.processRequestAck(serial);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void acknowledgeIncomingGsmSmsWithPduResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void acknowledgeLastIncomingCdmaSmsResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void acknowledgeLastIncomingGsmSmsResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void cancelPendingUssdResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void deleteSmsOnRuimResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void deleteSmsOnSimResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param configs Vector of CDMA broadcast SMS configs
+     */
+    public void getCdmaBroadcastConfigResponse(RadioResponseInfo responseInfo,
+            android.hardware.radio.messaging.CdmaBroadcastSmsConfigInfo[] configs) {
+        RILRequest rr = mRil.processResponse(responseInfo);
+
+        if (rr != null) {
+            int[] ret;
+            int numServiceCategories = configs.length;
+            if (numServiceCategories == 0) {
+                // TODO: The logic of providing default values should not be done by this transport
+                // layer; it needs to be done by the vendor ril or application logic.
+                int numInts;
+                numInts = RILUtils.CDMA_BROADCAST_SMS_NO_OF_SERVICE_CATEGORIES
+                        * RILUtils.CDMA_BSI_NO_OF_INTS_STRUCT + 1;
+                ret = new int[numInts];
+
+                // Faking a default record for all possible records.
+                ret[0] = RILUtils.CDMA_BROADCAST_SMS_NO_OF_SERVICE_CATEGORIES;
+
+                // Loop over CDMA_BROADCAST_SMS_NO_OF_SERVICE_CATEGORIES set 'english' as
+                // default language and selection status to false for all.
+                for (int i = 1; i < numInts; i += RILUtils.CDMA_BSI_NO_OF_INTS_STRUCT) {
+                    ret[i] = i / RILUtils.CDMA_BSI_NO_OF_INTS_STRUCT;
+                    ret[i + 1] = 1;
+                    ret[i + 2] = 0;
+                }
+            } else {
+                int numInts;
+                numInts = (numServiceCategories * RILUtils.CDMA_BSI_NO_OF_INTS_STRUCT) + 1;
+                ret = new int[numInts];
+
+                ret[0] = numServiceCategories;
+                for (int i = 1, j = 0; j < configs.length;
+                        j++, i = i + RILUtils.CDMA_BSI_NO_OF_INTS_STRUCT) {
+                    ret[i] = configs[i].serviceCategory;
+                    ret[i + 1] = configs[i].language;
+                    ret[i + 2] = configs[i].selected ? 1 : 0;
+                }
+            }
+            if (responseInfo.error == RadioError.NONE) {
+                RadioResponse.sendMessageResponse(rr.mResult, ret);
+            }
+            mRil.processResponseDone(rr, responseInfo, ret);
+        }
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param configs Vector of GSM/WCDMA Cell broadcast SMS configs
+     */
+    public void getGsmBroadcastConfigResponse(RadioResponseInfo responseInfo,
+            android.hardware.radio.messaging.GsmBroadcastSmsConfigInfo[] configs) {
+        RILRequest rr = mRil.processResponse(responseInfo);
+
+        if (rr != null) {
+            ArrayList<SmsBroadcastConfigInfo> ret = new ArrayList<>();
+            for (android.hardware.radio.messaging.GsmBroadcastSmsConfigInfo info : configs) {
+                ret.add(new SmsBroadcastConfigInfo(info.fromServiceId, info.toServiceId,
+                        info.fromCodeScheme, info.toCodeScheme, info.selected));
+            }
+            if (responseInfo.error == RadioError.NONE) {
+                RadioResponse.sendMessageResponse(rr.mResult, ret);
+            }
+            mRil.processResponseDone(rr, responseInfo, ret);
+        }
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param smsc Short Message Service Center address on the device
+     */
+    public void getSmscAddressResponse(RadioResponseInfo responseInfo, String smsc) {
+        RadioResponse.responseString(mRil, responseInfo, smsc);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void reportSmsMemoryStatusResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param sms Response to SMS sent as defined by SendSmsResult
+     */
+    public void sendCdmaSmsExpectMoreResponse(RadioResponseInfo responseInfo,
+            android.hardware.radio.messaging.SendSmsResult sms) {
+        responseSms(responseInfo, sms);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param sms Response to SMS sent as defined by SendSmsResult
+     */
+    public void sendCdmaSmsResponse(RadioResponseInfo responseInfo,
+            android.hardware.radio.messaging.SendSmsResult sms) {
+        responseSms(responseInfo, sms);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param sms Response to SMS sent as defined by SendSmsResult
+     */
+    public void sendImsSmsResponse(RadioResponseInfo responseInfo,
+            android.hardware.radio.messaging.SendSmsResult sms) {
+        responseSms(responseInfo, sms);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param sms Response to SMS sent as defined by SendSmsResult
+     */
+    public void sendSmsExpectMoreResponse(RadioResponseInfo responseInfo,
+            android.hardware.radio.messaging.SendSmsResult sms) {
+        responseSms(responseInfo, sms);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param sms Response to sms sent as defined by SendSmsResult
+     */
+    public void sendSmsResponse(RadioResponseInfo responseInfo,
+            android.hardware.radio.messaging.SendSmsResult sms) {
+        responseSms(responseInfo, sms);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void sendUssdResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void setCdmaBroadcastActivationResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void setCdmaBroadcastConfigResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void setGsmBroadcastActivationResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void setGsmBroadcastConfigResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void setSmscAddressResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param index record index where the CDMA SMS message is stored
+     */
+    public void writeSmsToRuimResponse(RadioResponseInfo responseInfo, int index) {
+        RadioResponse.responseInts(mRil, responseInfo, index);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param index record index where the message is stored
+     */
+    public void writeSmsToSimResponse(RadioResponseInfo responseInfo, int index) {
+        RadioResponse.responseInts(mRil, responseInfo, index);
+    }
+}
diff --git a/src/java/com/android/internal/telephony/ModemIndication.java b/src/java/com/android/internal/telephony/ModemIndication.java
new file mode 100644
index 0000000..db188de
--- /dev/null
+++ b/src/java/com/android/internal/telephony/ModemIndication.java
@@ -0,0 +1,123 @@
+/*
+ * 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 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;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_RESPONSE_RADIO_STATE_CHANGED;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_RIL_CONNECTED;
+
+import android.hardware.radio.modem.IRadioModemIndication;
+import android.os.AsyncResult;
+
+import java.util.ArrayList;
+
+/**
+ * Interface declaring unsolicited radio indications for modem APIs.
+ */
+public class ModemIndication extends IRadioModemIndication.Stub {
+    private final RIL mRil;
+
+    public ModemIndication(RIL ril) {
+        mRil = ril;
+    }
+
+    /**
+     * Indicates when the hardware configuration associated with the RILd changes.
+     * @param indicationType Type of radio indication
+     * @param configs Array of hardware configs
+     */
+    public void hardwareConfigChanged(int indicationType,
+            android.hardware.radio.modem.HardwareConfig[] configs) {
+        mRil.processIndication(indicationType);
+
+        ArrayList<HardwareConfig> response = RILUtils.convertHalHardwareConfigList(configs);
+
+        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_HARDWARE_CONFIG_CHANGED, response);
+
+        mRil.mHardwareConfigChangeRegistrants.notifyRegistrants(
+                new AsyncResult(null, response, null));
+    }
+
+    /**
+     * Indicates when there is a modem reset.
+     * @param indicationType Type of radio indication
+     * @param reason The reason for the reset. It may be a crash signature if the restart was due to
+     *        a crash or some string such as "user-initiated restart" or "AT command initiated
+     *        restart" that explains the cause of the modem restart
+     */
+    public void modemReset(int indicationType, String reason) {
+        mRil.processIndication(indicationType);
+
+        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_MODEM_RESTART, reason);
+
+        mRil.writeMetricsModemRestartEvent(reason);
+        mRil.mModemResetRegistrants.notifyRegistrants(new AsyncResult(null, reason, null));
+    }
+
+    /**
+     * Sent when setRadioCapability() completes. Returns the same RadioCapability as
+     * getRadioCapability() and is the same as the one sent by setRadioCapability().
+     * @param indicationType Type of radio indication
+     * @param radioCapability Current radio capability
+     */
+    public void radioCapabilityIndication(int indicationType,
+            android.hardware.radio.modem.RadioCapability radioCapability) {
+        mRil.processIndication(indicationType);
+
+        RadioCapability response = RILUtils.convertHalRadioCapability(radioCapability, mRil);
+
+        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_RADIO_CAPABILITY, response);
+
+        mRil.mPhoneRadioCapabilityChangedRegistrants.notifyRegistrants(
+                new AsyncResult(null, response, null));
+    }
+
+    /**
+     * Indicates when radio state changes.
+     * @param indicationType Type of radio indication
+     * @param radioState Current radio state
+     */
+    public void radioStateChanged(int indicationType, int radioState) {
+        mRil.processIndication(indicationType);
+
+        int state = RILUtils.convertHalRadioState(radioState);
+        if (RIL.RILJ_LOGD) {
+            mRil.unsljLogMore(RIL_UNSOL_RESPONSE_RADIO_STATE_CHANGED, "radioStateChanged: "
+                    + state);
+        }
+
+        mRil.setRadioState(state, false /* forceNotifyRegistrants */);
+    }
+
+    /**
+     * Indicates the ril connects and returns the version.
+     * @param indicationType Type of radio indication
+     */
+    public void rilConnected(int indicationType) {
+        mRil.processIndication(indicationType);
+
+        if (RIL.RILJ_LOGD) mRil.unsljLog(RIL_UNSOL_RIL_CONNECTED);
+
+        // Initial conditions
+        mRil.setRadioPower(false, null);
+        mRil.setCdmaSubscriptionSource(mRil.mCdmaSubscription, null);
+        // TODO: This should not require a version number. Setting it to latest RIL version for now.
+        mRil.notifyRegistrantsRilConnectionChanged(15);
+    }
+}
diff --git a/src/java/com/android/internal/telephony/ModemResponse.java b/src/java/com/android/internal/telephony/ModemResponse.java
new file mode 100644
index 0000000..de6a46e
--- /dev/null
+++ b/src/java/com/android/internal/telephony/ModemResponse.java
@@ -0,0 +1,242 @@
+/*
+ * 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 android.hardware.radio.RadioError;
+import android.hardware.radio.RadioResponseInfo;
+import android.hardware.radio.modem.IRadioModemResponse;
+import android.os.SystemClock;
+import android.telephony.AnomalyReporter;
+import android.telephony.ModemActivityInfo;
+
+import java.util.ArrayList;
+import java.util.UUID;
+
+/**
+ * Interface declaring response functions to solicited radio requests for modem APIs.
+ */
+public class ModemResponse extends IRadioModemResponse.Stub {
+    private final RIL mRil;
+
+    public ModemResponse(RIL ril) {
+        mRil = ril;
+    }
+
+    /**
+     * Acknowledge the receipt of radio request sent to the vendor. This must be sent only for
+     * radio request which take long time to respond.
+     * For more details, refer https://source.android.com/devices/tech/connect/ril.html
+     * @param serial Serial no. of the request whose acknowledgement is sent.
+     */
+    public void acknowledgeRequest(int serial) {
+        mRil.processRequestAck(serial);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial number and error.
+     */
+    public void enableModemResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param version String containing version string for log reporting
+     */
+    public void getBasebandVersionResponse(RadioResponseInfo responseInfo, String version) {
+        RadioResponse.responseString(mRil, responseInfo, version);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param imei IMEI if GSM subscription is available
+     * @param imeisv IMEISV if GSM subscription is available
+     * @param esn ESN if CDMA subscription is available
+     * @param meid MEID if CDMA subscription is available
+     */
+    public void getDeviceIdentityResponse(RadioResponseInfo responseInfo, String imei,
+            String imeisv, String esn, String meid) {
+        RadioResponse.responseStrings(mRil, responseInfo, imei, imeisv, esn, meid);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param config Array of HardwareConfig of the radio
+     */
+    public void getHardwareConfigResponse(RadioResponseInfo responseInfo,
+            android.hardware.radio.modem.HardwareConfig[] config) {
+        RILRequest rr = mRil.processResponse(responseInfo);
+
+        if (rr != null) {
+            ArrayList<HardwareConfig> ret = RILUtils.convertHalHardwareConfigList(config);
+            if (responseInfo.error == RadioError.NONE) {
+                RadioResponse.sendMessageResponse(rr.mResult, ret);
+            }
+            mRil.processResponseDone(rr, responseInfo, ret);
+        }
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param activityInfo modem activity information
+     */
+    public void getModemActivityInfoResponse(RadioResponseInfo responseInfo,
+            android.hardware.radio.modem.ActivityStatsInfo activityInfo) {
+        RILRequest rr = mRil.processResponse(responseInfo);
+
+        if (rr != null) {
+            ModemActivityInfo ret = null;
+            if (responseInfo.error == RadioError.NONE) {
+                final int sleepModeTimeMs = activityInfo.sleepModeTimeMs;
+                final int idleModeTimeMs = activityInfo.idleModeTimeMs;
+                int [] txModeTimeMs = new int[ModemActivityInfo.getNumTxPowerLevels()];
+                for (int i = 0; i < ModemActivityInfo.getNumTxPowerLevels(); i++) {
+                    txModeTimeMs[i] = activityInfo.txmModetimeMs[i];
+                }
+                final int rxModeTimeMs = activityInfo.rxModeTimeMs;
+                ret = new ModemActivityInfo(SystemClock.elapsedRealtime(), sleepModeTimeMs,
+                        idleModeTimeMs, txModeTimeMs, rxModeTimeMs);
+            } else {
+                ret = new ModemActivityInfo(0, 0, 0,
+                        new int[ModemActivityInfo.getNumTxPowerLevels()], 0);
+                responseInfo.error = RadioError.NONE;
+            }
+            RadioResponse.sendMessageResponse(rr.mResult, ret);
+            mRil.processResponseDone(rr, responseInfo, ret);
+        }
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param isEnabled whether the modem stack is enabled.
+     */
+    public void getModemStackStatusResponse(RadioResponseInfo responseInfo, boolean isEnabled) {
+        RILRequest rr = mRil.processResponse(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 radioCapability RadioCapability from the modem
+     */
+    public void getRadioCapabilityResponse(RadioResponseInfo responseInfo,
+            android.hardware.radio.modem.RadioCapability radioCapability) {
+        RILRequest rr = mRil.processResponse(responseInfo);
+
+        if (rr != null) {
+            RadioCapability ret = RILUtils.convertHalRadioCapability(radioCapability, mRil);
+            if (responseInfo.error == RadioError.REQUEST_NOT_SUPPORTED
+                    || responseInfo.error == RadioError.GENERIC_FAILURE) {
+                // TODO: Construct the supported RAF bitmask based on preferred network bitmasks
+                ret = mRil.makeStaticRadioCapability();
+                responseInfo.error = RadioError.NONE;
+            }
+            if (responseInfo.error == RadioError.NONE) {
+                RadioResponse.sendMessageResponse(rr.mResult, ret);
+            }
+            mRil.processResponseDone(rr, responseInfo, ret);
+        }
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param result String containing the contents of the NV item
+     */
+    public void nvReadItemResponse(RadioResponseInfo responseInfo, String result) {
+        RadioResponse.responseString(mRil, responseInfo, result);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void nvResetConfigResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void nvWriteCdmaPrlResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void nvWriteItemResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void requestShutdownResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void sendDeviceStateResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param radioCapability RadioCapability to set
+     */
+    public void setRadioCapabilityResponse(RadioResponseInfo responseInfo,
+            android.hardware.radio.modem.RadioCapability radioCapability) {
+        RILRequest rr = mRil.processResponse(responseInfo);
+
+        if (rr != null) {
+            RadioCapability ret = RILUtils.convertHalRadioCapability(radioCapability, mRil);
+            if (responseInfo.error == RadioError.NONE) {
+                RadioResponse.sendMessageResponse(rr.mResult, ret);
+            }
+            mRil.processResponseDone(rr, responseInfo, ret);
+        }
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error.
+     */
+    public void setRadioPowerResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+        mRil.mLastRadioPowerResult = responseInfo.error;
+        if (responseInfo.error == RadioError.RF_HARDWARE_ISSUE) {
+            AnomalyReporter.reportAnomaly(
+                    UUID.fromString(RILUtils.RADIO_POWER_FAILURE_RF_HARDWARE_ISSUE_UUID),
+                    "RF HW damaged");
+        } else if (responseInfo.error == RadioError.NO_RF_CALIBRATION_INFO) {
+            AnomalyReporter.reportAnomaly(
+                    UUID.fromString(RILUtils.RADIO_POWER_FAILURE_NO_RF_CALIBRATION_UUID),
+                    "No RF calibration data");
+        } else if (responseInfo.error != RadioError.RADIO_NOT_AVAILABLE
+                && responseInfo.error != RadioError.NONE) {
+            AnomalyReporter.reportAnomaly(
+                    UUID.fromString(RILUtils.RADIO_POWER_FAILURE_BUGREPORT_UUID),
+                    "Radio power failure");
+        }
+    }
+}
diff --git a/src/java/com/android/internal/telephony/MultiSimSettingController.java b/src/java/com/android/internal/telephony/MultiSimSettingController.java
index 024f3c8..ca5d962 100644
--- a/src/java/com/android/internal/telephony/MultiSimSettingController.java
+++ b/src/java/com/android/internal/telephony/MultiSimSettingController.java
@@ -724,8 +724,9 @@
         if (mPrimarySubList.size() == 1 && change == PRIMARY_SUB_REMOVED
                 && (!dataSelected || !smsSelected || !voiceSelected)) {
             dialogType = EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_ALL;
-        } else if (mPrimarySubList.size() > 1 && isUserVisibleChange(change)) {
-            // If change is SWAPPED_IN_GROUP or MARKED_OPPT orINITIALIZED, don't ask user again.
+        } else if (mPrimarySubList.size() > 1 && (isUserVisibleChange(change)
+                || (change == PRIMARY_SUB_INITIALIZED && !dataSelected))) {
+            // If change is SWAPPED_IN_GROUP or MARKED_OPPT, don't ask user again.
             dialogType = EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_DATA;
         }
 
diff --git a/src/java/com/android/internal/telephony/NetworkIndication.java b/src/java/com/android/internal/telephony/NetworkIndication.java
new file mode 100644
index 0000000..b8c8f53
--- /dev/null
+++ b/src/java/com/android/internal/telephony/NetworkIndication.java
@@ -0,0 +1,369 @@
+/*
+ * 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 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_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_RESPONSE_IMS_NETWORK_STATE_CHANGED;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_RESPONSE_NETWORK_STATE_CHANGED;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_RESTRICTED_STATE_CHANGED;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_SIGNAL_STRENGTH;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_SUPP_SVC_NOTIFICATION;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_VOICE_RADIO_TECH_CHANGED;
+
+import android.annotation.ElapsedRealtimeLong;
+import android.hardware.radio.network.IRadioNetworkIndication;
+import android.os.AsyncResult;
+import android.sysprop.TelephonyProperties;
+import android.telephony.AnomalyReporter;
+import android.telephony.BarringInfo;
+import android.telephony.CellIdentity;
+import android.telephony.CellInfo;
+import android.telephony.LinkCapacityEstimate;
+import android.telephony.NetworkRegistrationInfo;
+import android.telephony.PhysicalChannelConfig;
+import android.telephony.ServiceState;
+import android.telephony.SignalStrength;
+import android.text.TextUtils;
+
+import com.android.internal.telephony.gsm.SuppServiceNotification;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * Interface declaring unsolicited radio indications for network APIs.
+ */
+public class NetworkIndication extends IRadioNetworkIndication.Stub {
+    private final RIL mRil;
+
+    public NetworkIndication(RIL ril) {
+        mRil = ril;
+    }
+
+    /**
+     * Indicate that BarringInfo has changed for the current cell and user.
+     * @param indicationType Type of radio indication
+     * @param cellIdentity the CellIdentity of the Cell
+     * @param barringInfos the updated barring information from the current cell, filtered for the
+     *        current PLMN and access class / access category.
+     */
+    public void barringInfoChanged(int indicationType,
+            android.hardware.radio.network.CellIdentity cellIdentity,
+            android.hardware.radio.network.BarringInfo[] barringInfos) {
+        mRil.processIndication(indicationType);
+
+        if (cellIdentity == null || barringInfos == null) {
+            AnomalyReporter.reportAnomaly(UUID.fromString("645b16bb-c930-4c1c-9c5d-568696542e05"),
+                    "Invalid barringInfoChanged indication");
+            mRil.riljLoge("Invalid barringInfoChanged indication");
+            return;
+        }
+
+        BarringInfo cbi = new BarringInfo(RILUtils.convertHalCellIdentity(cellIdentity),
+                RILUtils.convertHalBarringInfoList(barringInfos));
+
+        mRil.mBarringInfoChangedRegistrants.notifyRegistrants(new AsyncResult(null, cbi, null));
+    }
+
+    /**
+     * Indicates when PRL (preferred roaming list) changes.
+     * @param indicationType Type of radio indication
+     * @param version PRL version after PRL changes
+     */
+    public void cdmaPrlChanged(int indicationType, int version) {
+        mRil.processIndication(indicationType);
+
+        int[] response = new int[]{version};
+
+        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_CDMA_PRL_CHANGED, response);
+
+        mRil.mCdmaPrlChangedRegistrants.notifyRegistrants(new AsyncResult(null, response, null));
+    }
+
+    /**
+     * Report all of the current cell information known to the radio.
+     * @param indicationType Type of radio indication
+     * @param records Current cell information
+     */
+    public void cellInfoList(int indicationType,
+            android.hardware.radio.network.CellInfo[] records) {
+        mRil.processIndication(indicationType);
+        ArrayList<CellInfo> response = RILUtils.convertHalCellInfoList(records);
+        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_CELL_INFO_LIST, response);
+        mRil.mRilCellInfoListRegistrants.notifyRegistrants(new AsyncResult(null, response, null));
+    }
+
+    /**
+     * Indicates current link capacity estimate.
+     * @param indicationType Type of radio indication
+     * @param lce LinkCapacityEstimate
+     */
+    public void currentLinkCapacityEstimate(int indicationType,
+            android.hardware.radio.network.LinkCapacityEstimate lce) {
+        mRil.processIndication(indicationType);
+
+        List<LinkCapacityEstimate> response = RILUtils.convertHalLceData(lce);
+
+        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_LCEDATA_RECV, response);
+
+        if (mRil.mLceInfoRegistrants != null) {
+            mRil.mLceInfoRegistrants.notifyRegistrants(new AsyncResult(null, response, null));
+        }
+    }
+
+    /**
+     * Indicates current physical channel configuration.
+     * @param indicationType Type of radio indication
+     * @param configs Vector of PhysicalChannelConfigs
+     */
+    public void currentPhysicalChannelConfigs(int indicationType,
+            android.hardware.radio.network.PhysicalChannelConfig[] configs) {
+        mRil.processIndication(indicationType);
+        List<PhysicalChannelConfig> response = new ArrayList<>(configs.length);
+        for (android.hardware.radio.network.PhysicalChannelConfig config : configs) {
+            PhysicalChannelConfig.Builder builder = new PhysicalChannelConfig.Builder();
+            switch (config.band.getTag()) {
+                case android.hardware.radio.network.PhysicalChannelConfigBand.geranBand:
+                    builder.setBand(config.band.getGeranBand());
+                    break;
+                case android.hardware.radio.network.PhysicalChannelConfigBand.utranBand:
+                    builder.setBand(config.band.getUtranBand());
+                    break;
+                case android.hardware.radio.network.PhysicalChannelConfigBand.eutranBand:
+                    builder.setBand(config.band.getEutranBand());
+                    break;
+                case android.hardware.radio.network.PhysicalChannelConfigBand.ngranBand:
+                    builder.setBand(config.band.getNgranBand());
+                    break;
+                default:
+                    mRil.riljLoge("Unsupported band type " + config.band.getTag());
+            }
+            response.add(builder.setCellConnectionStatus(
+                    RILUtils.convertHalCellConnectionStatus(config.status))
+                    .setDownlinkChannelNumber(config.downlinkChannelNumber)
+                    .setUplinkChannelNumber(config.uplinkChannelNumber)
+                    .setCellBandwidthDownlinkKhz(config.cellBandwidthDownlinkKhz)
+                    .setCellBandwidthUplinkKhz(config.cellBandwidthUplinkKhz)
+                    .setNetworkType(ServiceState.rilRadioTechnologyToNetworkType(config.rat))
+                    .setPhysicalCellId(config.physicalCellId)
+                    .setContextIds(config.contextIds)
+                    .build());
+        }
+        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_PHYSICAL_CHANNEL_CONFIG, response);
+
+        mRil.mPhysicalChannelConfigurationRegistrants.notifyRegistrants(
+                new AsyncResult(null, response, null));
+    }
+
+    /**
+     * Indicates current signal strength of the radio.
+     * @param indicationType Type of radio indication
+     * @param signalStrength SignalStrength information
+     */
+    public void currentSignalStrength(int indicationType,
+            android.hardware.radio.network.SignalStrength signalStrength) {
+        mRil.processIndication(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.mSignalStrengthRegistrant != null) {
+            mRil.mSignalStrengthRegistrant.notifyRegistrant(new AsyncResult(null, ss, null));
+        }
+    }
+
+    /**
+     * Indicates when IMS registration state has changed.
+     * @param indicationType Type of radio indication
+     */
+    public void imsNetworkStateChanged(int indicationType) {
+        mRil.processIndication(indicationType);
+
+        if (RIL.RILJ_LOGD) mRil.unsljLog(RIL_UNSOL_RESPONSE_IMS_NETWORK_STATE_CHANGED);
+
+        mRil.mImsNetworkStateChangedRegistrants.notifyRegistrants();
+    }
+
+    /**
+     * Incremental network scan results.
+     * @param indicationType Type of radio indication
+     * @param result the result of the network scan
+     */
+    public void networkScanResult(int indicationType,
+            android.hardware.radio.network.NetworkScanResult result) {
+        mRil.processIndication(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);
+        mRil.mRilNetworkScanResultRegistrants.notifyRegistrants(new AsyncResult(null, nsr, null));
+    }
+
+    /**
+     * Indicates when either voice or data network state changed
+     * @param indicationType Type of radio indication
+     */
+    public void networkStateChanged(int indicationType) {
+        mRil.processIndication(indicationType);
+
+        if (RIL.RILJ_LOGD) mRil.unsljLog(RIL_UNSOL_RESPONSE_NETWORK_STATE_CHANGED);
+
+        mRil.mNetworkStateRegistrants.notifyRegistrants();
+    }
+
+    /**
+     * Indicates when radio has received a NITZ time message.
+     * @param indicationType Type of radio indication
+     * @param nitzTime NITZ time string in the form "yy/mm/dd,hh:mm:ss(+/-)tz,dt"
+     * @param receivedTimeMs time according to {@link android.os.SystemClock#elapsedRealtime()} when
+     *        the RIL sent the NITZ time to the framework
+     * @param ageMs time in milliseconds indicating how long NITZ was cached in RIL and modem.
+     *        This must track true age and therefore must be calculated using clocks that
+     *        include the time spend in sleep / low power states. If it can not be guaranteed,
+     *        there must not be any caching done at the modem and should fill in 0 for ageMs
+     */
+    public void nitzTimeReceived(int indicationType, String nitzTime,
+        @ElapsedRealtimeLong long receivedTimeMs, long ageMs) {
+        mRil.processIndication(indicationType);
+
+        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_NITZ_TIME_RECEIVED, nitzTime);
+
+        // Ignore the NITZ if ageMs is not a valid time, e.g. negative or greater than
+        // receivedTimeMs.
+        if ((ageMs < 0) || (ageMs >= receivedTimeMs)) {
+            AnomalyReporter.reportAnomaly(UUID.fromString("fc7c56d4-485d-475a-aaff-394203c6cdfc"),
+                    "NITZ indication with invalid age");
+
+            mRil.riljLoge("age time is invalid, ignoring nitzTimeReceived indication. "
+                + "receivedTimeMs = " + receivedTimeMs + ", ageMs = " + ageMs);
+            return;
+        }
+
+        // TODO: Clean this up with a parcelable class for better self-documentation
+        Object[] result = new Object[3];
+        result[0] = nitzTime;
+        result[1] = receivedTimeMs;
+        result[2] = ageMs;
+
+        boolean ignoreNitz = TelephonyProperties.ignore_nitz().orElse(false);
+
+        if (ignoreNitz) {
+            if (RIL.RILJ_LOGD) mRil.riljLog("ignoring UNSOL_NITZ_TIME_RECEIVED");
+        } else {
+            if (mRil.mNITZTimeRegistrant != null) {
+                mRil.mNITZTimeRegistrant.notifyRegistrant(new AsyncResult(null, result, null));
+            }
+            // in case NITZ time registrant isn't registered yet, or a new registrant
+            // registers later
+            mRil.mLastNITZTimeInfo = result;
+        }
+    }
+
+    /**
+     * Indicate that a registration failure has occurred.
+     * @param cellIdentity a CellIdentity the CellIdentity of the Cell
+     * @param chosenPlmn a 5 or 6 digit alphanumeric string indicating the PLMN on which
+     *        registration failed
+     * @param domain the domain of the failed procedure: CS, PS, or both
+     * @param causeCode the primary failure cause code of the procedure
+     * @param additionalCauseCode an additional cause code if applicable
+     */
+    public void registrationFailed(int indicationType,
+            android.hardware.radio.network.CellIdentity cellIdentity, String chosenPlmn,
+            @NetworkRegistrationInfo.Domain int domain, int causeCode, int additionalCauseCode) {
+        mRil.processIndication(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)) {
+            AnomalyReporter.reportAnomaly(UUID.fromString("f16e5703-6105-4341-9eb3-e68189156eb4"),
+                    "Invalid registrationFailed indication");
+
+            mRil.riljLoge("Invalid registrationFailed indication");
+            return;
+        }
+
+        mRil.mRegistrationFailedRegistrant.notifyRegistrant(
+                new AsyncResult(null, new RegistrationFailedEvent(
+                        ci, chosenPlmn, domain, causeCode, additionalCauseCode), null));
+    }
+
+    /**
+     * Indicates a restricted state change (eg, for Domain Specific Access Control).
+     * @param indicationType Type of radio indication
+     * @param state Bitmask of restricted state as defined by PhoneRestrictedState
+     */
+    public void restrictedStateChanged(int indicationType, int state) {
+        mRil.processIndication(indicationType);
+
+        if (RIL.RILJ_LOGD) mRil.unsljLogvRet(RIL_UNSOL_RESTRICTED_STATE_CHANGED, state);
+
+        if (mRil.mRestrictedStateRegistrant != null) {
+            mRil.mRestrictedStateRegistrant.notifyRegistrant(new AsyncResult(null, state, null));
+        }
+    }
+
+    /**
+     * Reports supplementary service related notification from the network.
+     * @param indicationType Type of radio indication
+     * @param suppSvcNotification SuppSvcNotification
+     */
+    public void suppSvcNotify(int indicationType,
+            android.hardware.radio.network.SuppSvcNotification suppSvcNotification) {
+        mRil.processIndication(indicationType);
+
+        SuppServiceNotification notification = new SuppServiceNotification();
+        notification.notificationType = suppSvcNotification.isMT ? 1 : 0;
+        notification.code = suppSvcNotification.code;
+        notification.index = suppSvcNotification.index;
+        notification.type = suppSvcNotification.type;
+        notification.number = suppSvcNotification.number;
+
+        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_SUPP_SVC_NOTIFICATION, notification);
+
+        if (mRil.mSsnRegistrant != null) {
+            mRil.mSsnRegistrant.notifyRegistrant(new AsyncResult(null, notification, null));
+        }
+    }
+
+    /**
+     * Indicates that voice technology has changed. Responds with new rat.
+     * @param indicationType Type of radio indication
+     * @param rat Current new voice rat
+     */
+    public void voiceRadioTechChanged(int indicationType, int rat) {
+        mRil.processIndication(indicationType);
+
+        int[] response = new int[] {rat};
+
+        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_VOICE_RADIO_TECH_CHANGED, response);
+
+        mRil.mVoiceRadioTechChangedRegistrants.notifyRegistrants(
+                new AsyncResult(null, response, null));
+    }
+}
diff --git a/src/java/com/android/internal/telephony/NetworkRegistrationManager.java b/src/java/com/android/internal/telephony/NetworkRegistrationManager.java
index 54f6aa1..3535678 100644
--- a/src/java/com/android/internal/telephony/NetworkRegistrationManager.java
+++ b/src/java/com/android/internal/telephony/NetworkRegistrationManager.java
@@ -202,7 +202,6 @@
                         new NetworkRegStateCallback());
             } catch (RemoteException exception) {
                 // Remote exception means that the binder already died.
-                mDeathRecipient.binderDied();
                 logd("RemoteException " + exception);
             }
         }
diff --git a/src/java/com/android/internal/telephony/NetworkResponse.java b/src/java/com/android/internal/telephony/NetworkResponse.java
new file mode 100644
index 0000000..fa82a23
--- /dev/null
+++ b/src/java/com/android/internal/telephony/NetworkResponse.java
@@ -0,0 +1,451 @@
+/*
+ * 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 android.content.Context;
+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.LinkCapacityEstimate;
+import android.telephony.NeighboringCellInfo;
+import android.telephony.RadioAccessSpecifier;
+import android.telephony.SignalStrength;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Interface declaring response functions to solicited radio requests for network APIs.
+ */
+public class NetworkResponse extends IRadioNetworkResponse.Stub {
+    private final RIL mRil;
+
+    public NetworkResponse(RIL ril) {
+        mRil = ril;
+    }
+
+    /**
+     * Acknowledge the receipt of radio request sent to the vendor. This must be sent only for
+     * radio request which take long time to respond.
+     * For more details, refer https://source.android.com/devices/tech/connect/ril.html
+     * @param serial Serial no. of the request whose acknowledgement is sent.
+     */
+    public void acknowledgeRequest(int serial) {
+        mRil.processRequestAck(serial);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param halRadioAccessFamilyBitmap a 32-bit bitmap of RadioAccessFamily.
+     */
+    public void getAllowedNetworkTypesBitmapResponse(RadioResponseInfo responseInfo,
+            int halRadioAccessFamilyBitmap) {
+        int networkTypeBitmask = RILUtils.convertHalNetworkTypeBitMask(halRadioAccessFamilyBitmap);
+        mRil.mAllowedNetworkTypesBitmask = networkTypeBitmask;
+        RadioResponse.responseInts(mRil, responseInfo, networkTypeBitmask);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param bandModes List of RadioBandMode listing supported modes
+     */
+    public void getAvailableBandModesResponse(RadioResponseInfo responseInfo, int[] bandModes) {
+        RadioResponse.responseIntArrayList(mRil, responseInfo,
+                RILUtils.primitiveArrayToArrayList(bandModes));
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param networkInfos List of network operator information as OperatorInfos
+     */
+    public void getAvailableNetworksResponse(RadioResponseInfo responseInfo,
+            android.hardware.radio.network.OperatorInfo[] networkInfos) {
+        RILRequest rr = mRil.processResponse(responseInfo);
+
+        if (rr != null) {
+            ArrayList<OperatorInfo> ret = new ArrayList<>();
+            for (android.hardware.radio.network.OperatorInfo info : networkInfos) {
+                ret.add(new OperatorInfo(info.alphaLong, info.alphaShort, info.operatorNumeric,
+                        RILUtils.convertHalOperatorStatus(info.status)));
+            }
+            if (responseInfo.error == RadioError.NONE) {
+                RadioResponse.sendMessageResponse(rr.mResult, ret);
+            }
+            mRil.processResponseDone(rr, responseInfo, ret);
+        }
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error.
+     * @param cellIdentity CellIdentity for the barringInfos.
+     * @param barringInfos List of BarringInfo for all the barring service types.
+     */
+    public void getBarringInfoResponse(RadioResponseInfo responseInfo,
+            android.hardware.radio.network.CellIdentity cellIdentity,
+            android.hardware.radio.network.BarringInfo[] barringInfos) {
+        RILRequest rr = mRil.processResponse(responseInfo);
+
+        if (rr != null) {
+            BarringInfo bi = new BarringInfo(RILUtils.convertHalCellIdentity(cellIdentity),
+                    RILUtils.convertHalBarringInfoList(barringInfos));
+            if (responseInfo.error == RadioError.NONE) {
+                RadioResponse.sendMessageResponse(rr.mResult, bi);
+                // notify all registrants for the possible barring info change
+                mRil.mBarringInfoChangedRegistrants.notifyRegistrants(
+                        new AsyncResult(null, bi, null));
+            }
+            mRil.processResponseDone(rr, responseInfo, bi);
+        }
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param type CdmaRoamingType defined in types.hal
+     */
+    public void getCdmaRoamingPreferenceResponse(RadioResponseInfo responseInfo, int type) {
+        RadioResponse.responseInts(mRil, responseInfo, type);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param cellInfo List of current cell information known to radio
+     */
+    public void getCellInfoListResponse(RadioResponseInfo responseInfo,
+            android.hardware.radio.network.CellInfo[] cellInfo) {
+        RILRequest rr = mRil.processResponse(responseInfo);
+
+        if (rr != null) {
+            ArrayList<CellInfo> ret = RILUtils.convertHalCellInfoList(cellInfo);
+            if (responseInfo.error == RadioError.NONE) {
+                RadioResponse.sendMessageResponse(rr.mResult, ret);
+            }
+            mRil.processResponseDone(rr, responseInfo, ret);
+        }
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param dataRegResponse Current data registration response as defined by DataRegStateResult
+     */
+    public void getDataRegistrationStateResponse(RadioResponseInfo responseInfo,
+            android.hardware.radio.network.RegStateResult dataRegResponse) {
+        RILRequest rr = mRil.processResponse(responseInfo);
+
+        if (rr != null) {
+            if (responseInfo.error == RadioError.NONE) {
+                RadioResponse.sendMessageResponse(rr.mResult, dataRegResponse);
+            }
+            mRil.processResponseDone(rr, responseInfo, dataRegResponse);
+        }
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param isRegistered false = not registered, true = registered
+     * @param ratFamily RadioTechnologyFamily. This value is valid only if isRegistered is true.
+     */
+    public void getImsRegistrationStateResponse(RadioResponseInfo responseInfo,
+            boolean isRegistered, int ratFamily) {
+        RadioResponse.responseInts(mRil, responseInfo, isRegistered ? 1 : 0,
+                ratFamily == android.hardware.radio.RadioTechnologyFamily.THREE_GPP
+                        ? PhoneConstants.PHONE_TYPE_GSM : PhoneConstants.PHONE_TYPE_CDMA);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param cells Vector of neighboring radio cell information
+     */
+    public void getNeighboringCidsResponse(RadioResponseInfo responseInfo,
+            android.hardware.radio.network.NeighboringCell[] cells) {
+        RILRequest rr = mRil.processResponse(responseInfo);
+
+        if (rr != null) {
+            ArrayList<NeighboringCellInfo> ret = new ArrayList<>();
+            int[] subId = SubscriptionManager.getSubId(mRil.mPhoneId);
+            int radioType = ((TelephonyManager) mRil.mContext.getSystemService(
+                    Context.TELEPHONY_SERVICE)).getDataNetworkType(subId[0]);
+
+            if (radioType != TelephonyManager.NETWORK_TYPE_UNKNOWN) {
+                for (android.hardware.radio.network.NeighboringCell cell : cells) {
+                    ret.add(new NeighboringCellInfo(cell.rssi, cell.cid, radioType));
+                }
+            }
+            if (responseInfo.error == RadioError.NONE) {
+                RadioResponse.sendMessageResponse(rr.mResult, ret);
+            }
+            mRil.processResponseDone(rr, responseInfo, ret);
+        }
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param selection false for automatic selection, true for manual selection
+     */
+    public void getNetworkSelectionModeResponse(RadioResponseInfo responseInfo, boolean selection) {
+        RadioResponse.responseInts(mRil, responseInfo, selection ? 1 : 0);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param longName is long alpha ONS or EONS or empty string if unregistered
+     * @param shortName is short alpha ONS or EONS or empty string if unregistered
+     * @param numeric is 5 or 6 digit numeric code (MCC + MNC) or empty string if unregistered
+     */
+    public void getOperatorResponse(RadioResponseInfo responseInfo, String longName,
+            String shortName, String numeric) {
+        RadioResponse.responseStrings(mRil, responseInfo, longName, shortName, numeric);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param signalStrength Current signal strength of camped/connected cells
+     */
+    public void getSignalStrengthResponse(RadioResponseInfo responseInfo,
+            android.hardware.radio.network.SignalStrength signalStrength) {
+        RILRequest rr = mRil.processResponse(responseInfo);
+
+        if (rr != null) {
+            SignalStrength ret = RILUtils.convertHalSignalStrength(signalStrength);
+            if (responseInfo.error == RadioError.NONE) {
+                RadioResponse.sendMessageResponse(rr.mResult, ret);
+            }
+            mRil.processResponseDone(rr, responseInfo, ret);
+        }
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error.
+     * @param halSpecifiers List of RadioAccessSpecifiers that are scanned.
+     */
+    public void getSystemSelectionChannelsResponse(RadioResponseInfo responseInfo,
+            android.hardware.radio.network.RadioAccessSpecifier[] halSpecifiers) {
+        RILRequest rr = mRil.processResponse(responseInfo);
+
+        if (rr != null) {
+            ArrayList<RadioAccessSpecifier> specifiers = new ArrayList<>();
+            for (android.hardware.radio.network.RadioAccessSpecifier specifier : halSpecifiers) {
+                specifiers.add(RILUtils.convertHalRadioAccessSpecifier(specifier));
+            }
+            mRil.riljLog("getSystemSelectionChannelsResponse: from HIDL: " + specifiers);
+            if (responseInfo.error == RadioError.NONE) {
+                RadioResponse.sendMessageResponse(rr.mResult, specifiers);
+            }
+            mRil.processResponseDone(rr, responseInfo, specifiers);
+        }
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param rat Current voice RAT
+     */
+    public void getVoiceRadioTechnologyResponse(RadioResponseInfo responseInfo, int rat) {
+        RadioResponse.responseInts(mRil, responseInfo, rat);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param voiceRegResponse Current Voice registration response as defined by VoiceRegStateResult
+     */
+    public void getVoiceRegistrationStateResponse(RadioResponseInfo responseInfo,
+            android.hardware.radio.network.RegStateResult voiceRegResponse) {
+        RILRequest rr = mRil.processResponse(responseInfo);
+        if (rr != null) {
+            if (responseInfo.error == RadioError.NONE) {
+                RadioResponse.sendMessageResponse(rr.mResult, voiceRegResponse);
+            }
+            mRil.processResponseDone(rr, responseInfo, voiceRegResponse);
+        }
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param isEnabled Indicates whether NR dual connectivity is enabled or not, True if enabled
+     *        else false.
+     */
+    public void isNrDualConnectivityEnabledResponse(RadioResponseInfo responseInfo,
+            boolean isEnabled) {
+        RILRequest rr = mRil.processResponse(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 lceInfo LceDataInfo indicating LCE data
+     */
+    public void pullLceDataResponse(RadioResponseInfo responseInfo,
+            android.hardware.radio.network.LceDataInfo lceInfo) {
+        RILRequest rr = mRil.processResponse(responseInfo);
+
+        if (rr != null) {
+            List<LinkCapacityEstimate> ret = RILUtils.convertHalLceData(lceInfo);
+            if (responseInfo.error == RadioError.NONE) {
+                RadioResponse.sendMessageResponse(rr.mResult, ret);
+            }
+            mRil.processResponseDone(rr, responseInfo, ret);
+        }
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void setAllowedNetworkTypesBitmapResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void setBandModeResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void setBarringPasswordResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void setCdmaRoamingPreferenceResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void setCellInfoListRateResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void setIndicationFilterResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void setLinkCapacityReportingCriteriaResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void setLocationUpdatesResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void setNetworkSelectionModeAutomaticResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void setNetworkSelectionModeManualResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void setNrDualConnectivityStateResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void setSignalStrengthReportingCriteriaResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void setSuppServiceNotificationsResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial number and error.
+     */
+    public void setSystemSelectionChannelsResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void startNetworkScanResponse(RadioResponseInfo responseInfo) {
+        RILRequest rr = mRil.processResponse(responseInfo);
+        if (rr != null) {
+            NetworkScanResult nsr = null;
+            if (responseInfo.error == RadioError.NONE) {
+                nsr = new NetworkScanResult(NetworkScanResult.SCAN_STATUS_PARTIAL,
+                        RadioError.NONE, null);
+                RadioResponse.sendMessageResponse(rr.mResult, nsr);
+            }
+            mRil.processResponseDone(rr, responseInfo, nsr);
+        }
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void stopNetworkScanResponse(RadioResponseInfo responseInfo) {
+        RILRequest rr = mRil.processResponse(responseInfo);
+        if (rr != null) {
+            NetworkScanResult nsr = null;
+            if (responseInfo.error == RadioError.NONE) {
+                nsr = new NetworkScanResult(NetworkScanResult.SCAN_STATUS_PARTIAL,
+                        RadioError.NONE, null);
+                RadioResponse.sendMessageResponse(rr.mResult, nsr);
+            }
+            mRil.processResponseDone(rr, responseInfo, nsr);
+        }
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param retriesRemaining Number of retries remaining, must be equal to -1 if unknown.
+     */
+    public void supplyNetworkDepersonalizationResponse(RadioResponseInfo responseInfo,
+            int retriesRemaining) {
+        RadioResponse.responseInts(mRil, responseInfo, retriesRemaining);
+    }
+}
diff --git a/src/java/com/android/internal/telephony/NetworkScanRequestTracker.java b/src/java/com/android/internal/telephony/NetworkScanRequestTracker.java
index 388703f..92f552a 100644
--- a/src/java/com/android/internal/telephony/NetworkScanRequestTracker.java
+++ b/src/java/com/android/internal/telephony/NetworkScanRequestTracker.java
@@ -445,6 +445,7 @@
                     .setCallingPackage(nsri.mCallingPackage)
                     .setCallingPid(nsri.mPid)
                     .setCallingUid(nsri.mUid)
+                    .setCallingFeatureId(nsri.mPhone.getContext().getAttributionTag())
                     .setMinSdkVersionForFine(Build.VERSION_CODES.Q)
                     .setMinSdkVersionForCoarse(Build.VERSION_CODES.Q)
                     .setMinSdkVersionForEnforcement(Build.VERSION_CODES.Q)
diff --git a/src/java/com/android/internal/telephony/NetworkTypeController.java b/src/java/com/android/internal/telephony/NetworkTypeController.java
index 5d5fca0..d4a2a50 100644
--- a/src/java/com/android/internal/telephony/NetworkTypeController.java
+++ b/src/java/com/android/internal/telephony/NetworkTypeController.java
@@ -98,6 +98,7 @@
     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 String[] sEvents = new String[EVENT_PCO_DATA_CHANGED + 1];
     static {
@@ -142,6 +143,7 @@
     private boolean mIsSecondaryTimerActive;
     private boolean mIsTimerResetEnabledForLegacyStateRRCIdle;
     private int mLtePlusThresholdBandwidth;
+    private int mNrAdvancedThresholdBandwidth;
     private int[] mAdditionalNrAdvancedBandsList;
     private String mPrimaryTimerState;
     private String mSecondaryTimerState;
@@ -150,6 +152,8 @@
     private boolean mIsPhysicalChannelConfig16Supported;
     private Boolean mIsNrAdvancedAllowedByPco = false;
     private int mNrAdvancedCapablePcoId = 0;
+    private boolean mIsUsingUserDataForRrcDetection = false;
+    private boolean mEnableNrAdvancedWhileRoaming = true;
 
     /**
      * NetworkTypeController constructor.
@@ -199,6 +203,8 @@
         mPhone.getServiceStateTracker().registerForDataRegStateOrRatChanged(
                 AccessNetworkConstants.TRANSPORT_TYPE_WWAN, getHandler(),
                 EVENT_DATA_RAT_CHANGED, null);
+        mPhone.getServiceStateTracker().registerForBandwidthChanged(
+                getHandler(), EVENT_BANDWIDTH_CHANGED, null);
         mIsPhysicalChannelConfig16Supported = mPhone.getContext().getSystemService(
                 TelephonyManager.class).isRadioInterfaceCapabilitySupported(
                 TelephonyManager.CAPABILITY_PHYSICAL_CHANNEL_CONFIG_1_6_SUPPORTED);
@@ -245,6 +251,10 @@
                         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);
@@ -275,10 +285,22 @@
                 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);
+                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) {
+                    mPhone.getDcTracker(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
+                            .registerForPhysicalLinkStateChanged(getHandler(),
+                                    EVENT_PHYSICAL_LINK_STATE_CHANGED);
+                }
             }
         }
         createTimerRules(nrIconConfiguration, overrideTimerRule, overrideSecondaryTimerRule);
@@ -500,10 +522,11 @@
                 case EVENT_NR_STATE_CHANGED:
                 case EVENT_NR_FREQUENCY_CHANGED:
                 case EVENT_PCO_DATA_CHANGED:
+                case EVENT_BANDWIDTH_CHANGED:
                     // ignored
                     break;
                 case EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED:
-                    if (mIsPhysicalChannelConfig16Supported) {
+                    if (isUsingPhysicalChannelConfigForRrcDetection()) {
                         mPhysicalLinkState = getPhysicalLinkStateFromPhysicalChannelConfig();
                     }
                     break;
@@ -608,7 +631,7 @@
                     // ignored
                     break;
                 case EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED:
-                    if (mIsPhysicalChannelConfig16Supported) {
+                    if (isUsingPhysicalChannelConfigForRrcDetection()) {
                         mPhysicalLinkState = getPhysicalLinkStateFromPhysicalChannelConfig();
                         if (mIsTimerResetEnabledForLegacyStateRRCIdle && !isPhysicalLinkActive()) {
                             resetAllTimers();
@@ -680,7 +703,7 @@
                     // ignore
                     break;
                 case EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED:
-                    if (mIsPhysicalChannelConfig16Supported) {
+                    if (isUsingPhysicalChannelConfigForRrcDetection()) {
                         mPhysicalLinkState = getPhysicalLinkStateFromPhysicalChannelConfig();
                         if (isNrNotRestricted()) {
                             // NOT_RESTRICTED_RRC_IDLE -> NOT_RESTRICTED_RRC_CON
@@ -764,7 +787,7 @@
                     // ignore
                     break;
                 case EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED:
-                    if (mIsPhysicalChannelConfig16Supported) {
+                    if (isUsingPhysicalChannelConfigForRrcDetection()) {
                         mPhysicalLinkState = getPhysicalLinkStateFromPhysicalChannelConfig();
                         if (isNrNotRestricted()) {
                             // NOT_RESTRICTED_RRC_CON -> NOT_RESTRICTED_RRC_IDLE
@@ -856,7 +879,7 @@
                     break;
                 case EVENT_NR_FREQUENCY_CHANGED:
                 case EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED:
-                    if (mIsPhysicalChannelConfig16Supported) {
+                    if (isUsingPhysicalChannelConfigForRrcDetection()) {
                         mPhysicalLinkState = getPhysicalLinkStateFromPhysicalChannelConfig();
                     }
                     updateNrAdvancedState();
@@ -869,6 +892,9 @@
                         sendMessage(EVENT_NR_STATE_CHANGED);
                     }
                     break;
+                case EVENT_BANDWIDTH_CHANGED:
+                    updateNrAdvancedState();
+                    break;
                 default:
                     return NOT_HANDLED;
             }
@@ -1125,8 +1151,32 @@
                 == NetworkRegistrationInfo.NR_STATE_RESTRICTED;
     }
 
+    /**
+     * @return {@code true} if the device is in NR advanced mode (i.e. 5G+).
+     */
     private boolean isNrAdvanced() {
-        return isNrAdvancedCapable() && (isNrMmwave() || isAdditionalNrAdvancedBand());
+        // Check PCO requirement. For carriers using PCO to indicate whether the data connection is
+        // NR advanced capable, mNrAdvancedCapablePcoId should be configured to non-zero.
+        if (mNrAdvancedCapablePcoId > 0 && !mIsNrAdvancedAllowedByPco) {
+            return false;
+        }
+
+        // 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) {
+            return false;
+        }
+
+        // Check if meeting minimum bandwidth requirement. For most carriers, there is no minimum
+        // bandwidth requirement and mNrAdvancedThresholdBandwidth is 0.
+        if (IntStream.of(mPhone.getServiceState().getCellBandwidths()).sum()
+                < mNrAdvancedThresholdBandwidth) {
+            return false;
+        }
+
+        // If all above tests passed, then check if the device is using millimeter wave bands or
+        // carrier designated bands.
+        return isNrMmwave() || isAdditionalNrAdvancedBand();
     }
 
     private boolean isNrMmwave() {
@@ -1150,13 +1200,6 @@
         return false;
     }
 
-    private boolean isNrAdvancedCapable() {
-        if (mNrAdvancedCapablePcoId > 0) {
-            return mIsNrAdvancedAllowedByPco;
-        }
-        return true;
-    }
-
     private boolean isLte(int rat) {
         return rat == TelephonyManager.NETWORK_TYPE_LTE
                 || rat == TelephonyManager.NETWORK_TYPE_LTE_CA;
@@ -1188,6 +1231,10 @@
         }
     }
 
+    private boolean isUsingPhysicalChannelConfigForRrcDetection() {
+        return mIsPhysicalChannelConfig16Supported && !mIsUsingUserDataForRrcDetection;
+    }
+
     protected void log(String s) {
         Rlog.d(TAG, "[" + mPhone.getPhoneId() + "] " + s);
     }
diff --git a/src/java/com/android/internal/telephony/NitzSignal.java b/src/java/com/android/internal/telephony/NitzSignal.java
new file mode 100644
index 0000000..889fe95
--- /dev/null
+++ b/src/java/com/android/internal/telephony/NitzSignal.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright 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 android.annotation.DurationMillisLong;
+import android.annotation.ElapsedRealtimeLong;
+import android.annotation.NonNull;
+import android.os.TimestampedValue;
+
+import java.time.Duration;
+import java.util.Objects;
+
+/** NITZ information and associated metadata. */
+public final class NitzSignal {
+
+    @ElapsedRealtimeLong private final long mReceiptElapsedMillis;
+    @NonNull private final NitzData mNitzData;
+    @DurationMillisLong private final long mAgeMillis;
+
+    /**
+     * @param receiptElapsedMillis the time according to {@link
+     *     android.os.SystemClock#elapsedRealtime()} when the NITZ signal was first received by
+     *     the platform code
+     * @param nitzData the NITZ data
+     * @param ageMillis the age of the NITZ when it was passed to the platform, e.g. if it was
+     *     cached by the modem for a period of time. Must not be negative.
+     */
+    public NitzSignal(
+            @ElapsedRealtimeLong long receiptElapsedMillis,
+            @NonNull NitzData nitzData,
+            long ageMillis) {
+        mReceiptElapsedMillis = receiptElapsedMillis;
+        mNitzData = Objects.requireNonNull(nitzData);
+        if (ageMillis < 0) {
+            throw new IllegalArgumentException("ageMillis < 0");
+        }
+        mAgeMillis = ageMillis;
+    }
+
+    /**
+     * Returns the time according to {@link android.os.SystemClock#elapsedRealtime()} when the NITZ
+     * signal was first received by the platform code.
+     */
+    @ElapsedRealtimeLong
+    public long getReceiptElapsedRealtimeMillis() {
+        return mReceiptElapsedMillis;
+    }
+
+    /**
+     * Returns the NITZ data.
+     */
+    @NonNull
+    public NitzData getNitzData() {
+        return mNitzData;
+    }
+
+    /**
+     * Returns the age of the NITZ when it was passed to the platform, e.g. if it was cached by the
+     * modem for a period of time. Must not be negative.
+     */
+    @DurationMillisLong
+    public long getAgeMillis() {
+        return mAgeMillis;
+    }
+
+    /**
+     * Returns a derived property of {@code receiptElapsedMillis - ageMillis}, i.e. the time
+     * according to the elapsed realtime clock when the NITZ signal was actually received by this
+     * device taking into time it was cached by layers before the RIL.
+     */
+    @ElapsedRealtimeLong
+    public long getAgeAdjustedElapsedRealtimeMillis() {
+        return mReceiptElapsedMillis - mAgeMillis;
+    }
+
+    /**
+     * 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.
+     */
+    public TimestampedValue<Long> createTimeSignal() {
+        return new TimestampedValue<>(
+                getAgeAdjustedElapsedRealtimeMillis(),
+                getNitzData().getCurrentTimeInMillis());
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        NitzSignal that = (NitzSignal) o;
+        return mReceiptElapsedMillis == that.mReceiptElapsedMillis
+                && mAgeMillis == that.mAgeMillis
+                && mNitzData.equals(that.mNitzData);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mReceiptElapsedMillis, mNitzData, mAgeMillis);
+    }
+
+    @Override
+    public String toString() {
+        return "NitzSignal{"
+                + "mReceiptElapsedMillis=" + Duration.ofMillis(mReceiptElapsedMillis)
+                + ", mNitzData=" + mNitzData
+                + ", mAgeMillis=" + mAgeMillis
+                + '}';
+    }
+}
diff --git a/src/java/com/android/internal/telephony/NitzStateMachine.java b/src/java/com/android/internal/telephony/NitzStateMachine.java
index e1f854c..f4f4deb 100644
--- a/src/java/com/android/internal/telephony/NitzStateMachine.java
+++ b/src/java/com/android/internal/telephony/NitzStateMachine.java
@@ -21,7 +21,6 @@
 import android.content.Context;
 import android.os.SystemClock;
 import android.os.SystemProperties;
-import android.os.TimestampedValue;
 import android.provider.Settings;
 
 import com.android.internal.util.IndentingPrintWriter;
@@ -65,7 +64,7 @@
     /**
      * Handle a new NITZ signal being received.
      */
-    void handleNitzReceived(@NonNull TimestampedValue<NitzData> nitzSignal);
+    void handleNitzReceived(@NonNull NitzSignal nitzSignal);
 
     /**
      * Handle the user putting the device into or out of airplane mode
@@ -90,14 +89,14 @@
     interface DeviceState {
 
         /**
-         * If time between NITZ updates is less than {@link #getNitzUpdateSpacingMillis()} the
-         * update may be ignored.
+         * If elapsed time between two NITZ signals is less than this value then the second signal
+         * can be ignored.
          */
         int getNitzUpdateSpacingMillis();
 
         /**
-         * If {@link #getNitzUpdateSpacingMillis()} hasn't been exceeded but update is >
-         * {@link #getNitzUpdateDiffMillis()} do the update
+         * If UTC time between two NITZ signals is less than this value then the second signal can
+         * be ignored.
          */
         int getNitzUpdateDiffMillis();
 
@@ -109,7 +108,7 @@
         /**
          * Returns the same value as {@link SystemClock#elapsedRealtime()}.
          */
-        long elapsedRealtime();
+        long elapsedRealtimeMillis();
 
         /**
          * Returns the same value as {@link System#currentTimeMillis()}.
@@ -158,7 +157,7 @@
         }
 
         @Override
-        public long elapsedRealtime() {
+        public long elapsedRealtimeMillis() {
             return SystemClock.elapsedRealtime();
         }
 
diff --git a/src/java/com/android/internal/telephony/OemHookIndication.java b/src/java/com/android/internal/telephony/OemHookIndication.java
deleted file mode 100644
index 122a70e..0000000
--- a/src/java/com/android/internal/telephony/OemHookIndication.java
+++ /dev/null
@@ -1,53 +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;
-
-import android.hardware.radio.deprecated.V1_0.IOemHookIndication;
-import android.os.AsyncResult;
-
-import java.util.ArrayList;
-
-import static com.android.internal.telephony.RILConstants.RIL_UNSOL_OEM_HOOK_RAW;
-
-/**
- * Class containing oem hook indication callbacks
- */
-public class OemHookIndication extends IOemHookIndication.Stub {
-    RIL mRil;
-
-    public OemHookIndication(RIL ril) {
-        mRil = ril;
-    }
-
-    /**
-     * @param indicationType RadioIndicationType
-     * @param data Data sent by oem
-     */
-    public void oemHookRaw(int indicationType, ArrayList<Byte> data) {
-        mRil.processIndication(indicationType);
-
-        byte[] response = RIL.arrayListToPrimitiveArray(data);
-        if (RIL.RILJ_LOGD) {
-            mRil.unsljLogvRet(RIL_UNSOL_OEM_HOOK_RAW,
-                    com.android.internal.telephony.uicc.IccUtils.bytesToHexString(response));
-        }
-
-        if (mRil.mUnsolOemHookRawRegistrant != null) {
-            mRil.mUnsolOemHookRawRegistrant.notifyRegistrant(new AsyncResult(null, response, null));
-        }
-    }
-}
diff --git a/src/java/com/android/internal/telephony/OemHookResponse.java b/src/java/com/android/internal/telephony/OemHookResponse.java
deleted file mode 100644
index 0afeac8..0000000
--- a/src/java/com/android/internal/telephony/OemHookResponse.java
+++ /dev/null
@@ -1,59 +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;
-
-import android.hardware.radio.deprecated.V1_0.IOemHookResponse;
-import android.hardware.radio.V1_0.RadioError;
-import android.hardware.radio.V1_0.RadioResponseInfo;
-
-import java.util.ArrayList;
-
-/**
- * Class containing oem hook response callbacks
- */
-public class OemHookResponse extends IOemHookResponse.Stub {
-    RIL mRil;
-
-    public OemHookResponse(RIL ril) {
-        mRil = ril;
-    }
-
-    /**
-     * @param responseInfo Response info struct containing response type, serial no. and error
-     * @param data Data returned by oem
-     */
-    public void sendRequestRawResponse(RadioResponseInfo responseInfo, ArrayList<Byte> data) {
-        RILRequest rr = mRil.processResponse(responseInfo);
-
-        if (rr != null) {
-            byte[] ret = null;
-            if (responseInfo.error == RadioError.NONE) {
-                ret = RIL.arrayListToPrimitiveArray(data);
-                RadioResponse.sendMessageResponse(rr.mResult, ret);
-            }
-            mRil.processResponseDone(rr, responseInfo, ret);
-        }
-    }
-
-    /**
-     * @param responseInfo Response info struct containing response type, serial no. and error
-     * @param data Data returned by oem
-     */
-    public void sendRequestStringsResponse(RadioResponseInfo responseInfo, ArrayList<String> data) {
-        RadioResponse.responseStringArrayList(mRil, responseInfo, data);
-    }
-}
diff --git a/src/java/com/android/internal/telephony/Phone.java b/src/java/com/android/internal/telephony/Phone.java
index 3e3b80f..9d16f1f 100644
--- a/src/java/com/android/internal/telephony/Phone.java
+++ b/src/java/com/android/internal/telephony/Phone.java
@@ -66,6 +66,7 @@
 import android.telephony.ims.stub.ImsRegistrationImplBase;
 import android.text.TextUtils;
 import android.util.LocalLog;
+import android.util.Log;
 import android.util.SparseArray;
 import android.util.Xml;
 
@@ -78,6 +79,7 @@
 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.LinkBandwidthEstimator;
 import com.android.internal.telephony.dataconnection.TransportManager;
 import com.android.internal.telephony.emergency.EmergencyNumberTracker;
 import com.android.internal.telephony.imsphone.ImsPhoneCall;
@@ -341,6 +343,7 @@
     protected DataEnabledSettings mDataEnabledSettings;
     // Used for identify the carrier of current subscription
     protected CarrierResolver mCarrierResolver;
+    protected SignalStrengthController mSignalStrengthController;
 
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     protected int mPhoneId;
@@ -457,6 +460,8 @@
     protected VoiceCallSessionStats mVoiceCallSessionStats;
     protected SmsStats mSmsStats;
 
+    protected LinkBandwidthEstimator mLinkBandwidthEstimator;
+
     public IccRecords getIccRecords() {
         return mIccRecords.get();
     }
@@ -732,6 +737,8 @@
                 break;
 
             case EVENT_INITIATE_SILENT_REDIAL:
+                // This is an ImsPhone -> GsmCdmaPhone redial
+                // See ImsPhone#initiateSilentRedial
                 Rlog.d(LOG_TAG, "Event EVENT_INITIATE_SILENT_REDIAL Received");
                 ar = (AsyncResult) msg.obj;
                 if ((ar.exception == null) && (ar.result != null)) {
@@ -742,6 +749,10 @@
                     if (TextUtils.isEmpty(dialString)) return;
                     try {
                         Connection cn = dialInternal(dialString, dialArgs);
+                        // The ImsPhoneConnection that is owned by the ImsPhone is currently the
+                        // one with a callback registered to TelephonyConnection. Notify the
+                        // redial happened over that Phone so that it can be replaced with the
+                        // new GSM/CDMA Connection.
                         Rlog.d(LOG_TAG, "Notify redial connection changed cn: " + cn);
                         if (mImsPhone != null) {
                             // Don't care it is null or not.
@@ -1890,6 +1901,14 @@
     }
 
     /**
+     * Retrieves the SignalStrengthController of the phone instance.
+     */
+    public SignalStrengthController getSignalStrengthController() {
+        Log.wtf(LOG_TAG, "getSignalStrengthController return null.");
+        return null;
+    }
+
+    /**
      * Update voice activation state
      */
     public void setVoiceActivationState(int state) {
@@ -2035,7 +2054,7 @@
      *  Retrieves manually selected network info.
      */
     public String getManualNetworkSelectionPlmn() {
-        return "";
+        return null;
     }
 
 
@@ -2179,11 +2198,11 @@
      * @return Current signal strength as SignalStrength
      */
     public SignalStrength getSignalStrength() {
-        ServiceStateTracker sst = getServiceStateTracker();
-        if (sst == null) {
+        SignalStrengthController ssc = getSignalStrengthController();
+        if (ssc == null) {
             return new SignalStrength();
         } else {
-            return sst.getSignalStrength();
+            return ssc.getSignalStrength();
         }
     }
 
@@ -2304,7 +2323,6 @@
      * Loads the allowed network type from subscription database.
      */
     public void loadAllowedNetworksFromSubscriptionDatabase() {
-        mIsAllowedNetworkTypesLoadedFromDb = false;
         // Try to load ALLOWED_NETWORK_TYPES from SIMINFO.
         if (SubscriptionController.getInstance() == null) {
             return;
@@ -2313,6 +2331,8 @@
         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) {
             return;
         }
@@ -2344,7 +2364,6 @@
                     }
                 }
             }
-            mIsAllowedNetworkTypesLoadedFromDb = true;
         } catch (NumberFormatException e) {
             Rlog.e(LOG_TAG, "allowedNetworkTypes NumberFormat exception" + e);
         }
@@ -2415,12 +2434,18 @@
         int subId = getSubId();
         if (!TelephonyManager.isValidAllowedNetworkTypesReason(reason)) {
             loge("setAllowedNetworkTypes: Invalid allowed network type reason: " + reason);
+            AsyncResult.forMessage(response, null,
+                    new CommandException(CommandException.Error.INVALID_ARGUMENTS));
+            response.sendToTarget();
             return;
         }
         if (!SubscriptionManager.isUsableSubscriptionId(subId)
                 || !mIsAllowedNetworkTypesLoadedFromDb) {
             loge("setAllowedNetworkTypes: no sim or network type is not loaded. SubscriptionId: "
                     + subId + ", isNetworkTypeLoaded" + mIsAllowedNetworkTypesLoadedFromDb);
+            AsyncResult.forMessage(response, null,
+                    new CommandException(CommandException.Error.MISSING_RESOURCE));
+            response.sendToTarget();
             return;
         }
         String mapAsString = "";
@@ -2713,6 +2738,18 @@
         mCi.nvResetConfig(2 /* erase NV */, response);
     }
 
+    /**
+     * Erase data saved in the SharedPreference. Used for network reset
+     *
+     */
+    public boolean eraseDataInSharedPreferences() {
+        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
+        SharedPreferences.Editor editor = sp.edit();
+        Rlog.d(LOG_TAG, "Erase all data saved in SharedPreferences");
+        editor.clear();
+        return editor.commit();
+    }
+
     public void setSystemSelectionChannels(List<RadioAccessSpecifier> specifiers,
             Message response) {
         mCi.setSystemSelectionChannels(specifiers, response);
@@ -4234,8 +4271,9 @@
         Phone imsPhone = mImsPhone;
         if (imsPhone != null) {
             imsPhone.getImsRegistrationState(callback);
+        } else {
+            callback.accept(RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED);
         }
-        callback.accept(RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED);
     }
 
 
@@ -4749,12 +4787,12 @@
      * @param isIdle true if the new state is idle
      */
     public void notifyDeviceIdleStateChanged(boolean isIdle) {
-        ServiceStateTracker sst = getServiceStateTracker();
-        if (sst == null) {
-            Rlog.e(LOG_TAG, "notifyDeviceIdleStateChanged: SST is null");
+        SignalStrengthController ssc = getSignalStrengthController();
+        if (ssc == null) {
+            Rlog.e(LOG_TAG, "notifyDeviceIdleStateChanged: SignalStrengthController is null");
             return;
         }
-        sst.onDeviceIdleStateChanged(isIdle);
+        ssc.onDeviceIdleStateChanged(isIdle);
     }
 
     /**
@@ -4768,6 +4806,21 @@
     }
 
     /**
+     *
+     * @return
+     */
+    public @NonNull List<String> getDataServicePackages() {
+        return Collections.emptyList();
+    }
+
+    /**
+     * Return link bandwidth estimator
+     */
+    public LinkBandwidthEstimator getLinkBandwidthEstimator() {
+        return mLinkBandwidthEstimator;
+    }
+
+    /**
      * Request to get the current slicing configuration including URSP rules and
      * NSSAIs (configured, allowed and rejected).
      */
@@ -4775,6 +4828,13 @@
         mCi.getSlicingConfig(response);
     }
 
+    /**
+     * Returns the InboundSmsHandler object for this phone
+     */
+    public InboundSmsHandler getInboundSmsHandler(boolean is3gpp2) {
+        return null;
+    }
+
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("Phone: subId=" + getSubId());
         pw.println(" mPhoneId=" + mPhoneId);
@@ -4930,6 +4990,12 @@
             pw.println("++++++++++++++++++++++++++++++++");
         }
 
+        if (mSignalStrengthController != null) {
+            pw.println("SignalStrengthController:");
+            mSignalStrengthController.dump(fd, pw, args);
+            pw.println("++++++++++++++++++++++++++++++++");
+        }
+
         if (mTransportManager != null) {
             mTransportManager.dump(fd, pw, args);
         }
@@ -4951,6 +5017,12 @@
             pw.println("++++++++++++++++++++++++++++++++");
         }
 
+        if (getLinkBandwidthEstimator() != null) {
+            pw.println("LinkBandwidthEstimator:");
+            getLinkBandwidthEstimator().dump(fd, pw, args);
+            pw.println("++++++++++++++++++++++++++++++++");
+        }
+
         pw.println("Phone Local Log: ");
         if (mLocalLog != null) {
             try {
@@ -4978,4 +5050,16 @@
     private static String pii(String s) {
         return Rlog.pii(LOG_TAG, s);
     }
+
+    /**
+     * Used in unit tests to set whether the AllowedNetworkTypes is loaded from Db.  Should not
+     * be used otherwise.
+     *
+     * @return {@code true} if the AllowedNetworkTypes is loaded from Db,
+     * {@code false} otherwise.
+     */
+    @VisibleForTesting
+    public boolean isAllowedNetworkTypesLoadedFromDb() {
+        return mIsAllowedNetworkTypesLoadedFromDb;
+    }
 }
diff --git a/src/java/com/android/internal/telephony/PhoneConfigurationManager.java b/src/java/com/android/internal/telephony/PhoneConfigurationManager.java
index d92f96d..4d5ae40 100644
--- a/src/java/com/android/internal/telephony/PhoneConfigurationManager.java
+++ b/src/java/com/android/internal/telephony/PhoneConfigurationManager.java
@@ -336,7 +336,7 @@
             log("switchMultiSimConfig: sending the request for switching");
             Message callback = Message.obtain(
                     mHandler, EVENT_SWITCH_DSDS_CONFIG_DONE, numOfSims, 0 /**dummy arg*/);
-            mRadioConfig.setModemsConfig(numOfSims, callback);
+            mRadioConfig.setNumOfLiveModems(numOfSims, callback);
         } else {
             log("switchMultiSimConfig: No need to switch. getNumOfActiveSims is already "
                     + numOfSims);
diff --git a/src/java/com/android/internal/telephony/PhoneFactory.java b/src/java/com/android/internal/telephony/PhoneFactory.java
index a480af8..d51fa91 100644
--- a/src/java/com/android/internal/telephony/PhoneFactory.java
+++ b/src/java/com/android/internal/telephony/PhoneFactory.java
@@ -177,7 +177,6 @@
                             cdmaSubscription, i);
                 }
 
-
                 if (numPhones > 0) {
                     final RadioConfig radioConfig = RadioConfig.make(context,
                             sCommandsInterfaces[0].getHalVersion());
@@ -190,6 +189,7 @@
                             radioConfig, null);
                 }
 
+
                 // Instantiate UiccController so that all other classes can just
                 // call getInstance()
                 sUiccController = UiccController.make(context);
diff --git a/src/java/com/android/internal/telephony/PhoneInternalInterface.java b/src/java/com/android/internal/telephony/PhoneInternalInterface.java
index a61954b..437459f 100644
--- a/src/java/com/android/internal/telephony/PhoneInternalInterface.java
+++ b/src/java/com/android/internal/telephony/PhoneInternalInterface.java
@@ -38,6 +38,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.List;
+import java.util.function.Consumer;
 
 /**
  * Internal interface used to control the phone; SDK developers cannot
@@ -489,12 +490,33 @@
      *
      * @param dialString The dial string.
      * @param dialArgs Parameters to perform the dial with.
+     * @param chosenPhone The Phone (either GsmCdmaPhone or ImsPhone) that has been chosen to dial
+     *                    this number. This is used for any setup that should occur before dial
+     *                    actually occurs.
      * @exception CallStateException if a new outgoing call is not currently
      *                possible because no more call slots exist or a call exists
      *                that is dialing, alerting, ringing, or waiting. Other
      *                errors are handled asynchronously.
      */
-    Connection dial(String dialString, @NonNull DialArgs dialArgs) throws CallStateException;
+    Connection dial(String dialString, @NonNull DialArgs dialArgs,
+            Consumer<Phone> chosenPhone) throws CallStateException;
+
+    /**
+     * Initiate a new voice connection. This happens asynchronously, so you
+     * cannot assume the audio path is connected (or a call index has been
+     * assigned) until PhoneStateChanged notification has occurred.
+     *
+     * @param dialString The dial string.
+     * @param dialArgs Parameters to perform the dial with.
+     * @exception CallStateException if a new outgoing call is not currently
+     *                possible because no more call slots exist or a call exists
+     *                that is dialing, alerting, ringing, or waiting. Other
+     *                errors are handled asynchronously.
+     */
+    default Connection dial(String dialString, @NonNull DialArgs dialArgs)
+            throws CallStateException {
+        return dial(dialString, dialArgs, (phone) -> {});
+    }
 
     /**
      * Initiate a new conference connection. This happens asynchronously, so you
diff --git a/src/java/com/android/internal/telephony/PhoneNotifier.java b/src/java/com/android/internal/telephony/PhoneNotifier.java
index 4378529..701a157 100644
--- a/src/java/com/android/internal/telephony/PhoneNotifier.java
+++ b/src/java/com/android/internal/telephony/PhoneNotifier.java
@@ -124,7 +124,6 @@
     void notifyPhysicalChannelConfig(Phone sender, List<PhysicalChannelConfig> configs);
 
     /** Notify DataEnabled has changed. */
-
     void notifyDataEnabled(Phone sender, boolean enabled, @DataEnabledReason int reason);
 
     /** Notify Allowed Network Type has changed. */
diff --git a/src/java/com/android/internal/telephony/PhoneSubInfoController.java b/src/java/com/android/internal/telephony/PhoneSubInfoController.java
index 8211f2d..8db71d9 100644
--- a/src/java/com/android/internal/telephony/PhoneSubInfoController.java
+++ b/src/java/com/android/internal/telephony/PhoneSubInfoController.java
@@ -23,7 +23,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.AppOpsManager;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.pm.PackageManager;
@@ -31,6 +30,7 @@
 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;
@@ -48,7 +48,6 @@
 
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private final Context mContext;
-    private final AppOpsManager mAppOps;
 
     public PhoneSubInfoController(Context context) {
         ServiceRegisterer phoneSubServiceRegisterer = TelephonyFrameworkInitializer
@@ -58,7 +57,6 @@
             phoneSubServiceRegisterer.register(this);
         }
         mContext = context;
-        mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
     }
 
     @Deprecated
@@ -143,7 +141,10 @@
 
     public String getSubscriberIdForSubscriber(int subId, String callingPackage,
             String callingFeatureId) {
-        String message = "getSubscriberId";
+        String message = "getSubscriberIdForSubscriber";
+
+        enforceCallingPackage(callingPackage, Binder.getCallingUid(), message);
+
         long identity = Binder.clearCallingIdentity();
         boolean isActive;
         try {
@@ -264,6 +265,17 @@
         return PhoneFactory.getPhone(phoneId);
     }
 
+    private boolean enforceIccSimChallengeResponsePermission(Context context, int subId,
+            String callingPackage, String callingFeatureId, String message) {
+        if (TelephonyPermissions.checkCallingOrSelfUseIccAuthWithDeviceIdentifier(context,
+                callingPackage, callingFeatureId, message)) {
+            return true;
+        }
+        if (VDBG) log("No USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER permission.");
+        enforcePrivilegedPermissionOrCarrierPrivilege(subId, message);
+        return true;
+    }
+
     /**
      * Make sure caller has either read privileged phone permission or carrier privilege.
      *
@@ -290,6 +302,28 @@
                 "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();
@@ -370,8 +404,9 @@
                 });
     }
 
-    public String getIccSimChallengeResponse(int subId, int appType, int authType, String data)
-            throws RemoteException {
+    @Override
+    public String getIccSimChallengeResponse(int subId, int appType, int authType, String data,
+            String callingPackage, String callingFeatureId) throws RemoteException {
         CallPhoneMethodHelper<String> toExecute = (phone)-> {
             UiccCard uiccCard = phone.getUiccCard();
             if (uiccCard == null) {
@@ -396,12 +431,9 @@
             return uiccApp.getIccRecords().getIccSimChallengeResponse(authType, data);
         };
 
-        return callPhoneMethodWithPermissionCheck(subId, null, null, "getIccSimChallengeResponse",
-                toExecute,
-                (aContext, aSubId, aCallingPackage, aCallingFeatureId, aMessage) -> {
-                    enforcePrivilegedPermissionOrCarrierPrivilege(aSubId, aMessage);
-                    return true;
-                });
+        return callPhoneMethodWithPermissionCheck(subId, callingPackage, callingFeatureId,
+                "getIccSimChallengeResponse", toExecute,
+                this::enforceIccSimChallengeResponsePermission);
     }
 
     public String getGroupIdLevel1ForSubscriber(int subId, String callingPackage,
diff --git a/src/java/com/android/internal/telephony/PhoneSwitcher.java b/src/java/com/android/internal/telephony/PhoneSwitcher.java
index 2dd83ca..d252ebe 100644
--- a/src/java/com/android/internal/telephony/PhoneSwitcher.java
+++ b/src/java/com/android/internal/telephony/PhoneSwitcher.java
@@ -24,6 +24,9 @@
 import static android.telephony.TelephonyManager.SET_OPPORTUNISTIC_SUB_INACTIVE_SUBSCRIPTION;
 import static android.telephony.TelephonyManager.SET_OPPORTUNISTIC_SUB_SUCCESS;
 import static android.telephony.TelephonyManager.SET_OPPORTUNISTIC_SUB_VALIDATION_FAILED;
+import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM;
+import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN;
+import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_NONE;
 
 import static java.util.Arrays.copyOf;
 
@@ -57,8 +60,14 @@
 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.LocalLog;
 
+import com.android.ims.ImsException;
+import com.android.ims.ImsManager;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.SubscriptionController.WatchedInt;
 import com.android.internal.telephony.dataconnection.ApnConfigTypeRepository;
@@ -166,6 +175,7 @@
     private final LocalLog mLocalLog;
     protected PhoneState[] mPhoneStates;
     protected int[] mPhoneSubscriptions;
+    private boolean mIsRegisteredForImsRadioTechChange;
     @VisibleForTesting
     protected final CellularNetworkValidator mValidator;
     private int mPendingSwitchSubId = INVALID_SUBSCRIPTION_ID;
@@ -246,7 +256,7 @@
     private static final int EVENT_EMERGENCY_TOGGLE               = 105;
     private static final int EVENT_RADIO_CAPABILITY_CHANGED       = 106;
     private static final int EVENT_OPPT_DATA_SUB_CHANGED          = 107;
-    private static final int EVENT_RADIO_AVAILABLE                = 108;
+    private static final int EVENT_RADIO_ON                       = 108;
     // A call has either started or ended. If an emergency ended and DDS is overridden using
     // 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
@@ -271,6 +281,11 @@
     public 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;
+
+    // List of events triggers re-evaluations
+    private static final String EVALUATION_REASON_RADIO_ON = "EVENT_RADIO_ON";
 
     // Depending on version of IRadioConfig, we need to send either RIL_REQUEST_ALLOW_DATA if it's
     // 1.0, or RIL_REQUEST_SET_PREFERRED_DATA if it's 1.1 or later. So internally mHalCommandToUse
@@ -290,6 +305,7 @@
     private Boolean mHasRegisteredDefaultNetworkChangeCallback = false;
 
     private ConnectivityManager mConnectivityManager;
+    private int mImsRegistrationTech = REGISTRATION_TECH_NONE;
 
     private List<Set<CommandException.Error>> mCurrentDdsSwitchFailure;
 
@@ -312,9 +328,42 @@
         }
     }
 
+    private RegistrationManager.RegistrationCallback mRegistrationCallback =
+            new RegistrationManager.RegistrationCallback() {
+        @Override
+        public void onRegistered(ImsRegistrationAttributes attributes) {
+            int imsRegistrationTech = attributes.getRegistrationTechnology();
+            if (imsRegistrationTech != mImsRegistrationTech) {
+                mImsRegistrationTech = imsRegistrationTech;
+                sendMessage(obtainMessage(EVENT_IMS_RADIO_TECH_CHANGED));
+            }
+        }
+
+        @Override
+        public void onUnregistered(ImsReasonInfo info) {
+            if (mImsRegistrationTech != REGISTRATION_TECH_NONE) {
+                mImsRegistrationTech = REGISTRATION_TECH_NONE;
+                sendMessage(obtainMessage(EVENT_IMS_RADIO_TECH_CHANGED));
+            }
+        }
+    };
+
     private final DefaultNetworkCallback mDefaultNetworkCallback = new DefaultNetworkCallback();
 
     /**
+     * Interface to get ImsRegistrationTech. It's a wrapper of ImsManager#getRegistrationTech,
+     * to make it mock-able in unittests.
+     */
+    public interface ImsRegTechProvider {
+        /** Get IMS registration tech. */
+        @ImsRegistrationImplBase.ImsRegistrationTech int get(Context context, int phoneId);
+    }
+
+    @VisibleForTesting
+    public ImsRegTechProvider mImsRegTechProvider =
+            (context, phoneId) -> ImsManager.getInstance(context, phoneId).getRegistrationTech();
+
+    /**
      * Method to get singleton instance.
      */
     public static PhoneSwitcher getInstance() {
@@ -333,6 +382,27 @@
         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() {
         int oldPhoneIdInVoiceCall = mPhoneIdInVoiceCall;
         // If there's no active call, the value will become INVALID_PHONE_INDEX
@@ -340,7 +410,8 @@
         // subscription.
         mPhoneIdInVoiceCall = SubscriptionManager.INVALID_PHONE_INDEX;
         for (Phone phone : PhoneFactory.getPhones()) {
-            if (isPhoneInVoiceCall(phone) || isPhoneInVoiceCall(phone.getImsPhone())) {
+            if (isPhoneInVoiceCall(phone) || (isPhoneInVoiceCall(phone.getImsPhone())
+                    && isImsOnOriginalNetwork(phone))) {
                 mPhoneIdInVoiceCall = phone.getPhoneId();
                 break;
             }
@@ -355,6 +426,35 @@
         }
     }
 
+    private void registerForImsRadioTechChange(Context context, int phoneId) {
+        try {
+            ImsManager.getInstance(context, phoneId).addRegistrationCallback(
+                    mRegistrationCallback, this::post);
+            mIsRegisteredForImsRadioTechChange = true;
+        } catch (ImsException imsException) {
+            mIsRegisteredForImsRadioTechChange = false;
+        }
+    }
+
+    private void registerForImsRadioTechChange() {
+        // register for radio tech change to listen to radio tech handover.
+        if (!mIsRegisteredForImsRadioTechChange) {
+            for (int i = 0; i < mActiveModemCount; i++) {
+                registerForImsRadioTechChange(mContext, i);
+            }
+        }
+    }
+
+    private void evaluateIfDataSwitchIsNeeded(String reason) {
+        if (onEvaluate(REQUESTS_UNCHANGED, reason)) {
+            logDataSwitchEvent(mPreferredDataSubId.get(),
+                    TelephonyEvent.EventState.EVENT_STATE_START,
+                    DataSwitch.Reason.DATA_SWITCH_REASON_IN_CALL);
+            registerDefaultNetworkChangeCallback(mPreferredDataSubId.get(),
+                    DataSwitch.Reason.DATA_SWITCH_REASON_IN_CALL);
+        }
+    }
+
     @VisibleForTesting
     public PhoneSwitcher(int maxActivePhones, Context context, Looper looper) {
         super(looper);
@@ -389,13 +489,14 @@
                 }
                 PhoneFactory.getPhone(i).getDataEnabledSettings().registerForDataEnabledChanged(
                         this, EVENT_DATA_ENABLED_CHANGED, null);
+                registerForImsRadioTechChange(context, i);
             }
             Set<CommandException.Error> ddsFailure = new HashSet<CommandException.Error>();
             mCurrentDdsSwitchFailure.add(ddsFailure);
         }
 
         if (mActiveModemCount > 0) {
-            PhoneFactory.getPhone(0).mCi.registerForAvailable(this, EVENT_RADIO_AVAILABLE, null);
+            PhoneFactory.getPhone(0).mCi.registerForOn(this, EVENT_RADIO_ON, null);
         }
 
         TelephonyRegistryManager telephonyRegistryManager = (TelephonyRegistryManager)
@@ -546,16 +647,31 @@
                 setOpportunisticDataSubscription(subId, needValidation, callback);
                 break;
             }
-            case EVENT_RADIO_AVAILABLE: {
+            case EVENT_RADIO_ON: {
                 updateHalCommandToUse();
-                onEvaluate(REQUESTS_UNCHANGED, "EVENT_RADIO_AVAILABLE");
+                onEvaluate(REQUESTS_UNCHANGED, EVALUATION_REASON_RADIO_ON);
                 break;
             }
+            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;
+                }
+                evaluateIfDataSwitchIsNeeded("EVENT_IMS_RADIO_TECH_CHANGED");
+                break;
+
             case EVENT_PRECISE_CALL_STATE_CHANGED: {
-                log("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()) break;
+                if (!isPhoneInVoiceCallChanged()) {
+                    break;
+                }
 
                 if (!isAnyVoiceCallActiveOnDevice()) {
                     for (int i = 0; i < mActiveModemCount; i++) {
@@ -582,16 +698,12 @@
                         mEmergencyOverride.mPendingOriginatingCall = false;
                     }
                 }
+                evaluateIfDataSwitchIsNeeded("EVENT_PRECISE_CALL_STATE_CHANGED");
+                break;
             }
-            // fall through
+
             case EVENT_DATA_ENABLED_CHANGED:
-                if (onEvaluate(REQUESTS_UNCHANGED, "EVENT_PRECISE_CALL_STATE_CHANGED")) {
-                    logDataSwitchEvent(mPreferredDataSubId.get(),
-                            TelephonyEvent.EventState.EVENT_STATE_START,
-                            DataSwitch.Reason.DATA_SWITCH_REASON_IN_CALL);
-                    registerDefaultNetworkChangeCallback(mPreferredDataSubId.get(),
-                            DataSwitch.Reason.DATA_SWITCH_REASON_IN_CALL);
-                }
+                evaluateIfDataSwitchIsNeeded("EVENT_DATA_ENABLED_CHANGED");
                 break;
             case EVENT_NETWORK_VALIDATION_DONE: {
                 int subId = msg.arg1;
@@ -730,6 +842,7 @@
 
             Set<CommandException.Error> ddsFailure = new HashSet<CommandException.Error>();
             mCurrentDdsSwitchFailure.add(ddsFailure);
+            registerForImsRadioTechChange(mContext, phoneId);
         }
     }
 
@@ -927,7 +1040,10 @@
             notifyPreferredDataSubIdChanged();
         }
 
-        if (diffDetected) {
+        // Always force DDS when radio on. This is to handle the corner cases that modem and android
+        // 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());
             if (mHalCommandToUse == HAL_COMMAND_PREFERRED_DATA) {
                 // With HAL_COMMAND_PREFERRED_DATA, all phones are assumed to allow PS attach.
diff --git a/src/java/com/android/internal/telephony/RIL.java b/src/java/com/android/internal/telephony/RIL.java
index 71a751c..5aa91f7 100644
--- a/src/java/com/android/internal/telephony/RIL.java
+++ b/src/java/com/android/internal/telephony/RIL.java
@@ -23,47 +23,37 @@
 import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
-import android.hardware.radio.V1_0.Carrier;
 import android.hardware.radio.V1_0.CarrierRestrictions;
 import android.hardware.radio.V1_0.CdmaBroadcastSmsConfigInfo;
 import android.hardware.radio.V1_0.CdmaSmsAck;
 import android.hardware.radio.V1_0.CdmaSmsMessage;
 import android.hardware.radio.V1_0.CdmaSmsWriteArgs;
-import android.hardware.radio.V1_0.DataProfileId;
 import android.hardware.radio.V1_0.Dial;
 import android.hardware.radio.V1_0.GsmBroadcastSmsConfigInfo;
 import android.hardware.radio.V1_0.GsmSmsMessage;
-import android.hardware.radio.V1_0.HardwareConfigModem;
 import android.hardware.radio.V1_0.IRadio;
 import android.hardware.radio.V1_0.IccIo;
 import android.hardware.radio.V1_0.ImsSmsMessage;
-import android.hardware.radio.V1_0.LceDataInfo;
-import android.hardware.radio.V1_0.MvnoType;
 import android.hardware.radio.V1_0.NvWriteItem;
 import android.hardware.radio.V1_0.RadioError;
 import android.hardware.radio.V1_0.RadioIndicationType;
 import android.hardware.radio.V1_0.RadioResponseInfo;
 import android.hardware.radio.V1_0.RadioResponseType;
 import android.hardware.radio.V1_0.RadioTechnologyFamily;
-import android.hardware.radio.V1_0.ResetNvType;
 import android.hardware.radio.V1_0.SelectUiccSub;
 import android.hardware.radio.V1_0.SimApdu;
 import android.hardware.radio.V1_0.SmsWriteArgs;
 import android.hardware.radio.V1_0.UusInfo;
 import android.hardware.radio.V1_4.CarrierRestrictionsWithPriority;
 import android.hardware.radio.V1_4.SimLockMultiSimPolicy;
-import android.hardware.radio.V1_5.AccessNetwork;
 import android.hardware.radio.V1_5.IndicationFilter;
-import android.hardware.radio.V1_5.PersoSubstate;
-import android.hardware.radio.V1_5.RadioAccessNetworks;
-import android.hardware.radio.V1_6.OptionalDnn;
-import android.hardware.radio.V1_6.OptionalOsAppId;
-import android.hardware.radio.V1_6.OptionalSliceInfo;
-import android.hardware.radio.V1_6.OptionalTrafficDescriptor;
-import android.hardware.radio.deprecated.V1_0.IOemHook;
-import android.net.InetAddresses;
+import android.hardware.radio.data.IRadioData;
+import android.hardware.radio.messaging.IRadioMessaging;
+import android.hardware.radio.modem.IRadioModem;
+import android.hardware.radio.network.IRadioNetwork;
+import android.hardware.radio.sim.IRadioSim;
+import android.hardware.radio.voice.IRadioVoice;
 import android.net.KeepalivePacketData;
-import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.os.AsyncResult;
 import android.os.Build;
@@ -76,7 +66,6 @@
 import android.os.SystemClock;
 import android.os.WorkSource;
 import android.provider.Settings;
-import android.service.carrier.CarrierIdentifier;
 import android.sysprop.TelephonyProperties;
 import android.telephony.AccessNetworkConstants.AccessNetworkType;
 import android.telephony.CarrierRestrictionRules;
@@ -89,7 +78,6 @@
 import android.telephony.CellSignalStrengthWcdma;
 import android.telephony.ClientRequestStats;
 import android.telephony.ImsiEncryptionInfo;
-import android.telephony.LinkCapacityEstimate;
 import android.telephony.ModemActivityInfo;
 import android.telephony.NeighboringCellInfo;
 import android.telephony.NetworkScanRequest;
@@ -99,28 +87,19 @@
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
 import android.telephony.SignalThresholdInfo;
-import android.telephony.SmsManager;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyHistogram;
 import android.telephony.TelephonyManager;
 import android.telephony.TelephonyManager.PrefNetworkMode;
-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.NetworkSliceInfo;
-import android.telephony.data.Qos;
-import android.telephony.data.QosBearerSession;
 import android.telephony.data.TrafficDescriptor;
 import android.telephony.emergency.EmergencyNumber;
 import android.text.TextUtils;
-import android.util.Log;
 import android.util.SparseArray;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.telephony.cat.ComprehensionTlv;
-import com.android.internal.telephony.cat.ComprehensionTlvTag;
 import com.android.internal.telephony.cdma.CdmaInformationRecords;
 import com.android.internal.telephony.cdma.CdmaSmsBroadcastConfigInfo;
 import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
@@ -133,10 +112,7 @@
 import com.android.internal.telephony.util.TelephonyUtils;
 import com.android.telephony.Rlog;
 
-import java.io.ByteArrayInputStream;
-import java.io.DataInputStream;
 import java.io.FileDescriptor;
-import java.io.IOException;
 import java.io.PrintWriter;
 import java.net.Inet4Address;
 import java.net.Inet6Address;
@@ -207,6 +183,9 @@
     /** @hide */
     public static final HalVersion RADIO_HAL_VERSION_1_6 = new HalVersion(1, 6);
 
+    /** @hide */
+    public static final HalVersion RADIO_HAL_VERSION_2_0 = new HalVersion(2, 0);
+
     // IRadio version
     private HalVersion mRadioVersion = RADIO_HAL_VERSION_UNKNOWN;
 
@@ -262,12 +241,6 @@
      */
     Set<Integer> mDisabledRadioServices = new HashSet();
 
-    /**
-     * A set that records if oem hook service is disabled in hal for
-     * a specific phone id slot to avoid further getService request.
-     */
-    Set<Integer> mDisabledOemHookServices = new HashSet();
-
     /* default work source which will blame phone process */
     private WorkSource mRILDefaultWorkSource;
 
@@ -283,9 +256,24 @@
     RadioResponse mRadioResponse;
     RadioIndication mRadioIndication;
     volatile IRadio mRadioProxy = null;
-    OemHookResponse mOemHookResponse;
-    OemHookIndication mOemHookIndication;
-    volatile IOemHook mOemHookProxy = null;
+    DataResponse mDataResponse;
+    DataIndication mDataIndication;
+    volatile IRadioData mDataProxy = null;
+    MessagingResponse mMessagingResponse;
+    MessagingIndication mMessagingIndication;
+    volatile IRadioMessaging mMessagingProxy = null;
+    ModemResponse mModemResponse;
+    ModemIndication mModemIndication;
+    volatile IRadioModem mModemProxy = null;
+    NetworkResponse mNetworkResponse;
+    NetworkIndication mNetworkIndication;
+    volatile IRadioNetwork mNetworkProxy = null;
+    SimResponse mSimResponse;
+    SimIndication mSimIndication;
+    volatile IRadioSim mSimProxy = null;
+    VoiceResponse mVoiceResponse;
+    VoiceIndication mVoiceIndication;
+    volatile IRadioVoice mVoiceProxy = null;
     final AtomicLong mRadioProxyCookie = new AtomicLong(0);
     final RadioProxyDeathRecipient mRadioProxyDeathRecipient;
     final RilHandler mRilHandler;
@@ -358,7 +346,7 @@
                                 for (int i = 0; i < count; i++) {
                                     rr = mRequestList.valueAt(i);
                                     Rlog.d(RILJ_LOG_TAG, i + ": [" + rr.mSerial + "] "
-                                            + requestToString(rr.mRequest));
+                                            + RILUtils.requestToString(rr.mRequest));
                                 }
                             }
                         }
@@ -448,7 +436,6 @@
 
     private synchronized void resetProxyAndRequestList() {
         mRadioProxy = null;
-        mOemHookProxy = null;
 
         // increment the cookie so that death notification can be ignored
         mRadioProxyCookie.incrementAndGet();
@@ -460,7 +447,6 @@
         clearRequestList(RADIO_NOT_AVAILABLE, false);
 
         getRadioProxy(null);
-        getOemHookProxy(null);
     }
 
     /** Set a radio HAL fallback compatibility override. */
@@ -599,65 +585,11 @@
         if (active) {
             // Try to connect to RIL services and set response functions.
             getRadioProxy(null);
-            getOemHookProxy(null);
         } else {
             resetProxyAndRequestList();
         }
     }
 
-    /** Returns an {@link IOemHook} instance or null if the service is not available. */
-    @VisibleForTesting
-    public synchronized IOemHook getOemHookProxy(Message result) {
-        if (!SubscriptionManager.isValidPhoneId((mPhoneId))) return null;
-        if (!mIsCellularSupported) {
-            if (RILJ_LOGV) riljLog("getOemHookProxy: Not calling getService(): wifi-only");
-            if (result != null) {
-                AsyncResult.forMessage(result, null,
-                        CommandException.fromRilErrno(RADIO_NOT_AVAILABLE));
-                result.sendToTarget();
-            }
-            return null;
-        }
-
-        if (mOemHookProxy != null) {
-            return mOemHookProxy;
-        }
-
-        try {
-            if (mDisabledOemHookServices.contains(mPhoneId)) {
-                riljLoge("getOemHookProxy: mOemHookProxy for " + HIDL_SERVICE_NAME[mPhoneId]
-                        + " is disabled");
-            } else {
-                mOemHookProxy = IOemHook.getService(HIDL_SERVICE_NAME[mPhoneId], true);
-                if (mOemHookProxy != null) {
-                    // not calling linkToDeath() as ril service runs in the same process and death
-                    // notification for that should be sufficient
-                    mOemHookProxy.setResponseFunctions(mOemHookResponse, mOemHookIndication);
-                } else {
-                    mDisabledOemHookServices.add(mPhoneId);
-                    riljLoge("getOemHookProxy: mOemHookProxy for " + HIDL_SERVICE_NAME[mPhoneId]
-                            + " is disabled");
-                }
-            }
-        } catch (NoSuchElementException e) {
-            mOemHookProxy = null;
-            riljLoge("IOemHook service is not on the device HAL: " + e);
-        }  catch (RemoteException e) {
-            mOemHookProxy = null;
-            riljLoge("OemHookProxy getService/setResponseFunctions: " + e);
-        }
-
-        if (mOemHookProxy == null) {
-            if (result != null) {
-                AsyncResult.forMessage(result, null,
-                        CommandException.fromRilErrno(RADIO_NOT_AVAILABLE));
-                result.sendToTarget();
-            }
-        }
-
-        return mOemHookProxy;
-    }
-
     //***** Constructors
 
     @UnsupportedAppUsage
@@ -689,8 +621,18 @@
 
         mRadioResponse = new RadioResponse(this);
         mRadioIndication = new RadioIndication(this);
-        mOemHookResponse = new OemHookResponse(this);
-        mOemHookIndication = new OemHookIndication(this);
+        mDataResponse = new DataResponse(this);
+        mDataIndication = new DataIndication(this);
+        mMessagingResponse = new MessagingResponse(this);
+        mMessagingIndication = new MessagingIndication(this);
+        mModemResponse = new ModemResponse(this);
+        mModemIndication = new ModemIndication(this);
+        mNetworkResponse = new NetworkResponse(this);
+        mNetworkIndication = new NetworkIndication(this);
+        mSimResponse = new SimResponse(this);
+        mSimIndication = new SimIndication(this);
+        mVoiceResponse = new VoiceResponse(this);
+        mVoiceIndication = new VoiceIndication(this);
         mRilHandler = new RilHandler();
         mRadioProxyDeathRecipient = new RadioProxyDeathRecipient();
 
@@ -714,7 +656,6 @@
         // 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)
         getRadioProxy(null);
-        getOemHookProxy(null);
 
         if (RILJ_LOGD) {
             riljLog("Radio HAL version: " + mRadioVersion);
@@ -766,10 +707,6 @@
         resetProxyAndRequestList();
     }
 
-    private static String convertNullToEmptyString(String string) {
-        return string != null ? string : "";
-    }
-
     @Override
     public void getIccCardStatus(Message result) {
         IRadio radioProxy = getRadioProxy(result);
@@ -777,7 +714,9 @@
             RILRequest rr = obtainRequest(RIL_REQUEST_GET_SIM_STATUS, result,
                     mRILDefaultWorkSource);
 
-            if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+            }
 
             try {
                 radioProxy.getIccCardStatus(rr.mSerial);
@@ -818,14 +757,14 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
                         + " aid = " + aid);
             }
 
             try {
                 radioProxy.supplyIccPinForApp(rr.mSerial,
-                        convertNullToEmptyString(pin),
-                        convertNullToEmptyString(aid));
+                        RILUtils.convertNullToEmptyString(pin),
+                        RILUtils.convertNullToEmptyString(aid));
             } catch (RemoteException | RuntimeException e) {
                 handleRadioProxyExceptionForRR(rr, "supplyIccPinForApp", e);
             }
@@ -844,18 +783,16 @@
             RILRequest rr = obtainRequest(RIL_REQUEST_ENTER_SIM_PUK, result,
                     mRILDefaultWorkSource);
 
-            String pukStr = convertNullToEmptyString(puk);
+            String pukStr = RILUtils.convertNullToEmptyString(puk);
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
-                        + " isPukEmpty = " + pukStr.isEmpty()
-                        + " aid = " + aid);
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                        + " isPukEmpty = " + pukStr.isEmpty() + " aid = " + aid);
             }
 
             try {
-                radioProxy.supplyIccPukForApp(rr.mSerial,
-                        pukStr,
-                        convertNullToEmptyString(newPin),
-                        convertNullToEmptyString(aid));
+                radioProxy.supplyIccPukForApp(rr.mSerial, pukStr,
+                        RILUtils.convertNullToEmptyString(newPin),
+                        RILUtils.convertNullToEmptyString(aid));
             } catch (RemoteException | RuntimeException e) {
                 handleRadioProxyExceptionForRR(rr, "supplyIccPukForApp", e);
             }
@@ -875,14 +812,14 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
                         + " aid = " + aid);
             }
 
             try {
                 radioProxy.supplyIccPin2ForApp(rr.mSerial,
-                        convertNullToEmptyString(pin),
-                        convertNullToEmptyString(aid));
+                        RILUtils.convertNullToEmptyString(pin),
+                        RILUtils.convertNullToEmptyString(aid));
             } catch (RemoteException | RuntimeException e) {
                 handleRadioProxyExceptionForRR(rr, "supplyIccPin2ForApp", e);
             }
@@ -902,15 +839,15 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
                         + " aid = " + aid);
             }
 
             try {
                 radioProxy.supplyIccPuk2ForApp(rr.mSerial,
-                        convertNullToEmptyString(puk),
-                        convertNullToEmptyString(newPin2),
-                        convertNullToEmptyString(aid));
+                        RILUtils.convertNullToEmptyString(puk),
+                        RILUtils.convertNullToEmptyString(newPin2),
+                        RILUtils.convertNullToEmptyString(aid));
             } catch (RemoteException | RuntimeException e) {
                 handleRadioProxyExceptionForRR(rr, "supplyIccPuk2ForApp", e);
             }
@@ -930,15 +867,15 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest) + " oldPin = "
-                        + oldPin + " newPin = " + newPin + " aid = " + aid);
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                        + " oldPin = " + oldPin + " newPin = " + newPin + " aid = " + aid);
             }
 
             try {
                 radioProxy.changeIccPinForApp(rr.mSerial,
-                        convertNullToEmptyString(oldPin),
-                        convertNullToEmptyString(newPin),
-                        convertNullToEmptyString(aid));
+                        RILUtils.convertNullToEmptyString(oldPin),
+                        RILUtils.convertNullToEmptyString(newPin),
+                        RILUtils.convertNullToEmptyString(aid));
             } catch (RemoteException | RuntimeException e) {
                 handleRadioProxyExceptionForRR(rr, "changeIccPinForApp", e);
             }
@@ -958,15 +895,15 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest) + " oldPin = "
-                        + oldPin2 + " newPin = " + newPin2 + " aid = " + aid);
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                        + " oldPin = " + oldPin2 + " newPin = " + newPin2 + " aid = " + aid);
             }
 
             try {
                 radioProxy.changeIccPin2ForApp(rr.mSerial,
-                        convertNullToEmptyString(oldPin2),
-                        convertNullToEmptyString(newPin2),
-                        convertNullToEmptyString(aid));
+                        RILUtils.convertNullToEmptyString(oldPin2),
+                        RILUtils.convertNullToEmptyString(newPin2),
+                        RILUtils.convertNullToEmptyString(aid));
             } catch (RemoteException | RuntimeException e) {
                 handleRadioProxyExceptionForRR(rr, "changeIccPin2ForApp", e);
             }
@@ -981,13 +918,13 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest) + " netpin = "
-                        + netpin);
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                        + " netpin = " + netpin);
             }
 
             try {
                 radioProxy.supplyNetworkDepersonalization(rr.mSerial,
-                        convertNullToEmptyString(netpin));
+                        RILUtils.convertNullToEmptyString(netpin));
             } catch (RemoteException | RuntimeException e) {
                 handleRadioProxyExceptionForRR(rr, "supplyNetworkDepersonalization", e);
             }
@@ -1006,13 +943,13 @@
                 RILRequest rr = obtainRequest(RIL_REQUEST_ENTER_SIM_DEPERSONALIZATION, result,
                         mRILDefaultWorkSource);
                 if (RILJ_LOGD) {
-                    riljLog(rr.serialString() + "> " + requestToString(rr.mRequest) + " controlKey = "
-                        + controlKey + " persoType" + persoType);
+                    riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                            + " controlKey = " + controlKey + " persoType" + persoType);
                 }
                 try {
                     radioProxy15.supplySimDepersonalization(rr.mSerial,
-                            convertPersoTypeToHalPersoType(persoType),
-                            convertNullToEmptyString(controlKey));
+                            RILUtils.convertToHalPersoType(persoType),
+                            RILUtils.convertNullToEmptyString(controlKey));
                 } catch (RemoteException | RuntimeException e) {
                     handleRadioProxyExceptionForRR(rr, "supplySimDepersonalization", e);
                 }
@@ -1026,83 +963,6 @@
         }
     }
 
-    private static int convertPersoTypeToHalPersoType(PersoSubState persoType) {
-
-        switch(persoType) {
-
-            case PERSOSUBSTATE_IN_PROGRESS:
-                return PersoSubstate.IN_PROGRESS;
-            case  PERSOSUBSTATE_READY:
-                return PersoSubstate.READY;
-            case PERSOSUBSTATE_SIM_NETWORK:
-                return PersoSubstate.SIM_NETWORK;
-            case PERSOSUBSTATE_SIM_NETWORK_SUBSET:
-                return PersoSubstate.SIM_NETWORK_SUBSET;
-            case PERSOSUBSTATE_SIM_CORPORATE:
-                return PersoSubstate.SIM_CORPORATE;
-            case PERSOSUBSTATE_SIM_SERVICE_PROVIDER:
-                return PersoSubstate.SIM_SERVICE_PROVIDER;
-            case PERSOSUBSTATE_SIM_SIM:
-                return PersoSubstate.SIM_SIM;
-            case PERSOSUBSTATE_SIM_NETWORK_PUK:
-                return PersoSubstate.SIM_NETWORK_PUK;
-            case PERSOSUBSTATE_SIM_NETWORK_SUBSET_PUK:
-                return PersoSubstate.SIM_NETWORK_SUBSET_PUK;
-            case PERSOSUBSTATE_SIM_CORPORATE_PUK:
-                return PersoSubstate.SIM_CORPORATE_PUK;
-            case PERSOSUBSTATE_SIM_SERVICE_PROVIDER_PUK:
-                return PersoSubstate.SIM_SERVICE_PROVIDER_PUK;
-            case PERSOSUBSTATE_SIM_SIM_PUK:
-                return PersoSubstate.SIM_SIM_PUK;
-            case PERSOSUBSTATE_RUIM_NETWORK1:
-                return PersoSubstate.RUIM_NETWORK1;
-            case PERSOSUBSTATE_RUIM_NETWORK2:
-                return PersoSubstate.RUIM_NETWORK2;
-            case PERSOSUBSTATE_RUIM_HRPD:
-                return PersoSubstate.RUIM_HRPD;
-            case PERSOSUBSTATE_RUIM_CORPORATE:
-                return PersoSubstate.RUIM_CORPORATE;
-            case PERSOSUBSTATE_RUIM_SERVICE_PROVIDER:
-                return PersoSubstate.RUIM_SERVICE_PROVIDER;
-            case PERSOSUBSTATE_RUIM_RUIM:
-                return PersoSubstate.RUIM_RUIM;
-            case PERSOSUBSTATE_RUIM_NETWORK1_PUK:
-                return PersoSubstate.RUIM_NETWORK1_PUK;
-            case PERSOSUBSTATE_RUIM_NETWORK2_PUK:
-                return PersoSubstate.RUIM_NETWORK2_PUK;
-            case PERSOSUBSTATE_RUIM_HRPD_PUK:
-                return PersoSubstate.RUIM_HRPD_PUK;
-            case PERSOSUBSTATE_RUIM_CORPORATE_PUK:
-                return PersoSubstate.RUIM_CORPORATE_PUK;
-            case PERSOSUBSTATE_RUIM_SERVICE_PROVIDER_PUK:
-                return PersoSubstate.RUIM_SERVICE_PROVIDER_PUK;
-            case PERSOSUBSTATE_RUIM_RUIM_PUK:
-                return PersoSubstate.RUIM_RUIM_PUK;
-            case PERSOSUBSTATE_SIM_SPN:
-                return PersoSubstate.SIM_SPN;
-            case PERSOSUBSTATE_SIM_SPN_PUK:
-                return PersoSubstate.SIM_SPN_PUK;
-            case PERSOSUBSTATE_SIM_SP_EHPLMN:
-                return PersoSubstate.SIM_SP_EHPLMN;
-            case PERSOSUBSTATE_SIM_SP_EHPLMN_PUK:
-                return PersoSubstate.SIM_SP_EHPLMN_PUK;
-            case PERSOSUBSTATE_SIM_ICCID:
-                return PersoSubstate.SIM_ICCID;
-            case PERSOSUBSTATE_SIM_ICCID_PUK:
-                return PersoSubstate.SIM_ICCID_PUK;
-            case PERSOSUBSTATE_SIM_IMPI:
-                return PersoSubstate.SIM_IMPI;
-            case PERSOSUBSTATE_SIM_IMPI_PUK:
-                return PersoSubstate.SIM_IMPI_PUK;
-            case PERSOSUBSTATE_SIM_NS_SP:
-                return PersoSubstate.SIM_NS_SP;
-            case PERSOSUBSTATE_SIM_NS_SP_PUK:
-                return PersoSubstate.SIM_NS_SP_PUK;
-            default:
-                return PersoSubstate.UNKNOWN;
-        }
-    }
-
     @Override
     public void getCurrentCalls(Message result) {
         IRadio radioProxy = getRadioProxy(result);
@@ -1111,7 +971,7 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
             }
 
             try {
@@ -1156,8 +1016,8 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest) + " enable = "
-                        + enable);
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                        + " enable = " + enable);
             }
 
             try {
@@ -1190,14 +1050,13 @@
                     (android.hardware.radio.V1_3.IRadio) radioProxy;
             if (radioProxy13 != null) {
                 if (RILJ_LOGD) {
-                    riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
-                            + " setSystemSelectionChannels_1.3= "
-                            + specifiers);
+                    riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                            + " setSystemSelectionChannels_1.3= " + specifiers);
                 }
 
                 ArrayList<android.hardware.radio.V1_1.RadioAccessSpecifier> halSpecifiers =
                         specifiers.stream()
-                                .map(this::convertRadioAccessSpecifierToRadioHAL)
+                                .map(RILUtils::convertToHalRadioAccessSpecifier11)
                                 .collect(Collectors.toCollection(ArrayList::new));
 
                 try {
@@ -1216,14 +1075,13 @@
 
             if (radioProxy15 != null) {
                 if (RILJ_LOGD) {
-                    riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
-                            + " setSystemSelectionChannels_1.5= "
-                            + specifiers);
+                    riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                            + " setSystemSelectionChannels_1.5= " + specifiers);
                 }
 
                 ArrayList<android.hardware.radio.V1_5.RadioAccessSpecifier> halSpecifiers =
                         specifiers.stream()
-                                .map(this::convertRadioAccessSpecifierToRadioHAL_1_5)
+                                .map(RILUtils::convertToHalRadioAccessSpecifier15)
                                 .collect(Collectors.toCollection(ArrayList::new));
 
                 try {
@@ -1258,7 +1116,7 @@
 
         if (radioProxy16 != null) {
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
                         + " getSystemSelectionChannels");
             }
 
@@ -1290,7 +1148,7 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
             }
 
             try {
@@ -1317,7 +1175,7 @@
                     mRILDefaultWorkSource);
 
             Dial dialInfo = new Dial();
-            dialInfo.address = convertNullToEmptyString(address);
+            dialInfo.address = RILUtils.convertNullToEmptyString(address);
             dialInfo.clir = clirMode;
             if (uusInfo != null) {
                 UusInfo info = new UusInfo();
@@ -1329,7 +1187,7 @@
 
             if (RILJ_LOGD) {
                 // Do not log function arg for privacy
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
             }
 
             try {
@@ -1347,7 +1205,7 @@
             RILRequest rr = obtainRequest(RIL_REQUEST_EMERGENCY_DIAL, result,
                     mRILDefaultWorkSource);
             Dial dialInfo = new Dial();
-            dialInfo.address = convertNullToEmptyString(address);
+            dialInfo.address = RILUtils.convertNullToEmptyString(address);
             dialInfo.clir = clirMode;
             if (uusInfo != null) {
                 UusInfo info = new UusInfo();
@@ -1359,7 +1217,7 @@
 
             if (RILJ_LOGD) {
                 // Do not log function arg for privacy
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
             }
 
             if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_6)) {
@@ -1413,11 +1271,11 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString()
-                        + ">  " + requestToString(rr.mRequest) + " aid = " + aid);
+                riljLog(rr.serialString() + ">  " + RILUtils.requestToString(rr.mRequest)
+                        + " aid = " + aid);
             }
             try {
-                radioProxy.getImsiForApp(rr.mSerial, convertNullToEmptyString(aid));
+                radioProxy.getImsiForApp(rr.mSerial, RILUtils.convertNullToEmptyString(aid));
             } catch (RemoteException | RuntimeException e) {
                 handleRadioProxyExceptionForRR(rr, "getIMSIForApp", e);
             }
@@ -1432,8 +1290,8 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest) + " gsmIndex = "
-                        + gsmIndex);
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                        + " gsmIndex = " + gsmIndex);
             }
 
             try {
@@ -1452,7 +1310,9 @@
             RILRequest rr = obtainRequest(RIL_REQUEST_HANGUP_WAITING_OR_BACKGROUND, result,
                     mRILDefaultWorkSource);
 
-            if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+            }
 
             try {
                 radioProxy.hangupWaitingOrBackground(rr.mSerial);
@@ -1470,7 +1330,9 @@
             RILRequest rr = obtainRequest(RIL_REQUEST_HANGUP_FOREGROUND_RESUME_BACKGROUND, result,
                     mRILDefaultWorkSource);
 
-            if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+            }
 
             try {
                 radioProxy.hangupForegroundResumeBackground(rr.mSerial);
@@ -1487,7 +1349,9 @@
             RILRequest rr = obtainRequest(RIL_REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE, result,
                     mRILDefaultWorkSource);
 
-            if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+            }
 
             try {
                 radioProxy.switchWaitingOrHoldingAndActive(rr.mSerial);
@@ -1504,7 +1368,9 @@
             RILRequest rr = obtainRequest(RIL_REQUEST_CONFERENCE, result,
                     mRILDefaultWorkSource);
 
-            if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+            }
 
             try {
                 radioProxy.conference(rr.mSerial);
@@ -1521,7 +1387,9 @@
             RILRequest rr = obtainRequest(RIL_REQUEST_UDUB, result,
                     mRILDefaultWorkSource);
 
-            if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+            }
 
             try {
                 radioProxy.rejectCall(rr.mSerial);
@@ -1538,7 +1406,9 @@
             RILRequest rr = obtainRequest(RIL_REQUEST_LAST_CALL_FAIL_CAUSE, result,
                     mRILDefaultWorkSource);
 
-            if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+            }
 
             try {
                 radioProxy.getLastCallFailCause(rr.mSerial);
@@ -1555,7 +1425,9 @@
             RILRequest rr = obtainRequest(RIL_REQUEST_SIGNAL_STRENGTH, result,
                     mRILDefaultWorkSource);
 
-            if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+            }
 
             if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_6)) {
                 android.hardware.radio.V1_6.IRadio radioProxy16 =
@@ -1590,7 +1462,9 @@
             RILRequest rr = obtainRequest(RIL_REQUEST_VOICE_REGISTRATION_STATE, result,
                     mRILDefaultWorkSource);
 
-            if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+            }
 
             HalVersion overrideHalVersion = getCompatVersion(RIL_REQUEST_VOICE_REGISTRATION_STATE);
             if (RILJ_LOGD) {
@@ -1634,7 +1508,9 @@
             RILRequest rr = obtainRequest(RIL_REQUEST_DATA_REGISTRATION_STATE, result,
                     mRILDefaultWorkSource);
 
-            if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+            }
 
             HalVersion overrideHalVersion = getCompatVersion(RIL_REQUEST_DATA_REGISTRATION_STATE);
             if (RILJ_LOGD) {
@@ -1678,7 +1554,9 @@
             RILRequest rr = obtainRequest(RIL_REQUEST_OPERATOR, result,
                     mRILDefaultWorkSource);
 
-            if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+            }
 
             try {
                 radioProxy.getOperator(rr.mSerial);
@@ -1698,7 +1576,7 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
                         + " on = " + on + " forEmergencyCall= " + forEmergencyCall
                         + " preferredForEmergencyCall="  + preferredForEmergencyCall);
             }
@@ -1740,7 +1618,7 @@
 
             if (RILJ_LOGD) {
                 // Do not log function arg for privacy
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
             }
 
             try {
@@ -1751,13 +1629,6 @@
         }
     }
 
-    private GsmSmsMessage constructGsmSendSmsRilRequest(String smscPdu, String pdu) {
-        GsmSmsMessage msg = new GsmSmsMessage();
-        msg.smscPdu = smscPdu == null ? "" : smscPdu;
-        msg.pdu = pdu == null ? "" : pdu;
-        return msg;
-    }
-
     @Override
     public void sendSMS(String smscPdu, String pdu, Message result) {
         IRadio radioProxy = getRadioProxy(result);
@@ -1766,9 +1637,11 @@
                     mRILDefaultWorkSource);
 
             // Do not log function args for privacy
-            if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+            }
 
-            GsmSmsMessage msg = constructGsmSendSmsRilRequest(smscPdu, pdu);
+            GsmSmsMessage msg = RILUtils.convertToHalGsmSmsMessage(smscPdu, pdu);
             if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_6)) {
                 try {
                     android.hardware.radio.V1_6.IRadio radioProxy16 =
@@ -1805,7 +1678,8 @@
         }
         long messageId = ((SMSDispatcher.SmsTracker) result.obj).mMessageId;
         if (RILJ_LOGV) {
-            Rlog.d(RILJ_LOG_TAG, "getOutgoingSmsMessageId messageId: " + messageId);
+            Rlog.d(RILJ_LOG_TAG, "getOutgoingSmsMessageId "
+                    + SmsController.formatCrossStackMessageId(messageId));
         }
         return messageId;
     }
@@ -1818,9 +1692,11 @@
                     mRILDefaultWorkSource);
 
             // Do not log function arg for privacy
-            if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+            }
 
-            GsmSmsMessage msg = constructGsmSendSmsRilRequest(smscPdu, pdu);
+            GsmSmsMessage msg = RILUtils.convertToHalGsmSmsMessage(smscPdu, pdu);
             if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_6)) {
                 try {
                     android.hardware.radio.V1_6.IRadio radioProxy16 =
@@ -1845,210 +1721,6 @@
         }
     }
 
-    /**
-     * Convert MVNO type string into MvnoType defined in types.hal.
-     * @param mvnoType MVNO type
-     * @return MVNO type in integer
-     */
-    private static int convertToHalMvnoType(String mvnoType) {
-        switch (mvnoType) {
-            case "imsi" : return MvnoType.IMSI;
-            case "gid" : return MvnoType.GID;
-            case "spn" : return MvnoType.SPN;
-            default: return MvnoType.NONE;
-        }
-    }
-
-    /**
-     * Convert to DataProfileInfo defined in radio/1.0/types.hal
-     * @param dp Data profile
-     * @return A converted data profile
-     */
-    private static android.hardware.radio.V1_0.DataProfileInfo convertToHalDataProfile10(
-            DataProfile dp) {
-        android.hardware.radio.V1_0.DataProfileInfo dpi =
-                new android.hardware.radio.V1_0.DataProfileInfo();
-
-        dpi.profileId = dp.getProfileId();
-        dpi.apn = dp.getApn();
-        dpi.protocol = ApnSetting.getProtocolStringFromInt(dp.getProtocolType());
-        dpi.roamingProtocol = ApnSetting.getProtocolStringFromInt(dp.getRoamingProtocolType());
-        dpi.authType = dp.getAuthType();
-        dpi.user = dp.getUserName();
-        dpi.password = dp.getPassword();
-        dpi.type = dp.getType();
-        dpi.maxConnsTime = dp.getMaxConnectionsTime();
-        dpi.maxConns = dp.getMaxConnections();
-        dpi.waitTime = dp.getWaitTime();
-        dpi.enabled = dp.isEnabled();
-        dpi.supportedApnTypesBitmap = dp.getSupportedApnTypesBitmask();
-        // Shift by 1 bit due to the discrepancy between
-        // android.hardware.radio.V1_0.RadioAccessFamily and the bitmask version of
-        // ServiceState.RIL_RADIO_TECHNOLOGY_XXXX.
-        dpi.bearerBitmap = ServiceState.convertNetworkTypeBitmaskToBearerBitmask(
-                dp.getBearerBitmask()) << 1;
-        dpi.mtu = dp.getMtuV4();
-        dpi.mvnoType = MvnoType.NONE;
-        dpi.mvnoMatchData = "";
-
-        return dpi;
-    }
-
-    /**
-     * Convert to DataProfileInfo defined in radio/1.4/types.hal
-     * @param dp Data profile
-     * @return A converted data profile
-     */
-    private static android.hardware.radio.V1_4.DataProfileInfo convertToHalDataProfile14(
-            DataProfile dp) {
-        android.hardware.radio.V1_4.DataProfileInfo dpi =
-                new android.hardware.radio.V1_4.DataProfileInfo();
-
-        dpi.apn = dp.getApn();
-        dpi.protocol = dp.getProtocolType();
-        dpi.roamingProtocol = dp.getRoamingProtocolType();
-        dpi.authType = dp.getAuthType();
-        dpi.user = dp.getUserName();
-        dpi.password = dp.getPassword();
-        dpi.type = dp.getType();
-        dpi.maxConnsTime = dp.getMaxConnectionsTime();
-        dpi.maxConns = dp.getMaxConnections();
-        dpi.waitTime = dp.getWaitTime();
-        dpi.enabled = dp.isEnabled();
-        dpi.supportedApnTypesBitmap = dp.getSupportedApnTypesBitmask();
-        // Shift by 1 bit due to the discrepancy between
-        // android.hardware.radio.V1_0.RadioAccessFamily and the bitmask version of
-        // ServiceState.RIL_RADIO_TECHNOLOGY_XXXX.
-        dpi.bearerBitmap = ServiceState.convertNetworkTypeBitmaskToBearerBitmask(
-                dp.getBearerBitmask()) << 1;
-        dpi.mtu = dp.getMtuV4();
-        dpi.persistent = dp.isPersistent();
-        dpi.preferred = dp.isPreferred();
-
-        // profile id is only meaningful when it's persistent on the modem.
-        dpi.profileId = (dpi.persistent) ? dp.getProfileId() : DataProfileId.INVALID;
-
-        return dpi;
-    }
-
-    private static OptionalSliceInfo convertToHalSliceInfo(@Nullable NetworkSliceInfo sliceInfo) {
-        OptionalSliceInfo optionalSliceInfo = new OptionalSliceInfo();
-        if (sliceInfo == null) {
-            return optionalSliceInfo;
-        }
-
-        android.hardware.radio.V1_6.SliceInfo si = new android.hardware.radio.V1_6.SliceInfo();
-        si.sst = (byte) sliceInfo.getSliceServiceType();
-        si.mappedHplmnSst = (byte) sliceInfo.getMappedHplmnSliceServiceType();
-        si.sliceDifferentiator = sliceInfo.getSliceDifferentiator();
-        si.mappedHplmnSD = sliceInfo.getMappedHplmnSliceDifferentiator();
-        optionalSliceInfo.value(si);
-        return optionalSliceInfo;
-    }
-
-    private static OptionalTrafficDescriptor convertToHalTrafficDescriptor(
-            @Nullable TrafficDescriptor trafficDescriptor) {
-        OptionalTrafficDescriptor optionalTrafficDescriptor = new OptionalTrafficDescriptor();
-        if (trafficDescriptor == null) {
-            return optionalTrafficDescriptor;
-        }
-
-        android.hardware.radio.V1_6.TrafficDescriptor td =
-                new android.hardware.radio.V1_6.TrafficDescriptor();
-
-        OptionalDnn optionalDnn = new OptionalDnn();
-        if (trafficDescriptor.getDataNetworkName() != null) {
-            optionalDnn.value(trafficDescriptor.getDataNetworkName());
-        }
-        td.dnn = optionalDnn;
-
-        OptionalOsAppId optionalOsAppId = new OptionalOsAppId();
-        if (trafficDescriptor.getOsAppId() != null) {
-            android.hardware.radio.V1_6.OsAppId osAppId = new android.hardware.radio.V1_6.OsAppId();
-            osAppId.osAppId = primitiveArrayToArrayList(trafficDescriptor.getOsAppId().getBytes());
-            optionalOsAppId.value(osAppId);
-        }
-        td.osAppId = optionalOsAppId;
-
-        optionalTrafficDescriptor.value(td);
-        return optionalTrafficDescriptor;
-    }
-
-    private static ArrayList<android.hardware.radio.V1_5.LinkAddress> convertToHalLinkProperties15(
-            LinkProperties linkProperties) {
-        ArrayList<android.hardware.radio.V1_5.LinkAddress> addresses15 = new ArrayList<>();
-        if (linkProperties != null) {
-            for (LinkAddress la : linkProperties.getAllLinkAddresses()) {
-                android.hardware.radio.V1_5.LinkAddress linkAddress =
-                        new android.hardware.radio.V1_5.LinkAddress();
-                linkAddress.address = la.getAddress().getHostAddress();
-                linkAddress.properties = la.getFlags();
-                linkAddress.deprecationTime = la.getDeprecationTime();
-                linkAddress.expirationTime = la.getExpirationTime();
-                addresses15.add(linkAddress);
-            }
-        }
-        return addresses15;
-    }
-
-    /**
-     * Convert to DataProfileInfo defined in radio/1.5/types.hal
-     * @param dp Data profile
-     * @return A converted data profile
-     */
-    private static android.hardware.radio.V1_5.DataProfileInfo convertToHalDataProfile15(
-            DataProfile dp) {
-        android.hardware.radio.V1_5.DataProfileInfo dpi =
-                new android.hardware.radio.V1_5.DataProfileInfo();
-
-        dpi.apn = dp.getApn();
-        dpi.protocol = dp.getProtocolType();
-        dpi.roamingProtocol = dp.getRoamingProtocolType();
-        dpi.authType = dp.getAuthType();
-        dpi.user = dp.getUserName();
-        dpi.password = dp.getPassword();
-        dpi.type = dp.getType();
-        dpi.maxConnsTime = dp.getMaxConnectionsTime();
-        dpi.maxConns = dp.getMaxConnections();
-        dpi.waitTime = dp.getWaitTime();
-        dpi.enabled = dp.isEnabled();
-        dpi.supportedApnTypesBitmap = dp.getSupportedApnTypesBitmask();
-        // Shift by 1 bit due to the discrepancy between
-        // android.hardware.radio.V1_0.RadioAccessFamily and the bitmask version of
-        // ServiceState.RIL_RADIO_TECHNOLOGY_XXXX.
-        dpi.bearerBitmap = ServiceState.convertNetworkTypeBitmaskToBearerBitmask(
-            dp.getBearerBitmask()) << 1;
-        dpi.mtuV4 = dp.getMtuV4();
-        dpi.mtuV6 = dp.getMtuV6();
-        dpi.persistent = dp.isPersistent();
-        dpi.preferred = dp.isPreferred();
-
-        // profile id is only meaningful when it's persistent on the modem.
-        dpi.profileId = (dpi.persistent) ? dp.getProfileId() : DataProfileId.INVALID;
-
-        return dpi;
-    }
-
-    /**
-     * Convert NV reset type into ResetNvType defined in types.hal.
-     * @param resetType NV reset type.
-     * @return Converted reset type in integer or -1 if param is invalid.
-     */
-    private static int convertToHalResetNvType(int resetType) {
-        /**
-         * resetType values
-         * 1 - reload all NV items
-         * 2 - erase NV reset (SCRTN)
-         * 3 - factory reset (RTN)
-         */
-        switch (resetType) {
-            case 1: return ResetNvType.RELOAD;
-            case 2: return ResetNvType.ERASE;
-            case 3: return ResetNvType.FACTORY_RESET;
-        }
-        return -1;
-    }
-
     @Override
     public void setupDataCall(int accessNetworkType, DataProfile dataProfile, boolean isRoaming,
             boolean allowRoaming, int reason, LinkProperties linkProperties, int pduSessionId,
@@ -2080,17 +1752,19 @@
 
                     // Convert to HAL data profile
                     android.hardware.radio.V1_5.DataProfileInfo dpi =
-                            convertToHalDataProfile15(dataProfile);
+                            RILUtils.convertToHalDataProfile15(dataProfile);
 
-                    OptionalSliceInfo si = convertToHalSliceInfo(sliceInfo);
+                    android.hardware.radio.V1_6.OptionalSliceInfo si =
+                            RILUtils.convertToHalSliceInfo(sliceInfo);
 
                     ArrayList<android.hardware.radio.V1_5.LinkAddress> addresses15 =
-                            convertToHalLinkProperties15(linkProperties);
+                            RILUtils.convertToHalLinkProperties15(linkProperties);
 
-                    OptionalTrafficDescriptor td = convertToHalTrafficDescriptor(trafficDescriptor);
+                    android.hardware.radio.V1_6.OptionalTrafficDescriptor td =
+                            RILUtils.convertToHalTrafficDescriptor(trafficDescriptor);
 
                     if (RILJ_LOGD) {
-                        riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+                        riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
                                 + ",accessNetworkType="
                                 + AccessNetworkType.toString(accessNetworkType) + ",isRoaming="
                                 + isRoaming + ",allowRoaming=" + allowRoaming + "," + dataProfile
@@ -2109,13 +1783,13 @@
 
                     // Convert to HAL data profile
                     android.hardware.radio.V1_5.DataProfileInfo dpi =
-                            convertToHalDataProfile15(dataProfile);
+                            RILUtils.convertToHalDataProfile15(dataProfile);
 
                     ArrayList<android.hardware.radio.V1_5.LinkAddress> addresses15 =
-                            convertToHalLinkProperties15(linkProperties);
+                            RILUtils.convertToHalLinkProperties15(linkProperties);
 
                     if (RILJ_LOGD) {
-                        riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+                        riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
                                 + ",accessNetworkType="
                                 + AccessNetworkType.toString(accessNetworkType) + ",isRoaming="
                                 + isRoaming + ",allowRoaming=" + allowRoaming + "," + dataProfile
@@ -2131,10 +1805,10 @@
 
                     // Convert to HAL data profile
                     android.hardware.radio.V1_4.DataProfileInfo dpi =
-                            convertToHalDataProfile14(dataProfile);
+                            RILUtils.convertToHalDataProfile14(dataProfile);
 
                     if (RILJ_LOGD) {
-                        riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+                        riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
                                 + ",accessNetworkType="
                                 + AccessNetworkType.toString(accessNetworkType) + ",isRoaming="
                                 + isRoaming + ",allowRoaming=" + allowRoaming + "," + dataProfile
@@ -2150,10 +1824,10 @@
 
                     // Convert to HAL data profile
                     android.hardware.radio.V1_0.DataProfileInfo dpi =
-                            convertToHalDataProfile10(dataProfile);
+                            RILUtils.convertToHalDataProfile10(dataProfile);
 
                     if (RILJ_LOGD) {
-                        riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+                        riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
                                 + ",accessNetworkType="
                                 + AccessNetworkType.toString(accessNetworkType) + ",isRoaming="
                                 + isRoaming + ",allowRoaming=" + allowRoaming + ","
@@ -2168,7 +1842,7 @@
 
                     // Convert to HAL data profile
                     android.hardware.radio.V1_0.DataProfileInfo dpi =
-                            convertToHalDataProfile10(dataProfile);
+                            RILUtils.convertToHalDataProfile10(dataProfile);
 
                     // Getting data RAT here is just a workaround to support the older 1.0
                     // vendor RIL. The new data service interface passes access network type
@@ -2184,7 +1858,7 @@
                         }
                     }
                     if (RILJ_LOGD) {
-                        riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+                        riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
                                 + ",dataRat=" + dataRat + ",isRoaming=" + isRoaming
                                 + ",allowRoaming=" + allowRoaming + "," + dataProfile);
                     }
@@ -2214,27 +1888,26 @@
 
             if (RILJ_LOGD) {
                 if (TelephonyUtils.IS_DEBUGGABLE) {
-                    riljLog(rr.serialString() + "> iccIO: "
-                            + requestToString(rr.mRequest) + " command = 0x"
-                            + Integer.toHexString(command) + " fileId = 0x"
-                            + Integer.toHexString(fileId) + " path = " + path + " p1 = "
-                            + p1 + " p2 = " + p2 + " p3 = " + " data = " + data
-                            + " aid = " + aid);
+                    riljLog(rr.serialString() + "> iccIO: " + RILUtils.requestToString(rr.mRequest)
+                            + " command = 0x" + Integer.toHexString(command) + " fileId = 0x"
+                            + Integer.toHexString(fileId) + " path = " + path + " p1 = " + p1
+                            + " p2 = " + p2 + " p3 = " + " data = " + data + " aid = " + aid);
                 } else {
-                    riljLog(rr.serialString() + "> iccIO: " + requestToString(rr.mRequest));
+                    riljLog(rr.serialString() + "> iccIO: "
+                            + RILUtils.requestToString(rr.mRequest));
                 }
             }
 
             IccIo iccIo = new IccIo();
             iccIo.command = command;
             iccIo.fileId = fileId;
-            iccIo.path = convertNullToEmptyString(path);
+            iccIo.path = RILUtils.convertNullToEmptyString(path);
             iccIo.p1 = p1;
             iccIo.p2 = p2;
             iccIo.p3 = p3;
-            iccIo.data = convertNullToEmptyString(data);
-            iccIo.pin2 = convertNullToEmptyString(pin2);
-            iccIo.aid = convertNullToEmptyString(aid);
+            iccIo.data = RILUtils.convertNullToEmptyString(data);
+            iccIo.pin2 = RILUtils.convertNullToEmptyString(pin2);
+            iccIo.aid = RILUtils.convertNullToEmptyString(aid);
 
             try {
                 radioProxy.iccIOForApp(rr.mSerial, iccIo);
@@ -2254,12 +1927,12 @@
             if (RILJ_LOGD) {
                 String logUssd = "*******";
                 if (RILJ_LOGV) logUssd = ussd;
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
                         + " ussd = " + logUssd);
             }
 
             try {
-                radioProxy.sendUssd(rr.mSerial, convertNullToEmptyString(ussd));
+                radioProxy.sendUssd(rr.mSerial, RILUtils.convertNullToEmptyString(ussd));
             } catch (RemoteException | RuntimeException e) {
                 handleRadioProxyExceptionForRR(rr, "sendUSSD", e);
             }
@@ -2274,8 +1947,7 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString()
-                        + "> " + requestToString(rr.mRequest));
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
             }
 
             try {
@@ -2293,7 +1965,9 @@
             RILRequest rr = obtainRequest(RIL_REQUEST_GET_CLIR, result,
                     mRILDefaultWorkSource);
 
-            if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+            }
 
             try {
                 radioProxy.getClir(rr.mSerial);
@@ -2310,7 +1984,7 @@
             RILRequest rr = obtainRequest(RIL_REQUEST_SET_CLIR, result, mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
                         + " clirMode = " + clirMode);
             }
 
@@ -2331,7 +2005,7 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
                         + " cfreason = " + cfReason + " serviceClass = " + serviceClass);
             }
 
@@ -2340,7 +2014,7 @@
             cfInfo.reason = cfReason;
             cfInfo.serviceClass = serviceClass;
             cfInfo.toa = PhoneNumberUtils.toaFromString(number);
-            cfInfo.number = convertNullToEmptyString(number);
+            cfInfo.number = RILUtils.convertNullToEmptyString(number);
             cfInfo.timeSeconds = 0;
 
             try {
@@ -2360,7 +2034,7 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
                         + " action = " + action + " cfReason = " + cfReason + " serviceClass = "
                         + serviceClass + " timeSeconds = " + timeSeconds);
             }
@@ -2371,7 +2045,7 @@
             cfInfo.reason = cfReason;
             cfInfo.serviceClass = serviceClass;
             cfInfo.toa = PhoneNumberUtils.toaFromString(number);
-            cfInfo.number = convertNullToEmptyString(number);
+            cfInfo.number = RILUtils.convertNullToEmptyString(number);
             cfInfo.timeSeconds = timeSeconds;
 
             try {
@@ -2391,7 +2065,7 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
                         + " serviceClass = " + serviceClass);
             }
 
@@ -2411,7 +2085,7 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
                         + " enable = " + enable + " serviceClass = " + serviceClass);
             }
 
@@ -2431,7 +2105,7 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
                         + " success = " + success + " cause = " + cause);
             }
 
@@ -2451,7 +2125,7 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
             }
 
             try {
@@ -2471,8 +2145,8 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> "
-                        + requestToString(rr.mRequest) + " cid = " + cid + " reason = " + reason);
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                        + " cid = " + cid + " reason = " + reason);
             }
 
             try {
@@ -2507,17 +2181,17 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
                         + " facility = " + facility + " serviceClass = " + serviceClass
                         + " appId = " + appId);
             }
 
             try {
                 radioProxy.getFacilityLockForApp(rr.mSerial,
-                        convertNullToEmptyString(facility),
-                        convertNullToEmptyString(password),
+                        RILUtils.convertNullToEmptyString(facility),
+                        RILUtils.convertNullToEmptyString(password),
                         serviceClass,
-                        convertNullToEmptyString(appId));
+                        RILUtils.convertNullToEmptyString(appId));
             } catch (RemoteException | RuntimeException e) {
                 handleRadioProxyExceptionForRR(rr, "getFacilityLockForApp", e);
             }
@@ -2539,18 +2213,18 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
                         + " facility = " + facility + " lockstate = " + lockState
                         + " serviceClass = " + serviceClass + " appId = " + appId);
             }
 
             try {
                 radioProxy.setFacilityLockForApp(rr.mSerial,
-                        convertNullToEmptyString(facility),
+                        RILUtils.convertNullToEmptyString(facility),
                         lockState,
-                        convertNullToEmptyString(password),
+                        RILUtils.convertNullToEmptyString(password),
                         serviceClass,
-                        convertNullToEmptyString(appId));
+                        RILUtils.convertNullToEmptyString(appId));
             } catch (RemoteException | RuntimeException e) {
                 handleRadioProxyExceptionForRR(rr, "setFacilityLockForApp", e);
             }
@@ -2567,15 +2241,15 @@
 
             // Do not log all function args for privacy
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
                         + "facility = " + facility);
             }
 
             try {
                 radioProxy.setBarringPassword(rr.mSerial,
-                        convertNullToEmptyString(facility),
-                        convertNullToEmptyString(oldPwd),
-                        convertNullToEmptyString(newPwd));
+                        RILUtils.convertNullToEmptyString(facility),
+                        RILUtils.convertNullToEmptyString(oldPwd),
+                        RILUtils.convertNullToEmptyString(newPwd));
             } catch (RemoteException | RuntimeException e) {
                 handleRadioProxyExceptionForRR(rr, "changeBarringPassword", e);
             }
@@ -2589,7 +2263,9 @@
             RILRequest rr = obtainRequest(RIL_REQUEST_QUERY_NETWORK_SELECTION_MODE, result,
                     mRILDefaultWorkSource);
 
-            if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+            }
 
             try {
                 radioProxy.getNetworkSelectionMode(rr.mSerial);
@@ -2606,7 +2282,9 @@
             RILRequest rr = obtainRequest(RIL_REQUEST_SET_NETWORK_SELECTION_AUTOMATIC, result,
                     mRILDefaultWorkSource);
 
-            if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+            }
 
             try {
                 radioProxy.setNetworkSelectionModeAutomatic(rr.mSerial);
@@ -2623,24 +2301,23 @@
             RILRequest rr = obtainRequest(RIL_REQUEST_SET_NETWORK_SELECTION_MANUAL, result,
                     mRILDefaultWorkSource);
             try {
-                int halRan = convertAntToRan(ran);
+                int halRan = RILUtils.convertToHalRadioAccessNetworks(ran);
                 if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_5)) {
                     android.hardware.radio.V1_5.IRadio radioProxy15 =
                             (android.hardware.radio.V1_5.IRadio) radioProxy;
                     if (RILJ_LOGD) {
-                        riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
-                                + " operatorNumeric = " + operatorNumeric
-                                + ", ran = " + halRan);
+                        riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                                + " operatorNumeric = " + operatorNumeric + ", ran = " + halRan);
                     }
                     radioProxy15.setNetworkSelectionModeManual_1_5(rr.mSerial,
-                            convertNullToEmptyString(operatorNumeric), halRan);
+                            RILUtils.convertNullToEmptyString(operatorNumeric), halRan);
                 } else {
                     if (RILJ_LOGD) {
-                        riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+                        riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
                                 + " operatorNumeric = " + operatorNumeric);
                     }
                     radioProxy.setNetworkSelectionModeManual(rr.mSerial,
-                            convertNullToEmptyString(operatorNumeric));
+                            RILUtils.convertNullToEmptyString(operatorNumeric));
                 }
             } catch (RemoteException | RuntimeException e) {
                 handleRadioProxyExceptionForRR(rr, "setNetworkSelectionModeManual", e);
@@ -2655,7 +2332,9 @@
             RILRequest rr = obtainRequest(RIL_REQUEST_QUERY_AVAILABLE_NETWORKS, result,
                     mRILDefaultWorkSource);
 
-            if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+            }
 
             try {
                 radioProxy.getAvailableNetworks(rr.mSerial);
@@ -2665,84 +2344,6 @@
         }
     }
 
-    private android.hardware.radio.V1_1.RadioAccessSpecifier convertRadioAccessSpecifierToRadioHAL(
-            RadioAccessSpecifier ras) {
-        android.hardware.radio.V1_1.RadioAccessSpecifier rasInHalFormat =
-                new android.hardware.radio.V1_1.RadioAccessSpecifier();
-        rasInHalFormat.radioAccessNetwork = ras.getRadioAccessNetwork();
-        ArrayList<Integer> bands = new ArrayList<>();
-        if (ras.getBands() != null) {
-            for (int band : ras.getBands()) {
-                bands.add(band);
-            }
-        }
-        switch (ras.getRadioAccessNetwork()) {
-            case AccessNetworkType.GERAN:
-                rasInHalFormat.geranBands = bands;
-                break;
-            case AccessNetworkType.UTRAN:
-                rasInHalFormat.utranBands = bands;
-                break;
-            case AccessNetworkType.EUTRAN:
-                rasInHalFormat.eutranBands = bands;
-                break;
-            default:
-                Log.wtf(RILJ_LOG_TAG, "radioAccessNetwork " + ras.getRadioAccessNetwork()
-                        + " not supported on IRadio < 1.5!");
-                return null;
-        }
-
-        if (ras.getChannels() != null) {
-            for (int channel : ras.getChannels()) {
-                rasInHalFormat.channels.add(channel);
-            }
-        }
-
-        return rasInHalFormat;
-    }
-
-    private android.hardware.radio.V1_5.RadioAccessSpecifier
-            convertRadioAccessSpecifierToRadioHAL_1_5(RadioAccessSpecifier ras) {
-        android.hardware.radio.V1_5.RadioAccessSpecifier rasInHalFormat =
-                new android.hardware.radio.V1_5.RadioAccessSpecifier();
-        android.hardware.radio.V1_5.RadioAccessSpecifier.Bands bandsInHalFormat =
-                new android.hardware.radio.V1_5.RadioAccessSpecifier.Bands();
-        rasInHalFormat.radioAccessNetwork = convertAntToRan(ras.getRadioAccessNetwork());
-        ArrayList<Integer> bands = new ArrayList<>();
-        if (ras.getBands() != null) {
-            for (int band : ras.getBands()) {
-                bands.add(band);
-            }
-        }
-        switch (ras.getRadioAccessNetwork()) {
-            case AccessNetworkType.GERAN:
-                bandsInHalFormat.geranBands(bands);
-                break;
-            case AccessNetworkType.UTRAN:
-                bandsInHalFormat.utranBands(bands);
-                break;
-            case AccessNetworkType.EUTRAN:
-                bandsInHalFormat.eutranBands(bands);
-                break;
-            case AccessNetworkType.NGRAN:
-                bandsInHalFormat.ngranBands(bands);
-                break;
-            default:
-                Log.wtf(RILJ_LOG_TAG, "radioAccessNetwork " + ras.getRadioAccessNetwork()
-                        + " not supported on IRadio 1.5!");
-                return null;
-        }
-        rasInHalFormat.bands = bandsInHalFormat;
-
-        if (ras.getChannels() != null) {
-            for (int channel : ras.getChannels()) {
-                rasInHalFormat.channels.add(channel);
-            }
-        }
-
-        return rasInHalFormat;
-    }
-
     /**
      * Radio HAL fallback compatibility feature (b/151106728) assumes that the input parameter
      * networkScanRequest is immutable (read-only) here. Once the caller invokes the method, the
@@ -2772,7 +2373,7 @@
 
                 for (RadioAccessSpecifier ras : nsr.getSpecifiers()) {
                     android.hardware.radio.V1_5.RadioAccessSpecifier rasInHalFormat =
-                            convertRadioAccessSpecifierToRadioHAL_1_5(ras);
+                            RILUtils.convertToHalRadioAccessSpecifier15(ras);
                     if (rasInHalFormat == null) {
                         AsyncResult.forMessage(result, null,
                                 CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
@@ -2787,7 +2388,7 @@
                         mRILDefaultWorkSource, nsr);
 
                 if (RILJ_LOGD) {
-                    riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+                    riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
                 }
 
                 try {
@@ -2809,7 +2410,7 @@
                 for (RadioAccessSpecifier ras : nsr.getSpecifiers()) {
 
                     android.hardware.radio.V1_1.RadioAccessSpecifier rasInHalFormat =
-                            convertRadioAccessSpecifierToRadioHAL(ras);
+                            RILUtils.convertToHalRadioAccessSpecifier11(ras);
                     if (rasInHalFormat == null) {
                         AsyncResult.forMessage(result, null,
                                 CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
@@ -2825,7 +2426,7 @@
                         mRILDefaultWorkSource);
 
                 if (RILJ_LOGD) {
-                    riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+                    riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
                 }
 
                 try {
@@ -2851,7 +2452,7 @@
                 request.interval = nsr.getSearchPeriodicity();
                 for (RadioAccessSpecifier ras : nsr.getSpecifiers()) {
                     android.hardware.radio.V1_1.RadioAccessSpecifier rasInHalFormat =
-                            convertRadioAccessSpecifierToRadioHAL(ras);
+                            RILUtils.convertToHalRadioAccessSpecifier11(ras);
                     if (rasInHalFormat == null) {
                         AsyncResult.forMessage(result, null,
                                 CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
@@ -2866,7 +2467,7 @@
                         mRILDefaultWorkSource);
 
                 if (RILJ_LOGD) {
-                    riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+                    riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
                 }
 
                 try {
@@ -2894,7 +2495,7 @@
                         mRILDefaultWorkSource);
 
                 if (RILJ_LOGD) {
-                    riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+                    riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
                 }
 
                 try {
@@ -2918,7 +2519,9 @@
                     mRILDefaultWorkSource);
 
             // Do not log function arg for privacy
-            if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+            }
 
             try {
                 radioProxy.startDtmf(rr.mSerial, c + "");
@@ -2935,7 +2538,9 @@
             RILRequest rr = obtainRequest(RIL_REQUEST_DTMF_STOP, result,
                     mRILDefaultWorkSource);
 
-            if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+            }
 
             try {
                 radioProxy.stopDtmf(rr.mSerial);
@@ -2953,7 +2558,7 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
                         + " gsmIndex = " + gsmIndex);
             }
 
@@ -2972,7 +2577,9 @@
             RILRequest rr = obtainRequest(RIL_REQUEST_BASEBAND_VERSION, result,
                     mRILDefaultWorkSource);
 
-            if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+            }
 
             try {
                 radioProxy.getBasebandVersion(rr.mSerial);
@@ -2990,7 +2597,7 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
                         + " enableMute = " + enableMute);
             }
 
@@ -3009,7 +2616,9 @@
             RILRequest rr = obtainRequest(RIL_REQUEST_GET_MUTE, result,
                     mRILDefaultWorkSource);
 
-            if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+            }
 
             try {
                 radioProxy.getMute(rr.mSerial);
@@ -3026,7 +2635,9 @@
             RILRequest rr = obtainRequest(RIL_REQUEST_QUERY_CLIP, result,
                     mRILDefaultWorkSource);
 
-            if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+            }
 
             try {
                 radioProxy.getClip(rr.mSerial);
@@ -3052,7 +2663,9 @@
             RILRequest rr = obtainRequest(RIL_REQUEST_DATA_CALL_LIST, result,
                     mRILDefaultWorkSource);
 
-            if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+            }
 
             try {
                 if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_6)) {
@@ -3068,58 +2681,15 @@
         }
     }
 
+    // TODO(b/171260715) Remove when HAL definition is removed
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     @Override
     public void invokeOemRilRequestRaw(byte[] data, Message response) {
-        IOemHook oemHookProxy = getOemHookProxy(response);
-        if (oemHookProxy != null) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_OEM_HOOK_RAW, response,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
-                        + "[" + IccUtils.bytesToHexString(data) + "]");
-            }
-
-            try {
-                oemHookProxy.sendRequestRaw(rr.mSerial, primitiveArrayToArrayList(data));
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(rr, "invokeOemRilRequestRaw", e);
-            }
-        } else {
-            // OEM Hook service is disabled for P and later devices.
-            // Deprecated OEM Hook APIs will perform no-op before being removed.
-            if (RILJ_LOGD) riljLog("Radio Oem Hook Service is disabled for P and later devices. ");
-        }
     }
 
+    // TODO(b/171260715) Remove when HAL definition is removed
     @Override
     public void invokeOemRilRequestStrings(String[] strings, Message result) {
-        IOemHook oemHookProxy = getOemHookProxy(result);
-        if (oemHookProxy != null) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_OEM_HOOK_STRINGS, result,
-                    mRILDefaultWorkSource);
-
-            String logStr = "";
-            for (int i = 0; i < strings.length; i++) {
-                logStr = logStr + strings[i] + " ";
-            }
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest) + " strings = "
-                        + logStr);
-            }
-
-            try {
-                oemHookProxy.sendRequestStrings(rr.mSerial,
-                        new ArrayList<String>(Arrays.asList(strings)));
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(rr, "invokeOemRilRequestStrings", e);
-            }
-        } else {
-            // OEM Hook service is disabled for P and later devices.
-            // Deprecated OEM Hook APIs will perform no-op before being removed.
-            if (RILJ_LOGD) riljLog("Radio Oem Hook Service is disabled for P and later devices. ");
-        }
     }
 
     @Override
@@ -3130,8 +2700,8 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest) + " enable = "
-                        + enable);
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                        + " enable = " + enable);
             }
 
             try {
@@ -3144,7 +2714,7 @@
 
     @Override
     public void writeSmsToSim(int status, String smsc, String pdu, Message result) {
-        status = translateStatus(status);
+        status = RILUtils.convertToHalSmsWriteArgsStatus(status);
         IRadio radioProxy = getRadioProxy(result);
         if (radioProxy != null) {
             RILRequest rr = obtainRequest(RIL_REQUEST_WRITE_SMS_TO_SIM, result,
@@ -3152,14 +2722,14 @@
 
             if (RILJ_LOGV) {
                 riljLog(rr.serialString() + "> "
-                        + requestToString(rr.mRequest)
+                        + RILUtils.requestToString(rr.mRequest)
                         + " " + status);
             }
 
             SmsWriteArgs args = new SmsWriteArgs();
             args.status = status;
-            args.smsc = convertNullToEmptyString(smsc);
-            args.pdu = convertNullToEmptyString(pdu);
+            args.smsc = RILUtils.convertNullToEmptyString(smsc);
+            args.pdu = RILUtils.convertNullToEmptyString(pdu);
 
             try {
                 radioProxy.writeSmsToSim(rr.mSerial, args);
@@ -3178,7 +2748,7 @@
 
             if (RILJ_LOGV) {
                 riljLog(rr.serialString() + "> "
-                        + requestToString(rr.mRequest) + " index = " + index);
+                        + RILUtils.requestToString(rr.mRequest) + " index = " + index);
             }
 
             try {
@@ -3197,7 +2767,7 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
                         + " bandMode = " + bandMode);
             }
 
@@ -3216,7 +2786,9 @@
             RILRequest rr = obtainRequest(RIL_REQUEST_QUERY_AVAILABLE_BAND_MODE, result,
                     mRILDefaultWorkSource);
 
-            if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+            }
 
             try {
                 radioProxy.getAvailableBandModes(rr.mSerial);
@@ -3234,12 +2806,12 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest) + " contents = "
-                        + contents);
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                        + " contents = " + contents);
             }
 
             try {
-                radioProxy.sendEnvelope(rr.mSerial, convertNullToEmptyString(contents));
+                radioProxy.sendEnvelope(rr.mSerial, RILUtils.convertNullToEmptyString(contents));
             } catch (RemoteException | RuntimeException e) {
                 handleRadioProxyExceptionForRR(rr, "sendEnvelope", e);
             }
@@ -3254,47 +2826,20 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest) + " contents = "
-                        + (TelephonyUtils.IS_DEBUGGABLE
-                            ? contents : censoredTerminalResponse(contents)));
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                        + " contents = " + (TelephonyUtils.IS_DEBUGGABLE
+                        ? contents : RILUtils.convertToCensoredTerminalResponse(contents)));
             }
 
             try {
                 radioProxy.sendTerminalResponseToSim(rr.mSerial,
-                        convertNullToEmptyString(contents));
+                        RILUtils.convertNullToEmptyString(contents));
             } catch (RemoteException | RuntimeException e) {
                 handleRadioProxyExceptionForRR(rr, "sendTerminalResponse", e);
             }
         }
     }
 
-    private String censoredTerminalResponse(String terminalResponse) {
-        try {
-            byte[] bytes = IccUtils.hexStringToBytes(terminalResponse);
-            if (bytes != null) {
-                List<ComprehensionTlv> ctlvs = ComprehensionTlv.decodeMany(bytes, 0);
-                int from = 0;
-                for (ComprehensionTlv ctlv : ctlvs) {
-                    // Find text strings which might be personal information input by user,
-                    // then replace it with "********".
-                    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(), "********");
-                    }
-                    // The text string tag and the length field should also be hidden.
-                    from = ctlv.getValueIndex() + ctlv.getLength();
-                }
-            }
-        } catch (Exception e) {
-            Rlog.e(RILJ_LOG_TAG, "Could not censor the terminal response: " + e);
-            terminalResponse = null;
-        }
-
-        return terminalResponse;
-    }
-
     @Override
     public void sendEnvelopeWithStatus(String contents, Message result) {
         IRadio radioProxy = getRadioProxy(result);
@@ -3303,12 +2848,13 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest) + " contents = "
-                        + contents);
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                        + " contents = " + contents);
             }
 
             try {
-                radioProxy.sendEnvelopeWithStatus(rr.mSerial, convertNullToEmptyString(contents));
+                radioProxy.sendEnvelopeWithStatus(rr.mSerial,
+                        RILUtils.convertNullToEmptyString(contents));
             } catch (RemoteException | RuntimeException e) {
                 handleRadioProxyExceptionForRR(rr, "sendEnvelopeWithStatus", e);
             }
@@ -3322,7 +2868,9 @@
             RILRequest rr = obtainRequest(RIL_REQUEST_EXPLICIT_CALL_TRANSFER, result,
                     mRILDefaultWorkSource);
 
-            if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+            }
 
             try {
                 radioProxy.explicitCallTransfer(rr.mSerial);
@@ -3340,7 +2888,7 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
                         + " networkType = " + networkType);
             }
             mAllowedNetworkTypesBitmask = RadioAccessFamily.getRafFromNetworkType(networkType);
@@ -3350,8 +2898,8 @@
                 android.hardware.radio.V1_4.IRadio radioProxy14 =
                         (android.hardware.radio.V1_4.IRadio) radioProxy;
                 try {
-                    radioProxy14.setPreferredNetworkTypeBitmap(
-                            rr.mSerial, convertToHalRadioAccessFamily(mAllowedNetworkTypesBitmask));
+                    radioProxy14.setPreferredNetworkTypeBitmap(rr.mSerial,
+                            RILUtils.convertToHalRadioAccessFamily(mAllowedNetworkTypesBitmask));
                 } catch (RemoteException | RuntimeException e) {
                     handleRadioProxyExceptionForRR(rr, "setPreferredNetworkTypeBitmap", e);
                 }
@@ -3365,154 +2913,15 @@
         }
     }
 
-    /**
-     * convert RAF from {@link android.hardware.radio.V1_0.RadioAccessFamily} to
-     * {@link TelephonyManager.NetworkTypeBitMask}, the bitmask represented by
-     * {@link android.telephony.Annotation.NetworkType}.
-     *
-     * @param raf {@link android.hardware.radio.V1_0.RadioAccessFamily}
-     * @return {@link TelephonyManager.NetworkTypeBitMask}
-     */
-    @TelephonyManager.NetworkTypeBitMask
-    public static int convertToNetworkTypeBitMask(int raf) {
-        int networkTypeRaf = 0;
-
-        if ((raf & android.hardware.radio.V1_0.RadioAccessFamily.GSM) != 0) {
-            networkTypeRaf |= TelephonyManager.NETWORK_TYPE_BITMASK_GSM;
-        }
-        if ((raf & android.hardware.radio.V1_0.RadioAccessFamily.GPRS) != 0) {
-            networkTypeRaf |= TelephonyManager.NETWORK_TYPE_BITMASK_GPRS;
-        }
-        if ((raf & android.hardware.radio.V1_0.RadioAccessFamily.EDGE) != 0) {
-            networkTypeRaf |= TelephonyManager.NETWORK_TYPE_BITMASK_EDGE;
-        }
-        // convert both IS95A/IS95B to CDMA as network mode doesn't support CDMA
-        if ((raf & android.hardware.radio.V1_0.RadioAccessFamily.IS95A) != 0) {
-            networkTypeRaf |= TelephonyManager.NETWORK_TYPE_BITMASK_CDMA;
-        }
-        if ((raf & android.hardware.radio.V1_0.RadioAccessFamily.IS95B) != 0) {
-            networkTypeRaf |= TelephonyManager.NETWORK_TYPE_BITMASK_CDMA;
-        }
-        if ((raf & android.hardware.radio.V1_0.RadioAccessFamily.ONE_X_RTT) != 0) {
-            networkTypeRaf |= TelephonyManager.NETWORK_TYPE_BITMASK_1xRTT;
-        }
-        if ((raf & android.hardware.radio.V1_0.RadioAccessFamily.EVDO_0) != 0) {
-            networkTypeRaf |= TelephonyManager.NETWORK_TYPE_BITMASK_EVDO_0;
-        }
-        if ((raf & android.hardware.radio.V1_0.RadioAccessFamily.EVDO_A) != 0) {
-            networkTypeRaf |= TelephonyManager.NETWORK_TYPE_BITMASK_EVDO_A;
-        }
-        if ((raf & android.hardware.radio.V1_0.RadioAccessFamily.EVDO_B) != 0) {
-            networkTypeRaf |= TelephonyManager.NETWORK_TYPE_BITMASK_EVDO_B;
-        }
-        if ((raf & android.hardware.radio.V1_0.RadioAccessFamily.EHRPD) != 0) {
-            networkTypeRaf |= TelephonyManager.NETWORK_TYPE_BITMASK_EHRPD;
-        }
-        if ((raf & android.hardware.radio.V1_0.RadioAccessFamily.HSUPA) != 0) {
-            networkTypeRaf |= TelephonyManager.NETWORK_TYPE_BITMASK_HSUPA;
-        }
-        if ((raf & android.hardware.radio.V1_0.RadioAccessFamily.HSDPA) != 0) {
-            networkTypeRaf |= TelephonyManager.NETWORK_TYPE_BITMASK_HSDPA;
-        }
-        if ((raf & android.hardware.radio.V1_0.RadioAccessFamily.HSPA) != 0) {
-            networkTypeRaf |= TelephonyManager.NETWORK_TYPE_BITMASK_HSPA;
-        }
-        if ((raf & android.hardware.radio.V1_0.RadioAccessFamily.HSPAP) != 0) {
-            networkTypeRaf |= TelephonyManager.NETWORK_TYPE_BITMASK_HSPAP;
-        }
-        if ((raf & android.hardware.radio.V1_0.RadioAccessFamily.UMTS) != 0) {
-            networkTypeRaf |= TelephonyManager.NETWORK_TYPE_BITMASK_UMTS;
-        }
-        if ((raf & android.hardware.radio.V1_0.RadioAccessFamily.TD_SCDMA) != 0) {
-            networkTypeRaf |= TelephonyManager.NETWORK_TYPE_BITMASK_TD_SCDMA;
-        }
-        if ((raf & android.hardware.radio.V1_0.RadioAccessFamily.LTE) != 0) {
-            networkTypeRaf |= TelephonyManager.NETWORK_TYPE_BITMASK_LTE;
-        }
-        if ((raf & android.hardware.radio.V1_0.RadioAccessFamily.LTE_CA) != 0) {
-            networkTypeRaf |= TelephonyManager.NETWORK_TYPE_BITMASK_LTE_CA;
-        }
-        if ((raf & android.hardware.radio.V1_4.RadioAccessFamily.NR) != 0) {
-            networkTypeRaf |= TelephonyManager.NETWORK_TYPE_BITMASK_NR;
-        }
-        // TODO: need hal definition
-        if ((raf & (1 << ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN)) != 0) {
-            networkTypeRaf |= TelephonyManager.NETWORK_TYPE_BITMASK_IWLAN;
-        }
-        return (networkTypeRaf == 0) ? TelephonyManager.NETWORK_TYPE_UNKNOWN : networkTypeRaf;
-    }
-
-    // convert to android.hardware.radio.V1_0.RadioAccessFamily
-    private static int convertToHalRadioAccessFamily(
-            @TelephonyManager.NetworkTypeBitMask int networkTypeBitmask) {
-        int raf = 0;
-
-        if ((networkTypeBitmask & TelephonyManager.NETWORK_TYPE_BITMASK_GSM) != 0) {
-            raf |= android.hardware.radio.V1_0.RadioAccessFamily.GSM;
-        }
-        if ((networkTypeBitmask & TelephonyManager.NETWORK_TYPE_BITMASK_GPRS) != 0) {
-            raf |= android.hardware.radio.V1_0.RadioAccessFamily.GPRS;
-        }
-        if ((networkTypeBitmask & TelephonyManager.NETWORK_TYPE_BITMASK_EDGE) != 0) {
-            raf |= android.hardware.radio.V1_0.RadioAccessFamily.EDGE;
-        }
-        // convert CDMA to IS95A, consistent with ServiceState.networkTypeToRilRadioTechnology
-        if ((networkTypeBitmask & TelephonyManager.NETWORK_TYPE_BITMASK_CDMA) != 0) {
-            raf |= android.hardware.radio.V1_0.RadioAccessFamily.IS95A;
-        }
-        if ((networkTypeBitmask & TelephonyManager.NETWORK_TYPE_BITMASK_1xRTT) != 0) {
-            raf |= android.hardware.radio.V1_0.RadioAccessFamily.ONE_X_RTT;
-        }
-        if ((networkTypeBitmask & TelephonyManager.NETWORK_TYPE_BITMASK_EVDO_0) != 0) {
-            raf |= android.hardware.radio.V1_0.RadioAccessFamily.EVDO_0;
-        }
-        if ((networkTypeBitmask & TelephonyManager.NETWORK_TYPE_BITMASK_EVDO_A) != 0) {
-            raf |= android.hardware.radio.V1_0.RadioAccessFamily.EVDO_A;
-        }
-        if ((networkTypeBitmask & TelephonyManager.NETWORK_TYPE_BITMASK_EVDO_B) != 0) {
-            raf |= android.hardware.radio.V1_0.RadioAccessFamily.EVDO_B;
-        }
-        if ((networkTypeBitmask & TelephonyManager.NETWORK_TYPE_BITMASK_EHRPD) != 0) {
-            raf |= android.hardware.radio.V1_0.RadioAccessFamily.EHRPD;
-        }
-        if ((networkTypeBitmask & TelephonyManager.NETWORK_TYPE_BITMASK_HSUPA) != 0) {
-            raf |= android.hardware.radio.V1_0.RadioAccessFamily.HSUPA;
-        }
-        if ((networkTypeBitmask & TelephonyManager.NETWORK_TYPE_BITMASK_HSDPA) != 0) {
-            raf |= android.hardware.radio.V1_0.RadioAccessFamily.HSDPA;
-        }
-        if ((networkTypeBitmask & TelephonyManager.NETWORK_TYPE_BITMASK_HSPA) != 0) {
-            raf |= android.hardware.radio.V1_0.RadioAccessFamily.HSPA;
-        }
-        if ((networkTypeBitmask & TelephonyManager.NETWORK_TYPE_BITMASK_HSPAP) != 0) {
-            raf |= android.hardware.radio.V1_0.RadioAccessFamily.HSPAP;
-        }
-        if ((networkTypeBitmask & TelephonyManager.NETWORK_TYPE_BITMASK_UMTS) != 0) {
-            raf |= android.hardware.radio.V1_0.RadioAccessFamily.UMTS;
-        }
-        if ((networkTypeBitmask & TelephonyManager.NETWORK_TYPE_BITMASK_TD_SCDMA) != 0) {
-            raf |= android.hardware.radio.V1_0.RadioAccessFamily.TD_SCDMA;
-        }
-        if ((networkTypeBitmask & TelephonyManager.NETWORK_TYPE_BITMASK_LTE) != 0) {
-            raf |= android.hardware.radio.V1_0.RadioAccessFamily.LTE;
-        }
-        if ((networkTypeBitmask & TelephonyManager.NETWORK_TYPE_BITMASK_LTE_CA) != 0) {
-            raf |= android.hardware.radio.V1_0.RadioAccessFamily.LTE_CA;
-        }
-        if ((networkTypeBitmask & TelephonyManager.NETWORK_TYPE_BITMASK_NR) != 0) {
-            raf |= android.hardware.radio.V1_4.RadioAccessFamily.NR;
-        }
-        // TODO: need hal definition for IWLAN
-        return (raf == 0) ? android.hardware.radio.V1_4.RadioAccessFamily.UNKNOWN : raf;
-    }
-
     @Override
     public void getPreferredNetworkType(Message result) {
         IRadio radioProxy = getRadioProxy(result);
         if (radioProxy != null) {
             RILRequest rr = obtainRequest(RIL_REQUEST_GET_PREFERRED_NETWORK_TYPE, result,
                     mRILDefaultWorkSource);
-            if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+            }
             if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_4)) {
                 android.hardware.radio.V1_4.IRadio radioProxy14 =
                         (android.hardware.radio.V1_4.IRadio) radioProxy;
@@ -3549,12 +2958,12 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
             }
             mAllowedNetworkTypesBitmask = networkTypeBitmask;
             try {
                 radioProxy16.setAllowedNetworkTypesBitmap(rr.mSerial,
-                        convertToHalRadioAccessFamily(mAllowedNetworkTypesBitmask));
+                        RILUtils.convertToHalRadioAccessFamily(mAllowedNetworkTypesBitmask));
             } catch (RemoteException | RuntimeException e) {
                 handleRadioProxyExceptionForRR(rr, "setAllowedNetworkTypeBitmask", e);
             }
@@ -3577,7 +2986,7 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
             }
 
             try {
@@ -3597,7 +3006,7 @@
 
             if (RILJ_LOGD) {
                 riljLog(rr.serialString() + "> "
-                        + requestToString(rr.mRequest) + " enable = " + enable);
+                        + RILUtils.requestToString(rr.mRequest) + " enable = " + enable);
             }
 
             try {
@@ -3631,8 +3040,7 @@
                     workSource == null ? mRILDefaultWorkSource : workSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> "
-                        + requestToString(rr.mRequest));
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
             }
 
             try {
@@ -3674,8 +3082,8 @@
                     workSource == null ? mRILDefaultWorkSource : workSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> "
-                        + requestToString(rr.mRequest) + " enable = " + nrDualConnectivityState);
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                        + " enable = " + nrDualConnectivityState);
             }
 
             try {
@@ -3695,7 +3103,7 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
                         + " cdmaSubscription = " + cdmaSubscription);
             }
 
@@ -3714,7 +3122,9 @@
             RILRequest rr = obtainRequest(RIL_REQUEST_CDMA_QUERY_ROAMING_PREFERENCE, result,
                     mRILDefaultWorkSource);
 
-            if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+            }
 
             try {
                 radioProxy.getCdmaRoamingPreference(rr.mSerial);
@@ -3732,7 +3142,7 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
                         + " cdmaRoamingType = " + cdmaRoamingType);
             }
 
@@ -3751,7 +3161,9 @@
             RILRequest rr = obtainRequest(RIL_REQUEST_QUERY_TTY_MODE, result,
                     mRILDefaultWorkSource);
 
-            if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+            }
 
             try {
                 radioProxy.getTTYMode(rr.mSerial);
@@ -3769,7 +3181,7 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
                         + " ttyMode = " + ttyMode);
             }
 
@@ -3789,7 +3201,7 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
                         + " enable = " + enable);
             }
 
@@ -3808,7 +3220,9 @@
             RILRequest rr = obtainRequest(RIL_REQUEST_CDMA_QUERY_PREFERRED_VOICE_PRIVACY_MODE,
                     result, mRILDefaultWorkSource);
 
-            if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+            }
 
             try {
                 radioProxy.getPreferredVoicePrivacy(rr.mSerial);
@@ -3826,12 +3240,13 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
                         + " featureCode = " + featureCode);
             }
 
             try {
-                radioProxy.sendCDMAFeatureCode(rr.mSerial, convertNullToEmptyString(featureCode));
+                radioProxy.sendCDMAFeatureCode(rr.mSerial,
+                        RILUtils.convertNullToEmptyString(featureCode));
             } catch (RemoteException | RuntimeException e) {
                 handleRadioProxyExceptionForRR(rr, "sendCDMAFeatureCode", e);
             }
@@ -3846,56 +3261,19 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
                         + " dtmfString = " + dtmfString + " on = " + on + " off = " + off);
             }
 
             try {
-                radioProxy.sendBurstDtmf(rr.mSerial, convertNullToEmptyString(dtmfString), on, off);
+                radioProxy.sendBurstDtmf(rr.mSerial, RILUtils.convertNullToEmptyString(dtmfString),
+                        on, off);
             } catch (RemoteException | RuntimeException e) {
                 handleRadioProxyExceptionForRR(rr, "sendBurstDtmf", e);
             }
         }
     }
 
-    private void constructCdmaSendSmsRilRequest(CdmaSmsMessage msg, byte[] pdu) {
-        int addrNbrOfDigits;
-        int subaddrNbrOfDigits;
-        int bearerDataLength;
-        ByteArrayInputStream bais = new ByteArrayInputStream(pdu);
-        DataInputStream dis = new DataInputStream(bais);
-
-        try {
-            msg.teleserviceId = dis.readInt(); // teleServiceId
-            msg.isServicePresent = (byte) dis.readInt() == 1 ? true : false; // servicePresent
-            msg.serviceCategory = dis.readInt(); // serviceCategory
-            msg.address.digitMode = dis.read();  // address digit mode
-            msg.address.numberMode = dis.read(); // address number mode
-            msg.address.numberType = dis.read(); // address number type
-            msg.address.numberPlan = dis.read(); // address number plan
-            addrNbrOfDigits = (byte) dis.read();
-            for (int i = 0; i < addrNbrOfDigits; i++) {
-                msg.address.digits.add(dis.readByte()); // address_orig_bytes[i]
-            }
-            msg.subAddress.subaddressType = dis.read(); //subaddressType
-            msg.subAddress.odd = (byte) dis.read() == 1 ? true : false; //subaddr odd
-            subaddrNbrOfDigits = (byte) dis.read();
-            for (int i = 0; i < subaddrNbrOfDigits; i++) {
-                msg.subAddress.digits.add(dis.readByte()); //subaddr_orig_bytes[i]
-            }
-
-            bearerDataLength = dis.read();
-            for (int i = 0; i < bearerDataLength; i++) {
-                msg.bearerData.add(dis.readByte()); //bearerData[i]
-            }
-        } catch (IOException ex) {
-            if (RILJ_LOGD) {
-                riljLog("sendSmsCdma: conversion from input stream to object failed: "
-                        + ex);
-            }
-        }
-    }
-
     @Override
     public void sendCdmaSMSExpectMore(byte[] pdu, Message result) {
         IRadio radioProxy = getRadioProxy(result);
@@ -3904,10 +3282,11 @@
                     mRILDefaultWorkSource);
 
             // Do not log function arg for privacy
-            if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+            }
 
-            CdmaSmsMessage msg = new CdmaSmsMessage();
-            constructCdmaSendSmsRilRequest(msg, pdu);
+            CdmaSmsMessage msg = RILUtils.convertToHalCdmaSmsMessage(pdu);
             if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_6)) {
                 android.hardware.radio.V1_6.IRadio radioProxy16 =
                         (android.hardware.radio.V1_6.IRadio) radioProxy;
@@ -3944,10 +3323,11 @@
                     mRILDefaultWorkSource);
 
             // Do not log function arg for privacy
-            if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+            }
 
-            CdmaSmsMessage msg = new CdmaSmsMessage();
-            constructCdmaSendSmsRilRequest(msg, pdu);
+            CdmaSmsMessage msg = RILUtils.convertToHalCdmaSmsMessage(pdu);
             if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_6)) {
                 try {
                     android.hardware.radio.V1_6.IRadio radioProxy16 =
@@ -3980,7 +3360,7 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
                         + " success = " + success + " cause = " + cause);
             }
 
@@ -4003,7 +3383,9 @@
             RILRequest rr = obtainRequest(RIL_REQUEST_GSM_GET_BROADCAST_CONFIG, result,
                     mRILDefaultWorkSource);
 
-            if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+            }
 
             try {
                 radioProxy.getGsmBroadcastConfig(rr.mSerial);
@@ -4021,7 +3403,7 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
                         + " with " + config.length + " configs : ");
                 for (int i = 0; i < config.length; i++) {
                     riljLog(config[i].toString());
@@ -4059,7 +3441,7 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
                         + " activate = " + activate);
             }
 
@@ -4078,7 +3460,9 @@
             RILRequest rr = obtainRequest(RIL_REQUEST_CDMA_GET_BROADCAST_CONFIG, result,
                     mRILDefaultWorkSource);
 
-            if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+            }
 
             try {
                 radioProxy.getCdmaBroadcastConfig(rr.mSerial);
@@ -4110,7 +3494,7 @@
             }
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
                         + " with " + halConfigs.size() + " configs : ");
                 for (CdmaBroadcastSmsConfigInfo config : halConfigs) {
                     riljLog(config.toString());
@@ -4133,7 +3517,7 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
                         + " activate = " + activate);
             }
 
@@ -4152,7 +3536,9 @@
             RILRequest rr = obtainRequest(RIL_REQUEST_CDMA_SUBSCRIPTION, result,
                     mRILDefaultWorkSource);
 
-            if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+            }
 
             try {
                 radioProxy.getCDMASubscription(rr.mSerial);
@@ -4164,7 +3550,7 @@
 
     @Override
     public void writeSmsToRuim(int status, byte[] pdu, Message result) {
-        status = translateStatus(status);
+        status = RILUtils.convertToHalSmsWriteArgsStatus(status);
         IRadio radioProxy = getRadioProxy(result);
         if (radioProxy != null) {
             RILRequest rr = obtainRequest(RIL_REQUEST_CDMA_WRITE_SMS_TO_RUIM, result,
@@ -4172,13 +3558,13 @@
 
             if (RILJ_LOGV) {
                 riljLog(rr.serialString() + "> "
-                        + requestToString(rr.mRequest)
+                        + RILUtils.requestToString(rr.mRequest)
                         + " status = " + status);
             }
 
             CdmaSmsWriteArgs args = new CdmaSmsWriteArgs();
             args.status = status;
-            constructCdmaSendSmsRilRequest(args.message, pdu);
+            args.message = RILUtils.convertToHalCdmaSmsMessage(pdu);
 
             try {
                 radioProxy.writeSmsToRuim(rr.mSerial, args);
@@ -4197,7 +3583,7 @@
 
             if (RILJ_LOGV) {
                 riljLog(rr.serialString() + "> "
-                        + requestToString(rr.mRequest)
+                        + RILUtils.requestToString(rr.mRequest)
                         + " index = " + index);
             }
 
@@ -4216,7 +3602,9 @@
             RILRequest rr = obtainRequest(RIL_REQUEST_DEVICE_IDENTITY, result,
                     mRILDefaultWorkSource);
 
-            if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+            }
 
             try {
                 radioProxy.getDeviceIdentity(rr.mSerial);
@@ -4233,7 +3621,9 @@
             RILRequest rr = obtainRequest(RIL_REQUEST_EXIT_EMERGENCY_CALLBACK_MODE, result,
                     mRILDefaultWorkSource);
 
-            if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+            }
 
             try {
                 radioProxy.exitEmergencyCallbackMode(rr.mSerial);
@@ -4250,7 +3640,9 @@
             RILRequest rr = obtainRequest(RIL_REQUEST_GET_SMSC_ADDRESS, result,
                     mRILDefaultWorkSource);
 
-            if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+            }
 
             try {
                 radioProxy.getSmscAddress(rr.mSerial);
@@ -4268,12 +3660,12 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
                         + " address = " + address);
             }
 
             try {
-                radioProxy.setSmscAddress(rr.mSerial, convertNullToEmptyString(address));
+                radioProxy.setSmscAddress(rr.mSerial, RILUtils.convertNullToEmptyString(address));
             } catch (RemoteException | RuntimeException e) {
                 handleRadioProxyExceptionForRR(rr, "setSmscAddress", e);
             }
@@ -4289,7 +3681,7 @@
 
             if (RILJ_LOGD) {
                 riljLog(rr.serialString() + "> "
-                        + requestToString(rr.mRequest) + " available = " + available);
+                        + RILUtils.requestToString(rr.mRequest) + " available = " + available);
             }
 
             try {
@@ -4307,7 +3699,9 @@
             RILRequest rr = obtainRequest(RIL_REQUEST_REPORT_STK_SERVICE_IS_RUNNING, result,
                     mRILDefaultWorkSource);
 
-            if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+            }
 
             try {
                 radioProxy.reportStkServiceIsRunning(rr.mSerial);
@@ -4324,7 +3718,9 @@
             RILRequest rr = obtainRequest(RIL_REQUEST_CDMA_GET_SUBSCRIPTION_SOURCE, result,
                     mRILDefaultWorkSource);
 
-            if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+            }
 
             try {
                 radioProxy.getCdmaSubscriptionSource(rr.mSerial);
@@ -4342,13 +3738,13 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
                         + " success = " + success);
             }
 
             try {
                 radioProxy.acknowledgeIncomingGsmSmsWithPdu(rr.mSerial, success,
-                        convertNullToEmptyString(ackPdu));
+                        RILUtils.convertNullToEmptyString(ackPdu));
             } catch (RemoteException | RuntimeException e) {
                 handleRadioProxyExceptionForRR(rr, "acknowledgeIncomingGsmSmsWithPdu", e);
             }
@@ -4362,7 +3758,9 @@
             RILRequest rr = obtainRequest(RIL_REQUEST_VOICE_RADIO_TECH, result,
                     mRILDefaultWorkSource);
 
-            if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+            }
 
             try {
                 radioProxy.getVoiceRadioTechnology(rr.mSerial);
@@ -4381,7 +3779,7 @@
                     workSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
             }
 
             try {
@@ -4408,7 +3806,7 @@
                     workSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
                         + " rateInMillis = " + rateInMillis);
             }
 
@@ -4428,7 +3826,8 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest) + dataProfile);
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                        + dataProfile);
             }
 
             try {
@@ -4437,18 +3836,18 @@
                     android.hardware.radio.V1_5.IRadio radioProxy15 =
                             (android.hardware.radio.V1_5.IRadio) radioProxy;
                     radioProxy15.setInitialAttachApn_1_5(rr.mSerial,
-                            convertToHalDataProfile15(dataProfile));
+                            RILUtils.convertToHalDataProfile15(dataProfile));
                 } else if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_4)) {
                     // v1.4
                     android.hardware.radio.V1_4.IRadio radioProxy14 =
                             (android.hardware.radio.V1_4.IRadio) radioProxy;
                     radioProxy14.setInitialAttachApn_1_4(rr.mSerial,
-                            convertToHalDataProfile14(dataProfile));
+                            RILUtils.convertToHalDataProfile14(dataProfile));
                 } else {
                     // v1.3, v1.2, v1.1, and v1.0
                     radioProxy.setInitialAttachApn(rr.mSerial,
-                            convertToHalDataProfile10(dataProfile), dataProfile.isPersistent(),
-                            isRoaming);
+                            RILUtils.convertToHalDataProfile10(dataProfile),
+                            dataProfile.isPersistent(), isRoaming);
                 }
             } catch (RemoteException | RuntimeException e) {
                 handleRadioProxyExceptionForRR(rr, "setInitialAttachApn", e);
@@ -4464,7 +3863,7 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
             }
 
             try {
@@ -4484,14 +3883,16 @@
                     mRILDefaultWorkSource);
 
             // Do not log function args for privacy
-            if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+            }
 
             ImsSmsMessage msg = new ImsSmsMessage();
             msg.tech = RadioTechnologyFamily.THREE_GPP;
             msg.retry = (byte) retry >= 1 ? true : false;
             msg.messageRef = messageRef;
 
-            GsmSmsMessage gsmMsg = constructGsmSendSmsRilRequest(smscPdu, pdu);
+            GsmSmsMessage gsmMsg = RILUtils.convertToHalGsmSmsMessage(smscPdu, pdu);
             msg.gsmMessage.add(gsmMsg);
             try {
                 radioProxy.sendImsSms(rr.mSerial, msg);
@@ -4511,16 +3912,15 @@
                     mRILDefaultWorkSource);
 
             // Do not log function args for privacy
-            if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+            }
 
             ImsSmsMessage msg = new ImsSmsMessage();
             msg.tech = RadioTechnologyFamily.THREE_GPP2;
             msg.retry = (byte) retry >= 1 ? true : false;
             msg.messageRef = messageRef;
-
-            CdmaSmsMessage cdmaMsg = new CdmaSmsMessage();
-            constructCdmaSendSmsRilRequest(cdmaMsg, pdu);
-            msg.cdmaMessage.add(cdmaMsg);
+            msg.cdmaMessage.add(RILUtils.convertToHalCdmaSmsMessage(pdu));
 
             try {
                 radioProxy.sendImsSms(rr.mSerial, msg);
@@ -4532,19 +3932,6 @@
         }
     }
 
-    private SimApdu createSimApdu(int channel, int cla, int instruction, int p1, int p2, int p3,
-                                  String data) {
-        SimApdu msg = new SimApdu();
-        msg.sessionId = channel;
-        msg.cla = cla;
-        msg.instruction = instruction;
-        msg.p1 = p1;
-        msg.p2 = p2;
-        msg.p3 = p3;
-        msg.data = convertNullToEmptyString(data);
-        return msg;
-    }
-
     @Override
     public void iccTransmitApduBasicChannel(int cla, int instruction, int p1, int p2,
                                             int p3, String data, Message result) {
@@ -4555,16 +3942,16 @@
 
             if (RILJ_LOGD) {
                 if (TelephonyUtils.IS_DEBUGGABLE) {
-                    riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+                    riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
                             + String.format(" cla = 0x%02X ins = 0x%02X", cla, instruction)
                             + String.format(" p1 = 0x%02X p2 = 0x%02X p3 = 0x%02X", p1, p2, p3)
                             + " data = " + data);
                 } else {
-                    riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+                    riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
                 }
             }
 
-            SimApdu msg = createSimApdu(0, cla, instruction, p1, p2, p3, data);
+            SimApdu msg = RILUtils.convertToHalSimApdu(0, cla, instruction, p1, p2, p3, data);
             try {
                 radioProxy.iccTransmitApduBasicChannel(rr.mSerial, msg);
             } catch (RemoteException | RuntimeException e) {
@@ -4582,15 +3969,16 @@
 
             if (RILJ_LOGD) {
                 if (TelephonyUtils.IS_DEBUGGABLE) {
-                    riljLog(rr.serialString() + "> " + requestToString(rr.mRequest) + " aid = " + aid
-                            + " p2 = " + p2);
+                    riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                            + " aid = " + aid + " p2 = " + p2);
                 } else {
-                    riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+                    riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
                 }
             }
 
             try {
-                radioProxy.iccOpenLogicalChannel(rr.mSerial, convertNullToEmptyString(aid), p2);
+                radioProxy.iccOpenLogicalChannel(rr.mSerial, RILUtils.convertNullToEmptyString(aid),
+                        p2);
             } catch (RemoteException | RuntimeException e) {
                 handleRadioProxyExceptionForRR(rr, "iccOpenLogicalChannel", e);
             }
@@ -4605,8 +3993,8 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest) + " channel = "
-                        + channel);
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                        + " channel = " + channel);
             }
 
             try {
@@ -4633,17 +4021,17 @@
 
             if (RILJ_LOGD) {
                 if (TelephonyUtils.IS_DEBUGGABLE) {
-                    riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+                    riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
                             + String.format(" channel = %d", channel)
                             + String.format(" cla = 0x%02X ins = 0x%02X", cla, instruction)
                             + String.format(" p1 = 0x%02X p2 = 0x%02X p3 = 0x%02X", p1, p2, p3)
                             + " data = " + data);
                 } else {
-                    riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+                    riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
                 }
             }
 
-            SimApdu msg = createSimApdu(channel, cla, instruction, p1, p2, p3, data);
+            SimApdu msg = RILUtils.convertToHalSimApdu(channel, cla, instruction, p1, p2, p3, data);
 
             try {
                 radioProxy.iccTransmitApduLogicalChannel(rr.mSerial, msg);
@@ -4662,7 +4050,7 @@
                     workSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
                         + " itemId = " + itemID);
             }
 
@@ -4683,13 +4071,13 @@
                     workSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
                         + " itemId = " + itemId + " itemValue = " + itemValue);
             }
 
             NvWriteItem item = new NvWriteItem();
             item.itemId = itemId;
-            item.value = convertNullToEmptyString(itemValue);
+            item.value = RILUtils.convertNullToEmptyString(itemValue);
 
             try {
                 radioProxy.nvWriteItem(rr.mSerial, item);
@@ -4707,7 +4095,7 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
                         + " PreferredRoamingList = 0x"
                         + IccUtils.bytesToHexString(preferredRoamingList));
             }
@@ -4733,12 +4121,12 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
                         + " resetType = " + resetType);
             }
 
             try {
-                radioProxy.nvResetConfig(rr.mSerial, convertToHalResetNvType(resetType));
+                radioProxy.nvResetConfig(rr.mSerial, RILUtils.convertToHalResetNvType(resetType));
             } catch (RemoteException | RuntimeException e) {
                 handleRadioProxyExceptionForRR(rr, "nvResetConfig", e);
             }
@@ -4754,7 +4142,7 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
                         + " slot = " + slotId + " appIndex = " + appIndex
                         + " subId = " + subId + " subStatus = " + subStatus);
             }
@@ -4781,7 +4169,7 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
                         + " allowed = " + allowed);
             }
 
@@ -4801,7 +4189,9 @@
                     mRILDefaultWorkSource);
 
             // Do not log function args for privacy
-            if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+            }
 
             try {
                 radioProxy.getHardwareConfig(rr.mSerial);
@@ -4820,13 +4210,15 @@
                     mRILDefaultWorkSource);
 
             // Do not log function args for privacy
-            if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+            }
 
             try {
                 radioProxy.requestIccSimAuthentication(rr.mSerial,
                         authContext,
-                        convertNullToEmptyString(data),
-                        convertNullToEmptyString(aid));
+                        RILUtils.convertNullToEmptyString(data),
+                        RILUtils.convertNullToEmptyString(aid));
             } catch (RemoteException | RuntimeException e) {
                 handleRadioProxyExceptionForRR(rr, "requestIccSimAuthentication", e);
             }
@@ -4847,11 +4239,11 @@
 
                     ArrayList<android.hardware.radio.V1_5.DataProfileInfo> dpis = new ArrayList<>();
                     for (DataProfile dp : dps) {
-                        dpis.add(convertToHalDataProfile15(dp));
+                        dpis.add(RILUtils.convertToHalDataProfile15(dp));
                     }
 
                     if (RILJ_LOGD) {
-                        riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+                        riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
                                 + " with data profiles : ");
                         for (DataProfile profile : dps) {
                             riljLog(profile.toString());
@@ -4866,11 +4258,11 @@
 
                     ArrayList<android.hardware.radio.V1_4.DataProfileInfo> dpis = new ArrayList<>();
                     for (DataProfile dp : dps) {
-                        dpis.add(convertToHalDataProfile14(dp));
+                        dpis.add(RILUtils.convertToHalDataProfile14(dp));
                     }
 
                     if (RILJ_LOGD) {
-                        riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+                        riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
                                 + " with data profiles : ");
                         for (DataProfile profile : dps) {
                             riljLog(profile.toString());
@@ -4885,13 +4277,13 @@
                         // For v1.0 to v1.2, we only send data profiles that has the persistent
                         // (a.k.a modem cognitive) bit set to true.
                         if (dp.isPersistent()) {
-                            dpis.add(convertToHalDataProfile10(dp));
+                            dpis.add(RILUtils.convertToHalDataProfile10(dp));
                         }
                     }
 
                     if (!dpis.isEmpty()) {
                         if (RILJ_LOGD) {
-                            riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+                            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
                                     + " with data profiles : ");
                             for (DataProfile profile : dps) {
                                 riljLog(profile.toString());
@@ -4915,7 +4307,7 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
             }
 
             try {
@@ -4934,7 +4326,7 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
             }
 
             try {
@@ -4953,7 +4345,7 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
                         + " RadioCapability = " + rc.toString());
             }
 
@@ -4963,7 +4355,7 @@
             halRc.session = rc.getSession();
             halRc.phase = rc.getPhase();
             halRc.raf = rc.getRadioAccessFamily();
-            halRc.logicalModemUuid = convertNullToEmptyString(rc.getLogicalModemUuid());
+            halRc.logicalModemUuid = RILUtils.convertNullToEmptyString(rc.getLogicalModemUuid());
             halRc.status = rc.getStatus();
 
             try {
@@ -4989,7 +4381,7 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
                         + " reportIntervalMs = " + reportIntervalMs + " pullMode = " + pullMode);
             }
 
@@ -5015,7 +4407,7 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
             }
 
             try {
@@ -5056,7 +4448,7 @@
 
             if (RILJ_LOGD) {
                 riljLog(rr.serialString() + "> "
-                        + requestToString(rr.mRequest)
+                        + RILUtils.requestToString(rr.mRequest)
                         + " dataThrottlingAction = " + dataThrottlingAction
                         + " completionWindowMillis " + completionWindowMillis);
             }
@@ -5089,7 +4481,7 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
             }
 
             try {
@@ -5109,7 +4501,7 @@
                     workSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
             }
 
             try {
@@ -5127,41 +4519,6 @@
 
     }
 
-    /**
-     * Convert a list of CarrierIdentifier into a list of Carrier defined in 1.0/types.hal.
-     * @param carriers List of CarrierIdentifier
-     * @return List of converted objects
-     */
-    @VisibleForTesting
-    public static ArrayList<Carrier> createCarrierRestrictionList(
-            List<CarrierIdentifier> carriers) {
-        ArrayList<Carrier> result = new ArrayList<>();
-        for (CarrierIdentifier ci : carriers) {
-            Carrier c = new Carrier();
-            c.mcc = convertNullToEmptyString(ci.getMcc());
-            c.mnc = convertNullToEmptyString(ci.getMnc());
-            int matchType = CarrierIdentifier.MatchType.ALL;
-            String matchData = null;
-            if (!TextUtils.isEmpty(ci.getSpn())) {
-                matchType = CarrierIdentifier.MatchType.SPN;
-                matchData = ci.getSpn();
-            } else if (!TextUtils.isEmpty(ci.getImsi())) {
-                matchType = CarrierIdentifier.MatchType.IMSI_PREFIX;
-                matchData = ci.getImsi();
-            } else if (!TextUtils.isEmpty(ci.getGid1())) {
-                matchType = CarrierIdentifier.MatchType.GID1;
-                matchData = ci.getGid1();
-            } else if (!TextUtils.isEmpty(ci.getGid2())) {
-                matchType = CarrierIdentifier.MatchType.GID2;
-                matchData = ci.getGid2();
-            }
-            c.matchType = matchType;
-            c.matchData = convertNullToEmptyString(matchData);
-            result.add(c);
-        }
-        return result;
-    }
-
     @Override
     public void setAllowedCarriers(CarrierRestrictionRules carrierRestrictionRules,
             Message result, WorkSource workSource) {
@@ -5176,7 +4533,7 @@
         RILRequest rr = obtainRequest(RIL_REQUEST_SET_ALLOWED_CARRIERS, result, workSource);
 
         if (RILJ_LOGD) {
-            riljLog(rr.serialString() + "> " + requestToString(rr.mRequest) + " params: "
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest) + " params: "
                     + carrierRestrictionRules);
         }
 
@@ -5197,10 +4554,10 @@
             // Prepare structure with allowed list, excluded list and priority
             CarrierRestrictionsWithPriority carrierRestrictions =
                     new CarrierRestrictionsWithPriority();
-            carrierRestrictions.allowedCarriers =
-                    createCarrierRestrictionList(carrierRestrictionRules.getAllowedCarriers());
-            carrierRestrictions.excludedCarriers =
-                    createCarrierRestrictionList(carrierRestrictionRules.getExcludedCarriers());
+            carrierRestrictions.allowedCarriers = RILUtils.convertToHalCarrierRestrictionList(
+                    carrierRestrictionRules.getAllowedCarriers());
+            carrierRestrictions.excludedCarriers = RILUtils.convertToHalCarrierRestrictionList(
+                    carrierRestrictionRules.getExcludedCarriers());
             carrierRestrictions.allowedCarriersPrioritized =
                     (carrierRestrictionRules.getDefaultCarrierRestriction()
                         == CarrierRestrictionRules.CARRIER_RESTRICTION_DEFAULT_NOT_ALLOWED);
@@ -5234,8 +4591,8 @@
 
             // Prepare structure with allowed list
             CarrierRestrictions carrierRestrictions = new CarrierRestrictions();
-            carrierRestrictions.allowedCarriers =
-                    createCarrierRestrictionList(carrierRestrictionRules.getAllowedCarriers());
+            carrierRestrictions.allowedCarriers = RILUtils.convertToHalCarrierRestrictionList(
+                    carrierRestrictionRules.getAllowedCarriers());
 
             try {
                 radioProxy.setAllowedCarriers(rr.mSerial, isAllCarriersAllowed,
@@ -5257,7 +4614,7 @@
                 workSource);
 
         if (RILJ_LOGD) {
-            riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
         }
 
         if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_4)) {
@@ -5291,7 +4648,7 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest) + " "
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest) + " "
                         + stateType + ":" + state);
             }
 
@@ -5311,7 +4668,8 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest) + " " + filter);
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                        + " " + filter);
             }
 
             if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_5)) {
@@ -5360,7 +4718,7 @@
                 RILRequest rr = obtainRequest(RIL_REQUEST_SET_SIGNAL_STRENGTH_REPORTING_CRITERIA,
                         result, mRILDefaultWorkSource);
                 if (RILJ_LOGD) {
-                    riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+                    riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
                 }
                 try {
                     android.hardware.radio.V1_2.IRadio radioProxy12 =
@@ -5368,8 +4726,8 @@
                     radioProxy12.setSignalStrengthReportingCriteria(rr.mSerial,
                             signalThresholdInfo.getHysteresisMs(),
                             signalThresholdInfo.getHysteresisDb(),
-                            primitiveArrayToArrayList(signalThresholdInfo.getThresholds()),
-                            convertAntToHalAnt(ran));
+                            RILUtils.primitiveArrayToArrayList(signalThresholdInfo.getThresholds()),
+                            RILUtils.convertToHalAccessNetwork(ran));
                 } catch (RemoteException | RuntimeException e) {
                     handleRadioProxyExceptionForRR(rr, "setSignalStrengthReportingCriteria", e);
                 }
@@ -5378,14 +4736,14 @@
                 RILRequest rr = obtainRequest(RIL_REQUEST_SET_SIGNAL_STRENGTH_REPORTING_CRITERIA,
                         result, mRILDefaultWorkSource);
                 if (RILJ_LOGD) {
-                    riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+                    riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
                 }
                 try {
                     android.hardware.radio.V1_5.IRadio radioProxy15 =
                             (android.hardware.radio.V1_5.IRadio) radioProxy;
                     radioProxy15.setSignalStrengthReportingCriteria_1_5(rr.mSerial,
-                            convertToHalSignalThresholdInfo(signalThresholdInfo),
-                            convertAntToHalAnt(ran));
+                            RILUtils.convertToHalSignalThresholdInfo(signalThresholdInfo),
+                            RILUtils.convertToHalAccessNetwork(ran));
                 } catch (RemoteException | RuntimeException e) {
                     handleRadioProxyExceptionForRR(
                             rr, "setSignalStrengthReportingCriteria_1_5", e);
@@ -5394,19 +4752,6 @@
         }
     }
 
-    private static android.hardware.radio.V1_5.SignalThresholdInfo convertToHalSignalThresholdInfo(
-            SignalThresholdInfo signalThresholdInfo) {
-        android.hardware.radio.V1_5.SignalThresholdInfo signalThresholdInfoHal =
-                new android.hardware.radio.V1_5.SignalThresholdInfo();
-        signalThresholdInfoHal.signalMeasurement = signalThresholdInfo.getSignalMeasurementType();
-        signalThresholdInfoHal.hysteresisMs = signalThresholdInfo.getHysteresisMs();
-        signalThresholdInfoHal.hysteresisDb = signalThresholdInfo.getHysteresisDb();
-        signalThresholdInfoHal.thresholds = primitiveArrayToArrayList(
-                signalThresholdInfo.getThresholds());
-        signalThresholdInfoHal.isEnabled = signalThresholdInfo.isEnabled();
-        return signalThresholdInfoHal;
-    }
-
     @Override
     public void setLinkCapacityReportingCriteria(int hysteresisMs, int hysteresisDlKbps,
             int hysteresisUlKbps, int[] thresholdsDlKbps, int[] thresholdsUlKbps, int ran,
@@ -5416,7 +4761,7 @@
             RILRequest rr = obtainRequest(RIL_REQUEST_SET_LINK_CAPACITY_REPORTING_CRITERIA, result,
                     mRILDefaultWorkSource);
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
             }
             try {
                 if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_5)) {
@@ -5424,8 +4769,9 @@
                             (android.hardware.radio.V1_5.IRadio) radioProxy;
                     radioProxy15.setLinkCapacityReportingCriteria_1_5(rr.mSerial, hysteresisMs,
                             hysteresisDlKbps, hysteresisUlKbps,
-                            primitiveArrayToArrayList(thresholdsDlKbps),
-                            primitiveArrayToArrayList(thresholdsUlKbps), convertAntToHalAnt(ran));
+                            RILUtils.primitiveArrayToArrayList(thresholdsDlKbps),
+                            RILUtils.primitiveArrayToArrayList(thresholdsUlKbps),
+                            RILUtils.convertToHalAccessNetwork(ran));
                 } else if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_2)) {
                     android.hardware.radio.V1_2.IRadio radioProxy12 =
                             (android.hardware.radio.V1_2.IRadio) radioProxy;
@@ -5434,8 +4780,9 @@
                     }
                     radioProxy12.setLinkCapacityReportingCriteria(rr.mSerial, hysteresisMs,
                             hysteresisDlKbps, hysteresisUlKbps,
-                            primitiveArrayToArrayList(thresholdsDlKbps),
-                            primitiveArrayToArrayList(thresholdsUlKbps), convertAntToHalAnt(ran));
+                            RILUtils.primitiveArrayToArrayList(thresholdsDlKbps),
+                            RILUtils.primitiveArrayToArrayList(thresholdsUlKbps),
+                            RILUtils.convertToHalAccessNetwork(ran));
                 } else {
                     riljLoge("setLinkCapacityReportingCriteria ignored on IRadio version less "
                             + "than 1.2");
@@ -5446,46 +4793,6 @@
         }
     }
 
-    /** Converts from AccessNetworkType in frameworks to AccessNetwork in HAL. */
-    private static int convertAntToHalAnt(int radioAccessNetwork) {
-        switch (radioAccessNetwork) {
-            case AccessNetworkType.GERAN:
-                return AccessNetwork.GERAN;
-            case AccessNetworkType.UTRAN:
-                return AccessNetwork.UTRAN;
-            case AccessNetworkType.EUTRAN:
-                return AccessNetwork.EUTRAN;
-            case AccessNetworkType.CDMA2000:
-                return AccessNetwork.CDMA2000;
-            case AccessNetworkType.IWLAN:
-                return AccessNetwork.IWLAN;
-            case AccessNetworkType.NGRAN:
-                return AccessNetwork.NGRAN;
-            case AccessNetworkType.UNKNOWN:
-            default:
-                return AccessNetwork.UNKNOWN;
-        }
-    }
-
-    /** Converts from AccessNetworkType in frameworks to RadioAccessNetworks in HAL. */
-    private static int convertAntToRan(int accessNetworkType) {
-        switch (accessNetworkType) {
-            case AccessNetworkType.GERAN:
-                return RadioAccessNetworks.GERAN;
-            case AccessNetworkType.UTRAN:
-                return RadioAccessNetworks.UTRAN;
-            case AccessNetworkType.EUTRAN:
-                return RadioAccessNetworks.EUTRAN;
-            case AccessNetworkType.NGRAN:
-                return RadioAccessNetworks.NGRAN;
-            case AccessNetworkType.CDMA2000:
-                return RadioAccessNetworks.CDMA2000;
-            case AccessNetworkType.UNKNOWN:
-            default:
-                return RadioAccessNetworks.UNKNOWN;
-        }
-    }
-
     @Override
     public void setSimCardPower(int state, Message result, WorkSource workSource) {
         workSource = getDefaultWorkSourceIfInvalid(workSource);
@@ -5495,7 +4802,8 @@
                     workSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest) + " " + state);
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                        + " " + state);
             }
 
             if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_6)) {
@@ -5552,7 +4860,9 @@
 
                 RILRequest rr = obtainRequest(RIL_REQUEST_SET_CARRIER_INFO_IMSI_ENCRYPTION, result,
                         mRILDefaultWorkSource);
-                if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+                if (RILJ_LOGD) {
+                    riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+                }
 
                 try {
                     android.hardware.radio.V1_6.ImsiEncryptionInfo halImsiInfo =
@@ -5574,14 +4884,15 @@
                 } catch (RemoteException | RuntimeException e) {
                     handleRadioProxyExceptionForRR(rr, "setCarrierInfoForImsiEncryption", e);
                 }
-            }
-            else if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_1)) {
+            } else if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_1)) {
                 android.hardware.radio.V1_1.IRadio radioProxy11 =
                         (android.hardware.radio.V1_1.IRadio ) radioProxy;
 
                 RILRequest rr = obtainRequest(RIL_REQUEST_SET_CARRIER_INFO_IMSI_ENCRYPTION, result,
                         mRILDefaultWorkSource);
-                if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+                if (RILJ_LOGD) {
+                    riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+                }
 
                 try {
                     android.hardware.radio.V1_1.ImsiEncryptionInfo halImsiInfo =
@@ -5632,7 +4943,7 @@
         RILRequest rr = obtainRequest(
                 RIL_REQUEST_START_KEEPALIVE, result, mRILDefaultWorkSource);
 
-        if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+        if (RILJ_LOGD) riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
 
         try {
             android.hardware.radio.V1_1.KeepaliveRequest req =
@@ -5653,10 +4964,10 @@
 
             final InetAddress srcAddress = packetData.getSrcAddress();
             final InetAddress dstAddress = packetData.getDstAddress();
-            appendPrimitiveArrayToArrayList(
+            RILUtils.appendPrimitiveArrayToArrayList(
                     srcAddress.getAddress(), req.sourceAddress);
             req.sourcePort = packetData.getSrcPort();
-            appendPrimitiveArrayToArrayList(
+            RILUtils.appendPrimitiveArrayToArrayList(
                     dstAddress.getAddress(), req.destinationAddress);
             req.destinationPort = packetData.getDstPort();
             req.maxKeepaliveIntervalMillis = intervalMillis;
@@ -5687,7 +4998,7 @@
         RILRequest rr = obtainRequest(
                 RIL_REQUEST_STOP_KEEPALIVE, result, mRILDefaultWorkSource);
 
-        if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+        if (RILJ_LOGD) riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
 
         try {
             radioProxy11.stopKeepalive(rr.mSerial, sessionHandle);
@@ -5749,7 +5060,7 @@
         RILRequest rr = obtainRequest(RIL_REQUEST_ENABLE_UICC_APPLICATIONS,
                 onCompleteMessage, mRILDefaultWorkSource);
 
-        if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+        if (RILJ_LOGD) riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
 
         try {
             radioProxy15.enableUiccApplications(rr.mSerial, enable);
@@ -5783,7 +5094,7 @@
         RILRequest rr = obtainRequest(RIL_REQUEST_GET_UICC_APPLICATIONS_ENABLEMENT,
                 onCompleteMessage, mRILDefaultWorkSource);
 
-        if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+        if (RILJ_LOGD) riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
 
         try {
             radioProxy15.areUiccApplicationsEnabled(rr.mSerial);
@@ -5801,26 +5112,6 @@
                 .greaterOrEqual(RADIO_HAL_VERSION_1_5);
     }
 
-    /**
-     *  Translates EF_SMS status bits to a status value compatible with
-     *  SMS AT commands.  See TS 27.005 3.1.
-     */
-    private int translateStatus(int status) {
-        switch(status & 0x7) {
-            case SmsManager.STATUS_ON_ICC_READ:
-                return 1;
-            case SmsManager.STATUS_ON_ICC_UNREAD:
-                return 0;
-            case SmsManager.STATUS_ON_ICC_SENT:
-                return 3;
-            case SmsManager.STATUS_ON_ICC_UNSENT:
-                return 2;
-        }
-
-        // Default to READ.
-        return 1;
-    }
-
     @Override
     public void resetRadio(Message result) {
         throw new RuntimeException("resetRadio not expected to be called");
@@ -5837,7 +5128,7 @@
                     result, mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
             }
 
             try {
@@ -5871,7 +5162,9 @@
             RILRequest rr = obtainRequest(RIL_REQUEST_GET_BARRING_INFO, result,
                     mRILDefaultWorkSource);
 
-            if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+            }
 
             try {
                 radioProxy15.getBarringInfo(rr.mSerial);
@@ -5891,7 +5184,9 @@
         if (radioProxy16 != null) {
             RILRequest rr = obtainRequest(RIL_REQUEST_ALLOCATE_PDU_SESSION_ID, result,
                     mRILDefaultWorkSource);
-            if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+            }
 
             try {
                 radioProxy16.allocatePduSessionId(rr.mSerial);
@@ -5915,7 +5210,9 @@
         if (radioProxy16 != null) {
             RILRequest rr = obtainRequest(RIL_REQUEST_RELEASE_PDU_SESSION_ID, result,
                     mRILDefaultWorkSource);
-            if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+            }
 
             try {
                 radioProxy16.releasePduSessionId(rr.mSerial, pduSessionId);
@@ -5939,7 +5236,9 @@
         if (radioProxy16 != null) {
             RILRequest rr = obtainRequest(RIL_REQUEST_START_HANDOVER, result,
                     mRILDefaultWorkSource);
-            if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+            }
 
             try {
                 radioProxy16.startHandover(rr.mSerial, callId);
@@ -5964,7 +5263,9 @@
         if (radioProxy16 != null) {
             RILRequest rr = obtainRequest(RIL_REQUEST_CANCEL_HANDOVER, result,
                     mRILDefaultWorkSource);
-            if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+            }
 
             try {
                 radioProxy16.cancelHandover(rr.mSerial, callId);
@@ -5979,6 +5280,34 @@
         }
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void getSlicingConfig(Message result) {
+        android.hardware.radio.V1_6.IRadio radioProxy16 = getRadioV16(result);
+
+        if (radioProxy16 != null) {
+            RILRequest rr = obtainRequest(RIL_REQUEST_GET_SLICING_CONFIG, result,
+                    mRILDefaultWorkSource);
+
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+            }
+
+            try {
+                radioProxy16.getSlicingConfig(rr.mSerial);
+            } catch (RemoteException | RuntimeException e) {
+                handleRadioProxyExceptionForRR(rr, "getSlicingConfig", e);
+            }
+        } else {
+            if (RILJ_LOGD) Rlog.d(RILJ_LOG_TAG, "getSlicingConfig: REQUEST_NOT_SUPPORTED");
+            AsyncResult.forMessage(result, null,
+                    CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
+            result.sendToTarget();
+        }
+    }
+
     @Override
     public void getSimPhonebookRecords(Message result) {
         IRadio radioProxy = getRadioProxy(result);
@@ -5987,7 +5316,7 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
             }
 
             if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_6)) {
@@ -6017,7 +5346,7 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
             }
 
             if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_6)) {
@@ -6047,7 +5376,7 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
                         + " with " + phonebookRecord.toString());
             }
 
@@ -6056,49 +5385,23 @@
                         android.hardware.radio.V1_6.IRadio.castFrom(radioProxy);
 
                 android.hardware.radio.V1_6.PhonebookRecordInfo pbRecordInfo =
-                        phonebookRecord.toPhonebookRecordInfo();
+                        RILUtils.convertToHalPhonebookRecordInfo(phonebookRecord);
                 try {
-                    radioProxy16.updateSimPhonebookRecords(rr.mSerial, pbRecordInfo);
+                     radioProxy16.updateSimPhonebookRecords(rr.mSerial, pbRecordInfo);
                 } catch (RemoteException | RuntimeException e) {
                     handleRadioProxyExceptionForRR(rr, "updatePhonebookRecord", e);
                 }
             } else {
-                riljLog("Unsupported API in lower than version 1.6 radio HAL");
+                riljLog("Unsupported API in lower than version 1.6 radio HAL" );
                 if (result != null) {
                     AsyncResult.forMessage(result, null,
-                            CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
+                    CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
                     result.sendToTarget();
                 }
             }
         }
     }
 
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public void getSlicingConfig(Message result) {
-        android.hardware.radio.V1_6.IRadio radioProxy16 = getRadioV16(result);
-
-        if (radioProxy16 != null) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_GET_SLICING_CONFIG, result,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
-
-            try {
-                radioProxy16.getSlicingConfig(rr.mSerial);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(rr, "getSlicingConfig", e);
-            }
-        } else {
-            if (RILJ_LOGD) Rlog.d(RILJ_LOG_TAG, "getSlicingConfig: REQUEST_NOT_SUPPORTED");
-            AsyncResult.forMessage(result, null,
-                    CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-            result.sendToTarget();
-        }
-    }
-
     //***** Private Methods
     /** Helper that gets V1.6 of the radio interface OR sends back REQUEST_NOT_SUPPORTED */
     @Nullable private android.hardware.radio.V1_6.IRadio getRadioV16(Message msg) {
@@ -6136,7 +5439,7 @@
         } else {
             decrementWakeLock(rr);
             if (RIL.RILJ_LOGD) {
-                riljLog(rr.serialString() + " Ack < " + RIL.requestToString(rr.mRequest));
+                riljLog(rr.serialString() + " Ack < " + RILUtils.requestToString(rr.mRequest));
             }
         }
     }
@@ -6167,6 +5470,19 @@
         return processResponseInternal(responseInfo.serial, responseInfo.error, responseInfo.type);
     }
 
+    /**
+     * This is a helper function for an AIDL RadioResponseInfo to be called when a RadioResponse
+     * callback is called.
+     * It takes care of acks, wakelocks, and finds and returns RILRequest corresponding to the
+     * response if one is found.
+     * @param responseInfo RadioResponseInfo received in response callback
+     * @return RILRequest corresponding to the response
+     */
+    @VisibleForTesting
+    public RILRequest processResponse(android.hardware.radio.RadioResponseInfo responseInfo) {
+        return processResponseInternal(responseInfo.serial, responseInfo.error, responseInfo.type);
+    }
+
     private RILRequest processResponseInternal(int serial, int error, int type) {
         RILRequest rr = null;
 
@@ -6182,7 +5498,7 @@
                     mRadioBugDetector.detectRadioBug(rr.mRequest, error);
                 }
                 if (RILJ_LOGD) {
-                    riljLog(rr.serialString() + " Ack < " + requestToString(rr.mRequest));
+                    riljLog(rr.serialString() + " Ack < " + RILUtils.requestToString(rr.mRequest));
                 }
             }
             return rr;
@@ -6204,7 +5520,7 @@
             sendAck();
             if (RIL.RILJ_LOGD) {
                 riljLog("Response received for " + rr.serialString() + " "
-                        + RIL.requestToString(rr.mRequest) + " Sending ack to ril.cpp");
+                        + RILUtils.requestToString(rr.mRequest) + " Sending ack to ril.cpp");
             }
         } else {
             // ack sent for SOLICITED_ACK_EXP above; nothing to do for SOLICITED response
@@ -6290,16 +5606,31 @@
         processResponseDoneInternal(rr, responseInfo.error, responseInfo.type, ret);
     }
 
+    /**
+     * This is a helper function to be called at the end of the RadioResponse callbacks using for
+     * RadioResponseInfo AIDL.
+     * It takes care of sending error response, logging, decrementing wakelock if needed, and
+     * releases the request from memory pool.
+     * @param rr RILRequest for which response callback was called
+     * @param responseInfo RadioResponseInfo received in the callback
+     * @param ret object to be returned to request sender
+     */
+    @VisibleForTesting
+    public void processResponseDone(RILRequest rr,
+            android.hardware.radio.RadioResponseInfo responseInfo, Object ret) {
+        processResponseDoneInternal(rr, responseInfo.error, responseInfo.type, ret);
+    }
+
     private void processResponseDoneInternal(
             RILRequest rr, int rilError, int responseType, Object ret) {
         if (rilError == 0) {
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "< " + requestToString(rr.mRequest)
+                riljLog(rr.serialString() + "< " + RILUtils.requestToString(rr.mRequest)
                         + " " + retToString(rr.mRequest, ret));
             }
         } else {
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "< " + requestToString(rr.mRequest)
+                riljLog(rr.serialString() + "< " + RILUtils.requestToString(rr.mRequest)
                         + " error " + rilError);
             }
             rr.onError(rilError, ret);
@@ -6319,7 +5650,7 @@
     @VisibleForTesting
     public void processResponseFallback(RILRequest rr, RadioResponseInfo responseInfo, Object ret) {
         if (responseInfo.error == REQUEST_NOT_SUPPORTED && RILJ_LOGD) {
-            riljLog(rr.serialString() + "< " + requestToString(rr.mRequest)
+            riljLog(rr.serialString() + "< " + RILUtils.requestToString(rr.mRequest)
                     + " request not supported, falling back");
         }
         processResponseCleanUp(rr, responseInfo.error, responseInfo.type, ret);
@@ -6515,7 +5846,7 @@
                 rr = mRequestList.valueAt(i);
                 if (RILJ_LOGD && loggable) {
                     Rlog.d(RILJ_LOG_TAG, i + ": [" + rr.mSerial + "] "
-                            + requestToString(rr.mRequest));
+                            + RILUtils.requestToString(rr.mRequest));
                 }
                 rr.onError(error, null);
                 decrementWakeLock(rr);
@@ -6692,8 +6023,7 @@
     }
 
     @UnsupportedAppUsage
-    void
-    notifyRegistrantsCdmaInfoRec(CdmaInformationRecords infoRec) {
+    void notifyRegistrantsCdmaInfoRec(CdmaInformationRecords infoRec) {
         int response = RIL_UNSOL_CDMA_INFO_REC;
         if (infoRec.record instanceof CdmaInformationRecords.CdmaDisplayInfoRec) {
             if (mDisplayInfoRegistrants != null) {
@@ -6743,493 +6073,6 @@
     }
 
     @UnsupportedAppUsage
-    static String requestToString(int request) {
-        switch(request) {
-            case RIL_REQUEST_GET_SIM_STATUS:
-                return "GET_SIM_STATUS";
-            case RIL_REQUEST_ENTER_SIM_PIN:
-                return "ENTER_SIM_PIN";
-            case RIL_REQUEST_ENTER_SIM_PUK:
-                return "ENTER_SIM_PUK";
-            case RIL_REQUEST_ENTER_SIM_PIN2:
-                return "ENTER_SIM_PIN2";
-            case RIL_REQUEST_ENTER_SIM_PUK2:
-                return "ENTER_SIM_PUK2";
-            case RIL_REQUEST_CHANGE_SIM_PIN:
-                return "CHANGE_SIM_PIN";
-            case RIL_REQUEST_CHANGE_SIM_PIN2:
-                return "CHANGE_SIM_PIN2";
-            case RIL_REQUEST_ENTER_NETWORK_DEPERSONALIZATION:
-                return "ENTER_NETWORK_DEPERSONALIZATION";
-            case RIL_REQUEST_GET_CURRENT_CALLS:
-                return "GET_CURRENT_CALLS";
-            case RIL_REQUEST_DIAL:
-                return "DIAL";
-            case RIL_REQUEST_GET_IMSI:
-                return "GET_IMSI";
-            case RIL_REQUEST_HANGUP:
-                return "HANGUP";
-            case RIL_REQUEST_HANGUP_WAITING_OR_BACKGROUND:
-                return "HANGUP_WAITING_OR_BACKGROUND";
-            case RIL_REQUEST_HANGUP_FOREGROUND_RESUME_BACKGROUND:
-                return "HANGUP_FOREGROUND_RESUME_BACKGROUND";
-            case RIL_REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE:
-                return "REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE";
-            case RIL_REQUEST_CONFERENCE:
-                return "CONFERENCE";
-            case RIL_REQUEST_UDUB:
-                return "UDUB";
-            case RIL_REQUEST_LAST_CALL_FAIL_CAUSE:
-                return "LAST_CALL_FAIL_CAUSE";
-            case RIL_REQUEST_SIGNAL_STRENGTH:
-                return "SIGNAL_STRENGTH";
-            case RIL_REQUEST_VOICE_REGISTRATION_STATE:
-                return "VOICE_REGISTRATION_STATE";
-            case RIL_REQUEST_DATA_REGISTRATION_STATE:
-                return "DATA_REGISTRATION_STATE";
-            case RIL_REQUEST_OPERATOR:
-                return "OPERATOR";
-            case RIL_REQUEST_RADIO_POWER:
-                return "RADIO_POWER";
-            case RIL_REQUEST_DTMF:
-                return "DTMF";
-            case RIL_REQUEST_SEND_SMS:
-                return "SEND_SMS";
-            case RIL_REQUEST_SEND_SMS_EXPECT_MORE:
-                return "SEND_SMS_EXPECT_MORE";
-            case RIL_REQUEST_SETUP_DATA_CALL:
-                return "SETUP_DATA_CALL";
-            case RIL_REQUEST_SIM_IO:
-                return "SIM_IO";
-            case RIL_REQUEST_SEND_USSD:
-                return "SEND_USSD";
-            case RIL_REQUEST_CANCEL_USSD:
-                return "CANCEL_USSD";
-            case RIL_REQUEST_GET_CLIR:
-                return "GET_CLIR";
-            case RIL_REQUEST_SET_CLIR:
-                return "SET_CLIR";
-            case RIL_REQUEST_QUERY_CALL_FORWARD_STATUS:
-                return "QUERY_CALL_FORWARD_STATUS";
-            case RIL_REQUEST_SET_CALL_FORWARD:
-                return "SET_CALL_FORWARD";
-            case RIL_REQUEST_QUERY_CALL_WAITING:
-                return "QUERY_CALL_WAITING";
-            case RIL_REQUEST_SET_CALL_WAITING:
-                return "SET_CALL_WAITING";
-            case RIL_REQUEST_SMS_ACKNOWLEDGE:
-                return "SMS_ACKNOWLEDGE";
-            case RIL_REQUEST_GET_IMEI:
-                return "GET_IMEI";
-            case RIL_REQUEST_GET_IMEISV:
-                return "GET_IMEISV";
-            case RIL_REQUEST_ANSWER:
-                return "ANSWER";
-            case RIL_REQUEST_DEACTIVATE_DATA_CALL:
-                return "DEACTIVATE_DATA_CALL";
-            case RIL_REQUEST_QUERY_FACILITY_LOCK:
-                return "QUERY_FACILITY_LOCK";
-            case RIL_REQUEST_SET_FACILITY_LOCK:
-                return "SET_FACILITY_LOCK";
-            case RIL_REQUEST_CHANGE_BARRING_PASSWORD:
-                return "CHANGE_BARRING_PASSWORD";
-            case RIL_REQUEST_QUERY_NETWORK_SELECTION_MODE:
-                return "QUERY_NETWORK_SELECTION_MODE";
-            case RIL_REQUEST_SET_NETWORK_SELECTION_AUTOMATIC:
-                return "SET_NETWORK_SELECTION_AUTOMATIC";
-            case RIL_REQUEST_SET_NETWORK_SELECTION_MANUAL:
-                return "SET_NETWORK_SELECTION_MANUAL";
-            case RIL_REQUEST_QUERY_AVAILABLE_NETWORKS :
-                return "QUERY_AVAILABLE_NETWORKS ";
-            case RIL_REQUEST_DTMF_START:
-                return "DTMF_START";
-            case RIL_REQUEST_DTMF_STOP:
-                return "DTMF_STOP";
-            case RIL_REQUEST_BASEBAND_VERSION:
-                return "BASEBAND_VERSION";
-            case RIL_REQUEST_SEPARATE_CONNECTION:
-                return "SEPARATE_CONNECTION";
-            case RIL_REQUEST_SET_MUTE:
-                return "SET_MUTE";
-            case RIL_REQUEST_GET_MUTE:
-                return "GET_MUTE";
-            case RIL_REQUEST_QUERY_CLIP:
-                return "QUERY_CLIP";
-            case RIL_REQUEST_LAST_DATA_CALL_FAIL_CAUSE:
-                return "LAST_DATA_CALL_FAIL_CAUSE";
-            case RIL_REQUEST_DATA_CALL_LIST:
-                return "DATA_CALL_LIST";
-            case RIL_REQUEST_RESET_RADIO:
-                return "RESET_RADIO";
-            case RIL_REQUEST_OEM_HOOK_RAW:
-                return "OEM_HOOK_RAW";
-            case RIL_REQUEST_OEM_HOOK_STRINGS:
-                return "OEM_HOOK_STRINGS";
-            case RIL_REQUEST_SCREEN_STATE:
-                return "SCREEN_STATE";
-            case RIL_REQUEST_SET_SUPP_SVC_NOTIFICATION:
-                return "SET_SUPP_SVC_NOTIFICATION";
-            case RIL_REQUEST_WRITE_SMS_TO_SIM:
-                return "WRITE_SMS_TO_SIM";
-            case RIL_REQUEST_DELETE_SMS_ON_SIM:
-                return "DELETE_SMS_ON_SIM";
-            case RIL_REQUEST_SET_BAND_MODE:
-                return "SET_BAND_MODE";
-            case RIL_REQUEST_QUERY_AVAILABLE_BAND_MODE:
-                return "QUERY_AVAILABLE_BAND_MODE";
-            case RIL_REQUEST_STK_GET_PROFILE:
-                return "STK_GET_PROFILE";
-            case RIL_REQUEST_STK_SET_PROFILE:
-                return "STK_SET_PROFILE";
-            case RIL_REQUEST_STK_SEND_ENVELOPE_COMMAND:
-                return "STK_SEND_ENVELOPE_COMMAND";
-            case RIL_REQUEST_STK_SEND_TERMINAL_RESPONSE:
-                return "STK_SEND_TERMINAL_RESPONSE";
-            case RIL_REQUEST_STK_HANDLE_CALL_SETUP_REQUESTED_FROM_SIM:
-                return "STK_HANDLE_CALL_SETUP_REQUESTED_FROM_SIM";
-            case RIL_REQUEST_EXPLICIT_CALL_TRANSFER:
-                return "EXPLICIT_CALL_TRANSFER";
-            case RIL_REQUEST_SET_PREFERRED_NETWORK_TYPE:
-                return "SET_PREFERRED_NETWORK_TYPE";
-            case RIL_REQUEST_GET_PREFERRED_NETWORK_TYPE:
-                return "GET_PREFERRED_NETWORK_TYPE";
-            case RIL_REQUEST_GET_NEIGHBORING_CELL_IDS:
-                return "GET_NEIGHBORING_CELL_IDS";
-            case RIL_REQUEST_SET_LOCATION_UPDATES:
-                return "SET_LOCATION_UPDATES";
-            case RIL_REQUEST_CDMA_SET_SUBSCRIPTION_SOURCE:
-                return "CDMA_SET_SUBSCRIPTION_SOURCE";
-            case RIL_REQUEST_CDMA_SET_ROAMING_PREFERENCE:
-                return "CDMA_SET_ROAMING_PREFERENCE";
-            case RIL_REQUEST_CDMA_QUERY_ROAMING_PREFERENCE:
-                return "CDMA_QUERY_ROAMING_PREFERENCE";
-            case RIL_REQUEST_SET_TTY_MODE:
-                return "SET_TTY_MODE";
-            case RIL_REQUEST_QUERY_TTY_MODE:
-                return "QUERY_TTY_MODE";
-            case RIL_REQUEST_CDMA_SET_PREFERRED_VOICE_PRIVACY_MODE:
-                return "CDMA_SET_PREFERRED_VOICE_PRIVACY_MODE";
-            case RIL_REQUEST_CDMA_QUERY_PREFERRED_VOICE_PRIVACY_MODE:
-                return "CDMA_QUERY_PREFERRED_VOICE_PRIVACY_MODE";
-            case RIL_REQUEST_CDMA_FLASH:
-                return "CDMA_FLASH";
-            case RIL_REQUEST_CDMA_BURST_DTMF:
-                return "CDMA_BURST_DTMF";
-            case RIL_REQUEST_CDMA_VALIDATE_AND_WRITE_AKEY:
-                return "CDMA_VALIDATE_AND_WRITE_AKEY";
-            case RIL_REQUEST_CDMA_SEND_SMS:
-                return "CDMA_SEND_SMS";
-            case RIL_REQUEST_CDMA_SMS_ACKNOWLEDGE:
-                return "CDMA_SMS_ACKNOWLEDGE";
-            case RIL_REQUEST_GSM_GET_BROADCAST_CONFIG:
-                return "GSM_GET_BROADCAST_CONFIG";
-            case RIL_REQUEST_GSM_SET_BROADCAST_CONFIG:
-                return "GSM_SET_BROADCAST_CONFIG";
-            case RIL_REQUEST_GSM_BROADCAST_ACTIVATION:
-                return "GSM_BROADCAST_ACTIVATION";
-            case RIL_REQUEST_CDMA_GET_BROADCAST_CONFIG:
-                return "CDMA_GET_BROADCAST_CONFIG";
-            case RIL_REQUEST_CDMA_SET_BROADCAST_CONFIG:
-                return "CDMA_SET_BROADCAST_CONFIG";
-            case RIL_REQUEST_CDMA_BROADCAST_ACTIVATION:
-                return "CDMA_BROADCAST_ACTIVATION";
-            case RIL_REQUEST_CDMA_SUBSCRIPTION:
-                return "CDMA_SUBSCRIPTION";
-            case RIL_REQUEST_CDMA_WRITE_SMS_TO_RUIM:
-                return "CDMA_WRITE_SMS_TO_RUIM";
-            case RIL_REQUEST_CDMA_DELETE_SMS_ON_RUIM:
-                return "CDMA_DELETE_SMS_ON_RUIM";
-            case RIL_REQUEST_DEVICE_IDENTITY:
-                return "DEVICE_IDENTITY";
-            case RIL_REQUEST_EXIT_EMERGENCY_CALLBACK_MODE:
-                return "EXIT_EMERGENCY_CALLBACK_MODE";
-            case RIL_REQUEST_GET_SMSC_ADDRESS:
-                return "GET_SMSC_ADDRESS";
-            case RIL_REQUEST_SET_SMSC_ADDRESS:
-                return "SET_SMSC_ADDRESS";
-            case RIL_REQUEST_REPORT_SMS_MEMORY_STATUS:
-                return "REPORT_SMS_MEMORY_STATUS";
-            case RIL_REQUEST_REPORT_STK_SERVICE_IS_RUNNING:
-                return "REPORT_STK_SERVICE_IS_RUNNING";
-            case RIL_REQUEST_CDMA_GET_SUBSCRIPTION_SOURCE:
-                return "CDMA_GET_SUBSCRIPTION_SOURCE";
-            case RIL_REQUEST_ISIM_AUTHENTICATION:
-                return "ISIM_AUTHENTICATION";
-            case RIL_REQUEST_ACKNOWLEDGE_INCOMING_GSM_SMS_WITH_PDU:
-                return "ACKNOWLEDGE_INCOMING_GSM_SMS_WITH_PDU";
-            case RIL_REQUEST_STK_SEND_ENVELOPE_WITH_STATUS:
-                return "STK_SEND_ENVELOPE_WITH_STATUS";
-            case RIL_REQUEST_VOICE_RADIO_TECH:
-                return "VOICE_RADIO_TECH";
-            case RIL_REQUEST_GET_CELL_INFO_LIST:
-                return "GET_CELL_INFO_LIST";
-            case RIL_REQUEST_SET_UNSOL_CELL_INFO_LIST_RATE:
-                return "SET_CELL_INFO_LIST_RATE";
-            case RIL_REQUEST_SET_INITIAL_ATTACH_APN:
-                return "SET_INITIAL_ATTACH_APN";
-            case RIL_REQUEST_IMS_REGISTRATION_STATE:
-                return "IMS_REGISTRATION_STATE";
-            case RIL_REQUEST_IMS_SEND_SMS:
-                return "IMS_SEND_SMS";
-            case RIL_REQUEST_SIM_TRANSMIT_APDU_BASIC:
-                return "SIM_TRANSMIT_APDU_BASIC";
-            case RIL_REQUEST_SIM_OPEN_CHANNEL:
-                return "SIM_OPEN_CHANNEL";
-            case RIL_REQUEST_SIM_CLOSE_CHANNEL:
-                return "SIM_CLOSE_CHANNEL";
-            case RIL_REQUEST_SIM_TRANSMIT_APDU_CHANNEL:
-                return "SIM_TRANSMIT_APDU_CHANNEL";
-            case RIL_REQUEST_NV_READ_ITEM:
-                return "NV_READ_ITEM";
-            case RIL_REQUEST_NV_WRITE_ITEM:
-                return "NV_WRITE_ITEM";
-            case RIL_REQUEST_NV_WRITE_CDMA_PRL:
-                return "NV_WRITE_CDMA_PRL";
-            case RIL_REQUEST_NV_RESET_CONFIG:
-                return "NV_RESET_CONFIG";
-            case RIL_REQUEST_SET_UICC_SUBSCRIPTION:
-                return "SET_UICC_SUBSCRIPTION";
-            case RIL_REQUEST_ALLOW_DATA:
-                return "ALLOW_DATA";
-            case RIL_REQUEST_GET_HARDWARE_CONFIG:
-                return "GET_HARDWARE_CONFIG";
-            case RIL_REQUEST_SIM_AUTHENTICATION:
-                return "SIM_AUTHENTICATION";
-            case RIL_REQUEST_GET_DC_RT_INFO:
-                return "GET_DC_RT_INFO";
-            case RIL_REQUEST_SET_DC_RT_INFO_RATE:
-                return "SET_DC_RT_INFO_RATE";
-            case RIL_REQUEST_SET_DATA_PROFILE:
-                return "SET_DATA_PROFILE";
-            case RIL_REQUEST_SHUTDOWN:
-                return "SHUTDOWN";
-            case RIL_REQUEST_GET_RADIO_CAPABILITY:
-                return "GET_RADIO_CAPABILITY";
-            case RIL_REQUEST_SET_RADIO_CAPABILITY:
-                return "SET_RADIO_CAPABILITY";
-            case RIL_REQUEST_START_LCE:
-                return "START_LCE";
-            case RIL_REQUEST_STOP_LCE:
-                return "STOP_LCE";
-            case RIL_REQUEST_PULL_LCEDATA:
-                return "PULL_LCEDATA";
-            case RIL_REQUEST_GET_ACTIVITY_INFO:
-                return "GET_ACTIVITY_INFO";
-            case RIL_REQUEST_SET_ALLOWED_CARRIERS:
-                return "SET_ALLOWED_CARRIERS";
-            case RIL_REQUEST_GET_ALLOWED_CARRIERS:
-                return "GET_ALLOWED_CARRIERS";
-            case RIL_REQUEST_SEND_DEVICE_STATE:
-                return "SEND_DEVICE_STATE";
-            case RIL_REQUEST_SET_UNSOLICITED_RESPONSE_FILTER:
-                return "SET_UNSOLICITED_RESPONSE_FILTER";
-            case RIL_REQUEST_SET_SIM_CARD_POWER:
-                return "SET_SIM_CARD_POWER";
-            case RIL_REQUEST_SET_CARRIER_INFO_IMSI_ENCRYPTION:
-                return "SET_CARRIER_INFO_IMSI_ENCRYPTION";
-            case RIL_REQUEST_START_NETWORK_SCAN:
-                return "START_NETWORK_SCAN";
-            case RIL_REQUEST_STOP_NETWORK_SCAN:
-                return "STOP_NETWORK_SCAN";
-            case RIL_REQUEST_START_KEEPALIVE:
-                return "START_KEEPALIVE";
-            case RIL_REQUEST_STOP_KEEPALIVE:
-                return "STOP_KEEPALIVE";
-            case RIL_REQUEST_ENABLE_MODEM:
-                return "ENABLE_MODEM";
-            case RIL_REQUEST_GET_MODEM_STATUS:
-                return "GET_MODEM_STATUS";
-            case RIL_REQUEST_CDMA_SEND_SMS_EXPECT_MORE:
-                return "CDMA_SEND_SMS_EXPECT_MORE";
-            case RIL_REQUEST_GET_SIM_PHONEBOOK_CAPACITY:
-                return "GET_SIM_PHONEBOOK_CAPACITY";
-            case RIL_REQUEST_GET_SIM_PHONEBOOK_RECORDS:
-                return "GET_SIM_PHONEBOOK_RECORDS";
-            case RIL_REQUEST_UPDATE_SIM_PHONEBOOK_RECORD:
-                return "UPDATE_SIM_PHONEBOOK_RECORD";
-            case RIL_REQUEST_GET_SLOT_STATUS:
-                return "GET_SLOT_STATUS";
-            case RIL_REQUEST_SET_LOGICAL_TO_PHYSICAL_SLOT_MAPPING:
-                return "SET_LOGICAL_TO_PHYSICAL_SLOT_MAPPING";
-            case RIL_REQUEST_SET_SIGNAL_STRENGTH_REPORTING_CRITERIA:
-                return "SET_SIGNAL_STRENGTH_REPORTING_CRITERIA";
-            case RIL_REQUEST_SET_LINK_CAPACITY_REPORTING_CRITERIA:
-                return "SET_LINK_CAPACITY_REPORTING_CRITERIA";
-            case RIL_REQUEST_SET_PREFERRED_DATA_MODEM:
-                return "SET_PREFERRED_DATA_MODEM";
-            case RIL_REQUEST_EMERGENCY_DIAL:
-                return "EMERGENCY_DIAL";
-            case RIL_REQUEST_GET_PHONE_CAPABILITY:
-                return "GET_PHONE_CAPABILITY";
-            case RIL_REQUEST_SWITCH_DUAL_SIM_CONFIG:
-                return "SWITCH_DUAL_SIM_CONFIG";
-            case RIL_REQUEST_ENABLE_UICC_APPLICATIONS:
-                return "ENABLE_UICC_APPLICATIONS";
-            case RIL_REQUEST_GET_UICC_APPLICATIONS_ENABLEMENT:
-                return "GET_UICC_APPLICATIONS_ENABLEMENT";
-            case RIL_REQUEST_SET_SYSTEM_SELECTION_CHANNELS:
-                return "SET_SYSTEM_SELECTION_CHANNELS";
-            case RIL_REQUEST_GET_BARRING_INFO:
-                return "GET_BARRING_INFO";
-            case RIL_REQUEST_ENTER_SIM_DEPERSONALIZATION:
-                return "ENTER_SIM_DEPERSONALIZATION";
-            case RIL_REQUEST_ENABLE_NR_DUAL_CONNECTIVITY:
-                return "ENABLE_NR_DUAL_CONNECTIVITY";
-            case RIL_REQUEST_IS_NR_DUAL_CONNECTIVITY_ENABLED:
-                return "IS_NR_DUAL_CONNECTIVITY_ENABLED";
-            case RIL_REQUEST_ALLOCATE_PDU_SESSION_ID:
-                return "ALLOCATE_PDU_SESSION_ID";
-            case RIL_REQUEST_RELEASE_PDU_SESSION_ID:
-                return "RELEASE_PDU_SESSION_ID";
-            case RIL_REQUEST_START_HANDOVER:
-                return "START_HANDOVER";
-            case RIL_REQUEST_CANCEL_HANDOVER:
-                return "CANCEL_HANDOVER";
-            case RIL_REQUEST_GET_SYSTEM_SELECTION_CHANNELS:
-                return "GET_SYSTEM_SELECTION_CHANNELS";
-            case RIL_REQUEST_GET_HAL_DEVICE_CAPABILITIES:
-                return "GET_HAL_DEVICE_CAPABILITIES";
-            case RIL_REQUEST_SET_DATA_THROTTLING:
-                return "SET_DATA_THROTTLING";
-            case RIL_REQUEST_SET_ALLOWED_NETWORK_TYPES_BITMAP:
-                return "SET_ALLOWED_NETWORK_TYPES_BITMAP";
-            case RIL_REQUEST_GET_ALLOWED_NETWORK_TYPES_BITMAP:
-                return "GET_ALLOWED_NETWORK_TYPES_BITMAP";
-            case RIL_REQUEST_GET_SLICING_CONFIG:
-                return "GET_SLICING_CONFIG";
-            default: return "<unknown request>";
-        }
-    }
-
-    @UnsupportedAppUsage
-    static String responseToString(int request) {
-        switch(request) {
-            case RIL_UNSOL_RESPONSE_RADIO_STATE_CHANGED:
-                return "UNSOL_RESPONSE_RADIO_STATE_CHANGED";
-            case RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED:
-                return "UNSOL_RESPONSE_CALL_STATE_CHANGED";
-            case RIL_UNSOL_RESPONSE_NETWORK_STATE_CHANGED:
-                return "UNSOL_RESPONSE_NETWORK_STATE_CHANGED";
-            case RIL_UNSOL_RESPONSE_NEW_SMS:
-                return "UNSOL_RESPONSE_NEW_SMS";
-            case RIL_UNSOL_RESPONSE_NEW_SMS_STATUS_REPORT:
-                return "UNSOL_RESPONSE_NEW_SMS_STATUS_REPORT";
-            case RIL_UNSOL_RESPONSE_NEW_SMS_ON_SIM:
-                return "UNSOL_RESPONSE_NEW_SMS_ON_SIM";
-            case RIL_UNSOL_ON_USSD:
-                return "UNSOL_ON_USSD";
-            case RIL_UNSOL_ON_USSD_REQUEST:
-                return "UNSOL_ON_USSD_REQUEST";
-            case RIL_UNSOL_NITZ_TIME_RECEIVED:
-                return "UNSOL_NITZ_TIME_RECEIVED";
-            case RIL_UNSOL_SIGNAL_STRENGTH:
-                return "UNSOL_SIGNAL_STRENGTH";
-            case RIL_UNSOL_DATA_CALL_LIST_CHANGED:
-                return "UNSOL_DATA_CALL_LIST_CHANGED";
-            case RIL_UNSOL_SUPP_SVC_NOTIFICATION:
-                return "UNSOL_SUPP_SVC_NOTIFICATION";
-            case RIL_UNSOL_STK_SESSION_END:
-                return "UNSOL_STK_SESSION_END";
-            case RIL_UNSOL_STK_PROACTIVE_COMMAND:
-                return "UNSOL_STK_PROACTIVE_COMMAND";
-            case RIL_UNSOL_STK_EVENT_NOTIFY:
-                return "UNSOL_STK_EVENT_NOTIFY";
-            case RIL_UNSOL_STK_CALL_SETUP:
-                return "UNSOL_STK_CALL_SETUP";
-            case RIL_UNSOL_SIM_SMS_STORAGE_FULL:
-                return "UNSOL_SIM_SMS_STORAGE_FULL";
-            case RIL_UNSOL_SIM_REFRESH:
-                return "UNSOL_SIM_REFRESH";
-            case RIL_UNSOL_CALL_RING:
-                return "UNSOL_CALL_RING";
-            case RIL_UNSOL_RESPONSE_SIM_STATUS_CHANGED:
-                return "UNSOL_RESPONSE_SIM_STATUS_CHANGED";
-            case RIL_UNSOL_RESPONSE_CDMA_NEW_SMS:
-                return "UNSOL_RESPONSE_CDMA_NEW_SMS";
-            case RIL_UNSOL_RESPONSE_NEW_BROADCAST_SMS:
-                return "UNSOL_RESPONSE_NEW_BROADCAST_SMS";
-            case RIL_UNSOL_CDMA_RUIM_SMS_STORAGE_FULL:
-                return "UNSOL_CDMA_RUIM_SMS_STORAGE_FULL";
-            case RIL_UNSOL_RESTRICTED_STATE_CHANGED:
-                return "UNSOL_RESTRICTED_STATE_CHANGED";
-            case RIL_UNSOL_ENTER_EMERGENCY_CALLBACK_MODE:
-                return "UNSOL_ENTER_EMERGENCY_CALLBACK_MODE";
-            case RIL_UNSOL_CDMA_CALL_WAITING:
-                return "UNSOL_CDMA_CALL_WAITING";
-            case RIL_UNSOL_CDMA_OTA_PROVISION_STATUS:
-                return "UNSOL_CDMA_OTA_PROVISION_STATUS";
-            case RIL_UNSOL_CDMA_INFO_REC:
-                return "UNSOL_CDMA_INFO_REC";
-            case RIL_UNSOL_OEM_HOOK_RAW:
-                return "UNSOL_OEM_HOOK_RAW";
-            case RIL_UNSOL_RINGBACK_TONE:
-                return "UNSOL_RINGBACK_TONE";
-            case RIL_UNSOL_RESEND_INCALL_MUTE:
-                return "UNSOL_RESEND_INCALL_MUTE";
-            case RIL_UNSOL_CDMA_SUBSCRIPTION_SOURCE_CHANGED:
-                return "UNSOL_CDMA_SUBSCRIPTION_SOURCE_CHANGED";
-            case RIL_UNSOl_CDMA_PRL_CHANGED:
-                return "UNSOL_CDMA_PRL_CHANGED";
-            case RIL_UNSOL_EXIT_EMERGENCY_CALLBACK_MODE:
-                return "UNSOL_EXIT_EMERGENCY_CALLBACK_MODE";
-            case RIL_UNSOL_RIL_CONNECTED:
-                return "UNSOL_RIL_CONNECTED";
-            case RIL_UNSOL_VOICE_RADIO_TECH_CHANGED:
-                return "UNSOL_VOICE_RADIO_TECH_CHANGED";
-            case RIL_UNSOL_CELL_INFO_LIST:
-                return "UNSOL_CELL_INFO_LIST";
-            case RIL_UNSOL_RESPONSE_IMS_NETWORK_STATE_CHANGED:
-                return "UNSOL_RESPONSE_IMS_NETWORK_STATE_CHANGED";
-            case RIL_UNSOL_UICC_SUBSCRIPTION_STATUS_CHANGED:
-                return "UNSOL_UICC_SUBSCRIPTION_STATUS_CHANGED";
-            case RIL_UNSOL_SRVCC_STATE_NOTIFY:
-                return "UNSOL_SRVCC_STATE_NOTIFY";
-            case RIL_UNSOL_HARDWARE_CONFIG_CHANGED:
-                return "UNSOL_HARDWARE_CONFIG_CHANGED";
-            case RIL_UNSOL_DC_RT_INFO_CHANGED:
-                return "UNSOL_DC_RT_INFO_CHANGED";
-            case RIL_UNSOL_RADIO_CAPABILITY:
-                return "UNSOL_RADIO_CAPABILITY";
-            case RIL_UNSOL_ON_SS:
-                return "UNSOL_ON_SS";
-            case RIL_UNSOL_STK_CC_ALPHA_NOTIFY:
-                return "UNSOL_STK_CC_ALPHA_NOTIFY";
-            case RIL_UNSOL_LCEDATA_RECV:
-                return "UNSOL_LCE_INFO_RECV";
-            case RIL_UNSOL_PCO_DATA:
-                return "UNSOL_PCO_DATA";
-            case RIL_UNSOL_MODEM_RESTART:
-                return "UNSOL_MODEM_RESTART";
-            case RIL_UNSOL_CARRIER_INFO_IMSI_ENCRYPTION:
-                return "UNSOL_CARRIER_INFO_IMSI_ENCRYPTION";
-            case RIL_UNSOL_NETWORK_SCAN_RESULT:
-                return "UNSOL_NETWORK_SCAN_RESULT";
-            case RIL_UNSOL_KEEPALIVE_STATUS:
-                return "UNSOL_KEEPALIVE_STATUS";
-            case RIL_UNSOL_UNTHROTTLE_APN:
-                return "UNSOL_UNTHROTTLE_APN";
-            case RIL_UNSOL_RESPONSE_SIM_PHONEBOOK_CHANGED:
-                return "UNSOL_RESPONSE_SIM_PHONEBOOK_CHANGED";
-            case RIL_UNSOL_RESPONSE_SIM_PHONEBOOK_RECORDS_RECEIVED:
-                return "UNSOL_RESPONSE_SIM_PHONEBOOK_RECORDS_RECEIVED";
-            case RIL_UNSOL_ICC_SLOT_STATUS:
-                return "UNSOL_ICC_SLOT_STATUS";
-            case RIL_UNSOL_PHYSICAL_CHANNEL_CONFIG:
-                return "UNSOL_PHYSICAL_CHANNEL_CONFIG";
-            case RIL_UNSOL_EMERGENCY_NUMBER_LIST:
-                return "UNSOL_EMERGENCY_NUMBER_LIST";
-            case RIL_UNSOL_UICC_APPLICATIONS_ENABLEMENT_CHANGED:
-                return "UNSOL_UICC_APPLICATIONS_ENABLEMENT_CHANGED";
-            case RIL_UNSOL_REGISTRATION_FAILED:
-                return "UNSOL_REGISTRATION_FAILED";
-            case RIL_UNSOL_BARRING_INFO_CHANGED:
-                return "UNSOL_BARRING_INFO_CHANGED";
-            default:
-                return "<unknown response>";
-        }
-    }
-
-    @UnsupportedAppUsage
     void riljLog(String msg) {
         Rlog.d(RILJ_LOG_TAG, msg + (" [PHONE" + mPhoneId + "]"));
     }
@@ -7238,32 +6081,30 @@
         Rlog.e(RILJ_LOG_TAG, msg + (" [PHONE" + mPhoneId + "]"));
     }
 
-    void riljLoge(String msg, Exception e) {
-        Rlog.e(RILJ_LOG_TAG, msg + (" [PHONE" + mPhoneId + "]"), e);
-    }
-
     void riljLogv(String msg) {
         Rlog.v(RILJ_LOG_TAG, msg + (" [PHONE" + mPhoneId + "]"));
     }
 
     @UnsupportedAppUsage
     void unsljLog(int response) {
-        riljLog("[UNSL]< " + responseToString(response));
+        riljLog("[UNSL]< " + RILUtils.responseToString(response));
     }
 
     @UnsupportedAppUsage
     void unsljLogMore(int response, String more) {
-        riljLog("[UNSL]< " + responseToString(response) + " " + more);
+        riljLog("[UNSL]< " + RILUtils.responseToString(response) + " " + more);
     }
 
     @UnsupportedAppUsage
     void unsljLogRet(int response, Object ret) {
-        riljLog("[UNSL]< " + responseToString(response) + " " + retToString(response, ret));
+        riljLog("[UNSL]< " + RILUtils.responseToString(response) + " "
+                + retToString(response, ret));
     }
 
     @UnsupportedAppUsage
     void unsljLogvRet(int response, Object ret) {
-        riljLogv("[UNSL]< " + responseToString(response) + " " + retToString(response, ret));
+        riljLogv("[UNSL]< " + RILUtils.responseToString(response) + " "
+                + retToString(response, ret));
     }
 
     @Override
@@ -7293,7 +6134,7 @@
             pw.println(" mRequestList count=" + count);
             for (int i = 0; i < count; i++) {
                 RILRequest rr = mRequestList.valueAt(i);
-                pw.println("  [" + rr.mSerial + "] " + requestToString(rr.mRequest));
+                pw.println("  [" + rr.mSerial + "] " + RILUtils.requestToString(rr.mRequest));
             }
         }
         pw.println(" mLastNITZTimeInfo=" + Arrays.toString(mLastNITZTimeInfo));
@@ -7306,180 +6147,6 @@
         return mClientWakelockTracker.getClientRequestStats();
     }
 
-    /** Append the data to the end of an ArrayList */
-    public static void appendPrimitiveArrayToArrayList(byte[] src, ArrayList<Byte> dst) {
-        for (byte b : src) {
-            dst.add(b);
-        }
-    }
-
-    public static ArrayList<Byte> primitiveArrayToArrayList(byte[] arr) {
-        ArrayList<Byte> arrayList = new ArrayList<>(arr.length);
-        for (byte b : arr) {
-            arrayList.add(b);
-        }
-        return arrayList;
-    }
-
-    /** Convert a primitive int array to an ArrayList<Integer>. */
-    public static ArrayList<Integer> primitiveArrayToArrayList(int[] arr) {
-        ArrayList<Integer> arrayList = new ArrayList<>(arr.length);
-        for (int i : arr) {
-            arrayList.add(i);
-        }
-        return arrayList;
-    }
-
-    /** Convert an ArrayList of Bytes to an exactly-sized primitive array */
-    public static byte[] arrayListToPrimitiveArray(ArrayList<Byte> bytes) {
-        byte[] ret = new byte[bytes.size()];
-        for (int i = 0; i < ret.length; i++) {
-            ret[i] = bytes.get(i);
-        }
-        return ret;
-    }
-
-    static ArrayList<HardwareConfig> convertHalHwConfigList(
-            ArrayList<android.hardware.radio.V1_0.HardwareConfig> hwListRil,
-            RIL ril) {
-        int num;
-        ArrayList<HardwareConfig> response;
-        HardwareConfig hw;
-
-        num = hwListRil.size();
-        response = new ArrayList<HardwareConfig>(num);
-
-        if (RILJ_LOGV) {
-            ril.riljLog("convertHalHwConfigList: num=" + num);
-        }
-        for (android.hardware.radio.V1_0.HardwareConfig hwRil : hwListRil) {
-            int type = hwRil.type;
-            switch(type) {
-                case HardwareConfig.DEV_HARDWARE_TYPE_MODEM: {
-                    hw = new HardwareConfig(type);
-                    HardwareConfigModem hwModem = hwRil.modem.get(0);
-                    hw.assignModem(hwRil.uuid, hwRil.state, hwModem.rilModel, hwModem.rat,
-                            hwModem.maxVoice, hwModem.maxData, hwModem.maxStandby);
-                    break;
-                }
-                case HardwareConfig.DEV_HARDWARE_TYPE_SIM: {
-                    hw = new HardwareConfig(type);
-                    hw.assignSim(hwRil.uuid, hwRil.state, hwRil.sim.get(0).modemUuid);
-                    break;
-                }
-                default: {
-                    throw new RuntimeException(
-                            "RIL_REQUEST_GET_HARDWARE_CONFIG invalid hardward type:" + type);
-                }
-            }
-
-            response.add(hw);
-        }
-
-        return response;
-    }
-
-    static RadioCapability convertHalRadioCapability(
-            android.hardware.radio.V1_0.RadioCapability rcRil, RIL ril) {
-        int session = rcRil.session;
-        int phase = rcRil.phase;
-        // convert to public bitmask {@link TelephonyManager.NetworkTypeBitMask}
-        int rat = convertToNetworkTypeBitMask(rcRil.raf);
-        String logicModemUuid = rcRil.logicalModemUuid;
-        int status = rcRil.status;
-
-        ril.riljLog("convertHalRadioCapability: session=" + session +
-                ", phase=" + phase +
-                ", rat=" + rat +
-                ", logicModemUuid=" + logicModemUuid +
-                ", status=" + status + ", rcRil.raf=" + rcRil.raf);
-        RadioCapability rc = new RadioCapability(
-                ril.mPhoneId, session, phase, rat, logicModemUuid, status);
-        return rc;
-    }
-
-    static List<LinkCapacityEstimate> convertHalLceData(LceDataInfo halData, RIL ril) {
-        final List<LinkCapacityEstimate> lceList = new ArrayList<>();
-        lceList.add(new LinkCapacityEstimate(LinkCapacityEstimate.LCE_TYPE_COMBINED,
-                halData.lastHopCapacityKbps,
-                LinkCapacityEstimate.INVALID));
-        ril.riljLog("LCE capacity information received:" + lceList);
-        return lceList;
-    }
-
-    static List<LinkCapacityEstimate> convertHalLceData(
-            android.hardware.radio.V1_2.LinkCapacityEstimate halData, RIL ril) {
-        final List<LinkCapacityEstimate> lceList = new ArrayList<>();
-        lceList.add(new LinkCapacityEstimate(LinkCapacityEstimate.LCE_TYPE_COMBINED,
-                halData.downlinkCapacityKbps,
-                halData.uplinkCapacityKbps));
-        ril.riljLog("LCE capacity information received:" + lceList);
-        return lceList;
-    }
-
-    static List<LinkCapacityEstimate> convertHalLceData(
-            android.hardware.radio.V1_6.LinkCapacityEstimate halData, RIL ril) {
-        final List<LinkCapacityEstimate> lceList = new ArrayList<>();
-        int primaryDownlinkCapacityKbps = halData.downlinkCapacityKbps;
-        int primaryUplinkCapacityKbps = halData.uplinkCapacityKbps;
-        if (primaryDownlinkCapacityKbps != LinkCapacityEstimate.INVALID
-                && halData.secondaryDownlinkCapacityKbps != LinkCapacityEstimate.INVALID) {
-            primaryDownlinkCapacityKbps =
-                    halData.downlinkCapacityKbps - halData.secondaryDownlinkCapacityKbps;
-        }
-        if (primaryUplinkCapacityKbps != LinkCapacityEstimate.INVALID
-                && halData.secondaryUplinkCapacityKbps != LinkCapacityEstimate.INVALID) {
-            primaryUplinkCapacityKbps =
-                    halData.uplinkCapacityKbps - halData.secondaryUplinkCapacityKbps;
-        }
-
-        lceList.add(new LinkCapacityEstimate(LinkCapacityEstimate.LCE_TYPE_PRIMARY,
-                primaryDownlinkCapacityKbps,
-                primaryUplinkCapacityKbps));
-        lceList.add(new LinkCapacityEstimate(LinkCapacityEstimate.LCE_TYPE_SECONDARY,
-                halData.secondaryDownlinkCapacityKbps,
-                halData.secondaryUplinkCapacityKbps));
-        ril.riljLog("LCE capacity information received:" + lceList);
-        return lceList;
-    }
-
-    /**
-     * Convert CellInfo defined in 1.0/types.hal to CellInfo type.
-     * @param records List of CellInfo defined in 1.0/types.hal
-     * @return List of converted CellInfo object
-     */
-    @VisibleForTesting
-    public static ArrayList<CellInfo> convertHalCellInfoList(
-            ArrayList<android.hardware.radio.V1_0.CellInfo> records) {
-        ArrayList<CellInfo> response = new ArrayList<CellInfo>(records.size());
-
-        final long nanotime = SystemClock.elapsedRealtimeNanos();
-        for (android.hardware.radio.V1_0.CellInfo record : records) {
-            record.timeStamp = nanotime;
-            response.add(CellInfo.create(record));
-        }
-
-        return response;
-    }
-
-    /**
-     * Convert CellInfo defined in 1.2/types.hal to CellInfo type.
-     * @param records List of CellInfo defined in 1.2/types.hal
-     * @return List of converted CellInfo object
-     */
-    @VisibleForTesting
-    public static ArrayList<CellInfo> convertHalCellInfoList_1_2(
-            ArrayList<android.hardware.radio.V1_2.CellInfo> records) {
-        ArrayList<CellInfo> response = new ArrayList<CellInfo>(records.size());
-
-        final long nanotime = SystemClock.elapsedRealtimeNanos();
-        for (android.hardware.radio.V1_2.CellInfo record : records) {
-            record.timeStamp = nanotime;
-            response.add(CellInfo.create(record));
-        }
-        return response;
-    }
-
     /**
      * Fixup for SignalStrength 1.0 to Assume GSM to WCDMA when
      * The current RAT type is one of the UMTS RATs.
@@ -7533,333 +6200,6 @@
     }
 
     /**
-     * Convert CellInfo defined in 1.4/types.hal to CellInfo type.
-     * @param records List of CellInfo defined in 1.4/types.hal.
-     * @return List of converted CellInfo object.
-     */
-    @VisibleForTesting
-    public static ArrayList<CellInfo> convertHalCellInfoList_1_4(
-            ArrayList<android.hardware.radio.V1_4.CellInfo> records) {
-        ArrayList<CellInfo> response = new ArrayList<CellInfo>(records.size());
-
-        final long nanotime = SystemClock.elapsedRealtimeNanos();
-        for (android.hardware.radio.V1_4.CellInfo record : records) {
-            response.add(CellInfo.create(record, nanotime));
-        }
-        return response;
-    }
-
-    /**
-     * Convert CellInfo defined in 1.5/types.hal to CellInfo type.
-     * @param records List of CellInfo defined in 1.5/types.hal.
-     * @return List of converted CellInfo object.
-     */
-    @VisibleForTesting
-    public static ArrayList<CellInfo> convertHalCellInfoList_1_5(
-            ArrayList<android.hardware.radio.V1_5.CellInfo> records) {
-        ArrayList<CellInfo> response = new ArrayList<>(records.size());
-
-        final long nanotime = SystemClock.elapsedRealtimeNanos();
-        for (android.hardware.radio.V1_5.CellInfo record : records) {
-            response.add(CellInfo.create(record, nanotime));
-        }
-        return response;
-    }
-
-    /**
-     * Convert CellInfo defined in 1.6/types.hal to CellInfo type.
-     * @param records List of CellInfo defined in 1.6/types.hal.
-     * @return List of converted CellInfo object.
-     */
-    @VisibleForTesting
-    public static ArrayList<CellInfo> convertHalCellInfoList_1_6(
-            ArrayList<android.hardware.radio.V1_6.CellInfo> records) {
-        ArrayList<CellInfo> response = new ArrayList<>(records.size());
-
-        final long nanotime = SystemClock.elapsedRealtimeNanos();
-        for (android.hardware.radio.V1_6.CellInfo record : records) {
-            response.add(CellInfo.create(record, nanotime));
-        }
-        return response;
-    }
-
-    private static LinkAddress createLinkAddressFromString(String addressString) {
-        return createLinkAddressFromString(addressString, 0, LinkAddress.LIFETIME_UNKNOWN,
-                LinkAddress.LIFETIME_UNKNOWN);
-    }
-
-    private static LinkAddress createLinkAddressFromString(String addressString, int properties,
-            long deprecationTime, long expirationTime) {
-        addressString = addressString.trim();
-        InetAddress address = null;
-        int prefixLength = -1;
-        try {
-            String[] pieces = addressString.split("/", 2);
-            address = InetAddresses.parseNumericAddress(pieces[0]);
-            if (pieces.length == 1) {
-                prefixLength = (address instanceof Inet4Address) ? 32 : 128;
-            } else if (pieces.length == 2) {
-                prefixLength = Integer.parseInt(pieces[1]);
-            }
-        } catch (NullPointerException e) {            // Null string.
-        } catch (ArrayIndexOutOfBoundsException e) {  // No prefix length.
-        } catch (NumberFormatException e) {           // Non-numeric prefix.
-        } catch (IllegalArgumentException e) {        // Invalid IP address.
-        }
-
-        if (address == null || prefixLength == -1) {
-            throw new IllegalArgumentException("Invalid link address " + addressString);
-        }
-
-        return new LinkAddress(address, prefixLength, properties, 0,
-                deprecationTime, expirationTime);
-    }
-
-    /**
-     * Convert SetupDataCallResult defined in 1.0, 1.4, 1.5 or 1.6 types.hal into DataCallResponse
-     * @param dcResult setup data call result
-     * @return converted DataCallResponse object
-     */
-    @VisibleForTesting
-    public static DataCallResponse convertDataCallResult(Object dcResult) {
-        if (dcResult == null) return null;
-
-        int cause, cid, active, mtu, mtuV4, mtuV6;
-        long suggestedRetryTime;
-        String ifname;
-        int protocolType;
-        String[] addresses = null;
-        String[] dnses = null;
-        String[] gateways = null;
-        String[] pcscfs = null;
-        Qos defaultQos = null;
-
-        @HandoverFailureMode
-        int handoverFailureMode = DataCallResponse.HANDOVER_FAILURE_MODE_LEGACY;
-
-        int pduSessionId = DataCallResponse.PDU_SESSION_ID_NOT_SET;
-
-        List<LinkAddress> laList = new ArrayList<>();
-        List<QosBearerSession> qosSessions = new ArrayList<>();
-        NetworkSliceInfo sliceInfo = null;
-        List<TrafficDescriptor> trafficDescriptors = new ArrayList<>();
-
-        if (dcResult instanceof android.hardware.radio.V1_0.SetupDataCallResult) {
-            final android.hardware.radio.V1_0.SetupDataCallResult result =
-                    (android.hardware.radio.V1_0.SetupDataCallResult) dcResult;
-            cause = result.status;
-            suggestedRetryTime = result.suggestedRetryTime;
-            cid = result.cid;
-            active = result.active;
-            protocolType = ApnSetting.getProtocolIntFromString(result.type);
-            ifname = result.ifname;
-            if (!TextUtils.isEmpty(result.addresses)) {
-                addresses = result.addresses.split("\\s+");
-            }
-            if (!TextUtils.isEmpty(result.dnses)) {
-                dnses = result.dnses.split("\\s+");
-            }
-            if (!TextUtils.isEmpty(result.gateways)) {
-                gateways = result.gateways.split("\\s+");
-            }
-            if (!TextUtils.isEmpty(result.pcscf)) {
-                pcscfs = result.pcscf.split("\\s+");
-            }
-            mtu = mtuV4 = mtuV6 = result.mtu;
-            if (addresses != null) {
-                for (String address : addresses) {
-                    laList.add(createLinkAddressFromString(address));
-                }
-            }
-        } else if (dcResult instanceof android.hardware.radio.V1_4.SetupDataCallResult) {
-            final android.hardware.radio.V1_4.SetupDataCallResult result =
-                    (android.hardware.radio.V1_4.SetupDataCallResult) dcResult;
-            cause = result.cause;
-            suggestedRetryTime = result.suggestedRetryTime;
-            cid = result.cid;
-            active = result.active;
-            protocolType = result.type;
-            ifname = result.ifname;
-            addresses = result.addresses.stream().toArray(String[]::new);
-            dnses = result.dnses.stream().toArray(String[]::new);
-            gateways = result.gateways.stream().toArray(String[]::new);
-            pcscfs = result.pcscf.stream().toArray(String[]::new);
-            mtu = mtuV4 = mtuV6 = result.mtu;
-            if (addresses != null) {
-                for (String address : addresses) {
-                    laList.add(createLinkAddressFromString(address));
-                }
-            }
-        } else if (dcResult instanceof android.hardware.radio.V1_5.SetupDataCallResult) {
-            final android.hardware.radio.V1_5.SetupDataCallResult result =
-                    (android.hardware.radio.V1_5.SetupDataCallResult) dcResult;
-            cause = result.cause;
-            suggestedRetryTime = result.suggestedRetryTime;
-            cid = result.cid;
-            active = result.active;
-            protocolType = result.type;
-            ifname = result.ifname;
-            laList = result.addresses.stream().map(la -> createLinkAddressFromString(
-                    la.address, la.properties, la.deprecationTime, la.expirationTime))
-                    .collect(Collectors.toList());
-
-            dnses = result.dnses.stream().toArray(String[]::new);
-            gateways = result.gateways.stream().toArray(String[]::new);
-            pcscfs = result.pcscf.stream().toArray(String[]::new);
-            mtu = Math.max(result.mtuV4, result.mtuV6);
-            mtuV4 = result.mtuV4;
-            mtuV6 = result.mtuV6;
-        } else if (dcResult instanceof android.hardware.radio.V1_6.SetupDataCallResult) {
-            final android.hardware.radio.V1_6.SetupDataCallResult result =
-                    (android.hardware.radio.V1_6.SetupDataCallResult) dcResult;
-            cause = result.cause;
-            suggestedRetryTime = result.suggestedRetryTime;
-            cid = result.cid;
-            active = result.active;
-            protocolType = result.type;
-            ifname = result.ifname;
-            laList = result.addresses.stream().map(la -> createLinkAddressFromString(
-                    la.address, la.properties, la.deprecationTime, la.expirationTime))
-                    .collect(Collectors.toList());
-            dnses = result.dnses.stream().toArray(String[]::new);
-            gateways = result.gateways.stream().toArray(String[]::new);
-            pcscfs = result.pcscf.stream().toArray(String[]::new);
-            mtu = Math.max(result.mtuV4, result.mtuV6);
-            mtuV4 = result.mtuV4;
-            mtuV6 = result.mtuV6;
-            handoverFailureMode = result.handoverFailureMode;
-            pduSessionId = result.pduSessionId;
-            defaultQos = Qos.create(result.defaultQos);
-            qosSessions = result.qosSessions.stream().map(session ->
-                    QosBearerSession.create(session)).collect(Collectors.toList());
-            sliceInfo = convertToSliceInfo(result.sliceInfo);
-            trafficDescriptors = result.trafficDescriptors.stream().map(td ->
-                    convertToTrafficDescriptor(td)).collect(Collectors.toList());
-        } else {
-            Rlog.e(RILJ_LOG_TAG, "Unsupported SetupDataCallResult " + dcResult);
-            return null;
-        }
-
-        // Process dns
-        List<InetAddress> dnsList = new ArrayList<>();
-        if (dnses != null) {
-            for (String dns : dnses) {
-                dns = dns.trim();
-                InetAddress ia;
-                try {
-                    ia = InetAddresses.parseNumericAddress(dns);
-                    dnsList.add(ia);
-                } catch (IllegalArgumentException e) {
-                    Rlog.e(RILJ_LOG_TAG, "Unknown dns: " + dns, e);
-                }
-            }
-        }
-
-        // Process gateway
-        List<InetAddress> gatewayList = new ArrayList<>();
-        if (gateways != null) {
-            for (String gateway : gateways) {
-                gateway = gateway.trim();
-                InetAddress ia;
-                try {
-                    ia = InetAddresses.parseNumericAddress(gateway);
-                    gatewayList.add(ia);
-                } catch (IllegalArgumentException e) {
-                    Rlog.e(RILJ_LOG_TAG, "Unknown gateway: " + gateway, e);
-                }
-            }
-        }
-
-        // Process gateway
-        List<InetAddress> pcscfList = new ArrayList<>();
-        if (pcscfs != null) {
-            for (String pcscf : pcscfs) {
-                pcscf = pcscf.trim();
-                InetAddress ia;
-                try {
-                    ia = InetAddresses.parseNumericAddress(pcscf);
-                    pcscfList.add(ia);
-                } catch (IllegalArgumentException e) {
-                    Rlog.e(RILJ_LOG_TAG, "Unknown pcscf: " + pcscf, e);
-                }
-            }
-        }
-
-        return new DataCallResponse.Builder()
-                .setCause(cause)
-                .setRetryDurationMillis(suggestedRetryTime)
-                .setId(cid)
-                .setLinkStatus(active)
-                .setProtocolType(protocolType)
-                .setInterfaceName(ifname)
-                .setAddresses(laList)
-                .setDnsAddresses(dnsList)
-                .setGatewayAddresses(gatewayList)
-                .setPcscfAddresses(pcscfList)
-                .setMtu(mtu)
-                .setMtuV4(mtuV4)
-                .setMtuV6(mtuV6)
-                .setHandoverFailureMode(handoverFailureMode)
-                .setPduSessionId(pduSessionId)
-                .setDefaultQos(defaultQos)
-                .setQosBearerSessions(qosSessions)
-                .setSliceInfo(sliceInfo)
-                .setTrafficDescriptors(trafficDescriptors)
-                .build();
-    }
-
-    private static NetworkSliceInfo convertToSliceInfo(OptionalSliceInfo optionalSliceInfo) {
-        if (optionalSliceInfo.getDiscriminator() == OptionalSliceInfo.hidl_discriminator.noinit) {
-            return null;
-        }
-
-        android.hardware.radio.V1_6.SliceInfo si = optionalSliceInfo.value();
-        NetworkSliceInfo.Builder builder =
-                new NetworkSliceInfo.Builder()
-                .setSliceServiceType(si.sst)
-                .setMappedHplmnSliceServiceType(si.mappedHplmnSst);
-        if (si.sliceDifferentiator != NetworkSliceInfo.SLICE_DIFFERENTIATOR_NO_SLICE) {
-            builder
-                .setSliceDifferentiator(si.sliceDifferentiator)
-                .setMappedHplmnSliceDifferentiator(si.mappedHplmnSD);
-        }
-        return builder.build();
-    }
-
-    private static TrafficDescriptor convertToTrafficDescriptor(
-            android.hardware.radio.V1_6.TrafficDescriptor td) {
-        String dnn = td.dnn.getDiscriminator() == OptionalDnn.hidl_discriminator.noinit
-                ? null : td.dnn.value();
-        String osAppId = td.osAppId.getDiscriminator() == OptionalOsAppId.hidl_discriminator.noinit
-                ? null : new String(arrayListToPrimitiveArray(td.osAppId.value().osAppId));
-        TrafficDescriptor.Builder builder = new TrafficDescriptor.Builder();
-        if (dnn != null) {
-            builder.setDataNetworkName(dnn);
-        }
-        if (osAppId != null) {
-            builder.setOsAppId(osAppId);
-        }
-        return builder.build();
-    }
-
-    /**
-     * Convert SetupDataCallResult defined in 1.0 or 1.4/types.hal into DataCallResponse
-     * @param dataCallResultList List of SetupDataCallResult defined in 1.0 or 1.4/types.hal
-     * @return List of converted DataCallResponse object
-     */
-    @VisibleForTesting
-    public static ArrayList<DataCallResponse> convertDataCallResultList(
-            List<? extends Object> dataCallResultList) {
-        ArrayList<DataCallResponse> response =
-                new ArrayList<DataCallResponse>(dataCallResultList.size());
-
-        for (Object obj : dataCallResultList) {
-            response.add(convertDataCallResult(obj));
-        }
-        return response;
-    }
-
-    /**
      * Get the HAL version.
      *
      * @return the current HalVersion
diff --git a/src/java/com/android/internal/telephony/RILRequest.java b/src/java/com/android/internal/telephony/RILRequest.java
index fff8de6..ca9531e 100644
--- a/src/java/com/android/internal/telephony/RILRequest.java
+++ b/src/java/com/android/internal/telephony/RILRequest.java
@@ -238,7 +238,7 @@
         final Message result = mResult;
         if (RIL.RILJ_LOGD) {
             Rlog.d(LOG_TAG, serialString() + "< "
-                    + RIL.requestToString(mRequest)
+                    + RILUtils.requestToString(mRequest)
                     + " error: " + ex + " ret=" + RIL.retToString(mRequest, ret)
                     + " result=" + result);
         }
diff --git a/src/java/com/android/internal/telephony/RILUtils.java b/src/java/com/android/internal/telephony/RILUtils.java
new file mode 100644
index 0000000..35c6eb1
--- /dev/null
+++ b/src/java/com/android/internal/telephony/RILUtils.java
@@ -0,0 +1,4442 @@
+/*
+ * 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.TelephonyManager.CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE;
+import static android.telephony.TelephonyManager.CAPABILITY_PHYSICAL_CHANNEL_CONFIG_1_6_SUPPORTED;
+import static android.telephony.TelephonyManager.CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE;
+import static android.telephony.TelephonyManager.CAPABILITY_SIM_PHONEBOOK_IN_MODEM;
+import static android.telephony.TelephonyManager.CAPABILITY_SLICING_CONFIG_SUPPORTED;
+import static android.telephony.TelephonyManager.CAPABILITY_THERMAL_MITIGATION_DATA_THROTTLING;
+import static android.telephony.TelephonyManager.CAPABILITY_USES_ALLOWED_NETWORK_TYPES_BITMASK;
+
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_ACKNOWLEDGE_INCOMING_GSM_SMS_WITH_PDU;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_ALLOCATE_PDU_SESSION_ID;
+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_HANDOVER;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_CANCEL_USSD;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_CDMA_BROADCAST_ACTIVATION;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_CDMA_BURST_DTMF;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_CDMA_DELETE_SMS_ON_RUIM;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_CDMA_FLASH;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_CDMA_GET_BROADCAST_CONFIG;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_CDMA_GET_SUBSCRIPTION_SOURCE;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_CDMA_QUERY_PREFERRED_VOICE_PRIVACY_MODE;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_CDMA_QUERY_ROAMING_PREFERENCE;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_CDMA_SEND_SMS;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_CDMA_SEND_SMS_EXPECT_MORE;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_CDMA_SET_BROADCAST_CONFIG;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_CDMA_SET_PREFERRED_VOICE_PRIVACY_MODE;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_CDMA_SET_ROAMING_PREFERENCE;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_CDMA_SET_SUBSCRIPTION_SOURCE;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_CDMA_SMS_ACKNOWLEDGE;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_CDMA_SUBSCRIPTION;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_CDMA_VALIDATE_AND_WRITE_AKEY;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_CDMA_WRITE_SMS_TO_RUIM;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_CHANGE_BARRING_PASSWORD;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_CHANGE_SIM_PIN;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_CHANGE_SIM_PIN2;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_CONFERENCE;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_DATA_CALL_LIST;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_DATA_REGISTRATION_STATE;
+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_DIAL;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_DTMF;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_DTMF_START;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_DTMF_STOP;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_EMERGENCY_DIAL;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_ENABLE_MODEM;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_ENABLE_NR_DUAL_CONNECTIVITY;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_ENABLE_UICC_APPLICATIONS;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_ENTER_NETWORK_DEPERSONALIZATION;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_ENTER_SIM_DEPERSONALIZATION;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_ENTER_SIM_PIN;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_ENTER_SIM_PIN2;
+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_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;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_ALLOWED_NETWORK_TYPES_BITMAP;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_BARRING_INFO;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_CELL_INFO_LIST;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_CLIR;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_CURRENT_CALLS;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_DC_RT_INFO;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_HAL_DEVICE_CAPABILITIES;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_HARDWARE_CONFIG;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_IMEI;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_IMEISV;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_IMSI;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_MODEM_STATUS;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_MUTE;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_NEIGHBORING_CELL_IDS;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_PHONE_CAPABILITY;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_PREFERRED_NETWORK_TYPE;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_RADIO_CAPABILITY;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_SIM_PHONEBOOK_CAPACITY;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_SIM_PHONEBOOK_RECORDS;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_SIM_STATUS;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_SLICING_CONFIG;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_SLOT_STATUS;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_SMSC_ADDRESS;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_SYSTEM_SELECTION_CHANNELS;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_UICC_APPLICATIONS_ENABLEMENT;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GSM_BROADCAST_ACTIVATION;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GSM_GET_BROADCAST_CONFIG;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GSM_SET_BROADCAST_CONFIG;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_HANGUP;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_HANGUP_FOREGROUND_RESUME_BACKGROUND;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_HANGUP_WAITING_OR_BACKGROUND;
+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_NR_DUAL_CONNECTIVITY_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;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_NV_READ_ITEM;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_NV_RESET_CONFIG;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_NV_WRITE_CDMA_PRL;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_NV_WRITE_ITEM;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_OEM_HOOK_RAW;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_OEM_HOOK_STRINGS;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_OPERATOR;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_PULL_LCEDATA;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_QUERY_AVAILABLE_BAND_MODE;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_QUERY_AVAILABLE_NETWORKS;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_QUERY_CALL_FORWARD_STATUS;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_QUERY_CALL_WAITING;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_QUERY_CLIP;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_QUERY_FACILITY_LOCK;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_QUERY_NETWORK_SELECTION_MODE;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_QUERY_TTY_MODE;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_RADIO_POWER;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_RELEASE_PDU_SESSION_ID;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_REPORT_SMS_MEMORY_STATUS;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_REPORT_STK_SERVICE_IS_RUNNING;
+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_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;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SEND_USSD;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SEPARATE_CONNECTION;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SETUP_DATA_CALL;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_ALLOWED_CARRIERS;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_ALLOWED_NETWORK_TYPES_BITMAP;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_BAND_MODE;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_CALL_FORWARD;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_CALL_WAITING;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_CARRIER_INFO_IMSI_ENCRYPTION;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_CLIR;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_DATA_PROFILE;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_DATA_THROTTLING;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_DC_RT_INFO_RATE;
+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_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_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_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;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_UICC_SUBSCRIPTION;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_UNSOLICITED_RESPONSE_FILTER;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_UNSOL_CELL_INFO_LIST_RATE;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SHUTDOWN;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SIGNAL_STRENGTH;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SIM_AUTHENTICATION;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SIM_CLOSE_CHANNEL;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SIM_IO;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SIM_OPEN_CHANNEL;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SIM_TRANSMIT_APDU_BASIC;
+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_KEEPALIVE;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_START_LCE;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_START_NETWORK_SCAN;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_STK_GET_PROFILE;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_STK_HANDLE_CALL_SETUP_REQUESTED_FROM_SIM;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_STK_SEND_ENVELOPE_COMMAND;
+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_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_UDUB;
+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;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_WRITE_SMS_TO_SIM;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_BARRING_INFO_CHANGED;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_CALL_RING;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_CARRIER_INFO_IMSI_ENCRYPTION;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_CDMA_CALL_WAITING;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_CDMA_INFO_REC;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_CDMA_OTA_PROVISION_STATUS;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_CDMA_PRL_CHANGED;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_CDMA_RUIM_SMS_STORAGE_FULL;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_CDMA_SUBSCRIPTION_SOURCE_CHANGED;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_CELL_INFO_LIST;
+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_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;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_HARDWARE_CONFIG_CHANGED;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_ICC_SLOT_STATUS;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_KEEPALIVE_STATUS;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_LCEDATA_RECV;
+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_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;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_ON_USSD_REQUEST;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_PCO_DATA;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_PHYSICAL_CHANNEL_CONFIG;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_RADIO_CAPABILITY;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_REGISTRATION_FAILED;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_RESEND_INCALL_MUTE;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_RESPONSE_CDMA_NEW_SMS;
+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_RESPONSE_NEW_BROADCAST_SMS;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_RESPONSE_NEW_SMS;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_RESPONSE_NEW_SMS_ON_SIM;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_RESPONSE_NEW_SMS_STATUS_REPORT;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_RESPONSE_RADIO_STATE_CHANGED;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_RESPONSE_SIM_PHONEBOOK_CHANGED;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_RESPONSE_SIM_PHONEBOOK_RECORDS_RECEIVED;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_RESPONSE_SIM_STATUS_CHANGED;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_RESTRICTED_STATE_CHANGED;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_RIL_CONNECTED;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_RINGBACK_TONE;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_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_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;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_STK_EVENT_NOTIFY;
+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_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.Nullable;
+import android.net.InetAddresses;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.os.SystemClock;
+import android.service.carrier.CarrierIdentifier;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.Annotation;
+import android.telephony.BarringInfo;
+import android.telephony.CellConfigLte;
+import android.telephony.CellIdentity;
+import android.telephony.CellIdentityCdma;
+import android.telephony.CellIdentityGsm;
+import android.telephony.CellIdentityLte;
+import android.telephony.CellIdentityNr;
+import android.telephony.CellIdentityTdscdma;
+import android.telephony.CellIdentityWcdma;
+import android.telephony.CellInfo;
+import android.telephony.CellInfoCdma;
+import android.telephony.CellInfoGsm;
+import android.telephony.CellInfoLte;
+import android.telephony.CellInfoNr;
+import android.telephony.CellInfoTdscdma;
+import android.telephony.CellInfoWcdma;
+import android.telephony.CellSignalStrength;
+import android.telephony.CellSignalStrengthCdma;
+import android.telephony.CellSignalStrengthGsm;
+import android.telephony.CellSignalStrengthLte;
+import android.telephony.CellSignalStrengthNr;
+import android.telephony.CellSignalStrengthTdscdma;
+import android.telephony.CellSignalStrengthWcdma;
+import android.telephony.ClosedSubscriberGroupInfo;
+import android.telephony.LinkCapacityEstimate;
+import android.telephony.ModemInfo;
+import android.telephony.PhoneCapability;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.PhysicalChannelConfig;
+import android.telephony.RadioAccessSpecifier;
+import android.telephony.ServiceState;
+import android.telephony.SignalStrength;
+import android.telephony.SignalThresholdInfo;
+import android.telephony.SmsManager;
+import android.telephony.TelephonyManager;
+import android.telephony.data.ApnSetting;
+import android.telephony.data.DataCallResponse;
+import android.telephony.data.DataProfile;
+import android.telephony.data.EpsQos;
+import android.telephony.data.NetworkSliceInfo;
+import android.telephony.data.NetworkSlicingConfig;
+import android.telephony.data.NrQos;
+import android.telephony.data.Qos;
+import android.telephony.data.QosBearerFilter;
+import android.telephony.data.QosBearerSession;
+import android.telephony.data.RouteSelectionDescriptor;
+import android.telephony.data.TrafficDescriptor;
+import android.telephony.data.UrspRule;
+import android.text.TextUtils;
+import android.util.ArraySet;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.cat.ComprehensionTlv;
+import com.android.internal.telephony.cat.ComprehensionTlvTag;
+import com.android.internal.telephony.cdma.SmsMessage;
+import com.android.internal.telephony.cdma.sms.CdmaSmsAddress;
+import com.android.internal.telephony.cdma.sms.CdmaSmsSubaddress;
+import com.android.internal.telephony.cdma.sms.SmsEnvelope;
+import com.android.internal.telephony.dataconnection.KeepaliveStatus;
+import com.android.internal.telephony.uicc.AdnCapacity;
+import com.android.internal.telephony.uicc.IccCardApplicationStatus;
+import com.android.internal.telephony.uicc.IccCardStatus;
+import com.android.internal.telephony.uicc.IccSlotStatus;
+import com.android.internal.telephony.uicc.IccUtils;
+import com.android.internal.telephony.uicc.SimPhonebookRecord;
+import com.android.telephony.Rlog;
+
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * Utils class for HAL <-> RIL conversions
+ */
+public class RILUtils {
+    private static final String LOG_TAG = "RILUtils";
+
+    // The number of required config values for broadcast SMS stored in RIL_CdmaBroadcastServiceInfo
+    public static final int CDMA_BSI_NO_OF_INTS_STRUCT = 3;
+    // The number of service categories for broadcast SMS
+    public static final int CDMA_BROADCAST_SMS_NO_OF_SERVICE_CATEGORIES = 31;
+
+    // Radio power failure UUIDs
+    public static final String RADIO_POWER_FAILURE_BUGREPORT_UUID =
+            "316f3801-fa21-4954-a42f-0041eada3b31";
+    public static final String RADIO_POWER_FAILURE_RF_HARDWARE_ISSUE_UUID =
+            "316f3801-fa21-4954-a42f-0041eada3b32";
+    public static final String RADIO_POWER_FAILURE_NO_RF_CALIBRATION_UUID =
+            "316f3801-fa21-4954-a42f-0041eada3b33";
+
+
+    /**
+     * Convert to PersoSubstate defined in radio/1.5/types.hal
+     * @param persoType PersoSubState type
+     * @return The converted PersoSubstate
+     */
+    public static int convertToHalPersoType(
+            IccCardApplicationStatus.PersoSubState persoType) {
+        switch (persoType) {
+            case PERSOSUBSTATE_IN_PROGRESS:
+                return android.hardware.radio.V1_5.PersoSubstate.IN_PROGRESS;
+            case  PERSOSUBSTATE_READY:
+                return android.hardware.radio.V1_5.PersoSubstate.READY;
+            case PERSOSUBSTATE_SIM_NETWORK:
+                return android.hardware.radio.V1_5.PersoSubstate.SIM_NETWORK;
+            case PERSOSUBSTATE_SIM_NETWORK_SUBSET:
+                return android.hardware.radio.V1_5.PersoSubstate.SIM_NETWORK_SUBSET;
+            case PERSOSUBSTATE_SIM_CORPORATE:
+                return android.hardware.radio.V1_5.PersoSubstate.SIM_CORPORATE;
+            case PERSOSUBSTATE_SIM_SERVICE_PROVIDER:
+                return android.hardware.radio.V1_5.PersoSubstate.SIM_SERVICE_PROVIDER;
+            case PERSOSUBSTATE_SIM_SIM:
+                return android.hardware.radio.V1_5.PersoSubstate.SIM_SIM;
+            case PERSOSUBSTATE_SIM_NETWORK_PUK:
+                return android.hardware.radio.V1_5.PersoSubstate.SIM_NETWORK_PUK;
+            case PERSOSUBSTATE_SIM_NETWORK_SUBSET_PUK:
+                return android.hardware.radio.V1_5.PersoSubstate.SIM_NETWORK_SUBSET_PUK;
+            case PERSOSUBSTATE_SIM_CORPORATE_PUK:
+                return android.hardware.radio.V1_5.PersoSubstate.SIM_CORPORATE_PUK;
+            case PERSOSUBSTATE_SIM_SERVICE_PROVIDER_PUK:
+                return android.hardware.radio.V1_5.PersoSubstate.SIM_SERVICE_PROVIDER_PUK;
+            case PERSOSUBSTATE_SIM_SIM_PUK:
+                return android.hardware.radio.V1_5.PersoSubstate.SIM_SIM_PUK;
+            case PERSOSUBSTATE_RUIM_NETWORK1:
+                return android.hardware.radio.V1_5.PersoSubstate.RUIM_NETWORK1;
+            case PERSOSUBSTATE_RUIM_NETWORK2:
+                return android.hardware.radio.V1_5.PersoSubstate.RUIM_NETWORK2;
+            case PERSOSUBSTATE_RUIM_HRPD:
+                return android.hardware.radio.V1_5.PersoSubstate.RUIM_HRPD;
+            case PERSOSUBSTATE_RUIM_CORPORATE:
+                return android.hardware.radio.V1_5.PersoSubstate.RUIM_CORPORATE;
+            case PERSOSUBSTATE_RUIM_SERVICE_PROVIDER:
+                return android.hardware.radio.V1_5.PersoSubstate.RUIM_SERVICE_PROVIDER;
+            case PERSOSUBSTATE_RUIM_RUIM:
+                return android.hardware.radio.V1_5.PersoSubstate.RUIM_RUIM;
+            case PERSOSUBSTATE_RUIM_NETWORK1_PUK:
+                return android.hardware.radio.V1_5.PersoSubstate.RUIM_NETWORK1_PUK;
+            case PERSOSUBSTATE_RUIM_NETWORK2_PUK:
+                return android.hardware.radio.V1_5.PersoSubstate.RUIM_NETWORK2_PUK;
+            case PERSOSUBSTATE_RUIM_HRPD_PUK:
+                return android.hardware.radio.V1_5.PersoSubstate.RUIM_HRPD_PUK;
+            case PERSOSUBSTATE_RUIM_CORPORATE_PUK:
+                return android.hardware.radio.V1_5.PersoSubstate.RUIM_CORPORATE_PUK;
+            case PERSOSUBSTATE_RUIM_SERVICE_PROVIDER_PUK:
+                return android.hardware.radio.V1_5.PersoSubstate.RUIM_SERVICE_PROVIDER_PUK;
+            case PERSOSUBSTATE_RUIM_RUIM_PUK:
+                return android.hardware.radio.V1_5.PersoSubstate.RUIM_RUIM_PUK;
+            case PERSOSUBSTATE_SIM_SPN:
+                return android.hardware.radio.V1_5.PersoSubstate.SIM_SPN;
+            case PERSOSUBSTATE_SIM_SPN_PUK:
+                return android.hardware.radio.V1_5.PersoSubstate.SIM_SPN_PUK;
+            case PERSOSUBSTATE_SIM_SP_EHPLMN:
+                return android.hardware.radio.V1_5.PersoSubstate.SIM_SP_EHPLMN;
+            case PERSOSUBSTATE_SIM_SP_EHPLMN_PUK:
+                return android.hardware.radio.V1_5.PersoSubstate.SIM_SP_EHPLMN_PUK;
+            case PERSOSUBSTATE_SIM_ICCID:
+                return android.hardware.radio.V1_5.PersoSubstate.SIM_ICCID;
+            case PERSOSUBSTATE_SIM_ICCID_PUK:
+                return android.hardware.radio.V1_5.PersoSubstate.SIM_ICCID_PUK;
+            case PERSOSUBSTATE_SIM_IMPI:
+                return android.hardware.radio.V1_5.PersoSubstate.SIM_IMPI;
+            case PERSOSUBSTATE_SIM_IMPI_PUK:
+                return android.hardware.radio.V1_5.PersoSubstate.SIM_IMPI_PUK;
+            case PERSOSUBSTATE_SIM_NS_SP:
+                return android.hardware.radio.V1_5.PersoSubstate.SIM_NS_SP;
+            case PERSOSUBSTATE_SIM_NS_SP_PUK:
+                return android.hardware.radio.V1_5.PersoSubstate.SIM_NS_SP_PUK;
+            default:
+                return android.hardware.radio.V1_5.PersoSubstate.UNKNOWN;
+        }
+    }
+
+    /**
+     * Convert to GsmSmsMessage defined in radio/1.0/types.hal
+     * @param smscPdu SMSD address
+     * @param pdu SMS in PDU format
+     * @return A converted GsmSmsMessage
+     */
+    public static android.hardware.radio.V1_0.GsmSmsMessage convertToHalGsmSmsMessage(
+            String smscPdu, String pdu) {
+        android.hardware.radio.V1_0.GsmSmsMessage msg =
+                new android.hardware.radio.V1_0.GsmSmsMessage();
+        msg.smscPdu = smscPdu == null ? "" : smscPdu;
+        msg.pdu = pdu == null ? "" : pdu;
+        return msg;
+    }
+
+    /**
+     * Convert to CdmaSmsMessage defined in radio/1.0/types.hal
+     * @param pdu SMS in PDU format
+     * @return A converted CdmaSmsMessage
+     */
+    public static android.hardware.radio.V1_0.CdmaSmsMessage convertToHalCdmaSmsMessage(
+            byte[] pdu) {
+        android.hardware.radio.V1_0.CdmaSmsMessage msg =
+                new android.hardware.radio.V1_0.CdmaSmsMessage();
+        int addrNbrOfDigits;
+        int subaddrNbrOfDigits;
+        int bearerDataLength;
+        ByteArrayInputStream bais = new ByteArrayInputStream(pdu);
+        DataInputStream dis = new DataInputStream(bais);
+
+        try {
+            msg.teleserviceId = dis.readInt(); // teleServiceId
+            msg.isServicePresent = (byte) dis.readInt() == 1; // servicePresent
+            msg.serviceCategory = dis.readInt(); // serviceCategory
+            msg.address.digitMode = dis.read();  // address digit mode
+            msg.address.numberMode = dis.read(); // address number mode
+            msg.address.numberType = dis.read(); // address number type
+            msg.address.numberPlan = dis.read(); // address number plan
+            addrNbrOfDigits = (byte) dis.read();
+            for (int i = 0; i < addrNbrOfDigits; i++) {
+                msg.address.digits.add(dis.readByte()); // address_orig_bytes[i]
+            }
+            msg.subAddress.subaddressType = dis.read(); //subaddressType
+            msg.subAddress.odd = (byte) dis.read() == 1; //subaddr odd
+            subaddrNbrOfDigits = (byte) dis.read();
+            for (int i = 0; i < subaddrNbrOfDigits; i++) {
+                msg.subAddress.digits.add(dis.readByte()); //subaddr_orig_bytes[i]
+            }
+
+            bearerDataLength = dis.read();
+            for (int i = 0; i < bearerDataLength; i++) {
+                msg.bearerData.add(dis.readByte()); //bearerData[i]
+            }
+        } catch (IOException ex) {
+        }
+        return msg;
+    }
+
+    /**
+     * Convert CdmaSmsMessage defined in radio/1.0/types.hal to SmsMessage
+     * Note only primitive fields are set
+     * @param cdmaSmsMessage CdmaSmsMessage defined in radio/1.0/types.hal
+     * @return A converted SmsMessage
+     */
+    public static SmsMessage convertHalCdmaSmsMessage(
+            android.hardware.radio.V1_0.CdmaSmsMessage cdmaSmsMessage) {
+        // Note: Parcel.readByte actually reads one Int and masks to byte
+        SmsEnvelope env = new SmsEnvelope();
+        CdmaSmsAddress addr = new CdmaSmsAddress();
+        CdmaSmsSubaddress subaddr = new CdmaSmsSubaddress();
+        byte[] data;
+        byte count;
+        int countInt;
+        int addressDigitMode;
+
+        //currently not supported by the modem-lib: env.mMessageType
+        env.teleService = cdmaSmsMessage.teleserviceId;
+
+        if (cdmaSmsMessage.isServicePresent) {
+            env.messageType = SmsEnvelope.MESSAGE_TYPE_BROADCAST;
+        } else {
+            if (SmsEnvelope.TELESERVICE_NOT_SET == env.teleService) {
+                // assume type ACK
+                env.messageType = SmsEnvelope.MESSAGE_TYPE_ACKNOWLEDGE;
+            } else {
+                env.messageType = SmsEnvelope.MESSAGE_TYPE_POINT_TO_POINT;
+            }
+        }
+        env.serviceCategory = cdmaSmsMessage.serviceCategory;
+
+        // address
+        addressDigitMode = cdmaSmsMessage.address.digitMode;
+        addr.digitMode = (byte) (0xFF & addressDigitMode);
+        addr.numberMode = (byte) (0xFF & cdmaSmsMessage.address.numberMode);
+        addr.ton = cdmaSmsMessage.address.numberType;
+        addr.numberPlan = (byte) (0xFF & cdmaSmsMessage.address.numberPlan);
+        count = (byte) cdmaSmsMessage.address.digits.size();
+        addr.numberOfDigits = count;
+        data = new byte[count];
+        for (int index = 0; index < count; index++) {
+            data[index] = cdmaSmsMessage.address.digits.get(index);
+
+            // convert the value if it is 4-bit DTMF to 8 bit
+            if (addressDigitMode == CdmaSmsAddress.DIGIT_MODE_4BIT_DTMF) {
+                data[index] = SmsMessage.convertDtmfToAscii(data[index]);
+            }
+        }
+
+        addr.origBytes = data;
+
+        subaddr.type = cdmaSmsMessage.subAddress.subaddressType;
+        subaddr.odd = (byte) (cdmaSmsMessage.subAddress.odd ? 1 : 0);
+        count = (byte) cdmaSmsMessage.subAddress.digits.size();
+
+        if (count < 0) {
+            count = 0;
+        }
+
+        // p_cur->sSubAddress.digits[digitCount] :
+
+        data = new byte[count];
+
+        for (int index = 0; index < count; ++index) {
+            data[index] = cdmaSmsMessage.subAddress.digits.get(index);
+        }
+
+        subaddr.origBytes = data;
+
+        /* currently not supported by the modem-lib:
+            env.bearerReply
+            env.replySeqNo
+            env.errorClass
+            env.causeCode
+        */
+
+        // bearer data
+        countInt = cdmaSmsMessage.bearerData.size();
+        if (countInt < 0) {
+            countInt = 0;
+        }
+
+        data = new byte[countInt];
+        for (int index = 0; index < countInt; index++) {
+            data[index] = cdmaSmsMessage.bearerData.get(index);
+        }
+        // BD gets further decoded when accessed in SMSDispatcher
+        env.bearerData = data;
+
+        // link the filled objects to the SMS
+        env.origAddress = addr;
+        env.origSubaddress = subaddr;
+
+        SmsMessage msg = new SmsMessage(addr, env);
+
+        return msg;
+    }
+
+    /**
+     * Convert CdmaSmsMessage defined in CdmaSmsMessage.aidl to SmsMessage
+     * Note only primitive fields are set
+     * @param msg CdmaSmsMessage defined in CdmaSmsMessage.aidl
+     * @return A converted SmsMessage
+     */
+    public static SmsMessage convertHalCdmaSmsMessage(
+            android.hardware.radio.messaging.CdmaSmsMessage msg) {
+        // Note: Parcel.readByte actually reads one Int and masks to byte
+        SmsEnvelope env = new SmsEnvelope();
+        CdmaSmsAddress addr = new CdmaSmsAddress();
+        CdmaSmsSubaddress subaddr = new CdmaSmsSubaddress();
+
+        // address
+        int addressDigitMode = msg.address.digitMode;
+        addr.digitMode = (byte) (0xFF & addressDigitMode);
+        addr.numberMode = (byte) (0xFF & (msg.address.isNumberModeDataNetwork ? 1 : 0));
+        addr.ton = msg.address.numberType;
+        addr.numberPlan = (byte) (0xFF & msg.address.numberPlan);
+        addr.numberOfDigits = msg.address.digits.length;
+        byte[] data = new byte[msg.address.digits.length];
+        for (int index = 0; index < data.length; index++) {
+            data[index] = msg.address.digits[index];
+            // convert the value if it is 4-bit DTMF to 8 bit
+            if (addressDigitMode == CdmaSmsAddress.DIGIT_MODE_4BIT_DTMF) {
+                data[index] = SmsMessage.convertDtmfToAscii(data[index]);
+            }
+        }
+        addr.origBytes = data;
+
+        // subaddress
+        subaddr.type = msg.subAddress.subaddressType;
+        subaddr.odd = (byte) (msg.subAddress.odd ? 1 : 0);
+        subaddr.origBytes = msg.subAddress.digits;
+
+        // envelope
+        // currently not supported by the modem-lib: env.bearerReply, env.replySeqNo,
+        // env.errorClass, env.causeCode, env.mMessageType
+        env.teleService = msg.teleserviceId;
+        if (msg.isServicePresent) {
+            env.messageType = SmsEnvelope.MESSAGE_TYPE_BROADCAST;
+        } else {
+            if (SmsEnvelope.TELESERVICE_NOT_SET == env.teleService) {
+                // assume type ACK
+                env.messageType = SmsEnvelope.MESSAGE_TYPE_ACKNOWLEDGE;
+            } else {
+                env.messageType = SmsEnvelope.MESSAGE_TYPE_POINT_TO_POINT;
+            }
+        }
+        env.serviceCategory = msg.serviceCategory;
+
+        // bearer data is further decoded when accessed in SmsDispatcher
+        env.bearerData = msg.bearerData;
+
+        // link the filled objects to the SMS
+        env.origAddress = addr;
+        env.origSubaddress = subaddr;
+
+        return new SmsMessage(addr, env);
+    }
+
+    /**
+     * Convert to DataProfileInfo defined in radio/1.0/types.hal
+     * @param dp Data profile
+     * @return The converted DataProfileInfo
+     */
+    public static android.hardware.radio.V1_0.DataProfileInfo convertToHalDataProfile10(
+            DataProfile dp) {
+        android.hardware.radio.V1_0.DataProfileInfo dpi =
+                new android.hardware.radio.V1_0.DataProfileInfo();
+
+        dpi.profileId = dp.getProfileId();
+        dpi.apn = dp.getApn();
+        dpi.protocol = ApnSetting.getProtocolStringFromInt(dp.getProtocolType());
+        dpi.roamingProtocol = ApnSetting.getProtocolStringFromInt(dp.getRoamingProtocolType());
+        dpi.authType = dp.getAuthType();
+        dpi.user = TextUtils.emptyIfNull(dp.getUserName());
+        dpi.password = TextUtils.emptyIfNull(dp.getPassword());
+        dpi.type = dp.getType();
+        dpi.maxConnsTime = dp.getMaxConnectionsTime();
+        dpi.maxConns = dp.getMaxConnections();
+        dpi.waitTime = dp.getWaitTime();
+        dpi.enabled = dp.isEnabled();
+        dpi.supportedApnTypesBitmap = dp.getSupportedApnTypesBitmask();
+        // Shift by 1 bit due to the discrepancy between
+        // android.hardware.radio.V1_0.RadioAccessFamily and the bitmask version of
+        // ServiceState.RIL_RADIO_TECHNOLOGY_XXXX.
+        dpi.bearerBitmap = ServiceState.convertNetworkTypeBitmaskToBearerBitmask(
+                dp.getBearerBitmask()) << 1;
+        dpi.mtu = dp.getMtuV4();
+        dpi.mvnoType = android.hardware.radio.V1_0.MvnoType.NONE;
+        dpi.mvnoMatchData = "";
+
+        return dpi;
+    }
+
+    /**
+     * Convert to DataProfileInfo defined in radio/1.4/types.hal
+     * @param dp Data profile
+     * @return The converted DataProfileInfo
+     */
+    public static android.hardware.radio.V1_4.DataProfileInfo convertToHalDataProfile14(
+            DataProfile dp) {
+        android.hardware.radio.V1_4.DataProfileInfo dpi =
+                new android.hardware.radio.V1_4.DataProfileInfo();
+
+        dpi.apn = dp.getApn();
+        dpi.protocol = dp.getProtocolType();
+        dpi.roamingProtocol = dp.getRoamingProtocolType();
+        dpi.authType = dp.getAuthType();
+        dpi.user = TextUtils.emptyIfNull(dp.getUserName());
+        dpi.password = TextUtils.emptyIfNull(dp.getPassword());
+        dpi.type = dp.getType();
+        dpi.maxConnsTime = dp.getMaxConnectionsTime();
+        dpi.maxConns = dp.getMaxConnections();
+        dpi.waitTime = dp.getWaitTime();
+        dpi.enabled = dp.isEnabled();
+        dpi.supportedApnTypesBitmap = dp.getSupportedApnTypesBitmask();
+        // Shift by 1 bit due to the discrepancy between
+        // android.hardware.radio.V1_0.RadioAccessFamily and the bitmask version of
+        // ServiceState.RIL_RADIO_TECHNOLOGY_XXXX.
+        dpi.bearerBitmap = ServiceState.convertNetworkTypeBitmaskToBearerBitmask(
+                dp.getBearerBitmask()) << 1;
+        dpi.mtu = dp.getMtuV4();
+        dpi.persistent = dp.isPersistent();
+        dpi.preferred = dp.isPreferred();
+
+        // profile id is only meaningful when it's persistent on the modem.
+        dpi.profileId = (dpi.persistent) ? dp.getProfileId()
+                : android.hardware.radio.V1_0.DataProfileId.INVALID;
+
+        return dpi;
+    }
+
+    /**
+     * Convert to DataProfileInfo defined in radio/1.5/types.hal
+     * @param dp Data profile
+     * @return The converted DataProfileInfo
+     */
+    public static android.hardware.radio.V1_5.DataProfileInfo convertToHalDataProfile15(
+            DataProfile dp) {
+        android.hardware.radio.V1_5.DataProfileInfo dpi =
+                new android.hardware.radio.V1_5.DataProfileInfo();
+
+        dpi.apn = dp.getApn();
+        dpi.protocol = dp.getProtocolType();
+        dpi.roamingProtocol = dp.getRoamingProtocolType();
+        dpi.authType = dp.getAuthType();
+        dpi.user = TextUtils.emptyIfNull(dp.getUserName());
+        dpi.password = TextUtils.emptyIfNull(dp.getPassword());
+        dpi.type = dp.getType();
+        dpi.maxConnsTime = dp.getMaxConnectionsTime();
+        dpi.maxConns = dp.getMaxConnections();
+        dpi.waitTime = dp.getWaitTime();
+        dpi.enabled = dp.isEnabled();
+        dpi.supportedApnTypesBitmap = dp.getSupportedApnTypesBitmask();
+        // Shift by 1 bit due to the discrepancy between
+        // android.hardware.radio.V1_0.RadioAccessFamily and the bitmask version of
+        // ServiceState.RIL_RADIO_TECHNOLOGY_XXXX.
+        dpi.bearerBitmap = ServiceState.convertNetworkTypeBitmaskToBearerBitmask(
+                dp.getBearerBitmask()) << 1;
+        dpi.mtuV4 = dp.getMtuV4();
+        dpi.mtuV6 = dp.getMtuV6();
+        dpi.persistent = dp.isPersistent();
+        dpi.preferred = dp.isPreferred();
+
+        // profile id is only meaningful when it's persistent on the modem.
+        dpi.profileId = (dpi.persistent) ? dp.getProfileId()
+                : android.hardware.radio.V1_0.DataProfileId.INVALID;
+
+        return dpi;
+    }
+
+    /**
+     * Convert to OptionalSliceInfo defined in radio/1.6/types.hal
+     * @param sliceInfo Slice info
+     * @return The converted OptionalSliceInfo
+     */
+    public static android.hardware.radio.V1_6.OptionalSliceInfo convertToHalSliceInfo(
+            @Nullable NetworkSliceInfo sliceInfo) {
+        android.hardware.radio.V1_6.OptionalSliceInfo optionalSliceInfo =
+                new android.hardware.radio.V1_6.OptionalSliceInfo();
+        if (sliceInfo == null) {
+            return optionalSliceInfo;
+        }
+
+        android.hardware.radio.V1_6.SliceInfo si = new android.hardware.radio.V1_6.SliceInfo();
+        si.sst = (byte) sliceInfo.getSliceServiceType();
+        si.mappedHplmnSst = (byte) sliceInfo.getMappedHplmnSliceServiceType();
+        si.sliceDifferentiator = sliceInfo.getSliceDifferentiator();
+        si.mappedHplmnSD = sliceInfo.getMappedHplmnSliceDifferentiator();
+        optionalSliceInfo.value(si);
+        return optionalSliceInfo;
+    }
+
+    /**
+     * Convert to OptionalTrafficDescriptor defined in radio/1.6/types.hal
+     * @param trafficDescriptor Traffic descriptor
+     * @return The converted OptionalTrafficDescriptor
+     */
+    public static android.hardware.radio.V1_6.OptionalTrafficDescriptor
+            convertToHalTrafficDescriptor(@Nullable TrafficDescriptor trafficDescriptor) {
+        android.hardware.radio.V1_6.OptionalTrafficDescriptor optionalTrafficDescriptor =
+                new android.hardware.radio.V1_6.OptionalTrafficDescriptor();
+        if (trafficDescriptor == null) {
+            return optionalTrafficDescriptor;
+        }
+
+        android.hardware.radio.V1_6.TrafficDescriptor td =
+                new android.hardware.radio.V1_6.TrafficDescriptor();
+
+        android.hardware.radio.V1_6.OptionalDnn optionalDnn =
+                new android.hardware.radio.V1_6.OptionalDnn();
+        if (trafficDescriptor.getDataNetworkName() != null) {
+            optionalDnn.value(trafficDescriptor.getDataNetworkName());
+        }
+        td.dnn = optionalDnn;
+
+        android.hardware.radio.V1_6.OptionalOsAppId optionalOsAppId =
+                new android.hardware.radio.V1_6.OptionalOsAppId();
+        if (trafficDescriptor.getOsAppId() != null) {
+            android.hardware.radio.V1_6.OsAppId osAppId = new android.hardware.radio.V1_6.OsAppId();
+            osAppId.osAppId = primitiveArrayToArrayList(trafficDescriptor.getOsAppId());
+            optionalOsAppId.value(osAppId);
+        }
+        td.osAppId = optionalOsAppId;
+
+        optionalTrafficDescriptor.value(td);
+        return optionalTrafficDescriptor;
+    }
+
+    /**
+     * Convert to ResetNvType defined in radio/1.0/types.hal
+     * @param resetType NV reset type
+     * @return The converted reset type in integer or -1 if param is invalid
+     */
+    public static int convertToHalResetNvType(int resetType) {
+        /**
+         * resetType values
+         * 1 - reload all NV items
+         * 2 - erase NV reset (SCRTN)
+         * 3 - factory reset (RTN)
+         */
+        switch (resetType) {
+            case 1: return android.hardware.radio.V1_0.ResetNvType.RELOAD;
+            case 2: return android.hardware.radio.V1_0.ResetNvType.ERASE;
+            case 3: return android.hardware.radio.V1_0.ResetNvType.FACTORY_RESET;
+        }
+        return -1;
+    }
+
+    /**
+     * Convert to a list of LinkAddress defined in radio/1.5/types.hal
+     * @param linkProperties Link properties
+     * @return The converted list of LinkAddresses
+     */
+    public static ArrayList<android.hardware.radio.V1_5.LinkAddress> convertToHalLinkProperties15(
+            LinkProperties linkProperties) {
+        ArrayList<android.hardware.radio.V1_5.LinkAddress> addresses15 = new ArrayList<>();
+        if (linkProperties != null) {
+            for (android.net.LinkAddress la : linkProperties.getAllLinkAddresses()) {
+                android.hardware.radio.V1_5.LinkAddress linkAddress =
+                        new android.hardware.radio.V1_5.LinkAddress();
+                linkAddress.address = la.getAddress().getHostAddress();
+                linkAddress.properties = la.getFlags();
+                linkAddress.deprecationTime = la.getDeprecationTime();
+                linkAddress.expirationTime = la.getExpirationTime();
+                addresses15.add(linkAddress);
+            }
+        }
+        return addresses15;
+    }
+
+    /**
+     * Convert RadioAccessSpecifier defined in radio/1.5/types.hal to RadioAccessSpecifier
+     * @param specifier RadioAccessSpecifier defined in radio/1.5/types.hal
+     * @return The converted RadioAccessSpecifier
+     */
+    public static RadioAccessSpecifier convertHalRadioAccessSpecifier(
+            android.hardware.radio.V1_5.RadioAccessSpecifier specifier) {
+        if (specifier == null) return null;
+        ArrayList<Integer> halBands = new ArrayList<>();
+        switch (specifier.bands.getDiscriminator()) {
+            case android.hardware.radio.V1_5.RadioAccessSpecifier.Bands.hidl_discriminator
+                    .geranBands:
+                halBands = specifier.bands.geranBands();
+                break;
+            case android.hardware.radio.V1_5.RadioAccessSpecifier.Bands.hidl_discriminator
+                    .utranBands:
+                halBands = specifier.bands.utranBands();
+                break;
+            case android.hardware.radio.V1_5.RadioAccessSpecifier.Bands.hidl_discriminator
+                    .eutranBands:
+                halBands = specifier.bands.eutranBands();
+                break;
+            case android.hardware.radio.V1_5.RadioAccessSpecifier.Bands.hidl_discriminator
+                    .ngranBands:
+                halBands = specifier.bands.ngranBands();
+                break;
+        }
+        return new RadioAccessSpecifier(convertHalRadioAccessNetworks(specifier.radioAccessNetwork),
+                halBands.stream().mapToInt(Integer::intValue).toArray(),
+                specifier.channels.stream().mapToInt(Integer::intValue).toArray());
+    }
+
+    /**
+     * Convert RadioAccessSpecifier defined in RadioAccessSpecifier.aidl to RadioAccessSpecifier
+     * @param specifier RadioAccessSpecifier defined in RadioAccessSpecifier.aidl
+     * @return The converted RadioAccessSpecifier
+     */
+    public static RadioAccessSpecifier convertHalRadioAccessSpecifier(
+            android.hardware.radio.network.RadioAccessSpecifier specifier) {
+        if (specifier == null) return null;
+        int[] halBands = null;
+        switch (specifier.bands.getTag()) {
+            case android.hardware.radio.network.RadioAccessSpecifierBands.geranBands:
+                halBands = specifier.bands.getGeranBands();
+                break;
+            case android.hardware.radio.network.RadioAccessSpecifierBands.utranBands:
+                halBands = specifier.bands.getUtranBands();
+                break;
+            case android.hardware.radio.network.RadioAccessSpecifierBands.eutranBands:
+                halBands = specifier.bands.getEutranBands();
+                break;
+            case android.hardware.radio.network.RadioAccessSpecifierBands.ngranBands:
+                halBands = specifier.bands.getNgranBands();
+                break;
+        }
+        return new RadioAccessSpecifier(specifier.accessNetwork, halBands, specifier.channels);
+    }
+
+    /**
+     * Convert to RadioAccessSpecifier defined in radio/1.1/types.hal
+     * @param ras Radio access specifier
+     * @return The converted RadioAccessSpecifier
+     */
+    public static android.hardware.radio.V1_1.RadioAccessSpecifier
+            convertToHalRadioAccessSpecifier11(RadioAccessSpecifier ras) {
+        android.hardware.radio.V1_1.RadioAccessSpecifier rasInHalFormat =
+                new android.hardware.radio.V1_1.RadioAccessSpecifier();
+        rasInHalFormat.radioAccessNetwork = ras.getRadioAccessNetwork();
+        ArrayList<Integer> bands = new ArrayList<>();
+        if (ras.getBands() != null) {
+            for (int band : ras.getBands()) {
+                bands.add(band);
+            }
+        }
+        switch (ras.getRadioAccessNetwork()) {
+            case AccessNetworkConstants.AccessNetworkType.GERAN:
+                rasInHalFormat.geranBands = bands;
+                break;
+            case AccessNetworkConstants.AccessNetworkType.UTRAN:
+                rasInHalFormat.utranBands = bands;
+                break;
+            case AccessNetworkConstants.AccessNetworkType.EUTRAN:
+                rasInHalFormat.eutranBands = bands;
+                break;
+            default:
+                return null;
+        }
+
+        if (ras.getChannels() != null) {
+            for (int channel : ras.getChannels()) {
+                rasInHalFormat.channels.add(channel);
+            }
+        }
+
+        return rasInHalFormat;
+    }
+
+    /**
+     * Convert to RadioAccessSpecifier defined in radio/1.5/types.hal
+     * @param ras Radio access specifier
+     * @return The converted RadioAccessSpecifier
+     */
+    public static android.hardware.radio.V1_5.RadioAccessSpecifier
+            convertToHalRadioAccessSpecifier15(RadioAccessSpecifier ras) {
+        android.hardware.radio.V1_5.RadioAccessSpecifier rasInHalFormat =
+                new android.hardware.radio.V1_5.RadioAccessSpecifier();
+        android.hardware.radio.V1_5.RadioAccessSpecifier.Bands bandsInHalFormat =
+                new android.hardware.radio.V1_5.RadioAccessSpecifier.Bands();
+        rasInHalFormat.radioAccessNetwork = convertToHalRadioAccessNetworks(
+                ras.getRadioAccessNetwork());
+        ArrayList<Integer> bands = new ArrayList<>();
+        if (ras.getBands() != null) {
+            for (int band : ras.getBands()) {
+                bands.add(band);
+            }
+        }
+        switch (ras.getRadioAccessNetwork()) {
+            case AccessNetworkConstants.AccessNetworkType.GERAN:
+                bandsInHalFormat.geranBands(bands);
+                break;
+            case AccessNetworkConstants.AccessNetworkType.UTRAN:
+                bandsInHalFormat.utranBands(bands);
+                break;
+            case AccessNetworkConstants.AccessNetworkType.EUTRAN:
+                bandsInHalFormat.eutranBands(bands);
+                break;
+            case AccessNetworkConstants.AccessNetworkType.NGRAN:
+                bandsInHalFormat.ngranBands(bands);
+                break;
+            default:
+                return null;
+        }
+        rasInHalFormat.bands = bandsInHalFormat;
+
+        if (ras.getChannels() != null) {
+            for (int channel : ras.getChannels()) {
+                rasInHalFormat.channels.add(channel);
+            }
+        }
+
+        return rasInHalFormat;
+    }
+
+    /**
+     * Convert to censored terminal response
+     * @param terminalResponse Terminal response
+     * @return The converted censored terminal response
+     */
+    public static String convertToCensoredTerminalResponse(String terminalResponse) {
+        try {
+            byte[] bytes = IccUtils.hexStringToBytes(terminalResponse);
+            if (bytes != null) {
+                List<ComprehensionTlv> ctlvs = ComprehensionTlv.decodeMany(bytes, 0);
+                int from = 0;
+                for (ComprehensionTlv ctlv : ctlvs) {
+                    // Find text strings which might be personal information input by user,
+                    // then replace it with "********".
+                    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(), "********");
+                    }
+                    // The text string tag and the length field should also be hidden.
+                    from = ctlv.getValueIndex() + ctlv.getLength();
+                }
+            }
+        } catch (Exception e) {
+            terminalResponse = null;
+        }
+
+        return terminalResponse;
+    }
+
+    /**
+     * Convert to {@link TelephonyManager.NetworkTypeBitMask}, the bitmask represented by
+     * {@link android.telephony.Annotation.NetworkType}.
+     *
+     * @param raf {@link android.hardware.radio.V1_0.RadioAccessFamily}
+     * @return {@link TelephonyManager.NetworkTypeBitMask}
+     */
+    @TelephonyManager.NetworkTypeBitMask
+    public static int convertHalNetworkTypeBitMask(int raf) {
+        int networkTypeRaf = 0;
+
+        if ((raf & android.hardware.radio.V1_0.RadioAccessFamily.GSM) != 0) {
+            networkTypeRaf |= TelephonyManager.NETWORK_TYPE_BITMASK_GSM;
+        }
+        if ((raf & android.hardware.radio.V1_0.RadioAccessFamily.GPRS) != 0) {
+            networkTypeRaf |= TelephonyManager.NETWORK_TYPE_BITMASK_GPRS;
+        }
+        if ((raf & android.hardware.radio.V1_0.RadioAccessFamily.EDGE) != 0) {
+            networkTypeRaf |= TelephonyManager.NETWORK_TYPE_BITMASK_EDGE;
+        }
+        // convert both IS95A/IS95B to CDMA as network mode doesn't support CDMA
+        if ((raf & android.hardware.radio.V1_0.RadioAccessFamily.IS95A) != 0) {
+            networkTypeRaf |= TelephonyManager.NETWORK_TYPE_BITMASK_CDMA;
+        }
+        if ((raf & android.hardware.radio.V1_0.RadioAccessFamily.IS95B) != 0) {
+            networkTypeRaf |= TelephonyManager.NETWORK_TYPE_BITMASK_CDMA;
+        }
+        if ((raf & android.hardware.radio.V1_0.RadioAccessFamily.ONE_X_RTT) != 0) {
+            networkTypeRaf |= TelephonyManager.NETWORK_TYPE_BITMASK_1xRTT;
+        }
+        if ((raf & android.hardware.radio.V1_0.RadioAccessFamily.EVDO_0) != 0) {
+            networkTypeRaf |= TelephonyManager.NETWORK_TYPE_BITMASK_EVDO_0;
+        }
+        if ((raf & android.hardware.radio.V1_0.RadioAccessFamily.EVDO_A) != 0) {
+            networkTypeRaf |= TelephonyManager.NETWORK_TYPE_BITMASK_EVDO_A;
+        }
+        if ((raf & android.hardware.radio.V1_0.RadioAccessFamily.EVDO_B) != 0) {
+            networkTypeRaf |= TelephonyManager.NETWORK_TYPE_BITMASK_EVDO_B;
+        }
+        if ((raf & android.hardware.radio.V1_0.RadioAccessFamily.EHRPD) != 0) {
+            networkTypeRaf |= TelephonyManager.NETWORK_TYPE_BITMASK_EHRPD;
+        }
+        if ((raf & android.hardware.radio.V1_0.RadioAccessFamily.HSUPA) != 0) {
+            networkTypeRaf |= TelephonyManager.NETWORK_TYPE_BITMASK_HSUPA;
+        }
+        if ((raf & android.hardware.radio.V1_0.RadioAccessFamily.HSDPA) != 0) {
+            networkTypeRaf |= TelephonyManager.NETWORK_TYPE_BITMASK_HSDPA;
+        }
+        if ((raf & android.hardware.radio.V1_0.RadioAccessFamily.HSPA) != 0) {
+            networkTypeRaf |= TelephonyManager.NETWORK_TYPE_BITMASK_HSPA;
+        }
+        if ((raf & android.hardware.radio.V1_0.RadioAccessFamily.HSPAP) != 0) {
+            networkTypeRaf |= TelephonyManager.NETWORK_TYPE_BITMASK_HSPAP;
+        }
+        if ((raf & android.hardware.radio.V1_0.RadioAccessFamily.UMTS) != 0) {
+            networkTypeRaf |= TelephonyManager.NETWORK_TYPE_BITMASK_UMTS;
+        }
+        if ((raf & android.hardware.radio.V1_0.RadioAccessFamily.TD_SCDMA) != 0) {
+            networkTypeRaf |= TelephonyManager.NETWORK_TYPE_BITMASK_TD_SCDMA;
+        }
+        if ((raf & android.hardware.radio.V1_0.RadioAccessFamily.LTE) != 0) {
+            networkTypeRaf |= TelephonyManager.NETWORK_TYPE_BITMASK_LTE;
+        }
+        if ((raf & android.hardware.radio.V1_0.RadioAccessFamily.LTE_CA) != 0) {
+            networkTypeRaf |= TelephonyManager.NETWORK_TYPE_BITMASK_LTE_CA;
+        }
+        if ((raf & android.hardware.radio.V1_4.RadioAccessFamily.NR) != 0) {
+            networkTypeRaf |= TelephonyManager.NETWORK_TYPE_BITMASK_NR;
+        }
+        // TODO: need hal definition
+        if ((raf & (1 << ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN)) != 0) {
+            networkTypeRaf |= TelephonyManager.NETWORK_TYPE_BITMASK_IWLAN;
+        }
+        return (networkTypeRaf == 0) ? TelephonyManager.NETWORK_TYPE_UNKNOWN : networkTypeRaf;
+    }
+
+    /**
+     * Convert to RadioAccessFamily defined in radio/1.4/types.hal
+     * @param networkTypeBitmask {@link TelephonyManager.NetworkTypeBitMask}, the bitmask
+     *        represented by {@link android.telephony.Annotation.NetworkType}
+     * @return The converted RadioAccessFamily
+     */
+    public static int convertToHalRadioAccessFamily(
+            @TelephonyManager.NetworkTypeBitMask int networkTypeBitmask) {
+        int raf = 0;
+
+        if ((networkTypeBitmask & TelephonyManager.NETWORK_TYPE_BITMASK_GSM) != 0) {
+            raf |= android.hardware.radio.V1_0.RadioAccessFamily.GSM;
+        }
+        if ((networkTypeBitmask & TelephonyManager.NETWORK_TYPE_BITMASK_GPRS) != 0) {
+            raf |= android.hardware.radio.V1_0.RadioAccessFamily.GPRS;
+        }
+        if ((networkTypeBitmask & TelephonyManager.NETWORK_TYPE_BITMASK_EDGE) != 0) {
+            raf |= android.hardware.radio.V1_0.RadioAccessFamily.EDGE;
+        }
+        // convert CDMA to IS95A, consistent with ServiceState.networkTypeToRilRadioTechnology
+        if ((networkTypeBitmask & TelephonyManager.NETWORK_TYPE_BITMASK_CDMA) != 0) {
+            raf |= android.hardware.radio.V1_0.RadioAccessFamily.IS95A;
+        }
+        if ((networkTypeBitmask & TelephonyManager.NETWORK_TYPE_BITMASK_1xRTT) != 0) {
+            raf |= android.hardware.radio.V1_0.RadioAccessFamily.ONE_X_RTT;
+        }
+        if ((networkTypeBitmask & TelephonyManager.NETWORK_TYPE_BITMASK_EVDO_0) != 0) {
+            raf |= android.hardware.radio.V1_0.RadioAccessFamily.EVDO_0;
+        }
+        if ((networkTypeBitmask & TelephonyManager.NETWORK_TYPE_BITMASK_EVDO_A) != 0) {
+            raf |= android.hardware.radio.V1_0.RadioAccessFamily.EVDO_A;
+        }
+        if ((networkTypeBitmask & TelephonyManager.NETWORK_TYPE_BITMASK_EVDO_B) != 0) {
+            raf |= android.hardware.radio.V1_0.RadioAccessFamily.EVDO_B;
+        }
+        if ((networkTypeBitmask & TelephonyManager.NETWORK_TYPE_BITMASK_EHRPD) != 0) {
+            raf |= android.hardware.radio.V1_0.RadioAccessFamily.EHRPD;
+        }
+        if ((networkTypeBitmask & TelephonyManager.NETWORK_TYPE_BITMASK_HSUPA) != 0) {
+            raf |= android.hardware.radio.V1_0.RadioAccessFamily.HSUPA;
+        }
+        if ((networkTypeBitmask & TelephonyManager.NETWORK_TYPE_BITMASK_HSDPA) != 0) {
+            raf |= android.hardware.radio.V1_0.RadioAccessFamily.HSDPA;
+        }
+        if ((networkTypeBitmask & TelephonyManager.NETWORK_TYPE_BITMASK_HSPA) != 0) {
+            raf |= android.hardware.radio.V1_0.RadioAccessFamily.HSPA;
+        }
+        if ((networkTypeBitmask & TelephonyManager.NETWORK_TYPE_BITMASK_HSPAP) != 0) {
+            raf |= android.hardware.radio.V1_0.RadioAccessFamily.HSPAP;
+        }
+        if ((networkTypeBitmask & TelephonyManager.NETWORK_TYPE_BITMASK_UMTS) != 0) {
+            raf |= android.hardware.radio.V1_0.RadioAccessFamily.UMTS;
+        }
+        if ((networkTypeBitmask & TelephonyManager.NETWORK_TYPE_BITMASK_TD_SCDMA) != 0) {
+            raf |= android.hardware.radio.V1_0.RadioAccessFamily.TD_SCDMA;
+        }
+        if ((networkTypeBitmask & TelephonyManager.NETWORK_TYPE_BITMASK_LTE) != 0) {
+            raf |= android.hardware.radio.V1_0.RadioAccessFamily.LTE;
+        }
+        if ((networkTypeBitmask & TelephonyManager.NETWORK_TYPE_BITMASK_LTE_CA) != 0) {
+            raf |= android.hardware.radio.V1_0.RadioAccessFamily.LTE_CA;
+        }
+        if ((networkTypeBitmask & TelephonyManager.NETWORK_TYPE_BITMASK_NR) != 0) {
+            raf |= android.hardware.radio.V1_4.RadioAccessFamily.NR;
+        }
+        // TODO: need hal definition for IWLAN
+        return (raf == 0) ? android.hardware.radio.V1_4.RadioAccessFamily.UNKNOWN : raf;
+    }
+
+    /**
+     * Convert AccessNetworkType to AccessNetwork defined in radio/1.5/types.hal
+     * @param accessNetworkType Access networkt ype
+     * @return The converted AccessNetwork
+     */
+    public static int convertToHalAccessNetwork(int accessNetworkType) {
+        switch (accessNetworkType) {
+            case AccessNetworkConstants.AccessNetworkType.GERAN:
+                return android.hardware.radio.V1_5.AccessNetwork.GERAN;
+            case AccessNetworkConstants.AccessNetworkType.UTRAN:
+                return android.hardware.radio.V1_5.AccessNetwork.UTRAN;
+            case AccessNetworkConstants.AccessNetworkType.EUTRAN:
+                return android.hardware.radio.V1_5.AccessNetwork.EUTRAN;
+            case AccessNetworkConstants.AccessNetworkType.CDMA2000:
+                return android.hardware.radio.V1_5.AccessNetwork.CDMA2000;
+            case AccessNetworkConstants.AccessNetworkType.IWLAN:
+                return android.hardware.radio.V1_5.AccessNetwork.IWLAN;
+            case AccessNetworkConstants.AccessNetworkType.NGRAN:
+                return android.hardware.radio.V1_5.AccessNetwork.NGRAN;
+            case AccessNetworkConstants.AccessNetworkType.UNKNOWN:
+            default:
+                return android.hardware.radio.V1_5.AccessNetwork.UNKNOWN;
+        }
+    }
+
+    /**
+     * Convert AccessNetworkType to RadioAccessNetwork defined in radio/1.1/types.hal
+     * @param accessNetworkType Access network type
+     * @return The converted RadioAccessNetwork
+     */
+    public static int convertToHalRadioAccessNetworks(int accessNetworkType) {
+        switch (accessNetworkType) {
+            case AccessNetworkConstants.AccessNetworkType.GERAN:
+                return android.hardware.radio.V1_1.RadioAccessNetworks.GERAN;
+            case AccessNetworkConstants.AccessNetworkType.UTRAN:
+                return android.hardware.radio.V1_1.RadioAccessNetworks.UTRAN;
+            case AccessNetworkConstants.AccessNetworkType.EUTRAN:
+                return android.hardware.radio.V1_1.RadioAccessNetworks.EUTRAN;
+            case AccessNetworkConstants.AccessNetworkType.NGRAN:
+                return android.hardware.radio.V1_5.RadioAccessNetworks.NGRAN;
+            case AccessNetworkConstants.AccessNetworkType.CDMA2000:
+                return android.hardware.radio.V1_5.RadioAccessNetworks.CDMA2000;
+            case AccessNetworkConstants.AccessNetworkType.UNKNOWN:
+            default:
+                return android.hardware.radio.V1_5.RadioAccessNetworks.UNKNOWN;
+        }
+    }
+
+    /**
+     * Convert RadioAccessNetworks defined in radio/1.5/types.hal to AccessNetworkType
+     * @param ran RadioAccessNetwork defined in radio/1.5/types.hal
+     * @return The converted AccessNetworkType
+     */
+    public static int convertHalRadioAccessNetworks(int ran) {
+        switch (ran) {
+            case android.hardware.radio.V1_5.RadioAccessNetworks.GERAN:
+                return AccessNetworkConstants.AccessNetworkType.GERAN;
+            case android.hardware.radio.V1_5.RadioAccessNetworks.UTRAN:
+                return AccessNetworkConstants.AccessNetworkType.UTRAN;
+            case android.hardware.radio.V1_5.RadioAccessNetworks.EUTRAN:
+                return AccessNetworkConstants.AccessNetworkType.EUTRAN;
+            case android.hardware.radio.V1_5.RadioAccessNetworks.NGRAN:
+                return AccessNetworkConstants.AccessNetworkType.NGRAN;
+            case android.hardware.radio.V1_5.RadioAccessNetworks.CDMA2000:
+                return AccessNetworkConstants.AccessNetworkType.CDMA2000;
+            case android.hardware.radio.V1_5.RadioAccessNetworks.UNKNOWN:
+            default:
+                return AccessNetworkConstants.AccessNetworkType.UNKNOWN;
+        }
+    }
+
+    /**
+     * Convert to SimApdu defined in radio/1.0/types.hal
+     * @param channel channel
+     * @param cla cla
+     * @param instruction instruction
+     * @param p1 p1
+     * @param p2 p2
+     * @param p3 p3
+     * @param data data
+     * @return The converted SimApdu
+     */
+    public static android.hardware.radio.V1_0.SimApdu convertToHalSimApdu(int channel, int cla,
+            int instruction, int p1, int p2, int p3, String data) {
+        android.hardware.radio.V1_0.SimApdu msg = new android.hardware.radio.V1_0.SimApdu();
+        msg.sessionId = channel;
+        msg.cla = cla;
+        msg.instruction = instruction;
+        msg.p1 = p1;
+        msg.p2 = p2;
+        msg.p3 = p3;
+        msg.data = convertNullToEmptyString(data);
+        return msg;
+    }
+
+    /**
+     * Convert a list of CarrierIdentifiers into a list of Carrier defined in radio/1.0/types.hal
+     * @param carriers List of CarrierIdentifiers
+     * @return The converted list of Carriers
+     */
+    public static ArrayList<android.hardware.radio.V1_0.Carrier> convertToHalCarrierRestrictionList(
+            List<CarrierIdentifier> carriers) {
+        ArrayList<android.hardware.radio.V1_0.Carrier> result = new ArrayList<>();
+        for (CarrierIdentifier ci : carriers) {
+            android.hardware.radio.V1_0.Carrier c = new android.hardware.radio.V1_0.Carrier();
+            c.mcc = convertNullToEmptyString(ci.getMcc());
+            c.mnc = convertNullToEmptyString(ci.getMnc());
+            int matchType = CarrierIdentifier.MatchType.ALL;
+            String matchData = null;
+            if (!TextUtils.isEmpty(ci.getSpn())) {
+                matchType = CarrierIdentifier.MatchType.SPN;
+                matchData = ci.getSpn();
+            } else if (!TextUtils.isEmpty(ci.getImsi())) {
+                matchType = CarrierIdentifier.MatchType.IMSI_PREFIX;
+                matchData = ci.getImsi();
+            } else if (!TextUtils.isEmpty(ci.getGid1())) {
+                matchType = CarrierIdentifier.MatchType.GID1;
+                matchData = ci.getGid1();
+            } else if (!TextUtils.isEmpty(ci.getGid2())) {
+                matchType = CarrierIdentifier.MatchType.GID2;
+                matchData = ci.getGid2();
+            }
+            c.matchType = matchType;
+            c.matchData = convertNullToEmptyString(matchData);
+            result.add(c);
+        }
+        return result;
+    }
+
+    /**
+     * Convert to SignalThresholdInfo defined in radio/1.5/types.hal
+     * @param signalThresholdInfo Signal threshold info
+     * @return The converted SignalThresholdInfo
+     */
+    public static android.hardware.radio.V1_5.SignalThresholdInfo convertToHalSignalThresholdInfo(
+            SignalThresholdInfo signalThresholdInfo) {
+        android.hardware.radio.V1_5.SignalThresholdInfo signalThresholdInfoHal =
+                new android.hardware.radio.V1_5.SignalThresholdInfo();
+        signalThresholdInfoHal.signalMeasurement = signalThresholdInfo.getSignalMeasurementType();
+        signalThresholdInfoHal.hysteresisMs = signalThresholdInfo.getHysteresisMs();
+        signalThresholdInfoHal.hysteresisDb = signalThresholdInfo.getHysteresisDb();
+        signalThresholdInfoHal.thresholds = primitiveArrayToArrayList(
+                signalThresholdInfo.getThresholds());
+        signalThresholdInfoHal.isEnabled = signalThresholdInfo.isEnabled();
+        return signalThresholdInfoHal;
+    }
+
+    /**
+     * Convert StatusOnIcc to SmsWriteArgsStatus defined in radio/1.0/types.hal
+     * @param status StatusOnIcc
+     * @return The converted SmsWriteArgsStatus defined in radio/1.0/types.hal
+     */
+    public static int convertToHalSmsWriteArgsStatus(int status) {
+        switch(status & 0x7) {
+            case SmsManager.STATUS_ON_ICC_READ:
+                return android.hardware.radio.V1_0.SmsWriteArgsStatus.REC_READ;
+            case SmsManager.STATUS_ON_ICC_UNREAD:
+                return android.hardware.radio.V1_0.SmsWriteArgsStatus.REC_UNREAD;
+            case SmsManager.STATUS_ON_ICC_SENT:
+                return android.hardware.radio.V1_0.SmsWriteArgsStatus.STO_SENT;
+            case SmsManager.STATUS_ON_ICC_UNSENT:
+                return android.hardware.radio.V1_0.SmsWriteArgsStatus.STO_UNSENT;
+            default:
+                return android.hardware.radio.V1_0.SmsWriteArgsStatus.REC_READ;
+        }
+    }
+
+    /**
+     * Convert a list of HardwareConfig defined in radio/1.0/types.hal to a list of HardwareConfig
+     * @param hwListRil List of HardwareConfig defined in radio/1.0/types.hal
+     * @return The converted list of HardwareConfig
+     */
+    public static ArrayList<HardwareConfig> convertHalHardwareConfigList(
+            ArrayList<android.hardware.radio.V1_0.HardwareConfig> hwListRil) {
+        int num;
+        ArrayList<HardwareConfig> response;
+        HardwareConfig hw;
+
+        num = hwListRil.size();
+        response = new ArrayList<>(num);
+
+        for (android.hardware.radio.V1_0.HardwareConfig hwRil : hwListRil) {
+            int type = hwRil.type;
+            switch(type) {
+                case HardwareConfig.DEV_HARDWARE_TYPE_MODEM: {
+                    hw = new HardwareConfig(type);
+                    android.hardware.radio.V1_0.HardwareConfigModem hwModem = hwRil.modem.get(0);
+                    hw.assignModem(hwRil.uuid, hwRil.state, hwModem.rilModel, hwModem.rat,
+                            hwModem.maxVoice, hwModem.maxData, hwModem.maxStandby);
+                    break;
+                }
+                case HardwareConfig.DEV_HARDWARE_TYPE_SIM: {
+                    hw = new HardwareConfig(type);
+                    hw.assignSim(hwRil.uuid, hwRil.state, hwRil.sim.get(0).modemUuid);
+                    break;
+                }
+                default: {
+                    throw new RuntimeException(
+                            "RIL_REQUEST_GET_HARDWARE_CONFIG invalid hardware type:" + type);
+                }
+            }
+            response.add(hw);
+        }
+        return response;
+    }
+
+    /**
+     * Convert a list of HardwareConfig defined in HardwareConfig.aidl to a list of HardwareConfig
+     * @param hwListRil List of HardwareConfig defined in HardwareConfig.aidl
+     * @return The converted list of HardwareConfig
+     */
+    public static ArrayList<HardwareConfig> convertHalHardwareConfigList(
+            android.hardware.radio.modem.HardwareConfig[] hwListRil) {
+        ArrayList<HardwareConfig> response = new ArrayList<>(hwListRil.length);
+        HardwareConfig hw;
+
+        for (android.hardware.radio.modem.HardwareConfig hwRil : hwListRil) {
+            int type = hwRil.type;
+            switch (type) {
+                case HardwareConfig.DEV_HARDWARE_TYPE_MODEM: {
+                    hw = new HardwareConfig(type);
+                    android.hardware.radio.modem.HardwareConfigModem hwModem = hwRil.modem[0];
+                    hw.assignModem(hwRil.uuid, hwRil.state, hwModem.rilModel, hwModem.rat,
+                            hwModem.maxVoiceCalls, hwModem.maxDataCalls, hwModem.maxStandby);
+                    break;
+                }
+                case HardwareConfig.DEV_HARDWARE_TYPE_SIM: {
+                    hw = new HardwareConfig(type);
+                    hw.assignSim(hwRil.uuid, hwRil.state, hwRil.sim[0].modemUuid);
+                    break;
+                }
+                default: {
+                    throw new RuntimeException(
+                            "RIL_REQUEST_GET_HARDWARE_CONFIG invalid hardware type:" + type);
+                }
+            }
+            response.add(hw);
+        }
+        return response;
+    }
+
+    /**
+     * Convert RadioCapability defined in radio/1.0/types.hal to RadioCapability
+     * @param rc RadioCapability defined in radio/1.0/types.hal
+     * @param ril RIL
+     * @return The converted RadioCapability
+     */
+    public static RadioCapability convertHalRadioCapability(
+            android.hardware.radio.V1_0.RadioCapability rc, RIL ril) {
+        int session = rc.session;
+        int phase = rc.phase;
+        int rat = convertHalNetworkTypeBitMask(rc.raf);
+        String logicModemUuid = rc.logicalModemUuid;
+        int status = rc.status;
+
+        ril.riljLog("convertHalRadioCapability: session=" + session + ", phase=" + phase + ", rat="
+                + rat + ", logicModemUuid=" + logicModemUuid + ", status=" + status + ", rcRil.raf="
+                + rc.raf);
+        return new RadioCapability(ril.mPhoneId, session, phase, rat, logicModemUuid, status);
+    }
+
+    /**
+     * Convert RadioCapability defined in RadioCapability.aidl to RadioCapability
+     * @param rc RadioCapability defined in RadioCapability.aidl
+     * @param ril RIL
+     * @return The converted RadioCapability
+     */
+    public static RadioCapability convertHalRadioCapability(
+            android.hardware.radio.modem.RadioCapability rc, RIL ril) {
+        int session = rc.session;
+        int phase = rc.phase;
+        int rat = convertHalNetworkTypeBitMask(rc.raf);
+        String logicModemUuid = rc.logicalModemUuid;
+        int status = rc.status;
+
+        ril.riljLog("convertHalRadioCapability: session=" + session + ", phase=" + phase + ", rat="
+                + rat + ", logicModemUuid=" + logicModemUuid + ", status=" + status + ", rcRil.raf="
+                + rc.raf);
+        return new RadioCapability(ril.mPhoneId, session, phase, rat, logicModemUuid, status);
+    }
+
+    /**
+     * Convert LceDataInfo defined in radio/1.0/types.hal and LinkCapacityEstimate defined in
+     * radio/1.2, 1.6/types.hal to a list of LinkCapacityEstimates
+     * @param lceObj LceDataInfo defined in radio/1.0/types.hal or LinkCapacityEstimate defined in
+     *        radio/1.2, 1.6/types.hal
+     * @return The converted list of LinkCapacityEstimates
+     */
+    public static List<LinkCapacityEstimate> convertHalLceData(Object lceObj) {
+        final List<LinkCapacityEstimate> lceList = new ArrayList<>();
+        if (lceObj == null) return lceList;
+        if (lceObj instanceof android.hardware.radio.V1_0.LceDataInfo) {
+            android.hardware.radio.V1_0.LceDataInfo lce =
+                    (android.hardware.radio.V1_0.LceDataInfo) lceObj;
+            lceList.add(new LinkCapacityEstimate(LinkCapacityEstimate.LCE_TYPE_COMBINED,
+                    lce.lastHopCapacityKbps, LinkCapacityEstimate.INVALID));
+        } else if (lceObj instanceof android.hardware.radio.V1_2.LinkCapacityEstimate) {
+            android.hardware.radio.V1_2.LinkCapacityEstimate lce =
+                    (android.hardware.radio.V1_2.LinkCapacityEstimate) lceObj;
+            lceList.add(new LinkCapacityEstimate(LinkCapacityEstimate.LCE_TYPE_COMBINED,
+                    lce.downlinkCapacityKbps, lce.uplinkCapacityKbps));
+        } else if (lceObj instanceof android.hardware.radio.V1_6.LinkCapacityEstimate) {
+            android.hardware.radio.V1_6.LinkCapacityEstimate lce =
+                    (android.hardware.radio.V1_6.LinkCapacityEstimate) lceObj;
+            int primaryDownlinkCapacityKbps = lce.downlinkCapacityKbps;
+            int primaryUplinkCapacityKbps = lce.uplinkCapacityKbps;
+            if (primaryDownlinkCapacityKbps != LinkCapacityEstimate.INVALID
+                    && lce.secondaryDownlinkCapacityKbps != LinkCapacityEstimate.INVALID) {
+                primaryDownlinkCapacityKbps =
+                        lce.downlinkCapacityKbps - lce.secondaryDownlinkCapacityKbps;
+            }
+            if (primaryUplinkCapacityKbps != LinkCapacityEstimate.INVALID
+                    && lce.secondaryUplinkCapacityKbps != LinkCapacityEstimate.INVALID) {
+                primaryUplinkCapacityKbps =
+                        lce.uplinkCapacityKbps - lce.secondaryUplinkCapacityKbps;
+            }
+            lceList.add(new LinkCapacityEstimate(LinkCapacityEstimate.LCE_TYPE_PRIMARY,
+                    primaryDownlinkCapacityKbps, primaryUplinkCapacityKbps));
+            lceList.add(new LinkCapacityEstimate(LinkCapacityEstimate.LCE_TYPE_SECONDARY,
+                    lce.secondaryDownlinkCapacityKbps, lce.secondaryUplinkCapacityKbps));
+        }
+        return lceList;
+    }
+
+    /**
+     * Convert LceDataInfo defined in LceDataInfo.aidl to a list of LinkCapacityEstimates
+     * @param lce LceDataInfo defined in LceDataInfo.aidl
+     * @return The converted list of LinkCapacityEstimates
+     */
+    public static List<LinkCapacityEstimate> convertHalLceData(
+            android.hardware.radio.network.LceDataInfo lce) {
+        final List<LinkCapacityEstimate> lceList = new ArrayList<>();
+        lceList.add(new LinkCapacityEstimate(LinkCapacityEstimate.LCE_TYPE_COMBINED,
+                lce.lastHopCapacityKbps, LinkCapacityEstimate.INVALID));
+        return lceList;
+    }
+
+    /**
+     * Convert LinkCapacityEstimate defined in LinkCapacityEstimate.aidl to a list of
+     * LinkCapacityEstimates
+     * @param lce LinkCapacityEstimate defined in LinkCapacityEstimate.aidl
+     * @return The converted list of LinkCapacityEstimates
+     */
+    public static List<LinkCapacityEstimate> convertHalLceData(
+            android.hardware.radio.network.LinkCapacityEstimate lce) {
+        final List<LinkCapacityEstimate> lceList = new ArrayList<>();
+        int primaryDownlinkCapacityKbps = lce.downlinkCapacityKbps;
+        int primaryUplinkCapacityKbps = lce.uplinkCapacityKbps;
+        if (primaryDownlinkCapacityKbps != LinkCapacityEstimate.INVALID
+                && lce.secondaryDownlinkCapacityKbps != LinkCapacityEstimate.INVALID) {
+            primaryDownlinkCapacityKbps =
+                    lce.downlinkCapacityKbps - lce.secondaryDownlinkCapacityKbps;
+        }
+        if (primaryUplinkCapacityKbps != LinkCapacityEstimate.INVALID
+                && lce.secondaryUplinkCapacityKbps != LinkCapacityEstimate.INVALID) {
+            primaryUplinkCapacityKbps =
+                    lce.uplinkCapacityKbps - lce.secondaryUplinkCapacityKbps;
+        }
+        lceList.add(new LinkCapacityEstimate(LinkCapacityEstimate.LCE_TYPE_PRIMARY,
+                primaryDownlinkCapacityKbps, primaryUplinkCapacityKbps));
+        lceList.add(new LinkCapacityEstimate(LinkCapacityEstimate.LCE_TYPE_SECONDARY,
+                lce.secondaryDownlinkCapacityKbps, lce.secondaryUplinkCapacityKbps));
+        return lceList;
+    }
+
+
+    /**
+     * Convert a list of CellInfo defined in radio/1.0, 1.2, 1.4, 1.5, 1.6/types.hal to a list of
+     * CellInfos
+     * @param records List of CellInfo defined in radio/1.0, 1.2, 1.4, 1.5, 1.6/types.hal
+     * @return The converted list of CellInfos
+     */
+    public static ArrayList<CellInfo> convertHalCellInfoList(ArrayList<Object> records) {
+        ArrayList<CellInfo> response = new ArrayList<>(records.size());
+        if (records.isEmpty()) return response;
+        final long nanotime = SystemClock.elapsedRealtimeNanos();
+        for (Object obj : records) {
+            response.add(convertHalCellInfo(obj, nanotime));
+        }
+        return response;
+    }
+
+    /**
+     * Convert a list of CellInfo defined in CellInfo.aidl to a list of CellInfos
+     * @param records List of CellInfo defined in CellInfo.aidl
+     * @return The converted list of CellInfos
+     */
+    public static ArrayList<CellInfo> convertHalCellInfoList(
+            android.hardware.radio.network.CellInfo[] records) {
+        ArrayList<CellInfo> response = new ArrayList<>(records.length);
+        if (records.length == 0) return response;
+        final long nanotime = SystemClock.elapsedRealtimeNanos();
+        for (android.hardware.radio.network.CellInfo ci : records) {
+            response.add(convertHalCellInfo(ci, nanotime));
+        }
+        return response;
+    }
+
+    /**
+     * Convert a CellInfo defined in radio/1.0, 1.2, 1.4, 1.5, 1.6/types.hal to CellInfo
+     * @param cellInfo CellInfo defined in radio/1.0, 1.2, 1.4, 1.5, 1.6/types.hal
+     * @param nanotime time the CellInfo was created
+     * @return The converted CellInfo
+     */
+    private static CellInfo convertHalCellInfo(Object cellInfo, long nanotime) {
+        if (cellInfo == null) return null;
+        int type;
+        int connectionStatus;
+        boolean registered;
+        CellIdentityGsm gsmCi = null;
+        CellSignalStrengthGsm gsmSs = null;
+        CellIdentityCdma cdmaCi = null;
+        CellSignalStrengthCdma cdmaSs = null;
+        CellIdentityLte lteCi = null;
+        CellSignalStrengthLte lteSs = null;
+        CellConfigLte lteCc = null;
+        CellIdentityWcdma wcdmaCi = null;
+        CellSignalStrengthWcdma wcdmaSs = null;
+        CellIdentityTdscdma tdscdmaCi = null;
+        CellSignalStrengthTdscdma tdscdmaSs = null;
+        CellIdentityNr nrCi = null;
+        CellSignalStrengthNr nrSs = null;
+        if (cellInfo instanceof android.hardware.radio.V1_0.CellInfo) {
+            final android.hardware.radio.V1_0.CellInfo record =
+                    (android.hardware.radio.V1_0.CellInfo) cellInfo;
+            connectionStatus = CellInfo.CONNECTION_UNKNOWN;
+            registered = record.registered;
+            switch (record.cellInfoType) {
+                case android.hardware.radio.V1_0.CellInfoType.GSM:
+                    type = CellInfo.TYPE_GSM;
+                    android.hardware.radio.V1_0.CellInfoGsm gsm = record.gsm.get(0);
+                    gsmCi = convertHalCellIdentityGsm(gsm.cellIdentityGsm);
+                    gsmSs = convertHalGsmSignalStrength(gsm.signalStrengthGsm);
+                    break;
+                case android.hardware.radio.V1_0.CellInfoType.CDMA:
+                    type = CellInfo.TYPE_CDMA;
+                    android.hardware.radio.V1_0.CellInfoCdma cdma = record.cdma.get(0);
+                    cdmaCi = convertHalCellIdentityCdma(cdma.cellIdentityCdma);
+                    cdmaSs = convertHalCdmaSignalStrength(
+                            cdma.signalStrengthCdma, cdma.signalStrengthEvdo);
+                    break;
+                case android.hardware.radio.V1_0.CellInfoType.LTE:
+                    type = CellInfo.TYPE_LTE;
+                    android.hardware.radio.V1_0.CellInfoLte lte = record.lte.get(0);
+                    lteCi = convertHalCellIdentityLte(lte.cellIdentityLte);
+                    lteSs = convertHalLteSignalStrength(lte.signalStrengthLte);
+                    lteCc = new CellConfigLte();
+                    break;
+                case android.hardware.radio.V1_0.CellInfoType.WCDMA:
+                    type = CellInfo.TYPE_WCDMA;
+                    android.hardware.radio.V1_0.CellInfoWcdma wcdma = record.wcdma.get(0);
+                    wcdmaCi = convertHalCellIdentityWcdma(wcdma.cellIdentityWcdma);
+                    wcdmaSs = convertHalWcdmaSignalStrength(wcdma.signalStrengthWcdma);
+                    break;
+                case android.hardware.radio.V1_0.CellInfoType.TD_SCDMA:
+                    type = CellInfo.TYPE_TDSCDMA;
+                    android.hardware.radio.V1_0.CellInfoTdscdma tdscdma = record.tdscdma.get(0);
+                    tdscdmaCi = convertHalCellIdentityTdscdma(tdscdma.cellIdentityTdscdma);
+                    tdscdmaSs = convertHalTdscdmaSignalStrength(tdscdma.signalStrengthTdscdma);
+                    break;
+                default: return null;
+            }
+        } else if (cellInfo instanceof android.hardware.radio.V1_2.CellInfo) {
+            final android.hardware.radio.V1_2.CellInfo record =
+                    (android.hardware.radio.V1_2.CellInfo) cellInfo;
+            connectionStatus = record.connectionStatus;
+            registered = record.registered;
+            switch(record.cellInfoType) {
+                case android.hardware.radio.V1_0.CellInfoType.GSM:
+                    type = CellInfo.TYPE_GSM;
+                    android.hardware.radio.V1_2.CellInfoGsm gsm = record.gsm.get(0);
+                    gsmCi = convertHalCellIdentityGsm(gsm.cellIdentityGsm);
+                    gsmSs = convertHalGsmSignalStrength(gsm.signalStrengthGsm);
+                    break;
+                case android.hardware.radio.V1_0.CellInfoType.CDMA:
+                    type = CellInfo.TYPE_CDMA;
+                    android.hardware.radio.V1_2.CellInfoCdma cdma = record.cdma.get(0);
+                    cdmaCi = convertHalCellIdentityCdma(cdma.cellIdentityCdma);
+                    cdmaSs = convertHalCdmaSignalStrength(
+                            cdma.signalStrengthCdma, cdma.signalStrengthEvdo);
+                    break;
+                case android.hardware.radio.V1_0.CellInfoType.LTE:
+                    type = CellInfo.TYPE_LTE;
+                    android.hardware.radio.V1_2.CellInfoLte lte = record.lte.get(0);
+                    lteCi = convertHalCellIdentityLte(lte.cellIdentityLte);
+                    lteSs = convertHalLteSignalStrength(lte.signalStrengthLte);
+                    lteCc = new CellConfigLte();
+                    break;
+                case android.hardware.radio.V1_0.CellInfoType.WCDMA:
+                    type = CellInfo.TYPE_WCDMA;
+                    android.hardware.radio.V1_2.CellInfoWcdma wcdma = record.wcdma.get(0);
+                    wcdmaCi = convertHalCellIdentityWcdma(wcdma.cellIdentityWcdma);
+                    wcdmaSs = convertHalWcdmaSignalStrength(wcdma.signalStrengthWcdma);
+                    break;
+                case android.hardware.radio.V1_0.CellInfoType.TD_SCDMA:
+                    type = CellInfo.TYPE_TDSCDMA;
+                    android.hardware.radio.V1_2.CellInfoTdscdma tdscdma = record.tdscdma.get(0);
+                    tdscdmaCi = convertHalCellIdentityTdscdma(tdscdma.cellIdentityTdscdma);
+                    tdscdmaSs = convertHalTdscdmaSignalStrength(tdscdma.signalStrengthTdscdma);
+                    break;
+                default: return null;
+            }
+        } else if (cellInfo instanceof android.hardware.radio.V1_4.CellInfo) {
+            final android.hardware.radio.V1_4.CellInfo record =
+                    (android.hardware.radio.V1_4.CellInfo) cellInfo;
+            connectionStatus = record.connectionStatus;
+            registered = record.isRegistered;
+            switch (record.info.getDiscriminator()) {
+                case android.hardware.radio.V1_4.CellInfo.Info.hidl_discriminator.gsm:
+                    type = CellInfo.TYPE_GSM;
+                    android.hardware.radio.V1_2.CellInfoGsm gsm = record.info.gsm();
+                    gsmCi = convertHalCellIdentityGsm(gsm.cellIdentityGsm);
+                    gsmSs = convertHalGsmSignalStrength(gsm.signalStrengthGsm);
+                    break;
+                case android.hardware.radio.V1_4.CellInfo.Info.hidl_discriminator.cdma:
+                    type = CellInfo.TYPE_CDMA;
+                    android.hardware.radio.V1_2.CellInfoCdma cdma = record.info.cdma();
+                    cdmaCi = convertHalCellIdentityCdma(cdma.cellIdentityCdma);
+                    cdmaSs = convertHalCdmaSignalStrength(
+                            cdma.signalStrengthCdma, cdma.signalStrengthEvdo);
+                    break;
+                case android.hardware.radio.V1_4.CellInfo.Info.hidl_discriminator.lte:
+                    type = CellInfo.TYPE_LTE;
+                    android.hardware.radio.V1_4.CellInfoLte lte = record.info.lte();
+                    lteCi = convertHalCellIdentityLte(lte.base.cellIdentityLte);
+                    lteSs = convertHalLteSignalStrength(lte.base.signalStrengthLte);
+                    lteCc = new CellConfigLte(lte.cellConfig.isEndcAvailable);
+                    break;
+                case android.hardware.radio.V1_4.CellInfo.Info.hidl_discriminator.wcdma:
+                    type = CellInfo.TYPE_WCDMA;
+                    android.hardware.radio.V1_2.CellInfoWcdma wcdma = record.info.wcdma();
+                    wcdmaCi = convertHalCellIdentityWcdma(wcdma.cellIdentityWcdma);
+                    wcdmaSs = convertHalWcdmaSignalStrength(wcdma.signalStrengthWcdma);
+                    break;
+                case android.hardware.radio.V1_4.CellInfo.Info.hidl_discriminator.tdscdma:
+                    type = CellInfo.TYPE_TDSCDMA;
+                    android.hardware.radio.V1_2.CellInfoTdscdma tdscdma = record.info.tdscdma();
+                    tdscdmaCi = convertHalCellIdentityTdscdma(tdscdma.cellIdentityTdscdma);
+                    tdscdmaSs = convertHalTdscdmaSignalStrength(tdscdma.signalStrengthTdscdma);
+                    break;
+                case android.hardware.radio.V1_4.CellInfo.Info.hidl_discriminator.nr:
+                    type = CellInfo.TYPE_NR;
+                    android.hardware.radio.V1_4.CellInfoNr nr = record.info.nr();
+                    nrCi = convertHalCellIdentityNr(nr.cellidentity);
+                    nrSs = convertHalNrSignalStrength(nr.signalStrength);
+                    break;
+                default: return null;
+            }
+        } else if (cellInfo instanceof android.hardware.radio.V1_5.CellInfo) {
+            final android.hardware.radio.V1_5.CellInfo record =
+                    (android.hardware.radio.V1_5.CellInfo) cellInfo;
+            connectionStatus = record.connectionStatus;
+            registered = record.registered;
+            switch (record.ratSpecificInfo.getDiscriminator()) {
+                case android.hardware.radio.V1_5.CellInfo
+                        .CellInfoRatSpecificInfo.hidl_discriminator.gsm:
+                    type = CellInfo.TYPE_GSM;
+                    android.hardware.radio.V1_5.CellInfoGsm gsm = record.ratSpecificInfo.gsm();
+                    gsmCi = convertHalCellIdentityGsm(gsm.cellIdentityGsm);
+                    gsmSs = convertHalGsmSignalStrength(gsm.signalStrengthGsm);
+                    break;
+                case android.hardware.radio.V1_5.CellInfo
+                        .CellInfoRatSpecificInfo.hidl_discriminator.cdma:
+                    type = CellInfo.TYPE_CDMA;
+                    android.hardware.radio.V1_2.CellInfoCdma cdma = record.ratSpecificInfo.cdma();
+                    cdmaCi = convertHalCellIdentityCdma(cdma.cellIdentityCdma);
+                    cdmaSs = convertHalCdmaSignalStrength(
+                            cdma.signalStrengthCdma, cdma.signalStrengthEvdo);
+                    break;
+                case android.hardware.radio.V1_5.CellInfo
+                        .CellInfoRatSpecificInfo.hidl_discriminator.lte:
+                    type = CellInfo.TYPE_LTE;
+                    android.hardware.radio.V1_5.CellInfoLte lte = record.ratSpecificInfo.lte();
+                    lteCi = convertHalCellIdentityLte(lte.cellIdentityLte);
+                    lteSs = convertHalLteSignalStrength(lte.signalStrengthLte);
+                    lteCc = new CellConfigLte();
+                    break;
+                case android.hardware.radio.V1_5.CellInfo
+                        .CellInfoRatSpecificInfo.hidl_discriminator.wcdma:
+                    type = CellInfo.TYPE_WCDMA;
+                    android.hardware.radio.V1_5.CellInfoWcdma wcdma =
+                            record.ratSpecificInfo.wcdma();
+                    wcdmaCi = convertHalCellIdentityWcdma(wcdma.cellIdentityWcdma);
+                    wcdmaSs = convertHalWcdmaSignalStrength(wcdma.signalStrengthWcdma);
+                    break;
+                case android.hardware.radio.V1_5.CellInfo
+                        .CellInfoRatSpecificInfo.hidl_discriminator.tdscdma:
+                    type = CellInfo.TYPE_TDSCDMA;
+                    android.hardware.radio.V1_5.CellInfoTdscdma tdscdma =
+                            record.ratSpecificInfo.tdscdma();
+                    tdscdmaCi = convertHalCellIdentityTdscdma(tdscdma.cellIdentityTdscdma);
+                    tdscdmaSs = convertHalTdscdmaSignalStrength(tdscdma.signalStrengthTdscdma);
+                    break;
+                case android.hardware.radio.V1_5.CellInfo
+                        .CellInfoRatSpecificInfo.hidl_discriminator.nr:
+                    type = CellInfo.TYPE_NR;
+                    android.hardware.radio.V1_5.CellInfoNr nr = record.ratSpecificInfo.nr();
+                    nrCi = convertHalCellIdentityNr(nr.cellIdentityNr);
+                    nrSs = convertHalNrSignalStrength(nr.signalStrengthNr);
+                    break;
+                default: return null;
+            }
+        } else if (cellInfo instanceof android.hardware.radio.V1_6.CellInfo) {
+            final android.hardware.radio.V1_6.CellInfo record =
+                    (android.hardware.radio.V1_6.CellInfo) cellInfo;
+            connectionStatus = record.connectionStatus;
+            registered = record.registered;
+            switch (record.ratSpecificInfo.getDiscriminator()) {
+                case android.hardware.radio.V1_6.CellInfo
+                        .CellInfoRatSpecificInfo.hidl_discriminator.gsm:
+                    type = CellInfo.TYPE_GSM;
+                    android.hardware.radio.V1_5.CellInfoGsm gsm = record.ratSpecificInfo.gsm();
+                    gsmCi = convertHalCellIdentityGsm(gsm.cellIdentityGsm);
+                    gsmSs = convertHalGsmSignalStrength(gsm.signalStrengthGsm);
+                    break;
+                case android.hardware.radio.V1_6.CellInfo
+                        .CellInfoRatSpecificInfo.hidl_discriminator.cdma:
+                    type = CellInfo.TYPE_CDMA;
+                    android.hardware.radio.V1_2.CellInfoCdma cdma = record.ratSpecificInfo.cdma();
+                    cdmaCi = convertHalCellIdentityCdma(cdma.cellIdentityCdma);
+                    cdmaSs = convertHalCdmaSignalStrength(
+                            cdma.signalStrengthCdma, cdma.signalStrengthEvdo);
+                    break;
+                case android.hardware.radio.V1_6.CellInfo
+                        .CellInfoRatSpecificInfo.hidl_discriminator.lte:
+                    type = CellInfo.TYPE_LTE;
+                    android.hardware.radio.V1_6.CellInfoLte lte = record.ratSpecificInfo.lte();
+                    lteCi = convertHalCellIdentityLte(lte.cellIdentityLte);
+                    lteSs = convertHalLteSignalStrength(lte.signalStrengthLte);
+                    lteCc = new CellConfigLte();
+                    break;
+                case android.hardware.radio.V1_6.CellInfo
+                        .CellInfoRatSpecificInfo.hidl_discriminator.wcdma:
+                    type = CellInfo.TYPE_WCDMA;
+                    android.hardware.radio.V1_5.CellInfoWcdma wcdma =
+                            record.ratSpecificInfo.wcdma();
+                    wcdmaCi = convertHalCellIdentityWcdma(wcdma.cellIdentityWcdma);
+                    wcdmaSs = convertHalWcdmaSignalStrength(wcdma.signalStrengthWcdma);
+                    break;
+                case android.hardware.radio.V1_6.CellInfo
+                        .CellInfoRatSpecificInfo.hidl_discriminator.tdscdma:
+                    type = CellInfo.TYPE_TDSCDMA;
+                    android.hardware.radio.V1_5.CellInfoTdscdma tdscdma =
+                            record.ratSpecificInfo.tdscdma();
+                    tdscdmaCi = convertHalCellIdentityTdscdma(tdscdma.cellIdentityTdscdma);
+                    tdscdmaSs = convertHalTdscdmaSignalStrength(tdscdma.signalStrengthTdscdma);
+                    break;
+                case android.hardware.radio.V1_6.CellInfo
+                        .CellInfoRatSpecificInfo.hidl_discriminator.nr:
+                    type = CellInfo.TYPE_NR;
+                    android.hardware.radio.V1_6.CellInfoNr nr = record.ratSpecificInfo.nr();
+                    nrCi = convertHalCellIdentityNr(nr.cellIdentityNr);
+                    nrSs = convertHalNrSignalStrength(nr.signalStrengthNr);
+                    break;
+                default: return null;
+            }
+        } else {
+            return null;
+        }
+
+        switch (type) {
+            case CellInfo.TYPE_GSM:
+                return new CellInfoGsm(connectionStatus, registered, nanotime, gsmCi, gsmSs);
+            case CellInfo.TYPE_CDMA:
+                return new CellInfoCdma(connectionStatus, registered, nanotime, cdmaCi, cdmaSs);
+            case CellInfo.TYPE_LTE:
+                return new CellInfoLte(connectionStatus, registered, nanotime, lteCi, lteSs, lteCc);
+            case CellInfo.TYPE_WCDMA:
+                return new CellInfoWcdma(connectionStatus, registered, nanotime, wcdmaCi, wcdmaSs);
+            case CellInfo.TYPE_TDSCDMA:
+                return new CellInfoTdscdma(connectionStatus, registered, nanotime, tdscdmaCi,
+                        tdscdmaSs);
+            case CellInfo.TYPE_NR:
+                return new CellInfoNr(connectionStatus, registered, nanotime, nrCi, nrSs);
+            case CellInfo.TYPE_UNKNOWN:
+            default:
+                return null;
+        }
+    }
+
+    /**
+     * Convert a CellInfo defined in radio/1.0, 1.2, 1.4, 1.5, 1.6/types.hal to CellInfo
+     * @param cellInfo CellInfo defined in radio/1.0, 1.2, 1.4, 1.5, 1.6/types.hal
+     * @param nanotime time the CellInfo was created
+     * @return The converted CellInfo
+     */
+    private static CellInfo convertHalCellInfo(android.hardware.radio.network.CellInfo cellInfo,
+            long nanotime) {
+        if (cellInfo == null) return null;
+        int connectionStatus = cellInfo.connectionStatus;
+        boolean registered = cellInfo.registered;
+        switch (cellInfo.ratSpecificInfo.getTag()) {
+            case android.hardware.radio.network.CellInfoRatSpecificInfo.gsm:
+                android.hardware.radio.network.CellInfoGsm gsm = cellInfo.ratSpecificInfo.getGsm();
+                return new CellInfoGsm(connectionStatus, registered, nanotime,
+                        convertHalCellIdentityGsm(gsm.cellIdentityGsm),
+                        convertHalGsmSignalStrength(gsm.signalStrengthGsm));
+            case android.hardware.radio.network.CellInfoRatSpecificInfo.cdma:
+                android.hardware.radio.network.CellInfoCdma cdma =
+                        cellInfo.ratSpecificInfo.getCdma();
+                return new CellInfoCdma(connectionStatus, registered, nanotime,
+                        convertHalCellIdentityCdma(cdma.cellIdentityCdma),
+                        convertHalCdmaSignalStrength(cdma.signalStrengthCdma,
+                                cdma.signalStrengthEvdo));
+            case android.hardware.radio.network.CellInfoRatSpecificInfo.lte:
+                android.hardware.radio.network.CellInfoLte lte = cellInfo.ratSpecificInfo.getLte();
+                return new CellInfoLte(connectionStatus, registered, nanotime,
+                        convertHalCellIdentityLte(lte.cellIdentityLte),
+                        convertHalLteSignalStrength(lte.signalStrengthLte), new CellConfigLte());
+            case android.hardware.radio.network.CellInfoRatSpecificInfo.wcdma:
+                android.hardware.radio.network.CellInfoWcdma wcdma =
+                        cellInfo.ratSpecificInfo.getWcdma();
+                return new CellInfoWcdma(connectionStatus, registered, nanotime,
+                        convertHalCellIdentityWcdma(wcdma.cellIdentityWcdma),
+                        convertHalWcdmaSignalStrength(wcdma.signalStrengthWcdma));
+            case android.hardware.radio.network.CellInfoRatSpecificInfo.tdscdma:
+                android.hardware.radio.network.CellInfoTdscdma tdscdma =
+                        cellInfo.ratSpecificInfo.getTdscdma();
+                return new CellInfoTdscdma(connectionStatus, registered, nanotime,
+                        convertHalCellIdentityTdscdma(tdscdma.cellIdentityTdscdma),
+                        convertHalTdscdmaSignalStrength(tdscdma.signalStrengthTdscdma));
+            case android.hardware.radio.network.CellInfoRatSpecificInfo.nr:
+                android.hardware.radio.network.CellInfoNr nr = cellInfo.ratSpecificInfo.getNr();
+                return new CellInfoNr(connectionStatus, registered, nanotime,
+                        convertHalCellIdentityNr(nr.cellIdentityNr),
+                        convertHalNrSignalStrength(nr.signalStrengthNr));
+            default:
+                return null;
+        }
+    }
+
+    /**
+     * Convert a CellIdentity defined in radio/1.0, 1.2, 1.5/types.hal to CellIdentity
+     * @param halCi CellIdentity defined in radio/1.0, 1.2, 1.5/types.hal
+     * @return The converted CellIdentity
+     */
+    public static CellIdentity convertHalCellIdentity(Object halCi) {
+        if (halCi == null) return null;
+        if (halCi instanceof android.hardware.radio.V1_0.CellIdentity) {
+            android.hardware.radio.V1_0.CellIdentity ci =
+                    (android.hardware.radio.V1_0.CellIdentity) halCi;
+            switch (ci.cellInfoType) {
+                case CellInfo.TYPE_GSM:
+                    if (ci.cellIdentityGsm.size() == 1) {
+                        return convertHalCellIdentityGsm(ci.cellIdentityGsm.get(0));
+                    }
+                    break;
+                case CellInfo.TYPE_CDMA:
+                    if (ci.cellIdentityCdma.size() == 1) {
+                        return convertHalCellIdentityCdma(ci.cellIdentityCdma.get(0));
+                    }
+                    break;
+                case CellInfo.TYPE_LTE:
+                    if (ci.cellIdentityLte.size() == 1) {
+                        return convertHalCellIdentityLte(ci.cellIdentityLte.get(0));
+                    }
+                    break;
+                case CellInfo.TYPE_WCDMA:
+                    if (ci.cellIdentityWcdma.size() == 1) {
+                        return convertHalCellIdentityWcdma(ci.cellIdentityWcdma.get(0));
+                    }
+                    break;
+                case CellInfo.TYPE_TDSCDMA:
+                    if (ci.cellIdentityTdscdma.size() == 1) {
+                        return convertHalCellIdentityTdscdma(ci.cellIdentityTdscdma.get(0));
+                    }
+                    break;
+            }
+        } else if (halCi instanceof android.hardware.radio.V1_2.CellIdentity) {
+            android.hardware.radio.V1_2.CellIdentity ci =
+                    (android.hardware.radio.V1_2.CellIdentity) halCi;
+            switch (ci.cellInfoType) {
+                case CellInfo.TYPE_GSM:
+                    if (ci.cellIdentityGsm.size() == 1) {
+                        return convertHalCellIdentityGsm(ci.cellIdentityGsm.get(0));
+                    }
+                    break;
+                case CellInfo.TYPE_CDMA:
+                    if (ci.cellIdentityCdma.size() == 1) {
+                        return convertHalCellIdentityCdma(ci.cellIdentityCdma.get(0));
+                    }
+                    break;
+                case CellInfo.TYPE_LTE:
+                    if (ci.cellIdentityLte.size() == 1) {
+                        return convertHalCellIdentityLte(ci.cellIdentityLte.get(0));
+                    }
+                    break;
+                case CellInfo.TYPE_WCDMA:
+                    if (ci.cellIdentityWcdma.size() == 1) {
+                        return convertHalCellIdentityWcdma(ci.cellIdentityWcdma.get(0));
+                    }
+                    break;
+                case CellInfo.TYPE_TDSCDMA:
+                    if (ci.cellIdentityTdscdma.size() == 1) {
+                        return convertHalCellIdentityTdscdma(ci.cellIdentityTdscdma.get(0));
+                    }
+                    break;
+            }
+        } else if (halCi instanceof android.hardware.radio.V1_5.CellIdentity) {
+            android.hardware.radio.V1_5.CellIdentity ci =
+                    (android.hardware.radio.V1_5.CellIdentity) halCi;
+            switch (ci.getDiscriminator()) {
+                case android.hardware.radio.V1_5.CellIdentity.hidl_discriminator.gsm:
+                    return convertHalCellIdentityGsm(ci.gsm());
+                case android.hardware.radio.V1_5.CellIdentity.hidl_discriminator.cdma:
+                    return convertHalCellIdentityCdma(ci.cdma());
+                case android.hardware.radio.V1_5.CellIdentity.hidl_discriminator.lte:
+                    return convertHalCellIdentityLte(ci.lte());
+                case android.hardware.radio.V1_5.CellIdentity.hidl_discriminator.wcdma:
+                    return convertHalCellIdentityWcdma(ci.wcdma());
+                case android.hardware.radio.V1_5.CellIdentity.hidl_discriminator.tdscdma:
+                    return convertHalCellIdentityTdscdma(ci.tdscdma());
+                case android.hardware.radio.V1_5.CellIdentity.hidl_discriminator.nr:
+                    return convertHalCellIdentityNr(ci.nr());
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Convert a CellIdentity defined in CellIdentity.aidl to CellInfo
+     * @param ci CellIdentity defined in CellIdentity.aidl
+     * @return The converted CellIdentity
+     */
+    public static CellIdentity convertHalCellIdentity(
+            android.hardware.radio.network.CellIdentity ci) {
+        if (ci == null) return null;
+        switch (ci.getTag()) {
+            case android.hardware.radio.network.CellIdentity.gsm:
+                return convertHalCellIdentityGsm(ci.getGsm());
+            case android.hardware.radio.network.CellIdentity.cdma:
+                return convertHalCellIdentityCdma(ci.getCdma());
+            case android.hardware.radio.network.CellIdentity.lte:
+                return convertHalCellIdentityLte(ci.getLte());
+            case android.hardware.radio.network.CellIdentity.wcdma:
+                return convertHalCellIdentityWcdma(ci.getWcdma());
+            case android.hardware.radio.network.CellIdentity.tdscdma:
+                return convertHalCellIdentityTdscdma(ci.getTdscdma());
+            case android.hardware.radio.network.CellIdentity.nr:
+                return convertHalCellIdentityNr(ci.getNr());
+            default: return null;
+        }
+    }
+
+    /**
+     * Convert a CellIdentityGsm defined in radio/1.0, 1.2, 1.5/types.hal to CellIdentityGsm
+     * @param gsm CellIdentityGsm defined in radio/1.0, 1.2, 1.5/types.hal
+     * @return The converted CellIdentityGsm
+     */
+    public static CellIdentityGsm convertHalCellIdentityGsm(Object gsm) {
+        if (gsm == null) return null;
+        if (gsm instanceof android.hardware.radio.V1_0.CellIdentityGsm) {
+            android.hardware.radio.V1_0.CellIdentityGsm ci =
+                    (android.hardware.radio.V1_0.CellIdentityGsm) gsm;
+            return new CellIdentityGsm(ci.lac, ci.cid, ci.arfcn,
+                    ci.bsic == (byte) 0xFF ? CellInfo.UNAVAILABLE : ci.bsic, ci.mcc, ci.mnc, "", "",
+                    new ArraySet<>());
+        } else if (gsm instanceof android.hardware.radio.V1_2.CellIdentityGsm) {
+            android.hardware.radio.V1_2.CellIdentityGsm ci =
+                    (android.hardware.radio.V1_2.CellIdentityGsm) gsm;
+            return new CellIdentityGsm(ci.base.lac, ci.base.cid, ci.base.arfcn,
+                    ci.base.bsic == (byte) 0xFF ? CellInfo.UNAVAILABLE : ci.base.bsic, ci.base.mcc,
+                    ci.base.mnc, ci.operatorNames.alphaLong, ci.operatorNames.alphaShort,
+                    new ArraySet<>());
+        } else if (gsm instanceof android.hardware.radio.V1_5.CellIdentityGsm) {
+            android.hardware.radio.V1_5.CellIdentityGsm ci =
+                    (android.hardware.radio.V1_5.CellIdentityGsm) gsm;
+            return new CellIdentityGsm(ci.base.base.lac, ci.base.base.cid, ci.base.base.arfcn,
+                    ci.base.base.bsic == (byte) 0xFF ? CellInfo.UNAVAILABLE
+                            : ci.base.base.bsic, ci.base.base.mcc, ci.base.base.mnc,
+                    ci.base.operatorNames.alphaLong, ci.base.operatorNames.alphaShort,
+                    ci.additionalPlmns);
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Convert a CellIdentityGsm defined in CellIdentityGsm.aidl to CellIdentityGsm
+     * @param cid CellIdentityGsm defined in CellIdentityGsm.aidl
+     * @return The converted CellIdentityGsm
+     */
+    public static CellIdentityGsm convertHalCellIdentityGsm(
+            android.hardware.radio.network.CellIdentityGsm cid) {
+        return new CellIdentityGsm(cid.lac, cid.cid, cid.arfcn,
+                cid.bsic == (byte) 0xFF ? CellInfo.UNAVAILABLE : cid.bsic, cid.mcc, cid.mnc,
+                "", "", new ArraySet<>());
+    }
+
+    /**
+     * Convert a CellIdentityCdma defined in radio/1.0, 1.2/types.hal to CellIdentityCdma
+     * @param cdma CellIdentityCdma defined in radio/1.0, 1.2/types.hal
+     * @return The converted CellIdentityCdma
+     */
+    public static CellIdentityCdma convertHalCellIdentityCdma(Object cdma) {
+        if (cdma == null) return null;
+        if (cdma instanceof android.hardware.radio.V1_0.CellIdentityCdma) {
+            android.hardware.radio.V1_0.CellIdentityCdma ci =
+                    (android.hardware.radio.V1_0.CellIdentityCdma) cdma;
+            return new CellIdentityCdma(ci.networkId, ci.systemId, ci.baseStationId, ci.longitude,
+                    ci.latitude, "", "");
+        } else if (cdma instanceof android.hardware.radio.V1_2.CellIdentityCdma) {
+            android.hardware.radio.V1_2.CellIdentityCdma ci =
+                    (android.hardware.radio.V1_2.CellIdentityCdma) cdma;
+            return new CellIdentityCdma(ci.base.networkId, ci.base.systemId, ci.base.baseStationId,
+                    ci.base.longitude, ci.base.latitude, ci.operatorNames.alphaLong,
+                    ci.operatorNames.alphaShort);
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Convert a CellIdentityCdma defined in CellIdentityCdma.aidl to CellIdentityCdma
+     * @param cid CellIdentityCdma defined in CelIdentityCdma.aidl
+     * @return The converted CellIdentityCdma
+     */
+    public static CellIdentityCdma convertHalCellIdentityCdma(
+            android.hardware.radio.network.CellIdentityCdma cid) {
+        return new CellIdentityCdma(cid.networkId, cid.systemId, cid.baseStationId, cid.longitude,
+                cid.latitude, cid.operatorNames.alphaLong, cid.operatorNames.alphaShort);
+    }
+
+    /**
+     * Convert a CellIdentityLte defined in radio/1.0, 1.2, 1.5/types.hal to CellIdentityLte
+     * @param lte CellIdentityLte defined in radio/1.0, 1.2, 1.5/types.hal
+     * @return The converted CellIdentityLte
+     */
+    public static CellIdentityLte convertHalCellIdentityLte(Object lte) {
+        if (lte == null) return null;
+        if (lte instanceof android.hardware.radio.V1_0.CellIdentityLte) {
+            android.hardware.radio.V1_0.CellIdentityLte ci =
+                    (android.hardware.radio.V1_0.CellIdentityLte) lte;
+            return new CellIdentityLte(ci.ci, ci.pci, ci.tac, ci.earfcn, new int[] {},
+                    CellInfo.UNAVAILABLE, ci.mcc, ci.mnc, "", "", new ArraySet<>(), null);
+        } else if (lte instanceof android.hardware.radio.V1_2.CellIdentityLte) {
+            android.hardware.radio.V1_2.CellIdentityLte ci =
+                    (android.hardware.radio.V1_2.CellIdentityLte) lte;
+            return new CellIdentityLte(ci.base.ci, ci.base.pci, ci.base.tac, ci.base.earfcn,
+                    new int[] {}, ci.bandwidth, ci.base.mcc, ci.base.mnc,
+                    ci.operatorNames.alphaLong, ci.operatorNames.alphaShort, new ArraySet<>(),
+                    null);
+        } else if (lte instanceof android.hardware.radio.V1_5.CellIdentityLte) {
+            android.hardware.radio.V1_5.CellIdentityLte ci =
+                    (android.hardware.radio.V1_5.CellIdentityLte) lte;
+            return new CellIdentityLte(ci.base.base.ci, ci.base.base.pci, ci.base.base.tac,
+                    ci.base.base.earfcn, ci.bands.stream().mapToInt(Integer::intValue).toArray(),
+                    ci.base.bandwidth, ci.base.base.mcc, ci.base.base.mnc,
+                    ci.base.operatorNames.alphaLong, ci.base.operatorNames.alphaShort,
+                    ci.additionalPlmns, convertHalClosedSubscriberGroupInfo(ci.optionalCsgInfo));
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Convert a CellIdentityLte defined in CellIdentityLte.aidl to CellIdentityLte
+     * @param cid CellIdentityLte defined in CellIdentityLte.aidl
+     * @return The converted CellIdentityLte
+     */
+    public static CellIdentityLte convertHalCellIdentityLte(
+            android.hardware.radio.network.CellIdentityLte cid) {
+        return new CellIdentityLte(cid.ci, cid.pci, cid.tac, cid.earfcn, cid.bands, cid.bandwidth,
+                cid.mcc, cid.mnc, cid.operatorNames.alphaLong, cid.operatorNames.alphaShort,
+                primitiveArrayToArrayList(cid.additionalPlmns),
+                convertHalClosedSubscriberGroupInfo(cid.csgInfo));
+    }
+
+    /**
+     * Convert a CellIdentityWcdma defined in radio/1.0, 1.2, 1.5/types.hal to CellIdentityWcdma
+     * @param wcdma CellIdentityWcdma defined in radio/1.0, 1.2, 1.5/types.hal
+     * @return The converted CellIdentityWcdma
+     */
+    public static CellIdentityWcdma convertHalCellIdentityWcdma(Object wcdma) {
+        if (wcdma == null) return null;
+        if (wcdma instanceof android.hardware.radio.V1_0.CellIdentityWcdma) {
+            android.hardware.radio.V1_0.CellIdentityWcdma ci =
+                    (android.hardware.radio.V1_0.CellIdentityWcdma) wcdma;
+            return new CellIdentityWcdma(ci.lac, ci.cid, ci.psc, ci.uarfcn, ci.mcc, ci.mnc, "", "",
+                    new ArraySet<>(), null);
+        } else if (wcdma instanceof android.hardware.radio.V1_2.CellIdentityWcdma) {
+            android.hardware.radio.V1_2.CellIdentityWcdma ci =
+                    (android.hardware.radio.V1_2.CellIdentityWcdma) wcdma;
+            return new CellIdentityWcdma(ci.base.lac, ci.base.cid, ci.base.psc, ci.base.uarfcn,
+                    ci.base.mcc, ci.base.mnc, ci.operatorNames.alphaLong,
+                    ci.operatorNames.alphaShort, new ArraySet<>(), null);
+        } else if (wcdma instanceof android.hardware.radio.V1_5.CellIdentityWcdma) {
+            android.hardware.radio.V1_5.CellIdentityWcdma ci =
+                    (android.hardware.radio.V1_5.CellIdentityWcdma) wcdma;
+            return new CellIdentityWcdma(ci.base.base.lac, ci.base.base.cid, ci.base.base.psc,
+                    ci.base.base.uarfcn, ci.base.base.mcc, ci.base.base.mnc,
+                    ci.base.operatorNames.alphaLong, ci.base.operatorNames.alphaShort,
+                    ci.additionalPlmns, convertHalClosedSubscriberGroupInfo(ci.optionalCsgInfo));
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Convert a CellIdentityWcdma defined in CellIdentityWcdma.aidl to CellIdentityWcdma
+     * @param cid CellIdentityWcdma defined in CellIdentityWcdma.aidl
+     * @return The converted CellIdentityWcdma
+     */
+    public static CellIdentityWcdma convertHalCellIdentityWcdma(
+            android.hardware.radio.network.CellIdentityWcdma cid) {
+        return new CellIdentityWcdma(cid.lac, cid.cid, cid.psc, cid.uarfcn, cid.mcc, cid.mnc,
+                cid.operatorNames.alphaLong, cid.operatorNames.alphaShort,
+                primitiveArrayToArrayList(cid.additionalPlmns),
+                convertHalClosedSubscriberGroupInfo(cid.csgInfo));
+    }
+
+    /**
+     * Convert a CellIdentityTdscdma defined in radio/1.0, 1.2, 1.5/types.hal to CellIdentityTdscdma
+     * @param tdscdma CellIdentityTdscdma defined in radio/1.0, 1.2, 1.5/types.hal
+     * @return The converted CellIdentityTdscdma
+     */
+    public static CellIdentityTdscdma convertHalCellIdentityTdscdma(Object tdscdma) {
+        if (tdscdma == null) return null;
+        if (tdscdma instanceof android.hardware.radio.V1_0.CellIdentityTdscdma) {
+            android.hardware.radio.V1_0.CellIdentityTdscdma ci =
+                    (android.hardware.radio.V1_0.CellIdentityTdscdma) tdscdma;
+            return new CellIdentityTdscdma(ci.mcc, ci.mnc, ci.lac, ci.cid, ci.cpid,
+                    CellInfo.UNAVAILABLE, "", "", Collections.emptyList(), null);
+        } else if (tdscdma instanceof android.hardware.radio.V1_2.CellIdentityTdscdma) {
+            android.hardware.radio.V1_2.CellIdentityTdscdma ci =
+                    (android.hardware.radio.V1_2.CellIdentityTdscdma) tdscdma;
+            return new CellIdentityTdscdma(ci.base.mcc, ci.base.mnc, ci.base.lac, ci.base.cid,
+                    ci.base.cpid, ci.uarfcn, ci.operatorNames.alphaLong,
+                    ci.operatorNames.alphaShort, Collections.emptyList(), null);
+        } else if (tdscdma instanceof android.hardware.radio.V1_5.CellIdentityTdscdma) {
+            android.hardware.radio.V1_5.CellIdentityTdscdma ci =
+                    (android.hardware.radio.V1_5.CellIdentityTdscdma) tdscdma;
+            return new CellIdentityTdscdma(ci.base.base.mcc, ci.base.base.mnc, ci.base.base.lac,
+                    ci.base.base.cid, ci.base.base.cpid, ci.base.uarfcn,
+                    ci.base.operatorNames.alphaLong, ci.base.operatorNames.alphaShort,
+                    ci.additionalPlmns, convertHalClosedSubscriberGroupInfo(ci.optionalCsgInfo));
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Convert a CellIdentityTdscdma defined in CellIdentityTdscdma.aidl to CellIdentityTdscdma
+     * @param cid CellIdentityTdscdma defined in radio/1.0, 1.2, 1.5/types.hal
+     * @return The converted CellIdentityTdscdma
+     */
+    public static CellIdentityTdscdma convertHalCellIdentityTdscdma(
+            android.hardware.radio.network.CellIdentityTdscdma cid) {
+        return new CellIdentityTdscdma(cid.mcc, cid.mnc, cid.lac, cid.cid, cid.cpid, cid.uarfcn,
+                cid.operatorNames.alphaLong, cid.operatorNames.alphaShort,
+                primitiveArrayToArrayList(cid.additionalPlmns),
+                convertHalClosedSubscriberGroupInfo(cid.csgInfo));
+    }
+
+    /**
+     * Convert a CellIdentityNr defined in radio/1.4, 1.5/types.hal to CellIdentityNr
+     * @param nr CellIdentityNr defined in radio/1.4 1.5/types.hal
+     * @return The converted CellIdentityNr
+     */
+    public static CellIdentityNr convertHalCellIdentityNr(Object nr) {
+        if (nr == null) return null;
+        if (nr instanceof android.hardware.radio.V1_4.CellIdentityNr) {
+            android.hardware.radio.V1_4.CellIdentityNr ci =
+                    (android.hardware.radio.V1_4.CellIdentityNr) nr;
+            return new CellIdentityNr(ci.pci, ci.tac, ci.nrarfcn, new int[] {}, ci.mcc, ci.mnc,
+                    ci.nci, ci.operatorNames.alphaLong, ci.operatorNames.alphaShort,
+                    new ArraySet<>());
+        } else if (nr instanceof android.hardware.radio.V1_5.CellIdentityNr) {
+            android.hardware.radio.V1_5.CellIdentityNr ci =
+                    (android.hardware.radio.V1_5.CellIdentityNr) nr;
+            return new CellIdentityNr(ci.base.pci, ci.base.tac, ci.base.nrarfcn,
+                    ci.bands.stream().mapToInt(Integer::intValue).toArray(), ci.base.mcc,
+                    ci.base.mnc, ci.base.nci, ci.base.operatorNames.alphaLong,
+                    ci.base.operatorNames.alphaShort, ci.additionalPlmns);
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Convert a CellIdentityNr defined in CellIdentityNr.aidl to CellIdentityNr
+     * @param cid CellIdentityNr defined in CellIdentityNr.aidl
+     * @return The converted CellIdentityNr
+     */
+    public static CellIdentityNr convertHalCellIdentityNr(
+            android.hardware.radio.network.CellIdentityNr cid) {
+        return new CellIdentityNr(cid.pci, cid.tac, cid.nrarfcn, cid.bands, cid.mcc, cid.mnc,
+                cid.nci, cid.operatorNames.alphaLong, cid.operatorNames.alphaShort,
+                primitiveArrayToArrayList(cid.additionalPlmns));
+    }
+
+    /**
+     * Convert a SignalStrength defined in radio/1.0, 1.2, 1.4, 1.6/types.hal to SignalStrength
+     * @param ss SignalStrength defined in radio/1.0, 1.2, 1.4, 1.6/types.hal
+     * @return The converted SignalStrength
+     */
+    public static SignalStrength convertHalSignalStrength(Object ss) {
+        if (ss == null) return null;
+        if (ss instanceof android.hardware.radio.V1_0.SignalStrength) {
+            android.hardware.radio.V1_0.SignalStrength signalStrength =
+                    (android.hardware.radio.V1_0.SignalStrength) ss;
+            return new SignalStrength(
+                    RILUtils.convertHalCdmaSignalStrength(signalStrength.cdma, signalStrength.evdo),
+                    RILUtils.convertHalGsmSignalStrength(signalStrength.gw),
+                    new CellSignalStrengthWcdma(),
+                    RILUtils.convertHalTdscdmaSignalStrength(signalStrength.tdScdma),
+                    RILUtils.convertHalLteSignalStrength(signalStrength.lte),
+                    new CellSignalStrengthNr());
+        } else if (ss instanceof android.hardware.radio.V1_2.SignalStrength) {
+            android.hardware.radio.V1_2.SignalStrength signalStrength =
+                    (android.hardware.radio.V1_2.SignalStrength) ss;
+            return new SignalStrength(
+                    RILUtils.convertHalCdmaSignalStrength(signalStrength.cdma, signalStrength.evdo),
+                    RILUtils.convertHalGsmSignalStrength(signalStrength.gsm),
+                    RILUtils.convertHalWcdmaSignalStrength(signalStrength.wcdma),
+                    RILUtils.convertHalTdscdmaSignalStrength(signalStrength.tdScdma),
+                    RILUtils.convertHalLteSignalStrength(signalStrength.lte),
+                    new CellSignalStrengthNr());
+        } else if (ss instanceof android.hardware.radio.V1_4.SignalStrength) {
+            android.hardware.radio.V1_4.SignalStrength signalStrength =
+                    (android.hardware.radio.V1_4.SignalStrength) ss;
+            return new SignalStrength(
+                    RILUtils.convertHalCdmaSignalStrength(signalStrength.cdma, signalStrength.evdo),
+                    RILUtils.convertHalGsmSignalStrength(signalStrength.gsm),
+                    RILUtils.convertHalWcdmaSignalStrength(signalStrength.wcdma),
+                    RILUtils.convertHalTdscdmaSignalStrength(signalStrength.tdscdma),
+                    RILUtils.convertHalLteSignalStrength(signalStrength.lte),
+                    RILUtils.convertHalNrSignalStrength(signalStrength.nr));
+        } else if (ss instanceof android.hardware.radio.V1_6.SignalStrength) {
+            android.hardware.radio.V1_6.SignalStrength signalStrength =
+                    (android.hardware.radio.V1_6.SignalStrength) ss;
+            return new SignalStrength(
+                    RILUtils.convertHalCdmaSignalStrength(signalStrength.cdma, signalStrength.evdo),
+                    RILUtils.convertHalGsmSignalStrength(signalStrength.gsm),
+                    RILUtils.convertHalWcdmaSignalStrength(signalStrength.wcdma),
+                    RILUtils.convertHalTdscdmaSignalStrength(signalStrength.tdscdma),
+                    RILUtils.convertHalLteSignalStrength(signalStrength.lte),
+                    RILUtils.convertHalNrSignalStrength(signalStrength.nr));
+        }
+        return null;
+    }
+
+    /**
+     * Convert a SignalStrength defined in SignalStrength.aidl to SignalStrength
+     * @param signalStrength SignalStrength defined in SignalStrength.aidl
+     * @return The converted SignalStrength
+     */
+    public static SignalStrength convertHalSignalStrength(
+            android.hardware.radio.network.SignalStrength signalStrength) {
+        return new SignalStrength(
+                RILUtils.convertHalCdmaSignalStrength(signalStrength.cdma, signalStrength.evdo),
+                RILUtils.convertHalGsmSignalStrength(signalStrength.gsm),
+                RILUtils.convertHalWcdmaSignalStrength(signalStrength.wcdma),
+                RILUtils.convertHalTdscdmaSignalStrength(signalStrength.tdscdma),
+                RILUtils.convertHalLteSignalStrength(signalStrength.lte),
+                RILUtils.convertHalNrSignalStrength(signalStrength.nr));
+    }
+
+    /**
+     * Convert a GsmSignalStrength defined in radio/1.0/types.hal to CellSignalStrengthGsm
+     * @param ss GsmSignalStrength defined in radio/1.0/types.hal
+     * @return The converted CellSignalStrengthGsm
+     */
+    public static CellSignalStrengthGsm convertHalGsmSignalStrength(
+            android.hardware.radio.V1_0.GsmSignalStrength ss) {
+        CellSignalStrengthGsm ret = new CellSignalStrengthGsm(
+                CellSignalStrength.getRssiDbmFromAsu(ss.signalStrength), ss.bitErrorRate,
+                ss.timingAdvance);
+        if (ret.getRssi() == CellInfo.UNAVAILABLE) {
+            ret.setDefaultValues();
+            ret.updateLevel(null, null);
+        }
+        return ret;
+    }
+
+    /**
+     * Convert a GsmSignalStrength defined in GsmSignalStrength.aidl to CellSignalStrengthGsm
+     * @param ss GsmSignalStrength defined in GsmSignalStrength.aidl
+     * @return The converted CellSignalStrengthGsm
+     */
+    public static CellSignalStrengthGsm convertHalGsmSignalStrength(
+            android.hardware.radio.network.GsmSignalStrength ss) {
+        CellSignalStrengthGsm ret = new CellSignalStrengthGsm(
+                CellSignalStrength.getRssiDbmFromAsu(ss.signalStrength), ss.bitErrorRate,
+                ss.timingAdvance);
+        if (ret.getRssi() == CellInfo.UNAVAILABLE) {
+            ret.setDefaultValues();
+            ret.updateLevel(null, null);
+        }
+        return ret;
+    }
+
+    /**
+     * Convert a CdmaSignalStrength and EvdoSignalStrength defined in radio/1.0/types.hal to
+     * CellSignalStrengthCdma
+     * @param cdma CdmaSignalStrength defined in radio/1.0/types.hal
+     * @param evdo EvdoSignalStrength defined in radio/1.0/types.hal
+     * @return The converted CellSignalStrengthCdma
+     */
+    public static CellSignalStrengthCdma convertHalCdmaSignalStrength(
+            android.hardware.radio.V1_0.CdmaSignalStrength cdma,
+            android.hardware.radio.V1_0.EvdoSignalStrength evdo) {
+        return new CellSignalStrengthCdma(-cdma.dbm, -cdma.ecio, -evdo.dbm, -evdo.ecio,
+                evdo.signalNoiseRatio);
+    }
+
+    /**
+     * Convert a CdmaSignalStrength and EvdoSignalStrength defined in radio/network to
+     * CellSignalStrengthCdma
+     * @param cdma CdmaSignalStrength defined in CdmaSignalStrength.aidl
+     * @param evdo EvdoSignalStrength defined in EvdoSignalStrength.aidl
+     * @return The converted CellSignalStrengthCdma
+     */
+    public static CellSignalStrengthCdma convertHalCdmaSignalStrength(
+            android.hardware.radio.network.CdmaSignalStrength cdma,
+            android.hardware.radio.network.EvdoSignalStrength evdo) {
+        return new CellSignalStrengthCdma(-cdma.dbm, -cdma.ecio, -evdo.dbm, -evdo.ecio,
+                evdo.signalNoiseRatio);
+    }
+
+    /**
+     * Convert a LteSignalStrength defined in radio/1.0, 1.6/types.hal to CellSignalStrengthLte
+     * @param lte LteSignalStrength defined in radio/1.0, 1.6/types.hal
+     * @return The converted CellSignalStrengthLte
+     */
+    public static CellSignalStrengthLte convertHalLteSignalStrength(Object lte) {
+        if (lte == null) return null;
+        if (lte instanceof android.hardware.radio.V1_0.LteSignalStrength) {
+            android.hardware.radio.V1_0.LteSignalStrength ss =
+                    (android.hardware.radio.V1_0.LteSignalStrength) lte;
+            return new CellSignalStrengthLte(
+                    CellSignalStrengthLte.convertRssiAsuToDBm(ss.signalStrength),
+                    ss.rsrp != CellInfo.UNAVAILABLE ? -ss.rsrp : ss.rsrp,
+                    ss.rsrq != CellInfo.UNAVAILABLE ? -ss.rsrq : ss.rsrq,
+                    CellSignalStrengthLte.convertRssnrUnitFromTenDbToDB(ss.rssnr), ss.cqi,
+                    ss.timingAdvance);
+        } else if (lte instanceof android.hardware.radio.V1_6.LteSignalStrength) {
+            android.hardware.radio.V1_6.LteSignalStrength ss =
+                    (android.hardware.radio.V1_6.LteSignalStrength) lte;
+            return new CellSignalStrengthLte(
+                    CellSignalStrengthLte.convertRssiAsuToDBm(ss.base.signalStrength),
+                    ss.base.rsrp != CellInfo.UNAVAILABLE ? -ss.base.rsrp : ss.base.rsrp,
+                    ss.base.rsrq != CellInfo.UNAVAILABLE ? -ss.base.rsrq : ss.base.rsrq,
+                    CellSignalStrengthLte.convertRssnrUnitFromTenDbToDB(ss.base.rssnr),
+                    ss.cqiTableIndex, ss.base.cqi, ss.base.timingAdvance);
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Convert a LteSignalStrength defined in LteSignalStrength.aidl to CellSignalStrengthLte
+     * @param ss LteSignalStrength defined in LteSignalStrength.aidl
+     * @return The converted CellSignalStrengthLte
+     */
+    public static CellSignalStrengthLte convertHalLteSignalStrength(
+            android.hardware.radio.network.LteSignalStrength ss) {
+        return new CellSignalStrengthLte(
+                CellSignalStrengthLte.convertRssiAsuToDBm(ss.signalStrength),
+                ss.rsrp != CellInfo.UNAVAILABLE ? -ss.rsrp : ss.rsrp,
+                ss.rsrq != CellInfo.UNAVAILABLE ? -ss.rsrq : ss.rsrq,
+                CellSignalStrengthLte.convertRssnrUnitFromTenDbToDB(ss.rssnr), ss.cqiTableIndex,
+                ss.cqi, ss.timingAdvance);
+    }
+
+    /**
+     * Convert a WcdmaSignalStrength defined in radio/1.0, 1.2/types.hal to CellSignalStrengthWcdma
+     * @param wcdma WcdmaSignalStrength defined in radio/1.0, 1.2/types.hal
+     * @return The converted CellSignalStrengthWcdma
+     */
+    public static CellSignalStrengthWcdma convertHalWcdmaSignalStrength(Object wcdma) {
+        if (wcdma == null) return null;
+        CellSignalStrengthWcdma ret = null;
+        if (wcdma instanceof android.hardware.radio.V1_0.WcdmaSignalStrength) {
+            android.hardware.radio.V1_0.WcdmaSignalStrength ss =
+                    (android.hardware.radio.V1_0.WcdmaSignalStrength) wcdma;
+            ret = new CellSignalStrengthWcdma(
+                    CellSignalStrength.getRssiDbmFromAsu(ss.signalStrength), ss.bitErrorRate,
+                    CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE);
+        } else if (wcdma instanceof android.hardware.radio.V1_2.WcdmaSignalStrength) {
+            android.hardware.radio.V1_2.WcdmaSignalStrength ss =
+                    (android.hardware.radio.V1_2.WcdmaSignalStrength) wcdma;
+            ret = new CellSignalStrengthWcdma(
+                    CellSignalStrength.getRssiDbmFromAsu(ss.base.signalStrength),
+                    ss.base.bitErrorRate, CellSignalStrength.getRscpDbmFromAsu(ss.rscp),
+                    CellSignalStrength.getEcNoDbFromAsu(ss.ecno));
+        }
+        if (ret != null && ret.getRssi() == CellInfo.UNAVAILABLE
+                && ret.getRscp() == CellInfo.UNAVAILABLE) {
+            ret.setDefaultValues();
+            ret.updateLevel(null, null);
+        }
+        return ret;
+    }
+
+    /**
+     * Convert a WcdmaSignalStrength defined in WcdmaSignalStrength.aidl to CellSignalStrengthWcdma
+     * @param ss WcdmaSignalStrength defined in WcdmaSignalStrength.aidl
+     * @return The converted CellSignalStrengthWcdma
+     */
+    public static CellSignalStrengthWcdma convertHalWcdmaSignalStrength(
+            android.hardware.radio.network.WcdmaSignalStrength ss) {
+        CellSignalStrengthWcdma ret = new CellSignalStrengthWcdma(
+                CellSignalStrength.getRssiDbmFromAsu(ss.signalStrength),
+                ss.bitErrorRate, CellSignalStrength.getRscpDbmFromAsu(ss.rscp),
+                CellSignalStrength.getEcNoDbFromAsu(ss.ecno));
+        if (ret.getRssi() == CellInfo.UNAVAILABLE && ret.getRscp() == CellInfo.UNAVAILABLE) {
+            ret.setDefaultValues();
+            ret.updateLevel(null, null);
+        }
+        return ret;
+    }
+
+    /**
+     * Convert a TdScdmaSignalStrength defined in radio/1.0/types.hal or TdscdmaSignalStrength
+     * defined in radio/1.2/types.hal to CellSignalStrengthTdscdma
+     * @param tdscdma TdScdmaSignalStrength defined in radio/1.0/types.hal or TdscdmaSignalStrength
+     *        defined in radio/1.2/types.hal
+     * @return The converted CellSignalStrengthTdscdma
+     */
+    public static CellSignalStrengthTdscdma convertHalTdscdmaSignalStrength(Object tdscdma) {
+        if (tdscdma == null) return null;
+        CellSignalStrengthTdscdma ret = null;
+        if (tdscdma instanceof android.hardware.radio.V1_0.TdScdmaSignalStrength) {
+            android.hardware.radio.V1_0.TdScdmaSignalStrength ss =
+                    (android.hardware.radio.V1_0.TdScdmaSignalStrength) tdscdma;
+            ret = new CellSignalStrengthTdscdma(CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE,
+                    ss.rscp != CellInfo.UNAVAILABLE ? -ss.rscp : ss.rscp);
+        } else if (tdscdma instanceof android.hardware.radio.V1_2.TdscdmaSignalStrength) {
+            android.hardware.radio.V1_2.TdscdmaSignalStrength ss =
+                    (android.hardware.radio.V1_2.TdscdmaSignalStrength) tdscdma;
+            ret = new CellSignalStrengthTdscdma(
+                    CellSignalStrength.getRssiDbmFromAsu(ss.signalStrength), ss.bitErrorRate,
+                    CellSignalStrength.getRscpDbmFromAsu(ss.rscp));
+        }
+        if (ret != null && ret.getRssi() == CellInfo.UNAVAILABLE
+                && ret.getRscp() == CellInfo.UNAVAILABLE) {
+            ret.setDefaultValues();
+            ret.updateLevel(null, null);
+        }
+        return ret;
+    }
+
+    /**
+     * Convert a TdscdmaSignalStrength defined in TdscdmaSignalStrength.aidl to
+     * CellSignalStrengthTdscdma
+     * @param ss TdscdmaSignalStrength defined in TdscdmaSignalStrength.aidl
+     * @return The converted CellSignalStrengthTdscdma
+     */
+    public static CellSignalStrengthTdscdma convertHalTdscdmaSignalStrength(
+            android.hardware.radio.network.TdscdmaSignalStrength ss) {
+        CellSignalStrengthTdscdma ret = new CellSignalStrengthTdscdma(
+                CellSignalStrength.getRssiDbmFromAsu(ss.signalStrength),
+                ss.bitErrorRate, CellSignalStrength.getRscpDbmFromAsu(ss.rscp));
+        if (ret.getRssi() == CellInfo.UNAVAILABLE && ret.getRscp() == CellInfo.UNAVAILABLE) {
+            ret.setDefaultValues();
+            ret.updateLevel(null, null);
+        }
+        return ret;
+    }
+
+    /**
+     * Convert a NrSignalStrength defined in radio/1.4, 1.6/types.hal to CellSignalStrengthNr
+     * @param nr NrSignalStrength defined in radio/1.4, 1.6/types.hal
+     * @return The converted CellSignalStrengthNr
+     */
+    public static CellSignalStrengthNr convertHalNrSignalStrength(Object nr) {
+        if (nr == null) return null;
+        if (nr instanceof android.hardware.radio.V1_4.NrSignalStrength) {
+            android.hardware.radio.V1_4.NrSignalStrength ss =
+                    (android.hardware.radio.V1_4.NrSignalStrength) nr;
+            return new CellSignalStrengthNr(CellSignalStrengthNr.flip(ss.csiRsrp),
+                    CellSignalStrengthNr.flip(ss.csiRsrq), ss.csiSinr,
+                    CellSignalStrengthNr.flip(ss.ssRsrp), CellSignalStrengthNr.flip(ss.ssRsrq),
+                    ss.ssSinr);
+        } else if (nr instanceof android.hardware.radio.V1_6.NrSignalStrength) {
+            android.hardware.radio.V1_6.NrSignalStrength ss =
+                    (android.hardware.radio.V1_6.NrSignalStrength) nr;
+            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);
+        }
+        return null;
+    }
+
+    /**
+     * Convert a NrSignalStrength defined in NrSignalStrength.aidl to CellSignalStrengthNr
+     * @param ss NrSignalStrength defined in NrSignalStrength.aidl
+     * @return The converted CellSignalStrengthNr
+     */
+    public static CellSignalStrengthNr convertHalNrSignalStrength(
+            android.hardware.radio.network.NrSignalStrength ss) {
+        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);
+    }
+
+    private static ClosedSubscriberGroupInfo convertHalClosedSubscriberGroupInfo(
+            android.hardware.radio.V1_5.OptionalCsgInfo optionalCsgInfo) {
+        android.hardware.radio.V1_5.ClosedSubscriberGroupInfo csgInfo =
+                optionalCsgInfo.getDiscriminator()
+                        == android.hardware.radio.V1_5.OptionalCsgInfo.hidl_discriminator.csgInfo
+                        ? optionalCsgInfo.csgInfo() : null;
+        if (csgInfo == null) return null;
+        return new ClosedSubscriberGroupInfo(csgInfo.csgIndication, csgInfo.homeNodebName,
+                csgInfo.csgIdentity);
+    }
+
+    private static ClosedSubscriberGroupInfo convertHalClosedSubscriberGroupInfo(
+            android.hardware.radio.network.ClosedSubscriberGroupInfo csgInfo) {
+        if (csgInfo == null) return null;
+        return new ClosedSubscriberGroupInfo(csgInfo.csgIndication, csgInfo.homeNodebName,
+                csgInfo.csgIdentity);
+    }
+
+    /**
+     * Convert a list of BarringInfo defined in radio/1.5/types.hal to a sparse array of
+     * BarringServiceInfos
+     * @param halBarringInfos List of BarringInfos defined in radio/1.5/types.hal
+     * @return The converted sparse array of BarringServiceInfos
+     */
+    public static SparseArray<BarringInfo.BarringServiceInfo> convertHalBarringInfoList(
+            List<android.hardware.radio.V1_5.BarringInfo> halBarringInfos) {
+        SparseArray<BarringInfo.BarringServiceInfo> serviceInfos = new SparseArray<>();
+        for (android.hardware.radio.V1_5.BarringInfo halBarringInfo : halBarringInfos) {
+            if (halBarringInfo.barringType
+                    == android.hardware.radio.V1_5.BarringInfo.BarringType.CONDITIONAL) {
+                if (halBarringInfo.barringTypeSpecificInfo.getDiscriminator()
+                        != android.hardware.radio.V1_5.BarringInfo.BarringTypeSpecificInfo
+                        .hidl_discriminator.conditional) {
+                    // this is an error case where the barring info is conditional but the
+                    // conditional barring fields weren't included
+                    continue;
+                }
+                android.hardware.radio.V1_5.BarringInfo.BarringTypeSpecificInfo
+                        .Conditional conditionalInfo =
+                        halBarringInfo.barringTypeSpecificInfo.conditional();
+                serviceInfos.put(
+                        halBarringInfo.serviceType, new BarringInfo.BarringServiceInfo(
+                                halBarringInfo.barringType, // will always be CONDITIONAL here
+                                conditionalInfo.isBarred,
+                                conditionalInfo.factor,
+                                conditionalInfo.timeSeconds));
+            } else {
+                // Barring type is either NONE or UNCONDITIONAL
+                serviceInfos.put(
+                        halBarringInfo.serviceType, new BarringInfo.BarringServiceInfo(
+                                halBarringInfo.barringType, false, 0, 0));
+            }
+        }
+        return serviceInfos;
+    }
+
+    /**
+     * Convert a list of BarringInfo defined in BarringInfo.aidl to a sparse array of
+     * BarringServiceInfos
+     * @param halBarringInfos List of BarringInfos defined in BarringInfo.aidl
+     * @return The converted sparse array of BarringServiceInfos
+     */
+    public static SparseArray<BarringInfo.BarringServiceInfo> convertHalBarringInfoList(
+            android.hardware.radio.network.BarringInfo[] halBarringInfos) {
+        SparseArray<BarringInfo.BarringServiceInfo> serviceInfos = new SparseArray<>();
+        for (android.hardware.radio.network.BarringInfo halBarringInfo : halBarringInfos) {
+            if (halBarringInfo.barringType
+                    == android.hardware.radio.network.BarringInfo.BARRING_TYPE_CONDITIONAL) {
+                if (halBarringInfo.barringTypeSpecificInfo == null) {
+                    // this is an error case where the barring info is conditional but the
+                    // conditional barring fields weren't included
+                    continue;
+                }
+                serviceInfos.put(
+                        halBarringInfo.serviceType, new BarringInfo.BarringServiceInfo(
+                                halBarringInfo.barringType, // will always be CONDITIONAL here
+                                halBarringInfo.barringTypeSpecificInfo.isBarred,
+                                halBarringInfo.barringTypeSpecificInfo.factor,
+                                halBarringInfo.barringTypeSpecificInfo.timeSeconds));
+            } else {
+                // Barring type is either NONE or UNCONDITIONAL
+                serviceInfos.put(halBarringInfo.serviceType, new BarringInfo.BarringServiceInfo(
+                        halBarringInfo.barringType, false, 0, 0));
+            }
+        }
+        return serviceInfos;
+    }
+
+    private static LinkAddress convertToLinkAddress(String addressString) {
+        return convertToLinkAddress(addressString, 0, LinkAddress.LIFETIME_UNKNOWN,
+                LinkAddress.LIFETIME_UNKNOWN);
+    }
+
+    private static LinkAddress convertToLinkAddress(String addressString, int properties,
+            long deprecationTime, long expirationTime) {
+        addressString = addressString.trim();
+        InetAddress address = null;
+        int prefixLength = -1;
+        try {
+            String[] pieces = addressString.split("/", 2);
+            address = InetAddresses.parseNumericAddress(pieces[0]);
+            if (pieces.length == 1) {
+                prefixLength = (address instanceof Inet4Address) ? 32 : 128;
+            } else if (pieces.length == 2) {
+                prefixLength = Integer.parseInt(pieces[1]);
+            }
+        } catch (NullPointerException e) {            // Null string.
+        } catch (ArrayIndexOutOfBoundsException e) {  // No prefix length.
+        } catch (NumberFormatException e) {           // Non-numeric prefix.
+        } catch (IllegalArgumentException e) {        // Invalid IP address.
+        }
+
+        if (address == null || prefixLength == -1) {
+            throw new IllegalArgumentException("Invalid link address " + addressString);
+        }
+
+        return new LinkAddress(address, prefixLength, properties, 0, deprecationTime,
+                expirationTime);
+    }
+
+    /**
+     * Convert SetupDataCallResult defined in radio/1.0, 1.4, 1.5, 1.6/types.hal into
+     * DataCallResponse
+     * @param dcResult SetupDataCallResult defined in radio/1.0, 1.4, 1.5, 1.6/types.hal
+     * @return The converted DataCallResponse
+     */
+    @VisibleForTesting
+    public static DataCallResponse convertHalDataCallResult(Object dcResult) {
+        if (dcResult == null) return null;
+
+        int cause, cid, active, mtu, mtuV4, mtuV6;
+        long suggestedRetryTime;
+        String ifname;
+        int protocolType;
+        String[] addresses = null;
+        String[] dnses = null;
+        String[] gateways = null;
+        String[] pcscfs = null;
+        Qos defaultQos = null;
+        @DataCallResponse.HandoverFailureMode
+        int handoverFailureMode = DataCallResponse.HANDOVER_FAILURE_MODE_LEGACY;
+        int pduSessionId = DataCallResponse.PDU_SESSION_ID_NOT_SET;
+        List<LinkAddress> laList = new ArrayList<>();
+        List<QosBearerSession> qosSessions = new ArrayList<>();
+        NetworkSliceInfo sliceInfo = null;
+        List<TrafficDescriptor> trafficDescriptors = new ArrayList<>();
+
+        if (dcResult instanceof android.hardware.radio.V1_0.SetupDataCallResult) {
+            final android.hardware.radio.V1_0.SetupDataCallResult result =
+                    (android.hardware.radio.V1_0.SetupDataCallResult) dcResult;
+            cause = result.status;
+            suggestedRetryTime = result.suggestedRetryTime;
+            cid = result.cid;
+            active = result.active;
+            protocolType = ApnSetting.getProtocolIntFromString(result.type);
+            ifname = result.ifname;
+            if (!TextUtils.isEmpty(result.addresses)) {
+                addresses = result.addresses.split("\\s+");
+            }
+            if (!TextUtils.isEmpty(result.dnses)) {
+                dnses = result.dnses.split("\\s+");
+            }
+            if (!TextUtils.isEmpty(result.gateways)) {
+                gateways = result.gateways.split("\\s+");
+            }
+            if (!TextUtils.isEmpty(result.pcscf)) {
+                pcscfs = result.pcscf.split("\\s+");
+            }
+            mtu = mtuV4 = mtuV6 = result.mtu;
+            if (addresses != null) {
+                for (String address : addresses) {
+                    laList.add(convertToLinkAddress(address));
+                }
+            }
+        } else if (dcResult instanceof android.hardware.radio.V1_4.SetupDataCallResult) {
+            final android.hardware.radio.V1_4.SetupDataCallResult result =
+                    (android.hardware.radio.V1_4.SetupDataCallResult) dcResult;
+            cause = result.cause;
+            suggestedRetryTime = result.suggestedRetryTime;
+            cid = result.cid;
+            active = result.active;
+            protocolType = result.type;
+            ifname = result.ifname;
+            addresses = result.addresses.toArray(new String[0]);
+            dnses = result.dnses.toArray(new String[0]);
+            gateways = result.gateways.toArray(new String[0]);
+            pcscfs = result.pcscf.toArray(new String[0]);
+            mtu = mtuV4 = mtuV6 = result.mtu;
+            if (addresses != null) {
+                for (String address : addresses) {
+                    laList.add(convertToLinkAddress(address));
+                }
+            }
+        } else if (dcResult instanceof android.hardware.radio.V1_5.SetupDataCallResult) {
+            final android.hardware.radio.V1_5.SetupDataCallResult result =
+                    (android.hardware.radio.V1_5.SetupDataCallResult) dcResult;
+            cause = result.cause;
+            suggestedRetryTime = result.suggestedRetryTime;
+            cid = result.cid;
+            active = result.active;
+            protocolType = result.type;
+            ifname = result.ifname;
+            laList = result.addresses.stream().map(la -> convertToLinkAddress(
+                    la.address, la.properties, la.deprecationTime, la.expirationTime))
+                    .collect(Collectors.toList());
+            dnses = result.dnses.toArray(new String[0]);
+            gateways = result.gateways.toArray(new String[0]);
+            pcscfs = result.pcscf.toArray(new String[0]);
+            mtu = Math.max(result.mtuV4, result.mtuV6);
+            mtuV4 = result.mtuV4;
+            mtuV6 = result.mtuV6;
+        } else if (dcResult instanceof android.hardware.radio.V1_6.SetupDataCallResult) {
+            final android.hardware.radio.V1_6.SetupDataCallResult result =
+                    (android.hardware.radio.V1_6.SetupDataCallResult) dcResult;
+            cause = result.cause;
+            suggestedRetryTime = result.suggestedRetryTime;
+            cid = result.cid;
+            active = result.active;
+            protocolType = result.type;
+            ifname = result.ifname;
+            laList = result.addresses.stream().map(la -> convertToLinkAddress(
+                    la.address, la.properties, la.deprecationTime, la.expirationTime))
+                    .collect(Collectors.toList());
+            dnses = result.dnses.toArray(new String[0]);
+            gateways = result.gateways.toArray(new String[0]);
+            pcscfs = result.pcscf.toArray(new String[0]);
+            mtu = Math.max(result.mtuV4, result.mtuV6);
+            mtuV4 = result.mtuV4;
+            mtuV6 = result.mtuV6;
+            handoverFailureMode = result.handoverFailureMode;
+            pduSessionId = result.pduSessionId;
+            defaultQos = convertHalQos(result.defaultQos);
+            qosSessions = result.qosSessions.stream().map(RILUtils::convertHalQosBearerSession)
+                    .collect(Collectors.toList());
+            sliceInfo = result.sliceInfo.getDiscriminator()
+                    == android.hardware.radio.V1_6.OptionalSliceInfo.hidl_discriminator.noinit
+                    ? null : convertHalSliceInfo(result.sliceInfo.value());
+            trafficDescriptors = result.trafficDescriptors.stream().map(
+                    RILUtils::convertHalTrafficDescriptor).collect(Collectors.toList());
+        } else {
+            Rlog.e(LOG_TAG, "Unsupported SetupDataCallResult " + dcResult);
+            return null;
+        }
+
+        // Process dns
+        List<InetAddress> dnsList = new ArrayList<>();
+        if (dnses != null) {
+            for (String dns : dnses) {
+                dns = dns.trim();
+                InetAddress ia;
+                try {
+                    ia = InetAddresses.parseNumericAddress(dns);
+                    dnsList.add(ia);
+                } catch (IllegalArgumentException e) {
+                    Rlog.e(LOG_TAG, "Unknown dns: " + dns, e);
+                }
+            }
+        }
+
+        // Process gateway
+        List<InetAddress> gatewayList = new ArrayList<>();
+        if (gateways != null) {
+            for (String gateway : gateways) {
+                gateway = gateway.trim();
+                InetAddress ia;
+                try {
+                    ia = InetAddresses.parseNumericAddress(gateway);
+                    gatewayList.add(ia);
+                } catch (IllegalArgumentException e) {
+                    Rlog.e(LOG_TAG, "Unknown gateway: " + gateway, e);
+                }
+            }
+        }
+
+        // Process gateway
+        List<InetAddress> pcscfList = new ArrayList<>();
+        if (pcscfs != null) {
+            for (String pcscf : pcscfs) {
+                pcscf = pcscf.trim();
+                InetAddress ia;
+                try {
+                    ia = InetAddresses.parseNumericAddress(pcscf);
+                    pcscfList.add(ia);
+                } catch (IllegalArgumentException e) {
+                    Rlog.e(LOG_TAG, "Unknown pcscf: " + pcscf, e);
+                }
+            }
+        }
+
+        return new DataCallResponse.Builder()
+                .setCause(cause)
+                .setRetryDurationMillis(suggestedRetryTime)
+                .setId(cid)
+                .setLinkStatus(active)
+                .setProtocolType(protocolType)
+                .setInterfaceName(ifname)
+                .setAddresses(laList)
+                .setDnsAddresses(dnsList)
+                .setGatewayAddresses(gatewayList)
+                .setPcscfAddresses(pcscfList)
+                .setMtu(mtu)
+                .setMtuV4(mtuV4)
+                .setMtuV6(mtuV6)
+                .setHandoverFailureMode(handoverFailureMode)
+                .setPduSessionId(pduSessionId)
+                .setDefaultQos(defaultQos)
+                .setQosBearerSessions(qosSessions)
+                .setSliceInfo(sliceInfo)
+                .setTrafficDescriptors(trafficDescriptors)
+                .build();
+    }
+
+    /**
+     * Convert SetupDataCallResult defined in SetupDataCallResult.aidl into DataCallResponse
+     * @param result SetupDataCallResult defined in SetupDataCallResult.aidl
+     * @return The converted DataCallResponse
+     */
+    @VisibleForTesting
+    public static DataCallResponse convertHalDataCallResult(
+            android.hardware.radio.data.SetupDataCallResult result) {
+        if (result == null) return null;
+        List<LinkAddress> laList = new ArrayList<>();
+        for (android.hardware.radio.data.LinkAddress la : result.addresses) {
+            laList.add(convertToLinkAddress(la.address, la.addressProperties,
+                    la.deprecationTime, la.expirationTime));
+        }
+        List<InetAddress> dnsList = new ArrayList<>();
+        if (result.dnses != null) {
+            for (String dns : result.dnses) {
+                dns = dns.trim();
+                InetAddress ia;
+                try {
+                    ia = InetAddresses.parseNumericAddress(dns);
+                    dnsList.add(ia);
+                } catch (IllegalArgumentException e) {
+                    Rlog.e(LOG_TAG, "Unknown dns: " + dns, e);
+                }
+            }
+        }
+        List<InetAddress> gatewayList = new ArrayList<>();
+        if (result.gateways != null) {
+            for (String gateway : result.gateways) {
+                gateway = gateway.trim();
+                InetAddress ia;
+                try {
+                    ia = InetAddresses.parseNumericAddress(gateway);
+                    gatewayList.add(ia);
+                } catch (IllegalArgumentException e) {
+                    Rlog.e(LOG_TAG, "Unknown gateway: " + gateway, e);
+                }
+            }
+        }
+        List<InetAddress> pcscfList = new ArrayList<>();
+        if (result.pcscf != null) {
+            for (String pcscf : result.pcscf) {
+                pcscf = pcscf.trim();
+                InetAddress ia;
+                try {
+                    ia = InetAddresses.parseNumericAddress(pcscf);
+                    pcscfList.add(ia);
+                } catch (IllegalArgumentException e) {
+                    Rlog.e(LOG_TAG, "Unknown pcscf: " + pcscf, e);
+                }
+            }
+        }
+        List<QosBearerSession> qosSessions = new ArrayList<>();
+        for (android.hardware.radio.data.QosSession session : result.qosSessions) {
+            qosSessions.add(convertHalQosBearerSession(session));
+        }
+        List<TrafficDescriptor> trafficDescriptors = new ArrayList<>();
+        for (android.hardware.radio.data.TrafficDescriptor td : result.trafficDescriptors) {
+            trafficDescriptors.add(convertHalTrafficDescriptor(td));
+        }
+
+        return new DataCallResponse.Builder()
+                .setCause(result.cause)
+                .setRetryDurationMillis(result.suggestedRetryTime)
+                .setId(result.cid)
+                .setLinkStatus(result.active)
+                .setProtocolType(result.type)
+                .setInterfaceName(result.ifname)
+                .setAddresses(laList)
+                .setDnsAddresses(dnsList)
+                .setGatewayAddresses(gatewayList)
+                .setPcscfAddresses(pcscfList)
+                .setMtu(Math.max(result.mtuV4, result.mtuV6))
+                .setMtuV4(result.mtuV4)
+                .setMtuV6(result.mtuV6)
+                .setHandoverFailureMode(result.handoverFailureMode)
+                .setPduSessionId(result.pduSessionId)
+                .setDefaultQos(convertHalQos(result.defaultQos))
+                .setQosBearerSessions(qosSessions)
+                .setSliceInfo(convertHalSliceInfo(result.sliceInfo))
+                .setTrafficDescriptors(trafficDescriptors)
+                .build();
+    }
+
+    private static NetworkSliceInfo convertHalSliceInfo(android.hardware.radio.V1_6.SliceInfo si) {
+        NetworkSliceInfo.Builder builder = new NetworkSliceInfo.Builder()
+                .setSliceServiceType(si.sst)
+                .setMappedHplmnSliceServiceType(si.mappedHplmnSst);
+        if (si.sliceDifferentiator != NetworkSliceInfo.SLICE_DIFFERENTIATOR_NO_SLICE) {
+            builder.setSliceDifferentiator(si.sliceDifferentiator)
+                    .setMappedHplmnSliceDifferentiator(si.mappedHplmnSD);
+        }
+        return builder.build();
+    }
+
+    private static NetworkSliceInfo convertHalSliceInfo(android.hardware.radio.data.SliceInfo si) {
+        NetworkSliceInfo.Builder builder = new NetworkSliceInfo.Builder()
+                .setSliceServiceType(si.sliceServiceType)
+                .setMappedHplmnSliceServiceType(si.mappedHplmnSst);
+        if (si.sliceDifferentiator != NetworkSliceInfo.SLICE_DIFFERENTIATOR_NO_SLICE) {
+            builder.setSliceDifferentiator(si.sliceDifferentiator)
+                    .setMappedHplmnSliceDifferentiator(si.mappedHplmnSD);
+        }
+        return builder.build();
+    }
+
+    private static TrafficDescriptor convertHalTrafficDescriptor(
+            android.hardware.radio.V1_6.TrafficDescriptor td) {
+        String dnn = td.dnn.getDiscriminator()
+                == android.hardware.radio.V1_6.OptionalDnn.hidl_discriminator.noinit
+                ? null : td.dnn.value();
+        String osAppId = td.osAppId.getDiscriminator()
+                == android.hardware.radio.V1_6.OptionalOsAppId.hidl_discriminator.noinit
+                ? null : new String(arrayListToPrimitiveArray(td.osAppId.value().osAppId));
+        TrafficDescriptor.Builder builder = new TrafficDescriptor.Builder();
+        if (dnn != null) {
+            builder.setDataNetworkName(dnn);
+        }
+        if (osAppId != null) {
+            builder.setOsAppId(osAppId.getBytes());
+        }
+        return builder.build();
+    }
+
+    private static TrafficDescriptor convertHalTrafficDescriptor(
+            android.hardware.radio.data.TrafficDescriptor td) {
+        String dnn = td.dnn;
+        String osAppId = td.osAppId == null ? null : new String(td.osAppId.osAppId);
+        TrafficDescriptor.Builder builder = new TrafficDescriptor.Builder();
+        if (dnn != null) {
+            builder.setDataNetworkName(dnn);
+        }
+        if (osAppId != null) {
+            builder.setOsAppId(osAppId.getBytes());
+        }
+        return builder.build();
+    }
+
+    /**
+     * Convert SlicingConfig defined in radio/1.6/types.hal to NetworkSlicingConfig
+     * @param sc SlicingConfig defined in radio/1.6/types.hal
+     * @return The converted NetworkSlicingConfig
+     */
+    public static NetworkSlicingConfig convertHalSlicingConfig(
+            android.hardware.radio.V1_6.SlicingConfig sc) {
+        List<UrspRule> urspRules = sc.urspRules.stream().map(ur -> new UrspRule(ur.precedence,
+                ur.trafficDescriptors.stream().map(RILUtils::convertHalTrafficDescriptor)
+                        .collect(Collectors.toList()),
+                ur.routeSelectionDescriptor.stream().map(rsd -> new RouteSelectionDescriptor(
+                        rsd.precedence, rsd.sessionType.value(), rsd.sscMode.value(),
+                        rsd.sliceInfo.stream().map(RILUtils::convertHalSliceInfo)
+                                .collect(Collectors.toList()),
+                        rsd.dnn)).collect(Collectors.toList())))
+                .collect(Collectors.toList());
+        return new NetworkSlicingConfig(urspRules, sc.sliceInfo.stream()
+                .map(RILUtils::convertHalSliceInfo).collect(Collectors.toList()));
+    }
+
+    /**
+     * Convert SlicingConfig defined in SlicingConfig.aidl to NetworkSlicingConfig
+     * @param sc SlicingConfig defined in SlicingConfig.aidl
+     * @return The converted NetworkSlicingConfig
+     */
+    public static NetworkSlicingConfig convertHalSlicingConfig(
+            android.hardware.radio.data.SlicingConfig sc) {
+        List<UrspRule> urspRules = new ArrayList<>();
+        for (android.hardware.radio.data.UrspRule ur : sc.urspRules) {
+            List<TrafficDescriptor> tds = new ArrayList<>();
+            for (android.hardware.radio.data.TrafficDescriptor td : ur.trafficDescriptors) {
+                tds.add(convertHalTrafficDescriptor(td));
+            }
+            List<RouteSelectionDescriptor> rsds = new ArrayList<>();
+            for (android.hardware.radio.data.RouteSelectionDescriptor rsd
+                    : ur.routeSelectionDescriptor) {
+                List<NetworkSliceInfo> sliceInfo = new ArrayList<>();
+                for (android.hardware.radio.data.SliceInfo si : rsd.sliceInfo) {
+                    sliceInfo.add(convertHalSliceInfo(si));
+                }
+                rsds.add(new RouteSelectionDescriptor(rsd.precedence, rsd.sessionType, rsd.sscMode,
+                        sliceInfo, primitiveArrayToArrayList(rsd.dnn)));
+            }
+            urspRules.add(new UrspRule(ur.precedence, tds, rsds));
+        }
+        List<NetworkSliceInfo> sliceInfo = new ArrayList<>();
+        for (android.hardware.radio.data.SliceInfo si : sc.sliceInfo) {
+            sliceInfo.add(convertHalSliceInfo(si));
+        }
+        return new NetworkSlicingConfig(urspRules, sliceInfo);
+    }
+
+    private static Qos.QosBandwidth convertHalQosBandwidth(
+            android.hardware.radio.V1_6.QosBandwidth bandwidth) {
+        return new Qos.QosBandwidth(bandwidth.maxBitrateKbps, bandwidth.guaranteedBitrateKbps);
+    }
+
+    private static Qos.QosBandwidth convertHalQosBandwidth(
+            android.hardware.radio.data.QosBandwidth bandwidth) {
+        return new Qos.QosBandwidth(bandwidth.maxBitrateKbps, bandwidth.guaranteedBitrateKbps);
+    }
+
+    private static Qos convertHalQos(android.hardware.radio.V1_6.Qos qos) {
+        switch (qos.getDiscriminator()) {
+            case android.hardware.radio.V1_6.Qos.hidl_discriminator.eps:
+                android.hardware.radio.V1_6.EpsQos eps = qos.eps();
+                return new EpsQos(convertHalQosBandwidth(eps.downlink),
+                        convertHalQosBandwidth(eps.uplink), eps.qci);
+            case android.hardware.radio.V1_6.Qos.hidl_discriminator.nr:
+                android.hardware.radio.V1_6.NrQos nr = qos.nr();
+                return new NrQos(convertHalQosBandwidth(nr.downlink),
+                        convertHalQosBandwidth(nr.uplink), nr.qfi, nr.fiveQi, nr.averagingWindowMs);
+            default:
+                return null;
+        }
+    }
+
+    private static Qos convertHalQos(android.hardware.radio.data.Qos qos) {
+        switch (qos.getTag()) {
+            case android.hardware.radio.data.Qos.eps:
+                android.hardware.radio.data.EpsQos eps = qos.getEps();
+                return new EpsQos(convertHalQosBandwidth(eps.downlink),
+                        convertHalQosBandwidth(eps.uplink), eps.qci);
+            case android.hardware.radio.data.Qos.nr:
+                android.hardware.radio.data.NrQos nr = qos.getNr();
+                return new NrQos(convertHalQosBandwidth(nr.downlink),
+                        convertHalQosBandwidth(nr.uplink), nr.qfi, nr.fiveQi,
+                        nr.averagingWindowMs);
+            default:
+                return null;
+        }
+    }
+
+    private static QosBearerFilter convertHalQosBearerFilter(
+            android.hardware.radio.V1_6.QosFilter qosFilter) {
+        List<LinkAddress> localAddressList = new ArrayList<>();
+        String[] localAddresses = qosFilter.localAddresses.toArray(new String[0]);
+        if (localAddresses != null) {
+            for (String address : localAddresses) {
+                localAddressList.add(convertToLinkAddress(address));
+            }
+        }
+        List<LinkAddress> remoteAddressList = new ArrayList<>();
+        String[] remoteAddresses = qosFilter.remoteAddresses.toArray(new String[0]);
+        if (remoteAddresses != null) {
+            for (String address : remoteAddresses) {
+                remoteAddressList.add(convertToLinkAddress(address));
+            }
+        }
+        QosBearerFilter.PortRange localPort = null;
+        if (qosFilter.localPort != null) {
+            if (qosFilter.localPort.getDiscriminator()
+                    == android.hardware.radio.V1_6.MaybePort.hidl_discriminator.range) {
+                final android.hardware.radio.V1_6.PortRange portRange = qosFilter.localPort.range();
+                localPort = new QosBearerFilter.PortRange(portRange.start, portRange.end);
+            }
+        }
+        QosBearerFilter.PortRange remotePort = null;
+        if (qosFilter.remotePort != null) {
+            if (qosFilter.remotePort.getDiscriminator()
+                    == android.hardware.radio.V1_6.MaybePort.hidl_discriminator.range) {
+                final android.hardware.radio.V1_6.PortRange portRange =
+                        qosFilter.remotePort.range();
+                remotePort = new QosBearerFilter.PortRange(portRange.start, portRange.end);
+            }
+        }
+        int tos = -1;
+        if (qosFilter.tos != null) {
+            if (qosFilter.tos.getDiscriminator() == android.hardware.radio.V1_6.QosFilter
+                    .TypeOfService.hidl_discriminator.value) {
+                tos = qosFilter.tos.value();
+            }
+        }
+        long flowLabel = -1;
+        if (qosFilter.flowLabel != null) {
+            if (qosFilter.flowLabel.getDiscriminator() == android.hardware.radio.V1_6.QosFilter
+                    .Ipv6FlowLabel.hidl_discriminator.value) {
+                flowLabel = qosFilter.flowLabel.value();
+            }
+        }
+        long spi = -1;
+        if (qosFilter.spi != null) {
+            if (qosFilter.spi.getDiscriminator()
+                    == android.hardware.radio.V1_6.QosFilter.IpsecSpi.hidl_discriminator.value) {
+                spi = qosFilter.spi.value();
+            }
+        }
+        return new QosBearerFilter(localAddressList, remoteAddressList, localPort, remotePort,
+                qosFilter.protocol, tos, flowLabel, spi, qosFilter.direction, qosFilter.precedence);
+    }
+
+    private static QosBearerFilter convertHalQosBearerFilter(
+            android.hardware.radio.data.QosFilter qosFilter) {
+        List<LinkAddress> localAddressList = new ArrayList<>();
+        String[] localAddresses = qosFilter.localAddresses;
+        if (localAddresses != null) {
+            for (String address : localAddresses) {
+                localAddressList.add(convertToLinkAddress(address));
+            }
+        }
+        List<LinkAddress> remoteAddressList = new ArrayList<>();
+        String[] remoteAddresses = qosFilter.remoteAddresses;
+        if (remoteAddresses != null) {
+            for (String address : remoteAddresses) {
+                remoteAddressList.add(convertToLinkAddress(address));
+            }
+        }
+        QosBearerFilter.PortRange localPort = null;
+        if (qosFilter.localPort != null) {
+            localPort = new QosBearerFilter.PortRange(
+                    qosFilter.localPort.start, qosFilter.localPort.end);
+        }
+        QosBearerFilter.PortRange remotePort = null;
+        if (qosFilter.remotePort != null) {
+            remotePort = new QosBearerFilter.PortRange(
+                    qosFilter.remotePort.start, qosFilter.remotePort.end);
+        }
+        int tos = -1;
+        if (qosFilter.tos != null) {
+            if (qosFilter.tos.getTag()
+                    == android.hardware.radio.data.QosFilterTypeOfService.value) {
+                tos = qosFilter.tos.value;
+            }
+        }
+        long flowLabel = -1;
+        if (qosFilter.flowLabel != null) {
+            if (qosFilter.flowLabel.getTag()
+                    == android.hardware.radio.data.QosFilterIpv6FlowLabel.value) {
+                flowLabel = qosFilter.flowLabel.value;
+            }
+        }
+        long spi = -1;
+        if (qosFilter.spi != null) {
+            if (qosFilter.spi.getTag()
+                    == android.hardware.radio.data.QosFilterIpsecSpi.value) {
+                spi = qosFilter.spi.value;
+            }
+        }
+        return new QosBearerFilter(localAddressList, remoteAddressList, localPort, remotePort,
+                qosFilter.protocol, tos, flowLabel, spi, qosFilter.direction, qosFilter.precedence);
+    }
+
+    private static QosBearerSession convertHalQosBearerSession(
+            android.hardware.radio.V1_6.QosSession qosSession) {
+        List<QosBearerFilter> qosBearerFilters = new ArrayList<>();
+        if (qosSession.qosFilters != null) {
+            for (android.hardware.radio.V1_6.QosFilter filter : qosSession.qosFilters) {
+                qosBearerFilters.add(convertHalQosBearerFilter(filter));
+            }
+        }
+        return new QosBearerSession(qosSession.qosSessionId, convertHalQos(qosSession.qos),
+                qosBearerFilters);
+    }
+
+    private static QosBearerSession convertHalQosBearerSession(
+            android.hardware.radio.data.QosSession qosSession) {
+        List<QosBearerFilter> qosBearerFilters = new ArrayList<>();
+        if (qosSession.qosFilters != null) {
+            for (android.hardware.radio.data.QosFilter filter : qosSession.qosFilters) {
+                qosBearerFilters.add(convertHalQosBearerFilter(filter));
+            }
+        }
+        return new QosBearerSession(qosSession.qosSessionId, convertHalQos(qosSession.qos),
+                qosBearerFilters);
+    }
+
+    /**
+     * Convert a list of SetupDataCallResult defined in radio/1.0, 1.4, 1.5, 1.6/types.hal into
+     * a list of DataCallResponse
+     * @param dataCallResultList List of SetupDataCallResult defined in
+     *        radio/1.0, 1.4, 1.5, 1.6/types.hal
+     * @return The converted list of DataCallResponses
+     */
+    @VisibleForTesting
+    public static ArrayList<DataCallResponse> convertHalDataCallResultList(
+            List<? extends Object> dataCallResultList) {
+        ArrayList<DataCallResponse> response = new ArrayList<>(dataCallResultList.size());
+
+        for (Object obj : dataCallResultList) {
+            response.add(convertHalDataCallResult(obj));
+        }
+        return response;
+    }
+
+    /**
+     * Convert a list of SetupDataCallResult defined in SetupDataCallResult.aidl into a list of
+     * DataCallResponse
+     * @param dataCallResultList Array of SetupDataCallResult defined in SetupDataCallResult.aidl
+     * @return The converted list of DataCallResponses
+     */
+    @VisibleForTesting
+    public static ArrayList<DataCallResponse> convertHalDataCallResultList(
+            android.hardware.radio.data.SetupDataCallResult[] dataCallResultList) {
+        ArrayList<DataCallResponse> response = new ArrayList<>(dataCallResultList.length);
+
+        for (android.hardware.radio.data.SetupDataCallResult result : dataCallResultList) {
+            response.add(convertHalDataCallResult(result));
+        }
+        return response;
+    }
+
+    /**
+     * Convert KeepaliveStatusCode defined in radio/1.1/types.hal and KeepaliveStatus.aidl
+     * to KeepaliveStatus
+     * @param halCode KeepaliveStatus code defined in radio/1.1/types.hal or KeepaliveStatus.aidl
+     * @return The converted KeepaliveStatus
+     */
+    public static int convertHalKeepaliveStatusCode(int halCode) {
+        switch (halCode) {
+            case android.hardware.radio.V1_1.KeepaliveStatusCode.ACTIVE:
+                return KeepaliveStatus.STATUS_ACTIVE;
+            case android.hardware.radio.V1_1.KeepaliveStatusCode.INACTIVE:
+                return KeepaliveStatus.STATUS_INACTIVE;
+            case android.hardware.radio.V1_1.KeepaliveStatusCode.PENDING:
+                return KeepaliveStatus.STATUS_PENDING;
+            default:
+                return -1;
+        }
+    }
+
+    /**
+     * Convert RadioState defined in radio/1.0/types.hal and RadioState.aidl to RadioPowerState
+     * @param stateInt Radio state defined in radio/1.0/types.hal or RadioState.aidl
+     * @return The converted {@link Annotation.RadioPowerState RadioPowerState}
+     */
+    public static @Annotation.RadioPowerState int convertHalRadioState(int stateInt) {
+        int state;
+        switch(stateInt) {
+            case android.hardware.radio.V1_0.RadioState.OFF:
+                state = TelephonyManager.RADIO_POWER_OFF;
+                break;
+            case android.hardware.radio.V1_0.RadioState.UNAVAILABLE:
+                state = TelephonyManager.RADIO_POWER_UNAVAILABLE;
+                break;
+            case android.hardware.radio.V1_0.RadioState.ON:
+                state = TelephonyManager.RADIO_POWER_ON;
+                break;
+            default:
+                throw new RuntimeException("Unrecognized RadioState: " + stateInt);
+        }
+        return state;
+    }
+
+    /**
+     * Convert CellConnectionStatus defined in radio/1.2/types.hal to ConnectionStatus
+     * @param status Cell connection status defined in radio/1.2/types.hal
+     * @return The converted ConnectionStatus
+     */
+    public static int convertHalCellConnectionStatus(int status) {
+        switch (status) {
+            case android.hardware.radio.V1_2.CellConnectionStatus.PRIMARY_SERVING:
+                return PhysicalChannelConfig.CONNECTION_PRIMARY_SERVING;
+            case android.hardware.radio.V1_2.CellConnectionStatus.SECONDARY_SERVING:
+                return PhysicalChannelConfig.CONNECTION_SECONDARY_SERVING;
+            default:
+                return PhysicalChannelConfig.CONNECTION_UNKNOWN;
+        }
+    }
+
+    /**
+     * Convert Call defined in radio/1.0, 1.2, 1.6/types.hal to DriverCall
+     * @param halCall Call defined in radio/1.0, 1.2, 1.6/types.hal
+     * @return The converted DriverCall
+     */
+    public static DriverCall convertToDriverCall(Object halCall) {
+        DriverCall dc = new DriverCall();
+        final android.hardware.radio.V1_6.Call call16;
+        final android.hardware.radio.V1_2.Call call12;
+        final android.hardware.radio.V1_0.Call call10;
+        if (halCall instanceof android.hardware.radio.V1_6.Call) {
+            call16 = (android.hardware.radio.V1_6.Call) halCall;
+            call12 = call16.base;
+            call10 = call12.base;
+        } else if (halCall instanceof android.hardware.radio.V1_2.Call) {
+            call16 = null;
+            call12 = (android.hardware.radio.V1_2.Call) halCall;
+            call10 = call12.base;
+        } else if (halCall instanceof android.hardware.radio.V1_0.Call) {
+            call16 = null;
+            call12 = null;
+            call10 = (android.hardware.radio.V1_0.Call) halCall;
+        } else {
+            call16 = null;
+            call12 = null;
+            call10 = null;
+        }
+        if (call10 != null) {
+            dc.state = DriverCall.stateFromCLCC((int) (call10.state));
+            dc.index = call10.index;
+            dc.TOA = call10.toa;
+            dc.isMpty = call10.isMpty;
+            dc.isMT = call10.isMT;
+            dc.als = call10.als;
+            dc.isVoice = call10.isVoice;
+            dc.isVoicePrivacy = call10.isVoicePrivacy;
+            dc.number = call10.number;
+            dc.numberPresentation = DriverCall.presentationFromCLIP(
+                    (int) (call10.numberPresentation));
+            dc.name = call10.name;
+            dc.namePresentation = DriverCall.presentationFromCLIP((int) (call10.namePresentation));
+            if (call10.uusInfo.size() == 1) {
+                dc.uusInfo = new UUSInfo();
+                dc.uusInfo.setType(call10.uusInfo.get(0).uusType);
+                dc.uusInfo.setDcs(call10.uusInfo.get(0).uusDcs);
+                if (!TextUtils.isEmpty(call10.uusInfo.get(0).uusData)) {
+                    byte[] userData = call10.uusInfo.get(0).uusData.getBytes();
+                    dc.uusInfo.setUserData(userData);
+                }
+            }
+            // Make sure there's a leading + on addresses with a TOA of 145
+            dc.number = PhoneNumberUtils.stringFromStringAndTOA(dc.number, dc.TOA);
+        }
+        if (call12 != null) {
+            dc.audioQuality = (int) (call12.audioQuality);
+        }
+        if (call16 != null) {
+            dc.forwardedNumber = call16.forwardedNumber;
+        }
+        return dc;
+    }
+
+    /**
+     * Convert Call defined in Call.aidl to DriverCall
+     * @param halCall Call defined in Call.aidl
+     * @return The converted DriverCall
+     */
+    public static DriverCall convertToDriverCall(android.hardware.radio.voice.Call halCall) {
+        DriverCall dc = new DriverCall();
+        dc.state = DriverCall.stateFromCLCC((int) halCall.state);
+        dc.index = halCall.index;
+        dc.TOA = halCall.toa;
+        dc.isMpty = halCall.isMpty;
+        dc.isMT = halCall.isMT;
+        dc.als = halCall.als;
+        dc.isVoice = halCall.isVoice;
+        dc.isVoicePrivacy = halCall.isVoicePrivacy;
+        dc.number = halCall.number;
+        dc.numberPresentation = DriverCall.presentationFromCLIP((int) halCall.numberPresentation);
+        dc.name = halCall.name;
+        dc.namePresentation = DriverCall.presentationFromCLIP((int) halCall.namePresentation);
+        if (halCall.uusInfo.length == 1) {
+            dc.uusInfo = new UUSInfo();
+            dc.uusInfo.setType(halCall.uusInfo[0].uusType);
+            dc.uusInfo.setDcs(halCall.uusInfo[0].uusDcs);
+            if (!TextUtils.isEmpty(halCall.uusInfo[0].uusData)) {
+                dc.uusInfo.setUserData(halCall.uusInfo[0].uusData.getBytes());
+            }
+        }
+        // Make sure there's a leading + on addresses with a TOA of 145
+        dc.number = PhoneNumberUtils.stringFromStringAndTOA(dc.number, dc.TOA);
+        dc.audioQuality = (int) halCall.audioQuality;
+        dc.forwardedNumber = halCall.forwardedNumber;
+        return dc;
+    }
+
+    /**
+     * Convert OperatorStatus defined in radio/1.0/types.hal to OperatorInfo.State
+     * @param status Operator status defined in radio/1.0/types.hal
+     * @return The converted OperatorStatus as a String
+     */
+    public static String convertHalOperatorStatus(int status) {
+        if (status == android.hardware.radio.V1_0.OperatorStatus.UNKNOWN) {
+            return "unknown";
+        } else if (status == android.hardware.radio.V1_0.OperatorStatus.AVAILABLE) {
+            return "available";
+        } else if (status == android.hardware.radio.V1_0.OperatorStatus.CURRENT) {
+            return "current";
+        } else if (status == android.hardware.radio.V1_0.OperatorStatus.FORBIDDEN) {
+            return "forbidden";
+        } else {
+            return "";
+        }
+    }
+
+    /**
+     * Convert a list of Carriers defined in radio/1.0/types.hal to a list of CarrierIdentifiers
+     * @param carrierList List of Carriers defined in radio/1.0/types.hal
+     * @return The converted list of CarrierIdentifiers
+     */
+    public static List<CarrierIdentifier> convertHalCarrierList(
+            List<android.hardware.radio.V1_0.Carrier> carrierList) {
+        List<CarrierIdentifier> ret = new ArrayList<>();
+        for (int i = 0; i < carrierList.size(); i++) {
+            String mcc = carrierList.get(i).mcc;
+            String mnc = carrierList.get(i).mnc;
+            String spn = null, imsi = null, gid1 = null, gid2 = null;
+            int matchType = carrierList.get(i).matchType;
+            String matchData = carrierList.get(i).matchData;
+            if (matchType == CarrierIdentifier.MatchType.SPN) {
+                spn = matchData;
+            } else if (matchType == CarrierIdentifier.MatchType.IMSI_PREFIX) {
+                imsi = matchData;
+            } else if (matchType == CarrierIdentifier.MatchType.GID1) {
+                gid1 = matchData;
+            } else if (matchType == CarrierIdentifier.MatchType.GID2) {
+                gid2 = matchData;
+            }
+            ret.add(new CarrierIdentifier(mcc, mnc, spn, imsi, gid1, gid2));
+        }
+        return ret;
+    }
+
+    /**
+     * Convert a list of Carriers defined in radio/1.0/types.hal to a list of CarrierIdentifiers
+     * @param carrierList List of Carriers defined in radio/1.0/types.hal
+     * @return The converted list of CarrierIdentifiers
+     */
+    public static List<CarrierIdentifier> convertHalCarrierList(
+            android.hardware.radio.sim.Carrier[] carrierList) {
+        List<CarrierIdentifier> ret = new ArrayList<>();
+        for (int i = 0; i < carrierList.length; i++) {
+            String mcc = carrierList[i].mcc;
+            String mnc = carrierList[i].mnc;
+            String spn = null, imsi = null, gid1 = null, gid2 = null;
+            int matchType = carrierList[i].matchType;
+            String matchData = carrierList[i].matchData;
+            if (matchType == CarrierIdentifier.MatchType.SPN) {
+                spn = matchData;
+            } else if (matchType == CarrierIdentifier.MatchType.IMSI_PREFIX) {
+                imsi = matchData;
+            } else if (matchType == CarrierIdentifier.MatchType.GID1) {
+                gid1 = matchData;
+            } else if (matchType == CarrierIdentifier.MatchType.GID2) {
+                gid2 = matchData;
+            }
+            ret.add(new CarrierIdentifier(mcc, mnc, spn, imsi, gid1, gid2));
+        }
+        return ret;
+    }
+
+    /**
+     * Convert CardStatus defined in radio/1.0, 1.5/types.hal to IccCardStatus
+     * @param cardStatus CardStatus defined in radio/1.0, 1.5/types.hal
+     * @return The converted IccCardStatus
+     */
+    public static IccCardStatus convertHalCardStatus(Object cardStatus) {
+        final android.hardware.radio.V1_0.CardStatus cardStatus10;
+        final android.hardware.radio.V1_5.CardStatus cardStatus15;
+        if (cardStatus instanceof android.hardware.radio.V1_5.CardStatus) {
+            cardStatus15 = (android.hardware.radio.V1_5.CardStatus) cardStatus;
+            cardStatus10 = cardStatus15.base.base.base;
+        } else if (cardStatus instanceof android.hardware.radio.V1_0.CardStatus) {
+            cardStatus15 = null;
+            cardStatus10 = (android.hardware.radio.V1_0.CardStatus) cardStatus;
+        } else {
+            cardStatus15 = null;
+            cardStatus10 = null;
+        }
+
+        IccCardStatus iccCardStatus = new IccCardStatus();
+        if (cardStatus10 != null) {
+            iccCardStatus.setCardState(cardStatus10.cardState);
+            iccCardStatus.setUniversalPinState(cardStatus10.universalPinState);
+            iccCardStatus.mGsmUmtsSubscriptionAppIndex = cardStatus10.gsmUmtsSubscriptionAppIndex;
+            iccCardStatus.mCdmaSubscriptionAppIndex = cardStatus10.cdmaSubscriptionAppIndex;
+            iccCardStatus.mImsSubscriptionAppIndex = cardStatus10.imsSubscriptionAppIndex;
+            int numApplications = cardStatus10.applications.size();
+
+            // limit to maximum allowed applications
+            if (numApplications > com.android.internal.telephony.uicc.IccCardStatus.CARD_MAX_APPS) {
+                numApplications = com.android.internal.telephony.uicc.IccCardStatus.CARD_MAX_APPS;
+            }
+            iccCardStatus.mApplications = new IccCardApplicationStatus[numApplications];
+            for (int i = 0; i < numApplications; i++) {
+                android.hardware.radio.V1_0.AppStatus rilAppStatus =
+                        cardStatus10.applications.get(i);
+                IccCardApplicationStatus appStatus = new IccCardApplicationStatus();
+                appStatus.app_type = appStatus.AppTypeFromRILInt(rilAppStatus.appType);
+                appStatus.app_state = appStatus.AppStateFromRILInt(rilAppStatus.appState);
+                appStatus.perso_substate = appStatus.PersoSubstateFromRILInt(
+                        rilAppStatus.persoSubstate);
+                appStatus.aid = rilAppStatus.aidPtr;
+                appStatus.app_label = rilAppStatus.appLabelPtr;
+                appStatus.pin1_replaced = rilAppStatus.pin1Replaced != 0;
+                appStatus.pin1 = appStatus.PinStateFromRILInt(rilAppStatus.pin1);
+                appStatus.pin2 = appStatus.PinStateFromRILInt(rilAppStatus.pin2);
+                iccCardStatus.mApplications[i] = appStatus;
+            }
+        }
+        if (cardStatus15 != null) {
+            iccCardStatus.physicalSlotIndex = cardStatus15.base.base.physicalSlotId;
+            iccCardStatus.atr = cardStatus15.base.base.atr;
+            iccCardStatus.iccid = cardStatus15.base.base.iccid;
+            iccCardStatus.eid = cardStatus15.base.eid;
+            int numApplications = cardStatus15.applications.size();
+
+            // limit to maximum allowed applications
+            if (numApplications > com.android.internal.telephony.uicc.IccCardStatus.CARD_MAX_APPS) {
+                numApplications = com.android.internal.telephony.uicc.IccCardStatus.CARD_MAX_APPS;
+            }
+            iccCardStatus.mApplications = new IccCardApplicationStatus[numApplications];
+            for (int i = 0; i < numApplications; i++) {
+                android.hardware.radio.V1_5.AppStatus rilAppStatus =
+                        cardStatus15.applications.get(i);
+                IccCardApplicationStatus appStatus = new IccCardApplicationStatus();
+                appStatus.app_type = appStatus.AppTypeFromRILInt(rilAppStatus.base.appType);
+                appStatus.app_state = appStatus.AppStateFromRILInt(rilAppStatus.base.appState);
+                appStatus.perso_substate = appStatus.PersoSubstateFromRILInt(
+                        rilAppStatus.persoSubstate);
+                appStatus.aid = rilAppStatus.base.aidPtr;
+                appStatus.app_label = rilAppStatus.base.appLabelPtr;
+                appStatus.pin1_replaced = rilAppStatus.base.pin1Replaced != 0;
+                appStatus.pin1 = appStatus.PinStateFromRILInt(rilAppStatus.base.pin1);
+                appStatus.pin2 = appStatus.PinStateFromRILInt(rilAppStatus.base.pin2);
+                iccCardStatus.mApplications[i] = appStatus;
+            }
+        }
+        return iccCardStatus;
+    }
+
+    /**
+     * Convert CardStatus defined in CardStatus.aidl to IccCardStatus
+     * @param cardStatus CardStatus defined in CardStatus.aidl
+     * @return The converted IccCardStatus
+     */
+    public static IccCardStatus convertHalCardStatus(
+            android.hardware.radio.sim.CardStatus cardStatus) {
+        IccCardStatus iccCardStatus = new IccCardStatus();
+        iccCardStatus.setCardState(cardStatus.cardState);
+        iccCardStatus.setUniversalPinState(cardStatus.universalPinState);
+        iccCardStatus.mGsmUmtsSubscriptionAppIndex = cardStatus.gsmUmtsSubscriptionAppIndex;
+        iccCardStatus.mCdmaSubscriptionAppIndex = cardStatus.cdmaSubscriptionAppIndex;
+        iccCardStatus.mImsSubscriptionAppIndex = cardStatus.imsSubscriptionAppIndex;
+        iccCardStatus.physicalSlotIndex = cardStatus.slotMap.physicalSlotId;
+        iccCardStatus.atr = cardStatus.atr;
+        iccCardStatus.iccid = cardStatus.iccid;
+        iccCardStatus.eid = cardStatus.eid;
+
+        int numApplications = Math.min(cardStatus.applications.length,
+                com.android.internal.telephony.uicc.IccCardStatus.CARD_MAX_APPS);
+        iccCardStatus.mApplications = new IccCardApplicationStatus[numApplications];
+        for (int i = 0; i < numApplications; i++) {
+            android.hardware.radio.sim.AppStatus rilAppStatus = cardStatus.applications[i];
+            IccCardApplicationStatus appStatus = new IccCardApplicationStatus();
+            appStatus.app_type = appStatus.AppTypeFromRILInt(rilAppStatus.appType);
+            appStatus.app_state = appStatus.AppStateFromRILInt(rilAppStatus.appState);
+            appStatus.perso_substate = appStatus.PersoSubstateFromRILInt(
+                    rilAppStatus.persoSubstate);
+            appStatus.aid = rilAppStatus.aidPtr;
+            appStatus.app_label = rilAppStatus.appLabelPtr;
+            appStatus.pin1_replaced = rilAppStatus.pin1Replaced;
+            appStatus.pin1 = appStatus.PinStateFromRILInt(rilAppStatus.pin1);
+            appStatus.pin2 = appStatus.PinStateFromRILInt(rilAppStatus.pin2);
+            iccCardStatus.mApplications[i] = appStatus;
+        }
+        return iccCardStatus;
+    }
+
+    /**
+     * Convert PhonebookCapacity defined in radio/1.6/types.hal to AdnCapacity
+     * @param pbCap PhonebookCapacity defined in radio/1.6/types.hal
+     * @return The converted AdnCapacity
+     */
+    public static AdnCapacity convertHalPhonebookCapacity(
+            android.hardware.radio.V1_6.PhonebookCapacity pbCap) {
+        if (pbCap != null) {
+            return new AdnCapacity(pbCap.maxAdnRecords, pbCap.usedAdnRecords, pbCap.maxEmailRecords,
+                    pbCap.usedEmailRecords, pbCap.maxAdditionalNumberRecords,
+                    pbCap.usedAdditionalNumberRecords, pbCap.maxNameLen, pbCap.maxNumberLen,
+                    pbCap.maxEmailLen, pbCap.maxAdditionalNumberLen);
+        }
+        return null;
+    }
+
+    /**
+     * Convert PhonebookCapacity defined in PhonebookCapacity.aidl to AdnCapacity
+     * @param pbCap PhonebookCapacity defined in PhonebookCapacity.aidl
+     * @return The converted AdnCapacity
+     */
+    public static AdnCapacity convertHalPhonebookCapacity(
+            android.hardware.radio.sim.PhonebookCapacity pbCap) {
+        if (pbCap != null) {
+            return new AdnCapacity(pbCap.maxAdnRecords, pbCap.usedAdnRecords, pbCap.maxEmailRecords,
+                    pbCap.usedEmailRecords, pbCap.maxAdditionalNumberRecords,
+                    pbCap.usedAdditionalNumberRecords, pbCap.maxNameLen, pbCap.maxNumberLen,
+                    pbCap.maxEmailLen, pbCap.maxAdditionalNumberLen);
+        }
+        return null;
+    }
+
+    /**
+     * Convert PhonebookRecordInfo defined in radio/1.6/types.hal to SimPhonebookRecord
+     * @param recInfo PhonebookRecordInfo defined in radio/1.6/types.hal
+     * @return The converted SimPhonebookRecord
+     */
+    public static SimPhonebookRecord convertHalPhonebookRecordInfo(
+            android.hardware.radio.V1_6.PhonebookRecordInfo recInfo) {
+        String[] emails = recInfo.emails == null ? null
+                : recInfo.emails.toArray(new String[recInfo.emails.size()]);
+        String[] numbers = recInfo.additionalNumbers == null ? null
+                : recInfo.additionalNumbers.toArray(new String[recInfo.additionalNumbers.size()]);
+        return new SimPhonebookRecord(recInfo.recordId, recInfo.name, recInfo.number, emails,
+                numbers);
+    }
+
+    /**
+     * Convert PhonebookRecordInfo defined in PhonebookRecordInfo.aidl to SimPhonebookRecord
+     * @param recInfo PhonebookRecordInfo defined in PhonebookRecordInfo.aidl
+     * @return The converted SimPhonebookRecord
+     */
+    public static SimPhonebookRecord convertHalPhonebookRecordInfo(
+            android.hardware.radio.sim.PhonebookRecordInfo recInfo) {
+        return new SimPhonebookRecord(recInfo.recordId, recInfo.name, recInfo.number,
+                recInfo.emails, recInfo.additionalNumbers);
+    }
+
+    /**
+     * Convert SimPhonebookRecord to PhonebookRecordInfo defined in radio/1.6/types.hal
+     * @param record SimPhonebookRecord to convert
+     * @return The converted PhonebookRecordInfo defined in radio/1.6/types.hal
+     */
+    public static android.hardware.radio.V1_6.PhonebookRecordInfo convertToHalPhonebookRecordInfo(
+            SimPhonebookRecord record) {
+        android.hardware.radio.V1_6.PhonebookRecordInfo pbRecordInfo =
+                new android.hardware.radio.V1_6.PhonebookRecordInfo();
+        pbRecordInfo.recordId = record.getRecordIndex();
+        pbRecordInfo.name = convertNullToEmptyString(record.getAlphaTag());
+        pbRecordInfo.number = convertNullToEmptyString(
+                convertToHalPhonebookRecordInfoNumber(record.getNumber()));
+        if (record.getEmails() != null) {
+            for (String email : record.getEmails()) {
+                pbRecordInfo.emails.add(email);
+            }
+        }
+        if (record.getAdditionalNumbers() != null) {
+            for (String addNum : record.getAdditionalNumbers()) {
+                pbRecordInfo.additionalNumbers.add(convertToHalPhonebookRecordInfoNumber(addNum));
+            }
+        }
+        return pbRecordInfo;
+    }
+
+    /**
+     * Convert the GSM pause/wild/wait character to the phone number in the SIM PhonebookRecordInfo
+     * number format
+     * @param input GSM pause/wild/wait character
+     * @return The converted PhonebookRecordInfo number
+     */
+    private static String convertToHalPhonebookRecordInfoNumber(String input) {
+        return input == null ? null : input.replace(PhoneNumberUtils.WAIT, 'e')
+                .replace(PhoneNumberUtils.PAUSE, 'T')
+                .replace(PhoneNumberUtils.WILD, '?');
+    }
+
+    /**
+     * Convert array of SimSlotStatus to IccSlotStatus
+     * @param o object that represents array/list of SimSlotStatus
+     * @return ArrayList of IccSlotStatus
+     */
+    public static ArrayList<IccSlotStatus> convertHalSlotStatus(Object o) {
+        ArrayList<IccSlotStatus> response = new ArrayList<>();
+        if (o instanceof android.hardware.radio.config.SimSlotStatus[]) {
+            final android.hardware.radio.config.SimSlotStatus[] halSlotStatusArray =
+                    (android.hardware.radio.config.SimSlotStatus[]) o;
+            for (android.hardware.radio.config.SimSlotStatus slotStatus : halSlotStatusArray) {
+                IccSlotStatus iccSlotStatus = new IccSlotStatus();
+                iccSlotStatus.setCardState(slotStatus.cardState);
+                iccSlotStatus.setSlotState(slotStatus.portInfo[0].portState);
+                iccSlotStatus.logicalSlotIndex = slotStatus.portInfo[0].logicalSlotId;
+                iccSlotStatus.atr = slotStatus.atr;
+                iccSlotStatus.iccid = slotStatus.portInfo[0].iccId;
+                iccSlotStatus.eid = slotStatus.eid;
+                response.add(iccSlotStatus);
+            }
+        } else if (o instanceof ArrayList) {
+            final ArrayList<android.hardware.radio.config.V1_0.SimSlotStatus> halSlotStatusArray =
+                    (ArrayList<android.hardware.radio.config.V1_0.SimSlotStatus>) o;
+            for (android.hardware.radio.config.V1_0.SimSlotStatus slotStatus : halSlotStatusArray) {
+                IccSlotStatus iccSlotStatus = new IccSlotStatus();
+                iccSlotStatus.setCardState(slotStatus.cardState);
+                iccSlotStatus.setSlotState(slotStatus.slotState);
+                iccSlotStatus.logicalSlotIndex = slotStatus.logicalSlotId;
+                iccSlotStatus.atr = slotStatus.atr;
+                iccSlotStatus.iccid = slotStatus.iccid;
+                response.add(iccSlotStatus);
+            }
+        } else if (o instanceof ArrayList) {
+            final ArrayList<android.hardware.radio.config.V1_2.SimSlotStatus> halSlotStatusArray =
+                    (ArrayList<android.hardware.radio.config.V1_2.SimSlotStatus>) o;
+            for (android.hardware.radio.config.V1_2.SimSlotStatus slotStatus : halSlotStatusArray) {
+                IccSlotStatus iccSlotStatus = new IccSlotStatus();
+                iccSlotStatus.setCardState(slotStatus.base.cardState);
+                iccSlotStatus.setSlotState(slotStatus.base.slotState);
+                iccSlotStatus.logicalSlotIndex = slotStatus.base.logicalSlotId;
+                iccSlotStatus.atr = slotStatus.base.atr;
+                iccSlotStatus.iccid = slotStatus.base.iccid;
+                iccSlotStatus.eid = slotStatus.eid;
+                response.add(iccSlotStatus);
+            }
+        }
+        return response;
+    }
+
+    /**
+     * Convert int[] list to SlotPortMapping[]
+     * @param list int[] of slots mapping
+     * @return SlotPortMapping[] of slots mapping
+     */
+    public static android.hardware.radio.config.SlotPortMapping[] convertSimSlotsMapping(
+            int[] list) {
+        android.hardware.radio.config.SlotPortMapping[] res =
+                new android.hardware.radio.config.SlotPortMapping[list.length];
+        for (int i : list) {
+            res[i] = new android.hardware.radio.config.SlotPortMapping();
+            res[i].portId = i;
+        }
+        return res;
+    }
+
+
+    /**
+     * Convert PhoneCapability to telephony PhoneCapability.
+     * @param deviceNrCapabilities device's nr capability array
+     * @param o PhoneCapability to convert
+     * @return converted PhoneCapability
+     */
+    public static PhoneCapability convertHalPhoneCapability(int[] deviceNrCapabilities, Object o) {
+        int maxActiveVoiceCalls = 0;
+        int maxActiveData = 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;
+            validationBeforeSwitchSupported = phoneCapability.isInternetLingeringSupported;
+            for (int modemId : phoneCapability.logicalModemIds) {
+                logicalModemList.add(new ModemInfo(modemId));
+            }
+        } else if (o instanceof android.hardware.radio.config.V1_1.PhoneCapability) {
+            final android.hardware.radio.config.V1_1.PhoneCapability phoneCapability =
+                    (android.hardware.radio.config.V1_1.PhoneCapability) o;
+            maxActiveData = phoneCapability.maxActiveData;
+            validationBeforeSwitchSupported = phoneCapability.isInternetLingeringSupported;
+            for (android.hardware.radio.config.V1_1.ModemInfo modemInfo :
+                    phoneCapability.logicalModemList) {
+                logicalModemList.add(new ModemInfo(modemInfo.modemId));
+            }
+        }
+        return new PhoneCapability(maxActiveVoiceCalls, maxActiveData, logicalModemList,
+                validationBeforeSwitchSupported, deviceNrCapabilities);
+    }
+
+    /** Append the data to the end of an ArrayList */
+    public static void appendPrimitiveArrayToArrayList(byte[] src, ArrayList<Byte> dst) {
+        for (byte b : src) {
+            dst.add(b);
+        }
+    }
+
+    /** Convert a primitive byte array to an ArrayList<Integer>. */
+    public static ArrayList<Byte> primitiveArrayToArrayList(byte[] arr) {
+        ArrayList<Byte> arrayList = new ArrayList<>(arr.length);
+        for (byte b : arr) {
+            arrayList.add(b);
+        }
+        return arrayList;
+    }
+
+    /** Convert a primitive int array to an ArrayList<Integer>. */
+    public static ArrayList<Integer> primitiveArrayToArrayList(int[] arr) {
+        ArrayList<Integer> arrayList = new ArrayList<>(arr.length);
+        for (int i : arr) {
+            arrayList.add(i);
+        }
+        return arrayList;
+    }
+
+    /** Convert a primitive String array to an ArrayList<String>. */
+    public static ArrayList<String> primitiveArrayToArrayList(String[] arr) {
+        return new ArrayList<>(Arrays.asList(arr));
+    }
+
+    /** Convert an ArrayList of Bytes to an exactly-sized primitive array */
+    public static byte[] arrayListToPrimitiveArray(ArrayList<Byte> bytes) {
+        byte[] ret = new byte[bytes.size()];
+        for (int i = 0; i < ret.length; i++) {
+            ret[i] = bytes.get(i);
+        }
+        return ret;
+    }
+
+    /** Convert null to an empty String */
+    public static String convertNullToEmptyString(String string) {
+        return string != null ? string : "";
+    }
+
+    /**
+     * RIL request to String
+     * @param request request
+     * @return The converted String request
+     */
+    public static String requestToString(int request) {
+        switch(request) {
+            case RIL_REQUEST_GET_SIM_STATUS:
+                return "GET_SIM_STATUS";
+            case RIL_REQUEST_ENTER_SIM_PIN:
+                return "ENTER_SIM_PIN";
+            case RIL_REQUEST_ENTER_SIM_PUK:
+                return "ENTER_SIM_PUK";
+            case RIL_REQUEST_ENTER_SIM_PIN2:
+                return "ENTER_SIM_PIN2";
+            case RIL_REQUEST_ENTER_SIM_PUK2:
+                return "ENTER_SIM_PUK2";
+            case RIL_REQUEST_CHANGE_SIM_PIN:
+                return "CHANGE_SIM_PIN";
+            case RIL_REQUEST_CHANGE_SIM_PIN2:
+                return "CHANGE_SIM_PIN2";
+            case RIL_REQUEST_ENTER_NETWORK_DEPERSONALIZATION:
+                return "ENTER_NETWORK_DEPERSONALIZATION";
+            case RIL_REQUEST_GET_CURRENT_CALLS:
+                return "GET_CURRENT_CALLS";
+            case RIL_REQUEST_DIAL:
+                return "DIAL";
+            case RIL_REQUEST_GET_IMSI:
+                return "GET_IMSI";
+            case RIL_REQUEST_HANGUP:
+                return "HANGUP";
+            case RIL_REQUEST_HANGUP_WAITING_OR_BACKGROUND:
+                return "HANGUP_WAITING_OR_BACKGROUND";
+            case RIL_REQUEST_HANGUP_FOREGROUND_RESUME_BACKGROUND:
+                return "HANGUP_FOREGROUND_RESUME_BACKGROUND";
+            case RIL_REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE:
+                return "REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE";
+            case RIL_REQUEST_CONFERENCE:
+                return "CONFERENCE";
+            case RIL_REQUEST_UDUB:
+                return "UDUB";
+            case RIL_REQUEST_LAST_CALL_FAIL_CAUSE:
+                return "LAST_CALL_FAIL_CAUSE";
+            case RIL_REQUEST_SIGNAL_STRENGTH:
+                return "SIGNAL_STRENGTH";
+            case RIL_REQUEST_VOICE_REGISTRATION_STATE:
+                return "VOICE_REGISTRATION_STATE";
+            case RIL_REQUEST_DATA_REGISTRATION_STATE:
+                return "DATA_REGISTRATION_STATE";
+            case RIL_REQUEST_OPERATOR:
+                return "OPERATOR";
+            case RIL_REQUEST_RADIO_POWER:
+                return "RADIO_POWER";
+            case RIL_REQUEST_DTMF:
+                return "DTMF";
+            case RIL_REQUEST_SEND_SMS:
+                return "SEND_SMS";
+            case RIL_REQUEST_SEND_SMS_EXPECT_MORE:
+                return "SEND_SMS_EXPECT_MORE";
+            case RIL_REQUEST_SETUP_DATA_CALL:
+                return "SETUP_DATA_CALL";
+            case RIL_REQUEST_SIM_IO:
+                return "SIM_IO";
+            case RIL_REQUEST_SEND_USSD:
+                return "SEND_USSD";
+            case RIL_REQUEST_CANCEL_USSD:
+                return "CANCEL_USSD";
+            case RIL_REQUEST_GET_CLIR:
+                return "GET_CLIR";
+            case RIL_REQUEST_SET_CLIR:
+                return "SET_CLIR";
+            case RIL_REQUEST_QUERY_CALL_FORWARD_STATUS:
+                return "QUERY_CALL_FORWARD_STATUS";
+            case RIL_REQUEST_SET_CALL_FORWARD:
+                return "SET_CALL_FORWARD";
+            case RIL_REQUEST_QUERY_CALL_WAITING:
+                return "QUERY_CALL_WAITING";
+            case RIL_REQUEST_SET_CALL_WAITING:
+                return "SET_CALL_WAITING";
+            case RIL_REQUEST_SMS_ACKNOWLEDGE:
+                return "SMS_ACKNOWLEDGE";
+            case RIL_REQUEST_GET_IMEI:
+                return "GET_IMEI";
+            case RIL_REQUEST_GET_IMEISV:
+                return "GET_IMEISV";
+            case RIL_REQUEST_ANSWER:
+                return "ANSWER";
+            case RIL_REQUEST_DEACTIVATE_DATA_CALL:
+                return "DEACTIVATE_DATA_CALL";
+            case RIL_REQUEST_QUERY_FACILITY_LOCK:
+                return "QUERY_FACILITY_LOCK";
+            case RIL_REQUEST_SET_FACILITY_LOCK:
+                return "SET_FACILITY_LOCK";
+            case RIL_REQUEST_CHANGE_BARRING_PASSWORD:
+                return "CHANGE_BARRING_PASSWORD";
+            case RIL_REQUEST_QUERY_NETWORK_SELECTION_MODE:
+                return "QUERY_NETWORK_SELECTION_MODE";
+            case RIL_REQUEST_SET_NETWORK_SELECTION_AUTOMATIC:
+                return "SET_NETWORK_SELECTION_AUTOMATIC";
+            case RIL_REQUEST_SET_NETWORK_SELECTION_MANUAL:
+                return "SET_NETWORK_SELECTION_MANUAL";
+            case RIL_REQUEST_QUERY_AVAILABLE_NETWORKS :
+                return "QUERY_AVAILABLE_NETWORKS ";
+            case RIL_REQUEST_DTMF_START:
+                return "DTMF_START";
+            case RIL_REQUEST_DTMF_STOP:
+                return "DTMF_STOP";
+            case RIL_REQUEST_BASEBAND_VERSION:
+                return "BASEBAND_VERSION";
+            case RIL_REQUEST_SEPARATE_CONNECTION:
+                return "SEPARATE_CONNECTION";
+            case RIL_REQUEST_SET_MUTE:
+                return "SET_MUTE";
+            case RIL_REQUEST_GET_MUTE:
+                return "GET_MUTE";
+            case RIL_REQUEST_QUERY_CLIP:
+                return "QUERY_CLIP";
+            case RIL_REQUEST_LAST_DATA_CALL_FAIL_CAUSE:
+                return "LAST_DATA_CALL_FAIL_CAUSE";
+            case RIL_REQUEST_DATA_CALL_LIST:
+                return "DATA_CALL_LIST";
+            case RIL_REQUEST_RESET_RADIO:
+                return "RESET_RADIO";
+            case RIL_REQUEST_OEM_HOOK_RAW:
+                return "OEM_HOOK_RAW";
+            case RIL_REQUEST_OEM_HOOK_STRINGS:
+                return "OEM_HOOK_STRINGS";
+            case RIL_REQUEST_SCREEN_STATE:
+                return "SCREEN_STATE";
+            case RIL_REQUEST_SET_SUPP_SVC_NOTIFICATION:
+                return "SET_SUPP_SVC_NOTIFICATION";
+            case RIL_REQUEST_WRITE_SMS_TO_SIM:
+                return "WRITE_SMS_TO_SIM";
+            case RIL_REQUEST_DELETE_SMS_ON_SIM:
+                return "DELETE_SMS_ON_SIM";
+            case RIL_REQUEST_SET_BAND_MODE:
+                return "SET_BAND_MODE";
+            case RIL_REQUEST_QUERY_AVAILABLE_BAND_MODE:
+                return "QUERY_AVAILABLE_BAND_MODE";
+            case RIL_REQUEST_STK_GET_PROFILE:
+                return "STK_GET_PROFILE";
+            case RIL_REQUEST_STK_SET_PROFILE:
+                return "STK_SET_PROFILE";
+            case RIL_REQUEST_STK_SEND_ENVELOPE_COMMAND:
+                return "STK_SEND_ENVELOPE_COMMAND";
+            case RIL_REQUEST_STK_SEND_TERMINAL_RESPONSE:
+                return "STK_SEND_TERMINAL_RESPONSE";
+            case RIL_REQUEST_STK_HANDLE_CALL_SETUP_REQUESTED_FROM_SIM:
+                return "STK_HANDLE_CALL_SETUP_REQUESTED_FROM_SIM";
+            case RIL_REQUEST_EXPLICIT_CALL_TRANSFER:
+                return "EXPLICIT_CALL_TRANSFER";
+            case RIL_REQUEST_SET_PREFERRED_NETWORK_TYPE:
+                return "SET_PREFERRED_NETWORK_TYPE";
+            case RIL_REQUEST_GET_PREFERRED_NETWORK_TYPE:
+                return "GET_PREFERRED_NETWORK_TYPE";
+            case RIL_REQUEST_GET_NEIGHBORING_CELL_IDS:
+                return "GET_NEIGHBORING_CELL_IDS";
+            case RIL_REQUEST_SET_LOCATION_UPDATES:
+                return "SET_LOCATION_UPDATES";
+            case RIL_REQUEST_CDMA_SET_SUBSCRIPTION_SOURCE:
+                return "CDMA_SET_SUBSCRIPTION_SOURCE";
+            case RIL_REQUEST_CDMA_SET_ROAMING_PREFERENCE:
+                return "CDMA_SET_ROAMING_PREFERENCE";
+            case RIL_REQUEST_CDMA_QUERY_ROAMING_PREFERENCE:
+                return "CDMA_QUERY_ROAMING_PREFERENCE";
+            case RIL_REQUEST_SET_TTY_MODE:
+                return "SET_TTY_MODE";
+            case RIL_REQUEST_QUERY_TTY_MODE:
+                return "QUERY_TTY_MODE";
+            case RIL_REQUEST_CDMA_SET_PREFERRED_VOICE_PRIVACY_MODE:
+                return "CDMA_SET_PREFERRED_VOICE_PRIVACY_MODE";
+            case RIL_REQUEST_CDMA_QUERY_PREFERRED_VOICE_PRIVACY_MODE:
+                return "CDMA_QUERY_PREFERRED_VOICE_PRIVACY_MODE";
+            case RIL_REQUEST_CDMA_FLASH:
+                return "CDMA_FLASH";
+            case RIL_REQUEST_CDMA_BURST_DTMF:
+                return "CDMA_BURST_DTMF";
+            case RIL_REQUEST_CDMA_VALIDATE_AND_WRITE_AKEY:
+                return "CDMA_VALIDATE_AND_WRITE_AKEY";
+            case RIL_REQUEST_CDMA_SEND_SMS:
+                return "CDMA_SEND_SMS";
+            case RIL_REQUEST_CDMA_SMS_ACKNOWLEDGE:
+                return "CDMA_SMS_ACKNOWLEDGE";
+            case RIL_REQUEST_GSM_GET_BROADCAST_CONFIG:
+                return "GSM_GET_BROADCAST_CONFIG";
+            case RIL_REQUEST_GSM_SET_BROADCAST_CONFIG:
+                return "GSM_SET_BROADCAST_CONFIG";
+            case RIL_REQUEST_GSM_BROADCAST_ACTIVATION:
+                return "GSM_BROADCAST_ACTIVATION";
+            case RIL_REQUEST_CDMA_GET_BROADCAST_CONFIG:
+                return "CDMA_GET_BROADCAST_CONFIG";
+            case RIL_REQUEST_CDMA_SET_BROADCAST_CONFIG:
+                return "CDMA_SET_BROADCAST_CONFIG";
+            case RIL_REQUEST_CDMA_BROADCAST_ACTIVATION:
+                return "CDMA_BROADCAST_ACTIVATION";
+            case RIL_REQUEST_CDMA_SUBSCRIPTION:
+                return "CDMA_SUBSCRIPTION";
+            case RIL_REQUEST_CDMA_WRITE_SMS_TO_RUIM:
+                return "CDMA_WRITE_SMS_TO_RUIM";
+            case RIL_REQUEST_CDMA_DELETE_SMS_ON_RUIM:
+                return "CDMA_DELETE_SMS_ON_RUIM";
+            case RIL_REQUEST_DEVICE_IDENTITY:
+                return "DEVICE_IDENTITY";
+            case RIL_REQUEST_EXIT_EMERGENCY_CALLBACK_MODE:
+                return "EXIT_EMERGENCY_CALLBACK_MODE";
+            case RIL_REQUEST_GET_SMSC_ADDRESS:
+                return "GET_SMSC_ADDRESS";
+            case RIL_REQUEST_SET_SMSC_ADDRESS:
+                return "SET_SMSC_ADDRESS";
+            case RIL_REQUEST_REPORT_SMS_MEMORY_STATUS:
+                return "REPORT_SMS_MEMORY_STATUS";
+            case RIL_REQUEST_REPORT_STK_SERVICE_IS_RUNNING:
+                return "REPORT_STK_SERVICE_IS_RUNNING";
+            case RIL_REQUEST_CDMA_GET_SUBSCRIPTION_SOURCE:
+                return "CDMA_GET_SUBSCRIPTION_SOURCE";
+            case RIL_REQUEST_ISIM_AUTHENTICATION:
+                return "ISIM_AUTHENTICATION";
+            case RIL_REQUEST_ACKNOWLEDGE_INCOMING_GSM_SMS_WITH_PDU:
+                return "ACKNOWLEDGE_INCOMING_GSM_SMS_WITH_PDU";
+            case RIL_REQUEST_STK_SEND_ENVELOPE_WITH_STATUS:
+                return "STK_SEND_ENVELOPE_WITH_STATUS";
+            case RIL_REQUEST_VOICE_RADIO_TECH:
+                return "VOICE_RADIO_TECH";
+            case RIL_REQUEST_GET_CELL_INFO_LIST:
+                return "GET_CELL_INFO_LIST";
+            case RIL_REQUEST_SET_UNSOL_CELL_INFO_LIST_RATE:
+                return "SET_CELL_INFO_LIST_RATE";
+            case RIL_REQUEST_SET_INITIAL_ATTACH_APN:
+                return "SET_INITIAL_ATTACH_APN";
+            case RIL_REQUEST_IMS_REGISTRATION_STATE:
+                return "IMS_REGISTRATION_STATE";
+            case RIL_REQUEST_IMS_SEND_SMS:
+                return "IMS_SEND_SMS";
+            case RIL_REQUEST_SIM_TRANSMIT_APDU_BASIC:
+                return "SIM_TRANSMIT_APDU_BASIC";
+            case RIL_REQUEST_SIM_OPEN_CHANNEL:
+                return "SIM_OPEN_CHANNEL";
+            case RIL_REQUEST_SIM_CLOSE_CHANNEL:
+                return "SIM_CLOSE_CHANNEL";
+            case RIL_REQUEST_SIM_TRANSMIT_APDU_CHANNEL:
+                return "SIM_TRANSMIT_APDU_CHANNEL";
+            case RIL_REQUEST_NV_READ_ITEM:
+                return "NV_READ_ITEM";
+            case RIL_REQUEST_NV_WRITE_ITEM:
+                return "NV_WRITE_ITEM";
+            case RIL_REQUEST_NV_WRITE_CDMA_PRL:
+                return "NV_WRITE_CDMA_PRL";
+            case RIL_REQUEST_NV_RESET_CONFIG:
+                return "NV_RESET_CONFIG";
+            case RIL_REQUEST_SET_UICC_SUBSCRIPTION:
+                return "SET_UICC_SUBSCRIPTION";
+            case RIL_REQUEST_ALLOW_DATA:
+                return "ALLOW_DATA";
+            case RIL_REQUEST_GET_HARDWARE_CONFIG:
+                return "GET_HARDWARE_CONFIG";
+            case RIL_REQUEST_SIM_AUTHENTICATION:
+                return "SIM_AUTHENTICATION";
+            case RIL_REQUEST_GET_DC_RT_INFO:
+                return "GET_DC_RT_INFO";
+            case RIL_REQUEST_SET_DC_RT_INFO_RATE:
+                return "SET_DC_RT_INFO_RATE";
+            case RIL_REQUEST_SET_DATA_PROFILE:
+                return "SET_DATA_PROFILE";
+            case RIL_REQUEST_SHUTDOWN:
+                return "SHUTDOWN";
+            case RIL_REQUEST_GET_RADIO_CAPABILITY:
+                return "GET_RADIO_CAPABILITY";
+            case RIL_REQUEST_SET_RADIO_CAPABILITY:
+                return "SET_RADIO_CAPABILITY";
+            case RIL_REQUEST_START_LCE:
+                return "START_LCE";
+            case RIL_REQUEST_STOP_LCE:
+                return "STOP_LCE";
+            case RIL_REQUEST_PULL_LCEDATA:
+                return "PULL_LCEDATA";
+            case RIL_REQUEST_GET_ACTIVITY_INFO:
+                return "GET_ACTIVITY_INFO";
+            case RIL_REQUEST_SET_ALLOWED_CARRIERS:
+                return "SET_ALLOWED_CARRIERS";
+            case RIL_REQUEST_GET_ALLOWED_CARRIERS:
+                return "GET_ALLOWED_CARRIERS";
+            case RIL_REQUEST_SEND_DEVICE_STATE:
+                return "SEND_DEVICE_STATE";
+            case RIL_REQUEST_SET_UNSOLICITED_RESPONSE_FILTER:
+                return "SET_UNSOLICITED_RESPONSE_FILTER";
+            case RIL_REQUEST_SET_SIM_CARD_POWER:
+                return "SET_SIM_CARD_POWER";
+            case RIL_REQUEST_SET_CARRIER_INFO_IMSI_ENCRYPTION:
+                return "SET_CARRIER_INFO_IMSI_ENCRYPTION";
+            case RIL_REQUEST_START_NETWORK_SCAN:
+                return "START_NETWORK_SCAN";
+            case RIL_REQUEST_STOP_NETWORK_SCAN:
+                return "STOP_NETWORK_SCAN";
+            case RIL_REQUEST_START_KEEPALIVE:
+                return "START_KEEPALIVE";
+            case RIL_REQUEST_STOP_KEEPALIVE:
+                return "STOP_KEEPALIVE";
+            case RIL_REQUEST_ENABLE_MODEM:
+                return "ENABLE_MODEM";
+            case RIL_REQUEST_GET_MODEM_STATUS:
+                return "GET_MODEM_STATUS";
+            case RIL_REQUEST_CDMA_SEND_SMS_EXPECT_MORE:
+                return "CDMA_SEND_SMS_EXPECT_MORE";
+            case RIL_REQUEST_GET_SIM_PHONEBOOK_CAPACITY:
+                return "GET_SIM_PHONEBOOK_CAPACITY";
+            case RIL_REQUEST_GET_SIM_PHONEBOOK_RECORDS:
+                return "GET_SIM_PHONEBOOK_RECORDS";
+            case RIL_REQUEST_UPDATE_SIM_PHONEBOOK_RECORD:
+                return "UPDATE_SIM_PHONEBOOK_RECORD";
+            case RIL_REQUEST_GET_SLOT_STATUS:
+                return "GET_SLOT_STATUS";
+            case RIL_REQUEST_SET_LOGICAL_TO_PHYSICAL_SLOT_MAPPING:
+                return "SET_LOGICAL_TO_PHYSICAL_SLOT_MAPPING";
+            case RIL_REQUEST_SET_SIGNAL_STRENGTH_REPORTING_CRITERIA:
+                return "SET_SIGNAL_STRENGTH_REPORTING_CRITERIA";
+            case RIL_REQUEST_SET_LINK_CAPACITY_REPORTING_CRITERIA:
+                return "SET_LINK_CAPACITY_REPORTING_CRITERIA";
+            case RIL_REQUEST_SET_PREFERRED_DATA_MODEM:
+                return "SET_PREFERRED_DATA_MODEM";
+            case RIL_REQUEST_EMERGENCY_DIAL:
+                return "EMERGENCY_DIAL";
+            case RIL_REQUEST_GET_PHONE_CAPABILITY:
+                return "GET_PHONE_CAPABILITY";
+            case RIL_REQUEST_SWITCH_DUAL_SIM_CONFIG:
+                return "SWITCH_DUAL_SIM_CONFIG";
+            case RIL_REQUEST_ENABLE_UICC_APPLICATIONS:
+                return "ENABLE_UICC_APPLICATIONS";
+            case RIL_REQUEST_GET_UICC_APPLICATIONS_ENABLEMENT:
+                return "GET_UICC_APPLICATIONS_ENABLEMENT";
+            case RIL_REQUEST_SET_SYSTEM_SELECTION_CHANNELS:
+                return "SET_SYSTEM_SELECTION_CHANNELS";
+            case RIL_REQUEST_GET_BARRING_INFO:
+                return "GET_BARRING_INFO";
+            case RIL_REQUEST_ENTER_SIM_DEPERSONALIZATION:
+                return "ENTER_SIM_DEPERSONALIZATION";
+            case RIL_REQUEST_ENABLE_NR_DUAL_CONNECTIVITY:
+                return "ENABLE_NR_DUAL_CONNECTIVITY";
+            case RIL_REQUEST_IS_NR_DUAL_CONNECTIVITY_ENABLED:
+                return "IS_NR_DUAL_CONNECTIVITY_ENABLED";
+            case RIL_REQUEST_ALLOCATE_PDU_SESSION_ID:
+                return "ALLOCATE_PDU_SESSION_ID";
+            case RIL_REQUEST_RELEASE_PDU_SESSION_ID:
+                return "RELEASE_PDU_SESSION_ID";
+            case RIL_REQUEST_START_HANDOVER:
+                return "START_HANDOVER";
+            case RIL_REQUEST_CANCEL_HANDOVER:
+                return "CANCEL_HANDOVER";
+            case RIL_REQUEST_GET_SYSTEM_SELECTION_CHANNELS:
+                return "GET_SYSTEM_SELECTION_CHANNELS";
+            case RIL_REQUEST_GET_HAL_DEVICE_CAPABILITIES:
+                return "GET_HAL_DEVICE_CAPABILITIES";
+            case RIL_REQUEST_SET_DATA_THROTTLING:
+                return "SET_DATA_THROTTLING";
+            case RIL_REQUEST_SET_ALLOWED_NETWORK_TYPES_BITMAP:
+                return "SET_ALLOWED_NETWORK_TYPES_BITMAP";
+            case RIL_REQUEST_GET_ALLOWED_NETWORK_TYPES_BITMAP:
+                return "GET_ALLOWED_NETWORK_TYPES_BITMAP";
+            case RIL_REQUEST_GET_SLICING_CONFIG:
+                return "GET_SLICING_CONFIG";
+            default:
+                return "<unknown request " + request + ">";
+        }
+    }
+
+    /**
+     * RIL response to String
+     * @param response response
+     * @return The converted String response
+     */
+    public static String responseToString(int response) {
+        switch(response) {
+            case RIL_UNSOL_RESPONSE_RADIO_STATE_CHANGED:
+                return "UNSOL_RESPONSE_RADIO_STATE_CHANGED";
+            case RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED:
+                return "UNSOL_RESPONSE_CALL_STATE_CHANGED";
+            case RIL_UNSOL_RESPONSE_NETWORK_STATE_CHANGED:
+                return "UNSOL_RESPONSE_NETWORK_STATE_CHANGED";
+            case RIL_UNSOL_RESPONSE_NEW_SMS:
+                return "UNSOL_RESPONSE_NEW_SMS";
+            case RIL_UNSOL_RESPONSE_NEW_SMS_STATUS_REPORT:
+                return "UNSOL_RESPONSE_NEW_SMS_STATUS_REPORT";
+            case RIL_UNSOL_RESPONSE_NEW_SMS_ON_SIM:
+                return "UNSOL_RESPONSE_NEW_SMS_ON_SIM";
+            case RIL_UNSOL_ON_USSD:
+                return "UNSOL_ON_USSD";
+            case RIL_UNSOL_ON_USSD_REQUEST:
+                return "UNSOL_ON_USSD_REQUEST";
+            case RIL_UNSOL_NITZ_TIME_RECEIVED:
+                return "UNSOL_NITZ_TIME_RECEIVED";
+            case RIL_UNSOL_SIGNAL_STRENGTH:
+                return "UNSOL_SIGNAL_STRENGTH";
+            case RIL_UNSOL_DATA_CALL_LIST_CHANGED:
+                return "UNSOL_DATA_CALL_LIST_CHANGED";
+            case RIL_UNSOL_SUPP_SVC_NOTIFICATION:
+                return "UNSOL_SUPP_SVC_NOTIFICATION";
+            case RIL_UNSOL_STK_SESSION_END:
+                return "UNSOL_STK_SESSION_END";
+            case RIL_UNSOL_STK_PROACTIVE_COMMAND:
+                return "UNSOL_STK_PROACTIVE_COMMAND";
+            case RIL_UNSOL_STK_EVENT_NOTIFY:
+                return "UNSOL_STK_EVENT_NOTIFY";
+            case RIL_UNSOL_STK_CALL_SETUP:
+                return "UNSOL_STK_CALL_SETUP";
+            case RIL_UNSOL_SIM_SMS_STORAGE_FULL:
+                return "UNSOL_SIM_SMS_STORAGE_FULL";
+            case RIL_UNSOL_SIM_REFRESH:
+                return "UNSOL_SIM_REFRESH";
+            case RIL_UNSOL_CALL_RING:
+                return "UNSOL_CALL_RING";
+            case RIL_UNSOL_RESPONSE_SIM_STATUS_CHANGED:
+                return "UNSOL_RESPONSE_SIM_STATUS_CHANGED";
+            case RIL_UNSOL_RESPONSE_CDMA_NEW_SMS:
+                return "UNSOL_RESPONSE_CDMA_NEW_SMS";
+            case RIL_UNSOL_RESPONSE_NEW_BROADCAST_SMS:
+                return "UNSOL_RESPONSE_NEW_BROADCAST_SMS";
+            case RIL_UNSOL_CDMA_RUIM_SMS_STORAGE_FULL:
+                return "UNSOL_CDMA_RUIM_SMS_STORAGE_FULL";
+            case RIL_UNSOL_RESTRICTED_STATE_CHANGED:
+                return "UNSOL_RESTRICTED_STATE_CHANGED";
+            case RIL_UNSOL_ENTER_EMERGENCY_CALLBACK_MODE:
+                return "UNSOL_ENTER_EMERGENCY_CALLBACK_MODE";
+            case RIL_UNSOL_CDMA_CALL_WAITING:
+                return "UNSOL_CDMA_CALL_WAITING";
+            case RIL_UNSOL_CDMA_OTA_PROVISION_STATUS:
+                return "UNSOL_CDMA_OTA_PROVISION_STATUS";
+            case RIL_UNSOL_CDMA_INFO_REC:
+                return "UNSOL_CDMA_INFO_REC";
+            case RIL_UNSOL_OEM_HOOK_RAW:
+                return "UNSOL_OEM_HOOK_RAW";
+            case RIL_UNSOL_RINGBACK_TONE:
+                return "UNSOL_RINGBACK_TONE";
+            case RIL_UNSOL_RESEND_INCALL_MUTE:
+                return "UNSOL_RESEND_INCALL_MUTE";
+            case RIL_UNSOL_CDMA_SUBSCRIPTION_SOURCE_CHANGED:
+                return "UNSOL_CDMA_SUBSCRIPTION_SOURCE_CHANGED";
+            case RIL_UNSOL_CDMA_PRL_CHANGED:
+                return "UNSOL_CDMA_PRL_CHANGED";
+            case RIL_UNSOL_EXIT_EMERGENCY_CALLBACK_MODE:
+                return "UNSOL_EXIT_EMERGENCY_CALLBACK_MODE";
+            case RIL_UNSOL_RIL_CONNECTED:
+                return "UNSOL_RIL_CONNECTED";
+            case RIL_UNSOL_VOICE_RADIO_TECH_CHANGED:
+                return "UNSOL_VOICE_RADIO_TECH_CHANGED";
+            case RIL_UNSOL_CELL_INFO_LIST:
+                return "UNSOL_CELL_INFO_LIST";
+            case RIL_UNSOL_RESPONSE_IMS_NETWORK_STATE_CHANGED:
+                return "UNSOL_RESPONSE_IMS_NETWORK_STATE_CHANGED";
+            case RIL_UNSOL_UICC_SUBSCRIPTION_STATUS_CHANGED:
+                return "UNSOL_UICC_SUBSCRIPTION_STATUS_CHANGED";
+            case RIL_UNSOL_SRVCC_STATE_NOTIFY:
+                return "UNSOL_SRVCC_STATE_NOTIFY";
+            case RIL_UNSOL_HARDWARE_CONFIG_CHANGED:
+                return "UNSOL_HARDWARE_CONFIG_CHANGED";
+            case RIL_UNSOL_DC_RT_INFO_CHANGED:
+                return "UNSOL_DC_RT_INFO_CHANGED";
+            case RIL_UNSOL_RADIO_CAPABILITY:
+                return "UNSOL_RADIO_CAPABILITY";
+            case RIL_UNSOL_ON_SS:
+                return "UNSOL_ON_SS";
+            case RIL_UNSOL_STK_CC_ALPHA_NOTIFY:
+                return "UNSOL_STK_CC_ALPHA_NOTIFY";
+            case RIL_UNSOL_LCEDATA_RECV:
+                return "UNSOL_LCE_INFO_RECV";
+            case RIL_UNSOL_PCO_DATA:
+                return "UNSOL_PCO_DATA";
+            case RIL_UNSOL_MODEM_RESTART:
+                return "UNSOL_MODEM_RESTART";
+            case RIL_UNSOL_CARRIER_INFO_IMSI_ENCRYPTION:
+                return "UNSOL_CARRIER_INFO_IMSI_ENCRYPTION";
+            case RIL_UNSOL_NETWORK_SCAN_RESULT:
+                return "UNSOL_NETWORK_SCAN_RESULT";
+            case RIL_UNSOL_KEEPALIVE_STATUS:
+                return "UNSOL_KEEPALIVE_STATUS";
+            case RIL_UNSOL_UNTHROTTLE_APN:
+                return "UNSOL_UNTHROTTLE_APN";
+            case RIL_UNSOL_RESPONSE_SIM_PHONEBOOK_CHANGED:
+                return "UNSOL_RESPONSE_SIM_PHONEBOOK_CHANGED";
+            case RIL_UNSOL_RESPONSE_SIM_PHONEBOOK_RECORDS_RECEIVED:
+                return "UNSOL_RESPONSE_SIM_PHONEBOOK_RECORDS_RECEIVED";
+            case RIL_UNSOL_ICC_SLOT_STATUS:
+                return "UNSOL_ICC_SLOT_STATUS";
+            case RIL_UNSOL_PHYSICAL_CHANNEL_CONFIG:
+                return "UNSOL_PHYSICAL_CHANNEL_CONFIG";
+            case RIL_UNSOL_EMERGENCY_NUMBER_LIST:
+                return "UNSOL_EMERGENCY_NUMBER_LIST";
+            case RIL_UNSOL_UICC_APPLICATIONS_ENABLEMENT_CHANGED:
+                return "UNSOL_UICC_APPLICATIONS_ENABLEMENT_CHANGED";
+            case RIL_UNSOL_REGISTRATION_FAILED:
+                return "UNSOL_REGISTRATION_FAILED";
+            case RIL_UNSOL_BARRING_INFO_CHANGED:
+                return "UNSOL_BARRING_INFO_CHANGED";
+            default:
+                return "<unknown response>";
+        }
+    }
+
+    /**
+     * Create capabilities based off of the radio hal version and feature set configurations.
+     * @param radioHalVersion radio hal version
+     * @param modemReducedFeatureSet1 reduced feature set
+     * @return set of capabilities
+     */
+    @VisibleForTesting
+    public static Set<String> getCaps(HalVersion radioHalVersion, boolean modemReducedFeatureSet1) {
+        final Set<String> caps = new HashSet<>();
+
+        if (radioHalVersion.equals(RIL.RADIO_HAL_VERSION_UNKNOWN)) {
+            // If the Radio HAL is UNKNOWN, no capabilities will present themselves.
+            loge("Radio Hal Version is UNKNOWN!");
+        }
+
+        logd("Radio Hal Version = " + radioHalVersion.toString());
+        if (radioHalVersion.greaterOrEqual(RIL.RADIO_HAL_VERSION_1_6)) {
+            caps.add(CAPABILITY_USES_ALLOWED_NETWORK_TYPES_BITMASK);
+            logd("CAPABILITY_USES_ALLOWED_NETWORK_TYPES_BITMASK");
+
+            if (!modemReducedFeatureSet1) {
+                caps.add(CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE);
+                logd("CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE");
+                caps.add(CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE);
+                logd("CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE");
+                caps.add(CAPABILITY_THERMAL_MITIGATION_DATA_THROTTLING);
+                logd("CAPABILITY_THERMAL_MITIGATION_DATA_THROTTLING");
+                caps.add(CAPABILITY_SLICING_CONFIG_SUPPORTED);
+                logd("CAPABILITY_SLICING_CONFIG_SUPPORTED");
+                caps.add(CAPABILITY_PHYSICAL_CHANNEL_CONFIG_1_6_SUPPORTED);
+                logd("CAPABILITY_PHYSICAL_CHANNEL_CONFIG_1_6_SUPPORTED");
+            } else {
+                caps.add(CAPABILITY_SIM_PHONEBOOK_IN_MODEM);
+                logd("CAPABILITY_SIM_PHONEBOOK_IN_MODEM");
+            }
+        }
+        return caps;
+    }
+
+    private static void logd(String log) {
+        Rlog.d("RILUtils", log);
+    }
+
+    private static void loge(String log) {
+        Rlog.e("RILUtils", log);
+    }
+}
diff --git a/src/java/com/android/internal/telephony/RadioConfig.java b/src/java/com/android/internal/telephony/RadioConfig.java
index 32b24fe..d9a2608 100644
--- a/src/java/com/android/internal/telephony/RadioConfig.java
+++ b/src/java/com/android/internal/telephony/RadioConfig.java
@@ -29,21 +29,18 @@
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SWITCH_DUAL_SIM_CONFIG;
 
 import android.content.Context;
-import android.hardware.radio.V1_0.RadioResponseInfo;
-import android.hardware.radio.V1_0.RadioResponseType;
-import android.hardware.radio.config.V1_0.IRadioConfig;
-import android.hardware.radio.config.V1_1.ModemsConfig;
 import android.os.AsyncResult;
 import android.os.Handler;
 import android.os.HwBinder;
+import android.os.IBinder;
 import android.os.Message;
 import android.os.Registrant;
 import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.WorkSource;
 import android.telephony.TelephonyManager;
 import android.util.SparseArray;
 
-import com.android.internal.telephony.uicc.IccSlotStatus;
 import com.android.telephony.Rlog;
 
 import java.util.ArrayList;
@@ -59,35 +56,33 @@
     private static final String TAG = "RadioConfig";
     private static final boolean DBG = true;
     private static final boolean VDBG = false;   //STOPSHIP if true
-
     private static final int EVENT_SERVICE_DEAD = 1;
+    private static final Object sLock = new Object();
 
-    private static final HalVersion RADIO_CONFIG_HAL_VERSION_UNKNOWN = new HalVersion(-1, -1);
-
-    private static final HalVersion RADIO_CONFIG_HAL_VERSION_1_0 = new HalVersion(1, 0);
-
-    private static final HalVersion RADIO_CONFIG_HAL_VERSION_1_1 = new HalVersion(1, 1);
-
-    private static final HalVersion RADIO_CONFIG_HAL_VERSION_1_3 = new HalVersion(1, 3);
+    static final HalVersion RADIO_CONFIG_HAL_VERSION_UNKNOWN = new HalVersion(-1, -1);
+    static final HalVersion RADIO_CONFIG_HAL_VERSION_1_0 = new HalVersion(1, 0);
+    static final HalVersion RADIO_CONFIG_HAL_VERSION_1_1 = new HalVersion(1, 1);
+    static final HalVersion RADIO_CONFIG_HAL_VERSION_1_3 = new HalVersion(1, 3);
+    static final HalVersion RADIO_CONFIG_HAL_VERSION_2_0 = new HalVersion(2, 0);
 
     private final boolean mIsMobileNetworkSupported;
-    private volatile IRadioConfig mRadioConfigProxy = null;
-    // IRadioConfig version
-    private HalVersion mRadioConfigVersion = RADIO_CONFIG_HAL_VERSION_UNKNOWN;
-    private final ServiceDeathRecipient mServiceDeathRecipient;
-    private final AtomicLong mRadioConfigProxyCookie = new AtomicLong(0);
+    private final HwServiceDeathRecipient mHwServiceDeathRecipient;
+    private final BinderServiceDeathRecipient mBinderServiceDeathRecipient;
     private final RadioConfigResponse mRadioConfigResponse;
     private final RadioConfigIndication mRadioConfigIndication;
-    private final SparseArray<RILRequest> mRequestList = new SparseArray<RILRequest>();
+    private final SparseArray<RILRequest> mRequestList = new SparseArray<>();
     /* default work source which will blame phone process */
     private final WorkSource mDefaultWorkSource;
     private final int[] mDeviceNrCapabilities;
+    private final AtomicLong mRadioConfigProxyCookie = new AtomicLong(0);
+
     private static RadioConfig sRadioConfig;
-    private static final Object sLock = new Object();
+
+    private final RadioConfigProxy mRadioConfigProxy = new RadioConfigProxy();
 
     protected Registrant mSimSlotStatusRegistrant;
 
-    final class ServiceDeathRecipient implements HwBinder.DeathRecipient {
+    final class HwServiceDeathRecipient implements HwBinder.DeathRecipient {
         @Override
         public void serviceDied(long cookie) {
             // Deal with service going away
@@ -96,23 +91,201 @@
         }
     }
 
+    final class BinderServiceDeathRecipient implements IBinder.DeathRecipient {
+        private IBinder mBinder;
+
+        public void linkToDeath(IBinder service) throws RemoteException {
+            if (service != null) {
+                mBinder = service;
+                mBinder.linkToDeath(this, (int) mRadioConfigProxyCookie.incrementAndGet());
+            }
+        }
+
+        public synchronized void unlinkToDeath() {
+            if (mBinder != null) {
+                mBinder.unlinkToDeath(this, 0);
+                mBinder = null;
+            }
+        }
+
+        @Override
+        public void binderDied() {
+            logd("RadioConfigService has died.");
+            unlinkToDeath();
+        }
+
+    }
+
+    private final class RadioConfigProxy {
+        private boolean mIsAidl;
+
+        private volatile android.hardware.radio.config.V1_0.IRadioConfig mRadioConfigProxyV1 = null;
+        private volatile android.hardware.radio.config.IRadioConfig mRadioConfigProxyV2 = null;
+        private HalVersion mHalVersion = RADIO_CONFIG_HAL_VERSION_UNKNOWN;
+
+        public boolean isV2OrHigher() {
+            return mIsAidl;
+        }
+
+        public void setV1(
+                HalVersion halVersion, android.hardware.radio.config.V1_0.IRadioConfig config) {
+            mHalVersion = halVersion;
+            mRadioConfigProxyV1 = config;
+            mIsAidl = false;
+        }
+
+        public android.hardware.radio.config.V1_0.IRadioConfig getV1() {
+            return mRadioConfigProxyV1;
+        }
+
+        public void setV2(
+                HalVersion halVersion, android.hardware.radio.config.IRadioConfig config) {
+            mHalVersion = halVersion;
+            mRadioConfigProxyV2 = config;
+            mIsAidl = true;
+        }
+
+        public android.hardware.radio.config.IRadioConfig getV2() {
+            return mRadioConfigProxyV2;
+        }
+
+        public void clear() {
+            mHalVersion = RADIO_CONFIG_HAL_VERSION_UNKNOWN;
+            mRadioConfigProxyV1 = null;
+            mRadioConfigProxyV2 = null;
+        }
+
+        public boolean isEmpty() {
+            return mRadioConfigProxyV1 == null && mRadioConfigProxyV2 == null;
+        }
+
+        public void linkToDeath() {
+            if (isEmpty()) return;
+
+            if (isV2OrHigher()) {
+                try {
+                    // Link to death
+                    mBinderServiceDeathRecipient.linkToDeath(mRadioConfigProxyV2.asBinder());
+
+                    // Set response. If fails, set proxy to null and return.
+                    mRadioConfigProxyV2.setResponseFunctions(
+                            mRadioConfigResponse.getV2(), mRadioConfigIndication.getV2());
+                } catch (RemoteException | RuntimeException e) {
+                    mRadioConfigProxyV2 = null;
+                    loge("getRadioConfigProxyV2: RadioConfigProxy setResponseFunctions: " + e);
+                }
+            } else {
+                try {
+                    // Link to death recipient and set response.
+                    // If fails, set proxy to null and return.
+                    mRadioConfigProxyV1.linkToDeath(
+                            mHwServiceDeathRecipient, mRadioConfigProxyCookie.incrementAndGet());
+                    mRadioConfigProxyV1.setResponseFunctions(
+                            mRadioConfigResponse.getV1(), mRadioConfigIndication.getV1());
+                } catch (RemoteException | RuntimeException e) {
+                    mRadioConfigProxyV1 = null;
+                    loge("getRadioConfigProxyV1: RadioConfigProxy setResponseFunctions: " + e);
+                }
+            }
+        }
+
+        public void getPhoneCapability(int serial) throws RemoteException {
+            if (isEmpty()) return;
+            if (mHalVersion.less(RADIO_CONFIG_HAL_VERSION_1_1)) return;
+
+            if (isV2OrHigher()) {
+                mRadioConfigProxyV2.getPhoneCapability(serial);
+            } else {
+                ((android.hardware.radio.config.V1_1.IRadioConfig) mRadioConfigProxyV1)
+                        .getPhoneCapability(serial);
+            }
+        }
+
+        public void getSimSlotsStatus(int serial) throws RemoteException {
+            if (isEmpty()) return;
+
+            if (isV2OrHigher()) {
+                mRadioConfigProxyV2.getSimSlotsStatus(serial);
+            } else {
+                mRadioConfigProxyV1.getSimSlotsStatus(serial);
+            }
+        }
+
+        public void setPreferredDataModem(int serial, byte modemId) throws RemoteException {
+            if (isEmpty()) return;
+            if (mHalVersion.less(RADIO_CONFIG_HAL_VERSION_1_1)) return;
+
+            if (isV2OrHigher()) {
+                mRadioConfigProxyV2.setPreferredDataModem(serial, modemId);
+            } else {
+                ((android.hardware.radio.config.V1_1.IRadioConfig) mRadioConfigProxyV1)
+                        .setPreferredDataModem(serial, modemId);
+            }
+        }
+
+        public void setSimSlotsMapping(int serial, int[] list)
+                throws RemoteException {
+            if (isEmpty()) return;
+
+            if (isV2OrHigher()) {
+                mRadioConfigProxyV2.setSimSlotsMapping(serial,
+                        RILUtils.convertSimSlotsMapping(list));
+            } else {
+                mRadioConfigProxyV1.setSimSlotsMapping(serial,
+                        RILUtils.primitiveArrayToArrayList(list));
+            }
+        }
+
+        public void setModemsConfig(
+                int serial,
+                android.hardware.radio.config.V1_1.ModemsConfig modemsConfig)
+                throws RemoteException {
+            if (isEmpty()) return;
+            if (mHalVersion.less(RADIO_CONFIG_HAL_VERSION_1_1)) return;
+            if (mHalVersion.greaterOrEqual(RADIO_CONFIG_HAL_VERSION_2_0)) return;
+
+            ((android.hardware.radio.config.V1_1.IRadioConfig) mRadioConfigProxyV1)
+                    .setModemsConfig(serial, modemsConfig);
+        }
+
+        public void setNumOfLiveModems(int serial, byte numOfLiveModems) throws RemoteException {
+            if (isEmpty()) return;
+            if (mHalVersion.less(RADIO_CONFIG_HAL_VERSION_2_0)) return;
+
+            mRadioConfigProxyV2.setNumOfLiveModems(serial, numOfLiveModems);
+        }
+
+        public HalVersion getVersion() {
+            return mHalVersion;
+        }
+
+        public void getHalDeviceCapabilities(int serial) throws RemoteException {
+            if (isEmpty()) return;
+            if (mHalVersion.less(RADIO_CONFIG_HAL_VERSION_1_3)) return;
+
+            if (isV2OrHigher()) {
+                mRadioConfigProxyV2.getHalDeviceCapabilities(serial);
+            } else {
+                ((android.hardware.radio.config.V1_3.IRadioConfig) mRadioConfigProxyV1)
+                        .getHalDeviceCapabilities(serial);
+            }
+        }
+    }
+
     private boolean isMobileDataCapable(Context context) {
         final TelephonyManager tm = context.getSystemService(TelephonyManager.class);
-        if (tm == null) {
-            return false;
-        }
-        return tm.isDataCapable();
+        return tm != null && tm.isDataCapable();
     }
 
     private RadioConfig(Context context, HalVersion radioHalVersion) {
         mIsMobileNetworkSupported = isMobileDataCapable(context);
 
         mRadioConfigResponse = new RadioConfigResponse(this, radioHalVersion);
-        mRadioConfigIndication = new RadioConfigIndication(this);
-        mServiceDeathRecipient = new ServiceDeathRecipient();
-
-        mDefaultWorkSource = new WorkSource(context.getApplicationInfo().uid,
-                context.getPackageName());
+        mRadioConfigIndication = new RadioConfigIndication(this, radioHalVersion);
+        mHwServiceDeathRecipient = new HwServiceDeathRecipient();
+        mBinderServiceDeathRecipient = new BinderServiceDeathRecipient();
+        mDefaultWorkSource = new WorkSource(
+                context.getApplicationInfo().uid, context.getPackageName());
 
         boolean is5gStandalone = context.getResources().getBoolean(
                 com.android.internal.R.bool.config_telephony5gStandalone);
@@ -161,14 +334,12 @@
 
     @Override
     public void handleMessage(Message message) {
-        switch (message.what) {
-            case EVENT_SERVICE_DEAD:
-                logd("handleMessage: EVENT_SERVICE_DEAD cookie = " + message.obj
-                        + " mRadioConfigProxyCookie = " + mRadioConfigProxyCookie.get());
-                if ((long) message.obj == mRadioConfigProxyCookie.get()) {
-                    resetProxyAndRequestList("EVENT_SERVICE_DEAD", null);
-                }
-                break;
+        if (message.what == EVENT_SERVICE_DEAD) {
+            logd("handleMessage: EVENT_SERVICE_DEAD cookie = " + message.obj
+                    + " mRadioConfigProxyCookie = " + mRadioConfigProxyCookie.get());
+            if ((long) message.obj == mRadioConfigProxyCookie.get()) {
+                resetProxyAndRequestList("EVENT_SERVICE_DEAD", null);
+            }
         }
     }
 
@@ -188,7 +359,7 @@
             for (int i = 0; i < count; i++) {
                 rr = mRequestList.valueAt(i);
                 if (DBG && loggable) {
-                    logd(i + ": [" + rr.mSerial + "] " + requestToString(rr.mRequest));
+                    logd(i + ": [" + rr.mSerial + "] " + RILUtils.requestToString(rr.mRequest));
                 }
                 rr.onError(error, null);
                 rr.release();
@@ -199,7 +370,7 @@
 
     private void resetProxyAndRequestList(String caller, Exception e) {
         loge(caller + ": " + e);
-        mRadioConfigProxy = null;
+        mRadioConfigProxy.clear();
 
         // increment the cookie so that death notification can be ignored
         mRadioConfigProxyCookie.incrementAndGet();
@@ -211,8 +382,13 @@
         getRadioConfigProxy(null);
     }
 
-    /** Returns a {@link IRadioConfig} instance or null if the service is not available. */
-    public IRadioConfig getRadioConfigProxy(Message result) {
+    /**
+     * Returns a holder that has either:
+     * - getV1() -> {@link android.hardware.radio.config.V1_0.IRadioConfig}
+     * - getV2() -> {@link android.hardware.radio.config.IRadioConfig}
+     * that returns corresponding hal implementation
+     */
+    public RadioConfigProxy getRadioConfigProxy(Message result) {
         if (!mIsMobileNetworkSupported) {
             if (VDBG) logd("getRadioConfigProxy: Not calling getService(): wifi-only");
             if (result != null) {
@@ -223,69 +399,65 @@
             return null;
         }
 
-        if (mRadioConfigProxy != null) {
+        if (!mRadioConfigProxy.isEmpty()) {
             return mRadioConfigProxy;
         }
 
         updateRadioConfigProxy();
+        mRadioConfigProxy.linkToDeath();
 
-        if (mRadioConfigProxy == null) {
-            if (result != null) {
-                AsyncResult.forMessage(result, null,
-                        CommandException.fromRilErrno(RADIO_NOT_AVAILABLE));
-                result.sendToTarget();
-            }
+        if (mRadioConfigProxy.isEmpty() && result != null) {
+            AsyncResult.forMessage(
+                    result, null, CommandException.fromRilErrno(RADIO_NOT_AVAILABLE));
+            result.sendToTarget();
         }
 
         return mRadioConfigProxy;
     }
 
     private void updateRadioConfigProxy() {
-        try {
+        // Try to get service from different versions.
 
-            // Try to get service from different versions.
-            try {
-                mRadioConfigProxy = android.hardware.radio.config.V1_3.IRadioConfig.getService(
-                        true);
-                mRadioConfigVersion = RADIO_CONFIG_HAL_VERSION_1_3;
-            } catch (NoSuchElementException e) {
-            }
+        // Try to get AIDL variant first
+        IBinder service = ServiceManager.waitForDeclaredService(
+                android.hardware.radio.config.IRadioConfig.DESCRIPTOR + "/default");
 
-
-            if (mRadioConfigProxy == null) {
-                // Try to get service from different versions.
-                try {
-                    mRadioConfigProxy = android.hardware.radio.config.V1_1.IRadioConfig.getService(
-                            true);
-                    mRadioConfigVersion = RADIO_CONFIG_HAL_VERSION_1_1;
-                } catch (NoSuchElementException e) {
-                }
-            }
-
-            if (mRadioConfigProxy == null) {
-                try {
-                    mRadioConfigProxy = android.hardware.radio.config.V1_0
-                            .IRadioConfig.getService(true);
-                    mRadioConfigVersion = RADIO_CONFIG_HAL_VERSION_1_0;
-                } catch (NoSuchElementException e) {
-                }
-            }
-
-            if (mRadioConfigProxy == null) {
-                loge("getRadioConfigProxy: mRadioConfigProxy == null");
-                return;
-            }
-
-            // Link to death recipient and set response. If fails, set proxy to null and return.
-            mRadioConfigProxy.linkToDeath(mServiceDeathRecipient,
-                    mRadioConfigProxyCookie.incrementAndGet());
-            mRadioConfigProxy.setResponseFunctions(mRadioConfigResponse,
-                    mRadioConfigIndication);
-        } catch (RemoteException | RuntimeException e) {
-            mRadioConfigProxy = null;
-            loge("getRadioConfigProxy: RadioConfigProxy setResponseFunctions: " + e);
+        if (service != null) {
+            mRadioConfigProxy.setV2(
+                    RADIO_CONFIG_HAL_VERSION_2_0,
+                    android.hardware.radio.config.IRadioConfig.Stub.asInterface(service));
             return;
         }
+
+        // Now HIDL 1.3
+        try {
+            mRadioConfigProxy.setV1(
+                    RADIO_CONFIG_HAL_VERSION_1_3,
+                    android.hardware.radio.config.V1_3.IRadioConfig.getService(true));
+            return;
+        } catch (NoSuchElementException | RemoteException ignored) {
+        }
+
+        // HIDL 1.1
+        try {
+            mRadioConfigProxy.setV1(
+                    RADIO_CONFIG_HAL_VERSION_1_1,
+                    android.hardware.radio.config.V1_1.IRadioConfig.getService(true));
+            return;
+        } catch (NoSuchElementException | RemoteException ignored) {
+        }
+
+        // HIDL 1.0
+        try {
+            mRadioConfigProxy.setV1(
+                    RADIO_CONFIG_HAL_VERSION_1_0,
+                    android.hardware.radio.config.V1_0.IRadioConfig.getService(true));
+            return;
+        } catch (NoSuchElementException | RemoteException ignored) {
+        }
+
+        // Couldn't bind to anything!
+        loge("getRadioConfigProxy: mRadioConfigProxyHolder == null");
     }
 
     private RILRequest obtainRequest(int request, Message result, WorkSource workSource) {
@@ -314,12 +486,36 @@
      * @param responseInfo RadioResponseInfo received in response callback
      * @return RILRequest corresponding to the response
      */
-    public RILRequest processResponse(RadioResponseInfo responseInfo) {
+    public RILRequest processResponse(android.hardware.radio.RadioResponseInfo responseInfo) {
         int serial = responseInfo.serial;
         int error = responseInfo.error;
         int type = responseInfo.type;
 
-        if (type != RadioResponseType.SOLICITED) {
+        if (type != android.hardware.radio.RadioResponseType.SOLICITED) {
+            loge("processResponse: Unexpected response type " + type);
+        }
+
+        RILRequest rr = findAndRemoveRequestFromList(serial);
+        if (rr == null) {
+            loge("processResponse: Unexpected response! serial: " + serial + " error: " + error);
+            return null;
+        }
+
+        return rr;
+    }
+
+    /**
+     * This is a helper function to be called when a RadioConfigResponse callback is called.
+     * It finds and returns RILRequest corresponding to the response if one is found.
+     * @param responseInfo RadioResponseInfo received in response callback
+     * @return RILRequest corresponding to the response
+     */
+    public RILRequest processResponse(android.hardware.radio.V1_0.RadioResponseInfo responseInfo) {
+        int serial = responseInfo.serial;
+        int error = responseInfo.error;
+        int type = responseInfo.type;
+
+        if (type != android.hardware.radio.RadioResponseType.SOLICITED) {
             loge("processResponse: Unexpected response type " + type);
         }
 
@@ -343,8 +539,7 @@
         int serial = responseInfo.serial;
         int error = responseInfo.error;
         int type = responseInfo.type;
-
-        if (type != RadioResponseType.SOLICITED) {
+        if (type != android.hardware.radio.RadioResponseType.SOLICITED) {
             loge("processResponse: Unexpected response type " + type);
         }
 
@@ -361,16 +556,17 @@
      * Wrapper function for IRadioConfig.getSimSlotsStatus().
      */
     public void getSimSlotsStatus(Message result) {
-        IRadioConfig radioConfigProxy = getRadioConfigProxy(result);
-        if (radioConfigProxy != null) {
+        RadioConfigProxy proxy = getRadioConfigProxy(result);
+
+        if (!proxy.isEmpty()) {
             RILRequest rr = obtainRequest(RIL_REQUEST_GET_SLOT_STATUS, result, mDefaultWorkSource);
 
             if (DBG) {
-                logd(rr.serialString() + "> " + requestToString(rr.mRequest));
+                logd(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
             }
 
             try {
-                radioConfigProxy.getSimSlotsStatus(rr.mSerial);
+                proxy.getSimSlotsStatus(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
                 resetProxyAndRequestList("getSimSlotsStatus", e);
             }
@@ -381,6 +577,7 @@
      * Wrapper function for IRadioConfig.setPreferredDataModem(int modemId).
      */
     public void setPreferredDataModem(int modemId, Message result) {
+        RadioConfigProxy proxy = getRadioConfigProxy(null);
         if (!isSetPreferredDataCommandSupported()) {
             if (result != null) {
                 AsyncResult.forMessage(result, null,
@@ -392,14 +589,12 @@
 
         RILRequest rr = obtainRequest(RIL_REQUEST_SET_PREFERRED_DATA_MODEM,
                 result, mDefaultWorkSource);
-
         if (DBG) {
-            logd(rr.serialString() + "> " + requestToString(rr.mRequest));
+            logd(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
         }
 
         try {
-            ((android.hardware.radio.config.V1_1.IRadioConfig) mRadioConfigProxy)
-                    .setPreferredDataModem(rr.mSerial, (byte) modemId);
+            proxy.setPreferredDataModem(rr.mSerial, (byte) modemId);
         } catch (RemoteException | RuntimeException e) {
             resetProxyAndRequestList("setPreferredDataModem", e);
         }
@@ -409,8 +604,8 @@
      * Wrapper function for IRadioConfig.getPhoneCapability().
      */
     public void getPhoneCapability(Message result) {
-        IRadioConfig radioConfigProxy = getRadioConfigProxy(null);
-        if (radioConfigProxy == null || mRadioConfigVersion.less(RADIO_CONFIG_HAL_VERSION_1_1)) {
+        RadioConfigProxy proxy = getRadioConfigProxy(null);
+        if (proxy.isEmpty() || proxy.getVersion().less(RADIO_CONFIG_HAL_VERSION_1_1)) {
             if (result != null) {
                 AsyncResult.forMessage(result, null,
                         CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
@@ -422,12 +617,11 @@
         RILRequest rr = obtainRequest(RIL_REQUEST_GET_PHONE_CAPABILITY, result, mDefaultWorkSource);
 
         if (DBG) {
-            logd(rr.serialString() + "> " + requestToString(rr.mRequest));
+            logd(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
         }
 
         try {
-            ((android.hardware.radio.config.V1_1.IRadioConfig) mRadioConfigProxy)
-                    .getPhoneCapability(rr.mSerial);
+            proxy.getPhoneCapability(rr.mSerial);
         } catch (RemoteException | RuntimeException e) {
             resetProxyAndRequestList("getPhoneCapability", e);
         }
@@ -440,91 +634,71 @@
      * See PhoneSwitcher for more details.
      */
     public boolean isSetPreferredDataCommandSupported() {
-        IRadioConfig radioConfigProxy = getRadioConfigProxy(null);
-        return radioConfigProxy != null && mRadioConfigVersion
-                .greaterOrEqual(RADIO_CONFIG_HAL_VERSION_1_1);
+        RadioConfigProxy proxy = getRadioConfigProxy(null);
+        return !proxy.isEmpty() && proxy.getVersion().greaterOrEqual(RADIO_CONFIG_HAL_VERSION_1_1);
     }
 
     /**
      * Wrapper function for IRadioConfig.setSimSlotsMapping(int32_t serial, vec<uint32_t> slotMap).
+     * TODO(ag/15898089): Interface for setSimSlotsMapping was changes but underlying implementation
+     *                    was not provided. Need to update this with proper implementation.
      */
     public void setSimSlotsMapping(int[] physicalSlots, Message result) {
-        IRadioConfig radioConfigProxy = getRadioConfigProxy(result);
-        if (radioConfigProxy != null) {
+        RadioConfigProxy proxy = getRadioConfigProxy(result);
+        if (!proxy.isEmpty()) {
             RILRequest rr = obtainRequest(RIL_REQUEST_SET_LOGICAL_TO_PHYSICAL_SLOT_MAPPING, result,
                     mDefaultWorkSource);
 
             if (DBG) {
-                logd(rr.serialString() + "> " + requestToString(rr.mRequest)
+                logd(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
                         + " " + Arrays.toString(physicalSlots));
             }
 
             try {
-                radioConfigProxy.setSimSlotsMapping(rr.mSerial,
-                        primitiveArrayToArrayList(physicalSlots));
+                proxy.setSimSlotsMapping(rr.mSerial, physicalSlots);
             } catch (RemoteException | RuntimeException e) {
                 resetProxyAndRequestList("setSimSlotsMapping", e);
             }
         }
     }
 
-    private static ArrayList<Integer> primitiveArrayToArrayList(int[] arr) {
-        ArrayList<Integer> arrayList = new ArrayList<>(arr.length);
-        for (int i : arr) {
-            arrayList.add(i);
-        }
-        return arrayList;
-    }
-
-    static String requestToString(int request) {
-        switch (request) {
-            case RIL_REQUEST_GET_PHONE_CAPABILITY:
-                return "GET_PHONE_CAPABILITY";
-            case RIL_REQUEST_GET_SLOT_STATUS:
-                return "GET_SLOT_STATUS";
-            case RIL_REQUEST_SET_LOGICAL_TO_PHYSICAL_SLOT_MAPPING:
-                return "SET_LOGICAL_TO_PHYSICAL_SLOT_MAPPING";
-            case RIL_REQUEST_SET_PREFERRED_DATA_MODEM:
-                return "SET_PREFERRED_DATA_MODEM";
-            case RIL_REQUEST_SWITCH_DUAL_SIM_CONFIG:
-                return "SWITCH_DUAL_SIM_CONFIG";
-            case RIL_REQUEST_GET_HAL_DEVICE_CAPABILITIES:
-                return "GET_HAL_DEVICE_CAPABILITIES";
-            default:
-                return "<unknown request " + request + ">";
-        }
-    }
-
     /**
-     * Wrapper function for using IRadioConfig.setModemsConfig(int32_t serial,
-     * ModemsConfig modemsConfig) to switch between single-sim and multi-sim.
+     * Wrapper function for using IRadioConfig.setNumOfLiveModems(int32_t serial,
+     * byte numOfLiveModems) to switch between single-sim and multi-sim.
      */
-    public void setModemsConfig(int numOfLiveModems, Message result) {
-        IRadioConfig radioConfigProxy = getRadioConfigProxy(result);
-        if (radioConfigProxy != null
-                && mRadioConfigVersion.greaterOrEqual(RADIO_CONFIG_HAL_VERSION_1_1)) {
-            android.hardware.radio.config.V1_1.IRadioConfig radioConfigProxy11 =
-                    (android.hardware.radio.config.V1_1.IRadioConfig) radioConfigProxy;
-            RILRequest rr = obtainRequest(RIL_REQUEST_SWITCH_DUAL_SIM_CONFIG,
-                    result, mDefaultWorkSource);
-
-            if (DBG) {
-                logd(rr.serialString() + "> " + requestToString(rr.mRequest)
-                        + ", numOfLiveModems = " + numOfLiveModems);
+    public void setNumOfLiveModems(int numOfLiveModems, Message result) {
+        RadioConfigProxy proxy = getRadioConfigProxy(result);
+        if (proxy.isEmpty() || proxy.getVersion().less(RADIO_CONFIG_HAL_VERSION_1_1)) {
+            if (result != null) {
+                AsyncResult.forMessage(
+                        result, null, CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
+                result.sendToTarget();
             }
-
-            try {
-                ModemsConfig modemsConfig = new ModemsConfig();
-                modemsConfig.numOfLiveModems = (byte) numOfLiveModems;
-                radioConfigProxy11.setModemsConfig(rr.mSerial, modemsConfig);
-            } catch (RemoteException | RuntimeException e) {
-                resetProxyAndRequestList("setModemsConfig", e);
-            }
+            return;
         }
-    }
 
-    // TODO: not needed for now, but if we don't want to use System Properties any more,
-    // we need to implement a wrapper function for getModemsConfig as well
+        RILRequest rr = obtainRequest(RIL_REQUEST_SWITCH_DUAL_SIM_CONFIG,
+                result, mDefaultWorkSource);
+
+        if (DBG) {
+            logd(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + ", numOfLiveModems = " + numOfLiveModems);
+        }
+
+        try {
+            if (proxy.getVersion().greaterOrEqual(RADIO_CONFIG_HAL_VERSION_2_0)) {
+                proxy.setNumOfLiveModems(rr.mSerial, (byte) numOfLiveModems);
+            } else {
+                android.hardware.radio.config.V1_1.ModemsConfig modemsConfig =
+                        new android.hardware.radio.config.V1_1.ModemsConfig();
+                modemsConfig.numOfLiveModems = (byte) numOfLiveModems;
+                proxy.setModemsConfig(rr.mSerial, modemsConfig);
+            }
+        } catch (RemoteException | RuntimeException e) {
+            resetProxyAndRequestList("setModemsConfig", e);
+        }
+
+    }
 
     /**
      * Register a handler to get SIM slot status changed notifications.
@@ -547,26 +721,8 @@
      * Gets the hal capabilities from the device.
      */
     public void getHalDeviceCapabilities(Message result) {
-        IRadioConfig radioConfigProxy = getRadioConfigProxy(Message.obtain(result));
-        if (radioConfigProxy != null
-                && mRadioConfigVersion.greaterOrEqual(RADIO_CONFIG_HAL_VERSION_1_3)) {
-            android.hardware.radio.config.V1_3.IRadioConfig radioConfigProxy13 =
-                    (android.hardware.radio.config.V1_3.IRadioConfig) radioConfigProxy;
-            RILRequest rr = obtainRequest(RIL_REQUEST_GET_HAL_DEVICE_CAPABILITIES,
-                    result, mDefaultWorkSource);
-
-            if (DBG) {
-                logd(rr.serialString() + "> " + requestToString(rr.mRequest));
-            }
-
-            try {
-                mRadioConfigVersion = RADIO_CONFIG_HAL_VERSION_1_3;
-                radioConfigProxy13.getHalDeviceCapabilities(rr.mSerial);
-
-            } catch (RemoteException | RuntimeException e) {
-                resetProxyAndRequestList("getHalDeviceCapabilities", e);
-            }
-        } else {
+        RadioConfigProxy proxy = getRadioConfigProxy(Message.obtain(result));
+        if (proxy.isEmpty() || proxy.getVersion().less(RADIO_CONFIG_HAL_VERSION_1_3)) {
             if (result != null) {
                 if (DBG) {
                     logd("RIL_REQUEST_GET_HAL_DEVICE_CAPABILITIES > REQUEST_NOT_SUPPORTED");
@@ -583,6 +739,20 @@
                             + "on complete message not set.");
                 }
             }
+            return;
+        }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_GET_HAL_DEVICE_CAPABILITIES,
+                result, mDefaultWorkSource);
+
+        if (DBG) {
+            logd(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+        }
+
+        try {
+            proxy.getHalDeviceCapabilities(rr.mSerial);
+        } catch (RemoteException | RuntimeException e) {
+            resetProxyAndRequestList("getHalDeviceCapabilities", e);
         }
     }
 
@@ -593,37 +763,6 @@
         return mDeviceNrCapabilities;
     }
 
-    static ArrayList<IccSlotStatus> convertHalSlotStatus(
-            ArrayList<android.hardware.radio.config.V1_0.SimSlotStatus> halSlotStatusList) {
-        ArrayList<IccSlotStatus> response = new ArrayList<IccSlotStatus>(halSlotStatusList.size());
-        for (android.hardware.radio.config.V1_0.SimSlotStatus slotStatus : halSlotStatusList) {
-            IccSlotStatus iccSlotStatus = new IccSlotStatus();
-            iccSlotStatus.setCardState(slotStatus.cardState);
-            iccSlotStatus.setSlotState(slotStatus.slotState);
-            iccSlotStatus.logicalSlotIndex = slotStatus.logicalSlotId;
-            iccSlotStatus.atr = slotStatus.atr;
-            iccSlotStatus.iccid = slotStatus.iccid;
-            response.add(iccSlotStatus);
-        }
-        return response;
-    }
-
-    static ArrayList<IccSlotStatus> convertHalSlotStatus_1_2(
-            ArrayList<android.hardware.radio.config.V1_2.SimSlotStatus> halSlotStatusList) {
-        ArrayList<IccSlotStatus> response = new ArrayList<IccSlotStatus>(halSlotStatusList.size());
-        for (android.hardware.radio.config.V1_2.SimSlotStatus slotStatus : halSlotStatusList) {
-            IccSlotStatus iccSlotStatus = new IccSlotStatus();
-            iccSlotStatus.setCardState(slotStatus.base.cardState);
-            iccSlotStatus.setSlotState(slotStatus.base.slotState);
-            iccSlotStatus.logicalSlotIndex = slotStatus.base.logicalSlotId;
-            iccSlotStatus.atr = slotStatus.base.atr;
-            iccSlotStatus.iccid = slotStatus.base.iccid;
-            iccSlotStatus.eid = slotStatus.eid;
-            response.add(iccSlotStatus);
-        }
-        return response;
-    }
-
     private static void logd(String log) {
         Rlog.d(TAG, log);
     }
diff --git a/src/java/com/android/internal/telephony/RadioConfigIndication.java b/src/java/com/android/internal/telephony/RadioConfigIndication.java
index 639272c..f88a9d2 100644
--- a/src/java/com/android/internal/telephony/RadioConfigIndication.java
+++ b/src/java/com/android/internal/telephony/RadioConfigIndication.java
@@ -16,48 +16,26 @@
 
 package com.android.internal.telephony;
 
-import android.hardware.radio.config.V1_2.IRadioConfigIndication;
-import android.os.AsyncResult;
-
-import com.android.internal.telephony.uicc.IccSlotStatus;
-import com.android.telephony.Rlog;
-
-import java.util.ArrayList;
-
 /**
  * This class is the implementation of IRadioConfigIndication interface.
  */
-public class RadioConfigIndication extends IRadioConfigIndication.Stub {
-    private final RadioConfig mRadioConfig;
-    private static final String TAG = "RadioConfigIndication";
+public class RadioConfigIndication {
+    private RadioConfigIndicationHidl mRadioConfigIndicationHidl;
+    private RadioConfigIndicationAidl mRadioConfigIndicationAidl;
 
-    public RadioConfigIndication(RadioConfig radioConfig) {
-        mRadioConfig = radioConfig;
-    }
-
-    /**
-     * Unsolicited indication for slot status changed
-     */
-    public void simSlotsStatusChanged(int indicationType,
-            ArrayList<android.hardware.radio.config.V1_0.SimSlotStatus> slotStatus) {
-        ArrayList<IccSlotStatus> ret = RadioConfig.convertHalSlotStatus(slotStatus);
-        Rlog.d(TAG, "[UNSL]< " + " UNSOL_SIM_SLOT_STATUS_CHANGED " + ret.toString());
-        if (mRadioConfig.mSimSlotStatusRegistrant != null) {
-            mRadioConfig.mSimSlotStatusRegistrant.notifyRegistrant(
-                    new AsyncResult(null, ret, null));
+    public RadioConfigIndication(RadioConfig radioConfig, HalVersion halVersion) {
+        if (halVersion.greaterOrEqual(RIL.RADIO_HAL_VERSION_2_0)) {
+            mRadioConfigIndicationAidl = new RadioConfigIndicationAidl(radioConfig);
+        } else {
+            mRadioConfigIndicationHidl = new RadioConfigIndicationHidl(radioConfig);
         }
     }
 
-    /**
-     * Unsolicited indication for slot status changed
-     */
-    public void simSlotsStatusChanged_1_2(int indicationType,
-            ArrayList<android.hardware.radio.config.V1_2.SimSlotStatus> slotStatus) {
-        ArrayList<IccSlotStatus> ret = RadioConfig.convertHalSlotStatus_1_2(slotStatus);
-        Rlog.d(TAG, "[UNSL]< " + " UNSOL_SIM_SLOT_STATUS_CHANGED " + ret.toString());
-        if (mRadioConfig.mSimSlotStatusRegistrant != null) {
-            mRadioConfig.mSimSlotStatusRegistrant.notifyRegistrant(
-                    new AsyncResult(null, ret, null));
-        }
+    public android.hardware.radio.config.V1_2.IRadioConfigIndication getV1() {
+        return mRadioConfigIndicationHidl;
+    }
+
+    public android.hardware.radio.config.IRadioConfigIndication getV2() {
+        return mRadioConfigIndicationAidl;
     }
 }
diff --git a/src/java/com/android/internal/telephony/RadioConfigIndicationAidl.java b/src/java/com/android/internal/telephony/RadioConfigIndicationAidl.java
new file mode 100644
index 0000000..aecb5c0
--- /dev/null
+++ b/src/java/com/android/internal/telephony/RadioConfigIndicationAidl.java
@@ -0,0 +1,56 @@
+/*
+ * 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;
+
+import android.os.AsyncResult;
+
+import com.android.internal.telephony.uicc.IccSlotStatus;
+import com.android.telephony.Rlog;
+
+import java.util.ArrayList;
+
+/**
+ * This class is the AIDL implementation of IRadioConfigIndication interface.
+ */
+public class RadioConfigIndicationAidl extends
+        android.hardware.radio.config.IRadioConfigIndication.Stub {
+    private static final String TAG = "RadioConfigIndicationAidl";
+
+    private final RadioConfig mRadioConfig;
+
+    public RadioConfigIndicationAidl(RadioConfig radioConfig) {
+        mRadioConfig = radioConfig;
+    }
+
+    /**
+     * Unsolicited indication for slot status changed
+     */
+    @Override
+    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());
+        if (mRadioConfig.mSimSlotStatusRegistrant != null) {
+            mRadioConfig.mSimSlotStatusRegistrant.notifyRegistrant(
+                    new AsyncResult(null, ret, null));
+        }
+    }
+
+    private static void logd(String log) {
+        Rlog.d(TAG, log);
+    }
+}
diff --git a/src/java/com/android/internal/telephony/RadioConfigIndicationHidl.java b/src/java/com/android/internal/telephony/RadioConfigIndicationHidl.java
new file mode 100644
index 0000000..23a676b
--- /dev/null
+++ b/src/java/com/android/internal/telephony/RadioConfigIndicationHidl.java
@@ -0,0 +1,68 @@
+/*
+ * 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;
+
+import android.os.AsyncResult;
+
+import com.android.internal.telephony.uicc.IccSlotStatus;
+import com.android.telephony.Rlog;
+
+import java.util.ArrayList;
+
+/**
+ * This class is the HIDL implementation of IRadioConfigIndication interface.
+ */
+public class RadioConfigIndicationHidl extends
+        android.hardware.radio.config.V1_2.IRadioConfigIndication.Stub {
+    private static final String TAG = "RadioConfigIndicationHidl";
+
+    private final RadioConfig mRadioConfig;
+
+    public RadioConfigIndicationHidl(RadioConfig radioConfig) {
+        mRadioConfig = radioConfig;
+    }
+
+    /**
+     * Unsolicited indication for slot status changed
+     */
+    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());
+        if (mRadioConfig.mSimSlotStatusRegistrant != null) {
+            mRadioConfig.mSimSlotStatusRegistrant.notifyRegistrant(
+                    new AsyncResult(null, ret, null));
+        }
+    }
+
+    /**
+     * Unsolicited indication for slot status changed
+     */
+    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());
+        if (mRadioConfig.mSimSlotStatusRegistrant != null) {
+            mRadioConfig.mSimSlotStatusRegistrant.notifyRegistrant(
+                    new AsyncResult(null, ret, null));
+        }
+    }
+
+    private static void logd(String log) {
+        Rlog.d(TAG, log);
+    }
+}
diff --git a/src/java/com/android/internal/telephony/RadioConfigResponse.java b/src/java/com/android/internal/telephony/RadioConfigResponse.java
index cfec982..a1938bd 100644
--- a/src/java/com/android/internal/telephony/RadioConfigResponse.java
+++ b/src/java/com/android/internal/telephony/RadioConfigResponse.java
@@ -16,260 +16,37 @@
 
 package com.android.internal.telephony;
 
-import static android.telephony.TelephonyManager.CAPABILITY_ALLOWED_NETWORK_TYPES_USED;
-import static android.telephony.TelephonyManager
-        .CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE;
-import static android.telephony.TelephonyManager.CAPABILITY_PHYSICAL_CHANNEL_CONFIG_1_6_SUPPORTED;
-import static android.telephony.TelephonyManager.CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE;
-import static android.telephony.TelephonyManager.CAPABILITY_SIM_PHONEBOOK_IN_MODEM;
-import static android.telephony.TelephonyManager.CAPABILITY_THERMAL_MITIGATION_DATA_THROTTLING;
-import static android.telephony.TelephonyManager.RadioInterfaceCapability;
+import android.telephony.TelephonyManager;
 
-import android.hardware.radio.V1_0.RadioError;
-import android.hardware.radio.V1_0.RadioResponseInfo;
-import android.hardware.radio.config.V1_1.ModemsConfig;
-import android.hardware.radio.config.V1_3.IRadioConfigResponse;
-import android.telephony.ModemInfo;
-import android.telephony.PhoneCapability;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.telephony.uicc.IccSlotStatus;
-import com.android.telephony.Rlog;
-
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
 import java.util.Set;
 
 /**
  * This class is the implementation of IRadioConfigResponse interface.
  */
-public class RadioConfigResponse extends IRadioConfigResponse.Stub {
+public class RadioConfigResponse {
     private static final String TAG = "RadioConfigResponse";
 
-    private final RadioConfig mRadioConfig;
-    private final HalVersion mRadioHalVersion;
+    private final HalVersion mHalVersion;
 
-    public RadioConfigResponse(RadioConfig radioConfig, HalVersion radioHalVersion) {
-        mRadioConfig = radioConfig;
-        mRadioHalVersion = radioHalVersion;
-    }
+    private RadioConfigResponseAidl mRadioConfigResponseAidl = null;
+    private RadioConfigResponseHidl mRadioConfigResponseHidl = null;
 
-    /**
-     * Response function for IRadioConfig.getSimSlotsStatus().
-     */
-    public void getSimSlotsStatusResponse(RadioResponseInfo responseInfo,
-            ArrayList<android.hardware.radio.config.V1_0.SimSlotStatus> slotStatus) {
-        RILRequest rr = mRadioConfig.processResponse(responseInfo);
+    public RadioConfigResponse(RadioConfig radioConfig, HalVersion halVersion) {
+        mHalVersion = halVersion;
 
-        if (rr != null) {
-            ArrayList<IccSlotStatus> ret = RadioConfig.convertHalSlotStatus(slotStatus);
-            if (responseInfo.error == RadioError.NONE) {
-                // send response
-                RadioResponse.sendMessageResponse(rr.mResult, ret);
-                Rlog.d(TAG, rr.serialString() + "< "
-                        + mRadioConfig.requestToString(rr.mRequest) + " " + ret.toString());
-            } else {
-                rr.onError(responseInfo.error, ret);
-                Rlog.e(TAG, rr.serialString() + "< "
-                        + mRadioConfig.requestToString(rr.mRequest) + " error "
-                        + responseInfo.error);
-            }
-
+        if (mHalVersion.greaterOrEqual(RIL.RADIO_HAL_VERSION_2_0)) {
+            mRadioConfigResponseAidl = new RadioConfigResponseAidl(radioConfig, halVersion);
         } else {
-            Rlog.e(TAG, "getSimSlotsStatusResponse: Error " + responseInfo.toString());
+            mRadioConfigResponseHidl = new RadioConfigResponseHidl(radioConfig, halVersion);
         }
     }
 
-    /**
-     * Response function for IRadioConfig.getSimSlotsStatus().
-     */
-    public void getSimSlotsStatusResponse_1_2(RadioResponseInfo responseInfo,
-            ArrayList<android.hardware.radio.config.V1_2.SimSlotStatus> slotStatus) {
-        RILRequest rr = mRadioConfig.processResponse(responseInfo);
-
-        if (rr != null) {
-            ArrayList<IccSlotStatus> ret = RadioConfig.convertHalSlotStatus_1_2(slotStatus);
-            if (responseInfo.error == RadioError.NONE) {
-                // send response
-                RadioResponse.sendMessageResponse(rr.mResult, ret);
-                Rlog.d(TAG, rr.serialString() + "< "
-                        + mRadioConfig.requestToString(rr.mRequest) + " " + ret.toString());
-            } else {
-                rr.onError(responseInfo.error, ret);
-                Rlog.e(TAG, rr.serialString() + "< "
-                        + mRadioConfig.requestToString(rr.mRequest) + " error "
-                        + responseInfo.error);
-            }
-        } else {
-            Rlog.e(TAG, "getSimSlotsStatusResponse_1_2: Error " + responseInfo.toString());
-        }
+    public android.hardware.radio.config.V1_3.IRadioConfigResponse getV1() {
+        return mRadioConfigResponseHidl;
     }
 
-    /**
-     * Response function for IRadioConfig.setSimSlotsMapping().
-     */
-    public void setSimSlotsMappingResponse(RadioResponseInfo responseInfo) {
-        RILRequest rr = mRadioConfig.processResponse(responseInfo);
-
-        if (rr != null) {
-            if (responseInfo.error == RadioError.NONE) {
-                // send response
-                RadioResponse.sendMessageResponse(rr.mResult, null);
-                Rlog.d(TAG, rr.serialString() + "< "
-                        + mRadioConfig.requestToString(rr.mRequest));
-            } else {
-                rr.onError(responseInfo.error, null);
-                Rlog.e(TAG, rr.serialString() + "< "
-                        + mRadioConfig.requestToString(rr.mRequest) + " error "
-                        + responseInfo.error);
-            }
-        } else {
-            Rlog.e(TAG, "setSimSlotsMappingResponse: Error " + responseInfo.toString());
-        }
-    }
-
-    private PhoneCapability convertHalPhoneCapability(
-            android.hardware.radio.config.V1_1.PhoneCapability phoneCapability) {
-        // TODO b/121394331: clean up V1_1.PhoneCapability fields.
-        int maxActiveVoiceCalls = 0;
-        int maxActiveData = phoneCapability.maxActiveData;
-        boolean validationBeforeSwitchSupported = phoneCapability.isInternetLingeringSupported;
-        List<ModemInfo> logicalModemList = new ArrayList();
-
-        for (android.hardware.radio.config.V1_1.ModemInfo
-                modemInfo : phoneCapability.logicalModemList) {
-            logicalModemList.add(new ModemInfo(modemInfo.modemId));
-        }
-
-        return new PhoneCapability(maxActiveVoiceCalls, maxActiveData, logicalModemList,
-                validationBeforeSwitchSupported, mRadioConfig.getDeviceNrCapabilities());
-    }
-    /**
-     * Response function for IRadioConfig.getPhoneCapability().
-     */
-    public void getPhoneCapabilityResponse(RadioResponseInfo responseInfo,
-            android.hardware.radio.config.V1_1.PhoneCapability phoneCapability) {
-        RILRequest rr = mRadioConfig.processResponse(responseInfo);
-
-        if (rr != null) {
-            PhoneCapability ret = convertHalPhoneCapability(phoneCapability);
-            if (responseInfo.error == RadioError.NONE) {
-                // send response
-                RadioResponse.sendMessageResponse(rr.mResult, ret);
-                Rlog.d(TAG, rr.serialString() + "< "
-                        + mRadioConfig.requestToString(rr.mRequest) + " " + ret.toString());
-            } else {
-                rr.onError(responseInfo.error, ret);
-                Rlog.e(TAG, rr.serialString() + "< "
-                        + mRadioConfig.requestToString(rr.mRequest) + " error "
-                        + responseInfo.error);
-            }
-        } else {
-            Rlog.e(TAG, "getPhoneCapabilityResponse: Error " + responseInfo.toString());
-        }
-    }
-
-    /**
-     * Response function for IRadioConfig.setPreferredDataModem().
-     */
-    public void setPreferredDataModemResponse(RadioResponseInfo responseInfo) {
-        RILRequest rr = mRadioConfig.processResponse(responseInfo);
-
-        if (rr != null) {
-            if (responseInfo.error == RadioError.NONE) {
-                // send response
-                RadioResponse.sendMessageResponse(rr.mResult, null);
-                Rlog.d(TAG, rr.serialString() + "< "
-                        + mRadioConfig.requestToString(rr.mRequest));
-            } else {
-                rr.onError(responseInfo.error, null);
-                Rlog.e(TAG, rr.serialString() + "< "
-                        + mRadioConfig.requestToString(rr.mRequest) + " error "
-                        + responseInfo.error);
-            }
-        } else {
-            Rlog.e(TAG, "setPreferredDataModemResponse: Error " + responseInfo.toString());
-        }
-    }
-
-    /**
-     * Response function for IRadioConfig.setModemsConfigResponse()
-     * Currently this is being used as the callback for RadioConfig.setModemsConfig() method
-     */
-    public void setModemsConfigResponse(RadioResponseInfo responseInfo) {
-        RILRequest rr = mRadioConfig.processResponse(responseInfo);
-
-        if (rr != null) {
-            if (responseInfo.error == RadioError.NONE) {
-                // send response
-                RadioResponse.sendMessageResponse(rr.mResult, rr.mRequest);
-                Rlog.d(TAG, rr.serialString() + "< "
-                        + mRadioConfig.requestToString(rr.mRequest));
-            } else {
-                rr.onError(responseInfo.error, null);
-                Rlog.e(TAG, rr.serialString() + "< "
-                        + mRadioConfig.requestToString(rr.mRequest) + " error "
-                        + responseInfo.error);
-            }
-        } else {
-            Rlog.e(TAG, "setModemsConfigResponse: Error " + responseInfo.toString());
-        }
-    }
-
-    /**
-     * Response function for IRadioConfig.getModemsConfigResponse()
-     */
-    public void getModemsConfigResponse(RadioResponseInfo responseInfo, ModemsConfig modemsConfig) {
-        RILRequest rr = mRadioConfig.processResponse(responseInfo);
-
-        if (rr != null) {
-            if (responseInfo.error == RadioError.NONE) {
-                // send response
-                RadioResponse.sendMessageResponse(rr.mResult, modemsConfig);
-                Rlog.d(TAG, rr.serialString() + "< "
-                        + mRadioConfig.requestToString(rr.mRequest));
-            } else {
-                rr.onError(responseInfo.error, modemsConfig);
-                Rlog.e(TAG, rr.serialString() + "< "
-                        + mRadioConfig.requestToString(rr.mRequest) + " error "
-                        + responseInfo.error);
-            }
-        } else {
-            Rlog.e(TAG, "getModemsConfigResponse: Error " + responseInfo.toString());
-        }
-    }
-
-    /**
-     * Response function IRadioConfig.getHalDeviceCapabilities()
-     */
-    public void getHalDeviceCapabilitiesResponse(
-            android.hardware.radio.V1_6.RadioResponseInfo responseInfo,
-            boolean modemReducedFeatureSet1) {
-
-        // convert hal device capabilities to RadioInterfaceCapabilities
-
-        RILRequest rr = mRadioConfig.processResponse_1_6(responseInfo);
-        if (rr != null) {
-            // The response is compatible with Radio 1.6, it means the modem
-            // supports setAllowedNetworkTypeBitmap.
-
-            final Set<String> ret = getCaps(mRadioHalVersion, modemReducedFeatureSet1);
-
-            if (responseInfo.error == RadioError.NONE) {
-                // send response
-                RadioResponse.sendMessageResponse(rr.mResult, ret);
-                Rlog.d(TAG, rr.serialString() + "< "
-                        + mRadioConfig.requestToString(rr.mRequest));
-            } else {
-                rr.onError(responseInfo.error, ret);
-                Rlog.e(TAG, rr.serialString() + "< "
-                        + mRadioConfig.requestToString(rr.mRequest) + " error "
-                        + responseInfo.error);
-            }
-        } else {
-            Rlog.e(TAG, "getHalDeviceCapabilities: Error " + responseInfo.toString());
-        }
+    public android.hardware.radio.config.IRadioConfigResponse getV2() {
+        return mRadioConfigResponseAidl;
     }
 
     /**
@@ -279,42 +56,8 @@
      *
      * @return all capabilities
      */
-    @RadioInterfaceCapability
+    @TelephonyManager.RadioInterfaceCapability
     public Set<String> getFullCapabilitySet() {
-        return getCaps(mRadioHalVersion, false);
-    }
-
-    /**
-     * Create capabilities based off of the radio hal version and feature set configurations.
-     */
-    @VisibleForTesting
-    public static Set<String> getCaps(HalVersion radioHalVersion,
-            boolean modemReducedFeatureSet1) {
-        final Set<String> caps = new HashSet<>();
-
-        if (radioHalVersion.equals(RIL.RADIO_HAL_VERSION_UNKNOWN)) {
-            // If the Radio HAL is UNKNOWN, no capabilities will present themselves.
-            Rlog.e(TAG, "Radio Hal Version is UNKNOWN!");
-        }
-
-        Rlog.d(TAG, "Radio Hal Version = " + radioHalVersion.toString());
-        if (radioHalVersion.greaterOrEqual(RIL.RADIO_HAL_VERSION_1_6)) {
-            caps.add(CAPABILITY_ALLOWED_NETWORK_TYPES_USED);
-            Rlog.d(TAG, "CAPABILITY_ALLOWED_NETWORK_TYPES_USED");
-
-            if (!modemReducedFeatureSet1) {
-                caps.add(CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE);
-                Rlog.d(TAG, "CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE");
-                caps.add(CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE);
-                Rlog.d(TAG, "CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE");
-                caps.add(CAPABILITY_THERMAL_MITIGATION_DATA_THROTTLING);
-                Rlog.d(TAG, "CAPABILITY_THERMAL_MITIGATION_DATA_THROTTLING");
-                caps.add(CAPABILITY_SIM_PHONEBOOK_IN_MODEM);
-                Rlog.d(TAG, "CAPABILITY_SIM_PHONEBOOK_IN_MODEM");
-                caps.add(CAPABILITY_PHYSICAL_CHANNEL_CONFIG_1_6_SUPPORTED);
-                Rlog.d(TAG, "CAPABILITY_PHYSICAL_CHANNEL_CONFIG_1_6_SUPPORTED");
-            }
-        }
-        return caps;
+        return RILUtils.getCaps(mHalVersion, false);
     }
 }
diff --git a/src/java/com/android/internal/telephony/RadioConfigResponseAidl.java b/src/java/com/android/internal/telephony/RadioConfigResponseAidl.java
new file mode 100644
index 0000000..26975d8
--- /dev/null
+++ b/src/java/com/android/internal/telephony/RadioConfigResponseAidl.java
@@ -0,0 +1,217 @@
+/*
+ * 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;
+
+import android.os.RemoteException;
+import android.telephony.PhoneCapability;
+
+import com.android.internal.telephony.uicc.IccSlotStatus;
+import com.android.telephony.Rlog;
+
+import java.util.ArrayList;
+import java.util.Set;
+
+/**
+ * This class is the AIDL implementation of IRadioConfigResponse interface.
+ */
+public class RadioConfigResponseAidl extends
+        android.hardware.radio.config.IRadioConfigResponse.Stub {
+    private static final String TAG = "RadioConfigResponseAidl";
+
+    private final RadioConfig mRadioConfig;
+    private final HalVersion mHalVersion;
+
+    public RadioConfigResponseAidl(RadioConfig radioConfig, HalVersion halVersion) {
+        mRadioConfig = radioConfig;
+        mHalVersion = halVersion;
+    }
+
+    /**
+     * Response function IRadioConfig.getHalDeviceCapabilities()
+     */
+    @Override
+    public void getHalDeviceCapabilitiesResponse(
+            android.hardware.radio.RadioResponseInfo info,
+            boolean modemReducedFeatureSet1) throws RemoteException {
+        // convert hal device capabilities to RadioInterfaceCapabilities
+        RILRequest rr = mRadioConfig.processResponse(info);
+        if (rr != null) {
+            final Set<String> ret = RILUtils.getCaps(mHalVersion, modemReducedFeatureSet1);
+            if (info.error == android.hardware.radio.RadioError.NONE) {
+                // send response
+                RadioResponse.sendMessageResponse(rr.mResult, ret);
+                logd(rr, RILUtils.requestToString(rr.mRequest));
+            } else {
+                rr.onError(info.error, ret);
+                loge(rr, RILUtils.requestToString(rr.mRequest) + " error " + info.error);
+            }
+        } else {
+            loge("getHalDeviceCapabilities: Error " + info.toString());
+        }
+    }
+
+    /**
+     * Response function for IRadioConfig.getNumOfLiveModemsResponse()
+     */
+    @Override
+    public void getNumOfLiveModemsResponse(
+            android.hardware.radio.RadioResponseInfo info, byte numOfLiveModems)
+            throws RemoteException {
+        RILRequest rr = mRadioConfig.processResponse(info);
+        if (rr != null) {
+            if (info.error == android.hardware.radio.RadioError.NONE) {
+                // send response
+                RadioResponse.sendMessageResponse(rr.mResult, numOfLiveModems);
+                logd(rr, RILUtils.requestToString(rr.mRequest));
+            } else {
+                rr.onError(info.error, numOfLiveModems);
+                loge(rr, RILUtils.requestToString(rr.mRequest) + " error " + info.error);
+            }
+        } else {
+            loge("getNumOfLiveModemsResponse: Error " + info.toString());
+        }
+    }
+
+    /**
+     * Response function for IRadioConfig.getPhoneCapability().
+     */
+    @Override
+    public void getPhoneCapabilityResponse(
+            android.hardware.radio.RadioResponseInfo info,
+            android.hardware.radio.config.PhoneCapability phoneCapability)
+            throws RemoteException {
+        RILRequest rr = mRadioConfig.processResponse(info);
+        if (rr != null) {
+            PhoneCapability ret = RILUtils.convertHalPhoneCapability(
+                    mRadioConfig.getDeviceNrCapabilities(), phoneCapability);
+            if (info.error == android.hardware.radio.RadioError.NONE) {
+                // send response
+                RadioResponse.sendMessageResponse(rr.mResult, ret);
+                logd(rr, RILUtils.requestToString(rr.mRequest) + " " + ret.toString());
+            } else {
+                rr.onError(info.error, ret);
+                loge(rr, RILUtils.requestToString(rr.mRequest) + " error " + info.error);
+            }
+        } else {
+            loge("getPhoneCapabilityResponse: Error " + info.toString());
+        }
+    }
+
+    /**
+     * Response function for IRadioConfig.getSimSlotsStatus().
+     */
+    @Override
+    public void getSimSlotsStatusResponse(
+            android.hardware.radio.RadioResponseInfo info,
+            android.hardware.radio.config.SimSlotStatus[] slotStatus)
+            throws RemoteException {
+        RILRequest rr = mRadioConfig.processResponse(info);
+        if (rr != null) {
+            ArrayList<IccSlotStatus> ret = RILUtils.convertHalSlotStatus(slotStatus);
+            if (info.error == android.hardware.radio.RadioError.NONE) {
+                // send response
+                RadioResponse.sendMessageResponse(rr.mResult, ret);
+                logd(rr, RILUtils.requestToString(rr.mRequest) + " " + ret.toString());
+            } else {
+                rr.onError(info.error, ret);
+                loge(rr, RILUtils.requestToString(rr.mRequest) + " error " + info.error);
+            }
+        } else {
+            loge("getSimSlotsStatusResponse: Error " + info.toString());
+        }
+    }
+
+    /**
+     * Response function for IRadioConfig.setNumOfLiveModemsResponse()
+     * Currently this is being used as the callback for RadioConfig.setNumOfLiveModems() method
+     */
+    @Override
+    public void setNumOfLiveModemsResponse(
+            android.hardware.radio.RadioResponseInfo info) throws RemoteException {
+        RILRequest rr = mRadioConfig.processResponse(info);
+        if (rr != null) {
+            if (info.error == android.hardware.radio.RadioError.NONE) {
+                // send response
+                RadioResponse.sendMessageResponse(rr.mResult, rr.mRequest);
+                logd(rr, RILUtils.requestToString(rr.mRequest));
+            } else {
+                rr.onError(info.error, null);
+                loge(rr, RILUtils.requestToString(rr.mRequest) + " error " + info.error);
+            }
+        } else {
+            loge("setModemsConfigResponse: Error " + info.toString());
+        }
+    }
+
+    /**
+     * Response function for IRadioConfig.setPreferredDataModem().
+     */
+    @Override
+    public void setPreferredDataModemResponse(
+            android.hardware.radio.RadioResponseInfo info) throws RemoteException {
+        RILRequest rr = mRadioConfig.processResponse(info);
+        if (rr != null) {
+            if (info.error == android.hardware.radio.RadioError.NONE) {
+                // send response
+                RadioResponse.sendMessageResponse(rr.mResult, null);
+                logd(rr, RILUtils.requestToString(rr.mRequest));
+            } else {
+                rr.onError(info.error, null);
+                loge(rr, RILUtils.requestToString(rr.mRequest) + " error " + info.error);
+            }
+        } else {
+            loge("setPreferredDataModemResponse: Error " + info.toString());
+        }
+    }
+
+    /**
+     * Response function for IRadioConfig.setSimSlotsMapping().
+     */
+    @Override
+    public void setSimSlotsMappingResponse(
+            android.hardware.radio.RadioResponseInfo info) throws RemoteException {
+        RILRequest rr = mRadioConfig.processResponse(info);
+        if (rr != null) {
+            if (info.error == android.hardware.radio.RadioError.NONE) {
+                // send response
+                RadioResponse.sendMessageResponse(rr.mResult, null);
+                logd(rr, RILUtils.requestToString(rr.mRequest));
+            } else {
+                rr.onError(info.error, null);
+                loge(rr, RILUtils.requestToString(rr.mRequest) + " error " + info.error);
+            }
+        } else {
+            loge("setSimSlotsMappingResponse: Error " + info.toString());
+        }
+    }
+
+    private static void logd(String log) {
+        Rlog.d(TAG, log);
+    }
+
+    private static void loge(String log) {
+        Rlog.e(TAG, log);
+    }
+
+    private static void logd(RILRequest rr, String log) {
+        logd(rr.serialString() + "< " + log);
+    }
+
+    private static void loge(RILRequest rr, String log) {
+        loge(rr.serialString() + "< " + log);
+    }
+}
diff --git a/src/java/com/android/internal/telephony/RadioConfigResponseHidl.java b/src/java/com/android/internal/telephony/RadioConfigResponseHidl.java
new file mode 100644
index 0000000..a6ae258
--- /dev/null
+++ b/src/java/com/android/internal/telephony/RadioConfigResponseHidl.java
@@ -0,0 +1,244 @@
+/*
+ * 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;
+
+import android.os.RemoteException;
+import android.telephony.PhoneCapability;
+
+import com.android.internal.telephony.uicc.IccSlotStatus;
+import com.android.telephony.Rlog;
+
+import java.util.ArrayList;
+import java.util.Set;
+
+/**
+ * This class is the AIDL implementation of IRadioConfigResponse interface.
+ */
+public class RadioConfigResponseHidl extends
+        android.hardware.radio.config.V1_3.IRadioConfigResponse.Stub {
+    private static final String TAG = "RadioConfigResponse";
+
+    private final RadioConfig mRadioConfig;
+    private final HalVersion mHalVersion;
+
+    public RadioConfigResponseHidl(RadioConfig radioConfig, HalVersion halVersion) {
+        mRadioConfig = radioConfig;
+        mHalVersion = halVersion;
+    }
+
+    /**
+     * Response function for IRadioConfig.getSimSlotsStatus().
+     */
+    @Override
+    public void getSimSlotsStatusResponse(
+            android.hardware.radio.V1_0.RadioResponseInfo info,
+            ArrayList<android.hardware.radio.config.V1_0.SimSlotStatus> slotStatus)
+            throws RemoteException {
+        RILRequest rr = mRadioConfig.processResponse(info);
+
+        if (rr != null) {
+            ArrayList<IccSlotStatus> ret = RILUtils.convertHalSlotStatus(slotStatus);
+            if (info.error == android.hardware.radio.V1_0.RadioError.NONE) {
+                // send response
+                RadioResponse.sendMessageResponse(rr.mResult, ret);
+                logd(rr, RILUtils.requestToString(rr.mRequest) + " " + ret.toString());
+            } else {
+                rr.onError(info.error, ret);
+                loge(rr, RILUtils.requestToString(rr.mRequest) + " error " + info.error);
+            }
+        } else {
+            loge("getSimSlotsStatusResponse: Error " + info.toString());
+        }
+    }
+
+    /**
+     * Response function for IRadioConfig.setSimSlotsMapping().
+     */
+    @Override
+    public void setSimSlotsMappingResponse(android.hardware.radio.V1_0.RadioResponseInfo info)
+            throws RemoteException {
+        RILRequest rr = mRadioConfig.processResponse(info);
+        if (rr != null) {
+            if (info.error == android.hardware.radio.V1_0.RadioError.NONE) {
+                // send response
+                RadioResponse.sendMessageResponse(rr.mResult, null);
+                logd(rr, RILUtils.requestToString(rr.mRequest));
+            } else {
+                rr.onError(info.error, null);
+                loge(rr, RILUtils.requestToString(rr.mRequest) + " error " + info.error);
+            }
+        } else {
+            loge("setSimSlotsMappingResponse: Error " + info.toString());
+        }
+    }
+
+    /**
+     * Response function for IRadioConfig.getPhoneCapability().
+     */
+    @Override
+    public void getPhoneCapabilityResponse(android.hardware.radio.V1_0.RadioResponseInfo info,
+            android.hardware.radio.config.V1_1.PhoneCapability phoneCapability)
+            throws RemoteException {
+        RILRequest rr = mRadioConfig.processResponse(info);
+        if (rr != null) {
+            PhoneCapability ret = RILUtils.convertHalPhoneCapability(
+                    mRadioConfig.getDeviceNrCapabilities(), phoneCapability);
+            if (info.error == android.hardware.radio.V1_0.RadioError.NONE) {
+                // send response
+                RadioResponse.sendMessageResponse(rr.mResult, ret);
+                logd(rr, RILUtils.requestToString(rr.mRequest) + " " + ret.toString());
+            } else {
+                rr.onError(info.error, ret);
+                loge(rr, RILUtils.requestToString(rr.mRequest) + " error " + info.error);
+            }
+        } else {
+            loge("getPhoneCapabilityResponse: Error " + info.toString());
+        }
+    }
+
+    /**
+     * Response function for IRadioConfig.setPreferredDataModem().
+     */
+    @Override
+    public void setPreferredDataModemResponse(
+            android.hardware.radio.V1_0.RadioResponseInfo info) throws RemoteException {
+        RILRequest rr = mRadioConfig.processResponse(info);
+        if (rr != null) {
+            if (info.error == android.hardware.radio.V1_0.RadioError.NONE) {
+                // send response
+                RadioResponse.sendMessageResponse(rr.mResult, null);
+                logd(rr, RILUtils.requestToString(rr.mRequest));
+            } else {
+                rr.onError(info.error, null);
+                loge(rr, RILUtils.requestToString(rr.mRequest) + " error " + info.error);
+            }
+        } else {
+            loge("setPreferredDataModemResponse: Error " + info.toString());
+        }
+    }
+
+    /**
+     * Response function for IRadioConfig.setModemsConfigResponse()
+     * Currently this is being used as the callback for RadioConfig.setModemsConfig() method
+     */
+    @Override
+    public void setModemsConfigResponse(android.hardware.radio.V1_0.RadioResponseInfo info)
+            throws RemoteException {
+        RILRequest rr = mRadioConfig.processResponse(info);
+        if (rr != null) {
+            if (info.error == android.hardware.radio.V1_0.RadioError.NONE) {
+                // send response
+                RadioResponse.sendMessageResponse(rr.mResult, rr.mRequest);
+                logd(rr, RILUtils.requestToString(rr.mRequest));
+            } else {
+                rr.onError(info.error, null);
+                loge(rr, RILUtils.requestToString(rr.mRequest) + " error " + info.error);
+            }
+        } else {
+            loge("setModemsConfigResponse: Error " + info.toString());
+        }
+    }
+
+    /**
+     * Response function for IRadioConfig.getModemsConfigResponse()
+     */
+    @Override
+    public void getModemsConfigResponse(android.hardware.radio.V1_0.RadioResponseInfo info,
+            android.hardware.radio.config.V1_1.ModemsConfig modemsConfig)
+            throws RemoteException {
+        RILRequest rr = mRadioConfig.processResponse(info);
+        if (rr != null) {
+            if (info.error == android.hardware.radio.V1_0.RadioError.NONE) {
+                // send response
+                RadioResponse.sendMessageResponse(rr.mResult, modemsConfig);
+                logd(rr, RILUtils.requestToString(rr.mRequest));
+            } else {
+                rr.onError(info.error, modemsConfig);
+                loge(rr, RILUtils.requestToString(rr.mRequest) + " error " + info.error);
+            }
+        } else {
+            loge("getModemsConfigResponse: Error " + info.toString());
+        }
+    }
+
+    /**
+     * Response function for IRadioConfig.getSimSlotsStatus().
+     */
+    @Override
+    public void getSimSlotsStatusResponse_1_2(
+            android.hardware.radio.V1_0.RadioResponseInfo info,
+            ArrayList<android.hardware.radio.config.V1_2.SimSlotStatus> slotStatus)
+            throws RemoteException {
+        RILRequest rr = mRadioConfig.processResponse(info);
+        if (rr != null) {
+            ArrayList<IccSlotStatus> ret = RILUtils.convertHalSlotStatus(slotStatus);
+            if (info.error == android.hardware.radio.V1_0.RadioError.NONE) {
+                // send response
+                RadioResponse.sendMessageResponse(rr.mResult, ret);
+                logd(rr, RILUtils.requestToString(rr.mRequest) + " " + ret.toString());
+            } else {
+                rr.onError(info.error, ret);
+                loge(rr, RILUtils.requestToString(rr.mRequest) + " error " + info.error);
+            }
+        } else {
+            loge("getSimSlotsStatusResponse_1_2: Error " + info.toString());
+        }
+    }
+
+    /**
+     * Response function IRadioConfig.getHalDeviceCapabilities()
+     */
+    @Override
+    public void getHalDeviceCapabilitiesResponse(
+            android.hardware.radio.V1_6.RadioResponseInfo info, boolean modemReducedFeatureSet1)
+            throws RemoteException {
+        // convert hal device capabilities to RadioInterfaceCapabilities
+        RILRequest rr = mRadioConfig.processResponse_1_6(info);
+        if (rr != null) {
+            // The response is compatible with Radio 1.6, it means the modem
+            // supports setAllowedNetworkTypeBitmap.
+            final Set<String> ret = RILUtils.getCaps(mHalVersion,
+                    modemReducedFeatureSet1);
+            if (info.error == android.hardware.radio.V1_0.RadioError.NONE) {
+                // send response
+                RadioResponse.sendMessageResponse(rr.mResult, ret);
+                logd(rr, RILUtils.requestToString(rr.mRequest));
+            } else {
+                rr.onError(info.error, ret);
+                loge(rr, RILUtils.requestToString(rr.mRequest) + " error " + info.error);
+            }
+        } else {
+            loge("getHalDeviceCapabilities: Error " + info.toString());
+        }
+    }
+
+    private static void logd(String log) {
+        Rlog.d(TAG, log);
+    }
+
+    private static void loge(String log) {
+        Rlog.e(TAG, log);
+    }
+
+    private static void logd(RILRequest rr, String log) {
+        logd(rr.serialString() + "< " + log);
+    }
+
+    private static void loge(RILRequest rr, String log) {
+        loge(rr.serialString() + "< " + log);
+    }
+}
diff --git a/src/java/com/android/internal/telephony/RadioIndication.java b/src/java/com/android/internal/telephony/RadioIndication.java
index 0a3bdd4..ba74d6a 100644
--- a/src/java/com/android/internal/telephony/RadioIndication.java
+++ b/src/java/com/android/internal/telephony/RadioIndication.java
@@ -21,6 +21,7 @@
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_CDMA_CALL_WAITING;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_CDMA_INFO_REC;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_CDMA_OTA_PROVISION_STATUS;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_CDMA_PRL_CHANGED;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_CDMA_RUIM_SMS_STORAGE_FULL;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_CDMA_SUBSCRIPTION_SOURCE_CHANGED;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_CELL_INFO_LIST;
@@ -49,6 +50,8 @@
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_RESPONSE_NEW_SMS_ON_SIM;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_RESPONSE_NEW_SMS_STATUS_REPORT;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_RESPONSE_RADIO_STATE_CHANGED;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_RESPONSE_SIM_PHONEBOOK_CHANGED;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_RESPONSE_SIM_PHONEBOOK_RECORDS_RECEIVED;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_RESPONSE_SIM_STATUS_CHANGED;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_RESTRICTED_STATE_CHANGED;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_RIL_CONNECTED;
@@ -67,9 +70,6 @@
 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 static com.android.internal.telephony.RILConstants.RIL_UNSOl_CDMA_PRL_CHANGED;
-import static com.android.internal.telephony.RILConstants.RIL_UNSOL_RESPONSE_SIM_PHONEBOOK_RECORDS_RECEIVED;
-import static com.android.internal.telephony.RILConstants.RIL_UNSOL_RESPONSE_SIM_PHONEBOOK_CHANGED;
 
 import android.hardware.radio.V1_0.CdmaCallWaiting;
 import android.hardware.radio.V1_0.CdmaInformationRecord;
@@ -86,14 +86,12 @@
 import android.hardware.radio.V1_0.SsInfoData;
 import android.hardware.radio.V1_0.StkCcUnsolSsResult;
 import android.hardware.radio.V1_0.SuppSvcNotification;
-import android.hardware.radio.V1_2.CellConnectionStatus;
 import android.hardware.radio.V1_6.IRadioIndication;
-import android.hardware.radio.V1_6.PhysicalChannelConfig.Band;
 import android.hardware.radio.V1_6.PhonebookRecordInfo;
+import android.hardware.radio.V1_6.PhysicalChannelConfig.Band;
 import android.os.AsyncResult;
 import android.os.RemoteException;
 import android.sysprop.TelephonyProperties;
-import android.telephony.Annotation.RadioPowerState;
 import android.telephony.AnomalyReporter;
 import android.telephony.BarringInfo;
 import android.telephony.CellIdentity;
@@ -105,20 +103,18 @@
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
 import android.telephony.SmsMessage;
-import android.telephony.TelephonyManager;
 import android.telephony.data.DataCallResponse;
 import android.telephony.emergency.EmergencyNumber;
 import android.text.TextUtils;
 
 import com.android.internal.telephony.cdma.CdmaCallWaitingNotification;
 import com.android.internal.telephony.cdma.CdmaInformationRecords;
-import com.android.internal.telephony.cdma.SmsMessageConverter;
 import com.android.internal.telephony.dataconnection.KeepaliveStatus;
 import com.android.internal.telephony.gsm.SsData;
 import com.android.internal.telephony.gsm.SuppServiceNotification;
-import com.android.internal.telephony.uicc.ReceivedPhonebookRecords;
 import com.android.internal.telephony.uicc.IccRefreshResponse;
 import com.android.internal.telephony.uicc.IccUtils;
+import com.android.internal.telephony.uicc.ReceivedPhonebookRecords;
 import com.android.internal.telephony.uicc.SimPhonebookRecord;
 
 import java.util.ArrayList;
@@ -140,7 +136,7 @@
     public void radioStateChanged(int indicationType, int radioState) {
         mRil.processIndication(indicationType);
 
-        int state = getRadioStateFromInt(radioState);
+        int state = RILUtils.convertHalRadioState(radioState);
         if (RIL.RILJ_LOGD) {
             mRil.unsljLogMore(RIL_UNSOL_RESPONSE_RADIO_STATE_CHANGED, "radioStateChanged: " +
                     state);
@@ -172,7 +168,7 @@
     public void newSms(int indicationType, ArrayList<Byte> pdu) {
         mRil.processIndication(indicationType);
 
-        byte[] pduArray = RIL.arrayListToPrimitiveArray(pdu);
+        byte[] pduArray = RILUtils.arrayListToPrimitiveArray(pdu);
         if (RIL.RILJ_LOGD) mRil.unsljLog(RIL_UNSOL_RESPONSE_NEW_SMS);
 
         SmsMessageBase smsb = com.android.internal.telephony.gsm.SmsMessage.createFromPdu(pduArray);
@@ -185,7 +181,7 @@
     public void newSmsStatusReport(int indicationType, ArrayList<Byte> pdu) {
         mRil.processIndication(indicationType);
 
-        byte[] pduArray = RIL.arrayListToPrimitiveArray(pdu);
+        byte[] pduArray = RILUtils.arrayListToPrimitiveArray(pdu);
         if (RIL.RILJ_LOGD) mRil.unsljLog(RIL_UNSOL_RESPONSE_NEW_SMS_STATUS_REPORT);
 
         if (mRil.mSmsStatusRegistrant != null) {
@@ -245,7 +241,7 @@
                                       android.hardware.radio.V1_0.SignalStrength signalStrength) {
         mRil.processIndication(indicationType);
 
-        SignalStrength ssInitial = new SignalStrength(signalStrength);
+        SignalStrength ssInitial = RILUtils.convertHalSignalStrength(signalStrength);
 
         SignalStrength ss = mRil.fixupSignalStrength10(ssInitial);
         // Note this is set to "verbose" because it happens frequently
@@ -263,7 +259,7 @@
                                             android.hardware.radio.V1_2.LinkCapacityEstimate lce) {
         mRil.processIndication(indicationType);
 
-        List<LinkCapacityEstimate> response = RIL.convertHalLceData(lce, mRil);
+        List<LinkCapacityEstimate> response = RILUtils.convertHalLceData(lce);
 
         if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_LCEDATA_RECV, response);
 
@@ -279,7 +275,7 @@
             android.hardware.radio.V1_6.LinkCapacityEstimate lce) {
         mRil.processIndication(indicationType);
 
-        List<LinkCapacityEstimate> response = RIL.convertHalLceData(lce, mRil);
+        List<LinkCapacityEstimate> response = RILUtils.convertHalLceData(lce);
 
         if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_LCEDATA_RECV, response);
 
@@ -295,7 +291,7 @@
                                       android.hardware.radio.V1_2.SignalStrength signalStrength) {
         mRil.processIndication(indicationType);
 
-        SignalStrength ss = new SignalStrength(signalStrength);
+        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);
 
@@ -312,7 +308,7 @@
 
         mRil.processIndication(indicationType);
 
-        SignalStrength ss = new SignalStrength(signalStrength);
+        SignalStrength ss = RILUtils.convertHalSignalStrength(signalStrength);
 
         if (RIL.RILJ_LOGV) mRil.unsljLogvRet(RIL_UNSOL_SIGNAL_STRENGTH, ss);
 
@@ -329,7 +325,7 @@
 
         mRil.processIndication(indicationType);
 
-        SignalStrength ss = new SignalStrength(signalStrength);
+        SignalStrength ss = RILUtils.convertHalSignalStrength(signalStrength);
 
         if (RIL.RILJ_LOGV) mRil.unsljLogvRet(RIL_UNSOL_SIGNAL_STRENGTH, ss);
 
@@ -370,6 +366,7 @@
      */
     public void currentEmergencyNumberList(int indicationType,
             ArrayList<android.hardware.radio.V1_4.EmergencyNumber> emergencyNumberList) {
+        mRil.processIndication(indicationType);
         List<EmergencyNumber> response = new ArrayList<>(emergencyNumberList.size());
 
         for (android.hardware.radio.V1_4.EmergencyNumber emergencyNumberHal
@@ -538,9 +535,7 @@
 
         if (RIL.RILJ_LOGD) mRil.unsljLog(RIL_UNSOL_RESPONSE_CDMA_NEW_SMS);
 
-        // todo: conversion from CdmaSmsMessage to SmsMessage should be contained in this class so
-        // that usage of auto-generated HAL classes is limited to this file
-        SmsMessage sms = SmsMessageConverter.newSmsMessageFromCdmaSmsMessage(msg);
+        SmsMessage sms = new SmsMessage(RILUtils.convertHalCdmaSmsMessage(msg));
         if (mRil.mCdmaSmsRegistrant != null) {
             mRil.mCdmaSmsRegistrant.notifyRegistrant(new AsyncResult(null, sms, null));
         }
@@ -549,7 +544,7 @@
     public void newBroadcastSms(int indicationType, ArrayList<Byte> data) {
         mRil.processIndication(indicationType);
 
-        byte response[] = RIL.arrayListToPrimitiveArray(data);
+        byte[] response = RILUtils.arrayListToPrimitiveArray(data);
         if (RIL.RILJ_LOGD) {
             mRil.unsljLogvRet(RIL_UNSOL_RESPONSE_NEW_BROADCAST_SMS,
                     IccUtils.bytesToHexString(response));
@@ -754,7 +749,7 @@
         int response[] = new int[1];
         response[0] = version;
 
-        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOl_CDMA_PRL_CHANGED, response);
+        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_CDMA_PRL_CHANGED, response);
 
         mRil.mCdmaPrlChangedRegistrants.notifyRegistrants(
                 new AsyncResult (null, response, null));
@@ -795,61 +790,42 @@
 
     /** Get unsolicited message for cellInfoList */
     public void cellInfoList(int indicationType,
-                             ArrayList<android.hardware.radio.V1_0.CellInfo> records) {
+            ArrayList<android.hardware.radio.V1_0.CellInfo> records) {
         mRil.processIndication(indicationType);
-
-        ArrayList<CellInfo> response = RIL.convertHalCellInfoList(records);
-
-        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_CELL_INFO_LIST, response);
-
-        mRil.mRilCellInfoListRegistrants.notifyRegistrants(new AsyncResult(null, response, null));
+        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) {
+            ArrayList<android.hardware.radio.V1_2.CellInfo> records) {
         mRil.processIndication(indicationType);
-
-        ArrayList<CellInfo> response = RIL.convertHalCellInfoList_1_2(records);
-
-        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_CELL_INFO_LIST, response);
-
-        mRil.mRilCellInfoListRegistrants.notifyRegistrants(new AsyncResult(null, response, null));
+        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) {
+            ArrayList<android.hardware.radio.V1_4.CellInfo> records) {
         mRil.processIndication(indicationType);
-
-        ArrayList<CellInfo> response = RIL.convertHalCellInfoList_1_4(records);
-
-        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_CELL_INFO_LIST, response);
-
-        mRil.mRilCellInfoListRegistrants.notifyRegistrants(new AsyncResult(null, response, null));
+        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(indicationType);
-
-        ArrayList<CellInfo> response = RIL.convertHalCellInfoList_1_5(records);
-
-        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_CELL_INFO_LIST, response);
-
-        mRil.mRilCellInfoListRegistrants.notifyRegistrants(new AsyncResult(null, response, null));
+        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(indicationType);
+        responseCellInfoList(records);
+    }
 
-        ArrayList<CellInfo> response = RIL.convertHalCellInfoList_1_6(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);
-
         mRil.mRilCellInfoListRegistrants.notifyRegistrants(new AsyncResult(null, response, null));
     }
 
@@ -933,7 +909,7 @@
             ArrayList<android.hardware.radio.V1_0.HardwareConfig> configs) {
         mRil.processIndication(indicationType);
 
-        ArrayList<HardwareConfig> response = RIL.convertHalHwConfigList(configs, mRil);
+        ArrayList<HardwareConfig> response = RILUtils.convertHalHardwareConfigList(configs);
 
         if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_HARDWARE_CONFIG_CHANGED, response);
 
@@ -945,7 +921,7 @@
                                           android.hardware.radio.V1_0.RadioCapability rc) {
         mRil.processIndication(indicationType);
 
-        RadioCapability response = RIL.convertHalRadioCapability(rc, mRil);
+        RadioCapability response = RILUtils.convertHalRadioCapability(rc, mRil);
 
         if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_RADIO_CAPABILITY, response);
 
@@ -1014,7 +990,7 @@
     public void lceData(int indicationType, LceDataInfo lce) {
         mRil.processIndication(indicationType);
 
-        List<LinkCapacityEstimate> response = RIL.convertHalLceData(lce, mRil);
+        List<LinkCapacityEstimate> response = RILUtils.convertHalLceData(lce);
 
         if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_LCEDATA_RECV, response);
 
@@ -1026,10 +1002,8 @@
     public void pcoData(int indicationType, PcoDataInfo pco) {
         mRil.processIndication(indicationType);
 
-        PcoData response = new PcoData(pco.cid,
-                pco.bearerProto,
-                pco.pcoId,
-                RIL.arrayListToPrimitiveArray(pco.contents));
+        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);
 
@@ -1092,24 +1066,24 @@
     }
 
     /**
-     * Indicates the content of all the used records in the SIM phonebook..
+     * Indicates the content of all the used records in the SIM phonebook.
      * @param indicationType RadioIndicationType
+     * @param status Status of PbReceivedStatus
      * @param records Content of the SIM phonebook records
      */
     public void simPhonebookRecordsReceived(int indicationType, byte status,
             ArrayList<PhonebookRecordInfo> records) {
         mRil.processIndication(indicationType);
 
-        List<SimPhonebookRecord> simPhonebookRecords = new ArrayList<SimPhonebookRecord>();
+        List<SimPhonebookRecord> simPhonebookRecords = new ArrayList<>();
 
         for (PhonebookRecordInfo record : records) {
-            simPhonebookRecords.add(new SimPhonebookRecord(record));
+            simPhonebookRecords.add(RILUtils.convertHalPhonebookRecordInfo(record));
         }
 
         if (RIL.RILJ_LOGD) {
             mRil.unsljLogRet(RIL_UNSOL_RESPONSE_SIM_PHONEBOOK_RECORDS_RECEIVED,
-                    "status = " + status +
-                    " received " + records.size() + " records");
+                    "status = " + status + " received " + records.size() + " records");
         }
 
         mRil.mSimPhonebookRecordsReceivedRegistrants.notifyRegistrants(
@@ -1132,7 +1106,7 @@
             @NetworkRegistrationInfo.Domain int domain,
             int causeCode, int additionalCauseCode) {
         mRil.processIndication(indicationType);
-        CellIdentity ci = CellIdentity.create(cellIdentity);
+        CellIdentity ci = RILUtils.convertHalCellIdentity(cellIdentity);
         if (ci == null
                 || TextUtils.isEmpty(chosenPlmn)
                 || (domain & NetworkRegistrationInfo.DOMAIN_CS_PS) == 0
@@ -1148,11 +1122,8 @@
         }
 
         mRil.mRegistrationFailedRegistrant.notifyRegistrant(
-                new AsyncResult(
-                        null,
-                        new RegistrationFailedEvent(ci, chosenPlmn, domain,
-                                causeCode, additionalCauseCode),
-                        null));
+                new AsyncResult(null, new RegistrationFailedEvent(ci, chosenPlmn, domain,
+                        causeCode, additionalCauseCode), null));
     }
 
     /**
@@ -1176,38 +1147,14 @@
             return;
         }
 
-        CellIdentity ci = CellIdentity.create(cellIdentity);
-        BarringInfo cbi = BarringInfo.create(cellIdentity, barringInfos);
+        BarringInfo cbi = new BarringInfo(RILUtils.convertHalCellIdentity(cellIdentity),
+                RILUtils.convertHalBarringInfoList(barringInfos));
 
         mRil.mBarringInfoChangedRegistrants.notifyRegistrants(
                 new AsyncResult(null, cbi, null));
     }
 
     /**
-     * @param stateInt
-     * @return {@link RadioPowerState RadioPowerState}
-     */
-    private @RadioPowerState int getRadioStateFromInt(int stateInt) {
-        int state;
-
-        switch(stateInt) {
-            case android.hardware.radio.V1_0.RadioState.OFF:
-                state = TelephonyManager.RADIO_POWER_OFF;
-                break;
-            case android.hardware.radio.V1_0.RadioState.UNAVAILABLE:
-                state = TelephonyManager.RADIO_POWER_UNAVAILABLE;
-                break;
-            case android.hardware.radio.V1_0.RadioState.ON:
-                state = TelephonyManager.RADIO_POWER_ON;
-                break;
-            default:
-                throw new RuntimeException("Unrecognized RadioState: " + stateInt);
-        }
-        return state;
-    }
-
-
-    /**
      * Set the frequency range or channel number from the physical channel config. Only one of them
      * is valid, we should set the other to the unknown value.
      * @param builder the builder of {@link PhysicalChannelConfig}.
@@ -1228,49 +1175,6 @@
         }
     }
 
-    private int convertConnectionStatusFromCellConnectionStatus(int status) {
-        switch (status) {
-            case CellConnectionStatus.PRIMARY_SERVING:
-                return PhysicalChannelConfig.CONNECTION_PRIMARY_SERVING;
-            case CellConnectionStatus.SECONDARY_SERVING:
-                return PhysicalChannelConfig.CONNECTION_SECONDARY_SERVING;
-            default:
-                // only PRIMARY_SERVING and SECONDARY_SERVING are supported.
-                mRil.riljLoge("Unsupported CellConnectionStatus in PhysicalChannelConfig: "
-                        + status);
-                return PhysicalChannelConfig.CONNECTION_UNKNOWN;
-        }
-    }
-
-    /**
-     * Set the band from the physical channel config.
-     *
-     * @param builder the builder of {@link PhysicalChannelConfig}.
-     * @param config physical channel config from ril.
-     */
-    public void setBandToBuilder(PhysicalChannelConfig.Builder builder,
-            android.hardware.radio.V1_6.PhysicalChannelConfig config) {
-
-        android.hardware.radio.V1_6.PhysicalChannelConfig.Band band = config.band;
-
-        switch (band.getDiscriminator()) {
-            case Band.hidl_discriminator.geranBand:
-                builder.setBand(band.geranBand());
-                break;
-            case Band.hidl_discriminator.utranBand:
-                builder.setBand(band.utranBand());
-                break;
-            case Band.hidl_discriminator.eutranBand:
-                builder.setBand(band.eutranBand());
-                break;
-            case Band.hidl_discriminator.ngranBand:
-                builder.setBand(band.ngranBand());
-                break;
-            default:
-                mRil.riljLoge("Unsupported band type " + band.getDiscriminator());
-        }
-    }
-
     private void physicalChannelConfigsIndication(List<? extends Object> configs) {
         List<PhysicalChannelConfig> response = new ArrayList<>(configs.size());
         try {
@@ -1280,8 +1184,8 @@
                             (android.hardware.radio.V1_2.PhysicalChannelConfig) obj;
 
                     response.add(new PhysicalChannelConfig.Builder()
-                            .setCellConnectionStatus(
-                                    convertConnectionStatusFromCellConnectionStatus(config.status))
+                            .setCellConnectionStatus(RILUtils.convertHalCellConnectionStatus(
+                                    config.status))
                             .setCellBandwidthDownlinkKhz(config.cellBandwidthDownlink)
                             .build());
                 } else if (obj instanceof android.hardware.radio.V1_4.PhysicalChannelConfig) {
@@ -1290,7 +1194,7 @@
                     PhysicalChannelConfig.Builder builder = new PhysicalChannelConfig.Builder();
                     setFrequencyRangeOrChannelNumber(builder, config);
                     response.add(builder.setCellConnectionStatus(
-                            convertConnectionStatusFromCellConnectionStatus(config.base.status))
+                            RILUtils.convertHalCellConnectionStatus(config.base.status))
                             .setCellBandwidthDownlinkKhz(config.base.cellBandwidthDownlink)
                             .setNetworkType(
                                     ServiceState.rilRadioTechnologyToNetworkType(config.rat))
@@ -1301,9 +1205,24 @@
                     android.hardware.radio.V1_6.PhysicalChannelConfig config =
                             (android.hardware.radio.V1_6.PhysicalChannelConfig) obj;
                     PhysicalChannelConfig.Builder builder = new PhysicalChannelConfig.Builder();
-                    setBandToBuilder(builder, config);
+                    switch (config.band.getDiscriminator()) {
+                        case Band.hidl_discriminator.geranBand:
+                            builder.setBand(config.band.geranBand());
+                            break;
+                        case Band.hidl_discriminator.utranBand:
+                            builder.setBand(config.band.utranBand());
+                            break;
+                        case Band.hidl_discriminator.eutranBand:
+                            builder.setBand(config.band.eutranBand());
+                            break;
+                        case Band.hidl_discriminator.ngranBand:
+                            builder.setBand(config.band.ngranBand());
+                            break;
+                        default:
+                            mRil.riljLoge("Unsupported band " + config.band.getDiscriminator());
+                    }
                     response.add(builder.setCellConnectionStatus(
-                            convertConnectionStatusFromCellConnectionStatus(config.status))
+                            RILUtils.convertHalCellConnectionStatus(config.status))
                             .setDownlinkChannelNumber(config.downlinkChannelNumber)
                             .setUplinkChannelNumber(config.uplinkChannelNumber)
                             .setCellBandwidthDownlinkKhz(config.cellBandwidthDownlinkKhz)
@@ -1332,32 +1251,33 @@
     }
 
     private void responseNetworkScan(int indicationType,
-                                     android.hardware.radio.V1_1.NetworkScanResult result) {
+            android.hardware.radio.V1_1.NetworkScanResult result) {
         mRil.processIndication(indicationType);
 
-        NetworkScanResult nsr = null;
-        ArrayList<CellInfo> infos = RIL.convertHalCellInfoList(result.networkInfos);
-        nsr = new NetworkScanResult(result.status, result.error, infos);
+        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);
         mRil.mRilNetworkScanResultRegistrants.notifyRegistrants(new AsyncResult(null, nsr, null));
     }
 
     private void responseNetworkScan_1_2(int indicationType,
-                                         android.hardware.radio.V1_2.NetworkScanResult result) {
+            android.hardware.radio.V1_2.NetworkScanResult result) {
         mRil.processIndication(indicationType);
 
-        NetworkScanResult nsr = null;
-        ArrayList<CellInfo> infos = RIL.convertHalCellInfoList_1_2(result.networkInfos);
-        nsr = new NetworkScanResult(result.status, result.error, infos);
+        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);
         mRil.mRilNetworkScanResultRegistrants.notifyRegistrants(new AsyncResult(null, nsr, null));
     }
 
     private void responseNetworkScan_1_4(int indicationType,
-                                         android.hardware.radio.V1_4.NetworkScanResult result) {
+            android.hardware.radio.V1_4.NetworkScanResult result) {
         mRil.processIndication(indicationType);
 
-        ArrayList<CellInfo> cellInfos = RIL.convertHalCellInfoList_1_4(result.networkInfos);
+        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);
         mRil.mRilNetworkScanResultRegistrants.notifyRegistrants(new AsyncResult(null, nsr, null));
@@ -1367,7 +1287,8 @@
             android.hardware.radio.V1_5.NetworkScanResult result) {
         mRil.processIndication(indicationType);
 
-        ArrayList<CellInfo> cellInfos = RIL.convertHalCellInfoList_1_5(result.networkInfos);
+        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);
         mRil.mRilNetworkScanResultRegistrants.notifyRegistrants(new AsyncResult(null, nsr, null));
@@ -1377,7 +1298,8 @@
             android.hardware.radio.V1_6.NetworkScanResult result) {
         mRil.processIndication(indicationType);
 
-        ArrayList<CellInfo> cellInfos = RIL.convertHalCellInfoList_1_6(result.networkInfos);
+        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);
         mRil.mRilNetworkScanResultRegistrants.notifyRegistrants(new AsyncResult(null, nsr, null));
@@ -1388,7 +1310,7 @@
 
         if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_DATA_CALL_LIST_CHANGED, dcList);
 
-        ArrayList<DataCallResponse> response = RIL.convertDataCallResultList(dcList);
+        ArrayList<DataCallResponse> response = RILUtils.convertHalDataCallResultList(dcList);
         mRil.mDataCallListChangedRegistrants.notifyRegistrants(
                 new AsyncResult(null, response, null));
     }
diff --git a/src/java/com/android/internal/telephony/RadioResponse.java b/src/java/com/android/internal/telephony/RadioResponse.java
index c81dd3a..4e7f589 100644
--- a/src/java/com/android/internal/telephony/RadioResponse.java
+++ b/src/java/com/android/internal/telephony/RadioResponse.java
@@ -18,9 +18,7 @@
 
 import android.content.Context;
 import android.hardware.radio.V1_0.ActivityStatsInfo;
-import android.hardware.radio.V1_0.AppStatus;
 import android.hardware.radio.V1_0.CardStatus;
-import android.hardware.radio.V1_0.Carrier;
 import android.hardware.radio.V1_0.CarrierRestrictions;
 import android.hardware.radio.V1_0.CdmaBroadcastSmsConfigInfo;
 import android.hardware.radio.V1_0.DataRegStateResult;
@@ -41,8 +39,6 @@
 import android.os.AsyncResult;
 import android.os.Message;
 import android.os.SystemClock;
-import android.service.carrier.CarrierIdentifier;
-import android.telephony.AccessNetworkConstants;
 import android.telephony.AnomalyReporter;
 import android.telephony.BarringInfo;
 import android.telephony.CarrierRestrictionRules;
@@ -51,23 +47,20 @@
 import android.telephony.ModemActivityInfo;
 import android.telephony.NeighboringCellInfo;
 import android.telephony.NetworkScanRequest;
-import android.telephony.PhoneNumberUtils;
 import android.telephony.RadioAccessFamily;
 import android.telephony.RadioAccessSpecifier;
 import android.telephony.SignalStrength;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.telephony.data.DataCallResponse;
-import android.telephony.data.SlicingConfig;
+import android.telephony.data.NetworkSlicingConfig;
 import android.text.TextUtils;
 
 import com.android.internal.telephony.dataconnection.KeepaliveStatus;
 import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
 import com.android.internal.telephony.uicc.AdnCapacity;
-import com.android.internal.telephony.uicc.IccCardApplicationStatus;
 import com.android.internal.telephony.uicc.IccCardStatus;
 import com.android.internal.telephony.uicc.IccIoResult;
-import com.android.internal.telephony.uicc.IccUtils;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -75,19 +68,6 @@
 import java.util.UUID;
 
 public class RadioResponse extends IRadioResponse.Stub {
-    // The number of the required config values for broadcast SMS stored in the C struct
-    // RIL_CDMA_BroadcastServiceInfo
-    private static final int CDMA_BSI_NO_OF_INTS_STRUCT = 3;
-
-    private static final int CDMA_BROADCAST_SMS_NO_OF_SERVICE_CATEGORIES = 31;
-
-    private static final String RADIO_POWER_FAILURE_BUGREPORT_UUID =
-            "316f3801-fa21-4954-a42f-0041eada3b31";
-    private static final String RADIO_POWER_FAILURE_RF_HARDWARE_ISSUE_UUID =
-            "316f3801-fa21-4954-a42f-0041eada3b32";
-    private static final String RADIO_POWER_FAILURE_NO_RF_CALIBRATION_UUID =
-            "316f3801-fa21-4954-a42f-0041eada3b33";
-
     RIL mRil;
 
     public RadioResponse(RIL ril) {
@@ -540,9 +520,7 @@
      * @param numeric is 5 or 6 digit numeric code (MCC + MNC) or empty string if unregistered
      */
     public void getOperatorResponse(RadioResponseInfo responseInfo,
-            String longName,
-            String shortName,
-            String numeric) {
+            String longName, String shortName, String numeric) {
         responseStrings(responseInfo, longName, shortName, numeric);
     }
 
@@ -565,8 +543,7 @@
      * @param responseInfo Response info struct containing response type, serial no. and error
      * @param sms Response to sms sent as defined by SendSmsResult in types.hal
      */
-    public void sendSmsResponse(RadioResponseInfo responseInfo,
-            SendSmsResult sms) {
+    public void sendSmsResponse(RadioResponseInfo responseInfo, SendSmsResult sms) {
         responseSms(responseInfo, sms);
     }
 
@@ -595,8 +572,7 @@
      * @param sms Response to sms sent as defined by SendSmsResult in 1.6/types.hal
      */
     public void sendSmsExpectMoreResponse_1_6(
-            android.hardware.radio.V1_6.RadioResponseInfo responseInfo,
-            SendSmsResult sms) {
+            android.hardware.radio.V1_6.RadioResponseInfo responseInfo, SendSmsResult sms) {
         responseSms_1_6(responseInfo, sms);
     }
 
@@ -661,9 +637,8 @@
 
     @Override
     public void getAllowedNetworkTypesBitmapResponse(
-            android.hardware.radio.V1_6.RadioResponseInfo info,
-            int halRadioAccessFamilyBitmap) {
-        int networkTypeBitmask = RIL.convertToNetworkTypeBitMask(halRadioAccessFamilyBitmap);
+            android.hardware.radio.V1_6.RadioResponseInfo info, int halRadioAccessFamilyBitmap) {
+        int networkTypeBitmask = RILUtils.convertHalNetworkTypeBitMask(halRadioAccessFamilyBitmap);
         mRil.mAllowedNetworkTypesBitmask = networkTypeBitmask;
         responseInts_1_6(info, networkTypeBitmask);
     }
@@ -713,8 +688,7 @@
      *        each distinct registered phone number.
      */
     public void getCallForwardStatusResponse(RadioResponseInfo responseInfo,
-            ArrayList<android.hardware.radio.V1_0.CallForwardInfo>
-                    callForwardInfos) {
+            ArrayList<android.hardware.radio.V1_0.CallForwardInfo> callForwardInfos) {
         responseCallForwardInfo(responseInfo, callForwardInfos);
     }
 
@@ -736,8 +710,7 @@
      *        and voice and disabled for everything else.
      */
     public void getCallWaitingResponse(RadioResponseInfo responseInfo,
-            boolean enable,
-            int serviceClass) {
+            boolean enable, int serviceClass) {
         responseInts(responseInfo, enable ? 1 : 0, serviceClass);
     }
 
@@ -770,7 +743,6 @@
     }
 
     /**
-     *
      * @param responseInfo Response info struct containing response type, serial no. and error
      * @param response 0 is the TS 27.007 service class bit vector of
      *        services for which the specified barring facility
@@ -781,7 +753,6 @@
     }
 
     /**
-     *
      * @param responseInfo Response info struct containing response type, serial no. and error
      * @param retry 0 is the number of retries remaining, or -1 if unknown
      */
@@ -797,7 +768,6 @@
     }
 
     /**
-     *
      * @param responseInfo Response info struct containing response type, serial no. and error
      * @param selection false for automatic selection, true for manual selection
      */
@@ -827,14 +797,12 @@
     }
 
     /**
-     *
      * @param responseInfo Response info struct containing response type, serial no. and error
      * @param networkInfos List of network operator information as OperatorInfos defined in
      *                     types.hal
      */
     public void getAvailableNetworksResponse(RadioResponseInfo responseInfo,
-            ArrayList<android.hardware.radio.V1_0.OperatorInfo>
-                    networkInfos) {
+            ArrayList<android.hardware.radio.V1_0.OperatorInfo> networkInfos) {
         responseOperatorInfos(responseInfo, networkInfos);
     }
 
@@ -866,7 +834,6 @@
     }
 
     /**
-     *
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void stopNetworkScanResponse(RadioResponseInfo responseInfo) {
@@ -888,7 +855,6 @@
     }
 
     /**
-     *
      * @param responseInfo Response info struct containing response type, serial no. and error
      * @param version string containing version string for log reporting
      */
@@ -911,7 +877,6 @@
     }
 
     /**
-     *
      * @param responseInfo Response info struct containing response type, serial no. and error
      * @param enable true for "mute enabled" and false for "mute disabled"
      */
@@ -920,7 +885,6 @@
     }
 
     /**
-     *
      * @param responseInfo Response info struct containing response type, serial no. and error
      * @param status indicates CLIP status
      */
@@ -991,7 +955,6 @@
     }
 
     /**
-     *
      * @param responseInfo Response info struct containing response type, serial no. and error
      * @param bandModes List of RadioBandMode listing supported modes
      */
@@ -1001,7 +964,6 @@
     }
 
     /**
-     *
      * @param responseInfo Response info struct containing response type, serial no. and error
      * @param commandResponse SAT/USAT response in hexadecimal format
      *        string starting with first byte of response
@@ -1047,7 +1009,6 @@
     }
 
     /**
-     *
      * @param responseInfo Response info struct containing response type, serial no. and error
      * @param nwType RadioPreferredNetworkType defined in types.hal
      */
@@ -1065,13 +1026,12 @@
     public void getPreferredNetworkTypeBitmapResponse(
             RadioResponseInfo responseInfo, int halRadioAccessFamilyBitmap) {
 
-        int networkTypeBitmask = RIL.convertToNetworkTypeBitMask(halRadioAccessFamilyBitmap);
+        int networkTypeBitmask = RILUtils.convertHalNetworkTypeBitMask(halRadioAccessFamilyBitmap);
         mRil.mAllowedNetworkTypesBitmask = networkTypeBitmask;
         responseInts(responseInfo, networkTypeBitmask);
     }
 
     /**
-     *
      * @param responseInfo Response info struct containing response type, serial no. and error
      * @param cells Vector of neighboring radio cell information
      */
@@ -1102,7 +1062,6 @@
     }
 
     /**
-     *
      * @param responseInfo Response info struct containing response type, serial no. and error
      * @param type CdmaRoamingType defined in types.hal
      */
@@ -1118,7 +1077,6 @@
     }
 
     /**
-     *
      * @param responseInfo Response info struct containing response type, serial no. and error
      * @param mode TTY mode
      */
@@ -1134,13 +1092,11 @@
     }
 
     /**
-     *
      * @param responseInfo Response info struct containing response type, serial no. and error
      * @param enable false for Standard Privacy Mode (Public Long Code Mask)
      *        true for Enhanced Privacy Mode (Private Long Code Mask)
      */
-    public void getPreferredVoicePrivacyResponse(RadioResponseInfo responseInfo,
-            boolean enable) {
+    public void getPreferredVoicePrivacyResponse(RadioResponseInfo responseInfo, boolean enable) {
         responseInts(responseInfo, enable ? 1 : 0);
     }
 
@@ -1159,7 +1115,6 @@
     }
 
     /**
-     *
      * @param responseInfo Response info struct containing response type, serial no. and error
      * @param sms Sms result struct as defined by SendSmsResult in types.hal
      */
@@ -1168,7 +1123,6 @@
     }
 
     /**
-     *
      * @param responseInfo Response info struct containing response type, serial no. and error which
      *                     is defined in 1.6/types.hal
      * @param sms Sms result struct as defined by SendSmsResult in types.hal
@@ -1188,7 +1142,6 @@
     }
 
     /**
-     *
      * @param responseInfo Response info struct containing response type, serial no. and error which
      *                     is defined in 1.6/types.hal
      * @param sms Sms result struct as defined by SendSmsResult in types.hal
@@ -1214,7 +1167,6 @@
     }
 
     /**
-     *
      * @param responseInfo Response info struct containing response type, serial no. and error
      * @param configs Vector of GSM/WCDMA Cell broadcast configs
      */
@@ -1238,7 +1190,6 @@
     }
 
     /**
-     *
      * @param responseInfo Response info struct containing response type, serial no. and error
      * @param configs Vector of CDMA Broadcast SMS configs.
      */
@@ -1262,7 +1213,6 @@
     }
 
     /**
-     *
      * @param responseInfo Response info struct containing response type, serial no. and error
      * @param mdn MDN if CDMA subscription is available
      * @param hSid is a comma separated list of H_SID (Home SID) if
@@ -1278,7 +1228,6 @@
     }
 
     /**
-     *
      * @param responseInfo Response info struct containing response type, serial no. and error
      * @param index record index where the cmda sms message is stored
      */
@@ -1342,7 +1291,6 @@
     }
 
     /**
-     *
      * @param responseInfo Response info struct containing response type, serial no. and error
      * @param source CDMA subscription source
      */
@@ -1368,7 +1316,6 @@
     }
 
     /**
-     *
      * @param responseInfo Response info struct containing response type, serial no. and error
      * @param iccIo IccIoResult as defined in types.hal corresponding to ICC IO response
      */
@@ -1378,7 +1325,6 @@
     }
 
     /**
-     *
      * @param responseInfo Response info struct containing response type, serial no. and error
      * @param rat Current voice RAT
      */
@@ -1395,30 +1341,27 @@
      * @param responseInfo Response info struct containing response type, serial no. and error
      * @param cellInfo List of current cell information known to radio
      */
-    public void getCellInfoListResponse_1_2(
-            RadioResponseInfo responseInfo,
+    public void getCellInfoListResponse_1_2(RadioResponseInfo responseInfo,
             ArrayList<android.hardware.radio.V1_2.CellInfo> cellInfo) {
-        responseCellInfoList_1_2(responseInfo, cellInfo);
+        responseCellInfoList(responseInfo, cellInfo);
     }
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error.
      * @param cellInfo List of current cell information known to radio.
      */
-    public void getCellInfoListResponse_1_4(
-            RadioResponseInfo responseInfo,
+    public void getCellInfoListResponse_1_4(RadioResponseInfo responseInfo,
             ArrayList<android.hardware.radio.V1_4.CellInfo> cellInfo) {
-        responseCellInfoList_1_4(responseInfo, cellInfo);
+        responseCellInfoList(responseInfo, cellInfo);
     }
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error.
      * @param cellInfo List of current cell information known to radio.
      */
-    public void getCellInfoListResponse_1_5(
-            RadioResponseInfo responseInfo,
+    public void getCellInfoListResponse_1_5(RadioResponseInfo responseInfo,
             ArrayList<android.hardware.radio.V1_5.CellInfo> cellInfo) {
-        responseCellInfoList_1_5(responseInfo, cellInfo);
+        responseCellInfoList(responseInfo, cellInfo);
     }
 
     /**
@@ -1453,7 +1396,6 @@
     }
 
     /**
-     *
      * @param responseInfo Response info struct containing response type, serial no. and error
      * @param isRegistered false = not registered, true = registered
      * @param ratFamily RadioTechnologyFamily as defined in types.hal. This value is valid only if
@@ -1461,16 +1403,12 @@
      */
     public void getImsRegistrationStateResponse(RadioResponseInfo responseInfo,
             boolean isRegistered, int ratFamily) {
-        responseInts(
-                responseInfo,
-                isRegistered ? 1 : 0,
-                ratFamily == RadioTechnologyFamily.THREE_GPP
-                        ? PhoneConstants.PHONE_TYPE_GSM
+        responseInts(responseInfo, isRegistered ? 1 : 0,
+                ratFamily == RadioTechnologyFamily.THREE_GPP ? PhoneConstants.PHONE_TYPE_GSM
                         : PhoneConstants.PHONE_TYPE_CDMA);
     }
 
     /**
-     *
      * @param responseInfo Response info struct containing response type, serial no. and error
      * @param sms Response to sms sent as defined by SendSmsResult in types.hal
      */
@@ -1479,18 +1417,15 @@
     }
 
     /**
-     *
      * @param responseInfo Response info struct containing response type, serial no. and error
      * @param result IccIoResult as defined in types.hal
      */
     public void iccTransmitApduBasicChannelResponse(RadioResponseInfo responseInfo,
-            android.hardware.radio.V1_0.IccIoResult
-                    result) {
+            android.hardware.radio.V1_0.IccIoResult result) {
         responseIccIo(responseInfo, result);
     }
 
     /**
-     *
      * @param responseInfo Response info struct containing response type, serial no. and error
      * @param channelId session id of the logical channel.
      * @param selectResponse Contains the select response for the open channel command with one
@@ -1514,7 +1449,6 @@
     }
 
     /**
-     *
      * @param responseInfo Response info struct containing response type, serial no. and error
      * @param result IccIoResult as defined in types.hal
      */
@@ -1525,7 +1459,6 @@
     }
 
     /**
-     *
      * @param responseInfo Response info struct containing response type, serial no. and error
      * @param result string containing the contents of the NV item
      */
@@ -1575,21 +1508,16 @@
     }
 
     /**
-     *
      * @param responseInfo Response info struct containing response type, serial no. and error
      * @param result IccIoResult as defined in types.hal
      */
     public void requestIccSimAuthenticationResponse(RadioResponseInfo responseInfo,
-            android.hardware.radio.V1_0.IccIoResult
-                    result) {
+            android.hardware.radio.V1_0.IccIoResult result) {
         RILRequest rr = mRil.processResponse(responseInfo);
 
         if (rr != null) {
-            IccIoResult ret = new IccIoResult(
-                    result.sw1,
-                    result.sw2,
-                    TextUtils.isEmpty(result.simResponse)
-                            ? null : result.simResponse.getBytes());
+            IccIoResult ret = new IccIoResult(result.sw1, result.sw2,
+                    TextUtils.isEmpty(result.simResponse) ? null : result.simResponse.getBytes());
             if (responseInfo.error == RadioError.NONE) {
                 sendMessageResponse(rr.mResult, ret);
             }
@@ -1623,7 +1551,7 @@
         RILRequest rr = mRil.processResponse(responseInfo);
 
         if (rr != null) {
-            RadioCapability ret = RIL.convertHalRadioCapability(rc, mRil);
+            RadioCapability ret = RILUtils.convertHalRadioCapability(rc, mRil);
             if (responseInfo.error == RadioError.REQUEST_NOT_SUPPORTED
                     || responseInfo.error == RadioError.GENERIC_FAILURE) {
                 // we should construct the RAF bitmask the radio
@@ -1644,7 +1572,6 @@
     }
 
     /**
-     *
      * @param responseInfo Response info struct containing response type, serial no. and error
      * @param statusInfo LceStatusInfo indicating LCE status
      */
@@ -1653,7 +1580,6 @@
     }
 
     /**
-     *
      * @param responseInfo Response info struct containing response type, serial no. and error
      * @param statusInfo LceStatusInfo indicating LCE status
      */
@@ -1666,7 +1592,6 @@
     }
 
     /**
-     *
      * @param responseInfo Response info struct containing response type, serial no. and error
      * @param activityInfo modem activity information
      */
@@ -1676,7 +1601,6 @@
     }
 
     /**
-     *
      * @param responseInfo Response info struct containing response type, serial no. and error
      * @param isEnabled Indicates whether NR dual connectivity is enabled or not, True if enabled
      *               else false.
@@ -1695,7 +1619,6 @@
     }
 
     /**
-     *
      * @param info Response info struct containing response type, serial no. and error
      */
     public void setNrDualConnectivityStateResponse(
@@ -1704,7 +1627,6 @@
     }
 
     /**
-     *
      * @param responseInfo Response info struct containing response type, serial no. and error
      * @param numAllowed number of allowed carriers which have been set correctly.
      *        On success, it must match the length of list Carriers->allowedCarriers.
@@ -1734,7 +1656,6 @@
     }
 
     /**
-     *
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void setAllowedCarriersResponse_1_4(RadioResponseInfo responseInfo) {
@@ -1851,7 +1772,6 @@
         responseVoid(responseInfo);
     }
 
-
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      * @param keepaliveStatus status of the keepalive with a handle for the session
@@ -1866,7 +1786,8 @@
         try {
             switch(responseInfo.error) {
                 case RadioError.NONE:
-                    int convertedStatus = convertHalKeepaliveStatusCode(keepaliveStatus.code);
+                    int convertedStatus = RILUtils.convertHalKeepaliveStatusCode(
+                            keepaliveStatus.code);
                     if (convertedStatus < 0) {
                         ret = new KeepaliveStatus(KeepaliveStatus.ERROR_UNSUPPORTED);
                     } else {
@@ -1910,96 +1831,6 @@
         }
     }
 
-    private int convertHalKeepaliveStatusCode(int halCode) {
-        switch (halCode) {
-            case android.hardware.radio.V1_1.KeepaliveStatusCode.ACTIVE:
-                return KeepaliveStatus.STATUS_ACTIVE;
-            case android.hardware.radio.V1_1.KeepaliveStatusCode.INACTIVE:
-                return KeepaliveStatus.STATUS_INACTIVE;
-            case android.hardware.radio.V1_1.KeepaliveStatusCode.PENDING:
-                return KeepaliveStatus.STATUS_PENDING;
-            default:
-                mRil.riljLog("Invalid Keepalive Status" + halCode);
-                return -1;
-        }
-    }
-
-    private IccCardStatus convertHalCardStatus(CardStatus cardStatus) {
-        IccCardStatus iccCardStatus = new IccCardStatus();
-        iccCardStatus.setCardState(cardStatus.cardState);
-        iccCardStatus.setUniversalPinState(cardStatus.universalPinState);
-        iccCardStatus.mGsmUmtsSubscriptionAppIndex = cardStatus.gsmUmtsSubscriptionAppIndex;
-        iccCardStatus.mCdmaSubscriptionAppIndex = cardStatus.cdmaSubscriptionAppIndex;
-        iccCardStatus.mImsSubscriptionAppIndex = cardStatus.imsSubscriptionAppIndex;
-        int numApplications = cardStatus.applications.size();
-
-        // limit to maximum allowed applications
-        if (numApplications
-                > com.android.internal.telephony.uicc.IccCardStatus.CARD_MAX_APPS) {
-            numApplications =
-                    com.android.internal.telephony.uicc.IccCardStatus.CARD_MAX_APPS;
-        }
-        iccCardStatus.mApplications = new IccCardApplicationStatus[numApplications];
-        for (int i = 0; i < numApplications; i++) {
-            AppStatus rilAppStatus = cardStatus.applications.get(i);
-            IccCardApplicationStatus appStatus = new IccCardApplicationStatus();
-            appStatus.app_type       = appStatus.AppTypeFromRILInt(rilAppStatus.appType);
-            appStatus.app_state      = appStatus.AppStateFromRILInt(rilAppStatus.appState);
-            appStatus.perso_substate = appStatus.PersoSubstateFromRILInt(
-                    rilAppStatus.persoSubstate);
-            appStatus.aid            = rilAppStatus.aidPtr;
-            appStatus.app_label      = rilAppStatus.appLabelPtr;
-            appStatus.pin1_replaced  = rilAppStatus.pin1Replaced;
-            appStatus.pin1           = appStatus.PinStateFromRILInt(rilAppStatus.pin1);
-            appStatus.pin2           = appStatus.PinStateFromRILInt(rilAppStatus.pin2);
-            iccCardStatus.mApplications[i] = appStatus;
-            mRil.riljLog("IccCardApplicationStatus " + i + ":" + appStatus.toString());
-        }
-        return iccCardStatus;
-    }
-
-    private IccCardStatus convertHalCardStatus_1_5(
-            android.hardware.radio.V1_5.CardStatus cardStatus) {
-        IccCardStatus iccCardStatus = new IccCardStatus();
-        iccCardStatus.setCardState(cardStatus.base.base.base.cardState);
-        iccCardStatus.setUniversalPinState(cardStatus.base.base.base.universalPinState);
-        iccCardStatus.mGsmUmtsSubscriptionAppIndex =
-                cardStatus.base.base.base.gsmUmtsSubscriptionAppIndex;
-        iccCardStatus.mCdmaSubscriptionAppIndex =
-                cardStatus.base.base.base.cdmaSubscriptionAppIndex;
-        iccCardStatus.mImsSubscriptionAppIndex =
-                cardStatus.base.base.base.imsSubscriptionAppIndex;
-        iccCardStatus.physicalSlotIndex = cardStatus.base.base.physicalSlotId;
-        iccCardStatus.atr = cardStatus.base.base.atr;
-        iccCardStatus.iccid = cardStatus.base.base.iccid;
-        iccCardStatus.eid = cardStatus.base.eid;
-        int numApplications = cardStatus.applications.size();
-
-        // limit to maximum allowed applications
-        if (numApplications
-                > com.android.internal.telephony.uicc.IccCardStatus.CARD_MAX_APPS) {
-            numApplications =
-                    com.android.internal.telephony.uicc.IccCardStatus.CARD_MAX_APPS;
-        }
-        iccCardStatus.mApplications = new IccCardApplicationStatus[numApplications];
-        for (int i = 0; i < numApplications; i++) {
-            android.hardware.radio.V1_5.AppStatus rilAppStatus = cardStatus.applications.get(i);
-            IccCardApplicationStatus appStatus = new IccCardApplicationStatus();
-            appStatus.app_type       = appStatus.AppTypeFromRILInt(rilAppStatus.base.appType);
-            appStatus.app_state      = appStatus.AppStateFromRILInt(rilAppStatus.base.appState);
-            appStatus.perso_substate = appStatus.PersoSubstateFromRILInt(
-                    rilAppStatus.persoSubstate);
-            appStatus.aid            = rilAppStatus.base.aidPtr;
-            appStatus.app_label      = rilAppStatus.base.appLabelPtr;
-            appStatus.pin1_replaced  = rilAppStatus.base.pin1Replaced;
-            appStatus.pin1           = appStatus.PinStateFromRILInt(rilAppStatus.base.pin1);
-            appStatus.pin2           = appStatus.PinStateFromRILInt(rilAppStatus.base.pin2);
-            iccCardStatus.mApplications[i] = appStatus;
-            mRil.riljLog("IccCardApplicationStatus " + i + ":" + appStatus.toString());
-        }
-        return iccCardStatus;
-    }
-
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error.
      */
@@ -2015,7 +1846,7 @@
     public void getSimPhonebookCapacityResponse(
             android.hardware.radio.V1_6.RadioResponseInfo responseInfo,
             android.hardware.radio.V1_6.PhonebookCapacity pbCapacity) {
-        AdnCapacity capacity = new AdnCapacity(pbCapacity);
+        AdnCapacity capacity = RILUtils.convertHalPhonebookCapacity(pbCapacity);
         responseAdnCapacity(responseInfo, capacity);
     }
 
@@ -2045,7 +1876,7 @@
         RILRequest rr = mRil.processResponse(responseInfo);
 
         if (rr != null) {
-            IccCardStatus iccCardStatus = convertHalCardStatus(cardStatus);
+            IccCardStatus iccCardStatus = RILUtils.convertHalCardStatus(cardStatus);
             mRil.riljLog("responseIccCardStatus: from HIDL: " + iccCardStatus);
             if (responseInfo.error == RadioError.NONE) {
                 sendMessageResponse(rr.mResult, iccCardStatus);
@@ -2059,7 +1890,7 @@
         RILRequest rr = mRil.processResponse(responseInfo);
 
         if (rr != null) {
-            IccCardStatus iccCardStatus = convertHalCardStatus(cardStatus.base);
+            IccCardStatus iccCardStatus = RILUtils.convertHalCardStatus(cardStatus.base);
             iccCardStatus.physicalSlotIndex = cardStatus.physicalSlotId;
             iccCardStatus.atr = cardStatus.atr;
             iccCardStatus.iccid = cardStatus.iccid;
@@ -2076,7 +1907,7 @@
         RILRequest rr = mRil.processResponse(responseInfo);
 
         if (rr != null) {
-            IccCardStatus iccCardStatus = convertHalCardStatus(cardStatus.base.base);
+            IccCardStatus iccCardStatus = RILUtils.convertHalCardStatus(cardStatus.base.base);
             iccCardStatus.physicalSlotIndex = cardStatus.base.physicalSlotId;
             iccCardStatus.atr = cardStatus.base.atr;
             iccCardStatus.iccid = cardStatus.base.iccid;
@@ -2094,7 +1925,7 @@
         RILRequest rr = mRil.processResponse(responseInfo);
 
         if (rr != null) {
-            IccCardStatus iccCardStatus = convertHalCardStatus_1_5(cardStatus);
+            IccCardStatus iccCardStatus = RILUtils.convertHalCardStatus(cardStatus);
             mRil.riljLog("responseIccCardStatus: from HIDL: " + iccCardStatus);
             if (responseInfo.error == RadioError.NONE) {
                 sendMessageResponse(rr.mResult, iccCardStatus);
@@ -2127,6 +1958,21 @@
         responseIntArrayList_1_6(responseInfo, ints);
     }
 
+    /**
+     * Send int array response
+     * @param ril RIL to send response
+     * @param responseInfo responseInfo
+     * @param var response int array
+     */
+    public static void responseInts(RIL ril, android.hardware.radio.RadioResponseInfo responseInfo,
+            int ...var) {
+        final ArrayList<Integer> ints = new ArrayList<>();
+        for (int i = 0; i < var.length; i++) {
+            ints.add(var[i]);
+        }
+        responseIntArrayList(ril, responseInfo, ints);
+    }
+
     private void responseIntArrayList(RadioResponseInfo responseInfo, ArrayList<Integer> var) {
         RILRequest rr = mRil.processResponse(responseInfo);
 
@@ -2158,6 +2004,28 @@
         }
     }
 
+    /**
+     * Send int array list response
+     * @param ril RIL to send response
+     * @param responseInfo responseInfo
+     * @param var response int array list
+     */
+    public static void responseIntArrayList(RIL ril,
+            android.hardware.radio.RadioResponseInfo responseInfo, ArrayList<Integer> var) {
+        RILRequest rr = ril.processResponse(responseInfo);
+
+        if (rr != null) {
+            int[] ret = new int[var.size()];
+            for (int i = 0; i < var.size(); i++) {
+                ret[i] = var.get(i);
+            }
+            if (responseInfo.error == RadioError.NONE) {
+                sendMessageResponse(rr.mResult, ret);
+            }
+            ril.processResponseDone(rr, responseInfo, ret);
+        }
+    }
+
     private void responseCurrentCalls(RadioResponseInfo responseInfo,
             ArrayList<android.hardware.radio.V1_0.Call> calls) {
         RILRequest rr = mRil.processResponse(responseInfo);
@@ -2168,7 +2036,7 @@
             DriverCall dc;
 
             for (int i = 0; i < num; i++) {
-                dc = convertToDriverCall(calls.get(i));
+                dc = RILUtils.convertToDriverCall(calls.get(i));
 
                 dcCalls.add(dc);
 
@@ -2208,7 +2076,7 @@
             DriverCall dc;
 
             for (int i = 0; i < num; i++) {
-                dc = convertToDriverCall_1_2(calls.get(i));
+                dc = RILUtils.convertToDriverCall(calls.get(i));
 
                 dcCalls.add(dc);
 
@@ -2249,7 +2117,7 @@
             DriverCall dc;
 
             for (int i = 0; i < num; i++) {
-                dc = convertToDriverCall_1_6(calls.get(i));
+                dc = RILUtils.convertToDriverCall(calls.get(i));
 
                 dcCalls.add(dc);
 
@@ -2279,62 +2147,6 @@
         }
     }
 
-    private DriverCall convertToDriverCall(android.hardware.radio.V1_0.Call call) {
-        DriverCall dc = new DriverCall();
-        // TODO: change name of function stateFromCLCC() in DriverCall.java to name
-        // clarifying what is CLCC
-        dc.state = DriverCall.stateFromCLCC((int) (call.state));
-        dc.index = call.index;
-        dc.TOA = call.toa;
-        dc.isMpty = call.isMpty;
-        dc.isMT = call.isMT;
-        dc.als = call.als;
-        dc.isVoice = call.isVoice;
-        dc.isVoicePrivacy = call.isVoicePrivacy;
-        dc.number = call.number;
-        dc.numberPresentation = DriverCall.presentationFromCLIP((int) (call.numberPresentation));
-        dc.name = call.name;
-        dc.namePresentation = DriverCall.presentationFromCLIP((int) (call.namePresentation));
-        if (call.uusInfo.size() == 1) {
-            dc.uusInfo = new UUSInfo();
-            dc.uusInfo.setType(call.uusInfo.get(0).uusType);
-            dc.uusInfo.setDcs(call.uusInfo.get(0).uusDcs);
-            if (!TextUtils.isEmpty(call.uusInfo.get(0).uusData)) {
-                byte[] userData = call.uusInfo.get(0).uusData.getBytes();
-                dc.uusInfo.setUserData(userData);
-            } else {
-                mRil.riljLog("convertToDriverCall: uusInfo data is null or empty");
-            }
-
-            mRil.riljLogv(String.format("Incoming UUS : type=%d, dcs=%d, length=%d",
-                    dc.uusInfo.getType(), dc.uusInfo.getDcs(),
-                    dc.uusInfo.getUserData().length));
-            mRil.riljLogv("Incoming UUS : data (hex): "
-                    + IccUtils.bytesToHexString(dc.uusInfo.getUserData()));
-        } else {
-            mRil.riljLogv("Incoming UUS : NOT present!");
-        }
-
-        // Make sure there's a leading + on addresses with a TOA of 145
-        dc.number = PhoneNumberUtils.stringFromStringAndTOA(dc.number, dc.TOA);
-
-        return dc;
-    }
-
-    private DriverCall convertToDriverCall_1_2(android.hardware.radio.V1_2.Call call) {
-        android.hardware.radio.V1_0.Call earlierVersionCall = call.base;
-        DriverCall dc = convertToDriverCall(earlierVersionCall);
-        dc.audioQuality = (int) (call.audioQuality);
-        return dc;
-    }
-
-    private DriverCall convertToDriverCall_1_6(android.hardware.radio.V1_6.Call call) {
-        android.hardware.radio.V1_2.Call earlierVersionCall = call.base;
-        DriverCall dc = convertToDriverCall_1_2(earlierVersionCall);
-        dc.forwardedNumber = call.forwardedNumber;
-        return dc;
-    }
-
     private void responseVoid(RadioResponseInfo responseInfo) {
         RILRequest rr = mRil.processResponse(responseInfo);
 
@@ -2359,6 +2171,24 @@
         }
     }
 
+    /**
+     * Send void response
+     * @param ril RIL to send response
+     * @param responseInfo response void
+     */
+    public static void responseVoid(RIL ril,
+            android.hardware.radio.RadioResponseInfo responseInfo) {
+        RILRequest rr = ril.processResponse(responseInfo);
+
+        if (rr != null) {
+            Object ret = null;
+            if (responseInfo.error == RadioError.NONE) {
+                sendMessageResponse(rr.mResult, ret);
+            }
+            ril.processResponseDone(rr, responseInfo, ret);
+        }
+    }
+
     private void responseString(RadioResponseInfo responseInfo, String str) {
         RILRequest rr = mRil.processResponse(responseInfo);
 
@@ -2370,6 +2200,24 @@
         }
     }
 
+    /**
+     * Send string response
+     * @param ril RIL to send response
+     * @param responseInfo responseInfo
+     * @param str response string
+     */
+    public static void responseString(RIL ril,
+            android.hardware.radio.RadioResponseInfo responseInfo, String str) {
+        RILRequest rr = ril.processResponse(responseInfo);
+
+        if (rr != null) {
+            if (responseInfo.error == RadioError.NONE) {
+                sendMessageResponse(rr.mResult, str);
+            }
+            ril.processResponseDone(rr, responseInfo, str);
+        }
+    }
+
     private void responseStrings(RadioResponseInfo responseInfo, String ...str) {
         ArrayList<String> strings = new ArrayList<>();
         for (int i = 0; i < str.length; i++) {
@@ -2378,6 +2226,21 @@
         responseStringArrayList(mRil, responseInfo, strings);
     }
 
+    /**
+     * Send String array response
+     * @param ril RIL to send response
+     * @param responseInfo responseInfo
+     * @param str String array
+     */
+    public static void responseStrings(RIL ril,
+            android.hardware.radio.RadioResponseInfo responseInfo, String ...str) {
+        ArrayList<String> strings = new ArrayList<>();
+        for (int i = 0; i < str.length; i++) {
+            strings.add(str[i]);
+        }
+        responseStringArrayList(ril, responseInfo, strings);
+    }
+
     static void responseStringArrayList(RIL ril, RadioResponseInfo responseInfo,
             ArrayList<String> strings) {
         RILRequest rr = ril.processResponse(responseInfo);
@@ -2394,6 +2257,22 @@
         }
     }
 
+    private static void responseStringArrayList(RIL ril,
+            android.hardware.radio.RadioResponseInfo responseInfo, ArrayList<String> strings) {
+        RILRequest rr = ril.processResponse(responseInfo);
+
+        if (rr != null) {
+            String[] ret = new String[strings.size()];
+            for (int i = 0; i < strings.size(); i++) {
+                ret[i] = strings.get(i);
+            }
+            if (responseInfo.error == RadioError.NONE) {
+                sendMessageResponse(rr.mResult, ret);
+            }
+            ril.processResponseDone(rr, responseInfo, ret);
+        }
+    }
+
     private void responseLastCallFailCauseInfo(RadioResponseInfo responseInfo,
             LastCallFailCauseInfo fcInfo) {
         RILRequest rr = mRil.processResponse(responseInfo);
@@ -2415,7 +2294,7 @@
         RILRequest rr = mRil.processResponse(responseInfo);
 
         if (rr != null) {
-            SignalStrength ret = new SignalStrength(signalStrength);
+            SignalStrength ret = RILUtils.convertHalSignalStrength(signalStrength);
             if (responseInfo.error == RadioError.NONE) {
                 sendMessageResponse(rr.mResult, ret);
             }
@@ -2429,7 +2308,7 @@
         RILRequest rr = mRil.processResponse(responseInfo);
 
         if (rr != null) {
-            SignalStrength ret = new SignalStrength(signalStrength);
+            SignalStrength ret = RILUtils.convertHalSignalStrength(signalStrength);
             if (responseInfo.error == RadioError.NONE) {
                 sendMessageResponse(rr.mResult, ret);
             }
@@ -2443,7 +2322,7 @@
         RILRequest rr = mRil.processResponse(responseInfo);
 
         if (rr != null) {
-            SignalStrength ret = new SignalStrength(signalStrength);
+            SignalStrength ret = RILUtils.convertHalSignalStrength(signalStrength);
             if (responseInfo.error == RadioError.NONE) {
                 sendMessageResponse(rr.mResult, ret);
             }
@@ -2457,7 +2336,7 @@
         RILRequest rr = mRil.processResponse_1_6(responseInfo);
 
         if (rr != null) {
-            SignalStrength ret = new SignalStrength(signalStrength);
+            SignalStrength ret = RILUtils.convertHalSignalStrength(signalStrength);
             if (responseInfo.error == RadioError.NONE) {
                 sendMessageResponse(rr.mResult, ret);
             }
@@ -2497,7 +2376,7 @@
         RILRequest rr = mRil.processResponse(responseInfo);
 
         if (rr != null) {
-            DataCallResponse response = RIL.convertDataCallResult(setupDataCallResult);
+            DataCallResponse response = RILUtils.convertHalDataCallResult(setupDataCallResult);
             if (responseInfo.error == RadioError.NONE) {
                 sendMessageResponse(rr.mResult, response);
             }
@@ -2511,7 +2390,7 @@
         RILRequest rr = mRil.processResponse_1_6(responseInfo);
 
         if (rr != null) {
-            DataCallResponse response = RIL.convertDataCallResult(setupDataCallResult);
+            DataCallResponse response = RILUtils.convertHalDataCallResult(setupDataCallResult);
             if (responseInfo.error == RadioError.NONE) {
                 sendMessageResponse(rr.mResult, response);
             }
@@ -2533,8 +2412,7 @@
     }
 
     private void responseCallForwardInfo(RadioResponseInfo responseInfo,
-            ArrayList<android.hardware.radio.V1_0.CallForwardInfo>
-                    callForwardInfos) {
+            ArrayList<android.hardware.radio.V1_0.CallForwardInfo> callForwardInfos) {
         RILRequest rr = mRil.processResponse(responseInfo);
         if (rr != null) {
             CallForwardInfo[] ret = new CallForwardInfo[callForwardInfos.size()];
@@ -2554,23 +2432,8 @@
         }
     }
 
-    private static String convertOpertatorInfoToString(int status) {
-        if (status == android.hardware.radio.V1_0.OperatorStatus.UNKNOWN) {
-            return "unknown";
-        } else if (status == android.hardware.radio.V1_0.OperatorStatus.AVAILABLE) {
-            return "available";
-        } else if (status == android.hardware.radio.V1_0.OperatorStatus.CURRENT) {
-            return "current";
-        } else if (status == android.hardware.radio.V1_0.OperatorStatus.FORBIDDEN) {
-            return "forbidden";
-        } else {
-            return "";
-        }
-    }
-
     private void responseOperatorInfos(RadioResponseInfo responseInfo,
-            ArrayList<android.hardware.radio.V1_0.OperatorInfo>
-                    networkInfos) {
+            ArrayList<android.hardware.radio.V1_0.OperatorInfo> networkInfos) {
         RILRequest rr = mRil.processResponse(responseInfo);
 
         if (rr != null) {
@@ -2578,7 +2441,7 @@
             for (int i = 0; i < networkInfos.size(); i++) {
                 ret.add(new OperatorInfo(networkInfos.get(i).alphaLong,
                         networkInfos.get(i).alphaShort, networkInfos.get(i).operatorNumeric,
-                        convertOpertatorInfoToString(networkInfos.get(i).status)));
+                        RILUtils.convertHalOperatorStatus(networkInfos.get(i).status)));
             }
             if (responseInfo.error == RadioError.NONE) {
                 sendMessageResponse(rr.mResult, ret);
@@ -2626,7 +2489,7 @@
 
         if (rr != null) {
             ArrayList<DataCallResponse> response =
-                    RIL.convertDataCallResultList(dataCallResultList);
+                    RILUtils.convertHalDataCallResultList(dataCallResultList);
             if (responseInfo.error == RadioError.NONE) {
                 sendMessageResponse(rr.mResult, response);
             }
@@ -2640,7 +2503,7 @@
 
         if (rr != null) {
             ArrayList<DataCallResponse> response =
-                    RIL.convertDataCallResultList(dataCallResultList);
+                    RILUtils.convertHalDataCallResultList(dataCallResultList);
             if (responseInfo.error == RadioError.NONE) {
                 sendMessageResponse(rr.mResult, response);
             }
@@ -2683,7 +2546,7 @@
         RILRequest rr = mRil.processResponse(responseInfo);
 
         if (rr != null) {
-            ArrayList<SmsBroadcastConfigInfo> ret = new ArrayList<SmsBroadcastConfigInfo>();
+            ArrayList<SmsBroadcastConfigInfo> ret = new ArrayList<>();
             for (int i = 0; i < configs.size(); i++) {
                 ret.add(new SmsBroadcastConfigInfo(configs.get(i).fromServiceId,
                         configs.get(i).toServiceId, configs.get(i).fromCodeScheme,
@@ -2710,28 +2573,28 @@
                 // not be done by this transport layer. And needs to
                 // be done by the vendor ril or application logic.
                 int numInts;
-                numInts = CDMA_BROADCAST_SMS_NO_OF_SERVICE_CATEGORIES
-                        * CDMA_BSI_NO_OF_INTS_STRUCT + 1;
+                numInts = RILUtils.CDMA_BROADCAST_SMS_NO_OF_SERVICE_CATEGORIES
+                        * RILUtils.CDMA_BSI_NO_OF_INTS_STRUCT + 1;
                 ret = new int[numInts];
 
                 // Faking a default record for all possible records.
-                ret[0] = CDMA_BROADCAST_SMS_NO_OF_SERVICE_CATEGORIES;
+                ret[0] = RILUtils.CDMA_BROADCAST_SMS_NO_OF_SERVICE_CATEGORIES;
 
                 // Loop over CDMA_BROADCAST_SMS_NO_OF_SERVICE_CATEGORIES set 'english' as
                 // default language and selection status to false for all.
-                for (int i = 1; i < numInts; i += CDMA_BSI_NO_OF_INTS_STRUCT) {
-                    ret[i + 0] = i / CDMA_BSI_NO_OF_INTS_STRUCT;
+                for (int i = 1; i < numInts; i += RILUtils.CDMA_BSI_NO_OF_INTS_STRUCT) {
+                    ret[i + 0] = i / RILUtils.CDMA_BSI_NO_OF_INTS_STRUCT;
                     ret[i + 1] = 1;
                     ret[i + 2] = 0;
                 }
             } else {
                 int numInts;
-                numInts = (numServiceCategories * CDMA_BSI_NO_OF_INTS_STRUCT) + 1;
+                numInts = (numServiceCategories * RILUtils.CDMA_BSI_NO_OF_INTS_STRUCT) + 1;
                 ret = new int[numInts];
 
                 ret[0] = numServiceCategories;
                 for (int i = 1, j = 0; j < configs.size();
-                        j++, i = i + CDMA_BSI_NO_OF_INTS_STRUCT) {
+                        j++, i = i + RILUtils.CDMA_BSI_NO_OF_INTS_STRUCT) {
                     ret[i] = configs.get(j).serviceCategory;
                     ret[i + 1] = configs.get(j).language;
                     ret[i + 2] = configs.get(j).selected ? 1 : 0;
@@ -2745,52 +2608,11 @@
     }
 
     private void responseCellInfoList(RadioResponseInfo responseInfo,
-            ArrayList<android.hardware.radio.V1_0.CellInfo> cellInfo) {
+            ArrayList<? extends Object> cellInfo) {
         RILRequest rr = mRil.processResponse(responseInfo);
 
         if (rr != null) {
-            ArrayList<CellInfo> ret = RIL.convertHalCellInfoList(cellInfo);
-            if (responseInfo.error == RadioError.NONE) {
-                sendMessageResponse(rr.mResult, ret);
-            }
-            mRil.processResponseDone(rr, responseInfo, ret);
-        }
-    }
-
-    private void responseCellInfoList_1_2(
-            RadioResponseInfo responseInfo,
-            ArrayList<android.hardware.radio.V1_2.CellInfo> cellInfo) {
-        RILRequest rr = mRil.processResponse(responseInfo);
-
-        if (rr != null) {
-            ArrayList<CellInfo> ret = RIL.convertHalCellInfoList_1_2(cellInfo);
-            if (responseInfo.error == RadioError.NONE) {
-                sendMessageResponse(rr.mResult, ret);
-            }
-            mRil.processResponseDone(rr, responseInfo, ret);
-        }
-    }
-
-    private void responseCellInfoList_1_4(
-            RadioResponseInfo responseInfo,
-            ArrayList<android.hardware.radio.V1_4.CellInfo> cellInfo) {
-        RILRequest rr = mRil.processResponse(responseInfo);
-
-        if (rr != null) {
-            ArrayList<CellInfo> ret = RIL.convertHalCellInfoList_1_4(cellInfo);
-            if (responseInfo.error == RadioError.NONE) {
-                sendMessageResponse(rr.mResult, ret);
-            }
-            mRil.processResponseDone(rr, responseInfo, ret);
-        }
-    }
-
-    private void responseCellInfoList_1_5(RadioResponseInfo responseInfo,
-            ArrayList<android.hardware.radio.V1_5.CellInfo> cellInfo) {
-        RILRequest rr = mRil.processResponse(responseInfo);
-
-        if (rr != null) {
-            ArrayList<CellInfo> ret = RIL.convertHalCellInfoList_1_5(cellInfo);
+            ArrayList<CellInfo> ret = RILUtils.convertHalCellInfoList((ArrayList<Object>) cellInfo);
             if (responseInfo.error == RadioError.NONE) {
                 sendMessageResponse(rr.mResult, ret);
             }
@@ -2800,11 +2622,11 @@
 
     private void responseCellInfoList_1_6(
             android.hardware.radio.V1_6.RadioResponseInfo responseInfo,
-            ArrayList<android.hardware.radio.V1_6.CellInfo> cellInfo) {
+            ArrayList<? extends Object> cellInfo) {
         RILRequest rr = mRil.processResponse_1_6(responseInfo);
 
         if (rr != null) {
-            ArrayList<CellInfo> ret = RIL.convertHalCellInfoList_1_6(cellInfo);
+            ArrayList<CellInfo> ret = RILUtils.convertHalCellInfoList((ArrayList<Object>) cellInfo);
             if (responseInfo.error == RadioError.NONE) {
                 sendMessageResponse(rr.mResult, ret);
             }
@@ -2838,13 +2660,12 @@
         }
     }
 
-    private void responseHardwareConfig(
-            RadioResponseInfo responseInfo,
+    private void responseHardwareConfig(RadioResponseInfo responseInfo,
             ArrayList<android.hardware.radio.V1_0.HardwareConfig> config) {
         RILRequest rr = mRil.processResponse(responseInfo);
 
         if (rr != null) {
-            ArrayList<HardwareConfig> ret = RIL.convertHalHwConfigList(config, mRil);
+            ArrayList<HardwareConfig> ret = RILUtils.convertHalHardwareConfigList(config);
             if (responseInfo.error == RadioError.NONE) {
                 sendMessageResponse(rr.mResult, ret);
             }
@@ -2857,7 +2678,7 @@
         RILRequest rr = mRil.processResponse(responseInfo);
 
         if (rr != null) {
-            RadioCapability ret = RIL.convertHalRadioCapability(rc, mRil);
+            RadioCapability ret = RILUtils.convertHalRadioCapability(rc, mRil);
             if (responseInfo.error == RadioError.NONE) {
                 sendMessageResponse(rr.mResult, ret);
             }
@@ -2869,7 +2690,7 @@
         RILRequest rr = mRil.processResponse(responseInfo);
 
         if (rr != null) {
-            ArrayList<Integer> ret = new ArrayList<Integer>();
+            ArrayList<Integer> ret = new ArrayList<>();
             ret.add(statusInfo.lceStatus);
             ret.add(Byte.toUnsignedInt(statusInfo.actualIntervalMs));
             if (responseInfo.error == RadioError.NONE) {
@@ -2883,7 +2704,7 @@
         RILRequest rr = mRil.processResponse(responseInfo);
 
         if (rr != null) {
-            List<LinkCapacityEstimate> ret = RIL.convertHalLceData(lceInfo, mRil);
+            List<LinkCapacityEstimate> ret = RILUtils.convertHalLceData(lceInfo);
             if (responseInfo.error == RadioError.NONE) {
                 sendMessageResponse(rr.mResult, ret);
             }
@@ -2891,31 +2712,8 @@
         }
     }
 
-    private static List<CarrierIdentifier> convertCarrierList(List<Carrier> carrierList) {
-        List<CarrierIdentifier> ret = new ArrayList<>();
-        for (int i = 0; i < carrierList.size(); i++) {
-            String mcc = carrierList.get(i).mcc;
-            String mnc = carrierList.get(i).mnc;
-            String spn = null, imsi = null, gid1 = null, gid2 = null;
-            int matchType = carrierList.get(i).matchType;
-            String matchData = carrierList.get(i).matchData;
-            if (matchType == CarrierIdentifier.MatchType.SPN) {
-                spn = matchData;
-            } else if (matchType == CarrierIdentifier.MatchType.IMSI_PREFIX) {
-                imsi = matchData;
-            } else if (matchType == CarrierIdentifier.MatchType.GID1) {
-                gid1 = matchData;
-            } else if (matchType == CarrierIdentifier.MatchType.GID2) {
-                gid2 = matchData;
-            }
-            ret.add(new CarrierIdentifier(mcc, mnc, spn, imsi, gid1, gid2));
-        }
-        return ret;
-    }
-
     private void responseCarrierRestrictions(RadioResponseInfo responseInfo, boolean allAllowed,
-            CarrierRestrictionsWithPriority carriers,
-            int multiSimPolicy) {
+            CarrierRestrictionsWithPriority carriers, int multiSimPolicy) {
         RILRequest rr = mRil.processResponse(responseInfo);
         if (rr == null) {
             return;
@@ -2938,8 +2736,8 @@
             }
 
             ret = CarrierRestrictionRules.newBuilder()
-                    .setAllowedCarriers(convertCarrierList(carriers.allowedCarriers))
-                    .setExcludedCarriers(convertCarrierList(carriers.excludedCarriers))
+                    .setAllowedCarriers(RILUtils.convertHalCarrierList(carriers.allowedCarriers))
+                    .setExcludedCarriers(RILUtils.convertHalCarrierList(carriers.excludedCarriers))
                     .setDefaultCarrierRestriction(carrierRestrictionDefault)
                     .setMultiSimPolicy(policy)
                     .build();
@@ -3011,7 +2809,8 @@
         mRil.mLastRadioPowerResult = info.error;
         if (info.error != RadioError.RADIO_NOT_AVAILABLE && info.error != RadioError.NONE) {
             AnomalyReporter.reportAnomaly(
-                    UUID.fromString(RADIO_POWER_FAILURE_BUGREPORT_UUID), "Radio power failure");
+                    UUID.fromString(RILUtils.RADIO_POWER_FAILURE_BUGREPORT_UUID),
+                    "Radio power failure");
         }
     }
 
@@ -3023,15 +2822,17 @@
         mRil.mLastRadioPowerResult = info.error;
         if (info.error == android.hardware.radio.V1_6.RadioError.RF_HARDWARE_ISSUE) {
             AnomalyReporter.reportAnomaly(
-                    UUID.fromString(RADIO_POWER_FAILURE_RF_HARDWARE_ISSUE_UUID), "RF HW damaged");
+                    UUID.fromString(RILUtils.RADIO_POWER_FAILURE_RF_HARDWARE_ISSUE_UUID),
+                    "RF HW damaged");
         } else if (info.error == android.hardware.radio.V1_6.RadioError.NO_RF_CALIBRATION_INFO) {
             AnomalyReporter.reportAnomaly(
-                    UUID.fromString(RADIO_POWER_FAILURE_NO_RF_CALIBRATION_UUID),
+                    UUID.fromString(RILUtils.RADIO_POWER_FAILURE_NO_RF_CALIBRATION_UUID),
                     "No RF calibration data");
         } else if (info.error != android.hardware.radio.V1_6.RadioError.RADIO_NOT_AVAILABLE
                 && info.error != android.hardware.radio.V1_6.RadioError.NONE) {
             AnomalyReporter.reportAnomaly(
-                    UUID.fromString(RADIO_POWER_FAILURE_BUGREPORT_UUID), "Radio power failure");
+                    UUID.fromString(RILUtils.RADIO_POWER_FAILURE_BUGREPORT_UUID),
+                    "Radio power failure");
         }
     }
 
@@ -3055,7 +2856,7 @@
         if (rr != null) {
             ArrayList<RadioAccessSpecifier> specifiers = new ArrayList<>();
             for (android.hardware.radio.V1_5.RadioAccessSpecifier specifier : halSpecifiers) {
-                specifiers.add(convertRadioAccessSpecifier(specifier));
+                specifiers.add(RILUtils.convertHalRadioAccessSpecifier(specifier));
             }
             mRil.riljLog("getSystemSelectionChannelsResponse: from HIDL: " + specifiers);
             if (info.error == RadioError.NONE) {
@@ -3065,51 +2866,6 @@
         }
     }
 
-    private static RadioAccessSpecifier convertRadioAccessSpecifier(
-            android.hardware.radio.V1_5.RadioAccessSpecifier specifier) {
-        if (specifier == null) return null;
-        ArrayList<Integer> halBands = new ArrayList<>();
-        switch (specifier.bands.getDiscriminator()) {
-            case android.hardware.radio.V1_5.RadioAccessSpecifier.Bands.hidl_discriminator
-                    .geranBands:
-                halBands = specifier.bands.geranBands();
-                break;
-            case android.hardware.radio.V1_5.RadioAccessSpecifier.Bands.hidl_discriminator
-                    .utranBands:
-                halBands = specifier.bands.utranBands();
-                break;
-            case android.hardware.radio.V1_5.RadioAccessSpecifier.Bands.hidl_discriminator
-                    .eutranBands:
-                halBands = specifier.bands.eutranBands();
-                break;
-            case android.hardware.radio.V1_5.RadioAccessSpecifier.Bands.hidl_discriminator
-                    .ngranBands:
-                halBands = specifier.bands.ngranBands();
-                break;
-        }
-        return new RadioAccessSpecifier(convertRanToAnt(specifier.radioAccessNetwork),
-                halBands.stream().mapToInt(Integer::intValue).toArray(),
-                specifier.channels.stream().mapToInt(Integer::intValue).toArray());
-    }
-
-    private static int convertRanToAnt(int ran) {
-        switch (ran) {
-            case android.hardware.radio.V1_5.RadioAccessNetworks.GERAN:
-                return AccessNetworkConstants.AccessNetworkType.GERAN;
-            case android.hardware.radio.V1_5.RadioAccessNetworks.UTRAN:
-                return AccessNetworkConstants.AccessNetworkType.UTRAN;
-            case android.hardware.radio.V1_5.RadioAccessNetworks.EUTRAN:
-                return AccessNetworkConstants.AccessNetworkType.EUTRAN;
-            case android.hardware.radio.V1_5.RadioAccessNetworks.NGRAN:
-                return AccessNetworkConstants.AccessNetworkType.NGRAN;
-            case android.hardware.radio.V1_5.RadioAccessNetworks.CDMA2000:
-                return AccessNetworkConstants.AccessNetworkType.CDMA2000;
-            case android.hardware.radio.V1_5.RadioAccessNetworks.UNKNOWN:
-            default:
-                return AccessNetworkConstants.AccessNetworkType.UNKNOWN;
-        }
-    }
-
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error.
      * @param cellIdentity CellIdentity for the barringInfos.
@@ -3121,7 +2877,8 @@
         RILRequest rr = mRil.processResponse(responseInfo);
 
         if (rr != null) {
-            BarringInfo bi = BarringInfo.create(cellIdentity, barringInfos);
+            BarringInfo bi = new BarringInfo(RILUtils.convertHalCellIdentity(cellIdentity),
+                    RILUtils.convertHalBarringInfoList(barringInfos));
             if (responseInfo.error == RadioError.NONE) {
                 sendMessageResponse(rr.mResult, bi);
                 // notify all registrants for the possible barring info change
@@ -3177,7 +2934,7 @@
         RILRequest rr = mRil.processResponse_1_6(info);
 
         if (rr != null) {
-            SlicingConfig ret = new SlicingConfig(slicingConfig);
+            NetworkSlicingConfig ret = RILUtils.convertHalSlicingConfig(slicingConfig);
             if (info.error == RadioError.NONE) {
                 sendMessageResponse(rr.mResult, ret);
             }
diff --git a/src/java/com/android/internal/telephony/RetryManager.java b/src/java/com/android/internal/telephony/RetryManager.java
index 121b4c2..83864e4 100644
--- a/src/java/com/android/internal/telephony/RetryManager.java
+++ b/src/java/com/android/internal/telephony/RetryManager.java
@@ -134,11 +134,10 @@
 
     /**
      * If the network suggests a retry delay in the data call setup response, we will retry
-     * the current APN setting again. However, if the network keeps suggesting retrying the same
-     * APN setting, we'll fall into an infinite loop. Therefore adding a counter to retry up to
-     * MAX_SAME_APN_RETRY times can avoid it.
+     * 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 MAX_SAME_APN_RETRY = 3;
+    private static final int DEFAULT_MAX_SAME_APN_RETRY = 3;
 
     /**
      * The delay (in milliseconds) between APN trying within the same round
@@ -160,11 +159,17 @@
     private long mApnRetryAfterDisconnectDelay;
 
     /**
-     * The counter for same APN retrying. See MAX_SAME_APN_RETRY for the details.
+     * 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 {
@@ -364,6 +369,10 @@
             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(
@@ -522,11 +531,11 @@
         }
 
         // If the network had suggested a retry delay, we should retry the current APN again
-        // (up to MAX_SAME_APN_RETRY times) instead of getting the next APN setting from
+        // (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 < MAX_SAME_APN_RETRY) {
+                && mSameApnRetryCount < mMaxSameApnRetry) {
             mSameApnRetryCount++;
             return mWaitingApns.get(mCurrentApnIndex);
         }
@@ -575,7 +584,7 @@
         }
 
         if (networkSuggestedDelay != NO_SUGGESTED_RETRY_DELAY
-                && mSameApnRetryCount < MAX_SAME_APN_RETRY) {
+                && 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.");
diff --git a/src/java/com/android/internal/telephony/SMSDispatcher.java b/src/java/com/android/internal/telephony/SMSDispatcher.java
index 2d10307..7012313 100644
--- a/src/java/com/android/internal/telephony/SMSDispatcher.java
+++ b/src/java/com/android/internal/telephony/SMSDispatcher.java
@@ -43,6 +43,7 @@
 import android.os.Binder;
 import android.os.Build;
 import android.os.Handler;
+import android.os.Looper;
 import android.os.Message;
 import android.os.PersistableBundle;
 import android.os.Process;
@@ -63,6 +64,8 @@
 import android.text.Spanned;
 import android.text.TextUtils;
 import android.util.EventLog;
+import android.util.IndentingPrintWriter;
+import android.util.LocalLog;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -80,7 +83,10 @@
 import com.android.internal.telephony.uicc.UiccController;
 import com.android.telephony.Rlog;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Random;
@@ -149,6 +155,7 @@
     protected final CommandsInterface mCi;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     protected final TelephonyManager mTelephonyManager;
+    protected final LocalLog mLocalLog = new LocalLog(16);
 
     /** Maximum number of times to retry sending a failed SMS. */
     protected static final int MAX_SEND_RETRIES = 3;
@@ -157,6 +164,12 @@
     public static final int SEND_RETRY_DELAY = 2000;
     /** Message sending queue limit */
     private static final int MO_MSG_QUEUE_LIMIT = 5;
+    /** SMS anomaly uuid -- CarrierMessagingService did not respond */
+    private static final UUID sAnomalyNoResponseFromCarrierMessagingService =
+            UUID.fromString("279d9fbc-462d-4fc2-802c-bf21ddd9dd90");
+    /** SMS anomaly uuid -- CarrierMessagingService unexpected callback */
+    private static final UUID sAnomalyUnexpectedCallback =
+            UUID.fromString("0103b6d2-ad07-4d86-9102-14341b9074ef");
 
     /**
      * Message reference for a CONCATENATED_8_BIT_REFERENCE or
@@ -175,6 +188,9 @@
     protected boolean mSmsCapable = true;
     protected boolean mSmsSendDisabled;
 
+    @VisibleForTesting
+    public int mCarrierMessagingTimeout = 10 * 60 * 1000; //10 minutes
+
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     protected static int getNextConcatenatedRef() {
         sConcatenatedRef += 1;
@@ -347,44 +363,95 @@
     /**
      * Use the carrier messaging service to send a data or text SMS.
      */
-    protected abstract class SmsSender {
-        protected final SmsTracker mTracker;
+    protected abstract class SmsSender extends Handler {
+        private static final int EVENT_TIMEOUT = 1;
         // Initialized in sendSmsByCarrierApp
-        protected volatile SmsSenderCallback mSenderCallback;
+        protected volatile CarrierMessagingCallback mSenderCallback;
         protected final CarrierMessagingServiceWrapper mCarrierMessagingServiceWrapper =
                 new CarrierMessagingServiceWrapper();
+        private String mCarrierPackageName;
 
-        protected SmsSender(SmsTracker tracker) {
-            mTracker = tracker;
+        protected SmsSender() {
+            super(Looper.getMainLooper());
         }
 
-        public void sendSmsByCarrierApp(String carrierPackageName,
-                                        SmsSenderCallback senderCallback) {
+        /**
+         * Bind to carrierPackageName to send message through it
+         */
+        public synchronized void sendSmsByCarrierApp(String carrierPackageName,
+                CarrierMessagingCallback senderCallback) {
+            mCarrierPackageName = carrierPackageName;
             mSenderCallback = senderCallback;
             if (!mCarrierMessagingServiceWrapper.bindToCarrierMessagingService(
-                    mContext, carrierPackageName, ()->onServiceReady())) {
+                    mContext, carrierPackageName, runnable -> runnable.run(),
+                    ()->onServiceReady())) {
                 Rlog.e(TAG, "bindService() for carrier messaging service failed");
-                mSenderCallback.onSendSmsComplete(
-                        CarrierMessagingService.SEND_STATUS_RETRY_ON_CARRIER_NETWORK,
-                        0 /* messageRef */);
+                onSendComplete(CarrierMessagingService.SEND_STATUS_RETRY_ON_CARRIER_NETWORK);
             } else {
                 Rlog.d(TAG, "bindService() for carrier messaging service succeeded");
+                sendMessageDelayed(obtainMessage(EVENT_TIMEOUT), mCarrierMessagingTimeout);
             }
         }
 
+        /**
+         * Callback received from mCarrierPackageName on binding to it is done.
+         * NOTE: the implementations of this method must be synchronized to make sure it does not
+         * get called before {@link #sendSmsByCarrierApp} completes and {@link #EVENT_TIMEOUT} is
+         * posted
+         */
         public abstract void onServiceReady();
+
+        /**
+         * Method to call message send callback with passed in result and default parameters
+         */
+        public abstract void onSendComplete(@CarrierMessagingService.SendResult int result);
+
+        /**
+         * Used to get the SmsTracker for single part messages
+         */
+        public abstract SmsTracker getSmsTracker();
+
+        /**
+         * Used to get the SmsTrackers for multi part messages
+         */
+        public abstract SmsTracker[] getSmsTrackers();
+
+        @Override
+        public void handleMessage(Message msg) {
+            if (msg.what == EVENT_TIMEOUT) {
+                logWithLocalLog("handleMessage: No response from " + mCarrierPackageName
+                        + " for " + mCarrierMessagingTimeout + " ms");
+                AnomalyReporter.reportAnomaly(sAnomalyNoResponseFromCarrierMessagingService,
+                        "No response from " + mCarrierPackageName);
+                onSendComplete(CarrierMessagingService.SEND_STATUS_RETRY_ON_CARRIER_NETWORK);
+            } else {
+                logWithLocalLog("handleMessage: received unexpected message " + msg.what);
+            }
+        }
+
+        public void removeTimeout() {
+            removeMessages(EVENT_TIMEOUT);
+        }
+    }
+
+    private void logWithLocalLog(String logStr) {
+        mLocalLog.log(logStr);
+        Rlog.d(TAG, logStr);
     }
 
     /**
      * Use the carrier messaging service to send a text SMS.
      */
     protected final class TextSmsSender extends SmsSender {
+        private final SmsTracker mTracker;
         public TextSmsSender(SmsTracker tracker) {
-            super(tracker);
+            super();
+            mTracker = tracker;
         }
 
         @Override
-        public void onServiceReady() {
+        public synchronized void onServiceReady() {
+            Rlog.d(TAG, "TextSmsSender::onServiceReady");
             HashMap<String, Object> map = mTracker.getData();
             String text = (String) map.get(MAP_KEY_TEXT);
 
@@ -397,31 +464,49 @@
                             (mTracker.mDeliveryIntent != null)
                                     ? CarrierMessagingService.SEND_FLAG_REQUEST_DELIVERY_STATUS
                                     : 0,
+                            runnable -> runnable.run(),
                             mSenderCallback);
                 } catch (RuntimeException e) {
-                    Rlog.e(TAG, "Exception sending the SMS: " + e);
-                    mSenderCallback.onSendSmsComplete(
-                            CarrierMessagingService.SEND_STATUS_RETRY_ON_CARRIER_NETWORK,
-                            0 /* messageRef */);
+                    Rlog.e(TAG, "TextSmsSender::onServiceReady: Exception sending the SMS: "
+                            + e.getMessage());
+                    onSendComplete(CarrierMessagingService.SEND_STATUS_RETRY_ON_CARRIER_NETWORK);
                 }
             } else {
-                mSenderCallback.onSendSmsComplete(
-                        CarrierMessagingService.SEND_STATUS_RETRY_ON_CARRIER_NETWORK,
-                        0 /* messageRef */);
+                Rlog.d(TAG, "TextSmsSender::onServiceReady: text == null");
+                onSendComplete(CarrierMessagingService.SEND_STATUS_RETRY_ON_CARRIER_NETWORK);
             }
         }
+
+        @Override
+        public void onSendComplete(int result) {
+            mSenderCallback.onSendSmsComplete(result, 0 /* messageRef */);
+        }
+
+        @Override
+        public SmsTracker getSmsTracker() {
+            return mTracker;
+        }
+
+        @Override
+        public SmsTracker[] getSmsTrackers() {
+            Rlog.e(TAG, "getSmsTrackers: Unexpected call for TextSmsSender");
+            return null;
+        }
     }
 
     /**
      * Use the carrier messaging service to send a data SMS.
      */
     protected final class DataSmsSender extends SmsSender {
+        private final SmsTracker mTracker;
         public DataSmsSender(SmsTracker tracker) {
-            super(tracker);
+            super();
+            mTracker = tracker;
         }
 
         @Override
-        public void onServiceReady() {
+        public synchronized void onServiceReady() {
+            Rlog.d(TAG, "DataSmsSender::onServiceReady");
             HashMap<String, Object> map = mTracker.getData();
             byte[] data = (byte[]) map.get(MAP_KEY_DATA);
             int destPort = (int) map.get(MAP_KEY_DEST_PORT);
@@ -436,20 +521,34 @@
                             (mTracker.mDeliveryIntent != null)
                                     ? CarrierMessagingService.SEND_FLAG_REQUEST_DELIVERY_STATUS
                                     : 0,
+                            runnable -> runnable.run(),
                             mSenderCallback);
                 } catch (RuntimeException e) {
-                    Rlog.e(TAG, "Exception sending the SMS: " + e
-                            + " id: " + mTracker.mMessageId);
-                    mSenderCallback.onSendSmsComplete(
-                            CarrierMessagingService.SEND_STATUS_RETRY_ON_CARRIER_NETWORK,
-                            0 /* messageRef */);
+                    Rlog.e(TAG, "DataSmsSender::onServiceReady: Exception sending the SMS: " + e
+                            + " " + SmsController.formatCrossStackMessageId(mTracker.mMessageId));
+                    onSendComplete(CarrierMessagingService.SEND_STATUS_RETRY_ON_CARRIER_NETWORK);
                 }
             } else {
-                mSenderCallback.onSendSmsComplete(
-                        CarrierMessagingService.SEND_STATUS_RETRY_ON_CARRIER_NETWORK,
-                        0 /* messageRef */);
+                Rlog.d(TAG, "DataSmsSender::onServiceReady: data == null");
+                onSendComplete(CarrierMessagingService.SEND_STATUS_RETRY_ON_CARRIER_NETWORK);
             }
         }
+
+        @Override
+        public void onSendComplete(int result) {
+            mSenderCallback.onSendSmsComplete(result, 0 /* messageRef */);
+        }
+
+        @Override
+        public SmsTracker getSmsTracker() {
+            return mTracker;
+        }
+
+        @Override
+        public SmsTracker[] getSmsTrackers() {
+            Rlog.e(TAG, "getSmsTrackers: Unexpected call for DataSmsSender");
+            return null;
+        }
     }
 
     /**
@@ -458,6 +557,7 @@
      */
     protected final class SmsSenderCallback implements CarrierMessagingCallback {
         private final SmsSender mSmsSender;
+        private boolean mCallbackCalled = false;
 
         public SmsSenderCallback(SmsSender smsSender) {
             mSmsSender = smsSender;
@@ -468,11 +568,19 @@
          */
         @Override
         public void onSendSmsComplete(int result, int messageRef) {
-            checkCallerIsPhoneOrCarrierApp();
+            Rlog.d(TAG, "onSendSmsComplete: result=" + result + " messageRef=" + messageRef);
+            if (mCallbackCalled) {
+                logWithLocalLog("onSendSmsComplete: unexpected call");
+                AnomalyReporter.reportAnomaly(sAnomalyUnexpectedCallback,
+                        "Unexpected onSendSmsComplete");
+                return;
+            }
+            mCallbackCalled = true;
             final long identity = Binder.clearCallingIdentity();
             try {
-                mSmsSender.mCarrierMessagingServiceWrapper.disposeConnection(mContext);
-                processSendSmsResponse(mSmsSender.mTracker, result, messageRef);
+                mSmsSender.mCarrierMessagingServiceWrapper.disconnect();
+                processSendSmsResponse(mSmsSender.getSmsTracker(), result, messageRef);
+                mSmsSender.removeTimeout();
             } finally {
                 Binder.restoreCallingIdentity(identity);
             }
@@ -484,8 +592,8 @@
         }
 
         @Override
-        public void onFilterComplete(int result) {
-            Rlog.e(TAG, "Unexpected onFilterComplete call with result: " + result);
+        public void onReceiveSmsComplete(int result) {
+            Rlog.e(TAG, "Unexpected onReceiveSmsComplete call with result: " + result);
         }
 
         @Override
@@ -512,7 +620,8 @@
         switch (result) {
             case CarrierMessagingService.SEND_STATUS_OK:
                 Rlog.d(TAG, "processSendSmsResponse: Sending SMS by CarrierMessagingService "
-                        + "succeeded. id: " + tracker.mMessageId);
+                        + "succeeded. "
+                        + SmsController.formatCrossStackMessageId(tracker.mMessageId));
                 sendMessage(obtainMessage(EVENT_SEND_SMS_COMPLETE,
                                           new AsyncResult(tracker,
                                                           smsResponse,
@@ -520,19 +629,21 @@
                 break;
             case CarrierMessagingService.SEND_STATUS_ERROR:
                 Rlog.d(TAG, "processSendSmsResponse: Sending SMS by CarrierMessagingService failed."
-                        + " id: " + tracker.mMessageId);
+                        + " " + SmsController.formatCrossStackMessageId(tracker.mMessageId));
                 sendMessage(obtainMessage(EVENT_SEND_SMS_COMPLETE,
                         new AsyncResult(tracker, smsResponse,
                                 new CommandException(CommandException.Error.GENERIC_FAILURE))));
                 break;
             case CarrierMessagingService.SEND_STATUS_RETRY_ON_CARRIER_NETWORK:
                 Rlog.d(TAG, "processSendSmsResponse: Sending SMS by CarrierMessagingService failed."
-                        + " Retry on carrier network. id: " + tracker.mMessageId);
+                        + " Retry on carrier network. "
+                        + SmsController.formatCrossStackMessageId(tracker.mMessageId));
                 sendSubmitPdu(tracker);
                 break;
             default:
                 Rlog.d(TAG, "processSendSmsResponse: Unknown result " + result + " Retry on carrier"
-                        + " network. id: " + tracker.mMessageId);
+                        + " network. "
+                        + SmsController.formatCrossStackMessageId(tracker.mMessageId));
                 sendSubmitPdu(tracker);
         }
     }
@@ -540,15 +651,12 @@
     /**
      * Use the carrier messaging service to send a multipart text SMS.
      */
-    private final class MultipartSmsSender {
+    private final class MultipartSmsSender extends SmsSender {
         private final List<String> mParts;
         public final SmsTracker[] mTrackers;
-        // Initialized in sendSmsByCarrierApp
-        private volatile MultipartSmsSenderCallback mSenderCallback;
-        private final CarrierMessagingServiceWrapper mCarrierMessagingServiceWrapper =
-                new CarrierMessagingServiceWrapper();
 
         MultipartSmsSender(ArrayList<String> parts, SmsTracker[] trackers) {
+            super();
             mParts = parts;
             mTrackers = trackers;
         }
@@ -556,19 +664,12 @@
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
         void sendSmsByCarrierApp(String carrierPackageName,
                                  MultipartSmsSenderCallback senderCallback) {
-            mSenderCallback = senderCallback;
-            if (!mCarrierMessagingServiceWrapper.bindToCarrierMessagingService(
-                    mContext, carrierPackageName, ()->onServiceReady())) {
-                Rlog.e(TAG, "bindService() for carrier messaging service failed");
-                mSenderCallback.onSendMultipartSmsComplete(
-                        CarrierMessagingService.SEND_STATUS_RETRY_ON_CARRIER_NETWORK,
-                        null /* messageRefs */);
-            } else {
-                Rlog.d(TAG, "bindService() for carrier messaging service succeeded");
-            }
+            super.sendSmsByCarrierApp(carrierPackageName, senderCallback);
         }
 
-        private void onServiceReady() {
+        @Override
+        public synchronized void onServiceReady() {
+            Rlog.d(TAG, "MultipartSmsSender::onServiceReady");
             boolean statusReportRequested = false;
             for (SmsTracker tracker : mTrackers) {
                 if (tracker.mDeliveryIntent != null) {
@@ -585,14 +686,29 @@
                         statusReportRequested
                                 ? CarrierMessagingService.SEND_FLAG_REQUEST_DELIVERY_STATUS
                                 : 0,
+                        runnable -> runnable.run(),
                         mSenderCallback);
             } catch (RuntimeException e) {
-                Rlog.e(TAG, "Exception sending the SMS: " + e);
-                mSenderCallback.onSendMultipartSmsComplete(
-                        CarrierMessagingService.SEND_STATUS_RETRY_ON_CARRIER_NETWORK,
-                        null /* messageRefs */);
+                Rlog.e(TAG, "MultipartSmsSender::onServiceReady: Exception sending the SMS: " + e);
+                onSendComplete(CarrierMessagingService.SEND_STATUS_RETRY_ON_CARRIER_NETWORK);
             }
         }
+
+        @Override
+        public void onSendComplete(int result) {
+            mSenderCallback.onSendMultipartSmsComplete(result, null /* messageRefs */);
+        }
+
+        @Override
+        public SmsTracker getSmsTracker() {
+            Rlog.e(TAG, "getSmsTracker: Unexpected call for MultipartSmsSender");
+            return null;
+        }
+
+        @Override
+        public SmsTracker[] getSmsTrackers() {
+            return mTrackers;
+        }
     }
 
     /**
@@ -601,6 +717,7 @@
      */
     private final class MultipartSmsSenderCallback implements CarrierMessagingCallback {
         private final MultipartSmsSender mSmsSender;
+        private boolean mCallbackCalled = false;
 
         MultipartSmsSenderCallback(MultipartSmsSender smsSender) {
             mSmsSender = smsSender;
@@ -616,14 +733,23 @@
          */
         @Override
         public void onSendMultipartSmsComplete(int result, int[] messageRefs) {
-            mSmsSender.mCarrierMessagingServiceWrapper.disposeConnection(mContext);
+            Rlog.d(TAG, "onSendMultipartSmsComplete: result=" + result + " messageRefs="
+                    + Arrays.toString(messageRefs));
+            if (mCallbackCalled) {
+                logWithLocalLog("onSendMultipartSmsComplete: unexpected call");
+                AnomalyReporter.reportAnomaly(sAnomalyUnexpectedCallback,
+                        "Unexpected onSendMultipartSmsComplete");
+                return;
+            }
+            mCallbackCalled = true;
+            mSmsSender.removeTimeout();
+            mSmsSender.mCarrierMessagingServiceWrapper.disconnect();
 
             if (mSmsSender.mTrackers == null) {
                 Rlog.e(TAG, "Unexpected onSendMultipartSmsComplete call with null trackers.");
                 return;
             }
 
-            checkCallerIsPhoneOrCarrierApp();
             final long identity = Binder.clearCallingIdentity();
             try {
                 processSendMultipartSmsResponse(mSmsSender.mTrackers, result, messageRefs);
@@ -633,8 +759,8 @@
         }
 
         @Override
-        public void onFilterComplete(int result) {
-            Rlog.e(TAG, "Unexpected onFilterComplete call with result: " + result);
+        public void onReceiveSmsComplete(int result) {
+            Rlog.e(TAG, "Unexpected onReceiveSmsComplete call with result: " + result);
         }
 
         @Override
@@ -658,7 +784,8 @@
         switch (result) {
             case CarrierMessagingService.SEND_STATUS_OK:
                 Rlog.d(TAG, "processSendMultipartSmsResponse: Sending SMS by "
-                        + "CarrierMessagingService succeeded. id: " + trackers[0].mMessageId);
+                        + "CarrierMessagingService succeeded. "
+                        + SmsController.formatCrossStackMessageId(trackers[0].mMessageId));
                 // Sending a multi-part SMS by CarrierMessagingService successfully completed.
                 // Send EVENT_SEND_SMS_COMPLETE for all the parts one by one.
                 for (int i = 0; i < trackers.length; i++) {
@@ -678,7 +805,8 @@
                 break;
             case CarrierMessagingService.SEND_STATUS_ERROR:
                 Rlog.d(TAG, "processSendMultipartSmsResponse: Sending SMS by "
-                        + "CarrierMessagingService failed. id: " + trackers[0].mMessageId);
+                        + "CarrierMessagingService failed. "
+                        + SmsController.formatCrossStackMessageId(trackers[0].mMessageId));
                 // Sending a multi-part SMS by CarrierMessagingService failed.
                 // Send EVENT_SEND_SMS_COMPLETE with GENERIC_FAILURE for all the parts one by one.
                 for (int i = 0; i < trackers.length; i++) {
@@ -699,15 +827,16 @@
                 break;
             case CarrierMessagingService.SEND_STATUS_RETRY_ON_CARRIER_NETWORK:
                 Rlog.d(TAG, "processSendMultipartSmsResponse: Sending SMS by "
-                        + "CarrierMessagingService failed. Retry on carrier network. id: "
-                        + trackers[0].mMessageId);
+                        + "CarrierMessagingService failed. Retry on carrier network. "
+                        + SmsController.formatCrossStackMessageId(trackers[0].mMessageId));
                 // All the parts for a multi-part SMS are handled together for retry. It helps to
                 // check user confirmation once also if needed.
                 sendSubmitPdu(trackers);
                 break;
             default:
                 Rlog.d(TAG, "processSendMultipartSmsResponse: Unknown result " + result
-                        + ". Retry on carrier network. id: " + trackers[0].mMessageId);
+                        + ". Retry on carrier network. "
+                        + SmsController.formatCrossStackMessageId(trackers[0].mMessageId));
                 sendSubmitPdu(trackers);
         }
     }
@@ -757,7 +886,7 @@
         if (ar.exception == null) {
             if (DBG) {
                 Rlog.d(TAG, "SMS send complete. Broadcasting intent: " + sentIntent
-                        + " id: " + tracker.mMessageId);
+                        + " " + SmsController.formatCrossStackMessageId(tracker.mMessageId));
             }
 
             if (tracker.mDeliveryIntent != null) {
@@ -776,7 +905,8 @@
                     tracker.isFromDefaultSmsApplication(mContext));
         } else {
             if (DBG) {
-                Rlog.d(TAG, "SMS send failed id: " + tracker.mMessageId);
+                Rlog.d(TAG, "SMS send failed "
+                        + SmsController.formatCrossStackMessageId(tracker.mMessageId));
             }
 
             int ss = mPhone.getServiceState().getState();
@@ -795,7 +925,7 @@
                         + " mImsRetry=" + tracker.mImsRetry
                         + " mMessageRef=" + tracker.mMessageRef
                         + " SS= " + mPhone.getServiceState().getState()
-                        + " id=" + tracker.mMessageId);
+                        + " " + SmsController.formatCrossStackMessageId(tracker.mMessageId));
             }
 
             // if sms over IMS is not supported on data and voice is not available...
@@ -1160,7 +1290,7 @@
     private boolean sendSmsByCarrierApp(boolean isDataSms, SmsTracker tracker ) {
         String carrierPackage = getCarrierAppPackageName();
         if (carrierPackage != null) {
-            Rlog.d(TAG, "Found carrier package.");
+            Rlog.d(TAG, "Found carrier package " + carrierPackage);
             SmsSender smsSender;
             if (isDataSms) {
                 smsSender = new DataSmsSender(tracker);
@@ -1371,7 +1501,7 @@
 
         String carrierPackage = getCarrierAppPackageName();
         if (carrierPackage != null) {
-            Rlog.d(TAG, "Found carrier package."
+            Rlog.d(TAG, "Found carrier package " + carrierPackage
                     + " id: " + getMultiTrackermessageId(trackers));
             MultipartSmsSender smsSender = new MultipartSmsSender(parts, trackers);
             smsSender.sendSmsByCarrierApp(carrierPackage,
@@ -1724,7 +1854,7 @@
         PackageManager pm = mContext.getPackageManager();
         try {
             ApplicationInfo appInfo = pm.getApplicationInfoAsUser(appPackage, 0,
-                UserHandle.getUserHandleForUid(userId));
+                    UserHandle.of(userId));
             return appInfo.loadSafeLabel(pm);
         } catch (PackageManager.NameNotFoundException e) {
             Rlog.e(TAG, "PackageManager Name Not Found for package " + appPackage);
@@ -1849,7 +1979,7 @@
             mSmsDispatchersController.sendRetrySms(tracker);
         } else {
             Rlog.e(TAG, mSmsDispatchersController + " is null. Retry failed"
-                    + " id: " + tracker.mMessageId);
+                    + " " + SmsController.formatCrossStackMessageId(tracker.mMessageId));
         }
     }
 
@@ -1935,8 +2065,9 @@
 
         private Boolean mIsFromDefaultSmsApplication;
 
-        // SMS anomaly uuid
-        private final UUID mAnomalyUUID = UUID.fromString("43043600-ea7a-44d2-9ae6-a58567ac7886");
+        // SMS anomaly uuid -- unexpected error from RIL
+        private final UUID mAnomalyUnexpectedErrorFromRilUUID =
+                UUID.fromString("43043600-ea7a-44d2-9ae6-a58567ac7886");
 
         private SmsTracker(HashMap<String, Object> data, PendingIntent sentIntent,
                 PendingIntent deliveryIntent, PackageInfo appInfo, String destAddr, String format,
@@ -2145,7 +2276,7 @@
                     mSentIntent.send(context, error, fillIn);
                 } catch (CanceledException ex) {
                     Rlog.e(TAG, "Failed to send result"
-                            + " id: " + mMessageId);
+                            + " " + SmsController.formatCrossStackMessageId(mMessageId));
                 }
             }
             reportAnomaly(error, errorCode);
@@ -2172,8 +2303,9 @@
         private UUID generateUUID(int error, int errorCode) {
             long lerror = error;
             long lerrorCode = errorCode;
-            return new UUID(mAnomalyUUID.getMostSignificantBits(),
-                    mAnomalyUUID.getLeastSignificantBits() + ((lerrorCode << 32) + lerror));
+            return new UUID(mAnomalyUnexpectedErrorFromRilUUID.getMostSignificantBits(),
+                    mAnomalyUnexpectedErrorFromRilUUID.getLeastSignificantBits()
+                            + ((lerrorCode << 32) + lerror));
         }
 
         /**
@@ -2391,7 +2523,7 @@
         if (mSmsDispatchersController != null) {
             return mSmsDispatchersController.isIms();
         } else {
-            Rlog.e(TAG, "mSmsDispatchersController  is null");
+            Rlog.e(TAG, "mSmsDispatchersController is null");
             return false;
         }
     }
@@ -2469,4 +2601,18 @@
             Binder.restoreCallingIdentity(token);
         }
     }
+
+    /**
+     * Dump local logs
+     */
+    public void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) {
+        IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, "  ");
+        pw.println(TAG);
+        pw.increaseIndent();
+        pw.println("mLocalLog:");
+        pw.increaseIndent();
+        mLocalLog.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
index ae8e4e6..25e0ba1 100755
--- a/src/java/com/android/internal/telephony/ServiceStateTracker.java
+++ b/src/java/com/android/internal/telephony/ServiceStateTracker.java
@@ -42,16 +42,13 @@
 import android.os.BaseBundle;
 import android.os.Build;
 import android.os.Handler;
-import android.os.IBinder;
 import android.os.Message;
 import android.os.Parcel;
 import android.os.PersistableBundle;
 import android.os.Registrant;
 import android.os.RegistrantList;
-import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.SystemProperties;
-import android.os.TimestampedValue;
 import android.os.UserHandle;
 import android.os.WorkSource;
 import android.preference.PreferenceManager;
@@ -69,22 +66,18 @@
 import android.telephony.CellIdentityTdscdma;
 import android.telephony.CellIdentityWcdma;
 import android.telephony.CellInfo;
-import android.telephony.CellSignalStrengthLte;
-import android.telephony.CellSignalStrengthNr;
 import android.telephony.DataSpecificRegistrationInfo;
 import android.telephony.NetworkRegistrationInfo;
 import android.telephony.PhysicalChannelConfig;
 import android.telephony.RadioAccessFamily;
 import android.telephony.ServiceState;
 import android.telephony.ServiceState.RilRadioTechnology;
-import android.telephony.SignalStrength;
-import android.telephony.SignalStrengthUpdateRequest;
-import android.telephony.SignalThresholdInfo;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
 import android.telephony.TelephonyManager;
 import android.telephony.VoiceSpecificRegistrationInfo;
+import android.telephony.ims.stub.ImsRegistrationImplBase;
 import android.text.TextUtils;
 import android.util.EventLog;
 import android.util.LocalLog;
@@ -123,20 +116,15 @@
 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.Collections;
 import java.util.Comparator;
 import java.util.HashSet;
-import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Set;
-import java.util.TreeSet;
-import java.util.concurrent.TimeUnit;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
-import java.util.regex.PatternSyntaxException;
 import java.util.stream.Collectors;
 
 /**
@@ -149,9 +137,6 @@
 
     private static final String PROP_FORCE_ROAMING = "telephony.test.forceRoaming";
 
-    private static final long SIGNAL_STRENGTH_REFRESH_THRESHOLD_IN_MS =
-            TimeUnit.SECONDS.toMillis(10);
-
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private CommandsInterface mCi;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -193,10 +178,6 @@
 
     private final Set<Integer> mRadioPowerOffReasons = new HashSet();
 
-    @UnsupportedAppUsage
-    private SignalStrength mSignalStrength;
-    private long mSignalStrengthUpdatedTime;
-
     // TODO - this should not be public, right now used externally GsmConnetion.
     public RestrictedState mRestrictedState;
 
@@ -210,13 +191,6 @@
     @UnsupportedAppUsage
     private boolean mDesiredPowerState;
 
-    /**
-     * By default, strength polling is enabled.  However, if we're
-     * getting unsolicited signal strength updates from the radio, set
-     * value to true and don't bother polling any more.
-     */
-    private boolean mDontPollSignalStrength = false;
-
     @UnsupportedAppUsage
     private RegistrantList mVoiceRoamingOnRegistrants = new RegistrantList();
     @UnsupportedAppUsage
@@ -238,6 +212,7 @@
     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();
 
@@ -245,23 +220,17 @@
     private boolean mPendingRadioPowerOffAfterDataOff = false;
     private int mPendingRadioPowerOffAfterDataOffTag = 0;
 
-    /** Signal strength poll rate. */
-    private static final int POLL_PERIOD_MILLIS = 20 * 1000;
-
     /** Waiting period before recheck gprs and voice registration. */
     public static final int DEFAULT_GPRS_CHECK_PERIOD_MILLIS = 60 * 1000;
 
     /** GSM events */
     protected static final int EVENT_RADIO_STATE_CHANGED                    = 1;
     protected static final int EVENT_NETWORK_STATE_CHANGED                  = 2;
-    protected static final int EVENT_GET_SIGNAL_STRENGTH                    = 3;
     protected static final int EVENT_POLL_STATE_CS_CELLULAR_REGISTRATION    = 4;
     protected static final int EVENT_POLL_STATE_PS_CELLULAR_REGISTRATION    = 5;
     protected static final int EVENT_POLL_STATE_PS_IWLAN_REGISTRATION       = 6;
     protected static final int EVENT_POLL_STATE_OPERATOR                    = 7;
-    protected static final int EVENT_POLL_SIGNAL_STRENGTH                   = 10;
     protected static final int EVENT_NITZ_TIME                              = 11;
-    protected static final int EVENT_SIGNAL_STRENGTH_UPDATE                 = 12;
     protected static final int EVENT_POLL_STATE_NETWORK_SELECTION_MODE      = 14;
     protected static final int EVENT_GET_LOC_DONE                           = 15;
     protected static final int EVENT_SIM_RECORDS_LOADED                     = 16;
@@ -287,7 +256,8 @@
     public    static final int EVENT_ICC_CHANGED                       = 42;
     protected static final int EVENT_GET_CELL_INFO_LIST                = 43;
     protected static final int EVENT_UNSOL_CELL_INFO_LIST              = 44;
-    // Only sent if the IMS state is moving from true -> false
+    // Only sent if the IMS state is moving from true -> false and power off delay for IMS
+    // registration feature is enabled.
     protected static final int EVENT_CHANGE_IMS_STATE                  = 45;
     protected static final int EVENT_IMS_STATE_CHANGED                 = 46;
     protected static final int EVENT_IMS_STATE_DONE                    = 47;
@@ -301,9 +271,6 @@
     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;
-    private static final int EVENT_SET_SIGNAL_STRENGTH_UPDATE_REQUEST  = 59;
-    private static final int EVENT_CLEAR_SIGNAL_STRENGTH_UPDATE_REQUEST = 60;
-    private static final int EVENT_ON_DEVICE_IDLE_STATE_CHANGED         = 61;
     // 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;
 
@@ -428,7 +395,6 @@
             Context context = mPhone.getContext();
 
             mPhone.notifyPhoneStateChanged();
-            mPhone.notifyCallForwardingIndicator();
 
             if (!SubscriptionManager.isValidSubscriptionId(mPrevSubId)) {
                 // just went from invalid to valid subId, so notify with current service
@@ -633,24 +599,6 @@
     private final TransportManager mTransportManager;
     private final SparseArray<NetworkRegistrationManager> mRegStateManagers = new SparseArray<>();
 
-    /* list of LTE EARFCNs (E-UTRA Absolute Radio Frequency Channel Number,
-     * Reference: 3GPP TS 36.104 5.4.3)
-     * inclusive ranges for which the lte rsrp boost is applied */
-    private ArrayList<Pair<Integer, Integer>> mEarfcnPairListForRsrpBoost = null;
-    private int mLteRsrpBoost = 0; // offset which is reduced from the rsrp threshold
-                                   // while calculating signal strength level.
-
-    /* Ranges of NR ARFCNs (5G Absolute Radio Frequency Channel Number,
-     * Reference: 3GPP TS 38.104)
-     * inclusive ranges for which the corresponding nr rsrp boost is applied */
-    private ArrayList<Pair<Integer, Integer>> mNrarfcnRangeListForRsrpBoost = null;
-    private int[] mNrRsrpBoost;
-
-    private final Object mRsrpBoostLock = new Object();
-    private static final int INVALID_ARFCN = -1;
-
-    private final List<SignalRequestRecord> mSignalRequestRecords = new ArrayList<>();
-
     /* Last known TAC/LAC */
     private int mLastKnownAreaCode = CellInfo.UNAVAILABLE;
 
@@ -683,7 +631,6 @@
         mOutOfServiceSS.setStateOutOfService();
 
         mUiccController.registerForIccChanged(this, EVENT_ICC_CHANGED, null);
-        mCi.setOnSignalStrengthUpdate(this, EVENT_SIGNAL_STRENGTH_UPDATE, null);
         mCi.registerForCellInfoList(this, EVENT_UNSOL_CELL_INFO_LIST, null);
         mCi.registerForPhysicalChannelConfiguration(this, EVENT_PHYSICAL_CHANNEL_CONFIG, null);
 
@@ -722,7 +669,6 @@
                 enableCellularOnBoot);
 
 
-        setSignalStrengthDefaultValues();
         mPhone.getCarrierActionAgent().registerForCarrierAction(CARRIER_ACTION_SET_RADIO_ENABLED,
                 this, EVENT_RADIO_POWER_FROM_CARRIER, null, false);
 
@@ -791,7 +737,6 @@
         mNewSS.setStateOutOfService();
         mLastCellInfoReqTime = 0;
         mLastCellInfoList = null;
-        mSignalStrength = new SignalStrength();
         mStartedGprsRegCheck = false;
         mReportedGprsNoReg = false;
         mMdn = null;
@@ -801,7 +746,7 @@
         mLastNitzData = null;
         mNitzState.handleNetworkUnavailable();
         mCellIdentity = null;
-        mSignalStrengthUpdatedTime = System.currentTimeMillis();
+        mPhone.getSignalStrengthController().setSignalStrengthDefaultValues();
 
         //cancel any pending pollstate request on voice tech switching
         cancelPollState();
@@ -840,7 +785,7 @@
         // switching between GSM and CDMA phone), because the unsolicited signal strength
         // information might come late or even never come. This will get the accurate signal
         // strength information displayed on the UI.
-        mCi.getSignalStrength(obtainMessage(EVENT_GET_SIGNAL_STRENGTH));
+        mPhone.getSignalStrengthController().getSignalStrengthFromCi();
         sendMessage(obtainMessage(EVENT_PHONE_TYPE_SWITCHED));
 
         logPhoneTypeChange();
@@ -871,7 +816,7 @@
     }
 
     public void dispose() {
-        mCi.unSetOnSignalStrengthUpdate(this);
+        mPhone.getSignalStrengthController().dispose();
         mUiccController.unregisterForIccChanged(this);
         mCi.unregisterForCellInfoList(this);
         mCi.unregisterForPhysicalChannelConfiguration(this);
@@ -897,23 +842,6 @@
         return mLastPhysicalChannelConfigList;
     }
 
-    private SignalStrength mLastSignalStrength = null;
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    protected boolean notifySignalStrength() {
-        boolean notified = false;
-        if (!mSignalStrength.equals(mLastSignalStrength)) {
-            try {
-                mPhone.notifySignalStrength();
-                notified = true;
-                mLastSignalStrength = mSignalStrength;
-            } catch (NullPointerException ex) {
-                loge("updateSignalStrength() Phone already destroyed: " + ex
-                        + "SignalStrength not notified");
-            }
-        }
-        return notified;
-    }
-
     /**
      * Notify all mVoiceRegStateOrRatChangedRegistrants using an
      * AsyncResult in msg.obj where AsyncResult#result contains the
@@ -1356,8 +1284,6 @@
                 mPrevSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
                 mIsSimReady = true;
                 pollStateInternal(false);
-                // Signal strength polling stops when radio is off
-                queueNextSignalStrengthPoll();
                 break;
 
             case EVENT_RADIO_STATE_CHANGED:
@@ -1365,9 +1291,6 @@
                 if(!mPhone.isPhoneTypeGsm() &&
                         mCi.getRadioState() == TelephonyManager.RADIO_POWER_ON) {
                     handleCdmaSubscriptionSource(mCdmaSSM.getCdmaSubscriptionSource());
-
-                    // Signal strength polling stops when radio is off.
-                    queueNextSignalStrengthPoll();
                 }
                 // This will do nothing in the 'radio not available' case
                 setPowerStateToDesired();
@@ -1379,20 +1302,6 @@
                 pollStateInternal(true);
                 break;
 
-            case EVENT_GET_SIGNAL_STRENGTH:
-                // This callback is called when signal strength is polled
-                // all by itself
-
-                if (!(mCi.getRadioState() == TelephonyManager.RADIO_POWER_ON)) {
-                    // Polling will continue when radio turns back on
-                    return;
-                }
-                ar = (AsyncResult) msg.obj;
-                onSignalStrengthResult(ar);
-                queueNextSignalStrengthPoll();
-
-                break;
-
             case EVENT_GET_LOC_DONE:
                 ar = (AsyncResult) msg.obj;
                 if (ar.exception == null) {
@@ -1433,32 +1342,20 @@
                 }
                 break;
 
-            case EVENT_POLL_SIGNAL_STRENGTH:
-                // Just poll signal strength...not part of pollState()
-
-                mCi.getSignalStrength(obtainMessage(EVENT_GET_SIGNAL_STRENGTH));
-                break;
-
-            case EVENT_NITZ_TIME:
+            case EVENT_NITZ_TIME: {
                 ar = (AsyncResult) msg.obj;
 
-                String nitzString = (String)((Object[])ar.result)[0];
-                long nitzReceiveTime = ((Long)((Object[])ar.result)[1]).longValue();
+                Object[] nitzArgs = (Object[])ar.result;
+                String nitzString = (String)nitzArgs[0];
+                long nitzReceiveTimeMs = ((Long)nitzArgs[1]).longValue();
+                long ageMs = 0;
+                if (nitzArgs.length >= 3) {
+                    ageMs = ((Long)nitzArgs[2]).longValue();
+                }
 
-                setTimeFromNITZString(nitzString, nitzReceiveTime);
+                setTimeFromNITZString(nitzString, nitzReceiveTimeMs, ageMs);
                 break;
-
-            case EVENT_SIGNAL_STRENGTH_UPDATE:
-                // This is a notification from CommandsInterface.setOnSignalStrengthUpdate
-
-                ar = (AsyncResult) msg.obj;
-
-                // The radio is telling us about signal strength changes
-                // we don't have to ask it
-                mDontPollSignalStrength = true;
-
-                onSignalStrengthResult(ar);
-                break;
+            }
 
             case EVENT_SIM_RECORDS_LOADED:
                 log("EVENT_SIM_RECORDS_LOADED: what=" + msg.what);
@@ -1768,75 +1665,6 @@
                 pollStateInternal(false);
                 break;
 
-            case EVENT_SET_SIGNAL_STRENGTH_UPDATE_REQUEST: {
-                Pair<SignalRequestRecord, Message> pair =
-                        (Pair<SignalRequestRecord, Message>) msg.obj;
-                SignalRequestRecord record = pair.first;
-                Message onCompleted = pair.second;
-                AsyncResult ret = AsyncResult.forMessage(onCompleted);
-
-                // TODO(b/177956310): Check subId to filter out old request until a better solution
-                boolean dupRequest = mSignalRequestRecords.stream().anyMatch(
-                        srr -> srr.mCallingUid == record.mCallingUid
-                                && srr.mSubId == record.mSubId);
-                if (dupRequest) {
-                    ret.exception = new IllegalStateException(
-                            "setSignalStrengthUpdateRequest called again with same subId");
-                    onCompleted.sendToTarget();
-                    break;
-                }
-
-                try {
-                    record.mRequest.getLiveToken().linkToDeath(record, 0);
-                } catch (RemoteException | NullPointerException ex) {
-                    ret.exception = new IllegalStateException(
-                            "Signal request client is already dead.");
-                    onCompleted.sendToTarget();
-                    break;
-                }
-
-                mSignalRequestRecords.add(record);
-                updateAlwaysReportSignalStrength();
-                updateReportingCriteria(getCarrierConfig());
-
-                onCompleted.sendToTarget();
-
-                // Always poll signal strength after setting the update request which has waken up
-                // modem if it was idle. An additional signal strength polling is almost cost free.
-                obtainMessage(EVENT_POLL_SIGNAL_STRENGTH).sendToTarget();
-                break;
-            }
-
-            case EVENT_CLEAR_SIGNAL_STRENGTH_UPDATE_REQUEST: {
-                Pair<SignalRequestRecord, Message> pair =
-                        (Pair<SignalRequestRecord, Message>) msg.obj;
-                SignalRequestRecord record = pair.first;
-                Message onCompleted = pair.second;
-
-                // for loop with removal may cause ConcurrentModificationException
-                Iterator<SignalRequestRecord> it = mSignalRequestRecords.iterator();
-                while (it.hasNext()) {
-                    SignalRequestRecord srr = it.next();
-                    if (srr.mRequest.getLiveToken().equals(record.mRequest.getLiveToken())) {
-                        it.remove();
-                    }
-                }
-
-                updateAlwaysReportSignalStrength();
-                updateReportingCriteria(getCarrierConfig());
-
-                if (onCompleted != null) {
-                    AsyncResult ret = AsyncResult.forMessage(onCompleted);
-                    onCompleted.sendToTarget();
-                }
-                break;
-            }
-
-            case EVENT_ON_DEVICE_IDLE_STATE_CHANGED: {
-                updateReportingCriteria(getCarrierConfig());
-                break;
-            }
-
             case EVENT_POWER_OFF_RADIO_IMS_DEREG_TIMEOUT: {
                 if (DBG) log("EVENT_POWER_OFF_RADIO_IMS_DEREG_TIMEOUT triggered");
                 powerOffRadioSafely();
@@ -2382,7 +2210,6 @@
                 int serviceState = regCodeToServiceState(registrationState);
                 int newDataRat = ServiceState.networkTypeToRilRadioTechnology(
                         networkRegState.getAccessNetworkTechnology());
-                boolean nrHasChanged = false;
 
                 if (DBG) {
                     log("handlePollStateResultMessage: PS cellular. " + networkRegState);
@@ -2394,23 +2221,10 @@
                 if (serviceState == ServiceState.STATE_OUT_OF_SERVICE) {
                     mLastPhysicalChannelConfigList = null;
                 }
-                nrHasChanged |= updateNrFrequencyRangeFromPhysicalChannelConfigs(
-                        mLastPhysicalChannelConfigList, mNewSS);
-                nrHasChanged |= updateNrStateFromPhysicalChannelConfigs(
-                        mLastPhysicalChannelConfigList, mNewSS);
-                setPhyCellInfoFromCellIdentity(mNewSS, networkRegState.getCellIdentity());
-
-                if (nrHasChanged) {
-                    TelephonyMetrics.getInstance().writeServiceStateChanged(
-                            mPhone.getPhoneId(), mSS);
-                    mPhone.getVoiceCallSessionStats().onServiceStateChanged(mSS);
-                    mServiceStateStats.onServiceStateChanged(mSS);
-                }
 
                 mPSEmergencyOnly = networkRegState.isEmergencyEnabled();
                 mEmergencyOnly = (mCSEmergencyOnly || mPSEmergencyOnly);
                 if (mPhone.isPhoneTypeGsm()) {
-
                     mNewReasonDataDenied = networkRegState.getRejectCause();
                     mNewMaxDataCalls = dataSpecificStates.maxDataCalls;
                     mGsmDataRoaming = regCodeIsRoaming(registrationState);
@@ -2438,7 +2252,7 @@
                             && ServiceState.isPsOnlyTech(newDataRat))
                             || (ServiceState.isPsOnlyTech(oldDataRAT)
                             && ServiceState.isCdma(newDataRat))) {
-                        mCi.getSignalStrength(obtainMessage(EVENT_GET_SIGNAL_STRENGTH));
+                        mPhone.getSignalStrengthController().getSignalStrengthFromCi();
                     }
 
                     // voice roaming state in done while handling EVENT_POLL_STATE_REGISTRATION_CDMA
@@ -2449,7 +2263,8 @@
                     mNewSS.setDataRoamingFromRegistration(isDataRoaming);
                 }
 
-                updateServiceStateArfcnRsrpBoost(mNewSS, networkRegState.getCellIdentity());
+                mPhone.getSignalStrengthController().updateServiceStateArfcnRsrpBoost(mNewSS,
+                        networkRegState.getCellIdentity());
                 break;
             }
 
@@ -2975,6 +2790,36 @@
             wfcFlightSpnFormat = wfcSpnFormats[flightModeIdx];
         }
 
+        String crossSimSpnFormat = null;
+        if (mPhone.getImsPhone() != null
+                && (mPhone.getImsPhone() != null)
+                && (mPhone.getImsPhone().getImsRegistrationTech()
+                == ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM)) {
+            // In Cros 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);
+            boolean useRootLocale =
+                    bundle.getBoolean(CarrierConfigManager.KEY_WFC_SPN_USE_ROOT_LOCALE);
+
+            String[] crossSimSpnFormats = SubscriptionManager.getResourcesForSubId(
+                    mPhone.getContext(),
+                    mPhone.getSubId(), useRootLocale)
+                    .getStringArray(R.array.crossSimSpnFormats);
+
+            if (crossSimSpnFormatIdx < 0 || crossSimSpnFormatIdx >= crossSimSpnFormats.length) {
+                loge("updateSpnDisplay: KEY_CROSS_SIM_SPN_FORMAT_INT out of bounds: "
+                        + crossSimSpnFormatIdx);
+                crossSimSpnFormatIdx = 0;
+            }
+            crossSimSpnFormat = crossSimSpnFormats[crossSimSpnFormatIdx];
+        }
+
         if (mPhone.isPhoneTypeGsm()) {
             // The values of plmn/showPlmn change in different scenarios.
             // 1) No service but emergency call allowed -> expected
@@ -3042,9 +2887,27 @@
                     && ((rule & CARRIER_NAME_DISPLAY_BITMASK_SHOW_SPN)
                     == CARRIER_NAME_DISPLAY_BITMASK_SHOW_SPN);
             if (DBG) log("updateSpnDisplay: rawSpn = " + spn);
-
-            if (!TextUtils.isEmpty(spn) && !TextUtils.isEmpty(wfcVoiceSpnFormat) &&
-                    !TextUtils.isEmpty(wfcDataSpnFormat)) {
+            if (!TextUtils.isEmpty(crossSimSpnFormat)) {
+                if (!TextUtils.isEmpty(spn)) {
+                    // Show SPN + Cross-SIM Calling If SIM has SPN and SPN display condition
+                    // is satisfied or SPN override is enabled for this carrier.
+                    String originalSpn = spn.trim();
+                    spn = String.format(crossSimSpnFormat, originalSpn);
+                    dataSpn = spn;
+                    showSpn = true;
+                    showPlmn = false;
+                } 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(
+                            CarrierConfigManager.KEY_WFC_CARRIER_NAME_OVERRIDE_BY_PNN_BOOL)) {
+                        originalPlmn = mIccRecords.getPnnHomeName();
+                    }
+                    plmn = String.format(crossSimSpnFormat, originalPlmn);
+                }
+            } else if (!TextUtils.isEmpty(spn) && !TextUtils.isEmpty(wfcVoiceSpnFormat)
+                    && !TextUtils.isEmpty(wfcDataSpnFormat)) {
                 // Show SPN + Wi-Fi Calling If SIM has SPN and SPN display condition
                 // is satisfied or SPN override is enabled for this carrier.
 
@@ -3334,7 +3197,14 @@
 
         if (mImsRegistrationOnOff && !registered) {
             // moving to deregistered, only send this event if we need to re-evaluate
-            sendMessage(obtainMessage(EVENT_CHANGE_IMS_STATE));
+            if (getRadioPowerOffDelayTimeoutForImsRegistration() > 0) {
+                // only send this event if the power off delay for IMS deregistration feature is
+                // enabled.
+                sendMessage(obtainMessage(EVENT_CHANGE_IMS_STATE));
+            } else {
+                log("setImsRegistrationState: EVENT_CHANGE_IMS_STATE not sent because power off "
+                        + "delay for IMS deregistration is not enabled.");
+            }
         }
         mImsRegistrationOnOff = registered;
     }
@@ -3369,7 +3239,7 @@
         switch (mCi.getRadioState()) {
             case TelephonyManager.RADIO_POWER_UNAVAILABLE:
                 mNewSS.setStateOutOfService();
-                setSignalStrengthDefaultValues();
+                mPhone.getSignalStrengthController().setSignalStrengthDefaultValues();
                 mLastNitzData = null;
                 mNitzState.handleNetworkUnavailable();
                 pollStateDone();
@@ -3377,7 +3247,7 @@
 
             case TelephonyManager.RADIO_POWER_OFF:
                 mNewSS.setStateOff();
-                setSignalStrengthDefaultValues();
+                mPhone.getSignalStrengthController().setSignalStrengthDefaultValues();
                 mLastNitzData = null;
                 mNitzState.handleNetworkUnavailable();
                 // don't poll when device is shutting down or the poll was not modemTrigged
@@ -3468,6 +3338,12 @@
             mPhone.mTelephonyTester.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 + "]"
@@ -3601,6 +3477,8 @@
 
         boolean hasCssIndicatorChanged = (mSS.getCssIndicator() != mNewSS.getCssIndicator());
 
+        boolean hasBandwidthChanged = mSS.getCellBandwidths() != mNewSS.getCellBandwidths();
+
         boolean has4gHandoff = false;
         boolean hasMultiApnSupport = false;
         boolean hasLostMultiApnSupport = false;
@@ -3644,6 +3522,7 @@
                     + " hasCssIndicatorChanged = " + hasCssIndicatorChanged
                     + " hasNrFrequencyRangeChanged = " + hasNrFrequencyRangeChanged
                     + " hasNrStateChanged = " + hasNrStateChanged
+                    + " hasBandwidthChanged = " + hasBandwidthChanged
                     + " hasAirplaneModeOnlChanged = " + hasAirplaneModeOnChanged);
         }
 
@@ -3720,6 +3599,10 @@
             mCssIndicatorChangedRegistrants.notifyRegistrants();
         }
 
+        if (hasBandwidthChanged) {
+            mBandwidthChangedRegistrants.notifyRegistrants();
+        }
+
         if (hasRejectCauseChanged) {
             setNotification(CS_REJECT_CAUSE_ENABLED);
         }
@@ -3794,9 +3677,7 @@
             mPhone.getContext().getContentResolver()
                     .insert(getUriForSubscriptionId(mPhone.getSubId()),
                             getContentValuesForServiceState(mSS));
-        }
 
-        if (hasChanged || hasNrStateChanged) {
             TelephonyMetrics.getInstance().writeServiceStateChanged(mPhone.getPhoneId(), mSS);
             mPhone.getVoiceCallSessionStats().onServiceStateChanged(mSS);
             mServiceStateStats.onServiceStateChanged(mSS);
@@ -3817,13 +3698,15 @@
 
         if (hasRilVoiceRadioTechnologyChanged) {
             shouldLogRatChange = true;
-            notifySignalStrength();
+            // TODO(b/178429976): Remove the dependency on SSC. Double check if the SS broadcast
+            // is really needed when CS/PS RAT change.
+            mPhone.getSignalStrengthController().notifySignalStrength();
         }
 
         for (int transport : mTransportManager.getAvailableTransports()) {
             if (hasRilDataRadioTechnologyChanged.get(transport)) {
                 shouldLogRatChange = true;
-                notifySignalStrength();
+                mPhone.getSignalStrengthController().notifySignalStrength();
             }
 
             if (hasDataRegStateChanged.get(transport)
@@ -3854,7 +3737,8 @@
         // because the signal strength might come earlier RAT and radio state
         // changed.
         if (hasAirplaneModeOffChanged) {
-            mCi.getSignalStrength(obtainMessage(EVENT_GET_SIGNAL_STRENGTH));
+            // TODO(b/178429976): Remove the dependency on SSC. This should be done in SSC.
+            mPhone.getSignalStrengthController().getSignalStrengthFromCi();
         }
 
         if (shouldLogAttachedChange) {
@@ -4449,20 +4333,24 @@
     }
 
     /**
-     * nitzReceiveTime is time_t that the NITZ time was posted
+     * Handle the NITZ string from the modem
+     *
+     * @param nitzString NITZ time string in the form "yy/mm/dd,hh:mm:ss(+/-)tz,dt"
+     * @param nitzReceiveTimeMs time according to {@link android.os.SystemClock#elapsedRealtime()}
+     *        when the RIL sent the NITZ time to the framework
+     * @param ageMs time in milliseconds indicating how long NITZ was cached in RIL and modem
      */
-    private void setTimeFromNITZString(String nitzString, long nitzReceiveTime) {
+    private void setTimeFromNITZString(String nitzString, long nitzReceiveTimeMs, long ageMs) {
         long start = SystemClock.elapsedRealtime();
         if (DBG) {
-            Rlog.d(LOG_TAG, "NITZ: " + nitzString + "," + nitzReceiveTime
-                    + " start=" + start + " delay=" + (start - nitzReceiveTime));
+            Rlog.d(LOG_TAG, "NITZ: " + nitzString + "," + nitzReceiveTimeMs + ", ageMs=" + ageMs
+                    + " start=" + start + " delay=" + (start - nitzReceiveTimeMs));
         }
         NitzData newNitzData = NitzData.parse(nitzString);
         mLastNitzData = newNitzData;
         if (newNitzData != null) {
             try {
-                TimestampedValue<NitzData> nitzSignal =
-                        new TimestampedValue<>(nitzReceiveTime, newNitzData);
+                NitzSignal nitzSignal = new NitzSignal(nitzReceiveTimeMs, newNitzData, ageMs);
                 mNitzState.handleNitzReceived(nitzSignal);
             } finally {
                 if (DBG) {
@@ -4708,31 +4596,6 @@
         }
     }
 
-    private void queueNextSignalStrengthPoll() {
-        if (mDontPollSignalStrength) {
-            // The radio is telling us about signal strength changes
-            // we don't have to ask it
-            return;
-        }
-
-        // if there is no SIM present, do not poll signal strength
-        UiccCard uiccCard = UiccController.getInstance().getUiccCard(getPhoneId());
-        if (uiccCard == null || uiccCard.getCardState() == CardState.CARDSTATE_ABSENT) {
-            log("Not polling signal strength due to absence of SIM");
-            return;
-        }
-
-        Message msg;
-
-        msg = obtainMessage();
-        msg.what = EVENT_POLL_SIGNAL_STRENGTH;
-
-        long nextTime;
-
-        // TODO Don't poll signal strength if screen is off
-        sendMessageDelayed(msg, POLL_PERIOD_MILLIS);
-    }
-
     private void notifyCdmaSubscriptionInfoReady() {
         if (mCdmaForSubscriptionInfoReadyRegistrants != null) {
             if (DBG) log("CDMA_SUBSCRIPTION: call notifyRegistrants()");
@@ -5039,8 +4902,8 @@
                     msg.arg1 = ++mPendingRadioPowerOffAfterDataOffTag;
                     if (sendMessageDelayed(msg, 30000)) {
                         if (DBG) {
-                            log("powerOffRadioSafely: Wait up to 30s for data to disconnect, "
-                                    + "then turn off radio.");
+                            log("powerOffRadioSafely: Wait up to 30s for data to isconnect, then"
+                                    + " turn off radio.");
                         }
                         mPendingRadioPowerOffAfterDataOff = true;
                     } else {
@@ -5072,76 +4935,6 @@
         }
     }
 
-    /**
-     * Checks if the provided earfcn falls withing the range of earfcns.
-     *
-     * return int index in earfcnPairList if earfcn falls within the provided range; -1 otherwise.
-     */
-    private int containsEarfcnInEarfcnRange(ArrayList<Pair<Integer, Integer>> earfcnPairList,
-            int earfcn) {
-        int index = 0;
-        if (earfcnPairList != null) {
-            for (Pair<Integer, Integer> earfcnPair : earfcnPairList) {
-                if ((earfcn >= earfcnPair.first) && (earfcn <= earfcnPair.second)) {
-                    return index;
-                }
-                index++;
-            }
-        }
-
-        return -1;
-    }
-
-    /**
-     * Convert the earfcnStringArray to list of pairs.
-     *
-     * Format of the earfcnsList is expected to be {"erafcn1_start-earfcn1_end",
-     * "earfcn2_start-earfcn2_end" ... }
-     */
-    ArrayList<Pair<Integer, Integer>> convertEarfcnStringArrayToPairList(String[] earfcnsList) {
-        ArrayList<Pair<Integer, Integer>> earfcnPairList = new ArrayList<Pair<Integer, Integer>>();
-
-        if (earfcnsList != null) {
-            int earfcnStart;
-            int earfcnEnd;
-            for (int i = 0; i < earfcnsList.length; i++) {
-                try {
-                    String[] earfcns = earfcnsList[i].split("-");
-                    if (earfcns.length != 2) {
-                        if (VDBG) {
-                            log("Invalid earfcn range format");
-                        }
-                        return null;
-                    }
-
-                    earfcnStart = Integer.parseInt(earfcns[0]);
-                    earfcnEnd = Integer.parseInt(earfcns[1]);
-
-                    if (earfcnStart > earfcnEnd) {
-                        if (VDBG) {
-                            log("Invalid earfcn range format");
-                        }
-                        return null;
-                    }
-
-                    earfcnPairList.add(new Pair<Integer, Integer>(earfcnStart, earfcnEnd));
-                } catch (PatternSyntaxException pse) {
-                    if (VDBG) {
-                        log("Invalid earfcn range format");
-                    }
-                    return null;
-                } catch (NumberFormatException nfe) {
-                    if (VDBG) {
-                        log("Invalid earfcn number format");
-                    }
-                    return null;
-                }
-            }
-        }
-
-        return earfcnPairList;
-    }
-
     private void onCarrierConfigChanged() {
         PersistableBundle config = getCarrierConfig();
         log("CarrierConfigChange " + config);
@@ -5152,10 +4945,13 @@
             mCdnr.updateEfForEri(getOperatorNameFromEri());
         }
 
-        updateArfcnLists(config);
-        updateReportingCriteria(config);
+        // TODO(b/178429976): Listen config change in SSC and remove logic here
+        mPhone.getSignalStrengthController().updateArfcnLists(config);
+        mPhone.getSignalStrengthController().updateReportingCriteria(config);
+
         updateOperatorNamePattern(config);
         mCdnr.updateEfFromCarrierConfig(config);
+        mPhone.notifyCallForwardingIndicator();
 
         // Sometimes the network registration information comes before carrier config is ready.
         // For some cases like roaming/non-roaming overriding, we need carrier config. So it's
@@ -5163,139 +4959,6 @@
         pollStateInternal(false);
     }
 
-    private void updateArfcnLists(PersistableBundle config) {
-        synchronized (mRsrpBoostLock) {
-            mLteRsrpBoost = config.getInt(CarrierConfigManager.KEY_LTE_EARFCNS_RSRP_BOOST_INT, 0);
-            String[] earfcnsStringArrayForRsrpBoost = config.getStringArray(
-                    CarrierConfigManager.KEY_BOOSTED_LTE_EARFCNS_STRING_ARRAY);
-            mEarfcnPairListForRsrpBoost = convertEarfcnStringArrayToPairList(
-                    earfcnsStringArrayForRsrpBoost);
-
-            mNrRsrpBoost = config.getIntArray(
-                    CarrierConfigManager.KEY_NRARFCNS_RSRP_BOOST_INT_ARRAY);
-            String[] nrarfcnsStringArrayForRsrpBoost = config.getStringArray(
-                    CarrierConfigManager.KEY_BOOSTED_NRARFCNS_STRING_ARRAY);
-            mNrarfcnRangeListForRsrpBoost = convertEarfcnStringArrayToPairList(
-                    nrarfcnsStringArrayForRsrpBoost);
-
-            if ((mNrRsrpBoost == null && mNrarfcnRangeListForRsrpBoost != null)
-                    || (mNrRsrpBoost != null && mNrarfcnRangeListForRsrpBoost == null)
-                    || (mNrRsrpBoost != null && mNrarfcnRangeListForRsrpBoost != null
-                    && mNrRsrpBoost.length != mNrarfcnRangeListForRsrpBoost.size())) {
-                loge("Invalid parameters for NR RSRP boost");
-                mNrRsrpBoost = null;
-                mNrarfcnRangeListForRsrpBoost = null;
-            }
-        }
-    }
-
-    private void updateReportingCriteria(PersistableBundle config) {
-        int lteMeasurementEnabled = config.getInt(CarrierConfigManager
-                .KEY_PARAMETERS_USED_FOR_LTE_SIGNAL_BAR_INT, CellSignalStrengthLte.USE_RSRP);
-        mPhone.setSignalStrengthReportingCriteria(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRP,
-                config.getIntArray(CarrierConfigManager.KEY_LTE_RSRP_THRESHOLDS_INT_ARRAY),
-                AccessNetworkType.EUTRAN,
-                (lteMeasurementEnabled & CellSignalStrengthLte.USE_RSRP) != 0);
-        mPhone.setSignalStrengthReportingCriteria(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSCP,
-                config.getIntArray(CarrierConfigManager.KEY_WCDMA_RSCP_THRESHOLDS_INT_ARRAY),
-                AccessNetworkType.UTRAN, true);
-        mPhone.setSignalStrengthReportingCriteria(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI,
-                config.getIntArray(CarrierConfigManager.KEY_GSM_RSSI_THRESHOLDS_INT_ARRAY),
-                AccessNetworkType.GERAN, true);
-
-        if (mPhone.getHalVersion().greaterOrEqual(RIL.RADIO_HAL_VERSION_1_5)) {
-            mPhone.setSignalStrengthReportingCriteria(
-                    SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRQ,
-                    config.getIntArray(CarrierConfigManager.KEY_LTE_RSRQ_THRESHOLDS_INT_ARRAY),
-                    AccessNetworkType.EUTRAN,
-                    (lteMeasurementEnabled & CellSignalStrengthLte.USE_RSRQ) != 0);
-            mPhone.setSignalStrengthReportingCriteria(
-                    SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSNR,
-                    config.getIntArray(CarrierConfigManager.KEY_LTE_RSSNR_THRESHOLDS_INT_ARRAY),
-                    AccessNetworkType.EUTRAN,
-                    (lteMeasurementEnabled & CellSignalStrengthLte.USE_RSSNR) != 0);
-
-            int measurementEnabled = config.getInt(CarrierConfigManager
-                    .KEY_PARAMETERS_USE_FOR_5G_NR_SIGNAL_BAR_INT, CellSignalStrengthNr.USE_SSRSRP);
-            mPhone.setSignalStrengthReportingCriteria(
-                    SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRP,
-                    config.getIntArray(CarrierConfigManager.KEY_5G_NR_SSRSRP_THRESHOLDS_INT_ARRAY),
-                    AccessNetworkType.NGRAN,
-                    (measurementEnabled & CellSignalStrengthNr.USE_SSRSRP) != 0);
-            mPhone.setSignalStrengthReportingCriteria(
-                    SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRQ,
-                    config.getIntArray(CarrierConfigManager.KEY_5G_NR_SSRSRQ_THRESHOLDS_INT_ARRAY),
-                    AccessNetworkType.NGRAN,
-                    (measurementEnabled & CellSignalStrengthNr.USE_SSRSRQ) != 0);
-            mPhone.setSignalStrengthReportingCriteria(
-                    SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSSINR,
-                    config.getIntArray(CarrierConfigManager.KEY_5G_NR_SSSINR_THRESHOLDS_INT_ARRAY),
-                    AccessNetworkType.NGRAN,
-                    (measurementEnabled & CellSignalStrengthNr.USE_SSSINR) != 0);
-        }
-    }
-
-    private void updateServiceStateArfcnRsrpBoost(ServiceState serviceState,
-            CellIdentity cellIdentity) {
-        int rsrpBoost = 0;
-        int arfcn;
-
-        synchronized (mRsrpBoostLock) {
-            switch (cellIdentity.getType()) {
-                case CellInfo.TYPE_LTE:
-                    arfcn = ((CellIdentityLte) cellIdentity).getEarfcn();
-                    if (arfcn != INVALID_ARFCN
-                            && containsEarfcnInEarfcnRange(mEarfcnPairListForRsrpBoost,
-                            arfcn) != -1) {
-                        rsrpBoost = mLteRsrpBoost;
-                    }
-                    break;
-                case CellInfo.TYPE_NR:
-                    arfcn = ((CellIdentityNr) cellIdentity).getNrarfcn();
-                    if (arfcn != INVALID_ARFCN) {
-                        int index = containsEarfcnInEarfcnRange(mNrarfcnRangeListForRsrpBoost,
-                                arfcn);
-                        if (index != -1) {
-                            rsrpBoost = mNrRsrpBoost[index];
-                        }
-                    }
-                    break;
-                default:
-                    break;
-            }
-        }
-        serviceState.setArfcnRsrpBoost(rsrpBoost);
-    }
-
-    /**
-     * send signal-strength-changed notification if changed Called both for
-     * solicited and unsolicited signal strength updates
-     *
-     * @return true if the signal strength changed and a notification was sent.
-     */
-    protected boolean onSignalStrengthResult(AsyncResult ar) {
-
-        // This signal is used for both voice and data radio signal so parse
-        // all fields
-        // Under power off, let's suppress valid signal strength report, which is
-        // beneficial to avoid icon flickering.
-        if ((ar.exception == null) && (ar.result != null)
-                && mSS.getState() != ServiceState.STATE_POWER_OFF) {
-            mSignalStrength = (SignalStrength) ar.result;
-
-            PersistableBundle config = getCarrierConfig();
-            mSignalStrength.updateLevel(config, mSS);
-        } else {
-            log("onSignalStrengthResult() Exception from RIL : " + ar.exception);
-            mSignalStrength = new SignalStrength();
-        }
-        mSignalStrengthUpdatedTime = System.currentTimeMillis();
-
-        boolean ssChanged = notifySignalStrength();
-
-        return ssChanged;
-    }
-
     /**
      * Hang up all voice call and turn off radio. Implemented by derived class.
      */
@@ -5419,50 +5082,6 @@
     }
 
     /**
-     * @return signal strength
-     */
-    public SignalStrength getSignalStrength() {
-        if (shouldRefreshSignalStrength()) {
-            log("SST.getSignalStrength() refreshing signal strength.");
-            obtainMessage(EVENT_POLL_SIGNAL_STRENGTH).sendToTarget();
-        }
-        return mSignalStrength;
-    }
-
-    private boolean shouldRefreshSignalStrength() {
-        long curTime = System.currentTimeMillis();
-
-        // If last signal strength is older than 10 seconds, or somehow if curTime is smaller
-        // than mSignalStrengthUpdatedTime (system time update), it's considered stale.
-        boolean isStale = (mSignalStrengthUpdatedTime > curTime)
-                || (curTime - mSignalStrengthUpdatedTime > SIGNAL_STRENGTH_REFRESH_THRESHOLD_IN_MS);
-        if (!isStale) return false;
-
-        List<SubscriptionInfo> subInfoList = SubscriptionController.getInstance()
-                .getActiveSubscriptionInfoList(mPhone.getContext().getOpPackageName(),
-                        mPhone.getContext().getAttributionTag());
-
-        if (!ArrayUtils.isEmpty(subInfoList)) {
-            for (SubscriptionInfo info : subInfoList) {
-                // If we have an active opportunistic subscription whose data is IN_SERVICE,
-                // we need to get signal strength to decide data switching threshold. In this case,
-                // we poll latest signal strength from modem.
-                if (info.isOpportunistic()) {
-                    TelephonyManager tm = TelephonyManager.from(mPhone.getContext())
-                            .createForSubscriptionId(info.getSubscriptionId());
-                    ServiceState ss = tm.getServiceState();
-                    if (ss != null
-                            && ss.getDataRegistrationState() == ServiceState.STATE_IN_SERVICE) {
-                        return true;
-                    }
-                }
-            }
-        }
-
-        return false;
-    }
-
-    /**
      * Registration point for subscription info ready
      * @param h handler to notify
      * @param what what code of message when delivered
@@ -5513,25 +5132,6 @@
         }
     }
 
-    private void dumpEarfcnPairList(PrintWriter pw, ArrayList<Pair<Integer, Integer>> pairList,
-            String name) {
-        pw.print(" " + name + "={");
-        if (pairList != null) {
-            int i = pairList.size();
-            for (Pair<Integer, Integer> earfcnPair : pairList) {
-                pw.print("(");
-                pw.print(earfcnPair.first);
-                pw.print(",");
-                pw.print(earfcnPair.second);
-                pw.print(")");
-                if ((--i) != 0) {
-                    pw.print(",");
-                }
-            }
-        }
-        pw.println("}");
-    }
-
     private void dumpCellInfoList(PrintWriter pw) {
         pw.print(" mLastCellInfoList={");
         if(mLastCellInfoList != null) {
@@ -5557,9 +5157,6 @@
         pw.println(" mPollingContext=" + mPollingContext + " - " +
                 (mPollingContext != null ? mPollingContext[0] : ""));
         pw.println(" mDesiredPowerState=" + mDesiredPowerState);
-        pw.println(" mDontPollSignalStrength=" + mDontPollSignalStrength);
-        pw.println(" mSignalStrength=" + mSignalStrength);
-        pw.println(" mLastSignalStrength=" + mLastSignalStrength);
         pw.println(" mRestrictedState=" + mRestrictedState);
         pw.println(" mPendingRadioPowerOffAfterDataOff=" + mPendingRadioPowerOffAfterDataOff);
         pw.println(" mPendingRadioPowerOffAfterDataOffTag=" + mPendingRadioPowerOffAfterDataOffTag);
@@ -5614,12 +5211,8 @@
         pw.println(" mRadioDisabledByCarrier" + mRadioDisabledByCarrier);
         pw.println(" mDeviceShuttingDown=" + mDeviceShuttingDown);
         pw.println(" mSpnUpdatePending=" + mSpnUpdatePending);
-        pw.println(" mLteRsrpBoost=" + mLteRsrpBoost);
-        pw.println(" mNrRsrpBoost=" + Arrays.toString(mNrRsrpBoost));
         pw.println(" mCellInfoMinIntervalMs=" + mCellInfoMinIntervalMs);
         pw.println(" mEriManager=" + mEriManager);
-        dumpEarfcnPairList(pw, mEarfcnPairListForRsrpBoost, "mEarfcnPairListForRsrpBoost");
-        dumpEarfcnPairList(pw, mNrarfcnRangeListForRsrpBoost, "mNrarfcnRangeListForRsrpBoost");
 
         mLocaleTracker.dump(fd, pw, args);
         IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "    ");
@@ -5817,12 +5410,6 @@
         }
     }
 
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    private void setSignalStrengthDefaultValues() {
-        mSignalStrength = new SignalStrength();
-        mSignalStrengthUpdatedTime = System.currentTimeMillis();
-    }
-
     protected String getHomeOperatorNumeric() {
         String numeric = ((TelephonyManager) mPhone.getContext().
                 getSystemService(Context.TELEPHONY_SERVICE)).
@@ -6171,6 +5758,25 @@
     }
 
     /**
+     * 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.
@@ -6231,153 +5837,6 @@
     }
 
     /**
-     * Set a new request to update the signal strength thresholds.
-     */
-    public void setSignalStrengthUpdateRequest(int subId, int callingUid,
-            SignalStrengthUpdateRequest request, @NonNull Message onCompleted) {
-        SignalRequestRecord record = new SignalRequestRecord(subId, callingUid, request);
-        sendMessage(obtainMessage(EVENT_SET_SIGNAL_STRENGTH_UPDATE_REQUEST,
-                new Pair<SignalRequestRecord, Message>(record, onCompleted)));
-    }
-
-    /**
-     * Clear the previously set request.
-     */
-    public void clearSignalStrengthUpdateRequest(int subId, int callingUid,
-            SignalStrengthUpdateRequest request, @Nullable Message onCompleted) {
-        SignalRequestRecord record = new SignalRequestRecord(subId, callingUid, request);
-        sendMessage(obtainMessage(EVENT_CLEAR_SIGNAL_STRENGTH_UPDATE_REQUEST,
-                new Pair<SignalRequestRecord, Message>(record, onCompleted)));
-    }
-
-    /**
-     * Align all the qualified thresholds set from applications to the {@code systemThresholds}
-     * and consolidate a new thresholds array, follow rules below:
-     * 1. All threshold values (whose interval is guaranteed to be larger than hysteresis) in
-     *    {@code systemThresholds} will keep as it.
-     * 2. Any threshold from apps that has interval less than hysteresis from any threshold in
-     *    {@code systemThresholds} will be removed.
-     * 3. The target thresholds will be {@code systemThresholds} + all qualified thresholds from
-     *    apps, sorted in ascending order.
-     */
-    int[] getConsolidatedSignalThresholds(int ran, int measurement,
-            int[] systemThresholds, int hysteresis) {
-
-        // TreeSet with comparator that will filter element with interval less than hysteresis
-        // from any current element
-        Set<Integer> target = new TreeSet<>((x, y) -> {
-            if (y >= x - hysteresis && y <= x + hysteresis) {
-                return 0;
-            }
-            return Integer.compare(x, y);
-        });
-
-        for (int systemThreshold : systemThresholds) {
-            target.add(systemThreshold);
-        }
-
-        final boolean isDeviceIdle = mPhone.isDeviceIdle();
-        final int curSubId = mPhone.getSubId();
-        // The total number of record is small (10~15 tops). With each request has at most 5
-        // SignalThresholdInfo which has at most 8 thresholds arrays. So the nested loop should
-        // not be a concern here.
-        for (SignalRequestRecord record : mSignalRequestRecords) {
-            if (curSubId != record.mSubId
-                    || (isDeviceIdle && !record.mRequest.isReportingRequestedWhileIdle())) {
-                continue;
-            }
-            for (SignalThresholdInfo info : record.mRequest.getSignalThresholdInfos()) {
-                if (isRanAndSignalMeasurementTypeMatch(ran, measurement, info)) {
-                    for (int appThreshold : info.getThresholds()) {
-                        target.add(appThreshold);
-                    }
-                }
-            }
-        }
-
-        int[] targetArray = new int[target.size()];
-        int i = 0;
-        for (int element : target) {
-            targetArray[i++] = element;
-        }
-        return targetArray;
-    }
-
-    boolean shouldHonorSystemThresholds() {
-        if (!mPhone.isDeviceIdle()) {
-            return true;
-        }
-
-        final int curSubId = mPhone.getSubId();
-        return mSignalRequestRecords.stream().anyMatch(
-                srr -> curSubId == srr.mSubId
-                        && srr.mRequest.isSystemThresholdReportingRequestedWhileIdle());
-    }
-
-    void onDeviceIdleStateChanged(boolean isDeviceIdle) {
-        sendMessage(obtainMessage(EVENT_ON_DEVICE_IDLE_STATE_CHANGED, isDeviceIdle));
-    }
-
-    boolean shouldEnableSignalThresholdForAppRequest(
-            @AccessNetworkConstants.RadioAccessNetworkType int ran,
-            @SignalThresholdInfo.SignalMeasurementType int measurement,
-            int subId,
-            boolean isDeviceIdle) {
-        for (SignalRequestRecord record : mSignalRequestRecords) {
-            if (subId != record.mSubId) {
-                continue;
-            }
-            for (SignalThresholdInfo info : record.mRequest.getSignalThresholdInfos()) {
-                if (isRanAndSignalMeasurementTypeMatch(ran, measurement, info)
-                        && (!isDeviceIdle || isSignalReportRequestedWhileIdle(record.mRequest))) {
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
-    private static boolean isRanAndSignalMeasurementTypeMatch(
-            @AccessNetworkConstants.RadioAccessNetworkType int ran,
-            @SignalThresholdInfo.SignalMeasurementType int measurement,
-            SignalThresholdInfo info) {
-        return ran == info.getRadioAccessNetworkType()
-                && measurement == info.getSignalMeasurementType();
-    }
-
-    private static boolean isSignalReportRequestedWhileIdle(SignalStrengthUpdateRequest request) {
-        return request.isSystemThresholdReportingRequestedWhileIdle()
-                || request.isReportingRequestedWhileIdle();
-    }
-
-    private class SignalRequestRecord implements IBinder.DeathRecipient {
-        final int mSubId; // subId the request originally applied to
-        final int mCallingUid;
-        final SignalStrengthUpdateRequest mRequest;
-
-        SignalRequestRecord(int subId, int uid, @NonNull SignalStrengthUpdateRequest request) {
-            this.mCallingUid = uid;
-            this.mSubId = subId;
-            this.mRequest = request;
-        }
-
-        @Override
-        public void binderDied() {
-            clearSignalStrengthUpdateRequest(mSubId, mCallingUid, mRequest, null /*onCompleted*/);
-        }
-    }
-
-    private void updateAlwaysReportSignalStrength() {
-        final int curSubId = mPhone.getSubId();
-        boolean alwaysReport = mSignalRequestRecords.stream().anyMatch(
-                srr -> srr.mSubId == curSubId && isSignalReportRequestedWhileIdle(srr.mRequest));
-
-        // TODO(b/177924721): TM#setAlwaysReportSignalStrength will be removed and we will not
-        // worry about unset flag which was set by other client.
-        mPhone.setAlwaysReportSignalStrength(alwaysReport);
-    }
-
-    /**
      * Registers for TAC/LAC changed event.
      * @param h handler to notify
      * @param what what code of message when delivered
diff --git a/src/java/com/android/internal/telephony/SignalStrengthController.java b/src/java/com/android/internal/telephony/SignalStrengthController.java
new file mode 100644
index 0000000..37b8d04
--- /dev/null
+++ b/src/java/com/android/internal/telephony/SignalStrengthController.java
@@ -0,0 +1,905 @@
+/*
+ * 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 android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.PersistableBundle;
+import android.os.RemoteException;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.CarrierConfigManager;
+import android.telephony.CellIdentity;
+import android.telephony.CellIdentityLte;
+import android.telephony.CellIdentityNr;
+import android.telephony.CellInfo;
+import android.telephony.CellSignalStrengthLte;
+import android.telephony.CellSignalStrengthNr;
+import android.telephony.ServiceState;
+import android.telephony.SignalStrength;
+import android.telephony.SignalStrengthUpdateRequest;
+import android.telephony.SignalThresholdInfo;
+import android.telephony.SubscriptionInfo;
+import android.telephony.TelephonyManager;
+import android.util.Pair;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.util.ArrayUtils;
+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.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.PatternSyntaxException;
+
+/**
+ * SignalStrengthController handles signal polling request and unsolicited signal strength update.
+ */
+public class SignalStrengthController extends Handler {
+    private static final boolean DBG = false; /* STOPSHIP if true */
+    private static final String TAG = "SSCtr";
+
+    private static final long SIGNAL_STRENGTH_REFRESH_THRESHOLD_IN_MS =
+            TimeUnit.SECONDS.toMillis(10);
+    /** Signal strength poll rate. */
+    private static final long POLL_PERIOD_MILLIS = TimeUnit.SECONDS.toMillis(20);
+    private static final int INVALID_ARFCN = -1;
+
+    private static final int EVENT_SET_SIGNAL_STRENGTH_UPDATE_REQUEST       = 1;
+    private static final int EVENT_CLEAR_SIGNAL_STRENGTH_UPDATE_REQUEST     = 2;
+    private static final int EVENT_ON_DEVICE_IDLE_STATE_CHANGED             = 3;
+    private static final int EVENT_RIL_CONNECTED                            = 4;
+    private static final int EVENT_RADIO_AVAILABLE                          = 5;
+    private static final int EVENT_GET_SIGNAL_STRENGTH                      = 6;
+    private static final int EVENT_POLL_SIGNAL_STRENGTH                     = 7;
+    private static final int EVENT_SIGNAL_STRENGTH_UPDATE                   = 8;
+
+    private final Phone mPhone;
+    private final CommandsInterface mCi;
+
+    @NonNull
+    private SignalStrength mSignalStrength;
+    private long mSignalStrengthUpdatedTime;
+    private SignalStrength mLastSignalStrength = null;
+
+    /**
+     * List of LTE EARFCNs (E-UTRAN Absolute Radio Frequency Channel Number,
+     * Reference: 3GPP TS 36.104 5.4.3)
+     * inclusive ranges for which the lte rsrp boost is applied
+     */
+    private ArrayList<Pair<Integer, Integer>> mEarfcnPairListForRsrpBoost = null;
+    /**
+     * Offset which is reduced from the rsrp threshold while calculating signal strength level.
+     */
+    private int mLteRsrpBoost = 0;
+    /**
+     * Ranges of NR ARFCNs (5G Absolute Radio Frequency Channel Number,
+     * Reference: 3GPP TS 38.104)
+     * inclusive ranges for which the corresponding nr rsrp boost is applied
+     */
+    private ArrayList<Pair<Integer, Integer>> mNrarfcnRangeListForRsrpBoost = null;
+    @Nullable
+    private int[] mNrRsrpBoost = null;
+    private final Object mRsrpBoostLock = new Object();
+
+    private final List<SignalRequestRecord> mSignalRequestRecords = new ArrayList<>();
+
+    public SignalStrengthController(Phone phone) {
+        mPhone = phone;
+        mCi = mPhone.mCi;
+
+        mCi.registerForRilConnected(this, EVENT_RIL_CONNECTED, null);
+        mCi.registerForAvailable(this, EVENT_RADIO_AVAILABLE, null);
+        mCi.setOnSignalStrengthUpdate(this, EVENT_SIGNAL_STRENGTH_UPDATE, null);
+        setSignalStrengthDefaultValues();
+    }
+
+    @Override
+    public void handleMessage(Message msg) {
+        if (DBG) log("received event " + msg.what);
+        AsyncResult ar;
+
+        switch (msg.what) {
+            case EVENT_RIL_CONNECTED: // fall through
+            case EVENT_RADIO_AVAILABLE:
+                onReset();
+                break;
+            case EVENT_SET_SIGNAL_STRENGTH_UPDATE_REQUEST: {
+                Pair<SignalRequestRecord, Message> pair =
+                        (Pair<SignalRequestRecord, Message>) msg.obj;
+                SignalRequestRecord record = pair.first;
+                Message onCompleted = pair.second;
+                AsyncResult ret = AsyncResult.forMessage(onCompleted);
+
+                // TODO(b/177956310): Check subId to filter out old request until a better solution
+                boolean dupRequest = mSignalRequestRecords.stream().anyMatch(
+                        srr -> srr.mCallingUid == record.mCallingUid
+                                && srr.mSubId == record.mSubId);
+                if (dupRequest) {
+                    ret.exception = new IllegalStateException(
+                            "setSignalStrengthUpdateRequest called again with same subId");
+                    onCompleted.sendToTarget();
+                    break;
+                }
+
+                try {
+                    record.mRequest.getLiveToken().linkToDeath(record, 0);
+                } catch (RemoteException | NullPointerException ex) {
+                    ret.exception = new IllegalStateException(
+                            "Signal request client is already dead.");
+                    onCompleted.sendToTarget();
+                    break;
+                }
+
+                mSignalRequestRecords.add(record);
+
+                updateAlwaysReportSignalStrength();
+                updateReportingCriteria(getCarrierConfig());
+
+                onCompleted.sendToTarget();
+                break;
+            }
+
+            case EVENT_CLEAR_SIGNAL_STRENGTH_UPDATE_REQUEST: {
+                Pair<SignalRequestRecord, Message> pair =
+                        (Pair<SignalRequestRecord, Message>) msg.obj;
+                SignalRequestRecord record = pair.first;
+                Message onCompleted = pair.second;
+
+                // for loop with removal may cause ConcurrentModificationException
+                Iterator<SignalRequestRecord> it = mSignalRequestRecords.iterator();
+                while (it.hasNext()) {
+                    SignalRequestRecord srr = it.next();
+                    if (srr.mRequest.getLiveToken().equals(record.mRequest.getLiveToken())) {
+                        it.remove();
+                    }
+                }
+
+                updateAlwaysReportSignalStrength();
+                updateReportingCriteria(getCarrierConfig());
+
+                if (onCompleted != null) {
+                    AsyncResult ret = AsyncResult.forMessage(onCompleted);
+                    onCompleted.sendToTarget();
+                }
+                break;
+            }
+
+            case EVENT_ON_DEVICE_IDLE_STATE_CHANGED: {
+                updateReportingCriteria(getCarrierConfig());
+                break;
+            }
+
+            case EVENT_GET_SIGNAL_STRENGTH: {
+                // This callback is called when signal strength is polled
+                // all by itself
+
+                if (!(mCi.getRadioState() == TelephonyManager.RADIO_POWER_ON)) {
+                    // Polling will continue when radio turns back on
+                    return;
+                }
+                ar = (AsyncResult) msg.obj;
+                onSignalStrengthResult(ar);
+                break;
+            }
+
+            case EVENT_POLL_SIGNAL_STRENGTH: {
+                // Just poll signal strength...not part of pollState()
+
+                mCi.getSignalStrength(obtainMessage(EVENT_GET_SIGNAL_STRENGTH));
+                break;
+            }
+
+            case EVENT_SIGNAL_STRENGTH_UPDATE: {
+                // This is a notification from CommandsInterface.setOnSignalStrengthUpdate
+
+                ar = (AsyncResult) msg.obj;
+                onSignalStrengthResult(ar);
+                break;
+            }
+
+            default:
+                log("Unhandled message with number: " + msg.what);
+                break;
+        }
+    }
+
+    void dispose() {
+        mCi.unSetOnSignalStrengthUpdate(this);
+    }
+
+    /**
+     * Called when RIL is connected during boot up or after modem restart. Set the default criteria
+     * so that modem can start with default state before updated criteria is ready.
+     */
+    private void onReset() {
+        setDefaultSignalStrengthReportingCriteria();
+    }
+
+    void getSignalStrengthFromCi() {
+        mCi.getSignalStrength(obtainMessage(EVENT_GET_SIGNAL_STRENGTH));
+    }
+
+    /**
+     * send signal-strength-changed notification if changed Called both for
+     * solicited and unsolicited signal strength updates
+     *
+     * @return true if the signal strength changed and a notification was sent.
+     */
+    private boolean onSignalStrengthResult(AsyncResult ar) {
+
+        // This signal is used for both voice and data radio signal so parse
+        // all fields
+
+        if ((ar.exception == null) && (ar.result != null)) {
+            mSignalStrength = (SignalStrength) ar.result;
+
+            PersistableBundle config = getCarrierConfig();
+            if (mPhone.getServiceStateTracker() != null) {
+                mSignalStrength.updateLevel(config, mPhone.getServiceStateTracker().mSS);
+            }
+        } else {
+            log("onSignalStrengthResult() Exception from RIL : " + ar.exception);
+            mSignalStrength = new SignalStrength();
+        }
+        mSignalStrengthUpdatedTime = System.currentTimeMillis();
+
+        boolean ssChanged = notifySignalStrength();
+
+        return ssChanged;
+    }
+
+    /**
+     * @return signal strength
+     */
+    public SignalStrength getSignalStrength() {
+        if (shouldRefreshSignalStrength()) {
+            log("getSignalStrength() refreshing signal strength.");
+            obtainMessage(EVENT_POLL_SIGNAL_STRENGTH).sendToTarget();
+        }
+        return mSignalStrength;
+    }
+
+    private boolean shouldRefreshSignalStrength() {
+        long curTime = System.currentTimeMillis();
+
+        // If last signal strength is older than 10 seconds, or somehow if curTime is smaller
+        // than mSignalStrengthUpdatedTime (system time update), it's considered stale.
+        boolean isStale = (mSignalStrengthUpdatedTime > curTime)
+                || (curTime - mSignalStrengthUpdatedTime > SIGNAL_STRENGTH_REFRESH_THRESHOLD_IN_MS);
+        if (!isStale) return false;
+
+        List<SubscriptionInfo> subInfoList = SubscriptionController.getInstance()
+                .getActiveSubscriptionInfoList(mPhone.getContext().getOpPackageName(),
+                        mPhone.getContext().getAttributionTag());
+
+        if (!ArrayUtils.isEmpty(subInfoList)) {
+            for (SubscriptionInfo info : subInfoList) {
+                // If we have an active opportunistic subscription whose data is IN_SERVICE,
+                // we need to get signal strength to decide data switching threshold. In this case,
+                // we poll latest signal strength from modem.
+                if (info.isOpportunistic()) {
+                    TelephonyManager tm = TelephonyManager.from(mPhone.getContext())
+                            .createForSubscriptionId(info.getSubscriptionId());
+                    ServiceState ss = tm.getServiceState();
+                    if (ss != null
+                            && ss.getDataRegistrationState() == ServiceState.STATE_IN_SERVICE) {
+                        return true;
+                    }
+                }
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Update signal strength reporting criteria from the carrier config
+     */
+    @VisibleForTesting
+    public void updateReportingCriteria(PersistableBundle config) {
+        int lteMeasurementEnabled = config.getInt(CarrierConfigManager
+                .KEY_PARAMETERS_USED_FOR_LTE_SIGNAL_BAR_INT, CellSignalStrengthLte.USE_RSRP);
+        mPhone.setSignalStrengthReportingCriteria(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRP,
+                config.getIntArray(CarrierConfigManager.KEY_LTE_RSRP_THRESHOLDS_INT_ARRAY),
+                AccessNetworkConstants.AccessNetworkType.EUTRAN,
+                (lteMeasurementEnabled & CellSignalStrengthLte.USE_RSRP) != 0);
+        mPhone.setSignalStrengthReportingCriteria(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSCP,
+                config.getIntArray(CarrierConfigManager.KEY_WCDMA_RSCP_THRESHOLDS_INT_ARRAY),
+                AccessNetworkConstants.AccessNetworkType.UTRAN, true);
+        mPhone.setSignalStrengthReportingCriteria(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI,
+                config.getIntArray(CarrierConfigManager.KEY_GSM_RSSI_THRESHOLDS_INT_ARRAY),
+                AccessNetworkConstants.AccessNetworkType.GERAN, true);
+
+        if (mPhone.getHalVersion().greaterOrEqual(RIL.RADIO_HAL_VERSION_1_5)) {
+            mPhone.setSignalStrengthReportingCriteria(
+                    SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRQ,
+                    config.getIntArray(CarrierConfigManager.KEY_LTE_RSRQ_THRESHOLDS_INT_ARRAY),
+                    AccessNetworkConstants.AccessNetworkType.EUTRAN,
+                    (lteMeasurementEnabled & CellSignalStrengthLte.USE_RSRQ) != 0);
+            mPhone.setSignalStrengthReportingCriteria(
+                    SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSNR,
+                    config.getIntArray(CarrierConfigManager.KEY_LTE_RSSNR_THRESHOLDS_INT_ARRAY),
+                    AccessNetworkConstants.AccessNetworkType.EUTRAN,
+                    (lteMeasurementEnabled & CellSignalStrengthLte.USE_RSSNR) != 0);
+
+            int measurementEnabled = config.getInt(CarrierConfigManager
+                    .KEY_PARAMETERS_USE_FOR_5G_NR_SIGNAL_BAR_INT, CellSignalStrengthNr.USE_SSRSRP);
+            mPhone.setSignalStrengthReportingCriteria(
+                    SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRP,
+                    config.getIntArray(CarrierConfigManager.KEY_5G_NR_SSRSRP_THRESHOLDS_INT_ARRAY),
+                    AccessNetworkConstants.AccessNetworkType.NGRAN,
+                    (measurementEnabled & CellSignalStrengthNr.USE_SSRSRP) != 0);
+            mPhone.setSignalStrengthReportingCriteria(
+                    SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRQ,
+                    config.getIntArray(CarrierConfigManager.KEY_5G_NR_SSRSRQ_THRESHOLDS_INT_ARRAY),
+                    AccessNetworkConstants.AccessNetworkType.NGRAN,
+                    (measurementEnabled & CellSignalStrengthNr.USE_SSRSRQ) != 0);
+            mPhone.setSignalStrengthReportingCriteria(
+                    SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSSINR,
+                    config.getIntArray(CarrierConfigManager.KEY_5G_NR_SSSINR_THRESHOLDS_INT_ARRAY),
+                    AccessNetworkConstants.AccessNetworkType.NGRAN,
+                    (measurementEnabled & CellSignalStrengthNr.USE_SSSINR) != 0);
+        }
+    }
+
+    private void setDefaultSignalStrengthReportingCriteria() {
+        mPhone.setSignalStrengthReportingCriteria(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI,
+                AccessNetworkThresholds.GERAN, AccessNetworkConstants.AccessNetworkType.GERAN,
+                true);
+        mPhone.setSignalStrengthReportingCriteria(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSCP,
+                AccessNetworkThresholds.UTRAN, AccessNetworkConstants.AccessNetworkType.UTRAN,
+                true);
+        mPhone.setSignalStrengthReportingCriteria(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRP,
+                AccessNetworkThresholds.EUTRAN_RSRP,
+                AccessNetworkConstants.AccessNetworkType.EUTRAN, true);
+        mPhone.setSignalStrengthReportingCriteria(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI,
+                AccessNetworkThresholds.CDMA2000, AccessNetworkConstants.AccessNetworkType.CDMA2000,
+                true);
+        if (mPhone.getHalVersion().greaterOrEqual(RIL.RADIO_HAL_VERSION_1_5)) {
+            mPhone.setSignalStrengthReportingCriteria(
+                    SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRQ,
+                    AccessNetworkThresholds.EUTRAN_RSRQ,
+                    AccessNetworkConstants.AccessNetworkType.EUTRAN, false);
+            mPhone.setSignalStrengthReportingCriteria(
+                    SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSNR,
+                    AccessNetworkThresholds.EUTRAN_RSSNR,
+                    AccessNetworkConstants.AccessNetworkType.EUTRAN, true);
+
+            // Defaultly we only need SSRSRP for NGRAN signal criteria reporting
+            mPhone.setSignalStrengthReportingCriteria(
+                    SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRP,
+                    AccessNetworkThresholds.NGRAN_RSRSRP,
+                    AccessNetworkConstants.AccessNetworkType.NGRAN, true);
+            mPhone.setSignalStrengthReportingCriteria(
+                    SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRQ,
+                    AccessNetworkThresholds.NGRAN_RSRSRQ,
+                    AccessNetworkConstants.AccessNetworkType.NGRAN, false);
+            mPhone.setSignalStrengthReportingCriteria(
+                    SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSSINR,
+                    AccessNetworkThresholds.NGRAN_SSSINR,
+                    AccessNetworkConstants.AccessNetworkType.NGRAN, false);
+        }
+    }
+
+    void setSignalStrengthDefaultValues() {
+        mSignalStrength = new SignalStrength();
+        mSignalStrengthUpdatedTime = System.currentTimeMillis();
+    }
+
+    boolean notifySignalStrength() {
+        boolean notified = false;
+        if (!mSignalStrength.equals(mLastSignalStrength)) {
+            try {
+                mPhone.notifySignalStrength();
+                notified = true;
+                mLastSignalStrength = mSignalStrength;
+            } catch (NullPointerException ex) {
+                log("updateSignalStrength() Phone already destroyed: " + ex
+                        + "SignalStrength not notified");
+            }
+        }
+        return notified;
+    }
+
+    /**
+     * Print the SignalStrengthController states into the given stream.
+     *
+     * @param fd The raw file descriptor that the dump is being sent to.
+     * @param pw A PrintWriter to which the dump is to be set.
+     * @param args Additional arguments to the dump request.
+     */
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
+        ipw.increaseIndent();
+        pw.println("mSignalRequestRecords=" + mSignalRequestRecords);
+        pw.println(" mLastSignalStrength=" + mLastSignalStrength);
+        pw.println(" mSignalStrength=" + mSignalStrength);
+        pw.println(" mLteRsrpBoost=" + mLteRsrpBoost);
+        pw.println(" mNrRsrpBoost=" + Arrays.toString(mNrRsrpBoost));
+        dumpEarfcnPairList(pw, mEarfcnPairListForRsrpBoost, "mEarfcnPairListForRsrpBoost");
+        dumpEarfcnPairList(pw, mNrarfcnRangeListForRsrpBoost, "mNrarfcnRangeListForRsrpBoost");
+        ipw.decreaseIndent();
+        ipw.flush();
+    }
+
+    private void dumpEarfcnPairList(PrintWriter pw, ArrayList<Pair<Integer, Integer>> pairList,
+            String name) {
+        pw.print(" " + name + "={");
+        if (pairList != null) {
+            int i = pairList.size();
+            for (Pair<Integer, Integer> earfcnPair : pairList) {
+                pw.print("(");
+                pw.print(earfcnPair.first);
+                pw.print(",");
+                pw.print(earfcnPair.second);
+                pw.print(")");
+                if ((--i) != 0) {
+                    pw.print(",");
+                }
+            }
+        }
+        pw.println("}");
+    }
+
+    /**
+     * Set a new request to update the signal strength thresholds.
+     */
+    public void setSignalStrengthUpdateRequest(int subId, int callingUid,
+            SignalStrengthUpdateRequest request, @NonNull Message onCompleted) {
+        SignalRequestRecord record = new SignalRequestRecord(subId, callingUid, request);
+        sendMessage(obtainMessage(EVENT_SET_SIGNAL_STRENGTH_UPDATE_REQUEST,
+                new Pair<SignalRequestRecord, Message>(record, onCompleted)));
+    }
+
+    /**
+     * Clear the previously set request.
+     */
+    public void clearSignalStrengthUpdateRequest(int subId, int callingUid,
+            SignalStrengthUpdateRequest request, @Nullable Message onCompleted) {
+        SignalRequestRecord record = new SignalRequestRecord(subId, callingUid, request);
+        sendMessage(obtainMessage(EVENT_CLEAR_SIGNAL_STRENGTH_UPDATE_REQUEST,
+                new Pair<SignalRequestRecord, Message>(record, onCompleted)));
+    }
+
+    /**
+     * Align all the qualified thresholds set from applications to the {@code systemThresholds}
+     * and consolidate a new thresholds array, follow rules below:
+     * 1. All threshold values (whose interval is guaranteed to be larger than hysteresis) in
+     *    {@code systemThresholds} will keep as it.
+     * 2. Any threshold from apps that has interval less than hysteresis from any threshold in
+     *    {@code systemThresholds} will be removed.
+     * 3. The target thresholds will be {@code systemThresholds} + all qualified thresholds from
+     *    apps, sorted in ascending order.
+     */
+    @VisibleForTesting
+    public int[] getConsolidatedSignalThresholds(int ran, int measurement,
+            int[] systemThresholds, int hysteresis) {
+
+        // TreeSet with comparator that will filter element with interval less than hysteresis
+        // from any current element
+        Set<Integer> target = new TreeSet<>((x, y) -> {
+            if (y >= x - hysteresis && y <= x + hysteresis) {
+                return 0;
+            }
+            return Integer.compare(x, y);
+        });
+
+        for (int systemThreshold : systemThresholds) {
+            target.add(systemThreshold);
+        }
+
+        final boolean isDeviceIdle = mPhone.isDeviceIdle();
+        final int curSubId = mPhone.getSubId();
+        // The total number of record is small (10~15 tops). With each request has at most 5
+        // SignalThresholdInfo which has at most 8 thresholds arrays. So the nested loop should
+        // not be a concern here.
+        for (SignalRequestRecord record : mSignalRequestRecords) {
+            if (curSubId != record.mSubId
+                    || (isDeviceIdle && !record.mRequest.isReportingRequestedWhileIdle())) {
+                continue;
+            }
+            for (SignalThresholdInfo info : record.mRequest.getSignalThresholdInfos()) {
+                if (isRanAndSignalMeasurementTypeMatch(ran, measurement, info)) {
+                    for (int appThreshold : info.getThresholds()) {
+                        target.add(appThreshold);
+                    }
+                }
+            }
+        }
+
+        int[] targetArray = new int[target.size()];
+        int i = 0;
+        for (int element : target) {
+            targetArray[i++] = element;
+        }
+        return targetArray;
+    }
+
+    /**
+     * Return true if system thresholds should be honored when consolidating.
+     */
+    @VisibleForTesting
+    public boolean shouldHonorSystemThresholds() {
+        if (!mPhone.isDeviceIdle()) {
+            return true;
+        }
+
+        final int curSubId = mPhone.getSubId();
+        return mSignalRequestRecords.stream().anyMatch(
+                srr -> curSubId == srr.mSubId
+                        && srr.mRequest.isSystemThresholdReportingRequestedWhileIdle());
+    }
+
+    void onDeviceIdleStateChanged(boolean isDeviceIdle) {
+        sendMessage(obtainMessage(EVENT_ON_DEVICE_IDLE_STATE_CHANGED, isDeviceIdle));
+    }
+
+    /**
+     * Return true if signal threshold should be enabled due to the apps requests.
+     */
+    @VisibleForTesting
+    public boolean shouldEnableSignalThresholdForAppRequest(
+            @AccessNetworkConstants.RadioAccessNetworkType int ran,
+            @SignalThresholdInfo.SignalMeasurementType int measurement,
+            int subId,
+            boolean isDeviceIdle) {
+        for (SignalRequestRecord record : mSignalRequestRecords) {
+            if (subId != record.mSubId) {
+                continue;
+            }
+            for (SignalThresholdInfo info : record.mRequest.getSignalThresholdInfos()) {
+                if (isRanAndSignalMeasurementTypeMatch(ran, measurement, info)
+                        && (!isDeviceIdle || isSignalReportRequestedWhileIdle(record.mRequest))) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    private static boolean isRanAndSignalMeasurementTypeMatch(
+            @AccessNetworkConstants.RadioAccessNetworkType int ran,
+            @SignalThresholdInfo.SignalMeasurementType int measurement,
+            SignalThresholdInfo info) {
+        return ran == info.getRadioAccessNetworkType()
+                && measurement == info.getSignalMeasurementType();
+    }
+
+    private static boolean isSignalReportRequestedWhileIdle(SignalStrengthUpdateRequest request) {
+        return request.isSystemThresholdReportingRequestedWhileIdle()
+                || request.isReportingRequestedWhileIdle();
+    }
+
+    /**
+     * Gets the carrier configuration values for a particular subscription.
+     *
+     * @return A {@link PersistableBundle} containing the config for the given subId,
+     *         or default values for an invalid subId.
+     */
+    @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 class SignalRequestRecord implements IBinder.DeathRecipient {
+        final int mSubId; // subId the request originally applied to
+        final int mCallingUid;
+        final SignalStrengthUpdateRequest mRequest;
+
+        SignalRequestRecord(int subId, int uid, @NonNull SignalStrengthUpdateRequest request) {
+            this.mCallingUid = uid;
+            this.mSubId = subId;
+            this.mRequest = request;
+        }
+
+        @Override
+        public void binderDied() {
+            clearSignalStrengthUpdateRequest(mSubId, mCallingUid, mRequest, null /*onCompleted*/);
+        }
+
+        @Override
+        public String toString() {
+            StringBuffer sb = new StringBuffer("SignalRequestRecord {");
+            sb.append("mSubId=").append(mSubId);
+            sb.append(" mCallingUid=").append(mCallingUid);
+            sb.append(" mRequest=").append(mRequest).append("}");
+            return sb.toString();
+        }
+    }
+
+    private void updateAlwaysReportSignalStrength() {
+        final int curSubId = mPhone.getSubId();
+        boolean alwaysReport = mSignalRequestRecords.stream().anyMatch(
+                srr -> srr.mSubId == curSubId && isSignalReportRequestedWhileIdle(srr.mRequest));
+
+        // TODO(b/177924721): TM#setAlwaysReportSignalStrength will be removed and we will not
+        // worry about unset flag which was set by other client.
+        mPhone.setAlwaysReportSignalStrength(alwaysReport);
+    }
+
+    void updateArfcnLists(PersistableBundle config) {
+        synchronized (mRsrpBoostLock) {
+            mLteRsrpBoost = config.getInt(CarrierConfigManager.KEY_LTE_EARFCNS_RSRP_BOOST_INT, 0);
+            String[] earfcnsStringArrayForRsrpBoost = config.getStringArray(
+                    CarrierConfigManager.KEY_BOOSTED_LTE_EARFCNS_STRING_ARRAY);
+            mEarfcnPairListForRsrpBoost = convertEarfcnStringArrayToPairList(
+                    earfcnsStringArrayForRsrpBoost);
+
+            mNrRsrpBoost = config.getIntArray(
+                    CarrierConfigManager.KEY_NRARFCNS_RSRP_BOOST_INT_ARRAY);
+            String[] nrarfcnsStringArrayForRsrpBoost = config.getStringArray(
+                    CarrierConfigManager.KEY_BOOSTED_NRARFCNS_STRING_ARRAY);
+            mNrarfcnRangeListForRsrpBoost = convertEarfcnStringArrayToPairList(
+                    nrarfcnsStringArrayForRsrpBoost);
+
+            if ((mNrRsrpBoost == null && mNrarfcnRangeListForRsrpBoost != null)
+                    || (mNrRsrpBoost != null && mNrarfcnRangeListForRsrpBoost == null)
+                    || (mNrRsrpBoost != null && mNrarfcnRangeListForRsrpBoost != null
+                    && mNrRsrpBoost.length != mNrarfcnRangeListForRsrpBoost.size())) {
+                loge("Invalid parameters for NR RSRP boost");
+                mNrRsrpBoost = null;
+                mNrarfcnRangeListForRsrpBoost = null;
+            }
+        }
+    }
+
+    void updateServiceStateArfcnRsrpBoost(ServiceState serviceState,
+            CellIdentity cellIdentity) {
+        int rsrpBoost = 0;
+        int arfcn;
+
+        synchronized (mRsrpBoostLock) {
+            switch (cellIdentity.getType()) {
+                case CellInfo.TYPE_LTE:
+                    arfcn = ((CellIdentityLte) cellIdentity).getEarfcn();
+                    if (arfcn != INVALID_ARFCN
+                            && containsEarfcnInEarfcnRange(mEarfcnPairListForRsrpBoost,
+                            arfcn) != -1) {
+                        rsrpBoost = mLteRsrpBoost;
+                    }
+                    break;
+                case CellInfo.TYPE_NR:
+                    arfcn = ((CellIdentityNr) cellIdentity).getNrarfcn();
+                    if (arfcn != INVALID_ARFCN) {
+                        int index = containsEarfcnInEarfcnRange(mNrarfcnRangeListForRsrpBoost,
+                                arfcn);
+                        if (index != -1) {
+                            rsrpBoost = mNrRsrpBoost[index];
+                        }
+                    }
+                    break;
+                default:
+                    break;
+            }
+        }
+        serviceState.setArfcnRsrpBoost(rsrpBoost);
+    }
+
+    /**
+     * Checks if the provided earfcn falls within the range of earfcns.
+     *
+     * return int index in earfcnPairList if earfcn falls within the provided range; -1 otherwise.
+     */
+    private static int containsEarfcnInEarfcnRange(ArrayList<Pair<Integer, Integer>> earfcnPairList,
+            int earfcn) {
+        int index = 0;
+        if (earfcnPairList != null) {
+            for (Pair<Integer, Integer> earfcnPair : earfcnPairList) {
+                if ((earfcn >= earfcnPair.first) && (earfcn <= earfcnPair.second)) {
+                    return index;
+                }
+                index++;
+            }
+        }
+
+        return -1;
+    }
+
+    /**
+     * Convert the earfcnStringArray to list of pairs.
+     *
+     * Format of the earfcnsList is expected to be {"erafcn1_start-earfcn1_end",
+     * "earfcn2_start-earfcn2_end" ... }
+     */
+    private static ArrayList<Pair<Integer, Integer>> convertEarfcnStringArrayToPairList(
+            String[] earfcnsList) {
+        ArrayList<Pair<Integer, Integer>> earfcnPairList = new ArrayList<Pair<Integer, Integer>>();
+
+        if (earfcnsList != null) {
+            int earfcnStart;
+            int earfcnEnd;
+            for (int i = 0; i < earfcnsList.length; i++) {
+                try {
+                    String[] earfcns = earfcnsList[i].split("-");
+                    if (earfcns.length != 2) {
+                        if (DBG) {
+                            log("Invalid earfcn range format");
+                        }
+                        return null;
+                    }
+
+                    earfcnStart = Integer.parseInt(earfcns[0]);
+                    earfcnEnd = Integer.parseInt(earfcns[1]);
+
+                    if (earfcnStart > earfcnEnd) {
+                        if (DBG) {
+                            log("Invalid earfcn range format");
+                        }
+                        return null;
+                    }
+
+                    earfcnPairList.add(new Pair<Integer, Integer>(earfcnStart, earfcnEnd));
+                } catch (PatternSyntaxException pse) {
+                    if (DBG) {
+                        log("Invalid earfcn range format");
+                    }
+                    return null;
+                } catch (NumberFormatException nfe) {
+                    if (DBG) {
+                        log("Invalid earfcn number format");
+                    }
+                    return null;
+                }
+            }
+        }
+
+        return earfcnPairList;
+    }
+
+    /**
+     * dBm thresholds that correspond to changes in signal strength indications.
+     */
+    private static final class AccessNetworkThresholds {
+
+        /**
+         * List of dBm thresholds for GERAN {@link AccessNetworkConstants.AccessNetworkType}.
+         *
+         * Calculated from GSM asu level thresholds - TS 27.007 Sec 8.5
+         */
+        public static final int[] GERAN = new int[]{
+                -109,
+                -103,
+                -97,
+                -89,
+        };
+
+        /**
+         * List of default dBm thresholds for UTRAN
+         * {@link AccessNetworkConstants.AccessNetworkType}.
+         *
+         * These thresholds are taken from the WCDMA RSCP defaults in {@link CarrierConfigManager}.
+         * See TS 27.007 Sec 8.69.
+         */
+        public static final int[] UTRAN = new int[]{
+                -114, /* SIGNAL_STRENGTH_POOR */
+                -104, /* SIGNAL_STRENGTH_MODERATE */
+                -94,  /* SIGNAL_STRENGTH_GOOD */
+                -84   /* SIGNAL_STRENGTH_GREAT */
+        };
+
+        /**
+         * List of default dBm RSRP thresholds for EUTRAN
+         * {@link AccessNetworkConstants.AccessNetworkType}.
+         *
+         * These thresholds are taken from the LTE RSRP defaults in {@link CarrierConfigManager}.
+         */
+        public static final int[] EUTRAN_RSRP = new int[]{
+                -128, /* SIGNAL_STRENGTH_POOR */
+                -118, /* SIGNAL_STRENGTH_MODERATE */
+                -108, /* SIGNAL_STRENGTH_GOOD */
+                -98,  /* SIGNAL_STRENGTH_GREAT */
+        };
+
+        /**
+         * List of default dB RSRQ thresholds for EUTRAN
+         * {@link AccessNetworkConstants.AccessNetworkType}.
+         *
+         * These thresholds are taken from the LTE RSRQ defaults in {@link CarrierConfigManager}.
+         */
+        public static final int[] EUTRAN_RSRQ = new int[]{
+                -20,  /* SIGNAL_STRENGTH_POOR */
+                -17,  /* SIGNAL_STRENGTH_MODERATE */
+                -14,  /* SIGNAL_STRENGTH_GOOD */
+                -11   /* SIGNAL_STRENGTH_GREAT */
+        };
+
+        /**
+         * List of default dB RSSNR thresholds for EUTRAN
+         * {@link AccessNetworkConstants.AccessNetworkType}.
+         *
+         * These thresholds are taken from the LTE RSSNR defaults in {@link CarrierConfigManager}.
+         */
+        public static final int[] EUTRAN_RSSNR = new int[]{
+                -3,  /* SIGNAL_STRENGTH_POOR */
+                1,   /* SIGNAL_STRENGTH_MODERATE */
+                5,   /* SIGNAL_STRENGTH_GOOD */
+                13   /* SIGNAL_STRENGTH_GREAT */
+        };
+
+        /**
+         * List of dBm thresholds for CDMA2000 {@link AccessNetworkConstants.AccessNetworkType}.
+         *
+         * These correspond to EVDO level thresholds.
+         */
+        public static final int[] CDMA2000 = new int[]{
+                -105,
+                -90,
+                -75,
+                -65
+        };
+
+        /**
+         * List of dB thresholds for NGRAN {@link AccessNetworkConstants.AccessNetworkType} RSRSRP
+         */
+        public static final int[] NGRAN_RSRSRP = new int[]{
+                -110, /* SIGNAL_STRENGTH_POOR */
+                -90, /* SIGNAL_STRENGTH_MODERATE */
+                -80, /* SIGNAL_STRENGTH_GOOD */
+                -65,  /* SIGNAL_STRENGTH_GREAT */
+        };
+
+        /**
+         * List of dB thresholds for NGRAN {@link AccessNetworkConstants.AccessNetworkType} RSRSRP
+         */
+        public static final int[] NGRAN_RSRSRQ = new int[]{
+                -31, /* SIGNAL_STRENGTH_POOR */
+                -19, /* SIGNAL_STRENGTH_MODERATE */
+                -7, /* SIGNAL_STRENGTH_GOOD */
+                6  /* SIGNAL_STRENGTH_GREAT */
+        };
+
+        /**
+         * List of dB thresholds for NGRAN {@link AccessNetworkConstants.AccessNetworkType} SSSINR
+         */
+        public static final int[] NGRAN_SSSINR = new int[]{
+                -5, /* SIGNAL_STRENGTH_POOR */
+                5, /* SIGNAL_STRENGTH_MODERATE */
+                15, /* SIGNAL_STRENGTH_GOOD */
+                30  /* SIGNAL_STRENGTH_GREAT */
+        };
+    }
+
+    private static void log(String msg) {
+        if (DBG) Rlog.d(TAG, msg);
+    }
+
+    private static void loge(String msg) {
+        Rlog.e(TAG, msg);
+    }
+}
diff --git a/src/java/com/android/internal/telephony/SimIndication.java b/src/java/com/android/internal/telephony/SimIndication.java
new file mode 100644
index 0000000..24e4d47
--- /dev/null
+++ b/src/java/com/android/internal/telephony/SimIndication.java
@@ -0,0 +1,226 @@
+/*
+ * 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 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;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_RESPONSE_SIM_PHONEBOOK_RECORDS_RECEIVED;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_RESPONSE_SIM_STATUS_CHANGED;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_SIM_REFRESH;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_STK_EVENT_NOTIFY;
+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_UICC_APPLICATIONS_ENABLEMENT_CHANGED;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_UICC_SUBSCRIPTION_STATUS_CHANGED;
+
+import android.hardware.radio.sim.IRadioSimIndication;
+import android.os.AsyncResult;
+
+import com.android.internal.telephony.uicc.IccRefreshResponse;
+import com.android.internal.telephony.uicc.ReceivedPhonebookRecords;
+import com.android.internal.telephony.uicc.SimPhonebookRecord;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Interface declaring unsolicited radio indications for SIM APIs.
+ */
+public class SimIndication extends IRadioSimIndication.Stub {
+    private final RIL mRil;
+
+    public SimIndication(RIL ril) {
+        mRil = ril;
+    }
+
+    /**
+     * Indicates when the carrier info to encrypt IMSI is being requested.
+     * @param indicationType Type of radio indication
+     */
+    public void carrierInfoForImsiEncryption(int indicationType) {
+        mRil.processIndication(indicationType);
+
+        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_CARRIER_INFO_IMSI_ENCRYPTION, null);
+
+        mRil.mCarrierInfoForImsiEncryptionRegistrants.notifyRegistrants(
+                new AsyncResult(null, null, null));
+    }
+
+    /**
+     * Indicates when CDMA subscription source changed.
+     * @param indicationType Type of radio indication
+     * @param cdmaSource New CdmaSubscriptionSource
+     */
+    public void cdmaSubscriptionSourceChanged(int indicationType, int cdmaSource) {
+        mRil.processIndication(indicationType);
+
+        int[] response = new int[]{cdmaSource};
+        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_CDMA_SUBSCRIPTION_SOURCE_CHANGED, response);
+
+        mRil.mCdmaSubscriptionChangedRegistrants.notifyRegistrants(
+                new AsyncResult(null, response, null));
+    }
+
+    /**
+     * Indicates when the phonebook is changed.
+     * @param indicationType Type of radio indication
+     */
+    public void simPhonebookChanged(int indicationType) {
+        mRil.processIndication(indicationType);
+
+        if (RIL.RILJ_LOGD) {
+            mRil.unsljLog(RIL_UNSOL_RESPONSE_SIM_PHONEBOOK_CHANGED);
+        }
+
+        mRil.mSimPhonebookChangedRegistrants.notifyRegistrants();
+    }
+
+    /**
+     * Indicates the content of all the used records in the SIM phonebook.
+     * @param indicationType Type of radio indication
+     * @param status Status of PbReceivedStatus
+     * @param records Content of the SIM phonebook records
+     */
+    public void simPhonebookRecordsReceived(int indicationType, byte status,
+            android.hardware.radio.sim.PhonebookRecordInfo[] records) {
+        mRil.processIndication(indicationType);
+
+        List<SimPhonebookRecord> simPhonebookRecords = new ArrayList<>();
+
+        for (android.hardware.radio.sim.PhonebookRecordInfo record : records) {
+            simPhonebookRecords.add(RILUtils.convertHalPhonebookRecordInfo(record));
+        }
+
+        if (RIL.RILJ_LOGD) {
+            mRil.unsljLogRet(RIL_UNSOL_RESPONSE_SIM_PHONEBOOK_RECORDS_RECEIVED,
+                    "status = " + status + " received " + records.length + " records");
+        }
+
+        mRil.mSimPhonebookRecordsReceivedRegistrants.notifyRegistrants(new AsyncResult(
+                null, new ReceivedPhonebookRecords(status, simPhonebookRecords), null));
+    }
+
+    /**
+     * Indicates that file(s) on the SIM have been updated, or the SIM has been reinitialized.
+     * @param indicationType Type of radio indication
+     * @param refreshResult Result of SIM refresh
+     */
+    public void simRefresh(int indicationType,
+            android.hardware.radio.sim.SimRefreshResult refreshResult) {
+        mRil.processIndication(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);
+
+        mRil.mIccRefreshRegistrants.notifyRegistrants(new AsyncResult(null, response, null));
+    }
+
+    /**
+     * Indicates that SIM state changed.
+     * @param indicationType Type of radio indication
+     */
+    public void simStatusChanged(int indicationType) {
+        mRil.processIndication(indicationType);
+
+        if (RIL.RILJ_LOGD) mRil.unsljLog(RIL_UNSOL_RESPONSE_SIM_STATUS_CHANGED);
+
+        mRil.mIccStatusChangedRegistrants.notifyRegistrants();
+    }
+
+    /**
+     * Indicates when SIM notifies applications some event happens.
+     * @param indicationType Type of radio indication
+     * @param cmd SAT/USAT commands or responses sent by ME to SIM or commands handled by ME,
+     *        represented as byte array starting with first byte of response data for command tag.
+     *        Refer to TS 102.223 section 9.4 for command types
+     */
+    public void stkEventNotify(int indicationType, String cmd) {
+        mRil.processIndication(indicationType);
+
+        if (RIL.RILJ_LOGD) mRil.unsljLog(RIL_UNSOL_STK_EVENT_NOTIFY);
+
+        if (mRil.mCatEventRegistrant != null) {
+            mRil.mCatEventRegistrant.notifyRegistrant(new AsyncResult(null, cmd, null));
+        }
+    }
+
+    /**
+     * Indicates when SIM issue a STK proactive command to applications.
+     * @param indicationType Type of radio indication
+     * @param cmd SAT/USAT proactive represented as byte array starting with command tag.
+     *        Refer to TS 102.223 section 9.4 for command types
+     */
+    public void stkProactiveCommand(int indicationType, String cmd) {
+        mRil.processIndication(indicationType);
+
+        if (RIL.RILJ_LOGD) mRil.unsljLog(RIL_UNSOL_STK_PROACTIVE_COMMAND);
+
+        if (mRil.mCatProCmdRegistrant != null) {
+            mRil.mCatProCmdRegistrant.notifyRegistrant(new AsyncResult(null, cmd, null));
+        }
+    }
+
+    /**
+     * Indicates when STK session is terminated by SIM.
+     * @param indicationType Type of radio indication
+     */
+    public void stkSessionEnd(int indicationType) {
+        mRil.processIndication(indicationType);
+
+        if (RIL.RILJ_LOGD) mRil.unsljLog(RIL_UNSOL_STK_SESSION_END);
+
+        if (mRil.mCatSessionEndRegistrant != null) {
+            mRil.mCatSessionEndRegistrant.notifyRegistrant(new AsyncResult(null, null, null));
+        }
+    }
+
+    /**
+     * Indicated when there is a change in subscription status.
+     * @param indicationType Type of radio indication
+     * @param activate false for subscription deactivated, true for subscription activated
+     */
+    public void subscriptionStatusChanged(int indicationType, boolean activate) {
+        mRil.processIndication(indicationType);
+
+        int[] response = new int[]{activate ? 1 : 0};
+
+        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_UICC_SUBSCRIPTION_STATUS_CHANGED, response);
+
+        mRil.mSubscriptionStatusRegistrants.notifyRegistrants(
+                new AsyncResult(null, response, null));
+    }
+
+    /**
+     * Report change of whether uiccApplications are enabled or disabled.
+     * @param indicationType Type of radio indication
+     * @param enabled Whether uiccApplications are enabled or disabled
+     */
+    public void uiccApplicationsEnablementChanged(int indicationType, boolean enabled) {
+        mRil.processIndication(indicationType);
+
+        if (RIL.RILJ_LOGD) {
+            mRil.unsljLogRet(RIL_UNSOL_UICC_APPLICATIONS_ENABLEMENT_CHANGED, enabled);
+        }
+
+        mRil.mUiccApplicationsEnablementRegistrants.notifyResult(enabled);
+    }
+}
diff --git a/src/java/com/android/internal/telephony/SimResponse.java b/src/java/com/android/internal/telephony/SimResponse.java
new file mode 100644
index 0000000..a365b07
--- /dev/null
+++ b/src/java/com/android/internal/telephony/SimResponse.java
@@ -0,0 +1,440 @@
+/*
+ * 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 android.hardware.radio.RadioError;
+import android.hardware.radio.RadioResponseInfo;
+import android.hardware.radio.sim.IRadioSimResponse;
+import android.telephony.CarrierRestrictionRules;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+
+import com.android.internal.telephony.uicc.AdnCapacity;
+import com.android.internal.telephony.uicc.IccCardStatus;
+import com.android.internal.telephony.uicc.IccIoResult;
+
+import java.util.ArrayList;
+
+/**
+ * Interface declaring response functions to solicited radio requests for SIM APIs.
+ */
+public class SimResponse extends IRadioSimResponse.Stub {
+    private final RIL mRil;
+
+    public SimResponse(RIL ril) {
+        mRil = ril;
+    }
+
+    private void responseIccIo(RadioResponseInfo responseInfo,
+            android.hardware.radio.sim.IccIoResult result) {
+        RILRequest rr = mRil.processResponse(responseInfo);
+
+        if (rr != null) {
+            IccIoResult ret = new IccIoResult(result.sw1, result.sw2, result.simResponse);
+            if (responseInfo.error == RadioError.NONE) {
+                RadioResponse.sendMessageResponse(rr.mResult, ret);
+            }
+            mRil.processResponseDone(rr, responseInfo, ret);
+        }
+    }
+
+    /**
+     * Acknowledge the receipt of radio request sent to the vendor. This must be sent only for
+     * radio request which take long time to respond.
+     * For more details, refer https://source.android.com/devices/tech/connect/ril.html
+     * @param serial Serial no. of the request whose acknowledgement is sent.
+     */
+    public void acknowledgeRequest(int serial) {
+        mRil.processRequestAck(serial);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error.
+     * @param enabled whether UICC applications are enabled.
+     */
+    public void areUiccApplicationsEnabledResponse(RadioResponseInfo responseInfo,
+            boolean enabled) {
+        RILRequest rr = mRil.processResponse(responseInfo);
+
+        if (rr != null) {
+            if (responseInfo.error == RadioError.NONE) {
+                RadioResponse.sendMessageResponse(rr.mResult, enabled);
+            }
+            mRil.processResponseDone(rr, responseInfo, enabled);
+        }
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param remainingAttempts Number of retries remaining, must be equal to -1 if unknown.
+     */
+    public void changeIccPin2ForAppResponse(RadioResponseInfo responseInfo, int remainingAttempts) {
+        RadioResponse.responseInts(mRil, responseInfo, remainingAttempts);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param remainingAttempts Number of retries remaining, must be equal to -1 if unknown.
+     */
+    public void changeIccPinForAppResponse(RadioResponseInfo responseInfo, int remainingAttempts) {
+        RadioResponse.responseInts(mRil, responseInfo, remainingAttempts);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error.
+     */
+    public void enableUiccApplicationsResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param carrierRestrictions Carrier restriction information.
+     * @param multiSimPolicy Policy for multi-sim devices.
+     */
+    public void getAllowedCarriersResponse(RadioResponseInfo responseInfo,
+            android.hardware.radio.sim.CarrierRestrictions carrierRestrictions,
+            int multiSimPolicy) {
+        RILRequest rr = mRil.processResponse(responseInfo);
+        if (rr == null) {
+            return;
+        }
+        CarrierRestrictionRules ret;
+        int policy = CarrierRestrictionRules.MULTISIM_POLICY_NONE;
+        if (multiSimPolicy
+                == android.hardware.radio.sim.SimLockMultiSimPolicy.ONE_VALID_SIM_MUST_BE_PRESENT) {
+            policy = CarrierRestrictionRules.MULTISIM_POLICY_ONE_VALID_SIM_MUST_BE_PRESENT;
+        }
+
+        int carrierRestrictionDefault =
+                CarrierRestrictionRules.CARRIER_RESTRICTION_DEFAULT_NOT_ALLOWED;
+        if (carrierRestrictions.priority && !carrierRestrictions.allowedCarriersPrioritized) {
+            carrierRestrictionDefault = CarrierRestrictionRules.CARRIER_RESTRICTION_DEFAULT_ALLOWED;
+        }
+
+        ret = CarrierRestrictionRules.newBuilder()
+                .setAllowedCarriers(RILUtils.convertHalCarrierList(
+                        carrierRestrictions.allowedCarriers))
+                .setExcludedCarriers(RILUtils.convertHalCarrierList(
+                        carrierRestrictions.excludedCarriers))
+                .setDefaultCarrierRestriction(carrierRestrictionDefault)
+                .setMultiSimPolicy(policy)
+                .build();
+
+        if (responseInfo.error == RadioError.NONE) {
+            RadioResponse.sendMessageResponse(rr.mResult, ret);
+        }
+        mRil.processResponseDone(rr, responseInfo, ret);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param mdn MDN if CDMA subscription is available
+     * @param hSid is a comma separated list of H_SID (Home SID) if
+     *        CDMA subscription is available, in decimal format
+     * @param hNid is a comma separated list of H_NID (Home NID) if
+     *        CDMA subscription is available, in decimal format
+     * @param min MIN (10 digits, MIN2+MIN1) if CDMA subscription is available
+     * @param prl PRL version if CDMA subscription is available
+     */
+    public void getCdmaSubscriptionResponse(RadioResponseInfo responseInfo, String mdn,
+            String hSid, String hNid, String min, String prl) {
+        RadioResponse.responseStrings(mRil, responseInfo, mdn, hSid, hNid, min, prl);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param source CDMA subscription source
+     */
+    public void getCdmaSubscriptionSourceResponse(RadioResponseInfo responseInfo, int source) {
+        RadioResponse.responseInts(mRil, responseInfo, source);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param response 0 is the TS 27.007 service class bit vector of services for which the
+     *        specified barring facility is active. "0" means "disabled for all"
+     */
+    public void getFacilityLockForAppResponse(RadioResponseInfo responseInfo, int response) {
+        RadioResponse.responseInts(mRil, responseInfo, response);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param cardStatus ICC card status as defined by CardStatus
+     */
+    public void getIccCardStatusResponse(RadioResponseInfo responseInfo,
+            android.hardware.radio.sim.CardStatus cardStatus) {
+        RILRequest rr = mRil.processResponse(responseInfo);
+
+        if (rr != null) {
+            IccCardStatus iccCardStatus = RILUtils.convertHalCardStatus(cardStatus);
+            mRil.riljLog("responseIccCardStatus: from HIDL: " + iccCardStatus);
+            if (responseInfo.error == RadioError.NONE) {
+                RadioResponse.sendMessageResponse(rr.mResult, iccCardStatus);
+            }
+            mRil.processResponseDone(rr, responseInfo, iccCardStatus);
+        }
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param imsi String containing the IMSI
+     */
+    public void getImsiForAppResponse(RadioResponseInfo responseInfo, String imsi) {
+        RadioResponse.responseString(mRil, responseInfo, imsi);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error.
+     * @param pbCapacity Contains the adn, email, anr capacities in the sim card.
+     */
+    public void getSimPhonebookCapacityResponse(RadioResponseInfo responseInfo,
+            android.hardware.radio.sim.PhonebookCapacity pbCapacity) {
+        AdnCapacity capacity = RILUtils.convertHalPhonebookCapacity(pbCapacity);
+        RILRequest rr = mRil.processResponse(responseInfo);
+        if (rr != null) {
+            if (responseInfo.error == RadioError.NONE) {
+                RadioResponse.sendMessageResponse(rr.mResult, capacity);
+            }
+            mRil.processResponseDone(rr, responseInfo, capacity);
+        }
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error.
+     */
+    public void getSimPhonebookRecordsResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void iccCloseLogicalChannelResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param iccIo ICC IO operation response as defined by IccIoResult
+     */
+    public void iccIoForAppResponse(RadioResponseInfo responseInfo,
+            android.hardware.radio.sim.IccIoResult iccIo) {
+        responseIccIo(responseInfo, iccIo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param channelId session id of the logical channel.
+     * @param selectResponse Contains the select response for the open channel command with one
+     *        byte per integer
+     */
+    public void iccOpenLogicalChannelResponse(RadioResponseInfo responseInfo, int channelId,
+            byte[] selectResponse) {
+        ArrayList<Integer> arr = new ArrayList<>();
+        arr.add(channelId);
+        for (int i = 0; i < selectResponse.length; i++) {
+            arr.add((int) selectResponse[i]);
+        }
+        RadioResponse.responseIntArrayList(mRil, responseInfo, arr);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param iccIo ICC IO operation response as defined by IccIoResult
+     */
+    public void iccTransmitApduBasicChannelResponse(RadioResponseInfo responseInfo,
+            android.hardware.radio.sim.IccIoResult iccIo) {
+        responseIccIo(responseInfo, iccIo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param iccIo ICC IO operation response as defined by IccIoResult
+     */
+    public void iccTransmitApduLogicalChannelResponse(RadioResponseInfo responseInfo,
+            android.hardware.radio.sim.IccIoResult iccIo) {
+        responseIccIo(responseInfo, iccIo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void reportStkServiceIsRunningResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param iccIo ICC IO operation response as defined by IccIoResult
+     */
+    public void requestIccSimAuthenticationResponse(RadioResponseInfo responseInfo,
+            android.hardware.radio.sim.IccIoResult iccIo) {
+        RILRequest rr = mRil.processResponse(responseInfo);
+
+        if (rr != null) {
+            IccIoResult ret = new IccIoResult(iccIo.sw1, iccIo.sw2,
+                    TextUtils.isEmpty(iccIo.simResponse) ? null : iccIo.simResponse);
+            if (responseInfo.error == RadioError.NONE) {
+                RadioResponse.sendMessageResponse(rr.mResult, ret);
+            }
+            mRil.processResponseDone(rr, responseInfo, ret);
+        }
+    }
+
+    /**
+     * This method is deprecated and should not be used.
+     *
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param response response string of the challenge/response algo for ISIM auth in base64 format
+     */
+    public void requestIsimAuthenticationResponse(RadioResponseInfo responseInfo, String response) {
+        // TODO (b/199433581): remove this method
+        throw new RuntimeException("Inexplicable response received for requestIsimAuthentication");
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param commandResponse SAT/USAT response in hexadecimal format
+     *        string starting with first byte of response
+     */
+    public void sendEnvelopeResponse(RadioResponseInfo responseInfo, String commandResponse) {
+        RadioResponse.responseString(mRil, responseInfo, commandResponse);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param iccIo ICC IO operation response as defined by IccIoResult
+     */
+    public void sendEnvelopeWithStatusResponse(RadioResponseInfo responseInfo,
+            android.hardware.radio.sim.IccIoResult iccIo) {
+        responseIccIo(responseInfo, iccIo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void sendTerminalResponseToSimResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void setAllowedCarriersResponse(RadioResponseInfo responseInfo) {
+        int ret = TelephonyManager.SET_CARRIER_RESTRICTION_ERROR;
+        RILRequest rr = mRil.processResponse(responseInfo);
+        if (rr != null) {
+            mRil.riljLog("setAllowedCarriersResponse - error = " + responseInfo.error);
+
+            if (responseInfo.error == RadioError.NONE) {
+                ret = TelephonyManager.SET_CARRIER_RESTRICTION_SUCCESS;
+                RadioResponse.sendMessageResponse(rr.mResult, ret);
+            }
+            mRil.processResponseDone(rr, responseInfo, ret);
+        }
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void setCarrierInfoForImsiEncryptionResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void setCdmaSubscriptionSourceResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param retry 0 is the number of retries remaining, or -1 if unknown
+     */
+    public void setFacilityLockForAppResponse(RadioResponseInfo responseInfo, int retry) {
+        RadioResponse.responseInts(mRil, responseInfo, retry);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void setSimCardPowerResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void setUiccSubscriptionResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param remainingAttempts Number of retries remaining, must be equal to -1 if unknown.
+     */
+    public void supplyIccPin2ForAppResponse(RadioResponseInfo responseInfo, int remainingAttempts) {
+        RadioResponse.responseInts(mRil, responseInfo, remainingAttempts);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param remainingAttempts Number of retries remaining, must be equal to -1 if unknown.
+     */
+    public void supplyIccPinForAppResponse(RadioResponseInfo responseInfo, int remainingAttempts) {
+        RadioResponse.responseInts(mRil, responseInfo, remainingAttempts);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param remainingAttempts Number of retries remaining, must be equal to -1 if unknown.
+     */
+    public void supplyIccPuk2ForAppResponse(RadioResponseInfo responseInfo, int remainingAttempts) {
+        RadioResponse.responseInts(mRil, responseInfo, remainingAttempts);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param remainingAttempts Number of retries remaining, must be equal to -1 if unknown.
+     */
+    public void supplyIccPukForAppResponse(RadioResponseInfo responseInfo, int remainingAttempts) {
+        RadioResponse.responseInts(mRil, responseInfo, remainingAttempts);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param persoType SIM Personalisation type
+     * @param remainingRetries postiive values indicates number of retries remaining,
+     * must be equal to -1 if number of retries is infinite.
+     */
+    public void supplySimDepersonalizationResponse(RadioResponseInfo responseInfo, int persoType,
+            int remainingRetries) {
+        RadioResponse.responseInts(mRil, responseInfo, persoType, remainingRetries);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error.
+     * @param updatedRecordIndex The index of the updated record.
+     */
+    public void updateSimPhonebookRecordsResponse(RadioResponseInfo responseInfo,
+            int updatedRecordIndex) {
+        RadioResponse.responseInts(mRil, responseInfo, updatedRecordIndex);
+    }
+}
diff --git a/src/java/com/android/internal/telephony/SlidingWindowEventCounter.java b/src/java/com/android/internal/telephony/SlidingWindowEventCounter.java
index c7402f3..7540ce6 100644
--- a/src/java/com/android/internal/telephony/SlidingWindowEventCounter.java
+++ b/src/java/com/android/internal/telephony/SlidingWindowEventCounter.java
@@ -17,6 +17,7 @@
 package com.android.internal.telephony;
 
 import android.annotation.IntRange;
+import android.os.SystemClock;
 import android.util.LongArrayQueue;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -50,6 +51,16 @@
      *
      * Returns true if an event has occurred at least mNumOccurrences times within the
      * time span mWindowSizeMillis.
+     */
+    public synchronized boolean addOccurrence() {
+        return addOccurrence(SystemClock.elapsedRealtime());
+    }
+
+    /**
+     * Increments the occurrence counter.
+     *
+     * Returns true if an event has occurred at least mNumOccurrences times within the
+     * time span mWindowSizeMillis.
      * @param timestampMillis
      */
     public synchronized boolean addOccurrence(long timestampMillis) {
diff --git a/src/java/com/android/internal/telephony/SmsBroadcastUndelivered.java b/src/java/com/android/internal/telephony/SmsBroadcastUndelivered.java
index 40a6f03..7c52a42 100644
--- a/src/java/com/android/internal/telephony/SmsBroadcastUndelivered.java
+++ b/src/java/com/android/internal/telephony/SmsBroadcastUndelivered.java
@@ -16,7 +16,6 @@
 
 package com.android.internal.telephony;
 
-import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
@@ -93,12 +92,6 @@
     /** Content resolver to use to access raw table from SmsProvider. */
     private final ContentResolver mResolver;
 
-    /** Handler for 3GPP-format messages (may be null). */
-    private final GsmInboundSmsHandler mGsmInboundSmsHandler;
-
-    /** Handler for 3GPP2-format messages (may be null). */
-    private final CdmaInboundSmsHandler mCdmaInboundSmsHandler;
-
     /** Broadcast receiver that processes the raw table when the user unlocks the phone for the
      *  first time after reboot and the credential-encrypted storage is available.
      */
@@ -121,7 +114,7 @@
 
         @Override
         public void run() {
-            scanRawTable(context, mCdmaInboundSmsHandler, mGsmInboundSmsHandler,
+            scanRawTable(context,
                     System.currentTimeMillis() - getUndeliveredSmsExpirationTime(context));
             InboundSmsHandler.cancelNewMessageNotification(context);
         }
@@ -130,8 +123,7 @@
     public static void initialize(Context context, GsmInboundSmsHandler gsmInboundSmsHandler,
         CdmaInboundSmsHandler cdmaInboundSmsHandler) {
         if (instance == null) {
-            instance = new SmsBroadcastUndelivered(
-                context, gsmInboundSmsHandler, cdmaInboundSmsHandler);
+            instance = new SmsBroadcastUndelivered(context);
         }
 
         // Tell handlers to start processing new messages and transit from the startup state to the
@@ -146,11 +138,8 @@
     }
 
     @UnsupportedAppUsage
-    private SmsBroadcastUndelivered(Context context, GsmInboundSmsHandler gsmInboundSmsHandler,
-            CdmaInboundSmsHandler cdmaInboundSmsHandler) {
+    private SmsBroadcastUndelivered(Context context) {
         mResolver = context.getContentResolver();
-        mGsmInboundSmsHandler = gsmInboundSmsHandler;
-        mCdmaInboundSmsHandler = cdmaInboundSmsHandler;
 
         UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
 
@@ -166,8 +155,7 @@
     /**
      * Scan the raw table for complete SMS messages to broadcast, and old PDUs to delete.
      */
-    static void scanRawTable(Context context, CdmaInboundSmsHandler cdmaInboundSmsHandler,
-            GsmInboundSmsHandler gsmInboundSmsHandler, long oldMessageTimestamp) {
+    static void scanRawTable(Context context, long oldMessageTimestamp) {
         if (DBG) Rlog.d(TAG, "scanning raw table for undelivered messages");
         long startTime = System.nanoTime();
         ContentResolver contentResolver = context.getContentResolver();
@@ -200,7 +188,7 @@
 
                 if (tracker.getMessageCount() == 1) {
                     // deliver single-part message
-                    broadcastSms(tracker, cdmaInboundSmsHandler, gsmInboundSmsHandler);
+                    broadcastSms(tracker);
                 } else {
                     SmsReferenceKey reference = new SmsReferenceKey(tracker);
                     Integer receivedCount = multiPartReceivedCount.get(reference);
@@ -217,7 +205,7 @@
                             // looks like we've got all the pieces; send a single tracker
                             // to state machine which will find the other pieces to broadcast
                             if (DBG) Rlog.d(TAG, "found complete multi-part message");
-                            broadcastSms(tracker, cdmaInboundSmsHandler, gsmInboundSmsHandler);
+                            broadcastSms(tracker);
                             // don't delete this old message until after we broadcast it
                             oldMultiPartMessages.remove(reference);
                         } else {
@@ -227,9 +215,11 @@
                 }
             }
             // Retrieve the phone and phone id, required for metrics
-            Phone phone = getPhone(gsmInboundSmsHandler, cdmaInboundSmsHandler);
-            int phoneId = phone != null ? phone.getPhoneId()
-                    : SubscriptionManager.INVALID_PHONE_INDEX;
+            // TODO don't hardcode to the first phone (phoneId = 0) but this is no worse than
+            //  earlier. Also phoneId for old messages may not be known (messages may be from an
+            //  inactive sub)
+            Phone phone = PhoneFactory.getPhone(0);
+            int phoneId = 0;
 
             // Delete old incomplete message segments
             for (SmsReferenceKey message : oldMultiPartMessages) {
@@ -265,31 +255,24 @@
     }
 
     /**
-     * Retrieve the phone for the GSM or CDMA Inbound SMS handler
-     */
-    @Nullable
-    private static Phone getPhone(GsmInboundSmsHandler gsmInboundSmsHandler,
-            CdmaInboundSmsHandler cdmaInboundSmsHandler) {
-        if (gsmInboundSmsHandler != null) {
-            return gsmInboundSmsHandler.getPhone();
-        } else if (cdmaInboundSmsHandler != null) {
-            return cdmaInboundSmsHandler.getPhone();
-        }
-        return null;
-    }
-
-    /**
      * Send tracker to appropriate (3GPP or 3GPP2) inbound SMS handler for broadcast.
      */
-    private static void broadcastSms(InboundSmsTracker tracker,
-            CdmaInboundSmsHandler cdmaInboundSmsHandler,
-            GsmInboundSmsHandler gsmInboundSmsHandler) {
+    private static void broadcastSms(InboundSmsTracker tracker) {
         InboundSmsHandler handler;
-        if (tracker.is3gpp2()) {
-            handler = cdmaInboundSmsHandler;
-        } else {
-            handler = gsmInboundSmsHandler;
+        int subId = tracker.getSubId();
+        // TODO consider other subs in this subId's group as well
+        int phoneId = SubscriptionController.getInstance().getPhoneId(subId);
+        if (!SubscriptionManager.isValidPhoneId(phoneId)) {
+            Rlog.e(TAG, "broadcastSms: ignoring message; no phone found for subId " + subId);
+            return;
         }
+        Phone phone = PhoneFactory.getPhone(phoneId);
+        if (phone == null) {
+            Rlog.e(TAG, "broadcastSms: ignoring message; no phone found for subId " + subId
+                    + " phoneId " + phoneId);
+            return;
+        }
+        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 3c9fe0a..9f79161 100644
--- a/src/java/com/android/internal/telephony/SmsController.java
+++ b/src/java/com/android/internal/telephony/SmsController.java
@@ -515,6 +515,10 @@
             Uri messageUri, String scAddress, PendingIntent sentIntent,
             PendingIntent deliveryIntent) {
         IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId);
+        if (!getCallingPackage().equals(callingPkg)) {
+            throw new SecurityException("sendStoredText: Package " + callingPkg
+                    + "does not belong to " + Binder.getCallingUid());
+        }
         if (iccSmsIntMgr != null) {
             iccSmsIntMgr.sendStoredText(callingPkg, callingAttributionTag, messageUri, scAddress,
                     sentIntent, deliveryIntent);
@@ -529,6 +533,10 @@
             Uri messageUri, String scAddress, List<PendingIntent> sentIntents,
             List<PendingIntent> deliveryIntents) {
         IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId);
+        if (!getCallingPackage().equals(callingPkg)) {
+            throw new SecurityException("sendStoredMultipartText: Package " + callingPkg
+                    + " does not belong to " + Binder.getCallingUid());
+        }
         if (iccSmsIntMgr != null) {
             iccSmsIntMgr.sendStoredMultipartText(callingPkg, callingAttributionTag, messageUri,
                     scAddress, sentIntents, deliveryIntents);
@@ -841,4 +849,11 @@
             return false;
         }
     }
+
+    /**
+     * Internal API to consistently format the debug log output of the cross-stack message id.
+     */
+    public static String formatCrossStackMessageId(long id) {
+        return "{x-message-id:" + id + "}";
+    }
 }
diff --git a/src/java/com/android/internal/telephony/SmsDispatchersController.java b/src/java/com/android/internal/telephony/SmsDispatchersController.java
index ddaf571..53556ac 100644
--- a/src/java/com/android/internal/telephony/SmsDispatchersController.java
+++ b/src/java/com/android/internal/telephony/SmsDispatchersController.java
@@ -16,8 +16,6 @@
 
 package com.android.internal.telephony;
 
-import static com.android.internal.telephony.IccSmsInterfaceManager.SMS_MESSAGE_PERIOD_NOT_SPECIFIED;
-import static com.android.internal.telephony.IccSmsInterfaceManager.SMS_MESSAGE_PRIORITY_NOT_SPECIFIED;
 import static com.android.internal.telephony.SmsResponse.NO_ERROR_CODE;
 import static com.android.internal.telephony.cdma.sms.BearerData.ERROR_NONE;
 import static com.android.internal.telephony.cdma.sms.BearerData.ERROR_TEMPORARY;
@@ -316,8 +314,7 @@
         // Timer expired. This indicates that device has been in service for
         // PARTIAL_SEGMENT_WAIT_DURATION since waitTimerStart. Delete orphaned message segments
         // older than waitTimerStart.
-        SmsBroadcastUndelivered.scanRawTable(mContext, mCdmaInboundSmsHandler,
-                mGsmInboundSmsHandler, waitTimerStart);
+        SmsBroadcastUndelivered.scanRawTable(mContext, waitTimerStart);
         if (VDBG) {
             logd("handlePartialSegmentTimerExpiry: scanRawTable() done");
         }
@@ -788,9 +785,8 @@
             long messageId) {
         if (mImsSmsDispatcher.isAvailable() || mImsSmsDispatcher.isEmergencySmsSupport(destAddr)) {
             mImsSmsDispatcher.sendText(destAddr, scAddr, text, sentIntent, deliveryIntent,
-                    messageUri, callingPkg, persistMessage, SMS_MESSAGE_PRIORITY_NOT_SPECIFIED,
-                    false /*expectMore*/, SMS_MESSAGE_PERIOD_NOT_SPECIFIED, isForVvm,
-                    messageId);
+                    messageUri, callingPkg, persistMessage, priority, false /*expectMore*/,
+                    validityPeriod, isForVvm, messageId);
         } else {
             if (isCdmaMo()) {
                 mCdmaDispatcher.sendText(destAddr, scAddr, text, sentIntent, deliveryIntent,
@@ -915,10 +911,8 @@
             long messageId) {
         if (mImsSmsDispatcher.isAvailable()) {
             mImsSmsDispatcher.sendMultipartText(destAddr, scAddr, parts, sentIntents,
-                    deliveryIntents, messageUri, callingPkg, persistMessage,
-                    SMS_MESSAGE_PRIORITY_NOT_SPECIFIED,
-                    false /*expectMore*/, SMS_MESSAGE_PERIOD_NOT_SPECIFIED,
-                    messageId);
+                    deliveryIntents, messageUri, callingPkg, persistMessage, priority,
+                    false /*expectMore*/, validityPeriod, messageId);
         } else {
             if (isCdmaMo()) {
                 mCdmaDispatcher.sendMultipartText(destAddr, scAddr, parts, sentIntents,
@@ -1048,6 +1042,13 @@
         }
     }
 
+    /**
+     * Get InboundSmsHandler for the phone.
+     */
+    public InboundSmsHandler getInboundSmsHandler(boolean is3gpp2) {
+        if (is3gpp2) return mCdmaInboundSmsHandler;
+        else return mGsmInboundSmsHandler;
+    }
 
     public interface SmsInjectionCallback {
         void onSmsInjectedResult(int result);
@@ -1056,6 +1057,9 @@
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         mGsmInboundSmsHandler.dump(fd, pw, args);
         mCdmaInboundSmsHandler.dump(fd, pw, args);
+        mGsmDispatcher.dump(fd, pw, args);
+        mCdmaDispatcher.dump(fd, pw, args);
+        mImsSmsDispatcher.dump(fd, pw, args);
     }
 
     private void logd(String msg) {
diff --git a/src/java/com/android/internal/telephony/SmsResponse.java b/src/java/com/android/internal/telephony/SmsResponse.java
index 2c8afca..851d04b 100644
--- a/src/java/com/android/internal/telephony/SmsResponse.java
+++ b/src/java/com/android/internal/telephony/SmsResponse.java
@@ -59,8 +59,7 @@
         String ret = "{ mMessageRef = " + mMessageRef
                         + ", mErrorCode = " + mErrorCode
                 + ", mAckPdu = " + mAckPdu
-                + ", mMessageId = " + mMessageId
-                        + "}";
+                + ", " + SmsController.formatCrossStackMessageId(mMessageId) + "}";
         return ret;
     }
 }
diff --git a/src/java/com/android/internal/telephony/SmsUsageMonitor.java b/src/java/com/android/internal/telephony/SmsUsageMonitor.java
index 7f07d4c..8bcdc07 100644
--- a/src/java/com/android/internal/telephony/SmsUsageMonitor.java
+++ b/src/java/com/android/internal/telephony/SmsUsageMonitor.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.telephony;
 
+import android.app.role.RoleManager;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -52,6 +53,7 @@
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.regex.Pattern;
@@ -126,6 +128,8 @@
     /** Last modified time for pattern file */
     private long mPatternFileLastModified = 0;
 
+    private RoleManager mRoleManager;
+
     /** Directory for per-app SMS permission XML file. */
     private static final String SMS_POLICY_FILE_DIRECTORY = "/data/misc/sms";
 
@@ -249,6 +253,7 @@
     public SmsUsageMonitor(Context context) {
         mContext = context;
         ContentResolver resolver = context.getContentResolver();
+        mRoleManager = (RoleManager) mContext.getSystemService(Context.ROLE_SERVICE);
 
         mMaxAllowed = Settings.Global.getInt(resolver,
                 Settings.Global.SMS_OUTGOING_CHECK_MAX_COUNT,
@@ -346,7 +351,7 @@
     /**
      * Check to see if an application is allowed to send new SMS messages, and confirm with
      * user if the send limit was reached or if a non-system app is potentially sending to a
-     * premium SMS short code or number.
+     * premium SMS short code or number. If the app is the default SMS app, there's no send limit.
      *
      * @param appName the package name of the app requesting to send an SMS
      * @param smsWaiting the number of new messages desired to send
@@ -364,7 +369,12 @@
                 mSmsStamp.put(appName, sentList);
             }
 
-            return isUnderLimit(sentList, smsWaiting);
+            List<String> defaultApp = mRoleManager.getRoleHolders(RoleManager.ROLE_SMS);
+            if (defaultApp.contains(appName)) {
+                return true;
+            } else {
+                return isUnderLimit(sentList, smsWaiting);
+            }
         }
     }
 
@@ -582,16 +592,19 @@
         if (appId == Process.SYSTEM_UID || appId == Process.PHONE_UID || uid == 0) {
             return;
         }
+        // log string should be same in both exception scenarios below, otherwise it can be used to
+        // detect if a package is installed on the device which is a privacy/security issue
+        String errorLog = "Calling uid " + uid + " gave package " + pkg + " which is either "
+                + "unknown or owned by another uid";
         try {
             ApplicationInfo ai = mContext.getPackageManager().getApplicationInfoAsUser(
                     pkg, 0, UserHandle.getUserHandleForUid(uid));
 
-          if (UserHandle.getAppId(ai.uid) != UserHandle.getAppId(uid)) {
-                throw new SecurityException("Calling uid " + uid + " gave package"
-                        + pkg + " which is owned by uid " + ai.uid);
+            if (UserHandle.getAppId(ai.uid) != UserHandle.getAppId(uid)) {
+                throw new SecurityException(errorLog);
             }
         } catch (NameNotFoundException ex) {
-            throw new SecurityException("Unknown package " + pkg + "\n" + ex);
+            throw new SecurityException(errorLog);
         }
     }
 
diff --git a/src/java/com/android/internal/telephony/SubscriptionController.java b/src/java/com/android/internal/telephony/SubscriptionController.java
index ddfe85a..5d2cb24 100644
--- a/src/java/com/android/internal/telephony/SubscriptionController.java
+++ b/src/java/com/android/internal/telephony/SubscriptionController.java
@@ -93,6 +93,7 @@
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 /**
  * Implementation of the ISub interface.
@@ -455,10 +456,10 @@
      * SecurityException.
      */
     private boolean hasSubscriberIdentifierAccess(int subId, String callingPackage,
-            String callingFeatureId, String message) {
+            String callingFeatureId, String message, boolean reportFailure) {
         try {
             return TelephonyPermissions.checkCallingOrSelfReadSubscriberIdentifiers(mContext, subId,
-                    callingPackage, callingFeatureId, message);
+                    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
@@ -769,6 +770,7 @@
                     return subInfo;
                 }
             }
+
             // check cache for opportunistic subscriptions too, before querying db
             for (SubscriptionInfo subInfo : mCacheOpportunisticSubInfoList) {
                 if (subInfo.getSubscriptionId() == subId) {
@@ -910,22 +912,22 @@
 
         // Now that all security checks passes, perform the operation as ourselves.
         final long identity = Binder.clearCallingIdentity();
+        List<SubscriptionInfo> subList;
         try {
-            List<SubscriptionInfo> subList = null;
             subList = getSubInfo(null, null);
-            if (subList != null) {
-                if (VDBG) logd("[getAllSubInfoList]- " + subList.size() + " infos return");
-                subList.stream().map(
-                        subscriptionInfo -> conditionallyRemoveIdentifiers(subscriptionInfo,
-                                callingPackage, callingFeatureId, "getAllSubInfoList"))
-                        .collect(Collectors.toList());
-            } else {
-                if (VDBG) logd("[getAllSubInfoList]- no info return");
-            }
-            return subList;
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
+        if (subList != null) {
+            if (VDBG) logd("[getAllSubInfoList]- " + subList.size() + " infos return");
+            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) {
@@ -1770,17 +1772,26 @@
         // Now that all security checks passes, perform the operation as ourselves.
         final long identity = Binder.clearCallingIdentity();
         try {
-            ContentValues value = new ContentValues(1);
-            value.put(SubscriptionManager.CARRIER_NAME, text);
+            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);
 
-            int result = mContext.getContentResolver().update(
-                    SubscriptionManager.getUriForSubscriptionId(subId), value, null, null);
+                result = mContext.getContentResolver().update(
+                        SubscriptionManager.getUriForSubscriptionId(subId), value, null, null);
 
-            // Refresh the Cache of Active Subscription Info List
-            refreshCachedActiveSubscriptionInfoList();
+                // Refresh the Cache of Active Subscription Info List
+                refreshCachedActiveSubscriptionInfoList();
 
-            notifySubscriptionInfoChanged();
-
+                notifySubscriptionInfoChanged();
+            } else {
+                if (DBG) logd("[setCarrierText]: no value update");
+            }
             return result;
         } finally {
             Binder.restoreCallingIdentity(identity);
@@ -2007,30 +2018,35 @@
         final long identity = Binder.clearCallingIdentity();
         try {
             validateSubId(subId);
-            int result;
+            int result = 0;
             int phoneId = getPhoneId(subId);
 
             if (number == null || phoneId < 0 ||
                     phoneId >= mTelephonyManager.getPhoneCount()) {
-                if (DBG) logd("[setDispalyNumber]- fail");
+                if (DBG) logd("[setDisplayNumber]- fail");
                 return -1;
             }
-            ContentValues value = new ContentValues(1);
-            value.put(SubscriptionManager.NUMBER, number);
+            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);
-
-            // Refresh the Cache of Active Subscription Info List
-            refreshCachedActiveSubscriptionInfoList();
-
-            if (DBG) logd("[setDisplayNumber]- update result :" + result);
-            notifySubscriptionInfoChanged();
-
+                // 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);
@@ -2051,21 +2067,36 @@
             return;
         }
 
-        String formattedEhplmns = ehplmns == null ? "" : String.join(",", ehplmns);
-        String formattedHplmns = hplmns == null ? "" : String.join(",", hplmns);
+        // 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);
 
-        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);
-
-        // Refresh the Cache of Active Subscription Info List
-        refreshCachedActiveSubscriptionInfoList();
-
-        if (DBG) logd("[setAssociatedPlmns]- update result :" + count);
-        notifySubscriptionInfoChanged();
+            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");
+        }
     }
 
     /**
@@ -2265,16 +2296,25 @@
         final long identity = Binder.clearCallingIdentity();
         try {
             validateSubId(subId);
-            ContentValues value = new ContentValues(1);
-            value.put(SubscriptionManager.CARRIER_ID, carrierId);
-            int result = mContext.getContentResolver().update(
-                    SubscriptionManager.getUriForSubscriptionId(subId), value, null, null);
+            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();
+                // Refresh the Cache of Active Subscription Info List
+                refreshCachedActiveSubscriptionInfoList();
 
-            notifySubscriptionInfoChanged();
-
+                notifySubscriptionInfoChanged();
+            } else {
+                if (DBG) logd("[setCarrierId]: no value update");
+            }
             return result;
         } finally {
             Binder.restoreCallingIdentity(identity);
@@ -2298,21 +2338,31 @@
         } catch (NumberFormatException e) {
             loge("[setMccMnc] - couldn't parse mcc/mnc: " + mccMnc);
         }
-        if (DBG) logd("[setMccMnc]+ mcc/mnc:" + mcc + "/" + mnc + " subId:" + subId);
-        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);
+        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);
 
-        int result = mContext.getContentResolver().update(
-                SubscriptionManager.getUriForSubscriptionId(subId), value, null, null);
-
-        // Refresh the Cache of Active Subscription Info List
-        refreshCachedActiveSubscriptionInfoList();
-
-        notifySubscriptionInfoChanged();
-
+            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;
     }
 
@@ -2336,17 +2386,25 @@
      */
     public int setImsi(String imsi, int subId) {
         if (DBG) logd("[setImsi]+ imsi:" + scrubImsi(imsi) + " subId:" + subId);
-        ContentValues value = new ContentValues(1);
-        value.put(SubscriptionManager.IMSI, imsi);
+        boolean update = true;
+        int result = 0;
+        SubscriptionInfo subInfo = getSubscriptionInfo(subId);
+        if (subInfo != null) {
+            update = !TextUtils.equals(getImsiPrivileged(subId),imsi);
+        }
 
-        int result = mContext.getContentResolver().update(
-                SubscriptionManager.getUriForSubscriptionId(subId), value, null, null);
+        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();
 
-        // Refresh the Cache of Active Subscription Info List
-        refreshCachedActiveSubscriptionInfoList();
-
-        notifySubscriptionInfoChanged();
-
+            notifySubscriptionInfoChanged();
+        } else {
+            if (DBG) logd("[setImsi]: no value update");
+        }
         return result;
     }
 
@@ -2440,16 +2498,25 @@
      */
     public int setCountryIso(String iso, int subId) {
         if (DBG) logd("[setCountryIso]+ iso:" + iso + " subId:" + subId);
-        ContentValues value = new ContentValues();
-        value.put(SubscriptionManager.ISO_COUNTRY_CODE, iso);
+        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);
 
-        int result = mContext.getContentResolver().update(
-                SubscriptionManager.getUriForSubscriptionId(subId), value, null, null);
+            result = mContext.getContentResolver().update(
+                    SubscriptionManager.getUriForSubscriptionId(subId), value, null, null);
+            // Refresh the Cache of Active Subscription Info List
+            refreshCachedActiveSubscriptionInfoList();
 
-        // Refresh the Cache of Active Subscription Info List
-        refreshCachedActiveSubscriptionInfoList();
-
-        notifySubscriptionInfoChanged();
+            notifySubscriptionInfoChanged();
+        } else {
+            if (DBG) logd("[setCountryIso]: no value update");
+        }
         return result;
     }
 
@@ -3839,6 +3906,30 @@
 
     }
 
+    /**
+     * 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
@@ -4140,7 +4231,7 @@
             if (canReadPhoneState) {
                 canReadIdentifiers = hasSubscriberIdentifierAccess(
                         SubscriptionManager.INVALID_SUBSCRIPTION_ID, callingPackage,
-                        callingFeatureId, "getSubscriptionInfoList");
+                        callingFeatureId, "getSubscriptionInfoList", false);
                 canReadPhoneNumber = hasPhoneNumberAccess(
                         SubscriptionManager.INVALID_SUBSCRIPTION_ID, callingPackage,
                         callingFeatureId, "getSubscriptionInfoList");
@@ -4192,7 +4283,7 @@
         SubscriptionInfo result = subInfo;
         int subId = subInfo.getSubscriptionId();
         boolean hasIdentifierAccess = hasSubscriberIdentifierAccess(subId, callingPackage,
-                callingFeatureId, message);
+                callingFeatureId, message, true);
         boolean hasPhoneNumberAccess = hasPhoneNumberAccess(subId, callingPackage, callingFeatureId,
                 message);
         return conditionallyRemoveIdentifiers(subInfo, hasIdentifierAccess, hasPhoneNumberAccess);
diff --git a/src/java/com/android/internal/telephony/SubscriptionInfoUpdater.java b/src/java/com/android/internal/telephony/SubscriptionInfoUpdater.java
index baad5dc..d05ef12 100644
--- a/src/java/com/android/internal/telephony/SubscriptionInfoUpdater.java
+++ b/src/java/com/android/internal/telephony/SubscriptionInfoUpdater.java
@@ -419,13 +419,8 @@
         UiccSlot uiccSlot = UiccController.getInstance().getUiccSlotForPhone(phoneId);
         String iccId = (uiccSlot != null) ? IccUtils.stripTrailingFs(uiccSlot.getIccId()) : null;
         if (!TextUtils.isEmpty(iccId)) {
-            // Call updateSubscriptionInfoByIccId() only if was
-            // not done earlier from SIM Locked event
-            if (sIccId[phoneId] == null) {
-                sIccId[phoneId] = iccId;
-
-                updateSubscriptionInfoByIccId(phoneId, true /* updateEmbeddedSubs */);
-            }
+            sIccId[phoneId] = iccId;
+            updateSubscriptionInfoByIccId(phoneId, true /* updateEmbeddedSubs */);
         }
 
         cardIds.add(getCardIdFromPhoneId(phoneId));
@@ -458,6 +453,8 @@
             // 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,
@@ -600,6 +597,10 @@
     }
 
     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);
@@ -718,7 +719,7 @@
         mSubscriptionController.clearSubInfoRecord(phoneId);
 
         // If SIM is not absent, insert new record or update existing record.
-        if (!ICCID_STRING_FOR_NO_SIM.equals(sIccId[phoneId])) {
+        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);
@@ -1128,12 +1129,10 @@
 
     @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);
-        // TODO - we'd like this intent to have a single snapshot of all sim state,
-        // but until then this should not use REPLACE_PENDING or we may lose
-        // information
-        // i.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
-        //         | Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
         i.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
         i.putExtra(PhoneConstants.PHONE_NAME_KEY, "Phone");
         i.putExtra(IccCardConstants.INTENT_KEY_ICC_STATE, state);
diff --git a/src/java/com/android/internal/telephony/TelephonyComponentFactory.java b/src/java/com/android/internal/telephony/TelephonyComponentFactory.java
index 39a36af..3356f8c 100644
--- a/src/java/com/android/internal/telephony/TelephonyComponentFactory.java
+++ b/src/java/com/android/internal/telephony/TelephonyComponentFactory.java
@@ -35,6 +35,7 @@
 import com.android.internal.telephony.cdma.EriManager;
 import com.android.internal.telephony.dataconnection.DataEnabledSettings;
 import com.android.internal.telephony.dataconnection.DcTracker;
+import com.android.internal.telephony.dataconnection.LinkBandwidthEstimator;
 import com.android.internal.telephony.dataconnection.TransportManager;
 import com.android.internal.telephony.emergency.EmergencyNumberTracker;
 import com.android.internal.telephony.imsphone.ImsExternalCallTracker;
@@ -69,6 +70,7 @@
     private static final String TAG = TelephonyComponentFactory.class.getSimpleName();
 
     private static TelephonyComponentFactory sInstance;
+    private final TelephonyFacade mTelephonyFacade = new TelephonyFacade();
 
     private InjectedComponents mInjectedComponents;
 
@@ -443,8 +445,22 @@
         return MultiSimSettingController.init(c, sc);
     }
 
+    /**
+     * Create a new SignalStrengthController instance.
+     */
+    public SignalStrengthController makeSignalStrengthController(GsmCdmaPhone phone) {
+        return new SignalStrengthController(phone);
+    }
+
     public SubscriptionInfoUpdater makeSubscriptionInfoUpdater(Looper looper, Context context,
             SubscriptionController sc) {
         return new SubscriptionInfoUpdater(looper, context, sc);
     }
+
+    /**
+     * Create a new LinkBandwidthEstimator.
+     */
+    public LinkBandwidthEstimator makeLinkBandwidthEstimator(Phone phone) {
+        return new LinkBandwidthEstimator(phone, mTelephonyFacade);
+    }
 }
diff --git a/src/java/com/android/internal/telephony/TelephonyFacade.java b/src/java/com/android/internal/telephony/TelephonyFacade.java
new file mode 100644
index 0000000..31a2242
--- /dev/null
+++ b/src/java/com/android/internal/telephony/TelephonyFacade.java
@@ -0,0 +1,48 @@
+/*
+ * 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 android.net.TrafficStats;
+import android.os.SystemClock;
+
+/**
+ * This class is a wrapper of various static methods to simplify unit tests with static methods
+ */
+public class TelephonyFacade {
+    /**
+     * Returns milliseconds since boot, including time spent in sleep.
+     *
+     * @return Current time since boot in milliseconds.
+     */
+    public long getElapsedSinceBootMillis() {
+        return SystemClock.elapsedRealtime();
+    }
+
+    /**
+     * Wrapper for {@link TrafficStats#getMobileTxBytes}.
+     */
+    public long getMobileTxBytes() {
+        return TrafficStats.getMobileTxBytes();
+    }
+
+    /**
+     * Wrapper for {@link TrafficStats#getMobileRxBytes}.
+     */
+    public long getMobileRxBytes() {
+        return TrafficStats.getMobileRxBytes();
+    }
+}
diff --git a/src/java/com/android/internal/telephony/VoiceIndication.java b/src/java/com/android/internal/telephony/VoiceIndication.java
new file mode 100644
index 0000000..da2c33b
--- /dev/null
+++ b/src/java/com/android/internal/telephony/VoiceIndication.java
@@ -0,0 +1,422 @@
+/*
+ * 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 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;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_CDMA_OTA_PROVISION_STATUS;
+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;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_ON_SS;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_RESEND_INCALL_MUTE;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_RINGBACK_TONE;
+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;
+
+import android.hardware.radio.voice.IRadioVoiceIndication;
+import android.os.AsyncResult;
+import android.telephony.emergency.EmergencyNumber;
+
+import com.android.internal.telephony.cdma.CdmaCallWaitingNotification;
+import com.android.internal.telephony.cdma.CdmaInformationRecords;
+import com.android.internal.telephony.gsm.SsData;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Interface declaring unsolicited radio indications for voice APIs.
+ */
+public class VoiceIndication extends IRadioVoiceIndication.Stub {
+    private final RIL mRil;
+
+    public VoiceIndication(RIL ril) {
+        mRil = ril;
+    }
+
+    /**
+     * Ring indication for an incoming call (eg, RING or CRING event).
+     * The rate of these events is controlled by ro.telephony.call_ring.delay and has a default
+     * value of 3000 (3 seconds) if absent.
+     * @param indicationType Type of radio indication
+     * @param isGsm true for GSM & false for CDMA
+     * @param record CDMA signal information record
+     */
+    public void callRing(int indicationType, boolean isGsm,
+            android.hardware.radio.voice.CdmaSignalInfoRecord record) {
+        mRil.processIndication(indicationType);
+
+        char[] response = null;
+
+        // Ignore record for gsm
+        if (!isGsm) {
+            // TODO: Clean this up with a parcelable class for better self-documentation
+            response = new char[4];
+            response[0] = (char) (record.isPresent ? 1 : 0);
+            response[1] = (char) record.signalType;
+            response[2] = (char) record.alertPitch;
+            response[3] = (char) record.signal;
+            mRil.writeMetricsCallRing(response);
+        }
+
+        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_CALL_RING, response);
+
+        if (mRil.mRingRegistrant != null) {
+            mRil.mRingRegistrant.notifyRegistrant(new AsyncResult(null, response, null));
+        }
+    }
+
+    /**
+     * Indicates when call state has changed. Redundant or extraneous invocations are tolerated.
+     * @param indicationType Type of radio indication
+     */
+    public void callStateChanged(int indicationType) {
+        mRil.processIndication(indicationType);
+
+        if (RIL.RILJ_LOGD) mRil.unsljLog(RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED);
+
+        mRil.mCallStateRegistrants.notifyRegistrants();
+    }
+
+    /**
+     * Indicates when CDMA radio receives a call waiting indication.
+     * @param indicationType Type of radio indication
+     * @param callWaitingRecord Cdma CallWaiting information
+     */
+    public void cdmaCallWaiting(int indicationType,
+            android.hardware.radio.voice.CdmaCallWaiting callWaitingRecord) {
+        mRil.processIndication(indicationType);
+
+        // TODO: create a CdmaCallWaitingNotification constructor that takes in these fields to make
+        // sure no fields are missing
+        CdmaCallWaitingNotification notification = new CdmaCallWaitingNotification();
+        notification.number = callWaitingRecord.number;
+        notification.numberPresentation = CdmaCallWaitingNotification.presentationFromCLIP(
+                callWaitingRecord.numberPresentation);
+        notification.name = callWaitingRecord.name;
+        notification.namePresentation = notification.numberPresentation;
+        notification.isPresent = callWaitingRecord.signalInfoRecord.isPresent ? 1 : 0;
+        notification.signalType = callWaitingRecord.signalInfoRecord.signalType;
+        notification.alertPitch = callWaitingRecord.signalInfoRecord.alertPitch;
+        notification.signal = callWaitingRecord.signalInfoRecord.signal;
+        notification.numberType = callWaitingRecord.numberType;
+        notification.numberPlan = callWaitingRecord.numberPlan;
+
+        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_CDMA_CALL_WAITING, notification);
+
+        mRil.mCallWaitingInfoRegistrants.notifyRegistrants(
+                new AsyncResult(null, notification, null));
+    }
+
+    /**
+     * Indicates when CDMA radio receives one or more info recs.
+     * @param indicationType Type of radio indication
+     * @param records New CDMA information
+     */
+    public void cdmaInfoRec(int indicationType,
+            android.hardware.radio.voice.CdmaInformationRecords records) {
+        mRil.processIndication(indicationType);
+
+        int numberOfInfoRecs = records.infoRec.length;
+        for (int i = 0; i < numberOfInfoRecs; i++) {
+            android.hardware.radio.voice.CdmaInformationRecord record = records.infoRec[i];
+            int id = record.name;
+            CdmaInformationRecords cdmaInformationRecords;
+            switch (id) {
+                case CdmaInformationRecords.RIL_CDMA_DISPLAY_INFO_REC:
+                case CdmaInformationRecords.RIL_CDMA_EXTENDED_DISPLAY_INFO_REC:
+                    CdmaInformationRecords.CdmaDisplayInfoRec cdmaDisplayInfoRec =
+                            new CdmaInformationRecords.CdmaDisplayInfoRec(id,
+                                    record.display[0].alphaBuf);
+                    cdmaInformationRecords = new CdmaInformationRecords(cdmaDisplayInfoRec);
+                    break;
+
+                case CdmaInformationRecords.RIL_CDMA_CALLED_PARTY_NUMBER_INFO_REC:
+                case CdmaInformationRecords.RIL_CDMA_CALLING_PARTY_NUMBER_INFO_REC:
+                case CdmaInformationRecords.RIL_CDMA_CONNECTED_NUMBER_INFO_REC:
+                    android.hardware.radio.voice.CdmaNumberInfoRecord numInfoRecord =
+                            record.number[0];
+                    CdmaInformationRecords.CdmaNumberInfoRec cdmaNumberInfoRec =
+                            new CdmaInformationRecords.CdmaNumberInfoRec(id, numInfoRecord.number,
+                                    numInfoRecord.numberType, numInfoRecord.numberPlan,
+                                    numInfoRecord.pi, numInfoRecord.si);
+                    cdmaInformationRecords = new CdmaInformationRecords(cdmaNumberInfoRec);
+                    break;
+
+                case CdmaInformationRecords.RIL_CDMA_SIGNAL_INFO_REC:
+                    android.hardware.radio.voice.CdmaSignalInfoRecord signalInfoRecord =
+                            record.signal[0];
+                    CdmaInformationRecords.CdmaSignalInfoRec cdmaSignalInfoRec =
+                            new CdmaInformationRecords.CdmaSignalInfoRec(
+                                    signalInfoRecord.isPresent ? 1 : 0, signalInfoRecord.signalType,
+                                    signalInfoRecord.alertPitch, signalInfoRecord.signal);
+                    cdmaInformationRecords = new CdmaInformationRecords(cdmaSignalInfoRec);
+                    break;
+
+                case CdmaInformationRecords.RIL_CDMA_REDIRECTING_NUMBER_INFO_REC:
+                    android.hardware.radio.voice.CdmaRedirectingNumberInfoRecord
+                            redirectingNumberInfoRecord = record.redir[0];
+                    CdmaInformationRecords.CdmaRedirectingNumberInfoRec
+                            cdmaRedirectingNumberInfoRec =
+                            new CdmaInformationRecords.CdmaRedirectingNumberInfoRec(
+                                    redirectingNumberInfoRecord.redirectingNumber.number,
+                                    redirectingNumberInfoRecord.redirectingNumber.numberType,
+                                    redirectingNumberInfoRecord.redirectingNumber.numberPlan,
+                                    redirectingNumberInfoRecord.redirectingNumber.pi,
+                                    redirectingNumberInfoRecord.redirectingNumber.si,
+                                    redirectingNumberInfoRecord.redirectingReason);
+                    cdmaInformationRecords = new CdmaInformationRecords(
+                            cdmaRedirectingNumberInfoRec);
+                    break;
+
+                case CdmaInformationRecords.RIL_CDMA_LINE_CONTROL_INFO_REC:
+                    android.hardware.radio.voice.CdmaLineControlInfoRecord lineControlInfoRecord =
+                            record.lineCtrl[0];
+                    CdmaInformationRecords.CdmaLineControlInfoRec cdmaLineControlInfoRec =
+                            new CdmaInformationRecords.CdmaLineControlInfoRec(
+                                    lineControlInfoRecord.lineCtrlPolarityIncluded,
+                                    lineControlInfoRecord.lineCtrlToggle,
+                                    lineControlInfoRecord.lineCtrlReverse,
+                                    lineControlInfoRecord.lineCtrlPowerDenial);
+                    cdmaInformationRecords = new CdmaInformationRecords(cdmaLineControlInfoRec);
+                    break;
+
+                case CdmaInformationRecords.RIL_CDMA_T53_CLIR_INFO_REC:
+                    CdmaInformationRecords.CdmaT53ClirInfoRec cdmaT53ClirInfoRec =
+                            new CdmaInformationRecords.CdmaT53ClirInfoRec(record.clir[0].cause);
+                    cdmaInformationRecords = new CdmaInformationRecords(cdmaT53ClirInfoRec);
+                    break;
+
+                case CdmaInformationRecords.RIL_CDMA_T53_AUDIO_CONTROL_INFO_REC:
+                    android.hardware.radio.voice.CdmaT53AudioControlInfoRecord
+                            audioControlInfoRecord = record.audioCtrl[0];
+                    CdmaInformationRecords.CdmaT53AudioControlInfoRec cdmaT53AudioControlInfoRec =
+                            new CdmaInformationRecords.CdmaT53AudioControlInfoRec(
+                                    audioControlInfoRecord.upLink,
+                                    audioControlInfoRecord.downLink);
+                    cdmaInformationRecords = new CdmaInformationRecords(cdmaT53AudioControlInfoRec);
+                    break;
+
+                default:
+                    throw new RuntimeException("RIL_UNSOL_CDMA_INFO_REC: unsupported record. Got "
+                            + CdmaInformationRecords.idToString(id) + " ");
+            }
+
+            if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_CDMA_INFO_REC, cdmaInformationRecords);
+            mRil.notifyRegistrantsCdmaInfoRec(cdmaInformationRecords);
+        }
+    }
+
+    /**
+     * Indicates when CDMA radio receives an update of the progress of an OTASP/OTAPA call.
+     * @param indicationType Type of radio indication
+     * @param status CDMA OTA provision status
+     */
+    public void cdmaOtaProvisionStatus(int indicationType, int status) {
+        mRil.processIndication(indicationType);
+
+        int[] response = new int[] {status};
+
+        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_CDMA_OTA_PROVISION_STATUS, response);
+
+        mRil.mOtaProvisionRegistrants.notifyRegistrants(new AsyncResult(null, response, null));
+    }
+
+
+    /**
+     * Indicates current emergency number list.
+     * @param indicationType Type of radio indication
+     * @param emergencyNumberList Current list of emergency numbers known to radio
+     */
+    public void currentEmergencyNumberList(int indicationType,
+            android.hardware.radio.voice.EmergencyNumber[] emergencyNumberList) {
+        mRil.processIndication(indicationType);
+
+        List<EmergencyNumber> response = new ArrayList<>(emergencyNumberList.length);
+        for (android.hardware.radio.voice.EmergencyNumber enHal : emergencyNumberList) {
+            EmergencyNumber emergencyNumber = new EmergencyNumber(enHal.number,
+                    MccTable.countryCodeForMcc(enHal.mcc), enHal.mnc, enHal.categories,
+                    RILUtils.primitiveArrayToArrayList(enHal.urns), enHal.sources,
+                    EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN);
+            response.add(emergencyNumber);
+        }
+
+        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_EMERGENCY_NUMBER_LIST, response);
+
+        // Cache emergency number list from last indication.
+        mRil.cacheEmergencyNumberListIndication(response);
+
+        // Notify emergency number list from radio to registrants
+        mRil.mEmergencyNumberListRegistrants.notifyRegistrants(
+                new AsyncResult(null, response, null));
+    }
+
+    /**
+     * Indicates that the radio system selection module has autonomously entered emergency
+     * callback mode.
+     * @param indicationType Type of radio indication
+     */
+    public void enterEmergencyCallbackMode(int indicationType) {
+        mRil.processIndication(indicationType);
+
+        if (RIL.RILJ_LOGD) mRil.unsljLog(RIL_UNSOL_ENTER_EMERGENCY_CALLBACK_MODE);
+
+        if (mRil.mEmergencyCallbackModeRegistrant != null) {
+            mRil.mEmergencyCallbackModeRegistrant.notifyRegistrant();
+        }
+    }
+
+    /**
+     * Indicates when Emergency Callback Mode ends. Indicates that the radio system selection module
+     * has proactively exited emergency callback mode.
+     * @param indicationType Type of radio indication
+     */
+    public void exitEmergencyCallbackMode(int indicationType) {
+        mRil.processIndication(indicationType);
+
+        if (RIL.RILJ_LOGD) mRil.unsljLog(RIL_UNSOL_EXIT_EMERGENCY_CALLBACK_MODE);
+
+        mRil.mExitEmergencyCallbackModeRegistrants.notifyRegistrants();
+    }
+
+    /**
+     * Indicates that network doesn't have in-band information, need to play out-band tone.
+     * @param indicationType Type of radio indication
+     * @param start true = start play ringback tone, false = stop playing ringback tone
+     */
+    public void indicateRingbackTone(int indicationType, boolean start) {
+        mRil.processIndication(indicationType);
+
+        if (RIL.RILJ_LOGD) mRil.unsljLogvRet(RIL_UNSOL_RINGBACK_TONE, start);
+
+        mRil.mRingbackToneRegistrants.notifyRegistrants(new AsyncResult(null, start, null));
+    }
+
+    /**
+     * Indicates when Supplementary service(SS) response is received when DIAL/USSD/SS is changed to
+     * SS by call control.
+     * @param indicationType Type of radio indication
+     * @param ss StkCcUnsolSsResult
+     */
+    public void onSupplementaryServiceIndication(int indicationType,
+            android.hardware.radio.voice.StkCcUnsolSsResult ss) {
+        mRil.processIndication(indicationType);
+
+        int num;
+        SsData ssData = new SsData();
+
+        ssData.serviceType = ssData.ServiceTypeFromRILInt(ss.serviceType);
+        ssData.requestType = ssData.RequestTypeFromRILInt(ss.requestType);
+        ssData.teleserviceType = ssData.TeleserviceTypeFromRILInt(ss.teleserviceType);
+        ssData.serviceClass = ss.serviceClass; // This is service class sent in the SS request.
+        ssData.result = ss.result; // This is the result of the SS request.
+
+        if (ssData.serviceType.isTypeCF() && ssData.requestType.isTypeInterrogation()) {
+            android.hardware.radio.voice.CfData cfData = ss.cfData[0];
+            num = cfData.cfInfo.length;
+            ssData.cfInfo = new CallForwardInfo[num];
+
+            for (int i = 0; i < num; i++) {
+                android.hardware.radio.voice.CallForwardInfo cfInfo = cfData.cfInfo[i];
+                ssData.cfInfo[i] = new CallForwardInfo();
+                ssData.cfInfo[i].status = cfInfo.status;
+                ssData.cfInfo[i].reason = cfInfo.reason;
+                ssData.cfInfo[i].serviceClass = cfInfo.serviceClass;
+                ssData.cfInfo[i].toa = cfInfo.toa;
+                ssData.cfInfo[i].number = cfInfo.number;
+                ssData.cfInfo[i].timeSeconds = cfInfo.timeSeconds;
+                mRil.riljLog("[SS Data] CF Info " + i + " : " +  ssData.cfInfo[i]);
+            }
+        } else {
+            android.hardware.radio.voice.SsInfoData ssInfo = ss.ssInfo[0];
+            num = ssInfo.ssInfo.length;
+            ssData.ssInfo = new int[num];
+            for (int i = 0; i < num; i++) {
+                ssData.ssInfo[i] = ssInfo.ssInfo[i];
+                mRil.riljLog("[SS Data] SS Info " + i + " : " +  ssData.ssInfo[i]);
+            }
+        }
+
+        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_ON_SS, ssData);
+
+        if (mRil.mSsRegistrant != null) {
+            mRil.mSsRegistrant.notifyRegistrant(new AsyncResult(null, ssData, null));
+        }
+    }
+
+    /**
+     * Indicates that framework/application must reset the uplink mute state.
+     * @param indicationType Type of radio indication
+     */
+    public void resendIncallMute(int indicationType) {
+        mRil.processIndication(indicationType);
+
+        if (RIL.RILJ_LOGD) mRil.unsljLog(RIL_UNSOL_RESEND_INCALL_MUTE);
+
+        mRil.mResendIncallMuteRegistrants.notifyRegistrants();
+    }
+
+    /**
+     * Indicates when Single Radio Voice Call Continuity (SRVCC) progress state has changed.
+     * @param indicationType Type of radio indication
+     * @param state New SRVCC State
+     */
+    public void srvccStateNotify(int indicationType, int state) {
+        mRil.processIndication(indicationType);
+
+        int[] response = new int[] {state};
+
+        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_SRVCC_STATE_NOTIFY, response);
+
+        mRil.writeMetricsSrvcc(state);
+        mRil.mSrvccStateRegistrants.notifyRegistrants(new AsyncResult(null, response, null));
+    }
+
+    /**
+     * Indicates when there is an ALPHA from UICC during Call Control.
+     * @param indicationType Type of radio indication
+     * @param alpha ALPHA string from UICC in UTF-8 format
+     */
+    public void stkCallControlAlphaNotify(int indicationType, String alpha) {
+        mRil.processIndication(indicationType);
+
+        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_STK_CC_ALPHA_NOTIFY, alpha);
+
+        if (mRil.mCatCcAlphaRegistrant != null) {
+            mRil.mCatCcAlphaRegistrant.notifyRegistrant(new AsyncResult(null, alpha, null));
+        }
+    }
+
+    /**
+     * Indicates when SIM wants application to setup a voice call.
+     * @param indicationType Type of radio indication
+     * @param timeout Timeout value in milliseconds for setting up voice call
+     */
+    public void stkCallSetup(int indicationType, long timeout) {
+        mRil.processIndication(indicationType);
+
+        if (RIL.RILJ_LOGD) 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
new file mode 100644
index 0000000..1a6e318
--- /dev/null
+++ b/src/java/com/android/internal/telephony/VoiceResponse.java
@@ -0,0 +1,356 @@
+/*
+ * 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 android.hardware.radio.RadioError;
+import android.hardware.radio.RadioResponseInfo;
+import android.hardware.radio.voice.IRadioVoiceResponse;
+
+import java.util.ArrayList;
+import java.util.Collections;
+
+/**
+ * Interface declaring response functions to solicited radio requests for SIM APIs.
+ */
+public class VoiceResponse extends IRadioVoiceResponse.Stub {
+    private final RIL mRil;
+
+    public VoiceResponse(RIL ril) {
+        mRil = ril;
+    }
+
+    /**
+     * Acknowledge the receipt of radio request sent to the vendor. This must be sent only for
+     * radio request which take long time to respond.
+     * For more details, refer https://source.android.com/devices/tech/connect/ril.html
+     * @param serial Serial no. of the request whose acknowledgement is sent.
+     */
+    public void acknowledgeRequest(int serial) {
+        mRil.processRequestAck(serial);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void acceptCallResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void conferenceResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void dialResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void emergencyDialResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void exitEmergencyCallbackModeResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void explicitCallTransferResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param callForwardInfos points to a vector of CallForwardInfo, one for
+     *        each distinct registered phone number.
+     */
+    public void getCallForwardStatusResponse(RadioResponseInfo responseInfo,
+            android.hardware.radio.voice.CallForwardInfo[] callForwardInfos) {
+        RILRequest rr = mRil.processResponse(responseInfo);
+        if (rr != null) {
+            CallForwardInfo[] ret = new CallForwardInfo[callForwardInfos.length];
+            for (int i = 0; i < callForwardInfos.length; i++) {
+                ret[i] = new CallForwardInfo();
+                ret[i].status = callForwardInfos[i].status;
+                ret[i].reason = callForwardInfos[i].reason;
+                ret[i].serviceClass = callForwardInfos[i].serviceClass;
+                ret[i].toa = callForwardInfos[i].toa;
+                ret[i].number = callForwardInfos[i].number;
+                ret[i].timeSeconds = callForwardInfos[i].timeSeconds;
+            }
+            if (responseInfo.error == RadioError.NONE) {
+                RadioResponse.sendMessageResponse(rr.mResult, ret);
+            }
+            mRil.processResponseDone(rr, responseInfo, ret);
+        }
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param enable If current call waiting state is disabled, enable = false else true
+     * @param serviceClass If enable, then callWaitingResp[1] must follow, with the TS 27.007
+     *        service class bit vector of services for which call waiting is enabled.
+     *        For example, if callWaitingResp[0] is 1 and callWaitingResp[1] is 3, then call waiting
+     *        is enabled for data and voice and disabled for everything else.
+     */
+    public void getCallWaitingResponse(RadioResponseInfo responseInfo, boolean enable,
+            int serviceClass) {
+        RadioResponse.responseInts(mRil, responseInfo, enable ? 1 : 0, serviceClass);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param status indicates CLIP status
+     */
+    public void getClipResponse(RadioResponseInfo responseInfo, int status) {
+        RadioResponse.responseInts(mRil, responseInfo, status);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param n is "n" parameter from TS 27.007 7.7
+     * @param m is "m" parameter from TS 27.007 7.7
+     */
+    public void getClirResponse(RadioResponseInfo responseInfo, int n, int m) {
+        RadioResponse.responseInts(mRil, responseInfo, n, m);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param calls Current call list
+     */
+    public void getCurrentCallsResponse(RadioResponseInfo responseInfo,
+            android.hardware.radio.voice.Call[] calls) {
+        RILRequest rr = mRil.processResponse(responseInfo);
+
+        if (rr != null) {
+            int num = calls.length;
+            ArrayList<DriverCall> dcCalls = new ArrayList<>(num);
+            DriverCall dc;
+            for (int i = 0; i < num; i++) {
+                dc = RILUtils.convertToDriverCall(calls[i]);
+                dcCalls.add(dc);
+                if (dc.isVoicePrivacy) {
+                    mRil.mVoicePrivacyOnRegistrants.notifyRegistrants();
+                    mRil.riljLog("InCall VoicePrivacy is enabled");
+                } else {
+                    mRil.mVoicePrivacyOffRegistrants.notifyRegistrants();
+                    mRil.riljLog("InCall VoicePrivacy is disabled");
+                }
+            }
+
+            Collections.sort(dcCalls);
+            if ((num == 0) && mRil.mTestingEmergencyCall.getAndSet(false)) {
+                if (mRil.mEmergencyCallbackModeRegistrant != null) {
+                    mRil.riljLog("responseCurrentCalls: call ended, testing emergency call,"
+                            + " notify ECM Registrants");
+                    mRil.mEmergencyCallbackModeRegistrant.notifyRegistrant();
+                }
+            }
+
+            if (responseInfo.error == RadioError.NONE) {
+                RadioResponse.sendMessageResponse(rr.mResult, dcCalls);
+            }
+            mRil.processResponseDone(rr, responseInfo, dcCalls);
+        }
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param fcInfo Contains LastCallFailCause and vendor cause code. GSM failure reasons
+     *        are mapped to cause codes defined in TS 24.008 Annex H where possible. CDMA failure
+     *        reasons are derived from the possible call failure scenarios described in the
+     *        "CDMA IS-2000 Release A (C.S0005-A v6.0)" standard.
+     */
+    public void getLastCallFailCauseResponse(RadioResponseInfo responseInfo,
+            android.hardware.radio.voice.LastCallFailCauseInfo fcInfo) {
+        RILRequest rr = mRil.processResponse(responseInfo);
+
+        if (rr != null) {
+            LastCallFailCause ret = new LastCallFailCause();
+            ret.causeCode = fcInfo.causeCode;
+            ret.vendorCause = fcInfo.vendorCause;
+            if (responseInfo.error == RadioError.NONE) {
+                RadioResponse.sendMessageResponse(rr.mResult, ret);
+            }
+            mRil.processResponseDone(rr, responseInfo, ret);
+        }
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param enable true for "mute enabled" and false for "mute disabled"
+     */
+    public void getMuteResponse(RadioResponseInfo responseInfo, boolean enable) {
+        RadioResponse.responseInts(mRil, responseInfo, enable ? 1 : 0);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param enable false for Standard Privacy Mode (Public Long Code Mask)
+     *        true for Enhanced Privacy Mode (Private Long Code Mask)
+     */
+    public void getPreferredVoicePrivacyResponse(RadioResponseInfo responseInfo, boolean enable) {
+        RadioResponse.responseInts(mRil, responseInfo, enable ? 1 : 0);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param mode TTY mode
+     */
+    public void getTtyModeResponse(RadioResponseInfo responseInfo, int mode) {
+        RadioResponse.responseInts(mRil, responseInfo, mode);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void handleStkCallSetupRequestFromSimResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void hangupConnectionResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void hangupForegroundResumeBackgroundResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void hangupWaitingOrBackgroundResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void rejectCallResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void sendBurstDtmfResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void sendCdmaFeatureCodeResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void sendDtmfResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void separateConnectionResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void setCallForwardResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void setCallWaitingResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void setClirResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void setMuteResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void setPreferredVoicePrivacyResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void setTtyModeResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void startDtmfResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void stopDtmfResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void switchWaitingOrHoldingAndActiveResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(mRil, responseInfo);
+    }
+}
diff --git a/src/java/com/android/internal/telephony/WapPushOverSms.java b/src/java/com/android/internal/telephony/WapPushOverSms.java
index c4cafd4..d6f69e2 100755
--- a/src/java/com/android/internal/telephony/WapPushOverSms.java
+++ b/src/java/com/android/internal/telephony/WapPushOverSms.java
@@ -16,6 +16,9 @@
 
 package com.android.internal.telephony;
 
+import static android.os.PowerWhitelistManager.REASON_EVENT_MMS;
+import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
+
 import static com.google.android.mms.pdu.PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND;
 
 import android.annotation.NonNull;
@@ -24,7 +27,6 @@
 import android.app.AppOpsManager;
 import android.app.BroadcastOptions;
 import android.compat.annotation.UnsupportedAppUsage;
-import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -314,8 +316,8 @@
      *         {@link Activity#RESULT_OK} if the message has been broadcast
      *         to applications
      */
-    public int dispatchWapPdu(byte[] pdu, BroadcastReceiver receiver, InboundSmsHandler handler,
-            String address, int subId, long messageId) {
+    public int dispatchWapPdu(byte[] pdu, InboundSmsHandler.SmsBroadcastReceiver receiver,
+            InboundSmsHandler handler, String address, int subId, long messageId) {
         DecodedResult result = decodeWapPdu(pdu, handler);
         if (result.statusCode != Activity.RESULT_OK) {
             return result.statusCode;
@@ -336,7 +338,8 @@
                 } else {
                     synchronized (this) {
                         mPowerWhitelistManager.whitelistAppTemporarilyForEvent(
-                                mWapPushManagerPackage, PowerWhitelistManager.EVENT_MMS, "mms-mgr");
+                                mWapPushManagerPackage, PowerWhitelistManager.EVENT_MMS,
+                                REASON_EVENT_MMS, "mms-mgr");
                     }
 
                     Intent intent = new Intent();
@@ -396,9 +399,13 @@
             if (DBG) Rlog.v(TAG, "Delivering MMS to: " + componentName.getPackageName() +
                     " " + componentName.getClassName());
             long duration = mPowerWhitelistManager.whitelistAppTemporarilyForEvent(
-                    componentName.getPackageName(), PowerWhitelistManager.EVENT_MMS, "mms-app");
+                    componentName.getPackageName(), PowerWhitelistManager.EVENT_MMS,
+                    REASON_EVENT_MMS, "mms-app");
             BroadcastOptions bopts = BroadcastOptions.makeBasic();
-            bopts.setTemporaryAppWhitelistDuration(duration);
+            bopts.setTemporaryAppAllowlist(duration,
+                    TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
+                    REASON_EVENT_MMS,
+                    "");
             options = bopts.toBundle();
         }
 
diff --git a/src/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java b/src/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
index aaea45a..5a830a8 100644
--- a/src/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
+++ b/src/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
@@ -30,6 +30,7 @@
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.SMSDispatcher;
 import com.android.internal.telephony.SmsConstants;
+import com.android.internal.telephony.SmsController;
 import com.android.internal.telephony.SmsDispatchersController;
 import com.android.internal.telephony.SmsHeader;
 import com.android.internal.telephony.SmsMessageBase;
@@ -108,7 +109,7 @@
                 + " mMessageRef=" + tracker.mMessageRef
                 + " mUsesImsServiceForIms=" + tracker.mUsesImsServiceForIms
                 + " SS=" + ss
-                + " id=" + tracker.mMessageId);
+                + " " + SmsController.formatCrossStackMessageId(tracker.mMessageId));
 
         // if sms over IMS is not supported on data and voice is not available...
         if (!isIms() && ss != ServiceState.STATE_IN_SERVICE) {
diff --git a/src/java/com/android/internal/telephony/cdma/SmsMessageConverter.java b/src/java/com/android/internal/telephony/cdma/SmsMessageConverter.java
deleted file mode 100644
index 84da264..0000000
--- a/src/java/com/android/internal/telephony/cdma/SmsMessageConverter.java
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Copyright (C) 2008 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.cdma;
-
-import android.hardware.radio.V1_0.CdmaSmsMessage;
-
-import com.android.internal.telephony.SmsMessageBase;
-import com.android.internal.telephony.cdma.sms.CdmaSmsAddress;
-import com.android.internal.telephony.cdma.sms.CdmaSmsSubaddress;
-import com.android.internal.telephony.cdma.sms.SmsEnvelope;
-
-/**
- * A Factory class to convert from RIL to Framework SMS
- *
- */
-public class SmsMessageConverter {
-    static final String LOG_TAG = "SmsMessageConverter";
-    static private final String LOGGABLE_TAG = "CDMA:SMS";
-    private static final boolean VDBG = false;
-
-    /**
-     *  Create a "raw" CDMA SmsMessage from a Parcel that was forged in ril.cpp.
-     *  Note: Only primitive fields are set.
-     */
-    public static SmsMessage newCdmaSmsMessageFromRil(
-            CdmaSmsMessage cdmaSmsMessage) {
-        // Note: Parcel.readByte actually reads one Int and masks to byte
-        SmsEnvelope env = new SmsEnvelope();
-        CdmaSmsAddress addr = new CdmaSmsAddress();
-        CdmaSmsSubaddress subaddr = new CdmaSmsSubaddress();
-        byte[] data;
-        byte count;
-        int countInt;
-        int addressDigitMode;
-
-        //currently not supported by the modem-lib: env.mMessageType
-        env.teleService = cdmaSmsMessage.teleserviceId;
-
-        if (cdmaSmsMessage.isServicePresent) {
-            env.messageType = SmsEnvelope.MESSAGE_TYPE_BROADCAST;
-        }
-        else {
-            if (SmsEnvelope.TELESERVICE_NOT_SET == env.teleService) {
-                // assume type ACK
-                env.messageType = SmsEnvelope.MESSAGE_TYPE_ACKNOWLEDGE;
-            } else {
-                env.messageType = SmsEnvelope.MESSAGE_TYPE_POINT_TO_POINT;
-            }
-        }
-        env.serviceCategory = cdmaSmsMessage.serviceCategory;
-
-        // address
-        addressDigitMode = cdmaSmsMessage.address.digitMode;
-        addr.digitMode = (byte) (0xFF & addressDigitMode);
-        addr.numberMode = (byte) (0xFF & cdmaSmsMessage.address.numberMode);
-        addr.ton = cdmaSmsMessage.address.numberType;
-        addr.numberPlan = (byte) (0xFF & cdmaSmsMessage.address.numberPlan);
-        count = (byte) cdmaSmsMessage.address.digits.size();
-        addr.numberOfDigits = count;
-        data = new byte[count];
-        for (int index=0; index < count; index++) {
-            data[index] = cdmaSmsMessage.address.digits.get(index);
-
-            // convert the value if it is 4-bit DTMF to 8 bit
-            if (addressDigitMode == CdmaSmsAddress.DIGIT_MODE_4BIT_DTMF) {
-                data[index] = SmsMessage.convertDtmfToAscii(data[index]);
-            }
-        }
-
-        addr.origBytes = data;
-
-        subaddr.type = cdmaSmsMessage.subAddress.subaddressType;
-        subaddr.odd = (byte) (cdmaSmsMessage.subAddress.odd ? 1 : 0);
-        count = (byte) cdmaSmsMessage.subAddress.digits.size();
-
-        if (count < 0) {
-            count = 0;
-        }
-
-        // p_cur->sSubAddress.digits[digitCount] :
-
-        data = new byte[count];
-
-        for (int index = 0; index < count; ++index) {
-            data[index] = cdmaSmsMessage.subAddress.digits.get(index);
-        }
-
-        subaddr.origBytes = data;
-
-        /* currently not supported by the modem-lib:
-            env.bearerReply
-            env.replySeqNo
-            env.errorClass
-            env.causeCode
-        */
-
-        // bearer data
-        countInt = cdmaSmsMessage.bearerData.size();
-        if (countInt < 0) {
-            countInt = 0;
-        }
-
-        data = new byte[countInt];
-        for (int index=0; index < countInt; index++) {
-            data[index] = cdmaSmsMessage.bearerData.get(index);
-        }
-        // BD gets further decoded when accessed in SMSDispatcher
-        env.bearerData = data;
-
-        // link the the filled objects to the SMS
-        env.origAddress = addr;
-        env.origSubaddress = subaddr;
-
-        SmsMessage msg = new SmsMessage(addr, env);
-
-        return msg;
-    }
-
-    public static android.telephony.SmsMessage newSmsMessageFromCdmaSmsMessage(
-            CdmaSmsMessage msg) {
-        return new android.telephony.SmsMessage((SmsMessageBase)newCdmaSmsMessageFromRil(msg));
-    }
-}
diff --git a/src/java/com/android/internal/telephony/cdnr/CarrierDisplayNameResolver.java b/src/java/com/android/internal/telephony/cdnr/CarrierDisplayNameResolver.java
index aba7b25..6299dac 100644
--- a/src/java/com/android/internal/telephony/cdnr/CarrierDisplayNameResolver.java
+++ b/src/java/com/android/internal/telephony/cdnr/CarrierDisplayNameResolver.java
@@ -34,10 +34,13 @@
 import android.os.PersistableBundle;
 import android.telephony.CarrierConfigManager;
 import android.telephony.ServiceState;
+import android.telephony.SubscriptionManager;
+import android.telephony.ims.stub.ImsRegistrationImplBase;
 import android.text.TextUtils;
 import android.util.LocalLog;
 import android.util.SparseArray;
 
+import com.android.internal.R;
 import com.android.internal.telephony.GsmCdmaPhone;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.cdnr.EfData.EFSource;
@@ -383,6 +386,48 @@
         return result;
     }
 
+    private CarrierDisplayNameData getCarrierDisplayNameFromCrossSimCallingOverride(
+            CarrierDisplayNameData rawCarrierDisplayNameData) {
+        PersistableBundle config = getCarrierConfig();
+        int crossSimSpnFormatIdx =
+                config.getInt(CarrierConfigManager.KEY_CROSS_SIM_SPN_FORMAT_INT);
+        boolean useRootLocale =
+                config.getBoolean(CarrierConfigManager.KEY_WFC_SPN_USE_ROOT_LOCALE);
+
+        String[] crossSimSpnFormats = SubscriptionManager.getResourcesForSubId(
+                mPhone.getContext(),
+                mPhone.getSubId(), useRootLocale)
+                .getStringArray(R.array.crossSimSpnFormats);
+
+        if (crossSimSpnFormatIdx < 0 || crossSimSpnFormatIdx >= crossSimSpnFormats.length) {
+            Rlog.e(TAG, "updateSpnDisplay: KEY_CROSS_SIM_SPN_FORMAT_INT out of bounds: "
+                    + crossSimSpnFormatIdx);
+            crossSimSpnFormatIdx = 0;
+        }
+        String crossSimSpnFormat = crossSimSpnFormats[crossSimSpnFormatIdx];
+        // Override the spn, data spn, plmn by Cross-SIM Calling
+        List<PlmnNetworkName> efPnn = getEfPnn();
+        String plmn = efPnn.isEmpty() ? "" : getPlmnNetworkName(efPnn.get(0));
+        CarrierDisplayNameData result = rawCarrierDisplayNameData;
+        String rawSpn = rawCarrierDisplayNameData.getSpn();
+        String rawPlmn = TextUtils.isEmpty(plmn) ? rawCarrierDisplayNameData.getPlmn() : plmn;
+        String crossSimSpn = String.format(crossSimSpnFormat, rawSpn);
+        String crossSimPlmn = String.format(crossSimSpnFormat, plmn);
+        if (!TextUtils.isEmpty(rawSpn) && !TextUtils.isEmpty(crossSimSpn)) {
+            result = new CarrierDisplayNameData.Builder()
+                    .setSpn(crossSimSpn)
+                    .setDataSpn(crossSimSpn)
+                    .setShowSpn(true)
+                    .build();
+        } else if (!TextUtils.isEmpty(rawPlmn) && !TextUtils.isEmpty(crossSimPlmn)) {
+            result = new CarrierDisplayNameData.Builder()
+                    .setPlmn(crossSimPlmn)
+                    .setShowPlmn(true)
+                    .build();
+        }
+        return result;
+    }
+
     /**
      * Override the given carrier display name data {@code data} by out of service rule.
      * @param data the carrier display name data need to be overridden.
@@ -429,7 +474,13 @@
     private void resolveCarrierDisplayName() {
         CarrierDisplayNameData data = getCarrierDisplayNameFromEf();
         if (DBG) Rlog.d(TAG, "CarrierName from EF: " + data);
-        if (mPhone.getServiceStateTracker().getCombinedRegState(getServiceState())
+        if ((mPhone.getImsPhone() != null) && (mPhone.getImsPhone().getImsRegistrationTech()
+                == ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM)) {
+            data = getCarrierDisplayNameFromCrossSimCallingOverride(data);
+            if (DBG) {
+                Rlog.d(TAG, "CarrierName override by Cross-SIM Calling " + data);
+            }
+        } else if (mPhone.getServiceStateTracker().getCombinedRegState(getServiceState())
                 == ServiceState.STATE_IN_SERVICE) {
             if (mPhone.isWifiCallingEnabled()) {
                 data = getCarrierDisplayNameFromWifiCallingOverride(data);
diff --git a/src/java/com/android/internal/telephony/d2d/Communicator.java b/src/java/com/android/internal/telephony/d2d/Communicator.java
index 8c3c630..1aac41b 100644
--- a/src/java/com/android/internal/telephony/d2d/Communicator.java
+++ b/src/java/com/android/internal/telephony/d2d/Communicator.java
@@ -24,7 +24,9 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 /**
  * Responsible for facilitating device-to-device communication between both ends of a call.
@@ -36,6 +38,7 @@
      */
     public interface Callback {
         void onMessagesReceived(@NonNull Set<Message> messages);
+        void onD2DAvailabilitychanged(boolean isAvailable);
     }
 
     public static final int MESSAGE_CALL_RADIO_ACCESS_TYPE = 1;
@@ -106,6 +109,9 @@
 
     public Communicator(@NonNull List<TransportProtocol> transportProtocols,
             @NonNull Callback callback) {
+        Log.i(this, "Initializing communicator with transports: %s",
+                transportProtocols.stream().map(p -> p.getClass().getSimpleName()).collect(
+                        Collectors.joining(",")));
         mTransportProtocols.addAll(transportProtocols);
         mTransportProtocols.forEach(p -> p.setCallback(this));
         mCallback = callback;
@@ -121,10 +127,11 @@
 
     /**
      * Handles state changes for a call.
-     * @param c The call in question.
+     * @param id The call in question.
      * @param state The new state.
      */
-    public void onStateChanged(Connection c, @Connection.ConnectionState int state) {
+    public void onStateChanged(String id, @Connection.ConnectionState int state) {
+        Log.i(this, "onStateChanged: id=%s, newState=%d", id, state);
         if (state == Connection.STATE_ACTIVE) {
             // Protocol negotiation can start as we are active
             if (mActiveTransport == null && !mIsNegotiationAttempted) {
@@ -152,6 +159,7 @@
         Log.i(this, "onNegotiationSuccess: %s negotiated; setting active.",
                 protocol.getClass().getSimpleName());
         mIsNegotiated = true;
+        notifyD2DStatus(true /* isAvailable */);
     }
 
     /**
@@ -204,6 +212,7 @@
         if (mActiveTransport == null) {
             // No more protocols, exit.
             Log.i(this, "negotiateNextProtocol: no remaining transports.");
+            notifyD2DStatus(false /* isAvailable */);
             return;
         }
         Log.i(this, "negotiateNextProtocol: trying %s",
@@ -218,7 +227,11 @@
     private TransportProtocol getNextCandidateProtocol() {
         TransportProtocol candidateProtocol = null;
         if (mActiveTransport == null) {
-            candidateProtocol = mTransportProtocols.get(0);
+            if (mTransportProtocols.size() > 0) {
+                candidateProtocol = mTransportProtocols.get(0);
+            } else {
+                mIsNegotiated = false;
+            }
         } else {
             for (int ix = 0; ix < mTransportProtocols.size(); ix++) {
                 TransportProtocol protocol = mTransportProtocols.get(ix);
@@ -234,6 +247,17 @@
         return candidateProtocol;
     }
 
+    /**
+     * Notifies listeners (okay, {@link com.android.services.telephony.TelephonyConnection} when
+     * the availability of D2D communication changes.
+     * @param isAvailable {@code true} if D2D is available, {@code false} otherwise.
+     */
+    private void notifyD2DStatus(boolean isAvailable) {
+        if (mCallback != null) {
+            mCallback.onD2DAvailabilitychanged(isAvailable);
+        }
+    }
+
     public static String messageToString(int messageType) {
         switch (messageType) {
             case MESSAGE_CALL_RADIO_ACCESS_TYPE:
@@ -291,4 +315,33 @@
         }
         return "";
     }
+
+    /**
+     * Test method used to force a transport type to be the active transport.
+     * @param transport The transport to activate.
+     */
+    public void setTransportActive(@NonNull String transport) {
+        Optional<TransportProtocol> tp = mTransportProtocols.stream()
+                .filter(t -> t.getClass().getSimpleName().equals(transport))
+                .findFirst();
+        if (!tp.isPresent()) {
+            Log.w(this, "setTransportActive: %s is not a valid transport.");
+            return;
+        }
+
+        mTransportProtocols.stream()
+                .filter(p -> p != tp.get())
+                .forEach(t -> t.forceNotNegotiated());
+        tp.get().forceNegotiated();
+        mActiveTransport = tp.get();
+        mIsNegotiated = true;
+        Log.i(this, "setTransportActive: %s has been forced active.", transport);
+    }
+
+    /**
+     * @return the list of {@link TransportProtocol} which are configured at the current time.
+     */
+    public @NonNull List<TransportProtocol> getTransportProtocols() {
+        return mTransportProtocols;
+    }
 }
diff --git a/src/java/com/android/internal/telephony/d2d/DtmfTransport.java b/src/java/com/android/internal/telephony/d2d/DtmfTransport.java
index 5ec9e50..de26037 100644
--- a/src/java/com/android/internal/telephony/d2d/DtmfTransport.java
+++ b/src/java/com/android/internal/telephony/d2d/DtmfTransport.java
@@ -611,4 +611,14 @@
             }
         }
     }
+
+    @Override
+    public void forceNegotiated() {
+
+    }
+
+    @Override
+    public void forceNotNegotiated() {
+
+    }
 }
diff --git a/src/java/com/android/internal/telephony/d2d/MessageTypeAndValueHelper.java b/src/java/com/android/internal/telephony/d2d/MessageTypeAndValueHelper.java
new file mode 100644
index 0000000..caf6575
--- /dev/null
+++ b/src/java/com/android/internal/telephony/d2d/MessageTypeAndValueHelper.java
@@ -0,0 +1,97 @@
+/*
+ * 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.d2d;
+
+import android.telecom.Connection;
+import android.telecom.CallDiagnostics;
+import android.telephony.TelephonyManager;
+
+import com.android.internal.telephony.BiMap;
+
+/**
+ * Helper class to map between the message types and values used in {@link Communicator} and those
+ * defined in the public API in {@link CallDiagnostics}.
+ */
+public class MessageTypeAndValueHelper {
+    // Maps between the local message and value types defined here and those defined in the
+    // DiagnosticCall class as part of the actual API.
+
+    /**
+     * Convert between the local message type (e.g.
+     * {@link Communicator#MESSAGE_CALL_RADIO_ACCESS_TYPE})
+     * and
+     * the ones referred to in {@link CallDiagnostics}.
+     */
+    public static final BiMap<Integer, Integer> MSG_TYPE_TO_DC_MSG_TYPE = new BiMap<>();
+
+    /**
+     * Convert between the local RAT type (e.g. {@link Communicator#RADIO_ACCESS_TYPE_IWLAN}) and
+     * the ones
+     * referred to by {@link CallDiagnostics#MESSAGE_CALL_NETWORK_TYPE}.
+     */
+    public static final BiMap<Integer, Integer> RAT_TYPE_TO_DC_NETWORK_TYPE = new BiMap<>();
+
+    /**
+     * Convert between the local codec (e.g. {@link Communicator#AUDIO_CODEC_AMR_WB}) and the ones
+     * referred to by {@link CallDiagnostics#MESSAGE_CALL_AUDIO_CODEC}.
+     */
+    public static final BiMap<Integer, Integer> CODEC_TO_DC_CODEC = new BiMap<>();
+
+    /**
+     * Convert between the local battery state (e.g. {@link Communicator#BATTERY_STATE_GOOD}) and
+     * the ones referred to by {@link CallDiagnostics#MESSAGE_DEVICE_BATTERY_STATE}.
+     */
+    public static final BiMap<Integer, Integer> BATTERY_STATE_TO_DC_BATTERY_STATE = new BiMap();
+
+    /**
+     * Convert between the local battery state (e.g. {@link Communicator#COVERAGE_GOOD}) and the
+     * ones referred to by {@link CallDiagnostics#MESSAGE_DEVICE_NETWORK_COVERAGE}.
+     */
+    public static final BiMap<Integer, Integer> COVERAGE_TO_DC_COVERAGE = new BiMap();
+
+    static {
+        MSG_TYPE_TO_DC_MSG_TYPE.put(Communicator.MESSAGE_CALL_RADIO_ACCESS_TYPE,
+                CallDiagnostics.MESSAGE_CALL_NETWORK_TYPE);
+        MSG_TYPE_TO_DC_MSG_TYPE.put(Communicator.MESSAGE_CALL_AUDIO_CODEC,
+                CallDiagnostics.MESSAGE_CALL_AUDIO_CODEC);
+        MSG_TYPE_TO_DC_MSG_TYPE.put(Communicator.MESSAGE_DEVICE_BATTERY_STATE,
+                CallDiagnostics.MESSAGE_DEVICE_BATTERY_STATE);
+        MSG_TYPE_TO_DC_MSG_TYPE.put(Communicator.MESSAGE_DEVICE_NETWORK_COVERAGE,
+                CallDiagnostics.MESSAGE_DEVICE_NETWORK_COVERAGE);
+
+        RAT_TYPE_TO_DC_NETWORK_TYPE.put(Communicator.RADIO_ACCESS_TYPE_LTE,
+                TelephonyManager.NETWORK_TYPE_LTE);
+        RAT_TYPE_TO_DC_NETWORK_TYPE.put(Communicator.RADIO_ACCESS_TYPE_IWLAN,
+                TelephonyManager.NETWORK_TYPE_IWLAN);
+        RAT_TYPE_TO_DC_NETWORK_TYPE.put(Communicator.RADIO_ACCESS_TYPE_NR,
+                TelephonyManager.NETWORK_TYPE_NR);
+
+        CODEC_TO_DC_CODEC.put(Communicator.AUDIO_CODEC_EVS, Connection.AUDIO_CODEC_EVS_WB);
+        CODEC_TO_DC_CODEC.put(Communicator.AUDIO_CODEC_AMR_WB, Connection.AUDIO_CODEC_AMR_WB);
+        CODEC_TO_DC_CODEC.put(Communicator.AUDIO_CODEC_AMR_NB, Connection.AUDIO_CODEC_AMR);
+
+        BATTERY_STATE_TO_DC_BATTERY_STATE.put(Communicator.BATTERY_STATE_LOW,
+                CallDiagnostics.BATTERY_STATE_LOW);
+        BATTERY_STATE_TO_DC_BATTERY_STATE.put(Communicator.BATTERY_STATE_GOOD,
+                CallDiagnostics.BATTERY_STATE_GOOD);
+        BATTERY_STATE_TO_DC_BATTERY_STATE.put(Communicator.BATTERY_STATE_CHARGING,
+                CallDiagnostics.BATTERY_STATE_CHARGING);
+
+        COVERAGE_TO_DC_COVERAGE.put(Communicator.COVERAGE_POOR, CallDiagnostics.COVERAGE_POOR);
+        COVERAGE_TO_DC_COVERAGE.put(Communicator.COVERAGE_GOOD, CallDiagnostics.COVERAGE_GOOD);
+    }
+}
diff --git a/src/java/com/android/internal/telephony/d2d/RtpTransport.java b/src/java/com/android/internal/telephony/d2d/RtpTransport.java
index eb86aba..595a94c 100644
--- a/src/java/com/android/internal/telephony/d2d/RtpTransport.java
+++ b/src/java/com/android/internal/telephony/d2d/RtpTransport.java
@@ -20,6 +20,7 @@
 import android.net.Uri;
 import android.os.Handler;
 import android.telecom.Log;
+import android.telephony.ims.ImsCallProfile;
 import android.telephony.ims.RtpHeaderExtension;
 import android.telephony.ims.RtpHeaderExtensionType;
 import android.util.ArraySet;
@@ -283,6 +284,15 @@
     private final Handler mHandler;
 
     /**
+     * {@code true} if the carrier supports negotiating the RTP header extensions using SDP.
+     * If {@code true}, we can expected the
+     * {@link ImsCallProfile#getAcceptedRtpHeaderExtensionTypes()} to contain the SDP negotiated RTP
+     * header extensions.  If {@code false} we will assume the protocol is negotiated only after
+     * receiving an RTP header extension of the expected type.
+     */
+    private final boolean mIsSdpNegotiationSupported;
+
+    /**
      * Protocol status.
      */
     private int mProtocolStatus = PROTOCOL_STATUS_NEGOTIATION_REQUIRED;
@@ -297,11 +307,14 @@
      * @param rtpAdapter Adapter for abstract send/receive of RTP header extension data.
      * @param timeoutsAdapter Timeouts adapter for dealing with time based configurations.
      * @param handler Handler for posting future events.
+     * @param isSdpNegotiationSupported Indicates whether SDP negotiation
      */
-    public RtpTransport(RtpAdapter rtpAdapter, Timeouts.Adapter timeoutsAdapter, Handler handler) {
+    public RtpTransport(RtpAdapter rtpAdapter, Timeouts.Adapter timeoutsAdapter, Handler handler,
+            boolean isSdpNegotiationSupported) {
         mRtpAdapter = rtpAdapter;
         mTimeoutsAdapter = timeoutsAdapter;
         mHandler = handler;
+        mIsSdpNegotiationSupported = isSdpNegotiationSupported;
     }
 
     /**
@@ -329,23 +342,36 @@
                 mRtpAdapter.getAcceptedRtpHeaderExtensions();
         mSupportedRtpHeaderExtensionTypes.addAll(acceptedExtensions);
 
-        boolean areExtensionsAvailable = acceptedExtensions.stream().anyMatch(
-                e -> e.getUri().equals(DEVICE_STATE_RTP_HEADER_EXTENSION))
-                && acceptedExtensions.stream().anyMatch(
+        Log.i(this, "startNegotiation: supportedExtensions=%s", mSupportedRtpHeaderExtensionTypes
+                .stream()
+                .map(e -> e.toString())
+                .collect(Collectors.joining(",")));
+
+        if (mIsSdpNegotiationSupported) {
+            boolean areExtensionsAvailable = acceptedExtensions.stream().anyMatch(
+                    e -> e.getUri().equals(DEVICE_STATE_RTP_HEADER_EXTENSION))
+                    && acceptedExtensions.stream().anyMatch(
                     e -> e.getUri().equals(CALL_STATE_RTP_HEADER_EXTENSION));
 
-        if (areExtensionsAvailable) {
-            // Headers were negotiated during SDP, so we can assume negotiation is complete and
-            // signal to the communicator that we can use this transport.
-            mProtocolStatus = PROTOCOL_STATUS_NEGOTIATION_COMPLETE;
-            Log.i(this, "startNegotiation: header extensions available, negotiation success");
-            notifyProtocolReady();
+            if (areExtensionsAvailable) {
+                // Headers were negotiated during SDP, so we can assume negotiation is complete and
+                // signal to the communicator that we can use this transport.
+                mProtocolStatus = PROTOCOL_STATUS_NEGOTIATION_COMPLETE;
+                Log.i(this, "startNegotiation: header extensions available, negotiation success");
+                notifyProtocolReady();
+            } else {
+                // Headers failed to be negotiated during SDP.   Assume protocol is not available.
+                // TODO: Implement fallback logic where we still try an SDP probe/response.
+                mProtocolStatus = PROTOCOL_STATUS_NEGOTIATION_FAILED;
+                Log.i(this,
+                        "startNegotiation: header extensions not available; negotiation failed");
+                notifyProtocolUnavailable();
+            }
         } else {
-            // Headers failed to be negotiated during SDP.   Assume protocol is not available.
-            // TODO: Implement fallback logic where we still try an SDP probe/response.
-            mProtocolStatus = PROTOCOL_STATUS_NEGOTIATION_FAILED;
-            Log.i(this, "startNegotiation: header extensions not available; negotiation failed");
-            notifyProtocolUnavailable();
+            Log.i(this, "startNegotiation: SDP negotiation not supported; negotiation complete");
+            // TODO: This is temporary; we will need to implement a probe/response in this scenario
+            // if SDP is not supported.  For now we will just assume the protocol is ready.
+            notifyProtocolReady();
         }
     }
 
@@ -358,10 +384,33 @@
     public void sendMessages(Set<Communicator.Message> messages) {
         Set<RtpHeaderExtension> toSend = messages.stream().map(m -> generateRtpHeaderExtension(m))
                 .collect(Collectors.toSet());
+        Log.i(this, "sendMessages: sending=%s", messages);
         mRtpAdapter.sendRtpHeaderExtensions(toSend);
     }
 
     /**
+     * Forces the protocol status to negotiated; for test purposes.
+     */
+    @Override
+    public void forceNegotiated() {
+        // If there is no supported RTP header extensions we need to fake it.
+        if (mSupportedRtpHeaderExtensionTypes == null
+                || mSupportedRtpHeaderExtensionTypes.isEmpty()) {
+            mSupportedRtpHeaderExtensionTypes.add(DEVICE_STATE_RTP_HEADER_EXTENSION_TYPE);
+            mSupportedRtpHeaderExtensionTypes.add(CALL_STATE_RTP_HEADER_EXTENSION_TYPE);
+        }
+        mProtocolStatus = PROTOCOL_STATUS_NEGOTIATION_COMPLETE;
+    }
+
+    /**
+     * Forces the protocol status to un-negotiated; for test purposes.
+     */
+    @Override
+    public void forceNotNegotiated() {
+        mProtocolStatus = PROTOCOL_STATUS_NEGOTIATION_REQUIRED;
+    }
+
+    /**
      * Called by the platform when RTP header extensions are received and need to be translated to
      * concrete messages.
      * Results in a callback via
diff --git a/src/java/com/android/internal/telephony/d2d/Timeouts.java b/src/java/com/android/internal/telephony/d2d/Timeouts.java
index 28ef0ec..e01c920 100644
--- a/src/java/com/android/internal/telephony/d2d/Timeouts.java
+++ b/src/java/com/android/internal/telephony/d2d/Timeouts.java
@@ -107,7 +107,7 @@
      * @return
      */
     public static long getDtmfNegotiationTimeoutMillis(ContentResolver cr) {
-        return get(cr, "dtmf_negotiation_timeout_millis", 1000L);
+        return get(cr, "dtmf_negotiation_timeout_millis", 3000L);
     }
 
     /**
diff --git a/src/java/com/android/internal/telephony/d2d/TransportProtocol.java b/src/java/com/android/internal/telephony/d2d/TransportProtocol.java
index 4943788..91bf7d3 100644
--- a/src/java/com/android/internal/telephony/d2d/TransportProtocol.java
+++ b/src/java/com/android/internal/telephony/d2d/TransportProtocol.java
@@ -70,4 +70,14 @@
      * @param messages the messages to send via the transport.
      */
     void sendMessages(Set<Communicator.Message> messages);
+
+    /**
+     * Forces this transport to be in a negotiated state.
+     */
+    void forceNegotiated();
+
+    /**
+     * Forces this transport to be in a non-negotiated state.
+     */
+    void forceNotNegotiated();
 }
diff --git a/src/java/com/android/internal/telephony/dataconnection/AccessNetworksManager.java b/src/java/com/android/internal/telephony/dataconnection/AccessNetworksManager.java
index 06f7c86..53b7515 100644
--- a/src/java/com/android/internal/telephony/dataconnection/AccessNetworksManager.java
+++ b/src/java/com/android/internal/telephony/dataconnection/AccessNetworksManager.java
@@ -41,10 +41,10 @@
 import android.telephony.data.QualifiedNetworksService;
 import android.telephony.data.ThrottleStatus;
 import android.text.TextUtils;
+import android.util.IndentingPrintWriter;
 import android.util.SparseArray;
 
 import com.android.internal.telephony.Phone;
-import com.android.internal.util.IndentingPrintWriter;
 import com.android.telephony.Rlog;
 
 import java.io.FileDescriptor;
@@ -194,7 +194,6 @@
                 registerDataThrottlersFirstTime();
 
             } catch (RemoteException e) {
-                mDeathRecipient.binderDied();
                 loge("Remote exception. " + e);
             }
         }
diff --git a/src/java/com/android/internal/telephony/dataconnection/ApnContext.java b/src/java/com/android/internal/telephony/dataconnection/ApnContext.java
index 6c1cec4..c130840 100644
--- a/src/java/com/android/internal/telephony/dataconnection/ApnContext.java
+++ b/src/java/com/android/internal/telephony/dataconnection/ApnContext.java
@@ -430,9 +430,7 @@
 
     public void releaseNetwork(NetworkRequest networkRequest, @ReleaseNetworkType int type) {
         synchronized (mRefCountLock) {
-            if (mNetworkRequests.contains(networkRequest) == false) {
-                logl("releaseNetwork can't find this request (" + networkRequest + ")");
-            } else {
+            if (mNetworkRequests.contains(networkRequest)) {
                 mNetworkRequests.remove(networkRequest);
                 if (mDataConnection != null) {
                     // New network request added. Should re-evaluate properties of
diff --git a/src/java/com/android/internal/telephony/dataconnection/DataConnection.java b/src/java/com/android/internal/telephony/dataconnection/DataConnection.java
index f98f549..85d3974 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DataConnection.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DataConnection.java
@@ -88,13 +88,13 @@
 import com.android.internal.telephony.RIL;
 import com.android.internal.telephony.RILConstants;
 import com.android.internal.telephony.RetryManager;
-import com.android.internal.telephony.ServiceStateTracker;
 import com.android.internal.telephony.TelephonyStatsLog;
 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.util.AsyncChannel;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Protocol;
@@ -315,6 +315,10 @@
     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;
@@ -386,7 +390,8 @@
     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;
-    private static final int CMD_TO_STRING_COUNT = EVENT_RELEASE_PDU_SESSION_ID - BASE + 1;
+    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 {
@@ -436,6 +441,8 @@
         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) {
@@ -485,28 +492,6 @@
         return new LinkProperties(mLinkProperties);
     }
 
-    boolean isInactive() {
-        return getCurrentState() == mInactiveState;
-    }
-
-    boolean isSuspended() {
-        // Data can only be (temporarily) suspended while data is in active state
-        if (getCurrentState() != mActiveState) return false;
-
-        // if we are not in-service change to SUSPENDED
-        final ServiceStateTracker sst = mPhone.getServiceStateTracker();
-        if (sst.getCurrentDataConnectionState() != ServiceState.STATE_IN_SERVICE) {
-            return true;
-        }
-
-        // check for voice call and concurrency issues
-        if (!sst.isConcurrentVoiceAndDataAllowed()) {
-            return mPhone.getCallTracker().getState() != PhoneConstants.State.IDLE;
-        }
-
-        return false;
-    }
-
     boolean isDisconnecting() {
         return getCurrentState() == mDisconnectingState
                 || getCurrentState() == mDisconnectingErrorCreatingConnection;
@@ -517,6 +502,11 @@
         return getCurrentState() == mActiveState;
     }
 
+    @VisibleForTesting
+    public boolean isInactive() {
+        return getCurrentState() == mInactiveState;
+    }
+
     boolean isActivating() {
         return getCurrentState() == mActivatingState;
     }
@@ -842,6 +832,10 @@
         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();
     }
 
@@ -909,10 +903,9 @@
                 || isUnmeteredApnType));
 
         String dnn = null;
-        String osAppId = null;
+        byte[] osAppId = null;
         if (cp.mApnContext.getApnTypeBitmask() == ApnSetting.TYPE_ENTERPRISE) {
-            osAppId = NetworkCapabilities.getCapabilityCarrierName(
-                    NetworkCapabilities.NET_CAPABILITY_ENTERPRISE);
+            osAppId = getEnterpriseOsAppId();
         } else {
             dnn = mApnSetting.getApnName();
         }
@@ -978,6 +971,7 @@
             return DataFailCause.NONE;
         }
 
+        // setup data call for REQUEST_TYPE_NORMAL
         allocatePduSessionId(psi -> {
             this.setPduSessionId(psi);
             mDataServiceManager.setupDataCall(
@@ -1004,14 +998,14 @@
             msg.obj = allocateCallback;
             mPhone.mCi.allocatePduSessionId(msg);
         } else {
-            allocateCallback.accept(EVENT_ALLOCATE_PDU_SESSION_ID);
+            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);
+                DataCallResponse.HANDOVER_FAILURE_MODE_UNKNOWN, false);
     }
 
     private void requestHandover(boolean inCorrectState, DataConnection srcDc,
@@ -1044,6 +1038,8 @@
         }
 
         if (srcDc == null) {
+            loge("requestHandover: Cannot find source data connection.");
+            onRquestHandoverFailed(cp);
             return;
         }
 
@@ -1055,8 +1051,7 @@
         mHandoverSourceNetworkAgent = srcDc.getNetworkAgent();
         if (mHandoverSourceNetworkAgent == null) {
             loge("requestHandover: Cannot get network agent from the source dc " + srcDc.getName());
-            notifyConnectCompleted(cp, DataFailCause.UNKNOWN,
-                    DataCallResponse.HANDOVER_FAILURE_MODE_UNKNOWN, false);
+            onRquestHandoverFailed(cp);
             return;
         }
 
@@ -1096,7 +1091,8 @@
     /**
      * Called on the source data connection from the target data connection.
      */
-    private void startHandover(Consumer<Integer> onTargetDcComplete) {
+    @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);
@@ -1384,6 +1380,9 @@
         } 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;
@@ -1403,13 +1402,12 @@
         } 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();
-            updateQosParameters(response);
-            updateSliceInfo(response);
 
             if (response.getPduSessionId() != getPduSessionId()) {
                 if (getDoAllocatePduSessionId()) {
@@ -1612,28 +1610,60 @@
                 uplinkUpdated = true;
             }
         }
+
         if (!downlinkUpdated || !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;
-                }
-            }
+            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));
@@ -1783,9 +1813,8 @@
     private boolean isEnterpriseUse() {
         boolean enterpriseTrafficDescriptor = mTrafficDescriptors
                 .stream()
-                .anyMatch(td -> td.getOsAppId() != null && td.getOsAppId().equals(
-                        NetworkCapabilities.getCapabilityCarrierName(
-                                NetworkCapabilities.NET_CAPABILITY_ENTERPRISE)));
+                .anyMatch(td -> td.getOsAppId() != null && Arrays.equals(td.getOsAppId(),
+                        getEnterpriseOsAppId()));
         boolean enterpriseApnContext = mApnContexts.keySet()
                 .stream()
                 .anyMatch(ac -> ac.getApnTypeBitmask() == ApnSetting.TYPE_ENTERPRISE);
@@ -1877,7 +1906,6 @@
             }
         }
 
-
         // 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.
@@ -1887,12 +1915,13 @@
             builder.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
         }
 
-        if (NetworkCapabilitiesUtils.inferRestrictedCapability(builder.build())) {
-            builder.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
-        }
-
         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) {
@@ -2197,6 +2226,10 @@
                     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);
@@ -2214,6 +2247,9 @@
             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);
@@ -2289,8 +2325,8 @@
                     mRilRat = drsRatPair.second;
                     if (DBG) {
                         log("DcDefaultState: EVENT_DATA_CONNECTION_DRS_OR_RAT_CHANGED"
-                                + " drs=" + mDataRegState
-                                + " mRilRat=" + mRilRat);
+                                + " regState=" + ServiceState.rilServiceStateToString(mDataRegState)
+                                + " RAT=" + ServiceState.rilRadioTechnologyToString(mRilRat));
                     }
                     mDataCallSessionStats.onDrsOrRatChanged(mRilRat);
                     break;
@@ -2373,12 +2409,28 @@
             Rlog.d(getName(), "Setting suspend state without a NetworkAgent");
         }
 
-        boolean suspended = isSuspended();
-        if (mIsSuspended != suspended) {
-            mIsSuspended = suspended;
+        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.
+            // changed event reflecting the latest suspended state.
             if (isActive()) {
                 notifyDataConnectionState();
             }
@@ -2386,11 +2438,27 @@
     }
 
     private void notifyDataConnectionState() {
-        mPhone.notifyDataConnection(getPreciseDataConnectionState());
+        // 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.
      */
@@ -2426,10 +2494,7 @@
             if (DBG) log("DcInactiveState: enter() mTag=" + mTag);
             TelephonyStatsLog.write(TelephonyStatsLog.MOBILE_CONNECTION_STATE_CHANGED,
                     TelephonyStatsLog.MOBILE_CONNECTION_STATE_CHANGED__STATE__INACTIVE,
-                    mPhone.getPhoneId(), mId,
-                    mApnSetting != null ? (long) mApnSetting.getApnTypeBitmask() : 0L,
-                    mApnSetting != null
-                        ? mApnSetting.canHandleType(ApnSetting.TYPE_DEFAULT) : false);
+                    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
@@ -2566,12 +2631,10 @@
     private class DcActivatingState extends State {
         @Override
         public void enter() {
-            int apnTypeBitmask = mApnSetting != null ? mApnSetting.getApnTypeBitmask() : 0;
+            int apnTypeBitmask = getApnTypeBitmask();
             TelephonyStatsLog.write(TelephonyStatsLog.MOBILE_CONNECTION_STATE_CHANGED,
                     TelephonyStatsLog.MOBILE_CONNECTION_STATE_CHANGED__STATE__ACTIVATING,
-                    mPhone.getPhoneId(), mId, (long) apnTypeBitmask,
-                    mApnSetting != null
-                        ? mApnSetting.canHandleType(ApnSetting.TYPE_DEFAULT) : false);
+                    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
@@ -2683,7 +2746,7 @@
                             }
                             int newRequestType = DcTracker.calculateNewRetryRequestType(
                                     mHandoverFailureMode, cp.mRequestType, mDcFailCause);
-                            mDct.getDataThrottler().setRetryTime(mApnSetting.getApnTypeBitmask(),
+                            mDct.getDataThrottler().setRetryTime(getApnTypeBitmask(),
                                     retryTime, newRequestType);
 
                             String str = "DcActivatingState: ERROR_DATA_SERVICE_SPECIFIC_ERROR "
@@ -2701,7 +2764,9 @@
                             // failure cause and determine if we need to retry this APN later
                             // or not.
                             mInactiveState.setEnterNotificationParams(cp, result.mFailCause,
-                                    dataCallResponse.getHandoverFailureMode());
+                                    dataCallResponse != null
+                                            ? dataCallResponse.getHandoverFailureMode()
+                                            : DataCallResponse.HANDOVER_FAILURE_MODE_UNKNOWN);
                             transitionTo(mInactiveState);
                             break;
                         case ERROR_STALE:
@@ -2714,7 +2779,7 @@
                     retVal = HANDLED;
                     mDataCallSessionStats
                             .onSetupDataCallResponse(dataCallResponse, cp.mRilRat,
-                                    mApnSetting.getApnTypeBitmask(), mApnSetting.getProtocol(),
+                                    getApnTypeBitmask(), mApnSetting.getProtocol(),
                                     result.mFailCause);
                     break;
                 case EVENT_CARRIER_PRIVILEGED_UIDS_CHANGED:
@@ -2754,10 +2819,7 @@
             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,
-                    mApnSetting != null ? (long) mApnSetting.getApnTypeBitmask() : 0L,
-                    mApnSetting != null
-                        ? mApnSetting.canHandleType(ApnSetting.TYPE_DEFAULT) : false);
+                    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);
@@ -2899,9 +2961,8 @@
                         getHandler(), DataConnection.EVENT_LINK_CAPACITY_CHANGED, null);
             }
             notifyDataConnectionState();
-            int apnBitMask = mApnSetting.getApnTypeBitmask();
             TelephonyMetrics.getInstance().writeRilDataCallEvent(mPhone.getPhoneId(),
-                    mCid, apnBitMask, RilDataCall.State.CONNECTED);
+                    mCid, getApnTypeBitmask(), RilDataCall.State.CONNECTED);
         }
 
         @Override
@@ -2929,7 +2990,7 @@
             mNetworkAgent = null;
 
             TelephonyMetrics.getInstance().writeRilDataCallEvent(mPhone.getPhoneId(),
-                    mCid, mApnSetting.getApnTypeBitmask(), RilDataCall.State.DISCONNECTED);
+                    mCid, getApnTypeBitmask(), RilDataCall.State.DISCONNECTED);
 
             mVcnManager.removeVcnNetworkPolicyChangeListener(mVcnPolicyChangeListener);
 
@@ -3230,6 +3291,20 @@
                     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.
@@ -3323,10 +3398,7 @@
         public void enter() {
             TelephonyStatsLog.write(TelephonyStatsLog.MOBILE_CONNECTION_STATE_CHANGED,
                     TelephonyStatsLog.MOBILE_CONNECTION_STATE_CHANGED__STATE__DISCONNECTING,
-                    mPhone.getPhoneId(), mId,
-                    mApnSetting != null ? (long) mApnSetting.getApnTypeBitmask() : 0L,
-                    mApnSetting != null
-                        ? mApnSetting.canHandleType(ApnSetting.TYPE_DEFAULT) : false);
+                    mPhone.getPhoneId(), mId, getApnTypeBitmask(), canHandleDefault());
             notifyDataConnectionState();
         }
         @Override
@@ -3387,10 +3459,7 @@
             TelephonyStatsLog.write(TelephonyStatsLog.MOBILE_CONNECTION_STATE_CHANGED,
                     TelephonyStatsLog
                             .MOBILE_CONNECTION_STATE_CHANGED__STATE__DISCONNECTION_ERROR_CREATING_CONNECTION,
-                    mPhone.getPhoneId(), mId,
-                    mApnSetting != null ? (long) mApnSetting.getApnTypeBitmask() : 0L,
-                    mApnSetting != null
-                        ? mApnSetting.canHandleType(ApnSetting.TYPE_DEFAULT) : false);
+                    mPhone.getPhoneId(), mId, getApnTypeBitmask(), canHandleDefault());
             notifyDataConnectionState();
         }
         @Override
@@ -3578,6 +3647,9 @@
          * 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();
 
@@ -3634,7 +3706,7 @@
         }
     }
 
-    /** Sets the {@link DataCallSessionStats} mock for this phone ID during unit testing. */
+    /** Sets the {@link DataCallSessionStats} mock for this data connection during unit testing. */
     @VisibleForTesting
     public void setDataCallSessionStats(DataCallSessionStats dataCallSessionStats) {
         mDataCallSessionStats = dataCallSessionStats;
@@ -3876,7 +3948,7 @@
             return TelephonyManager.DATA_CONNECTING;
         } else if (isActive()) {
             // The data connection can only be suspended when it's in active state.
-            if (isSuspended()) {
+            if (mIsSuspended) {
                 return TelephonyManager.DATA_SUSPENDED;
             }
             return TelephonyManager.DATA_CONNECTED;
diff --git a/src/java/com/android/internal/telephony/dataconnection/DataServiceManager.java b/src/java/com/android/internal/telephony/dataconnection/DataServiceManager.java
index 38cbdab..3ba577a 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DataServiceManager.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DataServiceManager.java
@@ -39,7 +39,7 @@
 import android.os.RegistrantList;
 import android.os.RemoteException;
 import android.os.UserHandle;
-import android.permission.PermissionManager;
+import android.permission.LegacyPermissionManager;
 import android.telephony.AccessNetworkConstants;
 import android.telephony.AccessNetworkConstants.TransportType;
 import android.telephony.AnomalyReporter;
@@ -91,7 +91,7 @@
 
     private final CarrierConfigManager mCarrierConfigManager;
     private final AppOpsManager mAppOps;
-    private final PermissionManager mPermissionManager;
+    private final LegacyPermissionManager mPermissionManager;
 
     private final int mTransportType;
 
@@ -222,6 +222,7 @@
             mDeathRecipient = new DataServiceManagerDeathRecipient();
             mBound = true;
             mLastBoundPackageName = getDataServicePackageName();
+            removeMessages(EVENT_WATCHDOG_TIMEOUT);
 
             try {
                 service.linkToDeath(mDeathRecipient, 0);
@@ -231,11 +232,9 @@
                 mIDataService.registerForUnthrottleApn(mPhone.getPhoneId(),
                         new CellularDataServiceCallback("unthrottleApn"));
             } catch (RemoteException e) {
-                mDeathRecipient.binderDied();
                 loge("Remote exception. " + e);
                 return;
             }
-            removeMessages(EVENT_WATCHDOG_TIMEOUT);
             mServiceBindingChangedRegistrants.notifyResult(true);
         }
         @Override
@@ -357,8 +356,8 @@
         // 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 =
-                (PermissionManager) phone.getContext().getSystemService(Context.PERMISSION_SERVICE);
+        mPermissionManager = (LegacyPermissionManager) phone.getContext().getSystemService(
+                Context.LEGACY_PERMISSION_SERVICE);
         mAppOps = (AppOpsManager) phone.getContext().getSystemService(Context.APP_OPS_SERVICE);
 
         IntentFilter intentFilter = new IntentFilter();
@@ -502,7 +501,7 @@
      *
      * @return package name of the data service package for the the current transportType.
      */
-    private String getDataServicePackageName() {
+    public String getDataServicePackageName() {
         return getDataServicePackageName(mTransportType);
     }
 
diff --git a/src/java/com/android/internal/telephony/dataconnection/DcController.java b/src/java/com/android/internal/telephony/dataconnection/DcController.java
index f21839c..2ee0f07 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DcController.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DcController.java
@@ -29,6 +29,7 @@
 import android.telephony.data.ApnSetting;
 import android.telephony.data.DataCallResponse;
 
+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;
@@ -89,8 +90,8 @@
     /**
      * Aggregated physical link state from all data connections. This reflects the device's RRC
      * connection state.
-     * // TODO: Instead of tracking the RRC state here, we should make PhysicalChannelConfig work in
-     *          S.
+     * 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 @PhysicalLinkState int mPhysicalLinkState = PHYSICAL_LINK_UNKNOWN;
 
@@ -229,6 +230,7 @@
 
         boolean isAnyDataCallDormant = false;
         boolean isAnyDataCallActive = false;
+        boolean isInternetDataCallActive = false;
 
         for (DataCallResponse newState : dcsList) {
 
@@ -249,6 +251,11 @@
                     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);
@@ -359,8 +366,12 @@
 
         if (mDataServiceManager.getTransportType()
                 == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) {
-            int physicalLinkState = isAnyDataCallActive
-                    ? PHYSICAL_LINK_ACTIVE : PHYSICAL_LINK_NOT_ACTIVE;
+            boolean isPhysicalLinkStateFocusingOnInternetData =
+                    mDct.getLteEndcUsingUserDataForIdleDetection();
+            int physicalLinkState =
+                    (isPhysicalLinkStateFocusingOnInternetData
+                            ? isInternetDataCallActive : isAnyDataCallActive)
+                            ? PHYSICAL_LINK_ACTIVE : PHYSICAL_LINK_NOT_ACTIVE;
             if (mPhysicalLinkState != physicalLinkState) {
                 mPhysicalLinkState = physicalLinkState;
                 mPhysicalLinkStateChangedRegistrants.notifyResult(mPhysicalLinkState);
@@ -408,11 +419,13 @@
 
     /**
      * Register for physical link state (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
      */
-    void registerForPhysicalLinkStateChanged(Handler h, int what) {
+    @VisibleForTesting
+    public void registerForPhysicalLinkStateChanged(Handler h, int what) {
         mPhysicalLinkStateChangedRegistrants.addUnique(h, what, null);
     }
 
diff --git a/src/java/com/android/internal/telephony/dataconnection/DcNetworkAgent.java b/src/java/com/android/internal/telephony/dataconnection/DcNetworkAgent.java
index 6a9565f..8f65f94 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DcNetworkAgent.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DcNetworkAgent.java
@@ -44,6 +44,7 @@
 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.metrics.TelephonyMetrics;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.telephony.Rlog;
@@ -59,6 +60,7 @@
 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
@@ -83,7 +85,7 @@
 
     public final DcKeepaliveTracker keepaliveTracker = new DcKeepaliveTracker();
 
-    private final QosCallbackTracker mQosCallbackTracker = new QosCallbackTracker(this);
+    private final QosCallbackTracker mQosCallbackTracker;
 
     private final Executor mQosCallbackExecutor = Executors.newSingleThreadExecutor();
 
@@ -94,6 +96,9 @@
     // 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;
+
     DcNetworkAgent(DataConnection dc, Phone phone, int score, NetworkAgentConfig config,
             NetworkProvider networkProvider, int transportType) {
         super(phone.getContext(), dc.getHandler().getLooper(), "DcNetworkAgent",
@@ -113,6 +118,7 @@
         } else {
             loge("The connection does not have a valid link properties.");
         }
+        mQosCallbackTracker = new QosCallbackTracker(this);
     }
 
     private @NetworkType int getNetworkType() {
@@ -188,8 +194,13 @@
         return mDataConnection;
     }
 
+    private static final SlidingWindowEventCounter sNetworkUnwantedCounter =
+            new SlidingWindowEventCounter(NETWORK_UNWANTED_ANOMALY_WINDOW_MS,
+                    NETWORK_UNWANTED_ANOMALY_NUM_OCCURRENCES);
+
     @Override
     public synchronized void onNetworkUnwanted() {
+        trackNetworkUnwanted();
         if (mDataConnection == null) {
             loge("onNetworkUnwanted found called on no-owner DcNetworkAgent!");
             return;
@@ -201,6 +212,27 @@
                 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.");
+        }
+    }
+
     @Override
     public synchronized void onBandwidthUpdateRequested() {
         if (mDataConnection == null) {
diff --git a/src/java/com/android/internal/telephony/dataconnection/DcTracker.java b/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
index e331d1c..6305e06 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
@@ -353,6 +353,9 @@
     private boolean mNrSaSub6Unmetered = false;
     private boolean mNrNsaRoamingUnmetered = false;
 
+    // it effect the PhysicalLinkStateChanged
+    private boolean mLteEndcUsingUserDataForRrcDetection = false;
+
     // stats per data call recovery event
     private DataStallRecoveryStats mDataStallRecoveryStats;
 
@@ -1237,6 +1240,8 @@
      * <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);
@@ -1517,9 +1522,12 @@
             }
 
             // 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.
+            // 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);
             }
@@ -1661,7 +1669,14 @@
 
             if (DBG) log(str.toString());
             apnContext.requestLog(str.toString());
-            sendHandoverCompleteMessages(apnContext.getApnTypeBitmask(), false, false);
+            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;
         }
 
@@ -1686,8 +1701,10 @@
                 String str = "trySetupData: X No APN found retValue=false";
                 if (DBG) log(str);
                 apnContext.requestLog(str);
-                sendHandoverCompleteMessages(apnContext.getApnTypeBitmask(), false,
-                        false);
+                if (requestType == REQUEST_TYPE_HANDOVER) {
+                    sendHandoverCompleteMessages(apnContext.getApnTypeBitmask(), false,
+                            false);
+                }
                 return;
             } else {
                 apnContext.setWaitingApns(waitingApns);
@@ -1698,7 +1715,8 @@
             }
         }
 
-        if (!setupData(apnContext, radioTech, requestType)) {
+        if (!setupData(apnContext, radioTech, requestType)
+                && requestType == REQUEST_TYPE_HANDOVER) {
             sendHandoverCompleteMessages(apnContext.getApnTypeBitmask(), false, false);
         }
     }
@@ -2039,6 +2057,9 @@
     }
 
     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()),
@@ -2089,7 +2110,15 @@
         return null;
     }
 
-    boolean isPermanentFailure(@DataFailureCause int dcFailCause) {
+    /**
+     * 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));
@@ -2310,6 +2339,7 @@
         // 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();
@@ -2419,9 +2449,13 @@
 
     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) {
@@ -2458,7 +2492,6 @@
         }
 
         mAutoAttachEnabled.set(false);
-        setDefaultDataRoamingEnabled();
         setDefaultPreferredApnIfNeeded();
         read5GConfiguration();
         registerSettingsObserver();
@@ -2498,6 +2531,7 @@
         readConfiguration();
 
         if (mSimState == TelephonyManager.SIM_STATE_LOADED) {
+            setDefaultDataRoamingEnabled();
             createAllApnList();
             setDataProfilesAsNeeded();
             setInitialAttachApn();
@@ -3077,7 +3111,8 @@
             Log.wtf(mLogTag, "bad failure mode: "
                     + DataCallResponse.failureModeToString(handoverFailureMode));
         } else if (handoverFailureMode
-                != DataCallResponse.HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_HANDOVER) {
+                != DataCallResponse.HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_HANDOVER
+                && cause != DataFailCause.SERVICE_TEMPORARILY_UNAVAILABLE) {
             sendHandoverCompleteMessages(apnContext.getApnTypeBitmask(), success,
                     fallbackOnFailedHandover);
         }
@@ -3284,10 +3319,6 @@
                         + ". Request type=" + requestTypeToString(requestType) + ", Retry in "
                         + delay + "ms.");
             }
-            apnContext.setState(DctConstants.State.RETRYING);
-            // Wait a bit before trying the next APN, so that
-            // we're not tying up the RIL command channel
-
             startReconnect(delay, apnContext, requestType);
         } else {
             // If we are not going to retry any APN, set this APN context to failed state.
@@ -3671,7 +3702,15 @@
             if (mPreferredApn.getOperatorNumeric().equals(operator)) {
                 if (mPreferredApn.canSupportNetworkType(
                         ServiceState.rilRadioTechnologyToNetworkType(radioTech))) {
-                    apnList.add(mPreferredApn);
+                    // 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;
                 }
@@ -3690,7 +3729,15 @@
                     if (apn.getApnSetId() == Telephony.Carriers.MATCH_ALL_APN_SET_ID
                             || preferredApnSetId == apn.getApnSetId()) {
                         if (VDBG) log("buildWaitingApns: adding apn=" + apn);
-                        apnList.add(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);
@@ -4542,7 +4589,7 @@
         boolean isNrNsa = (networkType == TelephonyManager.NETWORK_TYPE_LTE
                 || networkType == TelephonyManager.NETWORK_TYPE_LTE_CA)
                 && (overrideNetworkType == TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA
-                || overrideNetworkType == TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE);
+                || overrideNetworkType == TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED);
         boolean is5GHysteresisActive = mPhone.getDisplayInfoController().is5GHysteresisActive();
 
         // True if device is on NR SA or NR NSA, or neither but 5G hysteresis is active
@@ -5496,6 +5543,9 @@
                     cleanUpAllConnectionsInternal(false, Phone.REASON_IWLAN_DATA_SERVICE_DIED);
                 }
             }
+        } else {
+            //reset throttling after binding to data service
+            mDataThrottler.reset();
         }
         mDataServiceBound = bound;
     }
@@ -5567,13 +5617,21 @@
                         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 state (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
diff --git a/src/java/com/android/internal/telephony/dataconnection/LinkBandwidthEstimator.java b/src/java/com/android/internal/telephony/dataconnection/LinkBandwidthEstimator.java
new file mode 100644
index 0000000..3ab6ddf
--- /dev/null
+++ b/src/java/com/android/internal/telephony/dataconnection/LinkBandwidthEstimator.java
@@ -0,0 +1,1214 @@
+/*
+ * 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.dataconnection;
+
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.hardware.display.DisplayManager;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.os.AsyncResult;
+import android.os.Handler;
+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.CellIdentity;
+import android.telephony.CellIdentityGsm;
+import android.telephony.CellIdentityLte;
+import android.telephony.CellIdentityNr;
+import android.telephony.CellIdentityTdscdma;
+import android.telephony.CellIdentityWcdma;
+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.util.ArrayMap;
+import android.util.LocalLog;
+import android.util.Pair;
+import android.view.Display;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.DctConstants;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.TelephonyFacade;
+import com.android.internal.telephony.metrics.TelephonyMetrics;
+import com.android.internal.telephony.nano.TelephonyProto.NrMode;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.telephony.Rlog;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Link Bandwidth Estimator based on the byte counts in TrafficStats and the time reported in modem
+ * activity.
+ */
+public class LinkBandwidthEstimator extends Handler {
+    private static final String TAG = LinkBandwidthEstimator.class.getSimpleName();
+    private static final boolean DBG = false;
+    @VisibleForTesting
+    static final int MSG_SCREEN_STATE_CHANGED = 1;
+    @VisibleForTesting
+    static final int MSG_TRAFFIC_STATS_POLL = 2;
+    @VisibleForTesting
+    static final int MSG_MODEM_ACTIVITY_RETURNED = 3;
+    @VisibleForTesting
+    static final int MSG_DEFAULT_NETWORK_CHANGED = 4;
+    @VisibleForTesting
+    static final int MSG_SIGNAL_STRENGTH_CHANGED = 5;
+    @VisibleForTesting
+    static final int MSG_NR_FREQUENCY_CHANGED = 6;
+    @VisibleForTesting
+    static final int MSG_NR_STATE_CHANGED = 7;
+    @VisibleForTesting
+    static final int MSG_ACTIVE_PHONE_CHANGED = 8;
+    @VisibleForTesting
+    static final int MSG_DATA_REG_STATE_OR_RAT_CHANGED = 9;
+
+    // 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;
+    private static final int TRAFFIC_MODEM_POLL_BYTE_RATIO = 8;
+    private static final int TRAFFIC_POLL_BYTE_THRESHOLD_MAX = 20_000;
+    private static final int BYTE_DELTA_ACC_THRESHOLD_MAX_KB = 8_000;
+    private static final int MODEM_POLL_TIME_DELTA_MAX_MS = 10_000;
+    private static final int FILTER_UPDATE_MAX_INTERVAL_MS = 5_100;
+    // BW samples with Tx or Rx time below the following value is ignored.
+    private static final int TX_RX_TIME_MIN_MS = 200;
+    // The large time constant used in BW filter
+    private static final int TIME_CONSTANT_LARGE_SEC = 6;
+    // The small time constant used in BW filter
+    private static final int TIME_CONSTANT_SMALL_SEC = 6;
+    // If RSSI changes by more than the below value, update BW filter with small time constant
+    private static final int RSSI_DELTA_THRESHOLD_DB = 6;
+    // The up-scaling factor of filter coefficient.
+    private static final int FILTER_SCALE = 128;
+    // Force weight to 0 if the elapsed time is above LARGE_TIME_DECAY_RATIO * time constant
+    private static final int LARGE_TIME_DECAY_RATIO = 4;
+    // Modem Tx time may contain Rx time as defined in HAL. To work around the issue, if Tx time
+    // over Rx time ratio is above the following value, use Tx time + Rx time as Rx time.
+    private static final int TX_OVER_RX_TIME_RATIO_THRESHOLD_NUM = 3;
+    private static final int TX_OVER_RX_TIME_RATIO_THRESHOLD_DEN = 2;
+    // Default Link bandwidth value if the RAT entry is not found in static BW table.
+    private static final int DEFAULT_LINK_BAND_WIDTH_KBPS = 14;
+    // If Tx or Rx link bandwidth change is above the following value, send the BW update
+    private static final int BW_UPDATE_THRESHOLD_PERCENT = 15;
+
+    // To be used in link bandwidth estimation, each TrafficStats poll sample needs to be above
+    // a predefine threshold.
+    // For RAT with static BW above HIGH_BANDWIDTH_THRESHOLD_KBPS, it uses the following table.
+    // For others RATs, the thresholds are derived from the static BW values.
+    // The following table is defined per signal level, int [NUM_SIGNAL_LEVEL].
+    private static final int HIGH_BANDWIDTH_THRESHOLD_KBPS = 5000;
+    //Array dimension : int [NUM_LINK_DIRECTION][NUM_SIGNAL_LEVEL]
+    private static final int[][] BYTE_DELTA_THRESHOLD_KB =
+            {{200, 300, 400, 600, 1000}, {400, 600, 800, 1000, 1000}};
+    // Used to derive byte count threshold from avg BW
+    private static final int LOW_BW_TO_AVG_BW_RATIO_NUM = 3;
+    private static final int LOW_BW_TO_AVG_BW_RATIO_DEN = 8;
+    private static final int MAX_BW_TO_STATIC_BW_RATIO = 15;
+    private static final int BYTE_DELTA_THRESHOLD_MIN_KB = 10;
+    private static final int MAX_ERROR_PERCENT = 100 * 100;
+    private static final String[] AVG_BW_PER_RAT = {
+            "GPRS:24,24", "EDGE:70,18", "UMTS:115,115", "CDMA:14,14",
+            "CDMA - 1xRTT:30,30", "CDMA - EvDo rev. 0:750,48", "CDMA - EvDo rev. A:950,550",
+            "HSDPA:4300,620", "HSUPA:4300,1800", "HSPA:4300,1800", "CDMA - EvDo rev. B:1500,550",
+            "CDMA - eHRPD:750,48", "HSPA+:13000,3400", "TD_SCDMA:115,115",
+            "LTE:30000,15000", "NR_NSA:47000,18000",
+            "NR_NSA_MMWAVE:145000,60000", "NR:145000,60000", "NR_MMWAVE:145000,60000"};
+    private static final Map<String, Pair<Integer, Integer>> AVG_BW_PER_RAT_MAP = new ArrayMap<>();
+    private static final String UNKNOWN_PLMN = "";
+
+    // To be used in the long term avg, each count needs to be above the following value
+    public static final int BW_STATS_COUNT_THRESHOLD = 5;
+    public static final int NUM_SIGNAL_LEVEL = 5;
+    public static final int LINK_TX = 0;
+    public static final int LINK_RX = 1;
+    public static final int NUM_LINK_DIRECTION = 2;
+
+    // One common timestamp for all sim to avoid frequent modem polling
+    private final Phone mPhone;
+    private final TelephonyFacade mTelephonyFacade;
+    private final TelephonyManager mTelephonyManager;
+    private final ConnectivityManager mConnectivityManager;
+    private final LocalLog mLocalLog = new LocalLog(512);
+    private boolean mScreenOn = false;
+    private boolean mIsOnDefaultRoute = false;
+    private boolean mIsOnActiveData = false;
+    private long mLastModemPollTimeMs;
+    private boolean mLastTrafficValid = true;
+    private long mLastMobileTxBytes;
+    private long mLastMobileRxBytes;
+    private long mTxBytesDeltaAcc;
+    private long mRxBytesDeltaAcc;
+
+    private ModemActivityInfo mLastModemActivityInfo = null;
+    private final TelephonyCallback mTelephonyCallback = new TelephonyCallbackImpl();
+    private int mSignalStrengthDbm;
+    private int mSignalLevel;
+    private int mDataRat = TelephonyManager.NETWORK_TYPE_UNKNOWN;
+    private int mTac;
+    private String mPlmn = UNKNOWN_PLMN;
+    private NetworkCapabilities mNetworkCapabilities;
+    private NetworkBandwidth mPlaceholderNetwork;
+    private long mFilterUpdateTimeMs;
+
+    private int mBandwidthUpdateSignalDbm = -1;
+    private int mBandwidthUpdateSignalLevel = -1;
+    private int mBandwidthUpdateDataRat = TelephonyManager.NETWORK_TYPE_UNKNOWN;
+    private String mBandwidthUpdatePlmn = UNKNOWN_PLMN;
+    private BandwidthState mTxState = new BandwidthState(LINK_TX);
+    private BandwidthState mRxState = new BandwidthState(LINK_RX);
+    private RegistrantList mBandwidthChangedRegistrants = new RegistrantList();
+    private long mLastPlmnOrRatChangeTimeMs;
+    private long mLastDrsOrRatChangeTimeMs;
+
+    private static void initAvgBwPerRatTable() {
+        for (String config : AVG_BW_PER_RAT) {
+            int rxKbps = 14;
+            int txKbps = 14;
+            String[] kv = config.split(":");
+            if (kv.length == 2) {
+                String[] split = kv[1].split(",");
+                if (split.length == 2) {
+                    try {
+                        rxKbps = Integer.parseInt(split[0]);
+                        txKbps = Integer.parseInt(split[1]);
+                    } catch (NumberFormatException ignored) {
+                    }
+                }
+                AVG_BW_PER_RAT_MAP.put(kv[0], new Pair<>(rxKbps, txKbps));
+            }
+        }
+    }
+
+    private final DisplayManager.DisplayListener mDisplayListener =
+            new DisplayManager.DisplayListener() {
+                @Override
+                public void onDisplayAdded(int displayId) {
+                }
+
+                @Override
+                public void onDisplayRemoved(int displayId) {
+                }
+
+                @Override
+                public void onDisplayChanged(int displayId) {
+                    obtainMessage(MSG_SCREEN_STATE_CHANGED, isScreenOn()).sendToTarget();
+                }
+            };
+
+    private final OutcomeReceiver<ModemActivityInfo, TelephonyManager.ModemActivityInfoException>
+            mOutcomeReceiver =
+            new OutcomeReceiver<ModemActivityInfo, TelephonyManager.ModemActivityInfoException>() {
+                @Override
+                public void onResult(ModemActivityInfo result) {
+                    obtainMessage(MSG_MODEM_ACTIVITY_RETURNED, result).sendToTarget();
+                }
+
+                @Override
+                public void onError(TelephonyManager.ModemActivityInfoException e) {
+                    Rlog.e(TAG, "error reading modem stats:" + e);
+                    obtainMessage(MSG_MODEM_ACTIVITY_RETURNED, null).sendToTarget();
+                }
+            };
+
+    private final ConnectivityManager.NetworkCallback mDefaultNetworkCallback =
+            new ConnectivityManager.NetworkCallback() {
+                @Override
+                public void onCapabilitiesChanged(@NonNull Network network,
+                        @NonNull NetworkCapabilities networkCapabilities) {
+                    obtainMessage(MSG_DEFAULT_NETWORK_CHANGED, networkCapabilities).sendToTarget();
+                }
+
+                public void onLost(@NonNull Network network) {
+                    obtainMessage(MSG_DEFAULT_NETWORK_CHANGED, null).sendToTarget();
+                }
+            };
+
+    public LinkBandwidthEstimator(Phone phone, TelephonyFacade telephonyFacade) {
+        mPhone = phone;
+        mTelephonyFacade = telephonyFacade;
+        mTelephonyManager = phone.getContext()
+                .getSystemService(TelephonyManager.class)
+                .createForSubscriptionId(phone.getSubId());
+        mConnectivityManager = phone.getContext().getSystemService(ConnectivityManager.class);
+        DisplayManager dm = (DisplayManager) phone.getContext().getSystemService(
+                Context.DISPLAY_SERVICE);
+        dm.registerDisplayListener(mDisplayListener, null);
+        handleScreenStateChanged(isScreenOn());
+        mConnectivityManager.registerDefaultNetworkCallback(mDefaultNetworkCallback, this);
+        mTelephonyManager.registerTelephonyCallback(new HandlerExecutor(this),
+                mTelephonyCallback);
+        mPlaceholderNetwork = new NetworkBandwidth(UNKNOWN_PLMN);
+        initAvgBwPerRatTable();
+        registerNrStateFrequencyChange();
+        mPhone.getServiceStateTracker().registerForDataRegStateOrRatChanged(AccessNetworkConstants
+                .TRANSPORT_TYPE_WWAN, this, MSG_DATA_REG_STATE_OR_RAT_CHANGED, null);
+    }
+
+    @Override
+    public void handleMessage(Message msg) {
+        switch (msg.what) {
+            case MSG_SCREEN_STATE_CHANGED:
+                handleScreenStateChanged((boolean) msg.obj);
+                break;
+            case MSG_TRAFFIC_STATS_POLL:
+                handleTrafficStatsPoll();
+                break;
+            case MSG_MODEM_ACTIVITY_RETURNED:
+                handleModemActivityReturned((ModemActivityInfo) msg.obj);
+                break;
+            case MSG_DEFAULT_NETWORK_CHANGED:
+                handleDefaultNetworkChanged((NetworkCapabilities) msg.obj);
+                break;
+            case MSG_SIGNAL_STRENGTH_CHANGED:
+                handleSignalStrengthChanged((SignalStrength) msg.obj);
+                break;
+            case MSG_NR_FREQUENCY_CHANGED:
+                // fall through
+            case MSG_NR_STATE_CHANGED:
+                updateStaticBwValueResetFilter();
+                break;
+            case MSG_ACTIVE_PHONE_CHANGED:
+                handleActivePhoneChanged((int) msg.obj);
+                break;
+            case MSG_DATA_REG_STATE_OR_RAT_CHANGED:
+                handleDrsOrRatChanged((AsyncResult) msg.obj);
+                break;
+            default:
+                Rlog.e(TAG, "invalid message " + msg.what);
+                break;
+        }
+    }
+
+    /**
+     * 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
+     */
+    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
+     */
+    public void unregisterForBandwidthChanged(Handler h) {
+        mBandwidthChangedRegistrants.remove(h);
+    }
+    /**
+     * @return True if one the device's screen (e.g. main screen, wifi display, HDMI display etc...)
+     * is on.
+     */
+    private boolean isScreenOn() {
+        // Note that we don't listen to Intent.SCREEN_ON and Intent.SCREEN_OFF because they are no
+        // longer adequate for monitoring the screen state since they are not sent in cases where
+        // the screen is turned off transiently such as due to the proximity sensor.
+        final DisplayManager dm = (DisplayManager) mPhone.getContext().getSystemService(
+                Context.DISPLAY_SERVICE);
+        Display[] displays = dm.getDisplays();
+
+        if (displays != null) {
+            for (Display display : displays) {
+                // Anything other than STATE_ON is treated as screen off, such as STATE_DOZE,
+                // STATE_DOZE_SUSPEND, etc...
+                if (display.getState() == Display.STATE_ON) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        return false;
+    }
+
+    private void handleScreenStateChanged(boolean screenOn) {
+        if (mScreenOn == screenOn) {
+            return;
+        }
+        mScreenOn = screenOn;
+        handleTrafficStatsPollConditionChanged();
+    }
+
+    private void handleDefaultNetworkChanged(NetworkCapabilities networkCapabilities) {
+        mNetworkCapabilities = networkCapabilities;
+        boolean isOnDefaultRoute;
+        if (networkCapabilities == null) {
+            isOnDefaultRoute = false;
+        } else {
+            isOnDefaultRoute = networkCapabilities.hasTransport(TRANSPORT_CELLULAR);
+        }
+        if (mIsOnDefaultRoute == isOnDefaultRoute) {
+            return;
+        }
+        mIsOnDefaultRoute = isOnDefaultRoute;
+        handleTrafficStatsPollConditionChanged();
+    }
+
+    private void handleActivePhoneChanged(int activeDataSubId) {
+        boolean isOnActiveData = activeDataSubId == mPhone.getSubId();
+        if (mIsOnActiveData == isOnActiveData) {
+            return;
+        }
+        mIsOnActiveData = isOnActiveData;
+        logd("mIsOnActiveData " + mIsOnActiveData + " activeDataSubId " + activeDataSubId);
+        handleTrafficStatsPollConditionChanged();
+    }
+
+    private void handleDrsOrRatChanged(AsyncResult ar) {
+        Pair<Integer, Integer> drsRatPair = (Pair<Integer, Integer>) ar.result;
+        logd("DrsOrRatChanged dataRegState " + drsRatPair.first + " rilRat " + drsRatPair.second);
+        mLastDrsOrRatChangeTimeMs = mTelephonyFacade.getElapsedSinceBootMillis();
+    }
+
+    private void handleTrafficStatsPollConditionChanged() {
+        removeMessages(MSG_TRAFFIC_STATS_POLL);
+        if (mScreenOn && mIsOnDefaultRoute && mIsOnActiveData) {
+            updateDataRatCellIdentityBandwidth();
+            handleTrafficStatsPoll();
+        }
+    }
+
+    private void handleTrafficStatsPoll() {
+        invalidateTxRxSamples();
+        long mobileTxBytes = mTelephonyFacade.getMobileTxBytes();
+        long mobileRxBytes = mTelephonyFacade.getMobileRxBytes();
+        long txBytesDelta = mobileTxBytes - mLastMobileTxBytes;
+        long rxBytesDelta = mobileRxBytes - mLastMobileRxBytes;
+
+        // Schedule the next traffic stats poll
+        sendEmptyMessageDelayed(MSG_TRAFFIC_STATS_POLL, TRAFFIC_STATS_POLL_INTERVAL_MS);
+
+        mLastMobileTxBytes = mobileTxBytes;
+        mLastMobileRxBytes = mobileRxBytes;
+        // Sometimes TrafficStats byte counts return invalid values
+        // Ignore two polls if it happens
+        boolean trafficValid = txBytesDelta >= 0 && rxBytesDelta >= 0;
+        if (!mLastTrafficValid || !trafficValid) {
+            mLastTrafficValid = trafficValid;
+            Rlog.e(TAG, " run into invalid traffic count");
+            return;
+        }
+
+        mTxBytesDeltaAcc += txBytesDelta;
+        mRxBytesDeltaAcc += rxBytesDelta;
+
+        boolean doModemPoll = true;
+        // Check if it meets the requirement to request modem activity
+        long txByteDeltaThr = Math.min(mTxState.mByteDeltaAccThr / TRAFFIC_MODEM_POLL_BYTE_RATIO,
+                TRAFFIC_POLL_BYTE_THRESHOLD_MAX);
+        long rxByteDeltaThr = Math.min(mRxState.mByteDeltaAccThr / TRAFFIC_MODEM_POLL_BYTE_RATIO,
+                TRAFFIC_POLL_BYTE_THRESHOLD_MAX);
+        if (txBytesDelta < txByteDeltaThr && rxBytesDelta < rxByteDeltaThr
+                && mTxBytesDeltaAcc < mTxState.mByteDeltaAccThr
+                && mRxBytesDeltaAcc < mRxState.mByteDeltaAccThr) {
+            doModemPoll = false;
+        }
+
+        long currTimeMs = mTelephonyFacade.getElapsedSinceBootMillis();
+        long timeSinceLastModemPollMs = currTimeMs - mLastModemPollTimeMs;
+        if (timeSinceLastModemPollMs < MODEM_POLL_MIN_INTERVAL_MS) {
+            doModemPoll = false;
+        }
+
+        if (doModemPoll) {
+            StringBuilder sb = new StringBuilder();
+            logd(sb.append("txByteDelta ").append(txBytesDelta)
+                    .append(" rxByteDelta ").append(rxBytesDelta)
+                    .append(" txByteDeltaAcc ").append(mTxBytesDeltaAcc)
+                    .append(" rxByteDeltaAcc ").append(mRxBytesDeltaAcc)
+                    .append(" trigger modem activity request").toString());
+            updateDataRatCellIdentityBandwidth();
+            // Filter update will happen after the request
+            makeRequestModemActivity();
+            return;
+        }
+
+        long timeSinceLastFilterUpdateMs = currTimeMs - mFilterUpdateTimeMs;
+        // Update filter
+        if (timeSinceLastFilterUpdateMs >= FILTER_UPDATE_MAX_INTERVAL_MS) {
+            if (!updateDataRatCellIdentityBandwidth()) {
+                updateTxRxBandwidthFilterSendToDataConnection();
+            }
+        }
+    }
+
+    private void makeRequestModemActivity() {
+        mLastModemPollTimeMs = mTelephonyFacade.getElapsedSinceBootMillis();
+        // TODO: add CountDown in case that onResult/OnError() never happen
+        mTelephonyManager.requestModemActivityInfo(Runnable::run, mOutcomeReceiver);
+    }
+
+    private void handleModemActivityReturned(ModemActivityInfo result) {
+        updateBandwidthTxRxSamples(result);
+        updateTxRxBandwidthFilterSendToDataConnection();
+        mLastModemActivityInfo = result;
+        // Update for next poll
+        resetByteDeltaAcc();
+    }
+
+    private void resetByteDeltaAcc() {
+        mTxBytesDeltaAcc = 0;
+        mRxBytesDeltaAcc = 0;
+    }
+
+    private void invalidateTxRxSamples() {
+        mTxState.mBwSampleValid = false;
+        mRxState.mBwSampleValid = false;
+    }
+
+    private void updateBandwidthTxRxSamples(ModemActivityInfo modemActivityInfo) {
+        if (mLastModemActivityInfo == null || modemActivityInfo == null
+                || mNetworkCapabilities == null || hasRecentDataRegStatePlmnOrRatChange()) {
+            return;
+        }
+
+        long lastTimeMs = mLastModemActivityInfo.getTimestampMillis();
+        long currTimeMs = modemActivityInfo.getTimestampMillis();
+        long timeDeltaMs = currTimeMs - lastTimeMs;
+
+        if (timeDeltaMs > MODEM_POLL_TIME_DELTA_MAX_MS || timeDeltaMs <= 0) {
+            return;
+        }
+        ModemActivityInfo deltaInfo = mLastModemActivityInfo.getDelta(modemActivityInfo);
+        long txTimeDeltaMs = getModemTxTimeMs(deltaInfo);
+        long rxTimeDeltaMs = deltaInfo.getReceiveTimeMillis();
+
+        // Check if txTimeDeltaMs / rxTimeDeltaMs > TX_OVER_RX_TIME_RATIO_THRESHOLD
+        boolean isTxTimeOverRxTimeRatioLarge = (txTimeDeltaMs * TX_OVER_RX_TIME_RATIO_THRESHOLD_DEN
+                > rxTimeDeltaMs * TX_OVER_RX_TIME_RATIO_THRESHOLD_NUM);
+        long rxTimeBwEstMs = isTxTimeOverRxTimeRatioLarge
+                ? (txTimeDeltaMs + rxTimeDeltaMs) : rxTimeDeltaMs;
+
+        mTxState.updateBandwidthSample(mTxBytesDeltaAcc, txTimeDeltaMs);
+        mRxState.updateBandwidthSample(mRxBytesDeltaAcc, rxTimeBwEstMs);
+
+        int reportedTxTputKbps = mNetworkCapabilities.getLinkUpstreamBandwidthKbps();
+        int reportedRxTputKbps = mNetworkCapabilities.getLinkDownstreamBandwidthKbps();
+
+        StringBuilder sb = new StringBuilder();
+        logd(sb.append("UpdateBwSample")
+                .append(" dBm ").append(mSignalStrengthDbm)
+                .append(" level ").append(mSignalLevel)
+                .append(" rat ").append(getDataRatName(mDataRat))
+                .append(" plmn ").append(mPlmn)
+                .append(" tac ").append(mTac)
+                .append(" reportedTxKbps ").append(reportedTxTputKbps)
+                .append(" reportedRxKbps ").append(reportedRxTputKbps)
+                .append(" txMs ").append(txTimeDeltaMs)
+                .append(" rxMs ").append(rxTimeDeltaMs)
+                .append(" txKB ").append(mTxBytesDeltaAcc / 1024)
+                .append(" rxKB ").append(mRxBytesDeltaAcc / 1024)
+                .append(" txKBThr ").append(mTxState.mByteDeltaAccThr / 1024)
+                .append(" rxKBThr ").append(mRxState.mByteDeltaAccThr / 1024)
+                .toString());
+    }
+
+    private boolean hasRecentDataRegStatePlmnOrRatChange() {
+        if (mLastModemActivityInfo == null) {
+            return false;
+        }
+        return (mLastDrsOrRatChangeTimeMs > mLastModemActivityInfo.getTimestampMillis()
+            || mLastPlmnOrRatChangeTimeMs > mLastModemActivityInfo.getTimestampMillis());
+    }
+
+    private long getModemTxTimeMs(ModemActivityInfo modemActivity) {
+        long txTimeMs = 0;
+        for (int lvl = 0; lvl < ModemActivityInfo.getNumTxPowerLevels(); lvl++) {
+            txTimeMs += modemActivity.getTransmitDurationMillisAtPowerLevel(lvl);
+        }
+        return txTimeMs;
+    }
+
+    private void updateTxRxBandwidthFilterSendToDataConnection() {
+        mFilterUpdateTimeMs = mTelephonyFacade.getElapsedSinceBootMillis();
+        mTxState.updateBandwidthFilter();
+        mRxState.updateBandwidthFilter();
+
+        boolean isNetworkChanged = mTxState.hasLargeBwChange()
+                || mRxState.hasLargeBwChange()
+                || mBandwidthUpdateDataRat != mDataRat
+                || mBandwidthUpdateSignalLevel != mSignalLevel
+                || !mBandwidthUpdatePlmn.equals(mPlmn);
+        if (isValidNetwork() && isNetworkChanged) {
+            mTxState.mLastReportedBwKbps = mTxState.mAvgUsedKbps < 0 ? -1 : mTxState.mFilterKbps;
+            mRxState.mLastReportedBwKbps  = mRxState.mAvgUsedKbps < 0 ? -1 : mRxState.mFilterKbps;
+            sendLinkBandwidthToDataConnection(
+                    mTxState.mLastReportedBwKbps,
+                    mRxState.mLastReportedBwKbps);
+        }
+        mBandwidthUpdateSignalDbm = mSignalStrengthDbm;
+        mBandwidthUpdateSignalLevel = mSignalLevel;
+        mBandwidthUpdateDataRat = mDataRat;
+        mBandwidthUpdatePlmn = mPlmn;
+
+        mTxState.calculateError();
+        mRxState.calculateError();
+    }
+
+    private boolean isValidNetwork() {
+        return !mPlmn.equals(UNKNOWN_PLMN) && mDataRat != TelephonyManager.NETWORK_TYPE_UNKNOWN;
+    }
+
+    private class BandwidthState {
+        private final int mLink;
+        int mFilterKbps;
+        int mByteDeltaAccThr = BYTE_DELTA_THRESHOLD_KB[0][0];
+        int mAvgUsedKbps;
+        int mBwSampleKbps;
+        boolean mBwSampleValid;
+        long mBwSampleValidTimeMs;
+        int mStaticBwKbps;
+        int mLastReportedBwKbps;
+
+        BandwidthState(int link) {
+            mLink = link;
+        }
+
+        private void updateBandwidthSample(long bytesDelta, long timeDeltaMs) {
+            updateByteCountThr();
+            if (bytesDelta < mByteDeltaAccThr) {
+                return;
+            }
+            if (timeDeltaMs < TX_RX_TIME_MIN_MS) {
+                return;
+            }
+            long linkBandwidthLongKbps = bytesDelta * 8 / timeDeltaMs * 1000 / 1024;
+            if (linkBandwidthLongKbps > (long) mStaticBwKbps * MAX_BW_TO_STATIC_BW_RATIO
+                    || linkBandwidthLongKbps < 0) {
+                return;
+            }
+            int linkBandwidthKbps = (int) linkBandwidthLongKbps;
+            mBwSampleValid = true;
+            mBwSampleKbps = linkBandwidthKbps;
+
+            String dataRatName = getDataRatName(mDataRat);
+            NetworkBandwidth network = lookupNetwork(mPlmn, dataRatName);
+            // Update per RAT stats of all TAC
+            network.update(linkBandwidthKbps, mLink, mSignalLevel);
+
+            // Update per TAC stats
+            network = lookupNetwork(mPlmn, mTac, dataRatName);
+            network.update(linkBandwidthKbps, mLink, mSignalLevel);
+        }
+
+        private void updateBandwidthFilter() {
+            int avgKbps = getAvgLinkBandwidthKbps();
+            // Feed the filter with the long term avg if there is no valid BW sample so that filter
+            // will gradually converge the long term avg.
+            int filterInKbps = mBwSampleValid ? mBwSampleKbps : avgKbps;
+
+            long currTimeMs = mTelephonyFacade.getElapsedSinceBootMillis();
+            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
+            int timeConstantSec;
+            if (Math.abs(mBandwidthUpdateSignalDbm - mSignalStrengthDbm) > RSSI_DELTA_THRESHOLD_DB
+                    || !mBandwidthUpdatePlmn.equals(mPlmn)
+                    || mBandwidthUpdateDataRat != mDataRat
+                    || (mBwSampleValid && mBwSampleKbps > avgKbps)) {
+                timeConstantSec = TIME_CONSTANT_SMALL_SEC;
+            } else {
+                timeConstantSec = TIME_CONSTANT_LARGE_SEC;
+            }
+            // Update timestamp for next iteration
+            if (mBwSampleValid) {
+                mBwSampleValidTimeMs = currTimeMs;
+            }
+
+            if (filterInKbps == mFilterKbps) {
+                return;
+            }
+
+            int alpha = timeDeltaSec > LARGE_TIME_DECAY_RATIO * timeConstantSec ? 0
+                    : (int) (FILTER_SCALE * Math.exp(-1.0 * timeDeltaSec / timeConstantSec));
+            if (alpha == 0) {
+                mFilterKbps = filterInKbps;
+                return;
+            }
+            long filterOutKbps = (long) mFilterKbps * alpha
+                    + filterInKbps * FILTER_SCALE - filterInKbps * alpha;
+            filterOutKbps = filterOutKbps / FILTER_SCALE;
+            mFilterKbps = (int) Math.min(filterOutKbps, Integer.MAX_VALUE);
+
+            StringBuilder sb = new StringBuilder();
+            logv(sb.append(mLink)
+                    .append(" lastSampleWeight=").append(alpha)
+                    .append("/").append(FILTER_SCALE)
+                    .append(" filterInKbps=").append(filterInKbps)
+                    .append(" avgKbps=").append(avgKbps)
+                    .append(" filterOutKbps=").append(mFilterKbps)
+                    .toString());
+        }
+
+        private int getAvgUsedLinkBandwidthKbps() {
+            // Check if current TAC/RAT/level has enough stats
+            String dataRatName = getDataRatName(mDataRat);
+            NetworkBandwidth network = lookupNetwork(mPlmn, mTac, dataRatName);
+            int count = network.getCount(mLink, mSignalLevel);
+            if (count >= BW_STATS_COUNT_THRESHOLD) {
+                return (int) (network.getValue(mLink, mSignalLevel) / count);
+            }
+
+            // Check if current RAT/level has enough stats
+            network = lookupNetwork(mPlmn, dataRatName);
+            count = network.getCount(mLink, mSignalLevel);
+            if (count >= BW_STATS_COUNT_THRESHOLD) {
+                return (int) (network.getValue(mLink, mSignalLevel) / count);
+            }
+            return -1;
+        }
+
+        private int getAvgUsedBandwidthAdjacentThreeLevelKbps() {
+            String dataRatName = getDataRatName(mDataRat);
+            NetworkBandwidth network = lookupNetwork(mPlmn, dataRatName);
+
+            int bandwidthAtLow = getAvgUsedBandwidthAtLevel(network, mSignalLevel - 1);
+            int bandwidthAtHigh = getAvgUsedBandwidthAtLevel(network, mSignalLevel + 1);
+            if (bandwidthAtLow > 0 && bandwidthAtHigh > 0) {
+                return (bandwidthAtLow + bandwidthAtHigh) / 2;
+            }
+
+            int count = 0;
+            long value = 0;
+            for (int i = -1; i <= 1; i++) {
+                int currLevel = mSignalLevel + i;
+                if (currLevel < 0 || currLevel >= NUM_SIGNAL_LEVEL) {
+                    continue;
+                }
+                count += network.getCount(mLink, currLevel);
+                value += network.getValue(mLink, currLevel);
+            }
+
+            if (count >= BW_STATS_COUNT_THRESHOLD) {
+                return (int) (value / count);
+            }
+            return -1;
+        }
+
+        private int getAvgUsedBandwidthAtLevel(NetworkBandwidth network,
+                int signalLevel) {
+            if (signalLevel < 0 || signalLevel >= NUM_SIGNAL_LEVEL) {
+                return -1;
+            }
+            int count = network.getCount(mLink, signalLevel);
+            if (count >= BW_STATS_COUNT_THRESHOLD) {
+                return (int) (network.getValue(mLink, signalLevel) / count);
+            }
+            return -1;
+        }
+
+        private int getCurrentCount() {
+            String dataRatName = getDataRatName(mDataRat);
+            NetworkBandwidth network = lookupNetwork(mPlmn, dataRatName);
+            return network.getCount(mLink, mSignalLevel);
+        }
+
+        /** get a long term avg value (PLMN/RAT/TAC/level dependent) or static value */
+        private int getAvgLinkBandwidthKbps() {
+            mAvgUsedKbps = getAvgUsedLinkBandwidthKbps();
+            if (mAvgUsedKbps > 0) {
+                return mAvgUsedKbps;
+            }
+            mAvgUsedKbps = getAvgUsedBandwidthAdjacentThreeLevelKbps();
+            if (mAvgUsedKbps > 0) {
+                return mAvgUsedKbps;
+            }
+            // Fall back to static value
+            return mStaticBwKbps;
+        }
+
+        private void resetBandwidthFilter() {
+            mBwSampleValid = false;
+            mFilterKbps = getAvgLinkBandwidthKbps();
+        }
+
+        private void updateByteCountThr() {
+            // For high BW RAT cases, use predefined value + threshold derived from avg usage BW
+            if (mStaticBwKbps > HIGH_BANDWIDTH_THRESHOLD_KBPS) {
+                int lowBytes = calculateByteCountThreshold(getAvgUsedLinkBandwidthKbps(),
+                        MODEM_POLL_MIN_INTERVAL_MS);
+                // Start with a predefined value
+                mByteDeltaAccThr = BYTE_DELTA_THRESHOLD_KB[mLink][mSignalLevel] * 1024;
+                if (lowBytes > 0) {
+                    // Raise the threshold if the avg usage BW is high
+                    mByteDeltaAccThr = Math.max(lowBytes, mByteDeltaAccThr);
+                    mByteDeltaAccThr = Math.min(mByteDeltaAccThr,
+                            BYTE_DELTA_ACC_THRESHOLD_MAX_KB * 1024);
+                }
+                return;
+            }
+            // For low BW RAT cases, derive the threshold from avg BW values
+            mByteDeltaAccThr = calculateByteCountThreshold(mStaticBwKbps,
+                    MODEM_POLL_MIN_INTERVAL_MS);
+
+            mByteDeltaAccThr = Math.max(mByteDeltaAccThr, BYTE_DELTA_THRESHOLD_MIN_KB * 1024);
+            // Low BW RAT threshold value should be no more than high BW one.
+            mByteDeltaAccThr = Math.min(mByteDeltaAccThr, BYTE_DELTA_THRESHOLD_KB[mLink][0] * 1024);
+        }
+
+        // Calculate a byte count threshold for the given avg BW and observation window size
+        private int calculateByteCountThreshold(int avgBwKbps, int durationMs) {
+            long avgBytes = (long) avgBwKbps / 8 * durationMs;
+            long result = avgBytes * LOW_BW_TO_AVG_BW_RATIO_NUM / LOW_BW_TO_AVG_BW_RATIO_DEN;
+            return (int) Math.min(result, Integer.MAX_VALUE);
+        }
+
+        public boolean hasLargeBwChange() {
+            int deltaKbps = Math.abs(mLastReportedBwKbps - mFilterKbps);
+            return mAvgUsedKbps > 0
+                    && deltaKbps * 100 > BW_UPDATE_THRESHOLD_PERCENT * mLastReportedBwKbps;
+        }
+
+        public void calculateError() {
+            if (!mBwSampleValid || getCurrentCount() <= BW_STATS_COUNT_THRESHOLD + 1
+                    || mAvgUsedKbps <= 0) {
+                return;
+            }
+            int bwEstExtErrPercent = calculateErrorPercent(mLastReportedBwKbps, mBwSampleKbps);
+            int bwEstAvgErrPercent = calculateErrorPercent(mAvgUsedKbps, mBwSampleKbps);
+            int bwEstIntErrPercent = calculateErrorPercent(mFilterKbps, mBwSampleKbps);
+            int coldStartErrPercent = calculateErrorPercent(mStaticBwKbps, mBwSampleKbps);
+
+            TelephonyMetrics.getInstance().writeBandwidthStats(mLink, mDataRat, getNrMode(mDataRat),
+                    mSignalLevel, bwEstExtErrPercent, coldStartErrPercent, mBwSampleKbps);
+
+            StringBuilder sb = new StringBuilder();
+            logd(sb.append(mLink)
+                    .append(" sampKbps ").append(mBwSampleKbps)
+                    .append(" filtKbps ").append(mFilterKbps)
+                    .append(" reportKbps ").append(mLastReportedBwKbps)
+                    .append(" avgUsedKbps ").append(mAvgUsedKbps)
+                    .append(" csKbps ").append(mStaticBwKbps)
+                    .append(" intErrPercent ").append(bwEstIntErrPercent)
+                    .append(" avgErrPercent ").append(bwEstAvgErrPercent)
+                    .append(" extErrPercent ").append(bwEstExtErrPercent)
+                    .append(" csErrPercent ").append(coldStartErrPercent)
+                    .toString());
+        }
+
+        private int calculateErrorPercent(int inKbps, int bwSampleKbps) {
+            long errorPercent = 100L * (inKbps - bwSampleKbps) / bwSampleKbps;
+            return (int) Math.max(-MAX_ERROR_PERCENT, Math.min(errorPercent, MAX_ERROR_PERCENT));
+        }
+    }
+
+    /**
+     * Update the byte count threshold.
+     * It should be called whenever the RAT or signal level is changed.
+     * For the RAT with high BW (4G and beyond), use BYTE_DELTA_THRESHOLD_KB table.
+     * For other RATs, derive the threshold based on the static BW values.
+     */
+    private void updateByteCountThr() {
+        mTxState.updateByteCountThr();
+        mRxState.updateByteCountThr();
+    }
+
+    // Reset BW filter to a long term avg value (PLMN/RAT/TAC dependent) or static BW value.
+    // It should be called whenever PLMN/RAT or static BW value is changed;
+    private void resetBandwidthFilter() {
+        mTxState.resetBandwidthFilter();
+        mRxState.resetBandwidthFilter();
+    }
+
+    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));
+    }
+
+    private void handleSignalStrengthChanged(SignalStrength signalStrength) {
+        if (signalStrength == null) {
+            return;
+        }
+
+        mSignalStrengthDbm = signalStrength.getDbm();
+        mSignalLevel = signalStrength.getLevel();
+        updateByteCountThr();
+        if (updateDataRatCellIdentityBandwidth()) {
+            return;
+        }
+
+        if (Math.abs(mBandwidthUpdateSignalDbm - mSignalStrengthDbm) > RSSI_DELTA_THRESHOLD_DB) {
+            updateTxRxBandwidthFilterSendToDataConnection();
+        }
+    }
+
+    private void registerNrStateFrequencyChange() {
+        mPhone.getServiceStateTracker().registerForNrStateChanged(this,
+                MSG_NR_STATE_CHANGED, null);
+        mPhone.getServiceStateTracker().registerForNrFrequencyChanged(this,
+                MSG_NR_FREQUENCY_CHANGED, null);
+    }
+
+    /**
+     * Get a string based on current RAT
+     */
+    public String getDataRatName(int rat) {
+        return getDataRatName(rat, getNrMode(rat));
+    }
+
+    private int getNrMode(int rat) {
+        if (rat == TelephonyManager.NETWORK_TYPE_LTE && isNrNsaConnected()) {
+            return mPhone.getServiceState().getNrFrequencyRange()
+                    == ServiceState.FREQUENCY_RANGE_MMWAVE
+                    ? NrMode.NR_NSA_MMWAVE : NrMode.NR_NSA;
+        } else if (rat == TelephonyManager.NETWORK_TYPE_NR) {
+            return mPhone.getServiceState().getNrFrequencyRange()
+                    == ServiceState.FREQUENCY_RANGE_MMWAVE
+                    ? NrMode.NR_SA_MMWAVE : NrMode.NR_SA;
+        }
+        return NrMode.NR_NONE;
+    }
+
+    /**
+     * Get a string based on current RAT and NR operation mode.
+     */
+    public static String getDataRatName(int rat, int nrMode) {
+        if (rat == TelephonyManager.NETWORK_TYPE_LTE
+                && (nrMode == NrMode.NR_NSA || nrMode == NrMode.NR_NSA_MMWAVE)) {
+            return nrMode == NrMode.NR_NSA
+                    ? DctConstants.RAT_NAME_NR_NSA : DctConstants.RAT_NAME_NR_NSA_MMWAVE;
+        } else if (rat == TelephonyManager.NETWORK_TYPE_NR) {
+            return nrMode == NrMode.NR_SA
+                    ? TelephonyManager.getNetworkTypeName(rat) : DctConstants.RAT_NAME_NR_SA_MMWAVE;
+        }
+        return TelephonyManager.getNetworkTypeName(rat);
+    }
+
+    /**
+     * Check if the device is connected to NR 5G Non-Standalone network
+     */
+    private boolean isNrNsaConnected() {
+        return mPhone.getServiceState().getNrState()
+                == NetworkRegistrationInfo.NR_STATE_CONNECTED;
+    }
+
+    // Update avg BW values.
+    // It should be called whenever the RAT could be changed.
+    // return true if avg value is changed;
+    private boolean updateStaticBwValue(int dataRat) {
+        Pair<Integer, Integer> values = getStaticAvgBw(dataRat);
+        if (values == null) {
+            mTxState.mStaticBwKbps = DEFAULT_LINK_BAND_WIDTH_KBPS;
+            mRxState.mStaticBwKbps = DEFAULT_LINK_BAND_WIDTH_KBPS;
+            return true;
+        }
+        if (mTxState.mStaticBwKbps != values.second
+                || mRxState.mStaticBwKbps != values.first) {
+            mTxState.mStaticBwKbps = values.second;
+            mRxState.mStaticBwKbps = values.first;
+            return true;
+        }
+        return false;
+    }
+
+    /** get per-RAT static bandwidth value */
+    public Pair<Integer, Integer> getStaticAvgBw(int dataRat) {
+        String dataRatName = getDataRatName(dataRat);
+        Pair<Integer, Integer> values = AVG_BW_PER_RAT_MAP.get(dataRatName);
+        if (values == null) {
+            Rlog.e(TAG, dataRatName + " is not found in Avg BW table");
+        }
+        return values;
+    }
+
+    private void updateStaticBwValueResetFilter() {
+        if (updateStaticBwValue(mDataRat)) {
+            updateByteCountThr();
+            resetBandwidthFilter();
+            updateTxRxBandwidthFilterSendToDataConnection();
+        }
+    }
+
+    private NetworkRegistrationInfo getDataNri() {
+        return  mPhone.getServiceState().getNetworkRegistrationInfo(
+                NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+    }
+
+    private boolean updateDataRatCellIdentityBandwidth() {
+        boolean updatedPlmn = false;
+        CellIdentity cellIdentity = mPhone.getCurrentCellIdentity();
+        mTac = getTac(cellIdentity);
+        String plmn;
+
+        if (mPhone.getServiceState().getOperatorNumeric() != null) {
+            plmn = mPhone.getServiceState().getOperatorNumeric();
+        } else {
+            if (cellIdentity.getPlmn() != null) {
+                plmn = cellIdentity.getPlmn();
+            } else {
+                plmn = UNKNOWN_PLMN;
+            }
+        }
+        if (mPlmn == null || !plmn.equals(mPlmn)) {
+            updatedPlmn = true;
+            mPlmn = plmn;
+        }
+
+        boolean updatedRat = false;
+        NetworkRegistrationInfo nri = getDataNri();
+        if (nri != null) {
+            int dataRat = nri.getAccessNetworkTechnology();
+            if (dataRat != mDataRat) {
+                updatedRat = true;
+                mDataRat = dataRat;
+                updateStaticBwValue(mDataRat);
+                updateByteCountThr();
+            }
+        }
+
+        boolean updatedPlmnOrRat = updatedPlmn || updatedRat;
+        if (updatedPlmnOrRat) {
+            resetBandwidthFilter();
+            updateTxRxBandwidthFilterSendToDataConnection();
+            mLastPlmnOrRatChangeTimeMs = mTelephonyFacade.getElapsedSinceBootMillis();
+        }
+        return updatedPlmnOrRat;
+    }
+
+    private int getTac(@NonNull CellIdentity cellIdentity) {
+        if (cellIdentity instanceof CellIdentityLte) {
+            return ((CellIdentityLte) cellIdentity).getTac();
+        }
+        if (cellIdentity instanceof CellIdentityNr) {
+            return ((CellIdentityNr) cellIdentity).getTac();
+        }
+        if (cellIdentity instanceof CellIdentityWcdma) {
+            return ((CellIdentityWcdma) cellIdentity).getLac();
+        }
+        if (cellIdentity instanceof CellIdentityTdscdma) {
+            return ((CellIdentityTdscdma) cellIdentity).getLac();
+        }
+        if (cellIdentity instanceof CellIdentityGsm) {
+            return ((CellIdentityGsm) cellIdentity).getLac();
+        }
+        return 0;
+    }
+
+    private class TelephonyCallbackImpl extends TelephonyCallback implements
+            TelephonyCallback.SignalStrengthsListener,
+            TelephonyCallback.ActiveDataSubscriptionIdListener {
+        @Override
+        public void onSignalStrengthsChanged(SignalStrength signalStrength) {
+            obtainMessage(MSG_SIGNAL_STRENGTH_CHANGED, signalStrength).sendToTarget();
+        }
+        @Override
+        public void onActiveDataSubscriptionIdChanged(int subId) {
+            obtainMessage(MSG_ACTIVE_PHONE_CHANGED, subId).sendToTarget();
+        }
+    }
+
+    void logv(String msg) {
+        if (DBG) Rlog.v(TAG, msg);
+    }
+
+    void logd(String msg) {
+        if (DBG) Rlog.d(TAG, msg);
+        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;
+            mDataRat = dataRat;
+        }
+        @Override
+        public boolean equals(@Nullable Object o) {
+            if (o == null || !(o instanceof NetworkKey) || hashCode() != o.hashCode()) {
+                return false;
+            }
+
+            if (this == o) {
+                return true;
+            }
+
+            NetworkKey that = (NetworkKey) o;
+            return mPlmn.equals(that.mPlmn)
+                    && mTac == that.mTac
+                    && mDataRat.equals(that.mDataRat);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mPlmn, mDataRat, mTac);
+        }
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder();
+            sb.append("Plmn").append(mPlmn)
+                    .append("Rat").append(mDataRat)
+                    .append("Tac").append(mTac)
+                    .toString();
+            return sb.toString();
+        }
+    }
+
+    @NonNull
+    private NetworkBandwidth lookupNetwork(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(
+                TelephonyManager.getNetworkTypeName(TelephonyManager.NETWORK_TYPE_UNKNOWN))) {
+            return mPlaceholderNetwork;
+        }
+        NetworkKey key = new NetworkKey(plmn, tac, dataRat);
+        NetworkBandwidth ans = mNetworkMap.get(key);
+        if (ans == null) {
+            ans = new NetworkBandwidth(key.toString());
+            mNetworkMap.put(key, ans);
+        }
+        return ans;
+    }
+
+    /** A class holding link bandwidth related stats */
+    @VisibleForTesting
+    public class NetworkBandwidth {
+        private final String mKey;
+        NetworkBandwidth(String key) {
+            mKey = key;
+        }
+
+        /** Update link bandwidth stats */
+        public void update(long value, int link, int level) {
+            SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(
+                    mPhone.getContext());
+            String valueKey = getValueKey(link, level);
+            String countKey = getCountKey(link, level);
+            SharedPreferences.Editor editor = sp.edit();
+            long currValue = sp.getLong(valueKey, 0);
+            int currCount = sp.getInt(countKey, 0);
+            editor.putLong(valueKey, currValue + value);
+            editor.putInt(countKey, currCount + 1);
+            editor.apply();
+        }
+
+        private String getValueKey(int link, int level) {
+            return getDataKey(link, level) + "Data";
+        }
+
+        private String getCountKey(int link, int level) {
+            return getDataKey(link, level) + "Count";
+        }
+
+        private String getDataKey(int link, int level) {
+            StringBuilder sb = new StringBuilder();
+            return sb.append(mKey)
+                    .append("Link").append(link)
+                    .append("Level").append(level)
+                    .toString();
+        }
+
+        /** Get the accumulated bandwidth value */
+        public long getValue(int link, int level) {
+            SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(
+                    mPhone.getContext());
+            String valueKey = getValueKey(link, level);
+            return sp.getLong(valueKey, 0);
+        }
+
+        /** Get the accumulated bandwidth count */
+        public int getCount(int link, int level) {
+            SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(
+                    mPhone.getContext());
+            String countKey = getCountKey(link, level);
+            return sp.getInt(countKey, 0);
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder();
+            sb.append(mKey);
+            sb.append("\n");
+            for (int link = 0; link < NUM_LINK_DIRECTION; link++) {
+                sb.append((link == 0 ? "tx" : "rx"));
+                sb.append("\n avgKbps");
+                for (int level = 0; level < NUM_SIGNAL_LEVEL; level++) {
+                    int count = getCount(link, level);
+                    int avgKbps = count == 0 ? 0 : (int) (getValue(link, level) / count);
+                    sb.append(" ").append(avgKbps);
+                }
+                sb.append("\n count");
+                for (int level = 0; level < NUM_SIGNAL_LEVEL; level++) {
+                    int count = getCount(link, level);
+                    sb.append(" ").append(count);
+                }
+                sb.append("\n");
+            }
+            return sb.toString();
+        }
+    }
+
+    /**
+     * Dump the internal state and local logs
+     */
+    public void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) {
+        IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " ");
+        pw.increaseIndent();
+        pw.println("current PLMN " + mPlmn + " TAC " + mTac + " RAT " + getDataRatName(mDataRat));
+        pw.println("all networks visited since device boot");
+        for (NetworkBandwidth network : mNetworkMap.values()) {
+            pw.println(network.toString());
+        }
+
+        try {
+            mLocalLog.dump(fd, pw, args);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        pw.decreaseIndent();
+        pw.println();
+        pw.flush();
+    }
+
+}
diff --git a/src/java/com/android/internal/telephony/dataconnection/QosCallbackTracker.java b/src/java/com/android/internal/telephony/dataconnection/QosCallbackTracker.java
index d1021fe..795ed14 100644
--- a/src/java/com/android/internal/telephony/dataconnection/QosCallbackTracker.java
+++ b/src/java/com/android/internal/telephony/dataconnection/QosCallbackTracker.java
@@ -44,7 +44,7 @@
  * {@hide}
  */
 public class QosCallbackTracker {
-    private static final String LOG_TAG = QosCallbackTracker.class.getSimpleName();
+    @NonNull private final String mTag;
     @NonNull private final DcNetworkAgent mDcNetworkAgent;
     @NonNull private final Map<Integer, QosBearerSession> mQosBearerSessions;
 
@@ -59,6 +59,7 @@
         mQosBearerSessions = new HashMap<>();
         mCallbacksToFilter = new HashMap<>();
         mDcNetworkAgent = dcNetworkAgent;
+        mTag = "QosCallbackTracker" + "-" + mDcNetworkAgent.getNetwork().getNetId();
     }
 
     /**
@@ -206,15 +207,19 @@
 
         for (final QosBearerFilter sessionFilter : qosBearerSession.getQosBearerFilterList()) {
            if (!sessionFilter.getLocalAddresses().isEmpty()
-                   && !sessionFilter.getRemoteAddresses().isEmpty()) {
+                   && !sessionFilter.getRemoteAddresses().isEmpty()
+                   && sessionFilter.getLocalPortRange().isValid()
+                   && sessionFilter.getRemotePortRange().isValid()) {
                if (matchesByRemoteAndLocalAddress(sessionFilter, filter)) {
                    qosFilter = getFilterByPrecedence(qosFilter, sessionFilter);
                }
-           } else if (!sessionFilter.getRemoteAddresses().isEmpty()) {
+           } else if (!sessionFilter.getRemoteAddresses().isEmpty()
+                   && sessionFilter.getRemotePortRange().isValid()) {
                if (matchesByRemoteAddress(sessionFilter, filter)) {
                    qosFilter = getFilterByPrecedence(qosFilter, sessionFilter);
                }
-           } else if (!sessionFilter.getLocalAddresses().isEmpty()) {
+           } else if (!sessionFilter.getLocalAddresses().isEmpty()
+                   && sessionFilter.getLocalPortRange().isValid()) {
                if (matchesByLocalAddress(sessionFilter, filter)) {
                    qosFilter = getFilterByPrecedence(qosFilter, sessionFilter);
                }
@@ -256,12 +261,15 @@
             mDcNetworkAgent.notifyQosSessionAvailable(
                     callbackId, session.getQosBearerSessionId(), nrQosAttr);
         }
+
+        logd("sendSessionAvailable, callbackId=" + callbackId);
     }
 
     private void sendSessionLost(final int callbackId, @NonNull final QosBearerSession session) {
         mDcNetworkAgent.notifyQosSessionLost(callbackId, session.getQosBearerSessionId(),
                 session.getQos() instanceof EpsQos ?
                 QosSession.TYPE_EPS_BEARER : QosSession.TYPE_NR_BEARER);
+        logd("sendSessionLost, callbackId=" + callbackId);
     }
 
     public interface IFilter {
@@ -275,6 +283,6 @@
      * @param s is string log
      */
     private void logd(String s) {
-        Rlog.d(LOG_TAG, s);
+        Rlog.d(mTag, s);
     }
 }
diff --git a/src/java/com/android/internal/telephony/dataconnection/TelephonyNetworkFactory.java b/src/java/com/android/internal/telephony/dataconnection/TelephonyNetworkFactory.java
index 6dc5249..1e44219 100644
--- a/src/java/com/android/internal/telephony/dataconnection/TelephonyNetworkFactory.java
+++ b/src/java/com/android/internal/telephony/dataconnection/TelephonyNetworkFactory.java
@@ -40,6 +40,7 @@
 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;
 
@@ -230,6 +231,7 @@
      */
     private void requestNetworkInternal(NetworkRequest networkRequest,
             @RequestNetworkType int requestType, int transport, Message onHandoverCompleteMsg) {
+        NetworkRequestsStats.addNetworkRequest(networkRequest, mSubscriptionId);
         if (mPhone.getDcTracker(transport) != null) {
             mPhone.getDcTracker(transport).requestNetwork(networkRequest, requestType,
                     onHandoverCompleteMsg);
@@ -239,6 +241,7 @@
     private void releaseNetworkInternal(NetworkRequest networkRequest,
                                         @ReleaseNetworkType int releaseType,
                                         int transport) {
+        NetworkRequestsStats.addNetworkRelease(networkRequest, mSubscriptionId);
         if (mPhone.getDcTracker(transport) != null) {
             mPhone.getDcTracker(transport).releaseNetwork(networkRequest, releaseType);
         }
@@ -335,8 +338,16 @@
         logl("onReleaseNetworkFor " + networkRequest + " applied " + applied);
 
         if (applied) {
-            int transport = getTransportTypeFromNetworkRequest(networkRequest);
-            releaseNetworkInternal(networkRequest, DcTracker.RELEASE_TYPE_NORMAL, transport);
+            // 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);
         }
     }
 
@@ -439,12 +450,6 @@
             if (mNetworkRequests.containsKey(networkRequest)) {
                 // Update it with the target transport.
                 mNetworkRequests.put(networkRequest, targetTransport);
-            } else {
-                log("Network request was released before handover is completed. Now"
-                        + " we need to release this network request. "
-                        + networkRequest);
-                releaseNetworkInternal(networkRequest, DcTracker.RELEASE_TYPE_NORMAL,
-                        targetTransport);
             }
         } else {
             // If handover fails and requires to fallback, the context of target transport needs to
diff --git a/src/java/com/android/internal/telephony/dataconnection/TransportManager.java b/src/java/com/android/internal/telephony/dataconnection/TransportManager.java
index dbc5a06..5742a4e 100644
--- a/src/java/com/android/internal/telephony/dataconnection/TransportManager.java
+++ b/src/java/com/android/internal/telephony/dataconnection/TransportManager.java
@@ -30,6 +30,7 @@
 import android.telephony.Annotation.ApnType;
 import android.telephony.CarrierConfigManager;
 import android.telephony.data.ApnSetting;
+import android.util.IndentingPrintWriter;
 import android.util.LocalLog;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
@@ -39,7 +40,6 @@
 import com.android.internal.telephony.RIL;
 import com.android.internal.telephony.dataconnection.AccessNetworksManager.QualifiedNetworks;
 import com.android.internal.telephony.util.ArrayUtils;
-import com.android.internal.util.IndentingPrintWriter;
 import com.android.telephony.Rlog;
 
 import java.io.FileDescriptor;
diff --git a/src/java/com/android/internal/telephony/emergency/EmergencyNumberTracker.java b/src/java/com/android/internal/telephony/emergency/EmergencyNumberTracker.java
index cb53832..cf74f75 100644
--- a/src/java/com/android/internal/telephony/emergency/EmergencyNumberTracker.java
+++ b/src/java/com/android/internal/telephony/emergency/EmergencyNumberTracker.java
@@ -30,6 +30,7 @@
 import android.telephony.CarrierConfigManager;
 import android.telephony.PhoneNumberUtils;
 import android.telephony.ServiceState;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.telephony.emergency.EmergencyNumber;
 import android.telephony.emergency.EmergencyNumber.EmergencyCallRouting;
@@ -262,6 +263,22 @@
         return false;
     }
 
+    /**
+     * Checks if it's sim absent to decide whether to apply sim-absent emergency numbers from 3gpp
+     */
+    @VisibleForTesting
+    public boolean isSimAbsent() {
+        for (Phone phone: PhoneFactory.getPhones()) {
+            int slotId = SubscriptionController.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.
+                return false;
+            }
+        }
+        return true;
+    }
+
     private void initializeDatabaseEmergencyNumberList() {
         // If country iso has been cached when listener is set, don't need to cache the initial
         // country iso and initial database.
@@ -457,7 +474,7 @@
             }
             EmergencyNumber.mergeSameNumbersInEmergencyNumberList(updatedAssetEmergencyNumberList);
         } catch (IOException ex) {
-            loge("Cache asset emergency database failure: " + ex);
+            logw("Cache asset emergency database failure: " + ex);
         } finally {
             // close quietly by catching non-runtime exceptions.
             if (inputStream != null) {
@@ -868,7 +885,7 @@
                 emergencyNumberList.add(getLabeledEmergencyNumberForEcclist(emergencyNum));
             }
         }
-        emergencyNumbers = ((slotId < 0) ? "112,911,000,08,110,118,119,999" : "112,911");
+        emergencyNumbers = ((isSimAbsent()) ? "112,911,000,08,110,118,119,999" : "112,911");
         for (String emergencyNum : emergencyNumbers.split(",")) {
             emergencyNumberList.add(getLabeledEmergencyNumberForEcclist(emergencyNum));
         }
@@ -1028,10 +1045,9 @@
         logd("System property doesn't provide any emergency numbers."
                 + " Use embedded logic for determining ones.");
 
-        // If slot id is invalid, means that there is no sim card.
         // According spec 3GPP TS22.101, the following numbers should be
         // ECC numbers when SIM/USIM is not present.
-        emergencyNumbers = ((slotId < 0) ? "112,911,000,08,110,118,119,999" : "112,911");
+        emergencyNumbers = ((isSimAbsent()) ? "112,911,000,08,110,118,119,999" : "112,911");
 
         for (String emergencyNum : emergencyNumbers.split(",")) {
             if (useExactMatch) {
@@ -1156,6 +1172,10 @@
         Rlog.d(TAG, str);
     }
 
+    private static void logw(String str) {
+        Rlog.w(TAG, str);
+    }
+
     private static void loge(String str) {
         Rlog.e(TAG, str);
     }
diff --git a/src/java/com/android/internal/telephony/euicc/EuiccController.java b/src/java/com/android/internal/telephony/euicc/EuiccController.java
index 2d43271..4ac3176 100644
--- a/src/java/com/android/internal/telephony/euicc/EuiccController.java
+++ b/src/java/com/android/internal/telephony/euicc/EuiccController.java
@@ -1293,7 +1293,10 @@
                 confirmationCodeRetried);
         intent.putExtra(EXTRA_OPERATION, op);
         PendingIntent resolutionIntent = PendingIntent.getActivity(
-                mContext, 0 /* requestCode */, intent, PendingIntent.FLAG_ONE_SHOT);
+                mContext,
+                0 /* requestCode */,
+                intent,
+                PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_MUTABLE);
         extrasIntent.putExtra(
                 EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_RESOLUTION_INTENT, resolutionIntent);
     }
@@ -1535,8 +1538,13 @@
 
             // There is no active subscription on the target SIM, checks whether the caller can
             // manage any active subscription on any other SIM.
-            return mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(callingPackage)
+            final long token = Binder.clearCallingIdentity();
+            try {
+                return mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(callingPackage)
                     == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
         } else {
             for (SubscriptionInfo subInfo : subInfoList) {
                 if (subInfo.isEmbedded()
diff --git a/src/java/com/android/internal/telephony/gsm/GsmMmiCode.java b/src/java/com/android/internal/telephony/gsm/GsmMmiCode.java
index 59c99f7..dec2468 100644
--- a/src/java/com/android/internal/telephony/gsm/GsmMmiCode.java
+++ b/src/java/com/android/internal/telephony/gsm/GsmMmiCode.java
@@ -1228,7 +1228,9 @@
     onUssdFinishedError() {
         if (mState == State.PENDING) {
             mState = State.FAILED;
-            mMessage = mContext.getText(com.android.internal.R.string.mmiError);
+            if (TextUtils.isEmpty(mMessage)) {
+                mMessage = mContext.getText(com.android.internal.R.string.mmiError);
+            }
             Rlog.d(LOG_TAG, "onUssdFinishedError");
             mPhone.onMMIDone(this);
         }
diff --git a/src/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java b/src/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
index 48f22ee..1f237b2 100644
--- a/src/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
+++ b/src/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
@@ -29,6 +29,7 @@
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.SMSDispatcher;
 import com.android.internal.telephony.SmsConstants;
+import com.android.internal.telephony.SmsController;
 import com.android.internal.telephony.SmsDispatchersController;
 import com.android.internal.telephony.SmsHeader;
 import com.android.internal.telephony.SmsMessageBase;
@@ -147,7 +148,7 @@
                 + " mMessageRef=" + tracker.mMessageRef
                 + " mUsesImsServiceForIms=" + tracker.mUsesImsServiceForIms
                 + " SS=" + ss
-                + " id=" + tracker.mMessageId);
+                + " " + SmsController.formatCrossStackMessageId(tracker.mMessageId));
 
         // if sms over IMS is not supported on data and voice is not available...
         if (!isIms() && ss != ServiceState.STATE_IN_SERVICE) {
diff --git a/src/java/com/android/internal/telephony/ims/ImsResolver.java b/src/java/com/android/internal/telephony/ims/ImsResolver.java
index 2a9fc7c..8130101 100644
--- a/src/java/com/android/internal/telephony/ims/ImsResolver.java
+++ b/src/java/com/android/internal/telephony/ims/ImsResolver.java
@@ -234,7 +234,7 @@
 
     // Receives broadcasts from the system involving changes to the installed applications. If
     // an ImsService that we are configured to use is installed, we must bind to it.
-    private BroadcastReceiver mAppChangedReceiver = new BroadcastReceiver() {
+    private final BroadcastReceiver mAppChangedReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
             final String action = intent.getAction();
@@ -258,7 +258,7 @@
 
     // Receives the broadcast that a new Carrier Config has been loaded in order to possibly
     // unbind from one service and bind to another.
-    private BroadcastReceiver mConfigChangedReceiver = new BroadcastReceiver() {
+    private final BroadcastReceiver mConfigChangedReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
 
@@ -274,15 +274,18 @@
                     SubscriptionManager.INVALID_SUBSCRIPTION_ID);
             int slotSimState = mTelephonyManagerProxy.getSimState(mContext, slotId);
             if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID
-                    && slotSimState != TelephonyManager.SIM_STATE_ABSENT) {
+                    && (slotSimState != TelephonyManager.SIM_STATE_ABSENT
+                    && slotSimState != TelephonyManager.SIM_STATE_NOT_READY)) {
                 // We only care about carrier config updates that happen when a slot is known to be
-                // absent or populated and the carrier config has been loaded.
+                // absent, the subscription is disabled (not ready), or populated and the carrier
+                // config has been loaded.
                 Log.i(TAG, "Received CCC for slot " + slotId + " and sim state "
                         + slotSimState + ", ignoring.");
                 return;
             }
 
-            Log.i(TAG, "Received Carrier Config Changed for SlotId: " + slotId);
+            Log.i(TAG, "Received Carrier Config Changed for SlotId: " + slotId
+                    + ", sim state: " + slotSimState);
 
             mHandler.obtainMessage(HANDLER_CONFIG_CHANGED, slotId).sendToTarget();
         }
@@ -290,7 +293,7 @@
 
     // Receives the broadcast that the device has finished booting (and the device is no longer
     // encrypted).
-    private BroadcastReceiver mBootCompleted = new BroadcastReceiver() {
+    private final BroadcastReceiver mBootCompleted = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
             Log.i(TAG, "Received BOOT_COMPLETED");
@@ -396,7 +399,7 @@
                 ImsServiceFeatureQueryManager.Listener listener);
     }
 
-    private ImsServiceControllerFactory mImsServiceControllerFactoryCompat =
+    private final ImsServiceControllerFactory mImsServiceControllerFactoryCompat =
             new ImsServiceControllerFactory() {
                 @Override
                 public String getServiceInterface() {
@@ -428,18 +431,19 @@
     private int mNumSlots;
     // Array index corresponds to slot, per slot there is a feature->package name mapping.
     // should only be accessed from handler
-    private SparseArray<Map<Integer, String>> mCarrierServices;
+    private final SparseArray<Map<Integer, String>> mCarrierServices;
     // Package name of the default device services, Maps ImsFeature -> packageName.
-    // should only be accessed from handler
-    private Map<Integer, String> mDeviceServices;
+    // Must synchronize on this object to access.
+    private final Map<Integer, String> mDeviceServices = new ArrayMap<>();
     // Persistent Logging
     private final LocalLog mEventLog = new LocalLog(32);
 
     private boolean mBootCompletedHandlerRan = false;
+    private boolean mCarrierConfigReceived = false;
 
     // Synchronize all events on a handler to ensure that the cache includes the most recent
     // version of the installed ImsServices.
-    private Handler mHandler = new Handler(Looper.getMainLooper(), (msg) -> {
+    private final Handler mHandler = new Handler(Looper.getMainLooper(), (msg) -> {
         switch (msg.what) {
             case HANDLER_ADD_PACKAGE: {
                 String packageName = (String) msg.obj;
@@ -455,8 +459,16 @@
                 if (!mBootCompletedHandlerRan) {
                     mBootCompletedHandlerRan = true;
                     mEventLog.log("handling BOOT_COMPLETE");
-                    // Re-evaluate bound services for all slots after requerying packagemanager
-                    maybeAddedImsService(null /*packageName*/);
+                    if (mCarrierConfigReceived) {
+                        mEventLog.log("boot complete - reeval");
+                        // Re-evaluate bound services for all slots after requerying packagemanager
+                        maybeAddedImsService(null /*packageName*/);
+                    } else {
+                        mEventLog.log("boot complete - update cache");
+                        // Do not bind any ImsServices yet, just update the cache to include new
+                        // services. All will be re-evaluated after first carrier config changed.
+                        updateInstalledServicesCache();
+                    }
                 }
                 break;
             }
@@ -468,6 +480,7 @@
                     Log.w(TAG, "HANDLER_CONFIG_CHANGED for invalid slotid=" + slotId);
                     break;
                 }
+                mCarrierConfigReceived = true;
                 carrierConfigChanged(slotId);
                 break;
             }
@@ -510,10 +523,10 @@
         return true;
     });
 
-    private HandlerExecutor mRunnableExecutor = new HandlerExecutor(mHandler);
+    private final HandlerExecutor mRunnableExecutor = new HandlerExecutor(mHandler);
 
     // Results from dynamic queries to ImsService regarding the features they support.
-    private ImsServiceFeatureQueryManager.Listener mDynamicQueryListener =
+    private final ImsServiceFeatureQueryManager.Listener mDynamicQueryListener =
             new ImsServiceFeatureQueryManager.Listener() {
 
                 @Override
@@ -542,16 +555,16 @@
     // Used during testing, overrides the carrier services while non-empty.
     // Array index corresponds to slot, per slot there is a feature->package name mapping.
     // should only be accessed from handler
-    private SparseArray<SparseArray<String>> mOverrideServices;
+    private final SparseArray<SparseArray<String>> mOverrideServices;
     // Outer array index corresponds to Slot Id, Maps ImsFeature.FEATURE->bound ImsServiceController
     // Locked on mBoundServicesLock
-    private SparseArray<SparseArray<ImsServiceController>> mBoundImsServicesByFeature;
+    private final SparseArray<SparseArray<ImsServiceController>> mBoundImsServicesByFeature;
     // not locked, only accessed on a handler thread.
     // Tracks list of all installed ImsServices
-    private Map<ComponentName, ImsServiceInfo> mInstalledServicesCache = new HashMap<>();
+    private final Map<ComponentName, ImsServiceInfo> mInstalledServicesCache = new HashMap<>();
     // not locked, only accessed on a handler thread.
     // Active ImsServiceControllers, which are bound to ImsServices.
-    private Map<ComponentName, ImsServiceController> mActiveControllers = new HashMap<>();
+    private final Map<ComponentName, ImsServiceController> mActiveControllers = new HashMap<>();
     private ImsServiceFeatureQueryManager mFeatureQueryManager;
 
     public ImsResolver(Context context, String defaultMmTelPackageName,
@@ -564,7 +577,6 @@
         mReceiverContext = context.createContextAsUser(UserHandle.ALL, 0 /*flags*/);
 
         mCarrierServices = new SparseArray<>(mNumSlots);
-        mDeviceServices = new ArrayMap<>();
         setDeviceConfiguration(defaultMmTelPackageName, ImsFeature.FEATURE_EMERGENCY_MMTEL);
         setDeviceConfiguration(defaultMmTelPackageName, ImsFeature.FEATURE_MMTEL);
         setDeviceConfiguration(defaultRcsPackageName, ImsFeature.FEATURE_RCS);
@@ -572,26 +584,6 @@
                 Context.CARRIER_CONFIG_SERVICE);
         mOverrideServices = new SparseArray<>(0 /*initial size*/);
         mBoundImsServicesByFeature = new SparseArray<>(mNumSlots);
-
-        IntentFilter appChangedFilter = new IntentFilter();
-        appChangedFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
-        appChangedFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
-        appChangedFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
-        appChangedFilter.addDataScheme("package");
-        mReceiverContext.registerReceiver(mAppChangedReceiver, appChangedFilter);
-        mReceiverContext.registerReceiver(mConfigChangedReceiver, new IntentFilter(
-                CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED));
-
-        UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
-        if (userManager.isUserUnlocked()) {
-            mHandler.obtainMessage(HANDLER_BOOT_COMPLETE, null).sendToTarget();
-        } else {
-            mReceiverContext.registerReceiver(mBootCompleted, new IntentFilter(
-                    Intent.ACTION_BOOT_COMPLETED));
-            if (userManager.isUserUnlocked()) {
-                mHandler.obtainMessage(HANDLER_BOOT_COMPLETE, null).sendToTarget();
-            }
-        }
     }
 
     @VisibleForTesting
@@ -629,13 +621,28 @@
                 HANDLER_MSIM_CONFIGURATION_CHANGE, null);
         mFeatureQueryManager = mDynamicQueryManagerFactory.create(mContext, mDynamicQueryListener);
 
-        // This will get all services with the correct intent filter from PackageManager
-        List<ImsServiceInfo> infos = getImsServiceInfo(null);
-        for (ImsServiceInfo info : infos) {
-            if (!mInstalledServicesCache.containsKey(info.name)) {
-                mInstalledServicesCache.put(info.name, info);
+        updateInstalledServicesCache();
+
+        IntentFilter appChangedFilter = new IntentFilter();
+        appChangedFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+        appChangedFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+        appChangedFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
+        appChangedFilter.addDataScheme("package");
+        mReceiverContext.registerReceiver(mAppChangedReceiver, appChangedFilter);
+        mReceiverContext.registerReceiver(mConfigChangedReceiver, new IntentFilter(
+                CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED));
+
+        UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+        if (userManager.isUserUnlocked()) {
+            mHandler.obtainMessage(HANDLER_BOOT_COMPLETE, null).sendToTarget();
+        } else {
+            mReceiverContext.registerReceiver(mBootCompleted, new IntentFilter(
+                    Intent.ACTION_BOOT_COMPLETED));
+            if (userManager.isUserUnlocked()) {
+                mHandler.obtainMessage(HANDLER_BOOT_COMPLETE, null).sendToTarget();
             }
         }
+
         // Update the package names of the carrier ImsServices if they do not exist already and
         // possibly bind if carrier configs exist. Otherwise wait for CarrierConfigChanged
         // indication.
@@ -643,6 +650,19 @@
     }
 
     /**
+     * Query the system for all registered ImsServices and add them to the cache if there are any
+     * new ones that are not tracked.
+     */
+    private void updateInstalledServicesCache() {
+        // This will get all services with the correct intent filter from PackageManager
+        for (ImsServiceInfo info : getImsServiceInfo(null)) {
+            if (!mInstalledServicesCache.containsKey(info.name)) {
+                mInstalledServicesCache.put(info.name, info);
+            }
+        }
+    }
+
+    /**
      * Destroys this ImsResolver. Used for tearing down static resources during testing.
      */
     @VisibleForTesting
@@ -662,6 +682,8 @@
                 if (!TextUtils.isEmpty(newPackageName)) {
                     mEventLog.log("bindCarrierServicesIfAvailable - carrier package found: "
                             + newPackageName + " on slot " + slotId);
+                    // Carrier configs are already available, so mark received.
+                    mCarrierConfigReceived = true;
                     setCarrierConfiguredPackageName(newPackageName, slotId, f);
                     ImsServiceInfo info = getImsServiceInfoFromCache(newPackageName);
                     // We do not want to trigger feature configuration changes unless there is
@@ -778,14 +800,16 @@
         return true;
     }
 
-    // not synchronized, access through handler ONLY.
     private String getDeviceConfiguration(@ImsFeature.FeatureType int featureType) {
-        return mDeviceServices.getOrDefault(featureType, "");
+        synchronized (mDeviceServices) {
+            return mDeviceServices.getOrDefault(featureType, "");
+        }
     }
 
-    // not synchronized, access in handler ONLY.
     private void setDeviceConfiguration(String name, @ImsFeature.FeatureType int featureType) {
-        mDeviceServices.put(featureType, name);
+        synchronized (mDeviceServices) {
+            mDeviceServices.put(featureType, name);
+        }
     }
 
     // not synchronized, access in handler ONLY.
@@ -891,6 +915,26 @@
             return null;
         }
     }
+
+    /**
+     * Determines if there is a valid ImsService configured for the specified ImsFeature.
+     * @param slotId The slot ID to check for.
+     * @param featureType The ImsFeature featureType to check for.
+     * @return true if there is an ImsService configured for the specified ImsFeature type, false
+     * if there is not.
+     */
+    public boolean isImsServiceConfiguredForFeature(int slotId,
+            @ImsFeature.FeatureType int featureType) {
+        if (!TextUtils.isEmpty(getDeviceConfiguration(featureType))) {
+            // Shortcut a little bit here - instead of dynamically looking up the configured
+            // package name, which can be a long operation depending on the state, just return true
+            // if there is a configured device ImsService for the requested feature because that
+            // means there will always be at least a device configured ImsService.
+            return true;
+        }
+        return !TextUtils.isEmpty(getConfiguredImsServicePackageName(slotId, featureType));
+    }
+
     /**
      * Resolves the PackageName of the ImsService that is configured to be bound for the slotId and
      * FeatureType specified and returns it.
@@ -913,16 +957,13 @@
             return null;
         }
         CompletableFuture<String> packageNameFuture = new CompletableFuture<>();
+        final long startTimeMs = System.currentTimeMillis();
         if (mHandler.getLooper().isCurrentThread()) {
             // If we are on the same thread as the Handler's looper, run the internal method
             // directly.
             packageNameFuture.complete(getConfiguredImsServicePackageNameInternal(slotId,
                     featureType));
         } else {
-            mEventLog.log("getResolvedImsServicePackageName - [" + slotId + ", "
-                    + ImsFeature.FEATURE_LOG_MAP.get(featureType) + "], starting query...");
-            Log.d(TAG, "getResolvedImsServicePackageName: [" + slotId + ", "
-                    + ImsFeature.FEATURE_LOG_MAP.get(featureType) + "], starting query...");
             mHandler.post(() -> {
                 try {
                     packageNameFuture.complete(getConfiguredImsServicePackageNameInternal(slotId,
@@ -936,12 +977,18 @@
         }
         try {
             String packageName = packageNameFuture.get();
-            mEventLog.log("getResolvedImsServicePackageName - [" + slotId + ", "
-                    + ImsFeature.FEATURE_LOG_MAP.get(featureType)
-                    + "], async query complete with package name: " + packageName);
-            Log.d(TAG, "getResolvedImsServicePackageName: [" + slotId + ", "
-                    + ImsFeature.FEATURE_LOG_MAP.get(featureType)
-                    + "], async query complete with package name: " + packageName);
+            long timeDiff = System.currentTimeMillis() - startTimeMs;
+            if (timeDiff > 50) {
+                // Took an unusually long amount of time (> 50 ms), so log it.
+                mEventLog.log("getResolvedImsServicePackageName - [" + slotId + ", "
+                        + ImsFeature.FEATURE_LOG_MAP.get(featureType)
+                        + "], async query complete, took " + timeDiff + " ms with package name: "
+                        + packageName);
+                Log.w(TAG, "getResolvedImsServicePackageName: [" + slotId + ", "
+                        + ImsFeature.FEATURE_LOG_MAP.get(featureType)
+                        + "], async query complete, took " + timeDiff + " ms with package name: "
+                        + packageName);
+            }
             return packageName;
         } catch (Exception e) {
             mEventLog.log("getResolvedImsServicePackageName - [" + slotId + ", "
@@ -1093,7 +1140,9 @@
 
     private boolean isDeviceService(ImsServiceInfo info) {
         if (info == null) return false;
-        return mDeviceServices.containsValue(info.name.getPackageName());
+        synchronized (mDeviceServices) {
+            return mDeviceServices.containsValue(info.name.getPackageName());
+        }
     }
 
     private List<Integer> getSlotsForActiveCarrierService(ImsServiceInfo info) {
@@ -1545,9 +1594,9 @@
                     + name);
             return;
         }
+        sanitizeFeatureConfig(features);
         mEventLog.log("dynamicQueryComplete: for package " + name + ", features: "
                 + printFeatures(service.getSupportedFeatures()) + " -> " + printFeatures(features));
-        sanitizeFeatureConfig(features);
         // Add features to service
         service.replaceFeatures(features);
         // Wait until all queries have completed before changing the configuration to reduce churn.
@@ -1561,9 +1610,17 @@
     }
 
     /**
-     * Ensure the feature includes MMTEL when it supports EMERGENCY_MMTEL, if not, remove.
+     * Sanitize feature configurations from the ImsService.
+     * <ul>
+     *     <li> Strip out feature configs for inactive slots.</li>
+     *     <li> Ensure the feature includes MMTEL when it supports EMERGENCY_MMTEL, if not, remove.
+     *     </li>
+     * </ul>
      */
     private void sanitizeFeatureConfig(Set<ImsFeatureConfiguration.FeatureSlotPair> features) {
+        // remove configs for slots that are mot active.
+        features.removeIf(f -> f.slotId >= mNumSlots);
+        // Ensure that if EMERGENCY_MMTEL is defined for a slot, MMTEL is also defined.
         Set<ImsFeatureConfiguration.FeatureSlotPair> emergencyMmtelFeatures = features.stream()
                 .filter(feature -> feature.featureType == ImsFeature.FEATURE_EMERGENCY_MMTEL)
                 .collect(Collectors.toSet());
@@ -1643,7 +1700,7 @@
         for (ResolveInfo entry : packageManager.queryIntentServicesAsUser(
                 serviceIntent,
                 PackageManager.GET_META_DATA,
-                UserHandle.getUserHandleForUid(UserHandle.myUserId()))) {
+                UserHandle.of(UserHandle.myUserId()))) {
             ServiceInfo serviceInfo = entry.serviceInfo;
 
             if (serviceInfo != null) {
@@ -1711,8 +1768,10 @@
         pw.increaseIndent();
         pw.println("Device:");
         pw.increaseIndent();
-        for (Integer i : mDeviceServices.keySet()) {
-            pw.println(ImsFeature.FEATURE_LOG_MAP.get(i) + " -> " + mDeviceServices.get(i));
+        synchronized (mDeviceServices) {
+            for (Integer i : mDeviceServices.keySet()) {
+                pw.println(ImsFeature.FEATURE_LOG_MAP.get(i) + " -> " + mDeviceServices.get(i));
+            }
         }
         pw.decreaseIndent();
         pw.println("Carrier: ");
diff --git a/src/java/com/android/internal/telephony/ims/ImsServiceController.java b/src/java/com/android/internal/telephony/ims/ImsServiceController.java
index b4002fb..4bb7097 100644
--- a/src/java/com/android/internal/telephony/ims/ImsServiceController.java
+++ b/src/java/com/android/internal/telephony/ims/ImsServiceController.java
@@ -28,7 +28,7 @@
 import android.os.IInterface;
 import android.os.RemoteException;
 import android.os.UserHandle;
-import android.permission.PermissionManager;
+import android.permission.LegacyPermissionManager;
 import android.telephony.AnomalyReporter;
 import android.telephony.ims.ImsService;
 import android.telephony.ims.aidl.IImsConfig;
@@ -226,7 +226,7 @@
     private static final boolean ENFORCE_SINGLE_SERVICE_FOR_SIP_TRANSPORT = false;
     private final ComponentName mComponentName;
     private final HandlerThread mHandlerThread = new HandlerThread("ImsServiceControllerHandler");
-    private final PermissionManager mPermissionManager;
+    private final LegacyPermissionManager mPermissionManager;
     private ImsFeatureBinderRepository mRepo;
     private ImsServiceControllerCallbacks mCallbacks;
     private ExponentialBackoff mBackoff;
@@ -324,8 +324,8 @@
                 2, /* multiplier */
                 mHandlerThread.getLooper(),
                 mRestartImsServiceRunnable);
-        mPermissionManager =
-                (PermissionManager) mContext.getSystemService(Context.PERMISSION_SERVICE);
+        mPermissionManager = (LegacyPermissionManager) mContext.getSystemService(
+                Context.LEGACY_PERMISSION_SERVICE);
         mRepo = repo;
 
         mPackageManager = mContext.getPackageManager();
diff --git a/src/java/com/android/internal/telephony/ims/MmTelFeatureCompatAdapter.java b/src/java/com/android/internal/telephony/ims/MmTelFeatureCompatAdapter.java
index 85bb856..d9dfd79 100644
--- a/src/java/com/android/internal/telephony/ims/MmTelFeatureCompatAdapter.java
+++ b/src/java/com/android/internal/telephony/ims/MmTelFeatureCompatAdapter.java
@@ -67,6 +67,8 @@
                 TelephonyManager.NETWORK_TYPE_LTE);
         REG_TECH_TO_NET_TYPE.put(ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN,
                 TelephonyManager.NETWORK_TYPE_IWLAN);
+        REG_TECH_TO_NET_TYPE.put(ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM,
+                TelephonyManager.NETWORK_TYPE_IWLAN);
     }
 
     // Feature Type for compatibility with old "feature" updates
@@ -518,7 +520,8 @@
         Intent intent = new Intent(ImsManager.ACTION_IMS_INCOMING_CALL);
         intent.setPackage(TelephonyManager.PHONE_PROCESS_NAME);
         return PendingIntent.getBroadcast(mContext, 0, intent,
-                PendingIntent.FLAG_UPDATE_CURRENT);
+                // Mutable because information associated with the call is passed back here.
+                PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_UPDATE_CURRENT);
     }
 
     private int convertCapability(int capability, int radioTech) {
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhone.java b/src/java/com/android/internal/telephony/imsphone/ImsPhone.java
index a57690e..5620efb 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhone.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhone.java
@@ -925,7 +925,9 @@
     }
 
     @Override
-    public Connection dial(String dialString, DialArgs dialArgs) throws CallStateException {
+    public Connection dial(String dialString, DialArgs dialArgs,
+            Consumer<Phone> chosenPhoneConsumer) throws CallStateException {
+        chosenPhoneConsumer.accept(this);
         return dialInternal(dialString, dialArgs, null);
     }
 
@@ -1512,6 +1514,12 @@
                         isUssdRequest,
                         this);
                 onNetworkInitiatedUssd(mmi);
+        } else if (isUssdError) {
+            ImsPhoneMmiCode mmi;
+            mmi = ImsPhoneMmiCode.newNetworkInitiatedUssd(ussdMessage,
+                    true,
+                    this);
+            mmi.onUssdFinishedError();
         }
     }
 
@@ -1580,7 +1588,31 @@
                                          new SilentRedialParam(mLastDialString, cause, dialArgs),
                                          null);
         if (ar != null) {
-            mSilentRedialRegistrants.notifyRegistrants(ar);
+            // There is a race condition that can happen in some cases:
+            // (Main thread) dial start
+            // (Binder Thread) onCallSessionFailed
+            // (Binder Thread) schedule a redial for CS on the main thread
+            // (Main Thread) dial finish
+            // (Main Thread) schedule to associate ImsPhoneConnection with
+            //               GsmConnection on the main thread
+            // If scheduling the CS redial occurs before the command to schedule the
+            // ImsPhoneConnection to be  associated with the GsmConnection, the CS redial will occur
+            // before GsmConnection has had callbacks to ImsPhone correctly updated. This will cause
+            // Callbacks back to GsmCdmaPhone to never be set up correctly and we will lose track of
+            // the instance.
+            // Instead, schedule this redial to happen on the main thread, so that we know dial has
+            // finished before scheduling a redial:
+            // (Main thread) dial start
+            // (Binder Thread) onCallSessionFailed -> move notify registrants to main thread
+            // (Main Thread) dial finish
+            // (Main Thread) schedule on main thread to associate ImsPhoneConnection with
+            //               GsmConnection
+            // (Main Thread) schedule a redial for CS
+            mContext.getMainExecutor().execute(() -> {
+                logd("initiateSilentRedial: notifying registrants, isEmergency=" + isEmergency
+                        + ", eccCategory=" + eccCategory);
+                mSilentRedialRegistrants.notifyRegistrants(ar);
+            });
         }
     }
 
@@ -1888,6 +1920,7 @@
                 }
                 break;
             case EVENT_INITIATE_VOLTE_SILENT_REDIAL: {
+                // This is a CS -> IMS redial
                 if (VDBG) logd("EVENT_INITIATE_VOLTE_SILENT_REDIAL");
                 ar = (AsyncResult) msg.obj;
                 if (ar.exception == null && ar.result != null) {
@@ -1900,6 +1933,10 @@
                     try {
                         Connection cn = dial(dialString,
                                 updateDialArgsForVolteSilentRedial(dialArgs, causeCode));
+                        // The GSM/CDMA Connection that is owned by the GsmCdmaPhone is currently
+                        // the one with a callback registered to TelephonyConnection. Notify the
+                        // redial happened over that Phone so that it can be replaced with the
+                        // new ImsPhoneConnection.
                         Rlog.d(LOG_TAG, "Notify volte redial connection changed cn: " + cn);
                         if (mDefaultPhone != null) {
                             // don't care it is null or not.
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCall.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCall.java
index 0d93478..98cc441 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCall.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCall.java
@@ -334,31 +334,20 @@
         }
 
         ImsStreamMediaProfile mediaProfile = imsCall.getCallProfile().mMediaProfile;
-
-        return (mediaProfile.mAudioDirection == ImsStreamMediaProfile.DIRECTION_INACTIVE)
-                ? true : false;
+        boolean shouldPlayRingback =
+                (mediaProfile.mAudioDirection == ImsStreamMediaProfile.DIRECTION_INACTIVE)
+                        ? true : false;
+        Rlog.i(LOG_TAG, "isLocalTone: audioDirection=" + mediaProfile.mAudioDirection
+                + ", playRingback=" + shouldPlayRingback);
+        return shouldPlayRingback;
     }
 
-    public boolean update (ImsPhoneConnection conn, ImsCall imsCall, State state) {
+    public boolean update(ImsPhoneConnection conn, ImsCall imsCall, State state) {
         boolean changed = false;
         State oldState = mState;
 
-        //ImsCall.Listener.onCallProgressing can be invoked several times
-        //and ringback tone mode can be changed during the call setup procedure
-        if (state == State.ALERTING) {
-            if (mIsRingbackTonePlaying && !isLocalTone(imsCall)) {
-                getPhone().stopRingbackTone();
-                mIsRingbackTonePlaying = false;
-            } else if (!mIsRingbackTonePlaying && isLocalTone(imsCall)) {
-                getPhone().startRingbackTone();
-                mIsRingbackTonePlaying = true;
-            }
-        } else {
-            if (mIsRingbackTonePlaying) {
-                getPhone().stopRingbackTone();
-                mIsRingbackTonePlaying = false;
-            }
-        }
+        // We will try to start or stop ringback whenever the call has major call state changes.
+        maybeChangeRingbackState(imsCall, state);
 
         if ((state != mState) && (state != State.DISCONNECTED)) {
             mState = state;
@@ -374,6 +363,43 @@
         return changed;
     }
 
+    /**
+     * Determines whether to change the ringback state for a call.
+     * @param imsCall The call.
+     */
+    public void maybeChangeRingbackState(ImsCall imsCall) {
+        maybeChangeRingbackState(imsCall, mState);
+    }
+
+    /**
+     * Determines whether local ringback should be playing for the call.  We will play local
+     * ringback when a call is in an ALERTING state and the audio direction is DIRECTION_INACTIVE.
+     * @param imsCall The call the change pertains to.
+     * @param state The current state of the call.
+     */
+    private void maybeChangeRingbackState(ImsCall imsCall, State state) {
+        //ImsCall.Listener.onCallProgressing can be invoked several times
+        //and ringback tone mode can be changed during the call setup procedure
+        Rlog.i(LOG_TAG, "maybeChangeRingbackState: state=" + state);
+        if (state == State.ALERTING) {
+            if (mIsRingbackTonePlaying && !isLocalTone(imsCall)) {
+                Rlog.i(LOG_TAG, "maybeChangeRingbackState: stop ringback");
+                getPhone().stopRingbackTone();
+                mIsRingbackTonePlaying = false;
+            } else if (!mIsRingbackTonePlaying && isLocalTone(imsCall)) {
+                Rlog.i(LOG_TAG, "maybeChangeRingbackState: start ringback");
+                getPhone().startRingbackTone();
+                mIsRingbackTonePlaying = true;
+            }
+        } else {
+            if (mIsRingbackTonePlaying) {
+                Rlog.i(LOG_TAG, "maybeChangeRingbackState: stop ringback");
+                getPhone().stopRingbackTone();
+                mIsRingbackTonePlaying = false;
+            }
+        }
+    }
+
     /* package */ ImsPhoneConnection
     getHandoverConnection() {
         return (ImsPhoneConnection) getEarliestConnection();
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
index b28d866..d43f572 100755
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
@@ -43,6 +43,7 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
+import android.os.ParcelUuid;
 import android.os.PersistableBundle;
 import android.os.Registrant;
 import android.os.RegistrantList;
@@ -61,6 +62,7 @@
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyLocalConnection;
 import android.telephony.TelephonyManager;
 import android.telephony.emergency.EmergencyNumber;
 import android.telephony.ims.ImsCallProfile;
@@ -104,6 +106,7 @@
 import com.android.internal.telephony.CommandException;
 import com.android.internal.telephony.CommandsInterface;
 import com.android.internal.telephony.Connection;
+import com.android.internal.telephony.IccCardConstants;
 import com.android.internal.telephony.LocaleTracker;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneConstants;
@@ -378,6 +381,11 @@
     private Config mConfig = null;
 
     /**
+     * Whether D2D has been force enabled via the d2d telephony command.
+     */
+    private boolean mDeviceToDeviceForceEnabled = false;
+
+    /**
      * Network callback used to schedule the handover check when a wireless network connects.
      */
     private ConnectivityManager.NetworkCallback mNetworkCallback =
@@ -532,6 +540,8 @@
     private boolean mAlwaysPlayRemoteHoldTone = false;
     private boolean mAutoRetryFailedWifiEmergencyCall = false;
     private boolean mSupportCepOnPeer = true;
+    private boolean mSupportD2DUsingRtp = false;
+    private boolean mSupportSdpForRtpHeaderExtensions = false;
     // Tracks the state of our background/foreground calls while a call hold/swap operation is
     // in progress. Values listed above.
     private HoldSwapState mHoldSwitchingState = HoldSwapState.INACTIVE;
@@ -1025,22 +1035,47 @@
                     null);
         }
 
+        maybeConfigureRtpHeaderExtensions();
+        updateImsServiceConfig();
+        // For compatibility with apps that still use deprecated intent
+        sendImsServiceStateIntent(ImsManager.ACTION_IMS_SERVICE_UP);
+    }
+
+    /**
+     * Configures RTP header extension types used during SDP negotiation.
+     */
+    private void maybeConfigureRtpHeaderExtensions() {
         // Where device to device communication is available, ensure that the
         // supported RTP header extension types defined in {@link RtpTransport} are
         // set as the offered RTP header extensions for this device.
-        if (mConfig != null && mConfig.isD2DCommunicationSupported) {
+        if (mDeviceToDeviceForceEnabled
+                || (mConfig != null && mConfig.isD2DCommunicationSupported
+                && mSupportD2DUsingRtp)) {
             ArraySet<RtpHeaderExtensionType> types = new ArraySet<>();
-            types.add(RtpTransport.CALL_STATE_RTP_HEADER_EXTENSION_TYPE);
-            types.add(RtpTransport.DEVICE_STATE_RTP_HEADER_EXTENSION_TYPE);
-            logi("connectionReady: set offered RTP header extension types");
-            mImsManager.setOfferedRtpHeaderExtensionTypes(types);
-        }
+            if (mSupportSdpForRtpHeaderExtensions) {
+                types.add(RtpTransport.CALL_STATE_RTP_HEADER_EXTENSION_TYPE);
+                types.add(RtpTransport.DEVICE_STATE_RTP_HEADER_EXTENSION_TYPE);
+                logi("maybeConfigureRtpHeaderExtensions: set offered RTP header extension types");
 
-        if (mCarrierConfigLoaded) {
-            mImsManager.updateImsServiceConfig();
+            } else {
+                logi("maybeConfigureRtpHeaderExtensions: SDP negotiation not supported; not "
+                        + "setting offered RTP header extension types");
+            }
+            try {
+                mImsManager.setOfferedRtpHeaderExtensionTypes(types);
+            } catch (ImsException e) {
+                loge("maybeConfigureRtpHeaderExtensions: failed to set extensions; " + e);
+            }
         }
-        // For compatibility with apps that still use deprecated intent
-        sendImsServiceStateIntent(ImsManager.ACTION_IMS_SERVICE_UP);
+    }
+
+    /**
+     * Used via the telephony shell command to force D2D to be enabled.
+     * @param isEnabled {@code true} if D2D is force enabled.
+     */
+    public void setDeviceToDeviceForceEnabled(boolean isEnabled) {
+        mDeviceToDeviceForceEnabled = isEnabled;
+        maybeConfigureRtpHeaderExtensions();
     }
 
     private void stopListeningForCalls() {
@@ -1088,6 +1123,9 @@
             connection.getCall().detach(connection);
         }
         mConnections.clear();
+        // Pending MO was added to mConnections previously, so it has already been disconnected
+        // above. Remove all references to it.
+        mPendingMO = null;
         updatePhoneState();
     }
 
@@ -1424,6 +1462,20 @@
             return;
         }
 
+        Phone defaultPhone = getPhone().getDefaultPhone();
+        if (defaultPhone != null && defaultPhone.getIccCard() != null) {
+            IccCardConstants.State state = defaultPhone.getIccCard().getState();
+            // Bypass until PIN/PUK lock is removed as to ensure that we do not push a config down
+            // when the device is still locked. A CARRIER_CONFIG_CHANGED indication will be sent
+            // once the device moves to ready.
+            if (state != null && (!state.iccCardExist() || state.isPinLocked())) {
+                loge("cacheCarrierConfiguration: card state is not ready, skipping. State= "
+                        + state);
+                mCarrierConfigLoaded = false;
+                return;
+            }
+        }
+
         PersistableBundle carrierConfig = carrierConfigManager.getConfigForSubId(subId);
         if (carrierConfig == null) {
             loge("cacheCarrierConfiguration: Empty carrier config.");
@@ -1433,9 +1485,9 @@
         mCarrierConfigLoaded = true;
 
         updateCarrierConfigCache(carrierConfig);
-        if (mImsManager != null) {
-            mImsManager.updateImsServiceConfig();
-        }
+        updateImsServiceConfig();
+        // Check for changes due to carrier config.
+        maybeConfigureRtpHeaderExtensions();
     }
 
     /**
@@ -1479,6 +1531,11 @@
                 CarrierConfigManager.KEY_AUTO_RETRY_FAILED_WIFI_EMERGENCY_CALL);
         mSupportCepOnPeer = carrierConfig.getBoolean(
                 CarrierConfigManager.KEY_SUPPORT_IMS_CONFERENCE_EVENT_PACKAGE_ON_PEER_BOOL);
+        mSupportD2DUsingRtp = carrierConfig.getBoolean(
+                CarrierConfigManager.KEY_SUPPORTS_DEVICE_TO_DEVICE_COMMUNICATION_USING_RTP_BOOL);
+        mSupportSdpForRtpHeaderExtensions = carrierConfig.getBoolean(
+                CarrierConfigManager
+                        .KEY_SUPPORTS_SDP_NEGOTIATION_OF_D2D_RTP_HEADER_EXTENSIONS_BOOL);
 
         if (mPhone.getContext().getResources().getBoolean(
                 com.android.internal.R.bool.config_allow_ussd_over_ims)) {
@@ -1564,6 +1621,9 @@
             ImsCallProfile profile = mImsManager.createCallProfile(serviceType, callType);
             if (conn.isAdhocConference()) {
                 profile.setCallExtraBoolean(ImsCallProfile.EXTRA_CONFERENCE, true);
+                // Also set value for EXTRA_CONFERENCE_DEPRECATED in case receivers are using old
+                // values.
+                profile.setCallExtraBoolean(ImsCallProfile.EXTRA_CONFERENCE_DEPRECATED, true);
             }
             profile.setCallExtraInt(ImsCallProfile.EXTRA_OIR, clirMode);
             profile.setCallExtraInt(ImsCallProfile.EXTRA_RETRY_CALL_FAIL_REASON,
@@ -1584,7 +1644,7 @@
                             cleanseInstantLetteringMessage(intentExtras.getString(
                                     android.telecom.TelecomManager.EXTRA_CALL_SUBJECT))
                     );
-                    intentExtras.putString(ImsCallProfile.EXTRA_CALL_SUBJECT,
+                    profile.setCallExtra(ImsCallProfile.EXTRA_CALL_SUBJECT,
                             intentExtras.getString(TelecomManager.EXTRA_CALL_SUBJECT));
                 }
 
@@ -1601,7 +1661,10 @@
 
                 if (intentExtras.containsKey(
                         android.telecom.TelecomManager.EXTRA_OUTGOING_PICTURE)) {
-                    // TODO(hallliu) Set ImsCallProfile.EXTRA_PICTURE_URL with cached URL string
+                    String url = TelephonyLocalConnection.getCallComposerServerUrlForHandle(
+                            mPhone.getSubId(), ((ParcelUuid) intentExtras.getParcelable(
+                                    TelecomManager.EXTRA_OUTGOING_PICTURE)).getUuid());
+                    profile.setCallExtra(ImsCallProfile.EXTRA_PICTURE_URL, url);
                 }
 
                 if (conn.hasRttTextStream()) {
@@ -2039,7 +2102,10 @@
         }
 
         try {
-            fgImsCall.consultativeTransfer(bgImsCall);
+            // Per 3GPP TS 24.629 - A.2, the signalling for a consultative transfer should send the
+            // REFER on the background held call with the foreground call specified as the
+            // destination.
+            bgImsCall.consultativeTransfer(fgImsCall);
         } catch (ImsException e) {
             throw new CallStateException(e.getMessage());
         }
@@ -2513,6 +2579,9 @@
         if (ignoreState) {
             conn.updateAddressDisplay(imsCall);
             conn.updateExtras(imsCall);
+            // Some devices will change the audio direction between major call state changes, so we
+            // need to check whether to start or stop ringback
+            conn.maybeChangeRingbackState();
 
             maybeSetVideoCallProvider(conn, imsCall);
             return;
@@ -2646,6 +2715,7 @@
 
             case ImsReasonInfo.CODE_LOCAL_CALL_DECLINE:
             case ImsReasonInfo.CODE_REMOTE_CALL_DECLINE:
+            case ImsReasonInfo.CODE_REJECTED_ELSEWHERE:
                 // If the call has been declined locally (on this device), or on remotely (on
                 // another device using multiendpoint functionality), mark it as rejected.
                 return DisconnectCause.INCOMING_REJECTED;
@@ -2692,6 +2762,7 @@
                 return DisconnectCause.TIMED_OUT;
 
             case ImsReasonInfo.CODE_LOCAL_POWER_OFF:
+            case ImsReasonInfo.CODE_RADIO_OFF:
                 return DisconnectCause.POWER_OFF;
 
             case ImsReasonInfo.CODE_LOCAL_LOW_BATTERY:
@@ -3068,6 +3139,7 @@
 
             if (conn != null) {
                 conn.setPreciseDisconnectCause(getPreciseDisconnectCauseFromReasonInfo(reasonInfo));
+                conn.setImsReasonInfo(reasonInfo);
             }
 
             if (reasonInfo.getCode() == ImsReasonInfo.CODE_SIP_ALTERNATE_EMERGENCY_CALL
@@ -3154,9 +3226,7 @@
             if (mShouldUpdateImsConfigOnDisconnect) {
                 // Ensure we update the IMS config when the call is disconnected; we delayed this
                 // because a video call was paused.
-                if (mImsManager != null) {
-                    mImsManager.updateImsServiceConfig();
-                }
+                updateImsServiceConfig();
                 mShouldUpdateImsConfigOnDisconnect = false;
             }
 
@@ -3748,6 +3818,15 @@
             }
             cqm.saveCallQuality(callQuality);
             mCallQualityMetrics.put(callId, cqm);
+
+            ImsPhoneConnection conn = findConnection(imsCall);
+            if (conn != null) {
+                Bundle report = new Bundle();
+                report.putParcelable(android.telecom.Connection.EXTRA_CALL_QUALITY_REPORT,
+                        callQuality);
+                conn.onConnectionEvent(android.telecom.Connection.EVENT_CALL_QUALITY_REPORT,
+                        report);
+            }
         }
 
         /**
@@ -3885,7 +3964,7 @@
                     || item == ImsConfig.ConfigConstants.LVC_SETTING_ENABLED)) {
                 // Update Ims Service state to make sure updated provisioning values take effect
                 // immediately.
-                mImsManager.updateImsServiceConfig();
+                updateImsServiceConfig();
             }
         }
 
@@ -4374,7 +4453,13 @@
         pw.println(" mIsConferenceEventPackageHandlingEnabled=" + mIsConferenceEventPackageEnabled);
         pw.println(" mSupportCepOnPeer=" + mSupportCepOnPeer);
         if (mConfig != null) {
-            pw.println(" isDeviceToDeviceCommsSupported= " + mConfig.isD2DCommunicationSupported);
+            pw.print(" isDeviceToDeviceCommsSupported= " + mConfig.isD2DCommunicationSupported);
+            pw.println("(forceEnabled=" + mDeviceToDeviceForceEnabled + ")");
+            if (mConfig.isD2DCommunicationSupported) {
+                pw.println(" mSupportD2DUsingRtp= " + mSupportD2DUsingRtp);
+                pw.println(" mSupportSdpForRtpHeaderExtensions= "
+                        + mSupportSdpForRtpHeaderExtensions);
+            }
         }
         pw.println(" Event Log:");
         pw.increaseIndent();
@@ -4444,7 +4529,10 @@
 
     public boolean isVowifiEnabled() {
         return isImsCapabilityInCacheAvailable(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE,
-                ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN);
+                ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN)
+                || isImsCapabilityInCacheAvailable(
+                        MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE,
+                        ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM);
     }
 
     public boolean isVideoCallEnabled() {
@@ -4728,9 +4816,17 @@
                 && reason != DataEnabledSettings.REASON_REGISTERED && mCarrierConfigLoaded) {
             // This will call into updateVideoCallFeatureValue and eventually all clients will be
             // asynchronously notified that the availability of VT over LTE has changed.
-            if (mImsManager != null) {
-                mImsManager.updateImsServiceConfig();
-            }
+            updateImsServiceConfig();
+        }
+    }
+
+    /**
+     * If the ImsService is currently connected and we have loaded the carrier config, proceed to
+     * trigger the update of the configuration sent to the ImsService.
+     */
+    private void updateImsServiceConfig() {
+        if (mImsManager != null && mCarrierConfigLoaded) {
+            mImsManager.updateImsServiceConfig();
         }
     }
 
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java
index 70512ce..f4589dd 100755
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java
@@ -17,6 +17,7 @@
 package com.android.internal.telephony.imsphone;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.net.Uri;
@@ -39,6 +40,7 @@
 import android.telephony.TelephonyManager;
 import android.telephony.ims.AudioCodecAttributes;
 import android.telephony.ims.ImsCallProfile;
+import android.telephony.ims.ImsReasonInfo;
 import android.telephony.ims.ImsStreamMediaProfile;
 import android.telephony.ims.RtpHeaderExtension;
 import android.telephony.ims.RtpHeaderExtensionType;
@@ -57,6 +59,8 @@
 import com.android.internal.telephony.metrics.TelephonyMetrics;
 import com.android.telephony.Rlog;
 
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.Objects;
 import java.util.Set;
@@ -142,6 +146,12 @@
      */
     private boolean mIsLocalVideoCapable = true;
 
+    /**
+     * When the call is in a disconnected, state, will be set to the {@link ImsReasonInfo}
+     * associated with the disconnection, if known.
+     */
+    private ImsReasonInfo mImsReasonInfo;
+
     //***** Event Constants
     private static final int EVENT_DTMF_DONE = 1;
     private static final int EVENT_PAUSE_DONE = 2;
@@ -304,6 +314,10 @@
         }
     }
 
+    @VisibleForTesting
+    public void setTelephonyMetrics(TelephonyMetrics tm) {
+        mMetrics = tm;
+    }
 
     public void dispose() {
     }
@@ -342,6 +356,8 @@
         Rlog.w(LOG_TAG, "applyRemoteCallCapabilities - remoteProfile = "+remoteProfile);
         capabilities = removeCapability(capabilities,
                 Connection.Capability.SUPPORTS_VT_REMOTE_BIDIRECTIONAL);
+        capabilities = removeCapability(capabilities,
+                Connection.Capability.SUPPORTS_RTT_REMOTE);
 
         switch (remoteProfile.mCallType) {
             case ImsCallProfile.CALL_TYPE_VT:
@@ -351,6 +367,10 @@
                         Connection.Capability.SUPPORTS_VT_REMOTE_BIDIRECTIONAL);
                 break;
         }
+
+        if (remoteProfile.getMediaProfile().getRttMode() == ImsStreamMediaProfile.RTT_MODE_FULL) {
+            capabilities = addCapability(capabilities, Connection.Capability.SUPPORTS_RTT_REMOTE);
+        }
         return capabilities;
     }
 
@@ -842,6 +862,14 @@
         return updateParent || updateAddressDisplay || updateMediaCapabilities || updateExtras;
     }
 
+    /**
+     * Re-evaluate whether ringback should be playing.
+     */
+    public void maybeChangeRingbackState() {
+        Rlog.i(LOG_TAG, "maybeChangeRingbackState");
+        mParent.maybeChangeRingbackState(mImsCall);
+    }
+
     @Override
     public int getPreciseDisconnectCause() {
         return mPreciseDisconnectCause;
@@ -1072,6 +1100,8 @@
                 }
             }
 
+            boolean mediaAttributesChanged = false;
+
             // Metrics for audio codec
             if (localCallProfile != null
                     && localCallProfile.mMediaProfile.mAudioQuality != mAudioCodec) {
@@ -1079,6 +1109,7 @@
                 mMetrics.writeAudioCodecIms(mOwner.mPhone.getPhoneId(), imsCall.getCallSession());
                 mOwner.getPhone().getVoiceCallSessionStats().onAudioCodecChanged(this, mAudioCodec);
                 changed = true;
+                mediaAttributesChanged = true;
             }
 
             if (localCallProfile != null
@@ -1090,15 +1121,24 @@
                         - audioCodecAttributes.getBitrateRangeKbps().getUpper()) > THRESHOLD) {
                     mAudioCodecBitrateKbps = audioCodecAttributes.getBitrateRangeKbps().getUpper();
                     changed = true;
+                    mediaAttributesChanged = true;
                 }
                 if (Math.abs(mAudioCodecBandwidthKhz
                         - audioCodecAttributes.getBandwidthRangeKhz().getUpper()) > THRESHOLD) {
                     mAudioCodecBandwidthKhz =
                             audioCodecAttributes.getBandwidthRangeKhz().getUpper();
                     changed = true;
+                    mediaAttributesChanged = true;
                 }
             }
 
+            if (mediaAttributesChanged) {
+                Rlog.i(LOG_TAG, "updateMediaCapabilities: mediate attributes changed: codec = "
+                        + mAudioCodec + ", bitRate=" + mAudioCodecBitrateKbps + ", bandwidth="
+                        + mAudioCodecBandwidthKhz);
+                notifyMediaAttributesChanged();
+            }
+
             int newAudioQuality =
                     getAudioQualityFromCallProfile(localCallProfile, remoteCallProfile);
             if (getAudioQuality() != newAudioQuality) {
@@ -1280,7 +1320,11 @@
             return;
         }
         if (extras.containsKey(ImsCallProfile.EXTRA_FORWARDED_NUMBER)) {
-            mForwardedNumber = extras.getStringArrayList(ImsCallProfile.EXTRA_FORWARDED_NUMBER);
+            String[] forwardedNumberArray =
+                    extras.getStringArray(ImsCallProfile.EXTRA_FORWARDED_NUMBER);
+            if (forwardedNumberArray != null) {
+                mForwardedNumber = new ArrayList<String>(Arrays.asList(forwardedNumberArray));
+            }
         }
     }
 
@@ -1421,6 +1465,14 @@
     }
 
     /**
+     * Indicates whether current phone connection is cross sim calling or not
+     * @return boolean: true if cross sim calling, false otherwise
+     */
+    public boolean isCrossSimCall() {
+        return mImsCall != null && mImsCall.isCrossSimCall();
+    }
+
+    /**
      * Handles notifications from the {@link ImsVideoCallProviderWrapper} of session modification
      * responses received.
      *
@@ -1569,6 +1621,23 @@
     }
 
     /**
+     * For a connection being disconnected, sets the {@link ImsReasonInfo} which describes the
+     * reason for the disconnection.
+     * @param imsReasonInfo The IMS reason info.
+     */
+    public void setImsReasonInfo(@Nullable ImsReasonInfo imsReasonInfo) {
+        mImsReasonInfo = imsReasonInfo;
+    }
+
+    /**
+     * @return the {@link ImsReasonInfo} describing why this connection disconnected, or
+     * {@code null} otherwise.
+     */
+    public @Nullable ImsReasonInfo getImsReasonInfo() {
+        return mImsReasonInfo;
+    }
+
+    /**
      * Converts an {@link ImsCallProfile} verification status to a
      * {@link android.telecom.Connection} verification status.
      * @param verificationStatus The {@link ImsCallProfile} verification status.
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneMmiCode.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneMmiCode.java
index 93e09af..359079d 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneMmiCode.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneMmiCode.java
@@ -346,8 +346,8 @@
         return dialString;
     }
 
-    static ImsPhoneMmiCode
-    newNetworkInitiatedUssd(String ussdMessage, boolean isUssdRequest, ImsPhone phone) {
+    public static ImsPhoneMmiCode newNetworkInitiatedUssd(String ussdMessage,
+            boolean isUssdRequest, ImsPhone phone) {
         ImsPhoneMmiCode ret;
 
         ret = new ImsPhoneMmiCode(phone);
@@ -1194,12 +1194,12 @@
      *
      * The radio has reset, and this is still pending
      */
-
-    void
-    onUssdFinishedError() {
+    public void onUssdFinishedError() {
         if (mState == State.PENDING) {
             mState = State.FAILED;
-            mMessage = mContext.getText(com.android.internal.R.string.mmiError);
+            if (TextUtils.isEmpty(mMessage)) {
+                mMessage = mContext.getText(com.android.internal.R.string.mmiError);
+            }
             Rlog.d(LOG_TAG, "onUssdFinishedError: mmi=" + this);
             mPhone.onMMIDone(this);
         }
@@ -1418,7 +1418,8 @@
             } else if (ar.exception instanceof ImsException) {
                 sb.append(getImsErrorMessage(ar));
             }
-        } else if (ar.result != null && (int) ar.result == CommandsInterface.SS_STATUS_UNKNOWN) {
+        } else if (ar.result != null && ar.result instanceof Integer
+                && (int) ar.result == CommandsInterface.SS_STATUS_UNKNOWN) {
             mState = State.FAILED;
             sb = null;
         } else if (isActivate()) {
diff --git a/src/java/com/android/internal/telephony/metrics/CallQualityMetrics.java b/src/java/com/android/internal/telephony/metrics/CallQualityMetrics.java
index cf98acb..208aff6 100644
--- a/src/java/com/android/internal/telephony/metrics/CallQualityMetrics.java
+++ b/src/java/com/android/internal/telephony/metrics/CallQualityMetrics.java
@@ -25,7 +25,7 @@
 import android.util.Pair;
 
 import com.android.internal.telephony.Phone;
-import com.android.internal.telephony.ServiceStateTracker;
+import com.android.internal.telephony.SignalStrengthController;
 import com.android.internal.telephony.nano.TelephonyProto.TelephonyCallSession;
 import com.android.internal.telephony.util.TelephonyUtils;
 import com.android.telephony.Rlog;
@@ -215,13 +215,13 @@
 
     // Returns the LTE signal to noise ratio, or 0 if unavailable
     private Integer getLteSnr() {
-        ServiceStateTracker sst = mPhone.getDefaultPhone().getServiceStateTracker();
-        if (sst == null) {
-            Rlog.e(TAG, "getLteSnr: unable to get SST for phone " + mPhone.getPhoneId());
+        SignalStrengthController ssc = mPhone.getDefaultPhone().getSignalStrengthController();
+        if (ssc == null) {
+            Rlog.e(TAG, "getLteSnr: unable to get SSC for phone " + mPhone.getPhoneId());
             return CellInfo.UNAVAILABLE;
         }
 
-        SignalStrength ss = sst.getSignalStrength();
+        SignalStrength ss = ssc.getSignalStrength();
         if (ss == null) {
             Rlog.e(TAG, "getLteSnr: unable to get SignalStrength for phone " + mPhone.getPhoneId());
             return CellInfo.UNAVAILABLE;
diff --git a/src/java/com/android/internal/telephony/metrics/DataCallSessionStats.java b/src/java/com/android/internal/telephony/metrics/DataCallSessionStats.java
index ead54d3..f2710a9 100644
--- a/src/java/com/android/internal/telephony/metrics/DataCallSessionStats.java
+++ b/src/java/com/android/internal/telephony/metrics/DataCallSessionStats.java
@@ -67,6 +67,7 @@
     public synchronized void onSetupDataCall(@ApnType int apnTypeBitMask) {
         mDataCallSession = getDefaultProto(apnTypeBitMask);
         mStartTime = getTimeMillis();
+        PhoneFactory.getMetricsCollector().registerOngoingDataCallStat(this);
     }
 
     /**
@@ -116,6 +117,7 @@
                 mDataCallSession.oosAtEnd = getIsOos();
                 mDataCallSession.setupFailed = true;
                 mDataCallSession.ongoing = false;
+                PhoneFactory.getMetricsCollector().unregisterOngoingDataCallStat(this);
                 mAtomsStorage.addDataCallSession(mDataCallSession);
                 mDataCallSession = null;
             }
@@ -173,6 +175,7 @@
         mDataCallSession.durationMinutes = convertMillisToMinutes(getTimeMillis() - mStartTime);
         // store for the data call list event, after DataCall is disconnected and entered into
         // inactive mode
+        PhoneFactory.getMetricsCollector().unregisterOngoingDataCallStat(this);
         mAtomsStorage.addDataCallSession(mDataCallSession);
         mDataCallSession = null;
     }
@@ -198,10 +201,45 @@
         }
     }
 
+    /** Add the on-going data call segment to the atom storage. */
+    public synchronized void conclude() {
+        if (mDataCallSession != null) {
+            DataCallSession call = copyOf(mDataCallSession);
+            long nowMillis = getTimeMillis();
+            call.durationMinutes = convertMillisToMinutes(nowMillis - mStartTime);
+            mStartTime = nowMillis;
+            mDataCallSession.ratSwitchCount = 0L;
+            mAtomsStorage.addDataCallSession(call);
+        }
+    }
+
     private static long convertMillisToMinutes(long millis) {
         return Math.round(millis / 60000.0);
     }
 
+    private static DataCallSession copyOf(DataCallSession call) {
+        DataCallSession copy = new DataCallSession();
+        copy.dimension = call.dimension;
+        copy.isMultiSim = call.isMultiSim;
+        copy.isEsim = call.isEsim;
+        copy.apnTypeBitmask = call.apnTypeBitmask;
+        copy.carrierId = call.carrierId;
+        copy.isRoaming = call.isRoaming;
+        copy.ratAtEnd = call.ratAtEnd;
+        copy.oosAtEnd = call.oosAtEnd;
+        copy.ratSwitchCount = call.ratSwitchCount;
+        copy.isOpportunistic = call.isOpportunistic;
+        copy.ipType = call.ipType;
+        copy.setupFailed = call.setupFailed;
+        copy.failureCause = call.failureCause;
+        copy.suggestedRetryMillis = call.suggestedRetryMillis;
+        copy.deactivateReason = call.deactivateReason;
+        copy.durationMinutes = call.durationMinutes;
+        copy.ongoing = call.ongoing;
+        copy.bandAtEnd = call.bandAtEnd;
+        return copy;
+    }
+
     /** Creates a proto for a normal {@code DataCallSession} with default values. */
     private DataCallSession getDefaultProto(@ApnType int apnTypeBitmask) {
         DataCallSession proto = new DataCallSession();
diff --git a/src/java/com/android/internal/telephony/metrics/ImsStats.java b/src/java/com/android/internal/telephony/metrics/ImsStats.java
index fe00a4a..fefb799 100644
--- a/src/java/com/android/internal/telephony/metrics/ImsStats.java
+++ b/src/java/com/android/internal/telephony/metrics/ImsStats.java
@@ -35,8 +35,6 @@
 import android.telephony.AccessNetworkConstants;
 import android.telephony.AccessNetworkConstants.TransportType;
 import android.telephony.Annotation.NetworkType;
-import android.telephony.NetworkRegistrationInfo;
-import android.telephony.ServiceState;
 import android.telephony.TelephonyManager;
 import android.telephony.ims.ImsReasonInfo;
 import android.telephony.ims.ProvisioningManager;
@@ -265,10 +263,24 @@
             @ImsRegistrationTech int radioTech, MmTelCapabilities capabilities) {
         conclude();
 
-        if (mLastRegistrationStats != null) {
-            mLastRegistrationStats.rat = convertRegistrationTechToNetworkType(radioTech);
+        boolean ratChanged = false;
+        @NetworkType int newRat = convertRegistrationTechToNetworkType(radioTech);
+        if (mLastRegistrationStats != null && mLastRegistrationStats.rat != newRat) {
+            mLastRegistrationStats.rat = newRat;
+            ratChanged = true;
         }
+
+        boolean voiceAvailableNow = capabilities.isCapable(CAPABILITY_TYPE_VOICE);
+        boolean voiceAvailabilityChanged =
+                (mLastAvailableFeatures.isCapable(CAPABILITY_TYPE_VOICE) != voiceAvailableNow);
         mLastAvailableFeatures = capabilities;
+
+        // Notify voice RAT change if 1. RAT changed while voice over IMS is available, or 2. voice
+        // over IMS availability changed
+        if ((ratChanged && voiceAvailableNow) || voiceAvailabilityChanged) {
+            mPhone.getDefaultPhone().getServiceStateTracker().getServiceStateStats()
+                    .onImsVoiceRegistrationChanged();
+        }
     }
 
     /** Updates the stats when capable features changed. */
@@ -335,6 +347,20 @@
         mLastAvailableFeatures = new MmTelCapabilities();
     }
 
+    /**
+     * Returns the current RAT used for IMS voice registration, or {@link
+     * TelephonyManager#NETWORK_TYPE_UNKNOWN} if there isn't any.
+     */
+    @NetworkType
+    @VisibleForTesting
+    public synchronized int getImsVoiceRadioTech() {
+        if (mLastRegistrationStats == null
+                || !mLastAvailableFeatures.isCapable(CAPABILITY_TYPE_VOICE)) {
+            return TelephonyManager.NETWORK_TYPE_UNKNOWN;
+        }
+        return mLastRegistrationStats.rat;
+    }
+
     @NetworkType
     private int getRatAtEnd(@NetworkType int lastStateRat) {
         return lastStateRat == TelephonyManager.NETWORK_TYPE_IWLAN ? lastStateRat : getWwanPsRat();
@@ -354,14 +380,7 @@
 
     @NetworkType
     private int getWwanPsRat() {
-        ServiceState state = mPhone.getServiceStateTracker().getServiceState();
-        final NetworkRegistrationInfo wwanRegInfo =
-                state.getNetworkRegistrationInfo(
-                        NetworkRegistrationInfo.DOMAIN_PS,
-                        AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
-        return wwanRegInfo != null
-                ? wwanRegInfo.getAccessNetworkTechnology()
-                : TelephonyManager.NETWORK_TYPE_UNKNOWN;
+        return ServiceStateStats.getDataRat(mPhone.getServiceStateTracker().getServiceState());
     }
 
     private ImsRegistrationStats getDefaultImsRegistrationStats() {
diff --git a/src/java/com/android/internal/telephony/metrics/MetricsCollector.java b/src/java/com/android/internal/telephony/metrics/MetricsCollector.java
index f1e5255..6e54230 100644
--- a/src/java/com/android/internal/telephony/metrics/MetricsCollector.java
+++ b/src/java/com/android/internal/telephony/metrics/MetricsCollector.java
@@ -42,6 +42,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.TelephonyStatsLog;
 import com.android.internal.telephony.imsphone.ImsPhone;
 import com.android.internal.telephony.nano.PersistAtomsProto.CellularDataServiceSwitch;
 import com.android.internal.telephony.nano.PersistAtomsProto.CellularServiceState;
@@ -60,6 +61,8 @@
 import java.util.Comparator;
 import java.util.List;
 import java.util.Random;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 
 /**
  * Implements statsd pullers for Telephony.
@@ -101,6 +104,7 @@
     private PersistAtomsStorage mStorage;
     private final StatsManager mStatsManager;
     private final AirplaneModeStats mAirplaneModeStats;
+    private final Set<DataCallSessionStats> mOngoingDataCallStats = ConcurrentHashMap.newKeySet();
     private static final Random sRandom = new Random();
 
     public MetricsCollector(Context context) {
@@ -182,6 +186,19 @@
         return mStorage;
     }
 
+    /**
+     * Registers a {@link DataCallSessionStats} which will be pinged for on-going data calls when
+     * data call atoms are pulled.
+     */
+    public void registerOngoingDataCallStat(DataCallSessionStats call) {
+        mOngoingDataCallStats.add(call);
+    }
+
+    /** Unregisters a {@link DataCallSessionStats} when it no longer handles an active data call. */
+    public void unregisterOngoingDataCallStat(DataCallSessionStats call) {
+        mOngoingDataCallStats.remove(call);
+    }
+
     private static int pullSimSlotState(List<StatsEvent> data) {
         SimSlotState state;
         try {
@@ -191,14 +208,12 @@
             return StatsManager.PULL_SKIP;
         }
 
-        StatsEvent e =
-                StatsEvent.newBuilder()
-                        .setAtomId(SIM_SLOT_STATE)
-                        .writeInt(state.numActiveSlots)
-                        .writeInt(state.numActiveSims)
-                        .writeInt(state.numActiveEsims)
-                        .build();
-        data.add(e);
+        data.add(
+                TelephonyStatsLog.buildStatsEvent(
+                        SIM_SLOT_STATE,
+                        state.numActiveSlots,
+                        state.numActiveSims,
+                        state.numActiveEsims));
         return StatsManager.PULL_SUCCESS;
     }
 
@@ -214,12 +229,7 @@
             rafSupported |= phone.getRadioAccessFamily();
         }
 
-        StatsEvent e =
-                StatsEvent.newBuilder()
-                        .setAtomId(SUPPORTED_RADIO_ACCESS_FAMILY)
-                        .writeLong(rafSupported)
-                        .build();
-        data.add(e);
+        data.add(TelephonyStatsLog.buildStatsEvent(SUPPORTED_RADIO_ACCESS_FAMILY, rafSupported));
         return StatsManager.PULL_SUCCESS;
     }
 
@@ -231,11 +241,7 @@
             // All phones should have the same version of the carrier ID table, so only query the
             // first one.
             int version = phones[0].getCarrierIdListVersion();
-            data.add(
-                    StatsEvent.newBuilder()
-                            .setAtomId(CARRIER_ID_TABLE_VERSION)
-                            .writeInt(version)
-                            .build());
+            data.add(TelephonyStatsLog.buildStatsEvent(CARRIER_ID_TABLE_VERSION, version));
             return StatsManager.PULL_SUCCESS;
         }
     }
@@ -299,6 +305,11 @@
     }
 
     private int pullDataCallSession(List<StatsEvent> data) {
+        // Include ongoing data call segments
+        for (DataCallSessionStats stats : mOngoingDataCallStats) {
+            stats.conclude();
+        }
+
         DataCallSession[] dataCallSessions = mStorage.getDataCallSessions(MIN_COOLDOWN_MILLIS);
         if (dataCallSessions != null) {
             Arrays.stream(dataCallSessions)
@@ -396,217 +407,179 @@
     }
 
     private static StatsEvent buildStatsEvent(CellularDataServiceSwitch serviceSwitch) {
-        return StatsEvent.newBuilder()
-                .setAtomId(CELLULAR_DATA_SERVICE_SWITCH)
-                .writeInt(serviceSwitch.ratFrom)
-                .writeInt(serviceSwitch.ratTo)
-                .writeInt(serviceSwitch.simSlotIndex)
-                .writeBoolean(serviceSwitch.isMultiSim)
-                .writeInt(serviceSwitch.carrierId)
-                .writeInt(serviceSwitch.switchCount)
-                .build();
+        return TelephonyStatsLog.buildStatsEvent(
+                CELLULAR_DATA_SERVICE_SWITCH,
+                serviceSwitch.ratFrom,
+                serviceSwitch.ratTo,
+                serviceSwitch.simSlotIndex,
+                serviceSwitch.isMultiSim,
+                serviceSwitch.carrierId,
+                serviceSwitch.switchCount);
     }
 
     private static StatsEvent buildStatsEvent(CellularServiceState state) {
-        return StatsEvent.newBuilder()
-                .setAtomId(CELLULAR_SERVICE_STATE)
-                .writeInt(state.voiceRat)
-                .writeInt(state.dataRat)
-                .writeInt(state.voiceRoamingType)
-                .writeInt(state.dataRoamingType)
-                .writeBoolean(state.isEndc)
-                .writeInt(state.simSlotIndex)
-                .writeBoolean(state.isMultiSim)
-                .writeInt(state.carrierId)
-                .writeInt(
-                        (int)
-                                (round(state.totalTimeMillis, DURATION_BUCKET_MILLIS)
-                                        / SECOND_IN_MILLIS))
-                .build();
+        return TelephonyStatsLog.buildStatsEvent(
+                CELLULAR_SERVICE_STATE,
+                state.voiceRat,
+                state.dataRat,
+                state.voiceRoamingType,
+                state.dataRoamingType,
+                state.isEndc,
+                state.simSlotIndex,
+                state.isMultiSim,
+                state.carrierId,
+                (int) (round(state.totalTimeMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS));
     }
 
     private static StatsEvent buildStatsEvent(VoiceCallRatUsage usage) {
-        return StatsEvent.newBuilder()
-                .setAtomId(VOICE_CALL_RAT_USAGE)
-                .writeInt(usage.carrierId)
-                .writeInt(usage.rat)
-                .writeLong(
-                        round(usage.totalDurationMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS)
-                .writeLong(usage.callCount)
-                .build();
+        return TelephonyStatsLog.buildStatsEvent(
+                VOICE_CALL_RAT_USAGE,
+                usage.carrierId,
+                usage.rat,
+                round(usage.totalDurationMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS,
+                usage.callCount);
     }
 
     private static StatsEvent buildStatsEvent(VoiceCallSession session) {
-        return StatsEvent.newBuilder()
-                .setAtomId(VOICE_CALL_SESSION)
-                .writeInt(session.bearerAtStart)
-                .writeInt(session.bearerAtEnd)
-                .writeInt(session.direction)
-                .writeInt(session.setupDuration)
-                .writeBoolean(session.setupFailed)
-                .writeInt(session.disconnectReasonCode)
-                .writeInt(session.disconnectExtraCode)
-                .writeString(session.disconnectExtraMessage)
-                .writeInt(session.ratAtStart)
-                .writeInt(session.ratAtEnd)
-                .writeLong(session.ratSwitchCount)
-                .writeLong(session.codecBitmask)
-                .writeInt(session.concurrentCallCountAtStart)
-                .writeInt(session.concurrentCallCountAtEnd)
-                .writeInt(session.simSlotIndex)
-                .writeBoolean(session.isMultiSim)
-                .writeBoolean(session.isEsim)
-                .writeInt(session.carrierId)
-                .writeBoolean(session.srvccCompleted)
-                .writeLong(session.srvccFailureCount)
-                .writeLong(session.srvccCancellationCount)
-                .writeBoolean(session.rttEnabled)
-                .writeBoolean(session.isEmergency)
-                .writeBoolean(session.isRoaming)
+        return TelephonyStatsLog.buildStatsEvent(
+                VOICE_CALL_SESSION,
+                session.bearerAtStart,
+                session.bearerAtEnd,
+                session.direction,
+                session.setupDuration,
+                session.setupFailed,
+                session.disconnectReasonCode,
+                session.disconnectExtraCode,
+                session.disconnectExtraMessage,
+                session.ratAtStart,
+                session.ratAtEnd,
+                session.ratSwitchCount,
+                session.codecBitmask,
+                session.concurrentCallCountAtStart,
+                session.concurrentCallCountAtEnd,
+                session.simSlotIndex,
+                session.isMultiSim,
+                session.isEsim,
+                session.carrierId,
+                session.srvccCompleted,
+                session.srvccFailureCount,
+                session.srvccCancellationCount,
+                session.rttEnabled,
+                session.isEmergency,
+                session.isRoaming,
                 // workaround: dimension required for keeping multiple pulled atoms
-                .writeInt(sRandom.nextInt())
+                sRandom.nextInt(),
                 // New fields introduced in Android S
-                .writeInt(session.signalStrengthAtEnd)
-                .writeInt(session.bandAtEnd)
-                .writeInt(session.setupDurationMillis)
-                .writeInt(session.mainCodecQuality)
-                .writeBoolean(session.videoEnabled)
-                .writeInt(session.ratAtConnected)
-                .writeBoolean(session.isMultiparty)
-                .build();
+                session.signalStrengthAtEnd,
+                session.bandAtEnd,
+                session.setupDurationMillis,
+                session.mainCodecQuality,
+                session.videoEnabled,
+                session.ratAtConnected,
+                session.isMultiparty);
     }
 
     private static StatsEvent buildStatsEvent(IncomingSms sms) {
-        return StatsEvent.newBuilder()
-                .setAtomId(INCOMING_SMS)
-                .writeInt(sms.smsFormat)
-                .writeInt(sms.smsTech)
-                .writeInt(sms.rat)
-                .writeInt(sms.smsType)
-                .writeInt(sms.totalParts)
-                .writeInt(sms.receivedParts)
-                .writeBoolean(sms.blocked)
-                .writeInt(sms.error)
-                .writeBoolean(sms.isRoaming)
-                .writeInt(sms.simSlotIndex)
-                .writeBoolean(sms.isMultiSim)
-                .writeBoolean(sms.isEsim)
-                .writeInt(sms.carrierId)
-                .writeLong(sms.messageId)
-                .build();
+        return TelephonyStatsLog.buildStatsEvent(
+                INCOMING_SMS,
+                sms.smsFormat,
+                sms.smsTech,
+                sms.rat,
+                sms.smsType,
+                sms.totalParts,
+                sms.receivedParts,
+                sms.blocked,
+                sms.error,
+                sms.isRoaming,
+                sms.simSlotIndex,
+                sms.isMultiSim,
+                sms.isEsim,
+                sms.carrierId,
+                sms.messageId);
     }
 
     private static StatsEvent buildStatsEvent(OutgoingSms sms) {
-        return StatsEvent.newBuilder()
-                .setAtomId(OUTGOING_SMS)
-                .writeInt(sms.smsFormat)
-                .writeInt(sms.smsTech)
-                .writeInt(sms.rat)
-                .writeInt(sms.sendResult)
-                .writeInt(sms.errorCode)
-                .writeBoolean(sms.isRoaming)
-                .writeBoolean(sms.isFromDefaultApp)
-                .writeInt(sms.simSlotIndex)
-                .writeBoolean(sms.isMultiSim)
-                .writeBoolean(sms.isEsim)
-                .writeInt(sms.carrierId)
-                .writeLong(sms.messageId)
-                .writeInt(sms.retryId)
-                .build();
+        return TelephonyStatsLog.buildStatsEvent(
+                OUTGOING_SMS,
+                sms.smsFormat,
+                sms.smsTech,
+                sms.rat,
+                sms.sendResult,
+                sms.errorCode,
+                sms.isRoaming,
+                sms.isFromDefaultApp,
+                sms.simSlotIndex,
+                sms.isMultiSim,
+                sms.isEsim,
+                sms.carrierId,
+                sms.messageId,
+                sms.retryId);
     }
 
     private static StatsEvent buildStatsEvent(DataCallSession dataCallSession) {
-        return StatsEvent.newBuilder()
-                .setAtomId(DATA_CALL_SESSION)
-                .writeInt(dataCallSession.dimension)
-                .writeBoolean(dataCallSession.isMultiSim)
-                .writeBoolean(dataCallSession.isEsim)
-                .writeInt(0) // profile is deprecated, so we default to 0
-                .writeInt(dataCallSession.apnTypeBitmask)
-                .writeInt(dataCallSession.carrierId)
-                .writeBoolean(dataCallSession.isRoaming)
-                .writeInt(dataCallSession.ratAtEnd)
-                .writeBoolean(dataCallSession.oosAtEnd)
-                .writeLong(dataCallSession.ratSwitchCount)
-                .writeBoolean(dataCallSession.isOpportunistic)
-                .writeInt(dataCallSession.ipType)
-                .writeBoolean(dataCallSession.setupFailed)
-                .writeInt(dataCallSession.failureCause)
-                .writeInt(dataCallSession.suggestedRetryMillis)
-                .writeInt(dataCallSession.deactivateReason)
-                .writeLong(round(
-                        dataCallSession.durationMinutes, DURATION_BUCKET_MILLIS / MINUTE_IN_MILLIS))
-                .writeBoolean(dataCallSession.ongoing)
-                .writeInt(dataCallSession.bandAtEnd)
-                .build();
+        return TelephonyStatsLog.buildStatsEvent(
+                DATA_CALL_SESSION,
+                dataCallSession.dimension,
+                dataCallSession.isMultiSim,
+                dataCallSession.isEsim,
+                0, // profile is deprecated, so we default to 0
+                dataCallSession.apnTypeBitmask,
+                dataCallSession.carrierId,
+                dataCallSession.isRoaming,
+                dataCallSession.ratAtEnd,
+                dataCallSession.oosAtEnd,
+                dataCallSession.ratSwitchCount,
+                dataCallSession.isOpportunistic,
+                dataCallSession.ipType,
+                dataCallSession.setupFailed,
+                dataCallSession.failureCause,
+                dataCallSession.suggestedRetryMillis,
+                dataCallSession.deactivateReason,
+                round(dataCallSession.durationMinutes, DURATION_BUCKET_MILLIS / MINUTE_IN_MILLIS),
+                dataCallSession.ongoing,
+                dataCallSession.bandAtEnd);
     }
 
     private static StatsEvent buildStatsEvent(ImsRegistrationStats stats) {
-        return StatsEvent.newBuilder()
-                .setAtomId(IMS_REGISTRATION_STATS)
-                .writeInt(stats.carrierId)
-                .writeInt(stats.simSlotIndex)
-                .writeInt(stats.rat)
-                .writeInt(
-                        (int)
-                                (round(stats.registeredMillis, DURATION_BUCKET_MILLIS)
-                                        / SECOND_IN_MILLIS))
-                .writeInt(
-                        (int)
-                                (round(stats.voiceCapableMillis, DURATION_BUCKET_MILLIS)
-                                        / SECOND_IN_MILLIS))
-                .writeInt(
-                        (int)
-                                (round(stats.voiceAvailableMillis, DURATION_BUCKET_MILLIS)
-                                        / SECOND_IN_MILLIS))
-                .writeInt(
-                        (int)
-                                (round(stats.smsCapableMillis, DURATION_BUCKET_MILLIS)
-                                        / SECOND_IN_MILLIS))
-                .writeInt(
-                        (int)
-                                (round(stats.smsAvailableMillis, DURATION_BUCKET_MILLIS)
-                                        / SECOND_IN_MILLIS))
-                .writeInt(
-                        (int)
-                                (round(stats.videoCapableMillis, DURATION_BUCKET_MILLIS)
-                                        / SECOND_IN_MILLIS))
-                .writeInt(
-                        (int)
-                                (round(stats.videoAvailableMillis, DURATION_BUCKET_MILLIS)
-                                        / SECOND_IN_MILLIS))
-                .writeInt(
-                        (int)
-                                (round(stats.utCapableMillis, DURATION_BUCKET_MILLIS)
-                                        / SECOND_IN_MILLIS))
-                .writeInt(
-                        (int)
-                                (round(stats.utAvailableMillis, DURATION_BUCKET_MILLIS)
-                                        / SECOND_IN_MILLIS))
-                .build();
+        return TelephonyStatsLog.buildStatsEvent(
+                IMS_REGISTRATION_STATS,
+                stats.carrierId,
+                stats.simSlotIndex,
+                stats.rat,
+                (int) (round(stats.registeredMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS),
+                (int) (round(stats.voiceCapableMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS),
+                (int)
+                        (round(stats.voiceAvailableMillis, DURATION_BUCKET_MILLIS)
+                                / SECOND_IN_MILLIS),
+                (int) (round(stats.smsCapableMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS),
+                (int) (round(stats.smsAvailableMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS),
+                (int) (round(stats.videoCapableMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS),
+                (int)
+                        (round(stats.videoAvailableMillis, DURATION_BUCKET_MILLIS)
+                                / SECOND_IN_MILLIS),
+                (int) (round(stats.utCapableMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS),
+                (int) (round(stats.utAvailableMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS));
     }
 
     private static StatsEvent buildStatsEvent(ImsRegistrationTermination termination) {
-        return StatsEvent.newBuilder()
-                .setAtomId(IMS_REGISTRATION_TERMINATION)
-                .writeInt(termination.carrierId)
-                .writeBoolean(termination.isMultiSim)
-                .writeInt(termination.ratAtEnd)
-                .writeBoolean(termination.setupFailed)
-                .writeInt(termination.reasonCode)
-                .writeInt(termination.extraCode)
-                .writeString(termination.extraMessage)
-                .writeInt(termination.count)
-                .build();
+        return TelephonyStatsLog.buildStatsEvent(
+                IMS_REGISTRATION_TERMINATION,
+                termination.carrierId,
+                termination.isMultiSim,
+                termination.ratAtEnd,
+                termination.setupFailed,
+                termination.reasonCode,
+                termination.extraCode,
+                termination.extraMessage,
+                termination.count);
     }
 
     private static StatsEvent buildStatsEvent(NetworkRequests networkRequests) {
-        return StatsEvent.newBuilder()
-                .setAtomId(TELEPHONY_NETWORK_REQUESTS)
-                .writeInt(networkRequests.carrierId)
-                .writeInt(networkRequests.enterpriseRequestCount)
-                .writeInt(networkRequests.enterpriseReleaseCount)
-                .build();
+        return TelephonyStatsLog.buildStatsEvent(
+                TELEPHONY_NETWORK_REQUESTS,
+                networkRequests.carrierId,
+                networkRequests.enterpriseRequestCount,
+                networkRequests.enterpriseReleaseCount);
     }
 
     /** Returns all phones in {@link PhoneFactory}, or an empty array if phones not made yet. */
diff --git a/src/java/com/android/internal/telephony/metrics/NetworkRequestsStats.java b/src/java/com/android/internal/telephony/metrics/NetworkRequestsStats.java
index e0d2703..2975e72 100644
--- a/src/java/com/android/internal/telephony/metrics/NetworkRequestsStats.java
+++ b/src/java/com/android/internal/telephony/metrics/NetworkRequestsStats.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.telephony.metrics;
 
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 
@@ -28,8 +30,12 @@
 public class NetworkRequestsStats {
     private NetworkRequestsStats() { }
 
-    /** Generate metrics when enterprise network request occurs. */
-    public static void addEnterpriseRequest(int subId) {
+    /** Generate metrics when network request occurs. */
+    public static void addNetworkRequest(NetworkRequest networkRequest, int subId) {
+        if (!networkRequest.hasCapability(NetworkCapabilities.NET_CAPABILITY_ENTERPRISE)) {
+            // Currently only handle enterprise
+            return;
+        }
         NetworkRequests networkRequests = new NetworkRequests();
         networkRequests.carrierId = getCarrierId(subId);
         networkRequests.enterpriseRequestCount = 1;
@@ -38,8 +44,12 @@
         storage.addNetworkRequests(networkRequests);
     }
 
-    /** Generate metrics when enterprise network release occurs. */
-    public static void addEnterpriseRelease(int subId) {
+    /** Generate metrics when network release occurs. */
+    public static void addNetworkRelease(NetworkRequest networkRequest, int subId) {
+        if (!networkRequest.hasCapability(NetworkCapabilities.NET_CAPABILITY_ENTERPRISE)) {
+            // Currently only handle enterprise
+            return;
+        }
         NetworkRequests networkRequests = new NetworkRequests();
         networkRequests.carrierId = getCarrierId(subId);
         networkRequests.enterpriseReleaseCount = 1;
diff --git a/src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java b/src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java
index 4f0302f..6796dad 100644
--- a/src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java
+++ b/src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java
@@ -233,8 +233,17 @@
 
     /** Adds a data call session to the storage. */
     public synchronized void addDataCallSession(DataCallSession dataCall) {
-        mAtoms.dataCallSession =
-                insertAtRandomPlace(mAtoms.dataCallSession, dataCall, mMaxNumDataCallSessions);
+        int index = findIndex(dataCall);
+        if (index >= 0) {
+            DataCallSession existingCall = mAtoms.dataCallSession[index];
+            dataCall.ratSwitchCount += existingCall.ratSwitchCount;
+            dataCall.durationMinutes += existingCall.durationMinutes;
+            mAtoms.dataCallSession[index] = dataCall;
+        } else {
+            mAtoms.dataCallSession =
+                    insertAtRandomPlace(mAtoms.dataCallSession, dataCall, mMaxNumDataCallSessions);
+        }
+
         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
     }
 
@@ -730,6 +739,19 @@
     }
 
     /**
+     * Returns the index of data call session that has the same random dimension as the given one,
+     * or -1 if it does not exist.
+     */
+    private int findIndex(DataCallSession key) {
+        for (int i = 0; i < mAtoms.dataCallSession.length; i++) {
+            if (mAtoms.dataCallSession[i].dimension == key.dimension) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    /**
      * Inserts a new element in a random position in an array with a maximum size, replacing the
      * least recent item if possible.
      */
diff --git a/src/java/com/android/internal/telephony/metrics/ServiceStateStats.java b/src/java/com/android/internal/telephony/metrics/ServiceStateStats.java
index 7703261..6698e5b 100644
--- a/src/java/com/android/internal/telephony/metrics/ServiceStateStats.java
+++ b/src/java/com/android/internal/telephony/metrics/ServiceStateStats.java
@@ -29,6 +29,7 @@
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.ServiceStateTracker;
+import com.android.internal.telephony.imsphone.ImsPhone;
 import com.android.internal.telephony.nano.PersistAtomsProto.CellularDataServiceSwitch;
 import com.android.internal.telephony.nano.PersistAtomsProto.CellularServiceState;
 import com.android.telephony.Rlog;
@@ -58,6 +59,23 @@
         addServiceState(lastState, now);
     }
 
+    /** Updates service state when IMS voice registration changes. */
+    public void onImsVoiceRegistrationChanged() {
+        final long now = getTimeMillis();
+        TimestampedServiceState lastState =
+                mLastState.getAndUpdate(
+                        state -> {
+                            if (state.mServiceState == null) {
+                                return new TimestampedServiceState(null, now);
+                            }
+                            CellularServiceState newServiceState = copyOf(state.mServiceState);
+                            newServiceState.voiceRat =
+                                    getVoiceRat(mPhone, getServiceStateForPhone(mPhone));
+                            return new TimestampedServiceState(newServiceState, now);
+                        });
+        addServiceState(lastState, now);
+    }
+
     /** Updates the current service state. */
     public void onServiceStateChanged(ServiceState serviceState) {
         final long now = getTimeMillis();
@@ -197,26 +215,33 @@
         return copy;
     }
 
+    /**
+     * Returns {@code true} if modem radio is turned off (e.g. airplane mode).
+     *
+     * <p>Currently this is approximated by voice service state being {@code STATE_POWER_OFF}.
+     */
     private static boolean isModemOff(ServiceState state) {
-        // NOTE: Wifi calls can be made in airplane mode, where voice reg state is POWER_OFF but
-        // data reg state is IN_SERVICE. In this case, service state should still be tracked.
-        return state.getVoiceRegState() == ServiceState.STATE_POWER_OFF
-                && state.getDataRegState() == ServiceState.STATE_POWER_OFF;
+        // TODO(b/189335473): we should get this info from phone's radio power state, which is
+        // updated separately
+        return state.getVoiceRegState() == ServiceState.STATE_POWER_OFF;
     }
 
     /**
-     * Returns the current voice RAT from the service state, or {@link
-     * TelephonyManager#NETWORK_TYPE_IWLAN} if the phone has Wifi calling in use.
+     * Returns the current voice RAT from IMS registration if present, otherwise from the service
+     * state.
      */
-    public static @NetworkType int getVoiceRat(Phone phone, @Nullable ServiceState state) {
+    static @NetworkType int getVoiceRat(Phone phone, @Nullable ServiceState state) {
         if (state == null) {
             return TelephonyManager.NETWORK_TYPE_UNKNOWN;
         }
-        boolean isWifiCall =
-                phone.getImsPhone() != null
-                        && phone.getImsPhone().isWifiCallingEnabled()
-                        && state.getDataNetworkType() == TelephonyManager.NETWORK_TYPE_IWLAN;
-        return isWifiCall ? TelephonyManager.NETWORK_TYPE_IWLAN : state.getVoiceNetworkType();
+        ImsPhone imsPhone = (ImsPhone) phone.getImsPhone();
+        if (imsPhone != null) {
+            @NetworkType int imsVoiceRat = imsPhone.getImsStats().getImsVoiceRadioTech();
+            if (imsVoiceRat != TelephonyManager.NETWORK_TYPE_UNKNOWN) {
+                return imsVoiceRat;
+            }
+        }
+        return state.getVoiceNetworkType();
     }
 
     /**
@@ -233,7 +258,7 @@
     }
 
     /** Returns PS (data) RAT used by WWAN. */
-    private static @NetworkType int getDataRat(ServiceState state) {
+    static @NetworkType int getDataRat(ServiceState state) {
         final NetworkRegistrationInfo wwanRegInfo =
                 state.getNetworkRegistrationInfo(
                         NetworkRegistrationInfo.DOMAIN_PS,
diff --git a/src/java/com/android/internal/telephony/metrics/TelephonyMetrics.java b/src/java/com/android/internal/telephony/metrics/TelephonyMetrics.java
index aaf7bbe..5e43876 100644
--- a/src/java/com/android/internal/telephony/metrics/TelephonyMetrics.java
+++ b/src/java/com/android/internal/telephony/metrics/TelephonyMetrics.java
@@ -31,6 +31,7 @@
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SEND_SMS;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SEND_SMS_EXPECT_MORE;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SETUP_DATA_CALL;
+import static com.android.internal.telephony.dataconnection.LinkBandwidthEstimator.NUM_SIGNAL_LEVEL;
 import static com.android.internal.telephony.nano.TelephonyProto.PdpType.PDP_TYPE_IP;
 import static com.android.internal.telephony.nano.TelephonyProto.PdpType.PDP_TYPE_IPV4V6;
 import static com.android.internal.telephony.nano.TelephonyProto.PdpType.PDP_TYPE_IPV6;
@@ -70,6 +71,7 @@
 import android.telephony.ims.stub.ImsRegistrationImplBase;
 import android.telephony.ims.stub.ImsSmsImplBase;
 import android.text.TextUtils;
+import android.util.ArrayMap;
 import android.util.Base64;
 import android.util.SparseArray;
 
@@ -80,12 +82,15 @@
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.RIL;
 import com.android.internal.telephony.RILConstants;
+import com.android.internal.telephony.SmsController;
 import com.android.internal.telephony.SmsResponse;
 import com.android.internal.telephony.UUSInfo;
+import com.android.internal.telephony.dataconnection.LinkBandwidthEstimator;
 import com.android.internal.telephony.emergency.EmergencyNumberTracker;
 import com.android.internal.telephony.imsphone.ImsPhoneCall;
 import com.android.internal.telephony.nano.TelephonyProto;
 import com.android.internal.telephony.nano.TelephonyProto.ActiveSubscriptionInfo;
+import com.android.internal.telephony.nano.TelephonyProto.BandwidthEstimatorStats;
 import com.android.internal.telephony.nano.TelephonyProto.EmergencyNumberInfo;
 import com.android.internal.telephony.nano.TelephonyProto.ImsCapabilities;
 import com.android.internal.telephony.nano.TelephonyProto.ImsConnectionState;
@@ -128,6 +133,7 @@
 import java.util.Arrays;
 import java.util.Deque;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.ThreadLocalRandom;
 
 /**
@@ -221,6 +227,10 @@
     private final SparseArray<SparseArray<RilDataCall>> mLastRilDataCallEvents =
             new SparseArray<>();
 
+    /** List of Tx and Rx Bandwidth estimation stats maps */
+    private final List<Map<String, BwEstimationStats>> mBwEstStatsMapList = new ArrayList<>(
+            Arrays.asList(new ArrayMap<>(), new ArrayMap<>()));
+
     /** The start system time of the TelephonyLog in milliseconds*/
     private long mStartSystemTimeMs;
 
@@ -620,6 +630,20 @@
                 + new DecimalFormat("#.##").format(s.monitoredRailEnergyConsumedMah));
         pw.decreaseIndent();
         pw.println("Hardware Version: " + SystemProperties.get("ro.boot.revision", ""));
+
+        pw.decreaseIndent();
+        pw.println("LinkBandwidthEstimator stats:");
+        pw.increaseIndent();
+
+        pw.println("Tx");
+        for (BwEstimationStats stats : mBwEstStatsMapList.get(0).values()) {
+            pw.println(stats.toString());
+        }
+
+        pw.println("Rx");
+        for (BwEstimationStats stats : mBwEstStatsMapList.get(1).values()) {
+            pw.println(stats.toString());
+        }
     }
 
     /**
@@ -640,6 +664,8 @@
         mTelephonyEvents.clear();
         mCompletedCallSessions.clear();
         mCompletedSmsSessions.clear();
+        mBwEstStatsMapList.get(0).clear();
+        mBwEstStatsMapList.get(1).clear();
 
         mTelephonyEventsDropped = false;
 
@@ -792,7 +818,7 @@
             }
         }
         log.lastActiveSubscriptionInfo = activeSubscriptionInfo;
-
+        log.bandwidthEstimatorStats = buildBandwidthEstimatorStats();
         return log;
     }
 
@@ -1887,7 +1913,7 @@
                     SmsSession.Event.Type.SMS_SEND_RESULT)
                     .setImsServiceErrno(resultCode)
                     .setErrorCode(errorReason)
-                    .setMessageId((messageId))
+                    .setMessageId(messageId)
             );
 
             smsSession.decreaseExpectedResponse();
@@ -2456,7 +2482,7 @@
                 + " parts, source = " + smsSource
                 + " blocked = " + blocked
                 + " type = " + type
-                + " messageId = " + messageId);
+                + " " + SmsController.formatCrossStackMessageId(messageId));
 
         int smsFormat = convertSmsFormat(format);
         int smsError =
@@ -2920,4 +2946,121 @@
                 return SimState.SIM_STATE_UNKNOWN;
         }
     }
+
+    /**
+     * Write bandwidth estimator stats
+     */
+    public synchronized void writeBandwidthStats(int link, int rat, int nrMode,
+            int signalLevel, int bwEstExtErrPercent, int coldStartErrPercent, int bwKbps) {
+        BwEstimationStats stats = lookupEstimationStats(link, rat, nrMode);
+        stats.mBwEstErrorAcc[signalLevel] += Math.abs(bwEstExtErrPercent);
+        stats.mStaticBwErrorAcc[signalLevel] += Math.abs(coldStartErrPercent);
+        stats.mBwAccKbps[signalLevel] += bwKbps;
+        stats.mCount[signalLevel]++;
+    }
+
+    private BwEstimationStats lookupEstimationStats(int linkIndex, int dataRat, int nrMode) {
+        String dataRatName = LinkBandwidthEstimator.getDataRatName(dataRat, nrMode);
+        BwEstimationStats ans = mBwEstStatsMapList.get(linkIndex).get(dataRatName);
+        if (ans == null) {
+            ans = new BwEstimationStats(dataRat, nrMode);
+            mBwEstStatsMapList.get(linkIndex).put(dataRatName, ans);
+        }
+        return ans;
+    }
+
+    private BandwidthEstimatorStats buildBandwidthEstimatorStats() {
+        BandwidthEstimatorStats stats = new BandwidthEstimatorStats();
+        List<BandwidthEstimatorStats.PerRat> ratList;
+        ratList = writeBandwidthEstimatorStatsRatList(mBwEstStatsMapList.get(0));
+        stats.perRatTx = ratList.toArray(new BandwidthEstimatorStats.PerRat[0]);
+        ratList = writeBandwidthEstimatorStatsRatList(mBwEstStatsMapList.get(1));
+        stats.perRatRx = ratList.toArray(new BandwidthEstimatorStats.PerRat[0]);
+        return stats;
+    }
+
+    private List<BandwidthEstimatorStats.PerRat> writeBandwidthEstimatorStatsRatList(
+            Map<String, BwEstimationStats> bwEstStatsMap) {
+        List<BandwidthEstimatorStats.PerRat> ratList = new ArrayList<>();
+        for (BwEstimationStats perRat : bwEstStatsMap.values()) {
+            ratList.add(perRat.writeBandwidthStats());
+        }
+        return ratList;
+    }
+
+    private static class BwEstimationStats {
+        final int mRadioTechnology;
+        final int mNrMode;
+        final long[] mBwEstErrorAcc = new long[NUM_SIGNAL_LEVEL];
+        final long[] mStaticBwErrorAcc = new long[NUM_SIGNAL_LEVEL];
+        final long[] mBwAccKbps = new long[NUM_SIGNAL_LEVEL];
+        final int[] mCount = new int[NUM_SIGNAL_LEVEL];
+
+        BwEstimationStats(int radioTechnology, int nrMode) {
+            mRadioTechnology = radioTechnology;
+            mNrMode = nrMode;
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder();
+            return sb.append(LinkBandwidthEstimator.getDataRatName(mRadioTechnology, mNrMode))
+                    .append("\n Count\n").append(printValues(mCount))
+                    .append("\n AvgKbps\n").append(printAvgValues(mBwAccKbps, mCount))
+                    .append("\n BwEst Error\n").append(printAvgValues(mBwEstErrorAcc, mCount))
+                    .append("\n StaticBw Error\n").append(printAvgValues(mStaticBwErrorAcc, mCount))
+                    .toString();
+        }
+
+        private String printValues(int[] values) {
+            StringBuilder sb = new StringBuilder();
+            for (int k = 0; k < NUM_SIGNAL_LEVEL; k++) {
+                sb.append(" " + values[k]);
+            }
+            return sb.toString();
+        }
+
+        private String printAvgValues(long[] stats, int[] count) {
+            StringBuilder sb = new StringBuilder();
+            for (int k = 0; k < NUM_SIGNAL_LEVEL; k++) {
+                int avgStat = calculateAvg(stats[k], count[k]);
+                sb.append(" " + avgStat);
+            }
+            return sb.toString();
+        }
+
+        private BandwidthEstimatorStats.PerRat writeBandwidthStats() {
+            BandwidthEstimatorStats.PerRat stats = new BandwidthEstimatorStats.PerRat();
+            List<BandwidthEstimatorStats.PerLevel> levelList = new ArrayList<>();
+            for (int level = 0; level < NUM_SIGNAL_LEVEL; level++) {
+                BandwidthEstimatorStats.PerLevel currStats = writeBandwidthStatsPerLevel(level);
+                if (currStats != null) {
+                    levelList.add(currStats);
+                }
+            }
+            stats.rat = mRadioTechnology;
+            stats.perLevel = levelList.toArray(new BandwidthEstimatorStats.PerLevel[0]);
+            stats.nrMode = mNrMode;
+            return stats;
+        }
+
+        private BandwidthEstimatorStats.PerLevel writeBandwidthStatsPerLevel(int level) {
+            int count = mCount[level];
+            if (count > 0) {
+                BandwidthEstimatorStats.PerLevel stats = new BandwidthEstimatorStats.PerLevel();
+                stats.signalLevel = level;
+                stats.count = count;
+                stats.avgBwKbps = calculateAvg(mBwAccKbps[level], count);
+                stats.staticBwErrorPercent = calculateAvg(mStaticBwErrorAcc[level], count);
+                stats.bwEstErrorPercent = calculateAvg(mBwEstErrorAcc[level], count);
+                return stats;
+            }
+            return null;
+        }
+
+        private int calculateAvg(long acc, int count) {
+            return (count > 0) ? (int) (acc / count) : 0;
+        }
+    }
+
 }
diff --git a/src/java/com/android/internal/telephony/metrics/VoiceCallSessionStats.java b/src/java/com/android/internal/telephony/metrics/VoiceCallSessionStats.java
index d2f740c..be3c492 100644
--- a/src/java/com/android/internal/telephony/metrics/VoiceCallSessionStats.java
+++ b/src/java/com/android/internal/telephony/metrics/VoiceCallSessionStats.java
@@ -415,6 +415,12 @@
             // internal fields for tracking
             proto.setupBeginMillis = getTimeMillis();
 
+            // audio codec might have already been set
+            int codec = audioQualityToCodec(bearer, conn.getAudioCodec());
+            if (codec != AudioCodec.AUDIO_CODEC_UNKNOWN) {
+                proto.codecBitmask = (1L << codec);
+            }
+
             proto.concurrentCallCountAtStart = mCallProtos.size();
             mCallProtos.put(id, proto);
 
diff --git a/src/java/com/android/internal/telephony/nitz/NitzSignalInputFilterPredicateFactory.java b/src/java/com/android/internal/telephony/nitz/NitzSignalInputFilterPredicateFactory.java
index aa72722..965cd41 100644
--- a/src/java/com/android/internal/telephony/nitz/NitzSignalInputFilterPredicateFactory.java
+++ b/src/java/com/android/internal/telephony/nitz/NitzSignalInputFilterPredicateFactory.java
@@ -21,10 +21,10 @@
 import android.content.Context;
 import android.os.PowerManager;
 import android.os.PowerManager.WakeLock;
-import android.os.TimestampedValue;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.NitzData;
+import com.android.internal.telephony.NitzSignal;
 import com.android.internal.telephony.NitzStateMachine.DeviceState;
 import com.android.internal.telephony.nitz.NitzStateMachineImpl.NitzSignalInputFilterPredicate;
 import com.android.telephony.Rlog;
@@ -84,8 +84,8 @@
          */
         @Nullable
         Boolean mustProcessNitzSignal(
-                @Nullable TimestampedValue<NitzData> previousSignal,
-                @NonNull TimestampedValue<NitzData> newSignal);
+                @Nullable NitzSignal previousSignal,
+                @NonNull NitzSignal newSignal);
     }
 
     /**
@@ -132,8 +132,9 @@
                 // Acquire the wake lock as we are reading the elapsed realtime clock below.
                 wakeLock.acquire();
 
-                long elapsedRealtime = deviceState.elapsedRealtime();
-                long millisSinceNitzReceived = elapsedRealtime - newSignal.getReferenceTimeMillis();
+                long elapsedRealtime = deviceState.elapsedRealtimeMillis();
+                long millisSinceNitzReceived =
+                        elapsedRealtime - newSignal.getReceiptElapsedRealtimeMillis();
                 if (millisSinceNitzReceived < 0 || millisSinceNitzReceived > Integer.MAX_VALUE) {
                     if (DBG) {
                         Rlog.d(LOG_TAG, "mustProcessNitzSignal: Not processing NITZ signal"
@@ -178,15 +179,15 @@
             @Override
             @NonNull
             public Boolean mustProcessNitzSignal(
-                    @NonNull TimestampedValue<NitzData> previousSignal,
-                    @NonNull TimestampedValue<NitzData> newSignal) {
+                    @NonNull NitzSignal previousSignal,
+                    @NonNull NitzSignal newSignal) {
                 Objects.requireNonNull(newSignal);
-                Objects.requireNonNull(newSignal.getValue());
+                Objects.requireNonNull(newSignal.getNitzData());
                 Objects.requireNonNull(previousSignal);
-                Objects.requireNonNull(previousSignal.getValue());
+                Objects.requireNonNull(previousSignal.getNitzData());
 
-                NitzData newNitzData = newSignal.getValue();
-                NitzData previousNitzData = previousSignal.getValue();
+                NitzData newNitzData = newSignal.getNitzData();
+                NitzData previousNitzData = previousSignal.getNitzData();
 
                 // Compare the discrete NitzData fields associated with local time offset. Any
                 // difference and we should process the signal regardless of how recent the last one
@@ -195,26 +196,36 @@
                     return true;
                 }
 
-                // Now check the continuous NitzData field (time) to see if it is sufficiently
-                // different.
+                // Check the time-related NitzData fields to see if they are sufficiently different.
+
+                // See if the NITZ signals have been received sufficiently far apart. If yes, we
+                // want to process the new one.
                 int nitzUpdateSpacing = deviceState.getNitzUpdateSpacingMillis();
+                long elapsedRealtimeSinceLastSaved = newSignal.getReceiptElapsedRealtimeMillis()
+                        - previousSignal.getReceiptElapsedRealtimeMillis();
+                if (elapsedRealtimeSinceLastSaved > nitzUpdateSpacing) {
+                    return true;
+                }
+
+                // See if the NITZ signals have sufficiently different encoded UTC times. If yes,
+                // then we want to process the new one.
                 int nitzUpdateDiff = deviceState.getNitzUpdateDiffMillis();
 
-                // Calculate the elapsed time between the new signal and the last signal.
-                long elapsedRealtimeSinceLastSaved = newSignal.getReferenceTimeMillis()
-                        - previousSignal.getReferenceTimeMillis();
-
-                // Calculate the UTC difference between the time the two signals hold.
+                // Calculate the UTC difference between the time the two signals hold, accounting
+                // for any difference in receipt time and age.
                 long utcTimeDifferenceMillis = newNitzData.getCurrentTimeInMillis()
                         - previousNitzData.getCurrentTimeInMillis();
+                long ageAdjustedElapsedRealtimeDifferenceMillis =
+                        newSignal.getAgeAdjustedElapsedRealtimeMillis()
+                                - previousSignal.getAgeAdjustedElapsedRealtimeMillis();
 
-                // Ideally the difference between elapsedRealtimeSinceLastSaved and
-                // utcTimeDifferenceMillis would be zero.
-                long millisGainedOrLost = Math
-                        .abs(utcTimeDifferenceMillis - elapsedRealtimeSinceLastSaved);
-
-                if (elapsedRealtimeSinceLastSaved > nitzUpdateSpacing
-                        || millisGainedOrLost > nitzUpdateDiff) {
+                // In ideal conditions, the difference between
+                // ageAdjustedElapsedRealtimeSinceLastSaved and utcTimeDifferenceMillis will be zero
+                // if two NITZ signals are consistent and if the elapsed realtime clock is ticking
+                // at the correct rate.
+                long millisGainedOrLost = Math.abs(
+                        utcTimeDifferenceMillis - ageAdjustedElapsedRealtimeDifferenceMillis);
+                if (millisGainedOrLost > nitzUpdateDiff) {
                     return true;
                 }
 
@@ -256,8 +267,8 @@
         }
 
         @Override
-        public boolean mustProcessNitzSignal(@Nullable TimestampedValue<NitzData> oldSignal,
-                @NonNull TimestampedValue<NitzData> newSignal) {
+        public boolean mustProcessNitzSignal(@Nullable NitzSignal oldSignal,
+                @NonNull NitzSignal newSignal) {
             Objects.requireNonNull(newSignal);
 
             for (TrivalentPredicate component : mComponents) {
diff --git a/src/java/com/android/internal/telephony/nitz/NitzStateMachineImpl.java b/src/java/com/android/internal/telephony/nitz/NitzStateMachineImpl.java
index a36eb4f..7a53491 100644
--- a/src/java/com/android/internal/telephony/nitz/NitzStateMachineImpl.java
+++ b/src/java/com/android/internal/telephony/nitz/NitzStateMachineImpl.java
@@ -25,6 +25,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.NitzData;
+import com.android.internal.telephony.NitzSignal;
 import com.android.internal.telephony.NitzStateMachine;
 import com.android.internal.telephony.Phone;
 import com.android.internal.util.IndentingPrintWriter;
@@ -68,8 +69,8 @@
          * See {@link NitzSignalInputFilterPredicate}.
          */
         boolean mustProcessNitzSignal(
-                @Nullable TimestampedValue<NitzData> oldSignal,
-                @NonNull TimestampedValue<NitzData> newSignal);
+                @Nullable NitzSignal oldSignal,
+                @NonNull NitzSignal newSignal);
     }
 
     /**
@@ -89,7 +90,7 @@
         @NonNull
         TelephonyTimeZoneSuggestion getTimeZoneSuggestion(
                 int slotIndex, @Nullable String countryIsoCode,
-                @Nullable TimestampedValue<NitzData> nitzSignal);
+                @Nullable NitzSignal nitzSignal);
     }
 
     static final String LOG_TAG = "NewNitzStateMachineImpl";
@@ -114,7 +115,7 @@
      * needs to be recalculated when something else has changed.
      */
     @Nullable
-    private TimestampedValue<NitzData> mLatestNitzSignal;
+    private NitzSignal mLatestNitzSignal;
 
     // Time Zone detection state.
 
@@ -227,14 +228,14 @@
     }
 
     @Override
-    public void handleNitzReceived(@NonNull TimestampedValue<NitzData> nitzSignal) {
+    public void handleNitzReceived(@NonNull NitzSignal nitzSignal) {
         if (DBG) {
             Rlog.d(LOG_TAG, "handleNitzReceived: nitzSignal=" + nitzSignal);
         }
         Objects.requireNonNull(nitzSignal);
 
         // Perform input filtering to filter bad data and avoid processing signals too often.
-        TimestampedValue<NitzData> previousNitzSignal = mLatestNitzSignal;
+        NitzSignal previousNitzSignal = mLatestNitzSignal;
         if (!mNitzSignalInputFilter.mustProcessNitzSignal(previousNitzSignal, nitzSignal)) {
             return;
         }
@@ -278,7 +279,8 @@
      * Perform a round of time zone detection and notify the time zone detection service as needed.
      */
     private void doTimeZoneDetection(
-            @Nullable String countryIsoCode, @Nullable TimestampedValue<NitzData> nitzSignal,
+            @Nullable String countryIsoCode, @Nullable NitzSignal
+            nitzSignal,
             @NonNull String reason) {
         try {
             Objects.requireNonNull(reason);
@@ -306,7 +308,7 @@
     /**
      * Perform a round of time detection and notify the time detection service as needed.
      */
-    private void doTimeDetection(@Nullable TimestampedValue<NitzData> nitzSignal,
+    private void doTimeDetection(@Nullable NitzSignal nitzSignal,
             @NonNull String reason) {
         try {
             Objects.requireNonNull(reason);
@@ -317,9 +319,7 @@
                 builder.addDebugInfo("Clearing time suggestion"
                         + " reason=" + reason);
             } else {
-                TimestampedValue<Long> newNitzTime = new TimestampedValue<>(
-                        nitzSignal.getReferenceTimeMillis(),
-                        nitzSignal.getValue().getCurrentTimeInMillis());
+                TimestampedValue<Long> newNitzTime = nitzSignal.createTimeSignal();
                 builder.setUtcTime(newNitzTime);
                 builder.addDebugInfo("Sending new time suggestion"
                         + " nitzSignal=" + nitzSignal
@@ -350,6 +350,6 @@
 
     @Nullable
     public NitzData getCachedNitzData() {
-        return mLatestNitzSignal != null ? mLatestNitzSignal.getValue() : null;
+        return mLatestNitzSignal != null ? mLatestNitzSignal.getNitzData() : null;
     }
 }
diff --git a/src/java/com/android/internal/telephony/nitz/TimeZoneSuggesterImpl.java b/src/java/com/android/internal/telephony/nitz/TimeZoneSuggesterImpl.java
index 48491df..342705b 100644
--- a/src/java/com/android/internal/telephony/nitz/TimeZoneSuggesterImpl.java
+++ b/src/java/com/android/internal/telephony/nitz/TimeZoneSuggesterImpl.java
@@ -21,12 +21,12 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
-import android.os.TimestampedValue;
 import android.text.TextUtils;
 import android.timezone.CountryTimeZones.OffsetResult;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.NitzData;
+import com.android.internal.telephony.NitzSignal;
 import com.android.internal.telephony.NitzStateMachine.DeviceState;
 import com.android.internal.telephony.nitz.NitzStateMachineImpl.TimeZoneSuggester;
 import com.android.internal.telephony.nitz.TimeZoneLookupHelper.CountryResult;
@@ -55,12 +55,12 @@
     @Override
     @NonNull
     public TelephonyTimeZoneSuggestion getTimeZoneSuggestion(int slotIndex,
-            @Nullable String countryIsoCode, @Nullable TimestampedValue<NitzData> nitzSignal) {
+            @Nullable String countryIsoCode, @Nullable NitzSignal nitzSignal) {
         try {
             // Check for overriding NITZ-based signals from Android running in an emulator.
             TelephonyTimeZoneSuggestion overridingSuggestion = null;
             if (nitzSignal != null) {
-                NitzData nitzData = nitzSignal.getValue();
+                NitzData nitzData = nitzSignal.getNitzData();
                 if (nitzData.getEmulatorHostTimeZone() != null) {
                     TelephonyTimeZoneSuggestion.Builder builder =
                             new TelephonyTimeZoneSuggestion.Builder(slotIndex)
@@ -135,9 +135,9 @@
      */
     @NonNull
     private TelephonyTimeZoneSuggestion findTimeZoneForTestNetwork(
-            int slotIndex, @NonNull TimestampedValue<NitzData> nitzSignal) {
+            int slotIndex, @NonNull NitzSignal nitzSignal) {
         Objects.requireNonNull(nitzSignal);
-        NitzData nitzData = Objects.requireNonNull(nitzSignal.getValue());
+        NitzData nitzData = Objects.requireNonNull(nitzSignal.getNitzData());
 
         TelephonyTimeZoneSuggestion.Builder suggestionBuilder =
                 new TelephonyTimeZoneSuggestion.Builder(slotIndex);
@@ -166,7 +166,7 @@
     @NonNull
     private TelephonyTimeZoneSuggestion findTimeZoneFromCountryAndNitz(
             int slotIndex, @NonNull String countryIsoCode,
-            @NonNull TimestampedValue<NitzData> nitzSignal) {
+            @NonNull NitzSignal nitzSignal) {
         Objects.requireNonNull(countryIsoCode);
         Objects.requireNonNull(nitzSignal);
 
@@ -175,7 +175,7 @@
         suggestionBuilder.addDebugInfo("findTimeZoneFromCountryAndNitz:"
                 + " countryIsoCode=" + countryIsoCode
                 + ", nitzSignal=" + nitzSignal);
-        NitzData nitzData = Objects.requireNonNull(nitzSignal.getValue());
+        NitzData nitzData = Objects.requireNonNull(nitzSignal.getNitzData());
         if (isNitzSignalOffsetInfoBogus(countryIsoCode, nitzData)) {
             suggestionBuilder.addDebugInfo(
                     "findTimeZoneFromCountryAndNitz: NITZ signal looks bogus");
diff --git a/src/java/com/android/internal/telephony/sip/SipPhone.java b/src/java/com/android/internal/telephony/sip/SipPhone.java
index c88d01f..4ac1ddf 100755
--- a/src/java/com/android/internal/telephony/sip/SipPhone.java
+++ b/src/java/com/android/internal/telephony/sip/SipPhone.java
@@ -43,6 +43,7 @@
 import com.android.telephony.Rlog;
 
 import java.text.ParseException;
+import java.util.function.Consumer;
 import java.util.regex.Pattern;
 
 /**
@@ -192,7 +193,9 @@
     }
 
     @Override
-    public Connection dial(String dialString, DialArgs dialArgs) throws CallStateException {
+    public Connection dial(String dialString, DialArgs dialArgs,
+            Consumer<Phone> chosenPhoneConsumer) throws CallStateException {
+        chosenPhoneConsumer.accept(this);
         synchronized (SipPhone.class) {
             return dialInternal(dialString, dialArgs.videoState);
         }
diff --git a/src/java/com/android/internal/telephony/uicc/AdnCapacity.java b/src/java/com/android/internal/telephony/uicc/AdnCapacity.java
index 300759a..d951c71 100644
--- a/src/java/com/android/internal/telephony/uicc/AdnCapacity.java
+++ b/src/java/com/android/internal/telephony/uicc/AdnCapacity.java
@@ -16,7 +16,6 @@
 
 package com.android.internal.telephony.uicc;
 
-import android.hardware.radio.V1_6.PhonebookCapacity;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -55,21 +54,6 @@
         mMaxAnrLength = maxAnrLength;
     }
 
-    public AdnCapacity(PhonebookCapacity pbCap) {
-        if (pbCap != null) {
-            mMaxAdnCount = pbCap.maxAdnRecords;
-            mUsedAdnCount = pbCap.usedAdnRecords;
-            mMaxEmailCount = pbCap.maxEmailRecords;
-            mUsedEmailCount = pbCap.usedEmailRecords;
-            mMaxAnrCount = pbCap.maxAdditionalNumberRecords;
-            mUsedAnrCount = pbCap.usedAdditionalNumberRecords;
-            mMaxNameLength = pbCap.maxNameLen;
-            mMaxNumberLength = pbCap.maxNumberLen;
-            mMaxEmailLength = pbCap.maxEmailLen;
-            mMaxAnrLength = pbCap.maxAdditionalNumberLen;
-        }
-    }
-
     public int getMaxAdnCount() {
         return mMaxAdnCount;
     }
diff --git a/src/java/com/android/internal/telephony/uicc/IccCardApplicationStatus.java b/src/java/com/android/internal/telephony/uicc/IccCardApplicationStatus.java
index fb8b111..840a6b3 100644
--- a/src/java/com/android/internal/telephony/uicc/IccCardApplicationStatus.java
+++ b/src/java/com/android/internal/telephony/uicc/IccCardApplicationStatus.java
@@ -155,7 +155,7 @@
     // null terminated string
     public String         app_label;
     // applicable to USIM and CSIM
-    public int            pin1_replaced;
+    public boolean        pin1_replaced;
     public PinState       pin1;
     public PinState       pin2;
 
diff --git a/src/java/com/android/internal/telephony/uicc/PinStorage.java b/src/java/com/android/internal/telephony/uicc/PinStorage.java
index 96cb8fc..2885124 100644
--- a/src/java/com/android/internal/telephony/uicc/PinStorage.java
+++ b/src/java/com/android/internal/telephony/uicc/PinStorage.java
@@ -47,7 +47,6 @@
 import android.os.Handler;
 import android.os.Message;
 import android.os.PersistableBundle;
-import android.os.WorkSource;
 import android.provider.Settings;
 import android.security.keystore.KeyGenParameterSpec;
 import android.telephony.CarrierConfigManager;
@@ -215,8 +214,10 @@
     }
 
     /** Store the {@code pin} for the {@code slotId}. */
-    public synchronized void storePin(String pin, int slotId, String iccId) {
-        if (!validatePin(pin) || !validateIccid(iccId) || !validateSlotId(slotId)) {
+    public synchronized void storePin(String pin, int slotId) {
+        String iccid = getIccid(slotId);
+
+        if (!validatePin(pin) || !validateIccid(iccid) || !validateSlotId(slotId)) {
             // We are unable to store the PIN. At least clear the old one, if present.
             loge("storePin[%d] - Invalid PIN, slotId or ICCID", slotId);
             clearPin(slotId);
@@ -230,7 +231,7 @@
         logd("storePin[%d]", slotId);
 
         StoredPin storedPin = new StoredPin();
-        storedPin.iccid = iccId;
+        storedPin.iccid = iccid;
         storedPin.pin = pin;
         storedPin.slotId = slotId;
         storedPin.status = PinStatus.AVAILABLE;
@@ -269,7 +270,7 @@
                 savePinInformation(slotId, null);
                 TelephonyStatsLog.write(PIN_STORAGE_EVENT,
                         PIN_STORAGE_EVENT__EVENT__PIN_VERIFICATION_SKIPPED_SIM_CARD_MISMATCH,
-                        /* number_of_pins= */ 1, /* package_name= */ "");
+                        /* number_of_pins= */ 1);
             } else if (storedPin.status == PinStatus.VERIFICATION_READY) {
                 logd("getPin[%d] - Found PIN ready for verification", slotId);
                 // Move the state to AVAILABLE, so that it cannot be retrieved again.
@@ -290,7 +291,7 @@
      * @return The result of the reboot preparation.
      */
     @TelephonyManager.PrepareUnattendedRebootResult
-    public synchronized int prepareUnattendedReboot(WorkSource workSource) {
+    public synchronized int prepareUnattendedReboot() {
         // Unattended reboot should never occur before the device is unlocked.
         if (mIsDeviceLocked) {
             loge("prepareUnattendedReboot - Device is locked");
@@ -340,18 +341,14 @@
         }
 
         // Generate metrics
-        String callingPackage = workSource == null || workSource.size() == 0
-                                    ? "" : workSource.getPackageName(0);
         if (result == TelephonyManager.PREPARE_UNATTENDED_REBOOT_SUCCESS) {
             logd("prepareUnattendedReboot - Stored %d PINs", storedCount);
             TelephonyStatsLog.write(PIN_STORAGE_EVENT,
-                    PIN_STORAGE_EVENT__EVENT__PIN_STORED_FOR_VERIFICATION, storedCount,
-                    callingPackage);
+                    PIN_STORAGE_EVENT__EVENT__PIN_STORED_FOR_VERIFICATION, storedCount);
         } else if (result == TelephonyManager.PREPARE_UNATTENDED_REBOOT_PIN_REQUIRED) {
             logd("prepareUnattendedReboot - Required %d PINs after reboot", notAvailableCount);
             TelephonyStatsLog.write(PIN_STORAGE_EVENT,
-                    PIN_STORAGE_EVENT__EVENT__PIN_REQUIRED_AFTER_REBOOT, notAvailableCount,
-                    callingPackage);
+                    PIN_STORAGE_EVENT__EVENT__PIN_REQUIRED_AFTER_REBOOT, notAvailableCount);
         }
 
         // Save number of PINs to generate metrics after reboot
@@ -458,7 +455,7 @@
         if (prevCachedPinCount > verificationReadyCount) {
             TelephonyStatsLog.write(PIN_STORAGE_EVENT,
                     PIN_STORAGE_EVENT__EVENT__PIN_COUNT_NOT_MATCHING_AFTER_REBOOT,
-                    prevCachedPinCount - verificationReadyCount, /* package_name= */ "");
+                    prevCachedPinCount - verificationReadyCount);
         }
     }
 
@@ -510,8 +507,7 @@
         // Write metrics about number of discarded PINs
         if (discardedPin > 0) {
             TelephonyStatsLog.write(PIN_STORAGE_EVENT,
-                    PIN_STORAGE_EVENT__EVENT__CACHED_PIN_DISCARDED, discardedPin,
-                    /* package_name= */ "");
+                    PIN_STORAGE_EVENT__EVENT__CACHED_PIN_DISCARDED, discardedPin);
         }
     }
 
@@ -580,7 +576,7 @@
                 success
                     ? PIN_STORAGE_EVENT__EVENT__PIN_VERIFICATION_SUCCESS
                     : PIN_STORAGE_EVENT__EVENT__PIN_VERIFICATION_FAILURE,
-                /* number_of_pins= */ 1, /* package_name= */ "");
+                /* number_of_pins= */ 1);
     }
 
     @Override
@@ -935,7 +931,7 @@
     }
 
     /** Returns the ICCID of the SIM card for the given {@code slotId}. */
-    public String getIccid(int slotId) {
+    private String getIccid(int slotId) {
         Phone phone = PhoneFactory.getPhone(slotId);
         return phone != null ? phone.getFullIccSerialNumber() : "";
     }
@@ -1156,7 +1152,7 @@
         } catch (Exception e) {
             loge("Encrypt exception", e);
             TelephonyStatsLog.write(PIN_STORAGE_EVENT,
-                    PIN_STORAGE_EVENT__EVENT__PIN_ENCRYPTION_ERROR, 1, /* package_name= */ "");
+                    PIN_STORAGE_EVENT__EVENT__PIN_ENCRYPTION_ERROR, 1);
         }
         return new byte[0];
     }
@@ -1181,7 +1177,7 @@
         } catch (Exception e) {
             loge("Decrypt exception", e);
             TelephonyStatsLog.write(PIN_STORAGE_EVENT,
-                    PIN_STORAGE_EVENT__EVENT__PIN_DECRYPTION_ERROR, 1, /* package_name= */ "");
+                    PIN_STORAGE_EVENT__EVENT__PIN_DECRYPTION_ERROR, 1);
         }
         return new byte[0];
     }
diff --git a/src/java/com/android/internal/telephony/uicc/SIMRecords.java b/src/java/com/android/internal/telephony/uicc/SIMRecords.java
index aed860b..9958bfb 100644
--- a/src/java/com/android/internal/telephony/uicc/SIMRecords.java
+++ b/src/java/com/android/internal/telephony/uicc/SIMRecords.java
@@ -308,7 +308,9 @@
     @Override
     public void setMsisdnNumber(String alphaTag, String number,
             Message onComplete) {
-
+        if (mDestroyed.get()) {
+            return;
+        }
         // If the SIM card is locked by PIN, we will set EF_MSISDN fail.
         // In that case, msisdn and msisdnTag should not be update.
         mNewMsisdn = number;
@@ -412,6 +414,9 @@
     @Override
     public void
     setVoiceMessageWaiting(int line, int countWaiting) {
+        if (mDestroyed.get()) {
+            return;
+        }
         if (line != 1) {
             // only profile 1 is supported
             return;
@@ -515,7 +520,9 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     @Override
     public void setVoiceCallForwardingFlag(int line, boolean enable, String dialNumber) {
-
+        if (mDestroyed.get()) {
+            return;
+        }
         if (line != 1) return; // only line 1 is supported
 
         mCallForwardingStatus = enable ? CALL_FORWARDING_STATUS_ENABLED :
@@ -1514,6 +1521,9 @@
     //***** Private methods
 
     private void setVoiceMailByCountry (String spn) {
+        if (mDestroyed.get()) {
+            return;
+        }
         if (mVmConfig.containsCarrier(spn)) {
             mIsVoiceMailFixed = true;
             mVoiceMailNum = mVmConfig.getVoiceMailNumber(spn);
diff --git a/src/java/com/android/internal/telephony/uicc/SimPhonebookRecord.java b/src/java/com/android/internal/telephony/uicc/SimPhonebookRecord.java
index c6c7d6d..82caded 100644
--- a/src/java/com/android/internal/telephony/uicc/SimPhonebookRecord.java
+++ b/src/java/com/android/internal/telephony/uicc/SimPhonebookRecord.java
@@ -16,15 +16,12 @@
 
 package com.android.internal.telephony.uicc;
 
-import android.hardware.radio.V1_6.PhonebookRecordInfo;
-import android.text.TextUtils;
 import android.telephony.PhoneNumberUtils;
-import android.telephony.Rlog;
+import android.text.TextUtils;
 
 import com.android.internal.telephony.util.ArrayUtils;
 
 import java.util.Arrays;
-import java.util.List;
 
 /**
  * Represents a Phonebook entry from the SIM.
@@ -39,7 +36,6 @@
     private String[] mEmails;
     private String[] mAdditionalNumbers;
 
-    // Instance methods
     public SimPhonebookRecord (int recordIndex, String alphaTag, String number,
                String[] emails, String[] adNumbers) {
         mRecordIndex = recordIndex;
@@ -49,42 +45,11 @@
         if (adNumbers != null) {
             mAdditionalNumbers = new String[adNumbers.length];
             for (int i = 0 ; i < adNumbers.length; i++) {
-                mAdditionalNumbers[i] =
-                        convertRecordFormatToNumber(adNumbers[i]);
+                mAdditionalNumbers[i] = convertRecordFormatToNumber(adNumbers[i]);
             }
         }
     }
 
-    public SimPhonebookRecord(PhonebookRecordInfo recInfo) {
-        mRecordIndex = recInfo.recordId;
-        mAlphaTag = recInfo.name;
-        mNumber = recInfo.number;
-        mEmails = recInfo.emails == null ? null
-                : recInfo.emails.toArray(new String[recInfo.emails.size()]);
-        mAdditionalNumbers = recInfo.additionalNumbers == null ? null
-                : recInfo.additionalNumbers.toArray(
-                        new String[recInfo.additionalNumbers.size()]);
-    }
-
-    public SimPhonebookRecord() {}
-
-    public PhonebookRecordInfo toPhonebookRecordInfo() {
-        PhonebookRecordInfo pbRecordInfo = new PhonebookRecordInfo();
-        pbRecordInfo.recordId = mRecordIndex;
-        pbRecordInfo.name = convertNullToEmptyString(mAlphaTag);
-        pbRecordInfo.number = convertNullToEmptyString(convertNumberToRecordFormat(mNumber));
-        if (mEmails != null) {
-            for (String email : mEmails) {
-                pbRecordInfo.emails.add(email);
-            }
-        }
-        if (mAdditionalNumbers != null) {
-            for (String addNum : mAdditionalNumbers) {
-                pbRecordInfo.additionalNumbers.add(convertNumberToRecordFormat(addNum));
-            }
-        }
-        return pbRecordInfo;
-    }
     public int getRecordIndex() {
         return mRecordIndex;
     }
@@ -114,19 +79,6 @@
                 .replace( '?', PhoneNumberUtils.WILD );
     }
 
-    /**
-     * convert the GSM pause/wild/wait character to the phone number in the SIM pb record format
-     */
-    private static String convertNumberToRecordFormat(String input) {
-        return input == null ? null : input.replace(PhoneNumberUtils.WAIT, 'e')
-                .replace(PhoneNumberUtils.PAUSE, 'T')
-                .replace(PhoneNumberUtils.WILD, '?');
-    }
-
-    private static String convertNullToEmptyString(String string) {
-        return string != null ? string : "";
-    }
-
     public boolean isEmpty() {
         return TextUtils.isEmpty(mAlphaTag)
                 && TextUtils.isEmpty(mNumber)
@@ -158,17 +110,8 @@
         private String[] mAdditionalNumbers;
 
         public SimPhonebookRecord build() {
-            SimPhonebookRecord record = new SimPhonebookRecord();
-            record.mAlphaTag = mAlphaTag;
-            record.mRecordIndex = mRecordIndex;
-            record.mNumber = mNumber;
-            if (mEmails != null) {
-                record.mEmails = mEmails;
-            }
-            if (mAdditionalNumbers != null) {
-                record.mAdditionalNumbers = mAdditionalNumbers;
-            }
-            return record;
+            return new SimPhonebookRecord(mRecordIndex, mAlphaTag, mNumber, mEmails,
+                    mAdditionalNumbers);
         }
 
         public Builder setRecordIndex(int index) {
diff --git a/src/java/com/android/internal/telephony/uicc/UiccCardApplication.java b/src/java/com/android/internal/telephony/uicc/UiccCardApplication.java
index e876efd..3839610 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccCardApplication.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccCardApplication.java
@@ -113,7 +113,7 @@
         mPersoSubState = as.perso_substate;
         mAid = as.aid;
         mAppLabel = as.app_label;
-        mPin1Replaced = (as.pin1_replaced != 0);
+        mPin1Replaced = as.pin1_replaced;
         mPin1State = as.pin1;
         mPin2State = as.pin2;
         mIgnoreApp = false;
@@ -151,7 +151,7 @@
             mPersoSubState = as.perso_substate;
             mAid = as.aid;
             mAppLabel = as.app_label;
-            mPin1Replaced = (as.pin1_replaced != 0);
+            mPin1Replaced = as.pin1_replaced;
             mPin1State = as.pin1;
             mPin2State = as.pin2;
 
diff --git a/src/java/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRules.java b/src/java/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRules.java
index 0d905fd..72fb70b 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRules.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRules.java
@@ -381,10 +381,12 @@
             PackageManager packageManager, int uid) {
         String[] packages = packageManager.getPackagesForUid(uid);
 
-        for (String pkg : packages) {
-            int accessStatus = getCarrierPrivilegeStatus(packageManager, pkg);
-            if (accessStatus != TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS) {
-                return accessStatus;
+        if (packages != null) {
+            for (String pkg : packages) {
+                int accessStatus = getCarrierPrivilegeStatus(packageManager, pkg);
+                if (accessStatus != TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS) {
+                    return accessStatus;
+                }
             }
         }
         return TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS;
diff --git a/src/java/com/android/internal/telephony/uicc/UiccController.java b/src/java/com/android/internal/telephony/uicc/UiccController.java
index a2ab3bd..a7975ac 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccController.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccController.java
@@ -1169,12 +1169,6 @@
             CarrierConfigManager configManager = (CarrierConfigManager)
                     mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
             configManager.updateConfigForPhoneId(index, IccCardConstants.INTENT_VALUE_ICC_UNKNOWN);
-
-            boolean requirePowerOffOnSimRefreshReset = mContext.getResources().getBoolean(
-                    com.android.internal.R.bool.config_requireRadioPowerOffOnSimRefreshReset);
-            if (requirePowerOffOnSimRefreshReset) {
-                mCis[index].setRadioPower(false, null);
-            }
         }
 
         // The card status could have changed. Get the latest state.
diff --git a/src/java/com/android/internal/telephony/uicc/UiccProfile.java b/src/java/com/android/internal/telephony/uicc/UiccProfile.java
index bea2672..0db661b 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccProfile.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccProfile.java
@@ -528,6 +528,9 @@
         }
     }
 
+    /**
+     * ICC availability/state changed. Update corresponding fields and external state if needed.
+     */
     private void updateIccAvailability(boolean allAppsChanged) {
         synchronized (mLock) {
             UiccCardApplication newApp;
@@ -1771,21 +1774,6 @@
     }
 
     /**
-     * Make sure the iccid in SIM record matches the current active subId. If not, return false.
-     * When SIM switching in eSIM is happening, there are rare cases that setOperatorBrandOverride
-     * is called on old subId while new iccid is already loaded on SIM record. For those cases
-     * setOperatorBrandOverride would apply to the wrong (new) iccid. This check is to avoid it.
-     */
-    private boolean checkSubIdAndIccIdMatch(String iccid) {
-        if (TextUtils.isEmpty(iccid)) return false;
-        SubscriptionInfo subInfo = SubscriptionController.getInstance()
-                .getActiveSubscriptionInfoForSimSlotIndex(
-                        getPhoneId(), mContext.getOpPackageName(), null);
-        return subInfo != null && IccUtils.stripTrailingFs(subInfo.getIccId()).equals(
-                IccUtils.stripTrailingFs(iccid));
-    }
-
-    /**
      * Sets the overridden operator brand.
      */
     public boolean setOperatorBrandOverride(String brand) {
@@ -1796,7 +1784,7 @@
         if (TextUtils.isEmpty(iccId)) {
             return false;
         }
-        if (!checkSubIdAndIccIdMatch(iccId)) {
+        if (!SubscriptionController.getInstance().checkPhoneIdAndIccIdMatch(getPhoneId(), iccId)) {
             loge("iccId doesn't match current active subId.");
             return false;
         }
diff --git a/tests/telephonytests/Android.bp b/tests/telephonytests/Android.bp
index 5b2e904..5345b42 100644
--- a/tests/telephonytests/Android.bp
+++ b/tests/telephonytests/Android.bp
@@ -10,6 +10,8 @@
 
 android_test {
     name: "FrameworksTelephonyTests",
+    // For access hidden connectivity methods in tests
+    defaults: ["framework-connectivity-test-defaults"],
 
     srcs: ["**/*.java"],
 
diff --git a/tests/telephonytests/AndroidManifest.xml b/tests/telephonytests/AndroidManifest.xml
index e3ef48d..dc36777 100644
--- a/tests/telephonytests/AndroidManifest.xml
+++ b/tests/telephonytests/AndroidManifest.xml
@@ -40,4 +40,5 @@
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
     <uses-permission android:name="android.permission.BIND_TELEPHONY_NETWORK_SERVICE"/>
     <uses-permission android:name="android.permission.BIND_TELEPHONY_DATA_SERVICE"/>
+    <uses-permission android:name="android.permission.BATTERY_STATS"/>
 </manifest>
diff --git a/tests/telephonytests/src/android/telephony/ims/ImsFeatureTest.java b/tests/telephonytests/src/android/telephony/ims/ImsFeatureTest.java
index 319d200..80167d6 100644
--- a/tests/telephonytests/src/android/telephony/ims/ImsFeatureTest.java
+++ b/tests/telephonytests/src/android/telephony/ims/ImsFeatureTest.java
@@ -248,6 +248,8 @@
         // add some capabilities
         request.addCapabilitiesToEnableForTech(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE,
                 ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN);
+        request.addCapabilitiesToEnableForTech(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE,
+                ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM);
         request.addCapabilitiesToEnableForTech(
                 MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VIDEO
                         | MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE,
diff --git a/tests/telephonytests/src/android/telephony/ims/SipTransportImplBaseTest.java b/tests/telephonytests/src/android/telephony/ims/SipTransportImplBaseTest.java
new file mode 100644
index 0000000..11b1d22
--- /dev/null
+++ b/tests/telephonytests/src/android/telephony/ims/SipTransportImplBaseTest.java
@@ -0,0 +1,171 @@
+/*
+ * 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 android.telephony.ims;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyList;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.os.IBinder;
+import android.telephony.ims.aidl.ISipDelegate;
+import android.telephony.ims.aidl.ISipDelegateMessageCallback;
+import android.telephony.ims.aidl.ISipDelegateStateCallback;
+import android.telephony.ims.aidl.ISipTransport;
+import android.telephony.ims.stub.SipDelegate;
+import android.telephony.ims.stub.SipTransportImplBase;
+import android.util.ArraySet;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+
+import java.util.Collections;
+import java.util.Set;
+import java.util.concurrent.Executor;
+
+@RunWith(AndroidJUnit4.class)
+public class SipTransportImplBaseTest {
+
+    private static final int TEST_SUB_ID = 1;
+
+    private static class TestSipTransport extends SipTransportImplBase {
+
+        private static class SipDelegateContainer {
+            public final int subId;
+            public final DelegateRequest delegateRequest;
+            public final DelegateStateCallback delegateStateCallback;
+            public final DelegateMessageCallback delegateMessageCallback;
+            public final SipDelegate sipDelegate;
+
+            SipDelegateContainer(int subId, DelegateRequest request,
+                    DelegateStateCallback dc, DelegateMessageCallback mc, SipDelegate delegate) {
+                this.subId = subId;
+                delegateRequest = request;
+                delegateStateCallback = dc;
+                delegateMessageCallback = mc;
+                sipDelegate = delegate;
+            }
+        }
+
+        private final Set<SipDelegateContainer> mDelegates = new ArraySet<>();
+
+        TestSipTransport(Executor executor) {
+            super(executor);
+        }
+
+        @Override
+        public void createSipDelegate(int subscriptionId, DelegateRequest request,
+                DelegateStateCallback dc, DelegateMessageCallback mc) {
+            SipDelegate mockDelegate = mock(SipDelegate.class);
+            SipDelegateContainer container = new SipDelegateContainer(subscriptionId, request, dc,
+                    mc, mockDelegate);
+            mDelegates.add(container);
+            dc.onCreated(mockDelegate, Collections.emptySet());
+        }
+
+        @Override
+        public void destroySipDelegate(SipDelegate delegate, int reason) {
+            mDelegates.removeIf(candidate -> {
+                if (delegate.equals(candidate.sipDelegate)) {
+                    candidate.delegateStateCallback.onDestroyed(reason);
+                    return true;
+                }
+                return false;
+            });
+        }
+
+        public boolean isTrackedDelegateSetEmpty() {
+            return mDelegates.isEmpty();
+        }
+    }
+
+    @Test
+    public void createDestroyDelegate() throws Exception {
+        // Set up the executor to simply run inline
+        TestSipTransport t = new TestSipTransport(Runnable::run);
+
+        ISipDelegateStateCallback stateCb = mock(ISipDelegateStateCallback.class);
+        IBinder stateBinder = mock(IBinder.class);
+        doReturn(stateBinder).when(stateCb).asBinder();
+        ISipDelegateMessageCallback messageCb = mock(ISipDelegateMessageCallback.class);
+
+        ISipDelegate delegate = createSipDelegate(t, stateCb, messageCb);
+        assertFalse(t.isTrackedDelegateSetEmpty());
+        ArgumentCaptor<IBinder.DeathRecipient> captor =
+                ArgumentCaptor.forClass(IBinder.DeathRecipient.class);
+        verify(stateBinder).linkToDeath(captor.capture(), anyInt());
+        assertNotNull(captor.getValue());
+
+        destroySipDelegate(t, delegate,
+                SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_REQUESTED_BY_APP);
+        verify(stateBinder).unlinkToDeath(eq(captor.getValue()), anyInt());
+        verify(stateCb).onDestroyed(
+                eq(SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_REQUESTED_BY_APP));
+    }
+
+
+    @Test
+    public void testPhoneProcessCrash() throws Exception {
+        // Set up the executor to simply run inline
+        TestSipTransport t = new TestSipTransport(Runnable::run);
+
+        ISipDelegateStateCallback stateCb = mock(ISipDelegateStateCallback.class);
+        IBinder stateBinder = mock(IBinder.class);
+        doReturn(stateBinder).when(stateCb).asBinder();
+        ISipDelegateMessageCallback messageCb = mock(ISipDelegateMessageCallback.class);
+
+        createSipDelegate(t, stateCb, messageCb);
+        assertFalse(t.isTrackedDelegateSetEmpty());
+        ArgumentCaptor<IBinder.DeathRecipient> captor =
+                ArgumentCaptor.forClass(IBinder.DeathRecipient.class);
+        verify(stateBinder).linkToDeath(captor.capture(), anyInt());
+        assertNotNull(captor.getValue());
+        IBinder.DeathRecipient recipient = captor.getValue();
+
+        // simulate phone process crash
+        recipient.binderDied(stateBinder);
+        verify(stateCb).onDestroyed(SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_SERVICE_DEAD);
+        assertTrue(t.isTrackedDelegateSetEmpty());
+    }
+
+    private ISipDelegate createSipDelegate(TestSipTransport transport,
+            ISipDelegateStateCallback stateCb,
+            ISipDelegateMessageCallback messageCb) throws Exception {
+        ISipTransport transportBinder = transport.getBinder();
+        transportBinder.createSipDelegate(TEST_SUB_ID, new DelegateRequest(Collections.emptySet()),
+                stateCb, messageCb);
+        ArgumentCaptor<ISipDelegate> captor = ArgumentCaptor.forClass(ISipDelegate.class);
+        verify(stateCb).onCreated(captor.capture(), anyList());
+        assertNotNull(captor.getValue());
+        return captor.getValue();
+    }
+
+    private void destroySipDelegate(TestSipTransport transport, ISipDelegate delegate,
+            int reason) throws Exception {
+        ISipTransport transportBinder = transport.getBinder();
+        transportBinder.destroySipDelegate(delegate, reason);
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CarrierKeyDownloadMgrTest.java b/tests/telephonytests/src/com/android/internal/telephony/CarrierKeyDownloadMgrTest.java
index ea26867..4cd5d68 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CarrierKeyDownloadMgrTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CarrierKeyDownloadMgrTest.java
@@ -34,6 +34,7 @@
 import android.os.PersistableBundle;
 import android.telephony.CarrierConfigManager;
 import android.telephony.ImsiEncryptionInfo;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
@@ -328,14 +329,14 @@
     public void testAlarmRenewal() {
         CarrierConfigManager carrierConfigManager = (CarrierConfigManager)
                 mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
-        int slotId = mPhone.getPhoneId();
-        PersistableBundle bundle = carrierConfigManager.getConfigForSubId(slotId);
+        int slotIndex = SubscriptionManager.getSlotIndex(mPhone.getSubId());
+        PersistableBundle bundle = carrierConfigManager.getConfigForSubId(slotIndex);
         bundle.putInt(CarrierConfigManager.IMSI_KEY_AVAILABILITY_INT, 3);
         bundle.putString(CarrierConfigManager.IMSI_KEY_DOWNLOAD_URL_STRING, mURL);
 
         when(mTelephonyManager.getSimOperator(anyInt())).thenReturn("310260");
-        Intent mIntent = new Intent("com.android.internal.telephony.carrier_key_download_alarm"
-                + slotId);
+        Intent mIntent = new Intent("com.android.internal.telephony.carrier_key_download_alarm");
+        mIntent.putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, slotIndex);
         mContext.sendBroadcast(mIntent);
         processAllMessages();
         assertEquals("310260", mCarrierKeyDM.mMccMncForDownload);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CellIdentityLteTest.java b/tests/telephonytests/src/com/android/internal/telephony/CellIdentityLteTest.java
index 77bea60..3eb8d21 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CellIdentityLteTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CellIdentityLteTest.java
@@ -22,7 +22,10 @@
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
+import java.util.stream.Collectors;
 
 /** Unit tests for {@link CellIdentityLte}. */
 
@@ -253,4 +256,15 @@
         CellIdentityLte newCi = CellIdentityLte.CREATOR.createFromParcel(p);
         assertEquals(ci, newCi);
     }
+
+    @SmallTest
+    public void testBands() {
+        android.hardware.radio.V1_5.CellIdentityLte cid =
+                new android.hardware.radio.V1_5.CellIdentityLte();
+        cid.bands = Arrays.stream(BANDS).boxed().collect(Collectors.toCollection(ArrayList::new));
+
+        CellIdentityLte cellIdentityLte = RILUtils.convertHalCellIdentityLte(cid);
+        assertTrue(Arrays.equals(cellIdentityLte.getBands(), BANDS));
+
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CellSignalStrengthNrTest.java b/tests/telephonytests/src/com/android/internal/telephony/CellSignalStrengthNrTest.java
index a294471..02787df 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CellSignalStrengthNrTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CellSignalStrengthNrTest.java
@@ -108,7 +108,7 @@
         nrSignalStrength.base.ssSinr = SSSINR;
 
         // THEN the get method should return the correct value
-        CellSignalStrengthNr css = new CellSignalStrengthNr(nrSignalStrength);
+        CellSignalStrengthNr css = RILUtils.convertHalNrSignalStrength(nrSignalStrength);
         assertThat(css.getCsiRsrp()).isEqualTo(CSIRSRP);
         assertThat(css.getCsiRsrq()).isEqualTo(CSIRSRQ);
         assertThat(css.getCsiSinr()).isEqualTo(CSISINR);
@@ -134,7 +134,7 @@
         nrSignalStrength.base.ssSinr = CellInfo.UNAVAILABLE;
 
         // THEN the get method should return unavailable value
-        CellSignalStrengthNr css = new CellSignalStrengthNr(nrSignalStrength);
+        CellSignalStrengthNr css = RILUtils.convertHalNrSignalStrength(nrSignalStrength);
         assertThat(css.getCsiRsrp()).isEqualTo(CellInfo.UNAVAILABLE);
         assertThat(css.getCsiRsrq()).isEqualTo(CellInfo.UNAVAILABLE);
         assertThat(css.getCsiSinr()).isEqualTo(CellInfo.UNAVAILABLE);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java b/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java
index b34807d..f04e69f 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java
@@ -33,6 +33,7 @@
 import android.app.DownloadManager;
 import android.app.KeyguardManager;
 import android.app.NotificationManager;
+import android.app.UiModeManager;
 import android.app.usage.UsageStatsManager;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -82,6 +83,7 @@
 import android.test.mock.MockContentProvider;
 import android.test.mock.MockContentResolver;
 import android.test.mock.MockContext;
+import android.util.DisplayMetrics;
 import android.util.Log;
 
 import com.google.common.collect.ArrayListMultimap;
@@ -99,6 +101,7 @@
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * Controls a test {@link Context} as would be provided by the Android framework to an
@@ -195,6 +198,9 @@
                 Intent serviceIntent,
                 ServiceConnection connection,
                 int flags) {
+            if (mMockBindingFailureForPackage.contains(serviceIntent.getPackage())) {
+                return false;
+            }
             if (mServiceByServiceConnection.containsKey(connection)) {
                 throw new RuntimeException("ServiceConnection already bound: " + connection);
             }
@@ -216,10 +222,11 @@
         public void unbindService(
                 ServiceConnection connection) {
             IInterface service = mServiceByServiceConnection.remove(connection);
-            if (service == null) {
-                throw new RuntimeException("ServiceConnection not found: " + connection);
+            if (service != null) {
+                connection.onServiceDisconnected(mComponentNameByService.get(service));
+            } else {
+                logd("unbindService: ServiceConnection not found: " + connection);
             }
-            connection.onServiceDisconnected(mComponentNameByService.get(service));
         }
 
         @Override
@@ -271,6 +278,7 @@
                 case Context.DISPLAY_SERVICE:
                 case Context.POWER_SERVICE:
                 case Context.PERMISSION_SERVICE:
+                case Context.LEGACY_PERMISSION_SERVICE:
                     // These are final classes so cannot be mocked,
                     // return real services.
                     return TestApplication.getAppContext().getSystemService(name);
@@ -299,18 +307,20 @@
                 return Context.POWER_WHITELIST_MANAGER;
             } else if (serviceClass == SystemConfigManager.class) {
                 return Context.SYSTEM_CONFIG_SERVICE;
-            } else if (serviceClass == CarrierConfigManager.class){
-                return Context.CARRIER_CONFIG_SERVICE;
             } else if (serviceClass == ActivityManager.class) {
                 return Context.ACTIVITY_SERVICE;
+            } else if (serviceClass == LocationManager.class) {
+                return Context.LOCATION_SERVICE;
+            } else if (serviceClass == CarrierConfigManager.class) {
+                return Context.CARRIER_CONFIG_SERVICE;
             } else if (serviceClass == TelephonyManager.class) {
                 return Context.TELEPHONY_SERVICE;
+            } else if (serviceClass == UiModeManager.class) {
+                return Context.UI_MODE_SERVICE;
             } else if (serviceClass == KeyguardManager.class) {
                 return Context.KEYGUARD_SERVICE;
             } else if (serviceClass == VcnManager.class) {
                 return Context.VCN_MANAGEMENT_SERVICE;
-            } else if (serviceClass == LocationManager.class) {
-                return Context.LOCATION_SERVICE;
             }
             return super.getSystemServiceName(serviceClass);
         }
@@ -466,6 +476,12 @@
         }
 
         @Override
+        public void sendBroadcastMultiplePermissions(Intent intent,
+                String[] includePermissions, String[] excludePermissions) {
+            sendBroadcast(intent);
+        }
+
+        @Override
         public Context createContextAsUser(UserHandle user, int flags) {
             return this;
         }
@@ -613,6 +629,7 @@
     private final Map<ComponentName, IntentFilter> mIntentFilterByComponentName = new HashMap<>();
     private final Map<IInterface, ComponentName> mComponentNameByService =
             new HashMap<IInterface, ComponentName>();
+    private final Set<String> mMockBindingFailureForPackage = new HashSet();
     private final Map<ServiceConnection, IInterface> mServiceByServiceConnection =
             new HashMap<ServiceConnection, IInterface>();
     private final Multimap<String, BroadcastReceiver> mBroadcastReceiversByAction =
@@ -629,7 +646,6 @@
     // The application context is the most important object this class provides to the system
     // under test.
     private final Context mContext = spy(new FakeContext());
-
     // We then create a spy on the application context allowing standard Mockito-style
     // when(...) logic to be used to add specific little responses where needed.
 
@@ -657,13 +673,14 @@
         mock(TelephonyRegistryManager.class);
     private final SystemConfigManager mSystemConfigManager = mock(SystemConfigManager.class);
     private final PowerWhitelistManager mPowerWhitelistManager = mock(PowerWhitelistManager.class);
+    private final LocationManager mLocationManager = mock(LocationManager.class);
     private final KeyguardManager mKeyguardManager = mock(KeyguardManager.class);
     private final VcnManager mVcnManager = mock(VcnManager.class);
-    private final LocationManager mLocationManager = mock(LocationManager.class);
 
     private final ContentProvider mContentProvider = spy(new FakeContentProvider());
 
     private final Configuration mConfiguration = new Configuration();
+    private final DisplayMetrics mDisplayMetrics = new DisplayMetrics();
     private final SharedPreferences mSharedPreferences = PreferenceManager
             .getDefaultSharedPreferences(TestApplication.getAppContext());
     private final MockContentResolver mContentResolver = new MockContentResolver();
@@ -718,6 +735,8 @@
         mConfiguration.locale = Locale.US;
         doReturn(mConfiguration).when(mResources).getConfiguration();
 
+        mDisplayMetrics.density = 2.25f;
+        doReturn(mDisplayMetrics).when(mResources).getDisplayMetrics();
         mContentResolver.addProvider(Settings.AUTHORITY, mContentProvider);
         // Settings caches the provider after first get/set call, this is needed to make sure
         // Settings is using mContentProvider as the cached provider across all tests.
@@ -778,6 +797,10 @@
         mComponentNameByService.put(service, name);
     }
 
+    public void mockBindingFailureForPackage(String packageName) {
+        mMockBindingFailureForPackage.add(packageName);
+    }
+
     private List<ResolveInfo> doQueryIntentServices(Intent intent, int flags) {
         List<ResolveInfo> result = new ArrayList<ResolveInfo>();
         for (ComponentName componentName : mComponentNamesByAction.get(intent.getAction())) {
diff --git a/tests/telephonytests/src/com/android/internal/telephony/DeviceStateMonitorTest.java b/tests/telephonytests/src/com/android/internal/telephony/DeviceStateMonitorTest.java
index 13e89e1..aa52e71 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/DeviceStateMonitorTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/DeviceStateMonitorTest.java
@@ -25,6 +25,7 @@
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Matchers.nullable;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
@@ -32,6 +33,8 @@
 import static java.util.Arrays.asList;
 
 import android.annotation.IntDef;
+import android.app.UiModeManager;
+import android.content.Context;
 import android.content.Intent;
 import android.hardware.radio.V1_5.IndicationFilter;
 import android.net.ConnectivityManager;
@@ -47,6 +50,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -58,7 +62,7 @@
 public class DeviceStateMonitorTest extends TelephonyTest {
     private static final int INDICATION_FILTERS_MINIMUM = IndicationFilter.REGISTRATION_FAILURE;
 
-    // All implemented indiation filters set so far
+    // All implemented indication filters set so far
     // which is a subset of IndicationFilter.ALL
     private static final int INDICATION_FILTERS_ALL =
             IndicationFilter.SIGNAL_STRENGTH
@@ -89,9 +93,10 @@
     @Retention(RetentionPolicy.SOURCE)
     private @interface StateType {}
 
-    // Keep the same value as correspoinding event
+    // Keep the same value as corresponding event
     // See state2Event() for detail
     private static final int STATE_TYPE_RIL_CONNECTED = 0;
+    // EVENT_UPDATE_NODE_CHANGED is not here, it will be removed in aosp soon
     private static final int STATE_TYPE_SCREEN = 2;
     private static final int STATE_TYPE_POWER_SAVE_MODE = 3;
     private static final int STATE_TYPE_CHARGING = 4;
@@ -99,8 +104,6 @@
     private static final int STATE_TYPE_RADIO_AVAILABLE = 6;
     private static final int STATE_TYPE_WIFI_CONNECTED = 7;
     private static final int STATE_TYPE_ALWAYS_SIGNAL_STRENGTH_REPORTED = 8;
-    private static final int STATE_TYPE_RADIO_ON = 9;
-    private static final int STATE_TYPE_RADIO_OFF_OR_NOT_AVAILABLE = 10;
 
     /** @hide */
     @IntDef(prefix = {"STATE_"}, value = {
@@ -129,7 +132,10 @@
                 STATE_TYPE_CHARGING, STATE_TYPE_SCREEN, STATE_TYPE_TETHERING}
     );
 
+    @Mock
+    UiModeManager mUiModeManager;
     private DeviceStateMonitor mDSM;
+
     // Given a stateType, return the event type that can change the state
     private int state2Event(@StateType int stateType) {
         // As long as we keep the same value, we can directly return the stateType
@@ -156,12 +162,15 @@
     @Before
     public void setUp() throws Exception {
         super.setUp(getClass().getSimpleName());
+        mContextFixture.setSystemService(Context.UI_MODE_SERVICE, mUiModeManager);
+        // We don't even need a mock executor, we just need to not throw.
+        doReturn(null).when(mContextFixture.getTestDouble()).getMainExecutor();
         mDSM = new DeviceStateMonitor(mPhone);
 
         // Initialize with ALL states off
         updateAllStatesToOff();
 
-        // eliminate the accumuted impact on Mockito.verify()
+        // eliminate the accumulated impact on Mockito.verify()
         reset(mSimulatedCommandsVerifier);
     }
 
@@ -227,43 +236,20 @@
     public void testScreenOnOff() {
         // screen was off by default, turn it on now
         updateState(STATE_TYPE_SCREEN, STATE_ON);
+        processAllMessages();
 
         verify(mSimulatedCommandsVerifier).setUnsolResponseFilter(
                 eq(INDICATION_FILTERS_WHEN_SCREEN_ON), nullable(Message.class));
 
         // turn screen off
         updateState(STATE_TYPE_SCREEN, STATE_OFF);
+        processAllMessages();
 
         verify(mSimulatedCommandsVerifier).setUnsolResponseFilter(
                 eq(INDICATION_FILTERS_MINIMUM), nullable(Message.class));
     }
 
     @Test
-    public void testScreenOnOffwithRadioToggle() {
-        // screen was off by default, turn it on now
-        updateState(STATE_TYPE_SCREEN, STATE_ON);
-        // turn off radio
-        updateState(STATE_TYPE_RADIO_OFF_OR_NOT_AVAILABLE, /* stateValue is not used */ 0);
-
-        verify(mSimulatedCommandsVerifier)
-                .sendDeviceState(eq(LOW_DATA_EXPECTED), eq(true), nullable(Message.class));
-        reset(mSimulatedCommandsVerifier);
-
-        // turn screen off and on
-        updateState(STATE_TYPE_SCREEN, STATE_OFF);
-        updateState(STATE_TYPE_SCREEN, STATE_ON);
-
-        verify(mSimulatedCommandsVerifier, never())
-                .sendDeviceState(anyInt(), anyBoolean(), nullable(Message.class));
-
-        // turn on radio
-        updateState(STATE_TYPE_RADIO_ON, /* stateValue is not used */ 0);
-
-        verify(mSimulatedCommandsVerifier)
-                .sendDeviceState(eq(LOW_DATA_EXPECTED), eq(false), nullable(Message.class));
-    }
-
-    @Test
     public void testTethering() {
         // Turn tethering on
         Intent intent = new Intent(TetheringManager.ACTION_TETHER_STATE_CHANGED);
@@ -390,29 +376,4 @@
         updateState(STATE_TYPE_SCREEN, STATE_ON);
         verify(mSimulatedCommandsVerifier).getBarringInfo(nullable(Message.class));
     }
-
-    @Test
-    public void testGetBarringInfowithRadioToggle() {
-        // screen was off by default, turn it on now
-        updateState(STATE_TYPE_SCREEN, STATE_ON);
-
-        verify(mSimulatedCommandsVerifier).getBarringInfo(nullable(Message.class));
-        reset(mSimulatedCommandsVerifier);
-
-        // turn off radio
-        updateState(STATE_TYPE_RADIO_OFF_OR_NOT_AVAILABLE, /* stateValue is not used */ 0);
-
-        verify(mSimulatedCommandsVerifier, never()).getBarringInfo(nullable(Message.class));
-
-        // turn screen off and on
-        updateState(STATE_TYPE_SCREEN, STATE_OFF);
-        updateState(STATE_TYPE_SCREEN, STATE_ON);
-
-        verify(mSimulatedCommandsVerifier, never()).getBarringInfo(nullable(Message.class));
-
-        // turn on radio
-        updateState(STATE_TYPE_RADIO_ON, /* stateValue is not used */ 0);
-
-        verify(mSimulatedCommandsVerifier).getBarringInfo(nullable(Message.class));
-    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java b/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java
index 447a629..179ca0f 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java
@@ -25,6 +25,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -72,7 +73,6 @@
 
 import androidx.test.filters.FlakyTest;
 
-import com.android.ims.ImsManager;
 import com.android.internal.telephony.test.SimulatedCommands;
 import com.android.internal.telephony.test.SimulatedCommandsVerifier;
 import com.android.internal.telephony.uicc.IccCardApplicationStatus;
@@ -523,6 +523,28 @@
 
     @Test
     @SmallTest
+    public void testClirCs() {
+        mPhoneUT.mCi = mMockCi;
+        // Start out with no preference set and ensure CommandsInterface receives setClir with
+        // the default set.
+        mPhoneUT.sendEmptyMessage(Phone.EVENT_REGISTERED_TO_NETWORK);
+        processAllMessages();
+        verify(mMockCi).setCLIR(eq(CommandsInterface.CLIR_DEFAULT), any());
+        // Now set the CLIR mode explicitly
+        mPhoneUT.setOutgoingCallerIdDisplay(CommandsInterface.CLIR_SUPPRESSION, null);
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(mMockCi).setCLIR(eq(CommandsInterface.CLIR_SUPPRESSION), messageCaptor.capture());
+        Message message = messageCaptor.getValue();
+        assertNotNull(message);
+        message.obj = AsyncResult.forMessage(message);
+        // Now Call registered to network again and the CLIR mode sent should reflect the new value.
+        mPhoneUT.sendEmptyMessage(Phone.EVENT_REGISTERED_TO_NETWORK);
+        processAllMessages();
+        verify(mMockCi).setCLIR(eq(CommandsInterface.CLIR_SUPPRESSION), any());
+    }
+
+    @Test
+    @SmallTest
     public void testWpsClirActiveDialOverCs() throws Exception {
         try {
             setupForWpsCallTest();
@@ -1603,4 +1625,33 @@
         assertEquals(LinkCapacityEstimate.INVALID, lce3.getUplinkCapacityKbps());
         assertEquals(LinkCapacityEstimate.LCE_TYPE_COMBINED, lce3.getType());
     }
+
+    @Test
+    @SmallTest
+    public void testLoadAllowedNetworksFromSubscriptionDatabase_loadTheNullValue_isLoadedTrue() {
+        int subId = 1;
+        doReturn(subId).when(mSubscriptionController).getSubIdUsingPhoneId(anyInt());
+
+        doReturn(null).when(mSubscriptionController).getSubscriptionProperty(anyInt(),
+                eq(SubscriptionManager.ALLOWED_NETWORK_TYPES));
+
+        mPhoneUT.loadAllowedNetworksFromSubscriptionDatabase();
+
+        assertEquals(true,  mPhoneUT.isAllowedNetworkTypesLoadedFromDb());
+    }
+
+    @Test
+    @SmallTest
+    public void testLoadAllowedNetworksFromSubscriptionDatabase_subIdNotValid_isLoadedFalse() {
+        int subId = -1;
+        doReturn(subId).when(mSubscriptionController).getSubIdUsingPhoneId(anyInt());
+
+        when(mSubscriptionController.getSubscriptionProperty(anyInt(),
+                eq(SubscriptionManager.ALLOWED_NETWORK_TYPES))).thenReturn(null);
+
+
+        mPhoneUT.loadAllowedNetworksFromSubscriptionDatabase();
+
+        assertEquals(false, mPhoneUT.isAllowedNetworkTypesLoadedFromDb());
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/MultiSimSettingControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/MultiSimSettingControllerTest.java
index 0ec64a0..56689f4 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/MultiSimSettingControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/MultiSimSettingControllerTest.java
@@ -35,6 +35,7 @@
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
 import android.content.Intent;
@@ -423,6 +424,41 @@
 
     @Test
     @SmallTest
+    public void testSimpleDsdsFirstBoot() {
+        // at first boot default is not set
+        doReturn(-1).when(mSubControllerMock).getDefaultDataSubId();
+
+        doReturn(true).when(mPhoneMock1).isUserDataEnabled();
+        doReturn(true).when(mPhoneMock2).isUserDataEnabled();
+        // After initialization, sub 2 should have mobile data off.
+        mMultiSimSettingControllerUT.notifyAllSubscriptionLoaded();
+        mMultiSimSettingControllerUT.notifyCarrierConfigChanged(0, 1);
+        mMultiSimSettingControllerUT.notifyCarrierConfigChanged(1, 2);
+        processAllMessages();
+        verify(mDataEnabledSettingsMock1).setDataEnabled(
+                TelephonyManager.DATA_ENABLED_REASON_USER, false);
+        verify(mDataEnabledSettingsMock2).setDataEnabled(
+                TelephonyManager.DATA_ENABLED_REASON_USER, false);
+
+        // as a result of the above calls, update new values to be returned
+        doReturn(false).when(mPhoneMock1).isUserDataEnabled();
+        doReturn(false).when(mPhoneMock2).isUserDataEnabled();
+
+        Intent intent = captureBroadcastIntent();
+        assertEquals(ACTION_PRIMARY_SUBSCRIPTION_LIST_CHANGED, intent.getAction());
+        assertEquals(EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_DATA,
+                intent.getIntExtra(EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE, -1));
+
+        // Setting default data should not trigger any more setDataEnabled().
+        doReturn(2).when(mSubControllerMock).getDefaultDataSubId();
+        mMultiSimSettingControllerUT.notifyDefaultDataSubChanged();
+        processAllMessages();
+        verify(mDataEnabledSettingsMock1, times(1)).setDataEnabled(anyInt(), anyBoolean());
+        verify(mDataEnabledSettingsMock2, times(1)).setDataEnabled(anyInt(), anyBoolean());
+    }
+
+    @Test
+    @SmallTest
     public void testDsdsGrouping() {
         doReturn(2).when(mSubControllerMock).getDefaultDataSubId();
         doReturn(false).when(mPhoneMock1).isUserDataEnabled();
diff --git a/tests/telephonytests/src/com/android/internal/telephony/NetworkTypeControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/NetworkTypeControllerTest.java
index 1fc5d37..ac5facd 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/NetworkTypeControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/NetworkTypeControllerTest.java
@@ -114,6 +114,7 @@
                 mPhone).getCachedAllowedNetworkTypesBitmask();
         doReturn(false).when(mTelephonyManager).isRadioInterfaceCapabilitySupported(
                 TelephonyManager.CAPABILITY_PHYSICAL_CHANNEL_CONFIG_1_6_SUPPORTED);
+        doReturn(new int[] {0}).when(mServiceState).getCellBandwidths();
         mNetworkTypeController = new NetworkTypeController(mPhone, mDisplayInfoController);
         processAllMessages();
     }
@@ -302,6 +303,27 @@
     }
 
     @Test
+    public void testTransitionToCurrentStateIdle_usingUserDataForRrcDetection() throws Exception {
+        mBundle.putBoolean(
+                CarrierConfigManager.KEY_LTE_ENDC_USING_USER_DATA_FOR_RRC_DETECTION_BOOL, true);
+        doReturn(true).when(mTelephonyManager).isRadioInterfaceCapabilitySupported(
+                TelephonyManager.CAPABILITY_PHYSICAL_CHANNEL_CONFIG_1_6_SUPPORTED);
+        mNetworkTypeController = new NetworkTypeController(mPhone, mDisplayInfoController);
+        broadcastCarrierConfigs();
+        processAllMessages();
+        assertEquals("DefaultState", getCurrentState().getName());
+        doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mServiceState).getDataNetworkType();
+        doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(mServiceState).getNrState();
+        mNetworkTypeController.sendMessage(EVENT_PHYSICAL_LINK_STATE_CHANGED,
+                new AsyncResult(null, DcController.PHYSICAL_LINK_NOT_ACTIVE, null));
+        mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE);
+
+        processAllMessages();
+
+        assertEquals("not_restricted_rrc_idle", getCurrentState().getName());
+    }
+
+    @Test
     public void testTransitionToCurrentStateLteConnected() throws Exception {
         assertEquals("DefaultState", getCurrentState().getName());
         doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mServiceState).getDataNetworkType();
@@ -319,6 +341,7 @@
         doReturn(true).when(mTelephonyManager).isRadioInterfaceCapabilitySupported(
                 TelephonyManager.CAPABILITY_PHYSICAL_CHANNEL_CONFIG_1_6_SUPPORTED);
         mNetworkTypeController = new NetworkTypeController(mPhone, mDisplayInfoController);
+        broadcastCarrierConfigs();
         processAllMessages();
         assertEquals("DefaultState", getCurrentState().getName());
         doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mServiceState).getDataNetworkType();
@@ -331,6 +354,28 @@
     }
 
     @Test
+    public void testTransitionToCurrentStateLteConnected_usingUserDataForRrcDetection()
+            throws Exception {
+        mBundle.putBoolean(
+                CarrierConfigManager.KEY_LTE_ENDC_USING_USER_DATA_FOR_RRC_DETECTION_BOOL, true);
+        doReturn(true).when(mTelephonyManager).isRadioInterfaceCapabilitySupported(
+                TelephonyManager.CAPABILITY_PHYSICAL_CHANNEL_CONFIG_1_6_SUPPORTED);
+        mNetworkTypeController = new NetworkTypeController(mPhone, mDisplayInfoController);
+        broadcastCarrierConfigs();
+        processAllMessages();
+        assertEquals("DefaultState", getCurrentState().getName());
+        doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mServiceState).getDataNetworkType();
+        doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(mServiceState).getNrState();
+        mNetworkTypeController.sendMessage(EVENT_PHYSICAL_LINK_STATE_CHANGED,
+                new AsyncResult(null, DcController.PHYSICAL_LINK_ACTIVE, null));
+        mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE);
+
+        processAllMessages();
+
+        assertEquals("not_restricted_rrc_con", getCurrentState().getName());
+    }
+
+    @Test
     public void testTransitionToCurrentStateNrConnected() throws Exception {
         assertEquals("DefaultState", getCurrentState().getName());
         doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mServiceState).getDataNetworkType();
@@ -564,6 +609,30 @@
         assertEquals("not_restricted_rrc_con", getCurrentState().getName());
     }
 
+
+    @Test
+    public void testUsingUserDataForRrcDetection_FromNrConnectedMmwaveToLteConnected()
+            throws Exception {
+        mBundle.putBoolean(
+                CarrierConfigManager.KEY_LTE_ENDC_USING_USER_DATA_FOR_RRC_DETECTION_BOOL, true);
+        doReturn(true).when(mTelephonyManager).isRadioInterfaceCapabilitySupported(
+                TelephonyManager.CAPABILITY_PHYSICAL_CHANNEL_CONFIG_1_6_SUPPORTED);
+        mNetworkTypeController = new NetworkTypeController(mPhone, mDisplayInfoController);
+        broadcastCarrierConfigs();
+        processAllMessages();
+        testTransitionToCurrentStateNrConnectedMmwave();
+        doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mServiceState).getDataNetworkType();
+        doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(mServiceState).getNrState();
+        mNetworkTypeController.sendMessage(EVENT_PHYSICAL_LINK_STATE_CHANGED,
+                new AsyncResult(null, DcController.PHYSICAL_LINK_ACTIVE, null));
+        mNetworkTypeController.sendMessage(EVENT_NR_FREQUENCY_CHANGED);
+        mNetworkTypeController.sendMessage(EVENT_NR_STATE_CHANGED);
+
+        processAllMessages();
+
+        assertEquals("not_restricted_rrc_con", getCurrentState().getName());
+    }
+
     @Test
     public void testEventPhysicalChannelChangeFromLteToLteCaInLegacyState() throws Exception {
         testTransitionToCurrentStateLegacy();
@@ -669,6 +738,26 @@
     }
 
     @Test
+    public void testEventPhysicalLinkStateChanged_UsingUserDataForRrcDetection()
+            throws Exception {
+        mBundle.putBoolean(
+                CarrierConfigManager.KEY_LTE_ENDC_USING_USER_DATA_FOR_RRC_DETECTION_BOOL, true);
+        doReturn(true).when(mTelephonyManager).isRadioInterfaceCapabilitySupported(
+                TelephonyManager.CAPABILITY_PHYSICAL_CHANNEL_CONFIG_1_6_SUPPORTED);
+        mNetworkTypeController = new NetworkTypeController(mPhone, mDisplayInfoController);
+        broadcastCarrierConfigs();
+        processAllMessages();
+        testTransitionToCurrentStateLteConnected_usingUserDataForRrcDetection();
+        doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange();
+        mNetworkTypeController.sendMessage(EVENT_PHYSICAL_LINK_STATE_CHANGED,
+                new AsyncResult(null, DcController.PHYSICAL_LINK_NOT_ACTIVE, null));
+
+        processAllMessages();
+
+        assertEquals("not_restricted_rrc_idle", getCurrentState().getName());
+    }
+
+    @Test
     public void testEventPhysicalChannelConfigNotifChanged() throws Exception {
         testTransitionToCurrentStateNrConnected();
         assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
@@ -1098,4 +1187,49 @@
         }
         doReturn(lastPhysicalChannelConfigList).when(mSST).getPhysicalChannelConfigList();
     }
+
+    @Test
+    public void testTransitionToCurrentStateNrConnectedWithLowBandwidth() throws Exception {
+        assertEquals("DefaultState", getCurrentState().getName());
+        doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mServiceState).getDataNetworkType();
+        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
+        doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange();
+        doReturn(new int[] {19999}).when(mServiceState).getCellBandwidths();
+        mBundle.putInt(CarrierConfigManager.KEY_NR_ADVANCED_THRESHOLD_BANDWIDTH_KHZ_INT, 20000);
+        broadcastCarrierConfigs();
+
+        mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE);
+        processAllMessages();
+        assertEquals("connected", getCurrentState().getName());
+    }
+
+    @Test
+    public void testTransitionToCurrentStateNrConnectedWithHighBandwidth() throws Exception {
+        assertEquals("DefaultState", getCurrentState().getName());
+        doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mServiceState).getDataNetworkType();
+        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
+        doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange();
+        doReturn(new int[] {20001}).when(mServiceState).getCellBandwidths();
+        mBundle.putInt(CarrierConfigManager.KEY_NR_ADVANCED_THRESHOLD_BANDWIDTH_KHZ_INT, 20000);
+        broadcastCarrierConfigs();
+
+        mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE);
+        processAllMessages();
+        assertEquals("connected_mmwave", getCurrentState().getName());
+    }
+
+    @Test
+    public void testNrAdvancedDisabledWhileRoaming() throws Exception {
+        assertEquals("DefaultState", getCurrentState().getName());
+        doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mServiceState).getDataNetworkType();
+        doReturn(true).when(mServiceState).getDataRoaming();
+        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
+        doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange();
+        mBundle.putBoolean(CarrierConfigManager.KEY_ENABLE_NR_ADVANCED_WHILE_ROAMING_BOOL, false);
+        broadcastCarrierConfigs();
+
+        mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE);
+        processAllMessages();
+        assertEquals("connected", getCurrentState().getName());
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/NitzSignalTest.java b/tests/telephonytests/src/com/android/internal/telephony/NitzSignalTest.java
new file mode 100644
index 0000000..352a9aa
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/NitzSignalTest.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 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 org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import org.junit.Test;
+
+public class NitzSignalTest {
+
+    /** Sample cases for equals() and hashCode(). Not exhaustive. */
+    @Test
+    public void testEqualsAndHashCode() {
+        long receiptElapsedMillis1 = 1111;
+        NitzData nitzData1 = NitzData.createForTests(0, 0, 1234, null);
+        long ageMillis1 = 11;
+        NitzSignal nitzSignal1 = new NitzSignal(receiptElapsedMillis1, nitzData1, ageMillis1);
+        assertEquals(nitzSignal1, nitzSignal1);
+        assertEquals(nitzSignal1.hashCode(), nitzSignal1.hashCode());
+
+        NitzSignal nitzSignal1v2 = new NitzSignal(receiptElapsedMillis1, nitzData1, ageMillis1);
+        assertEquals(nitzSignal1, nitzSignal1v2);
+        assertEquals(nitzSignal1v2, nitzSignal1);
+        assertEquals(nitzSignal1.hashCode(), nitzSignal1v2.hashCode());
+
+        long receiptElapsedMillis2 = 2222;
+        NitzData nitzData2 = NitzData.createForTests(0, 0, 2345, null);
+        long ageMillis2 = 11;
+        NitzSignal nitzSignal2 = new NitzSignal(receiptElapsedMillis2, nitzData2, ageMillis2);
+        assertNotEquals(nitzSignal1, nitzSignal2);
+        assertNotEquals(nitzSignal2, nitzSignal1);
+    }
+
+    @Test
+    public void testGetAgeAdjustedRealtimeMillis_zeroAge() {
+        NitzData nitzData = NitzData.createForTests(0, 0, 1234, null);
+        long receiptElapsedRealtimeMillis = 1111;
+        long ageMillis = 0;
+        NitzSignal nitzSignal =
+                new NitzSignal(receiptElapsedRealtimeMillis, nitzData, ageMillis);
+        assertEquals(receiptElapsedRealtimeMillis,
+                nitzSignal.getReceiptElapsedRealtimeMillis());
+        assertEquals(ageMillis, nitzSignal.getAgeMillis());
+        assertEquals(receiptElapsedRealtimeMillis - ageMillis,
+                nitzSignal.getAgeAdjustedElapsedRealtimeMillis());
+    }
+
+    @Test
+    public void testGetAgeAdjustedRealtimeMillis_withAge() {
+        NitzData nitzData = NitzData.createForTests(0, 0, 1234, null);
+        long receiptElapsedRealtimeMillis = 1111;
+        long ageMillis = 5000;
+        NitzSignal nitzSignal =
+                new NitzSignal(receiptElapsedRealtimeMillis, nitzData, ageMillis);
+        assertEquals(receiptElapsedRealtimeMillis,
+                nitzSignal.getReceiptElapsedRealtimeMillis());
+        assertEquals(ageMillis, nitzSignal.getAgeMillis());
+        assertEquals(receiptElapsedRealtimeMillis - ageMillis,
+                nitzSignal.getAgeAdjustedElapsedRealtimeMillis());
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/PhoneConfigurationManagerTest.java b/tests/telephonytests/src/com/android/internal/telephony/PhoneConfigurationManagerTest.java
index a702aac..a468dcd 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/PhoneConfigurationManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/PhoneConfigurationManagerTest.java
@@ -140,7 +140,7 @@
 
         // Try switching to dual SIM. Shouldn't work as we haven't indicated DSDS is supported.
         mPcm.switchMultiSimConfig(2);
-        verify(mMockRadioConfig, never()).setModemsConfig(anyInt(), any());
+        verify(mMockRadioConfig, never()).setNumOfLiveModems(anyInt(), any());
     }
 
     @Test
@@ -155,7 +155,7 @@
 
         // Try switching to dual SIM. Shouldn't work as we haven't indicated DSDS is supported.
         mPcm.switchMultiSimConfig(2);
-        verify(mMockRadioConfig, never()).setModemsConfig(anyInt(), any());
+        verify(mMockRadioConfig, never()).setNumOfLiveModems(anyInt(), any());
 
         // Send static capability back to indicate DSDS is supported.
         clearInvocations(mMockRadioConfig);
@@ -169,7 +169,7 @@
         replaceInstance(PhoneFactory.class, "sPhones", null, mPhones);
         mPcm.switchMultiSimConfig(2);
         ArgumentCaptor<Message> captor = ArgumentCaptor.forClass(Message.class);
-        verify(mMockRadioConfig).setModemsConfig(eq(2), captor.capture());
+        verify(mMockRadioConfig).setNumOfLiveModems(eq(2), captor.capture());
 
         // Send message back to indicate switch success.
         Message message = captor.getValue();
@@ -221,7 +221,7 @@
         setRebootRequiredForConfigSwitch(false);
         mPcm.switchMultiSimConfig(1);
         ArgumentCaptor<Message> captor = ArgumentCaptor.forClass(Message.class);
-        verify(mMockRadioConfig).setModemsConfig(eq(1), captor.capture());
+        verify(mMockRadioConfig).setNumOfLiveModems(eq(1), captor.capture());
 
         // Send message back to indicate switch success.
         Message message = captor.getValue();
diff --git a/tests/telephonytests/src/com/android/internal/telephony/PhoneSubInfoControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/PhoneSubInfoControllerTest.java
index 6480f65..05256b8 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/PhoneSubInfoControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/PhoneSubInfoControllerTest.java
@@ -16,8 +16,6 @@
 package com.android.internal.telephony;
 
 import static android.Manifest.permission.READ_PHONE_STATE;
-import static android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE;
-import static android.Manifest.permission.READ_SMS;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
@@ -31,6 +29,8 @@
 import android.app.AppOpsManager;
 import android.app.PropertyInvalidatedCache;
 import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Binder;
 import android.os.Build;
 import android.test.suitebuilder.annotation.SmallTest;
 
@@ -45,6 +45,7 @@
 
     private PhoneSubInfoController mPhoneSubInfoControllerUT;
     private AppOpsManager mAppOsMgr;
+    private PackageManager mPm;
 
     @Mock
     GsmCdmaPhone mSecondPhone;
@@ -70,6 +71,7 @@
         doReturn(mContext).when(mSecondPhone).getContext();
 
         mAppOsMgr = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
+        mPm = mContext.getPackageManager();
 
         replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[]{mPhone, mSecondPhone});
         mPhoneSubInfoControllerUT = new PhoneSubInfoController(mContext);
@@ -82,6 +84,9 @@
         doReturn(AppOpsManager.MODE_ERRORED).when(mAppOsMgr).noteOpNoThrow(
                 eq(AppOpsManager.OPSTR_READ_DEVICE_IDENTIFIERS), anyInt(), eq(TAG), eq(FEATURE_ID),
                 nullable(String.class));
+
+        // Bypass calling package check.
+        doReturn(Binder.getCallingUid()).when(mPm).getPackageUid(eq(TAG), anyInt());
     }
 
     @After
@@ -589,16 +594,11 @@
 
         /* case 1: no READ_PRIVILEGED_PHONE_STATE & READ_PHONE_STATE &
         READ_SMS and no OP_WRITE_SMS & OP_READ_SMS from appOsMgr */
+        // All permission checks are handled by the LegacyPermissionManager, so this test only
+        // requires three case; all permissions / appops denied, READ_PHONE_STATE permission
+        // granted without the appop, and one or more of the permissions / appops granted.
         mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);
-        doReturn(AppOpsManager.MODE_ERRORED).when(mAppOsMgr).noteOp(
-                eq(AppOpsManager.OPSTR_READ_SMS), anyInt(), eq(TAG), eq(FEATURE_ID),
-                nullable(String.class));
-        doReturn(AppOpsManager.MODE_ERRORED).when(mAppOsMgr).noteOp(
-                eq(AppOpsManager.OPSTR_WRITE_SMS), anyInt(), eq(TAG), eq(FEATURE_ID),
-                nullable(String.class));
-        doReturn(AppOpsManager.MODE_ERRORED).when(mAppOsMgr).noteOp(
-                eq(AppOpsManager.OPSTR_READ_PHONE_STATE), anyInt(), eq(TAG), eq(FEATURE_ID),
-                nullable(String.class));
+        setPhoneNumberAccess(PackageManager.PERMISSION_DENIED);
         try {
             mPhoneSubInfoControllerUT.getLine1NumberForSubscriber(0, TAG, FEATURE_ID);
             Assert.fail("expected Security Exception Thrown");
@@ -613,61 +613,13 @@
             assertTrue(ex instanceof SecurityException);
         }
 
-        /* case 2: only enable WRITE_SMS permission */
-        doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOsMgr).noteOp(
-                eq(AppOpsManager.OPSTR_WRITE_SMS), anyInt(), eq(TAG), eq(FEATURE_ID),
-                nullable(String.class));
-        assertEquals("+18051234567",
-                mPhoneSubInfoControllerUT.getLine1NumberForSubscriber(0, TAG, FEATURE_ID));
-        assertEquals("+18052345678",
-                mPhoneSubInfoControllerUT.getLine1NumberForSubscriber(1, TAG, FEATURE_ID));
-
-        /* case 3: only enable READ_PRIVILEGED_PHONE_STATE */
-        doReturn(AppOpsManager.MODE_ERRORED).when(mAppOsMgr).noteOp(
-                eq(AppOpsManager.OPSTR_WRITE_SMS), anyInt(), eq(TAG), eq(FEATURE_ID),
-                nullable(String.class));
-        mContextFixture.addCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE);
-        assertEquals("+18051234567",
-                mPhoneSubInfoControllerUT.getLine1NumberForSubscriber(0, TAG, FEATURE_ID));
-        assertEquals("+18052345678",
-                mPhoneSubInfoControllerUT.getLine1NumberForSubscriber(1, TAG, FEATURE_ID));
-
-        /* case 4: only enable READ_PHONE_STATE permission */
-        mContextFixture.removeCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE);
-        mContextFixture.addCallingOrSelfPermission(READ_PHONE_STATE);
+        /* case 2: only enable READ_PHONE_STATE permission */
+        setPhoneNumberAccess(AppOpsManager.MODE_IGNORED);
         assertNull(mPhoneSubInfoControllerUT.getLine1NumberForSubscriber(0, TAG, FEATURE_ID));
         assertNull(mPhoneSubInfoControllerUT.getLine1NumberForSubscriber(1, TAG, FEATURE_ID));
 
-        /* case 5: enable appOsMgr READ_PHONE_PERMISSION & READ_PHONE_STATE */
-        doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOsMgr).noteOp(
-                eq(AppOpsManager.OPSTR_READ_PHONE_STATE), anyInt(), eq(TAG), eq(FEATURE_ID),
-                nullable(String.class));
-        assertEquals("+18051234567",
-                mPhoneSubInfoControllerUT.getLine1NumberForSubscriber(0, TAG, FEATURE_ID));
-        assertEquals("+18052345678",
-                mPhoneSubInfoControllerUT.getLine1NumberForSubscriber(1, TAG, FEATURE_ID));
-
-        /* case 6: only enable READ_SMS; without the appop should throw SecurityException */
-        doReturn(AppOpsManager.MODE_ERRORED).when(mAppOsMgr).noteOp(
-                eq(AppOpsManager.OPSTR_READ_PHONE_STATE), anyInt(), eq(TAG), eq(FEATURE_ID),
-                nullable(String.class));
-        mContextFixture.removeCallingOrSelfPermission(READ_PHONE_STATE);
-        mContextFixture.addCallingOrSelfPermission(READ_SMS);
-        try {
-            mPhoneSubInfoControllerUT.getLine1NumberForSubscriber(0, TAG, FEATURE_ID);
-            Assert.fail("expected SecurityException thrown");
-        } catch (SecurityException expected) {
-        }
-        try {
-            mPhoneSubInfoControllerUT.getLine1NumberForSubscriber(1, TAG, FEATURE_ID);
-            Assert.fail("expected SecurityException thrown");
-        } catch (SecurityException expected) {
-        }
-
-        /* case 7: enable READ_SMS and OP_READ_SMS */
-        doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOsMgr).noteOp(
-                eq(AppOpsManager.OPSTR_READ_SMS), anyInt(), eq(TAG), eq(FEATURE_ID),
-                nullable(String.class));
+        /* case 3: enable READ_SMS and OP_READ_SMS */
+        setPhoneNumberAccess(PackageManager.PERMISSION_GRANTED);
         assertEquals("+18051234567",
                 mPhoneSubInfoControllerUT.getLine1NumberForSubscriber(0, TAG, FEATURE_ID));
         assertEquals("+18052345678",
@@ -684,15 +636,7 @@
         /* case 1: no READ_PRIVILEGED_PHONE_STATE & READ_PHONE_STATE &
         READ_SMS and no OP_WRITE_SMS & OP_READ_SMS from appOsMgr */
         mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);
-        doReturn(AppOpsManager.MODE_ERRORED).when(mAppOsMgr).noteOp(
-                eq(AppOpsManager.OPSTR_READ_SMS), anyInt(), eq(TAG), eq(FEATURE_ID),
-                nullable(String.class));
-        doReturn(AppOpsManager.MODE_ERRORED).when(mAppOsMgr).noteOp(
-                eq(AppOpsManager.OPSTR_WRITE_SMS), anyInt(), eq(TAG), eq(FEATURE_ID),
-                nullable(String.class));
-        doReturn(AppOpsManager.MODE_ERRORED).when(mAppOsMgr).noteOp(
-                eq(AppOpsManager.OPSTR_READ_PHONE_STATE), anyInt(), eq(TAG), eq(FEATURE_ID),
-                nullable(String.class));
+        setPhoneNumberAccess(PackageManager.PERMISSION_DENIED);
         try {
             mPhoneSubInfoControllerUT.getLine1NumberForSubscriber(0, TAG, FEATURE_ID);
             Assert.fail("expected Security Exception Thrown");
@@ -707,32 +651,8 @@
             assertTrue(ex instanceof SecurityException);
         }
 
-        /* case 2: enable READ_PHONE_STATE permission */
-        doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOsMgr).noteOp(
-                eq(AppOpsManager.OPSTR_READ_PHONE_STATE), anyInt(), eq(TAG), eq(FEATURE_ID),
-                nullable(String.class));
-        try {
-            mPhoneSubInfoControllerUT.getLine1NumberForSubscriber(0, TAG, FEATURE_ID);
-            Assert.fail("expected Security Exception Thrown");
-        } catch (Exception ex) {
-            assertTrue(ex instanceof SecurityException);
-        }
-
-        try {
-            mPhoneSubInfoControllerUT.getLine1NumberForSubscriber(1, TAG, FEATURE_ID);
-            Assert.fail("expected Security Exception Thrown");
-        } catch (Exception ex) {
-            assertTrue(ex instanceof SecurityException);
-        }
-
-        /* case 3: enable READ_SMS and OP_READ_SMS */
-        doReturn(AppOpsManager.MODE_ERRORED).when(mAppOsMgr).noteOp(
-                eq(AppOpsManager.OPSTR_READ_PHONE_STATE), anyInt(), eq(TAG), eq(FEATURE_ID),
-                nullable(String.class));
-        mContextFixture.addCallingOrSelfPermission(READ_SMS);
-        doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOsMgr).noteOp(
-                eq(AppOpsManager.OPSTR_READ_SMS), anyInt(), eq(TAG), eq(FEATURE_ID),
-                nullable(String.class));
+        /* case 2: enable READ_SMS and OP_READ_SMS */
+        setPhoneNumberAccess(PackageManager.PERMISSION_GRANTED);
         assertEquals("+18051234567",
                 mPhoneSubInfoControllerUT.getLine1NumberForSubscriber(0, TAG, FEATURE_ID));
         assertEquals("+18052345678",
@@ -817,16 +737,10 @@
         doReturn("+18052345678").when(mSecondPhone).getMsisdn();
 
         /* case 1: no READ_PRIVILEGED_PHONE_STATE & READ_PHONE_STATE from appOsMgr */
+        // The LegacyPermissionManager handles these checks, so set its return code to indicate
+        // none of these have been granted.
         mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);
-        doReturn(AppOpsManager.MODE_ERRORED).when(mAppOsMgr).noteOp(
-                eq(AppOpsManager.OPSTR_READ_SMS), anyInt(), eq(TAG), eq(FEATURE_ID),
-                nullable(String.class));
-        doReturn(AppOpsManager.MODE_ERRORED).when(mAppOsMgr).noteOp(
-                eq(AppOpsManager.OPSTR_WRITE_SMS), anyInt(), eq(TAG), eq(FEATURE_ID),
-                nullable(String.class));
-        doReturn(AppOpsManager.MODE_ERRORED).when(mAppOsMgr).noteOp(
-                eq(AppOpsManager.OPSTR_READ_PHONE_STATE), anyInt(), eq(TAG), eq(FEATURE_ID),
-                nullable(String.class));
+        setPhoneNumberAccess(PackageManager.PERMISSION_DENIED);
         try {
             mPhoneSubInfoControllerUT.getMsisdnForSubscriber(0, TAG, FEATURE_ID);
             Assert.fail("expected Security Exception Thrown");
@@ -842,14 +756,14 @@
         }
 
         /* case 2: only enable READ_PHONE_STATE permission */
-        mContextFixture.addCallingOrSelfPermission(READ_PHONE_STATE);
+        // The LegacyPermissionManager will return AppOpsManager.MODE_IGNORED if the target SDK
+        // version < R and the READ_PHONE_STATE permission has been granted without the appop.
+        setPhoneNumberAccess(AppOpsManager.MODE_IGNORED);
         assertNull(mPhoneSubInfoControllerUT.getMsisdnForSubscriber(0, TAG, FEATURE_ID));
         assertNull(mPhoneSubInfoControllerUT.getMsisdnForSubscriber(1, TAG, FEATURE_ID));
 
         /* case 3: enable appOsMgr READ_PHONE_PERMISSION & READ_PHONE_STATE */
-        doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOsMgr).noteOp(
-                eq(AppOpsManager.OPSTR_READ_PHONE_STATE), anyInt(), eq(TAG), eq(FEATURE_ID),
-                nullable(String.class));
+        setPhoneNumberAccess(PackageManager.PERMISSION_GRANTED);
         assertEquals("+18051234567",
                 mPhoneSubInfoControllerUT.getMsisdnForSubscriber(0, TAG, FEATURE_ID));
         assertEquals("+18052345678",
@@ -865,16 +779,12 @@
 
         /* case 1: no READ_PRIVILEGED_PHONE_STATE & READ_PHONE_STATE &
         READ_SMS and no OP_WRITE_SMS & OP_READ_SMS from appOsMgr */
+        // Since the LegacyPermissionManager is performing this check the service will perform
+        // the READ_PHONE_STATE checks based on target SDK version; for apps targeting R+ it
+        // will not check the READ_PHONE_STATE permission and appop and will only return
+        // permission granted / denied.
         mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);
-        doReturn(AppOpsManager.MODE_ERRORED).when(mAppOsMgr).noteOp(
-                eq(AppOpsManager.OPSTR_READ_SMS), anyInt(), eq(TAG), eq(FEATURE_ID),
-                nullable(String.class));
-        doReturn(AppOpsManager.MODE_ERRORED).when(mAppOsMgr).noteOp(
-                eq(AppOpsManager.OPSTR_WRITE_SMS), anyInt(), eq(TAG), eq(FEATURE_ID),
-                nullable(String.class));
-        doReturn(AppOpsManager.MODE_ERRORED).when(mAppOsMgr).noteOp(
-                eq(AppOpsManager.OPSTR_READ_PHONE_STATE), anyInt(), eq(TAG), eq(FEATURE_ID),
-                nullable(String.class));
+        setPhoneNumberAccess(PackageManager.PERMISSION_DENIED);
         try {
             mPhoneSubInfoControllerUT.getMsisdnForSubscriber(0, TAG, FEATURE_ID);
             Assert.fail("expected Security Exception Thrown");
@@ -889,32 +799,8 @@
             assertTrue(ex instanceof SecurityException);
         }
 
-        /* case 2: only enable READ_PHONE_STATE permission */
-        doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOsMgr).noteOp(
-                eq(AppOpsManager.OPSTR_READ_PHONE_STATE), anyInt(), eq(TAG), eq(FEATURE_ID),
-                nullable(String.class));
-        try {
-            mPhoneSubInfoControllerUT.getMsisdnForSubscriber(0, TAG, FEATURE_ID);
-            Assert.fail("expected Security Exception Thrown");
-        } catch (Exception ex) {
-            assertTrue(ex instanceof SecurityException);
-        }
-
-        try {
-            mPhoneSubInfoControllerUT.getMsisdnForSubscriber(1, TAG, FEATURE_ID);
-            Assert.fail("expected Security Exception Thrown");
-        } catch (Exception ex) {
-            assertTrue(ex instanceof SecurityException);
-        }
-
-        /* case 3: enable READ_SMS and OP_READ_SMS */
-        doReturn(AppOpsManager.MODE_ERRORED).when(mAppOsMgr).noteOp(
-                eq(AppOpsManager.OPSTR_READ_PHONE_STATE), anyInt(), eq(TAG), eq(FEATURE_ID),
-                nullable(String.class));
-        mContextFixture.addCallingOrSelfPermission(READ_SMS);
-        doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOsMgr).noteOp(
-                eq(AppOpsManager.OPSTR_READ_SMS), anyInt(), eq(TAG), eq(FEATURE_ID),
-                nullable(String.class));
+        /* case 2: enable READ_SMS and OP_READ_SMS */
+        setPhoneNumberAccess(PackageManager.PERMISSION_GRANTED);
         assertEquals("+18051234567",
                 mPhoneSubInfoControllerUT.getMsisdnForSubscriber(0, TAG, FEATURE_ID));
         assertEquals("+18052345678",
diff --git a/tests/telephonytests/src/com/android/internal/telephony/PhoneSwitcherTest.java b/tests/telephonytests/src/com/android/internal/telephony/PhoneSwitcherTest.java
index 57be4d0..b0e9a37 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/PhoneSwitcherTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/PhoneSwitcherTest.java
@@ -23,9 +23,13 @@
 import static android.telephony.TelephonyManager.SET_OPPORTUNISTIC_SUB_SUCCESS;
 import static android.telephony.TelephonyManager.SET_OPPORTUNISTIC_SUB_VALIDATION_FAILED;
 import static android.telephony.TelephonyManager.SIM_STATE_LOADED;
+import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM;
+import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN;
+import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_LTE;
 
 import static com.android.internal.telephony.PhoneSwitcher.ECBM_DEFAULT_DATA_SWITCH_BASE_TIME_MS;
 import static com.android.internal.telephony.PhoneSwitcher.EVENT_DATA_ENABLED_CHANGED;
+import static com.android.internal.telephony.PhoneSwitcher.EVENT_IMS_RADIO_TECH_CHANGED;
 import static com.android.internal.telephony.PhoneSwitcher.EVENT_MULTI_SIM_CONFIG_CHANGED;
 import static com.android.internal.telephony.PhoneSwitcher.EVENT_PRECISE_CALL_STATE_CHANGED;
 
@@ -60,6 +64,7 @@
 import android.telephony.PhoneCapability;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
+import android.telephony.data.ApnSetting;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -80,6 +85,7 @@
 @TestableLooper.RunWithLooper
 public class PhoneSwitcherTest extends TelephonyTest {
     private static final int ACTIVE_PHONE_SWITCH = 1;
+    private static final int EVENT_RADIO_ON = 108;
     private static final int EVENT_MODEM_COMMAND_DONE = 112;
 
     @Mock
@@ -89,6 +95,8 @@
     @Mock
     private Phone mPhone2; // mPhone as phone 1 is already defined in TelephonyTest.
     @Mock
+    private Phone mImsPhone;
+    @Mock
     private DataEnabledSettings mDataEnabledSettings2;
     @Mock
     private Handler mActivePhoneSwitchHandler;
@@ -105,6 +113,8 @@
     @Mock
     CompletableFuture<Boolean> mFuturePhone;
     @Mock
+    PhoneSwitcher.ImsRegTechProvider mMockImsRegTechProvider;
+    @Mock
     private SubscriptionInfo mSubscriptionInfo;
 
     private PhoneSwitcher mPhoneSwitcher;
@@ -518,6 +528,12 @@
         // SetDataAllowed should never be triggered.
         verify(mCommandsInterface0, never()).setDataAllowed(anyBoolean(), any());
         verify(mCommandsInterface1, never()).setDataAllowed(anyBoolean(), any());
+
+        // Set preferred data modem should be triggered after radio on or available.
+        clearInvocations(mMockRadioConfig);
+        Message.obtain(mPhoneSwitcher, EVENT_RADIO_ON, res).sendToTarget();
+        processAllMessages();
+        verify(mMockRadioConfig).setPreferredDataModem(eq(0), any());
     }
 
     @Test
@@ -569,6 +585,102 @@
         assertEquals(0, mPhoneSwitcher.getPreferredDataPhoneId());
     }
 
+    private void mockImsRegTech(int phoneId, int regTech) {
+        doReturn(regTech).when(mMockImsRegTechProvider).get(any(), eq(phoneId));
+        mPhoneSwitcher.mImsRegTechProvider = mMockImsRegTechProvider;
+    }
+
+    @Test
+    @SmallTest
+    public void testNonDefaultDataPhoneInCall_ImsCallOnLte_shouldSwitchDds() throws Exception {
+        initialize();
+        setAllPhonesInactive();
+
+        // Phone 0 has sub 1, phone 1 has sub 2.
+        // Sub 1 is default data sub.
+        // Both are active subscriptions are active sub, as they are in both active slots.
+        setSlotIndexToSubId(0, 1);
+        setSlotIndexToSubId(1, 2);
+        setDefaultDataSubId(1);
+        processAllMessages();
+
+        // Phone 0 should be the default data phoneId.
+        assertEquals(0, mPhoneSwitcher.getPreferredDataPhoneId());
+
+        // Phone2 has active IMS call on LTE. And data of DEFAULT apn is enabled. This should
+        // trigger data switch.
+        doReturn(mImsPhone).when(mPhone2).getImsPhone();
+        doReturn(true).when(mDataEnabledSettings2).isDataEnabled(ApnSetting.TYPE_DEFAULT);
+        mockImsRegTech(1, REGISTRATION_TECH_LTE);
+        notifyPhoneAsInCall(mImsPhone);
+
+        // Phone 1 should become the default data phone.
+        assertEquals(1, mPhoneSwitcher.getPreferredDataPhoneId());
+    }
+
+    @Test
+    @SmallTest
+    public void testNonDefaultDataPhoneInCall_ImsCallOnWlan_shouldNotSwitchDds() throws Exception {
+        initialize();
+        setAllPhonesInactive();
+
+        // Phone 0 has sub 1, phone 1 has sub 2.
+        // Sub 1 is default data sub.
+        // Both are active subscriptions are active sub, as they are in both active slots.
+        setSlotIndexToSubId(0, 1);
+        setSlotIndexToSubId(1, 2);
+        setDefaultDataSubId(1);
+        processAllMessages();
+
+        // Phone 0 should be the default data phoneId.
+        assertEquals(0, mPhoneSwitcher.getPreferredDataPhoneId());
+
+        // Phone2 has active call, but data is turned off. So no data switching should happen.
+        doReturn(mImsPhone).when(mPhone2).getImsPhone();
+        doReturn(true).when(mDataEnabledSettings2).isDataEnabled(ApnSetting.TYPE_DEFAULT);
+        mockImsRegTech(1, REGISTRATION_TECH_IWLAN);
+        notifyPhoneAsInCall(mImsPhone);
+
+        // Phone 0 should remain the default data phone.
+        assertEquals(0, mPhoneSwitcher.getPreferredDataPhoneId());
+    }
+
+    @Test
+    @SmallTest
+    public void testNonDefaultDataPhoneInCall_ImsCallOnCrossSIM_HandoverToLTE() throws Exception {
+        initialize();
+        setAllPhonesInactive();
+
+        // Phone 0 has sub 1, phone 1 has sub 2.
+        // Sub 1 is default data sub.
+        // Both are active subscriptions are active sub, as they are in both active slots.
+        setSlotIndexToSubId(0, 1);
+        setSlotIndexToSubId(1, 2);
+        setDefaultDataSubId(1);
+        processAllMessages();
+
+        // Phone 0 should be the default data phoneId.
+        assertEquals(0, mPhoneSwitcher.getPreferredDataPhoneId());
+
+        // Phone 1 has active IMS call on CROSS_SIM. And data of DEFAULT apn is enabled. This should
+        // not trigger data switch.
+        doReturn(mImsPhone).when(mPhone2).getImsPhone();
+        doReturn(true).when(mDataEnabledSettings2).isDataEnabled(ApnSetting.TYPE_DEFAULT);
+        mockImsRegTech(1, REGISTRATION_TECH_CROSS_SIM);
+        notifyPhoneAsInCall(mImsPhone);
+
+        // Phone 0 should remain the default data phone.
+        assertEquals(0, mPhoneSwitcher.getPreferredDataPhoneId());
+
+        // Phone 1 has has handed over the call to LTE. And data of DEFAULT apn is enabled.
+        // This should trigger data switch.
+        mockImsRegTech(1, REGISTRATION_TECH_LTE);
+        notifyImsRegistrationTechChange(mPhone2);
+
+        // Phone 1 should become the default data phone.
+        assertEquals(1, mPhoneSwitcher.getPreferredDataPhoneId());
+    }
+
     @Test
     @SmallTest
     public void testNonDefaultDataPhoneInCall() throws Exception {
@@ -1114,6 +1226,9 @@
         doReturn(mInactiveCall).when(mPhone2).getForegroundCall();
         doReturn(mInactiveCall).when(mPhone2).getBackgroundCall();
         doReturn(mInactiveCall).when(mPhone2).getRingingCall();
+        doReturn(mInactiveCall).when(mImsPhone).getForegroundCall();
+        doReturn(mInactiveCall).when(mImsPhone).getBackgroundCall();
+        doReturn(mInactiveCall).when(mImsPhone).getRingingCall();
     }
 
     private void notifyPhoneAsInCall(Phone phone) {
@@ -1141,6 +1256,11 @@
         processAllMessages();
     }
 
+    private void notifyImsRegistrationTechChange(Phone phone) {
+        mPhoneSwitcher.sendEmptyMessage(EVENT_IMS_RADIO_TECH_CHANGED);
+        processAllMessages();
+    }
+
     private Message getEcbmRegistration(Phone phone) {
         ArgumentCaptor<Handler> handlerCaptor = ArgumentCaptor.forClass(Handler.class);
         ArgumentCaptor<Integer> intCaptor = ArgumentCaptor.forClass(Integer.class);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/RILTest.java b/tests/telephonytests/src/com/android/internal/telephony/RILTest.java
index d8b601b..149115a 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/RILTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/RILTest.java
@@ -116,7 +116,6 @@
 import android.hardware.radio.V1_0.RadioTechnologyFamily;
 import android.hardware.radio.V1_0.SmsWriteArgs;
 import android.hardware.radio.V1_6.IRadio;
-import android.hardware.radio.deprecated.V1_0.IOemHook;
 import android.net.ConnectivityManager;
 import android.net.InetAddresses;
 import android.net.LinkAddress;
@@ -159,6 +158,7 @@
 import android.telephony.data.DataCallResponse;
 import android.telephony.data.DataProfile;
 import android.telephony.data.EpsQos;
+import android.telephony.data.Qos;
 import android.telephony.data.QosBearerFilter;
 import android.telephony.data.QosBearerSession;
 import android.telephony.data.TrafficDescriptor;
@@ -203,8 +203,6 @@
     private TelephonyManager mTelephonyManager;
     @Mock
     private IRadio mRadioProxy;
-    @Mock
-    private IOemHook mOemHookProxy;
 
     private HalVersion mRadioVersionV10 = new HalVersion(1, 0);
     private HalVersion mRadioVersionV11 = new HalVersion(1, 1);
@@ -299,8 +297,7 @@
         } catch (RuntimeException e) {
         }
         Context context = new ContextFixture().getTestDouble();
-        doReturn(true).when(mConnectionManager)
-            .isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
+        doReturn(true).when(mConnectionManager).isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
         doReturn(mConnectionManager).when(context)
             .getSystemService(Context.CONNECTIVITY_SERVICE);
         doReturn(mTelephonyManager).when(context)
@@ -311,12 +308,10 @@
         doReturn(powerManager).when(context).getSystemService(Context.POWER_SERVICE);
         doReturn(new ApplicationInfo()).when(context).getApplicationInfo();
 
-        mRILInstance = new RIL(context,
-                RadioAccessFamily.getRafFromNetworkType(RILConstants.PREFERRED_NETWORK_MODE),
-            Phone.PREFERRED_CDMA_SUBSCRIPTION, 0);
+        mRILInstance = new RIL(context, RadioAccessFamily.getRafFromNetworkType(
+                RILConstants.PREFERRED_NETWORK_MODE), Phone.PREFERRED_CDMA_SUBSCRIPTION, 0);
         mRILUnderTest = spy(mRILInstance);
         doReturn(mRadioProxy).when(mRILUnderTest).getRadioProxy(any());
-        doReturn(mOemHookProxy).when(mRILUnderTest).getOemHookProxy(any());
 
         try {
             replaceInstance(RIL.class, "mRadioVersion", mRILUnderTest, mRadioVersionV10);
@@ -1383,22 +1378,6 @@
                 mRILUnderTest, mSerialNumberCaptor.getValue(), RIL_REQUEST_GET_BARRING_INFO);
     }
 
-    @Test
-    public void testInvokeOemRilRequestStrings() throws Exception {
-        String[] strings = new String[]{"a", "b", "c"};
-        mRILUnderTest.invokeOemRilRequestStrings(strings, obtainMessage());
-        verify(mOemHookProxy).sendRequestStrings(
-                mSerialNumberCaptor.capture(), eq(new ArrayList<>(Arrays.asList(strings))));
-    }
-
-    @Test
-    public void testInvokeOemRilRequestRaw() throws Exception {
-        byte[] data = new byte[]{1, 2, 3};
-        mRILUnderTest.invokeOemRilRequestRaw(data, obtainMessage());
-        verify(mOemHookProxy).sendRequestRaw(
-                mSerialNumberCaptor.capture(), eq(mRILUnderTest.primitiveArrayToArrayList(data)));
-    }
-
     private Message obtainMessage() {
         return mRILUnderTest.getRilHandler().obtainMessage();
     }
@@ -1512,11 +1491,10 @@
         record.timeStampType = RIL_TIMESTAMP_TYPE_OEM_RIL;
         record.timeStamp = TIMESTAMP;
         record.lte.add(lte);
-        ArrayList<android.hardware.radio.V1_0.CellInfo> records =
-                new ArrayList<android.hardware.radio.V1_0.CellInfo>();
+        ArrayList<Object> records = new ArrayList<>();
         records.add(record);
 
-        ArrayList<CellInfo> ret = RIL.convertHalCellInfoList(records);
+        ArrayList<CellInfo> ret = RILUtils.convertHalCellInfoList(records);
 
         assertEquals(1, ret.size());
         CellInfoLte cellInfoLte = (CellInfoLte) ret.get(0);
@@ -1554,11 +1532,10 @@
         record.timeStampType = RIL_TIMESTAMP_TYPE_OEM_RIL;
         record.timeStamp = TIMESTAMP;
         record.gsm.add(cellinfo);
-        ArrayList<android.hardware.radio.V1_0.CellInfo> records =
-                new ArrayList<android.hardware.radio.V1_0.CellInfo>();
+        ArrayList<Object> records = new ArrayList<>();
         records.add(record);
 
-        ArrayList<CellInfo> ret = RIL.convertHalCellInfoList(records);
+        ArrayList<CellInfo> ret = RILUtils.convertHalCellInfoList(records);
 
         assertEquals(1, ret.size());
         CellInfoGsm cellInfoGsm = (CellInfoGsm) ret.get(0);
@@ -1595,11 +1572,10 @@
         record.timeStampType = RIL_TIMESTAMP_TYPE_OEM_RIL;
         record.timeStamp = TIMESTAMP;
         record.wcdma.add(cellinfo);
-        ArrayList<android.hardware.radio.V1_0.CellInfo> records =
-                new ArrayList<android.hardware.radio.V1_0.CellInfo>();
+        ArrayList<Object> records = new ArrayList<>();
         records.add(record);
 
-        ArrayList<CellInfo> ret = RIL.convertHalCellInfoList(records);
+        ArrayList<CellInfo> ret = RILUtils.convertHalCellInfoList(records);
 
         assertEquals(1, ret.size());
         CellInfoWcdma cellInfoWcdma = (CellInfoWcdma) ret.get(0);
@@ -1645,11 +1621,10 @@
         record.timeStampType = RIL_TIMESTAMP_TYPE_OEM_RIL;
         record.timeStamp = TIMESTAMP;
         record.tdscdma.add(cellinfo);
-        ArrayList<android.hardware.radio.V1_2.CellInfo> records =
-                new ArrayList<android.hardware.radio.V1_2.CellInfo>();
+        ArrayList<Object> records = new ArrayList<>();
         records.add(record);
 
-        ArrayList<CellInfo> ret = RIL.convertHalCellInfoList_1_2(records);
+        ArrayList<CellInfo> ret = RILUtils.convertHalCellInfoList(records);
 
         assertEquals(1, ret.size());
         CellInfoTdscdma cellInfoTdscdma = (CellInfoTdscdma) ret.get(0);
@@ -1688,11 +1663,10 @@
         record.timeStampType = RIL_TIMESTAMP_TYPE_OEM_RIL;
         record.timeStamp = TIMESTAMP;
         record.cdma.add(cellinfo);
-        ArrayList<android.hardware.radio.V1_0.CellInfo> records =
-                new ArrayList<android.hardware.radio.V1_0.CellInfo>();
+        ArrayList<Object> records = new ArrayList<>();
         records.add(record);
 
-        ArrayList<CellInfo> ret = RIL.convertHalCellInfoList(records);
+        ArrayList<CellInfo> ret = RILUtils.convertHalCellInfoList(records);
 
         assertEquals(1, ret.size());
         CellInfoCdma cellInfoCdma = (CellInfoCdma) ret.get(0);
@@ -1975,10 +1949,10 @@
         android.hardware.radio.V1_4.CellInfo record = new android.hardware.radio.V1_4.CellInfo();
         record.info.nr(cellinfo);
 
-        ArrayList<android.hardware.radio.V1_4.CellInfo> records = new ArrayList<>();
+        ArrayList<Object> records = new ArrayList<>();
         records.add(record);
 
-        ArrayList<CellInfo> ret = RIL.convertHalCellInfoList_1_4(records);
+        ArrayList<CellInfo> ret = RILUtils.convertHalCellInfoList(records);
 
         CellInfoNr cellInfoNr = (CellInfoNr) ret.get(0);
         CellIdentityNr cellIdentityNr = (CellIdentityNr) cellInfoNr.getCellIdentity();
@@ -2028,7 +2002,7 @@
                 new android.hardware.radio.V1_5.CellIdentityLte();
         initializeCellIdentityLte_1_5(halCellIdentity, false, true);
 
-        CellIdentityLte cellIdentity = new CellIdentityLte(halCellIdentity);
+        CellIdentityLte cellIdentity = RILUtils.convertHalCellIdentityLte(halCellIdentity);
 
         assertEquals(CSG_INDICATION,
                 cellIdentity.getClosedSubscriberGroupInfo().getCsgIndicator());
@@ -2044,7 +2018,7 @@
                 new android.hardware.radio.V1_5.CellIdentityLte();
         initializeCellIdentityLte_1_5(halCellIdentity, true, false);
 
-        CellIdentityLte cellIdentity = new CellIdentityLte(halCellIdentity);
+        CellIdentityLte cellIdentity = RILUtils.convertHalCellIdentityLte(halCellIdentity);
 
         Set<String> additionalPlmns = new HashSet<>();
         Collections.addAll(additionalPlmns, ADDITIONAL_PLMNS);
@@ -2059,8 +2033,7 @@
         initializeCellIdentityWcdma_1_2(id.base);
 
         if (addAdditionalPlmns) {
-            id.additionalPlmns = new ArrayList<>(
-                    Arrays.asList(ADDITIONAL_PLMNS));
+            id.additionalPlmns = new ArrayList<>(Arrays.asList(ADDITIONAL_PLMNS));
         }
 
         if (addCsgInfo) {
@@ -2074,7 +2047,7 @@
                 new android.hardware.radio.V1_5.CellIdentityWcdma();
         initializeCellIdentityWcdma_1_5(halCellIdentity, false, true);
 
-        CellIdentityWcdma cellIdentity = new CellIdentityWcdma(halCellIdentity);
+        CellIdentityWcdma cellIdentity = RILUtils.convertHalCellIdentityWcdma(halCellIdentity);
 
         assertEquals(CSG_INDICATION,
                 cellIdentity.getClosedSubscriberGroupInfo().getCsgIndicator());
@@ -2090,7 +2063,7 @@
                 new android.hardware.radio.V1_5.CellIdentityWcdma();
         initializeCellIdentityWcdma_1_5(halCellIdentity, true, false);
 
-        CellIdentityWcdma cellIdentity = new CellIdentityWcdma(halCellIdentity);
+        CellIdentityWcdma cellIdentity = RILUtils.convertHalCellIdentityWcdma(halCellIdentity);
 
         Set<String> additionalPlmns = new HashSet<>();
         Collections.addAll(additionalPlmns, ADDITIONAL_PLMNS);
@@ -2105,8 +2078,7 @@
         initializeCellIdentityTdscdma_1_2(id.base);
 
         if (addAdditionalPlmns) {
-            id.additionalPlmns = new ArrayList<>(
-                    Arrays.asList(ADDITIONAL_PLMNS));
+            id.additionalPlmns = new ArrayList<>(Arrays.asList(ADDITIONAL_PLMNS));
         }
 
         if (addCsgInfo) {
@@ -2120,7 +2092,7 @@
                 new android.hardware.radio.V1_5.CellIdentityTdscdma();
         initializeCellIdentityTdscdma_1_5(halCellIdentity, false, true);
 
-        CellIdentityTdscdma cellIdentity = new CellIdentityTdscdma(halCellIdentity);
+        CellIdentityTdscdma cellIdentity = RILUtils.convertHalCellIdentityTdscdma(halCellIdentity);
 
         assertEquals(CSG_INDICATION,
                 cellIdentity.getClosedSubscriberGroupInfo().getCsgIndicator());
@@ -2136,7 +2108,7 @@
                 new android.hardware.radio.V1_5.CellIdentityTdscdma();
         initializeCellIdentityTdscdma_1_5(halCellIdentity, true, false);
 
-        CellIdentityTdscdma cellIdentity = new CellIdentityTdscdma(halCellIdentity);
+        CellIdentityTdscdma cellIdentity = RILUtils.convertHalCellIdentityTdscdma(halCellIdentity);
 
         Set<String> additionalPlmns = new HashSet<>();
         Collections.addAll(additionalPlmns, ADDITIONAL_PLMNS);
@@ -2186,7 +2158,7 @@
                 .setTrafficDescriptors(new ArrayList<>())
                 .build();
 
-        assertEquals(response, RIL.convertDataCallResult(result10));
+        assertEquals(response, RILUtils.convertHalDataCallResult(result10));
 
         // Test V1.4 SetupDataCallResult
         android.hardware.radio.V1_4.SetupDataCallResult result14 =
@@ -2205,7 +2177,7 @@
                 "fd00:976a:c206:20::6", "fd00:976a:c206:20::9", "fd00:976a:c202:1d::9"));
         result14.mtu = 1500;
 
-        assertEquals(response, RIL.convertDataCallResult(result14));
+        assertEquals(response, RILUtils.convertHalDataCallResult(result14));
 
         // Test V1.5 SetupDataCallResult
         android.hardware.radio.V1_5.SetupDataCallResult result15 =
@@ -2261,7 +2233,7 @@
                 .setTrafficDescriptors(new ArrayList<>())
                 .build();
 
-        assertEquals(response, RIL.convertDataCallResult(result15));
+        assertEquals(response, RILUtils.convertHalDataCallResult(result15));
 
         // Test V1.6 SetupDataCallResult
         android.hardware.radio.V1_6.SetupDataCallResult result16 =
@@ -2325,7 +2297,7 @@
 
         result16.qosSessions = new ArrayList<>(Arrays.asList(halQosSession));
 
-        EpsQos epsQos = new EpsQos(halEpsQos);
+        EpsQos epsQos = new EpsQos(new Qos.QosBandwidth(4, 7), new Qos.QosBandwidth(5, 8), 4);
         QosBearerFilter qosFilter = new QosBearerFilter(
                 Arrays.asList(
                         new LinkAddress(InetAddresses.parseNumericAddress("122.22.22.22"), 32)),
@@ -2350,7 +2322,8 @@
         android.hardware.radio.V1_6.OptionalOsAppId halOsAppId =
                 new android.hardware.radio.V1_6.OptionalOsAppId();
         android.hardware.radio.V1_6.OsAppId osAppId = new android.hardware.radio.V1_6.OsAppId();
-        osAppId.osAppId = mRILUnderTest.primitiveArrayToArrayList("OS_APP_ID".getBytes());
+        byte[] osAppIdArray = {1, 2, 3, 4};
+        osAppId.osAppId = RILUtils.primitiveArrayToArrayList(osAppIdArray);
         halOsAppId.value(osAppId);
 
         halTrafficDescriptor.dnn = halDnn;
@@ -2358,10 +2331,11 @@
         result16.trafficDescriptors = new ArrayList<>(Arrays.asList(halTrafficDescriptor));
 
         List<TrafficDescriptor> trafficDescriptors = Arrays.asList(
-                new TrafficDescriptor("DNN", "OS_APP_ID"));
+                new TrafficDescriptor("DNN", osAppIdArray));
 
         response = new DataCallResponse.Builder()
                 .setCause(0)
+                .setRetryDurationMillis(-1L)
                 .setId(0)
                 .setLinkStatus(2)
                 .setProtocolType(ApnSetting.PROTOCOL_IPV4V6)
@@ -2386,7 +2360,7 @@
                 .setTrafficDescriptors(trafficDescriptors)
                 .build();
 
-        assertEquals(response, RIL.convertDataCallResult(result16));
+        assertEquals(response, RILUtils.convertHalDataCallResult(result16));
     }
 
     @Test
@@ -2415,8 +2389,7 @@
 
     @Test
     public void testCellInfoTimestamp_1_4() {
-        ArrayList<android.hardware.radio.V1_4.CellInfo> records =
-                new ArrayList<android.hardware.radio.V1_4.CellInfo>();
+        ArrayList<Object> records = new ArrayList<>();
 
         for (int i = 0; i < 5 /* arbitrary */; i++) {
             android.hardware.radio.V1_4.CellInfo record =
@@ -2428,7 +2401,7 @@
 
             records.add(record);
         }
-        List<CellInfo> cil = RIL.convertHalCellInfoList_1_4(records);
+        List<CellInfo> cil = RILUtils.convertHalCellInfoList(records);
 
         // Check that all timestamps are set to a valid number and are equal
         final long ts = cil.get(0).getTimeStamp();
@@ -2440,8 +2413,7 @@
 
     @Test
     public void testCellInfoTimestamp_1_2() {
-        ArrayList<android.hardware.radio.V1_2.CellInfo> records =
-                new ArrayList<android.hardware.radio.V1_2.CellInfo>();
+        ArrayList<Object> records = new ArrayList<>();
 
         for (int i = 0; i < 5 /* arbitrary */; i++) {
             android.hardware.radio.V1_2.CellInfo record =
@@ -2456,7 +2428,7 @@
 
             records.add(record);
         }
-        List<CellInfo> cil = RIL.convertHalCellInfoList_1_2(records);
+        List<CellInfo> cil = RILUtils.convertHalCellInfoList(records);
 
         // Check that all timestamps are set to a valid number and are equal
         final long ts = cil.get(0).getTimeStamp();
@@ -2511,10 +2483,9 @@
         record.timeStamp = TIMESTAMP;
         record.lte.add(lte);
         record.connectionStatus = 0;
-        ArrayList<android.hardware.radio.V1_2.CellInfo> records =
-                new ArrayList<android.hardware.radio.V1_2.CellInfo>();
+        ArrayList<Object> records = new ArrayList<>();
         records.add(record);
-        return RIL.convertHalCellInfoList_1_2(records);
+        return RILUtils.convertHalCellInfoList(records);
     }
 
     private ArrayList<CellInfo> getCellInfoListForGSM(
@@ -2539,11 +2510,10 @@
         record.timeStamp = TIMESTAMP;
         record.gsm.add(cellinfo);
         record.connectionStatus = 0;
-        ArrayList<android.hardware.radio.V1_2.CellInfo> records =
-                new ArrayList<android.hardware.radio.V1_2.CellInfo>();
+        ArrayList<Object> records = new ArrayList<>();
         records.add(record);
 
-        return RIL.convertHalCellInfoList_1_2(records);
+        return RILUtils.convertHalCellInfoList(records);
     }
 
     private static void initializeCellIdentityWcdma_1_2(
@@ -2582,11 +2552,10 @@
         record.timeStamp = TIMESTAMP;
         record.wcdma.add(cellinfo);
         record.connectionStatus = 0;
-        ArrayList<android.hardware.radio.V1_2.CellInfo> records =
-                new ArrayList<android.hardware.radio.V1_2.CellInfo>();
+        ArrayList<Object> records = new ArrayList<>();
         records.add(record);
 
-        return RIL.convertHalCellInfoList_1_2(records);
+        return RILUtils.convertHalCellInfoList(records);
     }
 
     private ArrayList<CellInfo> getCellInfoListForCdma(String alphaLong, String alphaShort) {
@@ -2611,11 +2580,10 @@
         record.timeStamp = TIMESTAMP;
         record.cdma.add(cellinfo);
         record.connectionStatus = 0;
-        ArrayList<android.hardware.radio.V1_2.CellInfo> records =
-                new ArrayList<android.hardware.radio.V1_2.CellInfo>();
+        ArrayList<Object> records = new ArrayList<>();
         records.add(record);
 
-        return RIL.convertHalCellInfoList_1_2(records);
+        return RILUtils.convertHalCellInfoList(records);
     }
 
     @Test
@@ -2750,7 +2718,7 @@
         expected.add(c4);
         expected.add(c5);
 
-        ArrayList<Carrier> result = RIL.createCarrierRestrictionList(carriers);
+        ArrayList<Carrier> result = RILUtils.convertToHalCarrierRestrictionList(carriers);
 
         assertTrue(result.equals(expected));
     }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/RadioConfigResponseTest.java b/tests/telephonytests/src/com/android/internal/telephony/RadioConfigResponseTest.java
index f069262..281c900 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/RadioConfigResponseTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/RadioConfigResponseTest.java
@@ -44,43 +44,53 @@
 
     @Test
     public void testVersion_1_5() {
-        Set<String> caps = RadioConfigResponse.getCaps(RIL.RADIO_HAL_VERSION_1_5, false);
+        Set<String> caps = RILUtils.getCaps(RIL.RADIO_HAL_VERSION_1_5, false);
         assertFalse(
                 caps.contains(TelephonyManager.CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE));
         assertFalse(
-                caps.contains(TelephonyManager.CAPABILITY_ALLOWED_NETWORK_TYPES_USED));
+                caps.contains(TelephonyManager.CAPABILITY_USES_ALLOWED_NETWORK_TYPES_BITMASK));
         assertFalse(
                 caps.contains(
                         TelephonyManager.CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE));
         assertFalse(
                 caps.contains(TelephonyManager.CAPABILITY_THERMAL_MITIGATION_DATA_THROTTLING));
+        assertFalse(
+                caps.contains(TelephonyManager.CAPABILITY_SLICING_CONFIG_SUPPORTED));
     }
 
     @Test
     public void testReducedFeatureSet() {
-        Set<String> caps = RadioConfigResponse.getCaps(RIL.RADIO_HAL_VERSION_1_6, true);
+        Set<String> caps = RILUtils.getCaps(RIL.RADIO_HAL_VERSION_1_6, true);
         assertFalse(
                 caps.contains(TelephonyManager.CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE));
         assertTrue(
-                caps.contains(TelephonyManager.CAPABILITY_ALLOWED_NETWORK_TYPES_USED));
+                caps.contains(TelephonyManager.CAPABILITY_USES_ALLOWED_NETWORK_TYPES_BITMASK));
         assertFalse(
                 caps.contains(
                         TelephonyManager.CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE));
         assertFalse(
                 caps.contains(TelephonyManager.CAPABILITY_THERMAL_MITIGATION_DATA_THROTTLING));
+        assertFalse(
+                caps.contains(TelephonyManager.CAPABILITY_SLICING_CONFIG_SUPPORTED));
+        assertTrue(
+                caps.contains(TelephonyManager.CAPABILITY_SIM_PHONEBOOK_IN_MODEM));
     }
 
     @Test
     public void testNonReducedFeatureSet() {
-        Set<String> caps = RadioConfigResponse.getCaps(RIL.RADIO_HAL_VERSION_1_6, false);
+        Set<String> caps = RILUtils.getCaps(RIL.RADIO_HAL_VERSION_1_6, false);
         assertTrue(
                 caps.contains(TelephonyManager.CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE));
         assertTrue(
-                caps.contains(TelephonyManager.CAPABILITY_ALLOWED_NETWORK_TYPES_USED));
+                caps.contains(TelephonyManager.CAPABILITY_USES_ALLOWED_NETWORK_TYPES_BITMASK));
         assertTrue(
                 caps.contains(
                         TelephonyManager.CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE));
         assertTrue(
                 caps.contains(TelephonyManager.CAPABILITY_THERMAL_MITIGATION_DATA_THROTTLING));
+        assertTrue(
+                caps.contains(TelephonyManager.CAPABILITY_SLICING_CONFIG_SUPPORTED));
+        assertFalse(
+                caps.contains(TelephonyManager.CAPABILITY_SIM_PHONEBOOK_IN_MODEM));
     }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java
index 62e0467..5cfff52 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java
@@ -56,7 +56,6 @@
 import android.os.PersistableBundle;
 import android.os.Process;
 import android.os.SystemClock;
-import android.os.TimestampedValue;
 import android.os.UserHandle;
 import android.os.WorkSource;
 import android.telephony.AccessNetworkConstants;
@@ -70,13 +69,7 @@
 import android.telephony.CellIdentityWcdma;
 import android.telephony.CellInfo;
 import android.telephony.CellInfoGsm;
-import android.telephony.CellSignalStrength;
-import android.telephony.CellSignalStrengthCdma;
 import android.telephony.CellSignalStrengthGsm;
-import android.telephony.CellSignalStrengthLte;
-import android.telephony.CellSignalStrengthNr;
-import android.telephony.CellSignalStrengthTdscdma;
-import android.telephony.CellSignalStrengthWcdma;
 import android.telephony.INetworkService;
 import android.telephony.LteVopsSupportInfo;
 import android.telephony.NetworkRegistrationInfo;
@@ -84,12 +77,12 @@
 import android.telephony.NrVopsSupportInfo;
 import android.telephony.PhysicalChannelConfig;
 import android.telephony.ServiceState;
-import android.telephony.SignalStrength;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.telephony.cdma.CdmaCellLocation;
 import android.telephony.gsm.GsmCellLocation;
+import android.telephony.ims.stub.ImsRegistrationImplBase;
 import android.test.suitebuilder.annotation.MediumTest;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.text.TextUtils;
@@ -139,6 +132,10 @@
     @Mock
     private ServiceStateStats mServiceStateStats;
 
+    // SST now delegates all signal strength operations to SSC
+    // Add Mock SSC as the dependency to avoid NPE
+    private SignalStrengthController mSsc;
+
     private ServiceStateTracker sst;
     private ServiceStateTrackerTestHandler mSSTTestHandler;
     private PersistableBundle mBundle;
@@ -161,6 +158,7 @@
     private static final String CARRIER_NAME_DISPLAY_NO_SERVICE = "No service";
     private static final String CARRIER_NAME_DISPLAY_EMERGENCY_CALL = "emergency call";
     private static final String WIFI_CALLING_VOICE_FORMAT = "%s wifi calling";
+    private static final String CROSS_SIM_CALLING_VOICE_FORMAT = "%s Cross-SIM Calling";
     private static final String WIFI_CALLING_DATA_FORMAT = "%s wifi data";
     private static final String WIFI_CALLING_FLIGHT_MODE_FORMAT = "%s flight mode";
 
@@ -187,6 +185,9 @@
 
         @Override
         public void onLooperPrepared() {
+            mSsc = new SignalStrengthController(mPhone);
+            doReturn(mSsc).when(mPhone).getSignalStrengthController();
+
             sst = new ServiceStateTracker(mPhone, mSimulatedCommands);
             sst.setServiceStateStats(mServiceStateStats);
             doReturn(sst).when(mPhone).getServiceStateTracker();
@@ -695,58 +696,6 @@
         verify(mPhone, times(1)).notifyServiceStateChanged(any(ServiceState.class));
     }
 
-    private void sendSignalStrength(SignalStrength ss) {
-        mSimulatedCommands.setSignalStrength(ss);
-        mSimulatedCommands.notifySignalStrength();
-        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
-    }
-
-    @Test
-    @MediumTest
-    public void testSignalStrength() {
-        // Send in GSM Signal Strength Info and expect isGsm == true
-        SignalStrength ss = new SignalStrength(
-                new CellSignalStrengthCdma(),
-                new CellSignalStrengthGsm(-53, 0, SignalStrength.INVALID),
-                new CellSignalStrengthWcdma(),
-                new CellSignalStrengthTdscdma(),
-                new CellSignalStrengthLte(),
-                new CellSignalStrengthNr());
-
-        sendSignalStrength(ss);
-        assertEquals(sst.getSignalStrength(), ss);
-        assertEquals(sst.getSignalStrength().isGsm(), true);
-
-        // Send in CDMA+LTE Signal Strength Info and expect isGsm == true
-        ss = new SignalStrength(
-                new CellSignalStrengthCdma(-90, -12,
-                        SignalStrength.INVALID, SignalStrength.INVALID, SignalStrength.INVALID),
-                new CellSignalStrengthGsm(),
-                new CellSignalStrengthWcdma(),
-                new CellSignalStrengthTdscdma(),
-                new CellSignalStrengthLte(
-                        -110, -114, -5, 0, SignalStrength.INVALID, SignalStrength.INVALID),
-                new CellSignalStrengthNr());
-
-        sendSignalStrength(ss);
-        assertEquals(sst.getSignalStrength(), ss);
-        assertEquals(sst.getSignalStrength().isGsm(), true);
-
-        // Send in CDMA-only Signal Strength Info and expect isGsm == false
-        ss = new SignalStrength(
-                new CellSignalStrengthCdma(-90, -12,
-                        SignalStrength.INVALID, SignalStrength.INVALID, SignalStrength.INVALID),
-                new CellSignalStrengthGsm(),
-                new CellSignalStrengthWcdma(),
-                new CellSignalStrengthTdscdma(),
-                new CellSignalStrengthLte(),
-                new CellSignalStrengthNr());
-
-        sendSignalStrength(ss);
-        assertEquals(sst.getSignalStrength(), ss);
-        assertEquals(sst.getSignalStrength().isGsm(), false);
-    }
-
     private void sendCarrierConfigUpdate() {
         CarrierConfigManager mockConfigManager = Mockito.mock(CarrierConfigManager.class);
         when(mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE))
@@ -760,182 +709,6 @@
     }
 
     @Test
-    public void testLteSignalStrengthReportingCriteria() {
-        SignalStrength ss = new SignalStrength(
-                new CellSignalStrengthCdma(),
-                new CellSignalStrengthGsm(),
-                new CellSignalStrengthWcdma(),
-                new CellSignalStrengthTdscdma(),
-                new CellSignalStrengthLte(
-                        -110, /* rssi */
-                        -114, /* rsrp */
-                        -5, /* rsrq */
-                        0, /* rssnr */
-                        SignalStrength.INVALID, /* cqi */
-                        SignalStrength.INVALID /* ta */),
-                new CellSignalStrengthNr());
-
-        mBundle.putBoolean(CarrierConfigManager.KEY_USE_ONLY_RSRP_FOR_LTE_SIGNAL_BAR_BOOL,
-                true);
-
-        sendCarrierConfigUpdate();
-
-        mSimulatedCommands.setSignalStrength(ss);
-        mSimulatedCommands.notifySignalStrength();
-        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
-        // Default thresholds are POOR=-115 MODERATE=-105 GOOD=-95 GREAT=-85
-        assertEquals(CellSignalStrength.SIGNAL_STRENGTH_POOR, sst.getSignalStrength().getLevel());
-
-        int[] lteThresholds = {
-                -130, // SIGNAL_STRENGTH_POOR
-                -120, // SIGNAL_STRENGTH_MODERATE
-                -110, // SIGNAL_STRENGTH_GOOD
-                -100,  // SIGNAL_STRENGTH_GREAT
-        };
-        mBundle.putIntArray(CarrierConfigManager.KEY_LTE_RSRP_THRESHOLDS_INT_ARRAY,
-                lteThresholds);
-        sendCarrierConfigUpdate();
-
-        mSimulatedCommands.setSignalStrength(ss);
-        mSimulatedCommands.notifySignalStrength();
-        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
-        assertEquals(sst.getSignalStrength().getLevel(),
-                CellSignalStrength.SIGNAL_STRENGTH_MODERATE);
-    }
-
-    @Test
-    public void test5gNrSignalStrengthReportingCriteria_UseSsRsrp() {
-        SignalStrength ss = new SignalStrength(
-                new CellSignalStrengthCdma(),
-                new CellSignalStrengthGsm(),
-                new CellSignalStrengthWcdma(),
-                new CellSignalStrengthTdscdma(),
-                new CellSignalStrengthLte(),
-                new CellSignalStrengthNr(
-                    -139, /** csiRsrp NONE */
-                    -20, /** csiRsrq NONE */
-                    -23, /** CsiSinr NONE */
-                    -44, /** SsRsrp SIGNAL_STRENGTH_GREAT */
-                    -20, /** SsRsrq NONE */
-                    -23) /** SsSinr NONE */
-         );
-
-        // SSRSRP = 1 << 0
-        mBundle.putInt(CarrierConfigManager.KEY_PARAMETERS_USE_FOR_5G_NR_SIGNAL_BAR_INT,
-                CellSignalStrengthNr.USE_SSRSRP);
-        sendCarrierConfigUpdate();
-        mSimulatedCommands.setSignalStrength(ss);
-        mSimulatedCommands.notifySignalStrength();
-        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
-        assertEquals(CellSignalStrength.SIGNAL_STRENGTH_GREAT, sst.getSignalStrength().getLevel());
-    }
-
-    @Test
-    public void test5gNrSignalStrengthReportingCriteria_UseSsRsrpAndSsRsrq() {
-        SignalStrength ss = new SignalStrength(
-                new CellSignalStrengthCdma(),
-                new CellSignalStrengthGsm(),
-                new CellSignalStrengthWcdma(),
-                new CellSignalStrengthTdscdma(),
-                new CellSignalStrengthLte(),
-                new CellSignalStrengthNr(
-                    -139, /** csiRsrp NONE */
-                    -20, /** csiRsrq NONE */
-                    -23, /** CsiSinr NONE */
-                    -44, /** SsRsrp SIGNAL_STRENGTH_GREAT */
-                    -32, /** SsRsrq NONE */
-                    -23) /** SsSinr NONE */
-        );
-
-        // SSRSRP = 1 << 0 | SSSINR = 1 << 2
-        mBundle.putInt(CarrierConfigManager.KEY_PARAMETERS_USE_FOR_5G_NR_SIGNAL_BAR_INT,
-                CellSignalStrengthNr.USE_SSRSRP | CellSignalStrengthNr.USE_SSRSRQ);
-        sendCarrierConfigUpdate();
-        mSimulatedCommands.setSignalStrength(ss);
-        mSimulatedCommands.notifySignalStrength();
-        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
-        assertEquals(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
-                sst.getSignalStrength().getLevel());
-    }
-
-    @Test
-    public void test5gNrSignalStrengthReportingCriteria_ConfiguredThresholds() {
-        SignalStrength ss = new SignalStrength(
-                new CellSignalStrengthCdma(),
-                new CellSignalStrengthGsm(),
-                new CellSignalStrengthWcdma(),
-                new CellSignalStrengthTdscdma(),
-                new CellSignalStrengthLte(),
-                new CellSignalStrengthNr(
-                    -139, /** csiRsrp NONE */
-                    -20, /** csiRsrq NONE */
-                    -23, /** CsiSinr NONE */
-                    -44, /** SsRsrp SIGNAL_STRENGTH_GREAT */
-                    -20, /** SsRsrq NONE */
-                    -23) /** SsSinr NONE */
-        );
-
-        // SSRSRP = 1 << 0
-        mBundle.putInt(CarrierConfigManager.KEY_PARAMETERS_USE_FOR_5G_NR_SIGNAL_BAR_INT,
-                CellSignalStrengthNr.USE_SSRSRP);
-        sendCarrierConfigUpdate();
-        mSimulatedCommands.setSignalStrength(ss);
-        mSimulatedCommands.notifySignalStrength();
-        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
-        assertEquals(CellSignalStrength.SIGNAL_STRENGTH_GREAT, sst.getSignalStrength().getLevel());
-
-        int[] nrSsRsrpThresholds = {
-                -45, // SIGNAL_STRENGTH_POOR
-                -40, // SIGNAL_STRENGTH_MODERATE
-                -37, // SIGNAL_STRENGTH_GOOD
-                -34,  // SIGNAL_STRENGTH_GREAT
-        };
-        mBundle.putIntArray(CarrierConfigManager.KEY_5G_NR_SSRSRP_THRESHOLDS_INT_ARRAY,
-                nrSsRsrpThresholds);
-        mBundle.putInt(CarrierConfigManager.KEY_PARAMETERS_USE_FOR_5G_NR_SIGNAL_BAR_INT,
-                CellSignalStrengthNr.USE_SSRSRP);
-        sendCarrierConfigUpdate();
-        mSimulatedCommands.setSignalStrength(ss);
-        mSimulatedCommands.notifySignalStrength();
-        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
-        assertEquals(CellSignalStrength.SIGNAL_STRENGTH_POOR,
-                sst.getSignalStrength().getLevel());
-    }
-
-    @Test
-    public void testWcdmaSignalStrengthReportingCriteria() {
-        SignalStrength ss = new SignalStrength(
-                new CellSignalStrengthCdma(),
-                new CellSignalStrengthGsm(),
-                new CellSignalStrengthWcdma(-79, 0, -85, -5),
-                new CellSignalStrengthTdscdma(),
-                new CellSignalStrengthLte(),
-                new CellSignalStrengthNr());
-
-        mSimulatedCommands.setSignalStrength(ss);
-        mSimulatedCommands.notifySignalStrength();
-        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
-        assertEquals(sst.getSignalStrength().getLevel(), CellSignalStrength.SIGNAL_STRENGTH_GOOD);
-
-        int[] wcdmaThresholds = {
-                -110, // SIGNAL_STRENGTH_POOR
-                -100, // SIGNAL_STRENGTH_MODERATE
-                -90, // SIGNAL_STRENGTH_GOOD
-                -80  // SIGNAL_STRENGTH_GREAT
-        };
-        mBundle.putIntArray(CarrierConfigManager.KEY_WCDMA_RSCP_THRESHOLDS_INT_ARRAY,
-                wcdmaThresholds);
-        mBundle.putString(
-                CarrierConfigManager.KEY_WCDMA_DEFAULT_SIGNAL_STRENGTH_MEASUREMENT_STRING,
-                "rscp");
-        sendCarrierConfigUpdate();
-        mSimulatedCommands.setSignalStrength(ss);
-        mSimulatedCommands.notifySignalStrength();
-        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
-        assertEquals(sst.getSignalStrength().getLevel(), CellSignalStrength.SIGNAL_STRENGTH_GOOD);
-    }
-
-    @Test
     @MediumTest
     // TODO(nharold): we probably should remove support for this procedure (GET_LOC)
     public void testGsmCellLocation() {
@@ -2037,7 +1810,7 @@
 
     @Test
     @SmallTest
-    public void testSetTimeFromNITZStr() throws Exception {
+    public void testSetTimeFromNITZStr_withoutAge() throws Exception {
         {
             // Mock sending incorrect nitz str from RIL
             mSimulatedCommands.triggerNITZupdate("38/06/20,00:00:00+0");
@@ -2045,21 +1818,55 @@
             verify(mNitzStateMachine, times(0)).handleNitzReceived(any());
         }
         {
-            // Mock sending correct nitz str from RIL
+            // Mock sending correct nitz str from RIL with a zero ageMs
             String nitzStr = "15/06/20,00:00:00+0";
             NitzData expectedNitzData = NitzData.parse(nitzStr);
             mSimulatedCommands.triggerNITZupdate(nitzStr);
             waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
 
-            ArgumentCaptor<TimestampedValue<NitzData>> argumentsCaptor =
-                    ArgumentCaptor.forClass(TimestampedValue.class);
+            ArgumentCaptor<NitzSignal> argumentsCaptor =
+                    ArgumentCaptor.forClass(NitzSignal.class);
             verify(mNitzStateMachine, times(1))
                     .handleNitzReceived(argumentsCaptor.capture());
 
             // Confirm the argument was what we expected.
-            TimestampedValue<NitzData> actualNitzSignal = argumentsCaptor.getValue();
-            assertEquals(expectedNitzData, actualNitzSignal.getValue());
-            assertTrue(actualNitzSignal.getReferenceTimeMillis() <= SystemClock.elapsedRealtime());
+            NitzSignal actualNitzSignal = argumentsCaptor.getValue();
+            assertEquals(expectedNitzData, actualNitzSignal.getNitzData());
+            assertTrue(actualNitzSignal.getReceiptElapsedRealtimeMillis()
+                    <= SystemClock.elapsedRealtime());
+            assertEquals(actualNitzSignal.getAgeMillis(), 0);
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void testSetTimeFromNITZStr_withAge() throws Exception {
+        {
+            // Mock sending incorrect nitz str from RIL with a non-zero ageMs
+            long ageMs = 60 * 1000;
+            mSimulatedCommands.triggerNITZupdate("38/06/20,00:00:00+0", ageMs);
+            waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
+            verify(mNitzStateMachine, times(0)).handleNitzReceived(any());
+        }
+        {
+            // Mock sending correct nitz str from RIL with a non-zero ageMs
+            String nitzStr = "21/08/15,00:00:00+0";
+            long ageMs = 60 * 1000;
+            NitzData expectedNitzData = NitzData.parse(nitzStr);
+            mSimulatedCommands.triggerNITZupdate(nitzStr, ageMs);
+            waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
+
+            ArgumentCaptor<NitzSignal> argumentsCaptor =
+                    ArgumentCaptor.forClass(NitzSignal.class);
+            verify(mNitzStateMachine, times(1))
+                    .handleNitzReceived(argumentsCaptor.capture());
+
+            // Confirm the argument was what we expected.
+            NitzSignal actualNitzSignal = argumentsCaptor.getValue();
+            assertEquals(expectedNitzData, actualNitzSignal.getNitzData());
+            assertTrue(actualNitzSignal.getReceiptElapsedRealtimeMillis()
+                    <= SystemClock.elapsedRealtime());
+            assertEquals(actualNitzSignal.getAgeMillis(), ageMs);
         }
     }
 
@@ -2516,6 +2323,7 @@
         doReturn(true).when(mPhone).isPhoneTypeCdmaLte();
         doReturn(CdmaSubscriptionSourceManager.SUBSCRIPTION_FROM_RUIM).when(mCdmaSSM)
                 .getCdmaSubscriptionSource();
+        doReturn(PHONE_ID).when(mPhone).getPhoneId();
 
         logd("Calling updatePhoneType");
         // switch to CDMA
@@ -2786,6 +2594,40 @@
     }
 
     @Test
+    public void testUpdateSpnDisplay_spnNotEmptyAndCrossSimCallingEnabled_showSpnOnly() {
+        // GSM phone
+
+        doReturn(true).when(mPhone).isPhoneTypeGsm();
+
+        // In Service
+        ServiceState ss = new ServiceState();
+        ss.setVoiceRegState(ServiceState.STATE_IN_SERVICE);
+        ss.setDataRegState(ServiceState.STATE_IN_SERVICE);
+        sst.mSS = ss;
+
+        // cross-sim-calling is enable
+        doReturn(mImsPhone).when(mPhone).getImsPhone();
+        doReturn(ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM).when(mImsPhone)
+                .getImsRegistrationTech();
+        String[] formats = {CROSS_SIM_CALLING_VOICE_FORMAT, "%s"};
+        Resources r = mContext.getResources();
+        doReturn(formats).when(r).getStringArray(anyInt());
+
+        // update the spn
+        sst.updateSpnDisplay();
+
+        // Only spn should be shown
+        String spn = mBundle.getString(CarrierConfigManager.KEY_CARRIER_NAME_STRING);
+        Bundle b = getExtrasFromLastSpnUpdateIntent();
+        assertThat(b.getString(TelephonyManager.EXTRA_SPN))
+                .isEqualTo(String.format(CROSS_SIM_CALLING_VOICE_FORMAT, spn));
+        assertThat(b.getBoolean(TelephonyManager.EXTRA_SHOW_SPN)).isTrue();
+        assertThat(b.getString(TelephonyManager.EXTRA_DATA_SPN))
+                .isEqualTo(String.format(CROSS_SIM_CALLING_VOICE_FORMAT, spn));
+        assertThat(b.getBoolean(TelephonyManager.EXTRA_SHOW_PLMN)).isFalse();
+    }
+
+    @Test
     public void testUpdateSpnDisplay_spnNotEmptyAndWifiCallingEnabled_showSpnOnly() {
         // GSM phone
         doReturn(true).when(mPhone).isPhoneTypeGsm();
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SignalStrengthControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/SignalStrengthControllerTest.java
new file mode 100644
index 0000000..1f22352
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/SignalStrengthControllerTest.java
@@ -0,0 +1,652 @@
+/*
+ * 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.SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI;
+import static android.telephony.SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSSINR;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.Message;
+import android.os.PersistableBundle;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.CarrierConfigManager;
+import android.telephony.CellSignalStrength;
+import android.telephony.CellSignalStrengthCdma;
+import android.telephony.CellSignalStrengthGsm;
+import android.telephony.CellSignalStrengthLte;
+import android.telephony.CellSignalStrengthNr;
+import android.telephony.CellSignalStrengthTdscdma;
+import android.telephony.CellSignalStrengthWcdma;
+import android.telephony.SignalStrength;
+import android.telephony.SignalStrengthUpdateRequest;
+import android.telephony.SignalThresholdInfo;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Unit test for {@link SignalStrengthUpdateRequest}.
+ */
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class SignalStrengthControllerTest extends TelephonyTest {
+
+    private static final String TAG = "SignalStrengthControllerTest";
+
+    private static final int ACTIVE_SUB_ID = 0;
+    private static final int INVALID_SUB_ID = 1000;
+    private static final int CALLING_UID = 12345;
+    private static final int PHONE_ID = 0;
+    private static final String HOME_PLMN = "310260";
+    private static final String PLMN1 = "480123";
+    private static final String PLMN2 = "586111";
+    private static final String HOME_PNN = "home pnn";
+    private static final String[] CARRIER_CONFIG_SPDI = new String[] {HOME_PLMN, PLMN2};
+    private static final String[] CARRIER_CONFIG_EHPLMN = new String[] {HOME_PLMN, PLMN1};
+    private static final String[] CARRIER_CONFIG_PNN = new String[] {
+            String.format("%s,%s", HOME_PNN, "short"), "f2,s2"
+    };
+
+    @Mock
+    private Handler mHandler;
+
+    private SignalStrengthController mSsc;
+    private PersistableBundle mBundle;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp(TAG);
+
+        when(mPhone.getSubId()).thenReturn(ACTIVE_SUB_ID);
+        mSsc = new SignalStrengthController(mPhone);
+        replaceInstance(Handler.class, "mLooper", mHandler, mSsc.getLooper());
+        replaceInstance(Phone.class, "mLooper", mPhone, mSsc.getLooper());
+
+        mBundle = mContextFixture.getCarrierConfigBundle();
+        mBundle.putIntArray(CarrierConfigManager.KEY_5G_NR_SSRSRP_THRESHOLDS_INT_ARRAY,
+                new int[] {
+                        -110, /* SIGNAL_STRENGTH_POOR */
+                        -90, /* SIGNAL_STRENGTH_MODERATE */
+                        -80, /* SIGNAL_STRENGTH_GOOD */
+                        -65,  /* SIGNAL_STRENGTH_GREAT */
+                });
+        mBundle.putIntArray(CarrierConfigManager.KEY_5G_NR_SSRSRQ_THRESHOLDS_INT_ARRAY,
+                new int[] {
+                        -31, /* SIGNAL_STRENGTH_POOR */
+                        -19, /* SIGNAL_STRENGTH_MODERATE */
+                        -7, /* SIGNAL_STRENGTH_GOOD */
+                        6  /* SIGNAL_STRENGTH_GREAT */
+                });
+        mBundle.putIntArray(CarrierConfigManager.KEY_5G_NR_SSSINR_THRESHOLDS_INT_ARRAY,
+                new int[] {
+                        -5, /* SIGNAL_STRENGTH_POOR */
+                        5, /* SIGNAL_STRENGTH_MODERATE */
+                        15, /* SIGNAL_STRENGTH_GOOD */
+                        30  /* SIGNAL_STRENGTH_GREAT */
+                });
+        processAllMessages();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mSsc = null;
+        super.tearDown();
+    }
+
+    /**
+     * Verify that SignalStrengthUpdateRequest with invalid subId should trigger
+     * setAlwaysReportSignalStrength with false.
+     */
+    @Test
+    public void updateAlwaysReportSignalStrength_requestWithInvalidSubId_shouldBeFalse() {
+        SignalStrengthUpdateRequest request = createTestSignalStrengthUpdateRequest(
+                createTestSignalThresholdInfo(),
+                true /* shouldReportWhileIdle*/,
+                true /* shouldReportSystemWhileIdle */
+        );
+
+        mSsc.setSignalStrengthUpdateRequest(INVALID_SUB_ID, CALLING_UID,
+                request, Message.obtain(mHandler));
+        processAllMessages();
+
+        verify(mPhone).setAlwaysReportSignalStrength(eq(false));
+    }
+
+    /**
+     * Verify that with a valid subId, SignalStrengthUpdateRequest asking to report signal while
+     * idle should trigger setAlwaysReportSignalStrength with true.
+     */
+    @Test
+    public void updateAlwaysReportSignalStrength_requestReportWhileIdle_shouldBeTrue() {
+        SignalStrengthUpdateRequest request = createTestSignalStrengthUpdateRequest(
+                createTestSignalThresholdInfo(),
+                true /* shouldReportWhileIdle*/,
+                false /* shouldReportSystemWhileIdle */
+        );
+
+        mSsc.setSignalStrengthUpdateRequest(ACTIVE_SUB_ID, CALLING_UID,
+                request, Message.obtain(mHandler));
+        processAllMessages();
+
+        verify(mPhone).setAlwaysReportSignalStrength(eq(true));
+    }
+
+    /**
+     * Verify that with a valid subId, SignalStrengthUpdateRequest asking to report system signal
+     * while idle should trigger setAlwaysReportSignalStrength with true.
+     */
+    @Test
+    public void updateAlwaysReportSignalStrength_requestReportSystemWhileIdle_shouldBeTrue() {
+        SignalStrengthUpdateRequest request = createTestSignalStrengthUpdateRequest(
+                createTestSignalThresholdInfo(),
+                false /* shouldReportWhileIdle*/,
+                true /* shouldReportSystemWhileIdle */
+        );
+
+        mSsc.setSignalStrengthUpdateRequest(ACTIVE_SUB_ID, CALLING_UID,
+                request, Message.obtain(mHandler));
+        processAllMessages();
+
+        verify(mPhone).setAlwaysReportSignalStrength(eq(true));
+    }
+
+    /**
+     * Verify that when device is high powered, shouldHonorSystemThresholds should return true.
+     */
+    @Test
+    public void shouldHonorSystemThresholds_deviceIsHighPowered_returnTrue() {
+        when(mPhone.isDeviceIdle()).thenReturn(false);
+
+        assertThat(mSsc.shouldHonorSystemThresholds()).isTrue();
+    }
+
+    /**
+     * Verify that when device is idle and no SignalUpdateRequest received before,
+     * shouldHonorSystemThresholds should return false.
+     */
+    @Test
+    public void shouldHonorSystemThresholds_deviceIdle_noSignalRequest_returnTrue() {
+        when(mPhone.isDeviceIdle()).thenReturn(true);
+
+        assertThat(mSsc.shouldHonorSystemThresholds()).isFalse();
+    }
+
+    /**
+     * Verify that when device is idle and with SignalUpdateRequest to report system threshold
+     * received before, shouldHonorSystemThresholds should return false.
+     */
+    @Test
+    public void shouldHonorSystemThresholds_deviceIdle_systemSignalRequest_returnTrue() {
+        when(mPhone.isDeviceIdle()).thenReturn(true);
+
+        SignalStrengthUpdateRequest request = createTestSignalStrengthUpdateRequest(
+                createTestSignalThresholdInfo(),
+                false /* shouldReportWhileIdle*/,
+                true /* shouldReportSystemWhileIdle */
+        );
+        mSsc.setSignalStrengthUpdateRequest(ACTIVE_SUB_ID, CALLING_UID,
+                request, Message.obtain(mHandler));
+        processAllMessages();
+
+        assertThat(mSsc.shouldHonorSystemThresholds()).isTrue();
+    }
+
+    /**
+     * Verify that when no SignalUpdateRequest received, shouldEnableSignalThresholdForAppRequest
+     * should return false.
+     */
+    @Test
+    public void shouldEnableSignalThresholdForAppRequest_noRequest_returnFalse() {
+        assertThat(mSsc.shouldEnableSignalThresholdForAppRequest(
+                AccessNetworkConstants.AccessNetworkType.GERAN,
+                SIGNAL_MEASUREMENT_TYPE_RSSI,
+                ACTIVE_SUB_ID,
+                false /* isDeviceIdle */
+        )).isFalse();
+    }
+
+    /**
+     * Verify that in high power mode, the shouldEnableSignalThresholdForAppRequest should return
+     * true if the queried ran/measurement/subId parameters match exist SignalUpdateRecord.
+     */
+    @Test
+    public void shouldEnableSignalThresholdForAppRequest_highPowered_matchedRequest_returnTrue() {
+        SignalStrengthUpdateRequest request = createTestSignalStrengthUpdateRequest(
+                createTestSignalThresholdInfo(),
+                false /* shouldReportWhileIdle*/,
+                false /* shouldReportSystemWhileIdle */
+        );
+        mSsc.setSignalStrengthUpdateRequest(ACTIVE_SUB_ID, CALLING_UID,
+                request, Message.obtain(mHandler));
+        processAllMessages();
+
+        assertThat(mSsc.shouldEnableSignalThresholdForAppRequest(
+                AccessNetworkConstants.AccessNetworkType.GERAN,
+                SIGNAL_MEASUREMENT_TYPE_RSSI,
+                ACTIVE_SUB_ID,
+                false /* isDeviceIdle */
+        )).isTrue();
+    }
+
+    /**
+     * Verify that in idle mode, the shouldEnableSignalThresholdForAppRequest should return
+     * false if the queried ran/measurement/subId parameters match exist SignalUpdateRequest which
+     * did not ask to report signal while idle.
+     */
+    @Test
+    public void enableSignalThresholdForAppRequest_idle_noReportInIdle_returnTrue() {
+        SignalStrengthUpdateRequest request = createTestSignalStrengthUpdateRequest(
+                createTestSignalThresholdInfo(),
+                false /* shouldReportWhileIdle*/,
+                false /* shouldReportSystemWhileIdle */
+        );
+        mSsc.setSignalStrengthUpdateRequest(ACTIVE_SUB_ID, CALLING_UID,
+                request, Message.obtain(mHandler));
+        processAllMessages();
+
+        assertThat(mSsc.shouldEnableSignalThresholdForAppRequest(
+                AccessNetworkConstants.AccessNetworkType.GERAN,
+                SIGNAL_MEASUREMENT_TYPE_RSSI,
+                ACTIVE_SUB_ID,
+                true /* isDeviceIdle */
+        )).isFalse();
+    }
+
+    /**
+     * Verify that in idle mode, the shouldEnableSignalThresholdForAppRequest should return
+     * true if the queried ran/measurement/subId parameters match exist SignalUpdateRecord which
+     * request to report signal while idle.
+     */
+    @Test
+    public void shouldEnableSignalThresholdForAppRequest_idle_reportInIdle_returnTrue() {
+        SignalStrengthUpdateRequest request = createTestSignalStrengthUpdateRequest(
+                createTestSignalThresholdInfo(),
+                true /* shouldReportWhileIdle*/,
+                false /* shouldReportSystemWhileIdle */
+        );
+        mSsc.setSignalStrengthUpdateRequest(ACTIVE_SUB_ID, CALLING_UID,
+                request, Message.obtain(mHandler));
+        processAllMessages();
+
+        assertThat(mSsc.shouldEnableSignalThresholdForAppRequest(
+                AccessNetworkConstants.AccessNetworkType.GERAN,
+                SIGNAL_MEASUREMENT_TYPE_RSSI,
+                ACTIVE_SUB_ID,
+                true /* isDeviceIdle */
+        )).isTrue();
+    }
+
+    /**
+     * Verify that in idle mode, the shouldEnableSignalThresholdForAppRequest should return
+     * true if the queried ran/measurement/subId parameters match exist SignalUpdateRecord which
+     * request to report system signal while idle.
+     */
+    @Test
+    public void shouldEnableSignalThresholdForAppRequest_idle_reportSystemInIdle_returnTrue() {
+        SignalStrengthUpdateRequest request = createTestSignalStrengthUpdateRequest(
+                createTestSignalThresholdInfo(),
+                false /* shouldReportWhileIdle*/,
+                true /* shouldReportSystemWhileIdle */
+        );
+        mSsc.setSignalStrengthUpdateRequest(ACTIVE_SUB_ID, CALLING_UID,
+                request, Message.obtain(mHandler));
+        processAllMessages();
+
+        assertThat(mSsc.shouldEnableSignalThresholdForAppRequest(
+                AccessNetworkConstants.AccessNetworkType.GERAN,
+                SIGNAL_MEASUREMENT_TYPE_RSSI,
+                ACTIVE_SUB_ID,
+                true /* isDeviceIdle */
+        )).isTrue();
+    }
+
+    @Test
+    public void getConsolidatedSignalThresholds_consolidateAppsThresholdsWithSystem() {
+        when(mPhone.isDeviceIdle()).thenReturn(false);
+
+        final int ran = AccessNetworkConstants.AccessNetworkType.NGRAN;
+        final int measurement = SIGNAL_MEASUREMENT_TYPE_SSSINR;
+        final int[] systemThresholds = new int[]{0, 10, 20, 30};
+        final int hysteresis = 2;
+
+        // Map key is the candidate thresholds from application, map value is the expected
+        // consolidated thresholds with systemThresholds.
+        Map<int[], int[]> cases = Map.of(
+                new int[]{-3, -6}, new int[]{-6, -3, 0, 10, 20, 30},
+                new int[]{34, 39}, new int[]{0, 10, 20, 30, 34, 39},
+                new int[]{-5, 4, 13, 23, 33}, new int[]{-5, 0, 4, 10, 13, 20, 23, 30, 33},
+                new int[]{9, 10, 11, 12}, new int[]{0, 10, 20, 30},
+                new int[]{1, 3, 5, 7, 8}, new int[]{0, 3, 7, 10, 20, 30},
+                new int[]{17, 12, 16, 14, 17}, new int[]{0, 10, 14, 17, 20, 30}
+        );
+
+        for (int[] candidate : cases.keySet()) {
+            int[] target = cases.get(candidate);
+
+            SignalThresholdInfo info = new SignalThresholdInfo.Builder()
+                    .setRadioAccessNetworkType(ran)
+                    .setSignalMeasurementType(measurement)
+                    .setThresholds(candidate, true /* isSystem */)
+                    .build();
+            SignalStrengthUpdateRequest request = createTestSignalStrengthUpdateRequest(
+                    info,
+                    false /* shouldReportWhileIdle*/,
+                    false /* shouldReportSystemWhileIdle */
+            );
+            mSsc.setSignalStrengthUpdateRequest(ACTIVE_SUB_ID, CALLING_UID,
+                    request, Message.obtain(mHandler));
+            processAllMessages();
+
+            assertThat(mSsc.getConsolidatedSignalThresholds(
+                    ran, measurement, systemThresholds, hysteresis
+            )).isEqualTo(target);
+
+            // Each pair in the Map is tested separately (instead of cumulatively).
+            // Remove the request once it is done.
+            mSsc.clearSignalStrengthUpdateRequest(ACTIVE_SUB_ID, CALLING_UID,
+                    request, Message.obtain(mHandler));
+            processAllMessages();
+        }
+    }
+
+    @Test
+    @MediumTest
+    public void testSignalStrength() {
+        // Send in GSM Signal Strength Info and expect isGsm == true
+        SignalStrength ss = new SignalStrength(
+                new CellSignalStrengthCdma(),
+                new CellSignalStrengthGsm(-53, 0, SignalStrength.INVALID),
+                new CellSignalStrengthWcdma(),
+                new CellSignalStrengthTdscdma(),
+                new CellSignalStrengthLte(),
+                new CellSignalStrengthNr());
+
+        sendSignalStrength(ss);
+        assertEquals(mSsc.getSignalStrength(), ss);
+        assertEquals(mSsc.getSignalStrength().isGsm(), true);
+
+        // Send in CDMA+LTE Signal Strength Info and expect isGsm == true
+        ss = new SignalStrength(
+                new CellSignalStrengthCdma(-90, -12,
+                        SignalStrength.INVALID, SignalStrength.INVALID, SignalStrength.INVALID),
+                new CellSignalStrengthGsm(),
+                new CellSignalStrengthWcdma(),
+                new CellSignalStrengthTdscdma(),
+                new CellSignalStrengthLte(
+                        -110, -114, -5, 0, SignalStrength.INVALID, SignalStrength.INVALID),
+                new CellSignalStrengthNr());
+
+        sendSignalStrength(ss);
+        assertEquals(mSsc.getSignalStrength(), ss);
+        assertEquals(mSsc.getSignalStrength().isGsm(), true);
+
+        // Send in CDMA-only Signal Strength Info and expect isGsm == false
+        ss = new SignalStrength(
+                new CellSignalStrengthCdma(-90, -12,
+                        SignalStrength.INVALID, SignalStrength.INVALID, SignalStrength.INVALID),
+                new CellSignalStrengthGsm(),
+                new CellSignalStrengthWcdma(),
+                new CellSignalStrengthTdscdma(),
+                new CellSignalStrengthLte(),
+                new CellSignalStrengthNr());
+
+        sendSignalStrength(ss);
+        assertEquals(mSsc.getSignalStrength(), ss);
+        assertEquals(mSsc.getSignalStrength().isGsm(), false);
+    }
+
+    @Test
+    public void testLteSignalStrengthReportingCriteria() {
+        SignalStrength ss = new SignalStrength(
+                new CellSignalStrengthCdma(),
+                new CellSignalStrengthGsm(),
+                new CellSignalStrengthWcdma(),
+                new CellSignalStrengthTdscdma(),
+                new CellSignalStrengthLte(
+                        -110, /* rssi */
+                        -114, /* rsrp */
+                        -5, /* rsrq */
+                        0, /* rssnr */
+                        SignalStrength.INVALID, /* cqi */
+                        SignalStrength.INVALID /* ta */),
+                new CellSignalStrengthNr());
+
+        mBundle.putBoolean(CarrierConfigManager.KEY_USE_ONLY_RSRP_FOR_LTE_SIGNAL_BAR_BOOL,
+                true);
+
+        sendCarrierConfigUpdate();
+
+        mSimulatedCommands.setSignalStrength(ss);
+        mSimulatedCommands.notifySignalStrength();
+        processAllMessages();
+        // Default thresholds are POOR=-115 MODERATE=-105 GOOD=-95 GREAT=-85
+        assertEquals(CellSignalStrength.SIGNAL_STRENGTH_POOR, mSsc.getSignalStrength().getLevel());
+
+        int[] lteThresholds = {
+                -130, // SIGNAL_STRENGTH_POOR
+                -120, // SIGNAL_STRENGTH_MODERATE
+                -110, // SIGNAL_STRENGTH_GOOD
+                -100,  // SIGNAL_STRENGTH_GREAT
+        };
+        mBundle.putIntArray(CarrierConfigManager.KEY_LTE_RSRP_THRESHOLDS_INT_ARRAY,
+                lteThresholds);
+        sendCarrierConfigUpdate();
+
+        mSimulatedCommands.setSignalStrength(ss);
+        mSimulatedCommands.notifySignalStrength();
+        processAllMessages();
+        assertEquals(mSsc.getSignalStrength().getLevel(),
+                CellSignalStrength.SIGNAL_STRENGTH_MODERATE);
+    }
+
+    @Test
+    public void test5gNrSignalStrengthReportingCriteria_UseSsRsrp() {
+        SignalStrength ss = new SignalStrength(
+                new CellSignalStrengthCdma(),
+                new CellSignalStrengthGsm(),
+                new CellSignalStrengthWcdma(),
+                new CellSignalStrengthTdscdma(),
+                new CellSignalStrengthLte(),
+                new CellSignalStrengthNr(
+                        -139, /** csiRsrp NONE */
+                        -20, /** csiRsrq NONE */
+                        -23, /** CsiSinr NONE */
+                        -44, /** SsRsrp SIGNAL_STRENGTH_GREAT */
+                        -20, /** SsRsrq NONE */
+                        -23) /** SsSinr NONE */
+        );
+
+        // SSRSRP = 1 << 0
+        mBundle.putInt(CarrierConfigManager.KEY_PARAMETERS_USE_FOR_5G_NR_SIGNAL_BAR_INT,
+                CellSignalStrengthNr.USE_SSRSRP);
+        sendCarrierConfigUpdate();
+        mSimulatedCommands.setSignalStrength(ss);
+        mSimulatedCommands.notifySignalStrength();
+        processAllMessages();
+        assertEquals(CellSignalStrength.SIGNAL_STRENGTH_GREAT, mSsc.getSignalStrength().getLevel());
+    }
+
+    @Test
+    public void test5gNrSignalStrengthReportingCriteria_UseSsRsrpAndSsRsrq() {
+        SignalStrength ss = new SignalStrength(
+                new CellSignalStrengthCdma(),
+                new CellSignalStrengthGsm(),
+                new CellSignalStrengthWcdma(),
+                new CellSignalStrengthTdscdma(),
+                new CellSignalStrengthLte(),
+                new CellSignalStrengthNr(
+                        -139, /** csiRsrp NONE */
+                        -20, /** csiRsrq NONE */
+                        -23, /** CsiSinr NONE */
+                        -44, /** SsRsrp SIGNAL_STRENGTH_GREAT */
+                        -32, /** SsRsrq NONE */
+                        -23) /** SsSinr NONE */
+        );
+
+        // SSRSRP = 1 << 0 | SSSINR = 1 << 2
+        mBundle.putInt(CarrierConfigManager.KEY_PARAMETERS_USE_FOR_5G_NR_SIGNAL_BAR_INT,
+                CellSignalStrengthNr.USE_SSRSRP | CellSignalStrengthNr.USE_SSRSRQ);
+        sendCarrierConfigUpdate();
+        mSimulatedCommands.setSignalStrength(ss);
+        mSimulatedCommands.notifySignalStrength();
+        processAllMessages();
+        assertEquals(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
+                mSsc.getSignalStrength().getLevel());
+    }
+
+    @Test
+    public void test5gNrSignalStrengthReportingCriteria_ConfiguredThresholds() {
+        SignalStrength ss = new SignalStrength(
+                new CellSignalStrengthCdma(),
+                new CellSignalStrengthGsm(),
+                new CellSignalStrengthWcdma(),
+                new CellSignalStrengthTdscdma(),
+                new CellSignalStrengthLte(),
+                new CellSignalStrengthNr(
+                        -139, /** csiRsrp NONE */
+                        -20, /** csiRsrq NONE */
+                        -23, /** CsiSinr NONE */
+                        -44, /** SsRsrp SIGNAL_STRENGTH_GREAT */
+                        -20, /** SsRsrq NONE */
+                        -23) /** SsSinr NONE */
+        );
+
+        // SSRSRP = 1 << 0
+        mBundle.putInt(CarrierConfigManager.KEY_PARAMETERS_USE_FOR_5G_NR_SIGNAL_BAR_INT,
+                CellSignalStrengthNr.USE_SSRSRP);
+        sendCarrierConfigUpdate();
+        mSimulatedCommands.setSignalStrength(ss);
+        mSimulatedCommands.notifySignalStrength();
+        processAllMessages();
+        assertEquals(CellSignalStrength.SIGNAL_STRENGTH_GREAT, mSsc.getSignalStrength().getLevel());
+
+        int[] nrSsRsrpThresholds = {
+                -45, // SIGNAL_STRENGTH_POOR
+                -40, // SIGNAL_STRENGTH_MODERATE
+                -37, // SIGNAL_STRENGTH_GOOD
+                -34,  // SIGNAL_STRENGTH_GREAT
+        };
+        mBundle.putIntArray(CarrierConfigManager.KEY_5G_NR_SSRSRP_THRESHOLDS_INT_ARRAY,
+                nrSsRsrpThresholds);
+        mBundle.putInt(CarrierConfigManager.KEY_PARAMETERS_USE_FOR_5G_NR_SIGNAL_BAR_INT,
+                CellSignalStrengthNr.USE_SSRSRP);
+        sendCarrierConfigUpdate();
+        mSimulatedCommands.setSignalStrength(ss);
+        mSimulatedCommands.notifySignalStrength();
+        processAllMessages();
+        assertEquals(CellSignalStrength.SIGNAL_STRENGTH_POOR,
+                mSsc.getSignalStrength().getLevel());
+    }
+
+    @Test
+    public void testWcdmaSignalStrengthReportingCriteria() {
+        SignalStrength ss = new SignalStrength(
+                new CellSignalStrengthCdma(),
+                new CellSignalStrengthGsm(),
+                new CellSignalStrengthWcdma(-79, 0, -85, -5),
+                new CellSignalStrengthTdscdma(),
+                new CellSignalStrengthLte(),
+                new CellSignalStrengthNr());
+
+        mSimulatedCommands.setSignalStrength(ss);
+        mSimulatedCommands.notifySignalStrength();
+        processAllMessages();
+        assertEquals(mSsc.getSignalStrength().getLevel(), CellSignalStrength.SIGNAL_STRENGTH_GOOD);
+
+        int[] wcdmaThresholds = {
+                -110, // SIGNAL_STRENGTH_POOR
+                -100, // SIGNAL_STRENGTH_MODERATE
+                -90, // SIGNAL_STRENGTH_GOOD
+                -80  // SIGNAL_STRENGTH_GREAT
+        };
+        mBundle.putIntArray(CarrierConfigManager.KEY_WCDMA_RSCP_THRESHOLDS_INT_ARRAY,
+                wcdmaThresholds);
+        mBundle.putString(
+                CarrierConfigManager.KEY_WCDMA_DEFAULT_SIGNAL_STRENGTH_MEASUREMENT_STRING,
+                "rscp");
+        sendCarrierConfigUpdate();
+        mSimulatedCommands.setSignalStrength(ss);
+        mSimulatedCommands.notifySignalStrength();
+        processAllMessages();
+        assertEquals(mSsc.getSignalStrength().getLevel(), CellSignalStrength.SIGNAL_STRENGTH_GOOD);
+    }
+
+    private void sendCarrierConfigUpdate() {
+        CarrierConfigManager mockConfigManager = Mockito.mock(CarrierConfigManager.class);
+        when(mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE))
+                .thenReturn(mockConfigManager);
+        when(mockConfigManager.getConfigForSubId(anyInt())).thenReturn(mBundle);
+
+        Intent intent = new Intent().setAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
+        intent.putExtra(CarrierConfigManager.EXTRA_SLOT_INDEX, PHONE_ID);
+        mContext.sendBroadcast(intent);
+        processAllMessages();
+    }
+
+    private void sendSignalStrength(SignalStrength ss) {
+        mSimulatedCommands.setSignalStrength(ss);
+        mSimulatedCommands.notifySignalStrength();
+        processAllMessages();
+    }
+
+    private SignalThresholdInfo createTestSignalThresholdInfo() {
+        SignalThresholdInfo info = new SignalThresholdInfo.Builder()
+                .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.GERAN)
+                .setSignalMeasurementType(SIGNAL_MEASUREMENT_TYPE_RSSI)
+                .setThresholds(new int[]{-100, -90})
+                .build();
+        return info;
+    }
+
+    private SignalStrengthUpdateRequest createTestSignalStrengthUpdateRequest(
+            SignalThresholdInfo info, boolean shouldReportWhileIdle,
+            boolean shouldReportSystemWhileIdle) {
+        List<SignalThresholdInfo> infoList = new ArrayList<>();
+        infoList.add(info);
+
+        SignalStrengthUpdateRequest.Builder builder = new SignalStrengthUpdateRequest.Builder()
+                .setSignalThresholdInfos(infoList);
+        if (shouldReportWhileIdle) {
+            builder.setReportingRequestedWhileIdle(true);
+        }
+        if (shouldReportSystemWhileIdle) {
+            builder.setSystemThresholdReportingRequestedWhileIdle(true);
+        }
+        return builder.build();
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SimulatedCommands.java b/tests/telephonytests/src/com/android/internal/telephony/SimulatedCommands.java
old mode 100755
new mode 100644
index 95e2f06..8dd6a81
--- a/tests/telephonytests/src/com/android/internal/telephony/SimulatedCommands.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SimulatedCommands.java
@@ -61,7 +61,7 @@
 import com.android.internal.telephony.LastCallFailCause;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneConstants;
-import com.android.internal.telephony.RIL;
+import com.android.internal.telephony.RILUtils;
 import com.android.internal.telephony.RadioCapability;
 import com.android.internal.telephony.SmsResponse;
 import com.android.internal.telephony.UUSInfo;
@@ -69,12 +69,12 @@
 import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
 import com.android.internal.telephony.gsm.SuppServiceNotification;
 import com.android.internal.telephony.uicc.AdnCapacity;
-import com.android.internal.telephony.uicc.ReceivedPhonebookRecords;
-import com.android.internal.telephony.uicc.SimPhonebookRecord;
 import com.android.internal.telephony.uicc.IccCardApplicationStatus.PersoSubState;
 import com.android.internal.telephony.uicc.IccCardStatus;
 import com.android.internal.telephony.uicc.IccIoResult;
 import com.android.internal.telephony.uicc.IccSlotStatus;
+import com.android.internal.telephony.uicc.ReceivedPhonebookRecords;
+import com.android.internal.telephony.uicc.SimPhonebookRecord;
 import com.android.telephony.Rlog;
 
 import java.util.ArrayList;
@@ -1149,7 +1149,8 @@
      */
     @Override
     public void sendSMSExpectMore (String smscPDU, String pdu, Message result) {
-        unimplemented(result);
+        SimulatedCommandsVerifier.getInstance().sendSMSExpectMore(smscPDU, pdu, result);
+        resultSuccess(result, new SmsResponse(0 /*messageRef*/, null, SmsResponse.NO_ERROR_CODE));
     }
 
     @Override
@@ -1188,6 +1189,13 @@
         }
     }
 
+    public void triggerNITZupdate(String NITZStr, long ageMs) {
+        if (NITZStr != null) {
+            mNITZTimeRegistrant.notifyRegistrant(new AsyncResult (null, new Object[]{NITZStr,
+                    SystemClock.elapsedRealtime(), ageMs}, null));
+        }
+    }
+
     @Override
     public void setupDataCall(int accessNetworkType, DataProfile dataProfile, boolean isRoaming,
             boolean allowRoaming, int reason, LinkProperties linkProperties, int pduSessionId,
@@ -1218,7 +1226,7 @@
             }
         }
 
-        DataCallResponse response = RIL.convertDataCallResult(mSetupDataCallResult);
+        DataCallResponse response = RILUtils.convertHalDataCallResult(mSetupDataCallResult);
         if (mDcSuccess) {
             resultSuccess(result, response);
         } else {
@@ -2439,6 +2447,12 @@
         resultSuccess(message, null);
     }
 
+    @Override
+    public void getSlicingConfig(Message result) {
+        SimulatedCommandsVerifier.getInstance().getSlicingConfig(result);
+        resultSuccess(result, null);
+    }
+
     @VisibleForTesting
     public void setDataRegStateResult(Object regStateResult) {
         mDataRegStateResult = regStateResult;
@@ -2475,10 +2489,4 @@
     public void notifySimPhonebookChanged() {
         mSimPhonebookChangedRegistrants.notifyRegistrants();
     }
-
-    @Override
-    public void getSlicingConfig(Message result) {
-        SimulatedCommandsVerifier.getInstance().getSlicingConfig(result);
-        resultSuccess(result, null);
-    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SimulatedCommandsVerifier.java b/tests/telephonytests/src/com/android/internal/telephony/SimulatedCommandsVerifier.java
index 699199c..e9c74ae 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SimulatedCommandsVerifier.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SimulatedCommandsVerifier.java
@@ -1495,6 +1495,10 @@
     }
 
     @Override
+    public void getSlicingConfig(Message result) {
+    }
+
+    @Override
     public void getSimPhonebookRecords(Message result){
     }
 
@@ -1519,10 +1523,6 @@
     }
 
     @Override
-    public void unregisterForSimPhonebookRecordsReceived(Handler h) {
-    }
-
-    @Override
-    public void getSlicingConfig(Message result) {
+    public void unregisterForSimPhonebookRecordsReceived(Handler h){
     }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SubscriptionControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/SubscriptionControllerTest.java
index 6566d3d..b79b317 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SubscriptionControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SubscriptionControllerTest.java
@@ -31,7 +31,6 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.eq;
@@ -40,13 +39,14 @@
 import static org.mockito.Mockito.when;
 
 import android.Manifest;
-import android.app.AppOpsManager;
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.Looper;
 import android.os.ParcelUuid;
 import android.os.PersistableBundle;
 import android.os.UserHandle;
@@ -56,9 +56,9 @@
 import android.telephony.SubscriptionManager;
 import android.telephony.UiccSlotInfo;
 import android.test.mock.MockContentResolver;
-import android.test.suitebuilder.annotation.SmallTest;
 
 import androidx.test.filters.FlakyTest;
+import androidx.test.filters.SmallTest;
 
 import com.android.internal.telephony.uicc.IccCardStatus;
 import com.android.internal.telephony.uicc.UiccController;
@@ -112,6 +112,10 @@
     public void setUp() throws Exception {
         super.setUp("SubscriptionControllerTest");
 
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
+        }
+
         doReturn(SINGLE_SIM).when(mTelephonyManager).getSimCount();
         doReturn(SINGLE_SIM).when(mTelephonyManager).getPhoneCount();
         mMockContentResolver = (MockContentResolver) mContext.getContentResolver();
@@ -144,8 +148,10 @@
 
         /*clear sub info in mSubscriptionControllerUT since they will otherwise be persistent
          * between each test case. */
-        mSubscriptionControllerUT.clearSubInfo();
-        mSubscriptionControllerUT.resetStaticMembers();
+        if (mSubscriptionControllerUT != null) {
+            mSubscriptionControllerUT.clearSubInfo();
+            mSubscriptionControllerUT.resetStaticMembers();
+        }
 
         /* clear settings for default voice/data/sms sub ID */
         Settings.Global.putInt(mContext.getContentResolver(),
@@ -1321,12 +1327,13 @@
     }
 
     @Test
-    public void testGetActiveSubscriptionWithReadPhoneNumbers() throws Exception {
-        // If the calling package has the READ_PHONE_NUMBERS permission the number should be
-        // available in the SubscriptionInfo.
+    public void testGetActiveSubscriptionWithPhoneNumberAccess() throws Exception {
+        // If the calling package meets any of the requirements for the
+        // LegacyPermissionManager#checkPhoneNumberAccess test then the number should be available
+        // in the SubscriptionInfo.
         testInsertSim();
         setupReadPhoneNumbersTest();
-        mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PHONE_NUMBERS);
+        setPhoneNumberAccess(PackageManager.PERMISSION_GRANTED);
         int subId = getFirstSubId();
 
         SubscriptionInfo subscriptionInfo = mSubscriptionControllerUT.getActiveSubscriptionInfo(
@@ -1404,13 +1411,14 @@
     }
 
     @Test
-    public void testGetActiveSubscriptionInfoForSimSlotIndexWithReadPhoneNumbers()
+    public void testGetActiveSubscriptionInfoForSimSlotIndexWithPhoneNumberAccess()
             throws Exception {
-        // If the calling package has the READ_PHONE_NUMBERS permission the number should be
-        // available in the SubscriptionInfo.
+        // If the calling package meets any of the requirements for the
+        // LegacyPermissionManager#checkPhoneNumberAccess test then the number should be available
+        // in the SubscriptionInfo.
         testInsertSim();
         setupReadPhoneNumbersTest();
-        mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PHONE_NUMBERS);
+        setPhoneNumberAccess(PackageManager.PERMISSION_GRANTED);
 
         SubscriptionInfo subscriptionInfo =
                 mSubscriptionControllerUT.getActiveSubscriptionInfoForSimSlotIndex(0,
@@ -1490,12 +1498,13 @@
     }
 
     @Test
-    public void testGetActiveSubscriptionInfoListWithReadPhoneNumbers() throws Exception {
-        // If the calling package has the READ_PHONE_NUMBERS permission the number should be
-        // available in the SubscriptionInfo.
+    public void testGetActiveSubscriptionInfoListWithPhoneNumberAccess() throws Exception {
+        // If the calling package meets any of the requirements for the
+        // LegacyPermissionManager#checkPhoneNumberAccess test then the number should be available
+        // in the SubscriptionInfo.
         testInsertSim();
         setupReadPhoneNumbersTest();
-        mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PHONE_NUMBERS);
+        setPhoneNumberAccess(PackageManager.PERMISSION_GRANTED);
 
         List<SubscriptionInfo> subInfoList =
                 mSubscriptionControllerUT.getActiveSubscriptionInfoList(mCallingPackage,
@@ -1649,12 +1658,13 @@
     }
 
     @Test
-    public void testGetSubscriptionInGroupWithReadPhoneNumbers() throws Exception {
-        // If the calling package has the READ_PHONE_NUMBERS permission the number should be
-        // available in the SubscriptionInfo.
+    public void testGetSubscriptionInGroupWithPhoneNumberAccess() throws Exception {
+        // If the calling package meets any of the requirements for the
+        // LegacyPermissionManager#checkPhoneNumberAccess test then the number should be available
+        // in the SubscriptionInfo.
         ParcelUuid groupUuid = setupGetSubscriptionsInGroupTest();
         setupReadPhoneNumbersTest();
-        mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PHONE_NUMBERS);
+        setPhoneNumberAccess(PackageManager.PERMISSION_GRANTED);
 
         List<SubscriptionInfo> subInfoList = mSubscriptionControllerUT.getSubscriptionsInGroup(
                 groupUuid, mCallingPackage, mCallingFeature);
@@ -1711,9 +1721,7 @@
         mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);
         mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE);
         setupMocksForTelephonyPermissions(Build.VERSION_CODES.R);
-        doReturn(AppOpsManager.MODE_DEFAULT).when(mAppOpsManager).noteOp(
-                eq(AppOpsManager.OPSTR_WRITE_SMS), anyInt(), anyString(),
-                nullable(String.class), nullable(String.class));
+        setPhoneNumberAccess(PackageManager.PERMISSION_DENIED);
     }
 
     private void setupIdentifierCarrierPrivilegesTest() throws Exception {
@@ -1932,4 +1940,21 @@
                 mSubscriptionControllerUT
                         .getAllSubInfoList(mCallingPackage, mCallingFeature).size());
     }
+
+    @Test
+    @SmallTest
+    public void testCheckPhoneIdAndIccIdMatch() {
+        try {
+            testSetSubscriptionGroupWithModifyPermission();
+        } catch (Exception e) {
+            fail("Unexpected exception: " + e);
+        }
+
+        mSubscriptionControllerUT.addSubInfoRecord("test3",
+                SubscriptionManager.INVALID_SIM_SLOT_INDEX);
+
+        assertTrue(mSubscriptionControllerUT.checkPhoneIdAndIccIdMatch(0, "test"));
+        assertTrue(mSubscriptionControllerUT.checkPhoneIdAndIccIdMatch(0, "test2"));
+        assertFalse(mSubscriptionControllerUT.checkPhoneIdAndIccIdMatch(0, "test3"));
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoUpdaterTest.java b/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoUpdaterTest.java
index f9199f7..1161792 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoUpdaterTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoUpdaterTest.java
@@ -42,7 +42,6 @@
 import android.os.Looper;
 import android.os.ParcelUuid;
 import android.os.PersistableBundle;
-import android.permission.IPermissionManager;
 import android.service.euicc.EuiccProfileInfo;
 import android.service.euicc.EuiccService;
 import android.service.euicc.GetEuiccProfileInfoListResult;
@@ -88,6 +87,7 @@
     private static final String FAKE_ICCID_1 = "89012604200000000000";
     private static final String FAKE_MCC_MNC_1 = "123456";
     private static final String FAKE_MCC_MNC_2 = "456789";
+    private static final int FAKE_PHONE_ID_1 = 0;
 
     private SubscriptionInfoUpdater mUpdater;
     private IccRecords mIccRecord;
@@ -108,8 +108,6 @@
     @Mock
     private IPackageManager mPackageManager;
     @Mock
-    private IPermissionManager mPermissionManager;
-    @Mock
     private UiccSlot mUiccSlot;
 
     /*Custom ContentProvider */
@@ -239,16 +237,16 @@
     @SmallTest
     public void testSimNotReady() throws Exception {
         mUpdater.updateInternalIccState(
-                IccCardConstants.INTENT_VALUE_ICC_NOT_READY, null, FAKE_SUB_ID_1);
+                IccCardConstants.INTENT_VALUE_ICC_NOT_READY, null, FAKE_PHONE_ID_1);
 
         processAllMessages();
         assertFalse(mUpdater.isSubInfoInitialized());
         verify(mSubscriptionContent, never()).put(anyString(), any());
         CarrierConfigManager mConfigManager = (CarrierConfigManager)
                 mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
-        verify(mConfigManager, never()).updateConfigForPhoneId(eq(FAKE_SUB_ID_1),
+        verify(mConfigManager, never()).updateConfigForPhoneId(eq(FAKE_PHONE_ID_1),
                 eq(IccCardConstants.INTENT_VALUE_ICC_NOT_READY));
-        verify(mSubscriptionController, never()).clearSubInfo();
+        verify(mSubscriptionController, never()).clearSubInfoRecord(FAKE_PHONE_ID_1);
         verify(mSubscriptionController, never()).notifySubscriptionInfoChanged();
     }
 
@@ -259,19 +257,19 @@
         doReturn(true).when(mIccCard).isEmptyProfile();
 
         mUpdater.updateInternalIccState(
-                IccCardConstants.INTENT_VALUE_ICC_NOT_READY, null, FAKE_SUB_ID_1);
+                IccCardConstants.INTENT_VALUE_ICC_NOT_READY, null, FAKE_PHONE_ID_1);
 
         processAllMessages();
         assertTrue(mUpdater.isSubInfoInitialized());
         // Sub info should be cleared and change should be notified.
-        verify(mSubscriptionController).clearSubInfoRecord(eq(FAKE_SUB_ID_1));
+        verify(mSubscriptionController).clearSubInfoRecord(eq(FAKE_PHONE_ID_1));
         verify(mSubscriptionController).notifySubscriptionInfoChanged();
         // No new sub should be added.
         verify(mSubscriptionManager, never()).addSubscriptionInfoRecord(any(), anyInt());
         verify(mSubscriptionContent, never()).put(anyString(), any());
         CarrierConfigManager mConfigManager = (CarrierConfigManager)
                 mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
-        verify(mConfigManager).updateConfigForPhoneId(eq(FAKE_SUB_ID_1),
+        verify(mConfigManager).updateConfigForPhoneId(eq(FAKE_PHONE_ID_1),
                 eq(IccCardConstants.INTENT_VALUE_ICC_NOT_READY));
     }
 
@@ -286,24 +284,24 @@
         doReturn(false).when(mSubInfo).areUiccApplicationsEnabled();
 
         mUpdater.updateInternalIccState(
-                IccCardConstants.INTENT_VALUE_ICC_NOT_READY, null, FAKE_SUB_ID_1);
+                IccCardConstants.INTENT_VALUE_ICC_NOT_READY, null, FAKE_PHONE_ID_1);
 
         processAllMessages();
         assertTrue(mUpdater.isSubInfoInitialized());
         // Sub info should be cleared and change should be notified.
-        verify(mSubscriptionController).clearSubInfoRecord(eq(FAKE_SUB_ID_1));
+        verify(mSubscriptionController).clearSubInfoRecord(eq(FAKE_PHONE_ID_1));
         verify(mSubscriptionController).notifySubscriptionInfoChanged();
         // No new sub should be added.
         verify(mSubscriptionManager, never()).addSubscriptionInfoRecord(any(), anyInt());
         verify(mSubscriptionContent, never()).put(anyString(), any());
         CarrierConfigManager mConfigManager = (CarrierConfigManager)
                 mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
-        verify(mConfigManager).updateConfigForPhoneId(eq(FAKE_SUB_ID_1),
+        verify(mConfigManager).updateConfigForPhoneId(eq(FAKE_PHONE_ID_1),
                 eq(IccCardConstants.INTENT_VALUE_ICC_NOT_READY));
 
         // When becomes ABSENT, UICC_APPLICATIONS_ENABLED should be reset to true.
         mUpdater.updateInternalIccState(
-                IccCardConstants.INTENT_VALUE_ICC_ABSENT, null, FAKE_SUB_ID_1);
+                IccCardConstants.INTENT_VALUE_ICC_ABSENT, null, FAKE_PHONE_ID_1);
         processAllMessages();
         ArgumentCaptor<ContentValues> valueCapture = ArgumentCaptor.forClass(ContentValues.class);
         verify(mContentProvider).update(eq(SubscriptionManager.CONTENT_URI), valueCapture.capture(),
diff --git a/tests/telephonytests/src/com/android/internal/telephony/TelephonyPermissionsTest.java b/tests/telephonytests/src/com/android/internal/telephony/TelephonyPermissionsTest.java
index 8769b24..b545c2b 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/TelephonyPermissionsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/TelephonyPermissionsTest.java
@@ -36,7 +36,7 @@
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.ServiceManager;
-import android.permission.PermissionManager;
+import android.permission.LegacyPermissionManager;
 import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.telephony.SubscriptionManager;
@@ -46,7 +46,7 @@
 import android.test.suitebuilder.annotation.SmallTest;
 
 import com.android.internal.util.test.FakeSettingsProvider;
-import com.android.server.pm.permission.PermissionManagerService;
+import com.android.server.pm.permission.LegacyPermissionManagerService;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -88,7 +88,7 @@
     @Mock
     private TelephonyManager mTelephonyManagerMockForSub2;
     @Mock
-    private PermissionManagerService mMockPermissionManagerService;
+    private LegacyPermissionManagerService mMockLegacyPermissionManagerService;
 
     private MockContentResolver mMockContentResolver;
     private FakeSettingsConfigProvider mFakeSettingsConfigProvider;
@@ -107,10 +107,10 @@
         when(mMockSubscriptionManager.getCompleteActiveSubscriptionIdList()).thenReturn(
                 new int[]{SUB_ID});
 
-        PermissionManager permissionManager = new PermissionManager(mMockContext, null,
-                mMockPermissionManagerService);
-        when(mMockContext.getSystemService(Context.PERMISSION_SERVICE)).thenReturn(
-                permissionManager);
+        LegacyPermissionManager legacyPermissionManager = new LegacyPermissionManager(
+                mMockLegacyPermissionManagerService);
+        when(mMockContext.getSystemService(Context.LEGACY_PERMISSION_SERVICE)).thenReturn(
+                legacyPermissionManager);
 
         // By default, assume we have no permissions or app-ops bits.
         doThrow(new SecurityException()).when(mMockContext)
@@ -233,79 +233,42 @@
     }
 
     @Test
-    public void testCheckReadPhoneNumber_defaultSmsApp() throws Exception {
-        setupMocksForDeviceIdentifiersErrorPath();
-        when(mMockAppOps.noteOp(eq(AppOpsManager.OPSTR_WRITE_SMS), eq(UID), eq(PACKAGE),
-                eq(FEATURE), nullable(String.class))).thenReturn(AppOpsManager.MODE_ALLOWED);
-        assertTrue(TelephonyPermissions.checkReadPhoneNumber(
-                mMockContext, SUB_ID, PID, UID, PACKAGE, FEATURE, MSG));
+    public void testCheckReadPhoneNumber_targetPreRWithReadPhoneStateNoAppop() throws Exception {
+        // If ap app is targeting SDK version < R then the phone number should be accessible with
+        // both the READ_PHONE_STATE permission and appop granted; if only the permission is granted
+        // but the appop is denied then the LegacyPermissionManager should return MODE_IGNORED
+        // to indicate the check should fail silently (return empty / null data).
+        when(mMockLegacyPermissionManagerService.checkPhoneNumberAccess(PACKAGE, MSG, FEATURE,
+                PID, UID)).thenReturn(AppOpsManager.MODE_IGNORED);
+        assertFalse(
+                TelephonyPermissions.checkReadPhoneNumber(mMockContext, SUB_ID, PID, UID, PACKAGE,
+                        FEATURE, MSG));
     }
 
     @Test
-    public void testCheckReadPhoneNumber_hasPrivilegedPhoneStatePermission() throws Exception {
-        setupMocksForDeviceIdentifiersErrorPath();
-        doNothing().when(mMockContext).enforcePermission(
-                android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, PID, UID, MSG);
-        assertTrue(TelephonyPermissions.checkReadPhoneNumber(
-                mMockContext, SUB_ID, PID, UID, PACKAGE, FEATURE, MSG));
+    public void testCheckReadPhoneNumber_hasPermissionManagerPhoneNumberAccess() {
+        // To limit binder transactions the targetSdkVersion, permission, and appop checks are all
+        // performed by the LegacyPermissionManager; this test verifies when this API returns
+        // the calling package meets the requirements for phone number access the telephony
+        // check also returns true.
+        when(mMockLegacyPermissionManagerService.checkPhoneNumberAccess(PACKAGE, MSG, FEATURE,
+                PID, UID)).thenReturn(PackageManager.PERMISSION_GRANTED);
+        assertTrue(
+                TelephonyPermissions.checkReadPhoneNumber(mMockContext, SUB_ID, PID, UID, PACKAGE,
+                        FEATURE, MSG));
     }
 
     @Test
-    public void testCheckReadPhoneNumber_hasReadSms() throws Exception {
-        setupMocksForDeviceIdentifiersErrorPath();
-        doNothing().when(mMockContext).enforcePermission(
-                android.Manifest.permission.READ_SMS, PID, UID, MSG);
-        when(mMockAppOps.noteOp(eq(AppOpsManager.OPSTR_READ_SMS), eq(UID), eq(PACKAGE), eq(FEATURE),
-                nullable(String.class))).thenReturn(AppOpsManager.MODE_ALLOWED);
-        assertTrue(TelephonyPermissions.checkReadPhoneNumber(
-                mMockContext, SUB_ID, PID, UID, PACKAGE, FEATURE, MSG));
+    public void testCheckReadPhoneNumber_hasCarrierPrivileges() throws Exception {
+        when(mTelephonyManagerMock.createForSubscriptionId(eq(SUB_ID))).thenReturn(
+                mTelephonyManagerMockForSub1);
+        when(mTelephonyManagerMockForSub1.getCarrierPrivilegeStatus(anyInt())).thenReturn(
+                TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS);
+        assertTrue(
+                TelephonyPermissions.checkReadPhoneNumber(mMockContext, SUB_ID, PID, UID, PACKAGE,
+                        FEATURE, MSG));
     }
 
-    @Test
-    public void testCheckReadPhoneNumber_hasReadPhoneNumbers() throws Exception {
-        setupMocksForDeviceIdentifiersErrorPath();
-        doNothing().when(mMockContext).enforcePermission(
-                android.Manifest.permission.READ_PHONE_NUMBERS, PID, UID, MSG);
-        when(mMockAppOps.noteOp(eq(AppOpsManager.OPSTR_READ_PHONE_NUMBERS), eq(UID), eq(PACKAGE),
-                eq(FEATURE), nullable(String.class))).thenReturn(AppOpsManager.MODE_ALLOWED);
-        assertTrue(TelephonyPermissions.checkReadPhoneNumber(
-                mMockContext, SUB_ID, PID, UID, PACKAGE, FEATURE, MSG));
-    }
-
-    @Test
-    public void testCheckReadPhoneNumber_hasReadSmsNoAppop() throws Exception {
-        // If an app has been granted the READ_SMS permission, but the OPSTR_READ_SMS appop has been
-        // revoked then instead of immediately returning false the phone number access check should
-        // check if the caller has the READ_PHONE_NUMBERS permission and appop.
-        setupMocksForDeviceIdentifiersErrorPath();
-        doNothing().when(mMockContext).enforcePermission(
-                android.Manifest.permission.READ_SMS, PID, UID, MSG);
-        doNothing().when(mMockContext).enforcePermission(
-                android.Manifest.permission.READ_PHONE_NUMBERS, PID, UID, MSG);
-        when(mMockAppOps.noteOp(eq(AppOpsManager.OPSTR_READ_PHONE_NUMBERS), eq(UID), eq(PACKAGE),
-                eq(FEATURE), nullable(String.class))).thenReturn(AppOpsManager.MODE_ALLOWED);
-        assertTrue(TelephonyPermissions.checkReadPhoneNumber(
-                mMockContext, SUB_ID, PID, UID, PACKAGE, FEATURE, MSG));
-    }
-
-    @Test
-    public void testCheckReadPhoneNumber_hasReadSmsAndReadPhoneNumbersNoAppops() throws Exception {
-        // If an app has both the READ_SMS and READ_PHONE_NUMBERS permissions granted but does not
-        // have the corresponding appops instead of returning false for not having the appop granted
-        // a SecurityException should be thrown.
-        setupMocksForDeviceIdentifiersErrorPath();
-        doNothing().when(mMockContext).enforcePermission(
-                android.Manifest.permission.READ_SMS, PID, UID, MSG);
-        doNothing().when(mMockContext).enforcePermission(
-                android.Manifest.permission.READ_PHONE_NUMBERS, PID, UID, MSG);
-        try {
-            TelephonyPermissions.checkReadPhoneNumber(
-                    mMockContext, SUB_ID, PID, UID, PACKAGE, FEATURE, MSG);
-            fail("Should have thrown SecurityException");
-        } catch (SecurityException e) {
-            // expected
-        }
-    }
 
     @Test
     public void testCheckReadDeviceIdentifiers_noPermissions() throws Exception {
@@ -325,8 +288,8 @@
         // performed by a SystemAPI in PermissionManager; this test verifies when this API returns
         // the calling package meets the requirements for device identifier access the telephony
         // check also returns true.
-        when(mMockPermissionManagerService.checkDeviceIdentifierAccess(PACKAGE, MSG, FEATURE, PID,
-                UID)).thenReturn(PackageManager.PERMISSION_GRANTED);
+        when(mMockLegacyPermissionManagerService.checkDeviceIdentifierAccess(PACKAGE, MSG, FEATURE,
+                PID, UID)).thenReturn(PackageManager.PERMISSION_GRANTED);
         assertTrue(
                 TelephonyPermissions.checkCallingOrSelfReadDeviceIdentifiers(mMockContext,
                         SUB_ID, PACKAGE, FEATURE, MSG));
@@ -616,7 +579,9 @@
                 android.Manifest.permission.READ_DEVICE_CONFIG)).thenReturn(
                 PackageManager.PERMISSION_GRANTED);
 
-        when(mMockPermissionManagerService.checkDeviceIdentifierAccess(any(), any(), any(),
+        when(mMockLegacyPermissionManagerService.checkDeviceIdentifierAccess(any(), any(), any(),
+                anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_DENIED);
+        when(mMockLegacyPermissionManagerService.checkPhoneNumberAccess(any(), any(), any(),
                 anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_DENIED);
 
         // TelephonyPermissions queries DeviceConfig to determine if the identifier access
diff --git a/tests/telephonytests/src/com/android/internal/telephony/TelephonyRegistryTest.java b/tests/telephonytests/src/com/android/internal/telephony/TelephonyRegistryTest.java
index cec3a1c..c04bcfb 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/TelephonyRegistryTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/TelephonyRegistryTest.java
@@ -29,7 +29,7 @@
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.anyString;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
@@ -65,7 +65,6 @@
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -92,20 +91,16 @@
     private int mSrvccState = -1;
     private int mRadioPowerState = RADIO_POWER_UNAVAILABLE;
     private List<PhysicalChannelConfig> mPhysicalChannelConfigs;
+    private TelephonyRegistry.ConfigurationProvider mMockConfigurationProvider;
     private CellLocation mCellLocation;
 
     // All events contribute to TelephonyRegistry#isPhoneStatePermissionRequired
     private static final Set<Integer> READ_PHONE_STATE_EVENTS;
     static {
         READ_PHONE_STATE_EVENTS = new HashSet<>();
-        READ_PHONE_STATE_EVENTS.add(
-                TelephonyCallback.EVENT_CALL_FORWARDING_INDICATOR_CHANGED);
-        READ_PHONE_STATE_EVENTS.add(
-                TelephonyCallback.EVENT_MESSAGE_WAITING_INDICATOR_CHANGED);
-        READ_PHONE_STATE_EVENTS.add(
-                TelephonyCallback.EVENT_EMERGENCY_NUMBER_LIST_CHANGED);
-        READ_PHONE_STATE_EVENTS.add(
-                TelephonyCallback.EVENT_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGED);
+        READ_PHONE_STATE_EVENTS.add(TelephonyCallback.EVENT_CALL_FORWARDING_INDICATOR_CHANGED);
+        READ_PHONE_STATE_EVENTS.add(TelephonyCallback.EVENT_MESSAGE_WAITING_INDICATOR_CHANGED);
+        READ_PHONE_STATE_EVENTS.add(TelephonyCallback.EVENT_EMERGENCY_NUMBER_LIST_CHANGED);
     }
 
     // All events contribute to TelephonyRegistry#isPrecisePhoneStatePermissionRequired
@@ -139,6 +134,8 @@
         READ_PRIVILEGED_PHONE_STATE_EVENTS.add( TelephonyCallback.EVENT_RADIO_POWER_STATE_CHANGED);
         READ_PRIVILEGED_PHONE_STATE_EVENTS.add(
                 TelephonyCallback.EVENT_VOICE_ACTIVATION_STATE_CHANGED);
+        READ_PRIVILEGED_PHONE_STATE_EVENTS.add(
+                TelephonyCallback.EVENT_ALLOWED_NETWORK_TYPE_LIST_CHANGED);
     }
 
     // All events contribute to TelephonyRegistry#isActiveEmergencySessionPermissionRequired
@@ -198,18 +195,18 @@
         @Override
         public void onLinkCapacityEstimateChanged(
                 List<LinkCapacityEstimate> linkCapacityEstimateList) {
-            mLinkCapacityEstimateList =  linkCapacityEstimateList;
-        }
-
-        @Override
-        public void onPhysicalChannelConfigChanged(@NonNull List<PhysicalChannelConfig> configs) {
-            mPhysicalChannelConfigs = configs;
+            mLinkCapacityEstimateList = linkCapacityEstimateList;
         }
 
         @Override
         public void onCellLocationChanged(CellLocation location) {
             mCellLocation = location;
         }
+
+        @Override
+        public void onPhysicalChannelConfigChanged(@NonNull List<PhysicalChannelConfig> configs) {
+            mPhysicalChannelConfigs = configs;
+        }
     }
 
     private void addTelephonyRegistryService() {
@@ -227,12 +224,21 @@
     @Before
     public void setUp() throws Exception {
         super.setUp("TelephonyRegistryTest");
-        TelephonyRegistry.ConfigurationProvider mockConfigurationProvider =
-                mock(TelephonyRegistry.ConfigurationProvider.class);
-        when(mockConfigurationProvider.getRegistrationLimit()).thenReturn(-1);
-        when(mockConfigurationProvider.isRegistrationLimitEnabledInPlatformCompat(anyInt()))
+        mMockConfigurationProvider = mock(TelephonyRegistry.ConfigurationProvider.class);
+        when(mMockConfigurationProvider.getRegistrationLimit()).thenReturn(-1);
+        when(mMockConfigurationProvider.isRegistrationLimitEnabledInPlatformCompat(anyInt()))
                 .thenReturn(false);
-        mTelephonyRegistry = new TelephonyRegistry(mContext, mockConfigurationProvider);
+        when(mMockConfigurationProvider.isCallStateReadPhoneStateEnforcedInPlatformCompat(
+                anyString(), any())).thenReturn(false);
+        when(mMockConfigurationProvider.isActiveDataSubIdReadPhoneStateEnforcedInPlatformCompat(
+                anyString(), any())).thenReturn(false);
+        when(mMockConfigurationProvider.isCellInfoReadPhoneStateEnforcedInPlatformCompat(
+                anyString(), any())).thenReturn(false);
+        when(mMockConfigurationProvider.isDisplayInfoReadPhoneStateEnforcedInPlatformCompat(
+                anyString(), any())).thenReturn(false);
+        when(mMockConfigurationProvider.isDisplayInfoNrAdvancedSupported(
+                anyString(), any())).thenReturn(false);
+        mTelephonyRegistry = new TelephonyRegistry(mContext, mMockConfigurationProvider);
         addTelephonyRegistryService();
         mTelephonyCallback = new TelephonyCallbackWrapper();
         mTelephonyCallback.init(mSimpleExecutor);
@@ -487,19 +493,41 @@
         assertSecurityExceptionThrown(
                 READ_PHONE_STATE_EVENTS.stream().mapToInt(i -> i).toArray());
 
-        // Grant permssion
+        // Grant permission
         mContextFixture.addCallingOrSelfPermission(android.Manifest.permission.READ_PHONE_STATE);
         assertSecurityExceptionNotThrown(
                 READ_PHONE_STATE_EVENTS.stream().mapToInt(i -> i).toArray());
     }
 
     /**
+     * Test enforcement of READ_PHONE_STATE for call state related events.
+     */
+    @Test
+    public void testCallStateChangedPermission() {
+        int[] events = new int[] {TelephonyCallback.EVENT_CALL_STATE_CHANGED,
+                TelephonyCallback.EVENT_LEGACY_CALL_STATE_CHANGED};
+        // Disable change ID for READ_PHONE_STATE enforcement
+        when(mMockConfigurationProvider.isCallStateReadPhoneStateEnforcedInPlatformCompat(
+                anyString(), any())).thenReturn(false);
+        // Start without READ_PHONE_STATE permission
+        mContextFixture.addCallingOrSelfPermission("");
+        assertSecurityExceptionNotThrown(events);
+        // Grant permission
+        mContextFixture.addCallingOrSelfPermission(android.Manifest.permission.READ_PHONE_STATE);
+        assertSecurityExceptionNotThrown(events);
+        //Enable READ_PHONE_STATE enforcement
+        when(mMockConfigurationProvider.isCallStateReadPhoneStateEnforcedInPlatformCompat(
+                anyString(), any())).thenReturn(true);
+        assertSecurityExceptionNotThrown(events);
+        // revoke READ_PHONE_STATE permission
+        mContextFixture.removeCallingOrSelfPermission(android.Manifest.permission.READ_PHONE_STATE);
+        assertSecurityExceptionThrown(events);
+
+    }
+
+    /**
      * Test listen to events that require READ_PRECISE_PHONE_STATE permission.
      */
-    // FIXME(b/159082270) - Simply not granting location permission doesn't fix the test because
-    // Location is soft-denied to apps that aren't in the foreground, and soft-denial currently
-    // short-circuits the test.
-    @Ignore("Skip due to b/159082270")
     @Test
     public void testReadPrecisePhoneStatePermission() {
         // Clear all permission grants for test.
@@ -514,7 +542,7 @@
         assertSecurityExceptionThrown(
                 READ_PRECISE_PHONE_STATE_EVENTS.stream().mapToInt(i -> i).toArray());
 
-        // Grant permssion
+        // Grant permission
         mContextFixture.addCallingOrSelfPermission(
                 android.Manifest.permission.READ_PRECISE_PHONE_STATE);
         assertSecurityExceptionNotThrown(
@@ -555,7 +583,7 @@
         assertSecurityExceptionThrown(
                 READ_PRIVILEGED_PHONE_STATE_EVENTS.stream().mapToInt(i -> i).toArray());
 
-        // Grant permssion
+        // Grant permission
         mContextFixture.addCallingOrSelfPermission(
                 android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
         assertSecurityExceptionNotThrown(
@@ -572,7 +600,7 @@
         assertSecurityExceptionThrown(
                 READ_ACTIVE_EMERGENCY_SESSION_EVENTS.stream().mapToInt(i -> i).toArray());
 
-        // Grant permssion
+        // Grant permission
         mContextFixture.addCallingOrSelfPermission(
                 android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION);
         assertSecurityExceptionNotThrown(
@@ -615,7 +643,7 @@
                 .queryLocalInterface(anyString());
 
         UserInfo userInfo = new UserInfo(UserHandle.myUserId(), "" /* name */, 0 /* flags */);
-        doReturn(userInfo).when(mIActivityManager).getCurrentUser();
+        doReturn(userInfo.id).when(mIActivityManager).getCurrentUserId();
 
         doReturn(true).when(mLocationManager).isLocationEnabledForUser(any(UserHandle.class));
 
@@ -639,6 +667,7 @@
         // Broadcast ACTION_USER_SWITCHED for the current user Id + 1. Callback shouldn't be
         // triggered.
         userInfo.id++;
+        doReturn(userInfo.id).when(mIActivityManager).getCurrentUserId();
         mCellLocation = null;
         mContext.sendBroadcast(new Intent(Intent.ACTION_USER_SWITCHED));
 
@@ -695,4 +724,67 @@
         processAllMessages();
         assertEquals(lceList, mLinkCapacityEstimateList);
     }
+
+    @Test
+    public void testPreciseDataConnectionStateChangedForInvalidSubId() {
+        //set dual sim
+        doReturn(2).when(mTelephonyManager).getActiveModemCount();
+        mContext.sendBroadcast(new Intent(ACTION_MULTI_SIM_CONFIG_CHANGED));
+        // set default slot
+        mContext.sendBroadcast(new Intent(ACTION_DEFAULT_SUBSCRIPTION_CHANGED)
+                .putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, 2)
+                .putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, 0));
+        processAllMessages();
+
+        final int subId = 1;
+        int[] events = {TelephonyCallback.EVENT_PRECISE_DATA_CONNECTION_STATE_CHANGED};
+        doReturn(mMockSubInfo).when(mSubscriptionManager).getActiveSubscriptionInfo(anyInt());
+        doReturn(1).when(mMockSubInfo).getSimSlotIndex();
+
+        mTelephonyRegistry.listenWithEventList(subId, mContext.getOpPackageName(),
+                mContext.getAttributionTag(), mTelephonyCallback.callback, events, true);
+        processAllMessages();
+
+        // notify data connection with invalid sub id and default phone id
+        mTelephonyRegistry.notifyDataConnectionForSubscriber(
+                /*default phoneId*/ 0, /*invalid subId*/ -1,
+                new PreciseDataConnectionState.Builder()
+                        .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
+                        .setId(1)
+                        .setState(TelephonyManager.DATA_CONNECTED)
+                        .setNetworkType(TelephonyManager.NETWORK_TYPE_LTE)
+                        .setApnSetting(new ApnSetting.Builder()
+                                .setApnTypeBitmask(ApnSetting.TYPE_IMS)
+                                .setApnName("ims")
+                                .setEntryName("ims")
+                                .build())
+                        .setLinkProperties(new LinkProperties())
+                        .setFailCause(0)
+                        .build());
+
+        processAllMessages();
+
+        assertEquals(0, mTelephonyCallback.invocationCount.get());
+
+        // notify data connection with invalid sub id and default phone id
+        mTelephonyRegistry.notifyDataConnectionForSubscriber(
+                /*target phoneId*/ 1, /*invalid subId*/ -1,
+                new PreciseDataConnectionState.Builder()
+                        .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
+                        .setId(2)
+                        .setState(TelephonyManager.DATA_SUSPENDED)
+                        .setNetworkType(TelephonyManager.NETWORK_TYPE_LTE)
+                        .setApnSetting(new ApnSetting.Builder()
+                                .setApnTypeBitmask(ApnSetting.TYPE_IMS)
+                                .setApnName("ims")
+                                .setEntryName("ims")
+                                .build())
+                        .setLinkProperties(new LinkProperties())
+                        .setFailCause(0)
+                        .build());
+
+        processAllMessages();
+
+        assertEquals(1, mTelephonyCallback.invocationCount.get());
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java b/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
index 5fcc507..2242d55 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
@@ -58,7 +58,7 @@
 import android.os.RegistrantList;
 import android.os.ServiceManager;
 import android.os.UserManager;
-import android.permission.PermissionManager;
+import android.permission.LegacyPermissionManager;
 import android.provider.BlockedNumberContract;
 import android.provider.DeviceConfig;
 import android.provider.Settings;
@@ -90,6 +90,7 @@
 import com.android.internal.telephony.dataconnection.DataEnabledSettings;
 import com.android.internal.telephony.dataconnection.DataThrottler;
 import com.android.internal.telephony.dataconnection.DcTracker;
+import com.android.internal.telephony.dataconnection.LinkBandwidthEstimator;
 import com.android.internal.telephony.dataconnection.TransportManager;
 import com.android.internal.telephony.emergency.EmergencyNumberTracker;
 import com.android.internal.telephony.imsphone.ImsExternalCallTracker;
@@ -114,7 +115,7 @@
 import com.android.internal.telephony.uicc.UiccProfile;
 import com.android.internal.telephony.uicc.UiccSlot;
 import com.android.server.pm.PackageManagerService;
-import com.android.server.pm.permission.PermissionManagerService;
+import com.android.server.pm.permission.LegacyPermissionManagerService;
 
 import org.mockito.Mock;
 import org.mockito.Mockito;
@@ -210,7 +211,7 @@
     @Mock
     protected PackageManagerService mMockPackageManager;
     @Mock
-    protected PermissionManagerService mMockPermissionManager;
+    protected LegacyPermissionManagerService mMockLegacyPermissionManager;
 
     protected NetworkRegistrationInfo mNetworkRegistrationInfo =
             new NetworkRegistrationInfo.Builder()
@@ -322,6 +323,8 @@
     @Mock
     protected ImsStats mImsStats;
     @Mock
+    protected LinkBandwidthEstimator mLinkBandwidthEstimator;
+    @Mock
     protected PinStorage mPinStorage;
     @Mock
     protected LocationManager mLocationManager;
@@ -453,7 +456,7 @@
         TelephonyManager.disableServiceHandleCaching();
         SubscriptionController.disableCaching();
         // For testing do not allow Log.WTF as it can cause test process to crash
-        Log.setWtfHandler((tagString, what, system) -> logd("WTF captured, ignoring. Tag: "
+        Log.setWtfHandler((tagString, what, system) -> Log.d(TAG, "WTF captured, ignoring. Tag: "
                 + tagString + ", exception: " + what));
 
         mPhones = new Phone[] {mPhone};
@@ -540,6 +543,8 @@
                 .makeDataEnabledSettings(nullable(Phone.class));
         doReturn(mEriManager).when(mTelephonyComponentFactory)
                 .makeEriManager(nullable(Phone.class), anyInt());
+        doReturn(mLinkBandwidthEstimator).when(mTelephonyComponentFactory)
+                .makeLinkBandwidthEstimator(nullable(Phone.class));
 
         //mPhone
         doReturn(mContext).when(mPhone).getContext();
@@ -570,6 +575,7 @@
         doReturn(mSmsStats).when(mPhone).getSmsStats();
         doReturn(mImsStats).when(mImsPhone).getImsStats();
         mIccSmsInterfaceManager.mDispatchersController = mSmsDispatchersController;
+        doReturn(mLinkBandwidthEstimator).when(mPhone).getLinkBandwidthEstimator();
         doReturn(mCellIdentity).when(mPhone).getCurrentCellIdentity();
         doReturn(mCellLocation).when(mCellIdentity).asCellLocation();
 
@@ -580,6 +586,7 @@
                 eq(UiccController.APP_FAM_3GPP2));
         doReturn(mUiccCardApplicationIms).when(mUiccController).getUiccCardApplication(anyInt(),
                 eq(UiccController.APP_FAM_IMS));
+        doReturn(mUiccCard).when(mUiccController).getUiccCard(anyInt());
 
         doAnswer(new Answer<IccRecords>() {
             public IccRecords answer(InvocationOnMock invocation) {
@@ -648,8 +655,8 @@
         mSST.mRestrictedState = mRestrictedState;
         mServiceManagerMockedServices.put("connectivity_metrics_logger", mConnMetLoggerBinder);
         mServiceManagerMockedServices.put("package", mMockPackageManager);
-        mServiceManagerMockedServices.put("permissionmgr", mMockPermissionManager);
-        logd("mMockPermissionManager replaced");
+        mServiceManagerMockedServices.put("legacy_permission", mMockLegacyPermissionManager);
+        logd("mMockLegacyPermissionManager replaced");
         doReturn(new int[]{AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
                 AccessNetworkConstants.TRANSPORT_TYPE_WLAN})
                 .when(mTransportManager).getAvailableTransports();
@@ -858,12 +865,21 @@
 
         // TelephonyPermissions uses a SystemAPI to check if the calling package meets any of the
         // generic requirements for device identifier access (currently READ_PRIVILEGED_PHONE_STATE,
-        // appop, and device / profile owner checks. This sets up the PermissionManager to return
+        // appop, and device / profile owner checks). This sets up the PermissionManager to return
         // that access requirements are met.
         setIdentifierAccess(true);
-        PermissionManager permissionManager = new PermissionManager(mContext, null,
-                mMockPermissionManager);
-        doReturn(permissionManager).when(mContext).getSystemService(eq(Context.PERMISSION_SERVICE));
+        LegacyPermissionManager legacyPermissionManager =
+                new LegacyPermissionManager(mMockLegacyPermissionManager);
+        doReturn(legacyPermissionManager).when(mContext)
+                .getSystemService(Context.LEGACY_PERMISSION_SERVICE);
+        // Also make sure all appop checks fails, to not interfere tests. Tests should explicitly
+        // mock AppOpManager to return allowed/default mode. Note by default a mock returns 0 which
+        // is MODE_ALLOWED, hence this setup is necessary.
+        doReturn(AppOpsManager.MODE_IGNORED).when(mAppOpsManager).noteOpNoThrow(
+                /* op= */ anyString(), /* uid= */ anyInt(),
+                /* packageName= */ nullable(String.class),
+                /* attributionTag= */ nullable(String.class),
+                /* message= */ nullable(String.class));
 
         // TelephonyPermissions queries DeviceConfig to determine if the identifier access
         // restrictions should be enabled; this results in a NPE when DeviceConfig uses
@@ -890,9 +906,13 @@
 
     protected void setIdentifierAccess(boolean hasAccess) {
         doReturn(hasAccess ? PackageManager.PERMISSION_GRANTED
-                : PackageManager.PERMISSION_DENIED).when(
-                mMockPermissionManager).checkDeviceIdentifierAccess(any(), any(), any(), anyInt(),
-                anyInt());
+                : PackageManager.PERMISSION_DENIED).when(mMockLegacyPermissionManager)
+                .checkDeviceIdentifierAccess(any(), any(), any(), anyInt(), anyInt());
+    }
+
+    protected void setPhoneNumberAccess(int value) {
+        doReturn(value).when(mMockLegacyPermissionManager).checkPhoneNumberAccess(any(), any(),
+                any(), anyInt(), anyInt());
     }
 
     protected void setCarrierPrivileges(boolean hasCarrierPrivileges) {
diff --git a/tests/telephonytests/src/com/android/internal/telephony/WapPushOverSmsTest.java b/tests/telephonytests/src/com/android/internal/telephony/WapPushOverSmsTest.java
index 48adb64..ca1fca8 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/WapPushOverSmsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/WapPushOverSmsTest.java
@@ -29,7 +29,6 @@
 import static org.mockito.Mockito.when;
 
 import android.app.AppOpsManager;
-import android.content.BroadcastReceiver;
 import android.content.Intent;
 import android.os.Bundle;
 import android.os.UserHandle;
@@ -92,7 +91,7 @@
                 eq(android.Manifest.permission.RECEIVE_WAP_PUSH),
                 eq(AppOpsManager.OPSTR_RECEIVE_WAP_PUSH),
                 nullable(Bundle.class),
-                isNull(BroadcastReceiver.class),
+                isNull(InboundSmsHandler.SmsBroadcastReceiver.class),
                 eq(UserHandle.SYSTEM),
                 anyInt());
         Intent intent = intentArgumentCaptor.getValue();
@@ -147,7 +146,7 @@
                 any(String.class),
                 any(String.class),
                 any(Bundle.class),
-                any(BroadcastReceiver.class),
+                any(InboundSmsHandler.SmsBroadcastReceiver.class),
                 any(UserHandle.class),
                 anyInt());
     }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/cdma/CdmaSmsCbTest.java b/tests/telephonytests/src/com/android/internal/telephony/cdma/CdmaSmsCbTest.java
index 0dc9647..c9a16f5 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/cdma/CdmaSmsCbTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/cdma/CdmaSmsCbTest.java
@@ -26,6 +26,7 @@
 import androidx.test.filters.FlakyTest;
 
 import com.android.internal.telephony.GsmAlphabet;
+import com.android.internal.telephony.RILUtils;
 import com.android.internal.telephony.cdma.sms.BearerData;
 import com.android.internal.telephony.cdma.sms.CdmaSmsAddress;
 import com.android.internal.telephony.cdma.sms.SmsEnvelope;
@@ -120,7 +121,7 @@
         for (byte b : bearerData) {
             msg.bearerData.add(b);
         }
-        SmsMessage message = SmsMessageConverter.newCdmaSmsMessageFromRil(msg);
+        SmsMessage message = RILUtils.convertHalCdmaSmsMessage(msg);
         return message;
     }
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/d2d/CommunicatorTest.java b/tests/telephonytests/src/com/android/internal/telephony/d2d/CommunicatorTest.java
index 2ebb1bc..5681dc4 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/d2d/CommunicatorTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/d2d/CommunicatorTest.java
@@ -19,6 +19,7 @@
 import static junit.framework.Assert.assertEquals;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.verify;
@@ -39,6 +40,7 @@
 import org.mockito.MockitoAnnotations;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.Set;
 
@@ -57,7 +59,6 @@
         MockitoAnnotations.initMocks(this);
         TransportProtocol protocol1 = getMockTransportProtocol();
         TransportProtocol protocol2 = getMockTransportProtocol();
-
         mTransportProtocols.add(protocol1);
         mTransportProtocols.add(protocol2);
     }
@@ -77,6 +78,37 @@
         verify(mTransportProtocols.get(1)).startNegotiation();
         assertEquals(mTransportProtocols.get(1), mCommunicator.getActiveTransport());
         mCallback.onNegotiationSuccess(mTransportProtocols.get(1));
+        verify(mCommunicatorCallback).onD2DAvailabilitychanged(eq(true));
+    }
+
+    /**
+     * Verifies that D2D negotiation failed callback is invoked when D2D could not be negotiated.
+     */
+    @SmallTest
+    @Test
+    public void testNegotiationFailed() {
+        mCommunicator = new Communicator(mTransportProtocols, mCommunicatorCallback);
+        mCommunicator.onStateChanged(null, Connection.STATE_ACTIVE);
+        verify(mTransportProtocols.get(0)).startNegotiation();
+        assertEquals(mTransportProtocols.get(0), mCommunicator.getActiveTransport());
+        // Assume negotiation on the first one failed.
+        mCallback.onNegotiationFailed(mTransportProtocols.get(0));
+        verify(mTransportProtocols.get(1)).startNegotiation();
+        assertEquals(mTransportProtocols.get(1), mCommunicator.getActiveTransport());
+        // Oops, the second one failed too; not negotiated!
+        mCallback.onNegotiationFailed(mTransportProtocols.get(1));
+        verify(mCommunicatorCallback).onD2DAvailabilitychanged(eq(false));
+    }
+
+    /**
+     * Verifies that D2D negotiation failed callback is invoked when no transports are available.
+     */
+    @SmallTest
+    @Test
+    public void testNegotiationFailedNoProtocols() {
+        mCommunicator = new Communicator(Collections.EMPTY_LIST, mCommunicatorCallback);
+        mCommunicator.onStateChanged(null, Connection.STATE_ACTIVE);
+        verify(mCommunicatorCallback).onD2DAvailabilitychanged(eq(false));
     }
 
     /**
diff --git a/tests/telephonytests/src/com/android/internal/telephony/d2d/RtpTransportConversionTest.java b/tests/telephonytests/src/com/android/internal/telephony/d2d/RtpTransportConversionTest.java
index 72fda53..0c251a0 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/d2d/RtpTransportConversionTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/d2d/RtpTransportConversionTest.java
@@ -26,7 +26,6 @@
 
 import android.os.Handler;
 import android.telephony.ims.RtpHeaderExtension;
-import android.telephony.ims.RtpHeaderExtensionType;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.ArraySet;
 
@@ -84,7 +83,7 @@
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-        mRtpTransport = new RtpTransport(mRtpAdapter, mTimeoutsAdapter, mHandler);
+        mRtpTransport = new RtpTransport(mRtpAdapter, mTimeoutsAdapter, mHandler, true /* sdp */);
         mRtpTransport.setCallback(mCallback);
 
         when(mRtpAdapter.getAcceptedRtpHeaderExtensions()).thenReturn(
diff --git a/tests/telephonytests/src/com/android/internal/telephony/d2d/RtpTransportTest.java b/tests/telephonytests/src/com/android/internal/telephony/d2d/RtpTransportTest.java
index 4208212..c241cf2 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/d2d/RtpTransportTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/d2d/RtpTransportTest.java
@@ -67,7 +67,7 @@
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-        mRtpTransport = new RtpTransport(mRtpAdapter, mTimeoutsAdapter, mHandler);
+        mRtpTransport = new RtpTransport(mRtpAdapter, mTimeoutsAdapter, mHandler, true /* sdp */);
         mRtpTransport.setCallback(mCallback);
     }
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DataCallResponseTest.java b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DataCallResponseTest.java
index d28c2e8..d9208ab 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DataCallResponseTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DataCallResponseTest.java
@@ -28,6 +28,7 @@
 import android.telephony.data.ApnSetting;
 import android.telephony.data.DataCallResponse;
 import android.telephony.data.EpsQos;
+import android.telephony.data.Qos;
 import android.telephony.data.TrafficDescriptor;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
@@ -37,7 +38,8 @@
 
 public class DataCallResponseTest extends AndroidTestCase {
     public static final String FAKE_DNN = "FAKE_DNN";
-    public static final String FAKE_OS_APP_ID = "FAKE_OS_APP_ID";
+    public static final byte[] FAKE_OS_APP_ID = {1, 2, 3, 4};
+    public static final byte[] FAKE_OS_APP_ID_2 = {5, 6, 8, 9};
 
     @SmallTest
     public void testParcel() {
@@ -56,7 +58,8 @@
                         Arrays.asList(InetAddresses.parseNumericAddress(FAKE_PCSCF_ADDRESS)))
                 .setMtuV4(1440)
                 .setMtuV6(1440)
-                .setDefaultQos(new EpsQos())
+                .setDefaultQos(new EpsQos(
+                        new Qos.QosBandwidth(-1, -1), new Qos.QosBandwidth(-1, -1), -1))
                 .setQosBearerSessions(new ArrayList<>())
                 .setTrafficDescriptors(
                         Arrays.asList(new TrafficDescriptor(FAKE_DNN, FAKE_OS_APP_ID)))
@@ -131,7 +134,7 @@
                 .setMtuV4(1441)
                 .setMtuV6(1440)
                 .setTrafficDescriptors(
-                        Arrays.asList(new TrafficDescriptor("FAKE_DNN_2", "FAKE_OS_APP_ID_2")))
+                        Arrays.asList(new TrafficDescriptor("FAKE_DNN_2", FAKE_OS_APP_ID_2)))
                 .build();
 
         assertNotSame(response1, response2);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DataConnectionTest.java b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DataConnectionTest.java
index 1778a6f..ca8abeb 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DataConnectionTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DataConnectionTest.java
@@ -30,24 +30,25 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Matchers.argThat;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyBoolean;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
-import android.content.IntentFilter;
-import android.content.pm.ServiceInfo;
-import android.hardware.radio.V1_0.SetupDataCallResult;
 import android.net.InetAddresses;
 import android.net.KeepalivePacketData;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.NattKeepalivePacketData;
+import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.os.AsyncResult;
 import android.os.Handler;
@@ -57,23 +58,26 @@
 import android.telephony.AccessNetworkConstants.AccessNetworkType;
 import android.telephony.CarrierConfigManager;
 import android.telephony.ServiceState;
+import android.telephony.ServiceState.RegState;
+import android.telephony.ServiceState.RilRadioTechnology;
 import android.telephony.data.ApnSetting;
 import android.telephony.data.DataCallResponse;
 import android.telephony.data.DataProfile;
 import android.telephony.data.DataService;
+import android.telephony.data.DataServiceCallback;
 import android.telephony.data.TrafficDescriptor;
 import android.test.suitebuilder.annotation.MediumTest;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Pair;
 
 import com.android.internal.R;
+import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.RetryManager;
 import com.android.internal.telephony.TelephonyTest;
 import com.android.internal.telephony.dataconnection.DataConnection.ConnectionParams;
 import com.android.internal.telephony.dataconnection.DataConnection.DisconnectParams;
 import com.android.internal.telephony.dataconnection.DataConnection.SetupResult;
 import com.android.internal.telephony.metrics.DataCallSessionStats;
-import com.android.internal.util.IState;
-import com.android.internal.util.StateMachine;
 
 import org.junit.After;
 import org.junit.Before;
@@ -83,8 +87,10 @@
 
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.function.Consumer;
 
 public class DataConnectionTest extends TelephonyTest {
     private static final int DEFAULT_DC_CID = 10;
@@ -105,11 +111,12 @@
     DataCallSessionStats mDataCallSessionStats;
     @Mock
     DataConnection mDefaultDc;
+    @Mock
+    DataServiceManager mDataServiceManager;
 
     private DataConnection mDc;
     private DataConnectionTestHandler mDataConnectionTestHandler;
     private DcController mDcc;
-    private CellularDataService mCellularDataService;
 
     private ApnSetting mApn1 = ApnSetting.makeApnSetting(
             2163,                   // id
@@ -166,7 +173,7 @@
             "");                    // mnvo_match_data
 
     private ApnSetting mApn3 = ApnSetting.makeApnSetting(
-            2164,                   // id
+            2165,                   // id
             "44010",                // numeric
             "sp-mode",              // name
             "spmode.ne.jp",         // apn
@@ -196,7 +203,7 @@
             1);                     // skip_464xlat
 
     private ApnSetting mApn4 = ApnSetting.makeApnSetting(
-            2164,                   // id
+            2166,                   // id
             "44010",                // numeric
             "sp-mode",              // name
             "spmode.ne.jp",         // apn
@@ -223,7 +230,7 @@
             "");                    // mnvo_match_data
 
     private ApnSetting mApn5 = ApnSetting.makeApnSetting(
-            2164,                   // id
+            2167,                   // id
             "44010",                // numeric
             "sp-mode",              // name
             "spmode.ne.jp",         // apn
@@ -252,6 +259,33 @@
             -1,                     // carrier_id
             0);                     // skip_464xlat
 
+    private ApnSetting mApn6 = ApnSetting.makeApnSetting(
+            2168,                   // id
+            "44010",                // numeric
+            "sp-mode",              // name
+            "spmode.ne.jp",         // apn
+            null,                   // proxy
+            -1,                     // port
+            null,                   // mmsc
+            null,                   // mmsproxy
+            -1,                     // mmsport
+            "",                     // user
+            "",                     // password
+            -1,                     // authtype
+            ApnSetting.TYPE_EMERGENCY, // types
+            ApnSetting.PROTOCOL_IP, // protocol
+            ApnSetting.PROTOCOL_IP, // roaming_protocol
+            true,                   // carrier_enabled
+            0,                      // networktype_bitmask
+            0,                      // profile_id
+            false,                  // modem_cognitive
+            0,                      // max_conns
+            0,                      // wait_time
+            0,                      // max_conns_time
+            0,                      // mtu
+            -1,                     // mvno_type
+            "");                    // mnvo_match_data
+
     private class DataConnectionTestHandler extends HandlerThread {
 
         private DataConnectionTestHandler(String name) {
@@ -261,30 +295,61 @@
         @Override
         public void onLooperPrepared() {
             Handler h = new Handler();
-
-            DataServiceManager manager = new DataServiceManager(mPhone,
-                    AccessNetworkConstants.TRANSPORT_TYPE_WWAN, "");
-            mDcc = DcController.makeDcc(mPhone, mDcTracker, manager, h.getLooper(), "");
-            mDc = DataConnection.makeDataConnection(mPhone, 0, mDcTracker, manager,
+            mDcc = DcController.makeDcc(mPhone, mDcTracker, mDataServiceManager, h.getLooper(), "");
+            mDc = DataConnection.makeDataConnection(mPhone, 0, mDcTracker, mDataServiceManager,
                     mDcTesterFailBringUpAll, mDcc, true);
         }
     }
 
-    private void addDataService() {
-        mCellularDataService = new CellularDataService();
-        ServiceInfo serviceInfo = new ServiceInfo();
-        serviceInfo.packageName = "com.android.phone";
-        serviceInfo.permission = "android.permission.BIND_TELEPHONY_DATA_SERVICE";
-        IntentFilter filter = new IntentFilter();
-        mContextFixture.addService(
-                DataService.SERVICE_INTERFACE,
-                null,
-                "com.android.phone",
-                mCellularDataService.mBinder,
-                serviceInfo,
-                filter);
+    private void setSuccessfulSetupDataResponse(int cid) {
+        doAnswer(invocation -> {
+            final Message msg = (Message) invocation.getArguments()[10];
+
+            DataCallResponse response = new DataCallResponse.Builder()
+                    .setCause(0)
+                    .setRetryDurationMillis(-1L)
+                    .setId(cid)
+                    .setLinkStatus(2)
+                    .setProtocolType(ApnSetting.PROTOCOL_IPV4V6)
+                    .setInterfaceName("ifname")
+                    .setAddresses(Arrays.asList(
+                            new LinkAddress(InetAddresses.parseNumericAddress("10.0.2.15"), 32),
+                            new LinkAddress("2607:fb90:a620:651d:eabe:f8da:c107:44be/64")))
+                    .setDnsAddresses(Arrays.asList(InetAddresses.parseNumericAddress("10.0.2.3"),
+                            InetAddresses.parseNumericAddress("fd00:976a::9")))
+                    .setGatewayAddresses(Arrays.asList(
+                            InetAddresses.parseNumericAddress("10.0.2.15"),
+                            InetAddresses.parseNumericAddress("fe80::2")))
+                    .setPcscfAddresses(Arrays.asList(
+                            InetAddresses.parseNumericAddress("fd00:976a:c305:1d::8"),
+                            InetAddresses.parseNumericAddress("fd00:976a:c202:1d::7"),
+                            InetAddresses.parseNumericAddress("fd00:976a:c305:1d::5")))
+                    .setMtu(1500)
+                    .setMtuV4(1500)
+                    .setMtuV6(1500)
+                    .setPduSessionId(1)
+                    .setQosBearerSessions(new ArrayList<>())
+                    .setTrafficDescriptors(new ArrayList<>())
+                    .build();
+            msg.getData().putParcelable("data_call_response", response);
+            msg.arg1 = DataServiceCallback.RESULT_SUCCESS;
+            msg.sendToTarget();
+            return null;
+        }).when(mDataServiceManager).setupDataCall(anyInt(), any(DataProfile.class), anyBoolean(),
+                anyBoolean(), anyInt(), any(), anyInt(), any(), any(), anyBoolean(),
+                any(Message.class));
     }
 
+    private void setFailedSetupDataResponse(@DataServiceCallback.ResultCode int resultCode) {
+        doAnswer(invocation -> {
+            final Message msg = (Message) invocation.getArguments()[10];
+            msg.arg1 = resultCode;
+            msg.sendToTarget();
+            return null;
+        }).when(mDataServiceManager).setupDataCall(anyInt(), any(DataProfile.class), anyBoolean(),
+                anyBoolean(), anyInt(), any(), anyInt(), any(), any(), anyBoolean(),
+                any(Message.class));
+    }
 
     @Before
     public void setUp() throws Exception {
@@ -318,7 +383,18 @@
                 "com.android.phone");
 
         mDcp.mApnContext = mApnContext;
-        addDataService();
+
+        setSuccessfulSetupDataResponse(DEFAULT_DC_CID);
+
+        doAnswer(invocation -> {
+            final Message msg = (Message) invocation.getArguments()[2];
+            msg.arg1 = DataServiceCallback.RESULT_SUCCESS;
+            msg.sendToTarget();
+            return null;
+        }).when(mDataServiceManager).deactivateDataCall(anyInt(), anyInt(), any(Message.class));
+
+        doReturn(AccessNetworkConstants.TRANSPORT_TYPE_WWAN).when(mDataServiceManager)
+                .getTransportType();
 
         mDataConnectionTestHandler = new DataConnectionTestHandler(getClass().getSimpleName());
         mDataConnectionTestHandler.start();
@@ -336,16 +412,9 @@
         mDcc = null;
         mDataConnectionTestHandler.quit();
         mDataConnectionTestHandler.join();
-        mCellularDataService.onDestroy();
         super.tearDown();
     }
 
-    private IState getCurrentState() throws Exception {
-        Method method = StateMachine.class.getDeclaredMethod("getCurrentState");
-        method.setAccessible(true);
-        return (IState) method.invoke(mDc);
-    }
-
     private long getSuggestedRetryDelay(DataCallResponse response) throws Exception {
         Class[] cArgs = new Class[1];
         cArgs[0] = DataCallResponse.class;
@@ -366,6 +435,12 @@
         return (boolean) method.invoke(mDc);
     }
 
+    private boolean isSuspended() throws Exception {
+        Field field = DataConnection.class.getDeclaredField("mIsSuspended");
+        field.setAccessible(true);
+        return field.getBoolean(mDc);
+    }
+
     private SetupResult setLinkProperties(DataCallResponse response, LinkProperties linkProperties)
             throws Exception {
         Class[] cArgs = new Class[2];
@@ -378,14 +453,8 @@
 
     @Test
     @SmallTest
-    public void testSanity() throws Exception {
-        assertEquals("DcInactiveState", getCurrentState().getName());
-    }
-
-    @Test
-    @SmallTest
-    public void testConnectEvent() throws Exception {
-        testSanity();
+    public void testConnectEvent() {
+        assertTrue(mDc.isInactive());
         connectEvent(true);
 
         verify(mCT, times(1)).registerForVoiceCallStarted(any(Handler.class),
@@ -408,7 +477,7 @@
         ArgumentCaptor<DataProfile> dpCaptor = ArgumentCaptor.forClass(DataProfile.class);
         ArgumentCaptor<TrafficDescriptor> tdCaptor =
                 ArgumentCaptor.forClass(TrafficDescriptor.class);
-        verify(mSimulatedCommandsVerifier, times(1)).setupDataCall(
+        verify(mDataServiceManager, times(1)).setupDataCall(
                 eq(AccessNetworkType.UTRAN), dpCaptor.capture(), eq(false),
                 eq(false), eq(DataService.REQUEST_REASON_NORMAL), any(),
                 anyInt(), any(), tdCaptor.capture(), anyBoolean(), any(Message.class));
@@ -420,8 +489,8 @@
         if (tdCaptor.getValue() != null) {
             if (mApnContext.getApnTypeBitmask() == ApnSetting.TYPE_ENTERPRISE) {
                 assertEquals(null, tdCaptor.getValue().getDataNetworkName());
-                assertTrue(tdCaptor.getValue().getOsAppId()
-                        .contains(ApnSetting.TYPE_ENTERPRISE_STRING));
+                assertTrue(Arrays.equals(DataConnection.getEnterpriseOsAppId(),
+                        tdCaptor.getValue().getOsAppId()));
             } else {
                 assertEquals("spmode.ne.jp", tdCaptor.getValue().getDataNetworkName());
                 assertEquals(null, tdCaptor.getValue().getOsAppId());
@@ -438,22 +507,7 @@
 
     @Test
     public void testConnectEventDuplicateContextIds() throws Exception {
-        setUpDefaultData();
-
-        // Create successful result with the same CID as default
-        SetupDataCallResult result = new SetupDataCallResult();
-        result.status = 0;
-        result.suggestedRetryTime = -1;
-        result.cid = DEFAULT_DC_CID;
-        result.active = 2;
-        result.type = "IP";
-        result.ifname = FAKE_IFNAME;
-        result.addresses = FAKE_ADDRESS;
-        result.dnses = FAKE_DNS;
-        result.gateways = FAKE_GATEWAY;
-        result.pcscf = FAKE_PCSCF_ADDRESS;
-        result.mtu = 1440;
-        mSimulatedCommands.setDataCallResult(true, result);
+        setUpDefaultData(DEFAULT_DC_CID);
 
         // Try to connect ENTERPRISE with the same CID as default
         replaceInstance(ConnectionParams.class, "mApnContext", mCp, mEnterpriseApnContext);
@@ -463,11 +517,10 @@
 
         // Verify that ENTERPRISE wasn't set up
         connectEvent(false);
-        assertEquals("DcInactiveState", getCurrentState().getName());
+        assertTrue(mDc.isInactive());
 
         // Change the CID
-        result.cid = DEFAULT_DC_CID + 1;
-        mSimulatedCommands.setDataCallResult(true, result);
+        setSuccessfulSetupDataResponse(DEFAULT_DC_CID + 1);
 
         // Verify that ENTERPRISE was set up
         connectEvent(true);
@@ -487,11 +540,11 @@
 
         // Verify that ENTERPRISE wasn't set up
         connectEvent(false);
-        assertEquals("DcInactiveState", getCurrentState().getName());
+        assertTrue(mDc.isInactive());
 
         // Set up default data
         replaceInstance(ConnectionParams.class, "mApnContext", mCp, mApnContext);
-        setUpDefaultData();
+        setUpDefaultData(1);
 
         // Verify that ENTERPRISE was set up
         replaceInstance(ConnectionParams.class, "mApnContext", mCp, mEnterpriseApnContext);
@@ -500,8 +553,8 @@
                 NetworkCapabilities.NET_CAPABILITY_ENTERPRISE));
     }
 
-    private void setUpDefaultData() throws Exception {
-        replaceInstance(DataConnection.class, "mCid", mDefaultDc, DEFAULT_DC_CID);
+    private void setUpDefaultData(int cid) throws Exception {
+        replaceInstance(DataConnection.class, "mCid", mDefaultDc, cid);
         doReturn(true).when(mDefaultDc).isActive();
         doReturn(Arrays.asList(mApnContext)).when(mDefaultDc).getApnContexts();
         mDcc.addActiveDcByCid(mDefaultDc);
@@ -511,7 +564,7 @@
 
     @Test
     @SmallTest
-    public void testDisconnectEvent() throws Exception {
+    public void testDisconnectEvent() {
         testConnectEvent();
 
         mDc.setPduSessionId(5);
@@ -520,12 +573,12 @@
         verify(mSimulatedCommandsVerifier, times(1)).unregisterForLceInfo(any(Handler.class));
         verify(mSimulatedCommandsVerifier, times(1))
                 .unregisterForNattKeepaliveStatus(any(Handler.class));
-        verify(mSimulatedCommandsVerifier, times(1)).deactivateDataCall(eq(1),
+        verify(mDataServiceManager, times(1)).deactivateDataCall(eq(DEFAULT_DC_CID),
                 eq(DataService.REQUEST_REASON_NORMAL), any(Message.class));
         verify(mSimulatedCommandsVerifier, times(1))
                 .releasePduSessionId(any(), eq(5));
 
-        assertEquals("DcInactiveState", getCurrentState().getName());
+        assertTrue(mDc.isInactive());
     }
 
     @Test
@@ -733,7 +786,7 @@
                 .hasCapability(NetworkCapabilities.NET_CAPABILITY_ENTERPRISE));
 
         disconnectEvent();
-        setUpDefaultData();
+        setUpDefaultData(1);
         replaceInstance(ConnectionParams.class, "mApnContext", mCp, mEnterpriseApnContext);
         doReturn(mApn1).when(mEnterpriseApnContext).getApnSetting();
         doReturn(ApnSetting.TYPE_ENTERPRISE_STRING).when(mEnterpriseApnContext).getApnType();
@@ -742,7 +795,7 @@
 
         assertFalse("capabilities: " + getNetworkCapabilities(), getNetworkCapabilities()
                 .hasCapability(NetworkCapabilities.NET_CAPABILITY_DUN));
-        assertFalse("capabilities: " + getNetworkCapabilities(), getNetworkCapabilities()
+        assertTrue("capabilities: " + getNetworkCapabilities(), getNetworkCapabilities()
                 .hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET));
         assertFalse("capabilities: " + getNetworkCapabilities(), getNetworkCapabilities()
                 .hasCapability(NetworkCapabilities.NET_CAPABILITY_SUPL));
@@ -874,10 +927,16 @@
         }
     }
 
-    private void disconnectEvent() throws Exception {
+    private void disconnectEvent() {
         mDc.sendMessage(DataConnection.EVENT_DISCONNECT, mDcp);
         waitForMs(100);
-        assertEquals("DcInactiveState", getCurrentState().getName());
+        assertTrue(mDc.isInactive());
+    }
+
+    private void serviceStateChangedEvent(@RegState int dataRegState, @RilRadioTechnology int rat) {
+        mDc.obtainMessage(DataConnection.EVENT_DATA_CONNECTION_DRS_OR_RAT_CHANGED,
+                new AsyncResult(null, new Pair<>(dataRegState, rat), null)).sendToTarget();
+        waitForMs(100);
     }
 
     @Test
@@ -1194,7 +1253,7 @@
         assertFalse(mDc.getNetworkCapabilities().hasCapability(
                 NetworkCapabilities.NET_CAPABILITY_ENTERPRISE));
 
-        setUpDefaultData();
+        setUpDefaultData(1);
         replaceInstance(ConnectionParams.class, "mApnContext", mCp, mEnterpriseApnContext);
         doReturn(mApn1).when(mEnterpriseApnContext).getApnSetting();
         doReturn(ApnSetting.TYPE_ENTERPRISE_STRING).when(mEnterpriseApnContext).getApnType();
@@ -1217,4 +1276,108 @@
         assertEquals(ApnSetting.TYPE_MMS | ApnSetting.TYPE_SUPL | ApnSetting.TYPE_FOTA,
                 getDisallowedApnTypes());
     }
+
+    @Test
+    public void testIsSuspended() throws Exception {
+        // Return false if not active state
+        assertTrue(mDc.isInactive());
+        assertFalse(isSuspended());
+
+        // Return false for emergency APN
+        doReturn(mApn6).when(mApnContext).getApnSetting();
+        doReturn(ApnSetting.TYPE_EMERGENCY).when(mApnContext).getApnTypeBitmask();
+        connectEvent(true);
+        assertFalse(isSuspended());
+
+        // Back to DEFAULT APN
+        disconnectEvent();
+        assertTrue(mDc.isInactive());
+        doReturn(mApn1).when(mApnContext).getApnSetting();
+        doReturn(ApnSetting.TYPE_DEFAULT).when(mApnContext).getApnTypeBitmask();
+        doReturn(true).when(mSST).isConcurrentVoiceAndDataAllowed();
+        connectEvent(true);
+
+        // Before getting any service state event, the connection should not be suspended.
+        assertFalse(isSuspended());
+
+        // Return true if combined reg state is not in service
+        serviceStateChangedEvent(ServiceState.STATE_OUT_OF_SERVICE,
+                ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN);
+        assertTrue(isSuspended());
+
+        // Return false if in service and concurrent voice and data is allowed
+        serviceStateChangedEvent(ServiceState.STATE_IN_SERVICE,
+                ServiceState.RIL_RADIO_TECHNOLOGY_LTE);
+        assertFalse(isSuspended());
+
+        // Return false if in service and concurrent voice/data not allowed but call state is idle
+        doReturn(false).when(mSST).isConcurrentVoiceAndDataAllowed();
+        doReturn(PhoneConstants.State.IDLE).when(mCT).getState();
+        mDc.sendMessage(DataConnection.EVENT_DATA_CONNECTION_VOICE_CALL_STARTED);
+        waitForMs(100);
+        assertFalse(isSuspended());
+
+        // Return true if in service, concurrent voice/data not allowed, and call state not idle
+        doReturn(PhoneConstants.State.RINGING).when(mCT).getState();
+        mDc.sendMessage(DataConnection.EVENT_DATA_CONNECTION_VOICE_CALL_STARTED);
+        waitForMs(100);
+        assertTrue(isSuspended());
+    }
+
+    @Test
+    public void testDataCreatedWhenOutOfService() throws Exception {
+        serviceStateChangedEvent(ServiceState.STATE_OUT_OF_SERVICE,
+                ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN);
+        ArgumentCaptor<NetworkCapabilities> ncCaptor =
+                ArgumentCaptor.forClass(NetworkCapabilities.class);
+        doReturn(mock(Network.class)).when(mConnectivityManager).registerNetworkAgent(
+                any(), any(), any(), ncCaptor.capture(), any(), any(), anyInt());
+
+        doReturn(mApn1).when(mApnContext).getApnSetting();
+        doReturn(ApnSetting.TYPE_DEFAULT).when(mApnContext).getApnTypeBitmask();
+        doReturn(true).when(mSST).isConcurrentVoiceAndDataAllowed();
+        connectEvent(true);
+        waitForMs(100);
+
+        NetworkCapabilities nc = ncCaptor.getValue();
+        // The network must be created with NOT_SUSPENDED capability.
+        assertTrue(nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED));
+
+        // But it's final state must be suspended.
+        assertTrue(isSuspended());
+    }
+
+    @Test
+    public void testDataServiceTempUnavailable() throws Exception {
+        setFailedSetupDataResponse(DataServiceCallback.RESULT_ERROR_TEMPORARILY_UNAVAILABLE);
+        replaceInstance(ConnectionParams.class, "mRequestType", mCp,
+                DcTracker.REQUEST_TYPE_NORMAL);
+        // Verify that no data was setup
+        connectEvent(false);
+        assertTrue(mDc.isInactive());
+
+        // Verify that data service did not suggest any retry (i.e. Frameworks uses configured
+        // retry timer).
+        verify(mDataThrottler).setRetryTime(eq(ApnSetting.TYPE_DEFAULT | ApnSetting.TYPE_SUPL),
+                eq(RetryManager.NO_SUGGESTED_RETRY_DELAY), eq(DcTracker.REQUEST_TYPE_NORMAL));
+    }
+
+    @Test
+    public void testDataHandoverFailed() throws Exception {
+        doReturn(mDefaultDc).when(mDcTracker).getDataConnectionByApnType(anyString());
+
+        doAnswer(invocation -> {
+            final Consumer<Integer> consumer = (Consumer<Integer>) invocation.getArguments()[0];
+            consumer.accept(DataServiceCallback.RESULT_SUCCESS);
+            return null;
+        }).when(mDefaultDc).startHandover(any(Consumer.class));
+
+        replaceInstance(ConnectionParams.class, "mRequestType", mCp,
+                DcTracker.REQUEST_TYPE_HANDOVER);
+        assertTrue(mDc.isInactive());
+        connectEvent(false);
+
+        // Make sure the data connection is still in inactive state
+        assertTrue(mDc.isInactive());
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DcControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DcControllerTest.java
index 3c8e7ab..003f74e 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DcControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DcControllerTest.java
@@ -22,7 +22,9 @@
 import static com.android.internal.telephony.dataconnection.DcTrackerTest.FAKE_IFNAME;
 import static com.android.internal.telephony.dataconnection.DcTrackerTest.FAKE_PCSCF_ADDRESS;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.eq;
@@ -33,7 +35,9 @@
 import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.os.AsyncResult;
+import android.os.Handler;
 import android.os.Looper;
+import android.os.Message;
 import android.telephony.AccessNetworkConstants;
 import android.telephony.data.ApnSetting;
 import android.telephony.data.DataCallResponse;
@@ -51,6 +55,7 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 
 import java.lang.reflect.Method;
@@ -63,7 +68,10 @@
 public class DcControllerTest extends TelephonyTest {
 
     private static final int DATA_CONNECTION_ACTIVE_PH_LINK_DORMANT = 1;
+    private static final int DATA_CONNECTION_ACTIVE_PH_LINK_ACTIVE = 2;
+
     private static final int EVENT_DATA_STATE_CHANGED = 0x00040007;
+    private static final int EVENT_PHYSICAL_LINK_STATE_CHANGED = 1;
 
     @Mock
     private DataConnection mDc;
@@ -71,6 +79,8 @@
     private List<ApnContext> mApnContexts;
     @Mock
     private DataServiceManager mDataServiceManager;
+    @Mock
+    private Handler mTestHandler;
 
     UpdateLinkPropertyResult mResult;
 
@@ -142,4 +152,131 @@
 
         verify(mDcTracker, times(1)).sendStopNetStatPoll(eq(DctConstants.Activity.DORMANT));
     }
+
+    @Test
+    @SmallTest
+    public void testPhysicalLinkStateChanged_defaultApnTypeAndDormant_registrantNotifyResult()
+            throws Exception {
+        ArrayList<DataCallResponse> l = new ArrayList<>();
+        DataCallResponse dcResponse = new DataCallResponse.Builder()
+                .setCause(0)
+                .setRetryDurationMillis(-1)
+                .setId(1)
+                .setLinkStatus(DATA_CONNECTION_ACTIVE_PH_LINK_DORMANT)
+                .setProtocolType(ApnSetting.PROTOCOL_IP)
+                .setInterfaceName(FAKE_IFNAME)
+                .setAddresses(Arrays.asList(
+                        new LinkAddress(InetAddresses.parseNumericAddress(FAKE_ADDRESS), 0)))
+                .setDnsAddresses(Arrays.asList(InetAddresses.parseNumericAddress(FAKE_DNS)))
+                .setGatewayAddresses(Arrays.asList(InetAddresses.parseNumericAddress(FAKE_GATEWAY)))
+                .setPcscfAddresses(
+                        Arrays.asList(InetAddresses.parseNumericAddress(FAKE_PCSCF_ADDRESS)))
+                .setMtuV4(1440)
+                .setMtuV6(1440)
+                .build();
+        l.add(dcResponse);
+        mDc.mCid = 1;
+        mDcc.addActiveDcByCid(mDc);
+        ApnContext apnContext = new ApnContext(mPhone, ApnSetting.TYPE_DEFAULT, TAG, mDcTracker, 1);
+        List<ApnContext> apnContextList = new ArrayList<>();
+        apnContextList.add(apnContext);
+        doReturn(apnContextList).when(mDc).getApnContexts();
+        doReturn(true).when(mDcTracker).getLteEndcUsingUserDataForIdleDetection();
+        mDcc.registerForPhysicalLinkStateChanged(mTestHandler, EVENT_PHYSICAL_LINK_STATE_CHANGED);
+
+        mDcc.sendMessage(mDcc.obtainMessage(EVENT_DATA_STATE_CHANGED,
+                new AsyncResult(null, l, null)));
+        processAllMessages();
+
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(mTestHandler, times(1)).sendMessageDelayed(messageCaptor.capture(), anyLong());
+        Message message = messageCaptor.getValue();
+        assertEquals(EVENT_PHYSICAL_LINK_STATE_CHANGED, message.what);
+        AsyncResult ar = (AsyncResult) message.obj;
+        assertEquals(DcController.PHYSICAL_LINK_NOT_ACTIVE, (int) ar.result);
+    }
+
+    @Test
+    @SmallTest
+    public void testPhysicalLinkStateChanged_imsApnTypeAndDormant_NoNotifyResult()
+            throws Exception {
+        testPhysicalLinkStateChanged_defaultApnTypeAndDormant_registrantNotifyResult();
+
+        ArrayList<DataCallResponse> l = new ArrayList<>();
+        DataCallResponse dcResponse = new DataCallResponse.Builder()
+                .setCause(0)
+                .setRetryDurationMillis(-1)
+                .setId(1)
+                .setLinkStatus(DATA_CONNECTION_ACTIVE_PH_LINK_ACTIVE)
+                .setProtocolType(ApnSetting.PROTOCOL_IP)
+                .setInterfaceName(FAKE_IFNAME)
+                .setAddresses(Arrays.asList(
+                        new LinkAddress(InetAddresses.parseNumericAddress(FAKE_ADDRESS), 0)))
+                .setDnsAddresses(Arrays.asList(InetAddresses.parseNumericAddress(FAKE_DNS)))
+                .setGatewayAddresses(Arrays.asList(InetAddresses.parseNumericAddress(FAKE_GATEWAY)))
+                .setPcscfAddresses(
+                        Arrays.asList(InetAddresses.parseNumericAddress(FAKE_PCSCF_ADDRESS)))
+                .setMtuV4(1440)
+                .setMtuV6(1440)
+                .build();
+        l.add(dcResponse);
+        mDc.mCid = 1;
+        mDcc.addActiveDcByCid(mDc);
+        ApnContext apnContext = new ApnContext(mPhone, ApnSetting.TYPE_IMS, TAG, mDcTracker, 1);
+        List<ApnContext> apnContextList = new ArrayList<>();
+        apnContextList.add(apnContext);
+        doReturn(apnContextList).when(mDc).getApnContexts();
+        doReturn(true).when(mDcTracker).getLteEndcUsingUserDataForIdleDetection();
+
+        mDcc.sendMessage(mDcc.obtainMessage(EVENT_DATA_STATE_CHANGED,
+                new AsyncResult(null, l, null)));
+        processAllMessages();
+
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(mTestHandler, times(1)).sendMessageDelayed(messageCaptor.capture(), anyLong());
+    }
+
+    @Test
+    @SmallTest
+    public void testPhysicalLinkStateChanged_defaultApnTypeAndStateChanged_registrantNotifyResult()
+            throws Exception {
+        testPhysicalLinkStateChanged_imsApnTypeAndDormant_NoNotifyResult();
+
+        ArrayList<DataCallResponse> l = new ArrayList<>();
+        DataCallResponse dcResponse = new DataCallResponse.Builder()
+                .setCause(0)
+                .setRetryDurationMillis(-1)
+                .setId(1)
+                .setLinkStatus(DATA_CONNECTION_ACTIVE_PH_LINK_ACTIVE)
+                .setProtocolType(ApnSetting.PROTOCOL_IP)
+                .setInterfaceName(FAKE_IFNAME)
+                .setAddresses(Arrays.asList(
+                        new LinkAddress(InetAddresses.parseNumericAddress(FAKE_ADDRESS), 0)))
+                .setDnsAddresses(Arrays.asList(InetAddresses.parseNumericAddress(FAKE_DNS)))
+                .setGatewayAddresses(Arrays.asList(InetAddresses.parseNumericAddress(FAKE_GATEWAY)))
+                .setPcscfAddresses(
+                        Arrays.asList(InetAddresses.parseNumericAddress(FAKE_PCSCF_ADDRESS)))
+                .setMtuV4(1440)
+                .setMtuV6(1440)
+                .build();
+        l.add(dcResponse);
+        mDc.mCid = 1;
+        mDcc.addActiveDcByCid(mDc);
+        ApnContext apnContext = new ApnContext(mPhone, ApnSetting.TYPE_DEFAULT, TAG, mDcTracker, 1);
+        List<ApnContext> apnContextList = new ArrayList<>();
+        apnContextList.add(apnContext);
+        doReturn(apnContextList).when(mDc).getApnContexts();
+        doReturn(true).when(mDcTracker).getLteEndcUsingUserDataForIdleDetection();
+
+        mDcc.sendMessage(mDcc.obtainMessage(EVENT_DATA_STATE_CHANGED,
+                new AsyncResult(null, l, null)));
+        processAllMessages();
+
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(mTestHandler, times(2)).sendMessageDelayed(messageCaptor.capture(), anyLong());
+        Message message = messageCaptor.getValue();
+        assertEquals(EVENT_PHYSICAL_LINK_STATE_CHANGED, message.what);
+        AsyncResult ar = (AsyncResult) message.obj;
+        assertEquals(DcController.PHYSICAL_LINK_ACTIVE, (int) ar.result);
+    }
 }
\ No newline at end of file
diff --git a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DcTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DcTrackerTest.java
index 2b165c5..f0e2b88 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DcTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DcTrackerTest.java
@@ -58,6 +58,7 @@
 import android.net.NetworkRequest;
 import android.net.Uri;
 import android.os.AsyncResult;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
@@ -68,7 +69,9 @@
 import android.provider.Telephony;
 import android.telephony.AccessNetworkConstants;
 import android.telephony.AccessNetworkConstants.AccessNetworkType;
+import android.telephony.Annotation;
 import android.telephony.CarrierConfigManager;
+import android.telephony.DataFailCause;
 import android.telephony.NetworkRegistrationInfo;
 import android.telephony.PreciseDataConnectionState;
 import android.telephony.ServiceState;
@@ -79,6 +82,7 @@
 import android.telephony.TelephonyDisplayInfo;
 import android.telephony.TelephonyManager;
 import android.telephony.data.ApnSetting;
+import android.telephony.data.DataCallResponse;
 import android.telephony.data.DataProfile;
 import android.telephony.data.DataService;
 import android.telephony.data.TrafficDescriptor;
@@ -814,6 +818,18 @@
         }
     }
 
+    private void addHandoverCompleteMsg(Message onCompleteMsg,
+            @Annotation.ApnType int apnType) {
+        try {
+            Method method = DcTracker.class.getDeclaredMethod("addHandoverCompleteMsg",
+                    Message.class, int.class);
+            method.setAccessible(true);
+            method.invoke(mDct, onCompleteMsg, apnType);
+        } catch (Exception e) {
+            fail(e.toString());
+        }
+    }
+
     private void sendInitializationEvents() {
         sendCarrierConfigChanged("");
 
@@ -1356,7 +1372,8 @@
                 eq(DataService.REQUEST_REASON_NORMAL), any(), anyInt(), any(), tdCaptor.capture(),
                 anyBoolean(), any(Message.class));
         assertEquals(null, tdCaptor.getValue().getDataNetworkName());
-        assertEquals("ENTERPRISE", tdCaptor.getValue().getOsAppId());
+        assertTrue(Arrays.equals(DataConnection.getEnterpriseOsAppId(),
+                tdCaptor.getValue().getOsAppId()));
     }
 
     // Test the ENTERPRISE APN setup when default data is not set up yet.
@@ -1378,7 +1395,8 @@
                 eq(DataService.REQUEST_REASON_NORMAL), any(), anyInt(), any(), tdCaptor.capture(),
                 anyBoolean(), any(Message.class));
         assertEquals(null, tdCaptor.getValue().getDataNetworkName());
-        assertEquals("ENTERPRISE", tdCaptor.getValue().getOsAppId());
+        assertTrue(Arrays.equals(DataConnection.getEnterpriseOsAppId(),
+                tdCaptor.getValue().getOsAppId()));
 
         // Check APN contexts with no DEFAULT set up
         Map<Integer, ApnContext> apnContexts = mDct.getApnContexts()
@@ -1430,7 +1448,8 @@
                 eq(DataService.REQUEST_REASON_NORMAL), any(), anyInt(), any(), tdCaptor.capture(),
                 anyBoolean(), any(Message.class));
         assertEquals(null, tdCaptor.getValue().getDataNetworkName());
-        assertEquals("ENTERPRISE", tdCaptor.getValue().getOsAppId());
+        assertTrue(Arrays.equals(DataConnection.getEnterpriseOsAppId(),
+                tdCaptor.getValue().getOsAppId()));
 
         // Check APN contexts after DEFAULT is set up (and ENTERPRISE reenabled)
         apnContexts = mDct.getApnContexts()
@@ -1469,7 +1488,7 @@
         assertEquals(FAKE_APN1, tds.get(0).getDataNetworkName());
         assertEquals(null, tds.get(0).getOsAppId());
         assertEquals(null, tds.get(1).getDataNetworkName());
-        assertEquals("ENTERPRISE", tds.get(1).getOsAppId());
+        assertTrue(Arrays.equals(DataConnection.getEnterpriseOsAppId(), tds.get(1).getOsAppId()));
 
         // Check APN contexts after DEFAULT and ENTERPRISE set up
         Map<Integer, ApnContext> apnContexts = mDct.getApnContexts()
@@ -1963,7 +1982,8 @@
                 eq(DataService.REQUEST_REASON_NORMAL), any(), anyInt(), any(), tdCaptor.capture(),
                 anyBoolean(), any(Message.class));
         assertEquals(null, tdCaptor.getValue().getDataNetworkName());
-        assertEquals("ENTERPRISE", tdCaptor.getValue().getOsAppId());
+        assertTrue(Arrays.equals(DataConnection.getEnterpriseOsAppId(),
+                tdCaptor.getValue().getOsAppId()));
 
         // Check APN contexts after ENTERPRISE is set up
         Map<Integer, ApnContext> apnContextsAfterRowIdsChanged = mDct.getApnContexts()
@@ -2877,6 +2897,20 @@
     }
 
     @Test
+    public void testDataUnthrottledAfterAPNChanged() throws Exception {
+        initApns(ApnSetting.TYPE_IMS_STRING, new String[]{ApnSetting.TYPE_IMS_STRING});
+        replaceInstance(DcTracker.class, "mDataThrottler", mDct, mDataThrottler);
+
+        mDct.enableApn(ApnSetting.TYPE_IMS, DcTracker.REQUEST_TYPE_NORMAL, null);
+        sendInitializationEvents();
+        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_APN_CHANGED, null));
+        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
+
+        // Verify unthrottling
+        verify(mDataThrottler).reset();
+    }
+
+    @Test
     public void testHandlingSecondHandoverRequest() throws Exception {
         initApns(ApnSetting.TYPE_IMS_STRING, new String[]{ApnSetting.TYPE_IMS_STRING});
         setUpDataConnection();
@@ -2932,4 +2966,50 @@
             assertEquals(TelephonyManager.DATA_DISCONNECTED, state.getState());
         }
     }
+
+    /**
+     * There is a corresponding test {@link DataConnectionTest#testDataServiceTempUnavailable()} to
+     * test DataConnection behavior.
+     */
+    @Test
+    public void testDataServiceTempUnavailable() {
+        Handler handler = Mockito.mock(Handler.class);
+        Message handoverCompleteMessage = Message.obtain(handler);
+        addHandoverCompleteMsg(handoverCompleteMessage, ApnSetting.TYPE_IMS);
+        initApns(ApnSetting.TYPE_IMS_STRING, new String[]{ApnSetting.TYPE_IMS_STRING});
+        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_DATA_SETUP_COMPLETE,
+                DcTracker.REQUEST_TYPE_HANDOVER, DataCallResponse.HANDOVER_FAILURE_MODE_UNKNOWN,
+                new AsyncResult(Pair.create(mApnContext, 0),
+                        DataFailCause.SERVICE_TEMPORARILY_UNAVAILABLE, new Exception())));
+        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
+        // Ensure handover is not completed yet
+        verify(handler, never()).sendMessageDelayed(any(), anyLong());
+    }
+
+    @Test
+    public void testNormalRequestDoesNotFailHandoverRequest() {
+        Handler handler = Mockito.mock(Handler.class);
+        Message handoverCompleteMessage = Message.obtain(handler);
+        addHandoverCompleteMsg(handoverCompleteMessage, ApnSetting.TYPE_IMS);
+        initApns(ApnSetting.TYPE_IMS_STRING, new String[]{ApnSetting.TYPE_IMS_STRING});
+        mDct.enableApn(ApnSetting.TYPE_IMS, DcTracker.REQUEST_TYPE_NORMAL, null);
+        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
+        // Ensure handover is not completed yet
+        verify(handler, never()).sendMessageDelayed(any(), anyLong());
+    }
+
+    @Test
+    public void testPreferenceChangedFallback() {
+        Handler handler = Mockito.mock(Handler.class);
+        doReturn(AccessNetworkConstants.TRANSPORT_TYPE_WLAN).when(mTransportManager)
+                .getPreferredTransport(anyInt());
+        Message handoverCompleteMessage = Message.obtain(handler);
+        addHandoverCompleteMsg(handoverCompleteMessage, ApnSetting.TYPE_IMS);
+        initApns(ApnSetting.TYPE_IMS_STRING, new String[]{ApnSetting.TYPE_IMS_STRING});
+        mDct.enableApn(ApnSetting.TYPE_IMS, DcTracker.REQUEST_TYPE_HANDOVER,
+                handoverCompleteMessage);
+        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
+        Bundle bundle = handoverCompleteMessage.getData();
+        assertTrue(bundle.getBoolean("extra_handover_failure_fallback"));
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/LinkBandwidthEstimatorTest.java b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/LinkBandwidthEstimatorTest.java
new file mode 100644
index 0000000..3467f3f
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/LinkBandwidthEstimatorTest.java
@@ -0,0 +1,687 @@
+/*
+ * 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.dataconnection;
+
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+
+import static com.android.internal.telephony.dataconnection.LinkBandwidthEstimator.BW_STATS_COUNT_THRESHOLD;
+import static com.android.internal.telephony.dataconnection.LinkBandwidthEstimator.LINK_RX;
+import static com.android.internal.telephony.dataconnection.LinkBandwidthEstimator.LINK_TX;
+import static com.android.internal.telephony.dataconnection.LinkBandwidthEstimator.MSG_ACTIVE_PHONE_CHANGED;
+import static com.android.internal.telephony.dataconnection.LinkBandwidthEstimator.MSG_DEFAULT_NETWORK_CHANGED;
+import static com.android.internal.telephony.dataconnection.LinkBandwidthEstimator.MSG_MODEM_ACTIVITY_RETURNED;
+import static com.android.internal.telephony.dataconnection.LinkBandwidthEstimator.MSG_NR_FREQUENCY_CHANGED;
+import static com.android.internal.telephony.dataconnection.LinkBandwidthEstimator.MSG_SCREEN_STATE_CHANGED;
+import static com.android.internal.telephony.dataconnection.LinkBandwidthEstimator.MSG_SIGNAL_STRENGTH_CHANGED;
+import static com.android.internal.telephony.dataconnection.LinkBandwidthEstimator.UNKNOWN_TAC;
+
+import static org.junit.Assert.*;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.net.NetworkCapabilities;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
+import android.telephony.CellIdentityLte;
+import android.telephony.ModemActivityInfo;
+import android.telephony.NetworkRegistrationInfo;
+import android.telephony.ServiceState;
+import android.telephony.TelephonyManager;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.util.Pair;
+
+import com.android.internal.telephony.TelephonyFacade;
+import com.android.internal.telephony.TelephonyTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class LinkBandwidthEstimatorTest extends TelephonyTest {
+    private LinkBandwidthEstimator mLBE;
+    private static final int [] TX_TIME_1_MS = new int[]{0, 0, 0, 0, 0};
+    private static final int [] TX_TIME_2_MS = new int[]{100, 0, 0, 0, 100};
+    private static final int RX_TIME_1_MS = 100;
+    private static final int RX_TIME_2_MS = 200;
+    private static final ModemActivityInfo MAI_INIT =
+            new ModemActivityInfo(0, 0, 0, TX_TIME_1_MS, RX_TIME_1_MS);
+    private static final ModemActivityInfo MAI_TX_RX_TIME_HIGH =
+            new ModemActivityInfo(100L, 0, 0, TX_TIME_2_MS, RX_TIME_2_MS);
+    private static final ModemActivityInfo MAI_RX_TIME_HIGH =
+            new ModemActivityInfo(100L, 0, 0, TX_TIME_1_MS, RX_TIME_2_MS);
+    private static final int EVENT_BANDWIDTH_ESTIMATOR_UPDATE = 1;
+    private NetworkCapabilities mNetworkCapabilities;
+    private CellIdentityLte mCellIdentity;
+    private long mElapsedTimeMs = 0;
+    private long mTxBytes = 0;
+    private long mRxBytes = 0;
+    @Mock
+    TelephonyFacade mTelephonyFacade;
+    @Mock
+    private Handler mTestHandler;
+    private NetworkRegistrationInfo mNri;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp(getClass().getSimpleName());
+        mNetworkCapabilities = new NetworkCapabilities.Builder()
+                .addTransportType(TRANSPORT_CELLULAR)
+                .build();
+
+        mCellIdentity = new CellIdentityLte(310, 260, 1234, 123456, 366);
+        mNri = new NetworkRegistrationInfo.Builder()
+                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE)
+                .build();
+        when(mServiceState.getNetworkRegistrationInfo(anyInt(), anyInt())).thenReturn(mNri);
+        when(mServiceState.getOperatorNumeric()).thenReturn("310260");
+        when(mTelephonyFacade.getElapsedSinceBootMillis()).thenReturn(0L);
+        when(mTelephonyFacade.getMobileTxBytes()).thenReturn(0L);
+        when(mTelephonyFacade.getMobileTxBytes()).thenReturn(0L);
+        when(mPhone.getCurrentCellIdentity()).thenReturn(mCellIdentity);
+        // Note that signal level is 0 before 1st MSG_SIGNAL_STRENGTH_CHANGED
+        when(mPhone.getSubId()).thenReturn(1);
+        when(mSignalStrength.getDbm()).thenReturn(-100);
+        when(mSignalStrength.getLevel()).thenReturn(1);
+        mLBE = new LinkBandwidthEstimator(mPhone, mTelephonyFacade);
+        mLBE.registerForBandwidthChanged(mTestHandler, EVENT_BANDWIDTH_ESTIMATOR_UPDATE, null);
+        mLBE.obtainMessage(MSG_DEFAULT_NETWORK_CHANGED, mNetworkCapabilities).sendToTarget();
+        mLBE.obtainMessage(MSG_SCREEN_STATE_CHANGED, false).sendToTarget();
+        mLBE.obtainMessage(MSG_ACTIVE_PHONE_CHANGED, 1).sendToTarget();
+        processAllMessages();
+    }
+
+    private void addElapsedTime(long timeMs) {
+        mElapsedTimeMs += timeMs;
+        when(mTelephonyFacade.getElapsedSinceBootMillis()).thenReturn(mElapsedTimeMs);
+    }
+
+    private void addTxBytes(long txBytes) {
+        mTxBytes += txBytes;
+        when(mTelephonyFacade.getMobileTxBytes()).thenReturn(mTxBytes);
+    }
+
+    private void addRxBytes(long rxBytes) {
+        mRxBytes += rxBytes;
+        when(mTelephonyFacade.getMobileRxBytes()).thenReturn(mRxBytes);
+    }
+
+    private void subtractRxBytes(long rxBytes) {
+        mRxBytes -= rxBytes;
+        when(mTelephonyFacade.getMobileRxBytes()).thenReturn(mRxBytes);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    @Test
+    public void testScreenOnTxTrafficHighOneModemPoll() throws Exception {
+        addElapsedTime(4_100);
+        moveTimeForward(4_100);
+        mLBE.obtainMessage(MSG_SCREEN_STATE_CHANGED, true).sendToTarget();
+        addTxBytes(500_000L);
+        addRxBytes(10_000L);
+        addElapsedTime(2_100);
+        moveTimeForward(2_100);
+        processAllMessages();
+
+        verify(mTelephonyManager, times(1)).requestModemActivityInfo(any(), any());
+    }
+
+    @Test
+    public void testScreenOnTxTrafficHighNotActivePhoneNoModemPoll() throws Exception {
+        mLBE.obtainMessage(MSG_ACTIVE_PHONE_CHANGED, 0).sendToTarget();
+        addElapsedTime(4_100);
+        moveTimeForward(4_100);
+        processAllMessages();
+
+        mLBE.obtainMessage(MSG_SCREEN_STATE_CHANGED, true).sendToTarget();
+        addTxBytes(500_000L);
+        addRxBytes(10_000L);
+        addElapsedTime(2_100);
+        moveTimeForward(2_100);
+        processAllMessages();
+
+        verify(mTelephonyManager, times(0)).requestModemActivityInfo(any(), any());
+    }
+
+    private void verifyUpdateBandwidth(int txKbps, int rxKbps) {
+        ArgumentCaptor<Message> messageArgumentCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(mTestHandler, atLeast(1))
+                .sendMessageAtTime(messageArgumentCaptor.capture(), anyLong());
+        assertEquals(EVENT_BANDWIDTH_ESTIMATOR_UPDATE, messageArgumentCaptor.getValue().what);
+        assertEquals(new Pair<Integer, Integer>(txKbps, rxKbps),
+                ((AsyncResult) messageArgumentCaptor.getValue().obj).result);
+    }
+
+    @Test
+    public void testScreenOnTxRxTrafficHighTwoModemPoll() throws Exception {
+        addElapsedTime(4_100);
+        moveTimeForward(4_100);
+        mLBE.obtainMessage(MSG_SCREEN_STATE_CHANGED, true).sendToTarget();
+        processAllMessages();
+
+        addTxBytes(10_000L);
+        addRxBytes(20_000L);
+        addElapsedTime(2_100);
+        moveTimeForward(2_100);
+        processAllMessages();
+        verify(mTelephonyManager, times(1)).requestModemActivityInfo(any(), any());
+
+        mLBE.obtainMessage(MSG_MODEM_ACTIVITY_RETURNED, MAI_INIT).sendToTarget();
+        processAllMessages();
+
+        addTxBytes(100_000L);
+        addRxBytes(200_000L);
+        addElapsedTime(5_100);
+        moveTimeForward(5_100);
+        processAllMessages();
+
+        mLBE.obtainMessage(MSG_MODEM_ACTIVITY_RETURNED, MAI_TX_RX_TIME_HIGH).sendToTarget();
+        processAllMessages();
+
+        verify(mTelephonyManager, times(2)).requestModemActivityInfo(any(), any());
+        verifyUpdateBandwidth(-1, -1);
+    }
+
+    @Test
+    public void testScreenOnRxTrafficHighTwoModemPollRxTimeHigh() throws Exception {
+        addElapsedTime(4_100);
+        moveTimeForward(4_100);
+        mLBE.obtainMessage(MSG_SCREEN_STATE_CHANGED, true).sendToTarget();
+        processAllMessages();
+
+        addTxBytes(10_000L);
+        addRxBytes(20_000L);
+        addElapsedTime(2_100);
+        moveTimeForward(2_100);
+        processAllMessages();
+        verify(mTelephonyManager, times(1)).requestModemActivityInfo(any(), any());
+
+        mLBE.obtainMessage(MSG_MODEM_ACTIVITY_RETURNED, MAI_INIT).sendToTarget();
+        processAllMessages();
+
+        addTxBytes(100_000L);
+        addRxBytes(200_000L);
+        addElapsedTime(5_100);
+        moveTimeForward(5_100);
+        processAllMessages();
+
+        mLBE.obtainMessage(MSG_MODEM_ACTIVITY_RETURNED, MAI_RX_TIME_HIGH).sendToTarget();
+        processAllMessages();
+
+        verify(mTelephonyManager, times(2)).requestModemActivityInfo(any(), any());
+        verifyUpdateBandwidth(-1, -1);
+    }
+
+    @Test
+    public void testScreenOnTxRxTrafficLow() throws Exception {
+        addElapsedTime(4_100);
+        moveTimeForward(4_100);
+        mLBE.obtainMessage(MSG_SCREEN_STATE_CHANGED, true).sendToTarget();
+        addTxBytes(10_000L);
+        addRxBytes(10_000L);
+        addElapsedTime(2_100);
+        moveTimeForward(2_100);
+        processAllMessages();
+        verify(mTelephonyManager, never()).requestModemActivityInfo(any(), any());
+    }
+
+    @Test
+    public void testScreenOnTrafficLowSampleHighAcc() throws Exception {
+        addElapsedTime(4_100);
+        moveTimeForward(4_100);
+        mLBE.obtainMessage(MSG_SCREEN_STATE_CHANGED, true).sendToTarget();
+        for (int i = 0; i < 30; i++) {
+            addTxBytes(10_000L);
+            addRxBytes(19_000L);
+            addElapsedTime(1_100);
+            moveTimeForward(1_100);
+            processAllMessages();
+        }
+        verify(mTelephonyManager, times(2)).requestModemActivityInfo(any(), any());
+    }
+
+    @Test
+    public void testScreenOnDefaultNetworkToggleNoExtraTrafficPoll() throws Exception {
+        mLBE.obtainMessage(MSG_SCREEN_STATE_CHANGED, true).sendToTarget();
+        addElapsedTime(500);
+        moveTimeForward(500);
+        processAllMessages();
+        mLBE.obtainMessage(MSG_DEFAULT_NETWORK_CHANGED, null).sendToTarget();
+        addElapsedTime(500);
+        moveTimeForward(500);
+        processAllMessages();
+        mLBE.obtainMessage(MSG_DEFAULT_NETWORK_CHANGED, mNetworkCapabilities).sendToTarget();
+        for (int i = 0; i < 3; i++) {
+            addElapsedTime(1_100);
+            moveTimeForward(1_100);
+            processAllMessages();
+        }
+
+        verify(mTelephonyFacade, times(4)).getMobileTxBytes();
+    }
+
+    @Test
+    public void testRatChangeTriggerBandwidthUpdate() throws Exception {
+        mLBE.obtainMessage(MSG_SCREEN_STATE_CHANGED, true).sendToTarget();
+        addTxBytes(10_000L);
+        addRxBytes(19_000L);
+        addElapsedTime(2000);
+        moveTimeForward(2000);
+        processAllMessages();
+
+        addTxBytes(10_000L);
+        addRxBytes(19_000L);
+        mNri = new NetworkRegistrationInfo.Builder()
+                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_NR)
+                .build();
+        when(mServiceState.getNetworkRegistrationInfo(anyInt(), anyInt())).thenReturn(mNri);
+        addElapsedTime(6000);
+        moveTimeForward(6000);
+        processAllMessages();
+
+        verify(mTelephonyManager, times(0)).requestModemActivityInfo(any(), any());
+        verifyUpdateBandwidth(-1, -1);
+    }
+
+    @Test
+    public void testSignalLevelChangeTriggerBandwidthUpdate() throws Exception {
+        mLBE.obtainMessage(MSG_SCREEN_STATE_CHANGED, true).sendToTarget();
+        processAllMessages();
+
+        for (int i = 0; i < BW_STATS_COUNT_THRESHOLD + 2; i++) {
+            addTxBytes(10_000L);
+            addRxBytes(500_000L);
+            addElapsedTime(5_100);
+            moveTimeForward(5_100);
+            processAllMessages();
+            mLBE.obtainMessage(MSG_MODEM_ACTIVITY_RETURNED, new ModemActivityInfo(
+                    i * 5_100L, 0, 0, TX_TIME_2_MS, i * RX_TIME_2_MS)).sendToTarget();
+            processAllMessages();
+        }
+
+        verifyUpdateBandwidth(-1, 19_597);
+
+        when(mSignalStrength.getLevel()).thenReturn(1);
+        when(mSignalStrength.getDbm()).thenReturn(-110);
+        mLBE.obtainMessage(MSG_SIGNAL_STRENGTH_CHANGED, mSignalStrength).sendToTarget();
+        addElapsedTime(6000);
+        moveTimeForward(6000);
+        processAllMessages();
+        verifyUpdateBandwidth(-1, 19_535);
+
+        when(mSignalStrength.getLevel()).thenReturn(2);
+        when(mSignalStrength.getDbm()).thenReturn(-90);
+        mLBE.obtainMessage(MSG_SIGNAL_STRENGTH_CHANGED, mSignalStrength).sendToTarget();
+        addElapsedTime(6000);
+        moveTimeForward(6000);
+        processAllMessages();
+        verifyUpdateBandwidth(-1, -1);
+
+        for (int i = 0; i < BW_STATS_COUNT_THRESHOLD + 2; i++) {
+            addTxBytes(10_000L);
+            addRxBytes(1000_000L);
+            addElapsedTime(5_100);
+            moveTimeForward(5_100);
+            processAllMessages();
+            mLBE.obtainMessage(MSG_MODEM_ACTIVITY_RETURNED, new ModemActivityInfo(
+                    i * 5_100L, 0, 0, TX_TIME_2_MS, i * RX_TIME_2_MS)).sendToTarget();
+            processAllMessages();
+        }
+
+        when(mSignalStrength.getLevel()).thenReturn(1);
+        when(mSignalStrength.getDbm()).thenReturn(-110);
+        mLBE.obtainMessage(MSG_SIGNAL_STRENGTH_CHANGED, mSignalStrength).sendToTarget();
+        addElapsedTime(6000);
+        moveTimeForward(6000);
+        processAllMessages();
+        verifyUpdateBandwidth(-1, 30_821);
+    }
+
+
+    @Test
+    public void testAvgBwForAllPossibleRat() throws Exception {
+        Pair<Integer, Integer> values = mLBE.getStaticAvgBw(TelephonyManager.NETWORK_TYPE_GPRS);
+        assertEquals(24, (int) values.second);
+        values = mLBE.getStaticAvgBw(TelephonyManager.NETWORK_TYPE_EDGE);
+        assertEquals(18, (int) values.second);
+        values = mLBE.getStaticAvgBw(TelephonyManager.NETWORK_TYPE_UMTS);
+        assertEquals(115, (int) values.second);
+        values = mLBE.getStaticAvgBw(TelephonyManager.NETWORK_TYPE_CDMA);
+        assertEquals(14, (int) values.second);
+        values = mLBE.getStaticAvgBw(TelephonyManager.NETWORK_TYPE_1xRTT);
+        assertEquals(30, (int) values.second);
+        values = mLBE.getStaticAvgBw(TelephonyManager.NETWORK_TYPE_EVDO_0);
+        assertEquals(48, (int) values.second);
+        values = mLBE.getStaticAvgBw(TelephonyManager.NETWORK_TYPE_EVDO_A);
+        assertEquals(550, (int) values.second);
+        values = mLBE.getStaticAvgBw(TelephonyManager.NETWORK_TYPE_HSDPA);
+        assertEquals(620, (int) values.second);
+        values = mLBE.getStaticAvgBw(TelephonyManager.NETWORK_TYPE_HSUPA);
+        assertEquals(1800, (int) values.second);
+        values = mLBE.getStaticAvgBw(TelephonyManager.NETWORK_TYPE_HSPA);
+        assertEquals(1800, (int) values.second);
+        values = mLBE.getStaticAvgBw(TelephonyManager.NETWORK_TYPE_EVDO_B);
+        assertEquals(550, (int) values.second);
+        values = mLBE.getStaticAvgBw(TelephonyManager.NETWORK_TYPE_EHRPD);
+        assertEquals(750, (int) values.first);
+        values = mLBE.getStaticAvgBw(TelephonyManager.NETWORK_TYPE_HSPAP);
+        assertEquals(3400, (int) values.second);
+        values = mLBE.getStaticAvgBw(TelephonyManager.NETWORK_TYPE_TD_SCDMA);
+        assertEquals(115, (int) values.first);
+        values = mLBE.getStaticAvgBw(TelephonyManager.NETWORK_TYPE_LTE);
+        assertEquals(15000, (int) values.second);
+        when(mServiceState.getNrState()).thenReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED);
+        when(mServiceState.getNrFrequencyRange()).thenReturn(ServiceState.FREQUENCY_RANGE_MMWAVE);
+        values = mLBE.getStaticAvgBw(TelephonyManager.NETWORK_TYPE_LTE);
+        assertEquals(145000, (int) values.first);
+        when(mServiceState.getNrFrequencyRange()).thenReturn(ServiceState.FREQUENCY_RANGE_UNKNOWN);
+        values = mLBE.getStaticAvgBw(TelephonyManager.NETWORK_TYPE_LTE);
+        assertEquals(47000, (int) values.first);
+        values = mLBE.getStaticAvgBw(TelephonyManager.NETWORK_TYPE_NR);
+        assertEquals(145_000, (int) values.first);
+        when(mServiceState.getNrFrequencyRange()).thenReturn(ServiceState.FREQUENCY_RANGE_MMWAVE);
+        values = mLBE.getStaticAvgBw(TelephonyManager.NETWORK_TYPE_NR);
+        assertEquals("NR_MMWAVE", mLBE.getDataRatName(TelephonyManager.NETWORK_TYPE_NR));
+        assertEquals(145_000, (int) values.first);
+    }
+
+    @Test
+    public void testSwitchToNrMmwaveTriggerBandwidthUpdate() throws Exception {
+        mLBE.obtainMessage(MSG_SCREEN_STATE_CHANGED, true).sendToTarget();
+        addTxBytes(10_000L);
+        addRxBytes(19_000L);
+        addElapsedTime(2000);
+        moveTimeForward(2000);
+        processAllMessages();
+
+        addTxBytes(10_000L);
+        addRxBytes(19_000L);
+        when(mServiceState.getNrState()).thenReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED);
+        when(mServiceState.getNrFrequencyRange()).thenReturn(ServiceState.FREQUENCY_RANGE_MMWAVE);
+        mLBE.obtainMessage(MSG_NR_FREQUENCY_CHANGED).sendToTarget();
+        addElapsedTime(6000);
+        moveTimeForward(6000);
+        processAllMessages();
+
+        verifyUpdateBandwidth(-1, -1);
+    }
+
+    @Test
+    public void testEnoughModemPollTriggerBwUpdate() throws Exception {
+        mLBE.obtainMessage(MSG_SCREEN_STATE_CHANGED, true).sendToTarget();
+        processAllMessages();
+
+        for (int i = 0; i < BW_STATS_COUNT_THRESHOLD + 2; i++) {
+            addTxBytes(10_000L);
+            addRxBytes(500_000L);
+            addElapsedTime(5_100);
+            moveTimeForward(5_100);
+            processAllMessages();
+            mLBE.obtainMessage(MSG_MODEM_ACTIVITY_RETURNED, new ModemActivityInfo(
+                    i * 5_100L, 0, 0, TX_TIME_2_MS, i * RX_TIME_2_MS)).sendToTarget();
+            processAllMessages();
+        }
+
+        verify(mTelephonyManager, times(BW_STATS_COUNT_THRESHOLD + 2))
+                .requestModemActivityInfo(any(), any());
+        verifyUpdateBandwidth(-1, 19_597);
+    }
+
+    @Test
+    public void testAbnormalTrafficCountTriggerLessBwUpdate() throws Exception {
+        mLBE.obtainMessage(MSG_SCREEN_STATE_CHANGED, true).sendToTarget();
+        processAllMessages();
+
+        for (int i = 0; i < BW_STATS_COUNT_THRESHOLD + 2; i++) {
+            if (i == 1) {
+                addTxBytes(10_000L);
+                subtractRxBytes(500_000L);
+            } else {
+                addTxBytes(10_000L);
+                addRxBytes(500_000L);
+            }
+            addElapsedTime(5_100);
+            moveTimeForward(5_100);
+            processAllMessages();
+            mLBE.obtainMessage(MSG_MODEM_ACTIVITY_RETURNED, new ModemActivityInfo(
+                    i * 5_100L, 0, 0, TX_TIME_2_MS, i * RX_TIME_2_MS)).sendToTarget();
+            processAllMessages();
+        }
+
+        verify(mTelephonyManager, times(BW_STATS_COUNT_THRESHOLD))
+                .requestModemActivityInfo(any(), any());
+        verifyUpdateBandwidth(-1, -1);
+    }
+
+    @Test
+    public void testUseCurrentTacStatsWithEnoughData() throws Exception {
+        mLBE.obtainMessage(MSG_SCREEN_STATE_CHANGED, true).sendToTarget();
+        processAllMessages();
+
+        for (int i = 0; i < BW_STATS_COUNT_THRESHOLD; i++) {
+            addTxBytes(10_000L);
+            addRxBytes(500_000L);
+            addElapsedTime(5_100);
+            moveTimeForward(5_100);
+            processAllMessages();
+            mLBE.obtainMessage(MSG_MODEM_ACTIVITY_RETURNED, new ModemActivityInfo(
+                    i * 5_100L, 0, 0, TX_TIME_2_MS, i * RX_TIME_2_MS)).sendToTarget();
+            processAllMessages();
+        }
+
+        mCellIdentity = new CellIdentityLte(310, 260, 1235, 123457, 367);
+        when(mPhone.getCurrentCellIdentity()).thenReturn(mCellIdentity);
+        for (int i = BW_STATS_COUNT_THRESHOLD; i < 3 * BW_STATS_COUNT_THRESHOLD; i++) {
+            addTxBytes(10_000L);
+            addRxBytes(500_000L);
+            addElapsedTime(5_100);
+            moveTimeForward(5_100);
+            processAllMessages();
+            mLBE.obtainMessage(MSG_MODEM_ACTIVITY_RETURNED, new ModemActivityInfo(
+                    i * 5_100L, 0, 0, TX_TIME_2_MS, i * RX_TIME_2_MS)).sendToTarget();
+            processAllMessages();
+        }
+
+        verifyUpdateBandwidth(-1, 19_597);
+    }
+
+    @Test
+    public void testUseAllTacStatsIfNoEnoughDataWithCurrentTac() throws Exception {
+        mLBE.obtainMessage(MSG_SCREEN_STATE_CHANGED, true).sendToTarget();
+        processAllMessages();
+        mLBE.obtainMessage(MSG_SIGNAL_STRENGTH_CHANGED, mSignalStrength).sendToTarget();
+        processAllMessages();
+
+        for (int i = 0; i < BW_STATS_COUNT_THRESHOLD; i++) {
+            addTxBytes(10_000L);
+            addRxBytes(900_000L);
+            addElapsedTime(5_100);
+            moveTimeForward(5_100);
+            processAllMessages();
+            mLBE.obtainMessage(MSG_MODEM_ACTIVITY_RETURNED, new ModemActivityInfo(
+                    i * 5_100L, 0, 0, TX_TIME_2_MS, i * RX_TIME_2_MS)).sendToTarget();
+            processAllMessages();
+        }
+
+        mCellIdentity = new CellIdentityLte(310, 260, 1234, 123456, 367);
+        when(mPhone.getCurrentCellIdentity()).thenReturn(mCellIdentity);
+        for (int i = BW_STATS_COUNT_THRESHOLD; i < BW_STATS_COUNT_THRESHOLD * 3 / 2; i++) {
+            addTxBytes(10_000L);
+            addRxBytes(1_000_000L);
+            addElapsedTime(5_100);
+            moveTimeForward(5_100);
+            processAllMessages();
+            mLBE.obtainMessage(MSG_MODEM_ACTIVITY_RETURNED, new ModemActivityInfo(
+                    i * 5_100L, 0, 0, TX_TIME_2_MS, i * RX_TIME_2_MS)).sendToTarget();
+            processAllMessages();
+        }
+
+        LinkBandwidthEstimator.NetworkBandwidth network = mLBE.lookupNetwork("310260", 366, "LTE");
+        assertEquals(BW_STATS_COUNT_THRESHOLD - 1, network.getCount(LINK_RX, 1));
+        assertEquals(900_000L * 8 * 1000 / 200 / 1024 * (BW_STATS_COUNT_THRESHOLD - 1),
+                network.getValue(LINK_RX, 1));
+        network = mLBE.lookupNetwork("310260", 367, "LTE");
+        assertEquals(1, network.getCount(LINK_RX, 1));
+        assertEquals(1_000_000L * 8 * 1000 / 200 / 1024,
+                network.getValue(LINK_RX, 1));
+        network = mLBE.lookupNetwork("310260", UNKNOWN_TAC, "LTE");
+        assertEquals(BW_STATS_COUNT_THRESHOLD * 3 / 2 - 2, network.getCount(LINK_RX, 1));
+        assertEquals(179_686, network.getValue(LINK_RX, 1));
+        verifyUpdateBandwidth(-1, 37_350);
+    }
+
+    @Test
+    public void testSwitchCarrierFallbackToColdStartValue() throws Exception {
+        mLBE.obtainMessage(MSG_SCREEN_STATE_CHANGED, true).sendToTarget();
+        processAllMessages();
+
+        for (int i = 0; i < BW_STATS_COUNT_THRESHOLD + 5; i++) {
+            addTxBytes(10_000L);
+            addRxBytes(500_000L);
+            addElapsedTime(5_100);
+            moveTimeForward(5_100);
+            processAllMessages();
+            mLBE.obtainMessage(MSG_MODEM_ACTIVITY_RETURNED, new ModemActivityInfo(
+                    i * 5_100L, 0, 0, TX_TIME_2_MS, i * RX_TIME_2_MS)).sendToTarget();
+            processAllMessages();
+        }
+
+        verifyUpdateBandwidth(-1, 19_597);
+
+        mCellIdentity = new CellIdentityLte(320, 265, 1234, 123456, 366);
+        when(mPhone.getCurrentCellIdentity()).thenReturn(mCellIdentity);
+        when(mServiceState.getOperatorNumeric()).thenReturn("320265");
+
+        addTxBytes(10_000L);
+        addRxBytes(10_000L);
+        addElapsedTime(5_100);
+        moveTimeForward(5_100);
+        processAllMessages();
+
+        verifyUpdateBandwidth(-1, -1);
+    }
+
+    @Test
+    public void testIgnoreLowTxRxTime() throws Exception {
+        mLBE.obtainMessage(MSG_SCREEN_STATE_CHANGED, true).sendToTarget();
+        processAllMessages();
+
+        for (int i = 0; i < BW_STATS_COUNT_THRESHOLD + 5; i++) {
+            addTxBytes(10_000L);
+            addRxBytes(500_000L);
+            addElapsedTime(5_100);
+            moveTimeForward(5_100);
+            processAllMessages();
+            mLBE.obtainMessage(MSG_MODEM_ACTIVITY_RETURNED, new ModemActivityInfo(
+                    i * 5_100L, 0, 0, TX_TIME_2_MS, i * 80)).sendToTarget();
+            processAllMessages();
+        }
+
+        verifyUpdateBandwidth(-1, -1);
+    }
+
+    @Test
+    public void testEdgeThenLteShouldIgnoreTransitionStats() throws Exception {
+        mLBE.obtainMessage(MSG_SCREEN_STATE_CHANGED, true).sendToTarget();
+        processAllMessages();
+        mNri = new NetworkRegistrationInfo.Builder()
+                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_EDGE)
+                .build();
+        when(mServiceState.getNetworkRegistrationInfo(anyInt(), anyInt())).thenReturn(mNri);
+        mLBE.obtainMessage(MSG_SIGNAL_STRENGTH_CHANGED, mSignalStrength).sendToTarget();
+        processAllMessages();
+        for (int i = 0; i < BW_STATS_COUNT_THRESHOLD * 2; i++) {
+            addTxBytes(12_000L);
+            addRxBytes(24_000L);
+            addElapsedTime(5_100);
+            moveTimeForward(5_100);
+            processAllMessages();
+            mLBE.obtainMessage(MSG_MODEM_ACTIVITY_RETURNED, new ModemActivityInfo(
+                    i * 5_100L, 0, 0, TX_TIME_2_MS, i * RX_TIME_2_MS * 5)).sendToTarget();
+            processAllMessages();
+        }
+
+        LinkBandwidthEstimator.NetworkBandwidth network = mLBE.lookupNetwork("310260", 366, "EDGE");
+
+        assertEquals(0, network.getCount(LINK_TX, 1));
+        assertEquals(BW_STATS_COUNT_THRESHOLD * 2 - 1, network.getCount(LINK_RX, 1));
+        assertEquals(24_000L * 8 / 1024 * (BW_STATS_COUNT_THRESHOLD * 2 - 1),
+                network.getValue(LINK_RX, 1));
+
+        mNri = new NetworkRegistrationInfo.Builder()
+                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE)
+                .build();
+        when(mServiceState.getNetworkRegistrationInfo(anyInt(), anyInt())).thenReturn(mNri);
+        for (int i = BW_STATS_COUNT_THRESHOLD * 2; i < BW_STATS_COUNT_THRESHOLD * 4; i++) {
+            addTxBytes(1_200_000L);
+            addRxBytes(2_400_000L);
+            addElapsedTime(5_100);
+            moveTimeForward(5_100);
+            processAllMessages();
+            mLBE.obtainMessage(MSG_MODEM_ACTIVITY_RETURNED, new ModemActivityInfo(
+                    i * 5_100L, 0, 0, TX_TIME_2_MS, i * RX_TIME_2_MS * 10)).sendToTarget();
+            processAllMessages();
+        }
+        network = mLBE.lookupNetwork("310260", 366, "LTE");
+
+        assertEquals(BW_STATS_COUNT_THRESHOLD * 2 - 2, network.getCount(LINK_RX, 1));
+        assertEquals(0, network.getCount(LINK_TX, 1));
+    }
+
+
+    @Test
+    public void testVeryHighRxLinkBandwidthEstimationIgnored() throws Exception {
+        mLBE.obtainMessage(MSG_SCREEN_STATE_CHANGED, true).sendToTarget();
+        processAllMessages();
+        mLBE.obtainMessage(MSG_SIGNAL_STRENGTH_CHANGED, mSignalStrength).sendToTarget();
+        processAllMessages();
+        for (int i = 0; i < BW_STATS_COUNT_THRESHOLD + 5; i++) {
+            addTxBytes(8_000_000_000L);
+            addRxBytes(16_000_000_000L);
+            addElapsedTime(5_100);
+            moveTimeForward(5_100);
+            processAllMessages();
+            mLBE.obtainMessage(MSG_MODEM_ACTIVITY_RETURNED, new ModemActivityInfo(
+                    i * 5_100L, 0, 0, TX_TIME_2_MS, i * RX_TIME_2_MS * 5)).sendToTarget();
+            processAllMessages();
+        }
+
+        // This will result in link bandwidth estimation value 128Gbps which is too high for LTE.
+        // So it will be ignored by the estimator.
+        LinkBandwidthEstimator.NetworkBandwidth network = mLBE.lookupNetwork("310260", 366, "LTE");
+
+        assertEquals(0, network.getCount(LINK_RX, 1));
+        assertEquals(0, network.getValue(LINK_TX, 1));
+        assertEquals(0, network.getValue(LINK_RX, 1));
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/QosCallbackTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/QosCallbackTrackerTest.java
index e7c4db4..7939318 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/QosCallbackTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/QosCallbackTrackerTest.java
@@ -16,12 +16,9 @@
 
 package com.android.internal.telephony.dataconnection;
 
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
@@ -31,12 +28,11 @@
 import android.net.InetAddresses;
 import android.net.LinkAddress;
 import android.net.Network;
-import android.net.NetworkAgent;
 import android.telephony.data.EpsBearerQosSessionAttributes;
 import android.telephony.data.EpsQos;
+import android.telephony.data.Qos;
 import android.telephony.data.QosBearerFilter;
 import android.telephony.data.QosBearerSession;
-
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -47,15 +43,12 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 
-import java.lang.reflect.Field;
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.List;
 
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
@@ -94,12 +87,16 @@
 
     @Mock
     private DcNetworkAgent mDcNetworkAgent;
+    @Mock
+    private Network mNetwork;
 
     private QosCallbackTracker mQosCallbackTracker;
 
     @Before
     public void setUp() throws Exception {
         super.setUp(getClass().getSimpleName());
+        doReturn(mNetwork).when(mDcNetworkAgent).getNetwork();
+        doReturn(100).when(mNetwork).getNetId();
         mQosCallbackTracker = new QosCallbackTracker(mDcNetworkAgent);
         processAllMessages();
     }
@@ -118,7 +115,8 @@
         halEpsQos.uplink.maxBitrateKbps = ulMbr;
         halEpsQos.uplink.guaranteedBitrateKbps = ulGbr;
 
-        return new EpsQos(halEpsQos);
+        return new EpsQos(
+                new Qos.QosBandwidth(dlMbr, dlGbr), new Qos.QosBandwidth(ulMbr, ulGbr), 4);
     }
 
     private QosBearerFilter createIpv4QosFilter(String localAddress,
@@ -126,7 +124,7 @@
         return new QosBearerFilter(
                 Arrays.asList(
                         new LinkAddress(InetAddresses.parseNumericAddress(localAddress), 32)),
-                new ArrayList<LinkAddress>(), localPort, null, QosBearerFilter.QOS_PROTOCOL_TCP,
+                new ArrayList<>(), localPort, null, QosBearerFilter.QOS_PROTOCOL_TCP,
                 7, 987, 678, QosBearerFilter.QOS_FILTER_DIRECTION_BIDIRECTIONAL, precedence);
     }
 
@@ -452,5 +450,34 @@
         verify(mDcNetworkAgent, times(1)).notifyQosSessionLost(eq(1), eq(1234), eq(1));
         verify(mDcNetworkAgent, times(1)).notifyQosSessionLost(eq(2), eq(1235), eq(1));
     }
+
+    @Test
+    @SmallTest
+    public void testQosSessionWithInvalidPortRange() throws Exception {
+        // Non-matching QosBearerFilter
+        ArrayList<QosBearerFilter> qosFilters1 = new ArrayList<>();
+        qosFilters1.add(createIpv4QosFilter("155.55.55.55",
+                new QosBearerFilter.PortRange(0,0), 45));
+
+        ArrayList<QosBearerSession> qosSessions = new ArrayList<>();
+        qosSessions.add(new QosBearerSession(1234, createEpsQos(5, 6, 7, 8), qosFilters1));
+
+        // Matching QosBearerFilter
+        ArrayList<QosBearerFilter> qosFilters2 = new ArrayList<>();
+        qosFilters2.add(createIpv4QosFilter("122.22.22.22",
+                new QosBearerFilter.PortRange(-1, 1), 45));
+        qosSessions.add(new QosBearerSession(1235, createEpsQos(5, 6, 7, 8), qosFilters2));
+
+        mQosCallbackTracker.updateSessions(qosSessions);
+
+        // Add filter after updateSessions
+        Filter filter = new Filter(new InetSocketAddress(
+                InetAddresses.parseNumericAddress("122.22.22.22"), 2222));
+        mQosCallbackTracker.addFilter(1, filter);
+
+        verify(mDcNetworkAgent, never()).notifyQosSessionAvailable(eq(1),
+                eq(1235), any(EpsBearerQosSessionAttributes.class));
+
+    }
 }
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/RetryManagerTest.java b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/RetryManagerTest.java
index 1fcfa88..159e204 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/RetryManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/RetryManagerTest.java
@@ -983,15 +983,21 @@
     }
 
     /**
-     * Test the scenario where modem suggests the same retry for too many times
+     * Test the scenario that network suggests the same retry for too many times
      */
     @Test
     @SmallTest
-    public void testRetryManagerModemSuggestedRetryTooManyTimes() throws Exception {
+    public void testRetryNetworkSuggestedRetryTooManyTimes() throws Exception {
 
         mBundle.putStringArray(CarrierConfigManager.KEY_CARRIER_DATA_CALL_RETRY_CONFIG_STRINGS,
                 new String[]{"mms:2000,3000", "default:1000,4000,7000,9000"});
 
+        int maxRetryCount = 10;
+
+        mBundle.putInt(CarrierConfigManager
+                        .KEY_CARRIER_DATA_CALL_RETRY_NETWORK_REQUESTED_MAX_COUNT_INT,
+                maxRetryCount);
+
         ArrayList<ApnSetting> waitingApns = new ArrayList<ApnSetting>();
         ApnSetting myApn1 = ApnSetting.makeApnSetting(mApn1);
         ApnSetting myApn2 = ApnSetting.makeApnSetting(mApn2);
@@ -1011,26 +1017,14 @@
         delay = rm.getDelayForNextApn(false);
         assertEquals(1000, delay);
 
-        nextApn = rm.getNextApnSetting();
-        assertTrue(nextApn.equals(mApn1));
-        doReturn(2500 + SystemClock.elapsedRealtime()).when(mDataThrottler)
-                .getRetryTime(ApnSetting.TYPE_DEFAULT);
-        delay = rm.getDelayForNextApn(false);
-        assertRange(2450, 2500, delay);
-
-        nextApn = rm.getNextApnSetting();
-        assertTrue(nextApn.equals(mApn1));
-        doReturn(2500 + SystemClock.elapsedRealtime()).when(mDataThrottler)
-                .getRetryTime(ApnSetting.TYPE_DEFAULT);
-        delay = rm.getDelayForNextApn(false);
-        assertRange(2450, 2500, delay);
-
-        nextApn = rm.getNextApnSetting();
-        assertTrue(nextApn.equals(mApn1));
-        doReturn(2500 + SystemClock.elapsedRealtime()).when(mDataThrottler)
-                .getRetryTime(ApnSetting.TYPE_DEFAULT);
-        delay = rm.getDelayForNextApn(false);
-        assertRange(2450, 2500, delay);
+        for (int i = 0; i < maxRetryCount; i++) {
+            nextApn = rm.getNextApnSetting();
+            assertTrue(nextApn.equals(mApn1));
+            doReturn(2500 + SystemClock.elapsedRealtime()).when(mDataThrottler)
+                    .getRetryTime(ApnSetting.TYPE_DEFAULT);
+            delay = rm.getDelayForNextApn(false);
+            assertRange(2450, 2500, delay);
+        }
 
         nextApn = rm.getNextApnSetting();
         assertTrue(nextApn.equals(mApn1));
diff --git a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/TelephonyNetworkFactoryTest.java b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/TelephonyNetworkFactoryTest.java
index 1dfd485..30bd8b2 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/TelephonyNetworkFactoryTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/TelephonyNetworkFactoryTest.java
@@ -36,7 +36,6 @@
 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;
@@ -465,16 +464,6 @@
         mTelephonyNetworkFactoryUT.releaseNetworkFor(mmsNetworkRequest);
         processAllMessages();
 
-        Message msg = mNetworkRequestMessageMap.get(mmsNetworkRequest);
-
-        Bundle bundle = msg.getData();
-        bundle.putParcelable("extra_network_request", mmsNetworkRequest);
-        bundle.putBoolean("extra_success", true);
-        bundle.putInt("extra_transport_type", AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
-        bundle.putBoolean("extra_handover_failure_fallback", false);
-        h.sendMessage(msg);
-        processAllMessages();
-
         // Ensure the release is called one more time after the normal release
         verify(mDcTracker, times(2)).releaseNetwork(any(), eq(1));
     }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyNumberTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyNumberTrackerTest.java
index 2aa3f18..29f2dab 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyNumberTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyNumberTrackerTest.java
@@ -20,10 +20,12 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
 
 import android.os.AsyncResult;
 import android.os.Environment;
 import android.os.ParcelFileDescriptor;
+import android.telephony.SubscriptionManager;
 import android.telephony.emergency.EmergencyNumber;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -33,6 +35,7 @@
 import com.android.internal.telephony.HalVersion;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.SubscriptionController;
 import com.android.internal.telephony.TelephonyTest;
 
 import org.junit.After;
@@ -84,6 +87,14 @@
                                             EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN);
     private static final int OTA_UNIT_TEST_EMERGENCY_NUMBER_DB_VERSION = 999999;
     private static final String OTA_EMERGENCY_NUMBER_ADDRESS = "98765";
+    private static final int SUB_ID_PHONE_1 = 1;
+    private static final int SUB_ID_PHONE_2 = 2;
+    private static final int VALID_SLOT_INDEX_VALID_1 = 1;
+    private static final int VALID_SLOT_INDEX_VALID_2 = 2;
+    private static final int INVALID_SLOT_INDEX_VALID = SubscriptionManager.INVALID_SIM_SLOT_INDEX;
+
+    @Mock
+    private SubscriptionController mSubControllerMock;
 
     @Mock
     private Phone mPhone2; // mPhone as phone 1 is already defined in TelephonyTest.
@@ -107,9 +118,11 @@
 
         doReturn(mContext).when(mPhone).getContext();
         doReturn(0).when(mPhone).getPhoneId();
+        doReturn(SUB_ID_PHONE_1).when(mPhone).getSubId();
 
         doReturn(mContext).when(mPhone2).getContext();
         doReturn(1).when(mPhone2).getPhoneId();
+        doReturn(SUB_ID_PHONE_2).when(mPhone2).getSubId();
 
         initializeEmergencyNumberListTestSamples();
         mEmergencyNumberTrackerMock = new EmergencyNumberTracker(mPhone, mSimulatedCommands);
@@ -248,6 +261,36 @@
         replaceInstance(PhoneFactory.class, "sPhones", null, mPhones);
     }
 
+    /**
+     * Test EmergencyNumberTracker.isSimAbsent().
+     */
+    @Test
+    public void testIsSimAbsent() throws Exception {
+        setDsdsPhones();
+        replaceInstance(SubscriptionController.class, "sInstance", null, mSubControllerMock);
+
+        // Both sim slots are active
+        doReturn(VALID_SLOT_INDEX_VALID_1).when(mSubControllerMock).getSlotIndex(
+                eq(SUB_ID_PHONE_1));
+        doReturn(VALID_SLOT_INDEX_VALID_2).when(mSubControllerMock).getSlotIndex(
+                eq(SUB_ID_PHONE_2));
+        assertFalse(mEmergencyNumberTrackerMock.isSimAbsent());
+
+        // One sim slot is active; the other one is not active
+        doReturn(VALID_SLOT_INDEX_VALID_1).when(mSubControllerMock).getSlotIndex(
+                eq(SUB_ID_PHONE_1));
+        doReturn(INVALID_SLOT_INDEX_VALID).when(mSubControllerMock).getSlotIndex(
+                eq(SUB_ID_PHONE_2));
+        assertFalse(mEmergencyNumberTrackerMock.isSimAbsent());
+
+        // Both sim slots are not active
+        doReturn(INVALID_SLOT_INDEX_VALID).when(mSubControllerMock).getSlotIndex(
+                eq(SUB_ID_PHONE_1));
+        doReturn(INVALID_SLOT_INDEX_VALID).when(mSubControllerMock).getSlotIndex(
+                eq(SUB_ID_PHONE_2));
+        assertTrue(mEmergencyNumberTrackerMock.isSimAbsent());
+    }
+
     @Test
     public void testEmergencyNumberListFromRadio() throws Exception {
         sendEmergencyNumberListFromRadio();
diff --git a/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccControllerTest.java
index 7c33f34..43de6f6 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccControllerTest.java
@@ -1290,7 +1290,8 @@
     private void callGetDownloadableSubscriptionMetadata(DownloadableSubscription subscription,
             boolean complete, GetDownloadableSubscriptionMetadataResult result) {
         prepareGetDownloadableSubscriptionMetadataCall(complete, result);
-        PendingIntent resultCallback = PendingIntent.getBroadcast(mContext, 0, new Intent(), 0);
+        PendingIntent resultCallback = PendingIntent.getBroadcast(mContext, 0, new Intent(),
+                PendingIntent.FLAG_IMMUTABLE);
         mController.getDownloadableSubscriptionMetadata(0, subscription, PACKAGE_NAME,
                 resultCallback);
     }
@@ -1311,14 +1312,16 @@
             }
         }).when(mMockConnector).getDefaultDownloadableSubscriptionList(anyInt(), anyBoolean(),
                 any());
-        PendingIntent resultCallback = PendingIntent.getBroadcast(mContext, 0, new Intent(), 0);
+        PendingIntent resultCallback = PendingIntent.getBroadcast(mContext, 0, new Intent(),
+                PendingIntent.FLAG_IMMUTABLE);
         mController.getDefaultDownloadableSubscriptionList(CARD_ID, PACKAGE_NAME, resultCallback);
     }
 
     private void callDownloadSubscription(DownloadableSubscription subscription,
             boolean switchAfterDownload, final boolean complete, final int result,
             final int resolvableError, String callingPackage) {
-        PendingIntent resultCallback = PendingIntent.getBroadcast(mContext, 0, new Intent(), 0);
+        PendingIntent resultCallback = PendingIntent.getBroadcast(mContext, 0, new Intent(),
+                PendingIntent.FLAG_IMMUTABLE);
         doAnswer(new Answer<Void>() {
             @Override
             public Void answer(InvocationOnMock invocation) throws Exception {
@@ -1345,7 +1348,8 @@
 
     private void callDeleteSubscription(int subscriptionId, String iccid, final boolean complete,
             final int result, String callingPackage) {
-        PendingIntent resultCallback = PendingIntent.getBroadcast(mContext, 0, new Intent(), 0);
+        PendingIntent resultCallback = PendingIntent.getBroadcast(mContext, 0, new Intent(),
+                PendingIntent.FLAG_IMMUTABLE);
         doAnswer(new Answer<Void>() {
             @Override
             public Void answer(InvocationOnMock invocation) throws Exception {
@@ -1364,7 +1368,8 @@
 
     private void callSwitchToSubscription(int subscriptionId, String iccid, final boolean complete,
             final int result, String callingPackage) {
-        PendingIntent resultCallback = PendingIntent.getBroadcast(mContext, 0, new Intent(), 0);
+        PendingIntent resultCallback = PendingIntent.getBroadcast(mContext, 0, new Intent(),
+                PendingIntent.FLAG_IMMUTABLE);
         doAnswer(new Answer<Void>() {
             @Override
             public Void answer(InvocationOnMock invocation) throws Exception {
@@ -1383,7 +1388,8 @@
 
     private void callUpdateSubscriptionNickname(int subscriptionId, String iccid, String nickname,
             final boolean complete, final int result, String callingPackage) {
-        PendingIntent resultCallback = PendingIntent.getBroadcast(mContext, 0, new Intent(), 0);
+        PendingIntent resultCallback = PendingIntent.getBroadcast(mContext, 0, new Intent(),
+                PendingIntent.FLAG_IMMUTABLE);
         doAnswer(new Answer<Void>() {
             @Override
             public Void answer(InvocationOnMock invocation) throws Exception {
@@ -1403,7 +1409,8 @@
     }
 
     private void callEraseSubscriptions(final boolean complete, final int result) {
-        PendingIntent resultCallback = PendingIntent.getBroadcast(mContext, 0, new Intent(), 0);
+        PendingIntent resultCallback = PendingIntent.getBroadcast(mContext, 0, new Intent(),
+                PendingIntent.FLAG_IMMUTABLE);
         doAnswer(new Answer<Void>() {
             @Override
             public Void answer(InvocationOnMock invocation) throws Exception {
@@ -1421,7 +1428,8 @@
     }
 
     private void callEraseSubscriptionsWithOptions(final boolean complete, final int result) {
-        PendingIntent resultCallback = PendingIntent.getBroadcast(mContext, 0, new Intent(), 0);
+        PendingIntent resultCallback = PendingIntent.getBroadcast(mContext, 0, new Intent(),
+                PendingIntent.FLAG_IMMUTABLE);
         doAnswer(new Answer<Void>() {
             @Override
             public Void answer(InvocationOnMock invocation) throws Exception {
@@ -1440,7 +1448,8 @@
     }
 
     private void callRetainSubscriptionsForFactoryReset(final boolean complete, final int result) {
-        PendingIntent resultCallback = PendingIntent.getBroadcast(mContext, 0, new Intent(), 0);
+        PendingIntent resultCallback = PendingIntent.getBroadcast(mContext, 0, new Intent(),
+                PendingIntent.FLAG_IMMUTABLE);
         doAnswer(new Answer<Void>() {
             @Override
             public Void answer(InvocationOnMock invocation) throws Exception {
diff --git a/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmInboundSmsHandlerTest.java b/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmInboundSmsHandlerTest.java
index d201bcf..298c3e4 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmInboundSmsHandlerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmInboundSmsHandlerTest.java
@@ -20,7 +20,6 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.Matchers.any;
@@ -37,6 +36,8 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.Notification;
+import android.app.NotificationManager;
 import android.content.BroadcastReceiver;
 import android.content.ContentValues;
 import android.content.Context;
@@ -62,12 +63,10 @@
 import com.android.internal.telephony.InboundSmsTracker;
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.SmsBroadcastUndelivered;
-import com.android.internal.telephony.SmsConstants;
 import com.android.internal.telephony.SmsHeader;
 import com.android.internal.telephony.SmsStorageMonitor;
 import com.android.internal.telephony.TelephonyTest;
 import com.android.internal.telephony.cdma.CdmaInboundSmsHandler;
-import com.android.internal.util.HexDump;
 import com.android.internal.util.IState;
 import com.android.internal.util.StateMachine;
 
@@ -98,15 +97,10 @@
     @Mock
     private SmsHeader mSmsHeader;
     private InboundSmsTracker mInboundSmsTracker;
+    private InboundSmsTracker mInboundSmsTrackerSub1;
     private InboundSmsTracker mInboundSmsTrackerPart1;
     private InboundSmsTracker mInboundSmsTrackerPart2;
     @Mock
-    private InboundSmsTracker mMockInboundSmsTracker;
-    private ContentValues mInboundSmsTrackerCV;
-    @Mock
-    private InboundSmsTracker mMockInboundSmsTrackerSub1;
-    private ContentValues mInboundSmsTrackerCVSub1;
-    @Mock
     private CdmaInboundSmsHandler mCdmaInboundSmsHandler;
     @Mock
     private InboundSmsHandler.SmsFilter mSmsFilter;
@@ -141,63 +135,25 @@
     }
 
     /**
-     * This is used only for InboundSmsTracker constructed through Cursor. For other cases
-     * real objects should be used. This should be used only for tests related to
-     * SmsBroadcastUndelivered.
+     * This is used only for InboundSmsTracker constructed through Cursor. This should be used only
+     * for tests related to SmsBroadcastUndelivered. Also, this adds a second tracker for multisim.
      */
-    private void createMockInboundSmsTracker() {
-        mInboundSmsTrackerCV = new ContentValues();
-        mInboundSmsTrackerCV.put("destination_port", InboundSmsTracker.DEST_PORT_FLAG_NO_PORT);
-        mInboundSmsTrackerCV.put("pdu", HexDump.toHexString(mSmsPdu));
-        mInboundSmsTrackerCV.put("address", "1234567890");
-        mInboundSmsTrackerCV.put("reference_number", 1);
-        mInboundSmsTrackerCV.put("sequence", 1);
-        mInboundSmsTrackerCV.put("count", 1);
-        mInboundSmsTrackerCV.put("date", System.currentTimeMillis());
-        mInboundSmsTrackerCV.put("message_body", mMessageBody);
-        mInboundSmsTrackerCV.put("display_originating_addr", "1234567890");
-        mInboundSmsTrackerCV.put("sub_id", mSubId0);
+    private void createInboundSmsTrackerMultiSim() {
+        mInboundSmsTrackerSub1 = new InboundSmsTracker(
+                mContext,
+                mSmsPdu, /* pdu */
+                System.currentTimeMillis(), /* timestamp */
+                -1, /* destPort */
+                false, /* is3gpp2 */
+                false, /* is3gpp2WapPdu */
+                "1234567890", /* address */
+                "1234567890", /* displayAddress */
+                mMessageBody, /* messageBody */
+                false, /* isClass0 */
+                mSubId1,
+                InboundSmsHandler.SOURCE_NOT_INJECTED);
 
-        doReturn(1).when(mMockInboundSmsTracker).getMessageCount();
-        doReturn(1).when(mMockInboundSmsTracker).getReferenceNumber();
-        doReturn("1234567890").when(mMockInboundSmsTracker).getAddress();
-        doReturn(1).when(mMockInboundSmsTracker).getSequenceNumber();
-        doReturn(1).when(mMockInboundSmsTracker).getIndexOffset();
-        doReturn(-1).when(mMockInboundSmsTracker).getDestPort();
-        doReturn(mMessageBody).when(mMockInboundSmsTracker).getMessageBody();
-        doReturn(mSmsPdu).when(mMockInboundSmsTracker).getPdu();
-        doReturn(mInboundSmsTrackerCV.get("date")).when(mMockInboundSmsTracker).getTimestamp();
-        doReturn(SmsConstants.FORMAT_3GPP).when(mMockInboundSmsTracker).getFormat();
-        doReturn(mInboundSmsTrackerCV).when(mMockInboundSmsTracker).getContentValues();
-        doReturn(mSubId0).when(mMockInboundSmsTracker).getSubId();
-
-        mInboundSmsTrackerCVSub1 = new ContentValues();
-        mInboundSmsTrackerCVSub1.put("destination_port", InboundSmsTracker.DEST_PORT_FLAG_NO_PORT);
-        mInboundSmsTrackerCVSub1.put("pdu", HexDump.toHexString(mSmsPdu));
-        mInboundSmsTrackerCVSub1.put("address", "1234567890");
-        mInboundSmsTrackerCVSub1.put("reference_number", 1);
-        mInboundSmsTrackerCVSub1.put("sequence", 1);
-        mInboundSmsTrackerCVSub1.put("count", 1);
-        mInboundSmsTrackerCVSub1.put("date", System.currentTimeMillis());
-        mInboundSmsTrackerCVSub1.put("message_body", mMessageBody);
-        mInboundSmsTrackerCVSub1.put("display_originating_addr", "1234567890");
-        mInboundSmsTrackerCVSub1.put("sub_id", mSubId1);
-
-        doReturn(1).when(mMockInboundSmsTrackerSub1).getMessageCount();
-        doReturn(1).when(mMockInboundSmsTrackerSub1).getReferenceNumber();
-        doReturn("1234567890").when(mMockInboundSmsTrackerSub1).getAddress();
-        doReturn(1).when(mMockInboundSmsTrackerSub1).getSequenceNumber();
-        doReturn(1).when(mMockInboundSmsTrackerSub1).getIndexOffset();
-        doReturn(-1).when(mMockInboundSmsTrackerSub1).getDestPort();
-        doReturn(mMessageBody).when(mMockInboundSmsTrackerSub1).getMessageBody();
-        doReturn(mSmsPdu).when(mMockInboundSmsTrackerSub1).getPdu();
-        doReturn(mInboundSmsTrackerCVSub1.get("date")).when(mMockInboundSmsTrackerSub1)
-                .getTimestamp();
-        doReturn(SmsConstants.FORMAT_3GPP).when(mMockInboundSmsTrackerSub1).getFormat();
-        doReturn(mInboundSmsTrackerCVSub1).when(mMockInboundSmsTrackerSub1).getContentValues();
-        doReturn(mSubId1).when(mMockInboundSmsTrackerSub1).getSubId();
-
-        doReturn(mMockInboundSmsTracker).doReturn(mMockInboundSmsTrackerSub1)
+        doReturn(mInboundSmsTracker).doReturn(mInboundSmsTrackerSub1)
                 .when(mTelephonyComponentFactory)
                 .makeInboundSmsTracker(any(Context.class), nullable(Cursor.class),
                         anyBoolean());
@@ -239,7 +195,7 @@
                 anyBoolean(), nullable(String.class), nullable(String.class),
                 nullable(String.class), anyBoolean(), anyInt(), anyInt());
 
-        createMockInboundSmsTracker();
+        createInboundSmsTrackerMultiSim();
 
         mContentProvider = new FakeSmsContentProvider();
         ((MockContentResolver)mContext.getContentResolver()).addProvider(
@@ -252,7 +208,12 @@
         mSmsFilters.add(mSmsFilter2);
         mGsmInboundSmsHandler.setSmsFiltersForTesting(mSmsFilters);
         monitorTestableLooper(new TestableLooper(mGsmInboundSmsHandler.getHandler().getLooper()));
+
+        doReturn(mGsmInboundSmsHandler).when(mPhone).getInboundSmsHandler(false);
+        doReturn(mCdmaInboundSmsHandler).when(mPhone).getInboundSmsHandler(true);
+
         processAllMessages();
+        logd("setUp: complete");
     }
 
     @After
@@ -362,6 +323,7 @@
         verifySmsFiltersInvoked(times(1));
     }
 
+    @FlakyTest // temporarily disabled, see b/182498318
     @Test
     @MediumTest
     public void testNewSmsFromBlockedNumber_noBroadcastsSent() {
@@ -379,6 +341,64 @@
         verifySmsFiltersInvoked(times(1));
     }
 
+    @FlakyTest // temporarily disabled, see b/182498318
+    @Test
+    @MediumTest
+    public void testNewSmsWithUserLocked_notificationShown() {
+        // user locked
+        UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+        doReturn(false).when(userManager).isUserUnlocked();
+
+        transitionFromStartupToIdle();
+
+        sendNewSms();
+
+        verify(mContext, never()).sendBroadcast(any(Intent.class));
+        assertEquals("IdleState", getCurrentState().getName());
+
+        // Filter should be invoked.
+        verifySmsFiltersInvoked(times(1));
+
+        // New message notification should be shown.
+        NotificationManager notificationManager =
+                (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+        verify(notificationManager).notify(
+                eq(InboundSmsHandler.NOTIFICATION_TAG),
+                eq(InboundSmsHandler.NOTIFICATION_ID_NEW_MESSAGE),
+                any(Notification.class));
+    }
+
+    @FlakyTest // temporarily disabled, see b/182498318
+    @Test
+    @MediumTest
+    public void testNewSmsFromBlockedNumberWithUserLocked_noNotificationShown() {
+        String blockedNumber = "1234567890";
+        mFakeBlockedNumberContentProvider.mBlockedNumbers.add(blockedNumber);
+
+        // user locked
+        UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+        doReturn(false).when(userManager).isUserUnlocked();
+
+        transitionFromStartupToIdle();
+
+        sendNewSms();
+
+        verify(mContext, never()).sendBroadcast(any(Intent.class));
+        assertEquals("IdleState", getCurrentState().getName());
+
+        // Filter should be invoked.
+        verifySmsFiltersInvoked(times(1));
+
+        // No new message notification should be shown.
+        NotificationManager notificationManager =
+                (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+        verify(notificationManager, never()).notify(
+                eq(InboundSmsHandler.NOTIFICATION_TAG),
+                eq(InboundSmsHandler.NOTIFICATION_ID_NEW_MESSAGE),
+                any(Notification.class));
+    }
+
+    @FlakyTest // temporarily disabled, see b/182498318
     @Test
     @MediumTest
     public void testNewSms_filterInvoked_noBroadcastsSent() {
@@ -404,6 +424,7 @@
                 anyBoolean(), anyBoolean(), Mockito.<List<InboundSmsHandler.SmsFilter>>any());
     }
 
+    @FlakyTest // temporarily disabled, see b/182498318
     @Test
     @MediumTest
     public void testNewSms_filterChaining_noBroadcastsSent() {
@@ -443,9 +464,10 @@
                 intentArgumentCaptor.capture());
         assertEquals(Telephony.Sms.Intents.DATA_SMS_RECEIVED_ACTION,
                 intentArgumentCaptor.getAllValues().get(numPastBroadcasts).getAction());
-        assertNotEquals(0L,
+        // TODO mock messageId correctly in InboundSmsTracker
+        /* assertNotEquals(0L,
                 intentArgumentCaptor.getAllValues().get(numPastBroadcasts)
-                        .getLongExtra("messageId", 0L));
+                        .getLongExtra("messageId", 0L)); */
         assertEquals("WaitingState", getCurrentState().getName());
 
         mContextFixture.sendBroadcastToOrderedBroadcastReceivers();
@@ -454,6 +476,7 @@
         assertEquals("IdleState", getCurrentState().getName());
     }
 
+    @FlakyTest // temporarily disabled, see b/182498318
     @Test
     @MediumTest
     public void testClass0Sms() {
@@ -485,6 +508,7 @@
         verifySmsFiltersInvoked(times(1));
     }
 
+    @FlakyTest // temporarily disabled, see b/182498318
     @Test
     @MediumTest
     public void testBroadcastSms() {
@@ -586,6 +610,7 @@
                 InboundSmsHandler.SOURCE_NOT_INJECTED);
     }
 
+    @FlakyTest // temporarily disabled, see b/182498318
     @Test
     @MediumTest
     public void testMultiPartSmsWithIncompleteWAP() {
@@ -725,6 +750,7 @@
         assertEquals("IdleState", getCurrentState().getName());
     }
 
+    @FlakyTest // temporarily disabled, see b/182498318
     @Test
     @MediumTest
     public void testMultiPartIncompleteSms() {
@@ -789,6 +815,7 @@
         verifySmsFiltersInvoked(never());
     }
 
+    @FlakyTest // temporarily disabled, see b/182498318
     @Test
     @MediumTest
     public void testMultiPartSmsWithInvalidSeqNumber() {
@@ -848,6 +875,7 @@
         verifySmsFiltersInvoked(never());
     }
 
+    @FlakyTest // temporarily disabled, see b/182498318
     @Test
     @MediumTest
     public void testMultipartSmsFromBlockedNumber_noBroadcastsSent() {
@@ -886,6 +914,7 @@
         verifySmsFiltersInvoked(times(1));
     }
 
+    @FlakyTest // temporarily disabled, see b/182498318
     @Test
     @MediumTest
     public void testMultipartSmsFromBlockedEmail_noBroadcastsSent() {
@@ -941,6 +970,7 @@
         verifySmsFiltersInvoked(times(1));
     }
 
+    @FlakyTest // temporarily disabled, see b/182498318
     @Test
     @MediumTest
     public void testMultipartSms_filterInvoked_noBroadcastsSent() {
@@ -990,15 +1020,33 @@
                 anyBoolean(), anyBoolean(), Mockito.<List<InboundSmsHandler.SmsFilter>>any());
     }
 
+    @FlakyTest // temporarily disabled, see b/182498318
     @Test
     @MediumTest
     public void testBroadcastUndeliveredUserLocked() throws Exception {
         replaceInstance(SmsBroadcastUndelivered.class, "instance", null, null);
-        doReturn(0).when(mMockInboundSmsTracker).getDestPort();
-        doReturn(2131L).when(mMockInboundSmsTracker).getMessageId();
+
+        mInboundSmsTracker = new InboundSmsTracker(
+                mContext,
+                mSmsPdu, /* pdu */
+                System.currentTimeMillis(), /* timestamp */
+                0, /* destPort */
+                false, /* is3gpp2 */
+                false, /* is3gpp2WapPdu */
+                "1234567890", /* address */
+                "1234567890", /* displayAddress */
+                mMessageBody, /* messageBody */
+                false, /* isClass0 */
+                mSubId0,
+                InboundSmsHandler.SOURCE_NOT_INJECTED);
+
+        doReturn(mInboundSmsTracker)
+                .when(mTelephonyComponentFactory)
+                .makeInboundSmsTracker(any(Context.class), nullable(Cursor.class),
+                        anyBoolean());
 
         // add a fake entry to db
-        mContentProvider.insert(sRawUri, mMockInboundSmsTracker.getContentValues());
+        mContentProvider.insert(sRawUri, mInboundSmsTracker.getContentValues());
 
         // user locked
         UserManager userManager = (UserManager)mContext.getSystemService(Context.USER_SERVICE);
@@ -1030,15 +1078,32 @@
         verifySmsFiltersInvoked(times(1));
     }
 
+    @FlakyTest // temporarily disabled, see b/182498318
     @Test
     @MediumTest
     public void testBroadcastUndeliveredUserUnlocked() throws Exception {
         replaceInstance(SmsBroadcastUndelivered.class, "instance", null, null);
-        doReturn(0).when(mMockInboundSmsTracker).getDestPort();
-        doReturn(2131L).when(mMockInboundSmsTracker).getMessageId();
+        mInboundSmsTracker = new InboundSmsTracker(
+                mContext,
+                mSmsPdu, /* pdu */
+                System.currentTimeMillis(), /* timestamp */
+                0, /* destPort */
+                false, /* is3gpp2 */
+                false, /* is3gpp2WapPdu */
+                "1234567890", /* address */
+                "1234567890", /* displayAddress */
+                mMessageBody, /* messageBody */
+                false, /* isClass0 */
+                mSubId0,
+                InboundSmsHandler.SOURCE_NOT_INJECTED);
+
+        doReturn(mInboundSmsTracker)
+                .when(mTelephonyComponentFactory)
+                .makeInboundSmsTracker(any(Context.class), nullable(Cursor.class),
+                        anyBoolean());
 
         // add a fake entry to db
-        mContentProvider.insert(sRawUri, mMockInboundSmsTracker.getContentValues());
+        mContentProvider.insert(sRawUri, mInboundSmsTracker.getContentValues());
 
         SmsBroadcastUndelivered.initialize(mContext, mGsmInboundSmsHandler, mCdmaInboundSmsHandler);
 
@@ -1051,6 +1116,7 @@
         verifySmsFiltersInvoked(times(1));
     }
 
+    @FlakyTest // temporarily disabled, see b/182498318
     @Test
     @MediumTest
     public void testBroadcastUndeliveredDeleted() throws Exception {
@@ -1118,14 +1184,15 @@
         verifySmsFiltersInvoked(times(1));
     }
 
+    @FlakyTest // temporarily disabled, see b/182498318
     @Test
     @MediumTest
     public void testBroadcastUndeliveredMultiSim() throws Exception {
         replaceInstance(SmsBroadcastUndelivered.class, "instance", null, null);
 
         // add SMSs from different subs to db
-        mContentProvider.insert(sRawUri, mMockInboundSmsTracker.getContentValues());
-        mContentProvider.insert(sRawUri, mMockInboundSmsTrackerSub1.getContentValues());
+        mContentProvider.insert(sRawUri, mInboundSmsTracker.getContentValues());
+        mContentProvider.insert(sRawUri, mInboundSmsTrackerSub1.getContentValues());
 
         SmsBroadcastUndelivered.initialize(mContext, mGsmInboundSmsHandler, mCdmaInboundSmsHandler);
         // wait for ScanRawTableThread
@@ -1145,4 +1212,36 @@
                 any(InboundSmsTracker.class), any(InboundSmsHandler.SmsBroadcastReceiver.class),
                 anyBoolean(), anyBoolean(), Mockito.<List<InboundSmsHandler.SmsFilter>>any());
     }
+
+    @Test
+    @MediumTest
+    public void testBroadcastTimeout() {
+        InboundSmsHandler.sTimeoutDurationMillis = 100;
+        transitionFromStartupToIdle();
+
+        // send new SMS to state machine and verify that triggers SMS_DELIVER_ACTION
+        sendNewSms();
+
+        ArgumentCaptor<Intent> intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mContext).sendBroadcast(intentArgumentCaptor.capture());
+        Intent intent = intentArgumentCaptor.getAllValues().get(0);
+        assertEquals(Telephony.Sms.Intents.SMS_DELIVER_ACTION, intent.getAction());
+        assertEquals("WaitingState", getCurrentState().getName());
+
+        // don't send broadcast back to InboundSmsHandler, instead wait for timeout
+        waitForMs(300);
+        processAllMessages();
+
+        intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mContext, times(2)).sendBroadcast(intentArgumentCaptor.capture());
+        intent = intentArgumentCaptor.getAllValues().get(1);
+        assertEquals(Telephony.Sms.Intents.SMS_RECEIVED_ACTION, intent.getAction());
+
+        // don't send broadcast back to InboundSmsHandler, instead wait for timeout
+        waitForMs(300);
+        processAllMessages();
+
+        assertEquals("IdleState", getCurrentState().getName());
+
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmMmiCodeTest.java b/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmMmiCodeTest.java
index 08d5b13..ee4f6e5 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmMmiCodeTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmMmiCodeTest.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.telephony.gsm;
 
+import static junit.framework.Assert.fail;
+
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.doReturn;
@@ -95,6 +97,16 @@
         assertTrue(mGsmMmiCode == null);
     }
 
+    @Test
+    public void testNoCrashOnEmptyMessage() {
+        GsmMmiCode mmi = GsmMmiCode.newNetworkInitiatedUssd(null, true, mGsmCdmaPhoneUT, null);
+        try {
+            mmi.onUssdFinishedError();
+        } catch (Exception e) {
+            fail("Shouldn't crash!!!");
+        }
+    }
+
     private void setCarrierSupportsCallerIdVerticalServiceCodesCarrierConfig() {
         final PersistableBundle bundle = new PersistableBundle();
         bundle.putBoolean(CarrierConfigManager
diff --git a/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmSmsDispatcherTest.java b/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmSmsDispatcherTest.java
index 405e237..3fdbe79 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmSmsDispatcherTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmSmsDispatcherTest.java
@@ -23,32 +23,44 @@
 import static com.android.internal.telephony.TelephonyTestUtils.waitForMs;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.PendingIntent;
 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.content.pm.ServiceInfo;
 import android.location.Country;
 import android.location.CountryDetector;
 import android.os.HandlerThread;
 import android.os.Message;
+import android.os.RemoteException;
 import android.os.SystemProperties;
 import android.provider.Settings;
+import android.service.carrier.CarrierMessagingService;
+import android.service.carrier.ICarrierMessagingCallback;
+import android.service.carrier.ICarrierMessagingService;
 import android.telephony.SmsManager;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 import android.util.Singleton;
 
 import androidx.test.filters.FlakyTest;
+import androidx.test.filters.MediumTest;
+import androidx.test.filters.SmallTest;
 
 import com.android.internal.telephony.ContextFixture;
 import com.android.internal.telephony.ISub;
@@ -62,15 +74,22 @@
 import org.junit.Before;
 import org.junit.Ignore;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
 
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
 public class GsmSmsDispatcherTest extends TelephonyTest {
 
     private static final long TIMEOUT_MS = 500;
+    private static final String CARRIER_APP_PACKAGE_NAME = "com.android.carrier";
 
     @Mock
     private android.telephony.SmsMessage mSmsMessage;
@@ -86,6 +105,9 @@
     private SMSDispatcher.SmsTracker mSmsTracker;
     @Mock
     private ISub.Stub mISubStub;
+    @Mock
+    private ICarrierMessagingService.Stub mICarrierAppMessagingService;
+
     private Object mLock = new Object();
     private boolean mReceivedTestIntent;
     private static final String TEST_INTENT = "com.android.internal.telephony.TEST_INTENT";
@@ -95,6 +117,7 @@
             logd("onReceive");
             synchronized (mLock) {
                 mReceivedTestIntent = true;
+                mLock.notifyAll();
             }
         }
     };
@@ -129,6 +152,9 @@
         mGsmSmsDispatcherTestHandler = new GsmSmsDispatcherTestHandler(getClass().getSimpleName());
         mGsmSmsDispatcherTestHandler.start();
         waitUntilReady();
+        mGsmSmsDispatcher = new GsmSMSDispatcher(mPhone, mSmsDispatchersController,
+                mGsmInboundSmsHandler);
+        processAllMessages();
     }
 
     @After
@@ -142,7 +168,7 @@
     @Test @SmallTest
     public void testSmsStatus() {
         mSimulatedCommands.notifySmsStatus(new byte[]{(byte)0xFF, (byte)0xFF, (byte)0xFF});
-        TelephonyTestUtils.waitForMs(50);
+        processAllMessages();
         verify(mSimulatedCommandsVerifier).acknowledgeLastIncomingGsmSms(true, 0, null);
     }
 
@@ -202,18 +228,22 @@
         }
     }
 
-    @Test
-    @SmallTest
-    @FlakyTest
-    @Ignore
-    public void testSendTextWithInvalidDestAddr() throws Exception {
+    private void registerTestIntentReceiver() throws Exception {
         // unmock ActivityManager to be able to register receiver, create real PendingIntent and
         // receive TEST_INTENT
         restoreInstance(Singleton.class, "mInstance", mIActivityManagerSingleton);
         restoreInstance(ActivityManager.class, "IActivityManagerSingleton", null);
         Context realContext = TestApplication.getAppContext();
         realContext.registerReceiver(mTestReceiver, new IntentFilter(TEST_INTENT));
-        PendingIntent pendingIntent = PendingIntent.getBroadcast(realContext, 0,
+    }
+
+    @Test
+    @SmallTest
+    @FlakyTest
+    @Ignore
+    public void testSendTextWithInvalidDestAddr() throws Exception {
+        registerTestIntentReceiver();
+        PendingIntent pendingIntent = PendingIntent.getBroadcast(TestApplication.getAppContext(), 0,
                 new Intent(TEST_INTENT), 0);
         // send invalid dest address: +
         mReceivedTestIntent = false;
@@ -250,7 +280,8 @@
                 Settings.Global.DEVICE_PROVISIONED, 1);
 
         mGsmSmsDispatcher.sendRawPdu(new SMSDispatcher.SmsTracker[] {mSmsTracker});
-        waitForHandlerAction(mGsmSmsDispatcher, TIMEOUT_MS);
+        //waitForHandlerAction(mGsmSmsDispatcher, TIMEOUT_MS);
+        processAllMessages();
 
         verify(mSmsUsageMonitor, times(1)).checkDestination(any(), any());
         verify(mSmsUsageMonitor, times(1)).getPremiumSmsPermission(any());
@@ -264,13 +295,7 @@
     @FlakyTest
     @Ignore
     public void testSendMultipartTextWithInvalidText() throws Exception {
-        // unmock ActivityManager to be able to register receiver, create real PendingIntent and
-        // receive TEST_INTENT
-        restoreInstance(Singleton.class, "mInstance", mIActivityManagerSingleton);
-        restoreInstance(ActivityManager.class, "IActivityManagerSingleton", null);
-
-        Context realContext = TestApplication.getAppContext();
-        realContext.registerReceiver(mTestReceiver, new IntentFilter(TEST_INTENT));
+        registerTestIntentReceiver();
 
         // initiate parameters for an invalid text MO SMS (the 2nd segmeant has 161 characters)
         ArrayList<String> parts = new ArrayList<>();
@@ -280,8 +305,8 @@
                 + "8");
 
         ArrayList<PendingIntent> sentIntents = new ArrayList<>();
-        PendingIntent sentIntent = PendingIntent.getBroadcast(realContext, 0,
-                new Intent(TEST_INTENT), 0);
+        PendingIntent sentIntent = PendingIntent.getBroadcast(TestApplication.getAppContext(), 0,
+                new Intent(TEST_INTENT), PendingIntent.FLAG_IMMUTABLE);
         sentIntents.add(sentIntent);
         sentIntents.add(sentIntent);
 
@@ -296,4 +321,193 @@
             assertEquals(SmsManager.RESULT_ERROR_GENERIC_FAILURE, mTestReceiver.getResultCode());
         }
     }
+
+    private void mockCarrierApp()
+            throws RemoteException {
+        mContextFixture.addService(
+                CarrierMessagingService.SERVICE_INTERFACE,
+                new ComponentName(CARRIER_APP_PACKAGE_NAME, "CarrierAppFilterClass"),
+                CARRIER_APP_PACKAGE_NAME,
+                mICarrierAppMessagingService,
+                new ServiceInfo());
+        when(mICarrierAppMessagingService.asBinder()).thenReturn(mICarrierAppMessagingService);
+        mockUiccWithCarrierApp();
+    }
+
+    private void mockUiccWithCarrierApp() {
+        when(mUiccController.getUiccCard(mPhone.getPhoneId())).thenReturn(mUiccCard);
+        List<String> carrierPackages = new ArrayList<>();
+        carrierPackages.add(CARRIER_APP_PACKAGE_NAME);
+        when(mUiccCard.getCarrierPackageNamesForIntent(
+                any(PackageManager.class), any(Intent.class))).thenReturn(carrierPackages);
+    }
+
+    private void mockCarrierAppStubResults(final int result, ICarrierMessagingService.Stub stub,
+            boolean callOnFilterComplete)
+            throws RemoteException {
+        when(stub.queryLocalInterface(anyString())).thenReturn(stub);
+        when(stub.asBinder()).thenReturn(stub);
+        // for single part
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+                Object[] args = invocation.getArguments();
+                ICarrierMessagingCallback callback = (ICarrierMessagingCallback) args[4];
+                if (callOnFilterComplete) {
+                    callback.onSendSmsComplete(result, 0);
+                }
+                return null;
+            }
+        }).when(stub).sendTextSms(
+                anyString(), anyInt(), anyString(), anyInt(),
+                any(ICarrierMessagingCallback.class));
+
+        // for multi part
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+                Object[] args = invocation.getArguments();
+                ICarrierMessagingCallback callback = (ICarrierMessagingCallback) args[4];
+                if (callOnFilterComplete) {
+                    callback.onSendMultipartSmsComplete(result, null);
+                }
+                return null;
+            }
+        }).when(stub).sendMultipartTextSms(
+                any(), anyInt(), anyString(), anyInt(),
+                any(ICarrierMessagingCallback.class));
+    }
+
+    @Test
+    @SmallTest
+    public void testSendSmsByCarrierApp() throws Exception {
+        mockCarrierApp();
+        mockCarrierAppStubResults(CarrierMessagingService.SEND_STATUS_OK,
+                mICarrierAppMessagingService, true);
+        registerTestIntentReceiver();
+
+        PendingIntent pendingIntent = PendingIntent.getBroadcast(TestApplication.getAppContext(), 0,
+                new Intent(TEST_INTENT), PendingIntent.FLAG_MUTABLE);
+        mReceivedTestIntent = false;
+
+        mGsmSmsDispatcher.sendText("6501002000", "121" /*scAddr*/, "test sms",
+                pendingIntent, null, null, null, false, -1, false, -1, false, 0L);
+        processAllMessages();
+        synchronized (mLock) {
+            if (!mReceivedTestIntent) {
+                // long wait since sometimes broadcasts can take a long time if the system is loaded
+                mLock.wait(60000);
+            }
+            assertEquals(true, mReceivedTestIntent);
+            int resultCode = mTestReceiver.getResultCode();
+            assertTrue("Unexpected result code: " + resultCode,
+                    resultCode == SmsManager.RESULT_ERROR_NONE || resultCode == Activity.RESULT_OK);
+            verify(mSimulatedCommandsVerifier, times(0)).sendSMS(anyString(), anyString(),
+                    any(Message.class));
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void testSendSmsByCarrierAppNoResponse() throws Exception {
+        mockCarrierApp();
+        // do not mock result, instead reduce the timeout for test
+        mGsmSmsDispatcher.mCarrierMessagingTimeout = 100;
+
+        mGsmSmsDispatcher.sendText("6501002000", "121" /*scAddr*/, "test sms",
+                null, null, null, null, false, -1, false, -1, false, 0L);
+        // wait for timeout
+        waitForMs(150);
+        verify(mSimulatedCommandsVerifier).sendSMS(anyString(), anyString(), any(Message.class));
+    }
+
+    @Test
+    @SmallTest
+    public void testSendSmsByCarrierAppBindingFailed() throws Exception {
+        mContextFixture.mockBindingFailureForPackage(CARRIER_APP_PACKAGE_NAME);
+        // mock presence of carrier app, but do not create a mock service to make binding fail
+        mockUiccWithCarrierApp();
+
+        mGsmSmsDispatcher.sendText("6501002000", "121" /*scAddr*/, "test sms",
+                null, null, null, null, false, -1, false, -1, false, 0L);
+        processAllMessages();
+        verify(mSimulatedCommandsVerifier).sendSMS(anyString(), anyString(), any(Message.class));
+    }
+
+    private void sendMultipartTextSms(boolean withSentIntents) {
+        // initiate parameters for a multipart sms
+        ArrayList<String> parts = new ArrayList<>();
+        parts.add("segment1");
+        parts.add("segment2");
+
+        ArrayList<PendingIntent> sentIntents = new ArrayList<>();
+        PendingIntent sentIntent1 = PendingIntent.getBroadcast(TestApplication.getAppContext(), 0,
+                new Intent(TEST_INTENT), PendingIntent.FLAG_MUTABLE);
+        PendingIntent sentIntent2 = PendingIntent.getBroadcast(TestApplication.getAppContext(), 0,
+                new Intent(TEST_INTENT), PendingIntent.FLAG_MUTABLE);
+        sentIntents.add(sentIntent1);
+        sentIntents.add(sentIntent2);
+
+        mGsmSmsDispatcher.sendMultipartText("6501002000" /*destAddr*/, "222" /*scAddr*/, parts,
+                withSentIntents ? sentIntents : null, null, null, null, false, -1, false, -1, 0L);
+    }
+
+    @Test
+    @SmallTest
+    public void testSendMultipartSmsByCarrierApp() throws Exception {
+        mockCarrierApp();
+        mockCarrierAppStubResults(CarrierMessagingService.SEND_STATUS_OK,
+                mICarrierAppMessagingService, true);
+        registerTestIntentReceiver();
+
+        // send SMS and check sentIntent
+        mReceivedTestIntent = false;
+        sendMultipartTextSms(true);
+        processAllMessages();
+        synchronized (mLock) {
+            if (!mReceivedTestIntent) {
+                // long wait since sometimes broadcasts can take a long time if the system is loaded
+                mLock.wait(60000);
+            }
+            assertEquals(true, mReceivedTestIntent);
+            int resultCode = mTestReceiver.getResultCode();
+            assertTrue("Unexpected result code: " + resultCode,
+                    resultCode == SmsManager.RESULT_ERROR_NONE || resultCode == Activity.RESULT_OK);
+            verify(mSimulatedCommandsVerifier, times(0)).sendSMS(anyString(), anyString(),
+                    any(Message.class));
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void testSendMultipartSmsByCarrierAppNoResponse() throws Exception {
+        mockCarrierApp();
+        // do not mock result, instead reduce the timeout for test
+        mGsmSmsDispatcher.mCarrierMessagingTimeout = 100;
+
+        sendMultipartTextSms(false);
+
+        // wait for timeout
+        waitForMs(150);
+        verify(mSimulatedCommandsVerifier).sendSMSExpectMore(anyString(), anyString(),
+                any(Message.class));
+        verify(mSimulatedCommandsVerifier).sendSMS(anyString(), anyString(),
+                any(Message.class));
+    }
+
+    @Test
+    @SmallTest
+    public void testSendMultipartSmsByCarrierAppBindingFailed() throws Exception {
+        mContextFixture.mockBindingFailureForPackage(CARRIER_APP_PACKAGE_NAME);
+        // mock presence of carrier app, but do not create a mock service to make binding fail
+        mockUiccWithCarrierApp();
+
+        sendMultipartTextSms(false);
+
+        processAllMessages();
+        verify(mSimulatedCommandsVerifier).sendSMSExpectMore(anyString(), anyString(),
+                any(Message.class));
+        verify(mSimulatedCommandsVerifier).sendSMS(anyString(), anyString(),
+                any(Message.class));
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ims/ImsResolverTest.java b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsResolverTest.java
index 4ba26d1..d5c88e3 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ims/ImsResolverTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsResolverTest.java
@@ -132,8 +132,6 @@
     public void tearDown() throws Exception {
         mTestImsResolver.destroy();
         mTestImsResolver = null;
-        mLooper.destroy();
-        mLooper = null;
         super.tearDown();
     }
 
@@ -194,6 +192,39 @@
     }
 
     /**
+     * Add a device ImsService and ensure that querying ImsResolver to see if an ImsService is
+     * configured succeeds.
+     */
+    @Test
+    @SmallTest
+    public void testIsDeviceImsServiceConfigured() throws Exception {
+        setupResolver(1 /*numSlots*/, TEST_DEVICE_DEFAULT_NAME.getPackageName(),
+                TEST_DEVICE_DEFAULT_NAME.getPackageName());
+        HashSet<String> features = new HashSet<>();
+        features.add(ImsResolver.METADATA_EMERGENCY_MMTEL_FEATURE);
+        features.add(ImsResolver.METADATA_MMTEL_FEATURE);
+        features.add(ImsResolver.METADATA_RCS_FEATURE);
+        setupPackageQuery(TEST_DEVICE_DEFAULT_NAME, features, true);
+        setupController();
+
+        // Complete package manager lookup and cache.
+        startBindCarrierConfigAlreadySet();
+
+        // device package name should be returned for both features.
+        final Boolean[] isConfigured = new Boolean[1];
+        // Calling this method will block us until the looper processes the command, so use
+        // runWithLooper to allow the message to be processed.
+        mLooper.runWithLooper(() ->
+                isConfigured[0] = mTestImsResolver.isImsServiceConfiguredForFeature(0,
+                        ImsFeature.FEATURE_MMTEL));
+        assertTrue(isConfigured[0]);
+        mLooper.runWithLooper(() ->
+                isConfigured[0] = mTestImsResolver.isImsServiceConfiguredForFeature(0,
+                        ImsFeature.FEATURE_RCS));
+        assertTrue(isConfigured[0]);
+    }
+
+    /**
      * Add a device ImsService and ensure that querying the configured ImsService for all features
      * reports the device ImsService.
      */
@@ -227,7 +258,7 @@
     }
 
     /**
-     * Add in the case that there is no device or carrier ImsService found, we return null for
+     * In the case that there is no device or carrier ImsService found, we return null for
      * configuration queries.
      */
     @Test
@@ -255,7 +286,7 @@
     }
 
     /**
-     * Add in the case that there is no device or carrier ImsService configured, we return null for
+     * In the case that there is no device or carrier ImsService configured, we return null for
      * configuration queries.
      */
     @Test
@@ -613,7 +644,7 @@
         setConfigCarrierStringMmTelRcs(0, null);
         startBindCarrierConfigAlreadySet();
 
-        mLooper.processAllMessages();
+        processAllMessages();
         verify(mMockQueryManager, never()).startQuery(any(), any());
         verify(controller, never()).bind(any());
         verify(controller, never()).unbind();
@@ -640,7 +671,7 @@
 
 
         startBindNoCarrierConfig(1);
-        mLooper.processAllMessages();
+        processAllMessages();
 
         // There is no carrier override set, so make sure that the ImsServiceController binds
         // to all SIMs.
@@ -673,7 +704,7 @@
 
 
         startBindNoCarrierConfig(1);
-        mLooper.processAllMessages();
+        processAllMessages();
 
         // There is no carrier override set, so make sure that the ImsServiceController binds
         // to all SIMs.
@@ -687,7 +718,7 @@
         // Change number of SIMs and verify the features in the ImsServiceController are changed
         // as well
         PhoneConfigurationManager.notifyMultiSimConfigChange(1);
-        mLooper.processAllMessages();
+        processAllMessages();
         featureSet = convertToHashSet(features, 0);
         verify(controller).changeImsServiceFeatures(featureSet);
         verify(controller, never()).unbind();
@@ -715,7 +746,7 @@
 
 
         startBindNoCarrierConfig(1);
-        mLooper.processAllMessages();
+        processAllMessages();
 
         // There is no carrier override set, so make sure that the ImsServiceController binds
         // to all SIMs.
@@ -774,7 +805,7 @@
                 convertToFeatureSlotPairs(0, ImsResolver.METADATA_RCS_FEATURE);
 
         startBindNoCarrierConfig(1);
-        mLooper.processAllMessages();
+        processAllMessages();
         // ensure that startQuery was called
         verify(mMockQueryManager, times(1)).startQuery(eq(TEST_DEVICE_DEFAULT_NAME),
                 any(String.class));
@@ -784,7 +815,7 @@
 
         mDynamicQueryListener.onComplete(TEST_DEVICE_DEFAULT_NAME, deviceFeatures1);
         mDynamicQueryListener.onComplete(TEST_DEVICE2_DEFAULT_NAME, deviceFeatures2);
-        mLooper.processAllMessages();
+        processAllMessages();
 
         verify(deviceController, times(2)).bind(eq(deviceFeatures1));
         verify(deviceController2, times(1)).bind(eq(deviceFeatures2));
@@ -902,7 +933,7 @@
         // Move to single SIM and verify the features in the ImsServiceController are changed as
         // well.
         PhoneConfigurationManager.notifyMultiSimConfigChange(1);
-        mLooper.processAllMessages();
+        processAllMessages();
         carrierFeatures = new HashSet<>();
         carrierFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(0, ImsFeature.FEATURE_RCS));
         verify(carrierController).changeImsServiceFeatures(carrierFeatures);
@@ -933,7 +964,7 @@
 
 
         startBindNoCarrierConfig(1);
-        mLooper.processAllMessages();
+        processAllMessages();
 
         // There is no carrier override set, so make sure that the ImsServiceController binds
         // to all SIMs.
@@ -1550,7 +1581,7 @@
         info.add(getResolveInfo(TEST_CARRIER_DEFAULT_NAME, new HashSet<>(), true));
         // Boot complete has happened and the carrier ImsService is now available.
         mTestBootCompleteReceiver.onReceive(null, new Intent(Intent.ACTION_BOOT_COMPLETED));
-        mLooper.processAllMessages();
+        processAllMessages();
         setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, carrierFeatures, 1);
 
         // Verify that all features that have been defined for the carrier override are bound
@@ -1647,7 +1678,7 @@
         assertEquals(TEST_DEVICE_DEFAULT_NAME, deviceController.getComponentName());
 
         mTestImsResolver.imsServiceBindPermanentError(TEST_CARRIER_DEFAULT_NAME);
-        mLooper.processAllMessages();
+        processAllMessages();
         verify(carrierController).unbind();
         // Verify that the device ImsService features are changed to include the ones previously
         // taken by the carrier app.
@@ -1678,7 +1709,7 @@
         setImsServiceControllerFactory(deviceController1, deviceController2, null, null);
 
         startBindNoCarrierConfig(1);
-        mLooper.processAllMessages();
+        processAllMessages();
 
         Set<String> featureResult = new HashSet<>();
         featureResult.add(ImsResolver.METADATA_MMTEL_FEATURE);
@@ -1715,7 +1746,7 @@
         setImsServiceControllerFactory(deviceController1, deviceController2, null, null);
 
         startBindNoCarrierConfig(1);
-        mLooper.processAllMessages();
+        processAllMessages();
 
         Set<String> featureResult = new HashSet<>();
         featureResult.add(ImsResolver.METADATA_RCS_FEATURE);
@@ -1756,7 +1787,7 @@
         setImsServiceControllerFactory(deviceController1, deviceController2, null, null);
 
         startBindNoCarrierConfig(1);
-        mLooper.processAllMessages();
+        processAllMessages();
 
         HashSet<ImsFeatureConfiguration.FeatureSlotPair> featureSet1 =
                 convertToHashSet(features1, 0);
@@ -1797,7 +1828,7 @@
         setImsServiceControllerFactory(deviceController1, deviceController2, null, null);
 
         startBindNoCarrierConfig(1);
-        mLooper.processAllMessages();
+        processAllMessages();
 
         verify(deviceController1, never()).bind(any());
         verify(deviceController1, never()).unbind();
@@ -1835,22 +1866,17 @@
                 numSlots, mMockRepo);
         try {
             mLooper = new TestableLooper(mTestImsResolver.getHandler().getLooper());
+            monitorTestableLooper(mLooper);
         } catch (Exception e) {
             fail("Unable to create looper from handler.");
         }
 
-        ArgumentCaptor<BroadcastReceiver> receiversCaptor =
-                ArgumentCaptor.forClass(BroadcastReceiver.class);
-        verify(mMockContext, times(3)).registerReceiver(receiversCaptor.capture(), any());
-        mTestPackageBroadcastReceiver = receiversCaptor.getAllValues().get(0);
-        mTestCarrierConfigReceiver = receiversCaptor.getAllValues().get(1);
-        mTestBootCompleteReceiver = receiversCaptor.getAllValues().get(2);
         mTestImsResolver.setSubscriptionManagerProxy(mTestSubscriptionManagerProxy);
         mTestImsResolver.setTelephonyManagerProxy(mTestTelephonyManagerProxy);
         when(mMockQueryManagerFactory.create(any(Context.class),
                 any(ImsServiceFeatureQueryManager.Listener.class))).thenReturn(mMockQueryManager);
         mTestImsResolver.setImsDynamicQueryManagerFactory(mMockQueryManagerFactory);
-        mLooper.processAllMessages();
+        processAllMessages();
     }
 
     private void setupPackageQuery(List<ResolveInfo> infos) {
@@ -1896,13 +1922,19 @@
      */
     private void startBindCarrierConfigAlreadySet() {
         mTestImsResolver.initialize();
+        ArgumentCaptor<BroadcastReceiver> receiversCaptor =
+                ArgumentCaptor.forClass(BroadcastReceiver.class);
+        verify(mMockContext, times(3)).registerReceiver(receiversCaptor.capture(), any());
+        mTestPackageBroadcastReceiver = receiversCaptor.getAllValues().get(0);
+        mTestCarrierConfigReceiver = receiversCaptor.getAllValues().get(1);
+        mTestBootCompleteReceiver = receiversCaptor.getAllValues().get(2);
         ArgumentCaptor<ImsServiceFeatureQueryManager.Listener> queryManagerCaptor =
                 ArgumentCaptor.forClass(ImsServiceFeatureQueryManager.Listener.class);
         verify(mMockQueryManagerFactory).create(any(Context.class), queryManagerCaptor.capture());
         mDynamicQueryListener = queryManagerCaptor.getValue();
         when(mMockQueryManager.startQuery(any(ComponentName.class), any(String.class)))
                 .thenReturn(true);
-        mLooper.processAllMessages();
+        processAllMessages();
     }
 
     /**
@@ -1911,11 +1943,17 @@
      */
     private void startBindNoCarrierConfig(int numSlots) {
         mTestImsResolver.initialize();
+        ArgumentCaptor<BroadcastReceiver> receiversCaptor =
+                ArgumentCaptor.forClass(BroadcastReceiver.class);
+        verify(mMockContext, times(3)).registerReceiver(receiversCaptor.capture(), any());
+        mTestPackageBroadcastReceiver = receiversCaptor.getAllValues().get(0);
+        mTestCarrierConfigReceiver = receiversCaptor.getAllValues().get(1);
+        mTestBootCompleteReceiver = receiversCaptor.getAllValues().get(2);
         ArgumentCaptor<ImsServiceFeatureQueryManager.Listener> queryManagerCaptor =
                 ArgumentCaptor.forClass(ImsServiceFeatureQueryManager.Listener.class);
         verify(mMockQueryManagerFactory).create(any(Context.class), queryManagerCaptor.capture());
         mDynamicQueryListener = queryManagerCaptor.getValue();
-        mLooper.processAllMessages();
+        processAllMessages();
         // For ease of testing, slotId = subId
         for (int i = 0; i < numSlots; i++) {
             sendCarrierConfigChanged(i, i);
@@ -1924,19 +1962,19 @@
 
     private void setupDynamicQueryFeatures(ComponentName name,
             HashSet<ImsFeatureConfiguration.FeatureSlotPair> features, int times) {
-        mLooper.processAllMessages();
+        processAllMessages();
         // ensure that startQuery was called
         verify(mMockQueryManager, times(times)).startQuery(eq(name), any(String.class));
         mDynamicQueryListener.onComplete(name, features);
-        mLooper.processAllMessages();
+        processAllMessages();
     }
 
     private void setupDynamicQueryFeaturesFailure(ComponentName name, int times) {
-        mLooper.processAllMessages();
+        processAllMessages();
         // ensure that startQuery was called
         verify(mMockQueryManager, times(times)).startQuery(eq(name), any(String.class));
         mDynamicQueryListener.onPermanentError(name);
-        mLooper.processAllMessages();
+        processAllMessages();
     }
 
     public void packageChanged(String packageName) {
@@ -1946,7 +1984,7 @@
         addPackageIntent.setData(new Uri.Builder().scheme("package").opaquePart(packageName)
                 .build());
         mTestPackageBroadcastReceiver.onReceive(null, addPackageIntent);
-        mLooper.processAllMessages();
+        processAllMessages();
     }
 
     public void packageRemoved(String packageName) {
@@ -1955,7 +1993,7 @@
         removePackageIntent.setData(new Uri.Builder().scheme("package")
                 .opaquePart(TEST_CARRIER_DEFAULT_NAME.getPackageName()).build());
         mTestPackageBroadcastReceiver.onReceive(null, removePackageIntent);
-        mLooper.processAllMessages();
+        processAllMessages();
     }
 
     private void setImsServiceControllerFactory(Map<String, ImsServiceController> controllerMap) {
@@ -2075,7 +2113,7 @@
         carrierConfigIntent.putExtra(CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX, subId);
         carrierConfigIntent.putExtra(CarrierConfigManager.EXTRA_SLOT_INDEX, slotId);
         mTestCarrierConfigReceiver.onReceive(null, carrierConfigIntent);
-        mLooper.processAllMessages();
+        processAllMessages();
     }
 
     private void setConfigCarrierStringMmTelRcs(int subId, String packageName) {
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ims/ImsTestBase.java b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsTestBase.java
index ad8cd47..086390a 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ims/ImsTestBase.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsTestBase.java
@@ -16,14 +16,19 @@
 
 package com.android.internal.telephony.ims;
 
+import static org.junit.Assert.fail;
+
 import android.content.Context;
 import android.os.Handler;
 import android.os.Looper;
+import android.testing.TestableLooper;
 
 import androidx.test.InstrumentationRegistry;
 
 import org.mockito.MockitoAnnotations;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
@@ -33,6 +38,8 @@
 public class ImsTestBase {
 
     protected Context mContext;
+    protected List<TestableLooper> mTestableLoopers = new ArrayList<>();
+    protected TestableLooper mTestableLooper;
 
     public void setUp() throws Exception {
         mContext = InstrumentationRegistry.getTargetContext();
@@ -41,9 +48,16 @@
         if (Looper.myLooper() == null) {
             Looper.prepare();
         }
+        mTestableLooper = TestableLooper.get(ImsTestBase.this);
+        monitorTestableLooper(mTestableLooper);
     }
 
     public void tearDown() throws Exception {
+        unmonitorTestableLooper(mTestableLooper);
+        for (TestableLooper looper : mTestableLoopers) {
+            looper.destroy();
+        }
+        TestableLooper.remove(ImsTestBase.this);
     }
 
     protected final void waitForHandlerAction(Handler h, long timeoutMillis) {
@@ -61,4 +75,49 @@
             }
         }
     }
+
+    /**
+     * Add a TestableLooper to the list of monitored loopers
+     * @param looper looper to be added if it doesn't already exist
+     */
+    public void monitorTestableLooper(TestableLooper looper) {
+        if (looper != null && !mTestableLoopers.contains(looper)) {
+            mTestableLoopers.add(looper);
+        }
+    }
+
+    /**
+     * Remove a TestableLooper from the list of monitored loopers
+     * @param looper looper to be removed if it exists
+     */
+    public void unmonitorTestableLooper(TestableLooper looper) {
+        if (looper != null && mTestableLoopers.contains(looper)) {
+            mTestableLoopers.remove(looper);
+        }
+    }
+
+    /**
+     * Process all messages at the current time for all monitored TestableLoopers
+     */
+    public void processAllMessages() {
+        if (mTestableLoopers.isEmpty()) {
+            fail("mTestableLoopers is empty. Please make sure to add @RunWithLooper annotation");
+        }
+        while (!areAllTestableLoopersIdle()) {
+            for (TestableLooper looper : mTestableLoopers) looper.processAllMessages();
+        }
+    }
+
+    /**
+     * Check if there are any messages to be processed in any monitored TestableLooper
+     * Delayed messages to be handled at a later time will be ignored
+     * @return true if there are no messages that can be handled at the current time
+     *         across all monitored TestableLoopers
+     */
+    private boolean areAllTestableLoopersIdle() {
+        for (TestableLooper looper : mTestableLoopers) {
+            if (!looper.getLooper().getQueue().isIdle()) return false;
+        }
+        return true;
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneCallTest.java b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneCallTest.java
index 31fbfad..68ba403 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneCallTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneCallTest.java
@@ -21,6 +21,7 @@
 import static org.junit.Assert.fail;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -139,6 +140,63 @@
         assertEquals(Call.State.ACTIVE, mImsCallUT.getState());
     }
 
+    /**
+     * Verifies we can handle starting ringback between call state changes.
+     */
+    @Test
+    @SmallTest
+    public void testUpdateRingBackToneBetweenStateChange() {
+        mMediaProfile.mAudioDirection = ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE;
+        mImsCallProfile.mMediaProfile = mMediaProfile;
+
+        // This call state change should NOT start ringback since it the direction is wrong.
+        mImsCallUT.update(null, mImsCall, Call.State.ALERTING);
+        verify(mImsPhone, never()).startRingbackTone();
+        assertEquals(Call.State.ALERTING, mImsCallUT.getState());
+
+        // Simulate a change to the profile without a state change.
+        mMediaProfile.mAudioDirection = ImsStreamMediaProfile.DIRECTION_INACTIVE;
+        mImsCallUT.maybeChangeRingbackState(mImsCall);
+        verify(mImsPhone, times(1)).startRingbackTone();
+
+        // And then assume the call goes active, which would stop the ringback.
+        mImsCallUT.update(null, mImsCall, Call.State.ACTIVE);
+        verify(mImsPhone, times(1)).stopRingbackTone();
+        assertEquals(Call.State.ACTIVE, mImsCallUT.getState());
+    }
+
+    /**
+     * Verifies we can handle ringback start/stop entirely between call state changes.
+     */
+    @Test
+    @SmallTest
+    public void testUpdateRingBackToneBetweenStateChangeTwo() {
+        mMediaProfile.mAudioDirection = ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE;
+        mImsCallProfile.mMediaProfile = mMediaProfile;
+
+        // This call state change should NOT start ringback since it the direction is wrong.
+        mImsCallUT.update(null, mImsCall, Call.State.ALERTING);
+        verify(mImsPhone, never()).startRingbackTone();
+        assertEquals(Call.State.ALERTING, mImsCallUT.getState());
+
+        // Simulate a change to the profile without a state change.
+        mMediaProfile.mAudioDirection = ImsStreamMediaProfile.DIRECTION_INACTIVE;
+        mImsCallUT.maybeChangeRingbackState(mImsCall);
+        verify(mImsPhone, times(1)).startRingbackTone();
+
+        // Simulate another change to the profile without a state change.
+        mMediaProfile.mAudioDirection = ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE;
+        mImsCallUT.maybeChangeRingbackState(mImsCall);
+        verify(mImsPhone, times(1)).stopRingbackTone();
+
+        // And then assume the call goes active, which should not impact ringback state.
+        mImsCallUT.update(null, mImsCall, Call.State.ACTIVE);
+        assertEquals(Call.State.ACTIVE, mImsCallUT.getState());
+        // Should still have only started and stopped once
+        verify(mImsPhone, times(1)).startRingbackTone();
+        verify(mImsPhone, times(1)).stopRingbackTone();
+    }
+
     @Test
     @SmallTest
     public void testStopRingingOnHandover() {
diff --git a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneCallTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneCallTrackerTest.java
index 687d7d4..d1826d5 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneCallTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneCallTrackerTest.java
@@ -29,6 +29,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Mockito.any;
@@ -569,6 +570,12 @@
         // Now fake the ImsService crashing
         mCTUT.hangupAllOrphanedConnections(DisconnectCause.LOST_SIGNAL);
         assertEquals(PhoneConstants.State.IDLE, mCTUT.getState());
+        try {
+            // ensure new calls are not blocked by any lingering state after crash.
+            mCTUT.checkForDialIssues();
+        } catch (CallStateException e) {
+            fail("checkForDialIssues should not generate a CallStateException: " + e.getMessage());
+        }
     }
 
     /**
@@ -635,17 +642,32 @@
         }
     }
 
-    @FlakyTest
-    @Ignore
     @Test
     @SmallTest
     public void testImsMOCallDial() {
         startOutgoingCall();
         //call established
         mImsCallListener.onCallProgressing(mSecondImsCall);
+        processAllMessages();
         assertEquals(Call.State.ALERTING, mCTUT.mForegroundCall.getState());
     }
 
+    @Test
+    @SmallTest
+    public void testImsMoCallCrash() {
+        startOutgoingCall();
+        // Now fake the ImsService crashing
+        mCTUT.hangupAllOrphanedConnections(DisconnectCause.LOST_SIGNAL);
+        processAllMessages();
+        assertEquals(PhoneConstants.State.IDLE, mCTUT.getState());
+        try {
+            // ensure new calls are not blocked by any lingering state after crash.
+            mCTUT.checkForDialIssues();
+        } catch (CallStateException e) {
+            fail("checkForDialIssues should not generate a CallStateException: " + e.getMessage());
+        }
+    }
+
     private void startOutgoingCall() {
         assertEquals(Call.State.IDLE, mCTUT.mForegroundCall.getState());
         assertEquals(PhoneConstants.State.IDLE, mCTUT.getState());
@@ -658,6 +680,7 @@
             ex.printStackTrace();
             Assert.fail("unexpected exception thrown" + ex.getMessage());
         }
+        processAllMessages();
         assertEquals(PhoneConstants.State.OFFHOOK, mCTUT.getState());
         assertEquals(Call.State.DIALING, mCTUT.mForegroundCall.getState());
     }
@@ -1281,13 +1304,23 @@
     }
 
     /**
-     * Ensures when D2D communication is supported that we register the expected D2D RTP header
-     * extension types.
+     * Ensures when both RTP and SDP is supported that we register the expected header extension
+     * types.
      * @throws Exception
      */
     @Test
     @SmallTest
     public void testConfigureRtpHeaderExtensionTypes() throws Exception {
+
+        mContextFixture.getCarrierConfigBundle().putBoolean(
+                CarrierConfigManager.KEY_SUPPORTS_DEVICE_TO_DEVICE_COMMUNICATION_USING_RTP_BOOL,
+                true);
+        mContextFixture.getCarrierConfigBundle().putBoolean(
+                CarrierConfigManager.KEY_SUPPORTS_SDP_NEGOTIATION_OF_D2D_RTP_HEADER_EXTENSIONS_BOOL,
+                true);
+        // Hacky but ImsPhoneCallTracker caches carrier config, so necessary.
+        mCTUT.updateCarrierConfigCache(mContextFixture.getCarrierConfigBundle());
+
         ImsPhoneCallTracker.Config config = new ImsPhoneCallTracker.Config();
         config.isD2DCommunicationSupported = true;
         mCTUT.setConfig(config);
@@ -1303,6 +1336,35 @@
     }
 
     /**
+     * Ensures when SDP is not supported (by RTP is) we don't register any extensions.
+     * @throws Exception
+     */
+    @Test
+    @SmallTest
+    public void testRtpButNoSdp() throws Exception {
+
+        mContextFixture.getCarrierConfigBundle().putBoolean(
+                CarrierConfigManager.KEY_SUPPORTS_DEVICE_TO_DEVICE_COMMUNICATION_USING_RTP_BOOL,
+                true);
+        mContextFixture.getCarrierConfigBundle().putBoolean(
+                CarrierConfigManager.KEY_SUPPORTS_SDP_NEGOTIATION_OF_D2D_RTP_HEADER_EXTENSIONS_BOOL,
+                false);
+        // Hacky but ImsPhoneCallTracker caches carrier config, so necessary.
+        mCTUT.updateCarrierConfigCache(mContextFixture.getCarrierConfigBundle());
+
+        ImsPhoneCallTracker.Config config = new ImsPhoneCallTracker.Config();
+        config.isD2DCommunicationSupported = true;
+        mCTUT.setConfig(config);
+        mConnectorListener.connectionReady(mImsManager);
+
+        // Expect to get offered header extensions since d2d is supported.
+        verify(mImsManager).setOfferedRtpHeaderExtensionTypes(
+                mRtpHeaderExtensionTypeCaptor.capture());
+        Set<RtpHeaderExtensionType> types = mRtpHeaderExtensionTypeCaptor.getValue();
+        assertEquals(0, types.size());
+    }
+
+    /**
      * Ensures when D2D communication is not supported that we don't register the D2D RTP header
      * extension types.
      * @throws Exception
diff --git a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneConnectionTest.java b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneConnectionTest.java
index 70c8a56..6e816e6 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneConnectionTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneConnectionTest.java
@@ -15,6 +15,9 @@
  */
 package com.android.internal.telephony.imsphone;
 
+import static android.telephony.ims.ImsStreamMediaProfile.AUDIO_QUALITY_AMR_WB;
+import static android.telephony.ims.ImsStreamMediaProfile.AUDIO_QUALITY_EVS_SWB;
+
 import static com.android.internal.telephony.TelephonyTestUtils.waitForMs;
 
 import static org.junit.Assert.assertEquals;
@@ -27,8 +30,10 @@
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.os.AsyncResult;
 import android.os.Bundle;
@@ -40,16 +45,21 @@
 import android.telephony.ServiceState;
 import android.telephony.TelephonyManager;
 import android.telephony.ims.ImsCallProfile;
+import android.telephony.ims.ImsStreamMediaProfile;
 import android.test.suitebuilder.annotation.MediumTest;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import com.android.ims.ImsCall;
+import com.android.ims.ImsException;
 import com.android.internal.telephony.Call;
 import com.android.internal.telephony.Connection;
 import com.android.internal.telephony.GsmCdmaCall;
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.TelephonyTest;
+import com.android.internal.telephony.metrics.TelephonyMetrics;
+import com.android.internal.telephony.metrics.VoiceCallSessionStats;
 
 import org.junit.After;
 import org.junit.Assert;
@@ -61,10 +71,15 @@
 import org.mockito.stubbing.Answer;
 
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 public class ImsPhoneConnectionTest extends TelephonyTest {
+    private static final int TIMEOUT_MILLIS = 5000;
+
     private ImsPhoneConnection mConnectionUT;
     private Bundle mBundle = new Bundle();
     @Mock
@@ -412,14 +427,50 @@
     @SmallTest
     public void testSetRedirectingAddress() {
         mConnectionUT = new ImsPhoneConnection(mImsPhone, mImsCall, mImsCT, mForeGroundCall, false);
-        ArrayList<String> forwardedNumber = new ArrayList<String>();
-        forwardedNumber.add("11111");
-        forwardedNumber.add("22222");
-        forwardedNumber.add("33333");
+        String[] forwardedNumber = new String[]{"11111", "22222", "33333"};
+        ArrayList<String> forwardedNumberList =
+                new ArrayList<String>(Arrays.asList(forwardedNumber));
 
         assertEquals(mConnectionUT.getForwardedNumber(), null);
-        mBundle.putStringArrayList(ImsCallProfile.EXTRA_FORWARDED_NUMBER, forwardedNumber);
+        mBundle.putStringArray(ImsCallProfile.EXTRA_FORWARDED_NUMBER, forwardedNumber);
         assertTrue(mConnectionUT.update(mImsCall, Call.State.ACTIVE));
-        assertEquals(forwardedNumber, mConnectionUT.getForwardedNumber());
+        assertEquals(forwardedNumberList, mConnectionUT.getForwardedNumber());
+    }
+
+    @Test
+    @SmallTest
+    public void testReportMediaCodecChange() throws InterruptedException, ImsException {
+        ImsCall imsCall = mock(ImsCall.class);
+        ImsStreamMediaProfile mediaProfile = new ImsStreamMediaProfile();
+        ImsCallProfile profile = new ImsCallProfile();
+        profile.mMediaProfile = mediaProfile;
+        mediaProfile.mAudioQuality = AUDIO_QUALITY_AMR_WB;
+        when(imsCall.getLocalCallProfile()).thenReturn(profile);
+
+        // Blech; mocks required which are unrelated to this test
+        when(mImsCT.getPhone()).thenReturn(mImsPhone);
+        VoiceCallSessionStats stats = mock(VoiceCallSessionStats.class);
+        when(mImsPhone.getVoiceCallSessionStats()).thenReturn(stats);
+
+        mConnectionUT = new ImsPhoneConnection(mImsPhone, imsCall, mImsCT, mForeGroundCall, false);
+        mConnectionUT.setTelephonyMetrics(mock(TelephonyMetrics.class));
+        CountDownLatch latch = new CountDownLatch(1);
+        boolean[] receivedCountCallback = new boolean[1];
+        mConnectionUT.addListener(new Connection.ListenerBase() {
+            @Override
+            public void onMediaAttributesChanged() {
+                receivedCountCallback[0] = true;
+                latch.countDown();
+            }
+        });
+
+        mConnectionUT.updateMediaCapabilities(imsCall);
+
+        // Make an update to the media caps
+        mediaProfile.mAudioQuality = AUDIO_QUALITY_EVS_SWB;
+        mConnectionUT.updateMediaCapabilities(imsCall);
+
+        latch.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+        assertTrue(receivedCountCallback[0]);
     }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneMmiCodeTest.java b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneMmiCodeTest.java
index 220e7ce..aee46b7 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneMmiCodeTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneMmiCodeTest.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.telephony.imsphone;
 
+import static junit.framework.Assert.fail;
+
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.doReturn;
@@ -101,6 +103,16 @@
         assertTrue(mImsPhoneMmiCode == null);
     }
 
+    @Test
+    public void testNoCrashOnEmptyMessage() {
+        ImsPhoneMmiCode mmi = ImsPhoneMmiCode.newNetworkInitiatedUssd(null, true, mImsPhoneUT);
+        try {
+            mmi.onUssdFinishedError();
+        } catch (Exception e) {
+            fail("Shouldn't crash!!!");
+        }
+    }
+
     private void setCarrierSupportsCallerIdVerticalServiceCodesCarrierConfig() {
         final PersistableBundle bundle = new PersistableBundle();
         bundle.putBoolean(CarrierConfigManager
diff --git a/tests/telephonytests/src/com/android/internal/telephony/metrics/CallQualityMetricsTest.java b/tests/telephonytests/src/com/android/internal/telephony/metrics/CallQualityMetricsTest.java
index e530425..a0341b7 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/metrics/CallQualityMetricsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/CallQualityMetricsTest.java
@@ -28,15 +28,20 @@
 import android.telephony.CellSignalStrengthWcdma;
 import android.telephony.SignalStrength;
 
+import com.android.internal.telephony.SignalStrengthController;
 import com.android.internal.telephony.TelephonyTest;
 import com.android.internal.telephony.nano.TelephonyProto.TelephonyCallSession.Event.CallQualitySummary;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.mockito.Mock;
 
 public class CallQualityMetricsTest extends TelephonyTest {
 
+    @Mock
+    SignalStrengthController mSsc;
+
     private CallQualityMetrics mCallQualityMetrics;
 
     @Before
@@ -48,6 +53,7 @@
         // default phone from the ImsPhone and uses that to get the ServiceStateTracker, therefore
         // we need to mock the default phone as well.
         when(mPhone.getDefaultPhone()).thenReturn(mPhone);
+        when(mPhone.getSignalStrengthController()).thenReturn(mSsc);
     }
 
     @After
@@ -264,7 +270,7 @@
                 new CellSignalStrengthTdscdma(),
                 lteSs1,
                 new CellSignalStrengthNr());
-        when(mSST.getSignalStrength()).thenReturn(ss1);
+        when(mSsc.getSignalStrength()).thenReturn(ss1);
         mCallQualityMetrics.saveCallQuality(cq1);
 
         // save good quality with low rssnr
@@ -280,7 +286,7 @@
                 new CellSignalStrengthTdscdma(),
                 lteSs2,
                 new CellSignalStrengthNr());
-        when(mSST.getSignalStrength()).thenReturn(ss2);
+        when(mSsc.getSignalStrength()).thenReturn(ss2);
         mCallQualityMetrics.saveCallQuality(cq1);
 
         CallQualitySummary dlSummary = mCallQualityMetrics.getCallQualitySummaryDl();
diff --git a/tests/telephonytests/src/com/android/internal/telephony/metrics/ImsStatsTest.java b/tests/telephonytests/src/com/android/internal/telephony/metrics/ImsStatsTest.java
index c2b690f..02bdff5 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/metrics/ImsStatsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/ImsStatsTest.java
@@ -25,11 +25,13 @@
 import static android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE;
 import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN;
 import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_LTE;
+import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_NR;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -76,6 +78,7 @@
     @Mock private UiccSlot mPhysicalSlot1;
     @Mock private Phone mSecondPhone;
     @Mock private ImsPhone mSecondImsPhone;
+    @Mock private ServiceStateStats mServiceStateStats;
 
     private TestableImsStats mImsStats;
 
@@ -109,6 +112,7 @@
         doReturn(CARRIER1_ID).when(mPhone).getCarrierId();
         doReturn(mImsPhone).when(mPhone).getImsPhone();
         doReturn(mSST).when(mImsPhone).getServiceStateTracker();
+        doReturn(mServiceStateStats).when(mSST).getServiceStateStats();
 
         // WWAN PS RAT is LTE
         doReturn(
@@ -338,6 +342,86 @@
         assertEquals(0L, stats.smsCapableMillis);
         assertEquals(0L, stats.smsAvailableMillis);
         verifyNoMoreInteractions(mPersistAtomsStorage);
+        // ServiceStateStats should be notified
+        verify(mServiceStateStats).onImsVoiceRegistrationChanged();
+    }
+
+    @Test
+    @SmallTest
+    public void onImsCapabilitiesChanged_differentTech() throws Exception {
+        mImsStats.onSetFeatureResponse(
+                CAPABILITY_TYPE_VOICE,
+                REGISTRATION_TECH_LTE,
+                ProvisioningManager.PROVISIONING_VALUE_ENABLED);
+        mImsStats.onImsRegistered(TRANSPORT_TYPE_WWAN);
+        mImsStats.onImsCapabilitiesChanged(
+                REGISTRATION_TECH_LTE, new MmTelCapabilities(CAPABILITY_TYPE_VOICE));
+
+        verify(mServiceStateStats).onImsVoiceRegistrationChanged();
+
+        mImsStats.incTimeMillis(2000L);
+        mImsStats.onImsCapabilitiesChanged(
+                REGISTRATION_TECH_NR, new MmTelCapabilities(CAPABILITY_TYPE_VOICE));
+
+        // Atom with previous feature availability should be generated
+        ArgumentCaptor<ImsRegistrationStats> captor =
+                ArgumentCaptor.forClass(ImsRegistrationStats.class);
+        verify(mPersistAtomsStorage).addImsRegistrationStats(captor.capture());
+        ImsRegistrationStats stats = captor.getValue();
+        assertEquals(CARRIER1_ID, stats.carrierId);
+        assertEquals(0, stats.simSlotIndex);
+        assertEquals(TelephonyManager.NETWORK_TYPE_LTE, stats.rat);
+        assertEquals(2000L, stats.registeredMillis);
+        assertEquals(2000L, stats.voiceCapableMillis);
+        assertEquals(2000L, stats.voiceAvailableMillis);
+        assertEquals(0L, stats.videoCapableMillis);
+        assertEquals(0L, stats.videoAvailableMillis);
+        assertEquals(0L, stats.utCapableMillis);
+        assertEquals(0L, stats.utAvailableMillis);
+        assertEquals(0L, stats.smsCapableMillis);
+        assertEquals(0L, stats.smsAvailableMillis);
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+        // ServiceStateStats should be notified
+        verify(mServiceStateStats, times(2)).onImsVoiceRegistrationChanged();
+    }
+
+    @Test
+    @SmallTest
+    public void onImsCapabilitiesChanged_differentTechNoVoice() throws Exception {
+        mImsStats.onSetFeatureResponse(
+                CAPABILITY_TYPE_SMS,
+                REGISTRATION_TECH_LTE,
+                ProvisioningManager.PROVISIONING_VALUE_ENABLED);
+        mImsStats.onImsRegistered(TRANSPORT_TYPE_WWAN);
+        mImsStats.onImsCapabilitiesChanged(
+                REGISTRATION_TECH_LTE, new MmTelCapabilities(CAPABILITY_TYPE_SMS));
+
+        verify(mServiceStateStats, never()).onImsVoiceRegistrationChanged();
+
+        mImsStats.incTimeMillis(2000L);
+        mImsStats.onImsCapabilitiesChanged(
+                REGISTRATION_TECH_NR, new MmTelCapabilities(CAPABILITY_TYPE_SMS));
+
+        // Atom with previous feature availability should be generated
+        ArgumentCaptor<ImsRegistrationStats> captor =
+                ArgumentCaptor.forClass(ImsRegistrationStats.class);
+        verify(mPersistAtomsStorage).addImsRegistrationStats(captor.capture());
+        ImsRegistrationStats stats = captor.getValue();
+        assertEquals(CARRIER1_ID, stats.carrierId);
+        assertEquals(0, stats.simSlotIndex);
+        assertEquals(TelephonyManager.NETWORK_TYPE_LTE, stats.rat);
+        assertEquals(2000L, stats.registeredMillis);
+        assertEquals(0L, stats.voiceCapableMillis);
+        assertEquals(0L, stats.voiceAvailableMillis);
+        assertEquals(0L, stats.videoCapableMillis);
+        assertEquals(0L, stats.videoAvailableMillis);
+        assertEquals(0L, stats.utCapableMillis);
+        assertEquals(0L, stats.utAvailableMillis);
+        assertEquals(2000L, stats.smsCapableMillis);
+        assertEquals(2000L, stats.smsAvailableMillis);
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+        // ServiceStateStats should not be notified
+        verify(mServiceStateStats, never()).onImsVoiceRegistrationChanged();
     }
 
     @Test
@@ -722,4 +806,61 @@
         assertEquals("Timeout", termination.extraMessage);
         verifyNoMoreInteractions(mPersistAtomsStorage);
     }
+
+    @Test
+    @SmallTest
+    public void getImsVoiceRadioTech_noRegistration() throws Exception {
+        // Do nothing
+
+        assertEquals(TelephonyManager.NETWORK_TYPE_UNKNOWN, mImsStats.getImsVoiceRadioTech());
+    }
+
+    @Test
+    @SmallTest
+    public void getImsVoiceRadioTech_noVoiceRegistration() throws Exception {
+        mImsStats.onImsRegistered(TRANSPORT_TYPE_WWAN);
+        mImsStats.onImsCapabilitiesChanged(
+                REGISTRATION_TECH_LTE, new MmTelCapabilities(CAPABILITY_TYPE_SMS));
+
+        assertEquals(TelephonyManager.NETWORK_TYPE_UNKNOWN, mImsStats.getImsVoiceRadioTech());
+    }
+
+    @Test
+    @SmallTest
+    public void getImsVoiceRadioTech_cellularRegistration() throws Exception {
+        mImsStats.onImsRegistered(TRANSPORT_TYPE_WWAN);
+        mImsStats.onImsCapabilitiesChanged(
+                REGISTRATION_TECH_LTE, new MmTelCapabilities(CAPABILITY_TYPE_VOICE));
+
+        assertEquals(TelephonyManager.NETWORK_TYPE_LTE, mImsStats.getImsVoiceRadioTech());
+    }
+
+    @Test
+    @SmallTest
+    public void getImsVoiceRadioTech_wifiRegistration() throws Exception {
+        mImsStats.onImsRegistered(TRANSPORT_TYPE_WLAN);
+        mImsStats.onImsCapabilitiesChanged(
+                REGISTRATION_TECH_IWLAN, new MmTelCapabilities(CAPABILITY_TYPE_VOICE));
+
+        assertEquals(TelephonyManager.NETWORK_TYPE_IWLAN, mImsStats.getImsVoiceRadioTech());
+    }
+
+    @Test
+    @SmallTest
+    public void getImsVoiceRadioTech_unregistered() throws Exception {
+        mImsStats.onImsRegistered(TRANSPORT_TYPE_WWAN);
+        mImsStats.onImsCapabilitiesChanged(
+                REGISTRATION_TECH_LTE, new MmTelCapabilities(CAPABILITY_TYPE_VOICE));
+        mImsStats.onImsUnregistered(
+                new ImsReasonInfo(ImsReasonInfo.CODE_REGISTRATION_ERROR, 999, "Timeout"));
+        doReturn(
+                        new NetworkRegistrationInfo.Builder()
+                                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_UMTS)
+                                .build())
+                .when(mServiceState)
+                .getNetworkRegistrationInfo(DOMAIN_PS, TRANSPORT_TYPE_WWAN);
+
+
+        assertEquals(TelephonyManager.NETWORK_TYPE_UNKNOWN, mImsStats.getImsVoiceRadioTech());
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/metrics/PersistAtomsStorageTest.java b/tests/telephonytests/src/com/android/internal/telephony/metrics/PersistAtomsStorageTest.java
index df158c1..f200a0f 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/metrics/PersistAtomsStorageTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/PersistAtomsStorageTest.java
@@ -47,6 +47,7 @@
 import com.android.internal.telephony.TelephonyTest;
 import com.android.internal.telephony.nano.PersistAtomsProto.CellularDataServiceSwitch;
 import com.android.internal.telephony.nano.PersistAtomsProto.CellularServiceState;
+import com.android.internal.telephony.nano.PersistAtomsProto.DataCallSession;
 import com.android.internal.telephony.nano.PersistAtomsProto.ImsRegistrationStats;
 import com.android.internal.telephony.nano.PersistAtomsProto.ImsRegistrationTermination;
 import com.android.internal.telephony.nano.PersistAtomsProto.PersistAtoms;
@@ -130,6 +131,10 @@
     private ImsRegistrationStats[] mImsRegistrationStats;
     private ImsRegistrationTermination[] mImsRegistrationTerminations;
 
+    // Data call sessions
+    private DataCallSession mDataCallSession0;
+    private DataCallSession mDataCallSession1;
+
     private void makeTestData() {
         // MO call with SRVCC (LTE to UMTS)
         mCall1Proto = new VoiceCallSession();
@@ -432,6 +437,24 @@
                 new ImsRegistrationTermination[] {
                     mImsRegistrationTerminationLte, mImsRegistrationTerminationWifi
                 };
+
+        mDataCallSession0 = new DataCallSession();
+        mDataCallSession0.dimension = 111;
+        mDataCallSession0.carrierId = CARRIER1_ID;
+        mDataCallSession0.oosAtEnd = false;
+        mDataCallSession0.ratSwitchCount = 3L;
+        mDataCallSession0.setupFailed = false;
+        mDataCallSession0.durationMinutes = 20;
+        mDataCallSession0.ongoing = true;
+
+        mDataCallSession1 = new DataCallSession();
+        mDataCallSession1.dimension = 222;
+        mDataCallSession1.carrierId = CARRIER2_ID;
+        mDataCallSession1.oosAtEnd = true;
+        mDataCallSession1.ratSwitchCount = 1L;
+        mDataCallSession1.setupFailed = false;
+        mDataCallSession1.durationMinutes = 5;
+        mDataCallSession1.ongoing = false;
     }
 
     private static class TestablePersistAtomsStorage extends PersistAtomsStorage {
@@ -1273,6 +1296,53 @@
         inOrder.verifyNoMoreInteractions();
     }
 
+    @Test
+    @SmallTest
+    public void addDataCallSession_newEntry()
+            throws Exception {
+        createEmptyTestFile();
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+
+        mPersistAtomsStorage.addDataCallSession(mDataCallSession0);
+        mPersistAtomsStorage.addDataCallSession(mDataCallSession1);
+        mPersistAtomsStorage.incTimeMillis(100L);
+
+        // there should be 2 data calls
+        verifyCurrentStateSavedToFileOnce();
+        DataCallSession[] dataCalls = mPersistAtomsStorage.getDataCallSessions(0L);
+        assertProtoArrayEqualsIgnoringOrder(
+                new DataCallSession[] {mDataCallSession0, mDataCallSession1},
+                dataCalls);
+    }
+
+    @Test
+    @SmallTest
+    public void addDataCallSession_existingEntry()
+            throws Exception {
+        createEmptyTestFile();
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        DataCallSession newDataCallSession0 = copyOf(mDataCallSession0);
+        newDataCallSession0.ongoing = false;
+        newDataCallSession0.ratAtEnd = TelephonyManager.NETWORK_TYPE_LTE;
+        newDataCallSession0.durationMinutes = 10;
+        newDataCallSession0.ratSwitchCount = 5;
+        DataCallSession totalDataCallSession0 = copyOf(newDataCallSession0);
+        totalDataCallSession0.durationMinutes =
+                mDataCallSession0.durationMinutes + newDataCallSession0.durationMinutes;
+        totalDataCallSession0.ratSwitchCount =
+                mDataCallSession0.ratSwitchCount + newDataCallSession0.ratSwitchCount;
+
+        mPersistAtomsStorage.addDataCallSession(mDataCallSession0);
+        mPersistAtomsStorage.addDataCallSession(newDataCallSession0);
+        mPersistAtomsStorage.incTimeMillis(100L);
+
+        // there should be 1 data call
+        verifyCurrentStateSavedToFileOnce();
+        DataCallSession[] dataCalls = mPersistAtomsStorage.getDataCallSessions(0L);
+        assertProtoArrayEqualsIgnoringOrder(
+                new DataCallSession[] {totalDataCallSession0}, dataCalls);
+    }
+
     /* Utilities */
 
     private void createEmptyTestFile() throws Exception {
@@ -1351,6 +1421,11 @@
         return ImsRegistrationTermination.parseFrom(MessageNano.toByteArray(source));
     }
 
+    private static DataCallSession copyOf(DataCallSession source)
+            throws Exception {
+        return DataCallSession.parseFrom(MessageNano.toByteArray(source));
+    }
+
     private void assertAllPullTimestampEquals(long timestamp) {
         assertEquals(
                 timestamp,
diff --git a/tests/telephonytests/src/com/android/internal/telephony/metrics/ServiceStateStatsTest.java b/tests/telephonytests/src/com/android/internal/telephony/metrics/ServiceStateStatsTest.java
index ca967ab..a606e6e 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/metrics/ServiceStateStatsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/ServiceStateStatsTest.java
@@ -192,27 +192,13 @@
         doReturn(TelephonyManager.NETWORK_TYPE_UNKNOWN).when(mServiceState).getVoiceNetworkType();
         doReturn(TelephonyManager.NETWORK_TYPE_IWLAN).when(mServiceState).getDataNetworkType();
         mockWwanPsRat(TelephonyManager.NETWORK_TYPE_UNKNOWN);
-        doReturn(true).when(mImsPhone).isWifiCallingEnabled();
+        doReturn(TelephonyManager.NETWORK_TYPE_IWLAN).when(mImsStats).getImsVoiceRadioTech();
         mServiceStateStats.onServiceStateChanged(mServiceState);
 
         mServiceStateStats.incTimeMillis(100L);
         mServiceStateStats.conclude();
 
-        // Duration for Wifi calling should be counted and there should not be any switch
-        ArgumentCaptor<CellularServiceState> captor =
-                ArgumentCaptor.forClass(CellularServiceState.class);
-        verify(mPersistAtomsStorage)
-                .addCellularServiceStateAndCellularDataServiceSwitch(captor.capture(), eq(null));
-        CellularServiceState state = captor.getValue();
-        assertEquals(TelephonyManager.NETWORK_TYPE_IWLAN, state.voiceRat);
-        assertEquals(TelephonyManager.NETWORK_TYPE_UNKNOWN, state.dataRat);
-        assertEquals(ServiceState.ROAMING_TYPE_NOT_ROAMING, state.voiceRoamingType);
-        assertEquals(ServiceState.ROAMING_TYPE_NOT_ROAMING, state.dataRoamingType);
-        assertFalse(state.isEndc);
-        assertEquals(0, state.simSlotIndex);
-        assertFalse(state.isMultiSim);
-        assertEquals(CARRIER1_ID, state.carrierId);
-        assertEquals(100L, state.totalTimeMillis);
+        // There should be no new switches, service states, or added durations
         verifyNoMoreInteractions(mPersistAtomsStorage);
     }
 
@@ -409,7 +395,7 @@
         mServiceStateStats.incTimeMillis(100L);
         // Voice RAT changes to IWLAN and data RAT stays in LTE according to WWAN PS RAT
         doReturn(TelephonyManager.NETWORK_TYPE_IWLAN).when(mServiceState).getDataNetworkType();
-        doReturn(true).when(mImsPhone).isWifiCallingEnabled();
+        doReturn(TelephonyManager.NETWORK_TYPE_IWLAN).when(mImsStats).getImsVoiceRadioTech();
         mServiceStateStats.onServiceStateChanged(mServiceState);
         mServiceStateStats.incTimeMillis(100L);
 
@@ -447,7 +433,6 @@
     public void update_iwlanButNotWifiCalling() throws Exception {
         // Using default service state for LTE as WWAN PS RAT
         doReturn(TelephonyManager.NETWORK_TYPE_IWLAN).when(mServiceState).getDataNetworkType();
-        doReturn(false).when(mImsPhone).isWifiCallingEnabled();
 
         mServiceStateStats.onServiceStateChanged(mServiceState);
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/metrics/TelephonyMetricsTest.java b/tests/telephonytests/src/com/android/internal/telephony/metrics/TelephonyMetricsTest.java
index c70191c..c4e6e27 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/metrics/TelephonyMetricsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/TelephonyMetricsTest.java
@@ -29,6 +29,7 @@
 import static com.android.internal.telephony.dataconnection.DcTrackerTest.FAKE_GATEWAY;
 import static com.android.internal.telephony.dataconnection.DcTrackerTest.FAKE_IFNAME;
 import static com.android.internal.telephony.dataconnection.DcTrackerTest.FAKE_PCSCF_ADDRESS;
+import static com.android.internal.telephony.dataconnection.LinkBandwidthEstimator.NUM_SIGNAL_LEVEL;
 import static com.android.internal.telephony.nano.TelephonyProto.PdpType.PDP_TYPE_IPV4V6;
 
 import static org.junit.Assert.assertArrayEquals;
@@ -59,8 +60,11 @@
 import com.android.internal.telephony.SmsResponse;
 import com.android.internal.telephony.TelephonyTest;
 import com.android.internal.telephony.UUSInfo;
+import com.android.internal.telephony.dataconnection.LinkBandwidthEstimator;
 import com.android.internal.telephony.nano.TelephonyProto;
+import com.android.internal.telephony.nano.TelephonyProto.BandwidthEstimatorStats;
 import com.android.internal.telephony.nano.TelephonyProto.ImsConnectionState;
+import com.android.internal.telephony.nano.TelephonyProto.NrMode;
 import com.android.internal.telephony.nano.TelephonyProto.RadioAccessTechnology;
 import com.android.internal.telephony.nano.TelephonyProto.SmsSession;
 import com.android.internal.telephony.nano.TelephonyProto.TelephonyCallSession;
@@ -872,4 +876,39 @@
         assertEquals(false, event.imsCapabilities.videoOverLte);
         assertEquals(false, event.imsCapabilities.utOverLte);
     }
+
+
+    // Test write Bandwidth Stats
+    @Test
+    @SmallTest
+    public void testWriteBandwidthStats() throws Exception {
+        addBandwidthStats(LinkBandwidthEstimator.LINK_TX, TelephonyManager.NETWORK_TYPE_LTE,
+                NrMode.NR_NSA_MMWAVE);
+        addBandwidthStats(LinkBandwidthEstimator.LINK_RX, TelephonyManager.NETWORK_TYPE_LTE,
+                NrMode.NR_NSA_MMWAVE);
+        addBandwidthStats(LinkBandwidthEstimator.LINK_RX, TelephonyManager.NETWORK_TYPE_NR,
+                NrMode.NR_SA_MMWAVE);
+        TelephonyLog log = buildProto();
+
+        BandwidthEstimatorStats stats = log.bandwidthEstimatorStats;
+        assertEquals(1, stats.perRatTx.length);
+        assertEquals(2, stats.perRatRx.length);
+        assertEquals(TelephonyManager.NETWORK_TYPE_LTE, stats.perRatTx[0].rat);
+        assertEquals(NrMode.NR_NSA_MMWAVE, stats.perRatTx[0].nrMode);
+        assertEquals(NUM_SIGNAL_LEVEL - 1, stats.perRatTx[0].perLevel.length);
+        assertEquals(2, stats.perRatTx[0].perLevel[0].count);
+        assertEquals(0, stats.perRatTx[0].perLevel[0].signalLevel);
+        assertEquals(400_000, stats.perRatTx[0].perLevel[0].avgBwKbps);
+        assertEquals(40, stats.perRatTx[0].perLevel[0].staticBwErrorPercent);
+        assertEquals(30, stats.perRatTx[0].perLevel[0].bwEstErrorPercent);
+    }
+
+    private void addBandwidthStats(int link, int dataRat, int nrMode) {
+        for (int i = 0; i < NUM_SIGNAL_LEVEL - 1; i++) {
+            mMetrics.writeBandwidthStats(link, dataRat, nrMode,
+                    i, 20, 30, 300_000);
+            mMetrics.writeBandwidthStats(link, dataRat, nrMode,
+                    i, 40, 50, 500_000);
+        }
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/metrics/VoiceCallSessionStatsTest.java b/tests/telephonytests/src/com/android/internal/telephony/metrics/VoiceCallSessionStatsTest.java
index c5de239..988e252 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/metrics/VoiceCallSessionStatsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/VoiceCallSessionStatsTest.java
@@ -1311,9 +1311,9 @@
         expectedCall.ratSwitchCount = 1L;
         expectedCall.setupFailed = true;
         expectedCall.ratAtConnected = TelephonyManager.NETWORK_TYPE_UNKNOWN;
-        expectedCall.codecBitmask = 1L << AudioCodec.AUDIO_CODEC_AMR;
+        expectedCall.codecBitmask = 0L;
         expectedCall.mainCodecQuality =
-                VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_NARROWBAND;
+                VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_UNKNOWN;
         VoiceCallRatUsage expectedRatUsageLte =
                 makeRatUsageProto(
                         CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_LTE, 2000L, 3000L, 1L);
@@ -1331,8 +1331,6 @@
         mVoiceCallSessionStats0.setTimeMillis(3000L);
         setServiceState(mServiceState, TelephonyManager.NETWORK_TYPE_UMTS);
         mVoiceCallSessionStats0.onServiceStateChanged(mServiceState);
-        mVoiceCallSessionStats0.setTimeMillis(3100L);
-        mVoiceCallSessionStats0.onAudioCodecChanged(mGsmConnection0, DriverCall.AUDIO_QUALITY_AMR);
         mVoiceCallSessionStats0.setTimeMillis(15000L);
         doReturn(DisconnectCause.LOST_SIGNAL).when(mGsmConnection0).getDisconnectCause();
         mVoiceCallSessionStats0.onRilCallListChanged(List.of(mGsmConnection0));
@@ -1435,7 +1433,8 @@
         expectedCall.setupFailed = true;
         expectedCall.ratAtConnected = TelephonyManager.NETWORK_TYPE_UNKNOWN;
         expectedCall.bandAtEnd = 0;
-        expectedCall.codecBitmask = 1L << AudioCodec.AUDIO_CODEC_AMR;
+        expectedCall.codecBitmask =
+                (1L << AudioCodec.AUDIO_CODEC_AMR) | (1L << AudioCodec.AUDIO_CODEC_AMR_WB);
         expectedCall.mainCodecQuality =
                 VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_NARROWBAND;
         VoiceCallRatUsage expectedRatUsage =
@@ -1448,10 +1447,12 @@
         mVoiceCallSessionStats0.setTimeMillis(2500L);
         doReturn(Call.State.INCOMING).when(mCsCall0).getState();
         doReturn(Call.State.INCOMING).when(mGsmConnection0).getState();
+        doReturn(DriverCall.AUDIO_QUALITY_AMR_WB).when(mGsmConnection0).getAudioCodec();
         doReturn(DisconnectCause.NOT_DISCONNECTED).when(mGsmConnection0).getDisconnectCause();
         mVoiceCallSessionStats0.onRilCallListChanged(List.of(mGsmConnection0));
         mVoiceCallSessionStats0.setTimeMillis(3000L);
         mVoiceCallSessionStats0.onAudioCodecChanged(mGsmConnection0, DriverCall.AUDIO_QUALITY_AMR);
+        doReturn(DriverCall.AUDIO_QUALITY_AMR).when(mGsmConnection0).getAudioCodec();
         mVoiceCallSessionStats0.setTimeMillis(15000L);
         doReturn(DisconnectCause.NORMAL).when(mGsmConnection0).getDisconnectCause();
         doReturn(PreciseDisconnectCause.CALL_REJECTED)
@@ -1907,7 +1908,7 @@
     public void singleWifiCall_preferred() {
         setServiceStateWithWifiCalling(mServiceState, TelephonyManager.NETWORK_TYPE_LTE);
         doReturn(mImsPhone).when(mPhone).getImsPhone();
-        doReturn(true).when(mImsPhone).isWifiCallingEnabled();
+        doReturn(TelephonyManager.NETWORK_TYPE_IWLAN).when(mImsStats).getImsVoiceRadioTech();
         doReturn(true).when(mImsConnection0).isIncoming();
         doReturn(2000L).when(mImsConnection0).getCreateTime();
         doReturn(mImsCall0).when(mImsConnection0).getCall();
@@ -1955,7 +1956,7 @@
     public void singleWifiCall_airPlaneMode() {
         setServiceStateWithWifiCalling(mServiceState, TelephonyManager.NETWORK_TYPE_UNKNOWN);
         doReturn(mImsPhone).when(mPhone).getImsPhone();
-        doReturn(true).when(mImsPhone).isWifiCallingEnabled();
+        doReturn(TelephonyManager.NETWORK_TYPE_IWLAN).when(mImsStats).getImsVoiceRadioTech();
         doReturn(true).when(mImsConnection0).isIncoming();
         doReturn(2000L).when(mImsConnection0).getCreateTime();
         doReturn(mImsCall0).when(mImsConnection0).getCall();
diff --git a/tests/telephonytests/src/com/android/internal/telephony/nitz/NitzSignalInputFilterPredicateFactoryTest.java b/tests/telephonytests/src/com/android/internal/telephony/nitz/NitzSignalInputFilterPredicateFactoryTest.java
index 2339f08..1997114 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/nitz/NitzSignalInputFilterPredicateFactoryTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/nitz/NitzSignalInputFilterPredicateFactoryTest.java
@@ -19,16 +19,17 @@
 import static com.android.internal.telephony.nitz.NitzSignalInputFilterPredicateFactory.createBogusElapsedRealtimeCheck;
 import static com.android.internal.telephony.nitz.NitzSignalInputFilterPredicateFactory.createIgnoreNitzPropertyCheck;
 import static com.android.internal.telephony.nitz.NitzSignalInputFilterPredicateFactory.createRateLimitCheck;
+import static com.android.internal.telephony.nitz.NitzStateMachineTestSupport.ARBITRARY_AGE;
 import static com.android.internal.telephony.nitz.NitzStateMachineTestSupport.UNIQUE_US_ZONE_SCENARIO1;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
-import android.os.TimestampedValue;
-
 import com.android.internal.telephony.NitzData;
+import com.android.internal.telephony.NitzSignal;
 import com.android.internal.telephony.TelephonyTest;
 import com.android.internal.telephony.nitz.NitzSignalInputFilterPredicateFactory.NitzSignalInputFilterPredicateImpl;
 import com.android.internal.telephony.nitz.NitzSignalInputFilterPredicateFactory.TrivalentPredicate;
@@ -57,8 +58,8 @@
     @Test
     public void testNitzSignalInputFilterPredicateImpl_nullSecondArgumentRejected() {
         Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
-        TimestampedValue<NitzData> nitzSignal =
-                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+        NitzSignal nitzSignal =
+                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
         TrivalentPredicate[] triPredicates = {};
         NitzSignalInputFilterPredicateImpl impl =
                 new NitzSignalInputFilterPredicateImpl(triPredicates);
@@ -72,8 +73,8 @@
     @Test
     public void testNitzSignalInputFilterPredicateImpl_defaultIsTrue() {
         Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
-        TimestampedValue<NitzData> nitzSignal = scenario
-                .createNitzSignal(mFakeDeviceState.elapsedRealtime());
+        NitzSignal nitzSignal =
+                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
         NitzSignalInputFilterPredicateImpl impl =
                 new NitzSignalInputFilterPredicateImpl(new TrivalentPredicate[0]);
         assertTrue(impl.mustProcessNitzSignal(null, nitzSignal));
@@ -82,8 +83,8 @@
     @Test
     public void testNitzSignalInputFilterPredicateImpl_nullIsIgnored() {
         Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
-        TimestampedValue<NitzData> nitzSignal =
-                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+        NitzSignal nitzSignal =
+                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
         TrivalentPredicate nullPredicate = (x, y) -> null;
         TrivalentPredicate[] triPredicates = { nullPredicate };
         NitzSignalInputFilterPredicateImpl impl =
@@ -94,8 +95,8 @@
     @Test
     public void testNitzSignalInputFilterPredicateImpl_trueIsHonored() {
         Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
-        TimestampedValue<NitzData> nitzSignal =
-                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+        NitzSignal nitzSignal =
+                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
         TrivalentPredicate nullPredicate = (x, y) -> null;
         TrivalentPredicate truePredicate = (x, y) -> true;
         TrivalentPredicate exceptionPredicate = (x, y) -> {
@@ -114,8 +115,8 @@
     @Test
     public void testNitzSignalInputFilterPredicateImpl_falseIsHonored() {
         Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
-        TimestampedValue<NitzData> nitzSignal =
-                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+        NitzSignal nitzSignal =
+                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
         TrivalentPredicate nullPredicate = (x, y) -> null;
         TrivalentPredicate falsePredicate = (x, y) -> false;
         TrivalentPredicate exceptionPredicate = (x, y) -> {
@@ -145,24 +146,42 @@
     @Test
     public void testTrivalentPredicate_bogusElapsedRealtimeCheck() {
         Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
-        long elapsedRealtimeClock = mFakeDeviceState.elapsedRealtime();
-        TimestampedValue<NitzData> nitzSignal = scenario.createNitzSignal(elapsedRealtimeClock);
+        long elapsedRealtimeMillis = mFakeDeviceState.elapsedRealtimeMillis();
+        NitzSignal baseNitzSignal =
+                scenario.createNitzSignal(elapsedRealtimeMillis, ARBITRARY_AGE);
 
         TrivalentPredicate triPredicate =
                 createBogusElapsedRealtimeCheck(mContext, mFakeDeviceState);
-        assertNull(triPredicate.mustProcessNitzSignal(null, nitzSignal));
+        assertNull(triPredicate.mustProcessNitzSignal(null, baseNitzSignal));
 
         // Any signal that claims to be from the future must be rejected.
-        TimestampedValue<NitzData> bogusNitzSignal = new TimestampedValue<>(
-                elapsedRealtimeClock + 1, nitzSignal.getValue());
-        assertFalse(triPredicate.mustProcessNitzSignal(null, bogusNitzSignal));
+        {
+            long receiptElapsedMillis = elapsedRealtimeMillis + 1;
+            long ageMillis = 0;
+            NitzSignal bogusNitzSignal = new NitzSignal(
+                    receiptElapsedMillis, baseNitzSignal.getNitzData(), ageMillis);
+            assertFalse(triPredicate.mustProcessNitzSignal(null, bogusNitzSignal));
+        }
+
+        // Age should be ignored: the predicate is intended to check receipt time isn't obviously
+        // corrupt / fabricated to be in the future. Larger ages could imply that the NITZ was
+        // received by the modem before the elapsed realtime clock started ticking, but we don't
+        // currently check for that.
+        {
+            long receiptElapsedMillis = elapsedRealtimeMillis + 1;
+            long ageMillis = 10000;
+            NitzSignal bogusNitzSignal = new NitzSignal(
+                    receiptElapsedMillis, baseNitzSignal.getNitzData(), ageMillis);
+
+            assertFalse(triPredicate.mustProcessNitzSignal(null, bogusNitzSignal));
+        }
     }
 
     @Test
     public void testTrivalentPredicate_noOldSignalCheck() {
         Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
-        TimestampedValue<NitzData> nitzSignal =
-                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+        NitzSignal nitzSignal =
+                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
 
         TrivalentPredicate triPredicate =
                 NitzSignalInputFilterPredicateFactory.createNoOldSignalCheck();
@@ -171,36 +190,108 @@
     }
 
     @Test
-    public void testTrivalentPredicate_rateLimitCheck_elapsedRealtime() {
+    public void testTrivalentPredicate_rateLimitCheck_elapsedRealtime_zeroAge() {
         Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
         int nitzSpacingThreshold = mFakeDeviceState.getNitzUpdateSpacingMillis();
-        NitzData baseNitzData = scenario.createNitzData();
+        // Change the other setting that can affect the predicate behavior so it is not a factor in
+        // the test.
+        mFakeDeviceState.setNitzUpdateDiffMillis(Integer.MAX_VALUE);
 
         TrivalentPredicate triPredicate = createRateLimitCheck(mFakeDeviceState);
 
-        long baseElapsedRealtimeMillis = mFakeDeviceState.elapsedRealtime();
-        TimestampedValue<NitzData> baseSignal =
-                new TimestampedValue<>(baseElapsedRealtimeMillis, baseNitzData);
+        long baseElapsedRealtimeMillis = mFakeDeviceState.elapsedRealtimeMillis();
+        NitzData baseNitzData = scenario.createNitzData();
+        int baseAgeMillis = 0;
+        NitzSignal baseNitzSignal =
+                new NitzSignal(baseElapsedRealtimeMillis, baseNitzData, baseAgeMillis);
 
         // Two identical signals: no spacing so the new signal should not be processed.
-        {
-            assertFalse(triPredicate.mustProcessNitzSignal(baseSignal, baseSignal));
-        }
+        assertFalse(triPredicate.mustProcessNitzSignal(baseNitzSignal, baseNitzSignal));
 
-        // Two signals not spaced apart enough: the new signal should not processed.
+        // Two signals not spaced apart enough in receipt time: the new signal should not be
+        // processed.
         {
-            int elapsedTimeIncrement = nitzSpacingThreshold - 1;
-            TimestampedValue<NitzData> newSignal =
-                    createIncrementedNitzSignal(baseSignal, elapsedTimeIncrement);
-            assertFalse(triPredicate.mustProcessNitzSignal(baseSignal, newSignal));
+            int timeAdjustment = nitzSpacingThreshold - 1;
+            int utcAdjustment = 0;
+            long ageAdjustment = 0;
+            NitzSignal newSignal = createAdjustedNitzSignal(
+                    baseNitzSignal, timeAdjustment, utcAdjustment, ageAdjustment);
+            assertFalse(triPredicate.mustProcessNitzSignal(baseNitzSignal, newSignal));
         }
 
         // Two signals spaced apart: the new signal should be processed.
         {
-            int elapsedTimeIncrement = nitzSpacingThreshold + 1;
-            TimestampedValue<NitzData> newSignal =
-                    createIncrementedNitzSignal(baseSignal, elapsedTimeIncrement);
-            assertTrue(triPredicate.mustProcessNitzSignal(baseSignal, newSignal));
+            int timeAdjustment = nitzSpacingThreshold + 1;
+            int utcAdjustment = 0;
+            long ageAdjustment = 0;
+            NitzSignal newSignal = createAdjustedNitzSignal(
+                    baseNitzSignal, timeAdjustment, utcAdjustment, ageAdjustment);
+            assertTrue(triPredicate.mustProcessNitzSignal(baseNitzSignal, newSignal));
+        }
+    }
+
+    @Test
+    public void testTrivalentPredicate_rateLimitCheck_elapsedRealtime_withAge() {
+        Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
+        int nitzSpacingThreshold = 60000;
+        mFakeDeviceState.setNitzUpdateSpacingMillis(nitzSpacingThreshold);
+
+        // Change the other setting that can affect the predicate behavior so it is not a factor in
+        // the test.
+        mFakeDeviceState.setNitzUpdateDiffMillis(Integer.MAX_VALUE);
+
+        TrivalentPredicate triPredicate = createRateLimitCheck(mFakeDeviceState);
+
+        // Create a NITZ signal to be the first of two NITZ signals received.
+        long baseElapsedRealtimeMillis = mFakeDeviceState.elapsedRealtimeMillis();
+        NitzData baseNitzData = scenario.createNitzData();
+        int baseAgeMillis = 20000;
+        NitzSignal baseNitzSignal =
+                new NitzSignal(baseElapsedRealtimeMillis, baseNitzData, baseAgeMillis);
+
+        // Two identical signals: no spacing so the new signal should not be processed.
+        assertFalse(triPredicate.mustProcessNitzSignal(baseNitzSignal, baseNitzSignal));
+
+        // Two signals not spaced apart enough: the new signal should not be processed.
+        // The age is changed to prove it doesn't affect this check.
+        {
+            int elapsedRealtimeAdjustment = nitzSpacingThreshold - 1;
+            int utcAdjustment = 0;
+            long ageAdjustment = 10000;
+            NitzSignal newSignal = createAdjustedNitzSignal(
+                    baseNitzSignal, elapsedRealtimeAdjustment, utcAdjustment, ageAdjustment);
+            assertFalse(triPredicate.mustProcessNitzSignal(baseNitzSignal, newSignal));
+        }
+
+        // Two signals not spaced apart enough: the new signal should not be processed.
+        // The age is changed to prove it doesn't affect this check.
+        {
+            int elapsedRealtimeAdjustment = nitzSpacingThreshold - 1;
+            int utcAdjustment = 0;
+            long ageAdjustment = -10000;
+            NitzSignal newSignal = createAdjustedNitzSignal(
+                    baseNitzSignal, elapsedRealtimeAdjustment, utcAdjustment, ageAdjustment);
+            assertFalse(triPredicate.mustProcessNitzSignal(baseNitzSignal, newSignal));
+        }
+
+        // Two signals spaced far enough apart: the new signal should be processed.
+        {
+            int elapsedRealtimeAdjustment = nitzSpacingThreshold + 1;
+            int utcAdjustment = 0;
+            long ageAdjustment = 10000;
+            NitzSignal newSignal = createAdjustedNitzSignal(
+                    baseNitzSignal, elapsedRealtimeAdjustment, utcAdjustment, ageAdjustment);
+            assertTrue(triPredicate.mustProcessNitzSignal(baseNitzSignal, newSignal));
+        }
+
+        // Two signals spaced far enough apart: the new signal should be processed.
+        {
+            int elapsedRealtimeAdjustment = nitzSpacingThreshold + 1;
+            int utcAdjustment = 0;
+            long ageAdjustment = -10000;
+            NitzSignal newSignal = createAdjustedNitzSignal(
+                    baseNitzSignal, elapsedRealtimeAdjustment, utcAdjustment, ageAdjustment);
+            assertTrue(triPredicate.mustProcessNitzSignal(baseNitzSignal, newSignal));
         }
     }
 
@@ -208,20 +299,23 @@
     public void testTrivalentPredicate_rateLimitCheck_offsetDifference() {
         Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
         int nitzSpacingThreshold = mFakeDeviceState.getNitzUpdateSpacingMillis();
-        NitzData baseNitzData = scenario.createNitzData();
 
         TrivalentPredicate triPredicate = createRateLimitCheck(mFakeDeviceState);
 
-        long baseElapsedRealtimeMillis = mFakeDeviceState.elapsedRealtime();
-        TimestampedValue<NitzData> baseSignal =
-                new TimestampedValue<>(baseElapsedRealtimeMillis, baseNitzData);
+        long baseElapsedRealtimeMillis = mFakeDeviceState.elapsedRealtimeMillis();
+        NitzData baseNitzData = scenario.createNitzData();
+        long baseAgeMillis = 0;
+        NitzSignal baseNitzSignal = new NitzSignal(
+                baseElapsedRealtimeMillis, baseNitzData, baseAgeMillis);
 
-        // Create a new NitzSignal that should be filtered.
-        int elapsedTimeIncrement = nitzSpacingThreshold - 1;
-        TimestampedValue<NitzData> intermediateNitzSignal =
-                createIncrementedNitzSignal(baseSignal, elapsedTimeIncrement);
-        NitzData intermediateNitzData = intermediateNitzSignal.getValue();
-        assertFalse(triPredicate.mustProcessNitzSignal(baseSignal, intermediateNitzSignal));
+        // Create a new NitzSignal that would normally be filtered.
+        int timeAdjustment = nitzSpacingThreshold - 1;
+        long ageAdjustment = 0;
+        NitzSignal intermediateNitzSignal = createAdjustedNitzSignal(
+                baseNitzSignal, timeAdjustment, timeAdjustment, ageAdjustment);
+        NitzData intermediateNitzData = intermediateNitzSignal.getNitzData();
+        assertAgeAdjustedUtcTimeIsIdentical(baseNitzSignal, intermediateNitzSignal);
+        assertFalse(triPredicate.mustProcessNitzSignal(baseNitzSignal, intermediateNitzSignal));
 
         // Two signals spaced apart so that the second would be filtered, but they contain different
         // offset information so should be detected as "different" and processed.
@@ -232,102 +326,181 @@
                     intermediateNitzData.getDstAdjustmentMillis(),
                     intermediateNitzData.getCurrentTimeInMillis(),
                     intermediateNitzData.getEmulatorHostTimeZone());
-            TimestampedValue<NitzData> differentOffsetSignal = new TimestampedValue<>(
-                    baseSignal.getReferenceTimeMillis() + elapsedTimeIncrement,
-                    differentOffsetNitzData);
-            assertTrue(triPredicate.mustProcessNitzSignal(baseSignal, differentOffsetSignal));
+            NitzSignal differentOffsetSignal = new NitzSignal(
+                    baseNitzSignal.getReceiptElapsedRealtimeMillis() + timeAdjustment,
+                    differentOffsetNitzData,
+                    baseNitzSignal.getAgeMillis());
+            assertAgeAdjustedUtcTimeIsIdentical(baseNitzSignal, differentOffsetSignal);
+            assertTrue(triPredicate.mustProcessNitzSignal(baseNitzSignal, differentOffsetSignal));
         }
     }
 
     @Test
-    public void testTrivalentPredicate_rateLimitCheck_utcTimeDifferences() {
+    public void testTrivalentPredicate_rateLimitCheck_utcTimeDifferences_withZeroAge() {
         Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
-        int nitzSpacingThreshold = mFakeDeviceState.getNitzUpdateSpacingMillis();
+        // Change the other setting that can affect the predicate behavior so it is not a factor in
+        // the test.
+        mFakeDeviceState.setNitzUpdateSpacingMillis(Integer.MAX_VALUE);
         int nitzUtcDiffThreshold = mFakeDeviceState.getNitzUpdateDiffMillis();
-        NitzData baseNitzData = scenario.createNitzData();
 
         TrivalentPredicate triPredicate = createRateLimitCheck(mFakeDeviceState);
 
-        long baseElapsedRealtimeMillis = mFakeDeviceState.elapsedRealtime();
-        TimestampedValue<NitzData> baseSignal =
-                new TimestampedValue<>(baseElapsedRealtimeMillis, baseNitzData);
+        long baseElapsedRealtimeMillis = mFakeDeviceState.elapsedRealtimeMillis();
+        NitzData baseNitzData = scenario.createNitzData();
+        int baseAgeMillis = 0;
+        NitzSignal baseNitzSignal =
+                new NitzSignal(baseElapsedRealtimeMillis, baseNitzData, baseAgeMillis);
 
-        // Create a new NitzSignal that should be filtered.
-        int elapsedTimeIncrement = nitzSpacingThreshold - 1;
-        TimestampedValue<NitzData> intermediateSignal =
-                createIncrementedNitzSignal(baseSignal, elapsedTimeIncrement);
-        NitzData intermediateNitzData = intermediateSignal.getValue();
-        assertFalse(triPredicate.mustProcessNitzSignal(baseSignal, intermediateSignal));
-
-        // Two signals spaced apart so that the second would normally be filtered and it contains
-        // a UTC time that is not sufficiently different.
+        // Two signals spaced contain UTC times that are not sufficiently different and so should be
+        // filtered.
         {
-            NitzData incrementedUtcTimeNitzData = NitzData.createForTests(
-                    intermediateNitzData.getLocalOffsetMillis(),
-                    intermediateNitzData.getDstAdjustmentMillis(),
-                    intermediateNitzData.getCurrentTimeInMillis() + nitzUtcDiffThreshold - 1,
-                    intermediateNitzData.getEmulatorHostTimeZone());
-
-            TimestampedValue<NitzData> incrementedNitzSignal = new TimestampedValue<>(
-                    intermediateSignal.getReferenceTimeMillis(), incrementedUtcTimeNitzData);
-            assertFalse(triPredicate.mustProcessNitzSignal(baseSignal, incrementedNitzSignal));
+            int elapsedRealtimeAdjustment = 0;
+            int utcAdjustment = nitzUtcDiffThreshold - 1;
+            long ageAdjustment = 0;
+            NitzSignal nitzSignal = createAdjustedNitzSignal(baseNitzSignal,
+                    elapsedRealtimeAdjustment, utcAdjustment, ageAdjustment);
+            assertFalse(triPredicate.mustProcessNitzSignal(baseNitzSignal, nitzSignal));
         }
 
-        // Two signals spaced apart so that the second would normally be filtered but it contains
-        // a UTC time that is sufficiently different.
+        // Two signals spaced contain UTC times that are not sufficiently different and so should be
+        // filtered.
         {
-            NitzData incrementedUtcTimeNitzData = NitzData.createForTests(
-                    intermediateNitzData.getLocalOffsetMillis(),
-                    intermediateNitzData.getDstAdjustmentMillis(),
-                    intermediateNitzData.getCurrentTimeInMillis() + nitzUtcDiffThreshold + 1,
-                    intermediateNitzData.getEmulatorHostTimeZone());
-
-            TimestampedValue<NitzData> incrementedNitzSignal = new TimestampedValue<>(
-                    intermediateSignal.getReferenceTimeMillis(), incrementedUtcTimeNitzData);
-            assertTrue(triPredicate.mustProcessNitzSignal(baseSignal, incrementedNitzSignal));
+            int elapsedRealtimeAdjustment = 0;
+            int utcAdjustment = -(nitzUtcDiffThreshold - 1);
+            long ageAdjustment = 0;
+            NitzSignal nitzSignal = createAdjustedNitzSignal(baseNitzSignal,
+                    elapsedRealtimeAdjustment, utcAdjustment, ageAdjustment);
+            assertFalse(triPredicate.mustProcessNitzSignal(baseNitzSignal, nitzSignal));
         }
 
-        // Two signals spaced apart so that the second would normally be filtered and it contains
-        // a UTC time that is not sufficiently different.
+        // Two signals spaced contain UTC times that are sufficiently different and so should not be
+        // filtered.
         {
-            NitzData decrementedUtcTimeNitzData = NitzData.createForTests(
-                    intermediateNitzData.getLocalOffsetMillis(),
-                    intermediateNitzData.getDstAdjustmentMillis(),
-                    intermediateNitzData.getCurrentTimeInMillis() - nitzUtcDiffThreshold + 1,
-                    intermediateNitzData.getEmulatorHostTimeZone());
-
-            TimestampedValue<NitzData> decrementedNitzSignal = new TimestampedValue<>(
-                    intermediateSignal.getReferenceTimeMillis(), decrementedUtcTimeNitzData);
-            assertFalse(triPredicate.mustProcessNitzSignal(baseSignal, decrementedNitzSignal));
+            int elapsedRealtimeAdjustment = 0;
+            int utcAdjustment = nitzUtcDiffThreshold + 1;
+            long ageAdjustment = 0;
+            NitzSignal nitzSignal = createAdjustedNitzSignal(baseNitzSignal,
+                    elapsedRealtimeAdjustment, utcAdjustment, ageAdjustment);
+            assertTrue(triPredicate.mustProcessNitzSignal(baseNitzSignal, nitzSignal));
         }
 
-        // Two signals spaced apart so that the second would normally be filtered but it contains
-        // a UTC time that is sufficiently different.
+        // Two signals spaced contain UTC times that are sufficiently different and so should not be
+        // filtered.
         {
-            NitzData decrementedUtcTimeNitzData = NitzData.createForTests(
-                    intermediateNitzData.getLocalOffsetMillis(),
-                    intermediateNitzData.getDstAdjustmentMillis(),
-                    intermediateNitzData.getCurrentTimeInMillis() + nitzUtcDiffThreshold + 1,
-                    intermediateNitzData.getEmulatorHostTimeZone());
+            int elapsedRealtimeAdjustment = 0;
+            int utcAdjustment = -(nitzUtcDiffThreshold + 1);
+            long ageAdjustment = 0;
+            NitzSignal nitzSignal = createAdjustedNitzSignal(baseNitzSignal,
+                    elapsedRealtimeAdjustment, utcAdjustment, ageAdjustment);
+            assertTrue(triPredicate.mustProcessNitzSignal(baseNitzSignal, nitzSignal));
+        }
+    }
 
-            TimestampedValue<NitzData> decrementedNitzSignal = new TimestampedValue<>(
-                    intermediateSignal.getReferenceTimeMillis(), decrementedUtcTimeNitzData);
-            assertTrue(triPredicate.mustProcessNitzSignal(baseSignal, decrementedNitzSignal));
+    @Test
+    public void testTrivalentPredicate_rateLimitCheck_utcTimeDifferences_withAge() {
+        Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
+        // Change the other setting that can affect the predicate behavior so it is not a factor in
+        // the test.
+        mFakeDeviceState.setNitzUpdateSpacingMillis(Integer.MAX_VALUE);
+        int nitzUtcDiffThreshold = mFakeDeviceState.getNitzUpdateDiffMillis();
+
+        TrivalentPredicate triPredicate = createRateLimitCheck(mFakeDeviceState);
+
+        long baseElapsedRealtimeMillis = mFakeDeviceState.elapsedRealtimeMillis();
+        NitzData baseNitzData = scenario.createNitzData();
+        int baseAgeMillis = 20000;
+        NitzSignal baseNitzSignal =
+                new NitzSignal(baseElapsedRealtimeMillis, baseNitzData, baseAgeMillis);
+
+        // This is another NitzSignal that represents the same time as baseNitzSignal, but it has
+        // been cached by the modem for a different amount of time, so has different values even
+        // though it encodes for the same UTC time. Used to construct test signals below.
+        int intermediateSignalAgeAdjustment = -10000;
+        int intermediateUtcAdjustment = 0;
+        NitzSignal intermediateNitzSignal = createAdjustedNitzSignal(baseNitzSignal,
+                intermediateSignalAgeAdjustment, intermediateUtcAdjustment,
+                intermediateSignalAgeAdjustment);
+        assertAgeAdjustedUtcTimeIsIdentical(baseNitzSignal, intermediateNitzSignal);
+
+        // Two signals spaced contain UTC times that are not sufficiently different and so should be
+        // filtered.
+        {
+            int elapsedRealtimeAdjustment = 0;
+            int utcAdjustment = nitzUtcDiffThreshold - 1;
+            long ageAdjustment = 0;
+            NitzSignal nitzSignal = createAdjustedNitzSignal(intermediateNitzSignal,
+                    elapsedRealtimeAdjustment, utcAdjustment, ageAdjustment);
+            assertFalse(triPredicate.mustProcessNitzSignal(baseNitzSignal, nitzSignal));
+        }
+
+        // Two signals spaced contain UTC times that are not sufficiently different and so should be
+        // filtered.
+        {
+            int elapsedRealtimeAdjustment = 0;
+            int utcAdjustment = -(nitzUtcDiffThreshold - 1);
+            long ageAdjustment = 0;
+            NitzSignal nitzSignal = createAdjustedNitzSignal(intermediateNitzSignal,
+                    elapsedRealtimeAdjustment, utcAdjustment, ageAdjustment);
+            assertFalse(triPredicate.mustProcessNitzSignal(baseNitzSignal, nitzSignal));
+        }
+
+        // Two signals spaced contain UTC times that are sufficiently different and so should not be
+        // filtered.
+        {
+            int elapsedRealtimeAdjustment = 0;
+            int utcAdjustment = nitzUtcDiffThreshold + 1;
+            long ageAdjustment = 0;
+            NitzSignal nitzSignal = createAdjustedNitzSignal(intermediateNitzSignal,
+                    elapsedRealtimeAdjustment, utcAdjustment, ageAdjustment);
+            assertTrue(triPredicate.mustProcessNitzSignal(baseNitzSignal, nitzSignal));
+        }
+
+        // Two signals spaced contain UTC times that are sufficiently different and so should not be
+        // filtered.
+        {
+            int elapsedRealtimeAdjustment = 0;
+            int utcAdjustment = -(nitzUtcDiffThreshold + 1);
+            long ageAdjustment = 0;
+            NitzSignal nitzSignal = createAdjustedNitzSignal(intermediateNitzSignal,
+                    elapsedRealtimeAdjustment, utcAdjustment, ageAdjustment);
+            assertTrue(triPredicate.mustProcessNitzSignal(baseNitzSignal, nitzSignal));
         }
     }
 
     /**
-     * Creates an NITZ signal based on the the supplied signal but with all the fields related to
-     * elapsed time incremented by the specified number of milliseconds.
+     * Creates an NITZ signal based on the supplied signal but with all the fields associated with
+     * the time (receipt time, UTC and age) adjusted by the specified amounts.
      */
-    private static TimestampedValue<NitzData> createIncrementedNitzSignal(
-            TimestampedValue<NitzData> baseSignal, int incrementMillis) {
-        NitzData baseData = baseSignal.getValue();
-        return new TimestampedValue<>(baseSignal.getReferenceTimeMillis() + incrementMillis,
-                NitzData.createForTests(
-                        baseData.getLocalOffsetMillis(),
-                        baseData.getDstAdjustmentMillis(),
-                        baseData.getCurrentTimeInMillis() + incrementMillis,
-                        baseData.getEmulatorHostTimeZone()));
+    private static NitzSignal createAdjustedNitzSignal(
+            NitzSignal baseNitzSignal, int elapsedRealtimeMillisAdjustment, int utcMillisAdjustment,
+            long ageMillisAdjustment) {
+        long adjustedReceiptElapsedMillis =
+                baseNitzSignal.getReceiptElapsedRealtimeMillis() + elapsedRealtimeMillisAdjustment;
+        NitzData adjustedNitzData =
+                createAdjustedNitzData(baseNitzSignal.getNitzData(), utcMillisAdjustment);
+        long adjustedAgeMillis = baseNitzSignal.getAgeMillis() + ageMillisAdjustment;
+        return new NitzSignal(adjustedReceiptElapsedMillis, adjustedNitzData, adjustedAgeMillis);
+    }
+
+    /** Creates a new NitzData by adjusting the UTC time in the supplied NitzData */
+    private static NitzData createAdjustedNitzData(NitzData baseData, int utcMillisAdjustment) {
+        return NitzData.createForTests(
+                baseData.getLocalOffsetMillis(),
+                baseData.getDstAdjustmentMillis(),
+                baseData.getCurrentTimeInMillis() + utcMillisAdjustment,
+                baseData.getEmulatorHostTimeZone());
+    }
+
+    /**
+     * Used during tests to confirm that two NitzSignal test objects represent the same UTC time,
+     * even though their receipt times and ages may differ.
+     */
+    private static void assertAgeAdjustedUtcTimeIsIdentical(
+            NitzSignal signal1, NitzSignal signal2) {
+        long referenceTimeDifference = signal2.getAgeAdjustedElapsedRealtimeMillis()
+                - signal1.getAgeAdjustedElapsedRealtimeMillis();
+        long utcTimeDifference = signal2.getNitzData().getCurrentTimeInMillis()
+                - signal1.getNitzData().getCurrentTimeInMillis();
+        assertEquals(referenceTimeDifference, utcTimeDifference);
     }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/nitz/NitzStateMachineImplTest.java b/tests/telephonytests/src/com/android/internal/telephony/nitz/NitzStateMachineImplTest.java
index c433bd0..3f2e4d5 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/nitz/NitzStateMachineImplTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/nitz/NitzStateMachineImplTest.java
@@ -19,6 +19,7 @@
 import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY;
 import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET;
 
+import static com.android.internal.telephony.nitz.NitzStateMachineTestSupport.ARBITRARY_AGE;
 import static com.android.internal.telephony.nitz.NitzStateMachineTestSupport.ARBITRARY_SYSTEM_CLOCK_TIME;
 import static com.android.internal.telephony.nitz.NitzStateMachineTestSupport.UNIQUE_US_ZONE_SCENARIO1;
 import static com.android.internal.telephony.nitz.NitzStateMachineTestSupport.UNITED_KINGDOM_SCENARIO;
@@ -35,10 +36,9 @@
 
 import android.app.timedetector.TelephonyTimeSuggestion;
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
-import android.os.TimestampedValue;
 
 import com.android.internal.telephony.IndentingPrintWriter;
-import com.android.internal.telephony.NitzData;
+import com.android.internal.telephony.NitzSignal;
 import com.android.internal.telephony.TelephonyTest;
 import com.android.internal.telephony.nitz.NitzStateMachineImpl.NitzSignalInputFilterPredicate;
 import com.android.internal.telephony.nitz.NitzStateMachineTestSupport.FakeDeviceState;
@@ -105,8 +105,8 @@
     public void test_countryThenNitz() throws Exception {
         Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
         String networkCountryIsoCode = scenario.getNetworkCountryIsoCode();
-        TimestampedValue<NitzData> nitzSignal =
-                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+        NitzSignal nitzSignal =
+                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
 
         // Capture expected results from the real suggester and confirm we can tell the difference
         // between them.
@@ -140,14 +140,14 @@
                 expectedTimeSuggestion, expectedTimeZoneSuggestion2);
 
         // Check NitzStateMachine exposed state.
-        assertEquals(nitzSignal.getValue(), mNitzStateMachineImpl.getCachedNitzData());
+        assertEquals(nitzSignal.getNitzData(), mNitzStateMachineImpl.getCachedNitzData());
     }
 
     @Test
     public void test_nitzThenCountry() throws Exception {
         Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
-        TimestampedValue<NitzData> nitzSignal =
-                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+        NitzSignal nitzSignal =
+                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
 
         String networkCountryIsoCode = scenario.getNetworkCountryIsoCode();
 
@@ -175,21 +175,21 @@
                 expectedTimeSuggestion, expectedTimeZoneSuggestion1);
 
         // Check NitzStateMachine exposed state.
-        assertEquals(nitzSignal.getValue(), mNitzStateMachineImpl.getCachedNitzData());
+        assertEquals(nitzSignal.getNitzData(), mNitzStateMachineImpl.getCachedNitzData());
 
         // Simulate country being known and verify the behavior.
         script.countryReceived(networkCountryIsoCode)
                 .verifyOnlyTimeZoneWasSuggestedAndReset(expectedTimeZoneSuggestion2);
 
         // Check NitzStateMachine exposed state.
-        assertEquals(nitzSignal.getValue(), mNitzStateMachineImpl.getCachedNitzData());
+        assertEquals(nitzSignal.getNitzData(), mNitzStateMachineImpl.getCachedNitzData());
     }
 
     @Test
     public void test_emptyCountryString_countryReceivedFirst() throws Exception {
         Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
-        TimestampedValue<NitzData> nitzSignal =
-                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+        NitzSignal nitzSignal =
+                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
 
         Script script = new Script()
                 .initializeSystemClock(ARBITRARY_SYSTEM_CLOCK_TIME)
@@ -224,14 +224,14 @@
                 expectedTimeSuggestion, expectedTimeZoneSuggestion);
 
         // Check NitzStateMachine exposed state.
-        assertEquals(nitzSignal.getValue(), mNitzStateMachineImpl.getCachedNitzData());
+        assertEquals(nitzSignal.getNitzData(), mNitzStateMachineImpl.getCachedNitzData());
     }
 
     @Test
     public void test_emptyCountryStringUsTime_nitzReceivedFirst() throws Exception {
         Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
-        TimestampedValue<NitzData> nitzSignal =
-                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+        NitzSignal nitzSignal =
+                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
 
         Script script = new Script()
                 .initializeSystemClock(ARBITRARY_SYSTEM_CLOCK_TIME)
@@ -248,7 +248,7 @@
                 expectedTimeSuggestion, EMPTY_TIME_ZONE_SUGGESTION);
 
         // Check NitzStateMachine exposed state.
-        assertEquals(nitzSignal.getValue(), mNitzStateMachineImpl.getCachedNitzData());
+        assertEquals(nitzSignal.getNitzData(), mNitzStateMachineImpl.getCachedNitzData());
 
         // Simulate an empty country being set.
         script.countryReceived("");
@@ -267,7 +267,7 @@
         script.verifyOnlyTimeZoneWasSuggestedAndReset(expectedTimeZoneSuggestion);
 
         // Check NitzStateMachine exposed state.
-        assertEquals(nitzSignal.getValue(), mNitzStateMachineImpl.getCachedNitzData());
+        assertEquals(nitzSignal.getNitzData(), mNitzStateMachineImpl.getCachedNitzData());
     }
 
     @Test
@@ -281,8 +281,8 @@
 
         // Pre-flight: Simulate a device receiving signals that allow it to detect time and time
         // zone.
-        TimestampedValue<NitzData> preFlightNitzSignal =
-                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+        NitzSignal preFlightNitzSignal =
+                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
         TelephonyTimeSuggestion expectedPreFlightTimeSuggestion =
                 createTimeSuggestionFromNitzSignal(SLOT_INDEX, preFlightNitzSignal);
         String preFlightCountryIsoCode = scenario.getNetworkCountryIsoCode();
@@ -299,7 +299,7 @@
                 expectedPreFlightTimeSuggestion, expectedPreFlightTimeZoneSuggestion);
 
         // Check state that NitzStateMachine must expose.
-        assertEquals(preFlightNitzSignal.getValue(), mNitzStateMachineImpl.getCachedNitzData());
+        assertEquals(preFlightNitzSignal.getNitzData(), mNitzStateMachineImpl.getCachedNitzData());
 
         // Boarded flight: Airplane mode turned on / time zone detection still enabled.
         // The NitzStateMachine must lose all state and stop having an opinion about time zone.
@@ -347,8 +347,8 @@
 
         // Simulate the device receiving NITZ signal and country again after the flight. Now the
         // NitzStateMachine should be opinionated again.
-        TimestampedValue<NitzData> postFlightNitzSignal =
-                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+        NitzSignal postFlightNitzSignal =
+                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
         String postFlightCountryCode = scenario.getNetworkCountryIsoCode();
         script.countryReceived(postFlightCountryCode)
                 .nitzReceived(postFlightNitzSignal);
@@ -363,7 +363,7 @@
                 expectedPostFlightTimeSuggestion, expectedPostFlightTimeZoneSuggestion);
 
         // Check state that NitzStateMachine must expose.
-        assertEquals(postFlightNitzSignal.getValue(), mNitzStateMachineImpl.getCachedNitzData());
+        assertEquals(postFlightNitzSignal.getNitzData(), mNitzStateMachineImpl.getCachedNitzData());
     }
 
     /**
@@ -380,8 +380,8 @@
                 .networkAvailable();
 
         // Simulate a device receiving signals that allow it to detect time and time zone.
-        TimestampedValue<NitzData> initialNitzSignal =
-                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+        NitzSignal initialNitzSignal =
+                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
         TelephonyTimeSuggestion expectedInitialTimeSuggestion =
                 createTimeSuggestionFromNitzSignal(SLOT_INDEX, initialNitzSignal);
 
@@ -397,7 +397,7 @@
                 expectedInitialTimeSuggestion, expectedInitialTimeZoneSuggestion);
 
         // Check state that NitzStateMachine must expose.
-        assertEquals(initialNitzSignal.getValue(), mNitzStateMachineImpl.getCachedNitzData());
+        assertEquals(initialNitzSignal.getNitzData(), mNitzStateMachineImpl.getCachedNitzData());
 
         // Simulate the passage of time and update the device realtime clock.
         scenario.incrementTime(timeStepMillis);
@@ -433,8 +433,8 @@
 
         // Simulate the device receiving NITZ signal again. Now the NitzStateMachine should be
         // opinionated again.
-        TimestampedValue<NitzData> finalNitzSignal =
-                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+        NitzSignal finalNitzSignal =
+                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
         script.nitzReceived(finalNitzSignal);
 
         // Verify the state machine did the right thing.
@@ -447,14 +447,14 @@
                 expectedFinalTimeSuggestion, expectedFinalTimeZoneSuggestion);
 
         // Check state that NitzStateMachine must expose.
-        assertEquals(finalNitzSignal.getValue(), mNitzStateMachineImpl.getCachedNitzData());
+        assertEquals(finalNitzSignal.getNitzData(), mNitzStateMachineImpl.getCachedNitzData());
     }
 
     @Test
     public void test_countryUnavailableClearsTimeZoneSuggestion() throws Exception {
         Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
-        TimestampedValue<NitzData> nitzSignal =
-                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+        NitzSignal nitzSignal =
+                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
 
         Script script = new Script()
                 .initializeSystemClock(ARBITRARY_SYSTEM_CLOCK_TIME)
@@ -478,7 +478,7 @@
                 expectedTimeSuggestion, expectedTimeZoneSuggestion2);
 
         // Check state that NitzStateMachine must expose.
-        assertEquals(nitzSignal.getValue(), mNitzStateMachineImpl.getCachedNitzData());
+        assertEquals(nitzSignal.getNitzData(), mNitzStateMachineImpl.getCachedNitzData());
 
         // Simulate the country becoming unavailable and verify the state machine does the right
         // thing.
@@ -489,7 +489,7 @@
         script.verifyOnlyTimeZoneWasSuggestedAndReset(expectedTimeZoneSuggestion3);
 
         // Check state that NitzStateMachine must expose.
-        assertEquals(nitzSignal.getValue(), mNitzStateMachineImpl.getCachedNitzData());
+        assertEquals(nitzSignal.getNitzData(), mNitzStateMachineImpl.getCachedNitzData());
     }
 
     /**
@@ -524,7 +524,7 @@
             return this;
         }
 
-        Script nitzReceived(TimestampedValue<NitzData> nitzSignal) {
+        Script nitzReceived(NitzSignal nitzSignal) {
             mNitzStateMachineImpl.handleNitzReceived(nitzSignal);
             return this;
         }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/nitz/NitzStateMachineTestSupport.java b/tests/telephonytests/src/com/android/internal/telephony/nitz/NitzStateMachineTestSupport.java
index ed1c98b..6458517 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/nitz/NitzStateMachineTestSupport.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/nitz/NitzStateMachineTestSupport.java
@@ -23,9 +23,9 @@
 import android.icu.util.Calendar;
 import android.icu.util.GregorianCalendar;
 import android.icu.util.TimeZone;
-import android.os.TimestampedValue;
 
 import com.android.internal.telephony.NitzData;
+import com.android.internal.telephony.NitzSignal;
 import com.android.internal.telephony.NitzStateMachine;
 import com.android.internal.telephony.NitzStateMachine.DeviceState;
 
@@ -34,9 +34,12 @@
  */
 final class NitzStateMachineTestSupport {
 
+    /** Used to indicate that a NitzSignal ageMillis is unimportant for the test. */
+    static final int ARBITRARY_AGE = 54321;
+
     // Values used to when initializing device state but where the value isn't important.
     static final long ARBITRARY_SYSTEM_CLOCK_TIME = createUtcTime(1977, 1, 1, 12, 0, 0);
-    static final long ARBITRARY_REALTIME_MILLIS = 123456789L;
+    static final long ARBITRARY_ELAPSED_REALTIME = 123456789L;
     static final String ARBITRARY_DEBUG_INFO = "Test debug info";
 
     // A country with a single zone : the zone can be guessed from the country.
@@ -120,9 +123,11 @@
             mNetworkCountryIsoCode = countryIsoCode;
         }
 
-        /** Creates an NITZ signal to match the scenario. */
-        TimestampedValue<NitzData> createNitzSignal(long elapsedRealtimeClock) {
-            return new TimestampedValue<>(elapsedRealtimeClock, createNitzData());
+        /**
+         * Creates an NITZ signal to match the scenario with the specified receipt / age properties.
+         */
+        NitzSignal createNitzSignal(long receiptElapsedMillis, long ageMillis) {
+            return new NitzSignal(receiptElapsedMillis, createNitzData(), ageMillis);
         }
 
         /** Creates an NITZ signal to match the scenario. */
@@ -214,7 +219,7 @@
             ignoreNitz = false;
             nitzUpdateDiffMillis = 2000;
             nitzUpdateSpacingMillis = 1000 * 60 * 10;
-            elapsedRealtime = ARBITRARY_REALTIME_MILLIS;
+            elapsedRealtime = ARBITRARY_ELAPSED_REALTIME;
         }
 
         @Override
@@ -222,18 +227,26 @@
             return nitzUpdateSpacingMillis;
         }
 
+        public void setNitzUpdateSpacingMillis(int nitzUpdateSpacingMillis) {
+            this.nitzUpdateSpacingMillis = nitzUpdateSpacingMillis;
+        }
+
         @Override
         public int getNitzUpdateDiffMillis() {
             return nitzUpdateDiffMillis;
         }
 
+        public void setNitzUpdateDiffMillis(int nitzUpdateDiffMillis) {
+            this.nitzUpdateDiffMillis = nitzUpdateDiffMillis;
+        }
+
         @Override
         public boolean getIgnoreNitz() {
             return ignoreNitz;
         }
 
         @Override
-        public long elapsedRealtime() {
+        public long elapsedRealtimeMillis() {
             return elapsedRealtime;
         }
 
@@ -275,20 +288,13 @@
     }
 
     static TelephonyTimeSuggestion createTimeSuggestionFromNitzSignal(
-            int slotIndex, TimestampedValue<NitzData> nitzSignal) {
+            int slotIndex, NitzSignal nitzSignal) {
         return new TelephonyTimeSuggestion.Builder(slotIndex)
-                .setUtcTime(createTimeSignalFromNitzSignal(nitzSignal))
+                .setUtcTime(nitzSignal.createTimeSignal())
                 .addDebugInfo("Test")
                 .build();
     }
 
-    private static TimestampedValue<Long> createTimeSignalFromNitzSignal(
-            TimestampedValue<NitzData> nitzSignal) {
-        return new TimestampedValue<>(
-                nitzSignal.getReferenceTimeMillis(),
-                nitzSignal.getValue().getCurrentTimeInMillis());
-    }
-
     private static TimeZone zone(String zoneId) {
         TimeZone timeZone = TimeZone.getFrozenTimeZone(zoneId);
         if (timeZone.getID().equals(TimeZone.UNKNOWN_ZONE_ID)) {
diff --git a/tests/telephonytests/src/com/android/internal/telephony/nitz/TimeZoneSuggesterImplTest.java b/tests/telephonytests/src/com/android/internal/telephony/nitz/TimeZoneSuggesterImplTest.java
index 708d3de..5d59426 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/nitz/TimeZoneSuggesterImplTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/nitz/TimeZoneSuggesterImplTest.java
@@ -24,7 +24,8 @@
 import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET;
 import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE;
 
-import static com.android.internal.telephony.nitz.NitzStateMachineTestSupport.ARBITRARY_REALTIME_MILLIS;
+import static com.android.internal.telephony.nitz.NitzStateMachineTestSupport.ARBITRARY_AGE;
+import static com.android.internal.telephony.nitz.NitzStateMachineTestSupport.ARBITRARY_ELAPSED_REALTIME;
 import static com.android.internal.telephony.nitz.NitzStateMachineTestSupport.CZECHIA_SCENARIO;
 import static com.android.internal.telephony.nitz.NitzStateMachineTestSupport.NEW_ZEALAND_COUNTRY_DEFAULT_ZONE_ID;
 import static com.android.internal.telephony.nitz.NitzStateMachineTestSupport.NEW_ZEALAND_DEFAULT_SCENARIO;
@@ -40,9 +41,9 @@
 import static org.junit.Assert.assertTrue;
 
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
-import android.os.TimestampedValue;
 
 import com.android.internal.telephony.NitzData;
+import com.android.internal.telephony.NitzSignal;
 import com.android.internal.telephony.TelephonyTest;
 import com.android.internal.telephony.nitz.NitzStateMachineImpl.TimeZoneSuggester;
 import com.android.internal.telephony.nitz.NitzStateMachineTestSupport.FakeDeviceState;
@@ -95,8 +96,8 @@
     @Test
     public void test_emptySuggestionForNullCountryWithNitz() throws Exception {
         Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
-        TimestampedValue<NitzData> nitzSignal =
-                scenario.createNitzSignal(ARBITRARY_REALTIME_MILLIS);
+        NitzSignal nitzSignal =
+                scenario.createNitzSignal(ARBITRARY_ELAPSED_REALTIME, ARBITRARY_AGE);
         assertEquals(EMPTY_TIME_ZONE_SUGGESTION,
                 mTimeZoneSuggester.getTimeZoneSuggestion(
                         SLOT_INDEX, null /* countryIsoCode */, nitzSignal));
@@ -137,9 +138,10 @@
         // NITZ with a "" country code is interpreted as a test network so only offset is used
         // to get a match.
         {
+            NitzSignal nitzSignal = scenario.createNitzSignal(
+                    mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
             TelephonyTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
-                    SLOT_INDEX, "" /* countryIsoCode */,
-                    scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime()));
+                    SLOT_INDEX, "" /* countryIsoCode */, nitzSignal);
             assertEquals(SLOT_INDEX, actualSuggestion.getSlotIndex());
             assertEquals(MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY, actualSuggestion.getMatchType());
             assertEquals(QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET, actualSuggestion.getQuality());
@@ -147,9 +149,10 @@
 
         // NITZ alone is not enough to get a result when the country is not available.
         {
+            NitzSignal nitzSignal = scenario.createNitzSignal(
+                    mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
             TelephonyTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
-                    SLOT_INDEX, null /* countryIsoCode */,
-                    scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime()));
+                    SLOT_INDEX, null /* countryIsoCode */, nitzSignal);
             assertEquals(EMPTY_TIME_ZONE_SUGGESTION, actualSuggestion);
         }
 
@@ -161,9 +164,10 @@
                             .setMatchType(MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET)
                             .setQuality(QUALITY_SINGLE_ZONE)
                             .build();
+            NitzSignal nitzSignal = scenario.createNitzSignal(
+                    mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
             TelephonyTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
-                    SLOT_INDEX, scenario.getNetworkCountryIsoCode(),
-                    scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime()));
+                    SLOT_INDEX, scenario.getNetworkCountryIsoCode(), nitzSignal);
             assertEquals(expectedTimeZoneSuggestion, actualSuggestion);
         }
 
@@ -171,12 +175,11 @@
         // since there are multiple zones to choose from.
         {
             // We use an NITZ from CZ to generate an NITZ signal with a bad offset.
-            TimestampedValue<NitzData> badNitzSignal =
-                    CZECHIA_SCENARIO.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+            NitzSignal badNitzSignal = CZECHIA_SCENARIO.createNitzSignal(
+                    mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
             TelephonyTimeZoneSuggestion expectedTimeZoneSuggestion = EMPTY_TIME_ZONE_SUGGESTION;
             TelephonyTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
-                    SLOT_INDEX, scenario.getNetworkCountryIsoCode(),
-                    badNitzSignal);
+                    SLOT_INDEX, scenario.getNetworkCountryIsoCode(), badNitzSignal);
             assertEquals(expectedTimeZoneSuggestion, actualSuggestion);
         }
     }
@@ -209,9 +212,10 @@
         // NITZ with a "" country code is interpreted as a test network so only offset is used
         // to get a match.
         {
+            NitzSignal nitzSignal = scenario.createNitzSignal(
+                    mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
             TelephonyTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
-                    SLOT_INDEX, "" /* countryIsoCode */,
-                    scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime()));
+                    SLOT_INDEX, "" /* countryIsoCode */, nitzSignal);
             assertEquals(SLOT_INDEX, actualSuggestion.getSlotIndex());
             assertEquals(MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY, actualSuggestion.getMatchType());
             assertEquals(QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET, actualSuggestion.getQuality());
@@ -219,17 +223,19 @@
 
         // NITZ alone is not enough to get a result when the country is not available.
         {
+            NitzSignal nitzSignal = scenario.createNitzSignal(
+                    mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
             TelephonyTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
-                    SLOT_INDEX, null /* countryIsoCode */,
-                    scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime()));
+                    SLOT_INDEX, null /* countryIsoCode */, nitzSignal);
             assertEquals(EMPTY_TIME_ZONE_SUGGESTION, actualSuggestion);
         }
 
         // Country + NITZ is not enough for a unique time zone detection result for this scenario.
         {
+            NitzSignal nitzSignal = scenario.createNitzSignal(
+                    mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
             TelephonyTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
-                    SLOT_INDEX, scenario.getNetworkCountryIsoCode(),
-                    scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime()));
+                    SLOT_INDEX, scenario.getNetworkCountryIsoCode(), nitzSignal);
             assertEquals(SLOT_INDEX, actualSuggestion.getSlotIndex());
             assertEquals(MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET, actualSuggestion.getMatchType());
             assertEquals(QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET, actualSuggestion.getQuality());
@@ -241,12 +247,11 @@
         // since there are multiple zones to choose from.
         {
             // We use an NITZ from CZ to generate an NITZ signal with a bad offset.
-            TimestampedValue<NitzData> badNitzSignal =
-                    CZECHIA_SCENARIO.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+            NitzSignal badNitzSignal = CZECHIA_SCENARIO.createNitzSignal(
+                    mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
             TelephonyTimeZoneSuggestion expectedTimeZoneSuggestion = EMPTY_TIME_ZONE_SUGGESTION;
             TelephonyTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
-                    SLOT_INDEX, scenario.getNetworkCountryIsoCode(),
-                    badNitzSignal);
+                    SLOT_INDEX, scenario.getNetworkCountryIsoCode(), badNitzSignal);
             assertEquals(expectedTimeZoneSuggestion, actualSuggestion);
         }
     }
@@ -278,9 +283,10 @@
         // NITZ with a "" country code is interpreted as a test network so only offset is used
         // to get a match.
         {
+            NitzSignal nitzSignal = scenario.createNitzSignal(
+                    mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
             TelephonyTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
-                    SLOT_INDEX, "" /* countryIsoCode */,
-                    scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime()));
+                    SLOT_INDEX, "" /* countryIsoCode */, nitzSignal);
             assertEquals(SLOT_INDEX, actualSuggestion.getSlotIndex());
             assertEquals(MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY, actualSuggestion.getMatchType());
             assertEquals(QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET, actualSuggestion.getQuality());
@@ -289,9 +295,10 @@
 
         // NITZ alone is not enough to get a result when the country is not available.
         {
+            NitzSignal nitzSignal = scenario.createNitzSignal(
+                    mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
             TelephonyTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
-                    SLOT_INDEX, null /* countryIsoCode */,
-                    scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime()));
+                    SLOT_INDEX, null /* countryIsoCode */, nitzSignal);
             assertEquals(EMPTY_TIME_ZONE_SUGGESTION, actualSuggestion);
         }
 
@@ -304,9 +311,10 @@
                             .setQuality(QUALITY_SINGLE_ZONE)
                             .build();
 
+            NitzSignal nitzSignal = scenario.createNitzSignal(
+                    mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
             TelephonyTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
-                    SLOT_INDEX, scenario.getNetworkCountryIsoCode(),
-                    scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime()));
+                    SLOT_INDEX, scenario.getNetworkCountryIsoCode(), nitzSignal);
             assertEquals(expectedTimeZoneSuggestion, actualSuggestion);
         }
 
@@ -314,8 +322,8 @@
         // there's only one zone.
         {
             // We use an NITZ from Czechia to generate an NITZ signal with a bad offset.
-            TimestampedValue<NitzData> badNitzSignal =
-                    CZECHIA_SCENARIO.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+            NitzSignal badNitzSignal = CZECHIA_SCENARIO.createNitzSignal(
+                    mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
             TelephonyTimeZoneSuggestion expectedTimeZoneSuggestion =
                     new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX)
                             .setZoneId(scenario.getTimeZoneId())
@@ -324,8 +332,7 @@
                             .build();
 
             TelephonyTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
-                    SLOT_INDEX, scenario.getNetworkCountryIsoCode(),
-                    badNitzSignal);
+                    SLOT_INDEX, scenario.getNetworkCountryIsoCode(), badNitzSignal);
             assertEquals(expectedTimeZoneSuggestion, actualSuggestion);
         }
     }
@@ -356,21 +363,22 @@
         // NITZ with a "" country code is interpreted as a test network so only offset is used
         // to get a match.
         {
+            NitzSignal nitzSignal = scenario.createNitzSignal(
+                    mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
             TelephonyTimeZoneSuggestion actualSuggestion =
                     mTimeZoneSuggester.getTimeZoneSuggestion(
-                            SLOT_INDEX, "" /* countryIsoCode */,
-                            scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime()));
+                            SLOT_INDEX, "" /* countryIsoCode */, nitzSignal);
             assertEquals(SLOT_INDEX, actualSuggestion.getSlotIndex());
             assertEquals(MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY, actualSuggestion.getMatchType());
             assertEquals(QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET, actualSuggestion.getQuality());
-
         }
 
         // NITZ alone is not enough to get a result when the country is not available.
         {
+            NitzSignal nitzSignal = scenario.createNitzSignal(
+                    mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
             TelephonyTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
-                    SLOT_INDEX, null /* countryIsoCode */,
-                    scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime()));
+                    SLOT_INDEX, null /* countryIsoCode */, nitzSignal);
             assertEquals(EMPTY_TIME_ZONE_SUGGESTION, actualSuggestion);
         }
 
@@ -383,9 +391,10 @@
                             .setQuality(QUALITY_SINGLE_ZONE)
                             .build();
 
+            NitzSignal nitzSignal = scenario.createNitzSignal(
+                    mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
             TelephonyTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
-                    SLOT_INDEX, scenario.getNetworkCountryIsoCode(),
-                    scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime()));
+                    SLOT_INDEX, scenario.getNetworkCountryIsoCode(), nitzSignal);
             assertEquals(expectedTimeZoneSuggestion, actualSuggestion);
         }
 
@@ -393,8 +402,8 @@
         // there's only one zone.
         {
             // We use an NITZ from the US to generate an NITZ signal with a bad offset.
-            TimestampedValue<NitzData> badNitzSignal =
-                    UNIQUE_US_ZONE_SCENARIO1.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+            NitzSignal badNitzSignal = UNIQUE_US_ZONE_SCENARIO1.createNitzSignal(
+                    mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
             TelephonyTimeZoneSuggestion expectedTimeZoneSuggestion =
                     new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX)
                             .setZoneId(scenario.getTimeZoneId())
@@ -403,8 +412,7 @@
                             .build();
 
             TelephonyTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
-                    SLOT_INDEX, scenario.getNetworkCountryIsoCode(),
-                    badNitzSignal);
+                    SLOT_INDEX, scenario.getNetworkCountryIsoCode(), badNitzSignal);
             assertEquals(expectedTimeZoneSuggestion, actualSuggestion);
         }
     }
@@ -430,14 +438,15 @@
         // NITZ + bogus NITZ is not enough to get a result.
         {
             // Create a corrupted NITZ signal, where the offset information has been lost.
-            TimestampedValue<NitzData> goodNitzSignal =
-                    scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+            NitzSignal goodNitzSignal = scenario.createNitzSignal(
+                    mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
             NitzData bogusNitzData = NitzData.createForTests(
                     0 /* UTC! */, null /* dstOffsetMillis */,
-                    goodNitzSignal.getValue().getCurrentTimeInMillis(),
+                    goodNitzSignal.getNitzData().getCurrentTimeInMillis(),
                     null /* emulatorHostTimeZone */);
-            TimestampedValue<NitzData> badNitzSignal = new TimestampedValue<>(
-                    goodNitzSignal.getReferenceTimeMillis(), bogusNitzData);
+            NitzSignal badNitzSignal = new NitzSignal(
+                    goodNitzSignal.getReceiptElapsedRealtimeMillis(), bogusNitzData,
+                    goodNitzSignal.getAgeMillis());
 
             TelephonyTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
                     SLOT_INDEX, scenario.getNetworkCountryIsoCode(), badNitzSignal);
@@ -466,14 +475,15 @@
         // NITZ + bogus NITZ is not enough to get a result.
         {
             // Create a corrupted NITZ signal, where the offset information has been lost.
-            TimestampedValue<NitzData> goodNitzSignal =
-                    scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+            NitzSignal goodNitzSignal = scenario.createNitzSignal(
+                    mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
             NitzData bogusNitzData = NitzData.createForTests(
                     0 /* UTC! */, null /* dstOffsetMillis */,
-                    goodNitzSignal.getValue().getCurrentTimeInMillis(),
+                    goodNitzSignal.getNitzData().getCurrentTimeInMillis(),
                     null /* emulatorHostTimeZone */);
-            TimestampedValue<NitzData> badNitzSignal = new TimestampedValue<>(
-                    goodNitzSignal.getReferenceTimeMillis(), bogusNitzData);
+            NitzSignal badNitzSignal = new NitzSignal(
+                    goodNitzSignal.getReceiptElapsedRealtimeMillis(), bogusNitzData,
+                    goodNitzSignal.getAgeMillis());
 
             TelephonyTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
                     SLOT_INDEX, scenario.getNetworkCountryIsoCode(), badNitzSignal);
@@ -485,11 +495,11 @@
     public void test_emulatorNitzExtensionUsedForTimeZone() throws Exception {
         Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
 
-        TimestampedValue<NitzData> originalNitzSignal =
-                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+        NitzSignal originalNitzSignal = scenario.createNitzSignal(
+                mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
 
         // Create an NITZ signal with an explicit time zone (as can happen on emulators).
-        NitzData originalNitzData = originalNitzSignal.getValue();
+        NitzData originalNitzData = originalNitzSignal.getNitzData();
 
         // A time zone that is obviously not in the US, but because the explicit value is present it
         // should not be questioned.
@@ -499,8 +509,9 @@
                 originalNitzData.getDstAdjustmentMillis(),
                 originalNitzData.getCurrentTimeInMillis(),
                 java.util.TimeZone.getTimeZone(emulatorTimeZoneId) /* emulatorHostTimeZone */);
-        TimestampedValue<NitzData> emulatorNitzSignal = new TimestampedValue<>(
-                originalNitzSignal.getReferenceTimeMillis(), emulatorNitzData);
+        NitzSignal emulatorNitzSignal = new NitzSignal(
+                originalNitzSignal.getReceiptElapsedRealtimeMillis(), emulatorNitzData,
+                originalNitzSignal.getAgeMillis());
 
         TelephonyTimeZoneSuggestion expectedTimeZoneSuggestion =
                 new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX)
@@ -535,8 +546,8 @@
         // Confirm what happens when NITZ is correct for the country default.
         {
             Scenario scenario = NEW_ZEALAND_DEFAULT_SCENARIO;
-            TimestampedValue<NitzData> nitzSignal =
-                    scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+            NitzSignal nitzSignal = scenario.createNitzSignal(
+                    mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
             TelephonyTimeZoneSuggestion expectedSuggestion =
                     new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX)
                             .setZoneId(scenario.getTimeZoneId())
@@ -552,8 +563,8 @@
         // A valid NITZ signal for the non-default zone should still be correctly detected.
         {
             Scenario scenario = NEW_ZEALAND_OTHER_SCENARIO;
-            TimestampedValue<NitzData> nitzSignal =
-                    scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+            NitzSignal nitzSignal = scenario.createNitzSignal(
+                    mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
             TelephonyTimeZoneSuggestion expectedSuggestion =
                     new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX)
                             .setZoneId(scenario.getTimeZoneId())
@@ -571,8 +582,8 @@
         {
             Scenario scenario = NEW_ZEALAND_DEFAULT_SCENARIO;
             // Use a scenario that has a different offset than NZ to generate the NITZ signal.
-            TimestampedValue<NitzData> nitzSignal =
-                    CZECHIA_SCENARIO.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+            NitzSignal nitzSignal = CZECHIA_SCENARIO.createNitzSignal(
+                    mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
             TelephonyTimeZoneSuggestion expectedSuggestion =
                     new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX)
                             .setZoneId(NEW_ZEALAND_COUNTRY_DEFAULT_ZONE_ID)
@@ -607,8 +618,8 @@
         // Confirm what happens when NITZ is correct for the country default.
         {
             Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
-            TimestampedValue<NitzData> nitzSignal =
-                    scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+            NitzSignal nitzSignal = scenario.createNitzSignal(
+                    mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
             TelephonyTimeZoneSuggestion expectedSuggestion =
                     new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX)
                             .setZoneId(scenario.getTimeZoneId())
@@ -624,8 +635,8 @@
         // A valid NITZ signal for the non-default zone should still be correctly detected.
         {
             Scenario scenario = UNIQUE_US_ZONE_SCENARIO2;
-            TimestampedValue<NitzData> nitzSignal =
-                    scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+            NitzSignal nitzSignal = scenario.createNitzSignal(
+                    mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
             TelephonyTimeZoneSuggestion expectedSuggestion =
                     new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX)
                             .setZoneId(scenario.getTimeZoneId())
@@ -644,8 +655,8 @@
             // A scenario that has a different offset than US.
             Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
             // Use a scenario that has a different offset than the US to generate the NITZ signal.
-            TimestampedValue<NitzData> nitzSignal =
-                    CZECHIA_SCENARIO.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+            NitzSignal nitzSignal = CZECHIA_SCENARIO.createNitzSignal(
+                    mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
             TelephonyTimeZoneSuggestion expectedSuggestion = EMPTY_TIME_ZONE_SUGGESTION;
             TelephonyTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
                     SLOT_INDEX, scenario.getNetworkCountryIsoCode(), nitzSignal);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/PinStorageTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/PinStorageTest.java
index 9d7ad5b..a26c0f9 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/PinStorageTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/PinStorageTest.java
@@ -25,7 +25,6 @@
 
 import android.content.Intent;
 import android.os.PersistableBundle;
-import android.os.WorkSource;
 import android.preference.PreferenceManager;
 import android.provider.Settings;
 import android.telephony.CarrierConfigManager;
@@ -51,9 +50,6 @@
     private static final String ICCID_1 = "89010003006562472370";
     private static final String ICCID_2 = "89010003006562472399";
     private static final String ICCID_INVALID = "1234";
-    private static final String PACKAGE_NAME = "com.package.name";
-    private static final int UID = -1;
-    private static final WorkSource sWorkSource = new WorkSource(UID, PACKAGE_NAME);
 
     private int mBootCount;
     private int mSimulatedRebootsCount;
@@ -110,7 +106,7 @@
     @Test
     @SmallTest
     public void storePin_withoutReboot_pinCannotBeRetrieved() {
-        mPinStorage.storePin("1234", 0, mPinStorage.getIccid(0));
+        mPinStorage.storePin("1234", 0);
 
         assertThat(mPinStorage.getPin(0, ICCID_1)).isEqualTo("");
     }
@@ -118,7 +114,7 @@
     @Test
     @SmallTest
     public void storePin_normalReboot_pinCannotBeRetrieved() {
-        mPinStorage.storePin("1234", 0, mPinStorage.getIccid(0));
+        mPinStorage.storePin("1234", 0);
 
         simulateReboot();
 
@@ -128,7 +124,7 @@
     @Test
     @SmallTest
     public void storePin_crash_pinCannotBeRetrieved() {
-        mPinStorage.storePin("1234", 0, mPinStorage.getIccid(0));
+        mPinStorage.storePin("1234", 0);
 
         // Simulate crash
         mPinStorage = new PinStorage(mContext);
@@ -140,9 +136,9 @@
     @Test
     @SmallTest
     public void storePin_unattendedReboot_pinCanBeRetrievedOnce() {
-        mPinStorage.storePin("1234", 0, mPinStorage.getIccid(0));
+        mPinStorage.storePin("1234", 0);
 
-        int result = mPinStorage.prepareUnattendedReboot(sWorkSource);
+        int result = mPinStorage.prepareUnattendedReboot();
         assertThat(result).isEqualTo(TelephonyManager.PREPARE_UNATTENDED_REBOOT_SUCCESS);
 
         simulateReboot();
@@ -160,9 +156,9 @@
         when(mKeyguardManager.isDeviceLocked()).thenReturn(true);
         simulateReboot();
 
-        mPinStorage.storePin("1234", 0, mPinStorage.getIccid(0));
+        mPinStorage.storePin("1234", 0);
 
-        int result = mPinStorage.prepareUnattendedReboot(sWorkSource);
+        int result = mPinStorage.prepareUnattendedReboot();
         assertThat(result).isEqualTo(TelephonyManager.PREPARE_UNATTENDED_REBOOT_ERROR);
 
         simulateReboot();
@@ -174,9 +170,9 @@
     @Test
     @SmallTest
     public void storePin_unattendedReboot_pinIsRemovedAfterDelay() {
-        mPinStorage.storePin("1234", 0, mPinStorage.getIccid(0));
+        mPinStorage.storePin("1234", 0);
 
-        int result = mPinStorage.prepareUnattendedReboot(sWorkSource);
+        int result = mPinStorage.prepareUnattendedReboot();
         assertThat(result).isEqualTo(TelephonyManager.PREPARE_UNATTENDED_REBOOT_SUCCESS);
 
         simulateReboot();
@@ -188,7 +184,7 @@
         assertThat(mPinStorage.getPin(0, ICCID_1)).isEqualTo("");
 
         // Simulate a second unattended reboot to make sure that PIN was deleted.
-        result = mPinStorage.prepareUnattendedReboot(sWorkSource);
+        result = mPinStorage.prepareUnattendedReboot();
         assertThat(result).isEqualTo(TelephonyManager.PREPARE_UNATTENDED_REBOOT_SUCCESS);
 
         simulateReboot();
@@ -199,9 +195,9 @@
     @Test
     @SmallTest
     public void storePin_unattendedRebootNotDone_pinCannotBeRetrieved() {
-        mPinStorage.storePin("1234", 0, mPinStorage.getIccid(0));
+        mPinStorage.storePin("1234", 0);
 
-        int result = mPinStorage.prepareUnattendedReboot(sWorkSource);
+        int result = mPinStorage.prepareUnattendedReboot();
         assertThat(result).isEqualTo(TelephonyManager.PREPARE_UNATTENDED_REBOOT_SUCCESS);
 
         // Move time forward by 60 seconds before simulating reboot
@@ -215,9 +211,9 @@
     @Test
     @SmallTest
     public void storePin_unattendedReboot_iccidChange() {
-        mPinStorage.storePin("1234", 0, mPinStorage.getIccid(0));
+        mPinStorage.storePin("1234", 0);
 
-        int result = mPinStorage.prepareUnattendedReboot(sWorkSource);
+        int result = mPinStorage.prepareUnattendedReboot();
         assertThat(result).isEqualTo(TelephonyManager.PREPARE_UNATTENDED_REBOOT_SUCCESS);
 
         simulateReboot();
@@ -236,10 +232,10 @@
     @Test
     @SmallTest
     public void clearPin_pinCannotBeRetrieved() {
-        mPinStorage.storePin("1234", 0, mPinStorage.getIccid(0));
+        mPinStorage.storePin("1234", 0);
         mPinStorage.clearPin(0);
 
-        int result = mPinStorage.prepareUnattendedReboot(sWorkSource);
+        int result = mPinStorage.prepareUnattendedReboot();
         assertThat(result).isEqualTo(TelephonyManager.PREPARE_UNATTENDED_REBOOT_SUCCESS);
 
         simulateReboot();
@@ -250,10 +246,10 @@
     @Test
     @SmallTest
     public void storePin_pinChanged_pinIsUpdated() {
-        mPinStorage.storePin("1234", 0, mPinStorage.getIccid(0));
-        mPinStorage.storePin("5678", 0, mPinStorage.getIccid(0));
+        mPinStorage.storePin("1234", 0);
+        mPinStorage.storePin("5678", 0);
 
-        int result = mPinStorage.prepareUnattendedReboot(sWorkSource);
+        int result = mPinStorage.prepareUnattendedReboot();
         assertThat(result).isEqualTo(TelephonyManager.PREPARE_UNATTENDED_REBOOT_SUCCESS);
 
         simulateReboot();
@@ -264,9 +260,9 @@
     @Test
     @SmallTest
     public void storePin_pinTooShort_pinIsNotStored() {
-        mPinStorage.storePin("12", 0, mPinStorage.getIccid(0));
+        mPinStorage.storePin("12", 0);
 
-        int result = mPinStorage.prepareUnattendedReboot(sWorkSource);
+        int result = mPinStorage.prepareUnattendedReboot();
         assertThat(result).isEqualTo(TelephonyManager.PREPARE_UNATTENDED_REBOOT_SUCCESS);
 
         simulateReboot();
@@ -277,9 +273,9 @@
     @Test
     @SmallTest
     public void storePin_pinTooLong_pinIsNotStored() {
-        mPinStorage.storePin("123456789", 0, mPinStorage.getIccid(0));
+        mPinStorage.storePin("123456789", 0);
 
-        int result = mPinStorage.prepareUnattendedReboot(sWorkSource);
+        int result = mPinStorage.prepareUnattendedReboot();
         assertThat(result).isEqualTo(TelephonyManager.PREPARE_UNATTENDED_REBOOT_SUCCESS);
 
         simulateReboot();
@@ -292,8 +288,8 @@
     public void storePin_invalidIccid_pinIsNotStored() {
         doReturn(ICCID_INVALID).when(mPhone).getFullIccSerialNumber();
 
-        mPinStorage.storePin("1234", 0, mPinStorage.getIccid(0));
-        int result = mPinStorage.prepareUnattendedReboot(sWorkSource);
+        mPinStorage.storePin("1234", 0);
+        int result = mPinStorage.prepareUnattendedReboot();
 
         simulateReboot();
 
@@ -306,9 +302,9 @@
         mContextFixture.putBooleanResource(
                 R.bool.config_allow_pin_storage_for_unattended_reboot, false);
 
-        mPinStorage.storePin("1234", 0, mPinStorage.getIccid(0));
+        mPinStorage.storePin("1234", 0);
 
-        int result = mPinStorage.prepareUnattendedReboot(sWorkSource);
+        int result = mPinStorage.prepareUnattendedReboot();
         assertThat(result).isEqualTo(TelephonyManager.PREPARE_UNATTENDED_REBOOT_SUCCESS);
 
         simulateReboot();
@@ -325,9 +321,9 @@
         when(mUiccController.getUiccProfileForPhone(anyInt())).thenReturn(mUiccProfile);
         when(mUiccCardApplication3gpp.getPin1State()).thenReturn(PINSTATE_ENABLED_VERIFIED);
 
-        mPinStorage.storePin("1234", 0, mPinStorage.getIccid(0));
+        mPinStorage.storePin("1234", 0);
 
-        int result = mPinStorage.prepareUnattendedReboot(sWorkSource);
+        int result = mPinStorage.prepareUnattendedReboot();
         assertThat(result).isEqualTo(TelephonyManager.PREPARE_UNATTENDED_REBOOT_PIN_REQUIRED);
 
         simulateReboot();
@@ -343,9 +339,9 @@
                 CarrierConfigManager.KEY_STORE_SIM_PIN_FOR_UNATTENDED_REBOOT_BOOL, false);
         when(mCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(carrierConfigs);
 
-        mPinStorage.storePin("1234", 0, mPinStorage.getIccid(0));
+        mPinStorage.storePin("1234", 0);
 
-        int result = mPinStorage.prepareUnattendedReboot(sWorkSource);
+        int result = mPinStorage.prepareUnattendedReboot();
         assertThat(result).isEqualTo(TelephonyManager.PREPARE_UNATTENDED_REBOOT_SUCCESS);
 
         simulateReboot();
@@ -356,7 +352,7 @@
     @Test
     @SmallTest
     public void storePin_changeToDisabledInCarrierConfig_pinIsRemoved() {
-        mPinStorage.storePin("1234", 0, mPinStorage.getIccid(0));
+        mPinStorage.storePin("1234", 0);
 
         // Simulate change in the carrier configuration
         PersistableBundle carrierConfigs = new PersistableBundle();
@@ -368,7 +364,7 @@
         mContext.sendBroadcast(intent);
         processAllMessages();
 
-        int result = mPinStorage.prepareUnattendedReboot(sWorkSource);
+        int result = mPinStorage.prepareUnattendedReboot();
         assertThat(result).isEqualTo(TelephonyManager.PREPARE_UNATTENDED_REBOOT_SUCCESS);
 
         simulateReboot();
@@ -379,7 +375,7 @@
     @Test
     @SmallTest
     public void storePin_simIsRemoved_pinIsRemoved() {
-        mPinStorage.storePin("1234", 0, mPinStorage.getIccid(0));
+        mPinStorage.storePin("1234", 0);
 
         // SIM is removed
         final Intent intent = new Intent(TelephonyManager.ACTION_SIM_CARD_STATE_CHANGED);
@@ -388,7 +384,7 @@
         mContext.sendBroadcast(intent);
         processAllMessages();
 
-        int result = mPinStorage.prepareUnattendedReboot(sWorkSource);
+        int result = mPinStorage.prepareUnattendedReboot();
         assertThat(result).isEqualTo(TelephonyManager.PREPARE_UNATTENDED_REBOOT_SUCCESS);
 
         simulateReboot();
@@ -399,9 +395,9 @@
     @Test
     @SmallTest
     public void storePin_simReadyAfterUnattendedReboot_pinIsRemoved() {
-        mPinStorage.storePin("1234", 0, mPinStorage.getIccid(0));
+        mPinStorage.storePin("1234", 0);
 
-        int result = mPinStorage.prepareUnattendedReboot(sWorkSource);
+        int result = mPinStorage.prepareUnattendedReboot();
         assertThat(result).isEqualTo(TelephonyManager.PREPARE_UNATTENDED_REBOOT_SUCCESS);
 
         simulateReboot();
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccControllerTest.java
index e518f3e..c9fccaf 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccControllerTest.java
@@ -60,6 +60,7 @@
     private static final int ICC_CHANGED_EVENT = 0;
     private static final int EVENT_GET_ICC_STATUS_DONE = 3;
     private static final int EVENT_GET_SLOT_STATUS_DONE = 4;
+    private static final int EVENT_SIM_REFRESH = 8;
     private static final int EVENT_EID_READY = 9;
     @Mock
     private Handler mMockedHandler;
@@ -73,6 +74,8 @@
     private UiccCard mMockCard;
     @Mock
     private EuiccCard mMockEuiccCard;
+    @Mock
+    private UiccProfile mMockProfile;
 
     private IccCardApplicationStatus composeUiccApplicationStatus(
             IccCardApplicationStatus.AppType appType,
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccProfileTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccProfileTest.java
index 0118f74..199ff34 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccProfileTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccProfileTest.java
@@ -19,6 +19,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
@@ -561,13 +562,19 @@
         String fakeBrand = "operator";
 
         mUiccProfile.getApplicationIndex(0).getIccRecords().mIccId = fakeIccId;
-        doReturn(fakeIccId).when(mSubscriptionInfo).getIccId();
-        doReturn(mSubscriptionInfo).when(mSubscriptionController)
-                .getActiveSubscriptionInfoForSimSlotIndex(eq(0), any(), any());
 
+        doReturn(false).when(mSubscriptionController)
+                .checkPhoneIdAndIccIdMatch(anyInt(), anyString());
         mUiccProfile.setOperatorBrandOverride(fakeBrand);
         String brandInSharedPreference = mContext.getSharedPreferences("file name", 0)
                 .getString("operator_branding_" + fakeIccId, null);
+        assertNotEquals(fakeBrand, brandInSharedPreference);
+
+        doReturn(true).when(mSubscriptionController)
+                .checkPhoneIdAndIccIdMatch(anyInt(), anyString());
+        mUiccProfile.setOperatorBrandOverride(fakeBrand);
+        brandInSharedPreference = mContext.getSharedPreferences("file name", 0)
+                .getString("operator_branding_" + fakeIccId, null);
         assertEquals(fakeBrand, brandInSharedPreference);
     }