Merge "Make CellBroadcast APIs public"
diff --git a/src/java/com/android/internal/telephony/IccSmsInterfaceManager.java b/src/java/com/android/internal/telephony/IccSmsInterfaceManager.java
index 3558d4e..0e9598d 100644
--- a/src/java/com/android/internal/telephony/IccSmsInterfaceManager.java
+++ b/src/java/com/android/internal/telephony/IccSmsInterfaceManager.java
@@ -178,17 +178,17 @@
         int count = messages.size();
 
         for (int i = 0; i < count; i++) {
-             byte[] ba = messages.get(i);
-             if (ba[0] == STATUS_ON_ICC_UNREAD) {
-                 int n = ba.length;
-                 byte[] nba = new byte[n - 1];
-                 System.arraycopy(ba, 1, nba, 0, n - 1);
-                 byte[] record = makeSmsRecordData(STATUS_ON_ICC_READ, nba);
-                 fh.updateEFLinearFixed(IccConstants.EF_SMS, i + 1, record, null, null);
-                 if (Rlog.isLoggable("SMS", Log.DEBUG)) {
-                     log("SMS " + (i + 1) + " marked as read");
-                 }
-             }
+            byte[] ba = messages.get(i);
+            if ((ba[0] & 0x07) == STATUS_ON_ICC_UNREAD) {
+                int n = ba.length;
+                byte[] nba = new byte[n - 1];
+                System.arraycopy(ba, 1, nba, 0, n - 1);
+                byte[] record = makeSmsRecordData(STATUS_ON_ICC_READ, nba);
+                fh.updateEFLinearFixed(IccConstants.EF_SMS, i + 1, record, null, null);
+                if (Rlog.isLoggable("SMS", Log.DEBUG)) {
+                    log("SMS " + (i + 1) + " marked as read");
+                }
+            }
         }
     }
 
@@ -785,7 +785,7 @@
         }
 
         // Status bits for this record.  See TS 51.011 10.5.3
-        data[0] = (byte)(status & 7);
+        data[0] = (byte) (status & 0x07);
 
         System.arraycopy(pdu, 0, data, 1, pdu.length);
 
diff --git a/src/java/com/android/internal/telephony/MccTable.java b/src/java/com/android/internal/telephony/MccTable.java
index 2e70c45..9d00831 100644
--- a/src/java/com/android/internal/telephony/MccTable.java
+++ b/src/java/com/android/internal/telephony/MccTable.java
@@ -30,6 +30,7 @@
 import com.android.internal.app.LocaleStore;
 import com.android.internal.app.LocaleStore.LocaleInfo;
 
+import libcore.timezone.CountryTimeZones;
 import libcore.timezone.TimeZoneFinder;
 
 import java.util.ArrayList;
@@ -96,7 +97,9 @@
             return null;
         }
         final String lowerCaseCountryCode = entry.mIso;
