Merge branch 'dev/10/fp2/security-aosp-qt-release' into int/10/fp2

* dev/10/fp2/security-aosp-qt-release:
  Enforce privileged phone state for getSubscriptionProperty(GROUP_UUID)

Change-Id: I3ec92d0dea239bcfbb0535fde584acfc6ce3b155
diff --git a/Android.bp b/Android.bp
index d1a8937..23dc66a 100644
--- a/Android.bp
+++ b/Android.bp
@@ -58,6 +58,7 @@
         "voip-common",
         "ims-common",
         "services",
+        "telephony-ext",
     ],
     static_libs: [
         "android.hardware.radio.config-V1.0-java-shallow",
diff --git a/proto/src/telephony.proto b/proto/src/telephony.proto
index e9a564e..e4ce721 100644
--- a/proto/src/telephony.proto
+++ b/proto/src/telephony.proto
@@ -242,6 +242,17 @@
     optional string numeric = 3;
   }
 
+  message NetworkRegistrationInfo {
+    // Network domain
+    optional Domain domain = 1;
+
+    // Network transport
+    optional Transport transport = 2;
+
+    // Radio access technology
+    optional RadioAccessTechnology rat = 3;
+  }
+
   // Roaming type
   enum RoamingType {
 
@@ -262,6 +273,29 @@
     ROAMING_TYPE_INTERNATIONAL = 3;
   }
 
+  // Domain type
+  enum Domain {
+    // Unknown
+    DOMAIN_UNKNOWN = 0;
+
+    // Circuit switching domain
+    DOMAIN_CS = 1;
+
+    // Packet switching domain
+    DOMAIN_PS = 2;
+  }
+
+  enum Transport {
+    // Unknown
+    TRANSPORT_UNKNOWN = 0;
+
+    // Transport type for Wireless Wide Area Networks (i.e. Cellular)
+    TRANSPORT_WWAN = 1;
+
+    // Transport type for Wireless Local Area Networks (i.e. Wifi)
+    TRANSPORT_WLAN = 2;
+  }
+
   // Current registered operator
   optional TelephonyOperator voice_operator = 1;
 
@@ -282,6 +316,9 @@
 
   // Current Channel Number
   optional int32 channel_number = 7;
+
+  // Network registration info
+  repeated NetworkRegistrationInfo networkRegistrationInfo = 10;
 }
 
 // Radio access families
@@ -331,6 +368,10 @@
   RAT_IWLAN = 18;
 
   RAT_LTE_CA = 19;
+
+  RAT_NR_NSA = 20;
+
+  RAT_NR_SA = 21;
 }
 
 // The information about IMS errors
@@ -745,6 +786,9 @@
 
     // Emergency Number update event (Device HAL >= 1.4).
     EMERGENCY_NUMBER_REPORT = 21;
+
+    // Signal strength
+    SIGNAL_STRENGTH = 23;
   }
 
   enum ApnType {
@@ -1719,6 +1763,9 @@
 
   // Updated Emergency Call info.
   optional EmergencyNumberInfo updated_emergency_number = 25;
+
+  // Signal strength
+  optional int32 signal_strength = 27;
 }
 
 message ActiveSubscriptionInfo {
diff --git a/src/java/com/android/internal/telephony/BaseCommands.java b/src/java/com/android/internal/telephony/BaseCommands.java
index f72fdcd..4f29316 100644
--- a/src/java/com/android/internal/telephony/BaseCommands.java
+++ b/src/java/com/android/internal/telephony/BaseCommands.java
@@ -939,8 +939,7 @@
         return mRilVersion;
     }
 
-    public void setUiccSubscription(int slotId, int appIndex, int subId, int subStatus,
-            Message response) {
+    public void setUiccSubscription(int appIndex, boolean activate, Message response) {
     }
 
     public void setDataAllowed(boolean allowed, Message response) {
diff --git a/src/java/com/android/internal/telephony/CarrierServiceBindHelper.java b/src/java/com/android/internal/telephony/CarrierServiceBindHelper.java
index 7f6887d..c085a5f 100644
--- a/src/java/com/android/internal/telephony/CarrierServiceBindHelper.java
+++ b/src/java/com/android/internal/telephony/CarrierServiceBindHelper.java
@@ -63,6 +63,9 @@
     private String[] mLastSimState;
     private final PackageMonitor mPackageMonitor = new CarrierServicePackageMonitor();
 
+    // 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) {
@@ -235,6 +238,7 @@
                 if (mContext.bindServiceAsUser(carrierService, connection,
                         Context.BIND_AUTO_CREATE |  Context.BIND_FOREGROUND_SERVICE,
                         mHandler, Process.myUserHandle())) {
+                    mServiceBound = true;
                     return;
                 }
 
@@ -289,8 +293,13 @@
             carrierServiceClass = null;
 
             // Actually unbind
-            log("Unbinding from carrier app");
-            mContext.unbindService(connection);
+            if (mServiceBound) {
+                log("Unbinding from carrier app");
+                mServiceBound = false;
+                mContext.unbindService(connection);
+            } else {
+                log("Not bound, skipping unbindService call");
+            }
             connection = null;
             mUnbindScheduledUptimeMillis = -1;
         }
diff --git a/src/java/com/android/internal/telephony/CellBroadcastHandler.java b/src/java/com/android/internal/telephony/CellBroadcastHandler.java
index cd89cd9..50f3361 100644
--- a/src/java/com/android/internal/telephony/CellBroadcastHandler.java
+++ b/src/java/com/android/internal/telephony/CellBroadcastHandler.java
@@ -16,36 +16,60 @@
 
 package com.android.internal.telephony;
 
+import static android.content.PermissionChecker.PERMISSION_GRANTED;
 import static android.provider.Settings.Secure.CMAS_ADDITIONAL_BROADCAST_PKG;
 
 import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.AppOpsManager;
+import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
+import android.content.PermissionChecker;
+import android.location.Location;
+import android.location.LocationListener;
+import android.location.LocationManager;
+import android.net.Uri;
 import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
 import android.os.Message;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.provider.Telephony;
+import android.provider.Telephony.CellBroadcasts;
 import android.telephony.SmsCbMessage;
 import android.telephony.SubscriptionManager;
+import android.text.format.DateUtils;
 import android.util.LocalLog;
+import android.util.Log;
 
+import com.android.internal.telephony.CbGeoUtils.Geometry;
+import com.android.internal.telephony.CbGeoUtils.LatLng;
 import com.android.internal.telephony.metrics.TelephonyMetrics;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
 
 /**
  * Dispatch new Cell Broadcasts to receivers. Acquires a private wakelock until the broadcast
  * completes and our result receiver is called.
  */
 public class CellBroadcastHandler extends WakeLockStateMachine {
+    private static final String EXTRA_MESSAGE = "message";
 
     private final LocalLog mLocalLog = new LocalLog(100);
 
-    private static final String EXTRA_MESSAGE = "message";
+    protected static final Uri CELL_BROADCAST_URI = Uri.parse("content://cellbroadcasts_fwk");
+
+    /** Uses to request the location update. */
+    public final LocationRequester mLocationRequester;
 
     private CellBroadcastHandler(Context context, Phone phone) {
         this("CellBroadcastHandler", context, phone);
@@ -53,6 +77,10 @@
 
     protected CellBroadcastHandler(String debugTag, Context context, Phone phone) {
         super(debugTag, context, phone);
+        mLocationRequester = new LocationRequester(
+                context,
+                (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE),
+                getHandler().getLooper());
     }
 
     /**
@@ -71,7 +99,7 @@
      * 3GPP-format Cell Broadcast messages sent from radio are handled in the subclass.
      *
      * @param message the message to process
-     * @return true if an ordered broadcast was sent; false on failure
+     * @return true if need to wait for geo-fencing or an ordered broadcast was sent.
      */
     @Override
     protected boolean handleSmsMessage(Message message) {
@@ -89,9 +117,6 @@
      * @param message the Cell Broadcast to broadcast
      */
     protected void handleBroadcastSms(SmsCbMessage message) {
-        String receiverPermission;
-        int appOp;
-
         // Log Cellbroadcast msg received event
         TelephonyMetrics metrics = TelephonyMetrics.getInstance();
         metrics.writeNewCBSms(mPhone.getPhoneId(), message.getMessageFormat(),
@@ -99,6 +124,96 @@
                 message.getServiceCategory(), message.getSerialNumber(),
                 System.currentTimeMillis());
 
+        // TODO: Database inserting can be time consuming, therefore this should be changed to
+        // asynchronous.
+        ContentValues cv = message.getContentValues();
+        Uri uri = mContext.getContentResolver().insert(CELL_BROADCAST_URI, cv);
+
+        if (message.needGeoFencingCheck()) {
+            if (DBG) {
+                log("Request location update for geo-fencing. serialNumber = "
+                        + message.getSerialNumber());
+            }
+
+            requestLocationUpdate(location -> {
+                if (location == null) {
+                    // Broadcast the message directly if the location is not available.
+                    broadcastMessage(message, uri);
+                } else {
+                    performGeoFencing(message, uri, message.getGeometries(), location);
+                }
+            }, message.getMaximumWaitingTime());
+        } else {
+            if (DBG) {
+                log("Broadcast the message directly because no geo-fencing required, "
+                        + "serialNumber = " + message.getSerialNumber()
+                        + " needGeoFencing = " + message.needGeoFencingCheck());
+            }
+            broadcastMessage(message, uri);
+        }
+    }
+
+    /**
+     * Perform a geo-fencing check for {@code message}. Broadcast the {@code message} if the
+     * {@code location} is inside the {@code broadcastArea}.
+     * @param message the message need to geo-fencing check
+     * @param uri the message's uri
+     * @param broadcastArea the broadcast area of the message
+     * @param location current location
+     */
+    protected void performGeoFencing(SmsCbMessage message, Uri uri, List<Geometry> broadcastArea,
+            LatLng location) {
+
+        if (DBG) {
+            logd("Perform geo-fencing check for message identifier = "
+                    + message.getServiceCategory()
+                    + " serialNumber = " + message.getSerialNumber());
+        }
+
+        for (Geometry geo : broadcastArea) {
+            if (geo.contains(location)) {
+                broadcastMessage(message, uri);
+                return;
+            }
+        }
+
+        if (DBG) {
+            logd("Device location is outside the broadcast area "
+                    + CbGeoUtils.encodeGeometriesToString(broadcastArea));
+        }
+
+        sendMessage(EVENT_BROADCAST_NOT_REQUIRED);
+    }
+
+    /**
+     * Request a single location update.
+     * @param callback a callback will be called when the location is available.
+     * @param maximumWaitTimeSec the maximum wait time of this request. If location is not updated
+     * within the maximum wait time, {@code callback#onLocationUpadte(null)} will be called.
+     */
+    protected void requestLocationUpdate(LocationUpdateCallback callback, int maximumWaitTimeSec) {
+        mLocationRequester.requestLocationUpdate(callback, maximumWaitTimeSec);
+    }
+
+    /**
+     * Broadcast a list of cell broadcast messages.
+     * @param cbMessages a list of cell broadcast message.
+     * @param cbMessageUris the corresponding {@link Uri} of the cell broadcast messages.
+     */
+    protected void broadcastMessage(List<SmsCbMessage> cbMessages, List<Uri> cbMessageUris) {
+        for (int i = 0; i < cbMessages.size(); i++) {
+            broadcastMessage(cbMessages.get(i), cbMessageUris.get(i));
+        }
+    }
+
+    /**
+     * Broadcast the {@code message} to the applications.
+     * @param message a message need to broadcast
+     * @param messageUri message's uri
+     */
+    protected void broadcastMessage(@NonNull SmsCbMessage message, @Nullable Uri messageUri) {
+        String receiverPermission;
+        int appOp;
         String msg;
         Intent intent;
         if (message.isEmergencyMessage()) {
@@ -155,6 +270,13 @@
             mContext.sendOrderedBroadcastAsUser(intent, UserHandle.ALL, receiverPermission, appOp,
                     mReceiver, getHandler(), Activity.RESULT_OK, null, null);
         }
+
+        if (messageUri != null) {
+            ContentValues cv = new ContentValues();
+            cv.put(CellBroadcasts.MESSAGE_BROADCASTED, 1);
+            mContext.getContentResolver().update(CELL_BROADCAST_URI, cv,
+                    CellBroadcasts._ID + "=?", new String[] {messageUri.getLastPathSegment()});
+        }
     }
 
     @Override
@@ -163,4 +285,167 @@
         mLocalLog.dump(fd, pw, args);
         pw.flush();
     }
+
+    /** The callback interface of a location request. */
+    public interface LocationUpdateCallback {
+        /**
+         * Call when the location update is available.
+         * @param location a location in (latitude, longitude) format, or {@code null} if the
+         * location service is not available.
+         */
+        void onLocationUpdate(@Nullable LatLng location);
+    }
+
+    private static final class LocationRequester {
+        private static final String TAG = CellBroadcastHandler.class.getSimpleName();
+
+        /**
+         * Use as the default maximum wait time if the cell broadcast doesn't specify the value.
+         * Most of the location request should be responded within 30 seconds.
+         */
+        private static final int DEFAULT_MAXIMUM_WAIT_TIME_SEC = 30;
+
+        /**
+         * Trigger this event when the {@link LocationManager} is not responded within the given
+         * time.
+         */
+        private static final int EVENT_LOCATION_REQUEST_TIMEOUT = 1;
+
+        /** Request a single location update. */
+        private static final int EVENT_REQUEST_LOCATION_UPDATE = 2;
+
+        /**
+         * Request location update from network or gps location provider. Network provider will be
+         * used if available, otherwise use the gps provider.
+         */
+        private static final List<String> LOCATION_PROVIDERS = Arrays.asList(
+                LocationManager.NETWORK_PROVIDER, LocationManager.GPS_PROVIDER);
+
+        private final LocationManager mLocationManager;
+        private final Looper mLooper;
+        private final List<LocationUpdateCallback> mCallbacks;
+        private final Context mContext;
+        private Handler mLocationHandler;
+
+        LocationRequester(Context context, LocationManager locationManager, Looper looper) {
+            mLocationManager = locationManager;
+            mLooper = looper;
+            mCallbacks = new ArrayList<>();
+            mContext = context;
+            mLocationHandler = new LocationHandler(looper);
+        }
+
+        /**
+         * Request a single location update. If the location is not available, a callback with
+         * {@code null} location will be called immediately.
+         *
+         * @param callback a callback to the the response when the location is available
+         * @param maximumWaitTimeSec the maximum wait time of this request. If location is not
+         * updated within the maximum wait time, {@code callback#onLocationUpadte(null)} will be
+         * called.
+         */
+        void requestLocationUpdate(@NonNull LocationUpdateCallback callback,
+                int maximumWaitTimeSec) {
+            mLocationHandler.obtainMessage(EVENT_REQUEST_LOCATION_UPDATE, maximumWaitTimeSec,
+                    0 /* arg2 */, callback).sendToTarget();
+        }
+
+        private void onLocationUpdate(@Nullable LatLng location) {
+            for (LocationUpdateCallback callback : mCallbacks) {
+                callback.onLocationUpdate(location);
+            }
+            mCallbacks.clear();
+
+            for (LocationListener listener : mLocationListenerList) {
+                mLocationManager.removeUpdates(listener);
+            }
+            mLocationListenerList.clear();
+        }
+
+        private void requestLocationUpdateInternal(@NonNull LocationUpdateCallback callback,
+                int maximumWaitTimeSec) {
+            if (DBG) Log.d(TAG, "requestLocationUpdate");
+            if (!isLocationServiceAvailable()) {
+                if (DBG) {
+                    Log.d(TAG, "Can't request location update because of no location permission");
+                }
+                callback.onLocationUpdate(null);
+                return;
+            }
+
+            if (!mLocationHandler.hasMessages(EVENT_LOCATION_REQUEST_TIMEOUT)) {
+                if (maximumWaitTimeSec == SmsCbMessage.MAXIMUM_WAIT_TIME_NOT_SET) {
+                    maximumWaitTimeSec = DEFAULT_MAXIMUM_WAIT_TIME_SEC;
+                }
+                mLocationHandler.sendMessageDelayed(
+                        mLocationHandler.obtainMessage(EVENT_LOCATION_REQUEST_TIMEOUT),
+                        maximumWaitTimeSec * DateUtils.SECOND_IN_MILLIS);
+            }
+
+            mCallbacks.add(callback);
+
+            for (String provider : LOCATION_PROVIDERS) {
+                if (mLocationManager.isProviderEnabled(provider)) {
+                    LocationListener listener = new LocationListener() {
+                        @Override
+                        public void onLocationChanged(Location location) {
+                            mLocationListenerList.remove(this);
+                            mLocationHandler.removeMessages(EVENT_LOCATION_REQUEST_TIMEOUT);
+                            onLocationUpdate(new LatLng(location.getLatitude(),
+                                    location.getLongitude()));
+                        }
+
+                        @Override
+                        public void onStatusChanged(String provider, int status, Bundle extras) {}
+
+                        @Override
+                        public void onProviderEnabled(String provider) {}
+
+                        @Override
+                        public void onProviderDisabled(String provider) {}
+                    };
+                    mLocationListenerList.add(listener);
+                    Log.d(TAG, "Request location single update from " + provider);
+                    mLocationManager.requestSingleUpdate(provider, listener, mLooper);
+                }
+            }
+        }
+
+        private boolean isLocationServiceAvailable() {
+            if (!hasPermission(Manifest.permission.ACCESS_FINE_LOCATION)
+                    && !hasPermission(Manifest.permission.ACCESS_COARSE_LOCATION)) return false;
+            for (String provider : LOCATION_PROVIDERS) {
+                if (mLocationManager.isProviderEnabled(provider)) return true;
+            }
+            return false;
+        }
+
+        private boolean hasPermission(String permission) {
+            return PermissionChecker.checkCallingOrSelfPermissionForDataDelivery(mContext,
+                    permission) == PERMISSION_GRANTED;
+        }
+
+        private final List<LocationListener> mLocationListenerList = new ArrayList<>();
+
+        private final class LocationHandler extends Handler {
+            LocationHandler(Looper looper) {
+                super(looper);
+            }
+
+            @Override
+            public void handleMessage(Message msg) {
+                switch (msg.what) {
+                    case EVENT_LOCATION_REQUEST_TIMEOUT:
+                        if (DBG) Log.d(TAG, "location request timeout");
+                        onLocationUpdate(null);
+                        break;
+                    case EVENT_REQUEST_LOCATION_UPDATE:
+                        requestLocationUpdateInternal((LocationUpdateCallback) msg.obj, msg.arg1);
+                        break;
+                    default:
+                        Log.e(TAG, "Unsupported message type " + msg.what);
+                }
+            }
+        }
+    }
 }
diff --git a/src/java/com/android/internal/telephony/CellularNetworkValidator.java b/src/java/com/android/internal/telephony/CellularNetworkValidator.java
index 8168371..7f84cfe 100644
--- a/src/java/com/android/internal/telephony/CellularNetworkValidator.java
+++ b/src/java/com/android/internal/telephony/CellularNetworkValidator.java
@@ -16,18 +16,35 @@
 
 package com.android.internal.telephony;
 
+import static android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
+import static android.telephony.CarrierConfigManager.KEY_DATA_SWITCH_VALIDATION_MIN_GAP_LONG;
+import static android.telephony.NetworkRegistrationInfo.DOMAIN_PS;
+
 import android.content.Context;
 import android.net.ConnectivityManager;
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
 import android.os.Handler;
+import android.os.PersistableBundle;
+import android.telephony.CarrierConfigManager;
+import android.telephony.CellIdentity;
+import android.telephony.CellIdentityLte;
+import android.telephony.CellInfo;
+import android.telephony.NetworkRegistrationInfo;
 import android.telephony.SubscriptionManager;
 import android.util.Log;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.metrics.TelephonyMetrics;
 import com.android.internal.telephony.nano.TelephonyProto.TelephonyEvent;
 
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.PriorityQueue;
+import java.util.concurrent.TimeUnit;
+
 /**
  * This class will validate whether cellular network verified by Connectivity's
  * validation process. It listens request on a specific subId, sends a network request
@@ -35,6 +52,9 @@
  */
 public class CellularNetworkValidator {
     private static final String LOG_TAG = "NetworkValidator";
+    // If true, upon validated network cache hit, we report validationDone only when
+    // network becomes available. Otherwise, we report validationDone immediately.
+    private static boolean sWaitForNetworkAvailableWhenCacheHit = false;
 
     // States of validator. Only one validation can happen at once.
     // IDLE: no validation going on.
@@ -48,18 +68,139 @@
 
     // Singleton instance.
     private static CellularNetworkValidator sInstance;
+    @VisibleForTesting
+    public static final long MAX_VALIDATION_CACHE_TTL = TimeUnit.DAYS.toMillis(1);
 
     private int mState = STATE_IDLE;
     private int mSubId;
-    private int mTimeoutInMs;
+    private long mTimeoutInMs;
     private boolean mReleaseAfterValidation;
 
     private NetworkRequest mNetworkRequest;
     private ValidationCallback mValidationCallback;
     private Context mContext;
     private ConnectivityManager mConnectivityManager;
-    private Handler mHandler = new Handler();
-    private ConnectivityNetworkCallback mNetworkCallback;
+    @VisibleForTesting
+    public Handler mHandler = new Handler();
+    @VisibleForTesting
+    public ConnectivityNetworkCallback mNetworkCallback;
+    @VisibleForTesting
+    public Runnable mTimeoutCallback;
+    private final ValidatedNetworkCache mValidatedNetworkCache = new ValidatedNetworkCache();
+
+    private class ValidatedNetworkCache {
+        // A cache with fixed size. It remembers 10 most recently successfully validated networks.
+        private static final int VALIDATED_NETWORK_CACHE_SIZE = 10;
+        private final PriorityQueue<ValidatedNetwork> mValidatedNetworkPQ =
+                new PriorityQueue((Comparator<ValidatedNetwork>) (n1, n2) -> {
+                    if (n1.mValidationTimeStamp < n2.mValidationTimeStamp) {
+                        return -1;
+                    } else if (n1.mValidationTimeStamp > n2.mValidationTimeStamp) {
+                        return 1;
+                    } else {
+                        return 0;
+                    }
+                });
+        private final Map<String, ValidatedNetwork> mValidatedNetworkMap = new HashMap();
+
+        private final class ValidatedNetwork {
+            ValidatedNetwork(String identity, long timeStamp) {
+                mValidationIdentity = identity;
+                mValidationTimeStamp = timeStamp;
+            }
+            void update(long timeStamp) {
+                mValidationTimeStamp = timeStamp;
+            }
+            final String mValidationIdentity;
+            long mValidationTimeStamp;
+        }
+
+        boolean isRecentlyValidated(int subId) {
+            long cacheTtl = getValidationCacheTtl(subId);
+            if (cacheTtl == 0) return false;
+
+            String networkIdentity = getValidationNetworkIdentity(subId);
+            if (networkIdentity == null || !mValidatedNetworkMap.containsKey(networkIdentity)) {
+                return false;
+            }
+            long validatedTime = mValidatedNetworkMap.get(networkIdentity).mValidationTimeStamp;
+            boolean recentlyValidated = System.currentTimeMillis() - validatedTime < cacheTtl;
+            logd("isRecentlyValidated on subId " + subId + " ? " + recentlyValidated);
+            return recentlyValidated;
+        }
+
+        void storeLastValidationResult(int subId, boolean validated) {
+            if (getValidationCacheTtl(subId) == 0) return;
+
+            String networkIdentity = getValidationNetworkIdentity(subId);
+            logd("storeLastValidationResult for subId " + subId
+                    + (validated ? " validated." : " not validated."));
+            if (networkIdentity == null) return;
+
+            if (!validated) {
+                // If validation failed, clear it from the cache.
+                mValidatedNetworkPQ.remove(mValidatedNetworkMap.get(networkIdentity));
+                mValidatedNetworkMap.remove(networkIdentity);
+                return;
+            }
+            long time =  System.currentTimeMillis();
+            ValidatedNetwork network = mValidatedNetworkMap.get(networkIdentity);
+            if (network != null) {
+                // Already existed in cache, update.
+                network.update(time);
+                // Re-add to re-sort.
+                mValidatedNetworkPQ.remove(network);
+                mValidatedNetworkPQ.add(network);
+            } else {
+                network = new ValidatedNetwork(networkIdentity, time);
+                mValidatedNetworkMap.put(networkIdentity, network);
+                mValidatedNetworkPQ.add(network);
+            }
+            // If exceeded max size, remove the one with smallest validation timestamp.
+            if (mValidatedNetworkPQ.size() > VALIDATED_NETWORK_CACHE_SIZE) {
+                ValidatedNetwork networkToRemove = mValidatedNetworkPQ.poll();
+                mValidatedNetworkMap.remove(networkToRemove.mValidationIdentity);
+            }
+        }
+
+        private String getValidationNetworkIdentity(int subId) {
+            if (!SubscriptionManager.isUsableSubscriptionId(subId)) return null;
+            Phone phone = PhoneFactory.getPhone(SubscriptionController.getInstance()
+                    .getPhoneId(subId));
+            if (phone == null) return null;
+
+            if (phone.getServiceState() == null) return null;
+
+            NetworkRegistrationInfo regInfo = phone.getServiceState().getNetworkRegistrationInfo(
+                    DOMAIN_PS, TRANSPORT_TYPE_WWAN);
+            if (regInfo == null || regInfo.getCellIdentity() == null) return null;
+
+            CellIdentity cellIdentity = regInfo.getCellIdentity();
+            // TODO: add support for other technologies.
+            if (cellIdentity.getType() != CellInfo.TYPE_LTE
+                    || cellIdentity.getMccString() == null || cellIdentity.getMncString() == null
+                    || ((CellIdentityLte) cellIdentity).getTac() == CellInfo.UNAVAILABLE) {
+                return null;
+            }
+
+            return cellIdentity.getMccString() + cellIdentity.getMncString() + "_"
+                    + ((CellIdentityLte) cellIdentity).getTac() + "_" + subId;
+        }
+
+        private long getValidationCacheTtl(int subId) {
+            long ttl = 0;
+            CarrierConfigManager configManager = (CarrierConfigManager)
+                    mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
+            if (configManager != null) {
+                PersistableBundle b = configManager.getConfigForSubId(subId);
+                if (b != null) {
+                    ttl = b.getLong(KEY_DATA_SWITCH_VALIDATION_MIN_GAP_LONG);
+                }
+            }
+            // Ttl can't be bigger than one day for now.
+            return Math.min(ttl, MAX_VALIDATION_CACHE_TTL);
+        }
+    }
 
     /**
      * Callback to pass in when starting validation.
@@ -99,7 +240,8 @@
                 .validationBeforeSwitchSupported;
     }
 
-    private CellularNetworkValidator(Context context) {
+    @VisibleForTesting
+    public CellularNetworkValidator(Context context) {
         mContext = context;
         mConnectivityManager = (ConnectivityManager)
                 mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
@@ -108,13 +250,12 @@
     /**
      * API to start a validation
      */
-    public synchronized void validate(int subId, int timeoutInMs,
+    public synchronized void validate(int subId, long timeoutInMs,
             boolean releaseAfterValidation, ValidationCallback callback) {
         // If it's already validating the same subscription, do nothing.
         if (subId == mSubId) return;
 
-        Phone phone = PhoneFactory.getPhone(SubscriptionManager.getPhoneId(subId));
-        if (phone == null) {
+        if (!SubscriptionController.getInstance().isActiveSubId(subId)) {
             logd("Failed to start validation. Inactive subId " + subId);
             callback.onValidationResult(false, subId);
             return;
@@ -124,6 +265,12 @@
             stopValidation();
         }
 
+        if (!sWaitForNetworkAvailableWhenCacheHit && mValidatedNetworkCache
+                .isRecentlyValidated(subId)) {
+            callback.onValidationResult(true, subId);
+            return;
+        }
+
         mState = STATE_VALIDATING;
         mSubId = subId;
         mTimeoutInMs = timeoutInMs;
@@ -136,8 +283,24 @@
 
         mNetworkCallback = new ConnectivityNetworkCallback(subId);
 
-        mConnectivityManager.requestNetwork(
-                mNetworkRequest, mNetworkCallback, mHandler, mTimeoutInMs);
+        mConnectivityManager.requestNetwork(mNetworkRequest, mNetworkCallback, mHandler);
+
+        mTimeoutCallback = () -> {
+            logd("timeout on subId " + subId + " validation.");
+            // Remember latest validated network.
+            mValidatedNetworkCache.storeLastValidationResult(subId, false);
+            reportValidationResult(false, subId);
+        };
+
+        mHandler.postDelayed(mTimeoutCallback, mTimeoutInMs);
+    }
+
+    private void removeTimeoutCallback() {
+        // Remove timeout callback.
+        if (mTimeoutCallback != null) {
+            mHandler.removeCallbacks(mTimeoutCallback);
+            mTimeoutCallback = null;
+        }
     }
 
     /**
@@ -150,6 +313,8 @@
             mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
             mState = STATE_IDLE;
         }
+
+        removeTimeoutCallback();
         mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
     }
 
@@ -179,6 +344,8 @@
         // If the validation result is not for current subId, do nothing.
         if (mSubId != subId) return;
 
+        removeTimeoutCallback();
+
         // Deal with the result only when state is still VALIDATING. This is to avoid
         // receiving multiple callbacks in queue.
         if (mState == STATE_VALIDATING) {
@@ -198,7 +365,8 @@
         mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
     }
 
-    class ConnectivityNetworkCallback extends ConnectivityManager.NetworkCallback {
+    @VisibleForTesting
+    public class ConnectivityNetworkCallback extends ConnectivityManager.NetworkCallback {
         private final int mSubId;
 
         ConnectivityNetworkCallback(int subId) {
@@ -210,27 +378,37 @@
         @Override
         public void onAvailable(Network network) {
             logd("network onAvailable " + network);
-            if (ConnectivityNetworkCallback.this.mSubId == CellularNetworkValidator.this.mSubId) {
-                TelephonyMetrics.getInstance().writeNetworkValidate(
-                        TelephonyEvent.NetworkValidationState.NETWORK_VALIDATION_STATE_AVAILABLE);
+            if (ConnectivityNetworkCallback.this.mSubId != CellularNetworkValidator.this.mSubId) {
+                return;
+            }
+            TelephonyMetrics.getInstance().writeNetworkValidate(
+                    TelephonyEvent.NetworkValidationState.NETWORK_VALIDATION_STATE_AVAILABLE);
+            if (mValidatedNetworkCache.isRecentlyValidated(mSubId)) {
+                reportValidationResult(true, ConnectivityNetworkCallback.this.mSubId);
             }
         }
 
         @Override
         public void onLosing(Network network, int maxMsToLive) {
             logd("network onLosing " + network + " maxMsToLive " + maxMsToLive);
+            mValidatedNetworkCache.storeLastValidationResult(
+                    ConnectivityNetworkCallback.this.mSubId, false);
             reportValidationResult(false, ConnectivityNetworkCallback.this.mSubId);
         }
 
         @Override
         public void onLost(Network network) {
             logd("network onLost " + network);
+            mValidatedNetworkCache.storeLastValidationResult(
+                    ConnectivityNetworkCallback.this.mSubId, false);
             reportValidationResult(false, ConnectivityNetworkCallback.this.mSubId);
         }
 
         @Override
         public void onUnavailable() {
             logd("onUnavailable");
+            mValidatedNetworkCache.storeLastValidationResult(
+                    ConnectivityNetworkCallback.this.mSubId, false);
             reportValidationResult(false, ConnectivityNetworkCallback.this.mSubId);
         }
 
@@ -239,6 +417,8 @@
                 NetworkCapabilities networkCapabilities) {
             if (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)) {
                 logd("onValidated");
+                mValidatedNetworkCache.storeLastValidationResult(
+                        ConnectivityNetworkCallback.this.mSubId, true);
                 reportValidationResult(true, ConnectivityNetworkCallback.this.mSubId);
             }
         }
diff --git a/src/java/com/android/internal/telephony/CommandsInterface.java b/src/java/com/android/internal/telephony/CommandsInterface.java
index 2350cfa..7f61951 100644
--- a/src/java/com/android/internal/telephony/CommandsInterface.java
+++ b/src/java/com/android/internal/telephony/CommandsInterface.java
@@ -1151,6 +1151,8 @@
      */
     void sendCdmaSms(byte[] pdu, Message response);
 
+    void sendCdmaSms(byte[] pdu, Message response, boolean expectMore);
+
     /**
      * send SMS over IMS with 3GPP/GSM SMS format
      * @param smscPDU is smsc address in PDU form GSM BCD format prefixed
@@ -2026,21 +2028,16 @@
    /**
      * Sets user selected subscription at Modem.
      *
-     * @param slotId
-     *          Slot.
      * @param appIndex
      *          Application index in the card.
-     * @param subId
-     *          Indicates subscription 0 or subscription 1.
-     * @param subStatus
-     *          Activation status, 1 = activate and 0 = deactivate.
+     * @param activate
+     *          Whether to activate or deactivate the subscription.
      * @param result
      *          Callback message contains the information of SUCCESS/FAILURE.
      */
     // FIXME Update the doc and consider modifying the request to make more generic.
     @UnsupportedAppUsage
-    public void setUiccSubscription(int slotId, int appIndex, int subId, int subStatus,
-            Message result);
+    public void setUiccSubscription(int appIndex, boolean activate, Message result);
 
     /**
      * Tells the modem if data is allowed or not.
diff --git a/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java b/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java
index fc0a993..a887f9d 100644
--- a/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java
+++ b/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java
@@ -245,21 +245,10 @@
     public void notifyPhysicalChannelConfiguration(Phone sender,
             List<PhysicalChannelConfig> configs) {
         int subId = sender.getSubId();
+        int phoneId = sender.getPhoneId();
         try {
             if (mRegistry != null) {
-                mRegistry.notifyPhysicalChannelConfigurationForSubscriber(subId, configs);
-            }
-        } catch (RemoteException ex) {
-            // system process is dead
-        }
-    }
-
-    @Override
-    public void notifyOtaspChanged(Phone sender, int otaspMode) {
-        int subId = sender.getSubId();
-        try {
-            if (mRegistry != null) {
-                mRegistry.notifyOtaspChanged(subId, otaspMode);
+                mRegistry.notifyPhysicalChannelConfigurationForSubscriber(phoneId, subId, configs);
             }
         } catch (RemoteException ex) {
             // system process is dead
diff --git a/src/java/com/android/internal/telephony/GsmCdmaPhone.java b/src/java/com/android/internal/telephony/GsmCdmaPhone.java
index 6e539f0..06040d6 100644
--- a/src/java/com/android/internal/telephony/GsmCdmaPhone.java
+++ b/src/java/com/android/internal/telephony/GsmCdmaPhone.java
@@ -402,9 +402,25 @@
                 logd("update icc_operator_numeric=" + operatorNumeric);
                 tm.setSimOperatorNumericForPhone(mPhoneId, operatorNumeric);
 
-                SubscriptionController.getInstance().setMccMnc(operatorNumeric, getSubId());
                 // Sets iso country property by retrieving from build-time system property
-                setIsoCountryProperty(operatorNumeric);
+                String iso = "";
+                try {
+                    iso = MccTable.countryCodeForMcc(operatorNumeric.substring(0, 3));
+                } catch (StringIndexOutOfBoundsException ex) {
+                    Rlog.e(LOG_TAG, "init: countryCodeForMcc error", ex);
+                }
+
+                logd("init: set 'gsm.sim.operator.iso-country' to iso=" + iso);
+                tm.setSimCountryIsoForPhone(mPhoneId, iso);
+
+                // Skip if the subscription ID is not valid, because it can happen
+                // that the SIM is not loaded yet at this point.
+                final int subId = getSubId();
+                if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+                    SubscriptionController.getInstance().setMccMnc(operatorNumeric, subId);
+                    SubscriptionController.getInstance().setCountryIso(iso, subId);
+                }
+
                 // Updates MCC MNC device configuration information
                 logd("update mccmnc=" + operatorNumeric);
                 MccTable.updateMccMncConfiguration(mContext, operatorNumeric);
@@ -415,31 +431,6 @@
         }
     }
 
-    //CDMA
-    /**
-     * Sets PROPERTY_ICC_OPERATOR_ISO_COUNTRY property
-     *
-     */
-    private void setIsoCountryProperty(String operatorNumeric) {
-        TelephonyManager tm = TelephonyManager.from(mContext);
-        if (TextUtils.isEmpty(operatorNumeric)) {
-            logd("setIsoCountryProperty: clear 'gsm.sim.operator.iso-country'");
-            tm.setSimCountryIsoForPhone(mPhoneId, "");
-            SubscriptionController.getInstance().setCountryIso("", getSubId());
-        } else {
-            String iso = "";
-            try {
-                iso = MccTable.countryCodeForMcc(operatorNumeric.substring(0, 3));
-            } catch (StringIndexOutOfBoundsException ex) {
-                Rlog.e(LOG_TAG, "setIsoCountryProperty: countryCodeForMcc error", ex);
-            }
-
-            logd("setIsoCountryProperty: set 'gsm.sim.operator.iso-country' to iso=" + iso);
-            tm.setSimCountryIsoForPhone(mPhoneId, iso);
-            SubscriptionController.getInstance().setCountryIso(iso, getSubId());
-        }
-    }
-
     @UnsupportedAppUsage
     public boolean isPhoneTypeGsm() {
         return mPrecisePhoneType == PhoneConstants.PHONE_TYPE_GSM;
@@ -1258,11 +1249,11 @@
                 "cannot dial voice call in airplane mode");
         }
         // Check for service before placing non emergency CS voice call.
-        // Allow dial only if either CS is camped on any RAT (or) PS is in LTE service.
+        // Allow dial only if either CS is camped on any RAT (or) PS is in LTE/NR service.
         if (mSST != null
                 && mSST.mSS.getState() == ServiceState.STATE_OUT_OF_SERVICE /* CS out of service */
                 && !(mSST.mSS.getDataRegState() == ServiceState.STATE_IN_SERVICE
-                    && ServiceState.isLte(mSST.mSS.getRilDataRadioTechnology())) /* PS not in LTE */
+                && ServiceState.isPsTech(mSST.mSS.getRilDataRadioTechnology())) /* PS not in LTE/NR */
                 && !VideoProfile.isVideo(dialArgs.videoState) /* voice call */
                 && !isEmergency /* non-emergency call */) {
             throw new CallStateException(
@@ -1491,10 +1482,12 @@
     private void storeVoiceMailNumber(String number) {
         SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
         SharedPreferences.Editor editor = sp.edit();
+        setVmSimImsi(getSubscriberId());
+        logd("storeVoiceMailNumber: mPrecisePhoneType=" + mPrecisePhoneType + " vmNumber="
+                + number);
         if (isPhoneTypeGsm()) {
             editor.putString(VM_NUMBER + getPhoneId(), number);
             editor.apply();
-            setVmSimImsi(getSubscriberId());
         } else {
             editor.putString(VM_NUMBER_CDMA + getPhoneId(), number);
             editor.apply();
@@ -1504,17 +1497,23 @@
     @Override
     public String getVoiceMailNumber() {
         String number = null;
-        if (isPhoneTypeGsm()) {
+        if (isPhoneTypeGsm() || mSimRecords != null) {
             // Read from the SIM. If its null, try reading from the shared preference area.
-            IccRecords r = mIccRecords.get();
+            IccRecords r = isPhoneTypeGsm() ? mIccRecords.get() : mSimRecords;
             number = (r != null) ? r.getVoiceMailNumber() : "";
             if (TextUtils.isEmpty(number)) {
                 SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
-                number = sp.getString(VM_NUMBER + getPhoneId(), null);
+                String spName = isPhoneTypeGsm() ? VM_NUMBER : VM_NUMBER_CDMA;
+                number = sp.getString(spName + getPhoneId(), null);
+                logd("getVoiceMailNumber: from " + spName + " number=" + number);
+            } else {
+                logd("getVoiceMailNumber: from IccRecords number=" + number);
             }
-        } else {
+        }
+        if (!isPhoneTypeGsm() && TextUtils.isEmpty(number)) {
             SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
             number = sp.getString(VM_NUMBER_CDMA + getPhoneId(), null);
+            logd("getVoiceMailNumber: from VM_NUMBER_CDMA number=" + number);
         }
 
         if (TextUtils.isEmpty(number)) {
@@ -2441,7 +2440,7 @@
 
             case EVENT_RUIM_RECORDS_LOADED:
                 logd("Event EVENT_RUIM_RECORDS_LOADED Received");
-                updateCurrentCarrierInProvider();
+                updateDataConnectionTracker();
                 break;
 
             case EVENT_RADIO_ON:
@@ -2667,8 +2666,10 @@
 
             case EVENT_SET_VM_NUMBER_DONE:
                 ar = (AsyncResult)msg.obj;
-                if ((isPhoneTypeGsm() && IccVmNotSupportedException.class.isInstance(ar.exception)) ||
-                        (!isPhoneTypeGsm() && IccException.class.isInstance(ar.exception))){
+                if (((isPhoneTypeGsm() || mSimRecords != null)
+                        && IccVmNotSupportedException.class.isInstance(ar.exception))
+                        || (!isPhoneTypeGsm() && mSimRecords == null
+                        && IccException.class.isInstance(ar.exception))) {
                     storeVoiceMailNumber(mVmNumber);
                     ar.exception = null;
                 }
@@ -2871,6 +2872,7 @@
                 final IccRecords iccRecords = newUiccApplication.getIccRecords();
                 mUiccApplication.set(newUiccApplication);
                 mIccRecords.set(iccRecords);
+                logd("mIccRecords = " + mIccRecords);
                 registerForIccRecordEvents();
                 mIccPhoneBookIntManager.updateIccRecords(iccRecords);
                 if (iccRecords != null) {
@@ -3431,6 +3433,11 @@
     }
 
     @Override
+    public int getOtasp() {
+        return mSST.getOtasp();
+    }
+
+    @Override
     public int getCdmaEriIconIndex() {
         if (isPhoneTypeGsm()) {
             return super.getCdmaEriIconIndex();
@@ -3477,11 +3484,11 @@
                 cdmaApplication.getType() == AppType.APPTYPE_RUIM);
     }
 
-    private void phoneObjectUpdater(int newVoiceRadioTech) {
+    protected void phoneObjectUpdater(int newVoiceRadioTech) {
         logd("phoneObjectUpdater: newVoiceRadioTech=" + newVoiceRadioTech);
 
         // Check for a voice over lte replacement
-        if (ServiceState.isLte(newVoiceRadioTech)
+        if (ServiceState.isPsTech(newVoiceRadioTech)
                 || (newVoiceRadioTech == ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN)) {
             CarrierConfigManager configMgr = (CarrierConfigManager)
                     getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
@@ -3665,6 +3672,8 @@
         pw.println("GsmCdmaPhone extends:");
         super.dump(fd, pw, args);
         pw.println(" mPrecisePhoneType=" + mPrecisePhoneType);
+        pw.println(" mSimRecords=" + mSimRecords);
+        pw.println(" mIsimUiccRecords=" + mIsimUiccRecords);
         pw.println(" mCT=" + mCT);
         pw.println(" mSST=" + mSST);
         pw.println(" mPendingMMIs=" + mPendingMMIs);
@@ -3715,7 +3724,8 @@
     /**
      * @return operator numeric.
      */
-    private String getOperatorNumeric() {
+    @Override
+    public String getOperatorNumeric() {
         String operatorNumeric = null;
         if (isPhoneTypeGsm()) {
             IccRecords r = mIccRecords.get();
@@ -3780,7 +3790,8 @@
     private static final int[] VOICE_PS_CALL_RADIO_TECHNOLOGY = {
             ServiceState.RIL_RADIO_TECHNOLOGY_LTE,
             ServiceState.RIL_RADIO_TECHNOLOGY_LTE_CA,
-            ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN
+            ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN,
+            ServiceState.RIL_RADIO_TECHNOLOGY_NR
     };
 
     /**
diff --git a/src/java/com/android/internal/telephony/IIccPhoneBook.aidl b/src/java/com/android/internal/telephony/IIccPhoneBook.aidl
index 5090d1a..3dc3ba3 100644
--- a/src/java/com/android/internal/telephony/IIccPhoneBook.aidl
+++ b/src/java/com/android/internal/telephony/IIccPhoneBook.aidl
@@ -16,6 +16,8 @@
 
 package com.android.internal.telephony;
 
+import android.content.ContentValues;
+
 import com.android.internal.telephony.uicc.AdnRecord;
 
 
@@ -103,6 +105,22 @@
             String oldTag, String oldPhoneNumber,
             String newTag, String newPhoneNumber,
             String pin2);
+
+    /**
+     * Replace oldAdn with newAdn in ADN-like record in EF
+     *
+     * getAdnRecordsInEf must be called at least once before this function,
+     * otherwise an error will be returned
+     *
+     * @param subId user preferred subId
+     * @param efid must be one among EF_ADN, EF_FDN, and EF_SDN
+     * @param values including ADN,EMAIL,ANR to be updated
+     * @param pin2 required to update EF_FDN, otherwise must be null
+     * @return true for success
+     */
+    boolean updateAdnRecordsWithContentValuesInEfBySearchUsingSubId(int subId,
+            int efid, in ContentValues values, String pin2);
+
     /**
      * Update an ADN-like EF record by record index
      *
@@ -165,4 +183,39 @@
      */
     int[] getAdnRecordsSizeForSubscriber(int subId, int efid);
 
+    /**
+     * Get the capacity of ADN records
+     *
+     * @return  int[10] array
+     *            capacity[0]  is the max count of ADN
+     *            capacity[1]  is the used count of ADN
+     *            capacity[2]  is the max count of EMAIL
+     *            capacity[3]  is the used count of EMAIL
+     *            capacity[4]  is the max count of ANR
+     *            capacity[5]  is the used count of ANR
+     *            capacity[6]  is the max length of name
+     *            capacity[7]  is the max length of number
+     *            capacity[8]  is the max length of email
+     *            capacity[9]  is the max length of anr
+     */
+    int[] getAdnRecordsCapacity();
+
+    /**
+     * Get the capacity of ADN records
+     *
+     * @param subId user preferred subId
+     * @return  int[10] array
+     *            capacity[0]  is the max count of ADN
+     *            capacity[1]  is the used count of ADN
+     *            capacity[2]  is the max count of EMAIL
+     *            capacity[3]  is the used count of EMAIL
+     *            capacity[4]  is the max count of ANR
+     *            capacity[5]  is the used count of ANR
+     *            capacity[6]  is the max length of name
+     *            capacity[7]  is the max length of number
+     *            capacity[8]  is the max length of email
+     *            capacity[9]  is the max length of anr
+     */
+    int[] getAdnRecordsCapacityForSubscriber(int subId);
+
 }
diff --git a/src/java/com/android/internal/telephony/IccPhoneBookInterfaceManager.java b/src/java/com/android/internal/telephony/IccPhoneBookInterfaceManager.java
index e619e61..7d56d81 100644
--- a/src/java/com/android/internal/telephony/IccPhoneBookInterfaceManager.java
+++ b/src/java/com/android/internal/telephony/IccPhoneBookInterfaceManager.java
@@ -17,12 +17,14 @@
 package com.android.internal.telephony;
 
 import android.annotation.UnsupportedAppUsage;
+import android.content.ContentValues;
 import android.content.pm.PackageManager;
 import android.os.AsyncResult;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
 import android.telephony.Rlog;
+import android.text.TextUtils;
 
 import com.android.internal.telephony.uicc.AdnRecord;
 import com.android.internal.telephony.uicc.AdnRecordCache;
@@ -52,9 +54,9 @@
     protected static final int EVENT_LOAD_DONE = 2;
     protected static final int EVENT_UPDATE_DONE = 3;
 
-    private static final class Request {
+    public static final class Request {
         AtomicBoolean mStatus = new AtomicBoolean(false);
-        Object mResult = null;
+        public Object mResult = null;
     }
 
     @UnsupportedAppUsage
@@ -198,6 +200,68 @@
     }
 
     /**
+     * Replace oldAdn with newAdn in ADN-like record in EF
+     *
+     * getAdnRecordsInEf must be called at least once before this function,
+     * otherwise an error will be returned.
+     * throws SecurityException if no WRITE_CONTACTS permission
+     *
+     * @param efid must be one among EF_ADN, EF_FDN, and EF_SDN
+     * @param values old adn tag,  phone number, email and anr to be replaced
+     *        new adn tag,  phone number, email and anr to be stored
+     * @param newPhoneNumber adn number ot be stored
+     * @param oldPhoneNumber adn number to be replaced
+     *        Set both oldTag, oldPhoneNubmer, oldEmail and oldAnr to ""
+     *        means to replace an empty record, aka, insert new record
+     *        Set both newTag, newPhoneNubmer, newEmail and newAnr ""
+     *        means to replace the old record with empty one, aka, delete old record
+     * @param pin2 required to update EF_FDN, otherwise must be null
+     * @return true for success
+     */
+    public boolean updateAdnRecordsWithContentValuesInEfBySearch(int efid, ContentValues values,
+            String pin2) {
+
+        if (mPhone.getContext().checkCallingOrSelfPermission(
+                android.Manifest.permission.WRITE_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Requires android.permission.WRITE_CONTACTS permission");
+        }
+
+        String oldTag = values.getAsString(IccProvider.STR_TAG);
+        String newTag = values.getAsString(IccProvider.STR_NEW_TAG);
+        String oldPhoneNumber = values.getAsString(IccProvider.STR_NUMBER);
+        String newPhoneNumber = values.getAsString(IccProvider.STR_NEW_NUMBER);
+        String oldEmail = values.getAsString(IccProvider.STR_EMAILS);
+        String newEmail = values.getAsString(IccProvider.STR_NEW_EMAILS);
+        String oldAnr = values.getAsString(IccProvider.STR_ANRS);
+        String newAnr = values.getAsString(IccProvider.STR_NEW_ANRS);
+        String[] oldEmailArray = TextUtils.isEmpty(oldEmail) ? null : getStringArray(oldEmail);
+        String[] newEmailArray = TextUtils.isEmpty(newEmail) ? null : getStringArray(newEmail);
+        String[] oldAnrArray = TextUtils.isEmpty(oldAnr) ? null : getAnrStringArray(oldAnr);
+        String[] newAnrArray = TextUtils.isEmpty(newAnr) ? null : getAnrStringArray(newAnr);
+        efid = updateEfForIccType(efid);
+
+        if (DBG) {
+            logd("updateAdnRecordsWithContentValuesInEfBySearch: efid=" + efid + ", values = " +
+                    values + ", pin2=" + pin2);
+        }
+
+        checkThread();
+        Request updateRequest = new Request();
+        synchronized (updateRequest) {
+            Message response = mBaseHandler.obtainMessage(EVENT_UPDATE_DONE, updateRequest);
+            AdnRecord oldAdn = new AdnRecord(oldTag, oldPhoneNumber, oldEmailArray, oldAnrArray);
+            AdnRecord newAdn = new AdnRecord(newTag, newPhoneNumber, newEmailArray, newAnrArray);
+            if (mAdnCache != null) {
+                mAdnCache.updateAdnBySearch(efid, oldAdn, newAdn, pin2, response);
+                waitForResult(updateRequest);
+            } else {
+                loge("Failure while trying to update by search due to uninitialised adncache");
+            }
+        }
+        return (boolean) updateRequest.mResult;
+    }
+
+    /**
      * Update an ADN-like EF record by record index
      *
      * This is useful for iteration the whole ADN file, such as write the whole
@@ -331,7 +395,7 @@
     }
 
     @UnsupportedAppUsage
-    private int updateEfForIccType(int efid) {
+    protected int updateEfForIccType(int efid) {
         // Check if we are trying to read ADN records
         if (efid == IccConstants.EF_ADN) {
             if (mPhone.getCurrentUiccAppType() == AppType.APPTYPE_USIM) {
@@ -340,5 +404,41 @@
         }
         return efid;
     }
+
+    protected String[] getStringArray(String str) {
+        if (str != null) {
+            return str.split(",");
+        }
+        return null;
+    }
+
+    protected String[] getAnrStringArray(String str) {
+        if (str != null) {
+            return str.split(":");
+        }
+        return null;
+    }
+
+    /**
+     * Get the capacity of ADN records
+     *
+     * @return  int[10] array
+     *            capacity[0]  is the max count of ADN
+     *            capacity[1]  is the used count of ADN
+     *            capacity[2]  is the max count of EMAIL
+     *            capacity[3]  is the used count of EMAIL
+     *            capacity[4]  is the max count of ANR
+     *            capacity[5]  is the used count of ANR
+     *            capacity[6]  is the max length of name
+     *            capacity[7]  is the max length of number
+     *            capacity[8]  is the max length of email
+     *            capacity[9]  is the max length of anr
+     */
+    public int[] getAdnRecordsCapacity() {
+        if (DBG) logd("getAdnRecordsCapacity" );
+        int capacity[] = new int[10];
+
+        return capacity;
+    }
 }
 
diff --git a/src/java/com/android/internal/telephony/IccProvider.java b/src/java/com/android/internal/telephony/IccProvider.java
index 3ac4027..30943fa 100644
--- a/src/java/com/android/internal/telephony/IccProvider.java
+++ b/src/java/com/android/internal/telephony/IccProvider.java
@@ -51,6 +51,7 @@
         "name",
         "number",
         "emails",
+        "anrs",
         "_id"
     };
 
@@ -62,10 +63,15 @@
     protected static final int SDN_SUB = 6;
     protected static final int ADN_ALL = 7;
 
-    protected static final String STR_TAG = "tag";
-    protected static final String STR_NUMBER = "number";
-    protected static final String STR_EMAILS = "emails";
-    protected static final String STR_PIN2 = "pin2";
+    public static final String STR_TAG = "tag";
+    public static final String STR_NUMBER = "number";
+    public static final String STR_EMAILS = "emails";
+    public static final String STR_ANRS = "anrs";
+    public static final String STR_NEW_TAG = "newTag";
+    public static final String STR_NEW_NUMBER = "newNumber";
+    public static final String STR_NEW_EMAILS = "newEmails";
+    public static final String STR_NEW_ANRS = "newAnrs";
+    public static final String STR_PIN2 = "pin2";
 
     private static final UriMatcher URL_MATCHER =
                             new UriMatcher(UriMatcher.NO_MATCH);
@@ -199,10 +205,21 @@
                         "Cannot insert into URL: " + url);
         }
 
-        String tag = initialValues.getAsString("tag");
-        String number = initialValues.getAsString("number");
-        // TODO(): Read email instead of sending null.
-        boolean success = addIccRecordToEf(efType, tag, number, null, pin2, subId);
+        String tag = initialValues.getAsString(STR_TAG);
+        String number = initialValues.getAsString(STR_NUMBER);
+        String emails = initialValues.getAsString(STR_EMAILS);
+        String anrs = initialValues.getAsString(STR_ANRS);
+
+        ContentValues values = new ContentValues();
+        values.put(STR_TAG,"");
+        values.put(STR_NUMBER,"");
+        values.put(STR_EMAILS,"");
+        values.put(STR_ANRS,"");
+        values.put(STR_NEW_TAG,tag);
+        values.put(STR_NEW_NUMBER,number);
+        values.put(STR_NEW_EMAILS,emails);
+        values.put(STR_NEW_ANRS,anrs);
+        boolean success = updateIccRecordInEf(efType, values, pin2, subId);
 
         if (!success) {
             return null;
@@ -295,7 +312,8 @@
         // parse where clause
         String tag = null;
         String number = null;
-        String[] emails = null;
+        String emails = null;
+        String anrs = null;
         String pin2 = null;
 
         String[] tokens = where.split(" AND ");
@@ -319,18 +337,29 @@
             } else if (STR_NUMBER.equals(key)) {
                 number = normalizeValue(val);
             } else if (STR_EMAILS.equals(key)) {
-                //TODO(): Email is null.
-                emails = null;
+                emails = normalizeValue(val);
+            } else if (STR_ANRS.equals(key)) {
+                anrs = normalizeValue(val);
             } else if (STR_PIN2.equals(key)) {
                 pin2 = normalizeValue(val);
             }
         }
 
-        if (efType == FDN && TextUtils.isEmpty(pin2)) {
+        ContentValues values = new ContentValues();
+        values.put(STR_TAG, tag);
+        values.put(STR_NUMBER, number);
+        values.put(STR_EMAILS, emails);
+        values.put(STR_ANRS, anrs);
+        values.put(STR_NEW_TAG, "");
+        values.put(STR_NEW_NUMBER, "");
+        values.put(STR_NEW_EMAILS, "");
+        values.put(STR_NEW_ANRS, "");
+        if ((efType == FDN) && TextUtils.isEmpty(pin2)) {
             return 0;
         }
 
-        boolean success = deleteIccRecordFromEf(efType, tag, number, emails, pin2, subId);
+        if (DBG) log("delete mvalues= " + values);
+        boolean success = updateIccRecordInEf(efType, values, pin2, subId);
         if (!success) {
             return 0;
         }
@@ -376,15 +405,7 @@
                         "Cannot insert into URL: " + url);
         }
 
-        String tag = values.getAsString("tag");
-        String number = values.getAsString("number");
-        String[] emails = null;
-        String newTag = values.getAsString("newTag");
-        String newNumber = values.getAsString("newNumber");
-        String[] newEmails = null;
-        // TODO(): Update for email.
-        boolean success = updateIccRecordInEf(efType, tag, number,
-                newTag, newNumber, pin2, subId);
+        boolean success = updateIccRecordInEf(efType, values, pin2, subId);
 
         if (!success) {
             return 0;
@@ -458,21 +479,18 @@
     }
 
     private boolean
-    updateIccRecordInEf(int efType, String oldName, String oldNumber,
-            String newName, String newNumber, String pin2, int subId) {
-        if (DBG) log("updateIccRecordInEf: efType=0x" + Integer.toHexString(efType).toUpperCase() +
-                ", oldname=" + Rlog.pii(TAG, oldName) + ", oldnumber=" + Rlog.pii(TAG, oldNumber) +
-                ", newname=" + Rlog.pii(TAG, newName) + ", newnumber=" + Rlog.pii(TAG, newName) +
-                ", subscription=" + subId);
-
+    updateIccRecordInEf(int efType, ContentValues values, String pin2, int subId) {
         boolean success = false;
 
+        if (DBG) log("updateIccRecordInEf: efType=" + efType +
+                    ", values: [ "+ values + " ], subId:" + subId);
         try {
             IIccPhoneBook iccIpb = IIccPhoneBook.Stub.asInterface(
                     ServiceManager.getService("simphonebook"));
             if (iccIpb != null) {
-                success = iccIpb.updateAdnRecordsInEfBySearchForSubscriber(subId, efType, oldName,
-                        oldNumber, newName, newNumber, pin2);
+                success = iccIpb
+                        .updateAdnRecordsWithContentValuesInEfBySearchUsingSubId(
+                            subId, efType, values, pin2);
             }
         } catch (RemoteException ex) {
             // ignore it
@@ -518,7 +536,7 @@
     @UnsupportedAppUsage
     private void loadRecord(AdnRecord record, MatrixCursor cursor, int id) {
         if (!record.isEmpty()) {
-            Object[] contact = new Object[4];
+            Object[] contact = new Object[5];
             String alphaTag = record.getAlphaTag();
             String number = record.getNumber();
 
@@ -536,7 +554,19 @@
                 }
                 contact[2] = emailString.toString();
             }
-            contact[3] = id;
+
+            String[] anrs = record.getAdditionalNumbers();
+            if (anrs != null) {
+                StringBuilder anrString = new StringBuilder();
+                for (String anr : anrs) {
+                    if (DBG) log("Adding anr:" + anr);
+                    anrString.append(anr);
+                    anrString.append(":");
+                }
+                contact[3] = anrString.toString();
+            }
+
+            contact[4] = id;
             cursor.addRow(contact);
         }
     }
diff --git a/src/java/com/android/internal/telephony/InboundSmsHandler.java b/src/java/com/android/internal/telephony/InboundSmsHandler.java
index 932bbc0..616681a 100644
--- a/src/java/com/android/internal/telephony/InboundSmsHandler.java
+++ b/src/java/com/android/internal/telephony/InboundSmsHandler.java
@@ -148,6 +148,7 @@
     public static final int MESSAGE_BODY_COLUMN = 8;
     public static final int DISPLAY_ADDRESS_COLUMN = 9;
     public static final int DELETED_FLAG_COLUMN = 10;
+    public static final int SUBID_COLUMN = 11;
 
     public static final String SELECT_BY_ID = "_id=?";
 
@@ -758,7 +759,8 @@
                     .makeInboundSmsTracker(sms.getPdu(),
                     sms.getTimestampMillis(), destPort, is3gpp2(), false,
                     sms.getOriginatingAddress(), sms.getDisplayOriginatingAddress(),
-                    sms.getMessageBody(), sms.getMessageClass() == MessageClass.CLASS_0);
+                    sms.getMessageBody(), sms.getMessageClass() == MessageClass.CLASS_0,
+                            mPhone.getSubId());
         } else {
             // Create a tracker for this message segment.
             SmsHeader.ConcatRef concatRef = smsHeader.concatRef;
@@ -770,7 +772,7 @@
                     sms.getTimestampMillis(), destPort, is3gpp2(), sms.getOriginatingAddress(),
                     sms.getDisplayOriginatingAddress(), concatRef.refNumber, concatRef.seqNumber,
                     concatRef.msgCount, false, sms.getMessageBody(),
-                    sms.getMessageClass() == MessageClass.CLASS_0);
+                    sms.getMessageClass() == MessageClass.CLASS_0, mPhone.getSubId());
         }
 
         if (VDBG) log("created tracker: " + tracker);
@@ -958,7 +960,7 @@
                 output.write(pdu, 0, pdu.length);
             }
             int result = mWapPush.dispatchWapPdu(output.toByteArray(), resultReceiver,
-                    this, address);
+                    this, address, tracker.getSubId());
             if (DBG) log("dispatchWapPdu() returned " + result);
             // Add result of WAP-PUSH into metrics. RESULT_SMS_HANDLED indicates that the WAP-PUSH
             // needs to be ignored, so treating it as a success case.
@@ -990,7 +992,7 @@
 
         if (!filterInvoked) {
             dispatchSmsDeliveryIntent(pdus, tracker.getFormat(), destPort, resultReceiver,
-                    tracker.isClass0());
+                    tracker.isClass0(), tracker.getSubId());
         }
 
         return true;
@@ -1085,7 +1087,7 @@
         CarrierServicesSmsFilterCallback filterCallback =
                 new CarrierServicesSmsFilterCallback(
                         pdus, destPort, tracker.getFormat(), resultReceiver, userUnlocked,
-                        tracker.isClass0());
+                        tracker.isClass0(), tracker.getSubId());
         CarrierServicesSmsFilter carrierServicesFilter = new CarrierServicesSmsFilter(
                 mContext, mPhone, pdus, destPort, tracker.getFormat(),
                 filterCallback, getName(), mLocalLog);
@@ -1094,7 +1096,7 @@
         }
 
         if (VisualVoicemailSmsFilter.filter(
-                mContext, pdus, tracker.getFormat(), destPort, mPhone.getSubId())) {
+                mContext, pdus, tracker.getFormat(), destPort, tracker.getSubId())) {
             log("Visual voicemail SMS dropped");
             dropSms(resultReceiver);
             return true;
@@ -1114,7 +1116,7 @@
      */
     @UnsupportedAppUsage
     public void dispatchIntent(Intent intent, String permission, int appOp,
-            Bundle opts, BroadcastReceiver resultReceiver, UserHandle user) {
+            Bundle opts, BroadcastReceiver resultReceiver, UserHandle user, int subId) {
         intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT);
         final String action = intent.getAction();
         if (Intents.SMS_DELIVER_ACTION.equals(action)
@@ -1130,6 +1132,14 @@
             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
+        // different, specifically if the message is coming from SmsBroadcastUndelivered
+        if (SubscriptionManager.isValidSubscriptionId(subId)) {
+            intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId);
+            intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId);
+        }
+
         if (user.equals(UserHandle.ALL)) {
             // Get a list of currently started users.
             int[] users = null;
@@ -1221,7 +1231,7 @@
      * @param resultReceiver the receiver handling the delivery result
      */
     private void dispatchSmsDeliveryIntent(byte[][] pdus, String format, int destPort,
-            SmsBroadcastReceiver resultReceiver, boolean isClass0) {
+            SmsBroadcastReceiver resultReceiver, boolean isClass0, int subId) {
         Intent intent = new Intent();
         intent.putExtra("pdus", pdus);
         intent.putExtra("format", format);
@@ -1269,7 +1279,7 @@
 
         Bundle options = handleSmsWhitelisting(intent.getComponent(), isClass0);
         dispatchIntent(intent, android.Manifest.permission.RECEIVE_SMS,
-                AppOpsManager.OP_RECEIVE_SMS, options, resultReceiver, UserHandle.SYSTEM);
+                AppOpsManager.OP_RECEIVE_SMS, options, resultReceiver, UserHandle.SYSTEM, subId);
     }
 
     /**
@@ -1443,6 +1453,8 @@
         @Override
         public void onReceive(Context context, Intent intent) {
             String action = intent.getAction();
+            int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
+                    SubscriptionManager.INVALID_SUBSCRIPTION_ID);
             if (action.equals(Intents.SMS_DELIVER_ACTION)) {
                 // Now dispatch the notification only intent
                 intent.setAction(Intents.SMS_RECEIVED_ACTION);
@@ -1455,7 +1467,7 @@
 
                 dispatchIntent(intent, android.Manifest.permission.RECEIVE_SMS,
                         AppOpsManager.OP_RECEIVE_SMS,
-                        options, this, UserHandle.ALL);
+                        options, this, UserHandle.ALL, subId);
             } else if (action.equals(Intents.WAP_PUSH_DELIVER_ACTION)) {
                 // Now dispatch the notification only intent
                 intent.setAction(Intents.WAP_PUSH_RECEIVED_ACTION);
@@ -1478,7 +1490,7 @@
                 String mimeType = intent.getType();
                 dispatchIntent(intent, WapPushOverSms.getPermissionForType(mimeType),
                         WapPushOverSms.getAppOpsPermissionForIntent(mimeType), options, this,
-                        UserHandle.SYSTEM);
+                        UserHandle.SYSTEM, subId);
             } else {
                 // Now that the intents have been deleted we can clean up the PDU data.
                 if (!Intents.DATA_SMS_RECEIVED_ACTION.equals(action)
@@ -1520,16 +1532,18 @@
         private final SmsBroadcastReceiver mSmsBroadcastReceiver;
         private final boolean mUserUnlocked;
         private final boolean mIsClass0;
+        private final int mSubId;
 
         CarrierServicesSmsFilterCallback(byte[][] pdus, int destPort, String smsFormat,
                 SmsBroadcastReceiver smsBroadcastReceiver,  boolean userUnlocked,
-                boolean isClass0) {
+                boolean isClass0, int subId) {
             mPdus = pdus;
             mDestPort = destPort;
             mSmsFormat = smsFormat;
             mSmsBroadcastReceiver = smsBroadcastReceiver;
             mUserUnlocked = userUnlocked;
             mIsClass0 = isClass0;
+            mSubId = subId;
         }
 
         @Override
@@ -1537,7 +1551,7 @@
             logv("onFilterComplete: result is " + result);
             if ((result & CarrierMessagingService.RECEIVE_OPTIONS_DROP) == 0) {
                 if (VisualVoicemailSmsFilter.filter(mContext, mPdus,
-                        mSmsFormat, mDestPort, mPhone.getSubId())) {
+                        mSmsFormat, mDestPort, mSubId)) {
                     log("Visual voicemail SMS dropped");
                     dropSms(mSmsBroadcastReceiver);
                     return;
@@ -1545,7 +1559,7 @@
 
                 if (mUserUnlocked) {
                     dispatchSmsDeliveryIntent(
-                            mPdus, mSmsFormat, mDestPort, mSmsBroadcastReceiver, mIsClass0);
+                            mPdus, mSmsFormat, mDestPort, mSmsBroadcastReceiver, mIsClass0, mSubId);
                 } else {
                     // Don't do anything further, leave the message in the raw table if the
                     // credential-encrypted storage is still locked and show the new message
diff --git a/src/java/com/android/internal/telephony/InboundSmsTracker.java b/src/java/com/android/internal/telephony/InboundSmsTracker.java
index 5bf7931..4d9971e 100644
--- a/src/java/com/android/internal/telephony/InboundSmsTracker.java
+++ b/src/java/com/android/internal/telephony/InboundSmsTracker.java
@@ -42,6 +42,7 @@
     private final boolean mIs3gpp2WapPdu;
     private final String mMessageBody;
     private final boolean mIsClass0;
+    private final int mSubId;
 
     // Fields for concatenating multi-part SMS messages
     private final String mAddress;
@@ -105,7 +106,7 @@
      */
     public InboundSmsTracker(byte[] pdu, long timestamp, int destPort, boolean is3gpp2,
             boolean is3gpp2WapPdu, String address, String displayAddress, String messageBody,
-            boolean isClass0) {
+            boolean isClass0, int subId) {
         mPdu = pdu;
         mTimestamp = timestamp;
         mDestPort = destPort;
@@ -119,6 +120,7 @@
         mReferenceNumber = -1;
         mSequenceNumber = getIndexOffset();     // 0 or 1, depending on type
         mMessageCount = 1;
+        mSubId = subId;
     }
 
     /**
@@ -142,7 +144,8 @@
      */
     public InboundSmsTracker(byte[] pdu, long timestamp, int destPort, boolean is3gpp2,
             String address, String displayAddress, int referenceNumber, int sequenceNumber,
-            int messageCount, boolean is3gpp2WapPdu, String messageBody, boolean isClass0) {
+            int messageCount, boolean is3gpp2WapPdu, String messageBody, boolean isClass0,
+            int subId) {
         mPdu = pdu;
         mTimestamp = timestamp;
         mDestPort = destPort;
@@ -157,6 +160,7 @@
         mReferenceNumber = referenceNumber;
         mSequenceNumber = sequenceNumber;
         mMessageCount = messageCount;
+        mSubId = subId;
     }
 
     /**
@@ -190,6 +194,8 @@
         mTimestamp = cursor.getLong(InboundSmsHandler.DATE_COLUMN);
         mAddress = cursor.getString(InboundSmsHandler.ADDRESS_COLUMN);
         mDisplayAddress = cursor.getString(InboundSmsHandler.DISPLAY_ADDRESS_COLUMN);
+        mSubId = cursor.getInt(SmsBroadcastUndelivered.PDU_PENDING_MESSAGE_PROJECTION_INDEX_MAPPING
+                .get(InboundSmsHandler.SUBID_COLUMN));
 
         if (cursor.getInt(InboundSmsHandler.COUNT_COLUMN) == 1) {
             // single-part message
@@ -249,6 +255,7 @@
         }
         values.put("count", mMessageCount);
         values.put("message_body", mMessageBody);
+        values.put("sub_id", mSubId);
         return values;
     }
 
@@ -318,6 +325,10 @@
         return mIsClass0;
     }
 
+    public int getSubId() {
+        return mSubId;
+    }
+
     @UnsupportedAppUsage
     public String getFormat() {
         return mIs3gpp2 ? SmsConstants.FORMAT_3GPP2 : SmsConstants.FORMAT_3GPP;
diff --git a/src/java/com/android/internal/telephony/LocaleTracker.java b/src/java/com/android/internal/telephony/LocaleTracker.java
index fbbcb3b..13d7dfb 100755
--- a/src/java/com/android/internal/telephony/LocaleTracker.java
+++ b/src/java/com/android/internal/telephony/LocaleTracker.java
@@ -73,6 +73,9 @@
     /** Event for incoming cell info */
     private static final int EVENT_RESPONSE_CELL_INFO = 5;
 
+    /** Event to fire if the operator from ServiceState is considered truly lost */
+    private static final int EVENT_OPERATOR_LOST = 6;
+
     // Todo: Read this from Settings.
     /** The minimum delay to get cell info from the modem */
     private static final long CELL_INFO_MIN_DELAY_MS = 2 * SECOND_IN_MILLIS;
@@ -85,6 +88,13 @@
     /** The delay for periodically getting cell info from the modem */
     private static final long CELL_INFO_PERIODIC_POLLING_DELAY_MS = 10 * MINUTE_IN_MILLIS;
 
+    /**
+     * The delay after the last time the device camped on a cell before declaring that the
+     * ServiceState's MCC information can no longer be used (and thus kicking in the CellInfo
+     * based tracking.
+     */
+    private static final long SERVICE_OPERATOR_LOST_DELAY_MS = 10 * MINUTE_IN_MILLIS;
+
     /** The maximum fail count to prevent delay time overflow */
     private static final int MAX_FAIL_COUNT = 30;
 
@@ -106,7 +116,7 @@
     /** Count of invalid cell info we've got so far. Will reset once we get a successful one */
     private int mFailCellInfoCount;
 
-    /** The ISO-3166 code of device's current country */
+    /** The ISO-3166 two-letter code of device's current country */
     @Nullable
     private String mCurrentCountryIso;
 
@@ -166,6 +176,11 @@
                 onSimCardStateChanged(msg.arg1);
                 break;
 
+            case EVENT_OPERATOR_LOST:
+                updateOperatorNumericImmediate("");
+                updateTrackingStatus();
+                break;
+
             default:
                 throw new IllegalStateException("Unexpected message arrives. msg = " + msg.what);
         }
@@ -247,7 +262,7 @@
      *
      * @param state SIM card state. Must be one of TelephonyManager.SIM_STATE_XXX.
      */
-    private synchronized void onSimCardStateChanged(int state) {
+    private void onSimCardStateChanged(int state) {
         mSimState = state;
         updateLocale();
         updateTrackingStatus();
@@ -270,8 +285,17 @@
      * @param operatorNumeric MCC/MNC of the operator
      */
     public void updateOperatorNumeric(String operatorNumeric) {
+        if (TextUtils.isEmpty(operatorNumeric)) {
+            sendMessageDelayed(obtainMessage(EVENT_OPERATOR_LOST), SERVICE_OPERATOR_LOST_DELAY_MS);
+        } else {
+            removeMessages(EVENT_OPERATOR_LOST);
+            updateOperatorNumericImmediate(operatorNumeric);
+        }
+    }
+
+    private void updateOperatorNumericImmediate(String operatorNumeric) {
         // Check if the operator numeric changes.
-        if (!Objects.equals(mOperatorNumeric, operatorNumeric)) {
+        if (!operatorNumeric.equals(mOperatorNumeric)) {
             String msg = "Operator numeric changes to \"" + operatorNumeric + "\"";
             if (DBG) log(msg);
             mLocalLog.log(msg);
diff --git a/src/java/com/android/internal/telephony/MccTable.java b/src/java/com/android/internal/telephony/MccTable.java
index 76338c3..d515c58 100644
--- a/src/java/com/android/internal/telephony/MccTable.java
+++ b/src/java/com/android/internal/telephony/MccTable.java
@@ -360,23 +360,12 @@
     private static void setTimezoneFromMccIfNeeded(Context context, int mcc) {
         // Switch to use the time service helper associated with the NitzStateMachine impl
         // being used. This logic will be removed once the old implementation is removed.
-        if (TelephonyComponentFactory.USE_NEW_NITZ_STATE_MACHINE) {
-            if (!NewTimeServiceHelper.isTimeZoneSettingInitializedStatic()) {
-                String zoneId = defaultTimeZoneForMcc(mcc);
-                if (zoneId != null && zoneId.length() > 0) {
-                    // Set time zone based on MCC
-                    NewTimeServiceHelper.setDeviceTimeZoneStatic(context, zoneId);
-                    Slog.d(LOG_TAG, "timezone set to " + zoneId);
-                }
-            }
-        } else {
-            if (!OldTimeServiceHelper.isTimeZoneSettingInitializedStatic()) {
-                String zoneId = defaultTimeZoneForMcc(mcc);
-                if (zoneId != null && zoneId.length() > 0) {
-                    // Set time zone based on MCC
-                    OldTimeServiceHelper.setDeviceTimeZoneStatic(context, zoneId);
-                    Slog.d(LOG_TAG, "timezone set to " + zoneId);
-                }
+        if (!TimeServiceHelperImpl.isTimeZoneSettingInitializedStatic()) {
+            String zoneId = defaultTimeZoneForMcc(mcc);
+            if (zoneId != null && zoneId.length() > 0) {
+                // Set time zone based on MCC
+                TimeServiceHelperImpl.setDeviceTimeZoneStatic(context, zoneId);
+                Slog.d(LOG_TAG, "timezone set to " + zoneId);
             }
         }
     }
diff --git a/src/java/com/android/internal/telephony/MultiSimSettingController.java b/src/java/com/android/internal/telephony/MultiSimSettingController.java
index 11ad9a6..0fe1184 100644
--- a/src/java/com/android/internal/telephony/MultiSimSettingController.java
+++ b/src/java/com/android/internal/telephony/MultiSimSettingController.java
@@ -31,13 +31,16 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.os.Handler;
 import android.os.Message;
 import android.os.ParcelUuid;
 import android.provider.Settings;
 import android.provider.Settings.SettingNotFoundException;
+import android.telephony.CarrierConfigManager;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
@@ -51,6 +54,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.stream.Collectors;
 
@@ -73,6 +77,7 @@
     private static final int EVENT_SUBSCRIPTION_INFO_CHANGED         = 4;
     private static final int EVENT_SUBSCRIPTION_GROUP_CHANGED        = 5;
     private static final int EVENT_DEFAULT_DATA_SUBSCRIPTION_CHANGED = 6;
+    private static final int EVENT_CARRIER_CONFIG_CHANGED            = 7;
 
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = {"PRIMARY_SUB_"},
@@ -102,13 +107,14 @@
     // Subscription information is initially loaded.
     private static final int PRIMARY_SUB_INITIALIZED            = 6;
 
-    private final Context mContext;
-    private final SubscriptionController mSubController;
+    protected final Context mContext;
+    protected final SubscriptionController mSubController;
+    protected boolean mIsAllSubscriptionsLoaded;
     // Keep a record of active primary (non-opportunistic) subscription list.
-    @NonNull private List<Integer> mPrimarySubList = new ArrayList<>();
+    @NonNull protected List<Integer> mPrimarySubList = new ArrayList<>();
 
     /** The singleton instance. */
-    private static MultiSimSettingController sInstance = null;
+    protected static MultiSimSettingController sInstance = null;
 
     // This will be set true when handling EVENT_ALL_SUBSCRIPTIONS_LOADED. The reason of keeping
     // a local variable instead of calling SubscriptionInfoUpdater#isSubInfoInitialized is, there
@@ -118,6 +124,32 @@
     // the SIMs are newly inserted instead of being initialized.
     private boolean mSubInfoInitialized = false;
 
+    // mInitialHandling is to make sure we don't always ask user to re-select data SIM after reboot.
+    // After boot-up when things are firstly initialized (mSubInfoInitialized is changed to true
+    // and carrier configs are all loaded), we do a reEvaluateAll(). In the first reEvaluateAll(),
+    // mInitialHandling will be true and we won't pop up SIM select dialog.
+    private boolean mInitialHandling = true;
+
+    // Keep a record of which subIds have carrier config loaded. Length of the array is phone count.
+    // The index is phoneId, and value is subId. For example:
+    // If carrier config of subId 2 is loaded on phone 0,mCarrierConfigLoadedSubIds[0] = 2.
+    // Then if subId 2 is deactivated from phone 0, the value becomes INVALID,
+    // mCarrierConfigLoadedSubIds[0] = INVALID_SUBSCRIPTION_ID.
+    private int[] mCarrierConfigLoadedSubIds;
+
+    private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED.equals(intent.getAction())) {
+                int phoneId = intent.getIntExtra(CarrierConfigManager.EXTRA_SLOT_INDEX,
+                        SubscriptionManager.INVALID_SIM_SLOT_INDEX);
+                int subId = intent.getIntExtra(CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX,
+                        SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+                notifyCarrierConfigChanged(phoneId, subId);
+            }
+        }
+    };
+
     /**
      * Return the singleton or create one if not existed.
      */
@@ -149,6 +181,15 @@
     public MultiSimSettingController(Context context, SubscriptionController sc) {
         mContext = context;
         mSubController = sc;
+
+        // Initialize mCarrierConfigLoadedSubIds and register to listen to carrier config change.
+        final int phoneCount = ((TelephonyManager) mContext.getSystemService(
+                Context.TELEPHONY_SERVICE)).getPhoneCount();
+        mCarrierConfigLoadedSubIds = new int[phoneCount];
+        Arrays.fill(mCarrierConfigLoadedSubIds, SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+
+        context.registerReceiver(mIntentReceiver, new IntentFilter(
+                CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED));
     }
 
     /**
@@ -222,6 +263,11 @@
             case EVENT_DEFAULT_DATA_SUBSCRIPTION_CHANGED:
                 onDefaultDataSettingChanged();
                 break;
+            case EVENT_CARRIER_CONFIG_CHANGED:
+                int phoneId = msg.arg1;
+                int subId = msg.arg2;
+                onCarrierConfigChanged(phoneId, subId);
+                break;
         }
     }
 
@@ -231,14 +277,14 @@
      * If user is enabling a non-default non-opportunistic subscription, make it default
      * data subscription.
      */
-    private void onUserDataEnabled(int subId, boolean enable) {
+    protected void onUserDataEnabled(int subId, boolean enable) {
         if (DBG) log("onUserDataEnabled");
         // Make sure MOBILE_DATA of subscriptions in same group are synced.
         setUserDataEnabledForGroup(subId, enable);
 
         // If user is enabling a non-default non-opportunistic subscription, make it default.
         if (mSubController.getDefaultDataSubId() != subId && !mSubController.isOpportunistic(subId)
-                && enable) {
+                && enable && mSubController.isActiveSubId(subId)) {
             mSubController.setDefaultDataSubId(subId);
         }
     }
@@ -261,9 +307,7 @@
     private void onAllSubscriptionsLoaded() {
         if (DBG) log("onAllSubscriptionsLoaded");
         mSubInfoInitialized = true;
-        updateDefaults(/*init*/ true);
-        disableDataForNonDefaultNonOpportunisticSubscriptions();
-        deactivateGroupedOpportunisticSubscriptionIfNeeded();
+        reEvaluateAll();
     }
 
     /**
@@ -273,8 +317,58 @@
      */
     private void onSubscriptionsChanged() {
         if (DBG) log("onSubscriptionsChanged");
-        if (!mSubInfoInitialized) return;
-        updateDefaults(/*init*/ false);
+        reEvaluateAll();
+    }
+
+    /**
+     * Called when carrier config changes on any phone.
+     */
+    @VisibleForTesting
+    public void notifyCarrierConfigChanged(int phoneId, int subId) {
+        obtainMessage(EVENT_CARRIER_CONFIG_CHANGED, phoneId, subId).sendToTarget();
+    }
+
+    private void onCarrierConfigChanged(int phoneId, int subId) {
+        log("onCarrierConfigChanged phoneId " + phoneId + " subId " + subId);
+        if (!SubscriptionManager.isValidPhoneId(phoneId)) {
+            loge("Carrier config change with invalid phoneId " + phoneId);
+            return;
+        }
+
+        mCarrierConfigLoadedSubIds[phoneId] = subId;
+        reEvaluateAll();
+    }
+
+    private boolean isCarrierConfigLoadedForAllSub() {
+        int[] activeSubIds = mSubController.getActiveSubIdList(false);
+        for (int activeSubId : activeSubIds) {
+            boolean isLoaded = false;
+            for (int configLoadedSub : mCarrierConfigLoadedSubIds) {
+                if (configLoadedSub == activeSubId) {
+                    isLoaded = true;
+                    break;
+                }
+            }
+            if (!isLoaded) {
+                if (DBG) log("Carrier config subId " + activeSubId + " is not loaded.");
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Wait for subInfo initialization (after boot up) and carrier config load for all active
+     * subscriptions before re-evaluate multi SIM settings.
+     */
+    private boolean isReadyToReevaluate() {
+        return mSubInfoInitialized && isCarrierConfigLoadedForAllSub();
+    }
+
+    private void reEvaluateAll() {
+        if (!isReadyToReevaluate()) return;
+        updateDefaults();
         disableDataForNonDefaultNonOpportunisticSubscriptions();
         deactivateGroupedOpportunisticSubscriptionIfNeeded();
     }
@@ -353,12 +447,11 @@
      *    not a user settable value anymore.
      * 4) If non above is met, clear the default value to INVALID.
      *
-     * @param init whether the subscriptions are just initialized.
      */
-    private void updateDefaults(boolean init) {
+    protected void updateDefaults() {
         if (DBG) log("updateDefaults");
 
-        if (!mSubInfoInitialized) return;
+        if (!isReadyToReevaluate()) return;
 
         List<SubscriptionInfo> activeSubInfos = mSubController
                 .getActiveSubscriptionInfoList(mContext.getOpPackageName());
@@ -372,7 +465,7 @@
             return;
         }
 
-        int change = updatePrimarySubListAndGetChangeType(activeSubInfos, init);
+        int change = updatePrimarySubListAndGetChangeType(activeSubInfos);
         if (DBG) log("[updateDefaultValues] change: " + change);
         if (change == PRIMARY_SUB_NO_CHANGE) return;
 
@@ -414,8 +507,7 @@
     }
 
     @PrimarySubChangeType
-    private int updatePrimarySubListAndGetChangeType(List<SubscriptionInfo> activeSubList,
-            boolean init) {
+    private int updatePrimarySubListAndGetChangeType(List<SubscriptionInfo> activeSubList) {
         // Update mPrimarySubList. Opportunistic subscriptions can't be default
         // data / voice / sms subscription.
         List<Integer> prevPrimarySubList = mPrimarySubList;
@@ -423,7 +515,10 @@
                 .map(info -> info.getSubscriptionId())
                 .collect(Collectors.toList());
 
-        if (init) return PRIMARY_SUB_INITIALIZED;
+        if (mInitialHandling) {
+            mInitialHandling = false;
+            return PRIMARY_SUB_INITIALIZED;
+        }
         if (mPrimarySubList.equals(prevPrimarySubList)) return PRIMARY_SUB_NO_CHANGE;
         if (mPrimarySubList.size() > prevPrimarySubList.size()) return PRIMARY_SUB_ADDED;
 
@@ -553,8 +648,8 @@
                 || change == PRIMARY_SUB_SWAPPED);
     }
 
-    private void disableDataForNonDefaultNonOpportunisticSubscriptions() {
-        if (!mSubInfoInitialized) return;
+    protected void disableDataForNonDefaultNonOpportunisticSubscriptions() {
+        if (!isReadyToReevaluate()) return;
 
         int defaultDataSub = mSubController.getDefaultDataSubId();
 
@@ -584,7 +679,7 @@
      * Make sure MOBILE_DATA of subscriptions in the same group with the subId
      * are synced.
      */
-    private void setUserDataEnabledForGroup(int subId, boolean enable) {
+    protected void setUserDataEnabledForGroup(int subId, boolean enable) {
         log("setUserDataEnabledForGroup subId " + subId + " enable " + enable);
         List<SubscriptionInfo> infoList = mSubController.getSubscriptionsInGroup(
                 mSubController.getGroupUuid(subId), mContext.getOpPackageName());
@@ -600,7 +695,7 @@
                 // If enable is true and it's not opportunistic subscription, we don't enable it,
                 // as there can't e two
                 if (phone != null) {
-                    phone.getDataEnabledSettings().setUserDataEnabled(enable);
+                    phone.getDataEnabledSettings().setUserDataEnabled(enable, false);
                 }
             } else {
                 // For inactive subscription, directly write into global settings.
@@ -686,15 +781,16 @@
             EuiccManager euiccManager = (EuiccManager)
                     mContext.getSystemService(Context.EUICC_SERVICE);
             euiccManager.switchToSubscription(SubscriptionManager.INVALID_SUBSCRIPTION_ID,
-                    PendingIntent.getService(mContext, 0, new Intent(), 0));
+                    PendingIntent.getService(
+                            mContext, 0, new Intent(), PendingIntent.FLAG_IMMUTABLE));
         }
     }
 
-    private void log(String msg) {
+    protected void log(String msg) {
         Log.d(LOG_TAG, msg);
     }
 
-    private void loge(String msg) {
+    protected void loge(String msg) {
         Log.e(LOG_TAG, msg);
     }
 }
diff --git a/src/java/com/android/internal/telephony/NitzStateMachine.java b/src/java/com/android/internal/telephony/NitzStateMachine.java
index 9b91573..8aefb5c 100644
--- a/src/java/com/android/internal/telephony/NitzStateMachine.java
+++ b/src/java/com/android/internal/telephony/NitzStateMachine.java
@@ -83,8 +83,34 @@
      * A proxy over device state that allows things like system properties, system clock
      * to be faked for tests.
      */
-    // Non-final to allow mocking.
-    class DeviceState {
+    interface DeviceState {
+
+        /**
+         * If time between NITZ updates is less than {@link #getNitzUpdateSpacingMillis()} the
+         * update may be ignored.
+         */
+        int getNitzUpdateSpacingMillis();
+
+        /**
+         * If {@link #getNitzUpdateSpacingMillis()} hasn't been exceeded but update is >
+         * {@link #getNitzUpdateDiffMillis()} do the update
+         */
+        int getNitzUpdateDiffMillis();
+
+        /**
+         * Returns true if the {@code gsm.ignore-nitz} system property is set to "yes".
+         */
+        boolean getIgnoreNitz();
+
+        String getNetworkCountryIsoForPhone();
+    }
+
+    /**
+     * The real implementation of {@link DeviceState}.
+     *
+     * {@hide}
+     */
+    class DeviceStateImpl implements DeviceState {
         private static final int NITZ_UPDATE_SPACING_DEFAULT = 1000 * 60 * 10;
         private final int mNitzUpdateSpacing;
 
@@ -95,7 +121,7 @@
         private final TelephonyManager mTelephonyManager;
         private final ContentResolver mCr;
 
-        public DeviceState(GsmCdmaPhone phone) {
+        public DeviceStateImpl(GsmCdmaPhone phone) {
             mPhone = phone;
 
             Context context = phone.getContext();
@@ -108,31 +134,24 @@
                     SystemProperties.getInt("ro.nitz_update_diff", NITZ_UPDATE_DIFF_DEFAULT);
         }
 
-        /**
-         * If time between NITZ updates is less than {@link #getNitzUpdateSpacingMillis()} the
-         * update may be ignored.
-         */
+        @Override
         public int getNitzUpdateSpacingMillis() {
             return Settings.Global.getInt(mCr, Settings.Global.NITZ_UPDATE_SPACING,
                     mNitzUpdateSpacing);
         }
 
-        /**
-         * If {@link #getNitzUpdateSpacingMillis()} hasn't been exceeded but update is >
-         * {@link #getNitzUpdateDiffMillis()} do the update
-         */
+        @Override
         public int getNitzUpdateDiffMillis() {
             return Settings.Global.getInt(mCr, Settings.Global.NITZ_UPDATE_DIFF, mNitzUpdateDiff);
         }
 
-        /**
-         * Returns true if the {@code gsm.ignore-nitz} system property is set to "yes".
-         */
+        @Override
         public boolean getIgnoreNitz() {
             String ignoreNitz = SystemProperties.get("gsm.ignore-nitz");
             return ignoreNitz != null && ignoreNitz.equals("yes");
         }
 
+        @Override
         public String getNetworkCountryIsoForPhone() {
             return mTelephonyManager.getNetworkCountryIsoForPhone(mPhone.getPhoneId());
         }
diff --git a/src/java/com/android/internal/telephony/NewNitzStateMachine.java b/src/java/com/android/internal/telephony/NitzStateMachineImpl.java
similarity index 97%
rename from src/java/com/android/internal/telephony/NewNitzStateMachine.java
rename to src/java/com/android/internal/telephony/NitzStateMachineImpl.java
index 74d9862..8fbefac 100644
--- a/src/java/com/android/internal/telephony/NewNitzStateMachine.java
+++ b/src/java/com/android/internal/telephony/NitzStateMachineImpl.java
@@ -35,7 +35,7 @@
 /**
  * {@hide}
  */
-public final class NewNitzStateMachine implements NitzStateMachine {
+public final class NitzStateMachineImpl implements NitzStateMachine {
 
     private static final String LOG_TAG = ServiceStateTracker.LOG_TAG;
     private static final boolean DBG = ServiceStateTracker.DBG;
@@ -84,21 +84,21 @@
     private final LocalLog mTimeZoneLog = new LocalLog(15);
     private final GsmCdmaPhone mPhone;
     private final DeviceState mDeviceState;
-    private final NewTimeServiceHelper mTimeServiceHelper;
+    private final TimeServiceHelper mTimeServiceHelper;
     private final TimeZoneLookupHelper mTimeZoneLookupHelper;
     /** Wake lock used while setting time of day. */
     private final PowerManager.WakeLock mWakeLock;
     private static final String WAKELOCK_TAG = "NitzStateMachine";
 
-    public NewNitzStateMachine(GsmCdmaPhone phone) {
+    public NitzStateMachineImpl(GsmCdmaPhone phone) {
         this(phone,
-                new NewTimeServiceHelper(phone.getContext()),
-                new DeviceState(phone),
+                new TimeServiceHelperImpl(phone.getContext()),
+                new DeviceStateImpl(phone),
                 new TimeZoneLookupHelper());
     }
 
     @VisibleForTesting
-    public NewNitzStateMachine(GsmCdmaPhone phone, NewTimeServiceHelper timeServiceHelper,
+    public NitzStateMachineImpl(GsmCdmaPhone phone, TimeServiceHelper timeServiceHelper,
             DeviceState deviceState, TimeZoneLookupHelper timeZoneLookupHelper) {
         mPhone = phone;
 
@@ -110,7 +110,7 @@
         mDeviceState = deviceState;
         mTimeZoneLookupHelper = timeZoneLookupHelper;
         mTimeServiceHelper = timeServiceHelper;
-        mTimeServiceHelper.setListener(new NewTimeServiceHelper.Listener() {
+        mTimeServiceHelper.setListener(new TimeServiceHelper.Listener() {
             @Override
             public void onTimeZoneDetectionChange(boolean enabled) {
                 if (enabled) {
diff --git a/src/java/com/android/internal/telephony/OldNitzStateMachine.java b/src/java/com/android/internal/telephony/OldNitzStateMachine.java
deleted file mode 100644
index 916dc65..0000000
--- a/src/java/com/android/internal/telephony/OldNitzStateMachine.java
+++ /dev/null
@@ -1,534 +0,0 @@
-/*
- * Copyright 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.content.Context;
-import android.os.PowerManager;
-import android.telephony.Rlog;
-import android.text.TextUtils;
-import android.util.LocalLog;
-import android.util.TimestampedValue;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.telephony.TimeZoneLookupHelper.CountryResult;
-import com.android.internal.telephony.TimeZoneLookupHelper.OffsetResult;
-import com.android.internal.telephony.metrics.TelephonyMetrics;
-import com.android.internal.util.IndentingPrintWriter;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-
-/**
- * {@hide}
- */
-public final class OldNitzStateMachine implements NitzStateMachine {
-
-    private static final String LOG_TAG = ServiceStateTracker.LOG_TAG;
-    private static final boolean DBG = ServiceStateTracker.DBG;
-
-    // Time detection state.
-
-    /**
-     * The last NITZ-sourced time considered. If auto time detection was off at the time this may
-     * not have been used to set the device time, but it can be used if auto time detection is
-     * re-enabled.
-     */
-    private TimestampedValue<Long> mSavedNitzTime;
-
-    // Time Zone detection state.
-
-    /** We always keep the last NITZ signal received in mLatestNitzSignal. */
-    private TimestampedValue<NitzData> mLatestNitzSignal;
-
-    /**
-     * Records whether the device should have a country code available via
-     * {@link DeviceState#getNetworkCountryIsoForPhone()}. Before this an NITZ signal
-     * received is (almost always) not enough to determine time zone. On test networks the country
-     * code should be available but can still be an empty string but this flag indicates that the
-     * information available is unlikely to improve.
-     */
-    private boolean mGotCountryCode = false;
-
-    /**
-     * The last time zone ID that has been determined. It may not have been set as the device time
-     * zone if automatic time zone detection is disabled but may later be used to set the time zone
-     * if the user enables automatic time zone detection.
-     */
-    private String mSavedTimeZoneId;
-
-    /**
-     * Boolean is {@code true} if NITZ has been used to determine a time zone (which may not
-     * ultimately have been used due to user settings). Cleared by {@link #handleNetworkAvailable()}
-     * and {@link #handleNetworkCountryCodeUnavailable()}. The flag can be used when historic NITZ
-     * data may no longer be valid. {@code false} indicates it is reasonable to try to set the time
-     * zone using less reliable algorithms than NITZ-based detection such as by just using network
-     * country code.
-     */
-    private boolean mNitzTimeZoneDetectionSuccessful = false;
-
-    // Miscellaneous dependencies and helpers not related to detection state.
-    private final LocalLog mTimeLog = new LocalLog(15);
-    private final LocalLog mTimeZoneLog = new LocalLog(15);
-    private final GsmCdmaPhone mPhone;
-    private final DeviceState mDeviceState;
-    private final OldTimeServiceHelper mTimeServiceHelper;
-    private final TimeZoneLookupHelper mTimeZoneLookupHelper;
-    /** Wake lock used while setting time of day. */
-    private final PowerManager.WakeLock mWakeLock;
-    private static final String WAKELOCK_TAG = "NitzStateMachine";
-
-    public OldNitzStateMachine(GsmCdmaPhone phone) {
-        this(phone,
-                new OldTimeServiceHelper(phone.getContext()),
-                new DeviceState(phone),
-                new TimeZoneLookupHelper());
-    }
-
-    @VisibleForTesting
-    public OldNitzStateMachine(GsmCdmaPhone phone, OldTimeServiceHelper timeServiceHelper,
-            DeviceState deviceState, TimeZoneLookupHelper timeZoneLookupHelper) {
-        mPhone = phone;
-
-        Context context = phone.getContext();
-        PowerManager powerManager =
-                (PowerManager) context.getSystemService(Context.POWER_SERVICE);
-        mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG);
-
-        mDeviceState = deviceState;
-        mTimeZoneLookupHelper = timeZoneLookupHelper;
-        mTimeServiceHelper = timeServiceHelper;
-        mTimeServiceHelper.setListener(new OldTimeServiceHelper.Listener() {
-            @Override
-            public void onTimeDetectionChange(boolean enabled) {
-                if (enabled) {
-                    handleAutoTimeEnabled();
-                }
-            }
-
-            @Override
-            public void onTimeZoneDetectionChange(boolean enabled) {
-                if (enabled) {
-                    handleAutoTimeZoneEnabled();
-                }
-            }
-        });
-    }
-
-    @Override
-    public void handleNetworkCountryCodeSet(boolean countryChanged) {
-        boolean hadCountryCode = mGotCountryCode;
-        mGotCountryCode = true;
-
-        String isoCountryCode = mDeviceState.getNetworkCountryIsoForPhone();
-        if (!TextUtils.isEmpty(isoCountryCode) && !mNitzTimeZoneDetectionSuccessful) {
-            updateTimeZoneFromNetworkCountryCode(isoCountryCode);
-        }
-
-        if (mLatestNitzSignal != null && (countryChanged || !hadCountryCode)) {
-            updateTimeZoneFromCountryAndNitz();
-        }
-    }
-
-    private void updateTimeZoneFromCountryAndNitz() {
-        String isoCountryCode = mDeviceState.getNetworkCountryIsoForPhone();
-        TimestampedValue<NitzData> nitzSignal = mLatestNitzSignal;
-
-        // TimeZone.getDefault() returns a default zone (GMT) even when time zone have never
-        // been set which makes it difficult to tell if it's what the user / time zone detection
-        // has chosen. isTimeZoneSettingInitialized() tells us whether the time zone of the
-        // device has ever been explicit set by the user or code.
-        final boolean isTimeZoneSettingInitialized =
-                mTimeServiceHelper.isTimeZoneSettingInitialized();
-
-        if (DBG) {
-            Rlog.d(LOG_TAG, "updateTimeZoneFromCountryAndNitz:"
-                    + " isTimeZoneSettingInitialized=" + isTimeZoneSettingInitialized
-                    + " nitzSignal=" + nitzSignal
-                    + " isoCountryCode=" + isoCountryCode);
-        }
-
-        try {
-            NitzData nitzData = nitzSignal.getValue();
-
-            String zoneId;
-            if (nitzData.getEmulatorHostTimeZone() != null) {
-                zoneId = nitzData.getEmulatorHostTimeZone().getID();
-            } else if (!mGotCountryCode) {
-                // We don't have a country code so we won't try to look up the time zone.
-                zoneId = null;
-            } else if (TextUtils.isEmpty(isoCountryCode)) {
-                // We have a country code but it's empty. This is most likely because we're on a
-                // test network that's using a bogus MCC (eg, "001"). Obtain a TimeZone based only
-                // on the NITZ parameters: it's only going to be correct in a few cases but it
-                // should at least have the correct offset.
-                OffsetResult lookupResult = mTimeZoneLookupHelper.lookupByNitz(nitzData);
-                String logMsg = "updateTimeZoneFromCountryAndNitz: lookupByNitz returned"
-                        + " lookupResult=" + lookupResult;
-                if (DBG) {
-                    Rlog.d(LOG_TAG, logMsg);
-                }
-                // We log this in the time zone log because it has been a source of bugs.
-                mTimeZoneLog.log(logMsg);
-
-                zoneId = lookupResult != null ? lookupResult.zoneId : null;
-            } else if (mLatestNitzSignal == null) {
-                if (DBG) {
-                    Rlog.d(LOG_TAG,
-                            "updateTimeZoneFromCountryAndNitz: No cached NITZ data available,"
-                                    + " not setting zone");
-                }
-                zoneId = null;
-            } else if (isNitzSignalOffsetInfoBogus(nitzSignal, isoCountryCode)) {
-                String logMsg = "updateTimeZoneFromCountryAndNitz: Received NITZ looks bogus, "
-                        + " isoCountryCode=" + isoCountryCode
-                        + " nitzSignal=" + nitzSignal;
-                if (DBG) {
-                    Rlog.d(LOG_TAG, logMsg);
-                }
-                // We log this in the time zone log because it has been a source of bugs.
-                mTimeZoneLog.log(logMsg);
-
-                zoneId = null;
-            } else {
-                OffsetResult lookupResult = mTimeZoneLookupHelper.lookupByNitzCountry(
-                        nitzData, isoCountryCode);
-                if (DBG) {
-                    Rlog.d(LOG_TAG, "updateTimeZoneFromCountryAndNitz: using"
-                            + " lookupByNitzCountry(nitzData, isoCountryCode),"
-                            + " nitzData=" + nitzData
-                            + " isoCountryCode=" + isoCountryCode
-                            + " lookupResult=" + lookupResult);
-                }
-                zoneId = lookupResult != null ? lookupResult.zoneId : null;
-            }
-
-            // Log the action taken to the dedicated time zone log.
-            final String tmpLog = "updateTimeZoneFromCountryAndNitz:"
-                    + " isTimeZoneSettingInitialized=" + isTimeZoneSettingInitialized
-                    + " isoCountryCode=" + isoCountryCode
-                    + " nitzSignal=" + nitzSignal
-                    + " zoneId=" + zoneId
-                    + " isTimeZoneDetectionEnabled()="
-                    + mTimeServiceHelper.isTimeZoneDetectionEnabled();
-            mTimeZoneLog.log(tmpLog);
-
-            // Set state as needed.
-            if (zoneId != null) {
-                if (DBG) {
-                    Rlog.d(LOG_TAG, "updateTimeZoneFromCountryAndNitz: zoneId=" + zoneId);
-                }
-                if (mTimeServiceHelper.isTimeZoneDetectionEnabled()) {
-                    setAndBroadcastNetworkSetTimeZone(zoneId);
-                } else {
-                    if (DBG) {
-                        Rlog.d(LOG_TAG, "updateTimeZoneFromCountryAndNitz: skip changing zone"
-                                + " as isTimeZoneDetectionEnabled() is false");
-                    }
-                }
-                mSavedTimeZoneId = zoneId;
-                mNitzTimeZoneDetectionSuccessful = true;
-            } else {
-                if (DBG) {
-                    Rlog.d(LOG_TAG,
-                            "updateTimeZoneFromCountryAndNitz: zoneId == null, do nothing");
-                }
-            }
-        } catch (RuntimeException ex) {
-            Rlog.e(LOG_TAG, "updateTimeZoneFromCountryAndNitz: Processing NITZ data"
-                    + " nitzSignal=" + nitzSignal
-                    + " isoCountryCode=" + isoCountryCode
-                    + " isTimeZoneSettingInitialized=" + isTimeZoneSettingInitialized
-                    + " ex=" + ex);
-        }
-    }
-
-    /**
-     * Returns true if the NITZ signal is definitely bogus, assuming that the country is correct.
-     */
-    private boolean isNitzSignalOffsetInfoBogus(
-            TimestampedValue<NitzData> nitzSignal, String isoCountryCode) {
-
-        if (TextUtils.isEmpty(isoCountryCode)) {
-            // We cannot say for sure.
-            return false;
-        }
-
-        NitzData newNitzData = nitzSignal.getValue();
-        boolean zeroOffsetNitz = newNitzData.getLocalOffsetMillis() == 0 && !newNitzData.isDst();
-        return zeroOffsetNitz && !countryUsesUtc(isoCountryCode, nitzSignal);
-    }
-
-    private boolean countryUsesUtc(
-            String isoCountryCode, TimestampedValue<NitzData> nitzSignal) {
-        return mTimeZoneLookupHelper.countryUsesUtc(
-                isoCountryCode,
-                nitzSignal.getValue().getCurrentTimeInMillis());
-    }
-
-    @Override
-    public void handleNetworkAvailable() {
-        if (DBG) {
-            Rlog.d(LOG_TAG, "handleNetworkAvailable: mNitzTimeZoneDetectionSuccessful="
-                    + mNitzTimeZoneDetectionSuccessful
-                    + ", Setting mNitzTimeZoneDetectionSuccessful=false");
-        }
-        mNitzTimeZoneDetectionSuccessful = false;
-    }
-
-    @Override
-    public void handleNetworkCountryCodeUnavailable() {
-        if (DBG) {
-            Rlog.d(LOG_TAG, "handleNetworkCountryCodeUnavailable");
-        }
-
-        mGotCountryCode = false;
-        mNitzTimeZoneDetectionSuccessful = false;
-    }
-
-    @Override
-    public void handleNitzReceived(TimestampedValue<NitzData> nitzSignal) {
-        // Always store the latest NITZ signal received.
-        mLatestNitzSignal = nitzSignal;
-
-        updateTimeZoneFromCountryAndNitz();
-        updateTimeFromNitz();
-    }
-
-    private void updateTimeFromNitz() {
-        TimestampedValue<NitzData> nitzSignal = mLatestNitzSignal;
-        try {
-            boolean ignoreNitz = mDeviceState.getIgnoreNitz();
-            if (ignoreNitz) {
-                if (DBG) {
-                    Rlog.d(LOG_TAG,
-                            "updateTimeFromNitz: Not setting clock because gsm.ignore-nitz is set");
-                }
-                return;
-            }
-
-            try {
-                // Acquire the wake lock as we are reading the elapsed realtime clock and system
-                // clock.
-                mWakeLock.acquire();
-
-                // Validate the nitzTimeSignal to reject obviously bogus elapsedRealtime values.
-                long elapsedRealtime = mTimeServiceHelper.elapsedRealtime();
-                long millisSinceNitzReceived =
-                        elapsedRealtime - nitzSignal.getReferenceTimeMillis();
-                if (millisSinceNitzReceived < 0 || millisSinceNitzReceived > Integer.MAX_VALUE) {
-                    if (DBG) {
-                        Rlog.d(LOG_TAG, "updateTimeFromNitz: not setting time, unexpected"
-                                + " elapsedRealtime=" + elapsedRealtime
-                                + " nitzSignal=" + nitzSignal);
-                    }
-                    return;
-                }
-
-                // Adjust the NITZ time by the delay since it was received to get the time now.
-                long adjustedCurrentTimeMillis =
-                        nitzSignal.getValue().getCurrentTimeInMillis() + millisSinceNitzReceived;
-                long gained = adjustedCurrentTimeMillis - mTimeServiceHelper.currentTimeMillis();
-
-                if (mTimeServiceHelper.isTimeDetectionEnabled()) {
-                    String logMsg = "updateTimeFromNitz:"
-                            + " nitzSignal=" + nitzSignal
-                            + " adjustedCurrentTimeMillis=" + adjustedCurrentTimeMillis
-                            + " millisSinceNitzReceived= " + millisSinceNitzReceived
-                            + " gained=" + gained;
-
-                    if (mSavedNitzTime == null) {
-                        logMsg += ": First update received.";
-                        setAndBroadcastNetworkSetTime(logMsg, adjustedCurrentTimeMillis);
-                    } else {
-                        long elapsedRealtimeSinceLastSaved = mTimeServiceHelper.elapsedRealtime()
-                                - mSavedNitzTime.getReferenceTimeMillis();
-                        int nitzUpdateSpacing = mDeviceState.getNitzUpdateSpacingMillis();
-                        int nitzUpdateDiff = mDeviceState.getNitzUpdateDiffMillis();
-                        if (elapsedRealtimeSinceLastSaved > nitzUpdateSpacing
-                                || Math.abs(gained) > nitzUpdateDiff) {
-                            // Either it has been a while since we received an update, or the gain
-                            // is sufficiently large that we want to act on it.
-                            logMsg += ": New update received.";
-                            setAndBroadcastNetworkSetTime(logMsg, adjustedCurrentTimeMillis);
-                        } else {
-                            if (DBG) {
-                                Rlog.d(LOG_TAG, logMsg + ": Update throttled.");
-                            }
-
-                            // Return early. This means that we don't reset the
-                            // mSavedNitzTime for next time and that we may act on more
-                            // NITZ time signals overall but should end up with a system clock that
-                            // tracks NITZ more closely than if we saved throttled values (which
-                            // would reset mSavedNitzTime.elapsedRealtime used to calculate time
-                            // since the last NITZ signal was received).
-                            return;
-                        }
-                    }
-                }
-
-                // Save the last NITZ time signal used so we can return to it later
-                // if auto-time detection is toggled.
-                mSavedNitzTime = new TimestampedValue<>(
-                        nitzSignal.getReferenceTimeMillis(),
-                        nitzSignal.getValue().getCurrentTimeInMillis());
-            } finally {
-                mWakeLock.release();
-            }
-        } catch (RuntimeException ex) {
-            Rlog.e(LOG_TAG, "updateTimeFromNitz: Processing NITZ data"
-                    + " nitzSignal=" + nitzSignal
-                    + " ex=" + ex);
-        }
-    }
-
-    private void setAndBroadcastNetworkSetTimeZone(String zoneId) {
-        if (DBG) {
-            Rlog.d(LOG_TAG, "setAndBroadcastNetworkSetTimeZone: zoneId=" + zoneId);
-        }
-        mTimeServiceHelper.setDeviceTimeZone(zoneId);
-        if (DBG) {
-            Rlog.d(LOG_TAG,
-                    "setAndBroadcastNetworkSetTimeZone: called setDeviceTimeZone()"
-                            + " zoneId=" + zoneId);
-        }
-    }
-
-    private void setAndBroadcastNetworkSetTime(String msg, long time) {
-        if (!mWakeLock.isHeld()) {
-            Rlog.w(LOG_TAG, "setAndBroadcastNetworkSetTime: Wake lock not held while setting device"
-                    + " time (msg=" + msg + ")");
-        }
-
-        msg = "setAndBroadcastNetworkSetTime: [Setting time to time=" + time + "]:" + msg;
-        if (DBG) {
-            Rlog.d(LOG_TAG, msg);
-        }
-        mTimeLog.log(msg);
-        mTimeServiceHelper.setDeviceTime(time);
-        TelephonyMetrics.getInstance().writeNITZEvent(mPhone.getPhoneId(), time);
-    }
-
-    private void handleAutoTimeEnabled() {
-        if (DBG) {
-            Rlog.d(LOG_TAG, "handleAutoTimeEnabled: Reverting to NITZ Time:"
-                    + " mSavedNitzTime=" + mSavedNitzTime);
-        }
-        if (mSavedNitzTime != null) {
-            try {
-                // Acquire the wakelock as we're reading the elapsed realtime clock here.
-                mWakeLock.acquire();
-
-                long elapsedRealtime = mTimeServiceHelper.elapsedRealtime();
-                String msg = "mSavedNitzTime: Reverting to NITZ time"
-                        + " elapsedRealtime=" + elapsedRealtime
-                        + " mSavedNitzTime=" + mSavedNitzTime;
-                long adjustedCurrentTimeMillis = mSavedNitzTime.getValue()
-                        + (elapsedRealtime - mSavedNitzTime.getReferenceTimeMillis());
-                setAndBroadcastNetworkSetTime(msg, adjustedCurrentTimeMillis);
-            } finally {
-                mWakeLock.release();
-            }
-        }
-    }
-
-    private void handleAutoTimeZoneEnabled() {
-        String tmpLog = "handleAutoTimeZoneEnabled: Reverting to NITZ TimeZone:"
-                + " mSavedTimeZoneId=" + mSavedTimeZoneId;
-        if (DBG) {
-            Rlog.d(LOG_TAG, tmpLog);
-        }
-        mTimeZoneLog.log(tmpLog);
-        if (mSavedTimeZoneId != null) {
-            setAndBroadcastNetworkSetTimeZone(mSavedTimeZoneId);
-        }
-    }
-
-    @Override
-    public void dumpState(PrintWriter pw) {
-        // Time Detection State
-        pw.println(" mSavedTime=" + mSavedNitzTime);
-
-        // Time Zone Detection State
-        pw.println(" mLatestNitzSignal=" + mLatestNitzSignal);
-        pw.println(" mGotCountryCode=" + mGotCountryCode);
-        pw.println(" mSavedTimeZoneId=" + mSavedTimeZoneId);
-        pw.println(" mNitzTimeZoneDetectionSuccessful=" + mNitzTimeZoneDetectionSuccessful);
-
-        // Miscellaneous
-        pw.println(" mWakeLock=" + mWakeLock);
-        pw.flush();
-    }
-
-    @Override
-    public void dumpLogs(FileDescriptor fd, IndentingPrintWriter ipw, String[] args) {
-        ipw.println(" Time Logs:");
-        ipw.increaseIndent();
-        mTimeLog.dump(fd, ipw, args);
-        ipw.decreaseIndent();
-
-        ipw.println(" Time zone Logs:");
-        ipw.increaseIndent();
-        mTimeZoneLog.dump(fd, ipw, args);
-        ipw.decreaseIndent();
-    }
-
-    /**
-     * Update time zone by network country code, works well on countries which only have one time
-     * zone or multiple zones with the same offset.
-     *
-     * @param iso Country code from network MCC
-     */
-    private void updateTimeZoneFromNetworkCountryCode(String iso) {
-        CountryResult lookupResult = mTimeZoneLookupHelper.lookupByCountry(
-                iso, mTimeServiceHelper.currentTimeMillis());
-        if (lookupResult != null && lookupResult.allZonesHaveSameOffset) {
-            String logMsg = "updateTimeZoneFromNetworkCountryCode: tz result found"
-                    + " iso=" + iso
-                    + " lookupResult=" + lookupResult;
-            if (DBG) {
-                Rlog.d(LOG_TAG, logMsg);
-            }
-            mTimeZoneLog.log(logMsg);
-            String zoneId = lookupResult.zoneId;
-            if (mTimeServiceHelper.isTimeZoneDetectionEnabled()) {
-                setAndBroadcastNetworkSetTimeZone(zoneId);
-            }
-            mSavedTimeZoneId = zoneId;
-        } else {
-            if (DBG) {
-                Rlog.d(LOG_TAG, "updateTimeZoneFromNetworkCountryCode: no good zone for"
-                        + " iso=" + iso
-                        + " lookupResult=" + lookupResult);
-            }
-        }
-    }
-
-    public boolean getNitzTimeZoneDetectionSuccessful() {
-        return mNitzTimeZoneDetectionSuccessful;
-    }
-
-    @Override
-    public NitzData getCachedNitzData() {
-        return mLatestNitzSignal != null ? mLatestNitzSignal.getValue() : null;
-    }
-
-    @Override
-    public String getSavedTimeZoneId() {
-        return mSavedTimeZoneId;
-    }
-
-}
diff --git a/src/java/com/android/internal/telephony/OldTimeServiceHelper.java b/src/java/com/android/internal/telephony/OldTimeServiceHelper.java
deleted file mode 100644
index 9c7a763..0000000
--- a/src/java/com/android/internal/telephony/OldTimeServiceHelper.java
+++ /dev/null
@@ -1,193 +0,0 @@
-/*
- * Copyright 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.app.AlarmManager;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.database.ContentObserver;
-import android.os.Handler;
-import android.os.SystemClock;
-import android.os.SystemProperties;
-import android.os.UserHandle;
-import android.provider.Settings;
-
-/**
- * An interface to various time / time zone detection behaviors that should be centralized into a
- * new service.
- */
-// Non-final to allow mocking.
-public class OldTimeServiceHelper {
-
-    /**
-     * Callback interface for automatic detection enable/disable changes.
-     */
-    public interface Listener {
-        /**
-         * Automatic time detection has been enabled or disabled.
-         */
-        void onTimeDetectionChange(boolean enabled);
-
-        /**
-         * Automatic time zone detection has been enabled or disabled.
-         */
-        void onTimeZoneDetectionChange(boolean enabled);
-    }
-
-    private static final String TIMEZONE_PROPERTY = "persist.sys.timezone";
-
-    private final Context mContext;
-    private final ContentResolver mCr;
-
-    private Listener mListener;
-
-    /** Creates a TimeServiceHelper */
-    public OldTimeServiceHelper(Context context) {
-        mContext = context;
-        mCr = context.getContentResolver();
-    }
-
-    /**
-     * Sets a listener that will be called when the automatic time / time zone detection setting
-     * changes.
-     */
-    public void setListener(Listener listener) {
-        if (listener == null) {
-            throw new NullPointerException("listener==null");
-        }
-        if (mListener != null) {
-            throw new IllegalStateException("listener already set");
-        }
-        this.mListener = listener;
-        mCr.registerContentObserver(
-                Settings.Global.getUriFor(Settings.Global.AUTO_TIME), true,
-                new ContentObserver(new Handler()) {
-                    public void onChange(boolean selfChange) {
-                        listener.onTimeDetectionChange(isTimeDetectionEnabled());
-                    }
-                });
-        mCr.registerContentObserver(
-                Settings.Global.getUriFor(Settings.Global.AUTO_TIME_ZONE), true,
-                new ContentObserver(new Handler()) {
-                    public void onChange(boolean selfChange) {
-                        listener.onTimeZoneDetectionChange(isTimeZoneDetectionEnabled());
-                    }
-                });
-    }
-
-    /**
-     * Returns the same value as {@link System#currentTimeMillis()}.
-     */
-    public long currentTimeMillis() {
-        return System.currentTimeMillis();
-    }
-
-    /**
-     * Returns the same value as {@link SystemClock#elapsedRealtime()}.
-     */
-    public long elapsedRealtime() {
-        return SystemClock.elapsedRealtime();
-    }
-
-    /**
-     * Returns true if the device has an explicit time zone set.
-     */
-    public boolean isTimeZoneSettingInitialized() {
-        return isTimeZoneSettingInitializedStatic();
-
-    }
-
-    /**
-     * Returns true if automatic time detection is enabled in settings.
-     */
-    public boolean isTimeDetectionEnabled() {
-        try {
-            return Settings.Global.getInt(mCr, Settings.Global.AUTO_TIME) > 0;
-        } catch (Settings.SettingNotFoundException snfe) {
-            return true;
-        }
-    }
-
-    /**
-     * Returns true if automatic time zone detection is enabled in settings.
-     */
-    public boolean isTimeZoneDetectionEnabled() {
-        try {
-            return Settings.Global.getInt(mCr, Settings.Global.AUTO_TIME_ZONE) > 0;
-        } catch (Settings.SettingNotFoundException snfe) {
-            return true;
-        }
-    }
-
-    /**
-     * Set the device time zone and send out a sticky broadcast so the system can
-     * determine if the timezone was set by the carrier.
-     *
-     * @param zoneId timezone set by carrier
-     */
-    public void setDeviceTimeZone(String zoneId) {
-        setDeviceTimeZoneStatic(mContext, zoneId);
-    }
-
-    /**
-     * Set the time and Send out a sticky broadcast so the system can determine
-     * if the time was set by the carrier.
-     *
-     * @param time time set by network
-     */
-    public void setDeviceTime(long time) {
-        SystemClock.setCurrentTimeMillis(time);
-        Intent intent = new Intent(TelephonyIntents.ACTION_NETWORK_SET_TIME);
-        intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
-        intent.putExtra("time", time);
-        mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
-    }
-
-    /**
-     * Static implementation of isTimeZoneSettingInitialized() for use from {@link MccTable}. This
-     * is a hack to deflake TelephonyTests when running on a device with a real SIM: in that
-     * situation real service events may come in while a TelephonyTest is running, leading to flakes
-     * as the real / fake instance of TimeServiceHelper is swapped in and out from
-     * {@link TelephonyComponentFactory}.
-     */
-    static boolean isTimeZoneSettingInitializedStatic() {
-        // timezone.equals("GMT") will be true and only true if the timezone was
-        // set to a default value by the system server (when starting, system server
-        // sets the persist.sys.timezone to "GMT" if it's not set). "GMT" is not used by
-        // any code that sets it explicitly (in case where something sets GMT explicitly,
-        // "Etc/GMT" Olsen ID would be used).
-        // TODO(b/64056758): Remove "timezone.equals("GMT")" hack when there's a
-        // better way of telling if the value has been defaulted.
-
-        String timeZoneId = SystemProperties.get(TIMEZONE_PROPERTY);
-        return timeZoneId != null && timeZoneId.length() > 0 && !timeZoneId.equals("GMT");
-    }
-
-    /**
-     * Static method for use by MccTable. See {@link #isTimeZoneSettingInitializedStatic()} for
-     * explanation.
-     */
-    static void setDeviceTimeZoneStatic(Context context, String zoneId) {
-        AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
-        alarmManager.setTimeZone(zoneId);
-        Intent intent = new Intent(TelephonyIntents.ACTION_NETWORK_SET_TIMEZONE);
-        intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
-        intent.putExtra("time-zone", zoneId);
-        context.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
-    }
-}
diff --git a/src/java/com/android/internal/telephony/Phone.java b/src/java/com/android/internal/telephony/Phone.java
index dcb117e..eddf121 100644
--- a/src/java/com/android/internal/telephony/Phone.java
+++ b/src/java/com/android/internal/telephony/Phone.java
@@ -327,7 +327,7 @@
     @UnsupportedAppUsage
     protected Phone mImsPhone = null;
 
-    private final AtomicReference<RadioCapability> mRadioCapability =
+    protected final AtomicReference<RadioCapability> mRadioCapability =
             new AtomicReference<RadioCapability>();
 
     private static final int DEFAULT_REPORT_INTERVAL_MS = 200;
@@ -380,6 +380,8 @@
 
     private final RegistrantList mCellInfoRegistrants = new RegistrantList();
 
+    private final RegistrantList mOtaspRegistrants = new RegistrantList();
+
     protected Registrant mPostDialHandler;
 
     protected final LocalLog mLocalLog;
@@ -1418,7 +1420,7 @@
 
     private void updateSavedNetworkOperator(NetworkSelectMessage nsm) {
         int subId = getSubId();
-        if (SubscriptionManager.isValidSubscriptionId(subId)) {
+        if (SubscriptionController.getInstance().isActiveSubId(subId)) {
             // open the shared preferences editor, and write the value.
             // nsm.operatorNumeric is "" if we're in automatic.selection.
             SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
@@ -1923,7 +1925,7 @@
     private int getCallForwardingIndicatorFromSharedPref() {
         int status = IccRecords.CALL_FORWARDING_STATUS_DISABLED;
         int subId = getSubId();
-        if (SubscriptionManager.isValidSubscriptionId(subId)) {
+        if (SubscriptionController.getInstance().isActiveSubId(subId)) {
             SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
             status = sp.getInt(CF_STATUS + subId, IccRecords.CALL_FORWARDING_STATUS_UNKNOWN);
             Rlog.d(LOG_TAG, "getCallForwardingIndicatorFromSharedPref: for subId " + subId + "= " +
@@ -2318,6 +2320,15 @@
         mCi.nvResetConfig(3 /* factory NV reset */, response);
     }
 
+    /**
+     * Perform modem configuration erase. Used for network reset
+     *
+     * @param response Callback message.
+     */
+    public void eraseModemConfig(Message response) {
+        mCi.nvResetConfig(2 /* erase NV */, response);
+    }
+
     public void notifyDataActivity() {
         mNotifier.notifyDataActivity(this);
     }
@@ -2347,7 +2358,7 @@
 
     @UnsupportedAppUsage
     public void notifyOtaspChanged(int otaspMode) {
-        mNotifier.notifyOtaspChanged(this, otaspMode);
+        mOtaspRegistrants.notifyRegistrants(new AsyncResult(null, otaspMode, null));
     }
 
     public void notifyVoiceActivationStateChanged(int state) {
@@ -2486,7 +2497,7 @@
     public void setVoiceMessageCount(int countWaiting) {
         mVmCount = countWaiting;
         int subId = getSubId();
-        if (SubscriptionManager.isValidSubscriptionId(subId)) {
+        if (SubscriptionController.getInstance().isActiveSubId(subId)) {
 
             Rlog.d(LOG_TAG, "setVoiceMessageCount: Storing Voice Mail Count = " + countWaiting +
                     " for mVmCountKey = " + VM_COUNT + subId + " in preferences.");
@@ -2515,7 +2526,7 @@
     protected int getStoredVoiceMessageCount() {
         int countVoiceMessages = 0;
         int subId = getSubId();
-        if (SubscriptionManager.isValidSubscriptionId(subId)) {
+        if (SubscriptionController.getInstance().isActiveSubId(subId)) {
             int invalidCount = -2;  //-1 is not really invalid. It is used for unknown number of vm
             SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
             int countFromSP = sp.getInt(VM_COUNT + subId, invalidCount);
@@ -2745,6 +2756,42 @@
     }
 
     /**
+     * Register for notifications when OTA Service Provisioning mode has changed.
+     *
+     * <p>The mode is integer. {@link TelephonyManager#OTASP_UNKNOWN}
+     * means the value is currently unknown and the system should wait until
+     * {@link TelephonyManager#OTASP_NEEDED} or {@link TelephonyManager#OTASP_NOT_NEEDED} is
+     * received before making the decision to perform OTASP or not.
+     *
+     * @param h Handler that receives the notification message.
+     * @param what User-defined message code.
+     * @param obj User object.
+     */
+    public void registerForOtaspChange(Handler h, int what, Object obj) {
+        checkCorrectThread(h);
+        mOtaspRegistrants.addUnique(h, what, obj);
+        // notify first
+        new Registrant(h, what, obj).notifyRegistrant(new AsyncResult(null, getOtasp(), null));
+    }
+
+    /**
+     * Unegister for notifications when OTA Service Provisioning mode has changed.
+     * @param h Handler to be removed from the registrant list.
+     */
+    public void unregisterForOtaspChange(Handler h) {
+        mOtaspRegistrants.remove(h);
+    }
+
+    /**
+     * Returns the current OTA Service Provisioning mode.
+     *
+     * @see registerForOtaspChange
+     */
+    public int getOtasp() {
+        return TelephonyManager.OTASP_UNKNOWN;
+    }
+
+    /**
      * Register for notifications when CDMA call waiting comes
      *
      * @param h Handler that receives the notification message.
@@ -3723,7 +3770,7 @@
 
     protected void setPreferredNetworkTypeIfSimLoaded() {
         int subId = getSubId();
-        if (SubscriptionManager.isValidSubscriptionId(subId)) {
+        if (SubscriptionManager.from(mContext).isActiveSubId(subId)) {
             int type = PhoneFactory.calculatePreferredNetworkType(mContext, getSubId());
             setPreferredNetworkType(type, null);
         }
@@ -3945,6 +3992,10 @@
     public void cancelUSSD(Message msg) {
     }
 
+    public String getOperatorNumeric() {
+        return "";
+    }
+
     /**
      * Set boolean broadcastEmergencyCallStateChanges
      */
@@ -4009,6 +4060,29 @@
     }
 
     /**
+     * Check if the device can only make the emergency call. The device is emergency call only if
+     * none of the phone is in service, and one of them has the capability to make the emergency
+     * call.
+     *
+     * @return {@code True} if the device is emergency call only, otherwise return {@code False}.
+     */
+    public static boolean isEmergencyCallOnly() {
+        boolean isEmergencyCallOnly = false;
+        for (Phone phone : PhoneFactory.getPhones()) {
+            if (phone != null) {
+                ServiceState ss = phone.getServiceStateTracker().getServiceState();
+                // One of the phone is in service, hence the device is not emergency call only.
+                if (ss.getState() == ServiceState.STATE_IN_SERVICE
+                        || ss.getDataRegState() == ServiceState.STATE_IN_SERVICE) {
+                    return false;
+                }
+                isEmergencyCallOnly |= ss.isEmergencyOnly();
+            }
+        }
+        return isEmergencyCallOnly;
+    }
+
+    /**
      * Get data connection tracker based on the transport type
      *
      * @param transportType Transport type defined in AccessNetworkConstants.TransportType
@@ -4070,6 +4144,7 @@
         pw.println(" getActiveApnTypes()=" + getActiveApnTypes());
         pw.println(" needsOtaServiceProvisioning=" + needsOtaServiceProvisioning());
         pw.println(" isInEmergencySmsMode=" + isInEmergencySmsMode());
+        pw.println(" service state=" + getServiceState());
         pw.flush();
         pw.println("++++++++++++++++++++++++++++++++");
 
diff --git a/src/java/com/android/internal/telephony/PhoneConfigurationManager.java b/src/java/com/android/internal/telephony/PhoneConfigurationManager.java
index 2771f25..197a7f3 100644
--- a/src/java/com/android/internal/telephony/PhoneConfigurationManager.java
+++ b/src/java/com/android/internal/telephony/PhoneConfigurationManager.java
@@ -208,12 +208,14 @@
         try {
             return getPhoneStatusFromCache(phoneId);
         } catch (NoSuchElementException ex) {
-            updatePhoneStatus(phone);
             // Return true if modem status cannot be retrieved. For most cases, modem status
             // is on. And for older version modems, GET_MODEM_STATUS and disable modem are not
             // supported. Modem is always on.
             //TODO: this should be fixed in R to support a third status UNKNOWN b/131631629
             return true;
+        } finally {
+            //in either case send an asynchronous request to retrieve the phone status
+            updatePhoneStatus(phone);
         }
     }
 
diff --git a/src/java/com/android/internal/telephony/PhoneFactory.java b/src/java/com/android/internal/telephony/PhoneFactory.java
index 452ee25..6329848 100644
--- a/src/java/com/android/internal/telephony/PhoneFactory.java
+++ b/src/java/com/android/internal/telephony/PhoneFactory.java
@@ -141,6 +141,8 @@
                 }
 
                 sPhoneNotifier = new DefaultPhoneNotifier();
+                TelephonyComponentFactory telephonyComponentFactory
+                        = TelephonyComponentFactory.getInstance();
 
                 int cdmaSubscription = CdmaSubscriptionSourceManager.getDefault(context);
                 Rlog.i(LOG_TAG, "Cdma Subscription set to " + cdmaSubscription);
@@ -148,9 +150,7 @@
                 /* In case of multi SIM mode two instances of Phone, RIL are created,
                    where as in single SIM mode only instance. isMultiSimEnabled() function checks
                    whether it is single SIM or multi SIM mode */
-                TelephonyManager tm = (TelephonyManager) context.getSystemService(
-                        Context.TELEPHONY_SERVICE);
-                int numPhones = tm.getPhoneCount();
+                int numPhones = TelephonyManager.getDefault().getPhoneCount();
 
                 int[] networkModes = new int[numPhones];
                 sPhones = new Phone[numPhones];
@@ -163,8 +163,8 @@
                     networkModes[i] = RILConstants.PREFERRED_NETWORK_MODE;
 
                     Rlog.i(LOG_TAG, "Network Mode set to " + Integer.toString(networkModes[i]));
-                    sCommandsInterfaces[i] = new RIL(context, networkModes[i],
-                            cdmaSubscription, i);
+                    sCommandsInterfaces[i] = telephonyComponentFactory.inject(RIL.class.getName()).
+                            makeRIL(context, networkModes[i], cdmaSubscription, i);
                 }
 
                 // Instantiate UiccController so that all other classes can just
@@ -172,8 +172,11 @@
                 sUiccController = UiccController.make(context, sCommandsInterfaces);
 
                 Rlog.i(LOG_TAG, "Creating SubscriptionController");
-                SubscriptionController.init(context, sCommandsInterfaces);
-                MultiSimSettingController.init(context, SubscriptionController.getInstance());
+                telephonyComponentFactory.inject(SubscriptionController.class.
+                                getName()).initSubscriptionController(context, sCommandsInterfaces);
+                telephonyComponentFactory.inject(MultiSimSettingController.class.
+                                getName()).initMultiSimSettingController(context,
+                                SubscriptionController.getInstance());
 
                 if (context.getPackageManager().hasSystemFeature(
                         PackageManager.FEATURE_TELEPHONY_EUICC)) {
@@ -184,13 +187,15 @@
                 for (int i = 0; i < numPhones; i++) {
                     Phone phone = null;
                     int phoneType = TelephonyManager.getPhoneType(networkModes[i]);
+                    TelephonyComponentFactory injectedComponentFactory =
+                            telephonyComponentFactory.inject(GsmCdmaPhone.class.getName());
                     if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
-                        phone = new GsmCdmaPhone(context,
+                        phone = injectedComponentFactory.makePhone(context,
                                 sCommandsInterfaces[i], sPhoneNotifier, i,
                                 PhoneConstants.PHONE_TYPE_GSM,
                                 TelephonyComponentFactory.getInstance());
                     } else if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
-                        phone = new GsmCdmaPhone(context,
+                        phone = injectedComponentFactory.makePhone(context,
                                 sCommandsInterfaces[i], sPhoneNotifier, i,
                                 PhoneConstants.PHONE_TYPE_CDMA_LTE,
                                 TelephonyComponentFactory.getInstance());
@@ -203,10 +208,8 @@
                 // Set the default phone in base class.
                 // FIXME: This is a first best guess at what the defaults will be. It
                 // FIXME: needs to be done in a more controlled manner in the future.
-                if (numPhones > 0) {
-                    sPhone = sPhones[0];
-                    sCommandsInterface = sCommandsInterfaces[0];
-                }
+                sPhone = sPhones[0];
+                sCommandsInterface = sCommandsInterfaces[0];
 
                 // Ensure that we have a default SMS app. Requesting the app with
                 // updateIfNeeded set to true is enough to configure a default SMS app.
@@ -224,8 +227,10 @@
                 sMadeDefaults = true;
 
                 Rlog.i(LOG_TAG, "Creating SubInfoRecordUpdater ");
-                sSubInfoRecordUpdater = new SubscriptionInfoUpdater(
-                        BackgroundThread.get().getLooper(), context, sPhones, sCommandsInterfaces);
+                sSubInfoRecordUpdater = telephonyComponentFactory.inject(
+                        SubscriptionInfoUpdater.class.getName()).
+                        makeSubscriptionInfoUpdater(BackgroundThread.get().
+                        getLooper(), context, sPhones, sCommandsInterfaces);
                 SubscriptionController.getInstance().updatePhonesAvailability(sPhones);
 
 
@@ -268,7 +273,8 @@
                 int maxActivePhones = sPhoneConfigurationManager
                         .getNumberOfModemsWithSimultaneousDataConnections();
 
-                sPhoneSwitcher = PhoneSwitcher.make(maxActivePhones, numPhones,
+                sPhoneSwitcher = telephonyComponentFactory.inject(PhoneSwitcher.class.getName()).
+                        makePhoneSwitcher(maxActivePhones, numPhones,
                         sContext, sc, Looper.myLooper(), tr, sCommandsInterfaces,
                         sPhones);
 
@@ -279,11 +285,12 @@
 
                 sNotificationChannelController = new NotificationChannelController(context);
 
-                sTelephonyNetworkFactories = new TelephonyNetworkFactory[numPhones];
                 for (int i = 0; i < numPhones; i++) {
                     sTelephonyNetworkFactories[i] = new TelephonyNetworkFactory(
-                            sSubscriptionMonitor, Looper.myLooper(), sPhones[i]);
+                            sSubscriptionMonitor, Looper.myLooper(), sPhones[i], sPhoneSwitcher);
                 }
+                telephonyComponentFactory.inject(TelephonyComponentFactory.class.getName()).
+                        makeExtTelephonyClasses(context, sPhones, sCommandsInterfaces);
             }
         }
     }
diff --git a/src/java/com/android/internal/telephony/PhoneNotifier.java b/src/java/com/android/internal/telephony/PhoneNotifier.java
index 2e7b461..7840379 100644
--- a/src/java/com/android/internal/telephony/PhoneNotifier.java
+++ b/src/java/com/android/internal/telephony/PhoneNotifier.java
@@ -54,8 +54,6 @@
 
     void notifyDataActivity(Phone sender);
 
-    void notifyOtaspChanged(Phone sender, int otaspMode);
-
     void notifyCellInfo(Phone sender, List<CellInfo> cellInfo);
 
     /** Notify of change to PhysicalChannelConfiguration. */
diff --git a/src/java/com/android/internal/telephony/PhoneSubInfoController.java b/src/java/com/android/internal/telephony/PhoneSubInfoController.java
index c5b7eda..7a52f9f 100644
--- a/src/java/com/android/internal/telephony/PhoneSubInfoController.java
+++ b/src/java/com/android/internal/telephony/PhoneSubInfoController.java
@@ -222,6 +222,11 @@
         if (!SubscriptionManager.isValidPhoneId(phoneId)) {
             phoneId = 0;
         }
+        if (phoneId < 0 || phoneId >= mPhone.length) {
+            log("getPhone, phoneId: " + phoneId +
+                ", mPhone.length: " + mPhone.length + ", should never here!");
+            return null;
+        }
         return mPhone[phoneId];
     }
 
@@ -467,6 +472,11 @@
         if (!SubscriptionManager.isValidPhoneId(phoneId)) {
             phoneId = 0;
         }
+        if (phoneId < 0 || phoneId >= mPhone.length) {
+            log("callPhoneMethodForPhoneIdWithReadDeviceIdentifiersCheck, phoneId: " + phoneId +
+                ", mPhone.length: " + mPhone.length + ", should never here!");
+            return null;
+        }
         final Phone phone = mPhone[phoneId];
         if (phone == null) {
             return null;
diff --git a/src/java/com/android/internal/telephony/PhoneSwitcher.java b/src/java/com/android/internal/telephony/PhoneSwitcher.java
index 638da48..636891f 100644
--- a/src/java/com/android/internal/telephony/PhoneSwitcher.java
+++ b/src/java/com/android/internal/telephony/PhoneSwitcher.java
@@ -17,6 +17,7 @@
 package com.android.internal.telephony;
 
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.telephony.CarrierConfigManager.KEY_DATA_SWITCH_VALIDATION_TIMEOUT_LONG;
 import static android.telephony.SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
 import static android.telephony.SubscriptionManager.INVALID_PHONE_INDEX;
 import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
@@ -30,7 +31,6 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.net.ConnectivityManager;
-import android.net.ConnectivityManager.NetworkCallback;
 import android.net.MatchAllNetworkSpecifier;
 import android.net.Network;
 import android.net.NetworkCapabilities;
@@ -42,10 +42,12 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.os.PersistableBundle;
 import android.os.Registrant;
 import android.os.RegistrantList;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.telephony.CarrierConfigManager;
 import android.telephony.PhoneCapability;
 import android.telephony.PhoneStateListener;
 import android.telephony.Rlog;
@@ -78,8 +80,8 @@
  * the active phones.  Note we don't wait for data attach (which may not happen anyway).
  */
 public class PhoneSwitcher extends Handler {
-    private static final String LOG_TAG = "PhoneSwitcher";
-    private static final boolean VDBG = false;
+    protected static final String LOG_TAG = "PhoneSwitcher";
+    protected static final boolean VDBG = false;
 
     private static final int DEFAULT_NETWORK_CHANGE_TIMEOUT_MS = 5000;
     private static final int MODEM_COMMAND_RETRY_PERIOD_MS     = 5000;
@@ -149,33 +151,33 @@
         }
     }
 
-    private final List<DcRequest> mPrioritizedDcRequests = new ArrayList<DcRequest>();
-    private final RegistrantList mActivePhoneRegistrants;
-    private final SubscriptionController mSubscriptionController;
-    private final int[] mPhoneSubscriptions;
-    private final CommandsInterface[] mCommandsInterfaces;
-    private final Context mContext;
-    private final PhoneState[] mPhoneStates;
+    protected final List<DcRequest> mPrioritizedDcRequests = new ArrayList<DcRequest>();
+    protected final RegistrantList mActivePhoneRegistrants;
+    protected final SubscriptionController mSubscriptionController;
+    protected final int[] mPhoneSubscriptions;
+    protected final CommandsInterface[] mCommandsInterfaces;
+    protected final Context mContext;
+    protected final PhoneState[] mPhoneStates;
     @UnsupportedAppUsage
-    private final int mNumPhones;
+    protected final int mNumPhones;
     @UnsupportedAppUsage
-    private final Phone[] mPhones;
+    protected final Phone[] mPhones;
     private final LocalLog mLocalLog;
     @VisibleForTesting
     public final PhoneStateListener mPhoneStateListener;
-    private final CellularNetworkValidator mValidator;
+    protected final CellularNetworkValidator mValidator;
     @VisibleForTesting
     public final CellularNetworkValidator.ValidationCallback mValidationCallback =
             (validated, subId) -> Message.obtain(PhoneSwitcher.this,
                     EVENT_NETWORK_VALIDATION_DONE, subId, validated ? 1 : 0).sendToTarget();
     @UnsupportedAppUsage
-    private int mMaxActivePhones;
-    private static PhoneSwitcher sPhoneSwitcher = null;
+    protected int mMaxActivePhones;
+    protected static PhoneSwitcher sPhoneSwitcher = null;
 
     // Which primary (non-opportunistic) subscription is set as data subscription among all primary
     // subscriptions. This value usually comes from user setting, and it's the subscription used for
     // Internet data if mOpptDataSubId is not set.
-    private int mPrimaryDataSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+    protected int mPrimaryDataSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
 
     // mOpptDataSubId must be an active subscription. If it's set, it overrides mPrimaryDataSubId
     // to be used for Internet data.
@@ -183,7 +185,7 @@
 
     // The phone ID that has an active voice call. If set, and its mobile data setting is on,
     // it will become the mPreferredDataPhoneId.
-    private int mPhoneIdInVoiceCall = SubscriptionManager.INVALID_PHONE_INDEX;
+    protected int mPhoneIdInVoiceCall = SubscriptionManager.INVALID_PHONE_INDEX;
 
     @VisibleForTesting
     // It decides:
@@ -195,7 +197,7 @@
     protected int mPreferredDataPhoneId = SubscriptionManager.INVALID_PHONE_INDEX;
 
     // Subscription ID corresponds to mPreferredDataPhoneId.
-    private int mPreferredDataSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+    protected int mPreferredDataSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
 
     // If non-null, An emergency call is about to be started, is ongoing, or has just ended and we
     // are overriding the DDS.
@@ -204,8 +206,8 @@
 
     private ISetOpportunisticDataCallback mSetOpptSubCallback;
 
-    private static final int EVENT_PRIMARY_DATA_SUB_CHANGED       = 101;
-    private static final int EVENT_SUBSCRIPTION_CHANGED           = 102;
+    protected static final int EVENT_PRIMARY_DATA_SUB_CHANGED       = 101;
+    protected static final int EVENT_SUBSCRIPTION_CHANGED           = 102;
     private static final int EVENT_REQUEST_NETWORK                = 103;
     private static final int EVENT_RELEASE_NETWORK                = 104;
     // ECBM has started/ended. If we just ended an emergency call and mEmergencyOverride is not
@@ -215,7 +217,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;
+    protected static final int EVENT_RADIO_AVAILABLE                = 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
@@ -235,16 +237,19 @@
     private static final int EVENT_OVERRIDE_DDS_FOR_EMERGENCY     = 115;
     // If it exists, remove the current mEmergencyOverride DDS override.
     private static final int EVENT_REMOVE_DDS_EMERGENCY_OVERRIDE  = 116;
+    protected final static int EVENT_VOICE_CALL_ENDED             = 117;
+    protected static final int EVENT_UNSOL_MAX_DATA_ALLOWED_CHANGED = 118;
+    protected static final int EVENT_OEM_HOOK_SERVICE_READY       = 119;
 
     // 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
     // will be either HAL_COMMAND_ALLOW_DATA or HAL_COMMAND_ALLOW_DATA or HAL_COMMAND_UNKNOWN.
-    private static final int HAL_COMMAND_UNKNOWN        = 0;
-    private static final int HAL_COMMAND_ALLOW_DATA     = 1;
-    private static final int HAL_COMMAND_PREFERRED_DATA = 2;
-    private int mHalCommandToUse = HAL_COMMAND_UNKNOWN;
+    protected static final int HAL_COMMAND_UNKNOWN        = 0;
+    protected static final int HAL_COMMAND_ALLOW_DATA     = 1;
+    protected static final int HAL_COMMAND_PREFERRED_DATA = 2;
+    protected int mHalCommandToUse = HAL_COMMAND_UNKNOWN;
 
-    private RadioConfig mRadioConfig;
+    protected RadioConfig mRadioConfig;
 
     private final static int MAX_LOCAL_LOG_LINES = 30;
 
@@ -255,20 +260,26 @@
 
     private ConnectivityManager mConnectivityManager;
 
-    private final ConnectivityManager.NetworkCallback mDefaultNetworkCallback =
-            new NetworkCallback() {
-                @Override
-                public void onAvailable(Network network) {
-                    if (mConnectivityManager.getNetworkCapabilities(network)
-                            .hasTransport(TRANSPORT_CELLULAR)) {
-                        logDataSwitchEvent(
-                                mOpptDataSubId,
-                                TelephonyEvent.EventState.EVENT_STATE_END,
-                                TelephonyEvent.DataSwitch.Reason.DATA_SWITCH_REASON_UNKNOWN);
-                    }
-                    removeDefaultNetworkChangeCallback();
-                }
-            };
+    private class DefaultNetworkCallback extends ConnectivityManager.NetworkCallback {
+        public int mExpectedSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+        public int mSwitchReason = TelephonyEvent.DataSwitch.Reason.DATA_SWITCH_REASON_UNKNOWN;
+        @Override
+        public void onCapabilitiesChanged(Network network,
+                NetworkCapabilities networkCapabilities) {
+            if (networkCapabilities.hasTransport(TRANSPORT_CELLULAR)
+                    && SubscriptionManager.isValidSubscriptionId(mExpectedSubId)
+                    && mExpectedSubId == getSubIdFromNetworkSpecifier(
+                            networkCapabilities.getNetworkSpecifier())) {
+                logDataSwitchEvent(
+                        mExpectedSubId,
+                        TelephonyEvent.EventState.EVENT_STATE_END,
+                        mSwitchReason);
+                removeDefaultNetworkChangeCallback();
+            }
+        }
+    }
+
+    private final DefaultNetworkCallback mDefaultNetworkCallback = new DefaultNetworkCallback();
 
     /**
      * Method to get singleton instance.
@@ -321,13 +332,19 @@
         // subscription.
         mPhoneIdInVoiceCall = SubscriptionManager.INVALID_PHONE_INDEX;
         for (Phone phone : mPhones) {
-            if (isCallActive(phone) || isCallActive(phone.getImsPhone())) {
+            if (isPhoneInVoiceCall(phone) || isPhoneInVoiceCall(phone.getImsPhone())) {
                 mPhoneIdInVoiceCall = phone.getPhoneId();
                 break;
             }
         }
 
-        return (mPhoneIdInVoiceCall != oldPhoneIdInVoiceCall);
+        if (mPhoneIdInVoiceCall != oldPhoneIdInVoiceCall) {
+            log("isPhoneInVoiceCallChanged from phoneId " + oldPhoneIdInVoiceCall
+                    + " to phoneId " + mPhoneIdInVoiceCall);
+            return true;
+        } else {
+            return false;
+        }
     }
 
     @VisibleForTesting
@@ -442,10 +459,11 @@
             }
             case EVENT_PRIMARY_DATA_SUB_CHANGED: {
                 if (onEvaluate(REQUESTS_UNCHANGED, "primary data subId changed")) {
-                    logDataSwitchEvent(mOpptDataSubId,
+                    logDataSwitchEvent(mPreferredDataSubId,
                             TelephonyEvent.EventState.EVENT_STATE_START,
                             DataSwitch.Reason.DATA_SWITCH_REASON_MANUAL);
-                    registerDefaultNetworkChangeCallback();
+                    registerDefaultNetworkChangeCallback(mPreferredDataSubId,
+                            DataSwitch.Reason.DATA_SWITCH_REASON_MANUAL);
                 }
                 break;
             }
@@ -515,10 +533,11 @@
             // fall through
             case EVENT_DATA_ENABLED_CHANGED:
                 if (onEvaluate(REQUESTS_UNCHANGED, "EVENT_PRECISE_CALL_STATE_CHANGED")) {
-                    logDataSwitchEvent(mOpptDataSubId,
+                    logDataSwitchEvent(mPreferredDataSubId,
                             TelephonyEvent.EventState.EVENT_STATE_START,
                             DataSwitch.Reason.DATA_SWITCH_REASON_IN_CALL);
-                    registerDefaultNetworkChangeCallback();
+                    registerDefaultNetworkChangeCallback(mPreferredDataSubId,
+                            DataSwitch.Reason.DATA_SWITCH_REASON_IN_CALL);
                 }
                 break;
             case EVENT_NETWORK_VALIDATION_DONE: {
@@ -606,7 +625,7 @@
         }
     }
 
-    private boolean isEmergency() {
+    protected boolean isEmergency() {
         if (isInEmergencyCallbackMode()) return true;
         for (Phone p : mPhones) {
             if (p == null) continue;
@@ -676,25 +695,20 @@
     }
 
     private void removeDefaultNetworkChangeCallback() {
-        synchronized (mHasRegisteredDefaultNetworkChangeCallback) {
-            if (mHasRegisteredDefaultNetworkChangeCallback) {
-                mHasRegisteredDefaultNetworkChangeCallback = false;
-                removeMessages(EVENT_REMOVE_DEFAULT_NETWORK_CHANGE_CALLBACK);
-                mConnectivityManager.unregisterNetworkCallback(mDefaultNetworkCallback);
-            }
-        }
+        removeMessages(EVENT_REMOVE_DEFAULT_NETWORK_CHANGE_CALLBACK);
+        mDefaultNetworkCallback.mExpectedSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+        mDefaultNetworkCallback.mSwitchReason =
+                TelephonyEvent.DataSwitch.Reason.DATA_SWITCH_REASON_UNKNOWN;
+        mConnectivityManager.unregisterNetworkCallback(mDefaultNetworkCallback);
     }
 
-    private void registerDefaultNetworkChangeCallback() {
-        removeDefaultNetworkChangeCallback();
-
-        synchronized (mHasRegisteredDefaultNetworkChangeCallback) {
-            mHasRegisteredDefaultNetworkChangeCallback = true;
-            mConnectivityManager.registerDefaultNetworkCallback(mDefaultNetworkCallback);
-            sendMessageDelayed(
-                    obtainMessage(EVENT_REMOVE_DEFAULT_NETWORK_CHANGE_CALLBACK),
-                    DEFAULT_NETWORK_CHANGE_TIMEOUT_MS);
-        }
+    private void registerDefaultNetworkChangeCallback(int expectedSubId, int reason) {
+        mDefaultNetworkCallback.mExpectedSubId = expectedSubId;
+        mDefaultNetworkCallback.mSwitchReason = reason;
+        mConnectivityManager.registerDefaultNetworkCallback(mDefaultNetworkCallback, this);
+        sendMessageDelayed(
+                obtainMessage(EVENT_REMOVE_DEFAULT_NETWORK_CHANGE_CALLBACK),
+                DEFAULT_NETWORK_CHANGE_TIMEOUT_MS);
     }
 
     private void collectRequestNetworkMetrics(NetworkRequest networkRequest) {
@@ -722,7 +736,7 @@
     }
 
     private static final boolean REQUESTS_CHANGED   = true;
-    private static final boolean REQUESTS_UNCHANGED = false;
+    protected static final boolean REQUESTS_UNCHANGED = false;
     /**
      * Re-evaluate things. Do nothing if nothing's changed.
      *
@@ -732,12 +746,8 @@
      *
      * @return {@code True} if the default data subscription need to be changed.
      */
-    private boolean onEvaluate(boolean requestsChanged, String reason) {
+    protected boolean onEvaluate(boolean requestsChanged, String reason) {
         StringBuilder sb = new StringBuilder(reason);
-        if (isEmergency()) {
-            log("onEvaluate for reason " + reason + " aborted due to Emergency");
-            return false;
-        }
 
         // If we use HAL_COMMAND_PREFERRED_DATA,
         boolean diffDetected = mHalCommandToUse != HAL_COMMAND_PREFERRED_DATA && requestsChanged;
@@ -812,12 +822,19 @@
                         newActivePhones.add(mPhones[i].getPhoneId());
                     }
                 } else {
-                    for (DcRequest dcRequest : mPrioritizedDcRequests) {
-                        int phoneIdForRequest = phoneIdForRequest(dcRequest.networkRequest);
-                        if (phoneIdForRequest == INVALID_PHONE_INDEX) continue;
-                        if (newActivePhones.contains(phoneIdForRequest)) continue;
-                        newActivePhones.add(phoneIdForRequest);
-                        if (newActivePhones.size() >= mMaxActivePhones) break;
+                    // First try to activate phone in voice call.
+                    if (mPhoneIdInVoiceCall != SubscriptionManager.INVALID_PHONE_INDEX) {
+                        newActivePhones.add(mPhoneIdInVoiceCall);
+                    }
+
+                    if (newActivePhones.size() < mMaxActivePhones) {
+                        for (DcRequest dcRequest : mPrioritizedDcRequests) {
+                            int phoneIdForRequest = phoneIdForRequest(dcRequest.networkRequest);
+                            if (phoneIdForRequest == INVALID_PHONE_INDEX) continue;
+                            if (newActivePhones.contains(phoneIdForRequest)) continue;
+                            newActivePhones.add(phoneIdForRequest);
+                            if (newActivePhones.size() >= mMaxActivePhones) break;
+                        }
                     }
 
                     if (newActivePhones.size() < mMaxActivePhones
@@ -857,22 +874,27 @@
         return diffDetected;
     }
 
-    private static class PhoneState {
+    protected static class PhoneState {
         public volatile boolean active = false;
         public long lastRequested = 0;
     }
 
     @UnsupportedAppUsage
-    private void activate(int phoneId) {
+    protected void activate(int phoneId) {
         switchPhone(phoneId, true);
     }
 
     @UnsupportedAppUsage
-    private void deactivate(int phoneId) {
+    protected void deactivate(int phoneId) {
         switchPhone(phoneId, false);
     }
 
     private void switchPhone(int phoneId, boolean active) {
+        if (phoneId < 0 || phoneId >= mNumPhones) {
+            log("switchPhone, phoneId: " + phoneId +
+                ", mNumPhones: " + mNumPhones + ", should never here!");
+            return;
+        }
         PhoneState state = mPhoneStates[phoneId];
         if (state.active == active) return;
         state.active = active;
@@ -921,7 +943,7 @@
         msg.sendToTarget();
     }
 
-    private void sendRilCommands(int phoneId) {
+    protected void sendRilCommands(int phoneId) {
         if (!SubscriptionManager.isValidPhoneId(phoneId) || phoneId >= mNumPhones) return;
 
         Message message = Message.obtain(this, EVENT_MODEM_COMMAND_DONE, phoneId);
@@ -946,8 +968,9 @@
         }
     }
 
-    private int phoneIdForRequest(NetworkRequest netRequest) {
-        int subId = getSubIdFromNetworkRequest(netRequest);
+    protected int phoneIdForRequest(NetworkRequest netRequest) {
+        int subId = getSubIdFromNetworkSpecifier(netRequest.networkCapabilities
+                .getNetworkSpecifier());
 
         if (subId == DEFAULT_SUBSCRIPTION_ID) return mPreferredDataPhoneId;
         if (subId == INVALID_SUBSCRIPTION_ID) return INVALID_PHONE_INDEX;
@@ -979,8 +1002,12 @@
         return phoneId;
     }
 
-    private int getSubIdFromNetworkRequest(NetworkRequest networkRequest) {
+    protected int getSubIdFromNetworkRequest(NetworkRequest networkRequest) {
         NetworkSpecifier specifier = networkRequest.networkCapabilities.getNetworkSpecifier();
+        return getSubIdFromNetworkSpecifier(specifier);
+    }
+
+    protected int getSubIdFromNetworkSpecifier(NetworkSpecifier specifier) {
         if (specifier == null) {
             return DEFAULT_SUBSCRIPTION_ID;
         }
@@ -1012,7 +1039,7 @@
 
     // This updates mPreferredDataPhoneId which decides which phone should handle default network
     // requests.
-    private void updatePreferredDataPhoneId() {
+    protected void updatePreferredDataPhoneId() {
         Phone voicePhone = findPhoneById(mPhoneIdInVoiceCall);
         if (mEmergencyOverride != null && findPhoneById(mEmergencyOverride.mPhoneId) != null) {
             // Override DDS for emergency even if user data is not enabled, since it is an
@@ -1048,7 +1075,7 @@
         mPreferredDataSubId = mSubscriptionController.getSubIdUsingPhoneId(mPreferredDataPhoneId);
     }
 
-    private void transitionToEmergencyPhone() {
+    protected void transitionToEmergencyPhone() {
         if (mPreferredDataPhoneId != DEFAULT_EMERGENCY_PHONE_ID) {
             log("No active subscriptions: resetting preferred phone to 0 for emergency");
             mPreferredDataPhoneId = DEFAULT_EMERGENCY_PHONE_ID;
@@ -1088,6 +1115,11 @@
 
     @VisibleForTesting
     protected boolean isPhoneActive(int phoneId) {
+        if (phoneId < 0 || phoneId >= mNumPhones) {
+            log("isPhoneActive, phoneId: " + phoneId +
+                ", mNumPhones: " + mNumPhones + ", should never here!");
+            return false;
+        }
         return mPhoneStates[phoneId].active;
     }
 
@@ -1135,12 +1167,17 @@
             return;
         }
 
+        // Remove EVENT_NETWORK_VALIDATION_DONE. Don't handle validation result of previously subId
+        // if queued.
+        removeMessages(EVENT_NETWORK_VALIDATION_DONE);
+
         int subIdToValidate = (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID)
                 ? mPrimaryDataSubId : subId;
 
-        if (mValidator.isValidating()
-                && (!needValidation || subIdToValidate != mValidator.getSubIdInValidation())) {
+        if (mValidator.isValidating()) {
             mValidator.stopValidation();
+            sendSetOpptCallbackHelper(mSetOpptSubCallback, SET_OPPORTUNISTIC_SUB_VALIDATION_FAILED);
+            mSetOpptSubCallback = null;
         }
 
         if (subId == mOpptDataSubId) {
@@ -1148,15 +1185,27 @@
             return;
         }
 
+        logDataSwitchEvent(subId == DEFAULT_SUBSCRIPTION_ID ? mPrimaryDataSubId : subId,
+                TelephonyEvent.EventState.EVENT_STATE_START,
+                DataSwitch.Reason.DATA_SWITCH_REASON_CBRS);
+        registerDefaultNetworkChangeCallback(
+                subId == DEFAULT_SUBSCRIPTION_ID ? mPrimaryDataSubId : subId,
+                DataSwitch.Reason.DATA_SWITCH_REASON_CBRS);
+
         // If validation feature is not supported, set it directly. Otherwise,
         // start validation on the subscription first.
         if (mValidator.isValidationFeatureSupported() && needValidation) {
-            logDataSwitchEvent(subId, TelephonyEvent.EventState.EVENT_STATE_START,
-                    DataSwitch.Reason.DATA_SWITCH_REASON_CBRS);
-            registerDefaultNetworkChangeCallback();
             mSetOpptSubCallback = callback;
-            mValidator.validate(subIdToValidate, DEFAULT_VALIDATION_EXPIRATION_TIME,
-                    false, mValidationCallback);
+            long validationTimeout = DEFAULT_VALIDATION_EXPIRATION_TIME;
+            CarrierConfigManager configManager = (CarrierConfigManager)
+                    mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
+            if (configManager != null) {
+                PersistableBundle b = configManager.getConfigForSubId(subIdToValidate);
+                if (b != null) {
+                    validationTimeout = b.getLong(KEY_DATA_SWITCH_VALIDATION_TIMEOUT_LONG);
+                }
+            }
+            mValidator.validate(subIdToValidate, validationTimeout, false, mValidationCallback);
         } else {
             setOpportunisticSubscriptionInternal(subId);
             sendSetOpptCallbackHelper(callback, SET_OPPORTUNISTIC_SUB_SUCCESS);
@@ -1178,12 +1227,7 @@
     private void setOpportunisticSubscriptionInternal(int subId) {
         if (mOpptDataSubId != subId) {
             mOpptDataSubId = subId;
-            if (onEvaluate(REQUESTS_UNCHANGED, "oppt data subId changed")) {
-                logDataSwitchEvent(mOpptDataSubId,
-                        TelephonyEvent.EventState.EVENT_STATE_START,
-                        DataSwitch.Reason.DATA_SWITCH_REASON_CBRS);
-                registerDefaultNetworkChangeCallback();
-            }
+            onEvaluate(REQUESTS_UNCHANGED, "oppt data subId changed");
         }
     }
 
@@ -1236,13 +1280,20 @@
                 subId, needValidation ? 1 : 0, callback).sendToTarget();
     }
 
-    private boolean isCallActive(Phone phone) {
+    protected boolean isPhoneInVoiceCall(Phone phone) {
         if (phone == null) {
             return false;
         }
 
+        // A phone in voice call might trigger data being switched to it.
+        // We only report true if its precise call state is ACTIVE, ALERTING or HOLDING.
+        // The reason is data switching is interrupting, so we only switch when necessary and
+        // acknowledged by the users. For incoming call, we don't switch until answered
+        // (RINGING -> ACTIVE), for outgoing call we don't switch until call is connected
+        // in network (DIALING -> ALERTING).
         return (phone.getForegroundCall().getState() == Call.State.ACTIVE
-                || phone.getForegroundCall().getState() == Call.State.ALERTING);
+                || phone.getForegroundCall().getState() == Call.State.ALERTING
+                || phone.getBackgroundCall().getState() == Call.State.HOLDING);
     }
 
     private void updateHalCommandToUse() {
@@ -1259,13 +1310,13 @@
     }
 
     @UnsupportedAppUsage
-    private void log(String l) {
+    protected void log(String l) {
         Rlog.d(LOG_TAG, l);
         mLocalLog.log(l);
     }
 
     private void logDataSwitchEvent(int subId, int state, int reason) {
-        subId = subId == DEFAULT_SUBSCRIPTION_ID ? mPrimaryDataSubId : subId;
+        log("logDataSwitchEvent subId " + subId + " state " + state + " reason " + reason);
         DataSwitch dataSwitch = new DataSwitch();
         dataSwitch.state = state;
         dataSwitch.reason = reason;
@@ -1275,7 +1326,7 @@
     /**
      * See {@link PhoneStateListener#LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE}.
      */
-    private void notifyPreferredDataSubIdChanged() {
+    protected void notifyPreferredDataSubIdChanged() {
         ITelephonyRegistry tr = ITelephonyRegistry.Stub.asInterface(ServiceManager.getService(
                 "telephony.registry"));
         try {
@@ -1286,6 +1337,13 @@
         }
     }
 
+    /**
+     * @return The active data subscription id
+     */
+    public int getActiveDataSubId() {
+        return mPreferredDataSubId;
+    }
+
     public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
         final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
         pw.println("PhoneSwitcher:");
diff --git a/src/java/com/android/internal/telephony/RIL.java b/src/java/com/android/internal/telephony/RIL.java
index f0b4f1b..83398b6 100644
--- a/src/java/com/android/internal/telephony/RIL.java
+++ b/src/java/com/android/internal/telephony/RIL.java
@@ -73,6 +73,12 @@
 import android.telephony.AccessNetworkConstants.AccessNetworkType;
 import android.telephony.CarrierRestrictionRules;
 import android.telephony.CellInfo;
+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.ClientRequestStats;
 import android.telephony.ImsiEncryptionInfo;
 import android.telephony.ModemActivityInfo;
@@ -83,6 +89,7 @@
 import android.telephony.RadioAccessSpecifier;
 import android.telephony.Rlog;
 import android.telephony.ServiceState;
+import android.telephony.SignalStrength;
 import android.telephony.SmsManager;
 import android.telephony.TelephonyHistogram;
 import android.telephony.TelephonyManager;
@@ -202,6 +209,8 @@
     AtomicBoolean mTestingEmergencyCall = new AtomicBoolean(false);
 
     final Integer mPhoneId;
+    private List<String> mOldRilFeatures;
+    private boolean mUseOldMncMccFormat;
 
     /**
      * A set that records if radio service is disabled in hal for
@@ -216,17 +225,17 @@
     Set<Integer> mDisabledOemHookServices = new HashSet();
 
     /* default work source which will blame phone process */
-    private WorkSource mRILDefaultWorkSource;
+    protected WorkSource mRILDefaultWorkSource;
 
     /* Worksource containing all applications causing wakelock to be held */
     private WorkSource mActiveWakelockWorkSource;
 
     /** Telephony metrics instance for logging metrics event */
-    private TelephonyMetrics mMetrics = TelephonyMetrics.getInstance();
+    protected TelephonyMetrics mMetrics = TelephonyMetrics.getInstance();
     /** Radio bug detector instance */
     private RadioBugDetector mRadioBugDetector = null;
 
-    boolean mIsMobileNetworkSupported;
+    protected boolean mIsMobileNetworkSupported;
     RadioResponse mRadioResponse;
     RadioIndication mRadioIndication;
     volatile IRadio mRadioProxy = null;
@@ -386,7 +395,7 @@
         }
     }
 
-    private void resetProxyAndRequestList() {
+    protected void resetProxyAndRequestList() {
         mRadioProxy = null;
         mOemHookProxy = null;
 
@@ -573,6 +582,12 @@
             mRadioBugDetector = new RadioBugDetector(context, mPhoneId);
         }
 
+        final String oldRilFeatures = SystemProperties.get("ro.telephony.ril.config", "");
+        mOldRilFeatures = Arrays.asList(oldRilFeatures.split(","));
+
+        mUseOldMncMccFormat = SystemProperties.getBoolean(
+                "ro.telephony.use_old_mnc_mcc_format", false);
+
         ConnectivityManager cm = (ConnectivityManager)context.getSystemService(
                 Context.CONNECTIVITY_SERVICE);
         mIsMobileNetworkSupported = cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
@@ -615,7 +630,7 @@
         return Settings.Global.getInt(
                 mContext.getContentResolver(),
                 Settings.Global.ENABLE_RADIO_BUG_DETECTION,
-                0) != 0;
+                1) != 0;
     }
 
     @Override
@@ -644,6 +659,12 @@
         return rr;
     }
 
+    protected int obtainRequestSerial(int request, Message result, WorkSource workSource) {
+        RILRequest rr = RILRequest.obtain(request, result, workSource);
+        addRequest(rr);
+        return rr.mSerial;
+    }
+
     private void handleRadioProxyExceptionForRR(RILRequest rr, String caller, Exception e) {
         riljLoge(caller + ": " + e);
         resetProxyAndRequestList();
@@ -1992,6 +2013,10 @@
             RILRequest rr = obtainRequest(RIL_REQUEST_SET_NETWORK_SELECTION_MANUAL, result,
                     mRILDefaultWorkSource);
 
+            if (mUseOldMncMccFormat && !TextUtils.isEmpty(operatorNumeric)) {
+                operatorNumeric += "+";
+            }
+
             if (RILJ_LOGD) {
                 riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
                         + " operatorNumeric = " + operatorNumeric);
@@ -2977,7 +3002,7 @@
         }
     }
 
-    private void constructCdmaSendSmsRilRequest(CdmaSmsMessage msg, byte[] pdu) {
+    protected void constructCdmaSendSmsRilRequest(CdmaSmsMessage msg, byte[] pdu) {
         int addrNbrOfDigits;
         int subaddrNbrOfDigits;
         int bearerDataLength;
@@ -3016,6 +3041,11 @@
     }
 
     @Override
+    public void sendCdmaSms(byte[] pdu, Message result, boolean expectMore) {
+        sendCdmaSms(pdu, result);
+    }
+
+    @Override
     public void sendCdmaSms(byte[] pdu, Message result) {
         IRadio radioProxy = getRadioProxy(result);
         if (radioProxy != null) {
@@ -3244,7 +3274,7 @@
 
             CdmaSmsWriteArgs args = new CdmaSmsWriteArgs();
             args.status = status;
-            constructCdmaSendSmsRilRequest(args.message, pdu.getBytes());
+            constructCdmaSendSmsRilRequest(args.message, IccUtils.hexStringToBytes(pdu));
 
             try {
                 radioProxy.writeSmsToRuim(rr.mSerial, args);
@@ -3800,8 +3830,7 @@
     }
 
     @Override
-    public void setUiccSubscription(int slotId, int appIndex, int subId,
-                                    int subStatus, Message result) {
+    public void setUiccSubscription(int appIndex, boolean activate, Message result) {
         IRadio radioProxy = getRadioProxy(result);
         if (radioProxy != null) {
             RILRequest rr = obtainRequest(RIL_REQUEST_SET_UICC_SUBSCRIPTION, result,
@@ -3809,15 +3838,14 @@
 
             if (RILJ_LOGD) {
                 riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
-                        + " slot = " + slotId + " appIndex = " + appIndex
-                        + " subId = " + subId + " subStatus = " + subStatus);
+                        + " appIndex: " + appIndex + " activate: " + activate);
             }
 
             SelectUiccSub info = new SelectUiccSub();
-            info.slot = slotId;
+            info.slot = mPhoneId;
             info.appIndex = appIndex;
-            info.subType = subId;
-            info.actStatus = subStatus;
+            info.subType = mPhoneId;
+            info.actStatus = activate ? 1 : 0;
 
             try {
                 radioProxy.setUiccSubscription(rr.mSerial, info);
@@ -4805,6 +4833,15 @@
         return rr;
     }
 
+    protected Message getMessageFromRequest(Object request) {
+        RILRequest rr = (RILRequest)request;
+        Message result = null;
+        if (rr != null) {
+                result = rr.mResult;
+        }
+        return result;
+    }
+
     /**
      * This is a helper function to be called at the end of all RadioResponse callbacks.
      * It takes care of sending error response, logging, decrementing wakelock if needed, and
@@ -4837,6 +4874,11 @@
         }
     }
 
+    protected void processResponseDone(Object request, RadioResponseInfo responseInfo, Object ret) {
+        RILRequest rr = (RILRequest)request;
+        processResponseDone(rr, responseInfo, ret);
+    }
+
     /**
      * Function to send ack and acquire related wakelock
      */
@@ -5261,6 +5303,8 @@
                 return "GET_CURRENT_CALLS";
             case RIL_REQUEST_DIAL:
                 return "DIAL";
+            case RIL_REQUEST_EMERGENCY_DIAL:
+                return "EMERGENCY_DIAL";
             case RIL_REQUEST_GET_IMSI:
                 return "GET_IMSI";
             case RIL_REQUEST_HANGUP:
@@ -5882,6 +5926,61 @@
     }
 
     /**
+     * Fixup for SignalStrength 1.0 to Assume GSM to WCDMA when
+     * The current RAT type is one of the UMTS RATs.
+     * @param signalStrength the initial signal strength
+     * @return a new SignalStrength if RAT is UMTS or existing SignalStrength
+     */
+    public SignalStrength fixupSignalStrength10(SignalStrength signalStrength) {
+        List<CellSignalStrengthGsm> gsmList = signalStrength.getCellSignalStrengths(
+                CellSignalStrengthGsm.class);
+        // If GSM is not the primary type, then bail out; no fixup needed.
+        if (gsmList.isEmpty() || !gsmList.get(0).isValid()) {
+            return signalStrength;
+        }
+
+        CellSignalStrengthGsm gsmStrength = gsmList.get(0);
+
+        // Use the voice RAT which is a guarantee in GSM and UMTS
+        int voiceRat = ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN;
+        Phone phone = PhoneFactory.getPhone(mPhoneId);
+        if (phone != null) {
+            ServiceState ss = phone.getServiceState();
+            if (ss != null) {
+                voiceRat = ss.getRilVoiceRadioTechnology();
+            }
+        }
+        switch (voiceRat) {
+            case ServiceState.RIL_RADIO_TECHNOLOGY_GPRS: /* fallthrough */
+            case ServiceState.RIL_RADIO_TECHNOLOGY_EDGE: /* fallthrough */
+            case ServiceState.RIL_RADIO_TECHNOLOGY_UMTS: /* fallthrough */
+            case ServiceState.RIL_RADIO_TECHNOLOGY_HSDPA: /* fallthrough */
+            case ServiceState.RIL_RADIO_TECHNOLOGY_HSUPA: /* fallthrough */
+            case ServiceState.RIL_RADIO_TECHNOLOGY_HSPA: /* fallthrough */
+            case ServiceState.RIL_RADIO_TECHNOLOGY_HSPAP: /* fallthrough */
+            case ServiceState.RIL_RADIO_TECHNOLOGY_GSM: /* fallthrough */
+                break;
+            default:
+                // If we are not currently on WCDMA/HSPA, then we don't need to do a fixup.
+                return signalStrength;
+        }
+
+        // The service state reports WCDMA, and the SignalStrength is reported for GSM, so at this
+        // point we take an educated guess that the GSM SignalStrength report is actually for
+        // WCDMA. Also, if we are in WCDMA/GSM we can safely assume that there are no other valid
+        // signal strength reports (no SRLTE, which is the only supported case in HAL 1.0).
+        // Thus, we just construct a new SignalStrength and migrate RSSI and BER from the
+        // GSM report to the WCDMA report, leaving everything else empty.
+        return new SignalStrength(
+                new CellSignalStrengthCdma(), new CellSignalStrengthGsm(),
+                new CellSignalStrengthWcdma(gsmStrength.getRssi(),
+                        gsmStrength.getBitErrorRate(),
+                        CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE),
+                new CellSignalStrengthTdscdma(), new CellSignalStrengthLte(),
+                new CellSignalStrengthNr());
+    }
+
+    /**
      * 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.
@@ -6054,4 +6153,8 @@
     public HalVersion getHalVersion() {
         return mRadioVersion;
     }
+
+    public boolean needsOldRilFeature(String feature) {
+        return mOldRilFeatures.contains(feature);
+    }
 }
diff --git a/src/java/com/android/internal/telephony/RadioIndication.java b/src/java/com/android/internal/telephony/RadioIndication.java
index 70e306d..6beab09 100644
--- a/src/java/com/android/internal/telephony/RadioIndication.java
+++ b/src/java/com/android/internal/telephony/RadioIndication.java
@@ -229,7 +229,9 @@
                                       android.hardware.radio.V1_0.SignalStrength signalStrength) {
         mRil.processIndication(indicationType);
 
-        SignalStrength ss = new SignalStrength(signalStrength);
+        SignalStrength ssInitial = new SignalStrength(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);
 
diff --git a/src/java/com/android/internal/telephony/RadioResponse.java b/src/java/com/android/internal/telephony/RadioResponse.java
index a36b7a6..97fe3dc 100644
--- a/src/java/com/android/internal/telephony/RadioResponse.java
+++ b/src/java/com/android/internal/telephony/RadioResponse.java
@@ -1995,8 +1995,12 @@
         if (rr != null) {
             ArrayList<OperatorInfo> ret = new ArrayList<OperatorInfo>();
             for (int i = 0; i < networkInfos.size(); i++) {
+                String operatorNumeric = networkInfos.get(i).operatorNumeric;
+                if (operatorNumeric != null) {
+                    operatorNumeric = operatorNumeric.split("\\+")[0];
+                }
                 ret.add(new OperatorInfo(networkInfos.get(i).alphaLong,
-                        networkInfos.get(i).alphaShort, networkInfos.get(i).operatorNumeric,
+                        networkInfos.get(i).alphaShort, operatorNumeric,
                         convertOpertatorInfoToString(networkInfos.get(i).status)));
             }
             if (responseInfo.error == RadioError.NONE) {
diff --git a/src/java/com/android/internal/telephony/ServiceStateTracker.java b/src/java/com/android/internal/telephony/ServiceStateTracker.java
index 9c251d1..438e966 100755
--- a/src/java/com/android/internal/telephony/ServiceStateTracker.java
+++ b/src/java/com/android/internal/telephony/ServiceStateTracker.java
@@ -70,6 +70,7 @@
 import android.telephony.PhysicalChannelConfig;
 import android.telephony.Rlog;
 import android.telephony.ServiceState;
+import android.telephony.ServiceState.RilRadioTechnology;
 import android.telephony.SignalStrength;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
@@ -116,10 +117,13 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 import java.util.TimeZone;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -135,6 +139,9 @@
 
     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
     private CommandsInterface mCi;
     @UnsupportedAppUsage
@@ -172,6 +179,7 @@
 
     @UnsupportedAppUsage
     private SignalStrength mSignalStrength;
+    private long mSignalStrengthUpdatedTime;
 
     // TODO - this should not be public, right now used externally GsmConnetion.
     public RestrictedState mRestrictedState;
@@ -211,6 +219,8 @@
     private RegistrantList mPsRestrictEnabledRegistrants = new RegistrantList();
     private RegistrantList mPsRestrictDisabledRegistrants = new RegistrantList();
     private RegistrantList mImsCapabilityChangedRegistrants = new RegistrantList();
+    private RegistrantList mNrStateChangedRegistrants = new RegistrantList();
+    private RegistrantList mNrFrequencyChangedRegistrants = new RegistrantList();
 
     /* Radio power off pending flag and tag counter */
     private boolean mPendingRadioPowerOffAfterDataOff = false;
@@ -360,11 +370,18 @@
             int subId = mPhone.getSubId();
             ServiceStateTracker.this.mPrevSubId = mPreviousSubId.get();
             if (mPreviousSubId.getAndSet(subId) != subId) {
-                if (SubscriptionManager.isValidSubscriptionId(subId)) {
+                if (mSubscriptionController.isActiveSubId(subId)) {
                     Context context = mPhone.getContext();
 
                     mPhone.notifyPhoneStateChanged();
                     mPhone.notifyCallForwardingIndicator();
+                    if (!SubscriptionManager.isValidSubscriptionId(
+                            ServiceStateTracker.this.mPrevSubId)) {
+                        // just went from invalid to valid subId, so notify with current service
+                        // state in case our service stat was never broadcasted (we don't notify
+                        // service states when the subId is invalid)
+                        mPhone.notifyServiceStateChanged(mSS);
+                    }
 
                     boolean restoreSelection = !context.getResources().getBoolean(
                             com.android.internal.R.bool.skip_restoring_network_selection);
@@ -420,7 +437,7 @@
 
     //Common
     @UnsupportedAppUsage
-    private final GsmCdmaPhone mPhone;
+    protected final GsmCdmaPhone mPhone;
 
     private CellIdentity mCellIdentity;
     private CellIdentity mNewCellIdentity;
@@ -546,6 +563,7 @@
     private String mPrlVersion;
     private boolean mIsMinInfoReady = false;
     private boolean mIsEriTextLoaded = false;
+    private String mEriText;
     @UnsupportedAppUsage
     private boolean mIsSubscriptionFromRuim = false;
     private CdmaSubscriptionSourceManager mCdmaSSM;
@@ -706,6 +724,7 @@
         mNitzState.handleNetworkCountryCodeUnavailable();
         mCellIdentity = null;
         mNewCellIdentity = null;
+        mSignalStrengthUpdatedTime = System.currentTimeMillis();
 
         //cancel any pending pollstate request on voice tech switching
         cancelPollState();
@@ -1190,8 +1209,14 @@
                 }
                 // This will do nothing in the 'radio not available' case
                 setPowerStateToDesired();
-                // These events are modem triggered, so pollState() needs to be forced
-                modemTriggeredPollState();
+                if (needsLegacyPollState()) {
+                    // Some older radio blobs need this to put device
+                    // properly into airplane mode.
+                    pollState();
+                } else {
+                    // These events are modem triggered, so pollState() needs to be forced
+                    modemTriggeredPollState();
+                }
                 break;
 
             case EVENT_NETWORK_STATE_CHANGED:
@@ -1538,14 +1563,20 @@
                     }
                     mPhone.notifyPhysicalChannelConfiguration(list);
                     mLastPhysicalChannelConfigList = list;
-                    boolean hasChanged =
-                            updateNrFrequencyRangeFromPhysicalChannelConfigs(list, mSS);
-                    hasChanged |= updateNrStateFromPhysicalChannelConfigs(
-                            list, mSS);
+                    boolean hasChanged = false;
+                    if (updateNrFrequencyRangeFromPhysicalChannelConfigs(list, mSS)) {
+                        mNrFrequencyChangedRegistrants.notifyRegistrants();
+                        hasChanged = true;
+                    }
+                    if (updateNrStateFromPhysicalChannelConfigs(list, mSS)) {
+                        mNrStateChangedRegistrants.notifyRegistrants();
+                        hasChanged = true;
+                    }
+                    hasChanged |= RatRatcheter
+                            .updateBandwidths(getBandwidthsFromConfigs(list), mSS);
 
                     // Notify NR frequency, NR connection status or bandwidths changed.
-                    if (hasChanged
-                            || RatRatcheter.updateBandwidths(getBandwidthsFromConfigs(list), mSS)) {
+                    if (hasChanged) {
                         mPhone.notifyServiceStateChanged(mSS);
                     }
                 }
@@ -1652,7 +1683,7 @@
                 getSystemService(Context.TELEPHONY_SERVICE)).
                 getSimOperatorNumericForPhone(mPhone.getPhoneId());
 
-        if (!TextUtils.isEmpty(operatorNumeric) && getCdmaMin() != null) {
+        if (!TextUtils.isEmpty(operatorNumeric) && !TextUtils.isEmpty(getCdmaMin())) {
             return (operatorNumeric + getCdmaMin());
         } else {
             return null;
@@ -1811,7 +1842,7 @@
                  * data roaming status. If TSB58 roaming indicator is not in the
                  * carrier-specified list of ERIs for home system then set roaming.
                  */
-                final int dataRat = mNewSS.getRilDataRadioTechnology();
+                final int dataRat = getRilDataRadioTechnologyForWwan(mNewSS);
                 if (ServiceState.isCdma(dataRat)) {
                     final boolean isVoiceInService =
                             (mNewSS.getVoiceRegState() == ServiceState.STATE_IN_SERVICE);
@@ -1853,9 +1884,10 @@
                         // Use default
                         mNewSS.setCdmaRoamingIndicator(mDefaultRoamingIndicator);
                     } else if (namMatch && !mIsInPrl) {
-                        // TODO this will be removed when we handle roaming on LTE on CDMA+LTE phones
-                        if (ServiceState.isLte(mNewSS.getRilVoiceRadioTechnology())) {
-                            log("Turn off roaming indicator as voice is LTE");
+                        // TODO this will be removed when we handle roaming on LTE/NR
+                        // on CDMA+LTE phones
+                        if (ServiceState.isPsTech(mNewSS.getRilVoiceRadioTechnology())) {
+                            log("Turn off roaming indicator as voice is LTE or NR");
                             mNewSS.setCdmaRoamingIndicator(EriInfo.ROAMING_INDICATOR_OFF);
                         } else {
                             mNewSS.setCdmaRoamingIndicator(EriInfo.ROAMING_INDICATOR_FLASH);
@@ -2018,7 +2050,7 @@
         }
     }
 
-    void handlePollStateResultMessage(int what, AsyncResult ar) {
+    protected void handlePollStateResultMessage(int what, AsyncResult ar) {
         int ints[];
         switch (what) {
             case EVENT_POLL_STATE_CS_CELLULAR_REGISTRATION: {
@@ -2143,7 +2175,7 @@
                 } else {
 
                     // If the unsolicited signal strength comes just before data RAT family changes
-                    // (i.e. from UNKNOWN to LTE, CDMA to LTE, LTE to CDMA), the signal bar might
+                    // (i.e. from UNKNOWN to LTE/NR, CDMA to LTE/NR, LTE/NR to CDMA), the signal bar might
                     // display the wrong information until the next unsolicited signal strength
                     // information coming from the modem, which might take a long time to come or
                     // even not come at all.  In order to provide the best user experience, we
@@ -2151,8 +2183,8 @@
                     int oldDataRAT = mSS.getRilDataRadioTechnology();
                     if (((oldDataRAT == ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN)
                             && (newDataRat != ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN))
-                            || (ServiceState.isCdma(oldDataRAT) && ServiceState.isLte(newDataRat))
-                            || (ServiceState.isLte(oldDataRAT)
+                            || (ServiceState.isCdma(oldDataRAT) && ServiceState.
+                            isPsTech(newDataRat)) || (ServiceState.isPsTech(oldDataRAT)
                             && ServiceState.isCdma(newDataRat))) {
                         mCi.getSignalStrength(obtainMessage(EVENT_GET_SIGNAL_STRENGTH));
                     }
@@ -2390,6 +2422,11 @@
              */
             boolean roaming = (mGsmRoaming || mDataRoaming);
 
+            // for IWLAN case, data is home. Only check voice roaming.
+            if (mNewSS.getRilDataRadioTechnology() == ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN) {
+                roaming = mGsmRoaming;
+            }
+
             if (mGsmRoaming && !isOperatorConsideredRoaming(mNewSS)
                     && (isSameNamedOperators(mNewSS) || isOperatorConsideredNonRoaming(mNewSS))) {
                 log("updateRoamingState: resource override set non roaming.isSameNamedOperators="
@@ -2625,10 +2662,8 @@
                 showPlmn = true;
 
                 // Force display no service
-                final boolean forceDisplayNoService = mPhone.getContext().getResources().getBoolean(
-                        com.android.internal.R.bool.config_display_no_service_when_sim_unready)
-                        && !mIsSimReady;
-                if (mEmergencyOnly && !forceDisplayNoService) {
+                final boolean forceDisplayNoService = shouldForceDisplayNoService() && !mIsSimReady;
+                if (!forceDisplayNoService && Phone.isEmergencyCallOnly()) {
                     // No service but emergency call allowed
                     plmn = Resources.getSystem().
                             getText(com.android.internal.R.string.emergency_calls_only).toString();
@@ -2738,6 +2773,28 @@
         log("updateSpnDisplayLegacy-");
     }
 
+    /**
+     * Checks whether force to display "no service" to the user based on the current country.
+     *
+     * This method should only be used when SIM is unready.
+     *
+     * @return {@code True} if "no service" should be displayed.
+     */
+    public boolean shouldForceDisplayNoService() {
+        String[] countriesWithNoService = mPhone.getContext().getResources().getStringArray(
+                com.android.internal.R.array.config_display_no_service_when_sim_unready);
+        if (ArrayUtils.isEmpty(countriesWithNoService)) {
+            return false;
+        }
+        String currentCountry = mLocaleTracker.getCurrentCountry();
+        for (String country : countriesWithNoService) {
+            if (country.equalsIgnoreCase(currentCountry)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     protected void setPowerStateToDesired() {
         if (DBG) {
             String tmpLog = "mDeviceShuttingDown=" + mDeviceShuttingDown +
@@ -2880,7 +2937,18 @@
             // Checking the Concurrent Service Supported flag first for all phone types.
             return true;
         } else if (mPhone.isPhoneTypeGsm()) {
-            return (mSS.getRilDataRadioTechnology() >= ServiceState.RIL_RADIO_TECHNOLOGY_UMTS);
+            int radioTechnology = mSS.getRilDataRadioTechnology();
+            // There are cases where we we would setup data connection even data is not yet
+            // attached. In these cases we check voice rat.
+            if (radioTechnology == ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN
+                    && mSS.getDataRegState() != ServiceState.STATE_IN_SERVICE) {
+                radioTechnology = mSS.getRilVoiceRadioTechnology();
+            }
+            // Concurrent voice and data is not allowed for 2G technologies. It's allowed in other
+            // rats e.g. UMTS, LTE, etc.
+            return radioTechnology != ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN
+                    && ServiceState.rilRadioTechnologyToAccessNetworkType(radioTechnology)
+                        != AccessNetworkType.GERAN;
         } else {
             return false;
         }
@@ -3087,6 +3155,11 @@
             }
         }
 
+        // Filter out per transport data RAT changes, only want to track changes based on
+        // transport preference changes (WWAN to WLAN, for example).
+        boolean hasDataTransportPreferenceChanged = !anyDataRatChanged
+                && (mSS.getRilDataRadioTechnology() != mNewSS.getRilDataRadioTechnology());
+
         boolean hasVoiceRegStateChanged =
                 mSS.getVoiceRegState() != mNewSS.getVoiceRegState();
 
@@ -3134,19 +3207,19 @@
         boolean hasLostMultiApnSupport = false;
         if (mPhone.isPhoneTypeCdmaLte()) {
             has4gHandoff = mNewSS.getDataRegState() == ServiceState.STATE_IN_SERVICE
-                    && ((ServiceState.isLte(mSS.getRilDataRadioTechnology())
+                    && ((ServiceState.isPsTech(mSS.getRilDataRadioTechnology())
                     && (mNewSS.getRilDataRadioTechnology()
                     == ServiceState.RIL_RADIO_TECHNOLOGY_EHRPD))
                     ||
                     ((mSS.getRilDataRadioTechnology()
                             == ServiceState.RIL_RADIO_TECHNOLOGY_EHRPD)
-                            && ServiceState.isLte(mNewSS.getRilDataRadioTechnology())));
+                            && ServiceState.isPsTech(mNewSS.getRilDataRadioTechnology())));
 
-            hasMultiApnSupport = ((ServiceState.isLte(mNewSS.getRilDataRadioTechnology())
+            hasMultiApnSupport = ((ServiceState.isPsTech(mNewSS.getRilDataRadioTechnology())
                     || (mNewSS.getRilDataRadioTechnology()
                     == ServiceState.RIL_RADIO_TECHNOLOGY_EHRPD))
                     &&
-                    (!ServiceState.isLte(mSS.getRilDataRadioTechnology())
+                    (!ServiceState.isPsTech(mSS.getRilDataRadioTechnology())
                             && (mSS.getRilDataRadioTechnology()
                             != ServiceState.RIL_RADIO_TECHNOLOGY_EHRPD)));
 
@@ -3166,6 +3239,7 @@
                     + " hasDataRegStateChanged = " + hasDataRegStateChanged
                     + " hasRilVoiceRadioTechnologyChanged = " + hasRilVoiceRadioTechnologyChanged
                     + " hasRilDataRadioTechnologyChanged = " + hasRilDataRadioTechnologyChanged
+                    + " hasDataTransportPreferenceChanged = " + hasDataTransportPreferenceChanged
                     + " hasChanged = " + hasChanged
                     + " hasVoiceRoamingOn = " + hasVoiceRoamingOn
                     + " hasVoiceRoamingOff = " + hasVoiceRoamingOff
@@ -3261,9 +3335,17 @@
             setNotification(CS_REJECT_CAUSE_ENABLED);
         }
 
-        if (hasChanged) {
+        String eriText = mPhone.getCdmaEriText();
+        boolean hasEriChanged = !TextUtils.equals(mEriText, eriText);
+        mEriText = eriText;
+        // Trigger updateSpnDisplay when
+        // 1. Service state is changed.
+        // 2. phone type is Cdma or CdmaLte and ERI text has changed.
+        if (hasChanged || (!mPhone.isPhoneTypeGsm() && hasEriChanged)) {
             updateSpnDisplay();
+        }
 
+        if (hasChanged) {
             tm.setNetworkOperatorNameForPhone(mPhone.getPhoneId(), mSS.getOperatorAlpha());
             String operatorNumeric = mSS.getOperatorNumeric();
 
@@ -3277,8 +3359,20 @@
 
             tm.setNetworkOperatorNumericForPhone(mPhone.getPhoneId(), operatorNumeric);
 
-            if (isInvalidOperatorNumeric(operatorNumeric)) {
-                if (DBG) log("operatorNumeric " + operatorNumeric + " is invalid");
+            // If the OPERATOR command hasn't returned a valid operator, but if the device has
+            // camped on a cell either to attempt registration or for emergency services, then
+            // for purposes of setting the locale, we don't care if registration fails or is
+            // incomplete.
+            // CellIdentity can return a null MCC and MNC in CDMA
+            String localeOperator = operatorNumeric;
+            if (isInvalidOperatorNumeric(operatorNumeric) && (mCellIdentity != null)
+                    && mCellIdentity.getMccString() != null
+                    && mCellIdentity.getMncString() != null) {
+                localeOperator = mCellIdentity.getMccString() + mCellIdentity.getMncString();
+            }
+
+            if (isInvalidOperatorNumeric(localeOperator)) {
+                if (DBG) log("localeOperator " + localeOperator + " is invalid");
                 // Passing empty string is important for the first update. The initial value of
                 // operator numeric in locale tracker is null. The async update will allow getting
                 // cell info from the modem instead of using the cached one.
@@ -3290,10 +3384,10 @@
 
                 // Update IDD.
                 if (!mPhone.isPhoneTypeGsm()) {
-                    setOperatorIdd(operatorNumeric);
+                    setOperatorIdd(localeOperator);
                 }
 
-                mLocaleTracker.updateOperatorNumeric(operatorNumeric);
+                mLocaleTracker.updateOperatorNumeric(localeOperator);
             }
 
             tm.setNetworkRoamingForPhone(mPhone.getPhoneId(),
@@ -3341,7 +3435,10 @@
             }
 
             if (hasDataRegStateChanged.get(transport)
-                    || hasRilDataRadioTechnologyChanged.get(transport)) {
+                    || hasRilDataRadioTechnologyChanged.get(transport)
+                    // Update all transports if preference changed so that consumers can be notified
+                    // that ServiceState#getRilDataRadioTechnology has changed.
+                    || hasDataTransportPreferenceChanged) {
                 notifyDataRegStateRilRadioTechnologyChanged(transport);
                 mPhone.notifyDataConnection();
             }
@@ -3433,7 +3530,7 @@
                     mUiccController.getUiccCard(getPhoneId()).getOperatorBrandOverride() != null;
             if (!hasBrandOverride && (mCi.getRadioState() == TelephonyManager.RADIO_POWER_ON)
                     && (mEriManager.isEriFileLoaded())
-                    && (!ServiceState.isLte(mSS.getRilVoiceRadioTechnology())
+                    && (!ServiceState.isPsTech(mSS.getRilVoiceRadioTechnology())
                     || mPhone.getContext().getResources().getBoolean(com.android.internal.R
                     .bool.config_LTE_eri_for_network_name))) {
                 // Only when CDMA is in service, ERI will take effect
@@ -3458,7 +3555,7 @@
 
             if (mUiccApplcation != null && mUiccApplcation.getState() == AppState.APPSTATE_READY &&
                     mIccRecords != null && getCombinedRegState(mSS) == ServiceState.STATE_IN_SERVICE
-                    && !ServiceState.isLte(mSS.getRilVoiceRadioTechnology())) {
+                    && !ServiceState.isPsTech(mSS.getRilVoiceRadioTechnology())) {
                 // SIM is found on the device. If ERI roaming is OFF, and SID/NID matches
                 // one configured in SIM, use operator name from CSIM record. Note that ERI, SID,
                 // and NID are CDMA only, not applicable to LTE.
@@ -4677,6 +4774,7 @@
             log("onSignalStrengthResult() Exception from RIL : " + ar.exception);
             mSignalStrength = new SignalStrength();
         }
+        mSignalStrengthUpdatedTime = System.currentTimeMillis();
 
         boolean ssChanged = notifySignalStrength();
 
@@ -4809,9 +4907,41 @@
      * @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());
+        for (SubscriptionInfo info : subInfoList) {
+            // If we have an active opportunistic subscription whose data is IN_SERVICE, we needs
+            // 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.getDataRegState() == ServiceState.STATE_IN_SERVICE) {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
     /**
      * Registration point for subscription info ready
      * @param h handler to notify
@@ -5117,7 +5247,7 @@
         }
         final boolean isDataInService =
                 (currentServiceState.getDataRegState() == ServiceState.STATE_IN_SERVICE);
-        final int dataRegType = currentServiceState.getRilDataRadioTechnology();
+        final int dataRegType = getRilDataRadioTechnologyForWwan(currentServiceState);
         if (isDataInService) {
             if (!currentServiceState.getDataRoaming()) {
                 currentServiceState.setDataRoamingType(ServiceState.ROAMING_TYPE_NOT_ROAMING);
@@ -5164,6 +5294,7 @@
     @UnsupportedAppUsage
     private void setSignalStrengthDefaultValues() {
         mSignalStrength = new SignalStrength();
+        mSignalStrengthUpdatedTime = System.currentTimeMillis();
     }
 
     protected String getHomeOperatorNumeric() {
@@ -5224,6 +5355,10 @@
                     mNewSS.addNetworkRegistrationInfo(nri);
                 }
                 mNewSS.setOperatorAlphaLong(operator);
+                // Since it's in airplane mode, cellular must be out of service. The only possible
+                // transport for data to go through is the IWLAN transport. Setting this to true
+                // so that ServiceState.getDataNetworkType can report the right RAT.
+                mNewSS.setIwlanPreferred(true);
                 log("pollStateDone: mNewSS = " + mNewSS);
             }
             return;
@@ -5430,4 +5565,80 @@
         }
         return operatorName;
     }
+
+    @RilRadioTechnology
+    private static int getRilDataRadioTechnologyForWwan(ServiceState ss) {
+        NetworkRegistrationInfo regInfo = ss.getNetworkRegistrationInfo(
+                NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+        int networkType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
+        if (regInfo != null) {
+            networkType = regInfo.getAccessNetworkTechnology();
+        }
+        return ServiceState.networkTypeToRilRadioTechnology(networkType);
+    }
+
+    /**
+     * Registers for 5G NR state changed.
+     * @param h handler to notify
+     * @param what what code of message when delivered
+     * @param obj placed in Message.obj
+     */
+    public void registerForNrStateChanged(Handler h, int what, Object obj) {
+        Registrant r = new Registrant(h, what, obj);
+        mNrStateChangedRegistrants.add(r);
+    }
+
+    /**
+     * Unregisters for 5G NR state changed.
+     * @param h handler to notify
+     */
+    public void unregisterForNrStateChanged(Handler h) {
+        mNrStateChangedRegistrants.remove(h);
+    }
+
+    /**
+     * Registers for 5G NR frequency changed.
+     * @param h handler to notify
+     * @param what what code of message when delivered
+     * @param obj placed in Message.obj
+     */
+    public void registerForNrFrequencyChanged(Handler h, int what, Object obj) {
+        Registrant r = new Registrant(h, what, obj);
+        mNrFrequencyChangedRegistrants.add(r);
+    }
+
+    /**
+     * Unregisters for 5G NR frequency changed.
+     * @param h handler to notify
+     */
+    public void unregisterForNrFrequencyChanged(Handler h) {
+        mNrFrequencyChangedRegistrants.remove(h);
+    }
+
+    /**
+     * Get the NR data connection context ids.
+     *
+     * @return data connection context ids.
+     */
+    @NonNull
+    public Set<Integer> getNrContextIds() {
+        Set<Integer> idSet = new HashSet<>();
+
+        for (PhysicalChannelConfig config : mLastPhysicalChannelConfigList) {
+            if (isNrPhysicalChannelConfig(config)) {
+                for (int id : config.getContextIds()) {
+                    idSet.add(id);
+                }
+            }
+        }
+
+        return idSet;
+    }
+
+    private boolean needsLegacyPollState() {
+        if (mCi instanceof RIL) {
+            return ((RIL) mCi).needsOldRilFeature("legacypollstate");
+        }
+        return false;
+    }
 }
diff --git a/src/java/com/android/internal/telephony/SmsBroadcastUndelivered.java b/src/java/com/android/internal/telephony/SmsBroadcastUndelivered.java
index 459d97a..a1d8faa 100644
--- a/src/java/com/android/internal/telephony/SmsBroadcastUndelivered.java
+++ b/src/java/com/android/internal/telephony/SmsBroadcastUndelivered.java
@@ -36,6 +36,7 @@
 
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Map;
 
 /**
  * Called when the credential-encrypted storage is unlocked, collecting all acknowledged messages
@@ -65,9 +66,27 @@
             "address",
             "_id",
             "message_body",
-            "display_originating_addr"
+            "display_originating_addr",
+            "sub_id"
     };
 
+    /** Mapping from DB COLUMN to PDU_PENDING_MESSAGE_PROJECTION index */
+    static final Map<Integer, Integer> PDU_PENDING_MESSAGE_PROJECTION_INDEX_MAPPING =
+            new HashMap<Integer, Integer>() {{
+                put(InboundSmsHandler.PDU_COLUMN, 0);
+                put(InboundSmsHandler.SEQUENCE_COLUMN, 1);
+                put(InboundSmsHandler.DESTINATION_PORT_COLUMN, 2);
+                put(InboundSmsHandler.DATE_COLUMN, 3);
+                put(InboundSmsHandler.REFERENCE_NUMBER_COLUMN, 4);
+                put(InboundSmsHandler.COUNT_COLUMN, 5);
+                put(InboundSmsHandler.ADDRESS_COLUMN, 6);
+                put(InboundSmsHandler.ID_COLUMN, 7);
+                put(InboundSmsHandler.MESSAGE_BODY_COLUMN, 8);
+                put(InboundSmsHandler.DISPLAY_ADDRESS_COLUMN, 9);
+                put(InboundSmsHandler.SUBID_COLUMN, 10);
+            }};
+
+
     private static SmsBroadcastUndelivered instance;
 
     /** Content resolver to use to access raw table from SmsProvider. */
diff --git a/src/java/com/android/internal/telephony/SmsController.java b/src/java/com/android/internal/telephony/SmsController.java
index cc2a75f..45efafd 100644
--- a/src/java/com/android/internal/telephony/SmsController.java
+++ b/src/java/com/android/internal/telephony/SmsController.java
@@ -400,7 +400,7 @@
             }
 
             // If reached here and multiple SIMs and subs present, sms sim pick activity is needed
-            if (subInfoLength > 0 && telephonyManager.getSimCount() > 1) {
+            if (subInfoLength > 1 && telephonyManager.getSimCount() > 1) {
                 return true;
             }
         }
diff --git a/src/java/com/android/internal/telephony/SubscriptionController.java b/src/java/com/android/internal/telephony/SubscriptionController.java
index 2fb109a..0a8d148 100644
--- a/src/java/com/android/internal/telephony/SubscriptionController.java
+++ b/src/java/com/android/internal/telephony/SubscriptionController.java
@@ -46,6 +46,7 @@
 import android.telephony.Rlog;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
+import android.telephony.SubscriptionManager.SimDisplayNameSource;
 import android.telephony.TelephonyManager;
 import android.telephony.UiccAccessRule;
 import android.telephony.UiccSlotInfo;
@@ -100,8 +101,8 @@
  */
 public class SubscriptionController extends ISub.Stub {
     private static final String LOG_TAG = "SubscriptionController";
-    private static final boolean DBG = true;
-    private static final boolean VDBG = Rlog.isLoggable(LOG_TAG, Log.VERBOSE);
+    protected static final boolean DBG = true;
+    protected static final boolean VDBG = Rlog.isLoggable(LOG_TAG, Log.VERBOSE);
     private static final boolean DBG_CACHE = false;
     private static final int DEPRECATED_SETTING = -1;
     private static final ParcelUuid INVALID_GROUP_UUID =
@@ -133,7 +134,7 @@
     protected final Object mLock = new Object();
 
     /** The singleton instance. */
-    private static SubscriptionController sInstance = null;
+    protected static SubscriptionController sInstance = null;
     protected static Phone[] sPhones;
     @UnsupportedAppUsage
     protected Context mContext;
@@ -144,14 +145,26 @@
 
     // Each slot can have multiple subs.
     private static Map<Integer, ArrayList<Integer>> sSlotIndexToSubIds = new ConcurrentHashMap<>();
-    private static int mDefaultFallbackSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+    protected static int mDefaultFallbackSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
     @UnsupportedAppUsage
-    private static int mDefaultPhoneId = SubscriptionManager.DEFAULT_PHONE_INDEX;
+    protected static int mDefaultPhoneId = SubscriptionManager.DEFAULT_PHONE_INDEX;
 
     @UnsupportedAppUsage
     private int[] colorArr;
     private long mLastISubServiceRegTime;
 
+    // The properties that should be shared and synced across grouped subscriptions.
+    private static final Set<String> GROUP_SHARING_PROPERTIES = new HashSet<>(Arrays.asList(
+            SubscriptionManager.ENHANCED_4G_MODE_ENABLED,
+            SubscriptionManager.VT_IMS_ENABLED,
+            SubscriptionManager.WFC_IMS_ENABLED,
+            SubscriptionManager.WFC_IMS_MODE,
+            SubscriptionManager.WFC_IMS_ROAMING_MODE,
+            SubscriptionManager.WFC_IMS_ROAMING_ENABLED,
+            SubscriptionManager.DATA_ROAMING,
+            SubscriptionManager.DISPLAY_NAME,
+            SubscriptionManager.DATA_ENABLED_OVERRIDE_RULES));
+
     public static SubscriptionController init(Phone phone) {
         synchronized (SubscriptionController.class) {
             if (sInstance == null) {
@@ -262,7 +275,7 @@
     }
 
     @UnsupportedAppUsage
-    private void enforceModifyPhoneState(String message) {
+    protected void enforceModifyPhoneState(String message) {
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.MODIFY_PHONE_STATE, message);
     }
@@ -388,6 +401,8 @@
         } else {
             accessRules = null;
         }
+        UiccAccessRule[] carrierConfigAccessRules = UiccAccessRule.decodeRules(cursor.getBlob(
+            cursor.getColumnIndexOrThrow(SubscriptionManager.ACCESS_RULES_FROM_CARRIER_CONFIGS)));
         boolean isOpportunistic = cursor.getInt(cursor.getColumnIndexOrThrow(
                 SubscriptionManager.IS_OPPORTUNISTIC)) == 1;
         String groupUUID = cursor.getString(cursor.getColumnIndexOrThrow(
@@ -408,9 +423,11 @@
                     + " dataRoaming:" + dataRoaming + " mcc:" + mcc + " mnc:" + mnc
                     + " countIso:" + countryIso + " isEmbedded:"
                     + isEmbedded + " accessRules:" + Arrays.toString(accessRules)
+                    + " carrierConfigAccessRules: " + Arrays.toString(carrierConfigAccessRules)
                     + " cardId:" + cardIdToPrint + " publicCardId:" + publicCardId
                     + " isOpportunistic:" + isOpportunistic + " groupUUID:" + groupUUID
-                    + " profileClass:" + profileClass + " subscriptionType: " + subType);
+                    + " profileClass:" + profileClass + " subscriptionType: " + subType
+                    + " carrierConfigAccessRules:" + carrierConfigAccessRules);
         }
 
         // If line1number has been set to a different number, use it instead.
@@ -422,7 +439,7 @@
                 carrierName, nameSource, iconTint, number, dataRoaming, iconBitmap, mcc, mnc,
                 countryIso, isEmbedded, accessRules, cardId, publicCardId, isOpportunistic,
                 groupUUID, false /* isGroupDisabled */, carrierId, profileClass, subType,
-                groupOwner);
+                groupOwner, carrierConfigAccessRules);
         info.setAssociatedPlmns(ehplmns, hplmns);
         return info;
     }
@@ -1177,7 +1194,7 @@
                             // Set the default sub if not set or if single sim device
                             if (!isSubscriptionForRemoteSim(subscriptionType)) {
                                 if (!SubscriptionManager.isValidSubscriptionId(defaultSubId)
-                                        || subIdCountMax == 1) {
+                                        || subIdCountMax == 1 || (!isActiveSubId(defaultSubId))) {
                                     logdl("setting default fallback subid to " + subId);
                                     setDefaultFallbackSubId(subId, subscriptionType);
                                 }
@@ -1575,30 +1592,27 @@
      * @param nameSource Source of display name
      * @return int representing the priority. Higher value means higher priority.
      */
-    public static int getNameSourcePriority(int nameSource) {
-        switch (nameSource) {
-            case SubscriptionManager.NAME_SOURCE_USER_INPUT:
-                return 3;
-            case SubscriptionManager.NAME_SOURCE_CARRIER:
-                return 2;
-            case SubscriptionManager.NAME_SOURCE_SIM_SOURCE:
-                return 1;
-            case SubscriptionManager.NAME_SOURCE_DEFAULT_SOURCE:
-            default:
-                return 0;
-        }
+    public static int getNameSourcePriority(@SimDisplayNameSource int nameSource) {
+        int index = Arrays.asList(
+                SubscriptionManager.NAME_SOURCE_DEFAULT_SOURCE,
+                SubscriptionManager.NAME_SOURCE_SIM_PNN,
+                SubscriptionManager.NAME_SOURCE_SIM_SPN,
+                SubscriptionManager.NAME_SOURCE_CARRIER,
+                SubscriptionManager.NAME_SOURCE_USER_INPUT // user has highest priority.
+        ).indexOf(nameSource);
+        return (index < 0) ? 0 : index;
     }
 
     /**
      * Set display name by simInfo index with name source
      * @param displayName the display name of SIM card
      * @param subId the unique SubInfoRecord index in database
-     * @param nameSource 0: NAME_SOURCE_DEFAULT_SOURCE, 1: NAME_SOURCE_SIM_SOURCE,
-     *                   2: NAME_SOURCE_USER_INPUT, 3: NAME_SOURCE_CARRIER
+     * @param nameSource SIM display name source
      * @return the number of records updated
      */
     @Override
-    public int setDisplayNameUsingSrc(String displayName, int subId, int nameSource) {
+    public int setDisplayNameUsingSrc(String displayName, int subId,
+                                      @SimDisplayNameSource int nameSource) {
         if (DBG) {
             logd("[setDisplayName]+  displayName:" + displayName + " subId:" + subId
                 + " nameSource:" + nameSource);
@@ -1618,6 +1632,10 @@
                         && (getNameSourcePriority(subInfo.getNameSource())
                                 > getNameSourcePriority(nameSource)
                         || (displayName != null && displayName.equals(subInfo.getDisplayName())))) {
+                    logd("Name source " + subInfo.getNameSource() + "'s priority "
+                            + getNameSourcePriority(subInfo.getNameSource()) + " is greater than "
+                            + "name source " + nameSource + "'s priority "
+                            + getNameSourcePriority(nameSource) + ", return now.");
                     return 0;
                 }
             }
@@ -1648,8 +1666,7 @@
                             mContext, 0 /* requestCode */, new Intent(), 0 /* flags */));
             }
 
-            int result = mContext.getContentResolver().update(
-                    SubscriptionManager.getUriForSubscriptionId(subId), value, null, null);
+            int result = updateDatabase(value, subId, true);
 
             // Refresh the Cache of Active Subscription Info List
             refreshCachedActiveSubscriptionInfoList();
@@ -1763,7 +1780,7 @@
             value.put(SubscriptionManager.DATA_ROAMING, roaming);
             if (DBG) logd("[setDataRoaming]- roaming:" + roaming + " set");
 
-            int result = databaseUpdateHelper(value, subId, true);
+            int result = updateDatabase(value, subId, true);
 
             // Refresh the Cache of Active Subscription Info List
             refreshCachedActiveSubscriptionInfoList();
@@ -1776,18 +1793,54 @@
         }
     }
 
-
     public void syncGroupedSetting(int refSubId) {
-        // Currently it only syncs allow MMS. Sync other settings as well if needed.
-        String dataEnabledOverrideRules = getSubscriptionProperty(
-                refSubId, SubscriptionManager.DATA_ENABLED_OVERRIDE_RULES);
+        logd("syncGroupedSetting");
+        try (Cursor cursor = mContext.getContentResolver().query(
+                SubscriptionManager.CONTENT_URI, null,
+                SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=?",
+                new String[] {String.valueOf(refSubId)}, null)) {
+            if (cursor == null || !cursor.moveToFirst()) {
+                logd("[syncGroupedSetting] failed. Can't find refSubId " + refSubId);
+                return;
+            }
 
-        ContentValues value = new ContentValues(1);
-        value.put(SubscriptionManager.DATA_ENABLED_OVERRIDE_RULES, dataEnabledOverrideRules);
-        databaseUpdateHelper(value, refSubId, true);
+            ContentValues values = new ContentValues(GROUP_SHARING_PROPERTIES.size());
+            for (String propKey : GROUP_SHARING_PROPERTIES) {
+                copyDataFromCursorToContentValue(propKey, cursor, values);
+            }
+            updateDatabase(values, refSubId, true);
+        }
     }
 
-    private int databaseUpdateHelper(ContentValues value, int subId, boolean updateEntireGroup) {
+    private void copyDataFromCursorToContentValue(String propKey, Cursor cursor,
+            ContentValues values) {
+        int columnIndex = cursor.getColumnIndex(propKey);
+        if (columnIndex == -1) {
+            logd("[copyDataFromCursorToContentValue] can't find column " + propKey);
+            return;
+        }
+
+        switch (propKey) {
+            case SubscriptionManager.ENHANCED_4G_MODE_ENABLED:
+            case SubscriptionManager.VT_IMS_ENABLED:
+            case SubscriptionManager.WFC_IMS_ENABLED:
+            case SubscriptionManager.WFC_IMS_MODE:
+            case SubscriptionManager.WFC_IMS_ROAMING_MODE:
+            case SubscriptionManager.WFC_IMS_ROAMING_ENABLED:
+            case SubscriptionManager.DATA_ROAMING:
+                values.put(propKey, cursor.getInt(columnIndex));
+                break;
+            case SubscriptionManager.DISPLAY_NAME:
+            case SubscriptionManager.DATA_ENABLED_OVERRIDE_RULES:
+                values.put(propKey, cursor.getString(columnIndex));
+                break;
+            default:
+                loge("[copyDataFromCursorToContentValue] invalid propKey " + propKey);
+        }
+    }
+
+    // TODO: replace all updates with this helper method.
+    private int updateDatabase(ContentValues value, int subId, boolean updateEntireGroup) {
         List<SubscriptionInfo> infoList = getSubscriptionsInGroup(getGroupUuid(subId),
                 mContext.getOpPackageName());
         if (!updateEntireGroup || infoList == null || infoList.size() == 0) {
@@ -2100,22 +2153,22 @@
         }
     }
 
-    private void logvl(String msg) {
+    protected void logvl(String msg) {
         logv(msg);
         mLocalLog.log(msg);
     }
 
-    private void logv(String msg) {
+    protected void logv(String msg) {
         Rlog.v(LOG_TAG, msg);
     }
 
     @UnsupportedAppUsage
-    private void logdl(String msg) {
+    protected void logdl(String msg) {
         logd(msg);
         mLocalLog.log(msg);
     }
 
-    private static void slogd(String msg) {
+    protected static void slogd(String msg) {
         Rlog.d(LOG_TAG, msg);
     }
 
@@ -2124,7 +2177,7 @@
         Rlog.d(LOG_TAG, msg);
     }
 
-    private void logel(String msg) {
+    protected void logel(String msg) {
         loge(msg);
         mLocalLog.log(msg);
     }
@@ -2321,7 +2374,7 @@
     }
 
     @UnsupportedAppUsage
-    private void updateAllDataConnectionTrackers() {
+    protected void updateAllDataConnectionTrackers() {
         // Tell Phone Proxies to update data connection tracker
         int len = sPhones.length;
         if (DBG) logd("[updateAllDataConnectionTrackers] sPhones.length=" + len);
@@ -2332,7 +2385,7 @@
     }
 
     @UnsupportedAppUsage
-    private void broadcastDefaultDataSubIdChanged(int subId) {
+    protected void broadcastDefaultDataSubIdChanged(int subId) {
         // Broadcast an Intent for default data sub change
         if (DBG) logdl("[broadcastDefaultDataSubIdChanged] subId=" + subId);
         Intent intent = new Intent(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED);
@@ -2348,7 +2401,7 @@
      * the first sub is set as default subscription
      */
     @UnsupportedAppUsage
-    private void setDefaultFallbackSubId(int subId, int subscriptionType) {
+    protected void setDefaultFallbackSubId(int subId, int subscriptionType) {
         if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
             throw new RuntimeException("setDefaultSubId called with DEFAULT_SUB_ID");
         }
@@ -2396,6 +2449,29 @@
         mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
     }
 
+    protected boolean shouldDefaultBeCleared(List<SubscriptionInfo> records, int subId) {
+        if (DBG) logdl("[shouldDefaultBeCleared: subId] " + subId);
+        if (records == null) {
+            if (DBG) logdl("[shouldDefaultBeCleared] return true no records subId=" + subId);
+            return true;
+        }
+        if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+            // If the subId parameter is not valid its already cleared so return false.
+            if (DBG) logdl("[shouldDefaultBeCleared] return false only one subId, subId=" + subId);
+            return false;
+        }
+        for (SubscriptionInfo record : records) {
+            int id = record.getSubscriptionId();
+            if (DBG) logdl("[shouldDefaultBeCleared] Record.id: " + id);
+            if (id == subId) {
+                logdl("[shouldDefaultBeCleared] return false subId is active, subId=" + subId);
+                return false;
+            }
+        }
+        if (DBG) logdl("[shouldDefaultBeCleared] return true not active subId=" + subId);
+        return true;
+    }
+
     /**
      * Whether a subscription is opportunistic or not.
      */
@@ -2614,9 +2690,10 @@
         }
     }
 
-    private static int setSubscriptionPropertyIntoContentResolver(
+    private int setSubscriptionPropertyIntoContentResolver(
             int subId, String propKey, String propValue, ContentResolver resolver) {
         ContentValues value = new ContentValues();
+        boolean updateEntireGroup = GROUP_SHARING_PROPERTIES.contains(propKey);
         switch (propKey) {
             case SubscriptionManager.CB_EXTREME_THREAT_ALERT:
             case SubscriptionManager.CB_SEVERE_THREAT_ALERT:
@@ -2644,8 +2721,7 @@
                 break;
         }
 
-        return resolver.update(SubscriptionManager.getUriForSubscriptionId(subId),
-                value, null, null);
+        return updateDatabase(value, subId, updateEntireGroup);
     }
 
     /**
@@ -2739,7 +2815,7 @@
         return resultValue;
     }
 
-    private static void printStackTrace(String msg) {
+    protected static void printStackTrace(String msg) {
         RuntimeException re = new RuntimeException();
         slogd("StackTrace - " + msg);
         StackTraceElement[] st = re.getStackTrace();
@@ -3319,7 +3395,7 @@
             int subId = info.getSubscriptionId();
             return TelephonyPermissions.checkCallingOrSelfReadPhoneState(mContext, subId,
                     callingPackage, "getSubscriptionsInGroup")
-                    || (info.isEmbedded() && info.canManageSubscription(mContext, callingPackage));
+                    || info.canManageSubscription(mContext, callingPackage);
         }).map(subscriptionInfo -> conditionallyRemoveIdentifiers(subscriptionInfo,
                 callingPackage, "getSubscriptionsInGroup"))
         .collect(Collectors.toList());
@@ -3823,7 +3899,7 @@
         ContentValues value = new ContentValues(1);
         value.put(SubscriptionManager.DATA_ENABLED_OVERRIDE_RULES, rules);
 
-        boolean result = databaseUpdateHelper(value, subId, true) > 0;
+        boolean result = updateDatabase(value, subId, true) > 0;
 
         if (result) {
             // Refresh the Cache of Active Subscription Info List
@@ -3845,4 +3921,31 @@
         return TextUtils.emptyIfNull(getSubscriptionProperty(subId,
                 SubscriptionManager.DATA_ENABLED_OVERRIDE_RULES));
     }
+
+    /**
+     * Get active data subscription id.
+     *
+     * @return Active data subscription id
+     *
+     * @hide
+     */
+    @Override
+    public int getActiveDataSubscriptionId() {
+        final long token = Binder.clearCallingIdentity();
+
+        try {
+            PhoneSwitcher phoneSwitcher = PhoneSwitcher.getInstance();
+            if (phoneSwitcher != null) {
+                int activeDataSubId = phoneSwitcher.getActiveDataSubId();
+                if (SubscriptionManager.isUsableSubscriptionId(activeDataSubId)) {
+                    return activeDataSubId;
+                }
+            }
+            // If phone switcher isn't ready, or active data sub id is not available, use default
+            // sub id from settings.
+            return getDefaultDataSubId();
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
 }
diff --git a/src/java/com/android/internal/telephony/SubscriptionInfoUpdater.java b/src/java/com/android/internal/telephony/SubscriptionInfoUpdater.java
index b265a08..c21d8e9 100644
--- a/src/java/com/android/internal/telephony/SubscriptionInfoUpdater.java
+++ b/src/java/com/android/internal/telephony/SubscriptionInfoUpdater.java
@@ -53,6 +53,7 @@
 import android.telephony.UiccAccessRule;
 import android.telephony.euicc.EuiccManager;
 import android.text.TextUtils;
+import android.util.Pair;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.euicc.EuiccController;
@@ -104,7 +105,7 @@
     @UnsupportedAppUsage
     private static Context mContext = null;
     @UnsupportedAppUsage
-    private static String mIccId[] = new String[PROJECT_SIM_NUM];
+    protected static String mIccId[] = new String[PROJECT_SIM_NUM];
     private static int[] sSimCardState = new int[PROJECT_SIM_NUM];
     private static int[] sSimApplicationState = new int[PROJECT_SIM_NUM];
     private static boolean sIsSubInfoInitialized = false;
@@ -157,7 +158,7 @@
     }
 
     private void initializeCarrierApps() {
-        // Initialize carrier apps:
+        // Initialize carrier apps (carrier-privileged and carrier-matching):
         // -Now (on system startup)
         // -Whenever new carrier privilege rules might change (new SIM is loaded)
         // -Whenever we switch to a new user
@@ -171,6 +172,8 @@
                     CarrierAppUtils.disableCarrierAppsUntilPrivileged(mContext.getOpPackageName(),
                             mPackageManager, TelephonyManager.getDefault(),
                             mContext.getContentResolver(), mCurrentlyActiveUserId);
+                    CarrierAppUtils.disableSpecialCarrierAppsUntilMatched(mContext,
+                            mPackageManager, TelephonyManager.getDefault(), mCurrentlyActiveUserId);
 
                     if (reply != null) {
                         try {
@@ -187,6 +190,8 @@
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(mContext.getOpPackageName(),
                 mPackageManager, TelephonyManager.getDefault(), mContext.getContentResolver(),
                 mCurrentlyActiveUserId);
+        CarrierAppUtils.disableSpecialCarrierAppsUntilMatched(mContext,
+                mPackageManager, TelephonyManager.getDefault(), mCurrentlyActiveUserId);
     }
 
     /**
@@ -220,7 +225,7 @@
     }
 
     @UnsupportedAppUsage
-    private boolean isAllIccIdQueryDone() {
+    protected boolean isAllIccIdQueryDone() {
         for (int i = 0; i < PROJECT_SIM_NUM; i++) {
             UiccSlot slot = UiccController.getInstance().getUiccSlotForPhone(i);
             int slotId = UiccController.getInstance().getSlotIdFromPhoneId(i);
@@ -352,7 +357,7 @@
                 EVENT_REFRESH_EMBEDDED_SUBSCRIPTIONS, cardId, 0 /* arg2 */, callback));
     }
 
-    private void handleSimLocked(int slotId, String reason) {
+    protected void handleSimLocked(int slotId, String reason) {
         if (mIccId[slotId] != null && mIccId[slotId].equals(ICCID_STRING_FOR_NO_SIM)) {
             logd("SIM" + (slotId + 1) + " hot plug in");
             mIccId[slotId] = null;
@@ -422,7 +427,7 @@
         broadcastSimApplicationStateChanged(slotId, TelephonyManager.SIM_STATE_NOT_READY);
     }
 
-    private void handleSimLoaded(int slotId) {
+    protected void handleSimLoaded(int slotId) {
         logd("handleSimLoaded: slotId: " + slotId);
 
         // The SIM should be loaded at this state, but it is possible in cases such as SIM being
@@ -457,7 +462,7 @@
                         mContext.getSystemService(Context.TELEPHONY_SERVICE);
                 String operator = tm.getSimOperatorNumeric(subId);
 
-                if (!TextUtils.isEmpty(operator)) {
+                if (operator != null && !TextUtils.isEmpty(operator)) {
                     if (subId == SubscriptionController.getInstance().getDefaultSubId()) {
                         MccTable.updateMccMncConfiguration(mContext, operator);
                     }
@@ -539,6 +544,8 @@
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(mContext.getOpPackageName(),
                 mPackageManager, TelephonyManager.getDefault(),
                 mContext.getContentResolver(), mCurrentlyActiveUserId);
+        CarrierAppUtils.disableSpecialCarrierAppsUntilMatched(mContext,
+                mPackageManager, TelephonyManager.getDefault(), mCurrentlyActiveUserId);
 
         /**
          * The sim loading sequence will be
@@ -568,7 +575,7 @@
         }
     }
 
-    private void handleSimAbsent(int slotId, int absentAndInactive) {
+    protected void handleSimAbsent(int slotId, int absentAndInactive) {
         if (mIccId[slotId] != null && !mIccId[slotId].equals(ICCID_STRING_FOR_NO_SIM)) {
             logd("SIM" + (slotId + 1) + " hot plug out, absentAndInactive=" + absentAndInactive);
         }
@@ -585,7 +592,7 @@
         }
     }
 
-    private void handleSimError(int slotId) {
+    protected void handleSimError(int slotId) {
         if (mIccId[slotId] != null && !mIccId[slotId].equals(ICCID_STRING_FOR_NO_SIM)) {
             logd("SIM" + (slotId + 1) + " Error ");
         }
@@ -599,7 +606,7 @@
         updateCarrierServices(slotId, IccCardConstants.INTENT_VALUE_ICC_CARD_IO_ERROR);
     }
 
-    private synchronized void updateSubscriptionInfoByIccId(int slotIndex,
+    protected synchronized void updateSubscriptionInfoByIccId(int slotIndex,
             boolean updateEmbeddedSubs) {
         logd("updateSubscriptionInfoByIccId:+ Start");
         if (!SubscriptionManager.isValidSlotIndex(slotIndex)) {
@@ -714,19 +721,20 @@
         }
 
         mBackgroundHandler.post(() -> {
-            List<GetEuiccProfileInfoListResult> results = new ArrayList<>();
+            List<Pair<Integer, GetEuiccProfileInfoListResult>> results = new ArrayList<>();
             for (int cardId : cardIds) {
                 GetEuiccProfileInfoListResult result =
                         EuiccController.get().blockingGetEuiccProfileInfoList(cardId);
                 if (DBG) logd("blockingGetEuiccProfileInfoList cardId " + cardId);
-                results.add(result);
+                results.add(Pair.create(cardId, result));
             }
 
             // The runnable will be executed in the main thread.
             this.post(() -> {
                 boolean hasChanges = false;
-                for (GetEuiccProfileInfoListResult result : results) {
-                    if (updateEmbeddedSubscriptionsCache(result)) {
+                for (Pair<Integer, GetEuiccProfileInfoListResult> cardIdAndResult : results) {
+                    if (updateEmbeddedSubscriptionsCache(cardIdAndResult.first,
+                            cardIdAndResult.second)) {
                         hasChanges = true;
                     }
                 }
@@ -747,7 +755,8 @@
      * but notifications about subscription changes may be skipped if this returns false as an
      * optimization to avoid spurious notifications.
      */
-    private boolean updateEmbeddedSubscriptionsCache(GetEuiccProfileInfoListResult result) {
+    private boolean updateEmbeddedSubscriptionsCache(int cardId,
+            GetEuiccProfileInfoListResult result) {
         if (DBG) logd("updateEmbeddedSubscriptionsCache");
 
         if (result == null) {
@@ -852,6 +861,14 @@
                 values.put(SubscriptionManager.MNC_STRING, mnc);
                 values.put(SubscriptionManager.MNC, mnc);
             }
+            // If cardId = unsupported or unitialized, we have no reason to update DB.
+            // Additionally, if the device does not support cardId for default eUICC, the CARD_ID
+            // field should not contain the EID
+            if (cardId >= 0 && UiccController.getInstance().getCardIdForDefaultEuicc()
+                    != TelephonyManager.UNSUPPORTED_CARD_ID) {
+                values.put(SubscriptionManager.CARD_ID,
+                        mEuiccManager.createForCardId(cardId).getEid());
+            }
             hasChanges = true;
             contentResolver.update(SubscriptionManager.CONTENT_URI, values,
                     SubscriptionManager.ICC_ID + "=\"" + embeddedProfile.getIccid() + "\"", null);
@@ -954,41 +971,55 @@
             return;
         }
 
+        ContentValues cv = new ContentValues();
+        ParcelUuid groupUuid = null;
+
+        // carrier certificates are not subscription-specific, so we want to load them even if
+        // this current package is not a CarrierServicePackage
+        String[] certs = config.getStringArray(
+            CarrierConfigManager.KEY_CARRIER_CERTIFICATE_STRING_ARRAY);
+        if (certs != null) {
+            UiccAccessRule[] carrierConfigAccessRules = new UiccAccessRule[certs.length];
+            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));
+        }
+
         if (!isCarrierServicePackage(phoneId, configPackageName)) {
             loge("Cannot manage subId=" + currentSubId + ", carrierPackage=" + configPackageName);
-            return;
-        }
+        } 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");
+            }
 
-        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");
-        }
-
-        String groupUuidString =
+            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
@@ -1064,10 +1095,14 @@
     private void broadcastSimApplicationStateChanged(int phoneId, int state) {
         // Broadcast if the state has changed, except if old state was UNKNOWN and new is NOT_READY,
         // because that's the initial state and a broadcast should be sent only on a transition
-        // after SIM is PRESENT
-        if (!(state == sSimApplicationState[phoneId]
-                || (state == TelephonyManager.SIM_STATE_NOT_READY
-                && sSimApplicationState[phoneId] == TelephonyManager.SIM_STATE_UNKNOWN))) {
+        // after SIM is PRESENT. The only exception is eSIM boot profile, where NOT_READY is the
+        // terminal state.
+        boolean isUnknownToNotReady =
+                (sSimApplicationState[phoneId] == TelephonyManager.SIM_STATE_UNKNOWN
+                        && state == TelephonyManager.SIM_STATE_NOT_READY);
+        IccCard iccCard = mPhone[phoneId].getIccCard();
+        boolean emptyProfile = iccCard != null && iccCard.isEmptyProfile();
+        if (state != sSimApplicationState[phoneId] && (!isUnknownToNotReady || emptyProfile)) {
             sSimApplicationState[phoneId] = state;
             Intent i = new Intent(TelephonyManager.ACTION_SIM_APPLICATION_STATE_CHANGED);
             i.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
diff --git a/src/java/com/android/internal/telephony/TelephonyComponentFactory.java b/src/java/com/android/internal/telephony/TelephonyComponentFactory.java
index e5ed3fb..de65ace 100644
--- a/src/java/com/android/internal/telephony/TelephonyComponentFactory.java
+++ b/src/java/com/android/internal/telephony/TelephonyComponentFactory.java
@@ -42,12 +42,15 @@
 import com.android.internal.telephony.imsphone.ImsExternalCallTracker;
 import com.android.internal.telephony.imsphone.ImsPhone;
 import com.android.internal.telephony.imsphone.ImsPhoneCallTracker;
+import com.android.internal.telephony.SubscriptionController;
 import com.android.internal.telephony.uicc.IccCardStatus;
 import com.android.internal.telephony.uicc.UiccCard;
 import com.android.internal.telephony.uicc.UiccProfile;
 
 import dalvik.system.PathClassLoader;
 
+import java.lang.reflect.Constructor;
+
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
@@ -67,6 +70,7 @@
  * this way makes it easier to mock them in tests.
  */
 public class TelephonyComponentFactory {
+    protected static String LOG_TAG = "TelephonyComponentFactory";
 
     private static final String TAG = TelephonyComponentFactory.class.getSimpleName();
 
@@ -223,7 +227,23 @@
 
     public static TelephonyComponentFactory getInstance() {
         if (sInstance == null) {
-            sInstance = new TelephonyComponentFactory();
+            String fullClsName = "com.qualcomm.qti.internal.telephony.QtiTelephonyComponentFactory";
+            String libPath = "/system/framework/qti-telephony-common.jar";
+
+            try {
+                PathClassLoader classLoader = new PathClassLoader(libPath,
+                        ClassLoader.getSystemClassLoader());
+                Class<?> cls = Class.forName(fullClsName, false, classLoader);
+                Constructor custMethod = cls.getConstructor();
+                sInstance = (TelephonyComponentFactory) custMethod.newInstance();
+                Rlog.i(LOG_TAG, "Using QtiTelephonyComponentFactory");
+            } catch (NoClassDefFoundError | ClassNotFoundException e) {
+                Rlog.e(LOG_TAG, "QtiTelephonyComponentFactory not used - fallback to default");
+                sInstance = new TelephonyComponentFactory();
+            } catch (Exception e) {
+                Rlog.e(LOG_TAG, "Error loading QtiTelephonyComponentFactory - fallback to default");
+                sInstance = new TelephonyComponentFactory();
+            }
         }
         return sInstance;
     }
@@ -270,18 +290,22 @@
     }
 
     public GsmCdmaCallTracker makeGsmCdmaCallTracker(GsmCdmaPhone phone) {
+        Rlog.d(LOG_TAG, "makeGsmCdmaCallTracker");
         return new GsmCdmaCallTracker(phone);
     }
 
     public SmsStorageMonitor makeSmsStorageMonitor(Phone phone) {
+        Rlog.d(LOG_TAG, "makeSmsStorageMonitor");
         return new SmsStorageMonitor(phone);
     }
 
     public SmsUsageMonitor makeSmsUsageMonitor(Context context) {
+        Rlog.d(LOG_TAG, "makeSmsUsageMonitor");
         return new SmsUsageMonitor(context);
     }
 
     public ServiceStateTracker makeServiceStateTracker(GsmCdmaPhone phone, CommandsInterface ci) {
+        Rlog.d(LOG_TAG, "makeServiceStateTracker");
         return new ServiceStateTracker(phone, ci);
     }
 
@@ -293,18 +317,10 @@
     }
 
     /**
-     * Sets the NitzStateMachine implementation to use during implementation. This boolean
-     * should be removed once the new implementation is stable.
-     */
-    static final boolean USE_NEW_NITZ_STATE_MACHINE = true;
-
-    /**
      * Returns a new {@link NitzStateMachine} instance.
      */
     public NitzStateMachine makeNitzStateMachine(GsmCdmaPhone phone) {
-        return USE_NEW_NITZ_STATE_MACHINE
-                ? new NewNitzStateMachine(phone)
-                : new OldNitzStateMachine(phone);
+        return new NitzStateMachineImpl(phone);
     }
 
     public SimActivationTracker makeSimActivationTracker(Phone phone) {
@@ -328,10 +344,12 @@
     }
 
     public IccPhoneBookInterfaceManager makeIccPhoneBookInterfaceManager(Phone phone) {
+        Rlog.d(LOG_TAG, "makeIccPhoneBookInterfaceManager");
         return new IccPhoneBookInterfaceManager(phone);
     }
 
     public IccSmsInterfaceManager makeIccSmsInterfaceManager(Phone phone) {
+        Rlog.d(LOG_TAG, "makeIccSmsInterfaceManager");
         return new IccSmsInterfaceManager(phone);
     }
 
@@ -344,10 +362,12 @@
     }
 
     public EriManager makeEriManager(Phone phone, int eriFileSource) {
+        Rlog.d(LOG_TAG, "makeEriManager");
         return new EriManager(phone, eriFileSource);
     }
 
     public WspTypeDecoder makeWspTypeDecoder(byte[] pdu) {
+        Rlog.d(LOG_TAG, "makeWspTypeDecoder");
         return new WspTypeDecoder(pdu);
     }
 
@@ -356,9 +376,10 @@
      */
     public InboundSmsTracker makeInboundSmsTracker(byte[] pdu, long timestamp, int destPort,
             boolean is3gpp2, boolean is3gpp2WapPdu, String address, String displayAddr,
-            String messageBody, boolean isClass0) {
+            String messageBody, boolean isClass0, int subId) {
+        Rlog.d(LOG_TAG, "makeInboundSmsTracker");
         return new InboundSmsTracker(pdu, timestamp, destPort, is3gpp2, is3gpp2WapPdu, address,
-                displayAddr, messageBody, isClass0);
+                displayAddr, messageBody, isClass0, subId);
     }
 
     /**
@@ -367,20 +388,23 @@
     public InboundSmsTracker makeInboundSmsTracker(byte[] pdu, long timestamp, int destPort,
             boolean is3gpp2, String address, String displayAddr, int referenceNumber,
             int sequenceNumber, int messageCount, boolean is3gpp2WapPdu, String messageBody,
-            boolean isClass0) {
+            boolean isClass0, int subId) {
+        Rlog.d(LOG_TAG, "makeInboundSmsTracker");
         return new InboundSmsTracker(pdu, timestamp, destPort, is3gpp2, address, displayAddr,
                 referenceNumber, sequenceNumber, messageCount, is3gpp2WapPdu, messageBody,
-                isClass0);
+                isClass0, subId);
     }
 
     /**
      * Create a tracker from a row of raw table
      */
     public InboundSmsTracker makeInboundSmsTracker(Cursor cursor, boolean isCurrentFormat3gpp2) {
+        Rlog.d(LOG_TAG, "makeInboundSmsTracker");
         return new InboundSmsTracker(cursor, isCurrentFormat3gpp2);
     }
 
     public ImsPhoneCallTracker makeImsPhoneCallTracker(ImsPhone imsPhone) {
+        Rlog.d(LOG_TAG, "makeImsPhoneCallTracker");
         return new ImsPhoneCallTracker(imsPhone);
     }
 
@@ -407,10 +431,12 @@
     public CdmaSubscriptionSourceManager
     getCdmaSubscriptionSourceManagerInstance(Context context, CommandsInterface ci, Handler h,
                                              int what, Object obj) {
+        Rlog.d(LOG_TAG, "getCdmaSubscriptionSourceManagerInstance");
         return CdmaSubscriptionSourceManager.getInstance(context, ci, h, what, obj);
     }
 
     public IDeviceIdleController getIDeviceIdleController() {
+        Rlog.d(LOG_TAG, "getIDeviceIdleController");
         return IDeviceIdleController.Stub.asInterface(
                 ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
     }
@@ -423,4 +449,49 @@
     public DataEnabledSettings makeDataEnabledSettings(Phone phone) {
         return new DataEnabledSettings(phone);
     }
+
+    public Phone makePhone(Context context, CommandsInterface ci, PhoneNotifier notifier,
+            int phoneId, int precisePhoneType,
+            TelephonyComponentFactory telephonyComponentFactory) {
+        Rlog.i(TAG, "makePhone");
+        return new GsmCdmaPhone(context, ci, notifier, phoneId, precisePhoneType,
+                telephonyComponentFactory);
+    }
+
+    public SubscriptionController initSubscriptionController(Context c, CommandsInterface[] ci) {
+        Rlog.i(TAG, "initSubscriptionController");
+        return SubscriptionController.init(c, ci);
+    }
+
+    public SubscriptionInfoUpdater makeSubscriptionInfoUpdater(Looper looper, Context context,
+            Phone[] phones, CommandsInterface[] ci) {
+        Rlog.i(TAG, "makeSubscriptionInfoUpdater");
+        return new SubscriptionInfoUpdater(looper, context, phones, ci);
+    }
+
+    public PhoneSwitcher makePhoneSwitcher(int maxActivePhones, int numPhones, Context context,
+            SubscriptionController subscriptionController, Looper looper, ITelephonyRegistry tr,
+            CommandsInterface[] cis, Phone[] phones) {
+        Rlog.i(TAG, "makePhoneSwitcher");
+        return PhoneSwitcher.make(maxActivePhones,numPhones,
+                context, subscriptionController, looper, tr, cis,
+                phones);
+    }
+
+    public RIL makeRIL(Context context, int preferredNetworkType,
+            int cdmaSubscription, Integer instanceId) {
+        Rlog.d(LOG_TAG, "makeRIL");
+        return new RIL(context, preferredNetworkType, cdmaSubscription, instanceId);
+    }
+
+    public MultiSimSettingController initMultiSimSettingController(Context c,
+            SubscriptionController sc) {
+        Rlog.i(TAG, " initMultiSimSettingController ");
+        return MultiSimSettingController.init(c, sc);
+    }
+
+    public void makeExtTelephonyClasses(Context context,
+            Phone[] phones, CommandsInterface[] commandsInterfaces) {
+        Rlog.d(LOG_TAG, "makeExtTelephonyClasses");
+    }
 }
diff --git a/src/java/com/android/internal/telephony/TimeServiceHelper.java b/src/java/com/android/internal/telephony/TimeServiceHelper.java
new file mode 100644
index 0000000..861b7bf
--- /dev/null
+++ b/src/java/com/android/internal/telephony/TimeServiceHelper.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 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.app.timedetector.TimeDetector;
+import android.os.SystemClock;
+import android.util.TimestampedValue;
+
+/**
+ * An interface to various time / time zone detection behaviors that should be centralized into a
+ * new service.
+ */
+public interface TimeServiceHelper {
+
+    /**
+     * Callback interface for automatic detection enable/disable changes.
+     */
+    interface Listener {
+        /**
+         * Automatic time zone detection has been enabled or disabled.
+         */
+        void onTimeZoneDetectionChange(boolean enabled);
+    }
+
+    /**
+     * Sets a listener that will be called when the automatic time / time zone detection setting
+     * changes.
+     */
+    void setListener(Listener listener);
+
+    /**
+     * Returns the same value as {@link System#currentTimeMillis()}.
+     */
+    long currentTimeMillis();
+
+    /**
+     * Returns the same value as {@link SystemClock#elapsedRealtime()}.
+     */
+    long elapsedRealtime();
+
+    /**
+     * Returns true if the device has an explicit time zone set.
+     */
+    boolean isTimeZoneSettingInitialized();
+
+    /**
+     * Returns true if automatic time zone detection is enabled in settings.
+     */
+    boolean isTimeZoneDetectionEnabled();
+
+    /**
+     * Set the device time zone and send out a sticky broadcast so the system can
+     * determine if the timezone was set by the carrier.
+     *
+     * @param zoneId timezone set by carrier
+     */
+    void setDeviceTimeZone(String zoneId);
+
+    /**
+     * Suggest the time to the {@link TimeDetector}.
+     *
+     * @param signalTimeMillis the signal time as received from the network
+     */
+    void suggestDeviceTime(TimestampedValue<Long> signalTimeMillis);
+}
diff --git a/src/java/com/android/internal/telephony/NewTimeServiceHelper.java b/src/java/com/android/internal/telephony/TimeServiceHelperImpl.java
similarity index 80%
rename from src/java/com/android/internal/telephony/NewTimeServiceHelper.java
rename to src/java/com/android/internal/telephony/TimeServiceHelperImpl.java
index 1346c5f..5b2d909 100644
--- a/src/java/com/android/internal/telephony/NewTimeServiceHelper.java
+++ b/src/java/com/android/internal/telephony/TimeServiceHelperImpl.java
@@ -34,18 +34,7 @@
  * An interface to various time / time zone detection behaviors that should be centralized into a
  * new service.
  */
-// Non-final to allow mocking.
-public class NewTimeServiceHelper {
-
-    /**
-     * Callback interface for automatic detection enable/disable changes.
-     */
-    public interface Listener {
-        /**
-         * Automatic time zone detection has been enabled or disabled.
-         */
-        void onTimeZoneDetectionChange(boolean enabled);
-    }
+public class TimeServiceHelperImpl implements TimeServiceHelper {
 
     private static final String TIMEZONE_PROPERTY = "persist.sys.timezone";
 
@@ -56,16 +45,13 @@
     private Listener mListener;
 
     /** Creates a TimeServiceHelper */
-    public NewTimeServiceHelper(Context context) {
+    public TimeServiceHelperImpl(Context context) {
         mContext = context;
         mCr = context.getContentResolver();
         mTimeDetector = context.getSystemService(TimeDetector.class);
     }
 
-    /**
-     * Sets a listener that will be called when the automatic time / time zone detection setting
-     * changes.
-     */
+    @Override
     public void setListener(Listener listener) {
         if (listener == null) {
             throw new NullPointerException("listener==null");
@@ -83,31 +69,23 @@
                 });
     }
 
-    /**
-     * Returns the same value as {@link System#currentTimeMillis()}.
-     */
+    @Override
     public long currentTimeMillis() {
         return System.currentTimeMillis();
     }
 
-    /**
-     * Returns the same value as {@link SystemClock#elapsedRealtime()}.
-     */
+    @Override
     public long elapsedRealtime() {
         return SystemClock.elapsedRealtime();
     }
 
-    /**
-     * Returns true if the device has an explicit time zone set.
-     */
+    @Override
     public boolean isTimeZoneSettingInitialized() {
         return isTimeZoneSettingInitializedStatic();
 
     }
 
-    /**
-     * Returns true if automatic time zone detection is enabled in settings.
-     */
+    @Override
     public boolean isTimeZoneDetectionEnabled() {
         try {
             return Settings.Global.getInt(mCr, Settings.Global.AUTO_TIME_ZONE) > 0;
@@ -116,21 +94,12 @@
         }
     }
 
-    /**
-     * Set the device time zone and send out a sticky broadcast so the system can
-     * determine if the timezone was set by the carrier.
-     *
-     * @param zoneId timezone set by carrier
-     */
+    @Override
     public void setDeviceTimeZone(String zoneId) {
         setDeviceTimeZoneStatic(mContext, zoneId);
     }
 
-    /**
-     * Suggest the time to the {@link TimeDetector}.
-     *
-     * @param signalTimeMillis the signal time as received from the network
-     */
+    @Override
     public void suggestDeviceTime(TimestampedValue<Long> signalTimeMillis) {
         TimeSignal timeSignal = new TimeSignal(TimeSignal.SOURCE_ID_NITZ, signalTimeMillis);
         mTimeDetector.suggestTime(timeSignal);
diff --git a/src/java/com/android/internal/telephony/UiccPhoneBookController.java b/src/java/com/android/internal/telephony/UiccPhoneBookController.java
index 5a4d480..494e6ae 100644
--- a/src/java/com/android/internal/telephony/UiccPhoneBookController.java
+++ b/src/java/com/android/internal/telephony/UiccPhoneBookController.java
@@ -19,6 +19,7 @@
 package com.android.internal.telephony;
 
 import android.annotation.UnsupportedAppUsage;
+import android.content.ContentValues;
 import android.os.ServiceManager;
 import android.telephony.Rlog;
 
@@ -130,6 +131,41 @@
         }
     }
 
+    @Override
+    public int[] getAdnRecordsCapacity() throws android.os.RemoteException {
+        return getAdnRecordsCapacityForSubscriber(getDefaultSubscription());
+    }
+
+    @Override
+    public int[] getAdnRecordsCapacityForSubscriber(int subId)
+           throws android.os.RemoteException {
+        IccPhoneBookInterfaceManager iccPbkIntMgr = getIccPhoneBookInterfaceManager(subId);
+        if (iccPbkIntMgr != null) {
+            return iccPbkIntMgr.getAdnRecordsCapacity();
+        } else {
+            Rlog.e(TAG,"getAdnRecordsCapacity iccPbkIntMgr is null for Subscription: " + subId);
+            return null;
+        }
+    }
+
+    public boolean updateAdnRecordsWithContentValuesInEfBySearch(int efid, ContentValues values,
+            String pin2) throws android.os.RemoteException {
+        return updateAdnRecordsWithContentValuesInEfBySearchUsingSubId(
+                getDefaultSubscription(), efid, values, pin2);
+    }
+
+    public boolean updateAdnRecordsWithContentValuesInEfBySearchUsingSubId(int subId, int efid,
+            ContentValues values, String pin2) throws android.os.RemoteException {
+        IccPhoneBookInterfaceManager iccPbkIntMgr = getIccPhoneBookInterfaceManager(subId);
+        if (iccPbkIntMgr != null) {
+            return iccPbkIntMgr.updateAdnRecordsWithContentValuesInEfBySearch(efid, values, pin2);
+        } else {
+            Rlog.e(TAG,"updateAdnRecordsWithContentValuesInEfBySearchUsingSubId " +
+                "iccPbkIntMgr is null for Subscription: " + subId);
+            return false;
+        }
+    }
+
     /**
      * get phone book interface manager object based on subscription.
      **/
diff --git a/src/java/com/android/internal/telephony/VisualVoicemailSmsFilter.java b/src/java/com/android/internal/telephony/VisualVoicemailSmsFilter.java
index 657d6bc..3ea5fc2 100644
--- a/src/java/com/android/internal/telephony/VisualVoicemailSmsFilter.java
+++ b/src/java/com/android/internal/telephony/VisualVoicemailSmsFilter.java
@@ -143,6 +143,7 @@
             return false;
         }
 
+        String clientPrefix = settings.clientPrefix;
         FullMessage fullMessage = getFullMessage(pdus, format);
 
         if (fullMessage == null) {
@@ -152,6 +153,10 @@
             String asciiMessage = parseAsciiPduMessage(pdus);
             WrappedMessageData messageData = VisualVoicemailSmsParser
                     .parseAlternativeFormat(asciiMessage);
+            if (messageData == null) {
+                Log.i(TAG, "Attempt to parse ascii PDU");
+                messageData = VisualVoicemailSmsParser.parse(clientPrefix, asciiMessage);
+            }
             if (messageData != null) {
                 sendVvmSmsBroadcast(context, settings, phoneAccountHandle, messageData, null);
             }
@@ -161,7 +166,6 @@
         }
 
         String messageBody = fullMessage.fullMessageBody;
-        String clientPrefix = settings.clientPrefix;
         WrappedMessageData messageData = VisualVoicemailSmsParser
                 .parse(clientPrefix, messageBody);
         if (messageData != null) {
diff --git a/src/java/com/android/internal/telephony/WakeLockStateMachine.java b/src/java/com/android/internal/telephony/WakeLockStateMachine.java
index fc95f1f..1bc6298 100644
--- a/src/java/com/android/internal/telephony/WakeLockStateMachine.java
+++ b/src/java/com/android/internal/telephony/WakeLockStateMachine.java
@@ -38,7 +38,7 @@
  * {@link #quit}.
  */
 public abstract class WakeLockStateMachine extends StateMachine {
-    protected static final boolean DBG = true;    // TODO: change to false
+    protected static final boolean DBG = Build.IS_DEBUGGABLE;
 
     private final PowerManager.WakeLock mWakeLock;
 
@@ -51,6 +51,9 @@
     /** Release wakelock after a short timeout when returning to idle state. */
     static final int EVENT_RELEASE_WAKE_LOCK = 3;
 
+    /** Broadcast not required due to geo-fencing check */
+    static final int EVENT_BROADCAST_NOT_REQUIRED = 4;
+
     @UnsupportedAppUsage
     protected Phone mPhone;
 
@@ -75,7 +78,8 @@
 
         PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
         mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, debugTag);
-        mWakeLock.acquire();    // wake lock released after we enter idle state
+        // wake lock released after we enter idle state
+        mWakeLock.acquire();
 
         addState(mDefaultState);
         addState(mIdleState, mDefaultState);
@@ -83,6 +87,16 @@
         setInitialState(mIdleState);
     }
 
+    private void releaseWakeLock() {
+        if (mWakeLock.isHeld()) {
+            mWakeLock.release();
+        }
+
+        if (mWakeLock.isHeld()) {
+            loge("Wait lock is held after release.");
+        }
+    }
+
     /**
      * Tell the state machine to quit after processing all messages.
      */
@@ -141,13 +155,14 @@
         @Override
         public void exit() {
             mWakeLock.acquire();
-            if (DBG) log("acquired wakelock, leaving Idle state");
+            if (DBG) log("Idle: acquired wakelock, leaving Idle state");
         }
 
         @Override
         public boolean processMessage(Message msg) {
             switch (msg.what) {
                 case EVENT_NEW_SMS_MESSAGE:
+                    log("Idle: new cell broadcast message");
                     // transition to waiting state if we sent a broadcast
                     if (handleSmsMessage(msg)) {
                         transitionTo(mWaitingState);
@@ -155,15 +170,12 @@
                     return HANDLED;
 
                 case EVENT_RELEASE_WAKE_LOCK:
-                    mWakeLock.release();
-                    if (DBG) {
-                        if (mWakeLock.isHeld()) {
-                            // this is okay as long as we call release() for every acquire()
-                            log("mWakeLock is still held after release");
-                        } else {
-                            log("mWakeLock released");
-                        }
-                    }
+                    log("Idle: release wakelock");
+                    releaseWakeLock();
+                    return HANDLED;
+
+                case EVENT_BROADCAST_NOT_REQUIRED:
+                    log("Idle: broadcast not required");
                     return HANDLED;
 
                 default:
@@ -181,20 +193,24 @@
         public boolean processMessage(Message msg) {
             switch (msg.what) {
                 case EVENT_NEW_SMS_MESSAGE:
-                    log("deferring message until return to idle");
+                    log("Waiting: deferring message until return to idle");
                     deferMessage(msg);
                     return HANDLED;
 
                 case EVENT_BROADCAST_COMPLETE:
-                    log("broadcast complete, returning to idle");
+                    log("Waiting: broadcast complete, returning to idle");
                     transitionTo(mIdleState);
                     return HANDLED;
 
                 case EVENT_RELEASE_WAKE_LOCK:
-                    mWakeLock.release();    // decrement wakelock from previous entry to Idle
-                    if (!mWakeLock.isHeld()) {
-                        // wakelock should still be held until 3 seconds after we enter Idle
-                        loge("mWakeLock released while still in WaitingState!");
+                    log("Waiting: release wakelock");
+                    releaseWakeLock();
+                    return HANDLED;
+
+                case EVENT_BROADCAST_NOT_REQUIRED:
+                    log("Waiting: broadcast not required");
+                    if (mReceiverCount.get() == 0) {
+                        transitionTo(mIdleState);
                     }
                     return HANDLED;
 
diff --git a/src/java/com/android/internal/telephony/WapPushOverSms.java b/src/java/com/android/internal/telephony/WapPushOverSms.java
index 58ce62b..14942b1 100755
--- a/src/java/com/android/internal/telephony/WapPushOverSms.java
+++ b/src/java/com/android/internal/telephony/WapPushOverSms.java
@@ -326,27 +326,13 @@
      * wap-230-wsp-20010705-a section 8 for details on the WAP PDU format.
      *
      * @param pdu The WAP PDU, made up of one or more SMS PDUs
-     * @return a result code from {@link android.provider.Telephony.Sms.Intents}, or
-     *         {@link Activity#RESULT_OK} if the message has been broadcast
-     *         to applications
-     */
-    @UnsupportedAppUsage
-    public int dispatchWapPdu(byte[] pdu, BroadcastReceiver receiver, InboundSmsHandler handler) {
-        return dispatchWapPdu(pdu, receiver, handler, null);
-    }
-
-    /**
-     * Dispatches inbound messages that are in the WAP PDU format. See
-     * wap-230-wsp-20010705-a section 8 for details on the WAP PDU format.
-     *
-     * @param pdu The WAP PDU, made up of one or more SMS PDUs
      * @param address The originating address
      * @return a result code from {@link android.provider.Telephony.Sms.Intents}, or
      *         {@link Activity#RESULT_OK} if the message has been broadcast
      *         to applications
      */
     public int dispatchWapPdu(byte[] pdu, BroadcastReceiver receiver, InboundSmsHandler handler,
-            String address) {
+            String address, int subId) {
         DecodedResult result = decodeWapPdu(pdu, handler);
         if (result.statusCode != Activity.RESULT_OK) {
             return result.statusCode;
@@ -441,7 +427,7 @@
 
         handler.dispatchIntent(intent, getPermissionForType(result.mimeType),
                 getAppOpsPermissionForIntent(result.mimeType), options, receiver,
-                UserHandle.SYSTEM);
+                UserHandle.SYSTEM, subId);
         return Activity.RESULT_OK;
     }
 
diff --git a/src/java/com/android/internal/telephony/cdma/CdmaInboundSmsHandler.java b/src/java/com/android/internal/telephony/cdma/CdmaInboundSmsHandler.java
index 555a421..ec14efa 100644
--- a/src/java/com/android/internal/telephony/cdma/CdmaInboundSmsHandler.java
+++ b/src/java/com/android/internal/telephony/cdma/CdmaInboundSmsHandler.java
@@ -294,7 +294,8 @@
         InboundSmsTracker tracker = TelephonyComponentFactory.getInstance()
                 .inject(InboundSmsTracker.class.getName()).makeInboundSmsTracker(
                 userData, timestamp, destinationPort, true, address, dispAddr, referenceNumber,
-                segment, totalSegments, true, HexDump.toHexString(userData), false /* isClass0 */);
+                segment, totalSegments, true, HexDump.toHexString(userData), false /* isClass0 */,
+                        mPhone.getSubId());
 
         // de-duping is done only for text messages
         return addTrackerToRawTableAndSendMessage(tracker, false /* don't de-dup */);
diff --git a/src/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java b/src/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
index 04bfbf2..913806d 100755
--- a/src/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
+++ b/src/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
@@ -151,7 +151,7 @@
         //   SMS over IMS is being handled by the ImsSmsDispatcher implementation and has indicated
         //   that the message should fall back to sending over CS.
         if (0 == tracker.mImsRetry && !isIms() || imsSmsDisabled || tracker.mUsesImsServiceForIms) {
-            mCi.sendCdmaSms(pdu, reply);
+            mCi.sendCdmaSms(pdu, reply, tracker.mRetryCount == 0 && tracker.mExpectMore);
         } else {
             mCi.sendImsCdmaSms(pdu, tracker.mImsRetry, tracker.mMessageRef, reply);
             // increment it here, so in case of SMS_FAIL_RETRY over IMS
diff --git a/src/java/com/android/internal/telephony/cdnr/CarrierDisplayNameResolver.java b/src/java/com/android/internal/telephony/cdnr/CarrierDisplayNameResolver.java
index 63986ad..87ee39d 100644
--- a/src/java/com/android/internal/telephony/cdnr/CarrierDisplayNameResolver.java
+++ b/src/java/com/android/internal/telephony/cdnr/CarrierDisplayNameResolver.java
@@ -39,6 +39,7 @@
 import android.util.SparseArray;
 
 import com.android.internal.telephony.GsmCdmaPhone;
+import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.cdnr.EfData.EFSource;
 import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppState;
 import com.android.internal.telephony.uicc.IccRecords;
@@ -376,12 +377,11 @@
         String plmn = null;
         boolean isSimReady = mPhone.getUiccCardApplication() != null
                 && mPhone.getUiccCardApplication().getState() == AppState.APPSTATE_READY;
-        boolean forceDisplayNoService = mContext.getResources().getBoolean(
-                com.android.internal.R.bool.config_display_no_service_when_sim_unready)
-                && !isSimReady;
+        boolean forceDisplayNoService =
+                mPhone.getServiceStateTracker().shouldForceDisplayNoService() && !isSimReady;
         ServiceState ss = getServiceState();
-        if (ss.getVoiceRegState() == ServiceState.STATE_POWER_OFF || !ss.isEmergencyOnly()
-                || forceDisplayNoService) {
+        if (ss.getVoiceRegState() == ServiceState.STATE_POWER_OFF
+                || forceDisplayNoService || !Phone.isEmergencyCallOnly()) {
             plmn = mContext.getResources().getString(
                     com.android.internal.R.string.lockscreen_carrier_default);
         } else {
diff --git a/src/java/com/android/internal/telephony/dataconnection/ApnSettingUtils.java b/src/java/com/android/internal/telephony/dataconnection/ApnSettingUtils.java
index fdf20a1..9a8d3ab 100644
--- a/src/java/com/android/internal/telephony/dataconnection/ApnSettingUtils.java
+++ b/src/java/com/android/internal/telephony/dataconnection/ApnSettingUtils.java
@@ -83,8 +83,8 @@
      */
     public static boolean mvnoMatches(IccRecords r, int mvnoType, String mvnoMatchData) {
         if (mvnoType == ApnSetting.MVNO_TYPE_SPN) {
-            if ((r.getServiceProviderName() != null)
-                    && r.getServiceProviderName().equalsIgnoreCase(mvnoMatchData)) {
+            String spn = r.getServiceProviderNameWithBrandOverride();
+            if ((spn != null) && spn.equalsIgnoreCase(mvnoMatchData)) {
                 return true;
             }
         } else if (mvnoType == ApnSetting.MVNO_TYPE_IMSI) {
diff --git a/src/java/com/android/internal/telephony/dataconnection/DataConnection.java b/src/java/com/android/internal/telephony/dataconnection/DataConnection.java
index 20ccee8..4d00284 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DataConnection.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DataConnection.java
@@ -22,6 +22,7 @@
 import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.app.PendingIntent;
+import android.content.Context;
 import android.net.ConnectivityManager;
 import android.net.KeepalivePacketData;
 import android.net.LinkAddress;
@@ -38,11 +39,13 @@
 import android.net.StringNetworkSpecifier;
 import android.os.AsyncResult;
 import android.os.Message;
+import android.os.PersistableBundle;
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.provider.Telephony;
 import android.telephony.AccessNetworkConstants;
 import android.telephony.AccessNetworkConstants.TransportType;
+import android.telephony.CarrierConfigManager;
 import android.telephony.DataFailCause;
 import android.telephony.NetworkRegistrationInfo;
 import android.telephony.Rlog;
@@ -50,11 +53,13 @@
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.telephony.data.ApnSetting;
+import android.telephony.data.ApnSetting.ApnType;
 import android.telephony.data.DataCallResponse;
 import android.telephony.data.DataProfile;
 import android.telephony.data.DataService;
 import android.telephony.data.DataServiceCallback;
 import android.text.TextUtils;
+import android.util.LocalLog;
 import android.util.Pair;
 import android.util.StatsLog;
 import android.util.TimeUtils;
@@ -109,8 +114,8 @@
  * as the coordinator has members which are used without synchronization.
  */
 public class DataConnection extends StateMachine {
-    private static final boolean DBG = true;
-    private static final boolean VDBG = true;
+    protected static final boolean DBG = true;
+    protected static final boolean VDBG = true;
 
     private static final String NETWORK_TYPE = "MOBILE";
 
@@ -164,7 +169,7 @@
     // The Tester for failing all bringup's
     private DcTesterFailBringUpAll mDcTesterFailBringUpAll;
 
-    private static AtomicInteger mInstanceNumber = new AtomicInteger(0);
+    protected static AtomicInteger mInstanceNumber = new AtomicInteger(0);
     private AsyncChannel mAc;
 
     // The DCT that's talking to us, we only support one!
@@ -174,12 +179,14 @@
 
     private final String mTagSuffix;
 
+    private final LocalLog mHandoverLocalLog = new LocalLog(100);
+
     /**
      * Used internally for saving connecting parameters.
      */
     public static class ConnectionParams {
         int mTag;
-        ApnContext mApnContext;
+        public ApnContext mApnContext;
         int mProfileId;
         int mRilRat;
         Message mOnCompletedMsg;
@@ -246,9 +253,9 @@
     @DataFailCause.FailCause
     private int mDcFailCause;
 
-    private Phone mPhone;
+    protected Phone mPhone;
     private DataServiceManager mDataServiceManager;
-    private final int mTransportType;
+    protected final int mTransportType;
     private LinkProperties mLinkProperties = new LinkProperties();
     private long mCreateTime;
     private long mLastFailTime;
@@ -257,7 +264,8 @@
     private static final String NULL_IP = "0.0.0.0";
     private Object mUserData;
     private int mSubscriptionOverride;
-    private int mRilRat = Integer.MAX_VALUE;
+    private int mRilRat = ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN;
+    private boolean mUnmeteredOverride;
     private int mDataRegState = Integer.MAX_VALUE;
     private NetworkInfo mNetworkInfo;
 
@@ -272,7 +280,9 @@
 
     private int mDisabledApnTypeBitMask = 0;
 
-    int mTag;
+    protected int mTag;
+
+    /** Data connection id assigned by the modem. This is unique across transports */
     public int mCid;
 
     @HandoverState
@@ -308,9 +318,11 @@
     static final int EVENT_RESET = BASE + 24;
     static final int EVENT_REEVALUATE_RESTRICTED_STATE = BASE + 25;
     static final int EVENT_REEVALUATE_DATA_CONNECTION_PROPERTIES = BASE + 26;
-
-    private static final int CMD_TO_STRING_COUNT =
-            EVENT_REEVALUATE_DATA_CONNECTION_PROPERTIES - BASE + 1;
+    static final int EVENT_NR_STATE_CHANGED = BASE + 27;
+    static final int EVENT_DATA_CONNECTION_METEREDNESS_CHANGED = BASE + 28;
+    static final int EVENT_NR_FREQUENCY_CHANGED = BASE + 29;
+    protected static final int EVENT_RETRY_CONNECTION = BASE + 30;
+    private static final int CMD_TO_STRING_COUNT = EVENT_RETRY_CONNECTION - BASE + 1;
 
     private static String[] sCmdToString = new String[CMD_TO_STRING_COUNT];
     static {
@@ -346,6 +358,11 @@
                 "EVENT_REEVALUATE_RESTRICTED_STATE";
         sCmdToString[EVENT_REEVALUATE_DATA_CONNECTION_PROPERTIES - BASE] =
                 "EVENT_REEVALUATE_DATA_CONNECTION_PROPERTIES";
+        sCmdToString[EVENT_NR_STATE_CHANGED - BASE] = "EVENT_NR_STATE_CHANGED";
+        sCmdToString[EVENT_DATA_CONNECTION_METEREDNESS_CHANGED - BASE] =
+                "EVENT_DATA_CONNECTION_METEREDNESS_CHANGED";
+        sCmdToString[EVENT_NR_FREQUENCY_CHANGED - BASE] = "EVENT_NR_FREQUENCY_CHANGED";
+        sCmdToString[EVENT_RETRY_CONNECTION - BASE] = "EVENT_RETRY_CONNECTION";
     }
     // Convert cmd to string or null if unknown
     static String cmdToString(int cmd) {
@@ -383,7 +400,7 @@
         return dc;
     }
 
-    void dispose() {
+    protected void dispose() {
         log("dispose: call quiteNow()");
         quitNow();
     }
@@ -558,7 +575,7 @@
     }
 
     //***** Constructor (NOTE: uses dcc.getHandler() as its Handler)
-    private DataConnection(Phone phone, String tagSuffix, int id,
+    protected DataConnection(Phone phone, String tagSuffix, int id,
                            DcTracker dct, DataServiceManager dataServiceManager,
                            DcTesterFailBringUpAll failBringUpAll, DcController dcc) {
         super("DC-" + tagSuffix, dcc.getHandler());
@@ -576,9 +593,16 @@
         mId = id;
         mCid = -1;
         ServiceState ss = mPhone.getServiceState();
-        mRilRat = ss.getRilDataRadioTechnology();
         mDataRegState = mPhone.getServiceState().getDataRegState();
-        int networkType = ss.getDataNetworkType();
+        int networkType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
+
+        NetworkRegistrationInfo nri = ss.getNetworkRegistrationInfo(
+                NetworkRegistrationInfo.DOMAIN_PS, mTransportType);
+        if (nri != null) {
+            networkType = nri.getAccessNetworkTechnology();
+            mRilRat = ServiceState.networkTypeToRilRadioTechnology(networkType);
+        }
+
         mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_MOBILE,
                 networkType, NETWORK_TYPE, TelephonyManager.getNetworkTypeName(networkType));
         mNetworkInfo.setRoaming(ss.getDataRoaming());
@@ -680,6 +704,7 @@
             linkProperties = dc.getLinkProperties();
             // Preserve the potential network agent from the source data connection. The ownership
             // is not transferred at this moment.
+            mHandoverLocalLog.log("Handover started. Preserved the agent.");
             mHandoverSourceNetworkAgent = dc.getNetworkAgent();
             log("Get the handover source network agent: " + mHandoverSourceNetworkAgent);
             dc.setHandoverState(HANDOVER_STATE_BEING_TRANSFERRED);
@@ -712,6 +737,14 @@
     }
 
     /**
+     * Update NetworkCapabilities.NET_CAPABILITY_NOT_METERED based on meteredness
+     * @param isUnmetered whether this DC should be set to unmetered or not
+     */
+    public void onMeterednessChanged(boolean isUnmetered) {
+        sendMessage(obtainMessage(EVENT_DATA_CONNECTION_METEREDNESS_CHANGED, isUnmetered));
+    }
+
+    /**
      * TearDown the data connection when the deactivation is complete a Message with
      * msg.what == EVENT_DEACTIVATE_DONE
      *
@@ -792,7 +825,7 @@
 
             connectionCompletedMsg.sendToTarget();
         }
-        if (sendAll) {
+        if (sendAll && !(isPdpRejectConfigEnabled() && isPdpRejectCause(cause))) {
             log("Send to all. " + alreadySent + " " + DataFailCause.toString(cause));
             notifyAllWithEvent(alreadySent, DctConstants.EVENT_DATA_SETUP_COMPLETE_ERROR,
                     DataFailCause.toString(cause));
@@ -804,7 +837,7 @@
      *
      * @param dp is the DisconnectParams.
      */
-    private void notifyDisconnectCompleted(DisconnectParams dp, boolean sendAll) {
+    protected void notifyDisconnectCompleted(DisconnectParams dp, boolean sendAll) {
         if (VDBG) log("NotifyDisconnectCompleted");
 
         ApnContext alreadySent = null;
@@ -880,6 +913,8 @@
         mDcFailCause = DataFailCause.NONE;
         mDisabledApnTypeBitMask = 0;
         mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+        mSubscriptionOverride = 0;
+        mUnmeteredOverride = false;
     }
 
     /**
@@ -983,10 +1018,14 @@
 
         // NR 5G Non-Standalone use LTE cell as the primary cell, the ril technology is LTE in this
         // case. We use NR 5G TCP buffer size when connected to NR 5G Non-Standalone network.
-        if (rilRat == ServiceState.RIL_RADIO_TECHNOLOGY_LTE && isNRConnected()) {
+        if (mTransportType == AccessNetworkConstants.TRANSPORT_TYPE_WWAN
+                && rilRat == ServiceState.RIL_RADIO_TECHNOLOGY_LTE && isNRConnected()
+                && mPhone.getServiceStateTracker().getNrContextIds().contains(mCid)) {
             ratName = RAT_NAME_5G;
         }
 
+        log("updateTcpBufferSizes: " + ratName);
+
         // in the form: "ratname:rmem_min,rmem_def,rmem_max,wmem_min,wmem_def,wmem_max"
         String[] configOverride = mPhone.getContext().getResources().getStringArray(
                 com.android.internal.R.array.config_mobile_tcp_buffers);
@@ -1033,7 +1072,7 @@
                 case ServiceState.RIL_RADIO_TECHNOLOGY_LTE:
                 case ServiceState.RIL_RADIO_TECHNOLOGY_LTE_CA:
                     // Use NR 5G TCP buffer size when connected to NR 5G Non-Standalone network.
-                    if (isNRConnected()) {
+                    if (RAT_NAME_5G.equals(ratName)) {
                         sizes = TCP_BUFFER_SIZES_NR;
                     } else {
                         sizes = TCP_BUFFER_SIZES_LTE;
@@ -1042,6 +1081,9 @@
                 case ServiceState.RIL_RADIO_TECHNOLOGY_HSPAP:
                     sizes = TCP_BUFFER_SIZES_HSPAP;
                     break;
+                case ServiceState.RIL_RADIO_TECHNOLOGY_NR:
+                    sizes = TCP_BUFFER_SIZES_NR;
+                    break;
                 default:
                     // Leave empty - this will let ConnectivityService use the system default.
                     break;
@@ -1050,6 +1092,24 @@
         mLinkProperties.setTcpBufferSizes(sizes);
     }
 
+    private void updateLinkBandwidths(NetworkCapabilities caps, int rilRat) {
+        String ratName = ServiceState.rilRadioTechnologyToString(rilRat);
+        if (rilRat == 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;
+        }
+
+        if (DBG) log("updateLinkBandwidths: " + ratName);
+
+        Pair<Integer, Integer> values = mDct.getLinkBandwidths(ratName);
+        if (values == null) {
+            values = new Pair<>(14, 14);
+        }
+        caps.setLinkDownstreamBandwidthKbps(values.first);
+        caps.setLinkUpstreamBandwidthKbps(values.second);
+    }
+
     /**
      * Indicates if this data connection was established for unmetered use only. Note that this
      * flag should be populated when data becomes active. And if it is set to true, it can be set to
@@ -1251,29 +1311,7 @@
             result.removeCapability(NetworkCapabilities.NET_CAPABILITY_DUN);
         }
 
-        int up = 14;
-        int down = 14;
-        switch (mRilRat) {
-            case ServiceState.RIL_RADIO_TECHNOLOGY_GPRS: up = 80; down = 80; break;
-            case ServiceState.RIL_RADIO_TECHNOLOGY_EDGE: up = 59; down = 236; break;
-            case ServiceState.RIL_RADIO_TECHNOLOGY_UMTS: up = 384; down = 384; break;
-            case ServiceState.RIL_RADIO_TECHNOLOGY_IS95A: // fall through
-            case ServiceState.RIL_RADIO_TECHNOLOGY_IS95B: up = 14; down = 14; break;
-            case ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_0: up = 153; down = 2457; break;
-            case ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_A: up = 1843; down = 3174; break;
-            case ServiceState.RIL_RADIO_TECHNOLOGY_1xRTT: up = 100; down = 100; break;
-            case ServiceState.RIL_RADIO_TECHNOLOGY_HSDPA: up = 2048; down = 14336; break;
-            case ServiceState.RIL_RADIO_TECHNOLOGY_HSUPA: up = 5898; down = 14336; break;
-            case ServiceState.RIL_RADIO_TECHNOLOGY_HSPA: up = 5898; down = 14336; break;
-            case ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_B: up = 1843; down = 5017; break;
-            case ServiceState.RIL_RADIO_TECHNOLOGY_LTE: up = 51200; down = 102400; break;
-            case ServiceState.RIL_RADIO_TECHNOLOGY_LTE_CA: up = 51200; down = 102400; break;
-            case ServiceState.RIL_RADIO_TECHNOLOGY_EHRPD: up = 153; down = 2516; break;
-            case ServiceState.RIL_RADIO_TECHNOLOGY_HSPAP: up = 11264; down = 43008; break;
-            default:
-        }
-        result.setLinkUpstreamBandwidthKbps(up);
-        result.setLinkDownstreamBandwidthKbps(down);
+        updateLinkBandwidths(result, mRilRat);
 
         result.setNetworkSpecifier(new StringNetworkSpecifier(Integer.toString(mSubId)));
 
@@ -1290,6 +1328,11 @@
             result.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED);
         }
 
+        // Override set by DcTracker
+        if (mUnmeteredOverride) {
+            result.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
+        }
+
         return result;
     }
 
@@ -1478,6 +1521,10 @@
                     DataConnection.EVENT_DATA_CONNECTION_ROAM_ON, null);
             mPhone.getServiceStateTracker().registerForDataRoamingOff(getHandler(),
                     DataConnection.EVENT_DATA_CONNECTION_ROAM_OFF, null, true);
+            mPhone.getServiceStateTracker().registerForNrStateChanged(getHandler(),
+                    DataConnection.EVENT_NR_STATE_CHANGED, null);
+            mPhone.getServiceStateTracker().registerForNrFrequencyChanged(getHandler(),
+                    DataConnection.EVENT_NR_FREQUENCY_CHANGED, null);
 
             // Add ourselves to the list of data connections
             mDcController.addDc(DataConnection.this);
@@ -1492,6 +1539,8 @@
 
             mPhone.getServiceStateTracker().unregisterForDataRoamingOn(getHandler());
             mPhone.getServiceStateTracker().unregisterForDataRoamingOff(getHandler());
+            mPhone.getServiceStateTracker().unregisterForNrStateChanged(getHandler());
+            mPhone.getServiceStateTracker().unregisterForNrFrequencyChanged(getHandler());
 
             // Remove ourselves from the DC lists
             mDcController.removeDc(DataConnection.this);
@@ -1575,6 +1624,14 @@
                         mNetworkAgent.sendLinkProperties(mLinkProperties, DataConnection.this);
                     }
                     break;
+                case EVENT_DATA_CONNECTION_METEREDNESS_CHANGED:
+                    boolean isUnmetered = (boolean) msg.obj;
+                    if (isUnmetered == mUnmeteredOverride) {
+                        break;
+                    }
+                    mUnmeteredOverride = isUnmetered;
+                    // fallthrough
+                case EVENT_NR_FREQUENCY_CHANGED:
                 case EVENT_DATA_CONNECTION_ROAM_ON:
                 case EVENT_DATA_CONNECTION_ROAM_OFF:
                 case EVENT_DATA_CONNECTION_OVERRIDE_CHANGED:
@@ -1592,10 +1649,16 @@
                                 msg.arg1, SocketKeepalive.ERROR_INVALID_NETWORK);
                     }
                     break;
+                case EVENT_RETRY_CONNECTION:
+                    if (DBG) {
+                        String s = "DcDefaultState ignore EVENT_RETRY_CONNECTION"
+                                + " tag=" + msg.arg1 + ":mTag=" + mTag;
+                        logAndAddLogRec(s);
+                    }
+                    break;
                 default:
                     if (DBG) {
-                        log("DcDefaultState: shouldn't happen but ignore msg.what="
-                                + getWhatToString(msg.what));
+                        log("DcDefaultState: ignore msg.what=" + getWhatToString(msg.what));
                     }
                     break;
             }
@@ -1642,7 +1705,7 @@
     /**
      * The state machine is inactive and expects a EVENT_CONNECT.
      */
-    private class DcInactiveState extends State {
+    protected class DcInactiveState extends State {
         // Inform all contexts we've failed connecting
         public void setEnterNotificationParams(ConnectionParams cp,
                                                @DataFailCause.FailCause int cause) {
@@ -1681,6 +1744,31 @@
                 mHandoverState = HANDOVER_STATE_COMPLETED;
             }
 
+            // Check for dangling agent. Ideally the handover source agent should be null if
+            // handover process is smooth. When it's not null, that means handover failed. The
+            // agent was not successfully transferred to the new data connection. We should
+            // gracefully notify connectivity service the network was disconnected.
+            if (mHandoverSourceNetworkAgent != null) {
+                DataConnection sourceDc = mHandoverSourceNetworkAgent.getDataConnection();
+                if (sourceDc != null) {
+                    // If the source data connection still owns this agent, then just reset the
+                    // handover state back to idle because handover is already failed.
+                    mHandoverLocalLog.log(
+                            "Handover failed. Reset the source dc state to idle");
+                    sourceDc.setHandoverState(HANDOVER_STATE_IDLE);
+                } else {
+                    // The agent is now a dangling agent. No data connection owns this agent.
+                    // Gracefully notify connectivity service disconnected.
+                    mHandoverLocalLog.log(
+                            "Handover failed and dangling agent found.");
+                    mHandoverSourceNetworkAgent.acquireOwnership(
+                            DataConnection.this, mTransportType);
+                    mHandoverSourceNetworkAgent.sendNetworkInfo(mNetworkInfo, DataConnection.this);
+                    mHandoverSourceNetworkAgent.releaseOwnership(DataConnection.this);
+                }
+                mHandoverSourceNetworkAgent = null;
+            }
+
             if (mConnectionParams != null) {
                 if (DBG) {
                     log("DcInactiveState: enter notifyConnectCompleted +ALL failCause="
@@ -1708,7 +1796,10 @@
             // Remove ourselves from cid mapping, before clearSettings
             mDcController.removeActiveDcByCid(DataConnection.this);
 
-            clearSettings();
+            if (!(isPdpRejectConfigEnabled() && isPdpRejectCause(mDcFailCause))) {
+                log("DcInactiveState: clearing settings");
+                clearSettings();
+            }
         }
 
         @Override
@@ -1727,6 +1818,11 @@
                     return HANDLED;
                 case EVENT_CONNECT:
                     if (DBG) log("DcInactiveState: mag.what=EVENT_CONNECT");
+                    if (isPdpRejectConfigEnabled() && !isDataCallConnectAllowed()) {
+                        if (DBG) log("DcInactiveState: skip EVENT_CONNECT");
+                        return HANDLED;
+                    }
+
                     ConnectionParams cp = (ConnectionParams) msg.obj;
 
                     if (!initConnection(cp)) {
@@ -1759,6 +1855,25 @@
                     if (DBG) log("DcInactiveState: msg.what=EVENT_DISCONNECT_ALL");
                     notifyDisconnectCompleted((DisconnectParams)msg.obj, false);
                     return HANDLED;
+                case EVENT_RETRY_CONNECTION:
+                    if (DBG) {
+                        log("DcInactiveState: msg.what=EVENT_RETRY_CONNECTION"
+                                + " mConnectionParams=" + mConnectionParams);
+                    }
+                    if (mConnectionParams != null) {
+                        if (initConnection(mConnectionParams)) {
+                            //In case of apn modify, get the latest apn to retry
+                            mApnSetting = mConnectionParams.mApnContext.getApnSetting();
+                            connect(mConnectionParams);
+                            transitionTo(mActivatingState);
+                        } else {
+                            if (DBG) {
+                                log("DcInactiveState: msg.what=EVENT_RETRY_CONNECTION"
+                                    + " initConnection failed");
+                            }
+                        }
+                    }
+                    return HANDLED;
                 default:
                     if (VDBG) {
                         log("DcInactiveState not handled msg.what=" + getWhatToString(msg.what));
@@ -1767,7 +1882,7 @@
             }
         }
     }
-    private DcInactiveState mInactiveState = new DcInactiveState();
+    protected DcInactiveState mInactiveState = new DcInactiveState();
 
     /**
      * The state machine is activating a connection.
@@ -1782,6 +1897,14 @@
                     mApnSetting != null
                         ? mApnSetting.canHandleType(ApnSetting.TYPE_DEFAULT) : false);
             setHandoverState(HANDOVER_STATE_IDLE);
+            // restricted evaluation depends on network requests from apnContext. The evaluation
+            // should happen once entering connecting state rather than active state because it's
+            // possible that restricted network request can be released during the connecting window
+            // and if we wait for connection established, then we might mistakenly
+            // consider it as un-restricted. ConnectivityService then will immediately
+            // tear down the connection through networkAgent unwanted callback if all requests for
+            // this connection are going away.
+            mRestrictedNetworkOverride = shouldRestrictNetwork();
         }
         @Override
         public boolean processMessage(Message msg) {
@@ -1804,6 +1927,10 @@
                     DataCallResponse dataCallResponse =
                             msg.getData().getParcelable(DataServiceManager.DATA_CALL_RESPONSE);
                     SetupResult result = onSetupConnectionCompleted(msg.arg1, dataCallResponse, cp);
+                    if (result != null) {
+                        log("EVENT_SETUP_DATA_CONNECTION_DONE, result: " + result
+                                + ", mFailCause: " + result.mFailCause);
+                    }
                     if (result != SetupResult.ERROR_STALE) {
                         if (mConnectionParams != cp) {
                             loge("DcActivatingState: WEIRD mConnectionsParams:"+ mConnectionParams
@@ -1822,6 +1949,7 @@
                             // All is well
                             mDcFailCause = DataFailCause.NONE;
                             transitionTo(mActiveState);
+                            handlePdpRejectCauseSuccess();
                             break;
                         case ERROR_RADIO_NOT_AVAILABLE:
                             // Vendor ril rejected the command and didn't connect.
@@ -1854,6 +1982,10 @@
                                     + " isPermanentFailure=" +
                                     mDct.isPermanentFailure(result.mFailCause);
                             if (DBG) log(str);
+                            if (isPdpRejectCauseFailureHandled(result, cp)) {
+                                if (DBG) log("isPdpRejectCauseFailureHandled true, breaking");
+                                break;
+                            }
                             if (cp.mApnContext != null) cp.mApnContext.requestLog(str);
 
                             // Save the cause. DcTracker.onDataSetupComplete will check this
@@ -1930,7 +2062,6 @@
             // set skip464xlat if it is not default otherwise
             misc.skip464xlat = shouldSkip464Xlat();
 
-            mRestrictedNetworkOverride = shouldRestrictNetwork();
             mUnmeteredUseOnly = isUnmeteredUseOnly();
 
             if (DBG) {
@@ -1955,15 +2086,25 @@
                 }
 
                 if (mHandoverSourceNetworkAgent != null) {
-                    log("Transfer network agent successfully.");
+                    String logStr = "Transfer network agent successfully.";
+                    log(logStr);
+                    mHandoverLocalLog.log(logStr);
                     mNetworkAgent = mHandoverSourceNetworkAgent;
                     mNetworkAgent.acquireOwnership(DataConnection.this, mTransportType);
+
+                    // TODO: Should evaluate mDisabledApnTypeBitMask again after handover. We don't
+                    // do it now because connectivity service does not support dynamically removing
+                    // immutable capabilities.
+
+                    // Update the capability after handover
                     mNetworkAgent.sendNetworkCapabilities(getNetworkCapabilities(),
                             DataConnection.this);
                     mNetworkAgent.sendLinkProperties(mLinkProperties, DataConnection.this);
                     mHandoverSourceNetworkAgent = null;
                 } else {
-                    loge("Failed to get network agent from original data connection");
+                    String logStr = "Failed to get network agent from original data connection";
+                    loge(logStr);
+                    mHandoverLocalLog.log(logStr);
                     return;
                 }
             } else {
@@ -1972,6 +2113,9 @@
                         mPhone.getPhoneId());
                 final int factorySerialNumber = (null == factory)
                         ? NetworkFactory.SerialNumber.NONE : factory.getSerialNumber();
+
+                mDisabledApnTypeBitMask |= getDisallowedApnTypes();
+
                 mNetworkAgent = DcNetworkAgent.createDcNetworkAgent(DataConnection.this,
                         mPhone, mNetworkInfo, mScore, misc, factorySerialNumber, mTransportType);
             }
@@ -2107,6 +2251,14 @@
                     retVal = HANDLED;
                     break;
                 }
+                case EVENT_DATA_CONNECTION_METEREDNESS_CHANGED:
+                    boolean isUnmetered = (boolean) msg.obj;
+                    if (isUnmetered == mUnmeteredOverride) {
+                        retVal = HANDLED;
+                        break;
+                    }
+                    mUnmeteredOverride = isUnmetered;
+                    // fallthrough
                 case EVENT_DATA_CONNECTION_ROAM_ON:
                 case EVENT_DATA_CONNECTION_ROAM_OFF:
                 case EVENT_DATA_CONNECTION_OVERRIDE_CHANGED: {
@@ -2124,13 +2276,21 @@
                     if (ar.exception != null) {
                         log("EVENT_BW_REFRESH_RESPONSE: error ignoring, e=" + ar.exception);
                     } else {
-                        final LinkCapacityEstimate lce = (LinkCapacityEstimate) ar.result;
+                        boolean useModem = DctConstants.BANDWIDTH_SOURCE_MODEM_KEY.equals(
+                                mPhone.getContext().getResources().getString(com.android.internal.R
+                                        .string.config_bandwidthEstimateSource));
                         NetworkCapabilities nc = getNetworkCapabilities();
-                        if (mPhone.getLceStatus() == RILConstants.LCE_ACTIVE) {
-                            nc.setLinkDownstreamBandwidthKbps(lce.downlinkCapacityKbps);
-                            if (mNetworkAgent != null) {
-                                mNetworkAgent.sendNetworkCapabilities(nc, DataConnection.this);
+                        LinkCapacityEstimate lce = (LinkCapacityEstimate) ar.result;
+                        if (useModem && mPhone.getLceStatus() == RILConstants.LCE_ACTIVE) {
+                            if (lce.downlinkCapacityKbps != LinkCapacityEstimate.INVALID) {
+                                nc.setLinkDownstreamBandwidthKbps(lce.downlinkCapacityKbps);
                             }
+                            if (lce.uplinkCapacityKbps != LinkCapacityEstimate.INVALID) {
+                                nc.setLinkUpstreamBandwidthKbps(lce.uplinkCapacityKbps);
+                            }
+                        }
+                        if (mNetworkAgent != null) {
+                            mNetworkAgent.sendNetworkCapabilities(nc, DataConnection.this);
                         }
                     }
                     retVal = HANDLED;
@@ -2246,13 +2406,18 @@
                     if (ar.exception != null) {
                         loge("EVENT_LINK_CAPACITY_CHANGED e=" + ar.exception);
                     } else {
-                        LinkCapacityEstimate lce = (LinkCapacityEstimate) ar.result;
+                        boolean useModem = DctConstants.BANDWIDTH_SOURCE_MODEM_KEY.equals(
+                                mPhone.getContext().getResources().getString(com.android.internal.R
+                                        .string.config_bandwidthEstimateSource));
                         NetworkCapabilities nc = getNetworkCapabilities();
-                        if (lce.downlinkCapacityKbps != LinkCapacityEstimate.INVALID) {
-                            nc.setLinkDownstreamBandwidthKbps(lce.downlinkCapacityKbps);
-                        }
-                        if (lce.uplinkCapacityKbps != LinkCapacityEstimate.INVALID) {
-                            nc.setLinkUpstreamBandwidthKbps(lce.uplinkCapacityKbps);
+                        if (useModem) {
+                            LinkCapacityEstimate lce = (LinkCapacityEstimate) ar.result;
+                            if (lce.downlinkCapacityKbps != LinkCapacityEstimate.INVALID) {
+                                nc.setLinkDownstreamBandwidthKbps(lce.downlinkCapacityKbps);
+                            }
+                            if (lce.uplinkCapacityKbps != LinkCapacityEstimate.INVALID) {
+                                nc.setLinkUpstreamBandwidthKbps(lce.uplinkCapacityKbps);
+                            }
                         }
                         if (mNetworkAgent != null) {
                             mNetworkAgent.sendNetworkCapabilities(nc, DataConnection.this);
@@ -2300,6 +2465,14 @@
                     retVal = HANDLED;
                     break;
                 }
+                case EVENT_NR_STATE_CHANGED: {
+                    updateTcpBufferSizes(mRilRat);
+                    if (mNetworkAgent != null) {
+                        mNetworkAgent.sendLinkProperties(mLinkProperties, DataConnection.this);
+                    }
+                    retVal = HANDLED;
+                    break;
+                }
                 default:
                     if (VDBG) {
                         log("DcActiveState not handled msg.what=" + getWhatToString(msg.what));
@@ -2575,6 +2748,8 @@
     }
 
     void setHandoverState(@HandoverState int state) {
+        mHandoverLocalLog.log("State changed from " + handoverStateToString(mHandoverState)
+                + " to " + handoverStateToString(state));
         mHandoverState = state;
     }
 
@@ -2726,6 +2901,33 @@
                 == NetworkRegistrationInfo.NR_STATE_CONNECTED;
     }
 
+    /**
+     * @return The disallowed APN types bitmask
+     */
+    private @ApnType int getDisallowedApnTypes() {
+        CarrierConfigManager configManager = (CarrierConfigManager)
+                mPhone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
+        int apnTypesBitmask = 0;
+        if (configManager != null) {
+            PersistableBundle bundle = configManager.getConfigForSubId(mSubId);
+            if (bundle != null) {
+                String key = (mTransportType == AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
+                        ? CarrierConfigManager.KEY_CARRIER_WWAN_DISALLOWED_APN_TYPES_STRING_ARRAY
+                        : CarrierConfigManager.KEY_CARRIER_WLAN_DISALLOWED_APN_TYPES_STRING_ARRAY;
+                if (bundle.getStringArray(key) != null) {
+                    String disallowedApnTypesString =
+                            TextUtils.join(",", bundle.getStringArray(key));
+                    if (!TextUtils.isEmpty(disallowedApnTypesString)) {
+                        apnTypesBitmask = ApnSetting.getApnTypesBitmaskFromString(
+                                disallowedApnTypesString);
+                    }
+                }
+            }
+        }
+
+        return apnTypesBitmask;
+    }
+
     private void dumpToLog() {
         dump(null, new PrintWriter(new StringWriter(0)) {
             @Override
@@ -2771,6 +2973,15 @@
         return score;
     }
 
+    private String handoverStateToString(@HandoverState int state) {
+        switch (state) {
+            case HANDOVER_STATE_IDLE: return "IDLE";
+            case HANDOVER_STATE_BEING_TRANSFERRED: return "BEING_TRANSFERRED";
+            case HANDOVER_STATE_COMPLETED: return "COMPLETED";
+            default: return "UNKNOWN";
+        }
+    }
+
     /**
      * Dump the current state.
      *
@@ -2800,7 +3011,7 @@
         pw.println("mLinkProperties=" + mLinkProperties);
         pw.flush();
         pw.println("mDataRegState=" + mDataRegState);
-        pw.println("mHandoverState=" + mHandoverState);
+        pw.println("mHandoverState=" + handoverStateToString(mHandoverState));
         pw.println("mRilRat=" + mRilRat);
         pw.println("mNetworkCapabilities=" + getNetworkCapabilities());
         pw.println("mCreateTime=" + TimeUtils.logTimeOfDay(mCreateTime));
@@ -2810,15 +3021,46 @@
         pw.println("mSubscriptionOverride=" + Integer.toHexString(mSubscriptionOverride));
         pw.println("mRestrictedNetworkOverride=" + mRestrictedNetworkOverride);
         pw.println("mUnmeteredUseOnly=" + mUnmeteredUseOnly);
+        pw.println("disallowedApnTypes="
+                + ApnSetting.getApnTypesStringFromBitmask(getDisallowedApnTypes()));
+        pw.println("mUnmeteredOverride=" + mUnmeteredOverride);
         pw.println("mInstanceNumber=" + mInstanceNumber);
         pw.println("mAc=" + mAc);
         pw.println("mScore=" + mScore);
         if (mNetworkAgent != null) {
             mNetworkAgent.dump(fd, pw, args);
         }
+        pw.println("handover local log:");
+        pw.increaseIndent();
+        mHandoverLocalLog.dump(fd, pw, args);
+        pw.decreaseIndent();
         pw.decreaseIndent();
         pw.println();
         pw.flush();
     }
-}
 
+    protected void handlePdpRejectCauseSuccess() {
+        if (DBG) log("DataConnection: handlePdpRejectCauseSuccess()");
+    }
+
+    protected boolean isPdpRejectCauseFailureHandled(SetupResult result,
+            ConnectionParams cp) {
+        if (DBG) log("DataConnection: isPdpRejectCauseFailureHandled()");
+        return false;
+    }
+
+    protected boolean isPdpRejectCause(int cause) {
+        return (cause == DataFailCause.USER_AUTHENTICATION
+                || cause == DataFailCause.SERVICE_OPTION_NOT_SUBSCRIBED
+                || cause == DataFailCause.MULTI_CONN_TO_SAME_PDN_NOT_ALLOWED);
+    }
+
+    protected boolean isPdpRejectConfigEnabled() {
+        return mPhone.getContext().getResources().getBoolean(
+                com.android.internal.R.bool.config_pdp_retry_for_29_33_55_enabled);
+    }
+
+    protected boolean isDataCallConnectAllowed() {
+        return true;
+    }
+}
diff --git a/src/java/com/android/internal/telephony/dataconnection/DataConnectionReasons.java b/src/java/com/android/internal/telephony/dataconnection/DataConnectionReasons.java
index e7afdff..9fc5b98 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DataConnectionReasons.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DataConnectionReasons.java
@@ -100,8 +100,9 @@
     // Disallowed reasons. There could be multiple reasons if data connection is not allowed.
     public enum DataDisallowedReasonType {
         // Soft failure reasons. Normally the reasons from users or policy settings.
-        DATA_DISABLED(false),                   // Data is disabled by the user or policy.
-        ROAMING_DISABLED(false),                // Data roaming is disabled by the user.
+        DATA_DISABLED(false),               // Data is disabled by the user or policy.
+        ROAMING_DISABLED(false),            // Data roaming is disabled by the user.
+        DEFAULT_DATA_UNSELECTED(false),     // Default data not selected.
 
         // Belows are all hard failure reasons.
         NOT_ATTACHED(true),
@@ -111,11 +112,13 @@
         PS_RESTRICTED(true),
         UNDESIRED_POWER_STATE(true),
         INTERNAL_DATA_DISABLED(true),
-        DEFAULT_DATA_UNSELECTED(true),
         RADIO_DISABLED_BY_CARRIER(true),
         APN_NOT_CONNECTABLE(true),
         ON_IWLAN(true),
-        IN_ECBM(true);
+        IN_ECBM(true),
+        ON_OTHER_TRANSPORT(true);   // When data retry occurs, the given APN type's preferred
+                                    // transport might be already changed. In this case we
+                                    // should disallow data retry.
 
         private boolean mIsHardReason;
 
diff --git a/src/java/com/android/internal/telephony/dataconnection/DataEnabledOverride.java b/src/java/com/android/internal/telephony/dataconnection/DataEnabledOverride.java
index 0cd565f..f639b36 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DataEnabledOverride.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DataEnabledOverride.java
@@ -18,14 +18,18 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
 import android.telephony.data.ApnSetting;
 import android.telephony.data.ApnSetting.ApnType;
 import android.text.TextUtils;
 import android.util.ArrayMap;
+import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.SubscriptionController;
 import com.android.internal.telephony.dataconnection.DataEnabledOverride.OverrideConditions.Condition;
 
@@ -52,7 +56,9 @@
      */
     private static final OverrideRule OVERRIDE_RULE_ALLOW_DATA_DURING_VOICE_CALL =
             new OverrideRule(ApnSetting.TYPE_ALL, OverrideConditions.CONDITION_IN_VOICE_CALL
-                    | OverrideConditions.CONDITION_NON_DEFAULT);
+                    | OverrideConditions.CONDITION_NON_DEFAULT
+                    | OverrideConditions.CONDITION_DEFAULT_DATA_ENABLED
+                    | OverrideConditions.CONDITION_DSDS_ENABLED);
 
     /**
      * The rule for always allowing mms. Without adding any condition to the rule, any condition can
@@ -157,6 +163,12 @@
         /** Enable data only when device has ongoing voice call */
         static final int CONDITION_IN_VOICE_CALL = 1 << 1;
 
+        /** Enable data only when default data is on */
+        static final int CONDITION_DEFAULT_DATA_ENABLED = 1 << 2;
+
+        /** Enable data only when device is in DSDS mode */
+        static final int CONDITION_DSDS_ENABLED = 1 << 3;
+
         /** Enable data unconditionally in string format */
         static final String CONDITION_UNCONDITIONALLY_STRING = "unconditionally";
 
@@ -166,10 +178,18 @@
         /** Enable data only when device has ongoing voice call in string format */
         static final String CONDITION_VOICE_CALL_STRING = "inVoiceCall";
 
+        /** Enable data only when default data is on in string format */
+        static final String CONDITION_DEFAULT_DATA_ENABLED_STRING = "DefaultDataOn";
+
+        /** Enable data only when device is in DSDS mode in string format */
+        static final String CONDITION_DSDS_ENABLED_STRING = "dsdsEnabled";
+
         /** @hide */
         @IntDef(flag = true, prefix = { "OVERRIDE_CONDITION_" }, value = {
                 CONDITION_NON_DEFAULT,
                 CONDITION_IN_VOICE_CALL,
+                CONDITION_DEFAULT_DATA_ENABLED,
+                CONDITION_DSDS_ENABLED
         })
         @Retention(RetentionPolicy.SOURCE)
         public @interface Condition {}
@@ -182,6 +202,10 @@
                     CONDITION_NON_DEFAULT_STRING);
             OVERRIDE_CONDITION_INT_MAP.put(CONDITION_IN_VOICE_CALL,
                     CONDITION_VOICE_CALL_STRING);
+            OVERRIDE_CONDITION_INT_MAP.put(CONDITION_DEFAULT_DATA_ENABLED,
+                    CONDITION_DEFAULT_DATA_ENABLED_STRING);
+            OVERRIDE_CONDITION_INT_MAP.put(CONDITION_DSDS_ENABLED,
+                    CONDITION_DSDS_ENABLED_STRING);
 
             OVERRIDE_CONDITION_STRING_MAP.put(CONDITION_UNCONDITIONALLY_STRING,
                     CONDITION_UNCONDITIONALLY);
@@ -189,6 +213,10 @@
                     CONDITION_NON_DEFAULT);
             OVERRIDE_CONDITION_STRING_MAP.put(CONDITION_VOICE_CALL_STRING,
                     CONDITION_IN_VOICE_CALL);
+            OVERRIDE_CONDITION_STRING_MAP.put(CONDITION_DEFAULT_DATA_ENABLED_STRING,
+                    CONDITION_DEFAULT_DATA_ENABLED);
+            OVERRIDE_CONDITION_STRING_MAP.put(CONDITION_DSDS_ENABLED_STRING,
+                    CONDITION_DSDS_ENABLED);
         }
 
         private final @Condition int mConditions;
@@ -353,9 +381,28 @@
                 conditions |= OverrideConditions.CONDITION_IN_VOICE_CALL;
             }
 
-            if (phone.getSubId() != SubscriptionController.getInstance().getDefaultDataSubId()) {
+            int defaultDataSubId = SubscriptionController.getInstance().getDefaultDataSubId();
+
+            if (phone.getSubId() != defaultDataSubId) {
                 conditions |= OverrideConditions.CONDITION_NON_DEFAULT;
             }
+
+            if (defaultDataSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+                int phoneId = SubscriptionController.getInstance().getPhoneId(defaultDataSubId);
+                try {
+                    Phone defaultDataPhone = PhoneFactory.getPhone(phoneId);
+                    if (defaultDataPhone != null && defaultDataPhone.isUserDataEnabled()) {
+                        conditions |= OverrideConditions.CONDITION_DEFAULT_DATA_ENABLED;
+                    }
+                } catch (IllegalStateException e) {
+                    //ignore the exception and do not add the condition
+                    Log.d("DataEnabledOverride", e.getMessage());
+                }
+            }
+
+            if (TelephonyManager.from(phone.getContext()).isMultiSimEnabled()) {
+                conditions |= OverrideConditions.CONDITION_DSDS_ENABLED;
+            }
         }
 
         return conditions;
diff --git a/src/java/com/android/internal/telephony/dataconnection/DataEnabledSettings.java b/src/java/com/android/internal/telephony/dataconnection/DataEnabledSettings.java
index 02f6842..1b05487 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DataEnabledSettings.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DataEnabledSettings.java
@@ -192,8 +192,8 @@
     }
 
     public synchronized void setInternalDataEnabled(boolean enabled) {
-        localLog("InternalDataEnabled", enabled);
         if (mInternalDataEnabled != enabled) {
+            localLog("InternalDataEnabled", enabled);
             mInternalDataEnabled = enabled;
             updateDataEnabledAndNotify(REASON_INTERNAL_DATA_ENABLED);
         }
@@ -203,17 +203,31 @@
     }
 
     public synchronized void setUserDataEnabled(boolean enabled) {
+        // By default the change should propagate to the group.
+        setUserDataEnabled(enabled, true);
+    }
+
+    /**
+     * @param notifyMultiSimSettingController if setUserDataEnabled is already from propagating
+     *        from MultiSimSettingController, don't notify MultiSimSettingController again.
+     *        For example, if sub1 and sub2 are in the same group and user enables data for sub
+     *        1, sub 2 will also be enabled but with propagateToGroup = false.
+     */
+    public synchronized void setUserDataEnabled(boolean enabled,
+            boolean notifyMultiSimSettingController) {
         // Can't disable data for stand alone opportunistic subscription.
         if (isStandAloneOpportunistic(mPhone.getSubId(), mPhone.getContext()) && !enabled) return;
 
-        localLog("UserDataEnabled", enabled);
         boolean changed = GlobalSettingsHelper.setInt(mPhone.getContext(),
                 Settings.Global.MOBILE_DATA, mPhone.getSubId(), (enabled ? 1 : 0));
         if (changed) {
+            localLog("UserDataEnabled", enabled);
             mPhone.notifyUserMobileDataStateChanged(enabled);
             updateDataEnabledAndNotify(REASON_USER_DATA_ENABLED);
-            MultiSimSettingController.getInstance().notifyUserDataEnabled(mPhone.getSubId(),
-                    enabled);
+            if (notifyMultiSimSettingController) {
+                MultiSimSettingController.getInstance().notifyUserDataEnabled(
+                        mPhone.getSubId(), enabled);
+            }
         }
     }
 
@@ -279,8 +293,8 @@
     }
 
     public synchronized void setPolicyDataEnabled(boolean enabled) {
-        localLog("PolicyDataEnabled", enabled);
         if (mPolicyDataEnabled != enabled) {
+            localLog("PolicyDataEnabled", enabled);
             mPolicyDataEnabled = enabled;
             updateDataEnabledAndNotify(REASON_POLICY_DATA_ENABLED);
         }
@@ -291,8 +305,8 @@
     }
 
     public synchronized void setCarrierDataEnabled(boolean enabled) {
-        localLog("CarrierDataEnabled", enabled);
         if (mCarrierDataEnabled != enabled) {
+            localLog("CarrierDataEnabled", enabled);
             mCarrierDataEnabled = enabled;
             updateDataEnabledAndNotify(REASON_DATA_ENABLED_BY_CARRIER);
         }
@@ -359,13 +373,12 @@
     }
 
     public synchronized void setDataRoamingEnabled(boolean enabled) {
-        localLog("setDataRoamingEnabled", enabled);
-
         // will trigger handleDataOnRoamingChange() through observer
         boolean changed = GlobalSettingsHelper.setBoolean(mPhone.getContext(),
                 Settings.Global.DATA_ROAMING, mPhone.getSubId(), enabled);
 
         if (changed) {
+            localLog("setDataRoamingEnabled", enabled);
             MultiSimSettingController.getInstance().notifyRoamingDataEnabled(mPhone.getSubId(),
                     enabled);
         }
diff --git a/src/java/com/android/internal/telephony/dataconnection/DataServiceManager.java b/src/java/com/android/internal/telephony/dataconnection/DataServiceManager.java
index 716331c..a5199d7 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DataServiceManager.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DataServiceManager.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.telephony.dataconnection;
 
+import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
+
 import android.annotation.NonNull;
 import android.app.AppOpsManager;
 import android.content.BroadcastReceiver;
@@ -39,6 +41,7 @@
 import android.os.UserHandle;
 import android.telephony.AccessNetworkConstants;
 import android.telephony.AccessNetworkConstants.TransportType;
+import android.telephony.AnomalyReporter;
 import android.telephony.CarrierConfigManager;
 import android.telephony.Rlog;
 import android.telephony.data.DataCallResponse;
@@ -55,6 +58,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.UUID;
 import java.util.concurrent.ConcurrentHashMap;
 
 /**
@@ -68,6 +72,10 @@
 
     private static final int EVENT_BIND_DATA_SERVICE = 1;
 
+    private static final int EVENT_WATCHDOG_TIMEOUT = 2;
+
+    private static final long REQUEST_UNRESPONDED_TIMEOUT = 10 * MINUTE_IN_MILLIS; // 10 mins
+
     private final Phone mPhone;
 
     private final String mTag;
@@ -171,18 +179,19 @@
                 service.linkToDeath(mDeathRecipient, 0);
                 mIDataService.createDataServiceProvider(mPhone.getPhoneId());
                 mIDataService.registerForDataCallListChanged(mPhone.getPhoneId(),
-                        new CellularDataServiceCallback());
+                        new CellularDataServiceCallback("dataCallListChanged"));
             } catch (RemoteException e) {
                 mDeathRecipient.binderDied();
                 loge("Remote exception. " + e);
                 return;
             }
-
+            removeMessages(EVENT_WATCHDOG_TIMEOUT);
             mServiceBindingChangedRegistrants.notifyResult(true);
         }
         @Override
         public void onServiceDisconnected(ComponentName name) {
             if (DBG) log("onServiceDisconnected");
+            removeMessages(EVENT_WATCHDOG_TIMEOUT);
             mIDataService.asBinder().unlinkToDeath(mDeathRecipient, 0);
             mIDataService = null;
             mBound = false;
@@ -192,6 +201,17 @@
     }
 
     private final class CellularDataServiceCallback extends IDataServiceCallback.Stub {
+
+        private final String mTag;
+
+        CellularDataServiceCallback(String tag) {
+            mTag = tag;
+        }
+
+        public String getTag() {
+            return mTag;
+        }
+
         @Override
         public void onSetupDataCallComplete(@DataServiceCallback.ResultCode int resultCode,
                                             DataCallResponse response) {
@@ -199,6 +219,7 @@
                 log("onSetupDataCallComplete. resultCode = " + resultCode + ", response = "
                         + response);
             }
+            removeMessages(EVENT_WATCHDOG_TIMEOUT, CellularDataServiceCallback.this);
             Message msg = mMessageMap.remove(asBinder());
             if (msg != null) {
                 msg.getData().putParcelable(DATA_CALL_RESPONSE, response);
@@ -211,6 +232,7 @@
         @Override
         public void onDeactivateDataCallComplete(@DataServiceCallback.ResultCode int resultCode) {
             if (DBG) log("onDeactivateDataCallComplete. resultCode = " + resultCode);
+            removeMessages(EVENT_WATCHDOG_TIMEOUT, CellularDataServiceCallback.this);
             Message msg = mMessageMap.remove(asBinder());
             sendCompleteMessage(msg, resultCode);
         }
@@ -279,11 +301,25 @@
             case EVENT_BIND_DATA_SERVICE:
                 bindDataService();
                 break;
+            case EVENT_WATCHDOG_TIMEOUT:
+                handleRequestUnresponded((CellularDataServiceCallback) msg.obj);
+                break;
             default:
                 loge("Unhandled event " + msg.what);
         }
     }
 
+    private void handleRequestUnresponded(CellularDataServiceCallback callback) {
+        String message = "Request " + callback.getTag() + " unresponded on transport "
+                + AccessNetworkConstants.transportTypeToString(mTransportType) + " in "
+                + REQUEST_UNRESPONDED_TIMEOUT / 1000 + " seconds.";
+        log(message);
+        // Using fixed UUID to avoid duplicate bugreport notification
+        AnomalyReporter.reportAnomaly(
+                UUID.fromString("f5d5cbe6-9bd6-4009-b764-42b1b649b1de"),
+                message);
+    }
+
     private void bindDataService() {
         String packageName = getDataServicePackageName();
         if (TextUtils.isEmpty(packageName)) {
@@ -433,11 +469,13 @@
             return;
         }
 
-        CellularDataServiceCallback callback = new CellularDataServiceCallback();
+        CellularDataServiceCallback callback = new CellularDataServiceCallback("setupDataCall");
         if (onCompleteMessage != null) {
             mMessageMap.put(callback.asBinder(), onCompleteMessage);
         }
         try {
+            sendMessageDelayed(obtainMessage(EVENT_WATCHDOG_TIMEOUT, callback),
+                    REQUEST_UNRESPONDED_TIMEOUT);
             mIDataService.setupDataCall(mPhone.getPhoneId(), accessNetworkType, dataProfile,
                     isRoaming, allowRoaming, reason, linkProperties, callback);
         } catch (RemoteException e) {
@@ -468,11 +506,14 @@
             return;
         }
 
-        CellularDataServiceCallback callback = new CellularDataServiceCallback();
+        CellularDataServiceCallback callback =
+                new CellularDataServiceCallback("deactivateDataCall");
         if (onCompleteMessage != null) {
             mMessageMap.put(callback.asBinder(), onCompleteMessage);
         }
         try {
+            sendMessageDelayed(obtainMessage(EVENT_WATCHDOG_TIMEOUT, callback),
+                    REQUEST_UNRESPONDED_TIMEOUT);
             mIDataService.deactivateDataCall(mPhone.getPhoneId(), cid, reason, callback);
         } catch (RemoteException e) {
             loge("Cannot invoke deactivateDataCall on data service.");
@@ -498,7 +539,8 @@
             return;
         }
 
-        CellularDataServiceCallback callback = new CellularDataServiceCallback();
+        CellularDataServiceCallback callback =
+                new CellularDataServiceCallback("setInitialAttachApn");
         if (onCompleteMessage != null) {
             mMessageMap.put(callback.asBinder(), onCompleteMessage);
         }
@@ -531,7 +573,7 @@
             return;
         }
 
-        CellularDataServiceCallback callback = new CellularDataServiceCallback();
+        CellularDataServiceCallback callback = new CellularDataServiceCallback("setDataProfile");
         if (onCompleteMessage != null) {
             mMessageMap.put(callback.asBinder(), onCompleteMessage);
         }
@@ -558,7 +600,8 @@
             return;
         }
 
-        CellularDataServiceCallback callback = new CellularDataServiceCallback();
+        CellularDataServiceCallback callback =
+                new CellularDataServiceCallback("requestDataCallList");
         if (onCompleteMessage != null) {
             mMessageMap.put(callback.asBinder(), onCompleteMessage);
         }
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/DcNetworkAgent.java b/src/java/com/android/internal/telephony/dataconnection/DcNetworkAgent.java
index b36a490..d9edd28 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DcNetworkAgent.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DcNetworkAgent.java
@@ -132,6 +132,13 @@
         mDataConnection = null;
     }
 
+    /**
+     * @return The data connection that owns this agent
+     */
+    public synchronized DataConnection getDataConnection() {
+        return mDataConnection;
+    }
+
     @Override
     protected synchronized void unwanted() {
         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 8424ed3..6a30c47 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
@@ -41,10 +41,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;
@@ -69,6 +71,7 @@
 import android.telephony.CarrierConfigManager;
 import android.telephony.CellLocation;
 import android.telephony.DataFailCause;
+import android.telephony.DataFailCause.FailCause;
 import android.telephony.NetworkRegistrationInfo;
 import android.telephony.PcoData;
 import android.telephony.Rlog;
@@ -76,7 +79,9 @@
 import android.telephony.ServiceState.RilRadioTechnology;
 import android.telephony.SubscriptionManager;
 import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
+import android.telephony.SubscriptionPlan;
 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;
@@ -115,6 +120,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.List;
@@ -130,7 +136,7 @@
  * {@hide}
  */
 public class DcTracker extends Handler {
-    private static final boolean DBG = true;
+    protected static final boolean DBG = true;
     private static final boolean VDBG = false; // STOPSHIP if true
     private static final boolean VDBG_STALL = false; // STOPSHIP if true
     private static final boolean RADIO_TESTS = false;
@@ -162,15 +168,15 @@
     public @interface RequestNetworkType {}
 
     /**
-     * Normal request for {@link #requestNetwork(NetworkRequest, int, LocalLog)}. For request
+     * Normal request for {@link #requestNetwork(NetworkRequest, int, Message)}. For request
      * network, this adds the request to the {@link ApnContext}. If there were no network request
      * attached to the {@link ApnContext} earlier, this request setups a data connection.
      */
     public static final int REQUEST_TYPE_NORMAL = 1;
 
     /**
-     * Handover request for {@link #requestNetwork(NetworkRequest, int, LocalLog)} or
-     * {@link #releaseNetwork(NetworkRequest, int, LocalLog)}. For request network, this
+     * Handover request for {@link #requestNetwork(NetworkRequest, int, Message)} or
+     * {@link #releaseNetwork(NetworkRequest, int)}. For request network, this
      * initiates the handover data setup process. The existing data connection will be seamlessly
      * handover to the new network. For release network, this performs a data connection softly
      * clean up at the underlying layer (versus normal data release).
@@ -193,7 +199,7 @@
     public static final int RELEASE_TYPE_NORMAL = 1;
 
     /**
-     * Detach request for {@link #releaseNetwork(NetworkRequest, int, LocalLog)} only. This
+     * Detach request for {@link #releaseNetwork(NetworkRequest, int)} only. This
      * forces the APN context detach from the data connection. If this {@link ApnContext} is the
      * last one attached to the data connection, the data connection will be torn down, otherwise
      * the data connection remains active.
@@ -201,7 +207,7 @@
     public static final int RELEASE_TYPE_DETACH = 2;
 
     /**
-     * Handover request for {@link #releaseNetwork(NetworkRequest, int, LocalLog)}. For release
+     * Handover request for {@link #releaseNetwork(NetworkRequest, int)}. For release
      * network, this performs a data connection softly clean up at the underlying layer (versus
      * normal data release).
      */
@@ -212,6 +218,12 @@
     static final String DATA_COMPLETE_MSG_EXTRA_TRANSPORT_TYPE = "extra_transport_type";
     static final String DATA_COMPLETE_MSG_EXTRA_REQUEST_TYPE = "extra_request_type";
     static final String DATA_COMPLETE_MSG_EXTRA_SUCCESS = "extra_success";
+    /**
+     * The flag indicates whether after handover failure, the data connection should remain on the
+     * original transport.
+     */
+    static final String DATA_COMPLETE_MSG_EXTRA_HANDOVER_FAILURE_FALLBACK =
+            "extra_handover_failure_fallback";
 
     private final String mLogTag;
 
@@ -244,7 +256,7 @@
     private static final int DATA_STALL_ALARM_AGGRESSIVE_DELAY_IN_MS_DEFAULT = 1000 * 60;
 
     private static final boolean DATA_STALL_SUSPECTED = true;
-    private static final boolean DATA_STALL_NOT_SUSPECTED = false;
+    protected static final boolean DATA_STALL_NOT_SUSPECTED = false;
 
     private static final String INTENT_RECONNECT_ALARM =
             "com.android.internal.telephony.data-reconnect";
@@ -261,8 +273,8 @@
     private static final String INTENT_DATA_STALL_ALARM_EXTRA_TRANSPORT_TYPE =
             "data_stall_alarm_extra_transport_type";
 
-    private DcTesterFailBringUpAll mDcTesterFailBringUpAll;
-    private DcController mDcc;
+    protected DcTesterFailBringUpAll mDcTesterFailBringUpAll;
+    protected DcController mDcc;
 
     /** kept in sync with mApnContexts
      * Higher numbers are higher priority and sorted so highest priority is first */
@@ -324,6 +336,25 @@
     private final LocalLog mDataRoamingLeakageLog = new LocalLog(50);
     private final LocalLog mApnSettingsInitializationLog = new LocalLog(50);
 
+    /* Default for 5G connection reevaluation alarm durations */
+    private int mHysteresisTimeSec = 0;
+    private long mWatchdogTimeMs = 1000 * 60 * 60;
+
+    /* Default for whether 5G frequencies are considered unmetered */
+    private boolean mAllUnmetered = false;
+    private boolean mMmwaveUnmetered = false;
+    private boolean mSub6Unmetered = false;
+
+    /* Used to check whether 5G timers are currently active and waiting to go off */
+    private boolean mHysteresis = false;
+    private boolean mWatchdog = false;
+
+    /* List of SubscriptionPlans, updated on SubscriptionManager.setSubscriptionPlans */
+    private List<SubscriptionPlan> mSubscriptionPlans = null;
+
+    /* Used to check whether phone was recently connected to 5G. */
+    private boolean m5GWasConnected = false;
+
     private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver () {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -336,12 +367,16 @@
                 stopNetStatPoll();
                 startNetStatPoll();
                 restartDataStallAlarm();
+                reevaluateUnmeteredConnections();
             } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
                 if (DBG) log("screen off");
                 mIsScreenOn = false;
                 stopNetStatPoll();
                 startNetStatPoll();
                 restartDataStallAlarm();
+                stopHysteresisAlarm();
+                stopWatchdogAlarm();
+                setDataConnectionUnmetered(false);
             } else if (action.startsWith(INTENT_RECONNECT_ALARM)) {
                 onActionIntentReconnectAlarm(intent);
             } else if (action.equals(INTENT_DATA_STALL_ALARM)) {
@@ -354,6 +389,35 @@
                 if (mIccRecords.get() != null && mIccRecords.get().getRecordsLoaded()) {
                     setDefaultDataRoamingEnabled();
                 }
+                String[] bandwidths = CarrierConfigManager.getDefaultConfig().getStringArray(
+                        CarrierConfigManager.KEY_BANDWIDTH_STRING_ARRAY);
+                boolean useLte = false;
+                CarrierConfigManager configManager = (CarrierConfigManager) mPhone.getContext()
+                        .getSystemService(Context.CARRIER_CONFIG_SERVICE);
+                if (configManager != null) {
+                    PersistableBundle b = configManager.getConfigForSubId(mPhone.getSubId());
+                    if (b != null) {
+                        if (b.getStringArray(CarrierConfigManager.KEY_BANDWIDTH_STRING_ARRAY)
+                                != null) {
+                            bandwidths = b.getStringArray(
+                                    CarrierConfigManager.KEY_BANDWIDTH_STRING_ARRAY);
+                        }
+                        useLte = b.getBoolean(CarrierConfigManager
+                                .KEY_BANDWIDTH_NR_NSA_USE_LTE_VALUE_FOR_UPSTREAM_BOOL);
+                        mHysteresisTimeSec = b.getInt(
+                                CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_SEC_INT);
+                        mWatchdogTimeMs = b.getLong(
+                                CarrierConfigManager.KEY_5G_WATCHDOG_TIME_MS_LONG);
+                        mAllUnmetered = b.getBoolean(
+                                CarrierConfigManager.KEY_UNMETERED_NR_NSA_BOOL);
+                        mMmwaveUnmetered = b.getBoolean(
+                                CarrierConfigManager.KEY_UNMETERED_NR_NSA_MMWAVE_BOOL);
+                        mSub6Unmetered = b.getBoolean(
+                                CarrierConfigManager.KEY_UNMETERED_NR_NSA_SUB6_BOOL);
+                    }
+                }
+                sendMessage(obtainMessage(DctConstants.EVENT_UPDATE_CARRIER_CONFIGS,
+                        useLte ? 1 : 0, 0 /* unused */, bandwidths));
             } else {
                 if (DBG) log("onReceive: Unknown action=" + action);
             }
@@ -397,16 +461,37 @@
             if (DBG) log("SubscriptionListener.onSubscriptionInfoChanged");
             // Set the network type, in case the radio does not restore it.
             int subId = mPhone.getSubId();
-            if (SubscriptionManager.isValidSubscriptionId(subId)) {
+            if (mSubscriptionManager.isActiveSubId(subId)) {
                 registerSettingsObserver();
             }
-            if (SubscriptionManager.isValidSubscriptionId(subId) &&
+            if (mSubscriptionManager.isActiveSubId(subId) &&
                     mPreviousSubId.getAndSet(subId) != subId) {
                 onRecordsLoadedOrSubIdChanged();
             }
         }
     };
 
+    private NetworkPolicyManager mNetworkPolicyManager;
+    private final INetworkPolicyListener mNetworkPolicyListener =
+            new NetworkPolicyManager.Listener() {
+        @Override
+        public void onSubscriptionOverride(int subId, int overrideMask, int overrideValue) {
+            if (mPhone == null || mPhone.getSubId() != subId) return;
+
+            for (DataConnection dataConnection : mDataConnections.values()) {
+                dataConnection.onSubscriptionOverride(overrideMask, overrideValue);
+            }
+        }
+
+        @Override
+        public void onSubscriptionPlansChanged(int subId, SubscriptionPlan[] plans) {
+            if (mPhone == null || mPhone.getSubId() != subId) return;
+
+            mSubscriptionPlans = plans == null ? null : Arrays.asList(plans);
+            reevaluateUnmeteredConnections();
+        }
+    };
+
     private final SettingsObserver mSettingsObserver;
 
     private void registerSettingsObserver() {
@@ -491,7 +576,7 @@
 
         // Stop reconnect if not current subId is not correct.
         // FIXME STOPSHIP - phoneSubId is coming up as -1 way after boot and failing this?
-        if (!SubscriptionManager.isValidSubscriptionId(currSubId) || (currSubId != phoneSubId)) {
+        if (!mSubscriptionManager.isActiveSubId(currSubId) || (currSubId != phoneSubId)) {
             return;
         }
 
@@ -552,9 +637,9 @@
     private RegistrantList mAllDataDisconnectedRegistrants = new RegistrantList();
 
     // member variables
-    private final Phone mPhone;
+    protected final Phone mPhone;
     private final UiccController mUiccController;
-    private final AtomicReference<IccRecords> mIccRecords = new AtomicReference<IccRecords>();
+    protected final AtomicReference<IccRecords> mIccRecords = new AtomicReference<IccRecords>();
     private DctConstants.Activity mActivity = DctConstants.Activity.NONE;
     private DctConstants.State mState = DctConstants.State.IDLE;
     private final Handler mDataConnectionTracker;
@@ -581,13 +666,13 @@
     private volatile boolean mFailFast = false;
 
     // True when in voice call
-    private boolean mInVoiceCall = false;
+    protected boolean mInVoiceCall = false;
 
     /** Intent sent when the reconnect alarm fires. */
     private PendingIntent mReconnectIntent = null;
 
     // When false we will not auto attach and manually attaching is required.
-    private boolean mAutoAttachOnCreationConfig = false;
+    protected boolean mAutoAttachOnCreationConfig = false;
     private AtomicBoolean mAutoAttachEnabled = new AtomicBoolean(false);
 
     // State of screen
@@ -596,10 +681,10 @@
     private boolean mIsScreenOn = true;
 
     /** Allows the generation of unique Id's for DataConnection objects */
-    private AtomicInteger mUniqueIdGenerator = new AtomicInteger(0);
+    protected AtomicInteger mUniqueIdGenerator = new AtomicInteger(0);
 
     /** The data connections. */
-    private HashMap<Integer, DataConnection> mDataConnections =
+    protected HashMap<Integer, DataConnection> mDataConnections =
             new HashMap<Integer, DataConnection>();
 
     /** Convert an ApnType string to Id (TODO: Use "enumeration" instead of String for ApnType) */
@@ -615,6 +700,10 @@
 
     private ArrayList<DataProfile> mLastDataProfileList = new ArrayList<>();
 
+    /** RAT name ===> (downstream, upstream) bandwidth values from carrier config. */
+    private final ConcurrentHashMap<String, Pair<Integer, Integer>> mBandwidths =
+            new ConcurrentHashMap<>();
+
     /**
      * Handles changes to the APN db.
      */
@@ -658,7 +747,7 @@
     private BroadcastReceiver mProvisionBroadcastReceiver;
     private ProgressDialog mProvisioningSpinner;
 
-    private final DataServiceManager mDataServiceManager;
+    protected final DataServiceManager mDataServiceManager;
 
     private final int mTransportType;
 
@@ -720,6 +809,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());
@@ -781,6 +873,8 @@
                 DctConstants.EVENT_PS_RESTRICT_DISABLED, null);
         mPhone.getServiceStateTracker().registerForDataRegStateOrRatChanged(mTransportType, this,
                 DctConstants.EVENT_DATA_RAT_CHANGED, null);
+        // listens for PhysicalChannelConfig changes
+        mPhone.registerForServiceStateChanged(this, DctConstants.EVENT_SERVICE_STATE_CHANGED, null);
     }
 
     public void unregisterServiceStateTrackerEvents() {
@@ -792,6 +886,7 @@
         mPhone.getServiceStateTracker().unregisterForPsRestrictedDisabled(this);
         mPhone.getServiceStateTracker().unregisterForDataRegStateOrRatChanged(mTransportType,
                 this);
+        mPhone.unregisterForServiceStateChanged(this);
     }
 
     private void registerForAllEvents() {
@@ -837,6 +932,7 @@
 
         mSubscriptionManager
                 .removeOnSubscriptionsChangedListener(mOnSubscriptionsChangedListener);
+        mNetworkPolicyManager.unregisterListener(mNetworkPolicyListener);
         mDcc.dispose();
         mDcTesterFailBringUpAll.dispose();
 
@@ -1217,6 +1313,10 @@
         setupDataOnAllConnectableApns(Phone.REASON_DATA_ATTACHED, RetryFailures.ALWAYS);
     }
 
+    protected boolean getAttachedStatus() {
+        return mAttached.get();
+    }
+
     /**
      * Check if it is allowed to make a data connection (without checking APN context specific
      * conditions).
@@ -1252,7 +1352,7 @@
 
         // Step 1: Get all environment conditions.
         final boolean internalDataEnabled = mDataEnabledSettings.isInternalDataEnabled();
-        boolean attachedState = mAttached.get();
+        boolean attachedState = getAttachedStatus();
         boolean desiredPowerState = mPhone.getServiceStateTracker().getDesiredPowerState();
         boolean radioStateFromCarrier = mPhone.getServiceStateTracker().getPowerStateFromCarrier();
         // TODO: Remove this hack added by ag/641832.
@@ -1344,6 +1444,16 @@
             reasons.add(DataDisallowedReasonType.RADIO_DISABLED_BY_CARRIER);
         }
 
+        if (apnContext != null) {
+            // If the transport has been already switched to the other transport, we should not
+            // allow the data setup. The only exception is the handover case, where we setup
+            // handover data connection before switching the transport.
+            if (mTransportType != mPhone.getTransportManager().getCurrentTransport(
+                    apnContext.getApnTypeBitmask()) && requestType != REQUEST_TYPE_HANDOVER) {
+                reasons.add(DataDisallowedReasonType.ON_OTHER_TRANSPORT);
+            }
+        }
+
         boolean isDataEnabled = apnContext == null ? mDataEnabledSettings.isDataEnabled()
                 : mDataEnabledSettings.isDataEnabled(apnContext.getApnTypeBitmask());
 
@@ -1364,11 +1474,8 @@
         // At this point, if data is not allowed, it must be because of the soft reasons. We
         // should start to check some special conditions that data will be allowed.
         if (!reasons.allowed()) {
-            // If the device is on IWLAN, then all data should be unmetered. Check if the transport
-            // is WLAN (for AP-assisted mode devices), or RAT equals IWLAN (for legacy mode devices)
-            if (mTransportType == AccessNetworkConstants.TRANSPORT_TYPE_WLAN
-                    || (mPhone.getTransportManager().isInLegacyMode()
-                    && dataRat == ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN)) {
+            // Check if the transport is WLAN ie wifi (for AP-assisted mode devices)
+            if (mTransportType == AccessNetworkConstants.TRANSPORT_TYPE_WLAN) {
                 reasons.add(DataAllowedReasonType.UNMETERED_APN);
             // Or if the data is on cellular, and the APN type is determined unmetered by the
             // configuration.
@@ -1407,6 +1514,10 @@
         ONLY_ON_CHANGE
     };
 
+    protected void setupDataOnAllConnectableApns(String reason) {
+        setupDataOnAllConnectableApns(reason, RetryFailures.ALWAYS);
+    }
+
     private void setupDataOnAllConnectableApns(String reason, RetryFailures retryFailures) {
         if (VDBG) log("setupDataOnAllConnectableApns: " + reason);
 
@@ -1838,7 +1949,7 @@
         }
     }
 
-    boolean isPermanentFailure(@DataFailCause.FailCause int dcFailCause) {
+    protected boolean isPermanentFailure(@FailCause int dcFailCause) {
         return (DataFailCause.isPermanentFailure(mPhone.getContext(), dcFailCause,
                 mPhone.getSubId())
                 && (mAttached.get() == false || dcFailCause != DataFailCause.SIGNAL_LOST));
@@ -1973,7 +2084,7 @@
         return true;
     }
 
-    private void setInitialAttachApn() {
+    protected  void setInitialAttachApn() {
         ApnSetting iaApnSetting = null;
         ApnSetting defaultApnSetting = null;
         ApnSetting firstNonEmergencyApnSetting = null;
@@ -2005,6 +2116,11 @@
             }
         }
 
+        if ((iaApnSetting == null) && (defaultApnSetting == null) &&
+                !allowInitialAttachForOperator()) {
+            log("Abort Initial attach");
+            return;
+        }
         // The priority of apn candidates from highest to lowest is:
         //   1) APN_TYPE_IA (Initial Attach)
         //   2) mPreferredApn, i.e. the current preferred apn
@@ -2037,6 +2153,10 @@
         }
     }
 
+    protected boolean allowInitialAttachForOperator() {
+        return true;
+    }
+
     /**
      * Handles changes to the APN database.
      */
@@ -2193,7 +2313,7 @@
                 SystemClock.elapsedRealtime() + delay, alarmIntent);
     }
 
-    private void notifyNoData(@DataFailCause.FailCause int lastFailCauseCode,
+    private void notifyNoData(@FailCause int lastFailCauseCode,
                               ApnContext apnContext) {
         if (DBG) log( "notifyNoData: type=" + apnContext.getApnType());
         if (isPermanentFailure(lastFailCauseCode)
@@ -2202,7 +2322,7 @@
         }
     }
 
-    private void onRecordsLoadedOrSubIdChanged() {
+    protected void onRecordsLoadedOrSubIdChanged() {
         if (DBG) log("onRecordsLoadedOrSubIdChanged: createAllApnList");
         if (mTransportType == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) {
             // Auto attach is for cellular only.
@@ -2319,13 +2439,19 @@
 
     private void sendRequestNetworkCompleteMsg(Message message, boolean success,
                                                @TransportType int transport,
-                                               @RequestNetworkType int requestType) {
+                                               @RequestNetworkType int requestType,
+                                               @FailCause int cause) {
         if (message == null) return;
 
         Bundle b = message.getData();
         b.putBoolean(DATA_COMPLETE_MSG_EXTRA_SUCCESS, success);
         b.putInt(DATA_COMPLETE_MSG_EXTRA_REQUEST_TYPE, requestType);
         b.putInt(DATA_COMPLETE_MSG_EXTRA_TRANSPORT_TYPE, transport);
+        // TODO: For now this is the only fail cause that we know modem keeps data connection on
+        // original transport. Might add more complicated logic or mapping in the future.
+        b.putBoolean(DATA_COMPLETE_MSG_EXTRA_HANDOVER_FAILURE_FALLBACK,
+                (requestType == REQUEST_TYPE_HANDOVER
+                        && cause == DataFailCause.HANDOFF_PREFERENCE_CHANGED));
         message.sendToTarget();
     }
 
@@ -2340,7 +2466,8 @@
         ApnContext apnContext = mApnContextsByType.get(apnType);
         if (apnContext == null) {
             loge("onEnableApn(" + apnType + "): NO ApnContext");
-            sendRequestNetworkCompleteMsg(onCompleteMsg, false, mTransportType, requestType);
+            sendRequestNetworkCompleteMsg(onCompleteMsg, false, mTransportType, requestType,
+                    DataFailCause.NONE);
             return;
         }
 
@@ -2355,7 +2482,8 @@
             str = "onEnableApn: dependency is not met.";
             if (DBG) log(str);
             apnContext.requestLog(str);
-            sendRequestNetworkCompleteMsg(onCompleteMsg, false, mTransportType, requestType);
+            sendRequestNetworkCompleteMsg(onCompleteMsg, false, mTransportType, requestType,
+                    DataFailCause.NONE);
             return;
         }
 
@@ -2369,16 +2497,15 @@
                     return;
                 case CONNECTED:
                     if (DBG) log("onEnableApn: 'CONNECTED' so return");
-                    apnContext.requestLog("onEnableApn state=CONNECTED, so return");
-
+                    // Don't add to local log since this is so common
                     sendRequestNetworkCompleteMsg(onCompleteMsg, true, mTransportType,
-                            requestType);
+                            requestType, DataFailCause.NONE);
                     return;
                 case DISCONNECTING:
                     if (DBG) log("onEnableApn: 'DISCONNECTING' so return");
                     apnContext.requestLog("onEnableApn state=DISCONNECTING, so return");
                     sendRequestNetworkCompleteMsg(onCompleteMsg, false, mTransportType,
-                            requestType);
+                            requestType, DataFailCause.NONE);
                     return;
                 case IDLE:
                     // fall through: this is unexpected but if it happens cleanup and try setup
@@ -2405,7 +2532,7 @@
             addRequestNetworkCompleteMsg(onCompleteMsg, apnType);
         } else {
             sendRequestNetworkCompleteMsg(onCompleteMsg, false, mTransportType,
-                    requestType);
+                    requestType, DataFailCause.NONE);
         }
     }
 
@@ -2695,7 +2822,7 @@
         List<Message> messageList = mRequestNetworkCompletionMsgs.get(apnType);
         if (messageList != null) {
             for (Message msg : messageList) {
-                sendRequestNetworkCompleteMsg(msg, success, mTransportType, requestType);
+                sendRequestNetworkCompleteMsg(msg, success, mTransportType, requestType, cause);
             }
             messageList.clear();
         }
@@ -3025,7 +3152,7 @@
         }
     }
 
-    private void onVoiceCallEnded() {
+    protected void onVoiceCallEnded() {
         if (DBG) log("onVoiceCallEnded");
         mInVoiceCall = false;
         if (isConnected()) {
@@ -3042,7 +3169,7 @@
         setupDataOnAllConnectableApns(Phone.REASON_VOICE_CALL_ENDED, RetryFailures.ALWAYS);
     }
 
-    private boolean isConnected() {
+    protected boolean isConnected() {
         for (ApnContext apnContext : mApnContexts.values()) {
             if (apnContext.getState() == DctConstants.State.CONNECTED) {
                 // At least one context is connected, return true
@@ -3064,7 +3191,7 @@
         return true;
     }
 
-    private void setDataProfilesAsNeeded() {
+    protected void setDataProfilesAsNeeded() {
         if (DBG) log("setDataProfilesAsNeeded");
 
         ArrayList<DataProfile> dataProfileList = new ArrayList<>();
@@ -3091,7 +3218,7 @@
      * Based on the sim operator numeric, create a list for all possible
      * Data Connections and setup the preferredApn.
      */
-    private void createAllApnList() {
+    protected void createAllApnList() {
         mAllApnSettings.clear();
         IccRecords r = mIccRecords.get();
         String operator = (r != null) ? r.getOperatorNumeric() : "";
@@ -3194,7 +3321,7 @@
             dest.getSkip464Xlat());
     }
 
-    private DataConnection createDataConnection() {
+    protected DataConnection createDataConnection() {
         if (DBG) log("createDataConnection E");
 
         int id = mUniqueIdGenerator.getAndIncrement();
@@ -3237,8 +3364,7 @@
             }
         }
 
-        IccRecords r = mIccRecords.get();
-        String operator = (r != null) ? r.getOperatorNumeric() : "";
+        String operator = mPhone.getOperatorNumeric();
 
         // This is a workaround for a bug (7305641) where we don't failover to other
         // suitable APNs if our preferred APN fails.  On prepaid ATT sims we need to
@@ -3261,7 +3387,7 @@
                     + " canSetPreferApn=" + mCanSetPreferApn
                     + " mPreferredApn=" + mPreferredApn
                     + " operator=" + operator + " radioTech=" + radioTech
-                    + " IccRecords r=" + r);
+                    + " IccRecords r=" + mIccRecords);
         }
 
         if (usePreferred && mCanSetPreferApn && mPreferredApn != null &&
@@ -3438,7 +3564,7 @@
                 // If onRecordsLoadedOrSubIdChanged() is not called here, it should be called on
                 // onSubscriptionsChanged() when a valid subId is available.
                 int subId = mPhone.getSubId();
-                if (SubscriptionManager.isValidSubscriptionId(subId)) {
+                if (mSubscriptionManager.isActiveSubId(subId)) {
                     onRecordsLoadedOrSubIdChanged();
                 } else {
                     log("Ignoring EVENT_RECORDS_LOADED as subId is not valid: " + subId);
@@ -3783,6 +3909,20 @@
             case DctConstants.EVENT_DATA_ENABLED_OVERRIDE_RULES_CHANGED:
                 onDataEnabledOverrideRulesChanged();
                 break;
+            case DctConstants.EVENT_SERVICE_STATE_CHANGED:
+                reevaluateUnmeteredConnections();
+                break;
+            case DctConstants.EVENT_5G_TIMER_HYSTERESIS:
+                reevaluateUnmeteredConnections();
+                mHysteresis = false;
+                break;
+            case DctConstants.EVENT_5G_TIMER_WATCHDOG:
+                mWatchdog = false;
+                reevaluateUnmeteredConnections();
+                break;
+            case DctConstants.EVENT_UPDATE_CARRIER_CONFIGS:
+                updateLinkBandwidths((String[]) msg.obj, msg.arg1 == 1);
+                break;
             default:
                 Rlog.e("DcTracker", "Unhandled event=" + msg);
                 break;
@@ -3830,7 +3970,7 @@
             return;
         }
 
-        IccRecords newIccRecords = getUiccRecords(UiccController.APP_FAM_3GPP);
+        IccRecords newIccRecords = mPhone.getIccRecords();
 
         IccRecords r = mIccRecords.get();
         if (r != newIccRecords) {
@@ -3840,7 +3980,7 @@
                 mIccRecords.set(null);
             }
             if (newIccRecords != null) {
-                if (SubscriptionManager.isValidSubscriptionId(mPhone.getSubId())) {
+                if (mSubscriptionManager.isActiveSubId(mPhone.getSubId())) {
                     log("New records found.");
                     mIccRecords.set(newIccRecords);
                     newIccRecords.registerForRecordsLoaded(
@@ -3853,6 +3993,57 @@
     }
 
     /**
+     * Update link bandwidth estimate default values from carrier config.
+     * @param bandwidths String array of "RAT:upstream,downstream" for each RAT
+     * @param useLte For NR NSA, whether to use LTE value for upstream or not
+     */
+    private void updateLinkBandwidths(String[] bandwidths, boolean useLte) {
+        synchronized (mBandwidths) {
+            mBandwidths.clear();
+            for (String config : bandwidths) {
+                int downstream = 14;
+                int upstream = 14;
+                String[] kv = config.split(":");
+                if (kv.length == 2) {
+                    String[] split = kv[1].split(",");
+                    if (split.length == 2) {
+                        try {
+                            downstream = Integer.parseInt(split[0]);
+                            upstream = Integer.parseInt(split[1]);
+                        } catch (NumberFormatException ignored) {
+                        }
+                    }
+                    mBandwidths.put(kv[0], new Pair<>(downstream, upstream));
+                }
+            }
+            if (useLte) {
+                Pair<Integer, Integer> ltePair = mBandwidths.get(DctConstants.RAT_NAME_LTE);
+                if (ltePair != null) {
+                    if (mBandwidths.containsKey(DctConstants.RAT_NAME_NR_NSA)) {
+                        mBandwidths.put(DctConstants.RAT_NAME_NR_NSA, new Pair<>(
+                                mBandwidths.get(DctConstants.RAT_NAME_NR_NSA).first,
+                                ltePair.second));
+                    }
+                    if (mBandwidths.containsKey(DctConstants.RAT_NAME_NR_NSA_MMWAVE)) {
+                        mBandwidths.put(DctConstants.RAT_NAME_NR_NSA_MMWAVE, new Pair<>(
+                                mBandwidths.get(DctConstants.RAT_NAME_NR_NSA_MMWAVE).first,
+                                ltePair.second));
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Return the link upstream/downstream values from CarrierConfig for the given RAT name.
+     * @param ratName RAT name from ServiceState#rilRadioTechnologyToString.
+     * @return pair of downstream/upstream values (kbps), or null if the config is not defined.
+     */
+    public Pair<Integer, Integer> getLinkBandwidths(String ratName) {
+        return mBandwidths.get(ratName);
+    }
+
+    /**
      * Update DcTracker.
      *
      * TODO: This should be cleaned up. DcTracker should listen to those events.
@@ -3867,23 +4058,27 @@
         mPhone.updateCurrentCarrierInProvider();
     }
 
-    /**
-     * For non DDS phone, mAutoAttachEnabled should be true because it may be detached
-     * automatically from network only because it's idle for too long. In this case, we should
-     * try setting up data call even if it's not attached for 2G or 3G networks. And doing so will
-     * trigger PS attach if possible.
-     */
     @VisibleForTesting
     public boolean shouldAutoAttach() {
         if (mAutoAttachEnabled.get()) return true;
 
         PhoneSwitcher phoneSwitcher = PhoneSwitcher.getInstance();
         ServiceState serviceState = mPhone.getServiceState();
-        return phoneSwitcher != null && serviceState != null
-                && mPhone.getPhoneId() != phoneSwitcher.getPreferredDataPhoneId()
-                && serviceState.getVoiceRegState() == ServiceState.STATE_IN_SERVICE
-                && serviceState.getVoiceNetworkType() != NETWORK_TYPE_LTE
-                && serviceState.getVoiceNetworkType() != NETWORK_TYPE_NR;
+
+        if (phoneSwitcher == null || serviceState == null) return false;
+
+        // If voice is also not in service, don't auto attach.
+        if (serviceState.getVoiceRegState() != ServiceState.STATE_IN_SERVICE) return false;
+
+        // If voice is on LTE or NR, don't auto attach as for LTE / NR data would be attached.
+        if (serviceState.getVoiceNetworkType() == NETWORK_TYPE_LTE
+                || serviceState.getVoiceNetworkType() == NETWORK_TYPE_NR) return false;
+
+        // If phone is non default phone, modem may have detached from data for optimization.
+        // If phone is in voice call, for DSDS case DDS switch may be limited so we do try our
+        // best to setup data connection and allow auto-attach.
+        return (mPhone.getPhoneId() != phoneSwitcher.getPreferredDataPhoneId()
+                || mPhone.getState() != PhoneConstants.State.IDLE);
     }
 
     private void notifyAllDataDisconnected() {
@@ -3937,7 +4132,102 @@
         }
     }
 
-    private void log(String s) {
+    private void reevaluateUnmeteredConnections() {
+        if (isNetworkTypeUnmetered(NETWORK_TYPE_NR) || isFrequencyRangeUnmetered()) {
+            if (DBG) log("NR NSA is unmetered");
+            if (mPhone.getServiceState().getNrState()
+                    == NetworkRegistrationInfo.NR_STATE_CONNECTED) {
+                if (!m5GWasConnected) { // 4G -> 5G
+                    stopHysteresisAlarm();
+                    setDataConnectionUnmetered(true);
+                }
+                if (!mWatchdog) {
+                    startWatchdogAlarm();
+                }
+                m5GWasConnected = true;
+            } else {
+                if (m5GWasConnected) { // 5G -> 4G
+                    if (!mHysteresis && !startHysteresisAlarm()) {
+                        // hysteresis is not active but carrier does not support hysteresis
+                        stopWatchdogAlarm();
+                        setDataConnectionUnmetered(isNetworkTypeUnmetered(
+                                mTelephonyManager.getNetworkType(mPhone.getSubId())));
+                    }
+                    m5GWasConnected = false;
+                } else { // 4G -> 4G
+                    if (!hasMessages(DctConstants.EVENT_5G_TIMER_HYSTERESIS)) {
+                        stopWatchdogAlarm();
+                        setDataConnectionUnmetered(isNetworkTypeUnmetered(
+                                mTelephonyManager.getNetworkType(mPhone.getSubId())));
+                    }
+                    // do nothing if waiting for hysteresis alarm to go off
+                }
+            }
+        } else {
+            stopWatchdogAlarm();
+            stopHysteresisAlarm();
+            setDataConnectionUnmetered(isNetworkTypeUnmetered(
+                    mTelephonyManager.getNetworkType(mPhone.getSubId())));
+            m5GWasConnected = false;
+        }
+    }
+
+    private void setDataConnectionUnmetered(boolean isUnmetered) {
+        for (DataConnection dataConnection : mDataConnections.values()) {
+            dataConnection.onMeterednessChanged(isUnmetered);
+        }
+    }
+
+    private boolean isNetworkTypeUnmetered(@NetworkType int networkType) {
+        if (mSubscriptionPlans == null || mSubscriptionPlans.size() == 0) {
+            // safe return false if unable to get subscription plans or plans don't exist
+            return false;
+        }
+
+        long bitmask = ServiceState.getBitmaskForTech(networkType);
+        boolean isGeneralUnmetered = true;
+        for (SubscriptionPlan plan : mSubscriptionPlans) {
+            // check plan applies to given network type
+            if ((plan.getNetworkTypesBitMask() & bitmask) == bitmask) {
+                // check plan is general or specific
+                if (plan.getNetworkTypes() == null) {
+                    if (!isPlanUnmetered(plan)) {
+                        // metered takes precedence over unmetered for safety
+                        isGeneralUnmetered = false;
+                    }
+                } else {
+                    // ensure network type unknown returns general value
+                    if (networkType != TelephonyManager.NETWORK_TYPE_UNKNOWN) {
+                        // there is only 1 specific plan per network type, so return value if found
+                        return isPlanUnmetered(plan);
+                    }
+                }
+            }
+        }
+        return isGeneralUnmetered;
+    }
+
+    private boolean isPlanUnmetered(SubscriptionPlan plan) {
+        return plan.getDataLimitBytes() == SubscriptionPlan.BYTES_UNLIMITED
+                && (plan.getDataLimitBehavior() == SubscriptionPlan.LIMIT_BEHAVIOR_UNKNOWN
+                || plan.getDataLimitBehavior() == SubscriptionPlan.LIMIT_BEHAVIOR_THROTTLED);
+    }
+
+    private boolean isFrequencyRangeUnmetered() {
+        boolean nrConnected = mPhone.getServiceState().getNrState()
+                == NetworkRegistrationInfo.NR_STATE_CONNECTED;
+        if (mMmwaveUnmetered || mSub6Unmetered) {
+            int frequencyRange = mPhone.getServiceState().getNrFrequencyRange();
+            boolean mmwave = frequencyRange == ServiceState.FREQUENCY_RANGE_MMWAVE;
+            // frequency range LOW, MID, or HIGH
+            boolean sub6 = frequencyRange != ServiceState.FREQUENCY_RANGE_UNKNOWN && !mmwave;
+            return (mMmwaveUnmetered && mmwave || mSub6Unmetered && sub6) && nrConnected;
+        } else {
+            return mAllUnmetered && nrConnected;
+        }
+    }
+
+    protected void log(String s) {
         Rlog.d(mLogTag, s);
     }
 
@@ -4206,13 +4496,13 @@
     /**
      * Polling stuff
      */
-    private void resetPollStats() {
+    protected void resetPollStats() {
         mTxPkts = -1;
         mRxPkts = -1;
         mNetStatPollPeriod = POLL_NETSTAT_MILLIS;
     }
 
-    private void startNetStatPoll() {
+    protected void startNetStatPoll() {
         if (getOverallState() == DctConstants.State.CONNECTED
                 && mNetStatPollEnabled == false) {
             if (DBG) {
@@ -4482,6 +4772,9 @@
             if (getOverallState() == DctConstants.State.CONNECTED) {
                 // Go through a series of recovery steps, each action transitions to the next action
                 @RecoveryAction final int recoveryAction = getRecoveryAction();
+                final int signalStrength = mPhone.getSignalStrength().getLevel();
+                TelephonyMetrics.getInstance().writeSignalStrengthEvent(
+                        mPhone.getPhoneId(), signalStrength);
                 TelephonyMetrics.getInstance().writeDataStallEvent(
                         mPhone.getPhoneId(), recoveryAction);
                 broadcastDataStallDetected(recoveryAction);
@@ -4638,7 +4931,7 @@
         startDataStallAlarm(suspectedStall);
     }
 
-    private void startDataStallAlarm(boolean suspectedStall) {
+    protected void startDataStallAlarm(boolean suspectedStall) {
         int delayInMs;
 
         if (mDsRecoveryHandler.isNoRxDataStallDetectionEnabled()
@@ -4750,6 +5043,36 @@
         }
     }
 
+    /**
+     * 5G connection reevaluation alarms
+     */
+    private boolean startHysteresisAlarm() {
+        if (mHysteresisTimeSec > 0) {
+            // only create hysteresis alarm if CarrierConfig allows it
+            sendMessageDelayed(obtainMessage(DctConstants.EVENT_5G_TIMER_HYSTERESIS),
+                    mHysteresisTimeSec * 1000);
+            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 ab6e6f6..d0dcdf9 100644
--- a/src/java/com/android/internal/telephony/dataconnection/TelephonyNetworkFactory.java
+++ b/src/java/com/android/internal/telephony/dataconnection/TelephonyNetworkFactory.java
@@ -87,7 +87,7 @@
 
 
     public TelephonyNetworkFactory(SubscriptionMonitor subscriptionMonitor, Looper looper,
-                                   Phone phone) {
+                                   Phone phone, PhoneSwitcher phoneSwitcher) {
         super(looper, phone.getContext(), "TelephonyNetworkFactory[" + phone.getPhoneId()
                 + "]", null);
         mPhone = phone;
@@ -99,7 +99,7 @@
         setCapabilityFilter(makeNetworkFilter(mSubscriptionController, mPhone.getPhoneId()));
         setScoreFilter(TELEPHONY_NETWORK_SCORE);
 
-        mPhoneSwitcher = PhoneSwitcher.getInstance();
+        mPhoneSwitcher = phoneSwitcher;
         mSubscriptionMonitor = subscriptionMonitor;
         LOG_TAG = "TelephonyNetworkFactory[" + mPhone.getPhoneId() + "]";
 
@@ -181,9 +181,12 @@
                                 DcTracker.DATA_COMPLETE_MSG_EXTRA_SUCCESS);
                         int transport = bundle.getInt(
                                 DcTracker.DATA_COMPLETE_MSG_EXTRA_TRANSPORT_TYPE);
+                        boolean fallback = bundle.getBoolean(
+                                DcTracker.DATA_COMPLETE_MSG_EXTRA_HANDOVER_FAILURE_FALLBACK);
                         HandoverParams handoverParams = mPendingHandovers.remove(msg);
                         if (handoverParams != null) {
-                            onDataHandoverSetupCompleted(nr, success, transport, handoverParams);
+                            onDataHandoverSetupCompleted(nr, success, transport, fallback,
+                                    handoverParams);
                         } else {
                             logl("Handover completed but cannot find handover entry!");
                         }
@@ -335,16 +338,16 @@
                 if (dcTracker != null) {
                     DataConnection dc = dcTracker.getDataConnectionByApnType(
                             ApnSetting.getApnTypeString(apnType));
-                    if (dc != null && (dc.isActive() || dc.isActivating())) {
+                    if (dc != null && (dc.isActive())) {
                         Message onCompleteMsg = mInternalHandler.obtainMessage(
                                 EVENT_DATA_HANDOVER_COMPLETED);
                         onCompleteMsg.getData().putParcelable(
                                 DcTracker.DATA_COMPLETE_MSG_EXTRA_NETWORK_REQUEST, networkRequest);
                         mPendingHandovers.put(onCompleteMsg, handoverParams);
-                        // TODO: Need to handle the case that the request is there, but there is no
-                        // actual data connections established.
                         requestNetworkInternal(networkRequest, DcTracker.REQUEST_TYPE_HANDOVER,
                                 targetTransport, onCompleteMsg);
+                        log("Requested handover " + ApnSetting.getApnTypeString(apnType) + " to "
+                                + AccessNetworkConstants.transportTypeToString(targetTransport));
                         handoverPending = true;
                     } else {
                         // Request is there, but no actual data connection. In this case, just move
@@ -354,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,
@@ -368,34 +372,37 @@
 
         if (!handoverPending) {
             log("No handover request pending. Handover process is now completed");
-            handoverParams.callback.onCompleted(true);
+            handoverParams.callback.onCompleted(true, false);
         }
     }
 
     private void onDataHandoverSetupCompleted(NetworkRequest networkRequest, boolean success,
-                                              int targetTransport, HandoverParams handoverParams) {
+                                              int targetTransport, boolean fallback,
+                                              HandoverParams handoverParams) {
         log("onDataHandoverSetupCompleted: " + networkRequest + ", success=" + success
                 + ", targetTransport="
-                + AccessNetworkConstants.transportTypeToString(targetTransport));
+                + AccessNetworkConstants.transportTypeToString(targetTransport)
+                + ", fallback=" + fallback);
 
-        // At this point, handover setup has been completed on the target transport. No matter
-        // succeeded or not, remove the request from the source transport because even the setup
-        // failed on target transport, we can retry again there.
+        // At this point, handover setup has been completed on the target transport.
+        // If it succeeded, or it failed without falling back to the original transport,
+        // we should release the request from the original transport.
+        if (!fallback) {
+            int originTransport = (targetTransport == AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
+                    ? AccessNetworkConstants.TRANSPORT_TYPE_WLAN
+                    : AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
+            int releaseType = success
+                    ? DcTracker.RELEASE_TYPE_HANDOVER
+                    // If handover fails, we need to tear down the existing connection, so the
+                    // new data connection can be re-established on the new transport. If we leave
+                    // the existing data connection in current transport, then DCT and qualified
+                    // network service will be out of sync.
+                    : DcTracker.RELEASE_TYPE_NORMAL;
+            releaseNetworkInternal(networkRequest, releaseType, originTransport);
+            mNetworkRequests.put(networkRequest, targetTransport);
+        }
 
-        int originTransport = (targetTransport == AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
-                ? AccessNetworkConstants.TRANSPORT_TYPE_WLAN
-                : AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
-        int releaseType = success
-                ? DcTracker.RELEASE_TYPE_HANDOVER
-                // If handover fails, we need to tear down the existing connection, so the
-                // new data connection can be re-established on the new transport. If we leave
-                // the existing data connection in current transport, then DCT and qualified
-                // network service will be out of sync.
-                : DcTracker.RELEASE_TYPE_NORMAL;
-        releaseNetworkInternal(networkRequest, releaseType, originTransport);
-        mNetworkRequests.put(networkRequest, targetTransport);
-
-        handoverParams.callback.onCompleted(success);
+        handoverParams.callback.onCompleted(success, fallback);
     }
 
     protected void log(String s) {
diff --git a/src/java/com/android/internal/telephony/dataconnection/TransportManager.java b/src/java/com/android/internal/telephony/dataconnection/TransportManager.java
index fd3b31d..5fb14af 100644
--- a/src/java/com/android/internal/telephony/dataconnection/TransportManager.java
+++ b/src/java/com/android/internal/telephony/dataconnection/TransportManager.java
@@ -199,14 +199,18 @@
              * Called when handover is completed.
              *
              * @param success {@true} if handover succeeded, otherwise failed.
+             * @param fallback {@true} if handover failed, the data connection fallback to the
+             * original transport
              */
-            void onCompleted(boolean success);
+            void onCompleted(boolean success, boolean fallback);
         }
 
         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;
@@ -347,28 +351,33 @@
                             + AccessNetworkConstants.transportTypeToString(targetTransport));
                     mPendingHandoverApns.put(networks.apnType, targetTransport);
                     mHandoverNeededEventRegistrants.notifyResult(
-                            new HandoverParams(networks.apnType, targetTransport, success -> {
-                                // The callback for handover completed.
-                                if (success) {
-                                    logl("Handover succeeded.");
-                                } else {
-                                    logl("APN type "
-                                            + ApnSetting.getApnTypeString(networks.apnType)
-                                            + " handover to "
-                                            + AccessNetworkConstants.transportTypeToString(
-                                                    targetTransport) + " failed.");
-                                }
-                                // No matter succeeded or not, we need to set the current transport
-                                // to the new one. If failed, there will be retry afterwards anyway.
-                                setCurrentTransport(networks.apnType, targetTransport);
-                                mPendingHandoverApns.delete(networks.apnType);
+                            new HandoverParams(networks.apnType, targetTransport,
+                                    (success, fallback) -> {
+                                        // The callback for handover completed.
+                                        if (success) {
+                                            logl("Handover succeeded.");
+                                        } else {
+                                            logl("APN type "
+                                                    + ApnSetting.getApnTypeString(networks.apnType)
+                                                    + " handover to "
+                                                    + AccessNetworkConstants.transportTypeToString(
+                                                    targetTransport) + " failed."
+                                                    + ", fallback=" + fallback);
+                                        }
+                                        if (success || !fallback) {
+                                            // If handover succeeds or failed without falling back
+                                            // to the original transport, we should move to the new
+                                            // transport (even if it is failed).
+                                            setCurrentTransport(networks.apnType, targetTransport);
+                                        }
+                                        mPendingHandoverApns.delete(networks.apnType);
 
-                                // If there are still pending available network changes, we need to
-                                // process the rest.
-                                if (mAvailableNetworksList.size() > 0) {
-                                    sendEmptyMessage(EVENT_UPDATE_AVAILABLE_NETWORKS);
-                                }
-                            }));
+                                        // If there are still pending available network changes, we
+                                        // need to process the rest.
+                                        if (mAvailableNetworksList.size() > 0) {
+                                            sendEmptyMessage(EVENT_UPDATE_AVAILABLE_NETWORKS);
+                                        }
+                                    }));
                 }
                 mCurrentAvailableNetworks.put(networks.apnType, networks.qualifiedNetworks);
             } else {
diff --git a/src/java/com/android/internal/telephony/emergency/EmergencyNumberTracker.java b/src/java/com/android/internal/telephony/emergency/EmergencyNumberTracker.java
index 2c822ba..a1a7aea 100644
--- a/src/java/com/android/internal/telephony/emergency/EmergencyNumberTracker.java
+++ b/src/java/com/android/internal/telephony/emergency/EmergencyNumberTracker.java
@@ -41,6 +41,7 @@
 import com.android.internal.telephony.LocaleTracker;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.ServiceStateTracker;
 import com.android.internal.telephony.SubscriptionController;
 import com.android.internal.telephony.metrics.TelephonyMetrics;
@@ -80,6 +81,11 @@
     private final CommandsInterface mCi;
     private final Phone mPhone;
     private String mCountryIso;
+    /**
+     * Indicates if the country iso is set by another subscription.
+     * @hide
+     */
+    public boolean mIsCountrySetByAnotherSub = false;
     private String[] mEmergencyNumberPrefix = new String[0];
 
     private static final String EMERGENCY_NUMBER_DB_ASSETS_FILE = "eccdata";
@@ -131,7 +137,9 @@
                     if (TextUtils.isEmpty(countryIso)) {
                         return;
                     }
-                    updateEmergencyNumberDatabaseCountryChange(countryIso);
+
+                    // Update country iso change for available Phones
+                    updateEmergencyCountryIsoAllPhones(countryIso);
                 }
                 return;
             }
@@ -222,11 +230,40 @@
         // If country iso has been cached when listener is set, don't need to cache the initial
         // country iso and initial database.
         if (mCountryIso == null) {
-            mCountryIso = getInitialCountryIso().toLowerCase();
+            updateEmergencyCountryIso(getInitialCountryIso().toLowerCase());
             cacheEmergencyDatabaseByCountry(mCountryIso);
         }
     }
 
+    /**
+     * Update Emergency country iso for all the Phones
+     */
+    @VisibleForTesting
+    public void updateEmergencyCountryIsoAllPhones(String countryIso) {
+        // Notify country iso change for current Phone
+        mIsCountrySetByAnotherSub = false;
+        updateEmergencyNumberDatabaseCountryChange(countryIso);
+
+        // Share and notify country iso change for other Phones if the country
+        // iso in their emergency number tracker is not available or the country
+        // iso there is set by another active subscription.
+        for (Phone phone: PhoneFactory.getPhones()) {
+            if (phone.getPhoneId() == mPhone.getPhoneId()) {
+                continue;
+            }
+            EmergencyNumberTracker emergencyNumberTracker;
+            if (phone != null && phone.getEmergencyNumberTracker() != null) {
+                emergencyNumberTracker = phone.getEmergencyNumberTracker();
+                if (TextUtils.isEmpty(emergencyNumberTracker.getEmergencyCountryIso())
+                        || emergencyNumberTracker.mIsCountrySetByAnotherSub) {
+                    emergencyNumberTracker.mIsCountrySetByAnotherSub = true;
+                    emergencyNumberTracker.updateEmergencyNumberDatabaseCountryChange(
+                            countryIso);
+                }
+            }
+        }
+    }
+
     private void onCarrierConfigChanged() {
         if (mPhone != null) {
             CarrierConfigManager configMgr = (CarrierConfigManager)
@@ -381,8 +418,7 @@
     private void updateEmergencyNumberListDatabaseAndNotify(String countryIso) {
         logd("updateEmergencyNumberListDatabaseAndNotify(): receiving countryIso: "
                 + countryIso);
-
-        mCountryIso = countryIso.toLowerCase();
+        updateEmergencyCountryIso(countryIso.toLowerCase());
         cacheEmergencyDatabaseByCountry(countryIso);
         writeUpdatedEmergencyNumberListMetrics(mEmergencyNumberListFromDatabase);
         if (!DBG) {
@@ -560,6 +596,14 @@
         return EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN;
     }
 
+    public String getEmergencyCountryIso() {
+        return mCountryIso;
+    }
+
+    private synchronized void updateEmergencyCountryIso(String countryIso) {
+        mCountryIso = countryIso;
+    }
+
     /**
      * Get Emergency number list based on EccList. This util is used for solving backward
      * compatibility if device does not support the 1.4 IRadioIndication HAL that reports
diff --git a/src/java/com/android/internal/telephony/euicc/EuiccController.java b/src/java/com/android/internal/telephony/euicc/EuiccController.java
index da1dc5d..3485a6c 100644
--- a/src/java/com/android/internal/telephony/euicc/EuiccController.java
+++ b/src/java/com/android/internal/telephony/euicc/EuiccController.java
@@ -1200,7 +1200,8 @@
 
         final PackageInfo info;
         try {
-            info = mPackageManager.getPackageInfo(callingPackage, PackageManager.GET_SIGNATURES);
+            info = mPackageManager.getPackageInfo(callingPackage,
+                PackageManager.GET_SIGNING_CERTIFICATES);
         } catch (PackageManager.NameNotFoundException e) {
             Log.e(TAG, "Calling package valid but gone");
             return false;
diff --git a/src/java/com/android/internal/telephony/gsm/GsmCellBroadcastHandler.java b/src/java/com/android/internal/telephony/gsm/GsmCellBroadcastHandler.java
index f30b386..9367b5d 100644
--- a/src/java/com/android/internal/telephony/gsm/GsmCellBroadcastHandler.java
+++ b/src/java/com/android/internal/telephony/gsm/GsmCellBroadcastHandler.java
@@ -16,20 +16,39 @@
 
 package com.android.internal.telephony.gsm;
 
+import static com.android.internal.telephony.gsm.SmsCbConstants.MESSAGE_ID_CMAS_GEO_FENCING_TRIGGER;
+
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.ContentUris;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.net.Uri;
 import android.os.AsyncResult;
 import android.os.Message;
+import android.os.SystemClock;
+import android.provider.Telephony.CellBroadcasts;
 import android.telephony.CellLocation;
 import android.telephony.SmsCbLocation;
 import android.telephony.SmsCbMessage;
 import android.telephony.TelephonyManager;
 import android.telephony.gsm.GsmCellLocation;
+import android.text.format.DateUtils;
 
+import com.android.internal.telephony.CbGeoUtils.Geometry;
 import com.android.internal.telephony.CellBroadcastHandler;
 import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.gsm.GsmSmsCbMessage.GeoFencingTriggerMessage;
+import com.android.internal.telephony.gsm.GsmSmsCbMessage.GeoFencingTriggerMessage.CellBroadcastIdentity;
 
+import java.text.DateFormat;
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Iterator;
+import java.util.List;
 
 /**
  * Handler for 3GPP format Cell Broadcasts. Parent class can also handle CDMA Cell Broadcasts.
@@ -37,13 +56,36 @@
 public class GsmCellBroadcastHandler extends CellBroadcastHandler {
     private static final boolean VDBG = false;  // log CB PDU data
 
+    /** Indicates that a message is not being broadcasted. */
+    private static final String MESSAGE_NOT_BROADCASTED = "0";
+
     /** This map holds incomplete concatenated messages waiting for assembly. */
     private final HashMap<SmsCbConcatInfo, byte[][]> mSmsCbPageMap =
             new HashMap<SmsCbConcatInfo, byte[][]>(4);
 
+    private long mLastAirplaneModeTime = 0;
+
     protected GsmCellBroadcastHandler(Context context, Phone phone) {
         super("GsmCellBroadcastHandler", context, phone);
         phone.mCi.setOnNewGsmBroadcastSms(getHandler(), EVENT_NEW_SMS_MESSAGE, null);
+
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+        mContext.registerReceiver(
+                new BroadcastReceiver() {
+                    @Override
+                    public void onReceive(Context context, Intent intent) {
+                        switch (intent.getAction()) {
+                            case Intent.ACTION_AIRPLANE_MODE_CHANGED:
+                                boolean airplaneModeOn = intent.getBooleanExtra("state", false);
+                                if (airplaneModeOn) {
+                                    mLastAirplaneModeTime = System.currentTimeMillis();
+                                }
+                                break;
+                        }
+
+                    }
+                }, intentFilter);
     }
 
     @Override
@@ -65,29 +107,143 @@
     }
 
     /**
+     * Find the cell broadcast messages specify by the geo-fencing trigger message and perform a
+     * geo-fencing check for these messages.
+     * @param geoFencingTriggerMessage the trigger message
+     *
+     * @return {@code True} if geo-fencing is need for some cell broadcast message.
+     */
+    private boolean handleGeoFencingTriggerMessage(
+            GeoFencingTriggerMessage geoFencingTriggerMessage) {
+        final List<SmsCbMessage> cbMessages = new ArrayList<>();
+        final List<Uri> cbMessageUris = new ArrayList<>();
+
+        long lastReceivedTime = System.currentTimeMillis() - DateUtils.DAY_IN_MILLIS;
+        Resources res = mContext.getResources();
+        if (res.getBoolean(com.android.internal.R.bool.reset_geo_fencing_check_after_boot_or_apm)) {
+            // Check last airplane mode time
+            lastReceivedTime = Long.max(lastReceivedTime, mLastAirplaneModeTime);
+            // Check last boot up time
+            lastReceivedTime = Long.max(lastReceivedTime,
+                    System.currentTimeMillis() - SystemClock.elapsedRealtime());
+        }
+
+        // Find the cell broadcast message identify by the message identifier and serial number
+        // and is not broadcasted.
+        String where = CellBroadcasts.SERVICE_CATEGORY + "=? AND "
+                + CellBroadcasts.SERIAL_NUMBER + "=? AND "
+                + CellBroadcasts.MESSAGE_BROADCASTED + "=? AND "
+                + CellBroadcasts.RECEIVED_TIME + ">?";
+
+        ContentResolver resolver = mContext.getContentResolver();
+        for (CellBroadcastIdentity identity : geoFencingTriggerMessage.cbIdentifiers) {
+            try (Cursor cursor = resolver.query(CELL_BROADCAST_URI,
+                    CellBroadcasts.QUERY_COLUMNS_FWK,
+                    where,
+                    new String[] { Integer.toString(identity.messageIdentifier),
+                            Integer.toString(identity.serialNumber), MESSAGE_NOT_BROADCASTED,
+                            Long.toString(lastReceivedTime) },
+                    null /* sortOrder */)) {
+                if (cursor != null) {
+                    while (cursor.moveToNext()) {
+                        cbMessages.add(SmsCbMessage.createFromCursor(cursor));
+                        cbMessageUris.add(ContentUris.withAppendedId(CELL_BROADCAST_URI,
+                                cursor.getInt(cursor.getColumnIndex(CellBroadcasts._ID))));
+                    }
+                }
+            }
+        }
+
+        log("Found " + cbMessages.size() + " messages since "
+                + DateFormat.getDateTimeInstance().format(lastReceivedTime));
+
+        List<Geometry> commonBroadcastArea = new ArrayList<>();
+        if (geoFencingTriggerMessage.shouldShareBroadcastArea()) {
+            for (SmsCbMessage msg : cbMessages) {
+                if (msg.getGeometries() != null) {
+                    commonBroadcastArea.addAll(msg.getGeometries());
+                }
+            }
+        }
+
+        // ATIS doesn't specify the geo fencing maximum wait time for the cell broadcasts specified
+        // in geo fencing trigger message. We will pick the largest maximum wait time among these
+        // cell broadcasts.
+        int maximumWaitTimeSec = 0;
+        for (SmsCbMessage msg : cbMessages) {
+            maximumWaitTimeSec = Math.max(maximumWaitTimeSec, msg.getMaximumWaitingTime());
+        }
+
+        if (DBG) {
+            logd("Geo-fencing trigger message = " + geoFencingTriggerMessage);
+            for (SmsCbMessage msg : cbMessages) {
+                logd(msg.toString());
+            }
+        }
+
+        if (cbMessages.isEmpty()) {
+            if (DBG) logd("No CellBroadcast message need to be broadcasted");
+            return false;
+        }
+
+        requestLocationUpdate(location -> {
+            if (location == null) {
+                // If the location is not available, broadcast the messages directly.
+                broadcastMessage(cbMessages, cbMessageUris);
+            } else {
+                for (int i = 0; i < cbMessages.size(); i++) {
+                    List<Geometry> broadcastArea = !commonBroadcastArea.isEmpty()
+                            ? commonBroadcastArea : cbMessages.get(i).getGeometries();
+                    if (broadcastArea == null || broadcastArea.isEmpty()) {
+                        broadcastMessage(cbMessages.get(i), cbMessageUris.get(i));
+                    } else {
+                        performGeoFencing(cbMessages.get(i), cbMessageUris.get(i), broadcastArea,
+                                location);
+                    }
+                }
+            }
+        }, maximumWaitTimeSec);
+        return true;
+    }
+
+    /**
      * Handle 3GPP-format Cell Broadcast messages sent from radio.
      *
      * @param message the message to process
-     * @return true if an ordered broadcast was sent; false on failure
+     * @return true if need to wait for geo-fencing or an ordered broadcast was sent.
      */
     @Override
     protected boolean handleSmsMessage(Message message) {
         if (message.obj instanceof AsyncResult) {
-            SmsCbMessage cbMessage = handleGsmBroadcastSms((AsyncResult) message.obj);
-            if (cbMessage != null) {
-                handleBroadcastSms(cbMessage);
-                return true;
+            SmsCbHeader header = createSmsCbHeader((AsyncResult) message.obj);
+            if (header == null) return false;
+
+            AsyncResult ar = (AsyncResult) message.obj;
+            byte[] pdu = (byte[]) ar.result;
+            if (header.getServiceCategory() == MESSAGE_ID_CMAS_GEO_FENCING_TRIGGER) {
+                GeoFencingTriggerMessage triggerMessage =
+                        GsmSmsCbMessage.createGeoFencingTriggerMessage(pdu);
+                if (triggerMessage != null) {
+                    return handleGeoFencingTriggerMessage(triggerMessage);
+                }
+            } else {
+                SmsCbMessage cbMessage = handleGsmBroadcastSms(header, ar);
+                if (cbMessage != null) {
+                    handleBroadcastSms(cbMessage);
+                    return true;
+                }
+                if (VDBG) log("Not handled GSM broadcasts.");
             }
-            if (VDBG) log("Not handled GSM broadcasts.");
         }
         return super.handleSmsMessage(message);
     }
 
     /**
      * Handle 3GPP format SMS-CB message.
+     * @param header the cellbroadcast header.
      * @param ar the AsyncResult containing the received PDUs
      */
-    private SmsCbMessage handleGsmBroadcastSms(AsyncResult ar) {
+    private SmsCbMessage handleGsmBroadcastSms(SmsCbHeader header, AsyncResult ar) {
         try {
             byte[] receivedPdu = (byte[]) ar.result;
 
@@ -106,7 +262,6 @@
                 }
             }
 
-            SmsCbHeader header = new SmsCbHeader(receivedPdu);
             if (VDBG) log("header=" + header);
             String plmn = TelephonyManager.from(mContext).getNetworkOperatorForPhone(
                     mPhone.getPhoneId());
@@ -197,6 +352,15 @@
         }
     }
 
+    private SmsCbHeader createSmsCbHeader(AsyncResult ar) {
+        try {
+            return new SmsCbHeader((byte[]) ar.result);
+        } catch (Exception ex) {
+            loge("Can't create SmsCbHeader, ex = " + ex.toString());
+            return null;
+        }
+    }
+
     /**
      * Holds all info about a message page needed to assemble a complete concatenated message.
      */
diff --git a/src/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java b/src/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
index 99082ee..998f765 100644
--- a/src/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
+++ b/src/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
@@ -189,8 +189,11 @@
         int ss = mPhone.getServiceState().getState();
         // if sms over IMS is not supported on data and voice is not available...
         if (!isIms() && ss != ServiceState.STATE_IN_SERVICE) {
-            tracker.onFailed(mContext, getNotInServiceError(ss), 0/*errorCode*/);
-            return;
+            if(mPhone.getServiceState().getRilDataRadioTechnology()
+                    != ServiceState.RIL_RADIO_TECHNOLOGY_NR) {
+                tracker.onFailed(mContext, getNotInServiceError(ss), 0/*errorCode*/);
+                return;
+            }
         }
 
         byte smsc[] = (byte[]) map.get("smsc");
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhone.java b/src/java/com/android/internal/telephony/imsphone/ImsPhone.java
index 539a539..138f875 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhone.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhone.java
@@ -1452,6 +1452,7 @@
                 mSS.addNetworkRegistrationInfo(nri);
             }
 
+            mSS.setIwlanPreferred(ss.isIwlanPreferred());
             logd("updateDataServiceState: defSs = " + ss + " imsSs = " + mSS);
         }
     }
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
index 9fbef9c..992d9aa 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
@@ -95,13 +95,13 @@
 import com.android.internal.telephony.LocaleTracker;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneConstants;
-import com.android.internal.telephony.PhoneInternalInterface;
 import com.android.internal.telephony.ServiceStateTracker;
 import com.android.internal.telephony.SubscriptionController;
 import com.android.internal.telephony.TelephonyProperties;
 import com.android.internal.telephony.dataconnection.DataEnabledSettings;
 import com.android.internal.telephony.dataconnection.DataEnabledSettings.DataEnabledChangedReason;
 import com.android.internal.telephony.gsm.SuppServiceNotification;
+import com.android.internal.telephony.imsphone.ImsPhone.ImsDialArgs;
 import com.android.internal.telephony.metrics.CallQualityMetrics;
 import com.android.internal.telephony.metrics.TelephonyMetrics;
 import com.android.internal.telephony.nano.TelephonyProto.ImsConnectionState;
@@ -301,6 +301,7 @@
     private static final int EVENT_REDIAL_WIFI_E911_TIMEOUT = 29;
     private static final int EVENT_ANSWER_WAITING_CALL = 30;
     private static final int EVENT_RESUME_NOW_FOREGROUND_CALL = 31;
+    private static final int EVENT_REDIAL_WITHOUT_RTT = 32;
 
     private static final int TIMEOUT_HANGUP_PENDINGMO = 500;
 
@@ -422,7 +423,8 @@
     private HoldSwapState mHoldSwitchingState = HoldSwapState.INACTIVE;
 
     private String mLastDialString = null;
-    private PhoneInternalInterface.DialArgs mLastDialArgs = null;
+    private ImsDialArgs mLastDialArgs = null;
+
     /**
      * Listeners to changes in the phone state.  Intended for use by other interested IMS components
      * without the need to register a full blown {@link android.telephony.PhoneStateListener}.
@@ -1465,6 +1467,7 @@
             fgImsCall.merge(bgImsCall);
         } catch (ImsException e) {
             log("conference " + e.getMessage());
+            handleConferenceFailed(foregroundConnection, backgroundConnection);
         }
     }
 
@@ -1948,6 +1951,9 @@
      */
     @VisibleForTesting
     public void addReasonCodeRemapping(Integer fromCode, String message, Integer toCode) {
+        if (message != null) {
+            message = message.toLowerCase();
+        }
         mImsReasonCodeMap.put(new Pair<>(fromCode, message), toCode);
     }
 
@@ -1966,6 +1972,8 @@
         String reason = reasonInfo.getExtraMessage();
         if (reason == null) {
             reason = "";
+        } else {
+            reason = reason.toLowerCase();
         }
         log("maybeRemapReasonCode : fromCode = " + reasonInfo.getCode() + " ; message = "
                 + reason);
@@ -2038,6 +2046,12 @@
             case ImsReasonInfo.CODE_SIP_GLOBAL_ERROR:
                 return DisconnectCause.SERVER_ERROR;
 
+            case ImsReasonInfo.CODE_EMERGENCY_CALL_OVER_WFC_NOT_AVAILABLE:
+                return DisconnectCause.EMERGENCY_CALL_OVER_WFC_NOT_AVAILABLE;
+
+            case ImsReasonInfo.CODE_WFC_SERVICE_NOT_AVAILABLE_IN_THIS_LOCATION:
+                return DisconnectCause.WFC_SERVICE_NOT_AVAILABLE_IN_THIS_LOCATION;
+
             case ImsReasonInfo.CODE_SIP_SERVICE_UNAVAILABLE:
             case ImsReasonInfo.CODE_SIP_SERVER_ERROR:
                 return DisconnectCause.SERVER_UNREACHABLE;
@@ -2374,6 +2388,10 @@
                         .getSystemService(Context.CONNECTIVITY_SERVICE);
                 mgr.setAirplaneMode(false);
                 return;
+            } else if (reasonInfo.getCode() == ImsReasonInfo.CODE_RETRY_ON_IMS_WITHOUT_RTT) {
+                Pair<ImsCall, ImsReasonInfo> callInfo = new Pair<>(imsCall, reasonInfo);
+                sendMessage(obtainMessage(EVENT_REDIAL_WITHOUT_RTT, callInfo));
+                return;
             } else {
                 processCallStateChange(imsCall, ImsPhoneCall.State.DISCONNECTED, cause);
             }
@@ -3405,6 +3423,30 @@
                 sendCallStartFailedDisconnect(callInfo.first, callInfo.second);
                 break;
             }
+
+            case EVENT_REDIAL_WITHOUT_RTT: {
+                Pair<ImsCall, ImsReasonInfo> callInfo = (Pair<ImsCall, ImsReasonInfo>) msg.obj;
+                removeMessages(EVENT_REDIAL_WITHOUT_RTT);
+                ImsPhoneConnection oldConnection = findConnection(callInfo.first);
+                if (oldConnection == null) {
+                    sendCallStartFailedDisconnect(callInfo.first, callInfo.second);
+                    break;
+                }
+                mForegroundCall.detach(oldConnection);
+                removeConnection(oldConnection);
+                try {
+                    mPendingMO = null;
+                    ImsDialArgs newDialArgs = ImsDialArgs.Builder.from(mLastDialArgs)
+                            .setRttTextStream(null)
+                            .build();
+                    Connection newConnection =
+                            mPhone.getDefaultPhone().dial(mLastDialString, newDialArgs);
+                    oldConnection.onOriginalConnectionReplaced(newConnection);
+                } catch (CallStateException e) {
+                    sendCallStartFailedDisconnect(callInfo.first, callInfo.second);
+                }
+                break;
+            }
         }
     }
 
@@ -4235,4 +4277,15 @@
     public ImsPhone getPhone() {
         return mPhone;
     }
+
+    private void handleConferenceFailed(ImsPhoneConnection fgConnection,
+            ImsPhoneConnection bgConnection) {
+        if (fgConnection != null) {
+            fgConnection.handleMergeComplete();
+        }
+        if (bgConnection != null) {
+            bgConnection.handleMergeComplete();
+        }
+        mPhone.notifySuppServiceFailed(Phone.SuppService.CONFERENCE);
+    }
 }
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCommandInterface.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCommandInterface.java
index 5ebd904..059906b 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCommandInterface.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCommandInterface.java
@@ -240,6 +240,10 @@
     }
 
     @Override
+    public void sendCdmaSms(byte[] pdu, Message result, boolean isExpectMore) {
+    }
+
+    @Override
     public void sendImsGsmSms (String smscPDU, String pdu,
             int retry, int messageRef, Message response) {
     }
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneMmiCode.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneMmiCode.java
index 1525ff7..b1d524d 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneMmiCode.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneMmiCode.java
@@ -16,8 +16,6 @@
 
 package com.android.internal.telephony.imsphone;
 
-import static android.telephony.ServiceState.STATE_IN_SERVICE;
-
 import static com.android.internal.telephony.CommandsInterface.SERVICE_CLASS_DATA;
 import static com.android.internal.telephony.CommandsInterface.SERVICE_CLASS_DATA_ASYNC;
 import static com.android.internal.telephony.CommandsInterface.SERVICE_CLASS_DATA_SYNC;
@@ -1009,19 +1007,11 @@
                     throw new RuntimeException ("Invalid or Unsupported MMI Code");
                 }
             } else if (mPoundString != null) {
-                // We'll normally send USSD over the CS pipe, but if it happens that the CS phone
-                // is out of service, we'll just try over IMS instead.
-                if (mPhone.getDefaultPhone().getServiceStateTracker().mSS.getState()
-                        == STATE_IN_SERVICE) {
-                    Rlog.i(LOG_TAG, "processCode: Sending ussd string '"
-                            + Rlog.pii(LOG_TAG, mPoundString) + "' over CS pipe.");
-                    throw new CallStateException(Phone.CS_FALLBACK);
-                } else {
-                    Rlog.i(LOG_TAG, "processCode: CS is out of service, sending ussd string '"
-                            + Rlog.pii(LOG_TAG, mPoundString) + "' over IMS pipe.");
-                    sendUssd(mPoundString);
-                }
-
+                // USSD codes are not supported over IMS due to modem limitations; send over the CS
+                // pipe instead.  This should be fixed in the future.
+                Rlog.i(LOG_TAG, "processCode: Sending ussd string '"
+                        + Rlog.pii(LOG_TAG, mPoundString) + "' over CS pipe.");
+                throw new CallStateException(Phone.CS_FALLBACK);
             } else {
                 Rlog.d(LOG_TAG, "processCode: invalid or unsupported MMI");
                 throw new RuntimeException ("Invalid or Unsupported MMI Code");
diff --git a/src/java/com/android/internal/telephony/metrics/TelephonyEventBuilder.java b/src/java/com/android/internal/telephony/metrics/TelephonyEventBuilder.java
index 73439c3..22fc562 100644
--- a/src/java/com/android/internal/telephony/metrics/TelephonyEventBuilder.java
+++ b/src/java/com/android/internal/telephony/metrics/TelephonyEventBuilder.java
@@ -93,6 +93,12 @@
         return this;
     }
 
+    public TelephonyEventBuilder setSignalStrength(int signalstrength) {
+        mEvent.type = TelephonyEvent.Type.SIGNAL_STRENGTH;
+        mEvent.signalStrength = signalstrength;
+        return this;
+    }
+
     public TelephonyEventBuilder setSetupDataCall(RilSetupDataCall request) {
         mEvent.type = TelephonyEvent.Type.DATA_CALL_SETUP;
         mEvent.setupDataCall = request;
diff --git a/src/java/com/android/internal/telephony/metrics/TelephonyMetrics.java b/src/java/com/android/internal/telephony/metrics/TelephonyMetrics.java
index e038a9d..044fb08 100644
--- a/src/java/com/android/internal/telephony/metrics/TelephonyMetrics.java
+++ b/src/java/com/android/internal/telephony/metrics/TelephonyMetrics.java
@@ -41,8 +41,10 @@
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.provider.Telephony.Sms.Intents;
+import android.telephony.AccessNetworkConstants;
 import android.telephony.CallQuality;
 import android.telephony.DisconnectCause;
+import android.telephony.NetworkRegistrationInfo;
 import android.telephony.Rlog;
 import android.telephony.ServiceState;
 import android.telephony.SmsManager;
@@ -434,6 +436,11 @@
                         + " Voice RAT " + event.serviceState.voiceRat
                         + " Channel Number " + event.serviceState.channelNumber
                         + ")");
+                for (int i = 0; i < event.serviceState.networkRegistrationInfo.length; i++) {
+                    pw.print("reg info: domain="
+                            + event.serviceState.networkRegistrationInfo[i].domain
+                            + ", rat=" + event.serviceState.networkRegistrationInfo[i].rat);
+                }
             } else {
                 pw.print(telephonyEventToString(event.type));
             }
@@ -908,6 +915,26 @@
             ssProto.dataOperator.numeric = serviceState.getDataOperatorNumeric();
         }
 
+        // Log PS WWAN only because CS WWAN would be exactly the same as voiceRat, and PS WLAN
+        // would be always IWLAN in the rat field.
+        // Note that we intentionally do not log reg state because it changes too frequently that
+        // will grow the proto size too much.
+        List<TelephonyServiceState.NetworkRegistrationInfo> nriList = new ArrayList<>();
+        NetworkRegistrationInfo nri = serviceState.getNetworkRegistrationInfo(
+                NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+        if (nri != null) {
+            TelephonyServiceState.NetworkRegistrationInfo nriProto =
+                    new TelephonyServiceState.NetworkRegistrationInfo();
+            nriProto.domain = TelephonyServiceState.Domain.DOMAIN_PS;
+            nriProto.transport = TelephonyServiceState.Transport.TRANSPORT_WWAN;
+            nriProto.rat = ServiceState.networkTypeToRilRadioTechnology(
+                    nri.getAccessNetworkTechnology());
+            nriList.add(nriProto);
+            ssProto.networkRegistrationInfo =
+                    new TelephonyServiceState.NetworkRegistrationInfo[nriList.size()];
+            nriList.toArray(ssProto.networkRegistrationInfo);
+        }
+
         ssProto.voiceRat = serviceState.getRilVoiceRadioTechnology();
         ssProto.dataRat = serviceState.getRilDataRadioTechnology();
         ssProto.channelNumber = serviceState.getChannelNumber();
@@ -1138,6 +1165,17 @@
     }
 
     /**
+     * Write SignalStrength event
+     *
+     * @param phoneId Phone id
+     * @param signalStrength Signal strength at the time of data stall recovery
+     */
+    public void writeSignalStrengthEvent(int phoneId, int signalStrength) {
+        addTelephonyEvent(new TelephonyEventBuilder(phoneId)
+                .setSignalStrength(signalStrength).build());
+    }
+
+    /**
      * Write IMS feature settings changed event
      *
      * @param phoneId Phone id
diff --git a/src/java/com/android/internal/telephony/sip/SipCommandInterface.java b/src/java/com/android/internal/telephony/sip/SipCommandInterface.java
index 075b4d6..f27f07c 100644
--- a/src/java/com/android/internal/telephony/sip/SipCommandInterface.java
+++ b/src/java/com/android/internal/telephony/sip/SipCommandInterface.java
@@ -241,6 +241,10 @@
     }
 
     @Override
+    public void sendCdmaSms(byte[] pdu, Message result, boolean isExpectMore) {
+    }
+
+    @Override
     public void sendImsGsmSms (String smscPDU, String pdu,
             int retry, int messageRef, Message response) {
     }
diff --git a/src/java/com/android/internal/telephony/test/SimulatedCommands.java b/src/java/com/android/internal/telephony/test/SimulatedCommands.java
index 0cdcb49..a2bef52 100644
--- a/src/java/com/android/internal/telephony/test/SimulatedCommands.java
+++ b/src/java/com/android/internal/telephony/test/SimulatedCommands.java
@@ -1788,6 +1788,13 @@
         resultSuccess(response, null);
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void sendCdmaSms(byte[] pdu, Message response, boolean  expectMore){
+    }
+
     @Override
     public void setCdmaBroadcastActivation(boolean activate, Message response) {
         unimplemented(response);
diff --git a/src/java/com/android/internal/telephony/test/SimulatedCommandsVerifier.java b/src/java/com/android/internal/telephony/test/SimulatedCommandsVerifier.java
index 722d1e8..c014daa 100644
--- a/src/java/com/android/internal/telephony/test/SimulatedCommandsVerifier.java
+++ b/src/java/com/android/internal/telephony/test/SimulatedCommandsVerifier.java
@@ -839,6 +839,11 @@
     }
 
     @Override
+    public void sendCdmaSms(byte[] pdu, Message response, boolean expectMore) {
+
+    }
+
+    @Override
     public void sendImsGsmSms(String smscPDU, String pdu, int retry, int messageRef,
                               Message response) {
 
@@ -1298,8 +1303,7 @@
     }
 
     @Override
-    public void setUiccSubscription(int slotId, int appIndex, int subId, int subStatus,
-                                    Message result) {
+    public void setUiccSubscription(int appIndex, boolean activate, Message result) {
 
     }
 
diff --git a/src/java/com/android/internal/telephony/uicc/AdnRecord.java b/src/java/com/android/internal/telephony/uicc/AdnRecord.java
index 7aa6a11..11e7297 100644
--- a/src/java/com/android/internal/telephony/uicc/AdnRecord.java
+++ b/src/java/com/android/internal/telephony/uicc/AdnRecord.java
@@ -47,6 +47,8 @@
     @UnsupportedAppUsage
     String[] mEmails;
     @UnsupportedAppUsage
+    String[] mAdditionalNumbers = null;
+    @UnsupportedAppUsage
     int mExtRecord = 0xff;
     @UnsupportedAppUsage
     int mEfid;                   // or 0 if none
@@ -88,14 +90,15 @@
             String alphaTag;
             String number;
             String[] emails;
-
+            String[] additionalNumbers;
             efid = source.readInt();
             recordNumber = source.readInt();
             alphaTag = source.readString();
             number = source.readString();
             emails = source.readStringArray();
+            additionalNumbers = source.readStringArray();
 
-            return new AdnRecord(efid, recordNumber, alphaTag, number, emails);
+            return new AdnRecord(efid, recordNumber, alphaTag, number, emails, additionalNumbers);
         }
 
         @Override
@@ -129,12 +132,29 @@
     }
 
     @UnsupportedAppUsage
+    public AdnRecord(String alphaTag, String number, String[] emails, String[] additionalNumbers) {
+        this(0, 0, alphaTag, number, emails, additionalNumbers);
+    }
+
+    @UnsupportedAppUsage
     public AdnRecord (int efid, int recordNumber, String alphaTag, String number, String[] emails) {
         this.mEfid = efid;
         this.mRecordNumber = recordNumber;
         this.mAlphaTag = alphaTag;
         this.mNumber = number;
         this.mEmails = emails;
+        this.mAdditionalNumbers = null;
+    }
+
+    @UnsupportedAppUsage
+    public AdnRecord(int efid, int recordNumber, String alphaTag, String number, String[] emails,
+            String[] additionalNumbers) {
+        this.mEfid = efid;
+        this.mRecordNumber = recordNumber;
+        this.mAlphaTag = alphaTag;
+        this.mNumber = number;
+        this.mEmails = emails;
+        this.mAdditionalNumbers = additionalNumbers;
     }
 
     @UnsupportedAppUsage
@@ -144,6 +164,7 @@
         this.mAlphaTag = alphaTag;
         this.mNumber = number;
         this.mEmails = null;
+        this.mAdditionalNumbers = null;
     }
 
     //***** Instance Methods
@@ -179,15 +200,35 @@
         this.mEmails = emails;
     }
 
+    @UnsupportedAppUsage
+    public String[] getAdditionalNumbers() {
+        return mAdditionalNumbers;
+    }
+
+    @UnsupportedAppUsage
+    public void setAdditionalNumbers(String[] additionalNumbers) {
+        this.mAdditionalNumbers = additionalNumbers;
+    }
+
+    public int getRecordNumber() {
+        return mRecordNumber;
+    }
+
+    public void setRecordNumber(int recNumber) {
+        mRecordNumber = recNumber;
+    }
+
     @Override
     public String toString() {
         return "ADN Record '" + mAlphaTag + "' '" + Rlog.pii(LOG_TAG, mNumber) + " "
-                + Rlog.pii(LOG_TAG, mEmails) + "'";
+                + Rlog.pii(LOG_TAG, mEmails) + " "
+                + Rlog.pii(LOG_TAG, mAdditionalNumbers) + "'";
     }
 
     @UnsupportedAppUsage
     public boolean isEmpty() {
-        return TextUtils.isEmpty(mAlphaTag) && TextUtils.isEmpty(mNumber) && mEmails == null;
+        return TextUtils.isEmpty(mAlphaTag) && TextUtils.isEmpty(mNumber) && mEmails == null
+                && mAdditionalNumbers == null;
     }
 
     public boolean hasExtendedRecord() {
@@ -208,11 +249,54 @@
         return (s1.equals(s2));
     }
 
+    /** Help function for ANR/EMAIL array compare. */
+    private static boolean arrayCompareNullEqualsEmpty(String s1[], String s2[]) {
+        if (s1 == s2) {
+            return true;
+        }
+
+        if (s1 == null) {
+            s1 = new String []{""};
+        }
+
+        if (s2 == null) {
+            s2 = new String []{""};
+        }
+
+        for (String str:s1) {
+            if (TextUtils.isEmpty(str)) {
+                continue;
+            } else {
+                if (Arrays.asList(s2).contains(str)) {
+                    continue;
+                } else {
+                    return false;
+                }
+            }
+        }
+
+        for (String str:s2) {
+            if (TextUtils.isEmpty(str)) {
+                continue;
+            } else {
+                if (Arrays.asList(s1).contains(str)) {
+                    continue;
+                } else {
+                    return false;
+                }
+            }
+        }
+
+        return true;
+    }
+
     public boolean isEqual(AdnRecord adn) {
         return ( stringCompareNullEqualsEmpty(mAlphaTag, adn.mAlphaTag) &&
                 stringCompareNullEqualsEmpty(mNumber, adn.mNumber) &&
-                Arrays.equals(mEmails, adn.mEmails));
+                arrayCompareNullEqualsEmpty(mEmails, adn.mEmails) &&
+                arrayCompareNullEqualsEmpty(mAdditionalNumbers, adn.mAdditionalNumbers));
     }
+
     //***** Parcelable Implementation
 
     @Override
@@ -227,6 +311,7 @@
         dest.writeString(mAlphaTag);
         dest.writeString(mNumber);
         dest.writeStringArray(mEmails);
+        dest.writeStringArray(mAdditionalNumbers);
     }
 
     /**
@@ -250,10 +335,10 @@
             adnString[i] = (byte) 0xFF;
         }
 
-        if (TextUtils.isEmpty(mNumber)) {
+        if (TextUtils.isEmpty(mNumber) && TextUtils.isEmpty(mAlphaTag)) {
             Rlog.w(LOG_TAG, "[buildAdnString] Empty dialing number");
             return adnString;   // return the empty record (for delete)
-        } else if (mNumber.length()
+        } else if (mNumber != null && mNumber.length()
                 > (ADN_DIALING_NUMBER_END - ADN_DIALING_NUMBER_START + 1) * 2) {
             Rlog.w(LOG_TAG,
                     "[buildAdnString] Max length of dialing number is 20");
@@ -267,14 +352,16 @@
             Rlog.w(LOG_TAG, "[buildAdnString] Max length of tag is " + footerOffset);
             return null;
         } else {
-            bcdNumber = PhoneNumberUtils.numberToCalledPartyBCD(
-                    mNumber, PhoneNumberUtils.BCD_EXTENDED_TYPE_EF_ADN);
+            if (!TextUtils.isEmpty(mNumber)) {
+                bcdNumber = PhoneNumberUtils.numberToCalledPartyBCD(
+                        mNumber, PhoneNumberUtils.BCD_EXTENDED_TYPE_EF_ADN);
 
-            System.arraycopy(bcdNumber, 0, adnString,
-                    footerOffset + ADN_TON_AND_NPI, bcdNumber.length);
+                System.arraycopy(bcdNumber, 0, adnString,
+                        footerOffset + ADN_TON_AND_NPI, bcdNumber.length);
 
-            adnString[footerOffset + ADN_BCD_NUMBER_LENGTH]
-                    = (byte) (bcdNumber.length);
+                adnString[footerOffset + ADN_BCD_NUMBER_LENGTH]
+                        = (byte) (bcdNumber.length);
+            }
             adnString[footerOffset + ADN_CAPABILITY_ID]
                     = (byte) 0xFF; // Capability Id
             adnString[footerOffset + ADN_EXTENSION_ID]
@@ -359,12 +446,14 @@
             mExtRecord = 0xff & record[record.length - 1];
 
             mEmails = null;
+            mAdditionalNumbers = null;
 
         } catch (RuntimeException ex) {
             Rlog.w(LOG_TAG, "Error parsing AdnRecord", ex);
             mNumber = "";
             mAlphaTag = "";
             mEmails = null;
+            mAdditionalNumbers = null;
         }
     }
 }
diff --git a/src/java/com/android/internal/telephony/uicc/CsimFileHandler.java b/src/java/com/android/internal/telephony/uicc/CsimFileHandler.java
index e45afa9..659af39 100644
--- a/src/java/com/android/internal/telephony/uicc/CsimFileHandler.java
+++ b/src/java/com/android/internal/telephony/uicc/CsimFileHandler.java
@@ -44,8 +44,13 @@
         case EF_CSIM_IMSIM:
         case EF_CSIM_CDMAHOME:
         case EF_CSIM_EPRL:
+        case EF_CSIM_PRL:
         case EF_CSIM_MIPUPP:
+        case EF_RUIM_ID:
             return MF_SIM + DF_ADF;
+        case EF_CSIM_MSPL:
+        case EF_CSIM_MLPL:
+            return MF_SIM + DF_TELECOM + DF_MMSS;
         }
         String path = getCommonIccEFPath(efid);
         if (path == null) {
diff --git a/src/java/com/android/internal/telephony/uicc/IccConstants.java b/src/java/com/android/internal/telephony/uicc/IccConstants.java
index 0f41f1e..3479412 100644
--- a/src/java/com/android/internal/telephony/uicc/IccConstants.java
+++ b/src/java/com/android/internal/telephony/uicc/IccConstants.java
@@ -62,6 +62,7 @@
     // CDMA RUIM file ids from 3GPP2 C.S0023-0
     static final int EF_CST = 0x6F32;
     static final int EF_RUIM_SPN =0x6F41;
+    static final int EF_RUIM_ID = 0x6F31;
 
     // ETSI TS.102.221
     static final int EF_PL = 0x2F05;
@@ -72,6 +73,10 @@
     static final int EF_CSIM_IMSIM = 0x6F22;
     static final int EF_CSIM_CDMAHOME = 0x6F28;
     static final int EF_CSIM_EPRL = 0x6F5A;
+    static final int EF_CSIM_PRL = 0x6F30;
+    // C.S0074-Av1.0 Section 4
+    static final int EF_CSIM_MLPL = 0x4F20;
+    static final int EF_CSIM_MSPL = 0x4F21;
     static final int EF_CSIM_MIPUPP = 0x6F4D;
 
     //ISIM access
@@ -103,6 +108,7 @@
     static final String DF_GRAPHICS = "5F50";
     static final String DF_GSM = "7F20";
     static final String DF_CDMA = "7F25";
+    static final String DF_MMSS = "5F3C";
 
     //UICC access
     static final String DF_ADF = "7FFF";
diff --git a/src/java/com/android/internal/telephony/uicc/IccRecords.java b/src/java/com/android/internal/telephony/uicc/IccRecords.java
index be664c2..7944f6f 100644
--- a/src/java/com/android/internal/telephony/uicc/IccRecords.java
+++ b/src/java/com/android/internal/telephony/uicc/IccRecords.java
@@ -17,6 +17,7 @@
 package com.android.internal.telephony.uicc;
 
 import android.annotation.IntDef;
+import android.annotation.Nullable;
 import android.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.os.AsyncResult;
@@ -694,6 +695,23 @@
         return mSpn;
     }
 
+    /**
+     * Return Service Provider Name stored in SIM (EF_SPN=0x6F46) or in RUIM (EF_RUIM_SPN=0x6F41) or
+     * the brand override. The brand override has higher priority than the SPN from SIM.
+     *
+     * @return service provider name.
+     */
+    @Nullable
+    public String getServiceProviderNameWithBrandOverride() {
+        if (mParentApp != null && mParentApp.getUiccProfile() != null) {
+            String brandOverride = mParentApp.getUiccProfile().getOperatorBrandOverride();
+            if (!TextUtils.isEmpty(brandOverride)) {
+                return brandOverride;
+            }
+        }
+        return mSpn;
+    }
+
     protected void setServiceProviderName(String spn) {
         if (!TextUtils.equals(mSpn, spn)) {
             mSpn = spn != null ? spn.trim() : null;
diff --git a/src/java/com/android/internal/telephony/uicc/RuimFileHandler.java b/src/java/com/android/internal/telephony/uicc/RuimFileHandler.java
index 58e939f..1f10304 100644
--- a/src/java/com/android/internal/telephony/uicc/RuimFileHandler.java
+++ b/src/java/com/android/internal/telephony/uicc/RuimFileHandler.java
@@ -64,8 +64,13 @@
         case EF_CSIM_IMSIM:
         case EF_CSIM_CDMAHOME:
         case EF_CSIM_EPRL:
+        case EF_CSIM_PRL:
         case EF_CSIM_MIPUPP:
+        case EF_RUIM_ID:
             return MF_SIM + DF_CDMA;
+        case EF_CSIM_MSPL:
+        case EF_CSIM_MLPL:
+            return MF_SIM + DF_TELECOM + DF_MMSS;
         }
         return getCommonIccEFPath(efid);
     }
diff --git a/src/java/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRules.java b/src/java/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRules.java
index d8be6fb..55718cb 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRules.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRules.java
@@ -114,6 +114,8 @@
     // Max number of retries for open logical channel, interval is 10s.
     private static final int MAX_RETRY = 1;
     private static final int RETRY_INTERVAL_MS = 10000;
+    private static final int STATUS_CODE_CONDITION_NOT_SATISFIED = 0x6985;
+    private static final int STATUS_CODE_APPLET_SELECT_FAILED = 0x6999;
 
     // Used for parsing the data from the UICC.
     public static class TLV {
@@ -316,7 +318,7 @@
             // is disabled by default, and some other component wants to enable it when it has
             // gained carrier privileges (as an indication that a matching SIM has been inserted).
             PackageInfo pInfo = packageManager.getPackageInfo(packageName,
-                    PackageManager.GET_SIGNATURES
+                    PackageManager.GET_SIGNING_CERTIFICATES
                             | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
                             | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS);
             return getCarrierPrivilegeStatus(pInfo);
@@ -426,6 +428,30 @@
         return null;
     }
 
+    /**
+     * The following three situations could be due to logical channels temporarily unavailable, so
+     * we retry up to MAX_RETRY times, with an interval of RETRY_INTERVAL_MS: 1. MISSING_RESOURCE,
+     * 2. NO_SUCH_ELEMENT and the status code is 6985, 3. INTERNAL_ERR and the status code is 6999.
+     */
+    public static boolean shouldRetry(AsyncResult ar, int retryCount) {
+        if (ar.exception instanceof CommandException && retryCount < MAX_RETRY) {
+            CommandException.Error error = ((CommandException) (ar.exception)).getCommandError();
+            int[] results = (int[]) ar.result;
+            int statusCode = 0;
+            if (ar.result != null && results.length == 3) {
+                byte[] bytes = new byte[]{(byte) results[1], (byte) results[2]};
+                statusCode = Integer.parseInt(IccUtils.bytesToHexString(bytes), 16);
+                log("status code: " + String.valueOf(statusCode));
+            }
+            return (error == CommandException.Error.MISSING_RESOURCE)
+                            || (error == CommandException.Error.NO_SUCH_ELEMENT
+                    && statusCode == STATUS_CODE_CONDITION_NOT_SATISFIED)
+                            || (error == CommandException.Error.INTERNAL_ERR
+                    && statusCode == STATUS_CODE_APPLET_SELECT_FAILED);
+        }
+        return false;
+    }
+
     @Override
     public void handleMessage(Message msg) {
         AsyncResult ar;
@@ -442,11 +468,8 @@
                             DATA, obtainMessage(EVENT_TRANSMIT_LOGICAL_CHANNEL_DONE, mChannelId,
                                     mAIDInUse));
                 } else {
-                    // MISSING_RESOURCE could be due to logical channels temporarily unavailable,
-                    // so we retry up to MAX_RETRY times, with an interval of RETRY_INTERVAL_MS.
-                    if (ar.exception instanceof CommandException && mRetryCount < MAX_RETRY
-                            && ((CommandException) (ar.exception)).getCommandError()
-                            == CommandException.Error.MISSING_RESOURCE) {
+                    if (shouldRetry(ar, mRetryCount)) {
+                        log("should retry");
                         mRetryCount++;
                         removeCallbacks(mRetryRunnable);
                         postDelayed(mRetryRunnable, RETRY_INTERVAL_MS);
@@ -489,6 +512,8 @@
                             mRules += IccUtils.bytesToHexString(response.payload)
                                     .toUpperCase(Locale.US);
                             if (isDataComplete()) {
+                                //TODO: here's where AccessRules are being updated from the psim
+                                // b/139133814
                                 mAccessRules.addAll(parseRules(mRules));
                                 if (mAIDInUse == ARAD) {
                                     mCheckedRules = true;
diff --git a/src/java/com/android/internal/telephony/uicc/UiccProfile.java b/src/java/com/android/internal/telephony/uicc/UiccProfile.java
index 116591a..e34f99d 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccProfile.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccProfile.java
@@ -55,6 +55,7 @@
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.RIL;
 import com.android.internal.telephony.SubscriptionController;
 import com.android.internal.telephony.cat.CatService;
 import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppType;
@@ -104,6 +105,7 @@
     private final UiccCard mUiccCard; //parent
     private CatService mCatService;
     private UiccCarrierPrivilegeRules mCarrierPrivilegeRules;
+    private boolean mDefaultAppsActivated;
     private boolean mDisposed = false;
 
     private RegistrantList mCarrierPrivilegeRegistrants = new RegistrantList();
@@ -136,6 +138,11 @@
     private IccRecords mIccRecords = null;
     private IccCardConstants.State mExternalState = IccCardConstants.State.UNKNOWN;
 
+    // The number of UiccApplications modem reported. It's different from mUiccApplications.length
+    // which is always CARD_MAX_APPS, and only updated when modem sends an update, and NOT updated
+    // during SIM refresh. It's currently only used to help identify empty profile.
+    private int mLastReportedNumOfUiccApplications;
+
     private final ContentObserver mProvisionCompleteContentObserver =
             new ContentObserver(new Handler()) {
                 @Override
@@ -312,7 +319,14 @@
             if (isGsm) {
                 mCurrentAppType = UiccController.APP_FAM_3GPP;
             } else {
-                mCurrentAppType = UiccController.APP_FAM_3GPP2;
+                //if CSIM application is not present, set current app to default app i.e 3GPP
+                UiccCardApplication newApp = null;
+                newApp = getApplication(UiccController.APP_FAM_3GPP2);
+                if (newApp != null) {
+                    mCurrentAppType = UiccController.APP_FAM_3GPP2;
+                } else {
+                    mCurrentAppType = UiccController.APP_FAM_3GPP;
+                }
             }
         }
     }
@@ -342,8 +356,8 @@
         String ccName = config.getString(CarrierConfigManager.KEY_CARRIER_NAME_STRING);
 
         String newCarrierName = null;
-        String currSpn = getServiceProviderName();
-        int nameSource = SubscriptionManager.NAME_SOURCE_SIM_SOURCE;
+        String currSpn = getServiceProviderName();  // Get the name from EF_SPN.
+        int nameSource = SubscriptionManager.NAME_SOURCE_SIM_SPN;
         // If carrier config is priority, use it regardless - the preference
         // and the name were both set by the carrier, so this is safe;
         // otherwise, if the SPN is priority but we don't have one *and* we have
@@ -352,10 +366,18 @@
             newCarrierName = ccName;
             nameSource = SubscriptionManager.NAME_SOURCE_CARRIER;
         } else if (TextUtils.isEmpty(currSpn)) {
-            // currSpn is empty and could not get name from carrier config; get name from carrier id
+            // currSpn is empty and could not get name from carrier config; get name from PNN or
+            // carrier id
             Phone phone = PhoneFactory.getPhone(mPhoneId);
             if (phone != null) {
-                newCarrierName = phone.getCarrierName();
+                String currPnn = phone.getPlmn();   // Get the name from EF_PNN.
+                if (!TextUtils.isEmpty(currPnn)) {
+                    newCarrierName = currPnn;
+                    nameSource = SubscriptionManager.NAME_SOURCE_SIM_PNN;
+                } else {
+                    newCarrierName = phone.getCarrierName();    // Get the name from carrier id.
+                    nameSource = SubscriptionManager.NAME_SOURCE_DEFAULT_SOURCE;
+                }
             }
         }
 
@@ -839,10 +861,11 @@
     public boolean isEmptyProfile() {
         // If there's no UiccCardApplication, it's an empty profile.
         // Empty profile is a valid case of eSIM (default boot profile).
-        for (UiccCardApplication app : mUiccApplications) {
-            if (app != null) return false;
-        }
-        return true;
+        // But we clear all apps of mUiccCardApplication to be null during refresh (see
+        // resetAppWithAid) but not mLastReportedNumOfUiccApplications.
+        // So if mLastReportedNumOfUiccApplications == 0, it means modem confirmed that we landed
+        // on empty profile.
+        return mLastReportedNumOfUiccApplications == 0;
     }
 
     @Override
@@ -939,6 +962,8 @@
 
             //update applications
             if (DBG) log(ics.mApplications.length + " applications");
+            mLastReportedNumOfUiccApplications = ics.mApplications.length;
+
             for (int i = 0; i < mUiccApplications.length; i++) {
                 if (mUiccApplications[i] == null) {
                     //Create newly added Applications
@@ -971,10 +996,59 @@
             }
 
             sanitizeApplicationIndexesLocked();
+
+            if (needsSimActivation()) {
+                if (ics.mCardState == CardState.CARDSTATE_PRESENT) {
+                    if (!mDefaultAppsActivated) {
+                        activateDefaultApps();
+                        mDefaultAppsActivated = true;
+                    }
+                } else {
+                    // SIM removed, reset activation flag to make sure
+                    // to re-run the activation at the next insertion
+                    mDefaultAppsActivated = false;
+                }
+            }
+
             updateIccAvailability(true);
         }
     }
 
+    private boolean needsSimActivation() {
+        if (mCi instanceof RIL) {
+            return ((RIL) mCi).needsOldRilFeature("simactivation");
+        }
+        return false;
+    }
+
+    private void activateDefaultApps() {
+        int gsmIndex = mGsmUmtsSubscriptionAppIndex;
+        int cdmaIndex = mCdmaSubscriptionAppIndex;
+
+        if (gsmIndex < 0 || cdmaIndex < 0) {
+            for (int i = 0; i < mUiccApplications.length; i++) {
+                if (mUiccApplications[i] == null) {
+                    continue;
+                }
+
+                AppType appType = mUiccApplications[i].getType();
+                if (gsmIndex < 0 &&
+                        (appType == AppType.APPTYPE_USIM || appType == AppType.APPTYPE_SIM)) {
+                    gsmIndex = i;
+                } else if (cdmaIndex < 0 &&
+                        (appType == AppType.APPTYPE_CSIM || appType == AppType.APPTYPE_RUIM)) {
+                    cdmaIndex = i;
+                }
+            }
+        }
+        if (gsmIndex >= 0) {
+            mCi.setUiccSubscription(gsmIndex, true, null);
+        }
+        if (cdmaIndex >= 0) {
+            mCi.setUiccSubscription(cdmaIndex, true, null);
+        }
+    }
+
     private void createAndUpdateCatServiceLocked() {
         if (mUiccApplications.length > 0 && mUiccApplications[0] != null) {
             // Initialize or Reinitialize CatService
diff --git a/src/java/com/android/internal/telephony/uicc/UiccSlot.java b/src/java/com/android/internal/telephony/uicc/UiccSlot.java
index b1e107a..f113cd6 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccSlot.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccSlot.java
@@ -61,6 +61,7 @@
     private int mLastRadioState = TelephonyManager.RADIO_POWER_UNAVAILABLE;
     private boolean mIsEuicc;
     private String mIccId;
+    private String mEid;
     private AnswerToReset mAtr;
     private int mPhoneId = INVALID_PHONE_ID;
     private boolean mIsRemovable;
@@ -145,6 +146,7 @@
             parseAtr(iss.atr);
             mCardState = iss.cardState;
             mIccId = iss.iccid;
+            mEid = iss.eid;
             mIsRemovable = isSlotRemovable(slotIndex);
             if (iss.slotState == IccSlotStatus.SlotState.SLOTSTATE_INACTIVE) {
                 // TODO: (b/79432584) evaluate whether should broadcast card state change
@@ -271,6 +273,10 @@
         }
     }
 
+    public String getEid() {
+        return mEid;
+    }
+
     public boolean isExtendedApduSupported() {
         return  (mAtr != null && mAtr.isExtendedApduSupported());
     }
@@ -424,6 +430,7 @@
         pw.println(" mIsEuicc=" + mIsEuicc);
         pw.println(" mLastRadioState=" + mLastRadioState);
         pw.println(" mIccId=" + mIccId);
+        pw.println(" mEid=" + mEid);
         pw.println(" mCardState=" + mCardState);
         if (mUiccCard != null) {
             pw.println(" mUiccCard=" + mUiccCard);
diff --git a/src/java/com/android/internal/telephony/uicc/euicc/EuiccCard.java b/src/java/com/android/internal/telephony/uicc/euicc/EuiccCard.java
index abc2c0e..12f2ead 100644
--- a/src/java/com/android/internal/telephony/uicc/euicc/EuiccCard.java
+++ b/src/java/com/android/internal/telephony/uicc/euicc/EuiccCard.java
@@ -752,17 +752,62 @@
         sendApdu(
                 newRequestProvider((RequestBuilder requestBuilder) -> {
                     Asn1Node bppNode = new Asn1Decoder(boundProfilePackage).nextNode();
+                    int actualLength = bppNode.getDataLength();
+                    int segmentedLength = 0;
                     // initialiseSecureChannelRequest (ES8+.InitialiseSecureChannel)
                     Asn1Node initialiseSecureChannelRequest = bppNode.getChild(
                             Tags.TAG_INITIALISE_SECURE_CHANNEL);
+                    segmentedLength += initialiseSecureChannelRequest.getEncodedLength();
                     // firstSequenceOf87 (ES8+.ConfigureISDP)
                     Asn1Node firstSequenceOf87 = bppNode.getChild(Tags.TAG_CTX_COMP_0);
+                    segmentedLength += firstSequenceOf87.getEncodedLength();
                     // sequenceOf88 (ES8+.StoreMetadata)
                     Asn1Node sequenceOf88 = bppNode.getChild(Tags.TAG_CTX_COMP_1);
                     List<Asn1Node> metaDataSeqs = sequenceOf88.getChildren(Tags.TAG_CTX_8);
-                    // sequenceOf86 (ES8+.LoadProfileElements #1)
+                    segmentedLength += sequenceOf88.getEncodedLength();
+                    // secondSequenceOf87 (ES8+.ReplaceSessionKeys), optional
+                    Asn1Node secondSequenceOf87 = null;
+                    if (bppNode.hasChild(Tags.TAG_CTX_COMP_2)) {
+                        secondSequenceOf87 = bppNode.getChild(Tags.TAG_CTX_COMP_2);
+                        segmentedLength += secondSequenceOf87.getEncodedLength();
+                    }
+                    // sequenceOf86 (ES8+.LoadProfileElements)
                     Asn1Node sequenceOf86 = bppNode.getChild(Tags.TAG_CTX_COMP_3);
                     List<Asn1Node> elementSeqs = sequenceOf86.getChildren(Tags.TAG_CTX_6);
+                    segmentedLength += sequenceOf86.getEncodedLength();
+
+                    if (mSpecVersion.compareTo(SGP22_V_2_1) >= 0) {
+                        // Per SGP.22 v2.1+ section 2.5.5, it's the LPA's job to "segment" the BPP
+                        // before sending it to the eUICC. This check was only instituted in SGP.22
+                        // v2.1 and higher. SGP.22 v2.0 doesn't mention this "segmentation" process
+                        // at all, or what the LPA should do in the case of unrecognized or missing
+                        // tags. Per section 3.1.3.3: "If the LPAd is unable to perform the
+                        // segmentation (e.g., because of an error in the BPP structure), ... the
+                        // LPAd SHALL perform the Sub-procedure "Profile Download and installation -
+                        // Download rejection" with reason code 'Load BPP execution error'." This
+                        // implies that if we detect an invalid BPP, we should short-circuit before
+                        // sending anything to the eUICC. There are two cases to account for:
+                        if (elementSeqs == null || elementSeqs.isEmpty()) {
+                            // 1. The BPP is missing a required tag. Upon calling bppNode.getChild,
+                            // an exception will occur if the expected tag is missing, though we
+                            // should make sure that the sequences are non-empty when appropriate as
+                            // well. A profile with no profile elements is invalid. This is
+                            // explicitly tested by SGP.23 case 4.4.25.2.1_03.
+                            throw new EuiccCardException("No profile elements in BPP");
+                        } else if (actualLength != segmentedLength) {
+                            // 2. The BPP came with extraneous tags other than what the spec
+                            // mandates. We keep track of the total length of the BPP and compare it
+                            // to the length of the segments we care about. If they're different,
+                            // we'll throw an exception to indicate this. This is explicitly tested
+                            // by SGP.23 case 4.4.25.2.1_05.
+                            throw new EuiccCardException(
+                                    "Actual BPP length ("
+                                            + actualLength
+                                            + ") does not match segmented length ("
+                                            + segmentedLength
+                                            + "), this must be due to a malformed BPP");
+                        }
+                    }
 
                     requestBuilder.addStoreData(bppNode.getHeadAsHex()
                             + initialiseSecureChannelRequest.toHex());
@@ -775,8 +820,8 @@
                         requestBuilder.addStoreData(metaDataSeqs.get(i).toHex());
                     }
 
-                    if (bppNode.hasChild(Tags.TAG_CTX_COMP_2)) {
-                        requestBuilder.addStoreData(bppNode.getChild(Tags.TAG_CTX_COMP_2).toHex());
+                    if (secondSequenceOf87 != null) {
+                        requestBuilder.addStoreData(secondSequenceOf87.toHex());
                     }
 
                     requestBuilder.addStoreData(sequenceOf86.getHeadAsHex());
diff --git a/src/java/com/android/internal/telephony/util/NotificationChannelController.java b/src/java/com/android/internal/telephony/util/NotificationChannelController.java
index df0b3de..329f3c6 100644
--- a/src/java/com/android/internal/telephony/util/NotificationChannelController.java
+++ b/src/java/com/android/internal/telephony/util/NotificationChannelController.java
@@ -44,6 +44,11 @@
     public static final String CHANNEL_ID_SMS = "sms";
     public static final String CHANNEL_ID_VOICE_MAIL = "voiceMail";
     public static final String CHANNEL_ID_WFC = "wfc";
+    /**
+     * This channel is for sim related notifications similar as CHANNEL_ID_SIM except that this is
+     * high priority while CHANNEL_ID_SIM is low priority.
+     */
+    public static final String CHANNEL_ID_SIM_HIGH_PRIORITY = "simHighPriority";
 
     /** deprecated channel, replaced with @see #CHANNEL_ID_MOBILE_DATA_STATUS */
     private static final String CHANNEL_ID_MOBILE_DATA_ALERT_DEPRECATED = "mobileDataAlert";
@@ -95,6 +100,9 @@
                 new NotificationChannel(CHANNEL_ID_WFC,
                         context.getText(R.string.notification_channel_wfc),
                         NotificationManager.IMPORTANCE_LOW),
+                new NotificationChannel(CHANNEL_ID_SIM_HIGH_PRIORITY,
+                        context.getText(R.string.notification_channel_sim_high_prio),
+                        NotificationManager.IMPORTANCE_HIGH),
                 alertChannel, mobileDataStatusChannel,
                 simChannel, callforwardChannel));
 
diff --git a/src/java/com/android/internal/telephony/util/TelephonyExtUtils.java b/src/java/com/android/internal/telephony/util/TelephonyExtUtils.java
new file mode 100644
index 0000000..ecfbfb5
--- /dev/null
+++ b/src/java/com/android/internal/telephony/util/TelephonyExtUtils.java
@@ -0,0 +1,302 @@
+/*
+ * Copyright (C) 2018 The LineageOS 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.util;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.AsyncTask;
+import android.os.IBinder.DeathRecipient;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemProperties;
+import android.telephony.SubscriptionManager;
+import android.util.Log;
+
+import com.android.internal.telephony.PhoneConstants;
+
+import org.codeaurora.internal.IExtTelephony;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.TimeUnit;
+
+public final class TelephonyExtUtils {
+    private static final boolean DEBUG = false;
+    private static final String TAG = "TelephonyExtUtils";
+
+    public static final String ACTION_UICC_MANUAL_PROVISION_STATUS_CHANGED =
+            "org.codeaurora.intent.action.ACTION_UICC_MANUAL_PROVISION_STATUS_CHANGED";
+
+    public static final String EXTRA_NEW_PROVISION_STATE = "newProvisionState";
+
+    private static final int ACTIVATE_TIME_OUT = 15000;
+    private static final String PROP_TIME_OUT = "sys.uicc.activate.timeout";
+
+    // This is the list of possible values that
+    // IExtTelephony.getCurrentUiccCardProvisioningStatus() can return
+    public static final int CARD_NOT_PRESENT = -2;
+    public static final int INVALID_STATE = -1;
+    public static final int NOT_PROVISIONED = 0;
+    public static final int PROVISIONED = 1;
+
+    private boolean mNoServiceAvailable = false;
+    private IExtTelephony mExtTelephony;
+
+    private static TelephonyExtUtils sInstance;
+
+    private final List<ProvisioningChangedListener> mListeners = new ArrayList<>();
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final String action = intent.getAction();
+            if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
+                if (DEBUG) Log.d(TAG, "Boot completed, registering service");
+                mNoServiceAvailable = getService() == null;
+            } else if (ACTION_UICC_MANUAL_PROVISION_STATUS_CHANGED.equals(action)) {
+                int slotId = intent.getIntExtra(PhoneConstants.PHONE_KEY,
+                        SubscriptionManager.INVALID_SIM_SLOT_INDEX);
+                boolean provisioned = intent.getIntExtra(EXTRA_NEW_PROVISION_STATE,
+                        NOT_PROVISIONED) == PROVISIONED;
+                if (DEBUG) Log.d(TAG, "Received ACTION_UICC_MANUAL_PROVISION_STATUS_CHANGED"
+                        + " on slotId: " + slotId + ", sub provisioned: " + provisioned);
+                notifyListeners(slotId, provisioned);
+            }
+        }
+    };
+
+    private final DeathRecipient mDeathRecipient = new DeathRecipient() {
+        @Override
+        public void binderDied() {
+            if (DEBUG) Log.d(TAG, "Binder died");
+            synchronized(TelephonyExtUtils.class) {
+                mExtTelephony.asBinder().unlinkToDeath(mDeathRecipient, 0);
+                mExtTelephony = null;
+            }
+        }
+    };
+
+    // This class is not supposed to be instantiated externally
+    private TelephonyExtUtils(Context context) {
+        if (DEBUG) Log.d(TAG, "Registering listeners!");
+
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(ACTION_UICC_MANUAL_PROVISION_STATUS_CHANGED);
+        intentFilter.addAction(Intent.ACTION_BOOT_COMPLETED);
+        context.registerReceiver(mReceiver, intentFilter);
+    }
+
+    /**
+     * Get an existing instance of the utils or create a new one if not existant
+     *
+     * @return An instance of this class
+     */
+    public static TelephonyExtUtils getInstance(Context context) {
+        if (sInstance == null) {
+            sInstance = new TelephonyExtUtils(context.getApplicationContext());
+        }
+        return sInstance;
+    }
+
+    /**
+     * Determines whether the ITelephonyExt service is available on the device
+     * Any result of the methods in this class are only valid if this method returns true
+     *
+     * @return true on success
+     */
+    public boolean hasService() {
+        return getService() != null;
+    }
+
+    /**
+     * Determines whether the SIM associated with the given SubscriptionId is provisioned
+     *
+     * @return true if the SIM associated with the given subId is provisioned
+     */
+    public boolean isSubProvisioned(int subId) {
+        return isSlotProvisioned(SubscriptionManager.getSlotIndex(subId));
+    }
+
+    /**
+     * Determines whether the given SIM is provisioned
+     *
+     * @return true if the SIM is provisioned
+     */
+    public boolean isSlotProvisioned(int slotId) {
+        return getCurrentUiccCardProvisioningStatus(slotId) == PROVISIONED;
+    }
+
+    /**
+     * Get the current provisioning status for a given SIM slot
+     *
+     * @return The provisioning status from the extension or INVALID_STATE if not possible
+     */
+    public int getCurrentUiccCardProvisioningStatus(int slotId) {
+        IExtTelephony service = getService();
+        if (service != null && slotId != SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
+            try {
+                return mExtTelephony.getCurrentUiccCardProvisioningStatus(slotId);
+            } catch (RemoteException ex) {
+                Log.e(TAG, "Failed to get provisioning status for slotId: " + slotId, ex);
+            }
+        }
+        return INVALID_STATE;
+    }
+
+    /**
+     * Activate the SIM card with the given slotId
+     *
+     * @return The result of the activation or -1
+     */
+    public int activateUiccCard(int slotId) {
+        return setUiccCardProvisioningStatus(PROVISIONED, slotId);
+    }
+
+    /**
+     * Deactivate the SIM card with the given slotId
+     *
+     * @return The result of the deactivation or -1
+     */
+    public int deactivateUiccCard(int slotId) {
+        return setUiccCardProvisioningStatus(NOT_PROVISIONED, slotId);
+    }
+
+    private int setUiccCardProvisioningStatus(int provStatus, int slotId) {
+        String actionStr;
+        switch (provStatus) {
+            case PROVISIONED:
+                actionStr = "Activating";
+                break;
+            case NOT_PROVISIONED:
+                actionStr = "Deactivating";
+                break;
+            default:
+                Log.e(TAG, "Invalid argument for setUiccCardProvisioningStatus " +
+                        "(provStatus=" + provStatus + ", slotId=" + slotId + ")");
+                return -1;
+        }
+
+        IExtTelephony service = getService();
+        if (service == null) {
+            return -1;
+        }
+
+        AsyncTask<Integer, Void, Integer> task = new AsyncTask<Integer, Void, Integer>() {
+            @Override
+            protected Integer doInBackground(Integer... params) {
+                try {
+                    return params[0] == PROVISIONED
+                            ? mExtTelephony.activateUiccCard(params[1])
+                            : mExtTelephony.deactivateUiccCard(params[1]);
+                } catch (RemoteException ex) {
+                    Log.e(TAG, actionStr + " sub failed for slotId: " + params[1]);
+                }
+                return -1;
+            }
+        };
+
+        try {
+            return task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, provStatus, slotId)
+                    .get(ACTIVATE_TIME_OUT, TimeUnit.MILLISECONDS);
+        } catch (TimeoutException ex) {
+            Log.e(TAG, actionStr + " sub timed out for slotId: " + slotId);
+            SystemProperties.set(PROP_TIME_OUT, Integer.toString(slotId + 1));
+        } catch (Exception ex) {
+            Log.e(TAG, actionStr + " sub task failed for slotId: " + slotId);
+        }
+
+        return -1;
+    }
+
+    /**
+     * Add a ProvisioningChangedListener to get notified in case of provisioning changes
+     */
+    public void addListener(ProvisioningChangedListener listener) {
+        if (listener != null) {
+            mListeners.remove(listener);
+            mListeners.add(listener);
+        }
+    }
+
+    /**
+     * Remove a ProvisioningChangedListener to not get notified of provisioning changes anymore
+     */
+    public void removeListener(ProvisioningChangedListener listener) {
+        if (listener != null) {
+            mListeners.remove(listener);
+        }
+    }
+
+    /**
+     * Notify all registered listeners about provisioning changes
+     */
+    private void notifyListeners(int slotId, boolean provisioned) {
+        for (ProvisioningChangedListener listener : mListeners) {
+            listener.onProvisioningChanged(slotId, provisioned);
+        }
+    }
+
+    /**
+     * Helper method to get an already instantiated service or instantiate it
+     *
+     * @return a valid service instance or null
+     */
+    private IExtTelephony getService() {
+        // We already tried to get the service but weren't successful, so just return null here
+        if (mNoServiceAvailable) {
+            if (DEBUG) Log.v(TAG, "Already tried to get a service without success, returning!");
+            return null;
+        }
+
+        if (DEBUG) Log.d(TAG, "Retrieving the service");
+
+        // Instead of getting a new service instance, return an already existing one here
+        if (mExtTelephony != null) {
+            if (DEBUG) Log.d(TAG, "Returning cached service instance");
+            return mExtTelephony;
+        }
+
+        synchronized(TelephonyExtUtils.class) {
+            try {
+                mExtTelephony =
+                        IExtTelephony.Stub.asInterface(ServiceManager.getService("extphone"));
+                if (mExtTelephony != null) {
+                    mExtTelephony.asBinder().linkToDeath(mDeathRecipient, 0);
+                }
+            } catch (NoClassDefFoundError ex) {
+                // Ignore, device does not ship with telephony-ext
+                Log.d(TAG, "Failed to get telephony extension service!");
+                mNoServiceAvailable = true;
+            } catch (RemoteException ex) {
+                Log.d(TAG, "linkToDeath failed!");
+            }
+        }
+
+        return mExtTelephony;
+    }
+
+    /**
+     * Interface definition so we can register callbacks to get the provisioning status
+     *  whenever it changes
+     */
+    public interface ProvisioningChangedListener {
+        public void onProvisioningChanged(int slotId, boolean isProvisioned);
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CbGeoUtilsTest.java b/tests/telephonytests/src/com/android/internal/telephony/CbGeoUtilsTest.java
new file mode 100644
index 0000000..787cb43
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/CbGeoUtilsTest.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.internal.telephony.CbGeoUtils.Circle;
+import com.android.internal.telephony.CbGeoUtils.Geometry;
+import com.android.internal.telephony.CbGeoUtils.LatLng;
+import com.android.internal.telephony.CbGeoUtils.Polygon;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class CbGeoUtilsTest {
+    @Test
+    public void testDistanceBetweenTwoLatLng() {
+        LatLng latlng1 = new LatLng(37.428402, -122.084238);
+        LatLng latlng2 = new LatLng(37.399525, -121.946445);
+        double distInMeter = latlng1.distance(latlng2);
+        assertThat(distInMeter).isWithin(0.1).of(12586.1);
+    }
+
+    @Test
+    public void testParseLatLngFromString() {
+        LatLng latlng = CbGeoUtils.parseLatLngFromString("-80.1234, 179.5678");
+        assertThat(latlng.lat).isWithin(CbGeoUtils.EPS).of(-80.1234);
+        assertThat(latlng.lng).isWithin(CbGeoUtils.EPS).of(179.5678);
+    }
+
+    @Test
+    public void testEncodeGeometries() {
+        List<Geometry> geo = Arrays.asList(
+                new Polygon(Arrays.asList(
+                        new LatLng(11.11, 22.22),
+                        new LatLng(33.33, 44.44),
+                        new LatLng(55.55, -56.0))),
+                new Circle(new LatLng(15.123, 123.456), 314));
+
+        // Encode a list of geometry objects.
+        String encodedStr = CbGeoUtils.encodeGeometriesToString(geo);
+
+        // Decode and verify the geometry objects.
+        List<Geometry> decodedGeo = CbGeoUtils.parseGeometriesFromString(encodedStr);
+        assertThat(decodedGeo.size()).isEqualTo(2);
+
+        // The first one is polygon with three vertexes
+        assertThat(geo.get(0)).isInstanceOf(CbGeoUtils.Polygon.class);
+        CbGeoUtils.Polygon polygon = (Polygon) geo.get(0);
+        List<LatLng> vertexes = polygon.getVertices();
+        assertThat(vertexes.size()).isEqualTo(3);
+        LatLng p = vertexes.get(0);
+        assertThat(p.lat).isWithin(CbGeoUtils.EPS).of(11.11);
+        assertThat(p.lng).isWithin(CbGeoUtils.EPS).of(22.22);
+        p = vertexes.get(1);
+        assertThat(p.lat).isWithin(CbGeoUtils.EPS).of(33.33);
+        assertThat(p.lng).isWithin(CbGeoUtils.EPS).of(44.44);
+        p = vertexes.get(2);
+        assertThat(p.lat).isWithin(CbGeoUtils.EPS).of(55.55);
+        assertThat(p.lng).isWithin(CbGeoUtils.EPS).of(-56.0);
+
+        // THe second one is circle.
+        assertThat(geo.get(1)).isInstanceOf(CbGeoUtils.Circle.class);
+        Circle circle = (Circle) geo.get(1);
+        p = circle.getCenter();
+        assertThat(p.lat).isWithin(CbGeoUtils.EPS).of(15.123);
+        assertThat(p.lng).isWithin(CbGeoUtils.EPS).of(123.456);
+        assertThat(circle.getRadius()).isWithin(CbGeoUtils.EPS).of(314);
+    }
+
+    @Test
+    public void testParseGeometriesFromString() {
+        String geometriesStr =
+                "polygon|11.11,22.22|33.33, 44.44| 55.55, -56.0; circle|15.123, 123.456|314";
+        List<Geometry> geo = CbGeoUtils.parseGeometriesFromString(geometriesStr);
+
+        assertThat(geo.size()).isEqualTo(2);
+
+        // The first one is polygon with three vertexes
+        assertThat(geo.get(0)).isInstanceOf(CbGeoUtils.Polygon.class);
+        CbGeoUtils.Polygon polygon = (Polygon) geo.get(0);
+        List<LatLng> vertexes = polygon.getVertices();
+        assertThat(vertexes.size()).isEqualTo(3);
+        LatLng p = vertexes.get(0);
+        assertThat(p.lat).isWithin(CbGeoUtils.EPS).of(11.11);
+        assertThat(p.lng).isWithin(CbGeoUtils.EPS).of(22.22);
+        p = vertexes.get(1);
+        assertThat(p.lat).isWithin(CbGeoUtils.EPS).of(33.33);
+        assertThat(p.lng).isWithin(CbGeoUtils.EPS).of(44.44);
+        p = vertexes.get(2);
+        assertThat(p.lat).isWithin(CbGeoUtils.EPS).of(55.55);
+        assertThat(p.lng).isWithin(CbGeoUtils.EPS).of(-56.0);
+
+        // THe second one is circle.
+        assertThat(geo.get(1)).isInstanceOf(CbGeoUtils.Circle.class);
+        Circle circle = (Circle) geo.get(1);
+        p = circle.getCenter();
+        assertThat(p.lat).isWithin(CbGeoUtils.EPS).of(15.123);
+        assertThat(p.lng).isWithin(CbGeoUtils.EPS).of(123.456);
+        assertThat(circle.getRadius()).isWithin(CbGeoUtils.EPS).of(314);
+    }
+
+    @Test
+    public void testPointInPolygon() {
+        List<LatLng> vertex = Arrays.asList(
+                new LatLng(-1, 0),
+                new LatLng(0, 1),
+                new LatLng(1, 0),
+                new LatLng(0, -1));
+        Polygon polygon = new Polygon(vertex);
+
+        assertThat(polygon.contains(new LatLng(0, 0))).isTrue();
+
+        assertThat(polygon.contains(new LatLng(0.5, 0.5))).isTrue();
+
+        assertThat(polygon.contains(new LatLng(-2, -1))).isFalse();
+
+        assertThat(polygon.contains(new LatLng(1.0001, 1.0001))).isFalse();
+    }
+
+    @Test
+    public void testPointInPolygon_crossing180thMeridian() {
+        List<LatLng> vertices = Arrays.asList(
+                new LatLng(68.7153, 176.76038),
+                new LatLng(68.69982, -179.61491),
+                new LatLng(68.09107, -177.87357),
+                new LatLng(67.51155, -179.73498),
+                new LatLng(66.69957, -178.76818),
+                new LatLng(66.7126, 177.43054),
+                new LatLng(67.95902, 178.33927));
+
+        Polygon polygon = new Polygon(vertices);
+
+        // Verify the points are inside the polygon(manually check in google map).
+        assertThat(polygon.contains(new LatLng(68.65294, 177.16205))).isTrue();
+        assertThat(polygon.contains(new LatLng(68.60522, 178.83294))).isTrue();
+        assertThat(polygon.contains(new LatLng(68.63098, -179.90943))).isTrue();
+        assertThat(polygon.contains(new LatLng(67.51219, -179.74427))).isTrue();
+        assertThat(polygon.contains(new LatLng(67.91933, 179.46802))).isTrue();
+
+        // Verify the points are outside the polygon(manually check in google map).
+        assertThat(polygon.contains(new LatLng(67.50498, -179.48277))).isFalse();
+        assertThat(polygon.contains(new LatLng(67.95463, 178.23206))).isFalse();
+    }
+
+    @Test
+    public void testPointInPolygon_crossing0thMeridian() {
+        List<LatLng> vertices = Arrays.asList(
+                new LatLng(51.79327, -1.00339),
+                new LatLng(51.79327, 1.00339),
+                new LatLng(49.79327, 1.00339),
+                new LatLng(49.79327, -2.1234));
+
+        Polygon polygon = new Polygon(vertices);
+
+        // Verify the points are inside the polygon(manually check on google map).
+        assertThat(polygon.contains(new LatLng(51.78091, 0.97431))).isTrue();
+        assertThat(polygon.contains(new LatLng(49.97102, 0.72206))).isTrue();
+        assertThat(polygon.contains(new LatLng(50.82538, -0.17881))).isTrue();
+        assertThat(polygon.contains(new LatLng(51.50735, -0.12775))).isTrue();
+
+        // Verify the points are outside the polygon(manually check on google map).
+        assertThat(polygon.contains(new LatLng(51.28268, 1.06951))).isFalse();
+        assertThat(polygon.contains(new LatLng(50.30352, -1.94073))).isFalse();
+        assertThat(polygon.contains(new LatLng(51.74758, -1.27057))).isFalse();
+    }
+
+    @Test
+    public void testPointInCircle() {
+        Circle circle = new Circle(new LatLng(37.42331, -122.08636), 500);
+
+        // ~ 307 meters
+        assertThat(circle.contains(new LatLng(37.42124, -122.08405))).isTrue();
+
+        // ~ 451 meters
+        assertThat(circle.contains(new LatLng(37.42093, -122.08222))).isTrue();
+
+        // ~ 622 meters
+        assertThat(circle.contains(new LatLng(37.41807, -122.08389))).isFalse();
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CellularNetworkValidatorTest.java b/tests/telephonytests/src/com/android/internal/telephony/CellularNetworkValidatorTest.java
new file mode 100644
index 0000000..9723150
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/CellularNetworkValidatorTest.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony;
+
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.verify;
+
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import android.os.HandlerThread;
+import android.telephony.PhoneCapability;
+import android.telephony.SubscriptionManager;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class CellularNetworkValidatorTest extends TelephonyTest {
+    private boolean mValidated = false;
+    private CellularNetworkValidator mValidatorUT;
+    private int mValidatedSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+    private static final PhoneCapability CAPABILITY_WITH_VALIDATION_SUPPORTED =
+            new PhoneCapability(1, 1, 0, null, true);
+    private static final PhoneCapability CAPABILITY_WITHOUT_VALIDATION_SUPPORTED =
+            new PhoneCapability(1, 1, 0, null, false);
+    private HandlerThread mHandlerThread;
+
+    CellularNetworkValidator.ValidationCallback mCallback = (validated, subId) -> {
+        mValidated = validated;
+        mValidatedSubId = subId;
+    };
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp(getClass().getSimpleName());
+
+        doReturn(CAPABILITY_WITH_VALIDATION_SUPPORTED).when(mPhoneConfigurationManager)
+                .getCurrentPhoneCapability();
+        doReturn(true).when(mSubscriptionController).isActiveSubId(anyInt());
+
+        mHandlerThread = new HandlerThread("PhoneSwitcherTestThread") {
+            @Override
+            public void onLooperPrepared() {
+                mValidatorUT = new CellularNetworkValidator(mContext);
+            }
+        };
+
+        mHandlerThread.start();
+        waitABit();
+
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    /**
+     * Test that a single phone case results in our phone being active and the RIL called
+     */
+    @Test
+    @SmallTest
+    public void testValidationSupported() throws Exception {
+        doReturn(CAPABILITY_WITH_VALIDATION_SUPPORTED).when(mPhoneConfigurationManager)
+                .getCurrentPhoneCapability();
+        assertTrue(mValidatorUT.isValidationFeatureSupported());
+
+        doReturn(CAPABILITY_WITHOUT_VALIDATION_SUPPORTED).when(mPhoneConfigurationManager)
+                .getCurrentPhoneCapability();
+        assertFalse(mValidatorUT.isValidationFeatureSupported());
+    }
+
+    /**
+     * Test that a single phone case results in our phone being active and the RIL called
+     */
+    @Test
+    @SmallTest
+    public void testValidateSuccess() throws Exception {
+        int subId = 1;
+        int timeout = 1000;
+        NetworkRequest expectedRequest = new NetworkRequest.Builder()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+                .setNetworkSpecifier(String.valueOf(subId))
+                .build();
+
+        mValidatorUT.validate(subId, timeout, true, mCallback);
+
+        assertTrue(mValidatorUT.isValidating());
+        assertEquals(subId, mValidatorUT.getSubIdInValidation());
+        verify(mConnectivityManager).requestNetwork(
+                eq(expectedRequest), eq(mValidatorUT.mNetworkCallback), any());
+
+        mValidatorUT.mNetworkCallback.onCapabilitiesChanged(null, new NetworkCapabilities()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED));
+
+        assertTrue(mValidated);
+        assertEquals(subId, mValidatedSubId);
+        verify(mConnectivityManager).unregisterNetworkCallback(eq(mValidatorUT.mNetworkCallback));
+        assertFalse(mValidatorUT.mHandler.hasCallbacks(mValidatorUT.mTimeoutCallback));
+        assertFalse(mValidatorUT.isValidating());
+        assertEquals(SubscriptionManager.INVALID_SUBSCRIPTION_ID,
+                mValidatorUT.getSubIdInValidation());
+    }
+
+    /**
+     * Test that a single phone case results in our phone being active and the RIL called
+     */
+    @Test
+    @SmallTest
+    public void testValidateTimeout() throws Exception {
+        int subId = 1;
+        int timeout = 100;
+        NetworkRequest expectedRequest = new NetworkRequest.Builder()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+                .setNetworkSpecifier(String.valueOf(subId))
+                .build();
+
+        mValidatorUT.validate(subId, timeout, true, mCallback);
+
+        assertTrue(mValidatorUT.isValidating());
+        assertEquals(subId, mValidatorUT.getSubIdInValidation());
+        verify(mConnectivityManager).requestNetwork(
+                eq(expectedRequest), eq(mValidatorUT.mNetworkCallback), any());
+
+        // Wait for timeout.
+        waitABit();
+
+        assertFalse(mValidated);
+        assertEquals(subId, mValidatedSubId);
+        verify(mConnectivityManager).unregisterNetworkCallback(eq(mValidatorUT.mNetworkCallback));
+        assertFalse(mValidatorUT.mHandler.hasCallbacks(mValidatorUT.mTimeoutCallback));
+        assertFalse(mValidatorUT.isValidating());
+        assertEquals(SubscriptionManager.INVALID_SUBSCRIPTION_ID,
+                mValidatorUT.getSubIdInValidation());
+    }
+
+    /**
+     * Test that a single phone case results in our phone being active and the RIL called
+     */
+    @Test
+    @SmallTest
+    public void testValidateFailure() throws Exception {
+        int subId = 1;
+        int timeout = 100;
+        NetworkRequest expectedRequest = new NetworkRequest.Builder()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+                .setNetworkSpecifier(String.valueOf(subId))
+                .build();
+
+        mValidatorUT.validate(subId, timeout, true, mCallback);
+
+        assertTrue(mValidatorUT.isValidating());
+        assertEquals(subId, mValidatorUT.getSubIdInValidation());
+        verify(mConnectivityManager).requestNetwork(
+                eq(expectedRequest), eq(mValidatorUT.mNetworkCallback), any());
+
+        mValidatorUT.mNetworkCallback.onUnavailable();
+
+        assertFalse(mValidated);
+        assertEquals(subId, mValidatedSubId);
+        verify(mConnectivityManager).unregisterNetworkCallback(eq(mValidatorUT.mNetworkCallback));
+        assertFalse(mValidatorUT.mHandler.hasCallbacks(mValidatorUT.mTimeoutCallback));
+        assertFalse(mValidatorUT.isValidating());
+        assertEquals(SubscriptionManager.INVALID_SUBSCRIPTION_ID,
+                mValidatorUT.getSubIdInValidation());
+    }
+
+    /**
+     * Test that a single phone case results in our phone being active and the RIL called
+     */
+    @Test
+    @SmallTest
+    public void testNetworkAvailableNotValidated() throws Exception {
+        int subId = 1;
+        int timeout = 100;
+        NetworkRequest expectedRequest = new NetworkRequest.Builder()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+                .setNetworkSpecifier(String.valueOf(subId))
+                .build();
+
+        mValidatorUT.validate(subId, timeout, true, mCallback);
+
+        assertTrue(mValidatorUT.isValidating());
+        assertEquals(subId, mValidatorUT.getSubIdInValidation());
+        verify(mConnectivityManager).requestNetwork(
+                eq(expectedRequest), eq(mValidatorUT.mNetworkCallback), any());
+
+        mValidatorUT.mNetworkCallback.onAvailable(new Network(100));
+        // Wait for timeout.
+        waitABit();
+
+        assertFalse(mValidated);
+        assertEquals(subId, mValidatedSubId);
+        verify(mConnectivityManager).unregisterNetworkCallback(eq(mValidatorUT.mNetworkCallback));
+        assertFalse(mValidatorUT.mHandler.hasCallbacks(mValidatorUT.mTimeoutCallback));
+        assertFalse(mValidatorUT.isValidating());
+        assertEquals(SubscriptionManager.INVALID_SUBSCRIPTION_ID,
+                mValidatorUT.getSubIdInValidation());
+    }
+
+    private void waitABit() {
+        try {
+            Thread.sleep(250);
+        } catch (Exception e) {
+        }
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/DefaultPhoneNotifierTest.java b/tests/telephonytests/src/com/android/internal/telephony/DefaultPhoneNotifierTest.java
index 3e1ecbe..be94c47 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/DefaultPhoneNotifierTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/DefaultPhoneNotifierTest.java
@@ -265,15 +265,4 @@
         assertEquals(3, cellLocationCapture.getValue().getInt("cid"));
         assertEquals(5, cellLocationCapture.getValue().getInt("psc"));
     }
-
-    @Test @SmallTest
-    public void testNotifyOtaspChanged() throws Exception {
-        mDefaultPhoneNotifierUT.notifyOtaspChanged(mPhone, TelephonyManager.OTASP_NEEDED);
-        verify(mTelephonyRegisteryMock).notifyOtaspChanged(eq(mPhone.getSubId()),
-                eq(TelephonyManager.OTASP_NEEDED));
-
-        mDefaultPhoneNotifierUT.notifyOtaspChanged(mPhone, TelephonyManager.OTASP_UNKNOWN);
-        verify(mTelephonyRegisteryMock).notifyOtaspChanged(eq(mPhone.getSubId()),
-                eq(TelephonyManager.OTASP_UNKNOWN));
-    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/FakeTelephonyProvider.java b/tests/telephonytests/src/com/android/internal/telephony/FakeTelephonyProvider.java
index 48926b4..a9e4ed0 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/FakeTelephonyProvider.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/FakeTelephonyProvider.java
@@ -109,8 +109,9 @@
                     + SubscriptionManager.SUBSCRIPTION_TYPE + " INTEGER DEFAULT 0,"
                     + SubscriptionManager.WHITE_LISTED_APN_DATA + " INTEGER DEFAULT 0,"
                     + SubscriptionManager.GROUP_OWNER + " TEXT,"
-                    + SubscriptionManager.DATA_ENABLED_OVERRIDE_RULES + " TEXT"
-                    + SubscriptionManager.IMSI + " TEXT"
+                    + SubscriptionManager.DATA_ENABLED_OVERRIDE_RULES + " TEXT,"
+                    + SubscriptionManager.IMSI + " TEXT,"
+                    + SubscriptionManager.ACCESS_RULES_FROM_CARRIER_CONFIGS + " BLOB"
                     + ");";
         }
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java b/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java
index 8655279..a26967c 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java
@@ -64,8 +64,8 @@
 
 import com.android.internal.telephony.test.SimulatedCommands;
 import com.android.internal.telephony.uicc.IccCardApplicationStatus;
-import com.android.internal.telephony.uicc.IccException;
 import com.android.internal.telephony.uicc.IccRecords;
+import com.android.internal.telephony.uicc.IccVmNotSupportedException;
 import com.android.internal.telephony.uicc.UiccController;
 import com.android.internal.telephony.uicc.UiccProfile;
 import com.android.internal.telephony.uicc.UiccSlot;
@@ -169,6 +169,89 @@
 
     @Test
     @SmallTest
+    public void testGetMergedServiceState() throws Exception {
+        ServiceState imsServiceState = new ServiceState();
+
+        NetworkRegistrationInfo imsCsWwanRegInfo = new NetworkRegistrationInfo.Builder()
+                .setDomain(NetworkRegistrationInfo.DOMAIN_CS)
+                .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
+                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE)
+                .setRegistrationState(
+                        NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
+                .build();
+
+        NetworkRegistrationInfo imsPsWwanRegInfo = new NetworkRegistrationInfo.Builder()
+                .setDomain(NetworkRegistrationInfo.DOMAIN_PS)
+                .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
+                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE)
+                .setRegistrationState(
+                        NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
+                .build();
+
+        NetworkRegistrationInfo imsPsWlanRegInfo = new NetworkRegistrationInfo.Builder()
+                .setDomain(NetworkRegistrationInfo.DOMAIN_PS)
+                .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WLAN)
+                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_IWLAN)
+                .setRegistrationState(
+                        NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
+                .build();
+
+        imsServiceState.addNetworkRegistrationInfo(imsCsWwanRegInfo);
+        imsServiceState.addNetworkRegistrationInfo(imsPsWwanRegInfo);
+        imsServiceState.addNetworkRegistrationInfo(imsPsWlanRegInfo);
+
+        imsServiceState.setVoiceRegState(ServiceState.STATE_IN_SERVICE);
+        imsServiceState.setDataRegState(ServiceState.STATE_IN_SERVICE);
+        imsServiceState.setIwlanPreferred(true);
+        doReturn(imsServiceState).when(mImsPhone).getServiceState();
+
+        replaceInstance(Phone.class, "mImsPhone", mPhoneUT, mImsPhone);
+
+        ServiceState serviceState = new ServiceState();
+
+        NetworkRegistrationInfo csWwanRegInfo = new NetworkRegistrationInfo.Builder()
+                .setDomain(NetworkRegistrationInfo.DOMAIN_CS)
+                .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
+                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE)
+                .setRegistrationState(
+                        NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING)
+                .build();
+
+        NetworkRegistrationInfo psWwanRegInfo = new NetworkRegistrationInfo.Builder()
+                .setDomain(NetworkRegistrationInfo.DOMAIN_PS)
+                .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
+                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE)
+                .setRegistrationState(
+                        NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING)
+                .build();
+
+        NetworkRegistrationInfo psWlanRegInfo = new NetworkRegistrationInfo.Builder()
+                .setDomain(NetworkRegistrationInfo.DOMAIN_PS)
+                .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WLAN)
+                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_IWLAN)
+                .setRegistrationState(
+                        NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
+                .build();
+
+        serviceState.addNetworkRegistrationInfo(csWwanRegInfo);
+        serviceState.addNetworkRegistrationInfo(psWwanRegInfo);
+        serviceState.addNetworkRegistrationInfo(psWlanRegInfo);
+        serviceState.setVoiceRegState(ServiceState.STATE_OUT_OF_SERVICE);
+        serviceState.setDataRegState(ServiceState.STATE_IN_SERVICE);
+        serviceState.setIwlanPreferred(true);
+
+        mSST.mSS = serviceState;
+        mPhoneUT.mSST = mSST;
+
+        ServiceState mergedServiceState = mPhoneUT.getServiceState();
+
+        assertEquals(ServiceState.STATE_IN_SERVICE, mergedServiceState.getVoiceRegState());
+        assertEquals(ServiceState.STATE_IN_SERVICE, mergedServiceState.getDataRegState());
+        assertEquals(TelephonyManager.NETWORK_TYPE_IWLAN, mergedServiceState.getDataNetworkType());
+    }
+
+    @Test
+    @SmallTest
     public void testGetSubscriberIdForGsmPhone() {
         final String subscriberId = "123456789";
         IccRecords iccRecords = Mockito.mock(IccRecords.class);
@@ -482,18 +565,38 @@
         assertEquals(voiceMailNumber, mPhoneUT.getVoiceMailNumber());
 
         // voicemail number from sharedPreference
+        voiceMailNumber = "1234567893";
         mPhoneUT.setVoiceMailNumber("alphaTag", voiceMailNumber, null);
         ArgumentCaptor<Message> messageArgumentCaptor = ArgumentCaptor.forClass(Message.class);
         verify(mSimRecords).setVoiceMailNumber(eq("alphaTag"), eq(voiceMailNumber),
                 messageArgumentCaptor.capture());
 
+        // SIM does not support voicemail number (IccVmNotSupportedException) so should be saved in
+        // shared pref
         Message msg = messageArgumentCaptor.getValue();
         AsyncResult.forMessage(msg).exception =
-                new IccException("setVoiceMailNumber not implemented");
+                new IccVmNotSupportedException("setVoiceMailNumber not implemented");
         msg.sendToTarget();
         waitForMs(50);
 
         assertEquals(voiceMailNumber, mPhoneUT.getVoiceMailNumber());
+
+        // voicemail number from SIM
+        voiceMailNumber = "1234567894";
+        mPhoneUT.setVoiceMailNumber("alphaTag", voiceMailNumber, null);
+        messageArgumentCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(mSimRecords).setVoiceMailNumber(eq("alphaTag"), eq(voiceMailNumber),
+                messageArgumentCaptor.capture());
+
+        // successfully saved on SIM
+        msg = messageArgumentCaptor.getValue();
+        AsyncResult.forMessage(msg);
+        msg.sendToTarget();
+        waitForMs(50);
+
+        doReturn(voiceMailNumber).when(mSimRecords).getVoiceMailNumber();
+
+        assertEquals(voiceMailNumber, mPhoneUT.getVoiceMailNumber());
     }
 
     @FlakyTest
@@ -1002,3 +1105,4 @@
         assertEquals(msisdn, mPhoneUT.getLine1Number());
     }
 }
+
diff --git a/tests/telephonytests/src/com/android/internal/telephony/InboundSmsTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/InboundSmsTrackerTest.java
index ff473c5..e072a62 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/InboundSmsTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/InboundSmsTrackerTest.java
@@ -42,26 +42,27 @@
     private static final int FAKE_SEQUENCE_NUMBER = 3;
     private static final int FAKE_MESSAGE_COUNT = 5;
     private static final String FAKE_MESSAGE_BODY = "message body";
+    private static final int FAKE_SUBID = 0;
 
     @Before
     public void setUp() throws Exception {
         mInboundSmsTracker = new InboundSmsTracker(FAKE_PDU, FAKE_TIMESTAMP, FAKE_DEST_PORT, false,
                 FAKE_ADDRESS, FAKE_DISPLAY_ADDRESS, FAKE_REFERENCE_NUMBER, FAKE_SEQUENCE_NUMBER,
-                FAKE_MESSAGE_COUNT, false, FAKE_MESSAGE_BODY, false /* isClass0 */);
+                FAKE_MESSAGE_COUNT, false, FAKE_MESSAGE_BODY, false /* isClass0 */, FAKE_SUBID);
     }
 
     public static MatrixCursor createFakeCursor() {
         MatrixCursor mc = new MatrixCursor(
                 new String[]{"pdu", "seq", "dest", "date", "ref", "cnt", "addr", "id", "msg_body",
-                        "display_originating_addr"});
+                        "display_originating_addr", "sub_id"});
         mc.addRow(new Object[]{HexDump.toHexString(FAKE_PDU),
                 FAKE_SEQUENCE_NUMBER, FAKE_DEST_PORT, FAKE_TIMESTAMP,
                 FAKE_REFERENCE_NUMBER, FAKE_MESSAGE_COUNT, FAKE_ADDRESS, 1, FAKE_MESSAGE_BODY,
-                FAKE_DISPLAY_ADDRESS});
+                FAKE_DISPLAY_ADDRESS, FAKE_SUBID});
         mc.addRow(new Object[]{HexDump.toHexString(FAKE_PDU),
                 FAKE_SEQUENCE_NUMBER, FAKE_DEST_PORT, FAKE_TIMESTAMP,
                 FAKE_REFERENCE_NUMBER, FAKE_MESSAGE_COUNT, FAKE_ADDRESS, 2, FAKE_MESSAGE_BODY,
-                FAKE_DISPLAY_ADDRESS});
+                FAKE_DISPLAY_ADDRESS, FAKE_SUBID});
         mc.moveToFirst();
         return mc;
     }
@@ -82,6 +83,7 @@
         assertEquals(FAKE_MESSAGE_BODY, mInboundSmsTracker.getMessageBody());
         assertEquals(FAKE_DISPLAY_ADDRESS, mInboundSmsTracker.getDisplayAddress());
         assertEquals(false, mInboundSmsTracker.isClass0());
+        assertEquals(FAKE_SUBID, mInboundSmsTracker.getSubId());
 
         String[] args = new String[]{"123"};
         mInboundSmsTracker.setDeleteWhere(InboundSmsHandler.SELECT_BY_ID, args);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/LocaleTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/LocaleTrackerTest.java
index a14129d..d161246 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/LocaleTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/LocaleTrackerTest.java
@@ -44,14 +44,19 @@
 import org.mockito.ArgumentCaptor;
 
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 
 public class LocaleTrackerTest extends TelephonyTest {
 
     private static final String US_MCC = "310";
+    private static final String LIECHTENSTEIN_MCC = "295";
+
     private static final String FAKE_MNC = "123";
-    private static final String US_COUNTRY_CODE = "us";
+
     private static final String COUNTRY_CODE_UNAVAILABLE = "";
+    private static final String US_COUNTRY_CODE = "us";
+    private static final String LIECHTENSTEIN_COUNTRY_CODE = "li";
 
     private LocaleTracker mLocaleTracker;
 
@@ -156,11 +161,12 @@
     @Test
     @SmallTest
     public void testNoSim() throws Exception {
+        // updateOperatorNumeric("") will not trigger an instantaneous country change
         mLocaleTracker.updateOperatorNumeric("");
         sendGsmCellInfo();
         sendServiceState(ServiceState.STATE_EMERGENCY_ONLY);
         assertEquals(US_COUNTRY_CODE, mLocaleTracker.getCurrentCountry());
-        verifyCountryCodeNotified(new String[]{COUNTRY_CODE_UNAVAILABLE, US_COUNTRY_CODE});
+        verifyCountryCodeNotified(new String[]{US_COUNTRY_CODE});
         assertTrue(mLocaleTracker.isTracking());
     }
 
@@ -183,11 +189,11 @@
         verifyCountryCodeNotified(new String[]{COUNTRY_CODE_UNAVAILABLE, US_COUNTRY_CODE});
         assertFalse(mLocaleTracker.isTracking());
 
+        // updateOperatorNumeric("") will not trigger an instantaneous country change
         mLocaleTracker.updateOperatorNumeric("");
         waitForHandlerAction(mLocaleTracker, 100);
-        assertEquals(COUNTRY_CODE_UNAVAILABLE, mLocaleTracker.getCurrentCountry());
-        verifyCountryCodeNotified(new String[]{COUNTRY_CODE_UNAVAILABLE, US_COUNTRY_CODE,
-                COUNTRY_CODE_UNAVAILABLE});
+        assertEquals(US_COUNTRY_CODE, mLocaleTracker.getCurrentCountry());
+        verifyCountryCodeNotified(new String[]{COUNTRY_CODE_UNAVAILABLE, US_COUNTRY_CODE});
         sendServiceState(ServiceState.STATE_POWER_OFF);
         assertFalse(mLocaleTracker.isTracking());
     }
@@ -211,6 +217,70 @@
 
     @Test
     @SmallTest
+    public void testToggleAirplaneModeOosPlmn() throws Exception {
+        sendServiceState(ServiceState.STATE_POWER_OFF);
+        mLocaleTracker.updateOperatorNumeric("");
+        waitForHandlerAction(mLocaleTracker, 100);
+        assertEquals(COUNTRY_CODE_UNAVAILABLE, mLocaleTracker.getCurrentCountry());
+        verifyCountryCodeNotified(new String[]{COUNTRY_CODE_UNAVAILABLE});
+        assertFalse(mLocaleTracker.isTracking());
+
+        // Override the setUp() function and return an empty list for CellInfo
+        doAnswer(invocation -> {
+            Message m = invocation.getArgument(1);
+            AsyncResult.forMessage(m, Collections.emptyList(), null);
+            m.sendToTarget();
+            return null; }).when(mPhone).requestCellInfoUpdate(any(), any());
+
+        sendServiceState(ServiceState.STATE_OUT_OF_SERVICE);
+        waitForHandlerAction(mLocaleTracker, 100);
+        assertTrue(mLocaleTracker.isTracking());
+        waitForHandlerAction(mLocaleTracker, 100);
+        assertEquals(COUNTRY_CODE_UNAVAILABLE, mLocaleTracker.getCurrentCountry());
+
+        mLocaleTracker.updateOperatorNumeric(US_MCC + FAKE_MNC);
+        assertEquals(US_COUNTRY_CODE, mLocaleTracker.getCurrentCountry());
+        verifyCountryCodeNotified(new String[]{COUNTRY_CODE_UNAVAILABLE, US_COUNTRY_CODE});
+
+        mLocaleTracker.updateOperatorNumeric("");
+        waitForHandlerAction(mLocaleTracker, 100);
+        assertEquals(US_COUNTRY_CODE, mLocaleTracker.getCurrentCountry());
+        verifyCountryCodeNotified(new String[]{COUNTRY_CODE_UNAVAILABLE, US_COUNTRY_CODE});
+
+        mLocaleTracker.updateOperatorNumeric(LIECHTENSTEIN_MCC + FAKE_MNC);
+        waitForHandlerAction(mLocaleTracker, 100);
+        assertEquals(LIECHTENSTEIN_COUNTRY_CODE, mLocaleTracker.getCurrentCountry());
+        verifyCountryCodeNotified(new String[]{
+                COUNTRY_CODE_UNAVAILABLE, US_COUNTRY_CODE, LIECHTENSTEIN_COUNTRY_CODE});
+    }
+
+    @Test
+    @SmallTest
+    public void testToggleAirplaneModeNoCellInfo() throws Exception {
+        sendServiceState(ServiceState.STATE_POWER_OFF);
+        mLocaleTracker.updateOperatorNumeric("");
+        waitForHandlerAction(mLocaleTracker, 100);
+        assertEquals(COUNTRY_CODE_UNAVAILABLE, mLocaleTracker.getCurrentCountry());
+        verifyCountryCodeNotified(new String[]{COUNTRY_CODE_UNAVAILABLE});
+        assertFalse(mLocaleTracker.isTracking());
+
+        // Override the setUp() function and return an empty list for CellInfo
+        doAnswer(invocation -> {
+            Message m = invocation.getArgument(1);
+            AsyncResult.forMessage(m, Collections.emptyList(), null);
+            m.sendToTarget();
+            return null; }).when(mPhone).requestCellInfoUpdate(any(), any());
+
+        sendServiceState(ServiceState.STATE_OUT_OF_SERVICE);
+        waitForHandlerAction(mLocaleTracker, 100);
+        assertTrue(mLocaleTracker.isTracking());
+        waitForHandlerAction(mLocaleTracker, 100);
+        assertEquals(COUNTRY_CODE_UNAVAILABLE, mLocaleTracker.getCurrentCountry());
+    }
+
+
+    @Test
+    @SmallTest
     public void testGetCellInfoDelayTime() throws Exception {
         assertEquals(2000, LocaleTracker.getCellInfoDelayTime(0));
         assertEquals(2000, LocaleTracker.getCellInfoDelayTime(1));
diff --git a/tests/telephonytests/src/com/android/internal/telephony/MultiSimSettingControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/MultiSimSettingControllerTest.java
index 74bbb82..91ed608 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/MultiSimSettingControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/MultiSimSettingControllerTest.java
@@ -83,19 +83,19 @@
             "T-mobile", 0, 255, "12345", 0, null, "310", "260",
             "156", false, null, null, -1, false, mGroupUuid1.toString(), false,
             TelephonyManager.UNKNOWN_CARRIER_ID, SubscriptionManager.PROFILE_CLASS_DEFAULT,
-            SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM, null);
+            SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM, null, null);
 
     private SubscriptionInfo mSubInfo3 = new SubscriptionInfo(3, "subInfo3 IccId", -1, "T-mobile",
             "T-mobile", 0, 255, "12345", 0, null, "310", "260",
             "156", false, null, null, -1, false, mGroupUuid1.toString(), false,
             TelephonyManager.UNKNOWN_CARRIER_ID, SubscriptionManager.PROFILE_CLASS_DEFAULT,
-            SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM, null);
+            SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM, null, null);
 
     private SubscriptionInfo mSubInfo4 = new SubscriptionInfo(4, "subInfo4 IccId", -1, "T-mobile",
             "T-mobile", 0, 255, "12345", 0, null, "310", "260",
             "156", false, null, null, -1, false, mGroupUuid1.toString(), false,
             TelephonyManager.UNKNOWN_CARRIER_ID, SubscriptionManager.PROFILE_CLASS_DEFAULT,
-            SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM, null);
+            SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM, null, null);
 
     @Before
     public void setUp() throws Exception {
@@ -118,6 +118,7 @@
         List<SubscriptionInfo> infoList = Arrays.asList(mSubInfo1, mSubInfo2);
         doReturn(infoList).when(mSubControllerMock)
                 .getActiveSubscriptionInfoList(anyString());
+        doReturn(new int[]{1, 2}).when(mSubControllerMock).getActiveSubIdList(anyBoolean());
 
         mPhones = new Phone[] {mPhoneMock1, mPhoneMock2};
         doReturn(mDataEnabledSettingsMock1).when(mPhoneMock1).getDataEnabledSettings();
@@ -175,6 +176,7 @@
         doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID).when(mPhoneMock2).getSubId();
         List<SubscriptionInfo> infoList = Arrays.asList(mSubInfo1);
         doReturn(infoList).when(mSubControllerMock).getActiveSubscriptionInfoList(anyString());
+        doReturn(new int[]{1}).when(mSubControllerMock).getActiveSubIdList(anyBoolean());
 
         // Mark subscription ready as false. The below sub info change should be ignored.
         mMultiSimSettingControllerUT.notifySubscriptionInfoChanged();
@@ -184,6 +186,7 @@
         verify(mSubControllerMock, never()).setDefaultSmsSubId(anyInt());
 
         mMultiSimSettingControllerUT.notifyAllSubscriptionLoaded();
+        mMultiSimSettingControllerUT.notifyCarrierConfigChanged(0, 1);
         waitABit();
 
         // Sub 1 should be default sub silently.
@@ -210,8 +213,10 @@
         doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID).when(mPhoneMock2).getSubId();
         List<SubscriptionInfo> infoList = Arrays.asList(mSubInfo1);
         doReturn(infoList).when(mSubControllerMock).getActiveSubscriptionInfoList(anyString());
+        doReturn(new int[]{1}).when(mSubControllerMock).getActiveSubIdList(anyBoolean());
 
         mMultiSimSettingControllerUT.notifyAllSubscriptionLoaded();
+        mMultiSimSettingControllerUT.notifyCarrierConfigChanged(0, 1);
         waitABit();
 
         // Sub 1 should be default sub silently.
@@ -223,8 +228,10 @@
         doReturn(2).when(mPhoneMock1).getSubId();
         infoList = Arrays.asList(mSubInfo2);
         doReturn(infoList).when(mSubControllerMock).getActiveSubscriptionInfoList(anyString());
+        doReturn(new int[]{2}).when(mSubControllerMock).getActiveSubIdList(anyBoolean());
 
         mMultiSimSettingControllerUT.notifySubscriptionInfoChanged();
+        mMultiSimSettingControllerUT.notifyCarrierConfigChanged(0, 2);
         waitABit();
 
         // Sub 1 should be default sub silently.
@@ -244,8 +251,10 @@
         doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID).when(mPhoneMock2).getSubId();
         List<SubscriptionInfo> infoList = Arrays.asList(mSubInfo1);
         doReturn(infoList).when(mSubControllerMock).getActiveSubscriptionInfoList(anyString());
+        doReturn(new int[]{1}).when(mSubControllerMock).getActiveSubIdList(anyBoolean());
 
         mMultiSimSettingControllerUT.notifyAllSubscriptionLoaded();
+        mMultiSimSettingControllerUT.notifyCarrierConfigChanged(0, 1);
         waitABit();
 
         // Sub 1 should be default sub silently.
@@ -262,8 +271,10 @@
         doReturn(2).when(mPhoneMock2).getSubId();
         infoList = Arrays.asList(mSubInfo1, mSubInfo2);
         doReturn(infoList).when(mSubControllerMock).getActiveSubscriptionInfoList(anyString());
+        doReturn(new int[]{1, 2}).when(mSubControllerMock).getActiveSubIdList(anyBoolean());
 
         mMultiSimSettingControllerUT.notifySubscriptionInfoChanged();
+        mMultiSimSettingControllerUT.notifyCarrierConfigChanged(1, 2);
         waitABit();
 
         // Intent should be broadcast to ask default data selection.
@@ -282,8 +293,10 @@
         doReturn(3).when(mPhoneMock2).getSubId();
         infoList = Arrays.asList(mSubInfo1, mSubInfo3);
         doReturn(infoList).when(mSubControllerMock).getActiveSubscriptionInfoList(anyString());
+        doReturn(new int[]{1, 3}).when(mSubControllerMock).getActiveSubIdList(anyBoolean());
 
         mMultiSimSettingControllerUT.notifySubscriptionInfoChanged();
+        mMultiSimSettingControllerUT.notifyCarrierConfigChanged(1, 3);
         waitABit();
 
         // Intent should be broadcast to ask default data selection.
@@ -300,6 +313,8 @@
         doReturn(true).when(mPhoneMock2).isUserDataEnabled();
         // After initialization, sub 2 should have mobile data off.
         mMultiSimSettingControllerUT.notifyAllSubscriptionLoaded();
+        mMultiSimSettingControllerUT.notifyCarrierConfigChanged(0, 1);
+        mMultiSimSettingControllerUT.notifyCarrierConfigChanged(1, 2);
         waitABit();
         verify(mDataEnabledSettingsMock2).setUserDataEnabled(false);
 
@@ -323,7 +338,10 @@
         doReturn(false).when(mSubControllerMock).isActiveSubId(1);
         List<SubscriptionInfo> infoList = Arrays.asList(mSubInfo2);
         doReturn(infoList).when(mSubControllerMock).getActiveSubscriptionInfoList(anyString());
+        doReturn(new int[]{2}).when(mSubControllerMock).getActiveSubIdList(anyBoolean());
         mMultiSimSettingControllerUT.notifySubscriptionInfoChanged();
+        mMultiSimSettingControllerUT.notifyCarrierConfigChanged(
+                1, SubscriptionManager.INVALID_SUBSCRIPTION_ID);
         waitABit();
         verify(mSubControllerMock).setDefaultDataSubId(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
         verify(mSubControllerMock).setDefaultSmsSubId(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
@@ -346,6 +364,8 @@
         GlobalSettingsHelper.setBoolean(mContext, Settings.Global.MOBILE_DATA, 2, true);
         GlobalSettingsHelper.setBoolean(mContext, Settings.Global.DATA_ROAMING, 2, false);
         mMultiSimSettingControllerUT.notifyAllSubscriptionLoaded();
+        mMultiSimSettingControllerUT.notifyCarrierConfigChanged(0, 1);
+        mMultiSimSettingControllerUT.notifyCarrierConfigChanged(1, 2);
         waitABit();
 
         // Create subscription grouping.
@@ -391,8 +411,10 @@
         doReturn(1).when(mSubControllerMock).getDefaultVoiceSubId();
         List<SubscriptionInfo> infoList = Arrays.asList(mSubInfo1, mSubInfo3);
         doReturn(infoList).when(mSubControllerMock).getActiveSubscriptionInfoList(anyString());
+        doReturn(new int[]{1, 3}).when(mSubControllerMock).getActiveSubIdList(anyBoolean());
 
         mMultiSimSettingControllerUT.notifySubscriptionInfoChanged();
+        mMultiSimSettingControllerUT.notifyCarrierConfigChanged(1, 3);
         waitABit();
 
         verify(mSubControllerMock).setDefaultDataSubId(3);
@@ -414,6 +436,8 @@
         // Notify subscriptions ready. Sub 2 should become the default. But shouldn't turn off
         // data of oppt sub 1.
         mMultiSimSettingControllerUT.notifyAllSubscriptionLoaded();
+        mMultiSimSettingControllerUT.notifyCarrierConfigChanged(0, 1);
+        mMultiSimSettingControllerUT.notifyCarrierConfigChanged(1, 2);
         waitABit();
         verify(mSubControllerMock).setDefaultDataSubId(2);
         verify(mDataEnabledSettingsMock1, never()).setUserDataEnabled(anyBoolean());
@@ -451,6 +475,8 @@
 
         // Notify subscriptions ready. Sub 2 should become the default, as sub 1 is opportunistic.
         mMultiSimSettingControllerUT.notifyAllSubscriptionLoaded();
+        mMultiSimSettingControllerUT.notifyCarrierConfigChanged(0, 1);
+        mMultiSimSettingControllerUT.notifyCarrierConfigChanged(1, 2);
         waitABit();
         verify(mSubControllerMock).setDefaultDataSubId(2);
         // No user selection needed, no intent should be sent.
@@ -465,7 +491,7 @@
         mMultiSimSettingControllerUT.notifySubscriptionGroupChanged(mGroupUuid1);
         waitABit();
         // This should result in setting sync.
-        verify(mDataEnabledSettingsMock1).setUserDataEnabled(false);
+        verify(mDataEnabledSettingsMock1).setUserDataEnabled(false, false);
         assertFalse(GlobalSettingsHelper.getBoolean(
                 mContext, Settings.Global.DATA_ROAMING, 1, true));
 
@@ -474,7 +500,7 @@
         // Turning data on on sub 2. Sub 1 should also be turned on.
         mMultiSimSettingControllerUT.notifyUserDataEnabled(2, true);
         waitABit();
-        verify(mDataEnabledSettingsMock1).setUserDataEnabled(true);
+        verify(mDataEnabledSettingsMock1).setUserDataEnabled(true, false);
         // No user selection needed, no intent should be sent.
         verify(mContext, never()).sendBroadcast(any());
     }
@@ -494,6 +520,8 @@
         GlobalSettingsHelper.setBoolean(mContext, Settings.Global.MOBILE_DATA, 1, true);
         GlobalSettingsHelper.setBoolean(mContext, Settings.Global.DATA_ROAMING, 1, false);
         mMultiSimSettingControllerUT.notifyAllSubscriptionLoaded();
+        mMultiSimSettingControllerUT.notifyCarrierConfigChanged(0, 1);
+        mMultiSimSettingControllerUT.notifyCarrierConfigChanged(1, 2);
         waitABit();
 
         // Create subscription grouping.
@@ -503,7 +531,7 @@
         mMultiSimSettingControllerUT.notifySubscriptionGroupChanged(mGroupUuid1);
         waitABit();
         // This should result in setting sync.
-        verify(mDataEnabledSettingsMock2).setUserDataEnabled(true);
+        verify(mDataEnabledSettingsMock2).setUserDataEnabled(true, false);
         assertFalse(GlobalSettingsHelper.getBoolean(
                 mContext, Settings.Global.DATA_ROAMING, 2, true));
         verify(mSubControllerMock).setDataRoaming(/*enable*/0, /*subId*/1);
@@ -512,6 +540,48 @@
         doReturn(false).when(mPhoneMock1).isUserDataEnabled();
         mMultiSimSettingControllerUT.notifyUserDataEnabled(1, false);
         waitABit();
+        verify(mDataEnabledSettingsMock2).setUserDataEnabled(false, false);
+    }
+
+    @Test
+    @SmallTest
+    public void testCarrierConfigLoading() throws Exception {
+        doReturn(true).when(mPhoneMock1).isUserDataEnabled();
+        doReturn(true).when(mPhoneMock2).isUserDataEnabled();
+        // Sub 2 should have mobile data off, but it shouldn't happen until carrier configs are
+        // loaded on both subscriptions.
+        mMultiSimSettingControllerUT.notifyAllSubscriptionLoaded();
+        waitABit();
+        verify(mDataEnabledSettingsMock2, never()).setUserDataEnabled(false);
+        mMultiSimSettingControllerUT.notifyCarrierConfigChanged(0, 1);
+        waitABit();
+        verify(mDataEnabledSettingsMock2, never()).setUserDataEnabled(false);
+        mMultiSimSettingControllerUT.notifyCarrierConfigChanged(1, 2);
+        waitABit();
         verify(mDataEnabledSettingsMock2).setUserDataEnabled(false);
+
+        // Switch from sub 2 to sub 3 in phone[1].
+        clearInvocations(mSubControllerMock);
+        doReturn(false).when(mSubControllerMock).isActiveSubId(2);
+        doReturn(true).when(mSubControllerMock).isActiveSubId(3);
+        doReturn(SubscriptionManager.INVALID_PHONE_INDEX).when(mSubControllerMock).getPhoneId(2);
+        doReturn(1).when(mSubControllerMock).getPhoneId(3);
+        doReturn(3).when(mPhoneMock2).getSubId();
+        List<SubscriptionInfo> infoList = Arrays.asList(mSubInfo1, mSubInfo3);
+        doReturn(infoList).when(mSubControllerMock).getActiveSubscriptionInfoList(anyString());
+        doReturn(new int[]{1, 3}).when(mSubControllerMock).getActiveSubIdList(anyBoolean());
+
+        // Nothing should happen until carrier config change is notified on sub 3.
+        mMultiSimSettingControllerUT.notifySubscriptionInfoChanged();
+        waitABit();
+        verify(mContext, never()).sendBroadcast(any());
+
+        mMultiSimSettingControllerUT.notifyCarrierConfigChanged(1, 3);
+        waitABit();
+        // Intent should be broadcast to ask default data selection.
+        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));
     }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/NewNitzStateMachineTest.java b/tests/telephonytests/src/com/android/internal/telephony/NitzStateMachineImplTest.java
similarity index 98%
rename from tests/telephonytests/src/com/android/internal/telephony/NewNitzStateMachineTest.java
rename to tests/telephonytests/src/com/android/internal/telephony/NitzStateMachineImplTest.java
index 028cbcc..5678e3b 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/NewNitzStateMachineTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/NitzStateMachineImplTest.java
@@ -42,7 +42,7 @@
 import org.junit.Test;
 import org.mockito.Mock;
 
-public class NewNitzStateMachineTest extends TelephonyTest {
+public class NitzStateMachineImplTest extends TelephonyTest {
 
     // A country with a single zone : the zone can be guessed from the country.
     // The UK uses UTC for part of the year so it is not good for detecting bogus NITZ signals.
@@ -76,14 +76,14 @@
             .build();
 
     @Mock
-    private NewNitzStateMachine.DeviceState mDeviceState;
+    private NitzStateMachine.DeviceState mDeviceState;
 
     @Mock
-    private NewTimeServiceHelper mTimeServiceHelper;
+    private TimeServiceHelper mTimeServiceHelper;
 
     private TimeZoneLookupHelper mRealTimeZoneLookupHelper;
 
-    private NewNitzStateMachine mNitzStateMachine;
+    private NitzStateMachineImpl mNitzStateMachine;
 
     @Before
     public void setUp() throws Exception {
@@ -92,7 +92,7 @@
 
         // In tests we use the real TimeZoneLookupHelper.
         mRealTimeZoneLookupHelper = new TimeZoneLookupHelper();
-        mNitzStateMachine = new NewNitzStateMachine(
+        mNitzStateMachine = new NitzStateMachineImpl(
                 mPhone, mTimeServiceHelper, mDeviceState, mRealTimeZoneLookupHelper);
 
         logd("ServiceStateTrackerTest -Setup!");
@@ -819,7 +819,7 @@
         }
 
         void checkNoUnverifiedSetOperations() {
-            NewNitzStateMachineTest.checkNoUnverifiedSetOperations(mTimeServiceHelper);
+            NitzStateMachineImplTest.checkNoUnverifiedSetOperations(mTimeServiceHelper);
         }
     }
 
@@ -957,7 +957,7 @@
     /**
      * Confirms all mTimeServiceHelper side effects were verified.
      */
-    private static void checkNoUnverifiedSetOperations(NewTimeServiceHelper mTimeServiceHelper) {
+    private static void checkNoUnverifiedSetOperations(TimeServiceHelper mTimeServiceHelper) {
         // We don't care about current auto time / time zone state retrievals / listening so we can
         // use "at least 0" times to indicate they don't matter.
         verify(mTimeServiceHelper, atLeast(0)).setListener(any());
diff --git a/tests/telephonytests/src/com/android/internal/telephony/OldNitzStateMachineTest.java b/tests/telephonytests/src/com/android/internal/telephony/OldNitzStateMachineTest.java
deleted file mode 100644
index 2fc864a..0000000
--- a/tests/telephonytests/src/com/android/internal/telephony/OldNitzStateMachineTest.java
+++ /dev/null
@@ -1,1110 +0,0 @@
-/*
- * Copyright 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 static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.Mockito.atLeast;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.when;
-
-import android.icu.util.Calendar;
-import android.icu.util.GregorianCalendar;
-import android.icu.util.TimeZone;
-import android.util.TimestampedValue;
-
-import com.android.internal.telephony.TimeZoneLookupHelper.CountryResult;
-import com.android.internal.telephony.TimeZoneLookupHelper.OffsetResult;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-
-public class OldNitzStateMachineTest extends TelephonyTest {
-
-    // A country with a single zone : the zone can be guessed from the country.
-    // The UK uses UTC for part of the year so it is not good for detecting bogus NITZ signals.
-    private static final Scenario UNITED_KINGDOM_SCENARIO = new Scenario.Builder()
-            .setInitialDeviceSystemClockUtc(1977, 1, 1, 12, 0, 0)
-            .setInitialDeviceRealtimeMillis(123456789L)
-            .setTimeZone("Europe/London")
-            .setActualTimeUtc(2018, 1, 1, 12, 0, 0)
-            .setCountryIso("gb")
-            .build();
-
-    // A country that has multiple zones, but there is only one matching time zone at the time :
-    // the zone cannot be guessed from the country alone, but can be guessed from the country +
-    // NITZ. The US never uses UTC so it can be used for testing bogus NITZ signal handling.
-    private static final Scenario UNIQUE_US_ZONE_SCENARIO = new Scenario.Builder()
-            .setInitialDeviceSystemClockUtc(1977, 1, 1, 12, 0, 0)
-            .setInitialDeviceRealtimeMillis(123456789L)
-            .setTimeZone("America/Los_Angeles")
-            .setActualTimeUtc(2018, 1, 1, 12, 0, 0)
-            .setCountryIso("us")
-            .build();
-
-    // A country with a single zone: the zone can be guessed from the country alone. CZ never uses
-    // UTC so it can be used for testing bogus NITZ signal handling.
-    private static final Scenario CZECHIA_SCENARIO = new Scenario.Builder()
-            .setInitialDeviceSystemClockUtc(1977, 1, 1, 12, 0, 0)
-            .setInitialDeviceRealtimeMillis(123456789L)
-            .setTimeZone("Europe/Prague")
-            .setActualTimeUtc(2018, 1, 1, 12, 0, 0)
-            .setCountryIso("cz")
-            .build();
-
-    @Mock
-    private OldNitzStateMachine.DeviceState mDeviceState;
-
-    @Mock
-    private OldTimeServiceHelper mTimeServiceHelper;
-
-    private TimeZoneLookupHelper mRealTimeZoneLookupHelper;
-
-    private OldNitzStateMachine mNitzStateMachine;
-
-    @Before
-    public void setUp() throws Exception {
-        logd("NitzStateMachineTest +Setup!");
-        super.setUp("NitzStateMachineTest");
-
-        // In tests we use the real TimeZoneLookupHelper.
-        mRealTimeZoneLookupHelper = new TimeZoneLookupHelper();
-        mNitzStateMachine = new OldNitzStateMachine(
-                mPhone, mTimeServiceHelper, mDeviceState, mRealTimeZoneLookupHelper);
-
-        logd("ServiceStateTrackerTest -Setup!");
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        checkNoUnverifiedSetOperations(mTimeServiceHelper);
-
-        super.tearDown();
-    }
-
-    @Test
-    public void test_uniqueUsZone_Assumptions() {
-        // Check we'll get the expected behavior from TimeZoneLookupHelper.
-
-        // allZonesHaveSameOffset == false, so we shouldn't pick an arbitrary zone.
-        CountryResult expectedCountryLookupResult = new CountryResult(
-                "America/New_York", false /* allZonesHaveSameOffset */,
-                UNIQUE_US_ZONE_SCENARIO.getInitialSystemClockMillis());
-        CountryResult actualCountryLookupResult =
-                mRealTimeZoneLookupHelper.lookupByCountry(
-                        UNIQUE_US_ZONE_SCENARIO.getNetworkCountryIsoCode(),
-                        UNIQUE_US_ZONE_SCENARIO.getInitialSystemClockMillis());
-        assertEquals(expectedCountryLookupResult, actualCountryLookupResult);
-
-        // isOnlyMatch == true, so the combination of country + NITZ should be enough.
-        OffsetResult expectedLookupResult =
-                new OffsetResult("America/Los_Angeles", true /* isOnlyMatch */);
-        OffsetResult actualLookupResult = mRealTimeZoneLookupHelper.lookupByNitzCountry(
-                UNIQUE_US_ZONE_SCENARIO.getNitzSignal().getValue(),
-                UNIQUE_US_ZONE_SCENARIO.getNetworkCountryIsoCode());
-        assertEquals(expectedLookupResult, actualLookupResult);
-    }
-
-    @Test
-    public void test_unitedKingdom_Assumptions() {
-        // Check we'll get the expected behavior from TimeZoneLookupHelper.
-
-        // allZonesHaveSameOffset == true (not only that, there is only one zone), so we can pick
-        // the zone knowing only the country.
-        CountryResult expectedCountryLookupResult = new CountryResult(
-                "Europe/London", true /* allZonesHaveSameOffset */,
-                UNITED_KINGDOM_SCENARIO.getInitialSystemClockMillis());
-        CountryResult actualCountryLookupResult =
-                mRealTimeZoneLookupHelper.lookupByCountry(
-                        UNITED_KINGDOM_SCENARIO.getNetworkCountryIsoCode(),
-                        UNITED_KINGDOM_SCENARIO.getInitialSystemClockMillis());
-        assertEquals(expectedCountryLookupResult, actualCountryLookupResult);
-
-        OffsetResult expectedLookupResult =
-                new OffsetResult("Europe/London", true /* isOnlyMatch */);
-        OffsetResult actualLookupResult = mRealTimeZoneLookupHelper.lookupByNitzCountry(
-                UNITED_KINGDOM_SCENARIO.getNitzSignal().getValue(),
-                UNITED_KINGDOM_SCENARIO.getNetworkCountryIsoCode());
-        assertEquals(expectedLookupResult, actualLookupResult);
-    }
-
-    @Test
-    public void test_uniqueUsZone_timeEnabledTimeZoneEnabled_countryThenNitz() throws Exception {
-        Scenario scenario = UNIQUE_US_ZONE_SCENARIO;
-        Device device = new DeviceBuilder()
-                .setClocksFromScenario(scenario)
-                .setTimeDetectionEnabled(true)
-                .setTimeZoneDetectionEnabled(true)
-                .setTimeZoneSettingInitialized(false)
-                .initialize();
-        Script script = new Script(device);
-
-        int clockIncrement = 1250;
-        long expectedTimeMillis = scenario.getActualTimeMillis() + clockIncrement;
-        script.countryReceived(scenario.getNetworkCountryIsoCode())
-                // Country won't be enough for time zone detection.
-                .verifyNothingWasSetAndReset()
-                // Increment the clock so we can tell the time was adjusted correctly when set.
-                .incrementClocks(clockIncrement)
-                .nitzReceived(scenario.getNitzSignal())
-                // Country + NITZ is enough for both time + time zone detection.
-                .verifyTimeAndZoneSetAndReset(expectedTimeMillis, scenario.getTimeZoneId());
-
-        // Check NitzStateMachine state.
-        assertTrue(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
-        assertEquals(scenario.getNitzSignal().getValue(), mNitzStateMachine.getCachedNitzData());
-        assertEquals(scenario.getTimeZoneId(), mNitzStateMachine.getSavedTimeZoneId());
-    }
-
-    @Test
-    public void test_unitedKingdom_timeEnabledTimeZoneEnabled_countryThenNitz() throws Exception {
-        Scenario scenario = UNITED_KINGDOM_SCENARIO;
-        Device device = new DeviceBuilder()
-                .setClocksFromScenario(scenario)
-                .setTimeDetectionEnabled(true)
-                .setTimeZoneDetectionEnabled(true)
-                .setTimeZoneSettingInitialized(false)
-                .initialize();
-        Script script = new Script(device);
-
-        int clockIncrement = 1250;
-        long expectedTimeMillis = scenario.getActualTimeMillis() + clockIncrement;
-        script.countryReceived(scenario.getNetworkCountryIsoCode())
-                // Country alone is enough to guess the time zone.
-                .verifyOnlyTimeZoneWasSetAndReset(scenario.getTimeZoneId())
-                // Increment the clock so we can tell the time was adjusted correctly when set.
-                .incrementClocks(clockIncrement)
-                .nitzReceived(scenario.getNitzSignal())
-                // Country + NITZ is enough for both time + time zone detection.
-                .verifyTimeAndZoneSetAndReset(expectedTimeMillis, scenario.getTimeZoneId());
-
-        // Check NitzStateMachine state.
-        assertTrue(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
-        assertEquals(scenario.getNitzSignal().getValue(), mNitzStateMachine.getCachedNitzData());
-        assertEquals(scenario.getTimeZoneId(), mNitzStateMachine.getSavedTimeZoneId());
-    }
-
-    @Test
-    public void test_uniqueUsZone_timeEnabledTimeZoneDisabled_countryThenNitz() throws Exception {
-        Scenario scenario = UNIQUE_US_ZONE_SCENARIO;
-        Device device = new DeviceBuilder()
-                .setClocksFromScenario(scenario)
-                .setTimeDetectionEnabled(true)
-                .setTimeZoneDetectionEnabled(false)
-                .setTimeZoneSettingInitialized(false)
-                .initialize();
-        Script script = new Script(device);
-
-        int clockIncrement = 1250;
-        script.countryReceived(scenario.getNetworkCountryIsoCode())
-                // Country is not enough to guess the time zone and time zone detection is disabled.
-                .verifyNothingWasSetAndReset()
-                // Increment the clock so we can tell the time was adjusted correctly when set.
-                .incrementClocks(clockIncrement)
-                .nitzReceived(scenario.getNitzSignal())
-                // Time zone detection is disabled, but time should be set from NITZ.
-                .verifyOnlyTimeWasSetAndReset(scenario.getActualTimeMillis() + clockIncrement);
-
-        // Check NitzStateMachine state.
-        assertTrue(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
-        assertEquals(scenario.getNitzSignal().getValue(), mNitzStateMachine.getCachedNitzData());
-        assertEquals(scenario.getTimeZoneId(), mNitzStateMachine.getSavedTimeZoneId());
-    }
-
-    @Test
-    public void test_unitedKingdom_timeEnabledTimeZoneDisabled_countryThenNitz()
-            throws Exception {
-        Scenario scenario = UNITED_KINGDOM_SCENARIO;
-        Device device = new DeviceBuilder()
-                .setClocksFromScenario(scenario)
-                .setTimeDetectionEnabled(true)
-                .setTimeZoneDetectionEnabled(false)
-                .setTimeZoneSettingInitialized(false)
-                .initialize();
-        Script script = new Script(device);
-
-        int clockIncrement = 1250;
-        script.countryReceived(scenario.getNetworkCountryIsoCode())
-                // Country alone would be enough for time zone detection, but it's disabled.
-                .verifyNothingWasSetAndReset()
-                // Increment the clock so we can tell the time was adjusted correctly when set.
-                .incrementClocks(clockIncrement)
-                .nitzReceived(scenario.getNitzSignal())
-                // Time zone detection is disabled, but time should be set from NITZ.
-                .verifyOnlyTimeWasSetAndReset(scenario.getActualTimeMillis() + clockIncrement);
-
-        // Check NitzStateMachine state.
-        assertTrue(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
-        assertEquals(scenario.getNitzSignal().getValue(), mNitzStateMachine.getCachedNitzData());
-        assertEquals(scenario.getTimeZoneId(), mNitzStateMachine.getSavedTimeZoneId());
-    }
-
-    @Test
-    public void test_uniqueUsZone_timeDisabledTimeZoneEnabled_countryThenNitz() throws Exception {
-        Scenario scenario = UNIQUE_US_ZONE_SCENARIO;
-        Device device = new DeviceBuilder()
-                .setClocksFromScenario(scenario)
-                .setTimeDetectionEnabled(false)
-                .setTimeZoneDetectionEnabled(true)
-                .setTimeZoneSettingInitialized(false)
-                .initialize();
-        Script script = new Script(device);
-
-        script.countryReceived(scenario.getNetworkCountryIsoCode())
-                // Country won't be enough for time zone detection.
-                .verifyNothingWasSetAndReset()
-                .nitzReceived(scenario.getNitzSignal())
-                // Time detection is disabled, but time zone should be detected from country + NITZ.
-                .verifyOnlyTimeZoneWasSetAndReset(scenario.getTimeZoneId());
-
-        // Check NitzStateMachine state.
-        assertTrue(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
-        assertEquals(scenario.getNitzSignal().getValue(), mNitzStateMachine.getCachedNitzData());
-        assertEquals(scenario.getTimeZoneId(), mNitzStateMachine.getSavedTimeZoneId());
-    }
-
-    @Test
-    public void test_unitedKingdom_timeDisabledTimeZoneEnabled_countryThenNitz() throws Exception {
-        Scenario scenario = UNITED_KINGDOM_SCENARIO;
-        Device device = new DeviceBuilder()
-                .setClocksFromScenario(scenario)
-                .setTimeDetectionEnabled(false)
-                .setTimeZoneDetectionEnabled(true)
-                .setTimeZoneSettingInitialized(false)
-                .initialize();
-        Script script = new Script(device);
-
-        script.countryReceived(scenario.getNetworkCountryIsoCode())
-                // Country alone is enough to detect time zone.
-                .verifyOnlyTimeZoneWasSetAndReset(scenario.getTimeZoneId())
-                .nitzReceived(scenario.getNitzSignal())
-                // Time detection is disabled, so we don't set the clock from NITZ.
-                .verifyOnlyTimeZoneWasSetAndReset(scenario.getTimeZoneId());
-
-        // Check NitzStateMachine state.
-        assertTrue(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
-        assertEquals(scenario.getNitzSignal().getValue(), mNitzStateMachine.getCachedNitzData());
-        assertEquals(scenario.getTimeZoneId(), mNitzStateMachine.getSavedTimeZoneId());
-    }
-
-    @Test
-    public void test_uniqueUsZone_timeDisabledTimeZoneDisabled_countryThenNitz() throws Exception {
-        Scenario scenario = UNIQUE_US_ZONE_SCENARIO;
-        Device device = new DeviceBuilder()
-                .setClocksFromScenario(scenario)
-                .setTimeDetectionEnabled(false)
-                .setTimeZoneDetectionEnabled(false)
-                .setTimeZoneSettingInitialized(false)
-                .initialize();
-        Script script = new Script(device);
-
-        script.countryReceived(scenario.getNetworkCountryIsoCode())
-                // Time and time zone detection is disabled.
-                .verifyNothingWasSetAndReset()
-                .nitzReceived(scenario.getNitzSignal())
-                // Time and time zone detection is disabled.
-                .verifyNothingWasSetAndReset();
-
-        // Check NitzStateMachine state.
-        assertTrue(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
-        assertEquals(scenario.getNitzSignal().getValue(), mNitzStateMachine.getCachedNitzData());
-        assertEquals(scenario.getTimeZoneId(), mNitzStateMachine.getSavedTimeZoneId());
-    }
-
-    @Test
-    public void test_unitedKingdom_timeDisabledTimeZoneDisabled_countryThenNitz() throws Exception {
-        Scenario scenario = UNITED_KINGDOM_SCENARIO;
-        Device device = new DeviceBuilder()
-                .setClocksFromScenario(scenario)
-                .setTimeDetectionEnabled(false)
-                .setTimeZoneDetectionEnabled(false)
-                .setTimeZoneSettingInitialized(false)
-                .initialize();
-        Script script = new Script(device);
-
-        script.countryReceived(scenario.getNetworkCountryIsoCode())
-                // Time and time zone detection is disabled.
-                .verifyNothingWasSetAndReset()
-                .nitzReceived(scenario.getNitzSignal())
-                // Time and time zone detection is disabled.
-                .verifyNothingWasSetAndReset();
-
-        // Check NitzStateMachine state.
-        assertTrue(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
-        assertEquals(scenario.getNitzSignal().getValue(), mNitzStateMachine.getCachedNitzData());
-        assertEquals(scenario.getTimeZoneId(), mNitzStateMachine.getSavedTimeZoneId());
-    }
-
-    @Test
-    public void test_uniqueUsZone_timeDisabledTimeZoneEnabled_nitzThenCountry() throws Exception {
-        Scenario scenario = UNIQUE_US_ZONE_SCENARIO;
-        Device device = new DeviceBuilder()
-                .setClocksFromScenario(scenario)
-                .setTimeDetectionEnabled(false)
-                .setTimeZoneDetectionEnabled(true)
-                .setTimeZoneSettingInitialized(false)
-                .initialize();
-        Script script = new Script(device);
-
-        // Simulate receiving an NITZ signal.
-        script.nitzReceived(scenario.getNitzSignal())
-                // The NITZ alone isn't enough to detect a time zone.
-                .verifyNothingWasSetAndReset();
-
-        // Check NitzStateMachine state.
-        assertFalse(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
-        assertEquals(scenario.getNitzSignal().getValue(), mNitzStateMachine.getCachedNitzData());
-        assertNull(mNitzStateMachine.getSavedTimeZoneId());
-
-        // Simulate the country code becoming known.
-        script.countryReceived(scenario.getNetworkCountryIsoCode())
-                // The NITZ + country is enough to detect the time zone.
-                .verifyOnlyTimeZoneWasSetAndReset(scenario.getTimeZoneId());
-
-        // Check NitzStateMachine state.
-        assertTrue(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
-        assertEquals(scenario.getNitzSignal().getValue(), mNitzStateMachine.getCachedNitzData());
-        assertEquals(scenario.getTimeZoneId(), mNitzStateMachine.getSavedTimeZoneId());
-    }
-
-    @Test
-    public void test_unitedKingdom_timeDisabledTimeZoneEnabled_nitzThenCountry() throws Exception {
-        Scenario scenario = UNITED_KINGDOM_SCENARIO;
-        Device device = new DeviceBuilder()
-                .setClocksFromScenario(scenario)
-                .setTimeDetectionEnabled(false)
-                .setTimeZoneDetectionEnabled(true)
-                .setTimeZoneSettingInitialized(false)
-                .initialize();
-        Script script = new Script(device);
-
-        // Simulate receiving an NITZ signal.
-        script.nitzReceived(scenario.getNitzSignal())
-                // The NITZ alone isn't enough to detect a time zone.
-                .verifyNothingWasSetAndReset();
-
-        // Check NitzStateMachine state.
-        assertFalse(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
-        assertEquals(scenario.getNitzSignal().getValue(), mNitzStateMachine.getCachedNitzData());
-        assertNull(mNitzStateMachine.getSavedTimeZoneId());
-
-        // Simulate the country code becoming known.
-        script.countryReceived(scenario.getNetworkCountryIsoCode());
-
-        // The NITZ + country is enough to detect the time zone.
-        // NOTE: setting the timezone happens twice because of a quirk in NitzStateMachine: it
-        // handles the country lookup / set, then combines the country with the NITZ state and does
-        // another lookup / set. We shouldn't require it is set twice but we do for simplicity.
-        script.verifyOnlyTimeZoneWasSetAndReset(scenario.getTimeZoneId(), 2 /* times */);
-
-        // Check NitzStateMachine state.
-        assertTrue(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
-        assertEquals(scenario.getNitzSignal().getValue(), mNitzStateMachine.getCachedNitzData());
-        assertEquals(scenario.getTimeZoneId(), mNitzStateMachine.getSavedTimeZoneId());
-    }
-
-    @Test
-    public void test_validCzNitzSignal_nitzReceivedFirst() throws Exception {
-        Scenario scenario = CZECHIA_SCENARIO;
-        Device device = new DeviceBuilder()
-                .setClocksFromScenario(scenario)
-                .setTimeDetectionEnabled(true)
-                .setTimeZoneDetectionEnabled(true)
-                .setTimeZoneSettingInitialized(true)
-                .initialize();
-        Script script = new Script(device);
-
-        TimestampedValue<NitzData> goodNitzSignal = scenario.getNitzSignal();
-
-        // Simulate receiving an NITZ signal.
-        script.nitzReceived(goodNitzSignal)
-                // The NITZ alone isn't enough to detect a time zone.
-                .verifyOnlyTimeWasSetAndReset(scenario.getActualTimeMillis());
-
-        // Check NitzStateMachine state.
-        assertFalse(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
-        assertEquals(goodNitzSignal.getValue(), mNitzStateMachine.getCachedNitzData());
-        assertNull(mNitzStateMachine.getSavedTimeZoneId());
-
-        // Simulate the country code becoming known.
-        script.countryReceived(scenario.getNetworkCountryIsoCode())
-                // The NITZ country is enough to detect the time zone, but the NITZ + country is
-                // also sufficient so we expect the time zone to be set twice.
-                .verifyOnlyTimeZoneWasSetAndReset(scenario.getTimeZoneId(), 2);
-
-        // Check NitzStateMachine state.
-        assertTrue(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
-        assertEquals(goodNitzSignal.getValue(), mNitzStateMachine.getCachedNitzData());
-        assertEquals(scenario.getTimeZoneId(), mNitzStateMachine.getSavedTimeZoneId());
-    }
-
-    @Test
-    public void test_validCzNitzSignal_countryReceivedFirst() throws Exception {
-        Scenario scenario = CZECHIA_SCENARIO;
-        Device device = new DeviceBuilder()
-                .setClocksFromScenario(scenario)
-                .setTimeDetectionEnabled(true)
-                .setTimeZoneDetectionEnabled(true)
-                .setTimeZoneSettingInitialized(true)
-                .initialize();
-        Script script = new Script(device);
-
-        TimestampedValue<NitzData> goodNitzSignal = scenario.getNitzSignal();
-
-        // Simulate the country code becoming known.
-        script.countryReceived(scenario.getNetworkCountryIsoCode())
-                // The NITZ country is enough to detect the time zone.
-                .verifyOnlyTimeZoneWasSetAndReset(scenario.getTimeZoneId(), 1);
-
-        // Check NitzStateMachine state.
-        assertFalse(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
-        assertNull(mNitzStateMachine.getCachedNitzData());
-        assertEquals(scenario.getTimeZoneId(), mNitzStateMachine.getSavedTimeZoneId());
-
-        // Simulate receiving an NITZ signal.
-        script.nitzReceived(goodNitzSignal)
-                // The time will be set from the NITZ signal.
-                // The combination of NITZ + country will cause the time zone to be set.
-                .verifyTimeAndZoneSetAndReset(
-                        scenario.getActualTimeMillis(), scenario.getTimeZoneId());
-
-        // Check NitzStateMachine state.
-        assertTrue(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
-        assertEquals(goodNitzSignal.getValue(), mNitzStateMachine.getCachedNitzData());
-        assertEquals(scenario.getTimeZoneId(), mNitzStateMachine.getSavedTimeZoneId());
-    }
-
-    @Test
-    public void test_bogusCzNitzSignal_nitzReceivedFirst() throws Exception {
-        Scenario scenario = CZECHIA_SCENARIO;
-        Device device = new DeviceBuilder()
-                .setClocksFromScenario(scenario)
-                .setTimeDetectionEnabled(true)
-                .setTimeZoneDetectionEnabled(true)
-                .setTimeZoneSettingInitialized(true)
-                .initialize();
-        Script script = new Script(device);
-
-        TimestampedValue<NitzData> goodNitzSignal = scenario.getNitzSignal();
-
-        // Create a corrupted NITZ signal, where the offset information has been lost.
-        NitzData bogusNitzData = NitzData.createForTests(
-                0 /* UTC! */, null /* dstOffsetMillis */,
-                goodNitzSignal.getValue().getCurrentTimeInMillis(),
-                null /* emulatorHostTimeZone */);
-        TimestampedValue<NitzData> badNitzSignal = new TimestampedValue<>(
-                goodNitzSignal.getReferenceTimeMillis(), bogusNitzData);
-
-        // Simulate receiving an NITZ signal.
-        script.nitzReceived(badNitzSignal)
-                // The NITZ alone isn't enough to detect a time zone, but there isn't enough
-                // information to work out its bogus.
-                .verifyOnlyTimeWasSetAndReset(scenario.getActualTimeMillis());
-
-        // Check NitzStateMachine state.
-        assertFalse(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
-        assertEquals(badNitzSignal.getValue(), mNitzStateMachine.getCachedNitzData());
-        assertNull(mNitzStateMachine.getSavedTimeZoneId());
-
-        // Simulate the country code becoming known.
-        script.countryReceived(scenario.getNetworkCountryIsoCode())
-                // The country is enough to detect the time zone for CZ. If the NITZ signal
-                // wasn't obviously bogus we'd try to set it twice.
-                .verifyOnlyTimeZoneWasSetAndReset(scenario.getTimeZoneId(), 1);
-
-        // Check NitzStateMachine state.
-        assertFalse(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
-        assertEquals(badNitzSignal.getValue(), mNitzStateMachine.getCachedNitzData());
-        assertEquals(scenario.getTimeZoneId(), mNitzStateMachine.getSavedTimeZoneId());
-    }
-
-    @Test
-    public void test_bogusCzNitzSignal_countryReceivedFirst() throws Exception {
-        Scenario scenario = CZECHIA_SCENARIO;
-        Device device = new DeviceBuilder()
-                .setClocksFromScenario(scenario)
-                .setTimeDetectionEnabled(true)
-                .setTimeZoneDetectionEnabled(true)
-                .setTimeZoneSettingInitialized(true)
-                .initialize();
-        Script script = new Script(device);
-
-        TimestampedValue<NitzData> goodNitzSignal = scenario.getNitzSignal();
-
-        // Create a corrupted NITZ signal, where the offset information has been lost.
-        NitzData bogusNitzData = NitzData.createForTests(
-                0 /* UTC! */, null /* dstOffsetMillis */,
-                goodNitzSignal.getValue().getCurrentTimeInMillis(),
-                null /* emulatorHostTimeZone */);
-        TimestampedValue<NitzData> badNitzSignal = new TimestampedValue<>(
-                goodNitzSignal.getReferenceTimeMillis(), bogusNitzData);
-
-        // Simulate the country code becoming known.
-        script.countryReceived(scenario.getNetworkCountryIsoCode())
-                // The country is enough to detect the time zone for CZ.
-                .verifyOnlyTimeZoneWasSetAndReset(scenario.getTimeZoneId());
-
-        // Check NitzStateMachine state.
-        assertFalse(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
-        assertNull(mNitzStateMachine.getCachedNitzData());
-        assertEquals(scenario.getTimeZoneId(), mNitzStateMachine.getSavedTimeZoneId());
-
-        // Simulate receiving an NITZ signal.
-        script.nitzReceived(badNitzSignal)
-                // The NITZ should be detected as bogus so only the time will be set.
-                .verifyOnlyTimeWasSetAndReset(scenario.getActualTimeMillis());
-
-        // Check NitzStateMachine state.
-        assertFalse(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
-        assertEquals(badNitzSignal.getValue(), mNitzStateMachine.getCachedNitzData());
-        assertEquals(scenario.getTimeZoneId(), mNitzStateMachine.getSavedTimeZoneId());
-    }
-
-    @Test
-    public void test_bogusUniqueUsNitzSignal_nitzReceivedFirst() throws Exception {
-        Scenario scenario = UNIQUE_US_ZONE_SCENARIO;
-        Device device = new DeviceBuilder()
-                .setClocksFromScenario(scenario)
-                .setTimeDetectionEnabled(true)
-                .setTimeZoneDetectionEnabled(true)
-                .setTimeZoneSettingInitialized(true)
-                .initialize();
-        Script script = new Script(device);
-
-        TimestampedValue<NitzData> goodNitzSignal = scenario.getNitzSignal();
-
-        // Create a corrupted NITZ signal, where the offset information has been lost.
-        NitzData bogusNitzData = NitzData.createForTests(
-                0 /* UTC! */, null /* dstOffsetMillis */,
-                goodNitzSignal.getValue().getCurrentTimeInMillis(),
-                null /* emulatorHostTimeZone */);
-        TimestampedValue<NitzData> badNitzSignal = new TimestampedValue<>(
-                goodNitzSignal.getReferenceTimeMillis(), bogusNitzData);
-
-        // Simulate receiving an NITZ signal.
-        script.nitzReceived(badNitzSignal)
-                // The NITZ alone isn't enough to detect a time zone, but there isn't enough
-                // information to work out its bogus.
-                .verifyOnlyTimeWasSetAndReset(scenario.getActualTimeMillis());
-
-        // Check NitzStateMachine state.
-        assertFalse(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
-        assertEquals(badNitzSignal.getValue(), mNitzStateMachine.getCachedNitzData());
-        assertNull(mNitzStateMachine.getSavedTimeZoneId());
-
-        // Simulate the country code becoming known.
-        script.countryReceived(scenario.getNetworkCountryIsoCode())
-                // The country isn't enough to detect the time zone for US so we will leave the time
-                // zone unset.
-                .verifyNothingWasSetAndReset();
-
-        // Check NitzStateMachine state.
-        assertFalse(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
-        assertEquals(badNitzSignal.getValue(), mNitzStateMachine.getCachedNitzData());
-        assertNull(mNitzStateMachine.getSavedTimeZoneId());
-    }
-
-    @Test
-    public void test_bogusUsUniqueNitzSignal_countryReceivedFirst() throws Exception {
-        Scenario scenario = UNIQUE_US_ZONE_SCENARIO;
-        Device device = new DeviceBuilder()
-                .setClocksFromScenario(scenario)
-                .setTimeDetectionEnabled(true)
-                .setTimeZoneDetectionEnabled(true)
-                .setTimeZoneSettingInitialized(true)
-                .initialize();
-        Script script = new Script(device);
-
-        TimestampedValue<NitzData> goodNitzSignal = scenario.getNitzSignal();
-
-        // Create a corrupted NITZ signal, where the offset information has been lost.
-        NitzData bogusNitzData = NitzData.createForTests(
-                0 /* UTC! */, null /* dstOffsetMillis */,
-                goodNitzSignal.getValue().getCurrentTimeInMillis(),
-                null /* emulatorHostTimeZone */);
-        TimestampedValue<NitzData> badNitzSignal = new TimestampedValue<>(
-                goodNitzSignal.getReferenceTimeMillis(), bogusNitzData);
-
-        // Simulate the country code becoming known.
-        script.countryReceived(scenario.getNetworkCountryIsoCode())
-                // The country isn't enough to detect the time zone for US so we will leave the time
-                // zone unset.
-                .verifyNothingWasSetAndReset();
-
-        // Check NitzStateMachine state.
-        assertFalse(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
-        assertNull(mNitzStateMachine.getCachedNitzData());
-        assertNull(mNitzStateMachine.getSavedTimeZoneId());
-
-        // Simulate receiving an NITZ signal.
-        script.nitzReceived(badNitzSignal)
-                // The NITZ should be detected as bogus so only the time will be set.
-                .verifyOnlyTimeWasSetAndReset(scenario.getActualTimeMillis());
-
-        // Check NitzStateMachine state.
-        assertFalse(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
-        assertEquals(badNitzSignal.getValue(), mNitzStateMachine.getCachedNitzData());
-        assertNull(mNitzStateMachine.getSavedTimeZoneId());
-    }
-
-    @Test
-    public void test_emulatorNitzExtensionUsedForTimeZone() throws Exception {
-        Scenario scenario = UNIQUE_US_ZONE_SCENARIO;
-        Device device = new DeviceBuilder()
-                .setClocksFromScenario(scenario)
-                .setTimeDetectionEnabled(false)
-                .setTimeZoneDetectionEnabled(true)
-                .setTimeZoneSettingInitialized(true)
-                .initialize();
-        Script script = new Script(device);
-
-        TimestampedValue<NitzData> originalNitzSignal = scenario.getNitzSignal();
-
-        // Create an NITZ signal with an explicit time zone (as can happen on emulators)
-        NitzData originalNitzData = originalNitzSignal.getValue();
-        // A time zone that is obviously not in the US, but it should not be questioned.
-        String emulatorTimeZoneId = "Europe/London";
-        NitzData emulatorNitzData = NitzData.createForTests(
-                originalNitzData.getLocalOffsetMillis(),
-                originalNitzData.getDstAdjustmentMillis(),
-                originalNitzData.getCurrentTimeInMillis(),
-                java.util.TimeZone.getTimeZone(emulatorTimeZoneId) /* emulatorHostTimeZone */);
-        TimestampedValue<NitzData> emulatorNitzSignal = new TimestampedValue<>(
-                originalNitzSignal.getReferenceTimeMillis(), emulatorNitzData);
-
-        // Simulate receiving the emulator NITZ signal.
-        script.nitzReceived(emulatorNitzSignal)
-                .verifyOnlyTimeZoneWasSetAndReset(emulatorTimeZoneId);
-
-        // Check NitzStateMachine state.
-        assertTrue(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
-        assertEquals(emulatorNitzSignal.getValue(), mNitzStateMachine.getCachedNitzData());
-        assertEquals(emulatorTimeZoneId, mNitzStateMachine.getSavedTimeZoneId());
-    }
-
-    @Test
-    public void test_emptyCountryStringUsTime_countryReceivedFirst() throws Exception {
-        Scenario scenario = UNIQUE_US_ZONE_SCENARIO;
-        Device device = new DeviceBuilder()
-                .setClocksFromScenario(scenario)
-                .setTimeDetectionEnabled(true)
-                .setTimeZoneDetectionEnabled(true)
-                .setTimeZoneSettingInitialized(true)
-                .initialize();
-        Script script = new Script(device);
-
-        String expectedZoneId = checkNitzOnlyLookupIsAmbiguousAndReturnZoneId(scenario);
-
-        // Nothing should be set. The country is not valid.
-        script.countryReceived("").verifyNothingWasSetAndReset();
-
-        // Check NitzStateMachine state.
-        assertFalse(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
-        assertNull(mNitzStateMachine.getCachedNitzData());
-        assertNull(mNitzStateMachine.getSavedTimeZoneId());
-
-        // Simulate receiving the NITZ signal.
-        script.nitzReceived(scenario.getNitzSignal())
-                .verifyTimeAndZoneSetAndReset(scenario.getActualTimeMillis(), expectedZoneId);
-
-        // Check NitzStateMachine state.
-        assertTrue(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
-        assertEquals(scenario.getNitzSignal().getValue(), mNitzStateMachine.getCachedNitzData());
-        assertEquals(expectedZoneId, mNitzStateMachine.getSavedTimeZoneId());
-    }
-
-    @Test
-    public void test_emptyCountryStringUsTime_nitzReceivedFirst() throws Exception {
-        Scenario scenario = UNIQUE_US_ZONE_SCENARIO;
-        Device device = new DeviceBuilder()
-                .setClocksFromScenario(scenario)
-                .setTimeDetectionEnabled(true)
-                .setTimeZoneDetectionEnabled(true)
-                .setTimeZoneSettingInitialized(true)
-                .initialize();
-        Script script = new Script(device);
-
-        String expectedZoneId = checkNitzOnlyLookupIsAmbiguousAndReturnZoneId(scenario);
-
-        // Simulate receiving the NITZ signal.
-        script.nitzReceived(scenario.getNitzSignal())
-                .verifyOnlyTimeWasSetAndReset(scenario.getActualTimeMillis());
-
-        // Check NitzStateMachine state.
-        assertFalse(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
-        assertEquals(scenario.getNitzSignal().getValue(), mNitzStateMachine.getCachedNitzData());
-        assertNull(mNitzStateMachine.getSavedTimeZoneId());
-
-        // The time zone should be set (but the country is not valid so it's unlikely to be
-        // correct).
-        script.countryReceived("").verifyOnlyTimeZoneWasSetAndReset(expectedZoneId);
-
-        // Check NitzStateMachine state.
-        assertTrue(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
-        assertEquals(scenario.getNitzSignal().getValue(), mNitzStateMachine.getCachedNitzData());
-        assertEquals(expectedZoneId, mNitzStateMachine.getSavedTimeZoneId());
-    }
-
-    /**
-     * Asserts a test scenario has the properties we expect for NITZ-only lookup. There are
-     * usually multiple zones that will share the same UTC offset so we get a low quality / low
-     * confidence answer, but the zone we find should at least have the correct offset.
-     */
-    private String checkNitzOnlyLookupIsAmbiguousAndReturnZoneId(Scenario scenario) {
-        OffsetResult result =
-                mRealTimeZoneLookupHelper.lookupByNitz(scenario.getNitzSignal().getValue());
-        String expectedZoneId = result.zoneId;
-        // All our scenarios should return multiple matches. The only cases where this wouldn't be
-        // true are places that use offsets like XX:15, XX:30 and XX:45.
-        assertFalse(result.isOnlyMatch);
-        assertSameOffset(scenario.getActualTimeMillis(), expectedZoneId, scenario.getTimeZoneId());
-        return expectedZoneId;
-    }
-
-    private static void assertSameOffset(long timeMillis, String zoneId1, String zoneId2) {
-        assertEquals(TimeZone.getTimeZone(zoneId1).getOffset(timeMillis),
-                TimeZone.getTimeZone(zoneId2).getOffset(timeMillis));
-    }
-
-    private static long createUtcTime(int year, int monthInYear, int day, int hourOfDay, int minute,
-            int second) {
-        Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("Etc/UTC"));
-        cal.clear();
-        cal.set(year, monthInYear - 1, day, hourOfDay, minute, second);
-        return cal.getTimeInMillis();
-    }
-
-    /**
-     * A helper class for common test operations involving a device.
-     */
-    class Script {
-        private final Device mDevice;
-
-        Script(Device device) {
-            this.mDevice = device;
-        }
-
-        Script countryReceived(String countryIsoCode) {
-            mDevice.networkCountryKnown(countryIsoCode);
-            return this;
-        }
-
-        Script nitzReceived(TimestampedValue<NitzData> nitzSignal) {
-            mDevice.nitzSignalReceived(nitzSignal);
-            return this;
-        }
-
-        Script incrementClocks(int clockIncrement) {
-            mDevice.incrementClocks(clockIncrement);
-            return this;
-        }
-
-        Script verifyNothingWasSetAndReset() {
-            mDevice.verifyTimeZoneWasNotSet();
-            mDevice.verifyTimeWasNotSet();
-            mDevice.checkNoUnverifiedSetOperations();
-            mDevice.resetInvocations();
-            return this;
-        }
-
-        Script verifyOnlyTimeZoneWasSetAndReset(String timeZoneId, int times) {
-            mDevice.verifyTimeZoneWasSet(timeZoneId, times);
-            mDevice.verifyTimeWasNotSet();
-            mDevice.checkNoUnverifiedSetOperations();
-            mDevice.resetInvocations();
-            return this;
-        }
-
-        Script verifyOnlyTimeZoneWasSetAndReset(String timeZoneId) {
-            return verifyOnlyTimeZoneWasSetAndReset(timeZoneId, 1);
-        }
-
-        Script verifyOnlyTimeWasSetAndReset(long expectedTimeMillis) {
-            mDevice.verifyTimeZoneWasNotSet();
-            mDevice.verifyTimeWasSet(expectedTimeMillis);
-            mDevice.checkNoUnverifiedSetOperations();
-            mDevice.resetInvocations();
-            return this;
-        }
-
-        Script verifyTimeAndZoneSetAndReset(long expectedTimeMillis, String timeZoneId) {
-            mDevice.verifyTimeZoneWasSet(timeZoneId);
-            mDevice.verifyTimeWasSet(expectedTimeMillis);
-            mDevice.checkNoUnverifiedSetOperations();
-            mDevice.resetInvocations();
-            return this;
-        }
-
-        Script reset() {
-            mDevice.checkNoUnverifiedSetOperations();
-            mDevice.resetInvocations();
-            return this;
-        }
-    }
-
-    /**
-     * An abstraction of a device for use in telephony time zone detection tests. It can be used to
-     * retrieve device state, modify device state and verify changes.
-     */
-    class Device {
-
-        private final long mInitialSystemClockMillis;
-        private final long mInitialRealtimeMillis;
-        private final boolean mTimeDetectionEnabled;
-        private final boolean mTimeZoneDetectionEnabled;
-        private final boolean mTimeZoneSettingInitialized;
-
-        Device(long initialSystemClockMillis, long initialRealtimeMillis,
-                boolean timeDetectionEnabled, boolean timeZoneDetectionEnabled,
-                boolean timeZoneSettingInitialized) {
-            mInitialSystemClockMillis = initialSystemClockMillis;
-            mInitialRealtimeMillis = initialRealtimeMillis;
-            mTimeDetectionEnabled = timeDetectionEnabled;
-            mTimeZoneDetectionEnabled = timeZoneDetectionEnabled;
-            mTimeZoneSettingInitialized = timeZoneSettingInitialized;
-        }
-
-        void initialize() {
-            // Set initial configuration.
-            when(mDeviceState.getIgnoreNitz()).thenReturn(false);
-            when(mDeviceState.getNitzUpdateDiffMillis()).thenReturn(2000);
-            when(mDeviceState.getNitzUpdateSpacingMillis()).thenReturn(1000 * 60 * 10);
-
-            // Simulate the country not being known.
-            when(mDeviceState.getNetworkCountryIsoForPhone()).thenReturn("");
-
-            when(mTimeServiceHelper.elapsedRealtime()).thenReturn(mInitialRealtimeMillis);
-            when(mTimeServiceHelper.currentTimeMillis()).thenReturn(mInitialSystemClockMillis);
-            when(mTimeServiceHelper.isTimeDetectionEnabled()).thenReturn(mTimeDetectionEnabled);
-            when(mTimeServiceHelper.isTimeZoneDetectionEnabled())
-                    .thenReturn(mTimeZoneDetectionEnabled);
-            when(mTimeServiceHelper.isTimeZoneSettingInitialized())
-                    .thenReturn(mTimeZoneSettingInitialized);
-        }
-
-        void networkCountryKnown(String countryIsoCode) {
-            when(mDeviceState.getNetworkCountryIsoForPhone()).thenReturn(countryIsoCode);
-            mNitzStateMachine.handleNetworkCountryCodeSet(true);
-        }
-
-        void incrementClocks(int millis) {
-            long currentElapsedRealtime = mTimeServiceHelper.elapsedRealtime();
-            when(mTimeServiceHelper.elapsedRealtime()).thenReturn(currentElapsedRealtime + millis);
-            long currentTimeMillis = mTimeServiceHelper.currentTimeMillis();
-            when(mTimeServiceHelper.currentTimeMillis()).thenReturn(currentTimeMillis + millis);
-        }
-
-        void nitzSignalReceived(TimestampedValue<NitzData> nitzSignal) {
-            mNitzStateMachine.handleNitzReceived(nitzSignal);
-        }
-
-        void verifyTimeZoneWasNotSet() {
-            verify(mTimeServiceHelper, times(0)).setDeviceTimeZone(any(String.class));
-        }
-
-        void verifyTimeZoneWasSet(String timeZoneId) {
-            verifyTimeZoneWasSet(timeZoneId, 1 /* times */);
-        }
-
-        void verifyTimeZoneWasSet(String timeZoneId, int times) {
-            verify(mTimeServiceHelper, times(times)).setDeviceTimeZone(timeZoneId);
-        }
-
-        void verifyTimeWasNotSet() {
-            verify(mTimeServiceHelper, times(0)).setDeviceTime(anyLong());
-        }
-
-        void verifyTimeWasSet(long expectedTimeMillis) {
-            ArgumentCaptor<Long> timeServiceTimeCaptor = ArgumentCaptor.forClass(Long.TYPE);
-            verify(mTimeServiceHelper, times(1)).setDeviceTime(timeServiceTimeCaptor.capture());
-            assertEquals(expectedTimeMillis, (long) timeServiceTimeCaptor.getValue());
-        }
-
-        /**
-         * Used after calling verify... methods to reset expectations.
-         */
-        void resetInvocations() {
-            clearInvocations(mTimeServiceHelper);
-        }
-
-        void checkNoUnverifiedSetOperations() {
-            OldNitzStateMachineTest.checkNoUnverifiedSetOperations(mTimeServiceHelper);
-        }
-    }
-
-    /** A class used to construct a Device. */
-    class DeviceBuilder {
-
-        private long mInitialSystemClock;
-        private long mInitialRealtimeMillis;
-        private boolean mTimeDetectionEnabled;
-        private boolean mTimeZoneDetectionEnabled;
-        private boolean mTimeZoneSettingInitialized;
-
-        Device initialize() {
-            Device device = new Device(mInitialSystemClock, mInitialRealtimeMillis,
-                    mTimeDetectionEnabled, mTimeZoneDetectionEnabled, mTimeZoneSettingInitialized);
-            device.initialize();
-            return device;
-        }
-
-        DeviceBuilder setTimeDetectionEnabled(boolean enabled) {
-            mTimeDetectionEnabled = enabled;
-            return this;
-        }
-
-        DeviceBuilder setTimeZoneDetectionEnabled(boolean enabled) {
-            mTimeZoneDetectionEnabled = enabled;
-            return this;
-        }
-
-        DeviceBuilder setTimeZoneSettingInitialized(boolean initialized) {
-            mTimeZoneSettingInitialized = initialized;
-            return this;
-        }
-
-        DeviceBuilder setClocksFromScenario(Scenario scenario) {
-            mInitialRealtimeMillis = scenario.getInitialRealTimeMillis();
-            mInitialSystemClock = scenario.getInitialSystemClockMillis();
-            return this;
-        }
-    }
-
-    /**
-     * A scenario used during tests. Describes a fictional reality.
-     */
-    static class Scenario {
-
-        private final long mInitialDeviceSystemClockMillis;
-        private final long mInitialDeviceRealtimeMillis;
-        private final long mActualTimeMillis;
-        private final TimeZone mZone;
-        private final String mNetworkCountryIsoCode;
-
-        private TimestampedValue<NitzData> mNitzSignal;
-
-        Scenario(long initialDeviceSystemClock, long elapsedRealtime, long timeMillis,
-                String zoneId, String countryIsoCode) {
-            mInitialDeviceSystemClockMillis = initialDeviceSystemClock;
-            mActualTimeMillis = timeMillis;
-            mInitialDeviceRealtimeMillis = elapsedRealtime;
-            mZone = TimeZone.getTimeZone(zoneId);
-            mNetworkCountryIsoCode = countryIsoCode;
-        }
-
-        TimestampedValue<NitzData> getNitzSignal() {
-            if (mNitzSignal == null) {
-                int[] offsets = new int[2];
-                mZone.getOffset(mActualTimeMillis, false /* local */, offsets);
-                int zoneOffsetMillis = offsets[0] + offsets[1];
-                NitzData nitzData = NitzData.createForTests(
-                        zoneOffsetMillis, offsets[1], mActualTimeMillis,
-                        null /* emulatorHostTimeZone */);
-                mNitzSignal = new TimestampedValue<>(mInitialDeviceRealtimeMillis, nitzData);
-            }
-            return mNitzSignal;
-        }
-
-        long getInitialRealTimeMillis() {
-            return mInitialDeviceRealtimeMillis;
-        }
-
-        long getInitialSystemClockMillis() {
-            return mInitialDeviceSystemClockMillis;
-        }
-
-        String getNetworkCountryIsoCode() {
-            return mNetworkCountryIsoCode;
-        }
-
-        String getTimeZoneId() {
-            return mZone.getID();
-        }
-
-        long getActualTimeMillis() {
-            return mActualTimeMillis;
-        }
-
-        static class Builder {
-
-            private long mInitialDeviceSystemClockMillis;
-            private long mInitialDeviceRealtimeMillis;
-            private long mActualTimeMillis;
-            private String mZoneId;
-            private String mCountryIsoCode;
-
-            Builder setInitialDeviceSystemClockUtc(int year, int monthInYear, int day,
-                    int hourOfDay, int minute, int second) {
-                mInitialDeviceSystemClockMillis = createUtcTime(year, monthInYear, day, hourOfDay,
-                        minute, second);
-                return this;
-            }
-
-            Builder setInitialDeviceRealtimeMillis(long realtimeMillis) {
-                mInitialDeviceRealtimeMillis = realtimeMillis;
-                return this;
-            }
-
-            Builder setActualTimeUtc(int year, int monthInYear, int day, int hourOfDay,
-                    int minute, int second) {
-                mActualTimeMillis = createUtcTime(year, monthInYear, day, hourOfDay, minute,
-                        second);
-                return this;
-            }
-
-            Builder setTimeZone(String zoneId) {
-                mZoneId = zoneId;
-                return this;
-            }
-
-            Builder setCountryIso(String isoCode) {
-                mCountryIsoCode = isoCode;
-                return this;
-            }
-
-            Scenario build() {
-                return new Scenario(mInitialDeviceSystemClockMillis, mInitialDeviceRealtimeMillis,
-                        mActualTimeMillis, mZoneId, mCountryIsoCode);
-            }
-        }
-    }
-
-    /**
-     * Confirms all mTimeServiceHelper side effects were verified.
-     */
-    private static void checkNoUnverifiedSetOperations(OldTimeServiceHelper mTimeServiceHelper) {
-        // We don't care about current auto time / time zone state retrievals / listening so we can
-        // use "at least 0" times to indicate they don't matter.
-        verify(mTimeServiceHelper, atLeast(0)).setListener(any());
-        verify(mTimeServiceHelper, atLeast(0)).isTimeDetectionEnabled();
-        verify(mTimeServiceHelper, atLeast(0)).isTimeZoneDetectionEnabled();
-        verify(mTimeServiceHelper, atLeast(0)).isTimeZoneSettingInitialized();
-        verify(mTimeServiceHelper, atLeast(0)).elapsedRealtime();
-        verify(mTimeServiceHelper, atLeast(0)).currentTimeMillis();
-        verifyNoMoreInteractions(mTimeServiceHelper);
-    }
-}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/PhoneSubInfoControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/PhoneSubInfoControllerTest.java
index 00334ff..9117c69 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/PhoneSubInfoControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/PhoneSubInfoControllerTest.java
@@ -22,6 +22,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.anyBoolean;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Mockito.anyString;
 import static org.mockito.Mockito.doReturn;
@@ -54,6 +55,8 @@
         doReturn(2).when(mTelephonyManager).getPhoneCount();
         doReturn(true).when(mSubscriptionController).isActiveSubId(0, TAG);
         doReturn(true).when(mSubscriptionController).isActiveSubId(1, TAG);
+        doReturn(new int[]{0, 1}).when(mSubscriptionManager)
+                .getActiveSubscriptionIdList(anyBoolean());
 
         mServiceManagerMockedServices.put("isub", mSubscriptionController);
         doReturn(mSubscriptionController).when(mSubscriptionController)
diff --git a/tests/telephonytests/src/com/android/internal/telephony/PhoneSwitcherTest.java b/tests/telephonytests/src/com/android/internal/telephony/PhoneSwitcherTest.java
index b154d35..f485a5a 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/PhoneSwitcherTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/PhoneSwitcherTest.java
@@ -16,6 +16,10 @@
 
 package com.android.internal.telephony;
 
+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 com.android.internal.telephony.PhoneSwitcher.EVENT_DATA_ENABLED_CHANGED;
 import static com.android.internal.telephony.PhoneSwitcher.EVENT_PRECISE_CALL_STATE_CHANGED;
 
@@ -91,7 +95,13 @@
     @Mock
     private GsmCdmaCall mActiveCall;
     @Mock
+    private GsmCdmaCall mHoldingCall;
+    @Mock
     private GsmCdmaCall mInactiveCall;
+    @Mock
+    private ISetOpportunisticDataCallback mSetOpptDataCallback1;
+    @Mock
+    private ISetOpportunisticDataCallback mSetOpptDataCallback2;
 
     // The thread that mPhoneSwitcher will handle events in.
     private HandlerThread mHandlerThread;
@@ -116,6 +126,7 @@
 
         doReturn(Call.State.ACTIVE).when(mActiveCall).getState();
         doReturn(Call.State.IDLE).when(mInactiveCall).getState();
+        doReturn(Call.State.HOLDING).when(mHoldingCall).getState();
     }
 
     @After
@@ -572,7 +583,6 @@
         notifyDataEnabled(false);
         notifyPhoneAsInCall(mPhone2);
         verify(mMockRadioConfig, never()).setPreferredDataModem(anyInt(), any());
-        waitABit();
         assertTrue(mPhoneSwitcher.shouldApplyNetworkRequest(internetRequest, 0));
         assertFalse(mPhoneSwitcher.shouldApplyNetworkRequest(internetRequest, 1));
 
@@ -588,6 +598,13 @@
         verify(mMockRadioConfig).setPreferredDataModem(eq(0), any());
         assertTrue(mPhoneSwitcher.shouldApplyNetworkRequest(internetRequest, 0));
         assertFalse(mPhoneSwitcher.shouldApplyNetworkRequest(internetRequest, 1));
+        clearInvocations(mMockRadioConfig);
+
+        // Phone2 has holding call, but data is turned off. So no data switching should happen.
+        notifyPhoneAsInHoldingCall(mPhone2);
+        verify(mMockRadioConfig).setPreferredDataModem(eq(1), any());
+        assertTrue(mPhoneSwitcher.shouldApplyNetworkRequest(internetRequest, 1));
+        assertFalse(mPhoneSwitcher.shouldApplyNetworkRequest(internetRequest, 0));
 
         mHandlerThread.quit();
     }
@@ -861,6 +878,107 @@
         mHandlerThread.quit();
     }
 
+    @Test
+    @SmallTest
+    public void testSetPreferredDataCallback() throws Exception {
+        final int numPhones = 2;
+        final int maxActivePhones = 1;
+        doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
+        initialize(numPhones, maxActivePhones);
+
+        // Mark sub 2 as opportunistic.
+        doReturn(true).when(mSubscriptionController).isOpportunistic(2);
+        // Phone 0 has sub 1, phone 1 has sub 2.
+        // Sub 1 is default data sub.
+        // Both are active subscriptions are active sub, as they are in both active slots.
+        setSlotIndexToSubId(0, 1);
+        setSlotIndexToSubId(1, 2);
+        setDefaultDataSubId(1);
+        waitABit();
+
+        // Validating on sub 10 which is inactive.
+        mPhoneSwitcher.trySetOpportunisticDataSubscription(10, true, mSetOpptDataCallback1);
+        waitABit();
+        verify(mSetOpptDataCallback1).onComplete(SET_OPPORTUNISTIC_SUB_INACTIVE_SUBSCRIPTION);
+
+        // Switch to active subId without validating. Should always succeed.
+        mPhoneSwitcher.trySetOpportunisticDataSubscription(2, false, mSetOpptDataCallback1);
+        waitABit();
+        verify(mSetOpptDataCallback1).onComplete(SET_OPPORTUNISTIC_SUB_SUCCESS);
+
+        // Validating on sub 1 and fails.
+        clearInvocations(mSetOpptDataCallback1);
+        mPhoneSwitcher.trySetOpportunisticDataSubscription(1, true, mSetOpptDataCallback1);
+        waitABit();
+        mPhoneSwitcher.mValidationCallback.onValidationResult(false, 1);
+        waitABit();
+        verify(mSetOpptDataCallback1).onComplete(SET_OPPORTUNISTIC_SUB_VALIDATION_FAILED);
+
+        // Validating on sub 2 and succeeds.
+        mPhoneSwitcher.trySetOpportunisticDataSubscription(2, true, mSetOpptDataCallback2);
+        waitABit();
+        mPhoneSwitcher.mValidationCallback.onValidationResult(true, 2);
+        waitABit();
+        verify(mSetOpptDataCallback2).onComplete(SET_OPPORTUNISTIC_SUB_SUCCESS);
+
+        // Switching data back to primary and validation fails.
+        clearInvocations(mSetOpptDataCallback2);
+        mPhoneSwitcher.trySetOpportunisticDataSubscription(
+                SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, true, mSetOpptDataCallback2);
+        waitABit();
+        mPhoneSwitcher.mValidationCallback.onValidationResult(false, 1);
+        waitABit();
+        verify(mSetOpptDataCallback1).onComplete(SET_OPPORTUNISTIC_SUB_VALIDATION_FAILED);
+
+        // Switching data back to primary and succeeds.
+        clearInvocations(mSetOpptDataCallback2);
+        mPhoneSwitcher.trySetOpportunisticDataSubscription(
+                SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, true, mSetOpptDataCallback2);
+        waitABit();
+        mPhoneSwitcher.mValidationCallback.onValidationResult(true, 1);
+        waitABit();
+        verify(mSetOpptDataCallback2).onComplete(SET_OPPORTUNISTIC_SUB_SUCCESS);
+
+        // Back to back call on same subId.
+        clearInvocations(mSetOpptDataCallback1);
+        clearInvocations(mSetOpptDataCallback2);
+        mPhoneSwitcher.trySetOpportunisticDataSubscription(2, true, mSetOpptDataCallback1);
+        waitABit();
+        verify(mCellularNetworkValidator).validate(eq(2), anyInt(), eq(false),
+                eq(mPhoneSwitcher.mValidationCallback));
+        doReturn(true).when(mCellularNetworkValidator).isValidating();
+        mPhoneSwitcher.trySetOpportunisticDataSubscription(2, true, mSetOpptDataCallback2);
+        waitABit();
+        verify(mSetOpptDataCallback1).onComplete(SET_OPPORTUNISTIC_SUB_VALIDATION_FAILED);
+        verify(mSetOpptDataCallback2, never()).onComplete(anyInt());
+        // Validation succeeds.
+        doReturn(false).when(mCellularNetworkValidator).isValidating();
+        mPhoneSwitcher.mValidationCallback.onValidationResult(true, 2);
+        waitABit();
+        verify(mSetOpptDataCallback2).onComplete(SET_OPPORTUNISTIC_SUB_SUCCESS);
+
+        mPhoneSwitcher.trySetOpportunisticDataSubscription(
+                SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, false, null);
+        waitABit();
+        clearInvocations(mSetOpptDataCallback1);
+        clearInvocations(mSetOpptDataCallback2);
+        clearInvocations(mCellularNetworkValidator);
+        // Back to back call, call 1 to switch to subId 2, call 2 to switch back.
+        mPhoneSwitcher.trySetOpportunisticDataSubscription(2, true, mSetOpptDataCallback1);
+        waitABit();
+        verify(mCellularNetworkValidator).validate(eq(2), anyInt(), eq(false),
+                eq(mPhoneSwitcher.mValidationCallback));
+        doReturn(true).when(mCellularNetworkValidator).isValidating();
+        mPhoneSwitcher.trySetOpportunisticDataSubscription(
+                SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, true, mSetOpptDataCallback2);
+        waitABit();
+        // Call 1 should be cancelled and failed. Call 2 return success immediately as there's no
+        // change.
+        verify(mSetOpptDataCallback1).onComplete(SET_OPPORTUNISTIC_SUB_VALIDATION_FAILED);
+        verify(mSetOpptDataCallback2).onComplete(SET_OPPORTUNISTIC_SUB_SUCCESS);
+        mHandlerThread.quit();
+    }
+
     /* Private utility methods start here */
 
     private void setAllPhonesInactive() {
@@ -878,6 +996,12 @@
         waitABit();
     }
 
+    private void notifyPhoneAsInHoldingCall(Phone phone) {
+        doReturn(mHoldingCall).when(phone).getBackgroundCall();
+        mPhoneSwitcher.sendEmptyMessage(EVENT_PRECISE_CALL_STATE_CHANGED);
+        waitABit();
+    }
+
     private void notifyPhoneAsInactive(Phone phone) {
         doReturn(mInactiveCall).when(phone).getForegroundCall();
         mPhoneSwitcher.sendEmptyMessage(EVENT_PRECISE_CALL_STATE_CHANGED);
@@ -964,9 +1088,10 @@
         mHandlerThread = new HandlerThread("PhoneSwitcherTestThread") {
             @Override
             public void onLooperPrepared() {
-                mPhoneSwitcher = new PhoneSwitcher(maxActivePhones, numPhones,
-                        mContext, mSubscriptionController, this.getLooper(),
-                        mTelRegistryMock, mCommandsInterfaces, mPhones);
+                mPhoneSwitcher = TelephonyComponentFactory.getInstance().inject(PhoneSwitcher.
+                    class.getName()).makePhoneSwitcher(maxActivePhones, numPhones,
+                    mContext, mSubscriptionController, this.getLooper(),
+                    mTelRegistryMock, mCommandsInterfaces, mPhones);
             }
         };
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/RILTest.java b/tests/telephonytests/src/com/android/internal/telephony/RILTest.java
index a6b455d..e08d2f1 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/RILTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/RILTest.java
@@ -130,8 +130,11 @@
 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.ServiceState;
+import android.telephony.SignalStrength;
 import android.telephony.SmsManager;
 import android.telephony.TelephonyManager;
 import android.telephony.data.ApnSetting;
@@ -148,7 +151,6 @@
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -295,7 +297,6 @@
     @Before
     public void setUp() throws Exception {
         super.setUp(RILTest.class.getSimpleName());
-        MockitoAnnotations.initMocks(this);
         mTestHandler = new RILTestHandler(getClass().getSimpleName());
         mTestHandler.start();
         waitUntilReady();
@@ -1897,6 +1898,46 @@
     }
 
     @Test
+    public void testFixupSignalStrength10() {
+        final int gsmWcdmaRssiDbm = -65;
+
+        // Test the positive case where rat=UMTS and SignalStrength=GSM
+        doReturn(ServiceState.RIL_RADIO_TECHNOLOGY_UMTS)
+                .when(mServiceState).getRilVoiceRadioTechnology();
+
+        SignalStrength gsmSignalStrength = new SignalStrength(
+                new CellSignalStrengthCdma(),
+                new CellSignalStrengthGsm(gsmWcdmaRssiDbm, 1, CellInfo.UNAVAILABLE),
+                new CellSignalStrengthWcdma(), new CellSignalStrengthTdscdma(),
+                new CellSignalStrengthLte(), new CellSignalStrengthNr());
+        SignalStrength result = mRILUnderTest.fixupSignalStrength10(gsmSignalStrength);
+
+        assertTrue(result.getCellSignalStrengths(CellSignalStrengthGsm.class).isEmpty());
+        assertFalse(result.getCellSignalStrengths(CellSignalStrengthWcdma.class).isEmpty());
+
+        // Even though the dBm values are equal, the above checks ensure that the value has
+        // been migrated to WCDMA (with no change in the top-level getDbm() result).
+        assertEquals(result.getDbm(), gsmSignalStrength.getDbm());
+
+        // Test the no-op case where rat=GSM and SignalStrength=GSM
+        doReturn(ServiceState.RIL_RADIO_TECHNOLOGY_GSM)
+                .when(mServiceState).getRilVoiceRadioTechnology();
+        result = mRILUnderTest.fixupSignalStrength10(gsmSignalStrength);
+        assertEquals(result, gsmSignalStrength);
+
+        // Check that non-GSM non-WCDMA signal strengths are also passed through.
+        SignalStrength lteSignalStrength = new SignalStrength(
+                new CellSignalStrengthCdma(), new CellSignalStrengthGsm(),
+                new CellSignalStrengthWcdma(), new CellSignalStrengthTdscdma(),
+                new CellSignalStrengthLte(CellInfo.UNAVAILABLE,
+                        -120, -10, CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE,
+                        CellInfo.UNAVAILABLE), new CellSignalStrengthNr());
+        SignalStrength lteResult = mRILUnderTest.fixupSignalStrength10(lteSignalStrength);
+
+        assertEquals(lteResult, lteSignalStrength);
+    }
+
+    @Test
     public void testCreateCarrierRestrictionList() {
         ArrayList<CarrierIdentifier> carriers = new ArrayList<>();
         carriers.add(new CarrierIdentifier("110", "120", null, null, null, null));
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTest.java b/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTest.java
index 60494a8..e605f06 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTest.java
@@ -171,6 +171,7 @@
         rats.add(new Pair<Integer, Boolean>(ServiceState.RIL_RADIO_TECHNOLOGY_GSM, false));
         rats.add(new Pair<Integer, Boolean>(ServiceState.RIL_RADIO_TECHNOLOGY_TD_SCDMA, false));
         rats.add(new Pair<Integer, Boolean>(ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN, false));
+        rats.add(new Pair<Integer, Boolean>(ServiceState.RIL_RADIO_TECHNOLOGY_NR, false));
 
         for (Pair<Integer, Boolean> rat : rats) {
             boolean isCdma = rat.second;
@@ -411,4 +412,41 @@
 
         assertEquals(ss.getDuplexMode(), ServiceState.DUPLEX_MODE_TDD);
     }
+
+    @SmallTest
+    public void testIsPsTech() {
+        ArrayList<Pair<Integer, Boolean>> rats = new ArrayList<Pair<Integer, Boolean>>();
+
+        rats.add(new Pair<Integer, Boolean>(ServiceState.RIL_RADIO_TECHNOLOGY_IS95A, false));
+        rats.add(new Pair<Integer, Boolean>(ServiceState.RIL_RADIO_TECHNOLOGY_IS95B, false));
+        rats.add(new Pair<Integer, Boolean>(ServiceState.RIL_RADIO_TECHNOLOGY_1xRTT, false));
+        rats.add(new Pair<Integer, Boolean>(ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_0, false));
+        rats.add(new Pair<Integer, Boolean>(ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_A, false));
+        rats.add(new Pair<Integer, Boolean>(ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_B, false));
+        rats.add(new Pair<Integer, Boolean>(ServiceState.RIL_RADIO_TECHNOLOGY_EHRPD, false));
+        rats.add(new Pair<Integer, Boolean>(ServiceState.RIL_RADIO_TECHNOLOGY_GPRS, false));
+        rats.add(new Pair<Integer, Boolean>(ServiceState.RIL_RADIO_TECHNOLOGY_EDGE, false));
+        rats.add(new Pair<Integer, Boolean>(ServiceState.RIL_RADIO_TECHNOLOGY_UMTS, false));
+        rats.add(new Pair<Integer, Boolean>(ServiceState.RIL_RADIO_TECHNOLOGY_HSDPA, false));
+        rats.add(new Pair<Integer, Boolean>(ServiceState.RIL_RADIO_TECHNOLOGY_HSUPA, false));
+        rats.add(new Pair<Integer, Boolean>(ServiceState.RIL_RADIO_TECHNOLOGY_HSPA, false));
+        rats.add(new Pair<Integer, Boolean>(ServiceState.RIL_RADIO_TECHNOLOGY_HSPAP, false));
+        rats.add(new Pair<Integer, Boolean>(ServiceState.RIL_RADIO_TECHNOLOGY_GSM, false));
+        rats.add(new Pair<Integer, Boolean>(ServiceState.RIL_RADIO_TECHNOLOGY_TD_SCDMA, false));
+        rats.add(new Pair<Integer, Boolean>(ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN, false));
+
+        rats.add(new Pair<Integer, Boolean>(ServiceState.RIL_RADIO_TECHNOLOGY_LTE, true));
+        rats.add(new Pair<Integer, Boolean>(ServiceState.RIL_RADIO_TECHNOLOGY_LTE_CA, true));
+        rats.add(new Pair<Integer, Boolean>(ServiceState.RIL_RADIO_TECHNOLOGY_NR, true));
+
+        for (Pair<Integer, Boolean> rat : rats) {
+            boolean isPsTech = rat.second;
+            if (isPsTech) {
+                assertTrue("RAT " + rat + " should be PsTech", ServiceState.isPsTech(rat.first));
+            } else {
+                assertFalse("RAT " + rat + " should not be PsTech",
+                        ServiceState.isPsTech(rat.first));
+            }
+        }
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java
index e4c7c11..6def71e 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java
@@ -299,9 +299,10 @@
         mBundle.putStringArray(CarrierConfigManager.KEY_PNN_OVERRIDE_STRING_ARRAY,
                 CARRIER_CONFIG_PNN);
 
-        // Do not force display "No service" when sim is not ready
-        mContextFixture.putBooleanResource(
-                com.android.internal.R.bool.config_display_no_service_when_sim_unready, false);
+        // Do not force display "No service" when sim is not ready in any locales
+        mContextFixture.putStringArrayResource(
+                com.android.internal.R.array.config_display_no_service_when_sim_unready,
+                new String[0]);
 
         logd("ServiceStateTrackerTest -Setup!");
     }
@@ -1756,6 +1757,44 @@
         waitForMs(200);
     }
 
+    private void changeRegStateWithIwlan(int state, CellIdentity cid, int voiceRat, int dataRat,
+            int iwlanState, int iwlanDataRat) {
+        LteVopsSupportInfo lteVopsSupportInfo =
+                new LteVopsSupportInfo(LteVopsSupportInfo.LTE_STATUS_NOT_AVAILABLE,
+                        LteVopsSupportInfo.LTE_STATUS_NOT_AVAILABLE);
+        sst.mPollingContext[0] = 3;
+
+        // PS WWAN
+        NetworkRegistrationInfo dataResult = new NetworkRegistrationInfo(
+                NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
+                state, dataRat, 0, false,
+                null, cid, 1, false, false, false, lteVopsSupportInfo, false);
+        sst.sendMessage(sst.obtainMessage(
+                ServiceStateTracker.EVENT_POLL_STATE_PS_CELLULAR_REGISTRATION,
+                new AsyncResult(sst.mPollingContext, dataResult, null)));
+        waitForMs(200);
+
+        // CS WWAN
+        NetworkRegistrationInfo voiceResult = new NetworkRegistrationInfo(
+                NetworkRegistrationInfo.DOMAIN_CS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
+                state, voiceRat, 0, false,
+                null, cid, false, 0, 0, 0);
+        sst.sendMessage(sst.obtainMessage(
+                ServiceStateTracker.EVENT_POLL_STATE_CS_CELLULAR_REGISTRATION,
+                new AsyncResult(sst.mPollingContext, voiceResult, null)));
+        waitForMs(200);
+
+        // PS WLAN
+        NetworkRegistrationInfo dataIwlanResult = new NetworkRegistrationInfo(
+                NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WLAN,
+                iwlanState, iwlanDataRat, 0, false,
+                null, null, 1, false, false, false, lteVopsSupportInfo, false);
+        sst.sendMessage(sst.obtainMessage(
+                ServiceStateTracker.EVENT_POLL_STATE_PS_IWLAN_REGISTRATION,
+                new AsyncResult(sst.mPollingContext, dataIwlanResult, null)));
+        waitForMs(200);
+    }
+
     // Edge and GPRS are grouped under the same family and Edge has higher rate than GPRS.
     // Expect no rat update when move from E to G.
     @Test
@@ -1928,6 +1967,49 @@
         assertTrue(Arrays.equals(new int[0], sst.mSS.getCellBandwidths()));
     }
 
+    /**
+     * Ensure that TransportManager changes due to transport preference changes are picked up in the
+     * new ServiceState when a poll event occurs. This causes ServiceState#getRilDataRadioTechnology
+     * to change even though the underlying transports have not changed state.
+     */
+    @SmallTest
+    @Test
+    public void testRilDataTechnologyChangeTransportPreference() {
+        when(mTransportManager.isAnyApnPreferredOnIwlan()).thenReturn(false);
+
+        // Start state: Cell data only LTE + IWLAN
+        CellIdentityLte cellIdentity =
+                new CellIdentityLte(1, 1, 5, 1, 5000, "001", "01", "test", "tst");
+        changeRegStateWithIwlan(
+                // WWAN
+                NetworkRegistrationInfo.REGISTRATION_STATE_HOME, cellIdentity,
+                TelephonyManager.NETWORK_TYPE_UNKNOWN, TelephonyManager.NETWORK_TYPE_LTE,
+                // WLAN
+                NetworkRegistrationInfo.REGISTRATION_STATE_HOME,
+                TelephonyManager.NETWORK_TYPE_IWLAN);
+        assertEquals(ServiceState.RIL_RADIO_TECHNOLOGY_LTE, sst.mSS.getRilDataRadioTechnology());
+
+        sst.registerForDataRegStateOrRatChanged(AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
+                mTestHandler, EVENT_DATA_RAT_CHANGED, null);
+        // transport preference change for a PDN for IWLAN occurred, no registration change, but
+        // trigger unrelated poll to pick up transport preference.
+        when(mTransportManager.isAnyApnPreferredOnIwlan()).thenReturn(true);
+        changeRegStateWithIwlan(
+                // WWAN
+                NetworkRegistrationInfo.REGISTRATION_STATE_HOME, cellIdentity,
+                TelephonyManager.NETWORK_TYPE_UNKNOWN, TelephonyManager.NETWORK_TYPE_LTE,
+                // WLAN
+                NetworkRegistrationInfo.REGISTRATION_STATE_HOME,
+                TelephonyManager.NETWORK_TYPE_IWLAN);
+        // Now check to make sure a transport independent notification occurred for the registrants.
+        // There will be two, one when the registration happened and another when the transport
+        // preference changed.
+        ArgumentCaptor<Message> messageArgumentCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(mTestHandler, times(2)).sendMessageAtTime(messageArgumentCaptor.capture(),
+                anyLong());
+        assertEquals(ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN, sst.mSS.getRilDataRadioTechnology());
+    }
+
     @Test
     public void testGetServiceProviderNameWithBrandOverride() {
         String brandOverride = "spn from brand override";
@@ -2279,6 +2361,27 @@
         assertThat(b.getBoolean(TelephonyIntents.EXTRA_SHOW_PLMN)).isTrue();
     }
 
+    @Test
+    public void testShouldForceDisplayNoService_forceBasedOnLocale() {
+        // set up unaffected locale (US) and clear the resource
+        doReturn("us").when(mLocaleTracker).getCurrentCountry();
+        mContextFixture.putStringArrayResource(
+                com.android.internal.R.array.config_display_no_service_when_sim_unready,
+                new String[0]);
+        assertFalse(sst.shouldForceDisplayNoService());
+
+        // set up the resource to include Germany
+        mContextFixture.putStringArrayResource(
+                com.android.internal.R.array.config_display_no_service_when_sim_unready,
+                new String[]{"de"});
+        doReturn("us").when(mLocaleTracker).getCurrentCountry();
+        assertFalse(sst.shouldForceDisplayNoService());
+
+        // mock the locale to Germany
+        doReturn("de").when(mLocaleTracker).getCurrentCountry();
+        assertTrue(sst.shouldForceDisplayNoService());
+    }
+
     private Bundle getExtrasFromLastSpnUpdateIntent() {
         // Verify the spn update notification was sent
         ArgumentCaptor<Intent> intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SubscriptionControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/SubscriptionControllerTest.java
index e85172a..0b84be6 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SubscriptionControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SubscriptionControllerTest.java
@@ -94,7 +94,8 @@
         replaceInstance(MultiSimSettingController.class, "sInstance", null,
                 mMultiSimSettingControllerMock);
 
-        SubscriptionController.init(mContext, null);
+        TelephonyComponentFactory.getInstance().inject(SubscriptionController.class.
+                getName()).initSubscriptionController(mContext, null);
         mSubscriptionControllerUT = SubscriptionController.getInstance();
         mCallingPackage = mContext.getOpPackageName();
 
@@ -207,7 +208,7 @@
 
         /* Setting */
         String disName = "TESTING";
-        int nameSource = SubscriptionManager.NAME_SOURCE_SIM_SOURCE;
+        int nameSource = SubscriptionManager.NAME_SOURCE_SIM_SPN;
         mSubscriptionControllerUT.setDisplayNameUsingSrc(disName, subID, nameSource);
         SubscriptionInfo subInfo = mSubscriptionControllerUT
                 .getActiveSubscriptionInfo(subID, mCallingPackage);
@@ -470,12 +471,12 @@
 
         // Changing non-opportunistic sub1 shouldn't trigger callback.
         mSubscriptionControllerUT.setDisplayNameUsingSrc("DisplayName", 1,
-                SubscriptionManager.NAME_SOURCE_SIM_SOURCE);
+                SubscriptionManager.NAME_SOURCE_SIM_SPN);
         verify(mTelephonyRegisteryMock, times(1))
                 .notifyOpportunisticSubscriptionInfoChanged();
 
         mSubscriptionControllerUT.setDisplayNameUsingSrc("DisplayName", 2,
-                SubscriptionManager.NAME_SOURCE_SIM_SOURCE);
+                SubscriptionManager.NAME_SOURCE_SIM_SPN);
         verify(mTelephonyRegisteryMock, times(2))
                 .notifyOpportunisticSubscriptionInfoChanged();
     }
@@ -1265,4 +1266,28 @@
         mSubscriptionControllerUT.setAlwaysAllowMmsData(0, false);
         verify(mDataEnabledSettings).setAlwaysAllowMmsData(eq(false));
     }
+
+    @Test
+    @SmallTest
+    public void testNameSourcePriority() throws Exception {
+        assertTrue(mSubscriptionControllerUT.getNameSourcePriority(
+                SubscriptionManager.NAME_SOURCE_USER_INPUT)
+                > mSubscriptionControllerUT.getNameSourcePriority(
+                        SubscriptionManager.NAME_SOURCE_CARRIER));
+
+        assertTrue(mSubscriptionControllerUT.getNameSourcePriority(
+                SubscriptionManager.NAME_SOURCE_CARRIER)
+                > mSubscriptionControllerUT.getNameSourcePriority(
+                SubscriptionManager.NAME_SOURCE_SIM_SPN));
+
+        assertTrue(mSubscriptionControllerUT.getNameSourcePriority(
+                SubscriptionManager.NAME_SOURCE_SIM_SPN)
+                > mSubscriptionControllerUT.getNameSourcePriority(
+                SubscriptionManager.NAME_SOURCE_SIM_PNN));
+
+        assertTrue(mSubscriptionControllerUT.getNameSourcePriority(
+                SubscriptionManager.NAME_SOURCE_SIM_PNN)
+                > mSubscriptionControllerUT.getNameSourcePriority(
+                SubscriptionManager.NAME_SOURCE_DEFAULT_SOURCE));
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoUpdaterTest.java b/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoUpdaterTest.java
index 6d3e2d4..e3b620c 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoUpdaterTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoUpdaterTest.java
@@ -48,6 +48,7 @@
 import android.telephony.CarrierConfigManager;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
+import android.telephony.UiccAccessRule;
 import android.test.mock.MockContentProvider;
 import android.test.mock.MockContentResolver;
 import android.test.suitebuilder.annotation.SmallTest;
@@ -55,6 +56,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;
@@ -76,6 +78,7 @@
     private static final int FAKE_SUB_ID_1 = 0;
     private static final int FAKE_SUB_ID_2 = 1;
     private static final int FAKE_CARD_ID = 0;
+    private static final String FAKE_EID = "89049032000001000000031328322874";
     private static final String FAKE_MCC_MNC_1 = "123456";
     private static final String FAKE_MCC_MNC_2 = "456789";
 
@@ -485,6 +488,8 @@
     @SmallTest
     public void testUpdateEmbeddedSubscriptions_listSuccess() throws Exception {
         when(mEuiccManager.isEnabled()).thenReturn(true);
+        when(mEuiccManager.createForCardId(anyInt())).thenReturn(mEuiccManager);
+        when(mEuiccManager.getEid()).thenReturn(FAKE_EID);
 
         EuiccProfileInfo[] euiccProfiles = new EuiccProfileInfo[] {
                 new EuiccProfileInfo("1", null /* accessRules */, null /* nickname */),
@@ -753,4 +758,47 @@
         assertNull(cvCaptor.getValue().getAsString(SubscriptionManager.GROUP_UUID));
         assertEquals(2, cvCaptor.getValue().size());
     }
+
+    @Test
+    @SmallTest
+    public void testUpdateFromCarrierConfigCarrierCertificates() {
+        String[] certs = new String[2];
+        certs[0] = "d1f1";
+        certs[1] = "b5d6";
+
+        UiccAccessRule[] carrierConfigAccessRules = new UiccAccessRule[certs.length];
+        for (int i = 0; i < certs.length; i++) {
+            carrierConfigAccessRules[i] = new UiccAccessRule(
+                IccUtils.hexStringToBytes(certs[i]), null, 0);
+        }
+
+        final int phoneId = mPhone.getPhoneId();
+        PersistableBundle carrierConfig = new PersistableBundle();
+        carrierConfig.putStringArray(
+                CarrierConfigManager.KEY_CARRIER_CERTIFICATE_STRING_ARRAY, certs);
+
+        String carrierPackageName = "FakeCarrierPackageName";
+
+        doReturn(FAKE_SUB_ID_1).when(mSubscriptionController).getSubIdUsingPhoneId(phoneId);
+        doReturn(mSubInfo).when(mSubscriptionController).getSubscriptionInfo(eq(FAKE_SUB_ID_1));
+        doReturn(false).when(mSubInfo).isOpportunistic();
+        doReturn(Collections.singletonList(carrierPackageName)).when(mTelephonyManager)
+                .getCarrierPackageNamesForIntentAndPhone(any(), eq(phoneId));
+        ((MockContentResolver) mContext.getContentResolver()).addProvider(
+                SubscriptionManager.CONTENT_URI.getAuthority(),
+                new FakeSubscriptionContentProvider());
+
+        mUpdater.updateSubscriptionByCarrierConfig(mPhone.getPhoneId(),
+                carrierPackageName, carrierConfig);
+
+        ArgumentCaptor<ContentValues> cvCaptor = ArgumentCaptor.forClass(ContentValues.class);
+        verify(mContentProvider, times(1)).update(
+                eq(SubscriptionManager.getUriForSubscriptionId(FAKE_SUB_ID_1)),
+                cvCaptor.capture(), eq(null), eq(null));
+        assertEquals(carrierConfigAccessRules, UiccAccessRule.decodeRules(cvCaptor.getValue()
+                .getAsByteArray(SubscriptionManager.ACCESS_RULES_FROM_CARRIER_CONFIGS)));
+        assertEquals(1, cvCaptor.getValue().size());
+        verify(mSubscriptionController, times(1)).refreshCachedActiveSubscriptionInfoList();
+        verify(mSubscriptionController, times(1)).notifySubscriptionInfoChanged();
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/TelephonyPermissionsTest.java b/tests/telephonytests/src/com/android/internal/telephony/TelephonyPermissionsTest.java
index 5576f39..d94863c 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/TelephonyPermissionsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/TelephonyPermissionsTest.java
@@ -18,6 +18,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
@@ -30,8 +31,11 @@
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.IBinder;
+import android.os.ServiceManager;
 import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.telephony.SubscriptionManager;
@@ -48,14 +52,15 @@
 import org.mockito.MockitoAnnotations;
 
 import java.lang.reflect.Field;
+import java.util.Map;
 
 @SmallTest
 public class TelephonyPermissionsTest {
 
     private static final int SUB_ID = 55555;
     private static final int SUB_ID_2 = 22222;
-    private static final int PID = 12345;
-    private static final int UID = 54321;
+    private static final int PID = Binder.getCallingPid();
+    private static final int UID = Binder.getCallingUid();
     private static final String PACKAGE = "com.example";
     private static final String MSG = "message";
 
@@ -68,6 +73,8 @@
     @Mock
     private ITelephony mMockTelephony;
     @Mock
+    private IBinder mMockTelephonyBinder;
+    @Mock
     private PackageManager mMockPackageManager;
     @Mock
     private ApplicationInfo mMockApplicationInfo;
@@ -85,7 +92,8 @@
                 mMockSubscriptionManager);
         when(mMockContext.getSystemService(Context.DEVICE_POLICY_SERVICE)).thenReturn(
                 mMockDevicePolicyManager);
-        when(mMockSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(new int[]{SUB_ID});
+        when(mMockSubscriptionManager.getActiveSubscriptionIdList(anyBoolean())).thenReturn(
+                new int[]{SUB_ID});
 
         // By default, assume we have no permissions or app-ops bits.
         doThrow(new SecurityException()).when(mMockContext)
@@ -98,10 +106,13 @@
                 AppOpsManager.MODE_ERRORED);
         when(mMockTelephony.getCarrierPrivilegeStatusForUid(eq(SUB_ID), eq(UID)))
                 .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
+        when(mMockTelephony.getCarrierPrivilegeStatusForUid(eq(SUB_ID_2), eq(UID)))
+                .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
         when(mMockContext.checkPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
                 PID, UID)).thenReturn(PackageManager.PERMISSION_DENIED);
         when(mMockDevicePolicyManager.checkDeviceIdentifierAccess(eq(PACKAGE), eq(PID),
                 eq(UID))).thenReturn(false);
+        setTelephonyMockAsService();
     }
 
     @Test
@@ -240,8 +251,8 @@
     public void testCheckReadDeviceIdentifiers_noPermissions() throws Exception {
         setupMocksForDeviceIdentifiersErrorPath();
         try {
-            TelephonyPermissions.checkReadDeviceIdentifiers(mMockContext, () -> mMockTelephony,
-                    SUB_ID, PID, UID, PACKAGE, MSG);
+            TelephonyPermissions.checkCallingOrSelfReadDeviceIdentifiers(mMockContext,
+                    SUB_ID, PACKAGE, MSG);
             fail("Should have thrown SecurityException");
         } catch (SecurityException e) {
             // expected
@@ -253,8 +264,8 @@
         when(mMockContext.checkPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
                 PID, UID)).thenReturn(PackageManager.PERMISSION_GRANTED);
         assertTrue(
-                TelephonyPermissions.checkReadDeviceIdentifiers(mMockContext, () -> mMockTelephony,
-                        SUB_ID, PID, UID, PACKAGE, MSG));
+                TelephonyPermissions.checkCallingOrSelfReadDeviceIdentifiers(mMockContext,
+                        SUB_ID, PACKAGE, MSG));
     }
 
     @Test
@@ -262,8 +273,8 @@
         when(mMockTelephony.getCarrierPrivilegeStatusForUid(eq(SUB_ID), eq(UID)))
                 .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS);
         assertTrue(
-                TelephonyPermissions.checkReadDeviceIdentifiers(mMockContext, () -> mMockTelephony,
-                        SUB_ID, PID, UID, PACKAGE, MSG));
+                TelephonyPermissions.checkCallingOrSelfReadDeviceIdentifiers(mMockContext,
+                        SUB_ID, PACKAGE, MSG));
     }
 
     @Test
@@ -271,8 +282,8 @@
         when(mMockAppOps.noteOpNoThrow(AppOpsManager.OPSTR_READ_DEVICE_IDENTIFIERS, UID,
                 PACKAGE)).thenReturn(AppOpsManager.MODE_ALLOWED);
         assertTrue(
-                TelephonyPermissions.checkReadDeviceIdentifiers(mMockContext, () -> mMockTelephony,
-                        SUB_ID, PID, UID, PACKAGE, MSG));
+                TelephonyPermissions.checkCallingOrSelfReadDeviceIdentifiers(mMockContext,
+                        SUB_ID, PACKAGE, MSG));
     }
 
     @Test
@@ -280,8 +291,8 @@
         when(mMockDevicePolicyManager.checkDeviceIdentifierAccess(eq(PACKAGE), eq(PID),
                 eq(UID))).thenReturn(true);
         assertTrue(
-                TelephonyPermissions.checkReadDeviceIdentifiers(mMockContext, () -> mMockTelephony,
-                        SUB_ID, PID, UID, PACKAGE, MSG));
+                TelephonyPermissions.checkCallingOrSelfReadDeviceIdentifiers(mMockContext,
+                        SUB_ID, PACKAGE, MSG));
     }
 
     @Test
@@ -293,8 +304,8 @@
                 UID)).thenReturn(PackageManager.PERMISSION_GRANTED);
         setupMocksForDeviceIdentifiersErrorPath();
         try {
-            TelephonyPermissions.checkReadDeviceIdentifiers(mMockContext, () -> mMockTelephony,
-                    SUB_ID, PID, UID, PACKAGE, MSG);
+            TelephonyPermissions.checkCallingOrSelfReadDeviceIdentifiers(mMockContext,
+                    SUB_ID, PACKAGE, MSG);
             fail("Should have thrown SecurityException");
         } catch (SecurityException e) {
             // expected
@@ -311,20 +322,34 @@
         setupMocksForDeviceIdentifiersErrorPath();
         mMockApplicationInfo.targetSdkVersion = Build.VERSION_CODES.P;
         assertFalse(
-                TelephonyPermissions.checkReadDeviceIdentifiers(mMockContext, () -> mMockTelephony,
-                        SUB_ID, PID, UID, PACKAGE, MSG));
+                TelephonyPermissions.checkCallingOrSelfReadDeviceIdentifiers(mMockContext,
+                        SUB_ID, PACKAGE, MSG));
     }
 
     @Test
     public void testCheckReadDeviceIdentifiers_hasCarrierPrivilegesOnOtherSubscription()
             throws Exception {
-        when(mMockSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(
+        when(mMockSubscriptionManager.getActiveSubscriptionIdList(anyBoolean())).thenReturn(
                 new int[]{SUB_ID, SUB_ID_2});
         when(mMockTelephony.getCarrierPrivilegeStatusForUid(eq(SUB_ID_2), eq(UID))).thenReturn(
                 TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS);
         assertTrue(
-                TelephonyPermissions.checkReadDeviceIdentifiers(mMockContext, () -> mMockTelephony,
-                        SUB_ID, PID, UID, PACKAGE, MSG));
+                TelephonyPermissions.checkCallingOrSelfReadDeviceIdentifiers(mMockContext,
+                        SUB_ID, PACKAGE, MSG));
+    }
+
+    @Test
+    public void testCheckReadDeviceIdentifiers_hasCarrierPrivilegesOnInvisibleSubscription()
+            throws Exception {
+        when(mMockSubscriptionManager.getActiveSubscriptionIdList(true)).thenReturn(
+                new int[]{SUB_ID});
+        when(mMockSubscriptionManager.getActiveSubscriptionIdList(false)).thenReturn(
+                new int[]{SUB_ID, SUB_ID_2});
+        when(mMockTelephony.getCarrierPrivilegeStatusForUid(eq(SUB_ID_2), eq(UID)))
+                .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS);
+        assertTrue(
+                TelephonyPermissions.checkCallingOrSelfReadDeviceIdentifiers(mMockContext,
+                        SUB_ID, PACKAGE, MSG));
     }
 
     @Test
@@ -337,8 +362,8 @@
         when(mMockAppOps.noteOpNoThrow(AppOpsManager.OPSTR_READ_DEVICE_IDENTIFIERS, UID,
                 PACKAGE)).thenReturn(AppOpsManager.MODE_ALLOWED);
         assertTrue(
-                TelephonyPermissions.checkReadDeviceIdentifiers(mMockContext, () -> mMockTelephony,
-                        SUB_ID, PID, UID, PACKAGE, MSG));
+                TelephonyPermissions.checkCallingOrSelfReadDeviceIdentifiers(mMockContext,
+                        SUB_ID, PACKAGE, MSG));
     }
 
     @Test
@@ -348,14 +373,79 @@
         // case.
         setupMocksForDeviceIdentifiersErrorPath();
         try {
-            TelephonyPermissions.checkReadDeviceIdentifiers(mMockContext, () -> mMockTelephony,
-                    SUB_ID, PID, UID, null, MSG);
+            TelephonyPermissions.checkCallingOrSelfReadDeviceIdentifiers(mMockContext,
+                    SUB_ID, null, MSG);
             fail("Should have thrown SecurityException");
         } catch (SecurityException e) {
             // expected
         }
     }
 
+    @Test
+    public void testCheckCallingOrSelfReadSubscriberIdentifiers_noPermissions() throws Exception {
+        setupMocksForDeviceIdentifiersErrorPath();
+        setTelephonyMockAsService();
+        when(mMockContext.checkPermission(
+                eq(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE),
+                anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_DENIED);
+        when(mMockAppOps.noteOpNoThrow(anyString(), anyInt(), eq(PACKAGE))).thenReturn(
+                AppOpsManager.MODE_ERRORED);
+        when(mMockDevicePolicyManager.checkDeviceIdentifierAccess(eq(PACKAGE), anyInt(),
+                anyInt())).thenReturn(false);
+        try {
+            TelephonyPermissions.checkCallingOrSelfReadSubscriberIdentifiers(mMockContext,
+                    SUB_ID, PACKAGE, MSG);
+            fail("Should have thrown SecurityException");
+        } catch (SecurityException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testCheckCallingOrSelfReadSubscriberIdentifiers_carrierPrivileges()
+            throws Exception {
+        setTelephonyMockAsService();
+        when(mMockContext.checkPermission(
+                eq(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE),
+                anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_DENIED);
+        when(mMockTelephony.getCarrierPrivilegeStatusForUid(eq(SUB_ID), anyInt()))
+                .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS);
+        assertTrue(
+                TelephonyPermissions.checkCallingOrSelfReadSubscriberIdentifiers(mMockContext,
+                        SUB_ID, PACKAGE, MSG));
+    }
+
+    @Test
+    public void testCheckCallingOrSelfReadSubscriberIdentifiers_carrierPrivilegesOnOtherSub()
+            throws Exception {
+        setupMocksForDeviceIdentifiersErrorPath();
+        setTelephonyMockAsService();
+        when(mMockContext.checkPermission(
+                eq(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE),
+                anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_DENIED);
+        when(mMockSubscriptionManager.getActiveSubscriptionIdList(anyBoolean())).thenReturn(
+                new int[]{SUB_ID, SUB_ID_2});
+        when(mMockTelephony.getCarrierPrivilegeStatusForUid(eq(SUB_ID_2), anyInt())).thenReturn(
+                TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS);
+        // Carrier privilege on the other active sub shouldn't allow access to this sub.
+        try {
+            TelephonyPermissions.checkCallingOrSelfReadSubscriberIdentifiers(mMockContext,
+                    SUB_ID, PACKAGE, MSG);
+            fail("Should have thrown SecurityException");
+        } catch (SecurityException e) {
+            // expected
+        }
+    }
+
+    // Put mMockTelephony into service cache so that TELEPHONY_SUPPLIER will get it.
+    private void setTelephonyMockAsService() throws Exception {
+        when(mMockTelephonyBinder.queryLocalInterface(anyString())).thenReturn(mMockTelephony);
+        Field field = ServiceManager.class.getDeclaredField("sCache");
+        field.setAccessible(true);
+        ((Map<String, IBinder>) field.get(null)).put(Context.TELEPHONY_SERVICE,
+                mMockTelephonyBinder);
+    }
+
     public static class FakeSettingsConfigProvider extends FakeSettingsProvider {
         private static final String PROPERTY_DEVICE_IDENTIFIER_ACCESS_RESTRICTIONS_DISABLED =
                 DeviceConfig.NAMESPACE_PRIVACY + "/"
diff --git a/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java b/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
index b18ded6..09fcc24 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
@@ -38,6 +38,7 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
+import android.net.ConnectivityManager;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
@@ -251,6 +252,7 @@
     protected SubscriptionManager mSubscriptionManager;
     protected EuiccManager mEuiccManager;
     protected PackageManager mPackageManager;
+    protected ConnectivityManager mConnectivityManager;
     protected AppOpsManager mAppOpsManager;
     protected SimulatedCommands mSimulatedCommands;
     protected ContextFixture mContextFixture;
@@ -377,6 +379,8 @@
         mSubscriptionManager = (SubscriptionManager) mContext.getSystemService(
                 Context.TELEPHONY_SUBSCRIPTION_SERVICE);
         mEuiccManager = (EuiccManager) mContext.getSystemService(Context.EUICC_SERVICE);
+        mConnectivityManager = (ConnectivityManager)
+                mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
         mPackageManager = mContext.getPackageManager();
         mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/VisualVoicemailSmsParserTest.java b/tests/telephonytests/src/com/android/internal/telephony/VisualVoicemailSmsParserTest.java
index 12a4655..13fa2d6 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/VisualVoicemailSmsParserTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/VisualVoicemailSmsParserTest.java
@@ -16,7 +16,9 @@
 package com.android.internal.telephony;
 
 import android.test.suitebuilder.annotation.SmallTest;
+
 import com.android.internal.telephony.VisualVoicemailSmsParser.WrappedMessageData;
+
 import junit.framework.TestCase;
 
 public class VisualVoicemailSmsParserTest extends TestCase {
diff --git a/tests/telephonytests/src/com/android/internal/telephony/WapPushOverSmsTest.java b/tests/telephonytests/src/com/android/internal/telephony/WapPushOverSmsTest.java
index 3643d47..284a859 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/WapPushOverSmsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/WapPushOverSmsTest.java
@@ -84,7 +84,7 @@
                 (byte) 0xFF
         };
 
-        mWapPushOverSmsUT.dispatchWapPdu(pdu, null, mInboundSmsHandler, "123456");
+        mWapPushOverSmsUT.dispatchWapPdu(pdu, null, mInboundSmsHandler, "123456", 0);
 
         ArgumentCaptor<Intent> intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class);
         verify(mInboundSmsHandler).dispatchIntent(intentArgumentCaptor.capture(),
@@ -92,7 +92,8 @@
                 eq(AppOpsManager.OP_RECEIVE_WAP_PUSH),
                 nullable(Bundle.class),
                 isNull(BroadcastReceiver.class),
-                eq(UserHandle.SYSTEM));
+                eq(UserHandle.SYSTEM),
+                anyInt());
         Intent intent = intentArgumentCaptor.getValue();
         assertEquals(Telephony.Sms.Intents.WAP_PUSH_DELIVER_ACTION, intent.getAction());
         assertEquals(0xFF, intent.getIntExtra("transactionId", 0));
@@ -138,13 +139,14 @@
                 111, 109, 47, 115, 97, 100, 102, 100, 100, 0};
 
         assertEquals(Telephony.Sms.Intents.RESULT_SMS_HANDLED,
-                mWapPushOverSmsUT.dispatchWapPdu(pdu, null, mInboundSmsHandler));
+                mWapPushOverSmsUT.dispatchWapPdu(pdu, null, mInboundSmsHandler, null, 0));
         verify(mInboundSmsHandler, never()).dispatchIntent(
                 any(Intent.class),
                 any(String.class),
                 anyInt(),
                 any(Bundle.class),
                 any(BroadcastReceiver.class),
-                any(UserHandle.class));
+                any(UserHandle.class),
+                anyInt());
     }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/cdma/CdmaInboundSmsHandlerTest.java b/tests/telephonytests/src/com/android/internal/telephony/cdma/CdmaInboundSmsHandlerTest.java
index 2dcd428..632f46f 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/cdma/CdmaInboundSmsHandlerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/cdma/CdmaInboundSmsHandlerTest.java
@@ -78,6 +78,7 @@
     private FakeSmsContentProvider mContentProvider;
     private InboundSmsTracker mInboundSmsTracker;
     private byte[] mSmsPdu = new byte[]{(byte) 0xFF, (byte) 0xFF, (byte) 0xFF};
+    private int mSubId0 = 0;
 
     private class CdmaInboundSmsHandlerTestHandler extends HandlerThread {
 
@@ -137,16 +138,17 @@
                 "1234567890", /* address */
                 "1234567890", /* displayAddress */
                 "This is the message body of a single-part message" /* messageBody */,
-                false /* isClass0 */);
+                false, /* isClass0 */
+                mSubId0);
 
         doReturn(mInboundSmsTracker).when(mTelephonyComponentFactory)
                 .makeInboundSmsTracker(nullable(byte[].class), anyLong(), anyInt(), anyBoolean(),
                 anyBoolean(), nullable(String.class), nullable(String.class),
-                nullable(String.class), anyBoolean());
+                nullable(String.class), anyBoolean(), anyInt());
         doReturn(mInboundSmsTracker).when(mTelephonyComponentFactory)
                 .makeInboundSmsTracker(nullable(byte[].class), anyLong(), anyInt(), anyBoolean(),
                 nullable(String.class), nullable(String.class), anyInt(), anyInt(),
-                anyInt(), anyBoolean(), nullable(String.class), anyBoolean());
+                anyInt(), anyBoolean(), nullable(String.class), anyBoolean(), anyInt());
         doReturn(mInboundSmsTracker).when(mTelephonyComponentFactory)
                 .makeInboundSmsTracker(nullable(Cursor.class), anyBoolean());
 
@@ -232,11 +234,12 @@
                 "1234567890", /* address */
                 blockedNumber, /* displayAddress */
                 "This is the message body of a single-part message" /* messageBody */,
-                false /* isClass0 */);
+                false, /* isClass0 */
+                mSubId0);
         doReturn(mInboundSmsTracker).when(mTelephonyComponentFactory)
                 .makeInboundSmsTracker(nullable(byte[].class), anyLong(), anyInt(), anyBoolean(),
                 anyBoolean(), nullable(String.class), nullable(String.class),
-                nullable(String.class), anyBoolean());
+                nullable(String.class), anyBoolean(), anyInt());
         mFakeBlockedNumberContentProvider.mBlockedNumbers.add(blockedNumber);
 
         transitionFromStartupToIdle();
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 601bb65..018747e 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DataConnectionTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DataConnectionTest.java
@@ -506,6 +506,12 @@
         return (NetworkCapabilities) method.invoke(mDc);
     }
 
+    private int getDisallowedApnTypes() throws Exception {
+        Method method = DataConnection.class.getDeclaredMethod("getDisallowedApnTypes");
+        method.setAccessible(true);
+        return (int) method.invoke(mDc);
+    }
+
     @Test
     @SmallTest
     public void testNetworkCapability() throws Exception {
@@ -522,6 +528,10 @@
         assertFalse("capabilities: " + getNetworkCapabilities(), getNetworkCapabilities()
                 .hasCapability(NetworkCapabilities.NET_CAPABILITY_MMS));
 
+        mContextFixture.getCarrierConfigBundle().putStringArray(
+                CarrierConfigManager.KEY_CARRIER_WWAN_DISALLOWED_APN_TYPES_STRING_ARRAY,
+                new String[] {"supl"});
+
         mDc.sendMessage(DataConnection.EVENT_DISCONNECT, mDcp);
         waitForMs(100);
         doReturn(mApn1).when(mApnContext).getApnSetting();
@@ -532,7 +542,7 @@
                 .hasCapability(NetworkCapabilities.NET_CAPABILITY_DUN));
         assertTrue("capabilities: " + getNetworkCapabilities(), getNetworkCapabilities()
                 .hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET));
-        assertTrue("capabilities: " + getNetworkCapabilities(), getNetworkCapabilities()
+        assertFalse("capabilities: " + getNetworkCapabilities(), getNetworkCapabilities()
                 .hasCapability(NetworkCapabilities.NET_CAPABILITY_SUPL));
     }
 
@@ -932,4 +942,16 @@
 
         assertTrue(isUnmeteredUseOnly());
     }
+
+    @Test
+    @SmallTest
+    public void testGetDisallowedApnTypes() throws Exception {
+        mContextFixture.getCarrierConfigBundle().putStringArray(
+                CarrierConfigManager.KEY_CARRIER_WWAN_DISALLOWED_APN_TYPES_STRING_ARRAY,
+                new String[] { "mms", "supl", "fota" });
+        testConnectEvent();
+
+        assertEquals(ApnSetting.TYPE_MMS | ApnSetting.TYPE_SUPL | ApnSetting.TYPE_FOTA,
+                getDisallowedApnTypes());
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DataEnabledOverrideTest.java b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DataEnabledOverrideTest.java
index 50265b9..3d0a4e0 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DataEnabledOverrideTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DataEnabledOverrideTest.java
@@ -210,10 +210,9 @@
         doReturn(2).when(mSubscriptionController).getDefaultSmsSubId();
 
         doReturn(PhoneConstants.State.OFFHOOK).when(mPhone).getState();
-        assertTrue(deo.getRules(), deo.shouldOverrideDataEnabledSettings(mPhone,
-                ApnSetting.TYPE_DEFAULT));
         deo.setDataAllowedInVoiceCall(false);
-        assertFalse(deo.shouldOverrideDataEnabledSettings(mPhone, ApnSetting.TYPE_DEFAULT));
+        assertFalse(deo.getRules(), deo.shouldOverrideDataEnabledSettings(
+                mPhone, ApnSetting.TYPE_DEFAULT));
         assertFalse(deo.isDataAllowedInVoiceCall());
     }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DataEnabledSettingsTest.java b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DataEnabledSettingsTest.java
index 3bc6f47..52c9573 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DataEnabledSettingsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DataEnabledSettingsTest.java
@@ -91,7 +91,7 @@
         ArgumentCaptor<String> stringCaptor = ArgumentCaptor.forClass(String.class);
         verify(mSubscriptionController).setDataEnabledOverrideRules(anyInt(),
                 stringCaptor.capture());
-        assertEquals("*=nonDefault&inVoiceCall", stringCaptor.getValue());
+        assertEquals("*=nonDefault&inVoiceCall&DefaultDataOn&dsdsEnabled", stringCaptor.getValue());
 
         clearInvocations(mSubscriptionController);
 
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 4009704..e2774d7 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;
@@ -66,8 +67,10 @@
 import android.telephony.CarrierConfigManager;
 import android.telephony.NetworkRegistrationInfo;
 import android.telephony.ServiceState;
+import android.telephony.SignalStrength;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
+import android.telephony.SubscriptionPlan;
 import android.telephony.TelephonyManager;
 import android.telephony.data.ApnSetting;
 import android.telephony.data.DataProfile;
@@ -97,9 +100,15 @@
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
 
+import java.lang.reflect.Field;
 import java.lang.reflect.Method;
+import java.time.Period;
+import java.time.ZonedDateTime;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
 
 public class DcTrackerTest extends TelephonyTest {
 
@@ -145,6 +154,8 @@
     PackageManagerService mMockPackageManagerInternal;
     @Mock
     Handler mHandler;
+    @Mock
+    NetworkPolicyManager mNetworkPolicyManager;
 
     private DcTracker mDct;
     private DcTrackerTestHandler mDcTrackerTestHandler;
@@ -503,7 +514,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);
@@ -610,6 +622,33 @@
         }
     }
 
+    // Test the unmetered APN setup when data is disabled.
+    @Test
+    @SmallTest
+    public void testTrySetupDataUnmeteredDefaultNotSelected() throws Exception {
+        initApns(PhoneConstants.APN_TYPE_FOTA, new String[]{PhoneConstants.APN_TYPE_ALL});
+        doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID).when(mIsub).getDefaultDataSubId();
+
+        mBundle.putStringArray(CarrierConfigManager.KEY_CARRIER_METERED_APN_TYPES_STRINGS,
+                new String[]{PhoneConstants.APN_TYPE_DEFAULT});
+
+        logd("Sending EVENT_RECORDS_LOADED");
+        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_RECORDS_LOADED, null));
+        waitForMs(200);
+
+        logd("Sending EVENT_DATA_CONNECTION_ATTACHED");
+        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_DATA_CONNECTION_ATTACHED, null));
+        waitForMs(200);
+
+        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_TRY_SETUP_DATA, mApnContext));
+        waitForMs(200);
+
+        verify(mSimulatedCommandsVerifier, times(1)).setupDataCall(
+                eq(AccessNetworkType.EUTRAN), any(DataProfile.class),
+                eq(false), eq(false), eq(DataService.REQUEST_REASON_NORMAL), any(),
+                any(Message.class));
+    }
+
     // Test the normal data call setup scenario.
     @Test
     @MediumTest
@@ -766,7 +805,6 @@
         assertEquals(DctConstants.State.CONNECTED, mDct.getState(PhoneConstants.APN_TYPE_IMS));
     }
 
-
     @Test
     @MediumTest
     public void testTrySetupDataMmsAllowedDataDisabled() throws Exception {
@@ -1594,6 +1632,7 @@
         ContentResolver resolver = mContext.getContentResolver();
         Settings.Global.putInt(resolver, Settings.Global.DATA_STALL_RECOVERY_ON_BAD_NETWORK, 1);
         Settings.System.putInt(resolver, "radio.data.stall.recovery.action", 0);
+        doReturn(new SignalStrength()).when(mPhone).getSignalStrength();
 
         mBundle.putStringArray(CarrierConfigManager.KEY_CARRIER_METERED_APN_TYPES_STRINGS,
                 new String[]{PhoneConstants.APN_TYPE_DEFAULT, PhoneConstants.APN_TYPE_MMS});
@@ -1636,6 +1675,7 @@
         Settings.Global.putLong(resolver,
                 Settings.Global.MIN_DURATION_BETWEEN_RECOVERY_STEPS_IN_MS, 100);
         Settings.System.putInt(resolver, "radio.data.stall.recovery.action", 1);
+        doReturn(new SignalStrength()).when(mPhone).getSignalStrength();
 
         mBundle.putStringArray(CarrierConfigManager.KEY_CARRIER_METERED_APN_TYPES_STRINGS,
                 new String[]{PhoneConstants.APN_TYPE_DEFAULT, PhoneConstants.APN_TYPE_MMS});
@@ -1676,6 +1716,7 @@
         Settings.Global.putLong(resolver,
                 Settings.Global.MIN_DURATION_BETWEEN_RECOVERY_STEPS_IN_MS, 100);
         Settings.System.putInt(resolver, "radio.data.stall.recovery.action", 2);
+        doReturn(new SignalStrength()).when(mPhone).getSignalStrength();
 
         mBundle.putStringArray(CarrierConfigManager.KEY_CARRIER_METERED_APN_TYPES_STRINGS,
                 new String[]{PhoneConstants.APN_TYPE_DEFAULT, PhoneConstants.APN_TYPE_MMS});
@@ -1713,6 +1754,7 @@
         Settings.Global.putLong(resolver,
                 Settings.Global.MIN_DURATION_BETWEEN_RECOVERY_STEPS_IN_MS, 100);
         Settings.System.putInt(resolver, "radio.data.stall.recovery.action", 3);
+        doReturn(new SignalStrength()).when(mPhone).getSignalStrength();
 
         mBundle.putStringArray(CarrierConfigManager.KEY_CARRIER_METERED_APN_TYPES_STRINGS,
                 new String[]{PhoneConstants.APN_TYPE_DEFAULT, PhoneConstants.APN_TYPE_MMS});
@@ -1750,4 +1792,276 @@
         assertEquals(reason, (int) result.second);
         clearInvocations(mHandler);
     }
+
+    private void setUpSubscriptionPlans(boolean is5GUnmetered) throws Exception {
+        List<SubscriptionPlan> plans = new ArrayList<>();
+        if (is5GUnmetered) {
+            plans.add(SubscriptionPlan.Builder
+                    .createRecurring(ZonedDateTime.parse("2007-03-14T00:00:00.000Z"),
+                            Period.ofMonths(1))
+                    .setTitle("Some NR 5G unmetered workaround plan")
+                    .setDataLimit(SubscriptionPlan.BYTES_UNLIMITED,
+                            SubscriptionPlan.LIMIT_BEHAVIOR_THROTTLED)
+                    .setNetworkTypes(new int[] {TelephonyManager.NETWORK_TYPE_NR})
+                    .build());
+        }
+        plans.add(SubscriptionPlan.Builder
+                .createRecurring(ZonedDateTime.parse("2007-03-14T00:00:00.000Z"),
+                        Period.ofMonths(1))
+                .setTitle("Some 5GB Plan")
+                .setDataLimit(1_000_000_000, SubscriptionPlan.LIMIT_BEHAVIOR_DISABLED)
+                .setDataUsage(500_000_000, System.currentTimeMillis())
+                .build());
+        replaceInstance(DcTracker.class, "mSubscriptionPlans", mDct, plans);
+    }
+
+    private boolean isNetworkTypeUnmetered(int networkType) throws Exception {
+        Method method = DcTracker.class.getDeclaredMethod(
+                "isNetworkTypeUnmetered", int.class);
+        method.setAccessible(true);
+        return (boolean) method.invoke(mDct, networkType);
+    }
+
+    private int setUpDataConnection() throws Exception {
+        Field dc = DcTracker.class.getDeclaredField("mDataConnections");
+        dc.setAccessible(true);
+        Field uig = DcTracker.class.getDeclaredField("mUniqueIdGenerator");
+        uig.setAccessible(true);
+        int id = ((AtomicInteger) uig.get(mDct)).getAndIncrement();
+        ((HashMap<Integer, DataConnection>) dc.get(mDct)).put(id, mDataConnection);
+        return id;
+    }
+
+    private void resetDataConnection(int id) throws Exception {
+        Field dc = DcTracker.class.getDeclaredField("mDataConnections");
+        dc.setAccessible(true);
+        ((HashMap<Integer, DataConnection>) dc.get(mDct)).remove(id);
+    }
+
+    private void setUpWatchdogTimer() {
+        // Watchdog active for 10s
+        mBundle.putLong(CarrierConfigManager.KEY_5G_WATCHDOG_TIME_MS_LONG, 10000);
+        Intent intent = new Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
+        intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, mPhone.getSubId());
+        mContext.sendBroadcast(intent);
+        waitForHandlerAction(mDcTrackerTestHandler.getThreadHandler(), 200);
+    }
+
+    private boolean getHysteresisStatus() throws Exception {
+        Field field = DcTracker.class.getDeclaredField(("mHysteresis"));
+        field.setAccessible(true);
+        return (boolean) field.get(mDct);
+    }
+
+    private boolean getWatchdogStatus() throws Exception {
+        Field field = DcTracker.class.getDeclaredField(("mWatchdog"));
+        field.setAccessible(true);
+        return (boolean) field.get(mDct);
+    }
+
+    @Test
+    public void testIsNetworkTypeUnmetered() throws Exception {
+        initApns(PhoneConstants.APN_TYPE_DEFAULT, new String[]{PhoneConstants.APN_TYPE_ALL});
+
+        // only 5G unmetered
+        setUpSubscriptionPlans(true);
+
+        assertTrue(isNetworkTypeUnmetered(TelephonyManager.NETWORK_TYPE_NR));
+        assertFalse(isNetworkTypeUnmetered(TelephonyManager.NETWORK_TYPE_LTE));
+        assertFalse(isNetworkTypeUnmetered(TelephonyManager.NETWORK_TYPE_UNKNOWN));
+
+        // all network types metered
+        setUpSubscriptionPlans(false);
+
+        assertFalse(isNetworkTypeUnmetered(TelephonyManager.NETWORK_TYPE_NR));
+        assertFalse(isNetworkTypeUnmetered(TelephonyManager.NETWORK_TYPE_LTE));
+        assertFalse(isNetworkTypeUnmetered(TelephonyManager.NETWORK_TYPE_UNKNOWN));
+
+        // all network types unmetered
+        List<SubscriptionPlan> plans = new ArrayList<>();
+        plans.add(SubscriptionPlan.Builder
+                .createRecurring(ZonedDateTime.parse("2007-03-14T00:00:00.000Z"),
+                        Period.ofMonths(1))
+                .setTitle("Some 5GB Plan")
+                .setDataLimit(SubscriptionPlan.BYTES_UNLIMITED,
+                        SubscriptionPlan.LIMIT_BEHAVIOR_THROTTLED)
+                .build());
+        replaceInstance(DcTracker.class, "mSubscriptionPlans", mDct, plans);
+
+        assertTrue(isNetworkTypeUnmetered(TelephonyManager.NETWORK_TYPE_NR));
+        assertTrue(isNetworkTypeUnmetered(TelephonyManager.NETWORK_TYPE_LTE));
+        assertTrue(isNetworkTypeUnmetered(TelephonyManager.NETWORK_TYPE_UNKNOWN));
+    }
+
+    @Test
+    public void testIsFrequencyRangeUnmetered() throws Exception {
+        initApns(PhoneConstants.APN_TYPE_DEFAULT, new String[]{PhoneConstants.APN_TYPE_ALL});
+        int id = setUpDataConnection();
+        setUpSubscriptionPlans(false);
+        setUpWatchdogTimer();
+        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
+
+        // NetCapability should be metered when connected to 5G with no unmetered plan or frequency
+        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_SERVICE_STATE_CHANGED));
+        waitForHandlerAction(mDcTrackerTestHandler.getThreadHandler(), 200);
+        verify(mDataConnection, times(1)).onMeterednessChanged(false);
+
+        // Set MMWAVE frequency to unmetered
+        mBundle.putBoolean(CarrierConfigManager.KEY_UNMETERED_NR_NSA_MMWAVE_BOOL, true);
+        Intent intent = new Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
+        intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, mPhone.getSubId());
+        mContext.sendBroadcast(intent);
+        waitForHandlerAction(mDcTrackerTestHandler.getThreadHandler(), 200);
+
+        // NetCapability should switch to unmetered when fr=MMWAVE and MMWAVE unmetered
+        doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange();
+        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_SERVICE_STATE_CHANGED));
+        waitForHandlerAction(mDcTrackerTestHandler.getThreadHandler(), 200);
+        verify(mDataConnection, times(1)).onMeterednessChanged(true);
+
+        // NetCapability should switch to metered when fr=SUB6 and MMWAVE unmetered
+        doReturn(ServiceState.FREQUENCY_RANGE_HIGH).when(mServiceState).getNrFrequencyRange();
+        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_SERVICE_STATE_CHANGED));
+        waitForHandlerAction(mDcTrackerTestHandler.getThreadHandler(), 200);
+        verify(mDataConnection, times(2)).onMeterednessChanged(false);
+
+        // Set SUB6 frequency to unmetered
+        mBundle.putBoolean(CarrierConfigManager.KEY_UNMETERED_NR_NSA_SUB6_BOOL, true);
+        intent = new Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
+        intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, mPhone.getSubId());
+        mContext.sendBroadcast(intent);
+        waitForHandlerAction(mDcTrackerTestHandler.getThreadHandler(), 200);
+
+        // NetCapability should switch to unmetered when fr=SUB6 and SUB6 unmetered
+        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_SERVICE_STATE_CHANGED));
+        waitForHandlerAction(mDcTrackerTestHandler.getThreadHandler(), 200);
+        verify(mDataConnection, times(2)).onMeterednessChanged(true);
+
+        resetDataConnection(id);
+    }
+
+    @Test
+    public void testReevaluateUnmeteredConnectionsOnNetworkChange() throws Exception {
+        initApns(PhoneConstants.APN_TYPE_DEFAULT, new String[]{PhoneConstants.APN_TYPE_ALL});
+        int id = setUpDataConnection();
+        setUpSubscriptionPlans(true);
+        setUpWatchdogTimer();
+
+        // NetCapability should be unmetered when connected to 5G
+        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
+        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_SERVICE_STATE_CHANGED));
+        waitForHandlerAction(mDcTrackerTestHandler.getThreadHandler(), 200);
+        verify(mDataConnection, times(1)).onMeterednessChanged(true);
+
+        // NetCapability should be metered when disconnected from 5G
+        doReturn(NetworkRegistrationInfo.NR_STATE_NONE).when(mServiceState).getNrState();
+        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_SERVICE_STATE_CHANGED));
+        waitForHandlerAction(mDcTrackerTestHandler.getThreadHandler(), 200);
+        verify(mDataConnection, times(1)).onMeterednessChanged(false);
+
+        resetDataConnection(id);
+    }
+
+    @Test
+    public void testReevaluateUnmeteredConnectionsOnHysteresis() throws Exception {
+        initApns(PhoneConstants.APN_TYPE_DEFAULT, new String[]{PhoneConstants.APN_TYPE_ALL});
+        int id = setUpDataConnection();
+        setUpSubscriptionPlans(true);
+        setUpWatchdogTimer();
+
+        // Hysteresis active for 10s
+        mBundle.putInt(CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_SEC_INT, 10000);
+        Intent intent = new Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
+        intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, mPhone.getSubId());
+        mContext.sendBroadcast(intent);
+        waitForHandlerAction(mDcTrackerTestHandler.getThreadHandler(), 200);
+
+        // Hysteresis inactive when unmetered and never connected to 5G
+        doReturn(NetworkRegistrationInfo.NR_STATE_NONE).when(mServiceState).getNrState();
+        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_5G_TIMER_HYSTERESIS));
+        waitForHandlerAction(mDcTrackerTestHandler.getThreadHandler(), 200);
+        assertFalse(getHysteresisStatus());
+
+        // Hysteresis inactive when unmetered and connected to 5G
+        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
+        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_SERVICE_STATE_CHANGED));
+        waitForHandlerAction(mDcTrackerTestHandler.getThreadHandler(), 200);
+        assertFalse(getHysteresisStatus());
+
+        // Hysteresis active when unmetered and disconnected after connected to 5G
+        doReturn(NetworkRegistrationInfo.NR_STATE_NONE).when(mServiceState).getNrState();
+        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_SERVICE_STATE_CHANGED));
+        waitForHandlerAction(mDcTrackerTestHandler.getThreadHandler(), 200);
+        assertTrue(getHysteresisStatus());
+
+        // NetCapability metered when hysteresis timer goes off
+        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_5G_TIMER_HYSTERESIS));
+        waitForHandlerAction(mDcTrackerTestHandler.getThreadHandler(), 200);
+        assertFalse(getHysteresisStatus());
+        verify(mDataConnection, times(1)).onMeterednessChanged(true);
+
+        // Hysteresis inactive when reconnected after timer goes off
+        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
+        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_SERVICE_STATE_CHANGED));
+        waitForHandlerAction(mDcTrackerTestHandler.getThreadHandler(), 200);
+        assertFalse(getHysteresisStatus());
+
+        // Hysteresis disabled
+        mBundle.putInt(CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_SEC_INT, 0);
+        intent = new Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
+        intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, mPhone.getSubId());
+        mContext.sendBroadcast(intent);
+        waitForHandlerAction(mDcTrackerTestHandler.getThreadHandler(), 200);
+
+        // Hysteresis inactive when CarrierConfig is set to 0
+        doReturn(NetworkRegistrationInfo.NR_STATE_NONE).when(mServiceState).getNrState();
+        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_SERVICE_STATE_CHANGED));
+        waitForHandlerAction(mDcTrackerTestHandler.getThreadHandler(), 200);
+        assertFalse(getHysteresisStatus());
+
+        resetDataConnection(id);
+    }
+
+    @Test
+    public void testReevaluateUnmeteredConnectionsOnWatchdog() throws Exception {
+        initApns(PhoneConstants.APN_TYPE_DEFAULT, new String[]{PhoneConstants.APN_TYPE_ALL});
+        int id = setUpDataConnection();
+        setUpSubscriptionPlans(true);
+        setUpWatchdogTimer();
+
+        // Watchdog inactive when unmetered and never connected to 5G
+        doReturn(NetworkRegistrationInfo.NR_STATE_NONE).when(mServiceState).getNrState();
+        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_5G_TIMER_WATCHDOG));
+        waitForHandlerAction(mDcTrackerTestHandler.getThreadHandler(), 200);
+        assertFalse(getWatchdogStatus());
+
+        // Hysteresis active for 10s
+        mBundle.putInt(CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_SEC_INT, 10000);
+        Intent intent = new Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
+        intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, mPhone.getSubId());
+        mContext.sendBroadcast(intent);
+        waitForHandlerAction(mDcTrackerTestHandler.getThreadHandler(), 200);
+
+        // Watchdog active when unmetered and connected to 5G
+        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
+        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_SERVICE_STATE_CHANGED));
+        waitForHandlerAction(mDcTrackerTestHandler.getThreadHandler(), 200);
+        assertTrue(getWatchdogStatus());
+        assertFalse(getHysteresisStatus());
+
+        // Watchdog active during hysteresis
+        doReturn(NetworkRegistrationInfo.NR_STATE_NONE).when(mServiceState).getNrState();
+        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_SERVICE_STATE_CHANGED));
+        waitForHandlerAction(mDcTrackerTestHandler.getThreadHandler(), 200);
+        assertTrue(getHysteresisStatus());
+        assertTrue(getWatchdogStatus());
+
+        // Watchdog inactive when metered
+        setUpSubscriptionPlans(false);
+        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_SERVICE_STATE_CHANGED));
+        waitForHandlerAction(mDcTrackerTestHandler.getThreadHandler(), 200);
+        assertFalse(getWatchdogStatus());
+
+        resetDataConnection(id);
+    }
 }
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..5a96ce4 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 {
@@ -164,7 +173,7 @@
         replaceInstance(PhoneSwitcher.class, "sPhoneSwitcher", null, mPhoneSwitcherMock);
 
         mTelephonyNetworkFactoryUT = new TelephonyNetworkFactory(mSubscriptionMonitorMock, mLooper,
-                mPhone);
+                mPhone, mPhoneSwitcherMock);
     }
 
     /**
@@ -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/dataconnection/TransportManagerTest.java b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/TransportManagerTest.java
index 540eb78..fc80e76 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/TransportManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/TransportManagerTest.java
@@ -129,7 +129,7 @@
         assertEquals(AccessNetworkConstants.TRANSPORT_TYPE_WLAN, params.targetTransport);
 
         // Notify handover succeeded.
-        params.callback.onCompleted(true);
+        params.callback.onCompleted(true, false);
         assertEquals(AccessNetworkConstants.TRANSPORT_TYPE_WLAN,
                 mTransportManager.getCurrentTransport(ApnSetting.TYPE_IMS));
 
@@ -158,7 +158,7 @@
         assertEquals(AccessNetworkConstants.TRANSPORT_TYPE_WLAN,
                 mTransportManager.getCurrentTransport(ApnSetting.TYPE_IMS));
         // Notify handover succeeded.
-        params.callback.onCompleted(true);
+        params.callback.onCompleted(true, false);
         assertEquals(AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
                 mTransportManager.getCurrentTransport(ApnSetting.TYPE_IMS));
     }
@@ -256,7 +256,7 @@
         assertEquals(1, listQueue.size());
 
         // Notify handover succeeded.
-        params.callback.onCompleted(true);
+        params.callback.onCompleted(true, false);
         assertEquals(AccessNetworkConstants.TRANSPORT_TYPE_WLAN,
                 mTransportManager.getCurrentTransport(ApnSetting.TYPE_IMS));
         waitForMs(100);
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 4c80685..290c535 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyNumberTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyNumberTrackerTest.java
@@ -17,17 +17,21 @@
 package com.android.internal.telephony.emergency;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.doReturn;
 
 import android.os.AsyncResult;
 import android.os.HandlerThread;
 import android.telephony.emergency.EmergencyNumber;
 
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.TelephonyTest;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.mockito.Mock;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -38,8 +42,16 @@
  */
 public class EmergencyNumberTrackerTest extends TelephonyTest {
 
+    @Mock
+    private Phone mPhone2; // mPhone as phone 1 is already defined in TelephonyTest.
+
+    // mEmergencyNumberTrackerMock for mPhone
     private EmergencyNumberTracker mEmergencyNumberTrackerMock;
+    // mEmergencyNumberTrackerMock2 for mPhone2
+    private EmergencyNumberTracker mEmergencyNumberTrackerMock2;
+
     private List<EmergencyNumber> mEmergencyNumberListTestSample = new ArrayList<>();
+    private EmergencyNumber mUsEmergencyNumber;
     private String[] mEmergencyNumberPrefixTestSample = {"123", "456"};
     private static final long TIMEOUT_MS = 500;
 
@@ -50,6 +62,8 @@
         @Override
         public void onLooperPrepared() {
             mEmergencyNumberTrackerMock = new EmergencyNumberTracker(mPhone, mSimulatedCommands);
+            mEmergencyNumberTrackerMock2 = new EmergencyNumberTracker(mPhone2, mSimulatedCommands);
+            doReturn(mEmergencyNumberTrackerMock2).when(mPhone2).getEmergencyNumberTracker();
             mEmergencyNumberTrackerMock.DBG = true;
             setReady(true);
         }
@@ -62,6 +76,11 @@
         logd("EmergencyNumberTrackerTest +Setup!");
         super.setUp("EmergencyNumberTrackerTest");
         doReturn(mContext).when(mPhone).getContext();
+        doReturn(0).when(mPhone).getPhoneId();
+
+        doReturn(mContext).when(mPhone2).getContext();
+        doReturn(1).when(mPhone2).getPhoneId();
+
         initializeEmergencyNumberListTestSamples();
         mHandlerThread = new EmergencyNumberTrackerTestHandler("EmergencyNumberTrackerTestHandler");
         mHandlerThread.start();
@@ -71,6 +90,9 @@
 
     @After
     public void tearDown() throws Exception {
+        // Set back to single sim mode
+        setSinglePhone();
+
         mHandlerThread.quit();
         mHandlerThread.join();
         super.tearDown();
@@ -82,6 +104,12 @@
                 new ArrayList<String>(),
                 EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING,
                 EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN);
+        mUsEmergencyNumber = new EmergencyNumber("911", "us", "",
+            EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_POLICE
+                | EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_AMBULANCE
+                | EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_FIRE_BRIGADE, new ArrayList<String>(),
+            EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE,
+            EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN);
         mEmergencyNumberListTestSample.add(emergencyNumberForTest);
     }
 
@@ -93,11 +121,26 @@
         waitForHandlerAction(mEmergencyNumberTrackerMock, TIMEOUT_MS);
     }
 
-    private void sendEmergencyNumberPrefix() {
-        mEmergencyNumberTrackerMock.obtainMessage(
+    private void cacheEmergencyNumberListFromDatabaseByCountry(String countryIso) {
+        mEmergencyNumberTrackerMock.updateEmergencyNumberDatabaseCountryChange(countryIso);
+        waitForHandlerAction(mEmergencyNumberTrackerMock, TIMEOUT_MS);
+    }
+
+    private void sendEmergencyNumberPrefix(EmergencyNumberTracker emergencyNumberTrackerMock) {
+        emergencyNumberTrackerMock.obtainMessage(
         	4 /* EVENT_UPDATE_EMERGENCY_NUMBER_PREFIX */,
                 mEmergencyNumberPrefixTestSample).sendToTarget();
-        waitForHandlerAction(mEmergencyNumberTrackerMock, TIMEOUT_MS);
+        waitForHandlerAction(emergencyNumberTrackerMock, TIMEOUT_MS);
+    }
+
+    private void setDsdsPhones() throws Exception {
+        mPhones = new Phone[] {mPhone, mPhone2};
+        replaceInstance(PhoneFactory.class, "sPhones", null, mPhones);
+    }
+
+    private void setSinglePhone() throws Exception {
+        mPhones = new Phone[] {mPhone};
+        replaceInstance(PhoneFactory.class, "sPhones", null, mPhones);
     }
 
     @Test
@@ -108,9 +151,66 @@
     }
 
     @Test
+    public void testUpdateEmergencyCountryIso() throws Exception {
+        sendEmergencyNumberPrefix(mEmergencyNumberTrackerMock);
+        mEmergencyNumberTrackerMock.updateEmergencyNumberDatabaseCountryChange("us");
+        waitForHandlerAction(mEmergencyNumberTrackerMock, TIMEOUT_MS);
+
+        assertTrue(mEmergencyNumberTrackerMock.getEmergencyCountryIso().equals("us"));
+    }
+
+    @Test
+    public void testUpdateEmergencyCountryIsoMultiSim() throws Exception {
+        setDsdsPhones();
+        sendEmergencyNumberPrefix(mEmergencyNumberTrackerMock);
+        sendEmergencyNumberPrefix(mEmergencyNumberTrackerMock2);
+        mEmergencyNumberTrackerMock.updateEmergencyCountryIsoAllPhones("jp");
+        waitForHandlerAction(mEmergencyNumberTrackerMock, TIMEOUT_MS);
+        waitForHandlerAction(mEmergencyNumberTrackerMock2, TIMEOUT_MS);
+
+        assertTrue(mEmergencyNumberTrackerMock.getEmergencyCountryIso().equals("jp"));
+        assertTrue(mEmergencyNumberTrackerMock2.getEmergencyCountryIso().equals("jp"));
+    }
+
+    @Test
+    public void testUpdateEmergencyCountryIsoFromAnotherSimOrNot() throws Exception {
+        setDsdsPhones();
+        sendEmergencyNumberPrefix(mEmergencyNumberTrackerMock);
+        sendEmergencyNumberPrefix(mEmergencyNumberTrackerMock2);
+
+        // First, both slots have empty country iso, trigger a country change to "jp".
+        // We should expect both sims have "jp" country iso.
+        mEmergencyNumberTrackerMock.updateEmergencyCountryIsoAllPhones("jp");
+        waitForHandlerAction(mEmergencyNumberTrackerMock, TIMEOUT_MS);
+        waitForHandlerAction(mEmergencyNumberTrackerMock2, TIMEOUT_MS);
+        assertTrue(mEmergencyNumberTrackerMock.getEmergencyCountryIso().equals("jp"));
+        assertTrue(mEmergencyNumberTrackerMock2.getEmergencyCountryIso().equals("jp"));
+
+        // Second, both slots now have "jp" country iso, trigger a country change to "us".
+        // We should expect both sims have "us" country iso.
+        mEmergencyNumberTrackerMock.updateEmergencyCountryIsoAllPhones("us");
+        waitForHandlerAction(mEmergencyNumberTrackerMock, TIMEOUT_MS);
+        waitForHandlerAction(mEmergencyNumberTrackerMock2, TIMEOUT_MS);
+        assertTrue(mEmergencyNumberTrackerMock.getEmergencyCountryIso().equals("us"));
+        assertTrue(mEmergencyNumberTrackerMock2.getEmergencyCountryIso().equals("us"));
+
+        // Third, both slots now have "us" country iso, manually configure
+        // "mIsCountrySetByAnotherSub" flag in "mPhone2" as false, and trigger a country
+        // change to "ca". We should expect the current phone to change the country iso
+        // to "ca", and should expect the other phone *not* to change their country iso
+        // to "ca".
+        mEmergencyNumberTrackerMock2.mIsCountrySetByAnotherSub = false;
+        mEmergencyNumberTrackerMock.updateEmergencyCountryIsoAllPhones("ca");
+        waitForHandlerAction(mEmergencyNumberTrackerMock, TIMEOUT_MS);
+        waitForHandlerAction(mEmergencyNumberTrackerMock2, TIMEOUT_MS);
+        assertTrue(mEmergencyNumberTrackerMock.getEmergencyCountryIso().equals("ca"));
+        assertTrue(mEmergencyNumberTrackerMock2.getEmergencyCountryIso().equals("us"));
+    }
+
+    @Test
     public void testEmergencyNumberListPrefix() throws Exception {
         sendEmergencyNumberListFromRadio();
-        sendEmergencyNumberPrefix();
+        sendEmergencyNumberPrefix(mEmergencyNumberTrackerMock);
         List<EmergencyNumber> resultToVerify = mEmergencyNumberListTestSample;
         resultToVerify.add(new EmergencyNumber("123119", "jp", "30",
                 EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_FIRE_BRIGADE,
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 41fec38..71399c5 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccControllerTest.java
@@ -1052,7 +1052,7 @@
         SubscriptionInfo subInfo = new SubscriptionInfo(
                 0, "", 0, "", "", 0, 0, "", 0, null, "", "", "", true /* isEmbedded */,
                 hasPrivileges ? new UiccAccessRule[] { ACCESS_RULE } : null, "", CARD_ID,
-                false, null, false, 0, 0, 0, null);
+                false, null, false, 0, 0, 0, null, null);
         when(mSubscriptionManager.canManageSubscription(subInfo, PACKAGE_NAME)).thenReturn(
                 hasPrivileges);
         when(mSubscriptionManager.getActiveSubscriptionInfoList(anyBoolean())).thenReturn(
@@ -1073,11 +1073,11 @@
         SubscriptionInfo subInfo1 = new SubscriptionInfo(
                 0, "", 0, "", "", 0, 0, "", 0, null, "", "", "", true /* isEmbedded */,
                 hasPrivileges ? new UiccAccessRule[] { ACCESS_RULE } : null, "", CARD_ID,
-                false, null, false, 0, 0, 0, null);
+                false, null, false, 0, 0, 0, null, null);
         SubscriptionInfo subInfo2 = new SubscriptionInfo(
                 0, "", 0, "", "", 0, 0, "", 0, null, "", "", "", true /* isEmbedded */,
                 hasPrivileges ? new UiccAccessRule[] { ACCESS_RULE } : null, "",
-                1 /* cardId */, false, null, false, 0, 0, 0, null);
+                1 /* cardId */, false, null, false, 0, 0, 0, null, null);
         when(mSubscriptionManager.canManageSubscription(subInfo1, PACKAGE_NAME)).thenReturn(
                 hasPrivileges);
         when(mSubscriptionManager.canManageSubscription(subInfo2, PACKAGE_NAME)).thenReturn(
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 03234e6..028e764 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmInboundSmsHandlerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmInboundSmsHandlerTest.java
@@ -48,6 +48,7 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Telephony;
+import android.telephony.SubscriptionManager;
 import android.test.mock.MockContentResolver;
 
 import androidx.test.filters.FlakyTest;
@@ -56,6 +57,7 @@
 import com.android.internal.telephony.FakeSmsContentProvider;
 import com.android.internal.telephony.InboundSmsHandler;
 import com.android.internal.telephony.InboundSmsTracker;
+import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.SmsBroadcastUndelivered;
 import com.android.internal.telephony.SmsHeader;
 import com.android.internal.telephony.SmsStorageMonitor;
@@ -89,6 +91,9 @@
     private InboundSmsTracker mMockInboundSmsTracker;
     private ContentValues mInboundSmsTrackerCV;
     @Mock
+    private InboundSmsTracker mMockInboundSmsTrackerSub1;
+    private ContentValues mInboundSmsTrackerCVSub1;
+    @Mock
     private CdmaInboundSmsHandler mCdmaInboundSmsHandler;
 
     private GsmInboundSmsHandler mGsmInboundSmsHandler;
@@ -103,6 +108,8 @@
     private String mMessageBody = "This is the message body of a single-part message";
     private String mMessageBodyPart1 = "This is the first part of a multi-part message";
     private String mMessageBodyPart2 = "This is the second part of a multi-part message";
+    private int mSubId0 = 0;
+    private int mSubId1 = 0;
 
     byte[] mSmsPdu = new byte[]{(byte)0xFF, (byte)0xFF, (byte)0xFF};
 
@@ -147,6 +154,7 @@
         mInboundSmsTrackerCV.put("date", System.currentTimeMillis());
         mInboundSmsTrackerCV.put("message_body", mMessageBody);
         mInboundSmsTrackerCV.put("display_originating_addr", "1234567890");
+        mInboundSmsTrackerCV.put("sub_id", mSubId0);
 
         doReturn(1).when(mMockInboundSmsTracker).getMessageCount();
         doReturn(1).when(mMockInboundSmsTracker).getReferenceNumber();
@@ -158,9 +166,36 @@
         doReturn(mSmsPdu).when(mMockInboundSmsTracker).getPdu();
         doReturn(mInboundSmsTrackerCV.get("date")).when(mMockInboundSmsTracker).getTimestamp();
         doReturn(mInboundSmsTrackerCV).when(mMockInboundSmsTracker).getContentValues();
+        doReturn(mSubId0).when(mMockInboundSmsTracker).getSubId();
 
-        doReturn(mMockInboundSmsTracker).when(mTelephonyComponentFactory)
-            .makeInboundSmsTracker(nullable(Cursor.class), anyBoolean());
+        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(mInboundSmsTrackerCVSub1).when(mMockInboundSmsTrackerSub1).getContentValues();
+        doReturn(mSubId1).when(mMockInboundSmsTrackerSub1).getSubId();
+
+        doReturn(mMockInboundSmsTracker).doReturn(mMockInboundSmsTrackerSub1)
+                .when(mTelephonyComponentFactory)
+                .makeInboundSmsTracker(nullable(Cursor.class), anyBoolean());
     }
 
     @Before
@@ -190,11 +225,12 @@
                 "1234567890", /* address */
                 "1234567890", /* displayAddress */
                 mMessageBody, /* messageBody */
-                false /* isClass0 */);
+                false, /* isClass0 */
+                mSubId0);
         doReturn(mInboundSmsTracker).when(mTelephonyComponentFactory)
                 .makeInboundSmsTracker(nullable(byte[].class), anyLong(), anyInt(), anyBoolean(),
                 anyBoolean(), nullable(String.class), nullable(String.class),
-                nullable(String.class), anyBoolean());
+                nullable(String.class), anyBoolean(), anyInt());
 
         createMockInboundSmsTracker();
 
@@ -239,11 +275,26 @@
     }
 
     private void verifySmsIntentBroadcasts(int numPastBroadcasts, boolean allowBgActivityStarts) {
+        verifySmsIntentBroadcasts(numPastBroadcasts, allowBgActivityStarts, mSubId0,
+                false /* moreMessages */);
+    }
+
+    private void verifySmsIntentBroadcasts(int numPastBroadcasts, int subId, boolean moreMessages) {
+        verifySmsIntentBroadcasts(numPastBroadcasts, false /* allowBgActivityStarts */, subId,
+                moreMessages);
+    }
+
+    private void verifySmsIntentBroadcasts(int numPastBroadcasts, boolean allowBgActivityStarts,
+            int subId, boolean moreMessages) {
         ArgumentCaptor<Intent> intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class);
         verify(mContext, times(1 + numPastBroadcasts)).sendBroadcast(
                 intentArgumentCaptor.capture());
-        assertEquals(Telephony.Sms.Intents.SMS_DELIVER_ACTION,
-                intentArgumentCaptor.getAllValues().get(numPastBroadcasts).getAction());
+        Intent intent = intentArgumentCaptor.getAllValues().get(numPastBroadcasts);
+        assertEquals(Telephony.Sms.Intents.SMS_DELIVER_ACTION, intent.getAction());
+        assertEquals(subId, intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
+                SubscriptionManager.INVALID_SUBSCRIPTION_ID));
+        assertEquals(subId, intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
+                SubscriptionManager.INVALID_SUBSCRIPTION_ID));
         assertEquals("WaitingState", getCurrentState().getName());
         if (allowBgActivityStarts) {
             Bundle broadcastOptions = mContextFixture.getLastBroadcastOptions();
@@ -256,8 +307,12 @@
         intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class);
         verify(mContext, times(2 + numPastBroadcasts)).sendBroadcast(
                 intentArgumentCaptor.capture());
-        assertEquals(Telephony.Sms.Intents.SMS_RECEIVED_ACTION,
-                intentArgumentCaptor.getAllValues().get(numPastBroadcasts + 1).getAction());
+        intent = intentArgumentCaptor.getAllValues().get(numPastBroadcasts + 1);
+        assertEquals(Telephony.Sms.Intents.SMS_RECEIVED_ACTION, intent.getAction());
+        assertEquals(subId, intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
+                SubscriptionManager.INVALID_SUBSCRIPTION_ID));
+        assertEquals(subId, intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
+                SubscriptionManager.INVALID_SUBSCRIPTION_ID));
         assertEquals("WaitingState", getCurrentState().getName());
 
         mContextFixture.sendBroadcastToOrderedBroadcastReceivers();
@@ -265,9 +320,12 @@
         waitForHandlerAction(mGsmInboundSmsHandler.getHandler(), TEST_TIMEOUT);
         // transition from waiting state to delivering state
         waitForHandlerAction(mGsmInboundSmsHandler.getHandler(), TEST_TIMEOUT);
-        // transition from delivering state to idle state
-        waitForHandlerAction(mGsmInboundSmsHandler.getHandler(), TEST_TIMEOUT);
-        assertEquals("IdleState", getCurrentState().getName());
+        if (!moreMessages) {
+            // transition from delivering state to idle state; if moreMessages are pending will
+            // transition to WaitingState instead of IdleState
+            waitForHandlerAction(mGsmInboundSmsHandler.getHandler(), TEST_TIMEOUT);
+            assertEquals("IdleState", getCurrentState().getName());
+        }
     }
 
     private void sendNewSms() {
@@ -344,11 +402,12 @@
                 "1234567890", /* address */
                 "1234567890", /* displayAddress */
                 mMessageBody, /* messageBody */
-                true /* isClass0 */);
+                true, /* isClass0 */
+                mSubId0);
         doReturn(mInboundSmsTracker).when(mTelephonyComponentFactory)
                 .makeInboundSmsTracker(nullable(byte[].class), anyLong(), anyInt(), anyBoolean(),
                         anyBoolean(), nullable(String.class), nullable(String.class),
-                        nullable(String.class), anyBoolean());
+                        nullable(String.class), anyBoolean(), anyInt());
         mGsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_BROADCAST_SMS,
                 mInboundSmsTracker);
         waitForHandlerAction(mGsmInboundSmsHandler.getHandler(), TEST_TIMEOUT);
@@ -371,11 +430,12 @@
                 "1234567890", /* address */
                 "1234567890", /* displayAddress */
                 mMessageBody, /* messageBody */
-                false /* isClass0 */);
+                false, /* isClass0 */
+                mSubId0);
         doReturn(mInboundSmsTracker).when(mTelephonyComponentFactory)
                 .makeInboundSmsTracker(nullable(byte[].class), anyLong(), anyInt(), anyBoolean(),
                 anyBoolean(), nullable(String.class), nullable(String.class),
-                nullable(String.class), anyBoolean());
+                nullable(String.class), anyBoolean(), anyInt());
         mGsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_BROADCAST_SMS,
                 mInboundSmsTracker);
         waitForHandlerAction(mGsmInboundSmsHandler.getHandler(), TEST_TIMEOUT);
@@ -429,7 +489,8 @@
                 2, /* messageCount */
                 is3gpp2WapPush, /* is3gpp2WapPdu */
                 mMessageBodyPart1, /* messageBody */
-                false /* isClass0 */);
+                false, /* isClass0 */
+                mSubId0);
 
         // Part 2
         mInboundSmsTrackerPart2 = new InboundSmsTracker(
@@ -444,7 +505,8 @@
                 2, /* messageCount */
                 is3gpp2WapPush, /* is3gpp2WapPdu */
                 mMessageBodyPart2, /* messageBody */
-                false /* isClass0 */);
+                false, /* isClass0 */
+                mSubId0);
     }
 
     @Test
@@ -467,7 +529,7 @@
         doReturn(mInboundSmsTrackerPart2).when(mTelephonyComponentFactory)
                 .makeInboundSmsTracker(nullable(byte[].class), anyLong(), anyInt(), anyBoolean(),
                         nullable(String.class), nullable(String.class), anyInt(), anyInt(),
-                        anyInt(), anyBoolean(), nullable(String.class), anyBoolean());
+                        anyInt(), anyBoolean(), nullable(String.class), anyBoolean(), anyInt());
         sendNewSms();
 
         // State machine should go back to idle and wait for second part
@@ -478,7 +540,7 @@
         doReturn(mInboundSmsTrackerPart2).when(mTelephonyComponentFactory)
                 .makeInboundSmsTracker(nullable(byte[].class), anyLong(), anyInt(), anyBoolean(),
                         nullable(String.class), nullable(String.class), anyInt(), anyInt(),
-                        anyInt(), anyBoolean(), nullable(String.class), anyBoolean());
+                        anyInt(), anyBoolean(), nullable(String.class), anyBoolean(), anyInt());
         sendNewSms();
 
         // State machine should go back to idle and wait for second part
@@ -492,7 +554,7 @@
         doReturn(mInboundSmsTrackerPart1).when(mTelephonyComponentFactory)
                 .makeInboundSmsTracker(nullable(byte[].class), anyLong(), anyInt(), anyBoolean(),
                         nullable(String.class), nullable(String.class), anyInt(), anyInt(),
-                        anyInt(), anyBoolean(), nullable(String.class), anyBoolean());
+                        anyInt(), anyBoolean(), nullable(String.class), anyBoolean(), anyInt());
         sendNewSms();
 
         // verify broadcast intents
@@ -518,7 +580,7 @@
         doReturn(mInboundSmsTrackerPart1).when(mTelephonyComponentFactory)
                 .makeInboundSmsTracker(nullable(byte[].class), anyLong(), anyInt(), anyBoolean(),
                         nullable(String.class), nullable(String.class), anyInt(), anyInt(),
-                        anyInt(), anyBoolean(), nullable(String.class), anyBoolean());
+                        anyInt(), anyBoolean(), nullable(String.class), anyBoolean(), anyInt());
         sendNewSms();
 
         // State machine should go back to idle and wait for second part
@@ -527,7 +589,7 @@
         doReturn(mInboundSmsTrackerPart2).when(mTelephonyComponentFactory)
                 .makeInboundSmsTracker(nullable(byte[].class), anyLong(), anyInt(), anyBoolean(),
                         nullable(String.class), nullable(String.class), anyInt(), anyInt(),
-                        anyInt(), anyBoolean(), nullable(String.class), anyBoolean());
+                        anyInt(), anyBoolean(), nullable(String.class), anyBoolean(), anyInt());
         sendNewSms();
 
         // verify broadcast intents
@@ -541,7 +603,7 @@
         doReturn(mInboundSmsTrackerPart2).when(mTelephonyComponentFactory)
                 .makeInboundSmsTracker(nullable(byte[].class), anyLong(), anyInt(), anyBoolean(),
                         nullable(String.class), nullable(String.class), anyInt(), anyInt(),
-                        anyInt(), anyBoolean(), nullable(String.class), anyBoolean());
+                        anyInt(), anyBoolean(), nullable(String.class), anyBoolean(), anyInt());
         sendNewSms();
 
         // verify no additional broadcasts sent
@@ -557,7 +619,7 @@
         doReturn(mInboundSmsTrackerPart1).when(mTelephonyComponentFactory)
                 .makeInboundSmsTracker(nullable(byte[].class), anyLong(), anyInt(), anyBoolean(),
                         nullable(String.class), nullable(String.class), anyInt(), anyInt(),
-                        anyInt(), anyBoolean(), nullable(String.class), anyBoolean());
+                        anyInt(), anyBoolean(), nullable(String.class), anyBoolean(), anyInt());
         sendNewSms();
 
         // verify no additional broadcasts sent
@@ -590,7 +652,8 @@
                 2, /* messageCount */
                 false, /* is3gpp2WapPdu */
                 mMessageBodyPart2, /* messageBody */
-                false /* isClass0 */);
+                false, /* isClass0 */
+                mSubId0);
 
         mSmsHeader.concatRef = new SmsHeader.ConcatRef();
         doReturn(mSmsHeader).when(mGsmSmsMessage).getUserDataHeader();
@@ -598,7 +661,7 @@
         doReturn(mInboundSmsTrackerPart1).when(mTelephonyComponentFactory)
                 .makeInboundSmsTracker(nullable(byte[].class), anyLong(), anyInt(), anyBoolean(),
                         nullable(String.class), nullable(String.class), anyInt(), anyInt(),
-                        anyInt(), anyBoolean(), nullable(String.class), anyBoolean());
+                        anyInt(), anyBoolean(), nullable(String.class), anyBoolean(), anyInt());
         sendNewSms();
 
         // State machine should go back to idle and wait for second part
@@ -607,7 +670,7 @@
         doReturn(mInboundSmsTrackerPart2).when(mTelephonyComponentFactory)
                 .makeInboundSmsTracker(nullable(byte[].class), anyLong(), anyInt(), anyBoolean(),
                         nullable(String.class), nullable(String.class), anyInt(), anyInt(),
-                        anyInt(), anyBoolean(), nullable(String.class), anyBoolean());
+                        anyInt(), anyBoolean(), nullable(String.class), anyBoolean(), anyInt());
         sendNewSms();
 
         // verify no broadcasts sent
@@ -636,7 +699,7 @@
         doReturn(mInboundSmsTrackerPart1).when(mTelephonyComponentFactory)
                 .makeInboundSmsTracker(nullable(byte[].class), anyLong(), anyInt(), anyBoolean(),
                         nullable(String.class), nullable(String.class), anyInt(), anyInt(),
-                        anyInt(), anyBoolean(), nullable(String.class), anyBoolean());
+                        anyInt(), anyBoolean(), nullable(String.class), anyBoolean(), anyInt());
         sendNewSms();
 
         // verify the message is stored in the raw table
@@ -659,12 +722,13 @@
                 2, /* messageCount */
                 false, /* is3gpp2WapPdu */
                 mMessageBodyPart2, /* messageBody */
-                false /* isClass0 */);
+                false, /* isClass0 */
+                mSubId0);
 
         doReturn(mInboundSmsTrackerPart2).when(mTelephonyComponentFactory)
                 .makeInboundSmsTracker(nullable(byte[].class), anyLong(), anyInt(), anyBoolean(),
                         nullable(String.class), nullable(String.class), anyInt(), anyInt(),
-                        anyInt(), anyBoolean(), nullable(String.class), anyBoolean());
+                        anyInt(), anyBoolean(), nullable(String.class), anyBoolean(), anyInt());
         sendNewSms();
 
         // verify no broadcasts sent
@@ -688,7 +752,7 @@
         doReturn(mInboundSmsTrackerPart1).when(mTelephonyComponentFactory)
                 .makeInboundSmsTracker(nullable(byte[].class), anyLong(), anyInt(), anyBoolean(),
                         nullable(String.class), nullable(String.class), anyInt(), anyInt(),
-                        anyInt(), anyBoolean(), nullable(String.class), anyBoolean());
+                        anyInt(), anyBoolean(), nullable(String.class), anyBoolean(), anyInt());
 
         sendNewSms();
 
@@ -698,7 +762,7 @@
         doReturn(mInboundSmsTrackerPart2).when(mTelephonyComponentFactory)
                 .makeInboundSmsTracker(nullable(byte[].class), anyLong(), anyInt(), anyBoolean(),
                         nullable(String.class), nullable(String.class), anyInt(), anyInt(),
-                        anyInt(), anyBoolean(), nullable(String.class), anyBoolean());
+                        anyInt(), anyBoolean(), nullable(String.class), anyBoolean(), anyInt());
         sendNewSms();
 
         verify(mContext, never()).sendBroadcast(any(Intent.class));
@@ -727,14 +791,15 @@
                 2, /* messageCount */
                 false, /* is3gpp2WapPdu */
                 mMessageBodyPart1, /* messageBody */
-                false /* isClass0 */);
+                false, /* isClass0 */
+                mSubId0);
 
         mSmsHeader.concatRef = new SmsHeader.ConcatRef();
         doReturn(mSmsHeader).when(mGsmSmsMessage).getUserDataHeader();
         doReturn(mInboundSmsTrackerPart1).when(mTelephonyComponentFactory)
                 .makeInboundSmsTracker(nullable(byte[].class), anyLong(), anyInt(), anyBoolean(),
                         nullable(String.class), nullable(String.class), anyInt(), anyInt(),
-                        anyInt(), anyBoolean(), nullable(String.class), anyBoolean());
+                        anyInt(), anyBoolean(), nullable(String.class), anyBoolean(), anyInt());
 
         sendNewSms();
 
@@ -744,7 +809,7 @@
         doReturn(mInboundSmsTrackerPart2).when(mTelephonyComponentFactory)
                 .makeInboundSmsTracker(nullable(byte[].class), anyLong(), anyInt(), anyBoolean(),
                         nullable(String.class), nullable(String.class), anyInt(), anyInt(),
-                        anyInt(), anyBoolean(), nullable(String.class), anyBoolean());
+                        anyInt(), anyBoolean(), nullable(String.class), anyBoolean(), anyInt());
         sendNewSms();
 
         verify(mContext, never()).sendBroadcast(any(Intent.class));
@@ -818,11 +883,12 @@
                 "1234567890", /* address */
                 "1234567890", /* displayAddress */
                 mMessageBody, /* messageBody */
-                false /* isClass0 */);
+                false, /* isClass0 */
+                mSubId0);
         doReturn(mInboundSmsTracker).when(mTelephonyComponentFactory)
                 .makeInboundSmsTracker(nullable(byte[].class), anyLong(), anyInt(), anyBoolean(),
                 anyBoolean(), nullable(String.class), nullable(String.class),
-                nullable(String.class), anyBoolean());
+                nullable(String.class), anyBoolean(), anyInt());
 
         //add a fake entry to db
         ContentValues rawSms = new ContentValues();
@@ -863,4 +929,21 @@
 
         verifySmsIntentBroadcasts(0);
     }
+
+    @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());
+
+        SmsBroadcastUndelivered.initialize(mContext, mGsmInboundSmsHandler, mCdmaInboundSmsHandler);
+        // wait for ScanRawTableThread
+        waitForMs(200);
+
+        verifySmsIntentBroadcasts(0, mSubId0, true);
+        verifySmsIntentBroadcasts(2, mSubId1, false);
+    }
 }
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 76fbdd6..f128404 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneCallTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneCallTrackerTest.java
@@ -119,6 +119,12 @@
                     ImsReasonInfo.CODE_ANSWERED_ELSEWHERE);
             mCTUT.addReasonCodeRemapping(ImsReasonInfo.CODE_USER_TERMINATED_BY_REMOTE, "",
                     ImsReasonInfo.CODE_SIP_FORBIDDEN);
+            mCTUT.addReasonCodeRemapping(ImsReasonInfo.CODE_SIP_SERVICE_UNAVAILABLE,
+                    "emergency calls over wifi not allowed in this location",
+                    ImsReasonInfo.CODE_EMERGENCY_CALL_OVER_WFC_NOT_AVAILABLE);
+            mCTUT.addReasonCodeRemapping(ImsReasonInfo.CODE_SIP_FORBIDDEN,
+                    "service not allowed in this location",
+                    ImsReasonInfo.CODE_WFC_SERVICE_NOT_AVAILABLE_IN_THIS_LOCATION);
             mCTUT.setDataEnabled(true);
             mCTHander = new Handler(mCTUT.getLooper());
             setReady(true);
@@ -621,7 +627,11 @@
         assertEquals(ImsReasonInfo.CODE_ANSWERED_ELSEWHERE,
                 mCTUT.maybeRemapReasonCode(new ImsReasonInfo(501, 1, "Call answered elsewhere.")));
         assertEquals(ImsReasonInfo.CODE_ANSWERED_ELSEWHERE,
+                mCTUT.maybeRemapReasonCode(new ImsReasonInfo(501, 1, "CALL answered elsewhere.")));
+        assertEquals(ImsReasonInfo.CODE_ANSWERED_ELSEWHERE,
                 mCTUT.maybeRemapReasonCode(new ImsReasonInfo(510, 1, "Call answered elsewhere.")));
+        assertEquals(ImsReasonInfo.CODE_ANSWERED_ELSEWHERE,
+                mCTUT.maybeRemapReasonCode(new ImsReasonInfo(510, 1, "CALL ANswered elsewhere.")));
         assertEquals(90210, mCTUT.maybeRemapReasonCode(new ImsReasonInfo(90210, 1,
                 "Call answered elsewhere.")));
     }
@@ -917,6 +927,38 @@
                 new ImsReasonInfo(ImsReasonInfo.CODE_USER_TERMINATED_BY_REMOTE, 0, "")));
     }
 
+    @Test
+    @SmallTest
+    public void testRemapEmergencyCallsOverWfc() {
+        assertEquals(ImsReasonInfo.CODE_SIP_SERVICE_UNAVAILABLE,
+                mCTUT.maybeRemapReasonCode(
+                        new ImsReasonInfo(ImsReasonInfo.CODE_SIP_SERVICE_UNAVAILABLE, 0)));
+        assertEquals(ImsReasonInfo.CODE_EMERGENCY_CALL_OVER_WFC_NOT_AVAILABLE,
+                mCTUT.maybeRemapReasonCode(
+                        new ImsReasonInfo(ImsReasonInfo.CODE_SIP_SERVICE_UNAVAILABLE, 0,
+                                "emergency calls over wifi not allowed in this location")));
+        assertEquals(ImsReasonInfo.CODE_EMERGENCY_CALL_OVER_WFC_NOT_AVAILABLE,
+                mCTUT.maybeRemapReasonCode(
+                        new ImsReasonInfo(ImsReasonInfo.CODE_SIP_SERVICE_UNAVAILABLE, 0,
+                                "EMERGENCY calls over wifi not allowed in this location")));
+    }
+
+    @Test
+    @SmallTest
+    public void testRemapWfcNotAvailable() {
+        assertEquals(ImsReasonInfo.CODE_SIP_FORBIDDEN,
+                mCTUT.maybeRemapReasonCode(
+                        new ImsReasonInfo(ImsReasonInfo.CODE_SIP_FORBIDDEN, 0)));
+        assertEquals(ImsReasonInfo.CODE_WFC_SERVICE_NOT_AVAILABLE_IN_THIS_LOCATION,
+                mCTUT.maybeRemapReasonCode(
+                        new ImsReasonInfo(ImsReasonInfo.CODE_SIP_FORBIDDEN, 0,
+                                "Service not allowed in this location")));
+        assertEquals(ImsReasonInfo.CODE_WFC_SERVICE_NOT_AVAILABLE_IN_THIS_LOCATION,
+                mCTUT.maybeRemapReasonCode(
+                        new ImsReasonInfo(ImsReasonInfo.CODE_SIP_FORBIDDEN, 0,
+                                "SERVICE not allowed in this location")));
+    }
+
     private void placeCallAndMakeActive() {
         try {
             doAnswer(new Answer<ImsCall>() {
diff --git a/tests/telephonytests/src/com/android/internal/telephony/mocks/SubscriptionControllerMock.java b/tests/telephonytests/src/com/android/internal/telephony/mocks/SubscriptionControllerMock.java
index b37c479..6d45c1c 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/mocks/SubscriptionControllerMock.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/mocks/SubscriptionControllerMock.java
@@ -84,7 +84,7 @@
         broadcastDefaultDataSubIdChanged(subId);
     }
 
-    private void broadcastDefaultDataSubIdChanged(int subId) {
+    protected void broadcastDefaultDataSubIdChanged(int subId) {
         // Broadcast an Intent for default data sub change
         Intent intent = new Intent(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED);
         intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/mocks/TelephonyRegistryMock.java b/tests/telephonytests/src/com/android/internal/telephony/mocks/TelephonyRegistryMock.java
index 8cf1fe5..d597505 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/mocks/TelephonyRegistryMock.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/mocks/TelephonyRegistryMock.java
@@ -353,12 +353,7 @@
     }
 
     @Override
-    public void notifyPhysicalChannelConfiguration(List<PhysicalChannelConfig> configs) {
-        throw new RuntimeException("Not implemented");
-    }
-
-    @Override
-    public void notifyPhysicalChannelConfigurationForSubscriber(int subId,
+    public void notifyPhysicalChannelConfigurationForSubscriber(int phoneId, int subId,
             List<PhysicalChannelConfig> configs) {
         throw new RuntimeException("Not implemented");
     }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/IccPhoneBookInterfaceManagerTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/IccPhoneBookInterfaceManagerTest.java
index 9510619..e70b560 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/IccPhoneBookInterfaceManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/IccPhoneBookInterfaceManagerTest.java
@@ -15,6 +15,7 @@
  */
 package com.android.internal.telephony.uicc;
 
+import android.content.ContentValues;
 import android.os.HandlerThread;
 import android.os.Message;
 import android.os.AsyncResult;
@@ -36,7 +37,9 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyString;
 
 import java.util.Arrays;
 import java.util.List;
@@ -115,4 +118,36 @@
         //verify the previous read is not got affected
         assertEquals(mAdnList, adnListResult);
     }
+
+    @Test
+    @SmallTest
+    public void testUpdateAdnRecord() {
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+                Message response = (Message) invocation.getArguments()[4];
+                //set result for update ADN EF
+                AsyncResult.forMessage(response).exception = null;
+                response.sendToTarget();
+                return null;
+            }
+        }).when(mAdnRecordCache).updateAdnBySearch(
+            anyInt(), (AdnRecord) anyObject(), (AdnRecord) anyObject(),
+            anyString(), (Message) anyObject());
+
+        ContentValues values = new ContentValues();
+        values.put(IccProvider.STR_TAG, "");
+        values.put(IccProvider.STR_NUMBER, "");
+        values.put(IccProvider.STR_EMAILS, "");
+        values.put(IccProvider.STR_ANRS, "");
+        values.put(IccProvider.STR_NEW_TAG, "test");
+        values.put(IccProvider.STR_NEW_NUMBER, "123456");
+        values.put(IccProvider.STR_NEW_EMAILS, "");
+        values.put(IccProvider.STR_NEW_ANRS, "");
+
+        boolean result = mIccPhoneBookInterfaceMgr.updateAdnRecordsWithContentValuesInEfBySearch(
+                IccConstants.EF_ADN, values , null);
+
+        assertTrue(result);
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRulesTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRulesTest.java
index 5edb2d8..4e56d3e 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRulesTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRulesTest.java
@@ -30,6 +30,7 @@
 import android.os.Message;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import com.android.internal.telephony.CommandException;
 import com.android.internal.telephony.TelephonyTest;
 
 import org.junit.After;
@@ -299,6 +300,38 @@
         assertEquals(0, mUiccCarrierPrivilegeRules.getPackageNames().size());
     }
 
+    @Test
+    @SmallTest
+    public void testRetryARAM_shouldRetry() {
+        AsyncResult ar1 = new AsyncResult(
+                null,
+                new int[]{0, 105, -123},
+                new CommandException(CommandException.Error.NO_SUCH_ELEMENT));
+        assertTrue(mUiccCarrierPrivilegeRules.shouldRetry(ar1, 0));
+
+        AsyncResult ar2 = new AsyncResult(
+                null,
+                new int[]{0},
+                new CommandException(CommandException.Error.MISSING_RESOURCE));
+        assertTrue(mUiccCarrierPrivilegeRules.shouldRetry(ar2, 0));
+
+        AsyncResult ar3 = new AsyncResult(
+                null,
+                new int[]{0, 105, 153},
+                new CommandException(CommandException.Error.INTERNAL_ERR));
+        assertTrue(mUiccCarrierPrivilegeRules.shouldRetry(ar3, 0));
+    }
+
+    @Test
+    @SmallTest
+    public void testRetryARAM_shouldNotRetry() {
+        AsyncResult ar = new AsyncResult(
+                null,
+                new int[]{0, 106, -126},
+                new CommandException(CommandException.Error.NO_SUCH_ELEMENT));
+        assertTrue(!mUiccCarrierPrivilegeRules.shouldRetry(ar, 0));
+    }
+
     private static final String ARAM = "A00000015141434C00";
     private static final String ARAD = "A00000015144414300";
     private static final String PKCS15_AID = "A000000063504B43532D3135";
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 1e9b643..f625237 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccProfileTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccProfileTest.java
@@ -630,4 +630,20 @@
         }
         assertTrue(carrierFound);
     }
+
+    @Test
+    @SmallTest
+    public void testIsEmptyProfile() {
+        testUpdateUiccProfileApplication();
+        assertFalse(mUiccProfile.isEmptyProfile());
+
+        // Manually resetting app shouldn't indicate we are on empty profile.
+        mUiccProfile.resetAppWithAid("", true);
+        assertFalse(mUiccProfile.isEmptyProfile());
+
+        // If we update there's no application, then we are on empty profile.
+        testUpdateUiccProfileApplicationNoApplication();
+        assertTrue(mUiccProfile.isEmptyProfile());
+
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/EuiccCardTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/EuiccCardTest.java
index 18b5b79..07919c5 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/EuiccCardTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/EuiccCardTest.java
@@ -22,7 +22,9 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.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;
@@ -858,6 +860,65 @@
         verifyStoreData(channel, "8803040506"); // ES8+.StoreMetadata
     }
 
+    @Test
+    public void testLoadBoundProfilePackage_NoProfileElements() {
+        int channel = mockLogicalChannelResponses_sgp22v210();
+
+        ResultCaptor<byte[]> resultCaptor = new ResultCaptor<>();
+        mEuiccCard.loadBoundProfilePackage(
+                Asn1Node.newBuilder(0xBF36)
+                        .addChild(Asn1Node.newBuilder(0xBF23))
+                        .addChild(Asn1Node.newBuilder(0xA0)
+                                .addChildAsBytes(0x87, new byte[] {1, 2, 3}))
+                        .addChild(Asn1Node.newBuilder(0xA1)
+                                .addChildAsBytes(0x88, new byte[] {4, 5, 6}))
+                        .addChild(Asn1Node.newBuilder(0xA2))
+                        // No children
+                        .addChild(Asn1Node.newBuilder(0xA3))
+                        .build().toBytes(),
+                resultCaptor, mHandler);
+        resultCaptor.await();
+
+        EuiccCardException e = (EuiccCardException) resultCaptor.exception;
+        assertEquals("No profile elements in BPP", e.getCause().getMessage());
+        verify(mMockCi, never())
+                .iccTransmitApduLogicalChannel(
+                        eq(channel), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), any(),
+                        any());
+    }
+
+    @Test
+    public void testLoadBoundProfilePackage_UnrecognizedTag() {
+        int channel = mockLogicalChannelResponses_sgp22v210();
+
+        ResultCaptor<byte[]> resultCaptor = new ResultCaptor<>();
+        mEuiccCard.loadBoundProfilePackage(
+                Asn1Node.newBuilder(0xBF36)
+                        .addChild(Asn1Node.newBuilder(0xBF23))
+                        .addChild(Asn1Node.newBuilder(0xA0)
+                                .addChildAsBytes(0x87, new byte[] {1, 2, 3}))
+                        .addChild(Asn1Node.newBuilder(0xA1)
+                                .addChildAsBytes(0x88, new byte[] {4, 5, 6}))
+                        .addChild(Asn1Node.newBuilder(0xA2))
+                        .addChild(Asn1Node.newBuilder(0xA3)
+                                .addChildAsBytes(0x86, new byte[] {7, 8, 9})
+                                .addChildAsBytes(0x86, new byte[] {0xA, 0xB, 0xC}))
+                        // Unrecognized tag
+                        .addChild(Asn1Node.newBuilder(0xA4))
+                        .build().toBytes(),
+                resultCaptor, mHandler);
+        resultCaptor.await();
+
+        EuiccCardException e = (EuiccCardException) resultCaptor.exception;
+        assertEquals(
+                "Actual BPP length (33) does not match segmented length (31), this must be due to a"
+                        + " malformed BPP",
+                e.getCause().getMessage());
+        verify(mMockCi, never())
+                .iccTransmitApduLogicalChannel(
+                        eq(channel), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), any(),
+                        any());
+    }
 
     @Test
     public void testCancelSession() {
@@ -1124,4 +1185,12 @@
         LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel);
         return channel;
     }
+
+    private int mockLogicalChannelResponses_sgp22v210(Object... responses) {
+        int channel = LogicalChannelMocker.mockOpenLogicalChannelResponse(mMockCi,
+                "E00582030201009000");
+        LogicalChannelMocker.mockSendToLogicalChannel(mMockCi, channel, responses);
+        LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel);
+        return channel;
+    }
 }