-        return TimeZoneFinder.getInstance().lookupDefaultTimeZoneIdByCountry(lowerCaseCountryCode);
+        CountryTimeZones countryTimeZones =
+                TimeZoneFinder.getInstance().lookupCountryTimeZones(lowerCaseCountryCode);
+        return countryTimeZones == null ? null : countryTimeZones.getDefaultTimeZoneId();
     }
 
     /**
diff --git a/src/java/com/android/internal/telephony/PhoneFactory.java b/src/java/com/android/internal/telephony/PhoneFactory.java
index b290104..886c83f 100644
--- a/src/java/com/android/internal/telephony/PhoneFactory.java
+++ b/src/java/com/android/internal/telephony/PhoneFactory.java
@@ -538,6 +538,16 @@
             pw.println("++++++++++++++++++++++++++++++++");
         }
 
+        pw.println("ImsResolver:");
+        pw.increaseIndent();
+        try {
+            if (sImsResolver != null) sImsResolver.dump(fd, pw, args);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        pw.decreaseIndent();
+        pw.println("++++++++++++++++++++++++++++++++");
+
         pw.println("SubscriptionMonitor:");
         pw.increaseIndent();
         try {
diff --git a/src/java/com/android/internal/telephony/PhoneSwitcher.java b/src/java/com/android/internal/telephony/PhoneSwitcher.java
index e350ba1..bf8fd7a 100644
--- a/src/java/com/android/internal/telephony/PhoneSwitcher.java
+++ b/src/java/com/android/internal/telephony/PhoneSwitcher.java
@@ -1047,6 +1047,11 @@
     }
 
     private void transitionToEmergencyPhone() {
+        if (mNumPhones <= 0) {
+            log("No phones: unable to reset preferred phone for emergency");
+            return;
+        }
+
         if (mPreferredDataPhoneId != DEFAULT_EMERGENCY_PHONE_ID) {
             log("No active subscriptions: resetting preferred phone to 0 for emergency");
             mPreferredDataPhoneId = DEFAULT_EMERGENCY_PHONE_ID;
diff --git a/src/java/com/android/internal/telephony/SubscriptionController.java b/src/java/com/android/internal/telephony/SubscriptionController.java
index 1ab70b6..31e5f4d 100644
--- a/src/java/com/android/internal/telephony/SubscriptionController.java
+++ b/src/java/com/android/internal/telephony/SubscriptionController.java
@@ -1589,8 +1589,16 @@
                 }
             }
             String nameToSet;
-            if (displayName == null) {
-                nameToSet = mContext.getString(SubscriptionManager.DEFAULT_NAME_RES);
+            if (TextUtils.isEmpty(displayName) || displayName.trim().length() == 0) {
+                nameToSet = mTelephonyManager.getSimOperatorName(subId);
+                if (TextUtils.isEmpty(nameToSet)) {
+                    if (nameSource == SubscriptionManager.NAME_SOURCE_USER_INPUT
+                            && SubscriptionManager.isValidSlotIndex(getSlotIndex(subId))) {
+                        nameToSet = "CARD " + (getSlotIndex(subId) + 1);
+                    } else {
+                        nameToSet = mContext.getString(SubscriptionManager.DEFAULT_NAME_RES);
+                    }
+                }
             } else {
                 nameToSet = displayName;
             }
diff --git a/src/java/com/android/internal/telephony/SubscriptionInfoUpdater.java b/src/java/com/android/internal/telephony/SubscriptionInfoUpdater.java
index 84bae1c..2a6a39b 100644
--- a/src/java/com/android/internal/telephony/SubscriptionInfoUpdater.java
+++ b/src/java/com/android/internal/telephony/SubscriptionInfoUpdater.java
@@ -68,8 +68,6 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -975,58 +973,55 @@
             return;
         }
 
-        if (!isCarrierServicePackage(phoneId, configPackageName)) {
-            loge("Cannot manage subId=" + currentSubId + ", carrierPackage=" + configPackageName);
-            return;
-        }
-
         ContentValues cv = new ContentValues();
-        boolean isOpportunistic = config.getBoolean(
-                CarrierConfigManager.KEY_IS_OPPORTUNISTIC_SUBSCRIPTION_BOOL, false);
-        if (currentSubInfo.isOpportunistic() != isOpportunistic) {
-            if (DBG) logd("Set SubId=" + currentSubId + " isOpportunistic=" + isOpportunistic);
-            cv.put(SubscriptionManager.IS_OPPORTUNISTIC, isOpportunistic ? "1" : "0");
-        }
+        ParcelUuid groupUuid = null;
 
+        // carrier certificates are not subscription-specific, so we want to load them even if
+        // this current package is not a CarrierServicePackage
         String[] certs = config.getStringArray(
             CarrierConfigManager.KEY_CARRIER_CERTIFICATE_STRING_ARRAY);
         if (certs != null) {
             UiccAccessRule[] carrierConfigAccessRules = new UiccAccessRule[certs.length];
-            try {
-                for (int i = 0; i < certs.length; i++) {
-                    carrierConfigAccessRules[i] = new UiccAccessRule(
-                        MessageDigest.getInstance("SHA-256").digest(certs[i].getBytes()), null, 0);
-                }
-            } catch (NoSuchAlgorithmException e) {
-                throw new RuntimeException("for setCarrierConfigAccessRules, SHA-256 must exist",
-                    e);
+            for (int i = 0; i < certs.length; i++) {
+                carrierConfigAccessRules[i] = new UiccAccessRule(IccUtils.hexStringToBytes(
+                    certs[i]), null, 0);
             }
             cv.put(SubscriptionManager.ACCESS_RULES_FROM_CARRIER_CONFIGS,
                     UiccAccessRule.encodeRules(carrierConfigAccessRules));
         }
 
-        String groupUuidString =
+        if (!isCarrierServicePackage(phoneId, configPackageName)) {
+            loge("Cannot manage subId=" + currentSubId + ", carrierPackage=" + configPackageName);
+        } else {
+            boolean isOpportunistic = config.getBoolean(
+                    CarrierConfigManager.KEY_IS_OPPORTUNISTIC_SUBSCRIPTION_BOOL, false);
+            if (currentSubInfo.isOpportunistic() != isOpportunistic) {
+                if (DBG) logd("Set SubId=" + currentSubId + " isOpportunistic=" + isOpportunistic);
+                cv.put(SubscriptionManager.IS_OPPORTUNISTIC, isOpportunistic ? "1" : "0");
+            }
+
+            String groupUuidString =
                 config.getString(CarrierConfigManager.KEY_SUBSCRIPTION_GROUP_UUID_STRING, "");
-        ParcelUuid groupUuid = null;
-        if (!TextUtils.isEmpty(groupUuidString)) {
-            try {
-                // Update via a UUID Structure to ensure consistent formatting
-                groupUuid = ParcelUuid.fromString(groupUuidString);
-                if (groupUuid.equals(REMOVE_GROUP_UUID)
+            if (!TextUtils.isEmpty(groupUuidString)) {
+                try {
+                    // Update via a UUID Structure to ensure consistent formatting
+                    groupUuid = ParcelUuid.fromString(groupUuidString);
+                    if (groupUuid.equals(REMOVE_GROUP_UUID)
                             && currentSubInfo.getGroupUuid() != null) {
-                    cv.put(SubscriptionManager.GROUP_UUID, (String) null);
-                    if (DBG) logd("Group Removed for" + currentSubId);
-                } else if (SubscriptionController.getInstance().canPackageManageGroup(groupUuid,
+                        cv.put(SubscriptionManager.GROUP_UUID, (String) null);
+                        if (DBG) logd("Group Removed for" + currentSubId);
+                    } else if (SubscriptionController.getInstance().canPackageManageGroup(groupUuid,
                         configPackageName)) {
-                    cv.put(SubscriptionManager.GROUP_UUID, groupUuid.toString());
-                    cv.put(SubscriptionManager.GROUP_OWNER, configPackageName);
-                    if (DBG) logd("Group Added for" + currentSubId);
-                } else {
-                    loge("configPackageName " + configPackageName + " doesn't own grouUuid "
+                        cv.put(SubscriptionManager.GROUP_UUID, groupUuid.toString());
+                        cv.put(SubscriptionManager.GROUP_OWNER, configPackageName);
+                        if (DBG) logd("Group Added for" + currentSubId);
+                    } else {
+                        loge("configPackageName " + configPackageName + " doesn't own grouUuid "
                             + groupUuid);
+                    }
+                } catch (IllegalArgumentException e) {
+                    loge("Invalid Group UUID=" + groupUuidString);
                 }
-            } catch (IllegalArgumentException e) {
-                loge("Invalid Group UUID=" + groupUuidString);
             }
         }
         if (cv.size() > 0 && mContext.getContentResolver().update(SubscriptionManager
diff --git a/src/java/com/android/internal/telephony/cdma/CdmaInboundSmsHandler.java b/src/java/com/android/internal/telephony/cdma/CdmaInboundSmsHandler.java
index ec14efa..5eadd86 100644
--- a/src/java/com/android/internal/telephony/cdma/CdmaInboundSmsHandler.java
+++ b/src/java/com/android/internal/telephony/cdma/CdmaInboundSmsHandler.java
@@ -152,6 +152,12 @@
                 mServiceCategoryProgramHandler.dispatchSmsMessage(sms);
                 return Intents.RESULT_SMS_HANDLED;
 
+            case SmsEnvelope.TELESERVICE_FDEA_WAP:
+                if (!sms.preprocessCdmaFdeaWap()) {
+                    return Intents.RESULT_SMS_HANDLED;
+                }
+                teleService = SmsEnvelope.TELESERVICE_WAP;
+                // fall through
             case SmsEnvelope.TELESERVICE_WAP:
                 // handled below, after storage check
                 break;
diff --git a/src/java/com/android/internal/telephony/dataconnection/DcController.java b/src/java/com/android/internal/telephony/dataconnection/DcController.java
index ed2bc82..5901985 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DcController.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DcController.java
@@ -18,10 +18,8 @@
 
 import android.content.Context;
 import android.hardware.radio.V1_4.DataConnActiveStatus;
-import android.net.INetworkPolicyListener;
 import android.net.LinkAddress;
 import android.net.LinkProperties.CompareResult;
-import android.net.NetworkPolicyManager;
 import android.net.NetworkUtils;
 import android.os.AsyncResult;
 import android.os.Build;
@@ -69,7 +67,6 @@
     private DccDefaultState mDccDefaultState = new DccDefaultState();
 
     final TelephonyManager mTelephonyManager;
-    final NetworkPolicyManager mNetworkPolicyManager;
 
     private PhoneStateListener mPhoneStateListener;
 
@@ -107,8 +104,6 @@
 
         mTelephonyManager = (TelephonyManager) phone.getContext()
                 .getSystemService(Context.TELEPHONY_SERVICE);
-        mNetworkPolicyManager = (NetworkPolicyManager) phone.getContext()
-                .getSystemService(Context.NETWORK_POLICY_SERVICE);
 
         mDcTesterDeactivateAll = (Build.IS_DEBUGGABLE)
                 ? new DcTesterDeactivateAll(mPhone, DcController.this, getHandler())
@@ -173,21 +168,6 @@
         return mExecutingCarrierChange;
     }
 
-    private final INetworkPolicyListener mListener = new NetworkPolicyManager.Listener() {
-        @Override
-        public void onSubscriptionOverride(int subId, int overrideMask, int overrideValue) {
-            if (mPhone == null || mPhone.getSubId() != subId) return;
-
-            final HashMap<Integer, DataConnection> dcListActiveByCid;
-            synchronized (mDcListAll) {
-                dcListActiveByCid = new HashMap<>(mDcListActiveByCid);
-            }
-            for (DataConnection dc : dcListActiveByCid.values()) {
-                dc.onSubscriptionOverride(overrideMask, overrideValue);
-            }
-        }
-    };
-
     private class DccDefaultState extends State {
         @Override
         public void enter() {
@@ -199,10 +179,6 @@
 
             mDataServiceManager.registerForDataCallListChanged(getHandler(),
                     DataConnection.EVENT_DATA_STATE_CHANGED);
-
-            if (mNetworkPolicyManager != null) {
-                mNetworkPolicyManager.registerListener(mListener);
-            }
         }
 
         @Override
@@ -216,9 +192,6 @@
             if (mDcTesterDeactivateAll != null) {
                 mDcTesterDeactivateAll.dispose();
             }
-            if (mNetworkPolicyManager != null) {
-                mNetworkPolicyManager.unregisterListener(mListener);
-            }
         }
 
         @Override
diff --git a/src/java/com/android/internal/telephony/dataconnection/DcTracker.java b/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
index a88b90b..ab3183c 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
@@ -17,6 +17,8 @@
 package com.android.internal.telephony.dataconnection;
 
 import static android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE;
+import static android.net.NetworkPolicyManager.OVERRIDE_UNMETERED;
+import static android.telephony.NetworkRegistrationInfo.NR_STATE_CONNECTED;
 import static android.telephony.TelephonyManager.NETWORK_TYPE_LTE;
 import static android.telephony.TelephonyManager.NETWORK_TYPE_NR;
 
@@ -41,10 +43,12 @@
 import android.database.ContentObserver;
 import android.database.Cursor;
 import android.net.ConnectivityManager;
+import android.net.INetworkPolicyListener;
 import android.net.LinkProperties;
 import android.net.NetworkAgent;
 import android.net.NetworkCapabilities;
 import android.net.NetworkConfig;
+import android.net.NetworkPolicyManager;
 import android.net.NetworkRequest;
 import android.net.ProxyInfo;
 import android.net.TrafficStats;
@@ -78,6 +82,7 @@
 import android.telephony.SubscriptionManager;
 import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
 import android.telephony.TelephonyManager;
+import android.telephony.TelephonyManager.NetworkType;
 import android.telephony.cdma.CdmaCellLocation;
 import android.telephony.data.ApnSetting;
 import android.telephony.data.ApnSetting.ApnType;
@@ -331,6 +336,20 @@
     private final LocalLog mDataRoamingLeakageLog = new LocalLog(50);
     private final LocalLog mApnSettingsInitializationLog = new LocalLog(50);
 
+    /* Default for 5G connection reevaluation alarm durations */
+    private long mHysteresisTimeMs = 0;
+    private long mWatchdogTimeMs = 1000 * 60 * 60;
+
+    /* Used to check whether 5G timers are currently active and waiting to go off */
+    private boolean mHysteresis = false;
+    private boolean mWatchdog = false;
+
+    /* Used to check whether phone was recently connected to 5G. */
+    private boolean m5GWasConnected = false;
+
+    /* Used to keep track of unmetered overrides per network type */
+    private long mUnmeteredOverrideBitMask = 0;
+
     private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver () {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -361,6 +380,17 @@
                 if (mIccRecords.get() != null && mIccRecords.get().getRecordsLoaded()) {
                     setDefaultDataRoamingEnabled();
                 }
+                CarrierConfigManager configManager = (CarrierConfigManager) mPhone.getContext()
+                        .getSystemService(Context.CARRIER_CONFIG_SERVICE);
+                if (configManager != null) {
+                    PersistableBundle b = configManager.getConfigForSubId(mPhone.getSubId());
+                    if (b != null) {
+                        mHysteresisTimeMs = b.getLong(
+                                CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_SEC_INT);
+                        mWatchdogTimeMs = b.getLong(
+                                CarrierConfigManager.KEY_5G_WATCHDOG_TIME_MS_LONG);
+                    }
+                }
             } else {
                 if (DBG) log("onReceive: Unknown action=" + action);
             }
@@ -414,6 +444,23 @@
         }
     };
 
+    private NetworkPolicyManager mNetworkPolicyManager;
+    private final INetworkPolicyListener mNetworkPolicyListener =
+            new NetworkPolicyManager.Listener() {
+        @Override
+        public void onSubscriptionOverride(int subId, int overrideMask, int overrideValue,
+                long networkTypeMask) {
+            if (mPhone == null || mPhone.getSubId() != subId) return;
+
+            if (overrideMask == OVERRIDE_UNMETERED) {
+                mUnmeteredOverrideBitMask = overrideValue == 0 ? 0 : networkTypeMask;
+                reevaluateUnmeteredConnections();
+            } else {
+                overrideDataConnections(overrideMask, overrideValue);
+            }
+        }
+    };
+
     private final SettingsObserver mSettingsObserver;
 
     private void registerSettingsObserver() {
@@ -727,6 +774,9 @@
         mSubscriptionManager = SubscriptionManager.from(mPhone.getContext());
         mSubscriptionManager.addOnSubscriptionsChangedListener(mOnSubscriptionsChangedListener);
 
+        mNetworkPolicyManager = NetworkPolicyManager.from(mPhone.getContext());
+        mNetworkPolicyManager.registerListener(mNetworkPolicyListener);
+
         HandlerThread dcHandlerThread = new HandlerThread("DcHandlerThread");
         dcHandlerThread.start();
         Handler dcHandler = new Handler(dcHandlerThread.getLooper());
@@ -788,6 +838,7 @@
                 DctConstants.EVENT_PS_RESTRICT_DISABLED, null);
         mPhone.getServiceStateTracker().registerForDataRegStateOrRatChanged(mTransportType, this,
                 DctConstants.EVENT_DATA_RAT_CHANGED, null);
+        mPhone.registerForServiceStateChanged(this, DctConstants.EVENT_5G_NETWORK_CHANGED, null);
     }
 
     public void unregisterServiceStateTrackerEvents() {
@@ -799,6 +850,7 @@
         mPhone.getServiceStateTracker().unregisterForPsRestrictedDisabled(this);
         mPhone.getServiceStateTracker().unregisterForDataRegStateOrRatChanged(mTransportType,
                 this);
+        mPhone.unregisterForServiceStateChanged(this);
     }
 
     private void registerForAllEvents() {
@@ -844,6 +896,7 @@
 
         mSubscriptionManager
                 .removeOnSubscriptionsChangedListener(mOnSubscriptionsChangedListener);
+        mNetworkPolicyManager.unregisterListener(mNetworkPolicyListener);
         mDcc.dispose();
         mDcTesterFailBringUpAll.dispose();
 
@@ -896,6 +949,12 @@
         }
     }
 
+    private void overrideDataConnections(int overrideMask, int overrideValue) {
+        for (DataConnection dataConnection : mDataConnections.values()) {
+            dataConnection.onSubscriptionOverride(overrideMask, overrideValue);
+        }
+    }
+
     public long getSubId() {
         return mPhone.getSubId();
     }
@@ -3808,6 +3867,17 @@
             case DctConstants.EVENT_DATA_ENABLED_OVERRIDE_RULES_CHANGED:
                 onDataEnabledOverrideRulesChanged();
                 break;
+            case DctConstants.EVENT_5G_NETWORK_CHANGED:
+                reevaluateUnmeteredConnections();
+                break;
+            case DctConstants.EVENT_5G_TIMER_HYSTERESIS:
+                reevaluateUnmeteredConnections();
+                mHysteresis = false;
+                break;
+            case DctConstants.EVENT_5G_TIMER_WATCHDOG:
+                mWatchdog = false;
+                reevaluateUnmeteredConnections();
+                break;
             default:
                 Rlog.e("DcTracker", "Unhandled event=" + msg);
                 break;
@@ -3966,6 +4036,53 @@
         }
     }
 
+    private void reevaluateUnmeteredConnections() {
+        if (isNetworkTypeUnmetered(NETWORK_TYPE_NR)) {
+            if (mPhone.getServiceState().getNrState() == NR_STATE_CONNECTED) {
+                if (!m5GWasConnected) { //4G -> 5G
+                    stopHysteresisAlarm();
+                    overrideDataConnections(OVERRIDE_UNMETERED, OVERRIDE_UNMETERED);
+                }
+                if (!mWatchdog) {
+                    startWatchdogAlarm();
+                }
+                m5GWasConnected = true;
+            } else {
+                if (m5GWasConnected) { //5G -> 4G
+                    if (!mHysteresis && !startHysteresisAlarm()) {
+                        // hysteresis is not active but carrier does not support hysteresis
+                        stopWatchdogAlarm();
+                        overrideMeterednessForNetworkType(
+                                mTelephonyManager.getNetworkType(mPhone.getSubId()));
+                    }
+                    m5GWasConnected = false;
+                } else { //4G -> 4G
+                    if (!hasMessages(DctConstants.EVENT_5G_TIMER_HYSTERESIS)) {
+                        stopWatchdogAlarm();
+                        overrideMeterednessForNetworkType(
+                                mTelephonyManager.getNetworkType(mPhone.getSubId()));
+                    }
+                    // do nothing if waiting for hysteresis alarm to go off
+                }
+            }
+        } else {
+            stopWatchdogAlarm();
+            stopHysteresisAlarm();
+            overrideMeterednessForNetworkType(mTelephonyManager.getNetworkType(mPhone.getSubId()));
+            m5GWasConnected = false;
+        }
+    }
+
+    private void overrideMeterednessForNetworkType(@NetworkType int networkType) {
+        int overrideValue = isNetworkTypeUnmetered(networkType) ? OVERRIDE_UNMETERED : 0;
+        overrideDataConnections(OVERRIDE_UNMETERED, overrideValue);
+    }
+
+    private boolean isNetworkTypeUnmetered(@NetworkType int networkType) {
+        long networkTypeMask = TelephonyManager.getBitMaskForNetworkType(networkType);
+        return (mUnmeteredOverrideBitMask & networkTypeMask) == networkTypeMask;
+    }
+
     private void log(String s) {
         Rlog.d(mLogTag, s);
     }
@@ -4779,6 +4896,37 @@
         }
     }
 
+    /**
+     * 5G connection reevaluation alarms
+     */
+    private boolean startHysteresisAlarm() {
+        if (mHysteresisTimeMs > 0) {
+            // only create hysteresis alarm if CarrierConfig allows it
+            sendMessageDelayed(obtainMessage(DctConstants.EVENT_5G_TIMER_HYSTERESIS),
+                    mHysteresisTimeMs);
+            mHysteresis = true;
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    private void stopHysteresisAlarm() {
+        removeMessages(DctConstants.EVENT_5G_TIMER_HYSTERESIS);
+        mHysteresis = false;
+    }
+
+    private void startWatchdogAlarm() {
+        sendMessageDelayed(obtainMessage(DctConstants.EVENT_5G_TIMER_WATCHDOG),
+                mWatchdogTimeMs);
+        mWatchdog = true;
+    }
+
+    private void stopWatchdogAlarm() {
+        removeMessages(DctConstants.EVENT_5G_TIMER_WATCHDOG);
+        mWatchdog = false;
+    }
+
     private static DataProfile createDataProfile(ApnSetting apn, boolean isPreferred) {
         return createDataProfile(apn, apn.getProfileId(), isPreferred);
     }
diff --git a/src/java/com/android/internal/telephony/dataconnection/TelephonyNetworkFactory.java b/src/java/com/android/internal/telephony/dataconnection/TelephonyNetworkFactory.java
index d60efaf..382c1d6 100644
--- a/src/java/com/android/internal/telephony/dataconnection/TelephonyNetworkFactory.java
+++ b/src/java/com/android/internal/telephony/dataconnection/TelephonyNetworkFactory.java
@@ -357,6 +357,7 @@
                                 + "connection. Just move the request to transport "
                                 + AccessNetworkConstants.transportTypeToString(targetTransport)
                                 + ", dc=" + dc);
+                        entry.setValue(targetTransport);
                         releaseNetworkInternal(networkRequest, DcTracker.RELEASE_TYPE_NORMAL,
                                 currentTransport);
                         requestNetworkInternal(networkRequest, DcTracker.REQUEST_TYPE_NORMAL,
diff --git a/src/java/com/android/internal/telephony/dataconnection/TransportManager.java b/src/java/com/android/internal/telephony/dataconnection/TransportManager.java
index 43275ec..ff72919 100644
--- a/src/java/com/android/internal/telephony/dataconnection/TransportManager.java
+++ b/src/java/com/android/internal/telephony/dataconnection/TransportManager.java
@@ -208,7 +208,9 @@
         public final @ApnType int apnType;
         public final int targetTransport;
         public final HandoverCallback callback;
-        HandoverParams(int apnType, int targetTransport, HandoverCallback callback) {
+
+        @VisibleForTesting
+        public HandoverParams(int apnType, int targetTransport, HandoverCallback callback) {
             this.apnType = apnType;
             this.targetTransport = targetTransport;
             this.callback = callback;
diff --git a/src/java/com/android/internal/telephony/ims/ImsResolver.java b/src/java/com/android/internal/telephony/ims/ImsResolver.java
index 78e416f..7558071 100644
--- a/src/java/com/android/internal/telephony/ims/ImsResolver.java
+++ b/src/java/com/android/internal/telephony/ims/ImsResolver.java
@@ -41,15 +41,20 @@
 import android.telephony.ims.aidl.IImsRcsFeature;
 import android.telephony.ims.aidl.IImsRegistration;
 import android.telephony.ims.feature.ImsFeature;
+import android.telephony.ims.feature.MmTelFeature;
 import android.telephony.ims.stub.ImsFeatureConfiguration;
 import android.text.TextUtils;
+import android.util.LocalLog;
 import android.util.Log;
 import android.util.SparseArray;
 
 import com.android.ims.internal.IImsServiceFeatureCallback;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.SomeArgs;
+import com.android.internal.util.IndentingPrintWriter;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -169,18 +174,12 @@
 
         @Override
         public String toString() {
-            StringBuilder res = new StringBuilder();
-            res.append("[ImsServiceInfo] name=");
-            res.append(name);
-            res.append(", supportedFeatures=[ ");
-            for (ImsFeatureConfiguration.FeatureSlotPair feature : mSupportedFeatures) {
-                res.append("(");
-                res.append(feature.slotId);
-                res.append(",");
-                res.append(ImsFeature.FEATURE_LOG_MAP.get(feature.featureType));
-                res.append(") ");
-            }
-            return res.toString();
+            return "[ImsServiceInfo] name="
+                    + name
+                    + ", featureFromMetadata="
+                    + featureFromMetadata
+                    + ","
+                    + printFeatures(mSupportedFeatures);
         }
     }
 
@@ -387,6 +386,8 @@
     private final boolean mIsDynamicBinding;
     // Package name of the default device service.
     private String mDeviceService;
+    // Persistent Logging
+    private final LocalLog mEventLog = new LocalLog(50);
 
     // Synchronize all messages on a handler to ensure that the cache includes the most recent
     // version of the installed ImsServices.
@@ -403,6 +404,7 @@
                 break;
             }
             case HANDLER_BOOT_COMPLETE: {
+                mEventLog.log("handling BOOT_COMPLETE");
                 // Re-evaluate bound services for all slots after requerying packagemanager
                 maybeAddedImsService(null);
                 break;
@@ -437,6 +439,7 @@
                     overrideService(slotId, packageName);
                 } else {
                     Log.i(TAG, "overriding device ImsService -  packageName=" + packageName);
+                    mEventLog.log("overriding device ImsService with " + packageName);
                     if (TextUtils.equals(mDeviceService, packageName)) {
                         // No change in device service.
                         break;
@@ -479,12 +482,14 @@
                 @Override
                 public void onError(ComponentName name) {
                     Log.w(TAG, "onError: " + name + "returned with an error result");
+                    mEventLog.log("onError - dynamic query error for " + name);
                     scheduleQueryForFeatures(name, DELAY_DYNAMIC_QUERY_MS);
                 }
 
                 @Override
                 public void onPermanentError(ComponentName name) {
                     Log.w(TAG, "onPermanentError: component=" + name);
+                    mEventLog.log("onPermanentError - error for " + name);
                     mHandler.obtainMessage(HANDLER_REMOVE_PACKAGE,
                             name.getPackageName()).sendToTarget();
                 }
@@ -571,6 +576,7 @@
      * Needs to be called after the constructor to kick off the process of binding to ImsServices.
      */
     public void initialize() {
+        mEventLog.log("Initializing");
         Log.i(TAG, "Initializing cache.");
         mFeatureQueryManager = mDynamicQueryManagerFactory.create(mContext, mDynamicQueryListener);
 
@@ -591,6 +597,7 @@
                 String newPackageName = config.getString(
                         CarrierConfigManager.KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING, null);
                 if (!TextUtils.isEmpty(newPackageName)) {
+                    mEventLog.log("Initializing: found carrier package.");
                     updateBoundCarrierServices(i, newPackageName);
                     Log.i(TAG, "Initializing, found package " + newPackageName + " on slot "
                             + i);
@@ -767,6 +774,8 @@
                 services = new SparseArray<>();
                 mBoundImsServicesByFeature.add(slotId, services);
             }
+            mEventLog.log("putImsController - [" + slotId + ", "
+                    + ImsFeature.FEATURE_LOG_MAP.get(feature) + "] -> " + controller);
             Log.i(TAG, "ImsServiceController added on slot: " + slotId + " with feature: "
                     + ImsFeature.FEATURE_LOG_MAP.get(feature) + " using package: "
                     + controller.getComponentName());
@@ -788,6 +797,8 @@
             }
             ImsServiceController c = services.get(feature, null);
             if (c != null) {
+                mEventLog.log("removeImsController - [" + slotId + ", "
+                        + ImsFeature.FEATURE_LOG_MAP.get(feature) + "] -> " + c);
                 Log.i(TAG, "ImsServiceController removed on slot: " + slotId + " with feature: "
                         + ImsFeature.FEATURE_LOG_MAP.get(feature) + " using package: "
                         + c.getComponentName());
@@ -813,18 +824,23 @@
                 // out the cache for the existing features or update yet. Instead start a query
                 // for features dynamically.
                 if (info.featureFromMetadata) {
-                    // update features in the cache
+                    mEventLog.log("maybeAddedImsService - updating features for " + info.name
+                            + ": " + printFeatures(match.getSupportedFeatures()) + " -> "
+                            + printFeatures(info.getSupportedFeatures()));
                     Log.i(TAG, "Updating features in cached ImsService: " + info.name);
                     Log.d(TAG, "Updating features - Old features: " + match + " new features: "
                             + info);
+                    // update features in the cache
                     match.replaceFeatures(info.getSupportedFeatures());
                     updateImsServiceFeatures(info);
                 } else {
+                    mEventLog.log("maybeAddedImsService - scheduling query for " + info);
                     // start a query to get ImsService features
                     scheduleQueryForFeatures(info);
                 }
             } else {
                 Log.i(TAG, "Adding newly added ImsService to cache: " + info.name);
+                mEventLog.log("maybeAddedImsService - adding new ImsService: " + info);
                 mInstalledServicesCache.put(info.name, info);
                 if (info.featureFromMetadata) {
                     newlyAddedInfos.add(info);
@@ -858,6 +874,7 @@
         ImsServiceInfo match = getInfoByPackageName(mInstalledServicesCache, packageName);
         if (match != null) {
             mInstalledServicesCache.remove(match.name);
+            mEventLog.log("maybeRemovedImsService - removing ImsService: " + match);
             Log.i(TAG, "Removing ImsService: " + match.name);
             unbindImsService(match);
             updateImsServiceFeatures(getImsServiceInfoFromCache(mDeviceService));
@@ -980,6 +997,8 @@
                 Log.i(TAG, "Binding ImsService: " + controller.getComponentName()
                         + " with features: " + features);
                 controller.bind(features);
+                mEventLog.log("bindImsServiceWithFeatures - create new controller: "
+                        + controller);
             }
             mActiveControllers.put(info.name, controller);
         }
@@ -995,6 +1014,7 @@
             // Calls imsServiceFeatureRemoved on all features in the controller
             try {
                 Log.i(TAG, "Unbinding ImsService: " + controller.getComponentName());
+                mEventLog.log("unbindImsService - unbinding and removing " + controller);
                 controller.unbind();
             } catch (RemoteException e) {
                 Log.e(TAG, "unbindImsService: Remote Exception: " + e.getMessage());
@@ -1074,6 +1094,8 @@
         }
         Log.i(TAG, "imsServiceFeaturesChanged: config=" + config.getServiceFeatures()
                 + ", ComponentName=" + controller.getComponentName());
+        mEventLog.log("imsServiceFeaturesChanged - for " + controller + ", new config "
+                + config.getServiceFeatures());
         handleFeaturesChanged(controller.getComponentName(), config.getServiceFeatures());
     }
 
@@ -1083,6 +1105,7 @@
             return;
         }
         Log.w(TAG, "imsServiceBindPermanentError: component=" + name);
+        mEventLog.log("imsServiceBindPermanentError - for " + name);
         mHandler.obtainMessage(HANDLER_REMOVE_PACKAGE, name.getPackageName()).sendToTarget();
     }
 
@@ -1103,6 +1126,8 @@
     // Possibly rebind to another ImsService for testing.
     // Called from the handler ONLY
     private void overrideService(int slotId, String newPackageName) {
+        mEventLog.log("overriding carrier ImsService to " + newPackageName
+                + " on slot " + slotId);
         if (slotId <= SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
             // not specified, replace package on all slots.
             for (int i = 0; i < mNumSlots; i++) {
@@ -1143,6 +1168,8 @@
             mCarrierServices[slotId] = newPackageName;
             if (!TextUtils.equals(newPackageName, oldPackageName)) {
                 Log.i(TAG, "Carrier Config updated, binding new ImsService");
+                mEventLog.log("updateBoundCarrierServices - carrier package changed: "
+                        + oldPackageName + " -> " + newPackageName + " on slot " + slotId);
                 // Unbind old ImsService, not needed anymore
                 // ImsService is retrieved from the cache. If the cache hasn't been populated yet,
                 // the calls to unbind/bind will fail (intended during initial start up).
@@ -1152,12 +1179,16 @@
             // if there is no carrier ImsService, newInfo is null. This we still want to update
             // bindings for device ImsService to pick up the missing features.
             if (newInfo == null || newInfo.featureFromMetadata) {
+                mEventLog.log("updateBoundCarrierServices - recalculating bindings "
+                        + (newInfo != null ? newInfo : "for device"));
                 bindImsService(newInfo);
                 // Recalculate the device ImsService features to reflect changes.
                 updateImsServiceFeatures(getImsServiceInfoFromCache(mDeviceService));
             } else {
                 // ImsServiceInfo that has not had features queried yet. Start async
                 // bind and query features.
+                mEventLog.log("updateBoundCarrierServices - scheduling feature query for "
+                        + newInfo);
                 scheduleQueryForFeatures(newInfo);
             }
         }
@@ -1212,10 +1243,13 @@
 
     // Starts a dynamic query. Called from handler ONLY.
     private void startDynamicQuery(ImsServiceInfo service) {
+        mEventLog.log("startDynamicQuery - starting query for " + service);
         boolean queryStarted = mFeatureQueryManager.startQuery(service.name,
                 service.controllerFactory.getServiceInterface());
         if (!queryStarted) {
             Log.w(TAG, "startDynamicQuery: service could not connect. Retrying after delay.");
+            mEventLog.log("startDynamicQuery - query failed. Retrying in "
+                    + DELAY_DYNAMIC_QUERY_MS + " mS");
             scheduleQueryForFeatures(service, DELAY_DYNAMIC_QUERY_MS);
         } else {
             Log.d(TAG, "startDynamicQuery: Service queried, waiting for response.");
@@ -1231,6 +1265,8 @@
                     + name);
             return;
         }
+        mEventLog.log("dynamicQueryComplete for package " + name + ", features: "
+                + printFeatures(service.getSupportedFeatures()) + " -> " + printFeatures(features));
         // Add features to service
         service.replaceFeatures(features);
         if (isActiveCarrierService(service)) {
@@ -1245,7 +1281,7 @@
         }
     }
 
-    private String printFeatures(Set<ImsFeatureConfiguration.FeatureSlotPair> features) {
+    private static String printFeatures(Set<ImsFeatureConfiguration.FeatureSlotPair> features) {
         StringBuilder featureString = new StringBuilder();
         featureString.append(" features: [");
         if (features != null) {
@@ -1254,7 +1290,7 @@
                 featureString.append(feature.slotId);
                 featureString.append(",");
                 featureString.append(ImsFeature.FEATURE_LOG_MAP.get(feature.featureType));
-                featureString.append("} ");
+                featureString.append("}");
             }
             featureString.append("]");
         }
@@ -1372,4 +1408,55 @@
         }
         return infos;
     }
+
+    // Dump is called on the main thread, since ImsResolver Handler is also handled on main thread,
+    // we shouldn't need to worry about concurrent access of private params.
+    public void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) {
+        IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, "  ");
+        pw.println("ImsResolver:");
+        pw.increaseIndent();
+        pw.println("mIsDynamicBinding = " + mIsDynamicBinding);
+        pw.println("mDeviceService = " + mDeviceService);
+        pw.println("mCarrierServices: ");
+        pw.increaseIndent();
+        for (String s : mCarrierServices) {
+            pw.println(s);
+        }
+        pw.decreaseIndent();
+        pw.println("Bound Features:");
+        pw.increaseIndent();
+        for (int i = 0; i < mNumSlots; i++) {
+            for (int j = 0; j < MmTelFeature.FEATURE_MAX; j++) {
+                pw.print("slot=");
+                pw.print(i);
+                pw.print(", feature=");
+                pw.print(MmTelFeature.FEATURE_LOG_MAP.getOrDefault(j, "?"));
+                pw.println(": ");
+                pw.increaseIndent();
+                ImsServiceController c = getImsServiceController(i, j);
+                pw.println(c == null ? "none" : c);
+                pw.decreaseIndent();
+            }
+        }
+        pw.decreaseIndent();
+        pw.println("Cached ImsServices:");
+        pw.increaseIndent();
+        for (ImsServiceInfo i : mInstalledServicesCache.values()) {
+            pw.println(i);
+        }
+        pw.decreaseIndent();
+        pw.println("Active controllers:");
+        pw.increaseIndent();
+        for (ImsServiceController c : mActiveControllers.values()) {
+            pw.println(c);
+            pw.increaseIndent();
+            c.dump(pw);
+            pw.decreaseIndent();
+        }
+        pw.decreaseIndent();
+        pw.println("Event Log:");
+        pw.increaseIndent();
+        mEventLog.dump(pw);
+        pw.decreaseIndent();
+    }
 }
diff --git a/src/java/com/android/internal/telephony/ims/ImsServiceController.java b/src/java/com/android/internal/telephony/ims/ImsServiceController.java
index f9658bd..dbd434f 100644
--- a/src/java/com/android/internal/telephony/ims/ImsServiceController.java
+++ b/src/java/com/android/internal/telephony/ims/ImsServiceController.java
@@ -36,6 +36,7 @@
 import android.telephony.ims.aidl.IImsServiceController;
 import android.telephony.ims.feature.ImsFeature;
 import android.telephony.ims.stub.ImsFeatureConfiguration;
+import android.util.LocalLog;
 import android.util.Log;
 
 import com.android.ims.internal.IImsFeatureStatusCallback;
@@ -43,6 +44,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.ExponentialBackoff;
 
+import java.io.PrintWriter;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Set;
@@ -73,6 +75,7 @@
                 mIsBound = true;
                 mIsBinding = false;
                 try {
+                    mLocalLog.log("onServiceConnected");
                     Log.d(LOG_TAG, "ImsService(" + name + "): onServiceConnected with binder: "
                             + service);
                     setServiceController(service);
@@ -87,6 +90,8 @@
                     // Remote exception means that the binder already died.
                     cleanupConnection();
                     startDelayedRebindToService();
+                    mLocalLog.log("onConnected exception=" + e.getMessage() + ", retry in "
+                            + mBackoff.getCurrentDelay() + " mS");
                     Log.e(LOG_TAG, "ImsService(" + name + ") RemoteException:"
                             + e.getMessage());
                 }
@@ -99,6 +104,7 @@
                 mIsBinding = false;
             }
             cleanupConnection();
+            mLocalLog.log("onServiceDisconnected");
             Log.w(LOG_TAG, "ImsService(" + name + "): onServiceDisconnected. Waiting...");
             // Service disconnected, but we are still technically bound. Waiting for reconnect.
         }
@@ -114,11 +120,13 @@
             mContext.unbindService(mImsServiceConnection);
             Log.w(LOG_TAG, "ImsService(" + name + "): onBindingDied. Starting rebind...");
             startDelayedRebindToService();
+            mLocalLog.log("onBindingDied, retrying in " + mBackoff.getCurrentDelay() + " mS");
         }
 
         @Override
         public void onNullBinding(ComponentName name) {
             Log.w(LOG_TAG, "ImsService(" + name + "): onNullBinding. Removing.");
+            mLocalLog.log("onNullBinding");
             synchronized (mLock) {
                 mIsBinding = false;
                 mIsBound = false;
@@ -137,16 +145,6 @@
         }
     }
 
-    private ImsService.Listener mFeatureChangedListener = new ImsService.Listener() {
-        @Override
-        public void onUpdateSupportedImsFeatures(ImsFeatureConfiguration c) {
-            if (mCallbacks == null) {
-                return;
-            }
-            mCallbacks.imsServiceFeaturesChanged(c, ImsServiceController.this);
-        }
-    };
-
     /**
      * Defines callbacks that are used by the ImsServiceController to notify when an ImsService
      * has created or removed a new feature as well as the associated ImsServiceController.
@@ -213,10 +211,22 @@
     private Set<IImsServiceFeatureCallback> mImsStatusCallbacks = ConcurrentHashMap.newKeySet();
     // Only added or removed, never accessed on purpose.
     private Set<ImsFeatureStatusCallback> mFeatureStatusCallbacks = new HashSet<>();
+    private final LocalLog mLocalLog = new LocalLog(10);
 
     protected final Object mLock = new Object();
     protected final Context mContext;
 
+    private ImsService.Listener mFeatureChangedListener = new ImsService.Listener() {
+        @Override
+        public void onUpdateSupportedImsFeatures(ImsFeatureConfiguration c) {
+            if (mCallbacks == null) {
+                return;
+            }
+            mLocalLog.log("onUpdateSupportedImsFeatures to " + c.getServiceFeatures());
+            mCallbacks.imsServiceFeaturesChanged(c, ImsServiceController.this);
+        }
+    };
+
     private class ImsFeatureContainer {
         public int slotId;
         public int featureType;
@@ -361,17 +371,22 @@
                 mImsServiceConnection = new ImsServiceConnection();
                 int serviceFlags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
                         | Context.BIND_IMPORTANT;
+                mLocalLog.log("binding " + imsFeatureSet);
                 Log.i(LOG_TAG, "Binding ImsService:" + mComponentName);
                 try {
                     boolean bindSucceeded = startBindToService(imsServiceIntent,
                             mImsServiceConnection, serviceFlags);
                     if (!bindSucceeded) {
+                        mLocalLog.log("    binding failed, retrying in "
+                                + mBackoff.getCurrentDelay() + " mS");
                         mIsBinding = false;
                         mBackoff.notifyFailed();
                     }
                     return bindSucceeded;
                 } catch (Exception e) {
                     mBackoff.notifyFailed();
+                    mLocalLog.log("    binding exception=" + e.getMessage() + ", retrying in "
+                            + mBackoff.getCurrentDelay() + " mS");
                     Log.e(LOG_TAG, "Error binding (" + mComponentName + ") with exception: "
                             + e.getMessage() + ", rebinding in " + mBackoff.getCurrentDelay()
                             + " ms");
@@ -406,6 +421,7 @@
             changeImsServiceFeatures(new HashSet<>());
             removeImsServiceFeatureCallbacks();
             Log.i(LOG_TAG, "Unbinding ImsService: " + mComponentName);
+            mLocalLog.log("unbinding");
             mContext.unbindService(mImsServiceConnection);
             mIsBound = false;
             mIsBinding = false;
@@ -424,6 +440,7 @@
             if (mImsFeatures.equals(newImsFeatures)) {
                 return;
             }
+            mLocalLog.log("Features changed (" + mImsFeatures + "->" + newImsFeatures + ")");
             Log.i(LOG_TAG, "Features changed (" + mImsFeatures + "->" + newImsFeatures + ") for "
                     + "ImsService: " + mComponentName);
             HashSet<ImsFeatureConfiguration.FeatureSlotPair> oldImsFeatures =
@@ -615,6 +632,7 @@
     // Grant runtime permissions to ImsService. PermissionManager ensures that the ImsService is
     // system/signed before granting permissions.
     private void grantPermissionsToService() {
+        mLocalLog.log("grant permissions to " + getComponentName());
         Log.i(LOG_TAG, "Granting Runtime permissions to:" + getComponentName());
         String[] pkgToGrant = {mComponentName.getPackageName()};
         try {
@@ -796,4 +814,18 @@
             setServiceController(null);
         }
     }
+
+    @Override
+    public String toString() {
+        synchronized (mLock) {
+            return "[ImsServiceController: componentName=" + getComponentName() + ", features="
+                    + mImsFeatures + ", isBinding=" + mIsBinding + ", isBound=" + mIsBound
+                    + ", serviceController=" + getImsServiceController() + ", rebindDelay="
+                    + getRebindDelay() + "]";
+        }
+    }
+
+    public void dump(PrintWriter printWriter) {
+        mLocalLog.dump(printWriter);
+    }
 }
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
index 6509be8..9eacadf 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
@@ -1216,6 +1216,7 @@
             logi("Ignoring hold request while already holding or swapping");
             return;
         }
+        HoldSwapState oldHoldState = mHoldSwitchingState;
         ImsCall callToHold = mForegroundCall.getImsCall();
 
         mHoldSwitchingState = HoldSwapState.HOLDING_TO_DIAL_OUTGOING;
@@ -1228,6 +1229,8 @@
                     ImsCommand.IMS_CMD_HOLD);
         } catch (ImsException e) {
             mForegroundCall.switchWith(mBackgroundCall);
+            mHoldSwitchingState = oldHoldState;
+            logHoldSwapState("holdActiveCallForPendingMo - fail");
             throw new CallStateException(e.getMessage());
         }
     }
@@ -1242,6 +1245,7 @@
                 logi("Ignoring hold request while already holding or swapping");
                 return;
             }
+            HoldSwapState oldHoldState = mHoldSwitchingState;
             ImsCall callToHold = mForegroundCall.getImsCall();
             if (mBackgroundCall.getState().isAlive()) {
                 mCallExpectedToResume = mBackgroundCall.getImsCall();
@@ -1257,6 +1261,8 @@
                         ImsCommand.IMS_CMD_HOLD);
             } catch (ImsException e) {
                 mForegroundCall.switchWith(mBackgroundCall);
+                mHoldSwitchingState = oldHoldState;
+                logHoldSwapState("holdActiveCall - fail");
                 throw new CallStateException(e.getMessage());
             }
         }
@@ -1270,6 +1276,7 @@
                 && mRingingCall.getState() == ImsPhoneCall.State.WAITING;
         if (switchingWithWaitingCall) {
             ImsCall callToHold = mForegroundCall.getImsCall();
+            HoldSwapState oldHoldState = mHoldSwitchingState;
             mHoldSwitchingState = HoldSwapState.HOLDING_TO_ANSWER_INCOMING;
             mForegroundCall.switchWith(mBackgroundCall);
             logHoldSwapState("holdActiveCallForWaitingCall");
@@ -1279,6 +1286,8 @@
                         ImsCommand.IMS_CMD_HOLD);
             } catch (ImsException e) {
                 mForegroundCall.switchWith(mBackgroundCall);
+                mHoldSwitchingState = oldHoldState;
+                logHoldSwapState("holdActiveCallForWaitingCall - fail");
                 throw new CallStateException(e.getMessage());
             }
         }
@@ -1288,24 +1297,28 @@
      * Unhold the currently held call.
      */
     void unholdHeldCall() throws CallStateException {
-        try {
-            ImsCall imsCall = mBackgroundCall.getImsCall();
-            if (mHoldSwitchingState == HoldSwapState.PENDING_SINGLE_CALL_UNHOLD
-                    || mHoldSwitchingState == HoldSwapState.SWAPPING_ACTIVE_AND_HELD) {
-                logi("Ignoring unhold request while already unholding or swapping");
-                return;
-            }
-            if (imsCall != null) {
-                mCallExpectedToResume = imsCall;
-                mHoldSwitchingState = HoldSwapState.PENDING_SINGLE_CALL_UNHOLD;
-                mForegroundCall.switchWith(mBackgroundCall);
-                logHoldSwapState("unholdCurrentCall");
+        ImsCall imsCall = mBackgroundCall.getImsCall();
+        if (mHoldSwitchingState == HoldSwapState.PENDING_SINGLE_CALL_UNHOLD
+                || mHoldSwitchingState == HoldSwapState.SWAPPING_ACTIVE_AND_HELD) {
+            logi("Ignoring unhold request while already unholding or swapping");
+            return;
+        }
+        if (imsCall != null) {
+            mCallExpectedToResume = imsCall;
+            HoldSwapState oldHoldState = mHoldSwitchingState;
+            mHoldSwitchingState = HoldSwapState.PENDING_SINGLE_CALL_UNHOLD;
+            mForegroundCall.switchWith(mBackgroundCall);
+            logHoldSwapState("unholdCurrentCall");
+            try {
                 imsCall.resume();
                 mMetrics.writeOnImsCommand(mPhone.getPhoneId(), imsCall.getSession(),
                         ImsCommand.IMS_CMD_RESUME);
+            } catch (ImsException e) {
+                mForegroundCall.switchWith(mBackgroundCall);
+                mHoldSwitchingState = oldHoldState;
+                logHoldSwapState("unholdCurrentCall - fail");
+                throw new CallStateException(e.getMessage());
             }
-        } catch (ImsException e) {
-            throw new CallStateException(e.getMessage());
         }
     }
 
diff --git a/src/java/com/google/android/mms/ContentType.java b/src/java/com/google/android/mms/ContentType.java
index 24b22e8..12e4b7e 100644
--- a/src/java/com/google/android/mms/ContentType.java
+++ b/src/java/com/google/android/mms/ContentType.java
@@ -63,6 +63,7 @@
     public static final String AUDIO_3GPP        = "audio/3gpp";
     public static final String AUDIO_X_WAV       = "audio/x-wav";
     public static final String AUDIO_OGG         = "application/ogg";
+    public static final String AUDIO_OGG2        = "audio/ogg";
 
     public static final String VIDEO_UNSPECIFIED = "video/*";
     public static final String VIDEO_3GPP        = "video/3gpp";
@@ -115,6 +116,7 @@
         sSupportedContentTypes.add(AUDIO_X_WAV);
         sSupportedContentTypes.add(AUDIO_3GPP);
         sSupportedContentTypes.add(AUDIO_OGG);
+        sSupportedContentTypes.add(AUDIO_OGG2);
 
         sSupportedContentTypes.add(VIDEO_3GPP);
         sSupportedContentTypes.add(VIDEO_3G2);
@@ -156,6 +158,7 @@
         sSupportedAudioTypes.add(AUDIO_X_WAV);
         sSupportedAudioTypes.add(AUDIO_3GPP);
         sSupportedAudioTypes.add(AUDIO_OGG);
+        sSupportedAudioTypes.add(AUDIO_OGG2);
 
         // add supported video types
         sSupportedVideoTypes.add(VIDEO_3GPP);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoUpdaterTest.java b/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoUpdaterTest.java
index 0fb1253..806a8de 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoUpdaterTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoUpdaterTest.java
@@ -57,6 +57,7 @@
 import com.android.internal.telephony.euicc.EuiccController;
 import com.android.internal.telephony.uicc.IccFileHandler;
 import com.android.internal.telephony.uicc.IccRecords;
+import com.android.internal.telephony.uicc.IccUtils;
 import com.android.internal.telephony.uicc.UiccSlot;
 
 import org.junit.After;
@@ -67,8 +68,6 @@
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
 
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -768,17 +767,13 @@
     @SmallTest
     public void testUpdateFromCarrierConfigCarrierCertificates() {
         String[] certs = new String[2];
-        certs[0] = "testCertificate";
-        certs[1] = "testCertificate2";
+        certs[0] = "d1f1";
+        certs[1] = "b5d6";
 
         UiccAccessRule[] carrierConfigAccessRules = new UiccAccessRule[certs.length];
-        try {
-            for (int i = 0; i < certs.length; i++) {
-                carrierConfigAccessRules[i] = new UiccAccessRule(
-                    MessageDigest.getInstance("SHA-256").digest(certs[i].getBytes()), null, 0);
-            }
-        } catch (NoSuchAlgorithmException e) {
-            throw new RuntimeException("for setCarrierConfigAccessRules, SHA-256 must exist", e);
+        for (int i = 0; i < certs.length; i++) {
+            carrierConfigAccessRules[i] = new UiccAccessRule(
+                IccUtils.hexStringToBytes(certs[i]), null, 0);
         }
 
         final int phoneId = mPhone.getPhoneId();
diff --git a/tests/telephonytests/src/com/android/internal/telephony/cdma/sms/CdmaSmsTest.java b/tests/telephonytests/src/com/android/internal/telephony/cdma/sms/CdmaSmsTest.java
index a315bde..069b08c 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/cdma/sms/CdmaSmsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/cdma/sms/CdmaSmsTest.java
@@ -22,9 +22,11 @@
 
 import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
 import com.android.internal.telephony.SmsHeader;
+import com.android.internal.telephony.SmsMessageBase;
 import com.android.internal.telephony.cdma.SmsMessage;
 import com.android.internal.util.HexDump;
 
+import java.lang.reflect.Field;
 import java.util.ArrayList;
 import java.util.Arrays;
 
@@ -1033,4 +1035,34 @@
             }
         }
     }
+
+    @SmallTest
+    public void testPreprocessFdeaWdpUserData() throws Exception {
+        // Refer to https://patents.google.com/patent/CN103906005A/en
+        String wdpUserData =
+                "0003156D60018103F80008011F805C26B031230B8383634B1B0BA34B7B717BB3732173BB0B81736B" +
+                "6B996B6B2B9B9B0B3B2805A43D7C246414C212522A3A522BD31AD3931210046C841B43A3A381D179" +
+                "798981719199A1718999B97189897A12522A3A522BD31AD393121004402C081815175C486C018999" +
+                "9989B181C9B99991C80454047011AF78";
+
+        SmsMessage cdmaSmsMessage = new SmsMessage();
+
+        Field field = SmsMessageBase.class.getDeclaredField("mUserData");
+        field.setAccessible(true);
+        field.set(cdmaSmsMessage, HexDump.hexStringToByteArray(wdpUserData));
+
+        BearerData bearerData = new BearerData();
+        bearerData.userData = new UserData();
+
+        field = SmsMessage.class.getDeclaredField("mBearerData");
+        field.setAccessible(true);
+        field.set(cdmaSmsMessage, bearerData);
+        bearerData = (BearerData) field.get(cdmaSmsMessage);
+
+        assertTrue(cdmaSmsMessage.preprocessCdmaFdeaWap());
+        assertEquals(BearerData.MESSAGE_TYPE_DELIVER, bearerData.messageType);
+        assertEquals(0x56D6, bearerData.messageId);
+        assertEquals(0x7F, bearerData.userData.numFields);
+        assertNotNull(bearerData.userData.payload);
+    }
 }
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 2e181f0..ec071ff 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DcTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DcTrackerTest.java
@@ -51,6 +51,7 @@
 import android.net.LinkProperties;
 import android.net.NetworkAgent;
 import android.net.NetworkCapabilities;
+import android.net.NetworkPolicyManager;
 import android.net.NetworkRequest;
 import android.net.Uri;
 import android.os.AsyncResult;
@@ -148,6 +149,8 @@
     PermissionManagerService mMockPermissionManager;
     @Mock
     Handler mHandler;
+    @Mock
+    NetworkPolicyManager mNetworkPolicyManager;
 
     private DcTracker mDct;
     private DcTrackerTestHandler mDcTrackerTestHandler;
@@ -506,7 +509,8 @@
                 }
         ).when(mSubscriptionManager).addOnSubscriptionsChangedListener(any());
         doReturn(mSubscriptionInfo).when(mSubscriptionManager).getActiveSubscriptionInfo(anyInt());
-
+        doReturn(mNetworkPolicyManager).when(mContext)
+                .getSystemService(Context.NETWORK_POLICY_SERVICE);
         doReturn(1).when(mIsub).getDefaultDataSubId();
         doReturn(mIsub).when(mBinder).queryLocalInterface(anyString());
         mServiceManagerMockedServices.put("isub", mBinder);
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 b624679..1b67149 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/TelephonyNetworkFactoryTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/TelephonyNetworkFactoryTest.java
@@ -23,6 +23,8 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
 
 import android.content.Context;
 import android.net.ConnectivityManager;
@@ -30,11 +32,15 @@
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
 import android.net.StringNetworkSpecifier;
+import android.os.AsyncResult;
 import android.os.Binder;
+import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Messenger;
+import android.telephony.AccessNetworkConstants;
 import android.telephony.Rlog;
+import android.telephony.data.ApnSetting;
 import android.test.suitebuilder.annotation.SmallTest;
 
 import androidx.test.filters.FlakyTest;
@@ -43,6 +49,8 @@
 import com.android.internal.telephony.RadioConfig;
 import com.android.internal.telephony.SubscriptionController;
 import com.android.internal.telephony.TelephonyTest;
+import com.android.internal.telephony.dataconnection.TransportManager.HandoverParams;
+import com.android.internal.telephony.dataconnection.TransportManager.HandoverParams.HandoverCallback;
 import com.android.internal.telephony.mocks.ConnectivityServiceMock;
 import com.android.internal.telephony.mocks.PhoneSwitcherMock;
 import com.android.internal.telephony.mocks.SubscriptionControllerMock;
@@ -54,6 +62,7 @@
 import org.junit.Test;
 import org.mockito.Mock;
 
+import java.lang.reflect.Field;
 import java.util.ArrayList;
 
 public class TelephonyNetworkFactoryTest extends TelephonyTest {
@@ -303,4 +312,45 @@
         waitForMs(250);
         assertEquals(3, mNetworkRequestList.size());
     }
+
+    /**
+     * Test handover when there is no live data connection
+     */
+    @Test
+    @SmallTest
+    public void testHandoverNoLiveData() throws Exception {
+        createMockedTelephonyComponents(1);
+        mPhoneSwitcherMock.setPreferredDataPhoneId(0);
+        mSubscriptionControllerMock.setDefaultDataSubId(0);
+        mSubscriptionControllerMock.setSlotSubId(0, 0);
+        mSubscriptionMonitorMock.notifySubscriptionChanged(0);
+
+        mPhoneSwitcherMock.setPhoneActive(0, true);
+        mConnectivityServiceMock.addDefaultRequest();
+
+        makeSubSpecificMmsRequest(0);
+
+        waitForMs(100);
+
+        Field f = TelephonyNetworkFactory.class.getDeclaredField("mInternalHandler");
+        f.setAccessible(true);
+        Handler h = (Handler) f.get(mTelephonyNetworkFactoryUT);
+
+        HandoverCallback handoverCallback = mock(HandoverCallback.class);
+
+        HandoverParams hp = new HandoverParams(ApnSetting.TYPE_MMS,
+                AccessNetworkConstants.TRANSPORT_TYPE_WLAN, handoverCallback);
+        AsyncResult ar = new AsyncResult(null, hp, null);
+        h.sendMessage(h.obtainMessage(5, ar));
+        waitForMs(100);
+
+        doReturn(AccessNetworkConstants.TRANSPORT_TYPE_WLAN).when(mTransportManager)
+                .getCurrentTransport(anyInt());
+
+        hp = new HandoverParams(ApnSetting.TYPE_MMS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
+                handoverCallback);
+        ar = new AsyncResult(null, hp, null);
+        h.sendMessage(h.obtainMessage(5, ar));
+        waitForMs(100);
+    }
 }
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 32afa2f..a72bc82 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneCallTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneCallTrackerTest.java
@@ -391,6 +391,31 @@
 
     @Test
     @SmallTest
+    public void testImsHoldException() throws Exception {
+        testImsMTCallAccept();
+        doThrow(new ImsException()).when(mImsCall).hold();
+        try {
+            mCTUT.holdActiveCall();
+            Assert.fail("No exception thrown");
+        } catch (Exception e) {
+            // expected
+            verify(mImsCall).hold();
+        }
+
+        // After the first hold exception, try holding (successfully) again to make sure that it
+        // goes through
+        doNothing().when(mImsCall).hold();
+        try {
+            mCTUT.holdActiveCall();
+            verify(mImsCall, times(2)).hold();
+        } catch (Exception ex) {
+            ex.printStackTrace();
+            Assert.fail("unexpected exception thrown" + ex.getMessage());
+        }
+    }
+
+    @Test
+    @SmallTest
     public void testImsMTCallReject() {
         testImsMTCall();
         assertTrue(mCTUT.mRingingCall.isRinging());