Merge "IMS-VT: Notify capability change to clients when reset"
diff --git a/proto/src/telephony.proto b/proto/src/telephony.proto
index b9243f7..215d14e 100644
--- a/proto/src/telephony.proto
+++ b/proto/src/telephony.proto
@@ -1199,6 +1199,9 @@
 
       // Notification about received SMS
       SMS_RECEIVED = 8;
+
+      // CB message received
+      CB_SMS_RECEIVED = 9;
     }
 
     // Formats used to encode SMS messages
@@ -1224,6 +1227,51 @@
       SMS_IMS = 3;
     }
 
+    message CBMessage {
+      // CB message format
+      optional Format msgFormat = 1;
+
+      // CB message priority
+      optional CBPriority msgPriority = 2;
+
+      // Type of CB msg
+      optional CBMessageType msgType = 3;
+
+      // Service category of CB message
+      optional int32 serviceCategory = 4;
+    }
+
+    enum CBMessageType {
+      // Unknown type
+      TYPE_UNKNOWN = 0;
+
+      // ETWS CB msg
+      ETWS = 1;
+
+      // CMAS CB msg
+      CMAS = 2;
+
+      // CB msg other than ETWS and CMAS
+      OTHER = 3;
+    }
+
+    enum CBPriority {
+      // Unknown priority
+      PRIORITY_UNKNOWN = 0;
+
+      // NORMAL priority
+      NORMAL = 1;
+
+      // Interactive priority
+      INTERACTIVE = 2;
+
+      // Urgent priority
+      URGENT = 3;
+
+      // Emergency priority
+      EMERGENCY = 4;
+    }
+
     // Event type
     optional Type type = 1;
 
@@ -1261,6 +1309,9 @@
 
     // Numeric ID
     optional int32 ril_request_id = 12;
+
+    // Cellbroadcast message content
+    optional CBMessage cell_broadcast_message = 13;
   }
 
   // Time when session has started, in minutes since epoch,
diff --git a/src/java/com/android/internal/telephony/BaseCommands.java b/src/java/com/android/internal/telephony/BaseCommands.java
index b44f58a..c8cea5a 100644
--- a/src/java/com/android/internal/telephony/BaseCommands.java
+++ b/src/java/com/android/internal/telephony/BaseCommands.java
@@ -48,6 +48,7 @@
     protected RegistrantList mIccSlotStatusChangedRegistrants = new RegistrantList();
     protected RegistrantList mVoicePrivacyOnRegistrants = new RegistrantList();
     protected RegistrantList mVoicePrivacyOffRegistrants = new RegistrantList();
+    protected Registrant mUnsolOemHookRawRegistrant;
     protected RegistrantList mOtaProvisionRegistrants = new RegistrantList();
     protected RegistrantList mCallWaitingInfoRegistrants = new RegistrantList();
     protected RegistrantList mDisplayInfoRegistrants = new RegistrantList();
@@ -596,6 +597,17 @@
         mSignalInfoRegistrants.add(r);
     }
 
+    public void setOnUnsolOemHookRaw(Handler h, int what, Object obj) {
+        mUnsolOemHookRawRegistrant = new Registrant (h, what, obj);
+    }
+
+    public void unSetOnUnsolOemHookRaw(Handler h) {
+        if (mUnsolOemHookRawRegistrant != null && mUnsolOemHookRawRegistrant.getHandler() == h) {
+            mUnsolOemHookRawRegistrant.clear();
+            mUnsolOemHookRawRegistrant = null;
+        }
+    }
+
     @Override
     public void unregisterForSignalInfo(Handler h) {
         mSignalInfoRegistrants.remove(h);
diff --git a/src/java/com/android/internal/telephony/BlockChecker.java b/src/java/com/android/internal/telephony/BlockChecker.java
index 2a6392f..456be56 100644
--- a/src/java/com/android/internal/telephony/BlockChecker.java
+++ b/src/java/com/android/internal/telephony/BlockChecker.java
@@ -17,6 +17,23 @@
      * <p>
      * This method catches all underlying exceptions to ensure that this method never throws any
      * exception.
+     * <p>
+     * @deprecated use {@link #isBlocked(Context, String, Bundle)} instead.
+     *
+     * @param context the context of the caller.
+     * @param phoneNumber the number to check.
+     * @return {@code true} if the number is blocked. {@code false} otherwise.
+     */
+    @Deprecated
+    public static boolean isBlocked(Context context, String phoneNumber) {
+        return isBlocked(context, phoneNumber, null /* extras */);
+    }
+
+    /**
+     * Returns {@code true} if {@code phoneNumber} is blocked according to {@code extras}.
+     * <p>
+     * This method catches all underlying exceptions to ensure that this method never throws any
+     * exception.
      *
      * @param context the context of the caller.
      * @param phoneNumber the number to check.
diff --git a/src/java/com/android/internal/telephony/CarrierIdentifier.java b/src/java/com/android/internal/telephony/CarrierIdentifier.java
index 4c6e439..69131ca 100644
--- a/src/java/com/android/internal/telephony/CarrierIdentifier.java
+++ b/src/java/com/android/internal/telephony/CarrierIdentifier.java
@@ -20,13 +20,11 @@
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
-import android.content.SharedPreferences;
 import android.database.ContentObserver;
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.Handler;
 import android.os.Message;
-import android.preference.PreferenceManager;
 import android.provider.Telephony;
 import android.telephony.Rlog;
 import android.telephony.SubscriptionManager;
@@ -38,6 +36,7 @@
 import com.android.internal.telephony.metrics.TelephonyMetrics;
 import com.android.internal.telephony.uicc.IccRecords;
 import com.android.internal.telephony.uicc.UiccController;
+import com.android.internal.telephony.uicc.UiccProfile;
 import com.android.internal.util.IndentingPrintWriter;
 
 import java.io.FileDescriptor;
@@ -85,12 +84,11 @@
     private Context mContext;
     private Phone mPhone;
     private IccRecords mIccRecords;
+    private UiccProfile mUiccProfile;
     private final LocalLog mCarrierIdLocalLog = new LocalLog(20);
     private final TelephonyManager mTelephonyMgr;
     private final SubscriptionsChangedListener mOnSubscriptionsChangedListener =
             new SubscriptionsChangedListener();
-    private final SharedPreferenceChangedListener mSharedPrefListener =
-            new SharedPreferenceChangedListener();
 
     private final ContentObserver mContentObserver = new ContentObserver(this) {
         @Override
@@ -130,20 +128,6 @@
         }
     }
 
-    private class SharedPreferenceChangedListener implements
-            SharedPreferences.OnSharedPreferenceChangeListener {
-        @Override
-        public void onSharedPreferenceChanged(
-                SharedPreferences sharedPreferences, String key) {
-            if (TextUtils.equals(key, OPERATOR_BRAND_OVERRIDE_PREFIX
-                    + mPhone.getIccSerialNumber())) {
-                // SPN override from carrier privileged apps
-                logd("[onSharedPreferenceChanged]: " + key);
-                sendEmptyMessage(SPN_OVERRIDE_EVENT);
-            }
-        }
-    }
-
     public CarrierIdentifier(Phone phone) {
         logd("Creating CarrierIdentifier[" + phone.getPhoneId() + "]");
         mContext = phone.getContext();
@@ -157,8 +141,6 @@
                 CarrierId.All.CONTENT_URI, false, mContentObserver);
         SubscriptionManager.from(mContext).addOnSubscriptionsChangedListener(
                 mOnSubscriptionsChangedListener);
-        PreferenceManager.getDefaultSharedPreferences(mContext)
-                .registerOnSharedPreferenceChangeListener(mSharedPrefListener);
         UiccController.getInstance().registerForIccChanged(this, ICC_CHANGED_EVENT, null);
     }
 
@@ -222,21 +204,36 @@
                 }
                 break;
             case ICC_CHANGED_EVENT:
-                final IccRecords newIccRecords = mPhone.getIccRecords();
+                // all records used for carrier identification are from SimRecord
+                final IccRecords newIccRecords = UiccController.getInstance().getIccRecords(
+                        mPhone.getPhoneId(), UiccController.APP_FAM_3GPP);
                 if (mIccRecords != newIccRecords) {
                     if (mIccRecords != null) {
                         logd("Removing stale icc objects.");
-                        mIccRecords.unregisterForSpnUpdate(this);
                         mIccRecords.unregisterForRecordsLoaded(this);
                         mIccRecords = null;
                     }
                     if (newIccRecords != null) {
                         logd("new Icc object");
-                        newIccRecords.registerForSpnUpdate(this, SPN_OVERRIDE_EVENT, null);
                         newIccRecords.registerForRecordsLoaded(this, SIM_LOAD_EVENT, null);
                         mIccRecords = newIccRecords;
                     }
                 }
+                // check UICC profile
+                final UiccProfile uiccProfile = UiccController.getInstance()
+                        .getUiccProfileForPhone(mPhone.getPhoneId());
+                if (mUiccProfile != uiccProfile) {
+                    if (mUiccProfile != null) {
+                        logd("unregister operatorBrandOverride");
+                        mUiccProfile.unregisterForOperatorBrandOverride(this);
+                        mUiccProfile = null;
+                    }
+                    if (uiccProfile != null) {
+                        logd("register operatorBrandOverride");
+                        uiccProfile.registerForOpertorBrandOverride(this, SPN_OVERRIDE_EVENT, null);
+                        mUiccProfile = uiccProfile;
+                    }
+                }
                 break;
             default:
                 loge("invalid msg: " + msg.what);
@@ -532,6 +529,7 @@
 
         if (VDBG) {
             logd("[matchCarrier]"
+                    + " mnnmnc:" + mccmnc
                     + " gid1: " + gid1
                     + " gid2: " + gid2
                     + " imsi: " + Rlog.pii(LOG_TAG, imsi)
@@ -567,7 +565,7 @@
 
         /*
          * Write Carrier Identification Matching event, logging with the
-         * carrierId, gid1 and carrier list version to differentiate below cases of metrics:
+         * carrierId, mccmnc, gid1 and carrier list version to differentiate below cases of metrics:
          * 1) unknown mccmnc - the Carrier Id provider contains no rule that matches the
          * read mccmnc.
          * 2) the Carrier Id provider contains some rule(s) that match the read mccmnc,
@@ -577,14 +575,15 @@
          */
         String unknownGid1ToLog = ((maxScore & CarrierMatchingRule.SCORE_GID1) == 0
                 && !TextUtils.isEmpty(subscriptionRule.mGid1)) ? subscriptionRule.mGid1 : null;
-        String unknownMccmncToLog = (maxScore == CarrierMatchingRule.SCORE_INVALID
+        String unknownMccmncToLog = ((maxScore == CarrierMatchingRule.SCORE_INVALID
+                || (maxScore & CarrierMatchingRule.SCORE_GID1) == 0)
                 && !TextUtils.isEmpty(subscriptionRule.mMccMnc)) ? subscriptionRule.mMccMnc : null;
         TelephonyMetrics.getInstance().writeCarrierIdMatchingEvent(
                 mPhone.getPhoneId(), getCarrierListVersion(), mCarrierId,
                 unknownMccmncToLog, unknownGid1ToLog);
     }
 
-    private int getCarrierListVersion() {
+    public int getCarrierListVersion() {
         final Cursor cursor = mContext.getContentResolver().query(
                 Uri.withAppendedPath(CarrierId.All.CONTENT_URI,
                 "get_version"), null, null, null);
diff --git a/src/java/com/android/internal/telephony/CarrierInfoManager.java b/src/java/com/android/internal/telephony/CarrierInfoManager.java
index ebf04e8..6a6a065 100644
--- a/src/java/com/android/internal/telephony/CarrierInfoManager.java
+++ b/src/java/com/android/internal/telephony/CarrierInfoManager.java
@@ -19,8 +19,10 @@
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.Context;
+import android.content.Intent;
 import android.database.Cursor;
 import android.database.sqlite.SQLiteConstraintException;
+import android.os.UserHandle;
 import android.provider.Telephony;
 import android.telephony.ImsiEncryptionInfo;
 import android.telephony.TelephonyManager;
@@ -34,6 +36,16 @@
  */
 public class CarrierInfoManager {
     private static final String LOG_TAG = "CarrierInfoManager";
+    private static final String KEY_TYPE = "KEY_TYPE";
+
+    /*
+    * Rate limit (in milliseconds) the number of times the Carrier keys can be reset.
+    * Do it at most once every 12 hours.
+    */
+    private static final int RESET_CARRIER_KEY_RATE_LIMIT = 12 * 60 * 60 * 1000;
+
+    // Last time the resetCarrierKeysForImsiEncryption API was called successfully.
+    private long mLastAccessResetCarrierKey = 0;
 
     /**
      * Returns Carrier specific information that will be used to encrypt the IMSI and IMPI.
@@ -158,4 +170,69 @@
         updateOrInsertCarrierKey(imsiEncryptionInfo, mContext);
         //todo send key to modem. Will be done in a subsequent CL.
     }
-}
\ No newline at end of file
+
+    /**
+     * Resets the Carrier Keys in the database. This involves 2 steps:
+     *  1. Delete the keys from the database.
+     *  2. Send an intent to download new Certificates.
+     * @param context Context
+     * @param mPhoneId phoneId
+     *
+     */
+    public void resetCarrierKeysForImsiEncryption(Context context, int mPhoneId) {
+        Log.i(LOG_TAG, "resetting carrier key");
+        // Check rate limit.
+        long now = System.currentTimeMillis();
+        if (now - mLastAccessResetCarrierKey < RESET_CARRIER_KEY_RATE_LIMIT) {
+            Log.i(LOG_TAG, "resetCarrierKeysForImsiEncryption: Access rate exceeded");
+            return;
+        }
+        mLastAccessResetCarrierKey = now;
+        deleteCarrierInfoForImsiEncryption(context);
+        Intent resetIntent = new Intent(TelephonyIntents.ACTION_CARRIER_CERTIFICATE_DOWNLOAD);
+        resetIntent.putExtra(PhoneConstants.PHONE_KEY, mPhoneId);
+        context.sendBroadcastAsUser(resetIntent, UserHandle.ALL);
+    }
+
+    /**
+     * Deletes all the keys for a given Carrier from the device keystore.
+     * @param context Context
+     */
+    public static void deleteCarrierInfoForImsiEncryption(Context context) {
+        Log.i(LOG_TAG, "deleting carrier key from db");
+        String mcc = "";
+        String mnc = "";
+        final TelephonyManager telephonyManager =
+                (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+        String simOperator = telephonyManager.getSimOperator();
+        if (!TextUtils.isEmpty(simOperator)) {
+            mcc = simOperator.substring(0, 3);
+            mnc = simOperator.substring(3);
+        } else {
+            Log.e(LOG_TAG, "Invalid networkOperator: " + simOperator);
+            return;
+        }
+        ContentResolver mContentResolver = context.getContentResolver();
+        try {
+            String whereClause = "mcc=? and mnc=?";
+            String[] whereArgs = new String[] { mcc, mnc };
+            mContentResolver.delete(Telephony.CarrierColumns.CONTENT_URI, whereClause, whereArgs);
+        } catch (Exception e) {
+            Log.e(LOG_TAG, "Delete failed" + e);
+        }
+    }
+
+    /**
+     * Deletes all the keys from the device keystore.
+     * @param context Context
+     */
+    public static void deleteAllCarrierKeysForImsiEncryption(Context context) {
+        Log.i(LOG_TAG, "deleting ALL carrier keys from db");
+        ContentResolver mContentResolver = context.getContentResolver();
+        try {
+            mContentResolver.delete(Telephony.CarrierColumns.CONTENT_URI, null, null);
+        } catch (Exception e) {
+            Log.e(LOG_TAG, "Delete failed" + e);
+        }
+    }
+}
diff --git a/src/java/com/android/internal/telephony/CarrierKeyDownloadManager.java b/src/java/com/android/internal/telephony/CarrierKeyDownloadManager.java
index 66bc529..1513a33 100644
--- a/src/java/com/android/internal/telephony/CarrierKeyDownloadManager.java
+++ b/src/java/com/android/internal/telephony/CarrierKeyDownloadManager.java
@@ -57,6 +57,7 @@
 import java.security.cert.CertificateFactory;
 import java.security.cert.X509Certificate;
 import java.util.Date;
+import java.util.Random;
 import java.util.zip.GZIPInputStream;
 
 /**
@@ -70,8 +71,15 @@
 
     private static final int DAY_IN_MILLIS = 24 * 3600 * 1000;
 
-    // Start trying to renew the cert X days before it expires.
-    private static final int DEFAULT_RENEWAL_WINDOW_DAYS = 7;
+    // Create a window prior to the key expiration, during which the cert will be
+    // downloaded. Defines the start date of that window. So if the key expires on
+    // Dec  21st, the start of the renewal window will be Dec 1st.
+    private static final int START_RENEWAL_WINDOW_DAYS = 21;
+
+    // This will define the end date of the window.
+    private static final int END_RENEWAL_WINDOW_DAYS = 7;
+
+
 
     /* Intent for downloading the public key */
     private static final String INTENT_KEY_RENEWAL_ALARM_PREFIX =
@@ -111,6 +119,7 @@
         filter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
         filter.addAction(DownloadManager.ACTION_DOWNLOAD_COMPLETE);
         filter.addAction(INTENT_KEY_RENEWAL_ALARM_PREFIX + mPhone.getPhoneId());
+        filter.addAction(TelephonyIntents.ACTION_CARRIER_CERTIFICATE_DOWNLOAD);
         mContext.registerReceiver(mBroadcastReceiver, filter, null, phone);
         mDownloadManager = (DownloadManager) mContext.getSystemService(Context.DOWNLOAD_SERVICE);
     }
@@ -123,6 +132,12 @@
             if (action.equals(INTENT_KEY_RENEWAL_ALARM_PREFIX + slotId)) {
                 Log.d(LOG_TAG, "Handling key renewal alarm: " + action);
                 handleAlarmOrConfigChange();
+            } else if (action.equals(TelephonyIntents.ACTION_CARRIER_CERTIFICATE_DOWNLOAD)) {
+                if (slotId == intent.getIntExtra(PhoneConstants.PHONE_KEY,
+                        SubscriptionManager.INVALID_SIM_SLOT_INDEX)) {
+                    Log.d(LOG_TAG, "Handling reset intent: " + action);
+                    handleAlarmOrConfigChange();
+                }
             } else if (action.equals(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)) {
                 if (slotId == intent.getIntExtra(PhoneConstants.PHONE_KEY,
                         SubscriptionManager.INVALID_SIM_SLOT_INDEX)) {
@@ -208,10 +223,16 @@
         // set the alarm to run in a day. Else, we'll set the alarm to run 7 days prior to
         // expiration.
         if (minExpirationDate == Long.MAX_VALUE || (minExpirationDate
-                < System.currentTimeMillis() + DEFAULT_RENEWAL_WINDOW_DAYS * DAY_IN_MILLIS)) {
+                < System.currentTimeMillis() + END_RENEWAL_WINDOW_DAYS * DAY_IN_MILLIS)) {
             minExpirationDate = System.currentTimeMillis() + DAY_IN_MILLIS;
         } else {
-            minExpirationDate = minExpirationDate - DEFAULT_RENEWAL_WINDOW_DAYS * DAY_IN_MILLIS;
+            // We don't want all the phones to download the certs simultaneously, so
+            // we pick a random time during the download window to avoid this situation.
+            Random random = new Random();
+            int max = START_RENEWAL_WINDOW_DAYS * DAY_IN_MILLIS;
+            int min = END_RENEWAL_WINDOW_DAYS * DAY_IN_MILLIS;
+            int randomTime = random.nextInt(max - min) + min;
+            minExpirationDate = minExpirationDate - randomTime;
         }
         return minExpirationDate;
     }
@@ -478,7 +499,7 @@
             }
             Date imsiDate = imsiEncryptionInfo.getExpirationTime();
             long timeToExpire = imsiDate.getTime() - System.currentTimeMillis();
-            return (timeToExpire < DEFAULT_RENEWAL_WINDOW_DAYS * DAY_IN_MILLIS) ? true : false;
+            return (timeToExpire < START_RENEWAL_WINDOW_DAYS * DAY_IN_MILLIS) ? true : false;
         }
         return false;
     }
@@ -501,6 +522,7 @@
             DownloadManager.Request request = new DownloadManager.Request(Uri.parse(mURL));
             request.setAllowedOverMetered(false);
             request.setVisibleInDownloadsUi(false);
+            request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN);
             Long carrierKeyDownloadRequestId = mDownloadManager.enqueue(request);
             SharedPreferences.Editor editor = getDefaultSharedPreferences(mContext).edit();
 
diff --git a/src/java/com/android/internal/telephony/CarrierServiceStateTracker.java b/src/java/com/android/internal/telephony/CarrierServiceStateTracker.java
index abf91c9..2f57838 100644
--- a/src/java/com/android/internal/telephony/CarrierServiceStateTracker.java
+++ b/src/java/com/android/internal/telephony/CarrierServiceStateTracker.java
@@ -23,6 +23,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.database.ContentObserver;
 import android.os.Handler;
 import android.os.Message;
 import android.os.PersistableBundle;
@@ -30,6 +31,8 @@
 import android.telephony.CarrierConfigManager;
 import android.telephony.Rlog;
 import android.telephony.ServiceState;
+import android.telephony.SubscriptionManager;
+import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.util.NotificationChannelController;
@@ -38,6 +41,7 @@
 import java.util.Map;
 
 
+
 /**
  * This contains Carrier specific logic based on the states/events
  * managed in ServiceStateTracker.
@@ -50,21 +54,75 @@
     protected static final int CARRIER_EVENT_VOICE_DEREGISTRATION = CARRIER_EVENT_BASE + 2;
     protected static final int CARRIER_EVENT_DATA_REGISTRATION = CARRIER_EVENT_BASE + 3;
     protected static final int CARRIER_EVENT_DATA_DEREGISTRATION = CARRIER_EVENT_BASE + 4;
+    protected static final int CARRIER_EVENT_IMS_CAPABILITIES_CHANGED = CARRIER_EVENT_BASE + 5;
+
     private static final int UNINITIALIZED_DELAY_VALUE = -1;
     private Phone mPhone;
     private ServiceStateTracker mSST;
-
+    private final Map<Integer, NotificationType> mNotificationTypeMap = new HashMap<>();
+    private int mPreviousSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
     public static final int NOTIFICATION_PREF_NETWORK = 1000;
     public static final int NOTIFICATION_EMERGENCY_NETWORK = 1001;
 
-    private final Map<Integer, NotificationType> mNotificationTypeMap = new HashMap<>();
-
     public CarrierServiceStateTracker(Phone phone, ServiceStateTracker sst) {
         this.mPhone = phone;
         this.mSST = sst;
         phone.getContext().registerReceiver(mBroadcastReceiver, new IntentFilter(
                 CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED));
+        // Listen for subscriber changes
+        SubscriptionManager.from(mPhone.getContext()).addOnSubscriptionsChangedListener(
+                new OnSubscriptionsChangedListener(this.getLooper()) {
+                    @Override
+                    public void onSubscriptionsChanged() {
+                        int subId = mPhone.getSubId();
+                        if (mPreviousSubId != subId) {
+                            mPreviousSubId = subId;
+                            registerPrefNetworkModeObserver();
+                        }
+                    }
+                });
+
         registerNotificationTypes();
+        registerPrefNetworkModeObserver();
+    }
+
+    private ContentObserver mPrefNetworkModeObserver = new ContentObserver(this) {
+        @Override
+        public void onChange(boolean selfChange) {
+            handlePrefNetworkModeChanged();
+        }
+    };
+
+    /**
+     * Return preferred network mode observer
+     */
+    @VisibleForTesting
+    public ContentObserver getContentObserver() {
+        return mPrefNetworkModeObserver;
+    }
+
+    private void registerPrefNetworkModeObserver() {
+        int subId = mPhone.getSubId();
+        unregisterPrefNetworkModeObserver();
+        if (SubscriptionManager.isValidSubscriptionId(subId)) {
+            mPhone.getContext().getContentResolver().registerContentObserver(
+                    Settings.Global.getUriFor(Settings.Global.PREFERRED_NETWORK_MODE + subId),
+                    true,
+                    mPrefNetworkModeObserver);
+        }
+    }
+
+    private void unregisterPrefNetworkModeObserver() {
+        mPhone.getContext().getContentResolver().unregisterContentObserver(
+                mPrefNetworkModeObserver);
+    }
+
+    /**
+     * Returns mNotificationTypeMap
+     */
+    @VisibleForTesting
+    public Map<Integer, NotificationType> getNotificationTypeMap() {
+        return mNotificationTypeMap;
     }
 
     private void registerNotificationTypes() {
@@ -83,6 +141,9 @@
             case CARRIER_EVENT_DATA_DEREGISTRATION:
                 handleConfigChanges();
                 break;
+            case CARRIER_EVENT_IMS_CAPABILITIES_CHANGED:
+                handleImsCapabilitiesChanged();
+                break;
             case NOTIFICATION_EMERGENCY_NETWORK:
             case NOTIFICATION_PREF_NETWORK:
                 Rlog.d(LOG_TAG, "sending notification after delay: " + msg.what);
@@ -152,14 +213,33 @@
     private void handleConfigChanges() {
         for (Map.Entry<Integer, NotificationType> entry : mNotificationTypeMap.entrySet()) {
             NotificationType notificationType = entry.getValue();
-            if (evaluateSendingMessage(notificationType)) {
-                Message notificationMsg = obtainMessage(notificationType.getTypeId(), null);
-                Rlog.i(LOG_TAG, "starting timer for notifications." + notificationType.getTypeId());
-                sendMessageDelayed(notificationMsg, getDelay(notificationType));
-            } else {
-                cancelNotification(notificationType.getTypeId());
-                Rlog.i(LOG_TAG, "canceling notifications: " + notificationType.getTypeId());
-            }
+            evaluateSendingMessageOrCancelNotification(notificationType);
+        }
+    }
+
+    private void handlePrefNetworkModeChanged() {
+        NotificationType notificationType = mNotificationTypeMap.get(NOTIFICATION_PREF_NETWORK);
+        if (notificationType != null) {
+            evaluateSendingMessageOrCancelNotification(notificationType);
+        }
+    }
+
+    private void handleImsCapabilitiesChanged() {
+        NotificationType notificationType = mNotificationTypeMap
+                .get(NOTIFICATION_EMERGENCY_NETWORK);
+        if (notificationType != null) {
+            evaluateSendingMessageOrCancelNotification(notificationType);
+        }
+    }
+
+    private void evaluateSendingMessageOrCancelNotification(NotificationType notificationType) {
+        if (evaluateSendingMessage(notificationType)) {
+            Message notificationMsg = obtainMessage(notificationType.getTypeId(), null);
+            Rlog.i(LOG_TAG, "starting timer for notifications." + notificationType.getTypeId());
+            sendMessageDelayed(notificationMsg, getDelay(notificationType));
+        } else {
+            cancelNotification(notificationType.getTypeId());
+            Rlog.i(LOG_TAG, "canceling notifications: " + notificationType.getTypeId());
         }
     }
 
@@ -240,6 +320,13 @@
     }
 
     /**
+     * Dispose the CarrierServiceStateTracker.
+     */
+    public void dispose() {
+        unregisterPrefNetworkModeObserver();
+    }
+
+    /**
      * Class that defines the different types of notifications.
      */
     public interface NotificationType {
@@ -293,7 +380,7 @@
             }
             this.mDelay = bundle.getInt(
                     CarrierConfigManager.KEY_PREF_NETWORK_NOTIFICATION_DELAY_INT);
-            Rlog.i(LOG_TAG, "reading time to delay notification emergency: " + mDelay);
+            Rlog.i(LOG_TAG, "reading time to delay notification pref network: " + mDelay);
         }
 
         public int getDelay() {
@@ -311,7 +398,7 @@
             Rlog.i(LOG_TAG, "PrefNetworkNotification: sendMessage() w/values: "
                     + "," + isPhoneStillRegistered() + "," + mDelay + "," + isGlobalMode()
                     + "," + mSST.isRadioOn());
-            if (mDelay == UNINITIALIZED_DELAY_VALUE ||  isPhoneStillRegistered() || isGlobalMode()
+            if (mDelay == UNINITIALIZED_DELAY_VALUE || isPhoneStillRegistered() || isGlobalMode()
                     || isRadioOffOrAirplaneMode()) {
                 return false;
             }
@@ -324,6 +411,7 @@
         public Notification.Builder getNotificationBuilder() {
             Context context = mPhone.getContext();
             Intent notificationIntent = new Intent(Settings.ACTION_DATA_ROAMING_SETTINGS);
+            notificationIntent.putExtra("expandable", true);
             PendingIntent settingsIntent = PendingIntent.getActivity(context, 0, notificationIntent,
                     PendingIntent.FLAG_ONE_SHOT);
             CharSequence title = context.getText(
diff --git a/src/java/com/android/internal/telephony/CellBroadcastHandler.java b/src/java/com/android/internal/telephony/CellBroadcastHandler.java
index 4227148..8fc4642 100644
--- a/src/java/com/android/internal/telephony/CellBroadcastHandler.java
+++ b/src/java/com/android/internal/telephony/CellBroadcastHandler.java
@@ -30,6 +30,12 @@
 import android.provider.Telephony;
 import android.telephony.SmsCbMessage;
 import android.telephony.SubscriptionManager;
+import android.util.LocalLog;
+
+import com.android.internal.telephony.metrics.TelephonyMetrics;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 
 /**
  * Dispatch new Cell Broadcasts to receivers. Acquires a private wakelock until the broadcast
@@ -37,6 +43,8 @@
  */
 public class CellBroadcastHandler extends WakeLockStateMachine {
 
+    private final LocalLog mLocalLog = new LocalLog(100);
+
     private CellBroadcastHandler(Context context, Phone phone) {
         this("CellBroadcastHandler", context, phone);
     }
@@ -82,9 +90,18 @@
         String receiverPermission;
         int appOp;
 
+        // Log Cellbroadcast msg received event
+        TelephonyMetrics metrics = TelephonyMetrics.getInstance();
+        metrics.writeNewCBSms(mPhone.getPhoneId(), message.getMessageFormat(),
+                message.getMessagePriority(), message.isCmasMessage(), message.isEtwsMessage(),
+                message.getServiceCategory());
+
+        String msg;
         Intent intent;
         if (message.isEmergencyMessage()) {
-            log("Dispatching emergency SMS CB, SmsCbMessage is: " + message);
+            msg = "Dispatching emergency SMS CB, SmsCbMessage is: " + message;
+            log(msg);
+            mLocalLog.log(msg);
             intent = new Intent(Telephony.Sms.Intents.SMS_EMERGENCY_CB_RECEIVED_ACTION);
             // Explicitly send the intent to the default cell broadcast receiver.
             intent.setPackage(mContext.getResources().getString(
@@ -92,7 +109,9 @@
             receiverPermission = Manifest.permission.RECEIVE_EMERGENCY_BROADCAST;
             appOp = AppOpsManager.OP_RECEIVE_EMERGECY_SMS;
         } else {
-            log("Dispatching SMS CB, SmsCbMessage is: " + message);
+            msg = "Dispatching SMS CB, SmsCbMessage is: " + message;
+            log(msg);
+            mLocalLog.log(msg);
             intent = new Intent(Telephony.Sms.Intents.SMS_CB_RECEIVED_ACTION);
             // Send implicit intent since there are various 3rd party carrier apps listen to
             // this intent.
@@ -121,4 +140,11 @@
         mContext.sendOrderedBroadcastAsUser(intent, UserHandle.ALL, receiverPermission, appOp,
                 mReceiver, getHandler(), Activity.RESULT_OK, null, null);
     }
+
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("CellBroadcastHandler:");
+        mLocalLog.dump(fd, pw, args);
+        pw.flush();
+    }
 }
diff --git a/src/java/com/android/internal/telephony/CellularNetworkService.java b/src/java/com/android/internal/telephony/CellularNetworkService.java
index a89a15a..8b840de 100644
--- a/src/java/com/android/internal/telephony/CellularNetworkService.java
+++ b/src/java/com/android/internal/telephony/CellularNetworkService.java
@@ -18,9 +18,7 @@
 
 import android.annotation.CallSuper;
 import android.hardware.radio.V1_0.CellInfoType;
-import android.hardware.radio.V1_0.DataRegStateResult;
 import android.hardware.radio.V1_0.RegState;
-import android.hardware.radio.V1_0.VoiceRegStateResult;
 import android.os.AsyncResult;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -202,8 +200,21 @@
 
             // TODO: unify when voiceRegStateResult and DataRegStateResult are unified.
             if (domain == NetworkRegistrationState.DOMAIN_CS) {
-                VoiceRegStateResult voiceRegState = (VoiceRegStateResult) result;
-                int transportType = TransportType.WWAN;
+                return createRegistrationStateFromVoiceRegState(result);
+            } else if (domain == NetworkRegistrationState.DOMAIN_PS) {
+                return createRegistrationStateFromDataRegState(result);
+            } else {
+                return null;
+            }
+        }
+
+        private NetworkRegistrationState createRegistrationStateFromVoiceRegState(Object result) {
+            int transportType = TransportType.WWAN;
+            int domain = NetworkRegistrationState.DOMAIN_CS;
+
+            if (result instanceof android.hardware.radio.V1_0.VoiceRegStateResult) {
+                android.hardware.radio.V1_0.VoiceRegStateResult voiceRegState =
+                        (android.hardware.radio.V1_0.VoiceRegStateResult) result;
                 int regState = getRegStateFromHalRegState(voiceRegState.regState);
                 int accessNetworkTechnology = getAccessNetworkTechnologyFromRat(voiceRegState.rat);
                 int reasonForDenial = voiceRegState.reasonForDenial;
@@ -217,13 +228,42 @@
                 CellIdentity cellIdentity =
                         convertHalCellIdentityToCellIdentity(voiceRegState.cellIdentity);
 
-                return new NetworkRegistrationState(transportType, domain, regState,
+                return new NetworkRegistrationState(domain, transportType, regState,
                         accessNetworkTechnology, reasonForDenial, emergencyOnly, availableServices,
                         cellIdentity, cssSupported, roamingIndicator, systemIsInPrl,
                         defaultRoamingIndicator);
-            } else if (domain == NetworkRegistrationState.DOMAIN_PS) {
-                DataRegStateResult dataRegState = (DataRegStateResult) result;
-                int transportType = TransportType.WWAN;
+            } else if (result instanceof android.hardware.radio.V1_2.VoiceRegStateResult) {
+                android.hardware.radio.V1_2.VoiceRegStateResult voiceRegState =
+                        (android.hardware.radio.V1_2.VoiceRegStateResult) result;
+                int regState = getRegStateFromHalRegState(voiceRegState.regState);
+                int accessNetworkTechnology = getAccessNetworkTechnologyFromRat(voiceRegState.rat);
+                int reasonForDenial = voiceRegState.reasonForDenial;
+                boolean emergencyOnly = isEmergencyOnly(voiceRegState.regState);
+                boolean cssSupported = voiceRegState.cssSupported;
+                int roamingIndicator = voiceRegState.roamingIndicator;
+                int systemIsInPrl = voiceRegState.systemIsInPrl;
+                int defaultRoamingIndicator = voiceRegState.defaultRoamingIndicator;
+                int[] availableServices = getAvailableServices(
+                        regState, domain, emergencyOnly);
+                CellIdentity cellIdentity =
+                        convertHalCellIdentityToCellIdentity(voiceRegState.cellIdentity);
+
+                return new NetworkRegistrationState(domain, transportType, regState,
+                        accessNetworkTechnology, reasonForDenial, emergencyOnly, availableServices,
+                        cellIdentity, cssSupported, roamingIndicator, systemIsInPrl,
+                        defaultRoamingIndicator);
+            }
+
+            return null;
+        }
+
+        private NetworkRegistrationState createRegistrationStateFromDataRegState(Object result) {
+            int domain = NetworkRegistrationState.DOMAIN_PS;
+            int transportType = TransportType.WWAN;
+
+            if (result instanceof android.hardware.radio.V1_0.DataRegStateResult) {
+                android.hardware.radio.V1_0.DataRegStateResult dataRegState =
+                        (android.hardware.radio.V1_0.DataRegStateResult) result;
                 int regState = getRegStateFromHalRegState(dataRegState.regState);
                 int accessNetworkTechnology = getAccessNetworkTechnologyFromRat(dataRegState.rat);
                 int reasonForDenial = dataRegState.reasonDataDenied;
@@ -232,12 +272,28 @@
                 int[] availableServices = getAvailableServices(regState, domain, emergencyOnly);
                 CellIdentity cellIdentity =
                         convertHalCellIdentityToCellIdentity(dataRegState.cellIdentity);
-                return new NetworkRegistrationState(transportType, domain, regState,
+
+                return new NetworkRegistrationState(domain, transportType, regState,
                         accessNetworkTechnology, reasonForDenial, emergencyOnly, availableServices,
                         cellIdentity, maxDataCalls);
-            } else {
-                return null;
+            } else if (result instanceof android.hardware.radio.V1_2.DataRegStateResult) {
+                android.hardware.radio.V1_2.DataRegStateResult dataRegState =
+                        (android.hardware.radio.V1_2.DataRegStateResult) result;
+                int regState = getRegStateFromHalRegState(dataRegState.regState);
+                int accessNetworkTechnology = getAccessNetworkTechnologyFromRat(dataRegState.rat);
+                int reasonForDenial = dataRegState.reasonDataDenied;
+                boolean emergencyOnly = isEmergencyOnly(dataRegState.regState);
+                int maxDataCalls = dataRegState.maxDataCalls;
+                int[] availableServices = getAvailableServices(regState, domain, emergencyOnly);
+                CellIdentity cellIdentity =
+                        convertHalCellIdentityToCellIdentity(dataRegState.cellIdentity);
+
+                return new NetworkRegistrationState(domain, transportType, regState,
+                        accessNetworkTechnology, reasonForDenial, emergencyOnly, availableServices,
+                        cellIdentity, maxDataCalls);
             }
+
+            return null;
         }
 
         private CellIdentity convertHalCellIdentityToCellIdentity(
@@ -274,7 +330,8 @@
                                 cellIdentity.cellIdentityTdscdma.get(0);
                         result = new  CellIdentityTdscdma(cellIdentityTdscdma.mcc,
                                 cellIdentityTdscdma.mnc, cellIdentityTdscdma.lac,
-                                cellIdentityTdscdma.cid, cellIdentityTdscdma.cpid);
+                                cellIdentityTdscdma.cid, cellIdentityTdscdma.cpid,
+                                Integer.MAX_VALUE, null, null);
                     }
                     break;
                 }
@@ -308,6 +365,107 @@
             return result;
         }
 
+        private CellIdentity convertHalCellIdentityToCellIdentity(
+                android.hardware.radio.V1_2.CellIdentity cellIdentity) {
+            if (cellIdentity == null) {
+                return null;
+            }
+
+            CellIdentity result = null;
+            switch(cellIdentity.cellInfoType) {
+                case CellInfoType.GSM: {
+                    if (cellIdentity.cellIdentityGsm.size() == 1) {
+                        android.hardware.radio.V1_2.CellIdentityGsm cellIdentityGsm =
+                                cellIdentity.cellIdentityGsm.get(0);
+
+                        result = new CellIdentityGsm(
+                                cellIdentityGsm.base.lac,
+                                cellIdentityGsm.base.cid,
+                                cellIdentityGsm.base.arfcn,
+                                cellIdentityGsm.base.bsic,
+                                cellIdentityGsm.base.mcc,
+                                cellIdentityGsm.base.mnc,
+                                cellIdentityGsm.operatorNames.alphaLong,
+                                cellIdentityGsm.operatorNames.alphaShort);
+                    }
+                    break;
+                }
+                case CellInfoType.WCDMA: {
+                    if (cellIdentity.cellIdentityWcdma.size() == 1) {
+                        android.hardware.radio.V1_2.CellIdentityWcdma cellIdentityWcdma =
+                                cellIdentity.cellIdentityWcdma.get(0);
+
+                        result = new CellIdentityWcdma(
+                                cellIdentityWcdma.base.lac,
+                                cellIdentityWcdma.base.cid,
+                                cellIdentityWcdma.base.psc,
+                                cellIdentityWcdma.base.uarfcn,
+                                cellIdentityWcdma.base.mcc,
+                                cellIdentityWcdma.base.mnc,
+                                cellIdentityWcdma.operatorNames.alphaLong,
+                                cellIdentityWcdma.operatorNames.alphaShort);
+                    }
+                    break;
+                }
+                case CellInfoType.TD_SCDMA: {
+                    if (cellIdentity.cellIdentityTdscdma.size() == 1) {
+                        android.hardware.radio.V1_2.CellIdentityTdscdma cellIdentityTdscdma =
+                                cellIdentity.cellIdentityTdscdma.get(0);
+
+                        result = new  CellIdentityTdscdma(
+                                cellIdentityTdscdma.base.mcc,
+                                cellIdentityTdscdma.base.mnc,
+                                cellIdentityTdscdma.base.lac,
+                                cellIdentityTdscdma.base.cid,
+                                cellIdentityTdscdma.base.cpid,
+                                cellIdentityTdscdma.uarfcn,
+                                cellIdentityTdscdma.operatorNames.alphaLong,
+                                cellIdentityTdscdma.operatorNames.alphaShort);
+                    }
+                    break;
+                }
+                case CellInfoType.LTE: {
+                    if (cellIdentity.cellIdentityLte.size() == 1) {
+                        android.hardware.radio.V1_2.CellIdentityLte cellIdentityLte =
+                                cellIdentity.cellIdentityLte.get(0);
+
+                        result = new CellIdentityLte(
+                                cellIdentityLte.base.ci,
+                                cellIdentityLte.base.pci,
+                                cellIdentityLte.base.tac,
+                                cellIdentityLte.base.earfcn,
+                                cellIdentityLte.bandwidth,
+                                cellIdentityLte.base.mcc,
+                                cellIdentityLte.base.mnc,
+                                cellIdentityLte.operatorNames.alphaLong,
+                                cellIdentityLte.operatorNames.alphaShort);
+                    }
+                    break;
+                }
+                case CellInfoType.CDMA: {
+                    if (cellIdentity.cellIdentityCdma.size() == 1) {
+                        android.hardware.radio.V1_2.CellIdentityCdma cellIdentityCdma =
+                                cellIdentity.cellIdentityCdma.get(0);
+
+                        result = new CellIdentityCdma(
+                                cellIdentityCdma.base.networkId,
+                                cellIdentityCdma.base.systemId,
+                                cellIdentityCdma.base.baseStationId,
+                                cellIdentityCdma.base.longitude,
+                                cellIdentityCdma.base.latitude,
+                                cellIdentityCdma.operatorNames.alphaLong,
+                                cellIdentityCdma.operatorNames.alphaShort);
+                    }
+                    break;
+                }
+                case CellInfoType.NONE:
+                default:
+                    break;
+            }
+
+            return result;
+        }
+
         @Override
         public void getNetworkRegistrationState(int domain, NetworkServiceCallback callback) {
             if (DBG) log("getNetworkRegistrationState for domain " + domain);
diff --git a/src/java/com/android/internal/telephony/CommandsInterface.java b/src/java/com/android/internal/telephony/CommandsInterface.java
index edf8359..9f6ce5e 100644
--- a/src/java/com/android/internal/telephony/CommandsInterface.java
+++ b/src/java/com/android/internal/telephony/CommandsInterface.java
@@ -1456,6 +1456,8 @@
      */
     void reportStkServiceIsRunning(Message result);
 
+    void invokeOemRilRequestRaw(byte[] data, Message response);
+
     /**
      * Sends carrier specific information to the vendor ril that can be used to
      * encrypt the IMSI and IMPI.
@@ -1468,6 +1470,14 @@
     void setCarrierInfoForImsiEncryption(ImsiEncryptionInfo imsiEncryptionInfo,
                                          Message response);
 
+    void invokeOemRilRequestStrings(String[] strings, Message response);
+
+    /**
+     * Fires when RIL_UNSOL_OEM_HOOK_RAW is received from the RIL.
+     */
+    void setOnUnsolOemHookRaw(Handler h, int what, Object obj);
+    void unSetOnUnsolOemHookRaw(Handler h);
+
     /**
      * Send TERMINAL RESPONSE to the SIM, after processing a proactive command
      * sent by the SIM.
@@ -2126,6 +2136,38 @@
     void setUnsolResponseFilter(int filter, Message result);
 
     /**
+     * Send the signal strength reporting criteria to the modem.
+     *
+     * @param hysteresisMs A hysteresis time in milliseconds. A value of 0 disables hysteresis.
+     * @param hysteresisDb An interval in dB defining the required magnitude change between reports.
+     *     A value of 0 disables hysteresis.
+     * @param thresholdsDbm An array of trigger thresholds in dBm. A size of 0 disables thresholds.
+     * @param ran RadioAccessNetwork for which to apply criteria.
+     * @param result callback message contains the information of SUCCESS/FAILURE
+     */
+    void setSignalStrengthReportingCriteria(int hysteresisMs, int hysteresisDb, int[] thresholdsDbm,
+            int ran, Message result);
+
+    /**
+     * Send the link capacity reporting criteria to the modem
+     *
+     * @param hysteresisMs A hysteresis time in milliseconds. A value of 0 disables hysteresis.
+     * @param hysteresisDlKbps An interval in kbps defining the required magnitude change between DL
+     *     reports. A value of 0 disables hysteresis.
+     * @param hysteresisUlKbps An interval in kbps defining the required magnitude change between UL
+     *     reports. A value of 0 disables hysteresis.
+     * @param thresholdsDlKbps An array of trigger thresholds in kbps for downlink reports. A size
+     *     of 0 disables thresholds.
+     * @param thresholdsUlKbps An array of trigger thresholds in kbps for uplink reports. A size
+     *     of 0 disables thresholds.
+     * @param ran RadioAccessNetwork for which to apply criteria.
+     * @param result callback message contains the information of SUCCESS/FAILURE
+     */
+    void setLinkCapacityReportingCriteria(int hysteresisMs, int hysteresisDlKbps,
+            int hysteresisUlKbps, int[] thresholdsDlKbps, int[] thresholdsUlKbps, int ran,
+            Message result);
+
+    /**
      * Set SIM card power up or down
      *
      * @param state  State of SIM (power down, power up, pass through)
diff --git a/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java b/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java
index 5a8a0a1..8f1f078 100644
--- a/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java
+++ b/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java
@@ -333,6 +333,15 @@
         }
     }
 
+    @Override
+    public void notifyOemHookRawEventForSubscriber(int subId, byte[] rawData) {
+        try {
+            mRegistry.notifyOemHookRawEventForSubscriber(subId, rawData);
+        } catch (RemoteException ex) {
+            // system process is dead
+        }
+    }
+
     /**
      * Convert the {@link Phone.DataActivityState} enum into the TelephonyManager.DATA_* constants
      * for the public API.
diff --git a/src/java/com/android/internal/telephony/DeviceStateMonitor.java b/src/java/com/android/internal/telephony/DeviceStateMonitor.java
index 12127aa..04e96ca 100644
--- a/src/java/com/android/internal/telephony/DeviceStateMonitor.java
+++ b/src/java/com/android/internal/telephony/DeviceStateMonitor.java
@@ -25,12 +25,14 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.hardware.display.DisplayManager;
-import android.hardware.radio.V1_0.IndicationFilter;
+import android.hardware.radio.V1_2.IndicationFilter;
 import android.net.ConnectivityManager;
 import android.os.BatteryManager;
 import android.os.Handler;
 import android.os.Message;
 import android.os.PowerManager;
+import android.telephony.AccessNetworkConstants.AccessNetworkType;
+import android.telephony.CarrierConfigManager;
 import android.telephony.Rlog;
 import android.telephony.TelephonyManager;
 import android.util.LocalLog;
@@ -63,6 +65,9 @@
     private static final int EVENT_CHARGING_STATE_CHANGED       = 4;
     private static final int EVENT_TETHERING_STATE_CHANGED      = 5;
 
+    // TODO(b/74006656) load hysteresis values from a property when DeviceStateMonitor starts
+    private static final int HYSTERESIS_KBPS = 50;
+
     private final Phone mPhone;
 
     private final LocalLog mLocalLog = new LocalLog(100);
@@ -201,18 +206,13 @@
      * @return True if low data is expected
      */
     private boolean isLowDataExpected() {
-        return mIsPowerSaveOn || (!mIsCharging && !mIsTetheringOn && !mIsScreenOn);
+        return !mIsCharging && !mIsTetheringOn && !mIsScreenOn;
     }
 
     /**
      * @return True if signal strength update should be turned off.
      */
     private boolean shouldTurnOffSignalStrength() {
-        // We don't want to get signal strength update when the device is in power save mode.
-        if (mIsPowerSaveOn) {
-            return true;
-        }
-
         // We should not turn off signal strength update if one of the following condition is true.
         // 1. The device is charging.
         // 2. When the screen is on.
@@ -234,11 +234,6 @@
      * trigger the network update unsolicited response.
      */
     private boolean shouldTurnOffFullNetworkUpdate() {
-        // We don't want to get full network update when the device is in power save mode.
-        if (mIsPowerSaveOn) {
-            return true;
-        }
-
         // We should not turn off full network update if one of the following condition is true.
         // 1. The device is charging.
         // 2. When the screen is on.
@@ -258,11 +253,6 @@
      * @return True if data dormancy status update should be turned off.
      */
     private boolean shouldTurnOffDormancyUpdate() {
-        // We don't want to get dormancy status update when the device is in power save mode.
-        if (mIsPowerSaveOn) {
-            return true;
-        }
-
         // We should not turn off data dormancy update if one of the following condition is true.
         // 1. The device is charging.
         // 2. When the screen is on.
@@ -279,6 +269,44 @@
     }
 
     /**
+     * @return True if link capacity estimate update should be turned off.
+     */
+    private boolean shouldTurnOffLinkCapacityEstimate() {
+        // We should not turn off link capacity update if one of the following condition is true.
+        // 1. The device is charging.
+        // 2. When the screen is on.
+        // 3. When data tethering is on.
+        // 4. When the update mode is IGNORE_SCREEN_OFF.
+        if (mIsCharging || mIsScreenOn || mIsTetheringOn
+                || mUpdateModes.get(TelephonyManager.INDICATION_FILTER_LINK_CAPACITY_ESTIMATE)
+                == TelephonyManager.INDICATION_UPDATE_MODE_IGNORE_SCREEN_OFF) {
+            return false;
+        }
+
+        // In all other cases, we turn off link capacity update.
+        return true;
+    }
+
+    /**
+     * @return True if physical channel config update should be turned off.
+     */
+    private boolean shouldTurnOffPhysicalChannelConfig() {
+        // We should not turn off physical channel update if one of the following condition is true.
+        // 1. The device is charging.
+        // 2. When the screen is on.
+        // 3. When data tethering is on.
+        // 4. When the update mode is IGNORE_SCREEN_OFF.
+        if (mIsCharging || mIsScreenOn || mIsTetheringOn
+                || mUpdateModes.get(TelephonyManager.INDICATION_FILTER_PHYSICAL_CHANNEL_CONFIG)
+                == TelephonyManager.INDICATION_UPDATE_MODE_IGNORE_SCREEN_OFF) {
+            return false;
+        }
+
+        // In all other cases, we turn off physical channel config update.
+        return true;
+    }
+
+    /**
      * Set indication update mode
      *
      * @param filters Indication filters. Should be a bitmask of INDICATION_FILTER_XXX.
@@ -298,6 +326,12 @@
         if ((filters & TelephonyManager.INDICATION_FILTER_DATA_CALL_DORMANCY_CHANGED) != 0) {
             mUpdateModes.put(TelephonyManager.INDICATION_FILTER_DATA_CALL_DORMANCY_CHANGED, mode);
         }
+        if ((filters & TelephonyManager.INDICATION_FILTER_LINK_CAPACITY_ESTIMATE) != 0) {
+            mUpdateModes.put(TelephonyManager.INDICATION_FILTER_LINK_CAPACITY_ESTIMATE, mode);
+        }
+        if ((filters & TelephonyManager.INDICATION_FILTER_PHYSICAL_CHANNEL_CONFIG) != 0) {
+            mUpdateModes.put(TelephonyManager.INDICATION_FILTER_PHYSICAL_CHANNEL_CONFIG, mode);
+        }
     }
 
     /**
@@ -374,6 +408,14 @@
             newFilter |= IndicationFilter.DATA_CALL_DORMANCY_CHANGED;
         }
 
+        if (!shouldTurnOffLinkCapacityEstimate()) {
+            newFilter |= IndicationFilter.LINK_CAPACITY_ESTIMATE;
+        }
+
+        if (!shouldTurnOffPhysicalChannelConfig()) {
+            newFilter |= IndicationFilter.PHYSICAL_CHANNEL_CONFIG;
+        }
+
         setUnsolResponseFilter(newFilter, false);
     }
 
@@ -390,6 +432,8 @@
         sendDeviceState(LOW_DATA_EXPECTED, mIsLowDataExpected);
         sendDeviceState(POWER_SAVE_MODE, mIsPowerSaveOn);
         setUnsolResponseFilter(mUnsolicitedResponseFilter, true);
+        setSignalStrengthReportingCriteria();
+        setLinkCapacityReportingCriteria();
     }
 
     /**
@@ -432,6 +476,28 @@
         }
     }
 
+    private void setSignalStrengthReportingCriteria() {
+        mPhone.setSignalStrengthReportingCriteria(
+                AccessNetworkThresholds.GERAN, AccessNetworkType.GERAN);
+        mPhone.setSignalStrengthReportingCriteria(
+                AccessNetworkThresholds.UTRAN, AccessNetworkType.UTRAN);
+        mPhone.setSignalStrengthReportingCriteria(
+                AccessNetworkThresholds.EUTRAN, AccessNetworkType.EUTRAN);
+        mPhone.setSignalStrengthReportingCriteria(
+                AccessNetworkThresholds.CDMA2000, AccessNetworkType.CDMA2000);
+    }
+
+    private void setLinkCapacityReportingCriteria() {
+        mPhone.setLinkCapacityReportingCriteria(LINK_CAPACITY_DOWNLINK_THRESHOLDS,
+                LINK_CAPACITY_UPLINK_THRESHOLDS, AccessNetworkType.GERAN);
+        mPhone.setLinkCapacityReportingCriteria(LINK_CAPACITY_DOWNLINK_THRESHOLDS,
+                LINK_CAPACITY_UPLINK_THRESHOLDS, AccessNetworkType.UTRAN);
+        mPhone.setLinkCapacityReportingCriteria(LINK_CAPACITY_DOWNLINK_THRESHOLDS,
+                LINK_CAPACITY_UPLINK_THRESHOLDS, AccessNetworkType.EUTRAN);
+        mPhone.setLinkCapacityReportingCriteria(LINK_CAPACITY_DOWNLINK_THRESHOLDS,
+                LINK_CAPACITY_UPLINK_THRESHOLDS, AccessNetworkType.CDMA2000);
+    }
+
     /**
      * @return True if the device is currently in power save mode.
      * See {@link android.os.BatteryManager#isPowerSaveMode BatteryManager.isPowerSaveMode()}.
@@ -517,4 +583,85 @@
         ipw.decreaseIndent();
         ipw.flush();
     }
+
+    /**
+     * dBm thresholds that correspond to changes in signal strength indications.
+     */
+    private static final class AccessNetworkThresholds {
+
+        /**
+         * List of dBm thresholds for GERAN {@link AccessNetworkType}.
+         *
+         * Calculated from GSM asu level thresholds - TS 27.007 Sec 8.5
+         */
+        public static final int[] GERAN = new int[] {
+            -109,
+            -103,
+            -97,
+            -89,
+        };
+
+        /**
+         * List of default dBm thresholds for UTRAN {@link AccessNetworkType}.
+         *
+         * These thresholds are taken from the WCDMA RSCP defaults in {@link CarrierConfigManager}.
+         * See TS 27.007 Sec 8.69.
+         */
+        public static final int[] UTRAN = new int[] {
+            -114, /* SIGNAL_STRENGTH_POOR */
+            -104, /* SIGNAL_STRENGTH_MODERATE */
+            -94,  /* SIGNAL_STRENGTH_GOOD */
+            -84   /* SIGNAL_STRENGTH_GREAT */
+        };
+
+        /**
+         * List of default dBm thresholds for EUTRAN {@link AccessNetworkType}.
+         *
+         * These thresholds are taken from the LTE RSRP defaults in {@link CarrierConfigManager}.
+         */
+        public static final int[] EUTRAN = new int[] {
+            -140, /* SIGNAL_STRENGTH_NONE_OR_UNKNOWN */
+            -128, /* SIGNAL_STRENGTH_POOR */
+            -118, /* SIGNAL_STRENGTH_MODERATE */
+            -108, /* SIGNAL_STRENGTH_GOOD */
+            -98,  /* SIGNAL_STRENGTH_GREAT */
+            -44   /* SIGNAL_STRENGTH_NONE_OR_UNKNOWN */
+        };
+
+        /**
+         * List of dBm thresholds for CDMA2000 {@link AccessNetworkType}.
+         *
+         * These correspond to EVDO level thresholds.
+         */
+        public static final int[] CDMA2000 = new int[] {
+            -105,
+            -90,
+            -75,
+            -65
+        };
+    }
+
+    /**
+     * Downlink reporting thresholds in kbps
+     *
+     * <p>Threshold values taken from FCC Speed Guide
+     * (https://www.fcc.gov/reports-research/guides/broadband-speed-guide) and Android WiFi speed
+     * labels (https://support.google.com/pixelphone/answer/2819519#strength_speed).
+     */
+    private static final int[] LINK_CAPACITY_DOWNLINK_THRESHOLDS = new int[] {
+            500,   // Web browsing
+            1000,  // SD video streaming
+            5000,  // HD video streaming
+            10000, // file downloading
+            20000, // 4K video streaming
+    };
+
+    /** Uplink reporting thresholds in kbps */
+    private static final int[] LINK_CAPACITY_UPLINK_THRESHOLDS = new int[] {
+            100,   // VoIP calls
+            500,
+            1000,
+            5000,
+            10000,
+    };
 }
diff --git a/src/java/com/android/internal/telephony/ExponentialBackoff.java b/src/java/com/android/internal/telephony/ExponentialBackoff.java
deleted file mode 100644
index 80958c0..0000000
--- a/src/java/com/android/internal/telephony/ExponentialBackoff.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.telephony;
-
-import android.annotation.NonNull;
-import android.os.Handler;
-import android.os.Looper;
-
-/** The implementation of exponential backoff with jitter applied. */
-public class ExponentialBackoff {
-    private int mRetryCounter;
-    private long mStartDelayMs;
-    private long mMaximumDelayMs;
-    private long mCurrentDelayMs;
-    private int mMultiplier;
-    private Runnable mRunnable;
-    private Handler mHandler;
-
-    public ExponentialBackoff(
-            long initialDelayMs,
-            long maximumDelayMs,
-            int multiplier,
-            @NonNull Looper looper,
-            @NonNull Runnable runnable) {
-        this(initialDelayMs, maximumDelayMs, multiplier, new Handler(looper), runnable);
-    }
-
-    public ExponentialBackoff(
-            long initialDelayMs,
-            long maximumDelayMs,
-            int multiplier,
-            @NonNull Handler handler,
-            @NonNull Runnable runnable) {
-        mRetryCounter = 0;
-        mStartDelayMs = initialDelayMs;
-        mMaximumDelayMs = maximumDelayMs;
-        mMultiplier = multiplier;
-        mHandler = handler;
-        mRunnable = runnable;
-    }
-
-    /** Starts the backoff, the runnable will be executed after {@link #mStartDelayMs}. */
-    public void start() {
-        mRetryCounter = 0;
-        mCurrentDelayMs = mStartDelayMs;
-        mHandler.removeCallbacks(mRunnable);
-        mHandler.postDelayed(mRunnable, mCurrentDelayMs);
-    }
-
-    /** Stops the backoff, all pending messages will be removed from the message queue. */
-    public void stop() {
-        mRetryCounter = 0;
-        mHandler.removeCallbacks(mRunnable);
-    }
-
-    /** Should call when the retry action has failed and we want to retry after a longer delay. */
-    public void notifyFailed() {
-        mRetryCounter++;
-        long temp = Math.min(
-                mMaximumDelayMs, (long) (mStartDelayMs * Math.pow(mMultiplier, mRetryCounter)));
-        mCurrentDelayMs = (long) (((1 + Math.random()) / 2) * temp);
-        mHandler.removeCallbacks(mRunnable);
-        mHandler.postDelayed(mRunnable, mCurrentDelayMs);
-    }
-
-    /** Returns the delay for the most recently posted message. */
-    public long getCurrentDelay() {
-        return mCurrentDelayMs;
-    }
-}
diff --git a/src/java/com/android/internal/telephony/GsmCdmaPhone.java b/src/java/com/android/internal/telephony/GsmCdmaPhone.java
index bdad00e..5dd36ad 100644
--- a/src/java/com/android/internal/telephony/GsmCdmaPhone.java
+++ b/src/java/com/android/internal/telephony/GsmCdmaPhone.java
@@ -89,6 +89,7 @@
 import com.android.internal.telephony.uicc.UiccCardApplication;
 import com.android.internal.telephony.uicc.UiccController;
 import com.android.internal.telephony.uicc.UiccProfile;
+import com.android.internal.telephony.uicc.UiccSlot;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -108,6 +109,13 @@
     private static final boolean DBG = true;
     private static final boolean VDBG = false; /* STOPSHIP if true */
 
+    /** Required magnitude change between unsolicited SignalStrength reports. */
+    private static final int REPORTING_HYSTERESIS_DB = 2;
+    /** Required throughput change between unsolicited LinkCapacityEstimate reports. */
+    private static final int REPORTING_HYSTERESIS_KBPS = 50;
+    /** Minimum time between unsolicited SignalStrength and LinkCapacityEstimate reports. */
+    private static final int REPORTING_HYSTERESIS_MILLIS = 3000;
+
     //GSM
     // Key used to read/write voice mail number
     private static final String VM_NUMBER = "vm_number_key";
@@ -190,6 +198,8 @@
     private int mRilVersion;
     private boolean mBroadcastEmergencyCallStateChanges = false;
     private CarrierKeyDownloadManager mCDM;
+    private CarrierInfoManager mCIM;
+
     // Constructors
 
     public GsmCdmaPhone(Context context, CommandsInterface ci, PhoneNotifier notifier, int phoneId,
@@ -273,6 +283,7 @@
         mContext.registerReceiver(mBroadcastReceiver, new IntentFilter(
                 CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED));
         mCDM = new CarrierKeyDownloadManager(this);
+        mCIM = new CarrierInfoManager();
     }
 
     private void initRatSpecific(int precisePhoneType) {
@@ -327,7 +338,7 @@
                 setIsoCountryProperty(operatorNumeric);
                 // Updates MCC MNC device configuration information
                 logd("update mccmnc=" + operatorNumeric);
-                MccTable.updateMccMncConfiguration(mContext, operatorNumeric, false);
+                MccTable.updateMccMncConfiguration(mContext, operatorNumeric);
             }
 
             // Sets current entry in the telephony carrier table
@@ -348,10 +359,7 @@
         } else {
             String iso = "";
             try {
-                iso = MccTable.countryCodeForMcc(Integer.parseInt(
-                        operatorNumeric.substring(0,3)));
-            } catch (NumberFormatException ex) {
-                Rlog.e(LOG_TAG, "setIsoCountryProperty: countryCodeForMcc error", ex);
+                iso = MccTable.countryCodeForMcc(operatorNumeric.substring(0, 3));
             } catch (StringIndexOutOfBoundsException ex) {
                 Rlog.e(LOG_TAG, "setIsoCountryProperty: countryCodeForMcc error", ex);
             }
@@ -522,12 +530,6 @@
             ret = PhoneConstants.DataState.DISCONNECTED;
         } else { /* mSST.gprsState == ServiceState.STATE_IN_SERVICE */
             switch (mDcTracker.getState(apnType)) {
-                case RETRYING:
-                case FAILED:
-                case IDLE:
-                    ret = PhoneConstants.DataState.DISCONNECTED;
-                break;
-
                 case CONNECTED:
                 case DISCONNECTING:
                     if ( mCT.mState != PhoneConstants.State.IDLE
@@ -536,12 +538,12 @@
                     } else {
                         ret = PhoneConstants.DataState.CONNECTED;
                     }
-                break;
-
+                    break;
                 case CONNECTING:
-                case SCANNING:
                     ret = PhoneConstants.DataState.CONNECTING;
-                break;
+                    break;
+                default:
+                    ret = PhoneConstants.DataState.DISCONNECTED;
             }
         }
 
@@ -1112,7 +1114,8 @@
         // Check non-emergency voice CS call - shouldn't dial when POWER_OFF
         if (mSST != null && mSST.mSS.getState() == ServiceState.STATE_POWER_OFF /* CS POWER_OFF */
                 && !VideoProfile.isVideo(dialArgs.videoState) /* voice call */
-                && !isEmergency /* non-emergency call */) {
+                && !isEmergency /* non-emergency call */
+                && !(isUt && useImsForUt) /* not UT */) {
             throw new CallStateException(
                 CallStateException.ERROR_POWER_OFF,
                 "cannot dial voice call in airplane mode");
@@ -1533,6 +1536,32 @@
     }
 
     @Override
+    public void resetCarrierKeysForImsiEncryption() {
+        mCIM.resetCarrierKeysForImsiEncryption(mContext, mPhoneId);
+    }
+
+    @Override
+    public int getCarrierIdListVersion() {
+        return mCarrerIdentifier.getCarrierListVersion();
+    }
+
+    @Override
+    public void setCarrierTestOverride(String mccmnc, String imsi, String iccid, String gid1,
+            String gid2, String pnn, String spn) {
+        IccRecords r = null;
+        if (isPhoneTypeGsm()) {
+            r = mIccRecords.get();
+        } else if (isPhoneTypeCdmaLte()) {
+            r = mSimRecords;
+        } else {
+            loge("setCarrierTestOverride fails in CDMA only");
+        }
+        if (r != null) {
+            r.setCarrierTestOverride(mccmnc, imsi, iccid, gid1, gid2, pnn, spn);
+        }
+    }
+
+    @Override
     public String getGroupIdLevel1() {
         if (isPhoneTypeGsm()) {
             IccRecords r = mIccRecords.get();
@@ -1641,7 +1670,13 @@
         Message resp;
         mVmNumber = voiceMailNumber;
         resp = obtainMessage(EVENT_SET_VM_NUMBER_DONE, 0, 0, onComplete);
+
         IccRecords r = mIccRecords.get();
+
+        if (!isPhoneTypeGsm() && mSimRecords != null) {
+            r = mSimRecords;
+        }
+
         if (r != null) {
             r.setVoiceMailNumber(alphaTag, mVmNumber, resp);
         }
@@ -1704,7 +1739,8 @@
                 } else {
                     resp = onComplete;
                 }
-                mCi.queryCallForwardStatus(commandInterfaceCFReason, 0, null, resp);
+                mCi.queryCallForwardStatus(commandInterfaceCFReason,
+                        CommandsInterface.SERVICE_CLASS_VOICE, null, resp);
             }
         } else {
             loge("getCallForwardingOption: not possible in CDMA");
@@ -2672,7 +2708,7 @@
 
                     // Updates MCC MNC device configuration information
                     logd("update mccmnc=" + operatorNumeric);
-                    MccTable.updateMccMncConfiguration(mContext, operatorNumeric, false);
+                    MccTable.updateMccMncConfiguration(mContext, operatorNumeric);
 
                     return true;
                 } catch (SQLException e) {
@@ -3347,6 +3383,18 @@
     }
 
     @Override
+    public void setSignalStrengthReportingCriteria(int[] thresholds, int ran) {
+        mCi.setSignalStrengthReportingCriteria(REPORTING_HYSTERESIS_MILLIS, REPORTING_HYSTERESIS_DB,
+                thresholds, ran, null);
+    }
+
+    @Override
+    public void setLinkCapacityReportingCriteria(int[] dlThresholds, int[] ulThresholds, int ran) {
+        mCi.setLinkCapacityReportingCriteria(REPORTING_HYSTERESIS_MILLIS, REPORTING_HYSTERESIS_KBPS,
+                REPORTING_HYSTERESIS_KBPS, dlThresholds, ulThresholds, ran, null);
+    }
+
+    @Override
     public IccSmsInterfaceManager getIccSmsInterfaceManager(){
         return mIccSmsInterfaceManager;
     }
@@ -3370,7 +3418,20 @@
 
     @Override
     public IccCard getIccCard() {
-        return UiccController.getInstance().getUiccProfileForPhone(mPhoneId);
+        // This function doesn't return null for backwards compatability purposes.
+        // To differentiate between cases where SIM is absent vs. unknown we return a dummy
+        // IccCard with the sim state set.
+        IccCard card = getUiccProfile();
+        if (card != null) {
+            return card;
+        } else {
+            UiccSlot slot = mUiccController.getUiccSlotForPhone(mPhoneId);
+            if (slot == null || slot.isStateUnknown()) {
+                return new IccCard(IccCardConstants.State.UNKNOWN);
+            } else {
+                return new IccCard(IccCardConstants.State.ABSENT);
+            }
+        }
     }
 
     private UiccProfile getUiccProfile() {
diff --git a/src/java/com/android/internal/telephony/IccCard.java b/src/java/com/android/internal/telephony/IccCard.java
index 7e98bc9..7bab408 100644
--- a/src/java/com/android/internal/telephony/IccCard.java
+++ b/src/java/com/android/internal/telephony/IccCard.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.telephony;
 
+import android.os.AsyncResult;
 import android.os.Handler;
 import android.os.Message;
 
@@ -34,26 +35,51 @@
  * Apps (those that have access to Phone object) can retrieve this object
  * by calling phone.getIccCard()
  *
- * This interface is implemented by IccCardProxy and the object PhoneApp
- * gets when it calls getIccCard is IccCardProxy.
+ * This interface is implemented by UiccProfile and the object PhoneApp
+ * gets when it calls getIccCard is UiccProfile.
  */
-public interface IccCard {
+public class IccCard {
+    private State mIccCardState = State.UNKNOWN;
+
+    /**
+     * Empty constructor.
+     */
+    public IccCard() {}
+
+    /**
+     * Set the state of the IccCard to be returned in {@link getState}.
+     */
+    public IccCard(State state) {
+        mIccCardState = state;
+    }
+
     /**
      * @return combined Card and current App state
      */
-    public State getState();
+    public State getState() {
+        return mIccCardState;
+    }
 
     // todo: delete
     /**
      * @return IccRecords object belonging to current UiccCardApplication
      */
-    public IccRecords getIccRecords();
+    public IccRecords getIccRecords() {
+        return null;
+    }
 
     /**
      * Notifies handler of any transition into IccCardConstants.State.NETWORK_LOCKED
      */
-    public void registerForNetworkLocked(Handler h, int what, Object obj);
-    public void unregisterForNetworkLocked(Handler h);
+    public void registerForNetworkLocked(Handler h, int what, Object obj) {
+        return;
+    }
+    /**
+     * Unregister for networkLocked state change.
+     */
+    public void unregisterForNetworkLocked(Handler h) {
+        return;
+    }
 
     /**
      * Supply the ICC PIN to the ICC
@@ -73,27 +99,37 @@
      * && ((CommandException)(((AsyncResult)onComplete.obj).exception))
      *          .getCommandError() == CommandException.Error.PASSWORD_INCORRECT
      */
-    public void supplyPin (String pin, Message onComplete);
+    public void supplyPin(String pin, Message onComplete) {
+        sendMessageWithCardAbsentException(onComplete);
+    }
 
     /**
      * Supply the ICC PUK to the ICC
      */
-    public void supplyPuk (String puk, String newPin, Message onComplete);
+    public void supplyPuk(String puk, String newPin, Message onComplete) {
+        sendMessageWithCardAbsentException(onComplete);
+    }
 
     /**
      * Supply the ICC PIN2 to the ICC
      */
-    public void supplyPin2 (String pin2, Message onComplete);
+    public void supplyPin2(String pin2, Message onComplete) {
+        sendMessageWithCardAbsentException(onComplete);
+    }
 
     /**
      * Supply the ICC PUK2 to the ICC
      */
-    public void supplyPuk2 (String puk2, String newPin2, Message onComplete);
+    public void supplyPuk2(String puk2, String newPin2, Message onComplete) {
+        sendMessageWithCardAbsentException(onComplete);
+    }
 
     /**
      * Supply Network depersonalization code to the RIL
      */
-    public void supplyNetworkDepersonalization (String pin, Message onComplete);
+    public void supplyNetworkDepersonalization(String pin, Message onComplete) {
+        sendMessageWithCardAbsentException(onComplete);
+    }
 
     /**
      * Check whether ICC pin lock is enabled
@@ -102,7 +138,18 @@
      * @return true for ICC locked enabled
      *         false for ICC locked disabled
      */
-    public boolean getIccLockEnabled();
+    public boolean getIccLockEnabled() {
+        return false;
+    }
+
+    /**
+     * Check whether fdn (fixed dialing number) service is available.
+     * @return true if ICC fdn service available
+     *         false if ICC fdn service not available
+     */
+    public boolean getIccFdnAvailable() {
+        return false;
+    }
 
     /**
      * Check whether ICC fdn (fixed dialing number) is enabled
@@ -111,7 +158,9 @@
      * @return true for ICC fdn enabled
      *         false for ICC fdn disabled
      */
-    public boolean getIccFdnEnabled();
+    public boolean getIccFdnEnabled() {
+        return false;
+    }
 
      /**
       * Set the ICC pin lock enabled or disabled
@@ -124,8 +173,10 @@
       *        ((AsyncResult)onComplete.obj).exception == null on success
       *        ((AsyncResult)onComplete.obj).exception != null on fail
       */
-     public void setIccLockEnabled (boolean enabled,
-             String password, Message onComplete);
+     public void setIccLockEnabled(boolean enabled,
+             String password, Message onComplete) {
+         sendMessageWithCardAbsentException(onComplete);
+     }
 
      /**
       * Set the ICC fdn enabled or disabled
@@ -138,8 +189,10 @@
       *        ((AsyncResult)onComplete.obj).exception == null on success
       *        ((AsyncResult)onComplete.obj).exception != null on fail
       */
-     public void setIccFdnEnabled (boolean enabled,
-             String password, Message onComplete);
+     public void setIccFdnEnabled(boolean enabled,
+             String password, Message onComplete) {
+         sendMessageWithCardAbsentException(onComplete);
+     }
 
      /**
       * Change the ICC password used in ICC pin lock
@@ -153,7 +206,9 @@
       *        ((AsyncResult)onComplete.obj).exception != null on fail
       */
      public void changeIccLockPassword(String oldPassword, String newPassword,
-             Message onComplete);
+             Message onComplete) {
+         sendMessageWithCardAbsentException(onComplete);
+     }
 
      /**
       * Change the ICC password used in ICC fdn enable
@@ -167,7 +222,9 @@
       *        ((AsyncResult)onComplete.obj).exception != null on fail
       */
      public void changeIccFdnPassword(String oldPassword, String newPassword,
-             Message onComplete);
+             Message onComplete) {
+         sendMessageWithCardAbsentException(onComplete);
+     }
 
     /**
      * Returns service provider name stored in ICC card.
@@ -185,26 +242,42 @@
      *         yet available
      *
      */
-    public String getServiceProviderName ();
+    public String getServiceProviderName() {
+        return null;
+    }
 
     /**
      * Checks if an Application of specified type present on the card
      * @param type is AppType to look for
      */
-    public boolean isApplicationOnIcc(IccCardApplicationStatus.AppType type);
+    public boolean isApplicationOnIcc(IccCardApplicationStatus.AppType type) {
+        return false;
+    }
 
     /**
      * @return true if a ICC card is present
      */
-    public boolean hasIccCard();
+    public boolean hasIccCard() {
+        return false;
+    }
 
     /**
      * @return true if ICC card is PIN2 blocked
      */
-    public boolean getIccPin2Blocked();
+    public boolean getIccPin2Blocked() {
+        return false;
+    }
 
     /**
      * @return true if ICC card is PUK2 blocked
      */
-    public boolean getIccPuk2Blocked();
+    public boolean getIccPuk2Blocked() {
+        return false;
+    }
+
+    private void sendMessageWithCardAbsentException(Message onComplete) {
+        AsyncResult ret = AsyncResult.forMessage(onComplete);
+        ret.exception = new RuntimeException("No valid IccCard");
+        onComplete.sendToTarget();
+    }
 }
diff --git a/src/java/com/android/internal/telephony/IccSmsInterfaceManager.java b/src/java/com/android/internal/telephony/IccSmsInterfaceManager.java
index 81050f2..d7a45d8 100644
--- a/src/java/com/android/internal/telephony/IccSmsInterfaceManager.java
+++ b/src/java/com/android/internal/telephony/IccSmsInterfaceManager.java
@@ -40,17 +40,19 @@
 import android.telephony.Rlog;
 import android.telephony.SmsManager;
 import android.telephony.SmsMessage;
-import android.telephony.TelephonyManager;
+import android.util.LocalLog;
 import android.util.Log;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.cdma.CdmaSmsBroadcastConfigInfo;
 import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
 import com.android.internal.telephony.uicc.IccConstants;
 import com.android.internal.telephony.uicc.IccFileHandler;
 import com.android.internal.telephony.uicc.IccUtils;
-import com.android.internal.telephony.uicc.UiccController;
 import com.android.internal.util.HexDump;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -87,6 +89,8 @@
     final private UserManager mUserManager;
     protected SmsDispatchersController mDispatchersController;
 
+    private final LocalLog mCellBroadcastLocalLog = new LocalLog(100);
+
     protected Handler mHandler = new Handler() {
         @Override
         public void handleMessage(Message msg) {
@@ -129,12 +133,22 @@
     };
 
     protected IccSmsInterfaceManager(Phone phone) {
+        this(phone, phone.getContext(),
+                (AppOpsManager) phone.getContext().getSystemService(Context.APP_OPS_SERVICE),
+                (UserManager) phone.getContext().getSystemService(Context.USER_SERVICE),
+                new SmsDispatchersController(
+                        phone, phone.mSmsStorageMonitor, phone.mSmsUsageMonitor));
+    }
+
+    @VisibleForTesting
+    public IccSmsInterfaceManager(
+            Phone phone, Context context, AppOpsManager appOps, UserManager userManager,
+            SmsDispatchersController dispatchersController) {
         mPhone = phone;
-        mContext = phone.getContext();
-        mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
-        mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
-        mDispatchersController = new SmsDispatchersController(phone,
-                phone.mSmsStorageMonitor, phone.mSmsUsageMonitor);
+        mContext = context;
+        mAppOps = appOps;
+        mUserManager = userManager;
+        mDispatchersController = dispatchersController;
     }
 
     protected void markMessagesAsRead(ArrayList<byte[]> messages) {
@@ -323,11 +337,10 @@
      */
     public void sendDataWithSelfPermissions(String callingPackage, String destAddr, String scAddr,
             int destPort, byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) {
-        mPhone.getContext().enforceCallingOrSelfPermission(
-                Manifest.permission.SEND_SMS,
-                "Sending SMS message");
-        sendDataInternal(callingPackage, destAddr, scAddr, destPort, data, sentIntent,
-                deliveryIntent);
+        if (!checkCallingOrSelfSendSmsPermission(callingPackage, "Sending SMS message")) {
+            return;
+        }
+        sendDataInternal(destAddr, scAddr, destPort, data, sentIntent, deliveryIntent);
     }
 
     /**
@@ -336,11 +349,10 @@
      */
     public void sendData(String callingPackage, String destAddr, String scAddr, int destPort,
             byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) {
-        mPhone.getContext().enforceCallingPermission(
-                Manifest.permission.SEND_SMS,
-                "Sending SMS message");
-        sendDataInternal(callingPackage, destAddr, scAddr, destPort, data, sentIntent,
-                deliveryIntent);
+        if (!checkCallingSendSmsPermission(callingPackage, "Sending SMS message")) {
+            return;
+        }
+        sendDataInternal(destAddr, scAddr, destPort, data, sentIntent, deliveryIntent);
     }
 
     /**
@@ -369,17 +381,13 @@
      *  raw pdu of the status report is in the extended data ("pdu").
      */
 
-    private void sendDataInternal(String callingPackage, String destAddr, String scAddr,
+    private void sendDataInternal(String destAddr, String scAddr,
             int destPort, byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) {
         if (Rlog.isLoggable("SMS", Log.VERBOSE)) {
             log("sendData: destAddr=" + destAddr + " scAddr=" + scAddr + " destPort=" +
                 destPort + " data='"+ HexDump.toHexString(data)  + "' sentIntent=" +
                 sentIntent + " deliveryIntent=" + deliveryIntent);
         }
-        if (mAppOps.noteOp(AppOpsManager.OP_SEND_SMS, Binder.getCallingUid(),
-                callingPackage) != AppOpsManager.MODE_ALLOWED) {
-            return;
-        }
         destAddr = filterDestAddress(destAddr);
         mDispatchersController.sendData(destAddr, scAddr, destPort, data, sentIntent,
                 deliveryIntent);
@@ -392,9 +400,10 @@
     public void sendText(String callingPackage, String destAddr, String scAddr,
             String text, PendingIntent sentIntent, PendingIntent deliveryIntent,
             boolean persistMessageForNonDefaultSmsApp) {
-        mPhone.getContext().enforceCallingPermission(
-                Manifest.permission.SEND_SMS,
-                "Sending SMS message");
+        if (!checkCallingSendTextPermissions(
+                persistMessageForNonDefaultSmsApp, callingPackage, "Sending SMS message")) {
+            return;
+        }
         sendTextInternal(callingPackage, destAddr, scAddr, text, sentIntent, deliveryIntent,
             persistMessageForNonDefaultSmsApp, SMS_MESSAGE_PRIORITY_NOT_SPECIFIED,
             false /* expectMore */, SMS_MESSAGE_PERIOD_NOT_SPECIFIED);
@@ -407,9 +416,9 @@
     public void sendTextWithSelfPermissions(String callingPackage, String destAddr, String scAddr,
             String text, PendingIntent sentIntent, PendingIntent deliveryIntent,
             boolean persistMessage) {
-        mPhone.getContext().enforceCallingOrSelfPermission(
-                Manifest.permission.SEND_SMS,
-                "Sending SMS message");
+        if (!checkCallingOrSelfSendSmsPermission(callingPackage, "Sending SMS message")) {
+            return;
+        }
         sendTextInternal(callingPackage, destAddr, scAddr, text, sentIntent, deliveryIntent,
             persistMessage, SMS_MESSAGE_PRIORITY_NOT_SPECIFIED, false /* expectMore */,
             SMS_MESSAGE_PERIOD_NOT_SPECIFIED);
@@ -472,13 +481,6 @@
                 + " priority=" + priority + " expectMore=" + expectMore
                 + " validityPeriod=" + validityPeriod);
         }
-        if (mAppOps.noteOp(AppOpsManager.OP_SEND_SMS, Binder.getCallingUid(),
-                callingPackage) != AppOpsManager.MODE_ALLOWED) {
-            return;
-        }
-        if (!persistMessageForNonDefaultSmsApp) {
-            enforcePrivilegedAppPermissions();
-        }
         destAddr = filterDestAddress(destAddr);
         mDispatchersController.sendText(destAddr, scAddr, text, sentIntent, deliveryIntent,
                 null/*messageUri*/, callingPackage, persistMessageForNonDefaultSmsApp,
@@ -535,9 +537,9 @@
             String text, PendingIntent sentIntent, PendingIntent deliveryIntent,
             boolean persistMessageForNonDefaultSmsApp, int priority, boolean expectMore,
             int validityPeriod) {
-        mPhone.getContext().enforceCallingOrSelfPermission(
-                Manifest.permission.SEND_SMS,
-                "Sending SMS message");
+        if (!checkCallingOrSelfSendSmsPermission(callingPackage, "Sending SMS message")) {
+            return;
+        }
         sendTextInternal(callingPackage, destAddr, scAddr, text, sentIntent, deliveryIntent,
                 persistMessageForNonDefaultSmsApp, priority, expectMore, validityPeriod);
     }
@@ -553,7 +555,11 @@
      *  the same time an SMS received from radio is acknowledged back.
      */
     public void injectSmsPdu(byte[] pdu, String format, PendingIntent receivedIntent) {
-        enforcePrivilegedAppPermissions();
+        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+                != PackageManager.PERMISSION_GRANTED) {
+            enforceCallerIsImsAppOrCarrierApp("injectSmsPdu");
+        }
+
         if (Rlog.isLoggable("SMS", Log.VERBOSE)) {
             log("pdu: " + pdu +
                 "\n format=" + format +
@@ -658,12 +664,9 @@
             String scAddr, List<String> parts, List<PendingIntent> sentIntents,
             List<PendingIntent> deliveryIntents, boolean persistMessageForNonDefaultSmsApp,
             int priority, boolean expectMore, int validityPeriod) {
-        mPhone.getContext().enforceCallingPermission(
-                Manifest.permission.SEND_SMS,
-                "Sending SMS message");
-        if (!persistMessageForNonDefaultSmsApp) {
-            // Only allow carrier app or carrier ims to skip auto message persistence.
-            enforcePrivilegedAppPermissions();
+        if (!checkCallingSendTextPermissions(
+                persistMessageForNonDefaultSmsApp, callingPackage, "Sending SMS message")) {
+            return;
         }
         if (Rlog.isLoggable("SMS", Log.VERBOSE)) {
             int i = 0;
@@ -672,10 +675,6 @@
                         ", part[" + (i++) + "]=" + part);
             }
         }
-        if (mAppOps.noteOp(AppOpsManager.OP_SEND_SMS, Binder.getCallingUid(),
-                callingPackage) != AppOpsManager.MODE_ALLOWED) {
-            return;
-        }
 
         destAddr = filterDestAddress(destAddr);
 
@@ -794,7 +793,7 @@
         } else if (ranType == SmsManager.CELL_BROADCAST_RAN_TYPE_CDMA) {
             return enableCdmaBroadcastRange(startMessageId, endMessageId);
         } else {
-            throw new IllegalArgumentException("Not a supportted RAN Type");
+            throw new IllegalArgumentException("Not a supported RAN Type");
         }
     }
 
@@ -804,30 +803,34 @@
         } else if (ranType == SmsManager.CELL_BROADCAST_RAN_TYPE_CDMA)  {
             return disableCdmaBroadcastRange(startMessageId, endMessageId);
         } else {
-            throw new IllegalArgumentException("Not a supportted RAN Type");
+            throw new IllegalArgumentException("Not a supported RAN Type");
         }
     }
 
     synchronized public boolean enableGsmBroadcastRange(int startMessageId, int endMessageId) {
 
-        Context context = mPhone.getContext();
-
-        context.enforceCallingPermission(
+        mContext.enforceCallingPermission(
                 "android.permission.RECEIVE_SMS",
                 "Enabling cell broadcast SMS");
 
-        String client = context.getPackageManager().getNameForUid(
+        String client = mContext.getPackageManager().getNameForUid(
                 Binder.getCallingUid());
 
+        String msg;
         if (!mCellBroadcastRangeManager.enableRange(startMessageId, endMessageId, client)) {
-            log("Failed to add GSM cell broadcast subscription for MID range " + startMessageId
-                    + " to " + endMessageId + " from client " + client);
+            msg = "Failed to add GSM cell broadcast channels range " + startMessageId
+                    + " to " + endMessageId;
+            log(msg);
+            mCellBroadcastLocalLog.log(msg);
             return false;
         }
 
-        if (DBG)
-            log("Added GSM cell broadcast subscription for MID range " + startMessageId
-                    + " to " + endMessageId + " from client " + client);
+        if (DBG) {
+            msg = "Added GSM cell broadcast channels range " + startMessageId
+                    + " to " + endMessageId;
+            log(msg);
+            mCellBroadcastLocalLog.log(msg);
+        }
 
         setCellBroadcastActivation(!mCellBroadcastRangeManager.isEmpty());
 
@@ -836,24 +839,28 @@
 
     synchronized public boolean disableGsmBroadcastRange(int startMessageId, int endMessageId) {
 
-        Context context = mPhone.getContext();
-
-        context.enforceCallingPermission(
+        mContext.enforceCallingPermission(
                 "android.permission.RECEIVE_SMS",
                 "Disabling cell broadcast SMS");
 
-        String client = context.getPackageManager().getNameForUid(
+        String client = mContext.getPackageManager().getNameForUid(
                 Binder.getCallingUid());
 
+        String msg;
         if (!mCellBroadcastRangeManager.disableRange(startMessageId, endMessageId, client)) {
-            log("Failed to remove GSM cell broadcast subscription for MID range " + startMessageId
-                    + " to " + endMessageId + " from client " + client);
+            msg = "Failed to remove GSM cell broadcast channels range " + startMessageId
+                    + " to " + endMessageId;
+            log(msg);
+            mCellBroadcastLocalLog.log(msg);
             return false;
         }
 
-        if (DBG)
-            log("Removed GSM cell broadcast subscription for MID range " + startMessageId
-                    + " to " + endMessageId + " from client " + client);
+        if (DBG) {
+            msg = "Removed GSM cell broadcast channels range " + startMessageId
+                    + " to " + endMessageId;
+            log(msg);
+            mCellBroadcastLocalLog.log(msg);
+        }
 
         setCellBroadcastActivation(!mCellBroadcastRangeManager.isEmpty());
 
@@ -862,24 +869,27 @@
 
     synchronized public boolean enableCdmaBroadcastRange(int startMessageId, int endMessageId) {
 
-        Context context = mPhone.getContext();
-
-        context.enforceCallingPermission(
+        mContext.enforceCallingPermission(
                 "android.permission.RECEIVE_SMS",
                 "Enabling cdma broadcast SMS");
 
-        String client = context.getPackageManager().getNameForUid(
+        String client = mContext.getPackageManager().getNameForUid(
                 Binder.getCallingUid());
 
+        String msg;
         if (!mCdmaBroadcastRangeManager.enableRange(startMessageId, endMessageId, client)) {
-            log("Failed to add cdma broadcast subscription for MID range " + startMessageId
-                    + " to " + endMessageId + " from client " + client);
+            msg = "Failed to add cdma broadcast channels range " + startMessageId + " to "
+                    + endMessageId;
+            log(msg);
+            mCellBroadcastLocalLog.log(msg);
             return false;
         }
 
-        if (DBG)
-            log("Added cdma broadcast subscription for MID range " + startMessageId
-                    + " to " + endMessageId + " from client " + client);
+        if (DBG) {
+            msg = "Added cdma broadcast channels range " + startMessageId + " to " + endMessageId;
+            log(msg);
+            mCellBroadcastLocalLog.log(msg);
+        }
 
         setCdmaBroadcastActivation(!mCdmaBroadcastRangeManager.isEmpty());
 
@@ -888,24 +898,27 @@
 
     synchronized public boolean disableCdmaBroadcastRange(int startMessageId, int endMessageId) {
 
-        Context context = mPhone.getContext();
-
-        context.enforceCallingPermission(
+        mContext.enforceCallingPermission(
                 "android.permission.RECEIVE_SMS",
                 "Disabling cell broadcast SMS");
 
-        String client = context.getPackageManager().getNameForUid(
+        String client = mContext.getPackageManager().getNameForUid(
                 Binder.getCallingUid());
 
+        String msg;
         if (!mCdmaBroadcastRangeManager.disableRange(startMessageId, endMessageId, client)) {
-            log("Failed to remove cdma broadcast subscription for MID range " + startMessageId
-                    + " to " + endMessageId + " from client " + client);
+            msg = "Failed to remove cdma broadcast channels range " + startMessageId + " to "
+                    + endMessageId;
+            log(msg);
+            mCellBroadcastLocalLog.log(msg);
             return false;
         }
 
-        if (DBG)
-            log("Removed cdma broadcast subscription for MID range " + startMessageId
-                    + " to " + endMessageId + " from client " + client);
+        if (DBG) {
+            msg = "Removed cdma broadcast channels range " + startMessageId + " to " + endMessageId;
+            log(msg);
+            mCellBroadcastLocalLog.log(msg);
+        }
 
         setCdmaBroadcastActivation(!mCdmaBroadcastRangeManager.isEmpty());
 
@@ -1086,17 +1099,14 @@
 
     public void sendStoredText(String callingPkg, Uri messageUri, String scAddress,
             PendingIntent sentIntent, PendingIntent deliveryIntent) {
-        mPhone.getContext().enforceCallingPermission(Manifest.permission.SEND_SMS,
-                "Sending SMS message");
+        if (!checkCallingSendSmsPermission(callingPkg, "Sending SMS message")) {
+            return;
+        }
         if (Rlog.isLoggable("SMS", Log.VERBOSE)) {
             log("sendStoredText: scAddr=" + scAddress + " messageUri=" + messageUri
                     + " sentIntent=" + sentIntent + " deliveryIntent=" + deliveryIntent);
         }
-        if (mAppOps.noteOp(AppOpsManager.OP_SEND_SMS, Binder.getCallingUid(), callingPkg)
-                != AppOpsManager.MODE_ALLOWED) {
-            return;
-        }
-        final ContentResolver resolver = mPhone.getContext().getContentResolver();
+        final ContentResolver resolver = mContext.getContentResolver();
         if (!isFailedOrDraft(resolver, messageUri)) {
             Log.e(LOG_TAG, "[IccSmsInterfaceManager]sendStoredText: not FAILED or DRAFT message");
             returnUnspecifiedFailure(sentIntent);
@@ -1117,13 +1127,10 @@
 
     public void sendStoredMultipartText(String callingPkg, Uri messageUri, String scAddress,
             List<PendingIntent> sentIntents, List<PendingIntent> deliveryIntents) {
-        mPhone.getContext().enforceCallingPermission(Manifest.permission.SEND_SMS,
-                "Sending SMS message");
-        if (mAppOps.noteOp(AppOpsManager.OP_SEND_SMS, Binder.getCallingUid(), callingPkg)
-                != AppOpsManager.MODE_ALLOWED) {
+        if (!checkCallingSendSmsPermission(callingPkg, "Sending SMS message")) {
             return;
         }
-        final ContentResolver resolver = mPhone.getContext().getContentResolver();
+        final ContentResolver resolver = mContext.getContentResolver();
         if (!isFailedOrDraft(resolver, messageUri)) {
             Log.e(LOG_TAG, "[IccSmsInterfaceManager]sendStoredMultipartText: "
                     + "not FAILED or DRAFT message");
@@ -1268,32 +1275,69 @@
         }
     }
 
-    private void enforceCarrierPrivilege() {
-        UiccController controller = UiccController.getInstance();
-        if (controller == null || controller.getUiccCard(mPhone.getPhoneId()) == null) {
-            throw new SecurityException("No Carrier Privilege: No UICC");
+    /**
+     * Check that the caller can send text messages.
+     *
+     * For persisted messages, the caller just needs the SEND_SMS permission. For unpersisted
+     * messages, the caller must either be the IMS app or a carrier-privileged app, or they must
+     * have both the MODIFY_PHONE_STATE and SEND_SMS permissions.
+     *
+     * @throws SecurityException if the caller is missing all necessary permission declaration or
+     *                           has had a necessary runtime permission revoked.
+     * @return true unless the caller has all necessary permissions but has a revoked AppOps bit.
+     */
+    @VisibleForTesting
+    public boolean checkCallingSendTextPermissions(
+            boolean persistMessageForNonDefaultSmsApp, String callingPackage, String message) {
+        // TODO(b/75978989): Should we allow IMS/carrier apps for persisted messages as well?
+        if (!persistMessageForNonDefaultSmsApp) {
+            try {
+                enforceCallerIsImsAppOrCarrierApp(message);
+                // No need to also check SEND_SMS.
+                return true;
+            } catch (SecurityException e) {
+                mContext.enforceCallingPermission(
+                        android.Manifest.permission.MODIFY_PHONE_STATE, message);
+            }
         }
-        if (controller.getUiccCard(mPhone.getPhoneId()).getCarrierPrivilegeStatusForCurrentTransaction(
-                mContext.getPackageManager()) !=
-                    TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
-            throw new SecurityException("No Carrier Privilege.");
-        }
+        return checkCallingSendSmsPermission(callingPackage, message);
     }
 
     /**
-     * Enforces that the caller has {@link android.Manifest.permission#MODIFY_PHONE_STATE}
-     * permission or is one of the following apps:
+     * Check that the caller (or self, if this is not an IPC) has SEND_SMS permissions.
+     *
+     * @throws SecurityException if the caller is missing the permission declaration or has had the
+     *                           permission revoked at runtime.
+     * @return whether the caller has the OP_SEND_SMS AppOps bit.
+     */
+    private boolean checkCallingOrSelfSendSmsPermission(String callingPackage, String message) {
+        mContext.enforceCallingOrSelfPermission(Manifest.permission.SEND_SMS, message);
+        return mAppOps.noteOp(AppOpsManager.OP_SEND_SMS, Binder.getCallingUid(), callingPackage)
+                == AppOpsManager.MODE_ALLOWED;
+    }
+
+    /**
+     * Check that the caller has SEND_SMS permissions. Can only be called during an IPC.
+     *
+     * @throws SecurityException if the caller is missing the permission declaration or has had the
+     *                           permission revoked at runtime.
+     * @return whether the caller has the OP_SEND_SMS AppOps bit.
+     */
+    private boolean checkCallingSendSmsPermission(String callingPackage, String message) {
+        mContext.enforceCallingPermission(Manifest.permission.SEND_SMS, message);
+        return mAppOps.noteOp(AppOpsManager.OP_SEND_SMS, Binder.getCallingUid(), callingPackage)
+                == AppOpsManager.MODE_ALLOWED;
+    }
+
+    /**
+     * Enforces that the caller is one of the following apps:
      * <ul>
      *     <li> IMS App
      *     <li> Carrier App
      * </ul>
      */
-    private void enforcePrivilegedAppPermissions() {
-        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
-                == PackageManager.PERMISSION_GRANTED) {
-            return;
-        }
-
+    @VisibleForTesting
+    public void enforceCallerIsImsAppOrCarrierApp(String message) {
         int callingUid = Binder.getCallingUid();
         String carrierImsPackage = CarrierSmsUtils.getCarrierImsPackageForIntent(mContext, mPhone,
                 new Intent(CarrierMessagingService.SERVICE_INTERFACE));
@@ -1309,7 +1353,7 @@
             }
         }
 
-        enforceCarrierPrivilege();
+        TelephonyPermissions.enforceCallingOrSelfCarrierPrivilege(mPhone.getSubId(), message);
     }
 
     private String filterDestAddress(String destAddr) {
@@ -1318,4 +1362,11 @@
         return result != null ? result : destAddr;
     }
 
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("CellBroadcast log:");
+        mCellBroadcastLocalLog.dump(fd, pw, args);
+        pw.println("SMS dispatcher controller log:");
+        mDispatchersController.dump(fd, pw, args);
+        pw.flush();
+    }
 }
diff --git a/src/java/com/android/internal/telephony/ImsSmsDispatcher.java b/src/java/com/android/internal/telephony/ImsSmsDispatcher.java
index 65ce0fe..5bc62d5 100644
--- a/src/java/com/android/internal/telephony/ImsSmsDispatcher.java
+++ b/src/java/com/android/internal/telephony/ImsSmsDispatcher.java
@@ -16,8 +16,11 @@
 
 package com.android.internal.telephony;
 
+import android.content.Context;
+import android.os.PersistableBundle;
 import android.os.RemoteException;
 import android.provider.Telephony.Sms.Intents;
+import android.telephony.CarrierConfigManager;
 import android.telephony.Rlog;
 import android.telephony.ims.ImsReasonInfo;
 import android.telephony.ims.aidl.IImsSmsListener;
@@ -26,6 +29,8 @@
 import android.telephony.ims.stub.ImsRegistrationImplBase;
 import android.telephony.ims.stub.ImsSmsImplBase;
 import android.telephony.ims.stub.ImsSmsImplBase.SendStatusResult;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.ServiceState;
 import android.util.Pair;
 
 import com.android.ims.ImsException;
@@ -162,7 +167,9 @@
         public void onSmsReceived(int token, String format, byte[] pdu)
                 throws RemoteException {
             Rlog.d(TAG, "SMS received.");
-            mSmsDispatchersController.injectSmsPdu(pdu, format, result -> {
+            android.telephony.SmsMessage message =
+                    android.telephony.SmsMessage.createFromPdu(pdu, format);
+            mSmsDispatchersController.injectSmsPdu(message, format, result -> {
                 Rlog.d(TAG, "SMS handled result: " + result);
                 int mappedResult;
                 switch (result) {
@@ -180,7 +187,13 @@
                         break;
                 }
                 try {
-                    getImsManager().acknowledgeSms(token, 0, mappedResult);
+                    if (message != null && message.mWrappedSmsMessage != null) {
+                        getImsManager().acknowledgeSms(token,
+                                message.mWrappedSmsMessage.mMessageRef, mappedResult);
+                    } else {
+                        Rlog.w(TAG, "SMS Received with a PDU that could not be parsed.");
+                        getImsManager().acknowledgeSms(token, 0, mappedResult);
+                    }
                 } catch (ImsException e) {
                     Rlog.e(TAG, "Failed to acknowledgeSms(). Error: " + e.getMessage());
                 }
@@ -220,6 +233,50 @@
         getImsManager().onSmsReady();
     }
 
+    private boolean isLteService() {
+        return ((mPhone.getServiceState().getRilVoiceRadioTechnology() ==
+            ServiceState.RIL_RADIO_TECHNOLOGY_LTE) && (mPhone.getServiceState().
+                getState() == ServiceState.STATE_IN_SERVICE));
+    }
+
+    private boolean isLimitedLteService() {
+        return ((mPhone.getServiceState().getRilVoiceRadioTechnology() ==
+            ServiceState.RIL_RADIO_TECHNOLOGY_LTE) && mPhone.getServiceState().isEmergencyOnly());
+    }
+
+    private boolean isEmergencySmsPossible() {
+        return isLteService() || isLimitedLteService();
+    }
+
+    public boolean isEmergencySmsSupport(String destAddr) {
+        PersistableBundle b;
+        boolean eSmsCarrierSupport = false;
+        if (!PhoneNumberUtils.isLocalEmergencyNumber(mContext, mPhone.getSubId(), destAddr)) {
+            Rlog.e(TAG, "Emergency Sms is not supported for: " + destAddr);
+            return false;
+        }
+        CarrierConfigManager configManager = (CarrierConfigManager) mPhone.getContext()
+                .getSystemService(Context.CARRIER_CONFIG_SERVICE);
+        if (configManager == null) {
+            Rlog.e(TAG, "configManager is null");
+            return false;
+        }
+        b = configManager.getConfigForSubId(getSubId());
+        if (b == null) {
+            Rlog.e(TAG, "PersistableBundle is null");
+            return false;
+        }
+        eSmsCarrierSupport = b.getBoolean(CarrierConfigManager.
+                                                      KEY_SUPPORT_EMERGENCY_SMS_OVER_IMS_BOOL);
+        boolean lteOrLimitedLte = isEmergencySmsPossible();
+        Rlog.i(TAG, "isEmergencySmsSupport emergencySmsCarrierSupport: "
+               + eSmsCarrierSupport + " destAddr: " + destAddr + " mIsImsServiceUp: "
+               + mIsImsServiceUp + " lteOrLimitedLte: " + lteOrLimitedLte);
+
+        return eSmsCarrierSupport && mIsImsServiceUp && lteOrLimitedLte;
+    }
+
+
     public boolean isAvailable() {
         synchronized (mLock) {
             Rlog.d(TAG, "isAvailable: up=" + mIsImsServiceUp + ", reg= " + mIsRegistered
@@ -239,8 +296,10 @@
     }
 
     @Override
-    protected boolean shouldBlockSms() {
-        return SMSDispatcherUtil.shouldBlockSms(isCdmaMo(), mPhone);
+    protected boolean shouldBlockSmsForEcbm() {
+        // We should not block outgoing SMS during ECM on IMS. It only applies to outgoing CDMA
+        // SMS.
+        return false;
     }
 
     @Override
@@ -270,6 +329,10 @@
                 + " mMessageRef=" + tracker.mMessageRef
                 + " SS=" + mPhone.getServiceState().getState());
 
+        // Flag that this Tracker is using the ImsService implementation of SMS over IMS for sending
+        // this message. Any fallbacks will happen over CS only.
+        tracker.mUsesImsServiceForIms = true;
+
         HashMap<String, Object> map = tracker.getData();
 
         byte[] pdu = (byte[]) map.get(MAP_KEY_PDU);
diff --git a/src/java/com/android/internal/telephony/InboundSmsHandler.java b/src/java/com/android/internal/telephony/InboundSmsHandler.java
index 53072c7..b51498e 100644
--- a/src/java/com/android/internal/telephony/InboundSmsHandler.java
+++ b/src/java/com/android/internal/telephony/InboundSmsHandler.java
@@ -60,6 +60,7 @@
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
+import android.util.LocalLog;
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
@@ -69,6 +70,8 @@
 import com.android.internal.util.StateMachine;
 
 import java.io.ByteArrayOutputStream;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
@@ -157,6 +160,9 @@
     /** New SMS received as an AsyncResult. */
     public static final int EVENT_INJECT_SMS = 8;
 
+    /** Update the sms tracker */
+    public static final int EVENT_UPDATE_TRACKER = 9;
+
     /** Wakelock release delay when returning to idle state. */
     private static final int WAKELOCK_TIMEOUT = 3000;
 
@@ -205,6 +211,8 @@
 
     private UserManager mUserManager;
 
+    private LocalLog mLocalLog = new LocalLog(64);
+
     IDeviceIdleController mDeviceIdleController;
 
     // Delete permanently from raw table
@@ -449,6 +457,7 @@
                     // if any broadcasts were sent, transition to waiting state
                     InboundSmsTracker inboundSmsTracker = (InboundSmsTracker) msg.obj;
                     if (processMessagePart(inboundSmsTracker)) {
+                        sendMessage(obtainMessage(EVENT_UPDATE_TRACKER, msg.obj));
                         transitionTo(mWaitingState);
                     } else {
                         // if event is sent from SmsBroadcastUndelivered.broadcastSms(), and
@@ -474,10 +483,17 @@
                     }
                     return HANDLED;
 
+                case EVENT_UPDATE_TRACKER:
+                    logd("process tracker message in DeliveringState " + msg.arg1);
+                    return HANDLED;
+
                 // we shouldn't get this message type in this state, log error and halt.
                 case EVENT_BROADCAST_COMPLETE:
                 case EVENT_START_ACCEPTING_SMS:
                 default:
+                    String errorMsg = "Unhandled msg in delivering state, msg.what = " + msg.what;
+                    loge(errorMsg);
+                    mLocalLog.log(errorMsg);
                     // let DefaultState handle these unexpected message types
                     return NOT_HANDLED;
             }
@@ -493,6 +509,8 @@
      */
     private class WaitingState extends State {
 
+        private InboundSmsTracker mLastDeliveredSmsTracker;
+
         @Override
         public void enter() {
             if (DBG) log("entering Waiting state");
@@ -512,10 +530,20 @@
             switch (msg.what) {
                 case EVENT_BROADCAST_SMS:
                     // defer until the current broadcast completes
+                    if (mLastDeliveredSmsTracker != null) {
+                        String str = "Defer sms broadcast due to undelivered sms, "
+                                + " messageCount = " + mLastDeliveredSmsTracker.getMessageCount()
+                                + " destPort = " + mLastDeliveredSmsTracker.getDestPort()
+                                + " timestamp = " + mLastDeliveredSmsTracker.getTimestamp()
+                                + " currentTimestamp = " + System.currentTimeMillis();
+                        logd(str);
+                        mLocalLog.log(str);
+                    }
                     deferMessage(msg);
                     return HANDLED;
 
                 case EVENT_BROADCAST_COMPLETE:
+                    mLastDeliveredSmsTracker = null;
                     // return to idle after handling all deferred messages
                     sendMessage(EVENT_RETURN_TO_IDLE);
                     transitionTo(mDeliveringState);
@@ -525,6 +553,13 @@
                     // not ready to return to idle; ignore
                     return HANDLED;
 
+                case EVENT_UPDATE_TRACKER:
+                    for (int i = 1; i < 10; i++) {
+                        deferMessage(obtainMessage(EVENT_UPDATE_TRACKER, i, i, msg.obj));
+                    }
+                    mLastDeliveredSmsTracker = (InboundSmsTracker) msg.obj;
+                    return HANDLED;
+
                 default:
                     // parent state handles the other message types
                     return NOT_HANDLED;
@@ -835,8 +870,10 @@
         // Do not process null pdu(s). Check for that and return false in that case.
         List<byte[]> pduList = Arrays.asList(pdus);
         if (pduList.size() == 0 || pduList.contains(null)) {
-            loge("processMessagePart: returning false due to " +
-                    (pduList.size() == 0 ? "pduList.size() == 0" : "pduList.contains(null)"));
+            String errorMsg = "processMessagePart: returning false due to "
+                    + (pduList.size() == 0 ? "pduList.size() == 0" : "pduList.contains(null)");
+            loge(errorMsg);
+            mLocalLog.log(errorMsg);
             return false;
         }
 
@@ -1529,6 +1566,15 @@
         }
     }
 
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        super.dump(fd, pw, args);
+        if (mCellBroadcastHandler != null) {
+            mCellBroadcastHandler.dump(fd, pw, args);
+        }
+        mLocalLog.dump(fd, pw, args);
+    }
+
     // Some providers send formfeeds in their messages. Convert those formfeeds to newlines.
     private static String replaceFormFeeds(String s) {
         return s == null ? "" : s.replace('\f', '\n');
diff --git a/src/java/com/android/internal/telephony/LocaleTracker.java b/src/java/com/android/internal/telephony/LocaleTracker.java
new file mode 100644
index 0000000..8977b03
--- /dev/null
+++ b/src/java/com/android/internal/telephony/LocaleTracker.java
@@ -0,0 +1,439 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony;
+
+import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
+import static android.text.format.DateUtils.SECOND_IN_MILLIS;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.wifi.WifiManager;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.telephony.CellInfo;
+import android.telephony.CellInfoGsm;
+import android.telephony.CellInfoLte;
+import android.telephony.CellInfoWcdma;
+import android.telephony.Rlog;
+import android.telephony.ServiceState;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.LocalLog;
+
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * The locale tracker keeps tracking the current locale of the phone.
+ */
+public class LocaleTracker extends Handler {
+    private static final boolean DBG = true;
+    private static final String TAG = LocaleTracker.class.getSimpleName();
+
+    /** Event for getting cell info from the modem */
+    private static final int EVENT_GET_CELL_INFO                = 1;
+
+    /** Event for operator numeric update */
+    private static final int EVENT_UPDATE_OPERATOR_NUMERIC      = 2;
+
+    /** Event for service state changed */
+    private static final int EVENT_SERVICE_STATE_CHANGED        = 3;
+
+    // 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;
+
+    // Todo: Read this from Settings.
+    /** The maximum delay to get cell info from the modem */
+    private static final long CELL_INFO_MAX_DELAY_MS = 10 * MINUTE_IN_MILLIS;
+
+    // Todo: Read this from Settings.
+    /** The delay for periodically getting cell info from the modem */
+    private static final long CELL_INFO_PERIODIC_POLLING_DELAY_MS = 10 * MINUTE_IN_MILLIS;
+
+    private final Phone mPhone;
+
+    /** SIM card state. Must be one of TelephonyManager.SIM_STATE_XXX */
+    private int mSimState;
+
+    /** Current serving PLMN's MCC/MNC */
+    @Nullable
+    private String mOperatorNumeric;
+
+    /** Current cell tower information */
+    @Nullable
+    private List<CellInfo> mCellInfo;
+
+    /** 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 */
+    @Nullable
+    private String mCurrentCountryIso;
+
+    /** Current service state. Must be one of ServiceState.STATE_XXX. */
+    private int mLastServiceState = -1;
+
+    private final LocalLog mLocalLog = new LocalLog(50);
+
+    /** Broadcast receiver to get SIM card state changed event */
+    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (TelephonyManager.ACTION_SIM_CARD_STATE_CHANGED.equals(intent.getAction())) {
+                int phoneId = intent.getIntExtra(PhoneConstants.PHONE_KEY, 0);
+                if (phoneId == mPhone.getPhoneId()) {
+                    onSimCardStateChanged(intent.getIntExtra(TelephonyManager.EXTRA_SIM_STATE,
+                            TelephonyManager.SIM_STATE_UNKNOWN));
+                }
+            }
+        }
+    };
+
+    /**
+     * Message handler
+     *
+     * @param msg The message
+     */
+    @Override
+    public void handleMessage(Message msg) {
+        switch (msg.what) {
+            case EVENT_GET_CELL_INFO:
+                synchronized (this) {
+                    getCellInfo();
+                    updateLocale();
+                }
+                break;
+            case EVENT_UPDATE_OPERATOR_NUMERIC:
+                updateOperatorNumericSync((String) msg.obj);
+                break;
+            case EVENT_SERVICE_STATE_CHANGED:
+                AsyncResult ar = (AsyncResult) msg.obj;
+                onServiceStateChanged((ServiceState) ar.result);
+                break;
+            default:
+                throw new IllegalStateException("Unexpected message arrives. msg = " + msg.what);
+        }
+    }
+
+    /**
+     * Constructor
+     *
+     * @param phone The phone object
+     * @param looper The looper message handler
+     */
+    public LocaleTracker(Phone phone, Looper looper)  {
+        super(looper);
+        mPhone = phone;
+        mSimState = TelephonyManager.SIM_STATE_UNKNOWN;
+
+        final IntentFilter filter = new IntentFilter();
+        filter.addAction(TelephonyManager.ACTION_SIM_CARD_STATE_CHANGED);
+        mPhone.getContext().registerReceiver(mBroadcastReceiver, filter);
+
+        mPhone.registerForServiceStateChanged(this, EVENT_SERVICE_STATE_CHANGED, null);
+    }
+
+    /**
+     * Get the device's current country.
+     *
+     * @return The device's current country. Empty string if the information is not available.
+     */
+    @NonNull
+    public synchronized String getCurrentCountry() {
+        return (mCurrentCountryIso != null) ? mCurrentCountryIso : "";
+    }
+
+    /**
+     * Get the MCC from cell tower information.
+     *
+     * @return MCC in string format. Null if the information is not available.
+     */
+    @Nullable
+    private String getMccFromCellInfo() {
+        String selectedMcc = null;
+        if (mCellInfo != null) {
+            Map<String, Integer> countryCodeMap = new HashMap<>();
+            int maxCount = 0;
+            for (CellInfo cellInfo : mCellInfo) {
+                String mcc = null;
+                if (cellInfo instanceof CellInfoGsm) {
+                    mcc = ((CellInfoGsm) cellInfo).getCellIdentity().getMccString();
+                } else if (cellInfo instanceof CellInfoLte) {
+                    mcc = ((CellInfoLte) cellInfo).getCellIdentity().getMccString();
+                } else if (cellInfo instanceof CellInfoWcdma) {
+                    mcc = ((CellInfoWcdma) cellInfo).getCellIdentity().getMccString();
+                }
+                if (mcc != null) {
+                    int count = 1;
+                    if (countryCodeMap.containsKey(mcc)) {
+                        count = countryCodeMap.get(mcc) + 1;
+                    }
+                    countryCodeMap.put(mcc, count);
+                    // This is unlikely, but if MCC from cell info looks different, we choose the
+                    // MCC that occurs most.
+                    if (count > maxCount) {
+                        maxCount = count;
+                        selectedMcc = mcc;
+                    }
+                }
+            }
+        }
+        return selectedMcc;
+    }
+
+    /**
+     * Called when SIM card state changed. Only when we absolutely know the SIM is absent, we get
+     * cell info from the network. Other SIM states like NOT_READY might be just a transitioning
+     * state.
+     *
+     * @param state SIM card state. Must be one of TelephonyManager.SIM_STATE_XXX.
+     */
+    private synchronized void onSimCardStateChanged(int state) {
+        if (mSimState != state && state == TelephonyManager.SIM_STATE_ABSENT) {
+            if (DBG) log("Sim absent. Get latest cell info from the modem.");
+            getCellInfo();
+            updateLocale();
+        }
+        mSimState = state;
+    }
+
+    /**
+     * Called when service state changed.
+     *
+     * @param serviceState Service state
+     */
+    private void onServiceStateChanged(ServiceState serviceState) {
+        int state = serviceState.getState();
+        if (state != mLastServiceState) {
+            if (state != ServiceState.STATE_POWER_OFF && TextUtils.isEmpty(mOperatorNumeric)) {
+                // When the device is out of airplane mode or powered on, and network's MCC/MNC is
+                // not available, we get cell info from the modem.
+                String msg = "Service state " + ServiceState.rilServiceStateToString(state)
+                        + ". Get cell info now.";
+                if (DBG) log(msg);
+                mLocalLog.log(msg);
+                getCellInfo();
+            } else if (state == ServiceState.STATE_POWER_OFF) {
+                // Clear the cell info when the device is in airplane mode.
+                if (mCellInfo != null) mCellInfo.clear();
+                stopCellInfoRetry();
+            }
+            updateLocale();
+            mLastServiceState = state;
+        }
+    }
+
+    /**
+     * Update MCC/MNC from network service state synchronously. Note if this is called from phone
+     * process's main thread and if the update operation requires getting cell info from the modem,
+     * the cached cell info will be used to determine the locale. If the cached cell info is not
+     * acceptable, use {@link #updateOperatorNumericAsync(String)} instead.
+     *
+     * @param operatorNumeric MCC/MNC of the operator
+     */
+    public synchronized void updateOperatorNumericSync(String operatorNumeric) {
+        // Check if the operator numeric changes.
+        if (DBG) log("updateOperatorNumericSync. mcc/mnc=" + operatorNumeric);
+        if (!Objects.equals(mOperatorNumeric, operatorNumeric)) {
+            String msg = "Operator numeric changes to " + operatorNumeric;
+            if (DBG) log(msg);
+            mLocalLog.log(msg);
+            mOperatorNumeric = operatorNumeric;
+
+            // If the operator numeric becomes unavailable, we need to get the latest cell info so
+            // that we can get MCC from it.
+            if (TextUtils.isEmpty(mOperatorNumeric)) {
+                if (DBG) {
+                    log("Operator numeric unavailable. Get latest cell info from the modem.");
+                }
+                getCellInfo();
+            } else {
+                // If operator numeric is available, that means we camp on network. So we should
+                // clear the cell info and stop cell info retry.
+                if (mCellInfo != null) mCellInfo.clear();
+                stopCellInfoRetry();
+            }
+            updateLocale();
+        }
+    }
+
+    /**
+     * Update MCC/MNC from network service state asynchronously. The update operation will run
+     * in locale tracker's handler's thread, which can get cell info synchronously from service
+     * state tracker. Note that the country code will not be available immediately after calling
+     * this method.
+     *
+     * @param operatorNumeric MCC/MNC of the operator
+     */
+    public void updateOperatorNumericAsync(String operatorNumeric) {
+        if (DBG) log("updateOperatorNumericAsync. mcc/mnc=" + operatorNumeric);
+        sendMessage(obtainMessage(EVENT_UPDATE_OPERATOR_NUMERIC, operatorNumeric));
+    }
+
+    /**
+     * Get the delay time to get cell info from modem. The delay time grows exponentially to prevent
+     * battery draining.
+     *
+     * @param failCount Count of invalid cell info we've got so far.
+     * @return The delay time for next get cell info
+     */
+    private long getCellInfoDelayTime(int failCount) {
+        // Exponentially grow the delay time
+        long delay = CELL_INFO_MIN_DELAY_MS * (long) Math.pow(2, failCount - 1);
+        if (delay < CELL_INFO_MIN_DELAY_MS) {
+            delay = CELL_INFO_MIN_DELAY_MS;
+        } else if (delay > CELL_INFO_MAX_DELAY_MS) {
+            delay = CELL_INFO_MAX_DELAY_MS;
+        }
+        return delay;
+    }
+
+    /**
+     * Stop retrying getting cell info from the modem. It cancels any scheduled cell info retrieving
+     * request.
+     */
+    private void stopCellInfoRetry() {
+        mFailCellInfoCount = 0;
+        removeMessages(EVENT_GET_CELL_INFO);
+    }
+
+    /**
+     * Get cell info from the modem.
+     */
+    private void getCellInfo() {
+        String msg;
+        if (!mPhone.getServiceStateTracker().getDesiredPowerState()) {
+            msg = "Radio is off. Stopped cell info retry. Cleared the previous cached cell info.";
+            if (mCellInfo != null) mCellInfo.clear();
+            if (DBG) log(msg);
+            mLocalLog.log(msg);
+            stopCellInfoRetry();
+            return;
+        }
+
+        // Get all cell info. Passing null to use default worksource, which indicates the original
+        // request is from telephony internally.
+        mCellInfo = mPhone.getAllCellInfo(null);
+        msg = "getCellInfo: cell info=" + mCellInfo;
+        if (DBG) log(msg);
+        mLocalLog.log(msg);
+        if (mCellInfo == null || mCellInfo.size() == 0) {
+            // If we can't get a valid cell info. Try it again later.
+            long delay = getCellInfoDelayTime(++mFailCellInfoCount);
+            if (DBG) log("Can't get cell info. Try again in " + delay / 1000 + " secs.");
+            removeMessages(EVENT_GET_CELL_INFO);
+            sendMessageDelayed(obtainMessage(EVENT_GET_CELL_INFO), delay);
+        } else {
+            // We successfully got cell info from the modem. We should stop cell info retry.
+            stopCellInfoRetry();
+
+            // Now we need to get the cell info from the modem periodically even if we already got
+            // the cell info because the user can move.
+            sendMessageDelayed(obtainMessage(EVENT_GET_CELL_INFO),
+                    CELL_INFO_PERIODIC_POLLING_DELAY_MS);
+        }
+    }
+
+    /**
+     * Update the device's current locale
+     */
+    private void updateLocale() {
+        // If MCC is available from network service state, use it first.
+        String mcc = null;
+        String countryIso = "";
+        if (!TextUtils.isEmpty(mOperatorNumeric)) {
+            try {
+                mcc = mOperatorNumeric.substring(0, 3);
+                countryIso = MccTable.countryCodeForMcc(mcc);
+            } catch (StringIndexOutOfBoundsException ex) {
+                loge("updateLocale: Can't get country from operator numeric. mcc = "
+                        + mcc + ". ex=" + ex);
+            }
+        }
+
+        // If for any reason we can't get country from operator numeric, try to get it from cell
+        // info.
+        if (TextUtils.isEmpty(countryIso)) {
+            mcc = getMccFromCellInfo();
+            countryIso = MccTable.countryCodeForMcc(mcc);
+        }
+
+        String msg = "updateLocale: mcc = " + mcc + ", country = " + countryIso;
+        log(msg);
+        mLocalLog.log(msg);
+        if (!Objects.equals(countryIso, mCurrentCountryIso)) {
+            msg = "updateLocale: Change the current country to " + countryIso;
+            log(msg);
+            mLocalLog.log(msg);
+            mCurrentCountryIso = countryIso;
+
+            TelephonyManager.setTelephonyProperty(mPhone.getPhoneId(),
+                    TelephonyProperties.PROPERTY_OPERATOR_ISO_COUNTRY, mCurrentCountryIso);
+
+            // Set the country code for wifi. This sets allowed wifi channels based on the
+            // country of the carrier we see. If we can't see any, reset to 0 so we don't
+            // broadcast on forbidden channels.
+            ((WifiManager) mPhone.getContext().getSystemService(Context.WIFI_SERVICE))
+                    .setCountryCode(countryIso, false);
+        }
+    }
+
+    private void log(String msg) {
+        Rlog.d(TAG, msg);
+    }
+
+    private void loge(String msg) {
+        Rlog.e(TAG, msg);
+    }
+
+    /**
+     * Print the DeviceStateMonitor into the given stream.
+     *
+     * @param fd The raw file descriptor that the dump is being sent to.
+     * @param pw A PrintWriter to which the dump is to be set.
+     * @param args Additional arguments to the dump request.
+     */
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
+        pw.println("LocaleTracker:");
+        ipw.increaseIndent();
+        ipw.println("mOperatorNumeric = " + mOperatorNumeric);
+        ipw.println("mSimState = " + mSimState);
+        ipw.println("mCellInfo = " + mCellInfo);
+        ipw.println("mCurrentCountryIso = " + mCurrentCountryIso);
+        ipw.println("mFailCellInfoCount = " + mFailCellInfoCount);
+        ipw.println("Local logs:");
+        ipw.increaseIndent();
+        mLocalLog.dump(fd, ipw, args);
+        ipw.decreaseIndent();
+        ipw.decreaseIndent();
+        ipw.flush();
+    }
+}
diff --git a/src/java/com/android/internal/telephony/MccTable.java b/src/java/com/android/internal/telephony/MccTable.java
index 7d4656f..fb28194 100644
--- a/src/java/com/android/internal/telephony/MccTable.java
+++ b/src/java/com/android/internal/telephony/MccTable.java
@@ -19,11 +19,9 @@
 import android.app.ActivityManager;
 import android.content.Context;
 import android.content.res.Configuration;
-import android.net.wifi.WifiManager;
 import android.os.Build;
 import android.os.RemoteException;
 import android.os.SystemProperties;
-import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.Slog;
 
@@ -114,6 +112,19 @@
 
     /**
      * Given a GSM Mobile Country Code, returns
+     * an ISO two-character country code if available.
+     * Returns empty string if unavailable.
+     */
+    public static String countryCodeForMcc(String mcc) {
+        try {
+            return countryCodeForMcc(Integer.parseInt(mcc));
+        } catch (NumberFormatException ex) {
+            return "";
+        }
+    }
+
+    /**
+     * Given a GSM Mobile Country Code, returns
      * an ISO 2-3 character language code if available.
      * Returns null if unavailable.
      */
@@ -159,11 +170,9 @@
      * correct version of resources.  If MCC is 0, MCC and MNC will be ignored (not set).
      * @param context Context to act on.
      * @param mccmnc truncated imsi with just the MCC and MNC - MNC assumed to be from 4th to end
-     * @param fromServiceState true if coming from the radio service state, false if from SIM
      */
-    public static void updateMccMncConfiguration(Context context, String mccmnc,
-            boolean fromServiceState) {
-        Slog.d(LOG_TAG, "updateMccMncConfiguration mccmnc='" + mccmnc + "' fromServiceState=" + fromServiceState);
+    public static void updateMccMncConfiguration(Context context, String mccmnc) {
+        Slog.d(LOG_TAG, "updateMccMncConfiguration mccmnc='" + mccmnc);
 
         if (Build.IS_DEBUGGABLE) {
             String overrideMcc = SystemProperties.get("persist.sys.override_mcc");
@@ -176,19 +185,11 @@
         if (!TextUtils.isEmpty(mccmnc)) {
             int mcc, mnc;
 
-            String defaultMccMnc = TelephonyManager.getDefault().getSimOperatorNumeric();
-            Slog.d(LOG_TAG, "updateMccMncConfiguration defaultMccMnc=" + defaultMccMnc);
-            //Update mccmnc only for default subscription in case of MultiSim.
-//            if (!defaultMccMnc.equals(mccmnc)) {
-//                Slog.d(LOG_TAG, "Not a Default subscription, ignoring mccmnc config update.");
-//                return;
-//            }
-
             try {
-                mcc = Integer.parseInt(mccmnc.substring(0,3));
+                mcc = Integer.parseInt(mccmnc.substring(0, 3));
                 mnc = Integer.parseInt(mccmnc.substring(3));
-            } catch (NumberFormatException e) {
-                Slog.e(LOG_TAG, "Error parsing IMSI: " + mccmnc);
+            } catch (NumberFormatException | StringIndexOutOfBoundsException ex) {
+                Slog.e(LOG_TAG, "Error parsing IMSI: " + mccmnc + ". ex=" + ex);
                 return;
             }
 
@@ -196,33 +197,24 @@
             if (mcc != 0) {
                 setTimezoneFromMccIfNeeded(context, mcc);
             }
-            if (fromServiceState) {
-                setWifiCountryCodeFromMcc(context, mcc);
-            } else {
-                // from SIM
-                try {
-                    Configuration config = new Configuration();
-                    boolean updateConfig = false;
-                    if (mcc != 0) {
-                        config.mcc = mcc;
-                        config.mnc = mnc == 0 ? Configuration.MNC_ZERO : mnc;
-                        updateConfig = true;
-                    }
 
-                    if (updateConfig) {
-                        Slog.d(LOG_TAG, "updateMccMncConfiguration updateConfig config=" + config);
-                        ActivityManager.getService().updateConfiguration(config);
-                    } else {
-                        Slog.d(LOG_TAG, "updateMccMncConfiguration nothing to update");
-                    }
-                } catch (RemoteException e) {
-                    Slog.e(LOG_TAG, "Can't update configuration", e);
+            try {
+                Configuration config = new Configuration();
+                boolean updateConfig = false;
+                if (mcc != 0) {
+                    config.mcc = mcc;
+                    config.mnc = mnc == 0 ? Configuration.MNC_ZERO : mnc;
+                    updateConfig = true;
                 }
-            }
-        } else {
-            if (fromServiceState) {
-                // an empty mccmnc means no signal - tell wifi we don't know
-                setWifiCountryCodeFromMcc(context, 0);
+
+                if (updateConfig) {
+                    Slog.d(LOG_TAG, "updateMccMncConfiguration updateConfig config=" + config);
+                    ActivityManager.getService().updateConfiguration(config);
+                } else {
+                    Slog.d(LOG_TAG, "updateMccMncConfiguration nothing to update");
+                }
+            } catch (RemoteException e) {
+                Slog.e(LOG_TAG, "Can't update configuration", e);
             }
         }
     }
@@ -358,12 +350,25 @@
      * @param mcc Mobile Country Code of the SIM or SIM-like entity (build prop on CDMA)
      */
     private static void setTimezoneFromMccIfNeeded(Context context, int mcc) {
-        if (!TimeServiceHelper.isTimeZoneSettingInitializedStatic()) {
-            String zoneId = defaultTimeZoneForMcc(mcc);
-            if (zoneId != null && zoneId.length() > 0) {
-                // Set time zone based on MCC
-                TimeServiceHelper.setDeviceTimeZoneStatic(context, zoneId);
-                Slog.d(LOG_TAG, "timezone set to " + zoneId);
+        // 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);
+                }
             }
         }
     }
@@ -396,20 +401,6 @@
         return locale;
     }
 
-    /**
-     * Set the country code for wifi.  This sets allowed wifi channels based on the
-     * country of the carrier we see.  If we can't see any, reset to 0 so we don't
-     * broadcast on forbidden channels.
-     * @param context Context to act on.
-     * @param mcc Mobile Country Code of the operator.  0 if not known
-     */
-    private static void setWifiCountryCodeFromMcc(Context context, int mcc) {
-        String country = MccTable.countryCodeForMcc(mcc);
-        Slog.d(LOG_TAG, "WIFI_COUNTRY_CODE set to " + country);
-        WifiManager wM = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
-        wM.setCountryCode(country, false);
-    }
-
     static {
         sTable = new ArrayList<MccEntry>(240);
 
diff --git a/src/java/com/android/internal/telephony/NetworkRegistrationManager.java b/src/java/com/android/internal/telephony/NetworkRegistrationManager.java
index 30210a8..ae7ede0 100644
--- a/src/java/com/android/internal/telephony/NetworkRegistrationManager.java
+++ b/src/java/com/android/internal/telephony/NetworkRegistrationManager.java
@@ -36,6 +36,9 @@
 import android.telephony.NetworkService;
 import android.telephony.Rlog;
 
+import java.util.Hashtable;
+import java.util.Map;
+
 /**
  * Class that serves as the layer between NetworkService and ServiceStateTracker. It helps binding,
  * sending request and registering for state change to NetworkService.
@@ -79,22 +82,27 @@
         mRegStateChangeRegistrants.addUnique(h, what, obj);
     }
 
+    private final Map<NetworkRegStateCallback, Message> mCallbackTable = new Hashtable();
+
     public void getNetworkRegistrationState(int domain, Message onCompleteMessage) {
         if (onCompleteMessage == null) return;
 
         logd("getNetworkRegistrationState domain " + domain);
         if (!isServiceConnected()) {
+            logd("service not connected.");
             onCompleteMessage.obj = new AsyncResult(onCompleteMessage.obj, null,
                     new IllegalStateException("Service not connected."));
             onCompleteMessage.sendToTarget();
             return;
         }
 
+        NetworkRegStateCallback callback = new NetworkRegStateCallback();
         try {
-            mServiceBinder.getNetworkRegistrationState(mPhone.getPhoneId(), domain,
-                    new NetworkRegStateCallback(onCompleteMessage));
+            mCallbackTable.put(callback, onCompleteMessage);
+            mServiceBinder.getNetworkRegistrationState(mPhone.getPhoneId(), domain, callback);
         } catch (RemoteException e) {
             Rlog.e(TAG, "getNetworkRegistrationState RemoteException " + e);
+            mCallbackTable.remove(callback);
             onCompleteMessage.obj = new AsyncResult(onCompleteMessage.obj, null, e);
             onCompleteMessage.sendToTarget();
         }
@@ -119,13 +127,14 @@
     private class NetworkServiceConnection implements ServiceConnection {
         @Override
         public void onServiceConnected(ComponentName name, IBinder service) {
+            logd("service connected.");
             mServiceBinder = (INetworkService.Stub) service;
             mDeathRecipient = new RegManagerDeathRecipient(name);
             try {
                 mServiceBinder.linkToDeath(mDeathRecipient, 0);
                 mServiceBinder.createNetworkServiceProvider(mPhone.getPhoneId());
                 mServiceBinder.registerForNetworkRegistrationStateChanged(mPhone.getPhoneId(),
-                        new NetworkRegStateCallback(null));
+                        new NetworkRegStateCallback());
             } catch (RemoteException exception) {
                 // Remote exception means that the binder already died.
                 mDeathRecipient.binderDied();
@@ -143,23 +152,19 @@
     }
 
     private class NetworkRegStateCallback extends INetworkServiceCallback.Stub {
-        // Message only used upon onGetNetworkRegistrationStateComplete.
-        // If the callback is passed to listen to network state change,
-        // this message is null.
-        private final Message mOnCompleteMessage;
-
-        NetworkRegStateCallback(Message onCompleteMessage) {
-            mOnCompleteMessage = onCompleteMessage;
-        }
-
         @Override
         public void onGetNetworkRegistrationStateComplete(
                 int result, NetworkRegistrationState state) {
             logd("onGetNetworkRegistrationStateComplete result "
                     + result + " state " + state);
-            mOnCompleteMessage.arg1 = result;
-            mOnCompleteMessage.obj = new AsyncResult(mOnCompleteMessage.obj, state, null);
-            mOnCompleteMessage.sendToTarget();
+            Message onCompleteMessage = mCallbackTable.remove(this);
+            if (onCompleteMessage != null) {
+                onCompleteMessage.arg1 = result;
+                onCompleteMessage.obj = new AsyncResult(onCompleteMessage.obj, state, null);
+                onCompleteMessage.sendToTarget();
+            } else {
+                loge("onCompleteMessage is null");
+            }
         }
 
         @Override
diff --git a/src/java/com/android/internal/telephony/NewNitzStateMachine.java b/src/java/com/android/internal/telephony/NewNitzStateMachine.java
new file mode 100644
index 0000000..20c729f
--- /dev/null
+++ b/src/java/com/android/internal/telephony/NewNitzStateMachine.java
@@ -0,0 +1,491 @@
+/*
+ * 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 NewNitzStateMachine 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 sent to the time detector service. Used to rate-limit
+     * calls to the time detector.
+     */
+    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 #handleNetworkUnavailable()}. 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 NewTimeServiceHelper 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) {
+        this(phone,
+                new NewTimeServiceHelper(phone.getContext()),
+                new DeviceState(phone),
+                new TimeZoneLookupHelper());
+    }
+
+    @VisibleForTesting
+    public NewNitzStateMachine(GsmCdmaPhone phone, NewTimeServiceHelper 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 NewTimeServiceHelper.Listener() {
+            @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 handleNetworkUnavailable() {
+        if (DBG) {
+            Rlog.d(LOG_TAG, "handleNetworkUnavailable");
+        }
+
+        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 suggesting system clock because"
+                            + " gsm.ignore-nitz is set");
+                }
+                return;
+            }
+
+            // Validate the nitzTimeSignal to reject obviously bogus elapsedRealtime values.
+            try {
+                // Acquire the wake lock as we are reading the elapsed realtime clock below.
+                mWakeLock.acquire();
+
+                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;
+                }
+            } finally {
+                mWakeLock.release();
+            }
+
+            TimestampedValue<Long> newNitzTime = new TimestampedValue<>(
+                    nitzSignal.getReferenceTimeMillis(),
+                    nitzSignal.getValue().getCurrentTimeInMillis());
+
+            // Perform rate limiting: a NITZ signal received too close to a previous
+            // one will be disregarded unless there is a significant difference between the
+            // UTC times they represent.
+            if (mSavedNitzTime != null) {
+                int nitzUpdateSpacing = mDeviceState.getNitzUpdateSpacingMillis();
+                int nitzUpdateDiff = mDeviceState.getNitzUpdateDiffMillis();
+
+                // Calculate the elapsed time between the new signal and the last signal.
+                long elapsedRealtimeSinceLastSaved = newNitzTime.getReferenceTimeMillis()
+                        - mSavedNitzTime.getReferenceTimeMillis();
+
+                // Calculate the UTC difference between the time the two signals hold.
+                long utcTimeDifferenceMillis =
+                        newNitzTime.getValue() - mSavedNitzTime.getValue();
+
+                // Ideally the difference between elapsedRealtimeSinceLastSaved and
+                // utcTimeDifferenceMillis would be zero.
+                long millisGained = utcTimeDifferenceMillis - elapsedRealtimeSinceLastSaved;
+
+                if (elapsedRealtimeSinceLastSaved <= nitzUpdateSpacing
+                        && Math.abs(millisGained) <= nitzUpdateDiff) {
+                    if (DBG) {
+                        Rlog.d(LOG_TAG, "updateTimeFromNitz: not setting time. NITZ signal is"
+                                + " too similar to previous value received "
+                                + " mSavedNitzTime=" + mSavedNitzTime
+                                + ", nitzSignal=" + nitzSignal
+                                + ", nitzUpdateSpacing=" + nitzUpdateSpacing
+                                + ", nitzUpdateDiff=" + nitzUpdateDiff);
+                    }
+                    return;
+                }
+            }
+
+            String logMsg = "updateTimeFromNitz: suggesting system clock update"
+                    + " nitzSignal=" + nitzSignal
+                    + ", newNitzTime=" + newNitzTime
+                    + ", mSavedNitzTime= " + mSavedNitzTime;
+            if (DBG) {
+                Rlog.d(LOG_TAG, logMsg);
+            }
+            mTimeLog.log(logMsg);
+            mTimeServiceHelper.suggestDeviceTime(newNitzTime);
+            TelephonyMetrics.getInstance().writeNITZEvent(
+                    mPhone.getPhoneId(), newNitzTime.getValue());
+
+            // Save the last NITZ time signal that was suggested to enable rate limiting.
+            mSavedNitzTime = newNitzTime;
+        } 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 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/TimeServiceHelper.java b/src/java/com/android/internal/telephony/NewTimeServiceHelper.java
similarity index 79%
copy from src/java/com/android/internal/telephony/TimeServiceHelper.java
copy to src/java/com/android/internal/telephony/NewTimeServiceHelper.java
index 94b094f..1346c5f 100644
--- a/src/java/com/android/internal/telephony/TimeServiceHelper.java
+++ b/src/java/com/android/internal/telephony/NewTimeServiceHelper.java
@@ -17,6 +17,8 @@
 package com.android.internal.telephony;
 
 import android.app.AlarmManager;
+import android.app.timedetector.TimeDetector;
+import android.app.timedetector.TimeSignal;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
@@ -26,24 +28,20 @@
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.util.TimestampedValue;
 
 /**
  * An interface to various time / time zone detection behaviors that should be centralized into a
  * new service.
  */
 // Non-final to allow mocking.
-public class TimeServiceHelper {
+public class NewTimeServiceHelper {
 
     /**
      * 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);
@@ -53,13 +51,15 @@
 
     private final Context mContext;
     private final ContentResolver mCr;
+    private final TimeDetector mTimeDetector;
 
     private Listener mListener;
 
     /** Creates a TimeServiceHelper */
-    public TimeServiceHelper(Context context) {
+    public NewTimeServiceHelper(Context context) {
         mContext = context;
         mCr = context.getContentResolver();
+        mTimeDetector = context.getSystemService(TimeDetector.class);
     }
 
     /**
@@ -75,13 +75,6 @@
         }
         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) {
@@ -113,17 +106,6 @@
     }
 
     /**
-     * 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() {
@@ -145,17 +127,13 @@
     }
 
     /**
-     * Set the time and Send out a sticky broadcast so the system can determine
-     * if the time was set by the carrier.
+     * Suggest the time to the {@link TimeDetector}.
      *
-     * @param time time set by network
+     * @param signalTimeMillis the signal time as received from the 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);
+    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/NitzStateMachine.java b/src/java/com/android/internal/telephony/NitzStateMachine.java
index 1a365ee..6a5e47a 100644
--- a/src/java/com/android/internal/telephony/NitzStateMachine.java
+++ b/src/java/com/android/internal/telephony/NitzStateMachine.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2017 The Android Open Source Project
+ * Copyright 2018 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -18,38 +18,72 @@
 
 import android.content.ContentResolver;
 import android.content.Context;
-import android.os.PowerManager;
 import android.os.SystemProperties;
 import android.provider.Settings;
-import android.telephony.Rlog;
 import android.telephony.TelephonyManager;
-import android.text.TextUtils;
-import android.util.LocalLog;
-import android.util.TimeUtils;
+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.telephony.util.TimeStampedValue;
 import com.android.internal.util.IndentingPrintWriter;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.util.TimeZone;
 
 /**
  * {@hide}
  */
-// Non-final to allow mocking.
-public class NitzStateMachine {
+public interface NitzStateMachine {
+
+    /**
+     * Called when the network country is set on the Phone. Although set, the network country code
+     * may be invalid.
+     *
+     * @param countryChanged true when the country code is known to have changed, false if it
+     *     probably hasn't
+     */
+    void handleNetworkCountryCodeSet(boolean countryChanged);
+
+    /**
+     * Informs the {@link NitzStateMachine} that the network has become available.
+     */
+    void handleNetworkAvailable();
+
+    /**
+     * Informs the {@link NitzStateMachine} that the network has become unavailable.
+     */
+    void handleNetworkUnavailable();
+
+    /**
+     * Handle a new NITZ signal being received.
+     */
+    void handleNitzReceived(TimestampedValue<NitzData> nitzSignal);
+
+    /**
+     * Dumps the current in-memory state to the supplied PrintWriter.
+     */
+    void dumpState(PrintWriter pw);
+
+    /**
+     * Dumps the time / time zone logs to the supplied IndentingPrintWriter.
+     */
+    void dumpLogs(FileDescriptor fd, IndentingPrintWriter ipw, String[] args);
+
+    /**
+     * Returns the last NITZ data that was cached.
+     */
+    NitzData getCachedNitzData();
+
+    /**
+     * Returns the time zone ID from the most recent time that a time zone could be determined by
+     * this state machine.
+     */
+    String getSavedTimeZoneId();
 
     /**
      * A proxy over device state that allows things like system properties, system clock
      * to be faked for tests.
      */
     // Non-final to allow mocking.
-    public static class DeviceState {
+    class DeviceState {
         private static final int NITZ_UPDATE_SPACING_DEFAULT = 1000 * 60 * 10;
         private final int mNitzUpdateSpacing;
 
@@ -102,588 +136,4 @@
             return mTelephonyManager.getNetworkCountryIsoForPhone(mPhone.getPhoneId());
         }
     }
-
-    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.
-
-    /**
-     * Sometimes we get the NITZ time before we know what country we are in. We keep the time zone
-     * information from the NITZ string in mLatestNitzSignal so we can fix the time zone once we
-     * know the country.
-     */
-    private boolean mNeedCountryCodeForNitz = false;
-
-    private TimeStampedValue<NitzData> mLatestNitzSignal;
-    private boolean mGotCountryCode = false;
-    private String mSavedTimeZoneId;
-
-    /**
-     * Boolean is {@code true} if {@link #handleNitzReceived(TimeStampedValue)} has been called and
-     * was able to determine a time zone (which may not ultimately have been used due to user
-     * settings). Cleared by {@link #handleNetworkAvailable()} and
-     * {@link #handleNetworkUnavailable()}. The flag can be used when historic NITZ data may no
-     * longer be valid. {@code true} indicates it's not 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 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 NitzStateMachine(GsmCdmaPhone phone) {
-        this(phone,
-                new TimeServiceHelper(phone.getContext()),
-                new DeviceState(phone),
-                new TimeZoneLookupHelper());
-    }
-
-    @VisibleForTesting
-    public NitzStateMachine(GsmCdmaPhone phone, TimeServiceHelper 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 TimeServiceHelper.Listener() {
-            @Override
-            public void onTimeDetectionChange(boolean enabled) {
-                if (enabled) {
-                    handleAutoTimeEnabled();
-                }
-            }
-
-            @Override
-            public void onTimeZoneDetectionChange(boolean enabled) {
-                if (enabled) {
-                    handleAutoTimeZoneEnabled();
-                }
-            }
-        });
-    }
-
-    /**
-     * Called when the network country is set on the Phone. Although set, the network country code
-     * may be invalid.
-     *
-     * @param countryChanged true when the country code is known to have changed, false if it
-     *     probably hasn't
-     */
-    public void handleNetworkCountryCodeSet(boolean countryChanged) {
-        mGotCountryCode = true;
-
-        String isoCountryCode = mDeviceState.getNetworkCountryIsoForPhone();
-        if (!TextUtils.isEmpty(isoCountryCode)
-                && !mNitzTimeZoneDetectionSuccessful
-                && mTimeServiceHelper.isTimeZoneDetectionEnabled()) {
-            updateTimeZoneByNetworkCountryCode(isoCountryCode);
-        }
-
-        if (countryChanged || mNeedCountryCodeForNitz) {
-            // 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, "handleNetworkCountryCodeSet:"
-                        + " isTimeZoneSettingInitialized=" + isTimeZoneSettingInitialized
-                        + " mLatestNitzSignal=" + mLatestNitzSignal
-                        + " isoCountryCode=" + isoCountryCode);
-            }
-            String zoneId;
-            if (TextUtils.isEmpty(isoCountryCode) && mNeedCountryCodeForNitz) {
-                // Country code not found.  This is likely a test network.
-                // Get a TimeZone based only on the NITZ parameters (best guess).
-
-                // mNeedCountryCodeForNitz is only set to true when mLatestNitzSignal is set so
-                // there's no need to check mLatestNitzSignal == null.
-                OffsetResult lookupResult =
-                        mTimeZoneLookupHelper.lookupByNitz(mLatestNitzSignal.mValue);
-                if (DBG) {
-                    Rlog.d(LOG_TAG, "handleNetworkCountryCodeSet: guessZoneIdByNitz() returned"
-                            + " lookupResult=" + lookupResult);
-                }
-                zoneId = lookupResult != null ? lookupResult.zoneId : null;
-            } else if (mLatestNitzSignal == null) {
-                zoneId = null;
-                if (DBG) {
-                    Rlog.d(LOG_TAG, "handleNetworkCountryCodeSet: No cached NITZ data available,"
-                            + " not setting zone");
-                }
-            } else { // mLatestNitzSignal != null
-                if (nitzOffsetMightBeBogus(mLatestNitzSignal.mValue)
-                        && isTimeZoneSettingInitialized
-                        && !countryUsesUtc(isoCountryCode, mLatestNitzSignal)) {
-
-                    // This case means that (1) the device received an NITZ signal that could be
-                    // bogus due to having a zero offset from UTC, (2) the device has had a time
-                    // zone set explicitly and (3) the iso tells us the country is NOT one that uses
-                    // a zero offset. This is interpreted as being NITZ incorrectly reporting a
-                    // local time and not a UTC time. The zone is left as the current device's zone
-                    // setting, and the system clock may be adjusted by taking the NITZ time and
-                    // assuming the current zone setting is correct.
-
-                    TimeZone zone = TimeZone.getDefault();
-                    if (DBG) {
-                        Rlog.d(LOG_TAG, "handleNetworkCountryCodeSet: NITZ looks bogus, maybe using"
-                                + " current default zone to adjust the system clock,"
-                                + " mNeedCountryCodeForNitz=" + mNeedCountryCodeForNitz
-                                + " mLatestNitzSignal=" + mLatestNitzSignal
-                                + " zone=" + zone);
-                    }
-                    zoneId = zone.getID();
-
-                    if (mNeedCountryCodeForNitz) {
-                        NitzData nitzData = mLatestNitzSignal.mValue;
-                        try {
-                            // Acquire the wakelock as we're reading the elapsed realtime clock
-                            // here.
-                            mWakeLock.acquire();
-
-                            // Use the time that came with the NITZ offset that we think is bogus:
-                            // we just interpret it as local time.
-                            long ctm = nitzData.getCurrentTimeInMillis();
-                            long delayAdjustedCtm = ctm + (mTimeServiceHelper.elapsedRealtime()
-                                    - mLatestNitzSignal.mElapsedRealtime);
-                            long tzOffset = zone.getOffset(delayAdjustedCtm);
-                            if (DBG) {
-                                Rlog.d(LOG_TAG, "handleNetworkCountryCodeSet:"
-                                        + " tzOffset=" + tzOffset
-                                        + " delayAdjustedCtm="
-                                        + TimeUtils.logTimeOfDay(delayAdjustedCtm));
-                            }
-                            if (mTimeServiceHelper.isTimeDetectionEnabled()) {
-                                long timeZoneAdjustedCtm = delayAdjustedCtm - tzOffset;
-                                String msg = "handleNetworkCountryCodeSet: setting time"
-                                        + " timeZoneAdjustedCtm="
-                                        + TimeUtils.logTimeOfDay(timeZoneAdjustedCtm);
-                                setAndBroadcastNetworkSetTime(msg, timeZoneAdjustedCtm);
-                            } else {
-                                // Adjust the saved NITZ time to account for tzOffset.
-                                mSavedNitzTime = new TimeStampedValue<>(
-                                        mSavedNitzTime.mValue - tzOffset,
-                                        mSavedNitzTime.mElapsedRealtime);
-                                if (DBG) {
-                                    Rlog.d(LOG_TAG, "handleNetworkCountryCodeSet:"
-                                            + "adjusting time mSavedNitzTime=" + mSavedNitzTime);
-                                }
-                            }
-                        } finally {
-                            mWakeLock.release();
-                        }
-                    }
-                } else {
-                    NitzData nitzData = mLatestNitzSignal.mValue;
-                    OffsetResult lookupResult =
-                            mTimeZoneLookupHelper.lookupByNitzCountry(nitzData, isoCountryCode);
-                    if (DBG) {
-                        Rlog.d(LOG_TAG, "handleNetworkCountryCodeSet: using"
-                                + " guessZoneIdByNitzCountry(nitzData, isoCountryCode),"
-                                + " nitzData=" + nitzData
-                                + " isoCountryCode=" + isoCountryCode
-                                + " lookupResult=" + lookupResult);
-                    }
-                    zoneId = lookupResult != null ? lookupResult.zoneId : null;
-                }
-            }
-            final String tmpLog = "handleNetworkCountryCodeSet:"
-                    + " isTimeZoneSettingInitialized=" + isTimeZoneSettingInitialized
-                    + " mLatestNitzSignal=" + mLatestNitzSignal
-                    + " isoCountryCode=" + isoCountryCode
-                    + " mNeedCountryCodeForNitz=" + mNeedCountryCodeForNitz
-                    + " zoneId=" + zoneId;
-            mTimeZoneLog.log(tmpLog);
-
-            if (zoneId != null) {
-                Rlog.d(LOG_TAG, "handleNetworkCountryCodeSet: zoneId != null, zoneId=" + zoneId);
-                if (mTimeServiceHelper.isTimeZoneDetectionEnabled()) {
-                    setAndBroadcastNetworkSetTimeZone(zoneId);
-                } else {
-                    Rlog.d(LOG_TAG, "handleNetworkCountryCodeSet: skip changing zone as"
-                            + " isTimeZoneDetectionEnabled() is false");
-                }
-                if (mNeedCountryCodeForNitz) {
-                    mSavedTimeZoneId = zoneId;
-                }
-            } else {
-                Rlog.d(LOG_TAG, "handleNetworkCountryCodeSet: lookupResult == null, do nothing");
-            }
-            mNeedCountryCodeForNitz = false;
-        }
-    }
-
-    private boolean countryUsesUtc(
-            String isoCountryCode, TimeStampedValue<NitzData> nitzSignal) {
-        return mTimeZoneLookupHelper.countryUsesUtc(
-                isoCountryCode,
-                nitzSignal.mValue.getCurrentTimeInMillis());
-    }
-
-    /**
-     * Informs the {@link NitzStateMachine} that the network has become available.
-     */
-    public void handleNetworkAvailable() {
-        if (DBG) {
-            Rlog.d(LOG_TAG, "handleNetworkAvailable: mNitzTimeZoneDetectionSuccessful="
-                    + mNitzTimeZoneDetectionSuccessful
-                    + ", Setting mNitzTimeZoneDetectionSuccessful=false");
-        }
-        mNitzTimeZoneDetectionSuccessful = false;
-    }
-
-    /**
-     * Informs the {@link NitzStateMachine} that the network has become unavailable.
-     */
-    public void handleNetworkUnavailable() {
-        if (DBG) {
-            Rlog.d(LOG_TAG, "handleNetworkUnavailable");
-        }
-
-        mGotCountryCode = false;
-        mNitzTimeZoneDetectionSuccessful = false;
-    }
-
-    /**
-     * Returns {@code true} if the NITZ data looks like it might be incomplete or bogus, i.e. it has
-     * a zero offset from UTC with either no DST information available or a zero DST offset.
-     */
-    private static boolean nitzOffsetMightBeBogus(NitzData nitzData) {
-        return nitzData.getLocalOffsetMillis() == 0 && !nitzData.isDst();
-    }
-
-    /**
-     * Handle a new NITZ signal being received.
-     */
-    public void handleNitzReceived(TimeStampedValue<NitzData> nitzSignal) {
-        handleTimeZoneFromNitz(nitzSignal);
-        handleTimeFromNitz(nitzSignal);
-    }
-
-    private void handleTimeZoneFromNitz(TimeStampedValue<NitzData> nitzSignal) {
-        try {
-            NitzData newNitzData = nitzSignal.mValue;
-            String iso = mDeviceState.getNetworkCountryIsoForPhone();
-            String zoneId;
-            if (newNitzData.getEmulatorHostTimeZone() != null) {
-                zoneId = newNitzData.getEmulatorHostTimeZone().getID();
-            } else {
-                if (!mGotCountryCode) {
-                    zoneId = null;
-                } else if (!TextUtils.isEmpty(iso)) {
-                    OffsetResult lookupResult =
-                            mTimeZoneLookupHelper.lookupByNitzCountry(newNitzData, iso);
-                    zoneId = lookupResult != null ? lookupResult.zoneId : null;
-                } else {
-                    // We don't have a valid iso country code.  This is
-                    // most likely because we're on a test network that's
-                    // using a bogus MCC (eg, "001"), so get a TimeZone
-                    // based only on the NITZ parameters.
-                    OffsetResult lookupResult = mTimeZoneLookupHelper.lookupByNitz(newNitzData);
-                    if (DBG) {
-                        Rlog.d(LOG_TAG, "handleTimeZoneFromNitz: guessZoneIdByNitz returned"
-                                + " lookupResult=" + lookupResult);
-                    }
-                    zoneId = lookupResult != null ? lookupResult.zoneId : null;
-                }
-            }
-
-            if ((zoneId == null)
-                    || mLatestNitzSignal == null
-                    || offsetInfoDiffers(newNitzData, mLatestNitzSignal.mValue)) {
-                // We got the time before the country, or the zone has changed
-                // so we don't know how to identify the DST rules yet.  Save
-                // the information and hope to fix it up later.
-                mNeedCountryCodeForNitz = true;
-                mLatestNitzSignal = nitzSignal;
-            }
-
-            String tmpLog = "handleTimeZoneFromNitz: nitzSignal=" + nitzSignal
-                    + " zoneId=" + zoneId
-                    + " iso=" + iso + " mGotCountryCode=" + mGotCountryCode
-                    + " mNeedCountryCodeForNitz=" + mNeedCountryCodeForNitz
-                    + " isTimeZoneDetectionEnabled()="
-                    + mTimeServiceHelper.isTimeZoneDetectionEnabled();
-            if (DBG) {
-                Rlog.d(LOG_TAG, tmpLog);
-            }
-            mTimeZoneLog.log(tmpLog);
-
-            if (zoneId != null) {
-                if (mTimeServiceHelper.isTimeZoneDetectionEnabled()) {
-                    setAndBroadcastNetworkSetTimeZone(zoneId);
-                }
-                mNitzTimeZoneDetectionSuccessful = true;
-                mSavedTimeZoneId = zoneId;
-            }
-        } catch (RuntimeException ex) {
-            Rlog.e(LOG_TAG, "handleTimeZoneFromNitz: Processing NITZ data"
-                    + " nitzSignal=" + nitzSignal
-                    + " ex=" + ex);
-        }
-    }
-
-    private static boolean offsetInfoDiffers(NitzData one, NitzData two) {
-        return one.getLocalOffsetMillis() != two.getLocalOffsetMillis()
-                || one.isDst() != two.isDst();
-    }
-
-    private void handleTimeFromNitz(TimeStampedValue<NitzData> nitzSignal) {
-        try {
-            boolean ignoreNitz = mDeviceState.getIgnoreNitz();
-            if (ignoreNitz) {
-                Rlog.d(LOG_TAG,
-                        "handleTimeFromNitz: 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.mElapsedRealtime;
-                if (millisSinceNitzReceived < 0 || millisSinceNitzReceived > Integer.MAX_VALUE) {
-                    if (DBG) {
-                        Rlog.d(LOG_TAG, "handleTimeFromNitz: 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.mValue.getCurrentTimeInMillis() + millisSinceNitzReceived;
-                long gained = adjustedCurrentTimeMillis - mTimeServiceHelper.currentTimeMillis();
-
-                if (mTimeServiceHelper.isTimeDetectionEnabled()) {
-                    String logMsg = "handleTimeFromNitz:"
-                            + " nitzSignal=" + nitzSignal
-                            + " adjustedCurrentTimeMillis=" + adjustedCurrentTimeMillis
-                            + " millisSinceNitzReceived= " + millisSinceNitzReceived
-                            + " gained=" + gained;
-
-                    if (mSavedNitzTime == null) {
-                        logMsg += ": First update received.";
-                        setAndBroadcastNetworkSetTime(logMsg, adjustedCurrentTimeMillis);
-                    } else {
-                        long elapsedRealtimeSinceLastSaved = mTimeServiceHelper.elapsedRealtime()
-                                - mSavedNitzTime.mElapsedRealtime;
-                        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<>(
-                        adjustedCurrentTimeMillis, nitzSignal.mElapsedRealtime);
-            } finally {
-                mWakeLock.release();
-            }
-        } catch (RuntimeException ex) {
-            Rlog.e(LOG_TAG, "handleTimeFromNitz: 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.mValue + (elapsedRealtime - mSavedNitzTime.mElapsedRealtime);
-                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);
-        } else {
-            String iso = mDeviceState.getNetworkCountryIsoForPhone();
-            if (!TextUtils.isEmpty(iso)) {
-                updateTimeZoneByNetworkCountryCode(iso);
-            }
-        }
-    }
-
-    /**
-     * Dumps the current in-memory state to the supplied PrintWriter.
-     */
-    public void dumpState(PrintWriter pw) {
-        // Time Detection State
-        pw.println(" mSavedTime=" + mSavedNitzTime);
-
-        // Time Zone Detection State
-        pw.println(" mNeedCountryCodeForNitz=" + mNeedCountryCodeForNitz);
-        pw.println(" mLatestNitzSignal=" + mLatestNitzSignal);
-        pw.println(" mGotCountryCode=" + mGotCountryCode);
-        pw.println(" mSavedTimeZoneId=" + mSavedTimeZoneId);
-        pw.println(" mNitzTimeZoneDetectionSuccessful=" + mNitzTimeZoneDetectionSuccessful);
-
-        // Miscellaneous
-        pw.println(" mWakeLock=" + mWakeLock);
-        pw.flush();
-    }
-
-    /**
-     * Dumps the time / time zone logs to the supplied IndentingPrintWriter.
-     */
-    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 updateTimeZoneByNetworkCountryCode(String iso) {
-        CountryResult lookupResult = mTimeZoneLookupHelper.lookupByCountry(
-                iso, mTimeServiceHelper.currentTimeMillis());
-        if (lookupResult != null && lookupResult.allZonesHaveSameOffset) {
-            String logMsg = "updateTimeZoneByNetworkCountryCode: set time"
-                    + " lookupResult=" + lookupResult
-                    + " iso=" + iso;
-            if (DBG) {
-                Rlog.d(LOG_TAG, logMsg);
-            }
-            mTimeZoneLog.log(logMsg);
-            setAndBroadcastNetworkSetTimeZone(lookupResult.zoneId);
-        } else {
-            if (DBG) {
-                Rlog.d(LOG_TAG, "updateTimeZoneByNetworkCountryCode: no good zone for"
-                        + " iso=" + iso
-                        + " lookupResult=" + lookupResult);
-            }
-        }
-    }
-
-    /**
-     * Get the mNitzTimeZoneDetectionSuccessful flag value.
-     */
-    public boolean getNitzTimeZoneDetectionSuccessful() {
-        return mNitzTimeZoneDetectionSuccessful;
-    }
-
-    /**
-     * Returns the last NITZ data that was cached.
-     */
-    public NitzData getCachedNitzData() {
-        return mLatestNitzSignal != null ? mLatestNitzSignal.mValue : null;
-    }
-
-    /**
-     * Returns the time zone ID from the most recent time that a time zone could be determined by
-     * this state machine.
-     */
-    public String getSavedTimeZoneId() {
-        return mSavedTimeZoneId;
-    }
-
 }
diff --git a/src/java/com/android/internal/telephony/OemHookIndication.java b/src/java/com/android/internal/telephony/OemHookIndication.java
new file mode 100644
index 0000000..122a70e
--- /dev/null
+++ b/src/java/com/android/internal/telephony/OemHookIndication.java
@@ -0,0 +1,53 @@
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony;
+
+import android.hardware.radio.deprecated.V1_0.IOemHookIndication;
+import android.os.AsyncResult;
+
+import java.util.ArrayList;
+
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_OEM_HOOK_RAW;
+
+/**
+ * Class containing oem hook indication callbacks
+ */
+public class OemHookIndication extends IOemHookIndication.Stub {
+    RIL mRil;
+
+    public OemHookIndication(RIL ril) {
+        mRil = ril;
+    }
+
+    /**
+     * @param indicationType RadioIndicationType
+     * @param data Data sent by oem
+     */
+    public void oemHookRaw(int indicationType, ArrayList<Byte> data) {
+        mRil.processIndication(indicationType);
+
+        byte[] response = RIL.arrayListToPrimitiveArray(data);
+        if (RIL.RILJ_LOGD) {
+            mRil.unsljLogvRet(RIL_UNSOL_OEM_HOOK_RAW,
+                    com.android.internal.telephony.uicc.IccUtils.bytesToHexString(response));
+        }
+
+        if (mRil.mUnsolOemHookRawRegistrant != null) {
+            mRil.mUnsolOemHookRawRegistrant.notifyRegistrant(new AsyncResult(null, response, null));
+        }
+    }
+}
diff --git a/src/java/com/android/internal/telephony/OemHookResponse.java b/src/java/com/android/internal/telephony/OemHookResponse.java
new file mode 100644
index 0000000..0afeac8
--- /dev/null
+++ b/src/java/com/android/internal/telephony/OemHookResponse.java
@@ -0,0 +1,59 @@
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony;
+
+import android.hardware.radio.deprecated.V1_0.IOemHookResponse;
+import android.hardware.radio.V1_0.RadioError;
+import android.hardware.radio.V1_0.RadioResponseInfo;
+
+import java.util.ArrayList;
+
+/**
+ * Class containing oem hook response callbacks
+ */
+public class OemHookResponse extends IOemHookResponse.Stub {
+    RIL mRil;
+
+    public OemHookResponse(RIL ril) {
+        mRil = ril;
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param data Data returned by oem
+     */
+    public void sendRequestRawResponse(RadioResponseInfo responseInfo, ArrayList<Byte> data) {
+        RILRequest rr = mRil.processResponse(responseInfo);
+
+        if (rr != null) {
+            byte[] ret = null;
+            if (responseInfo.error == RadioError.NONE) {
+                ret = RIL.arrayListToPrimitiveArray(data);
+                RadioResponse.sendMessageResponse(rr.mResult, ret);
+            }
+            mRil.processResponseDone(rr, responseInfo, ret);
+        }
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param data Data returned by oem
+     */
+    public void sendRequestStringsResponse(RadioResponseInfo responseInfo, ArrayList<String> data) {
+        RadioResponse.responseStringArrayList(mRil, responseInfo, data);
+    }
+}
diff --git a/src/java/com/android/internal/telephony/OldNitzStateMachine.java b/src/java/com/android/internal/telephony/OldNitzStateMachine.java
new file mode 100644
index 0000000..bb43f1e
--- /dev/null
+++ b/src/java/com/android/internal/telephony/OldNitzStateMachine.java
@@ -0,0 +1,534 @@
+/*
+ * 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 #handleNetworkUnavailable()}. 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 handleNetworkUnavailable() {
+        if (DBG) {
+            Rlog.d(LOG_TAG, "handleNetworkUnavailable");
+        }
+
+        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/TimeServiceHelper.java b/src/java/com/android/internal/telephony/OldTimeServiceHelper.java
similarity index 98%
rename from src/java/com/android/internal/telephony/TimeServiceHelper.java
rename to src/java/com/android/internal/telephony/OldTimeServiceHelper.java
index 94b094f..9c7a763 100644
--- a/src/java/com/android/internal/telephony/TimeServiceHelper.java
+++ b/src/java/com/android/internal/telephony/OldTimeServiceHelper.java
@@ -32,7 +32,7 @@
  * new service.
  */
 // Non-final to allow mocking.
-public class TimeServiceHelper {
+public class OldTimeServiceHelper {
 
     /**
      * Callback interface for automatic detection enable/disable changes.
@@ -57,7 +57,7 @@
     private Listener mListener;
 
     /** Creates a TimeServiceHelper */
-    public TimeServiceHelper(Context context) {
+    public OldTimeServiceHelper(Context context) {
         mContext = context;
         mCr = context.getContentResolver();
     }
diff --git a/src/java/com/android/internal/telephony/Phone.java b/src/java/com/android/internal/telephony/Phone.java
index 3da6326..899f1fc 100644
--- a/src/java/com/android/internal/telephony/Phone.java
+++ b/src/java/com/android/internal/telephony/Phone.java
@@ -186,6 +186,7 @@
     private static final int EVENT_SRVCC_STATE_CHANGED              = 31;
     private static final int EVENT_INITIATE_SILENT_REDIAL           = 32;
     private static final int EVENT_RADIO_NOT_AVAILABLE              = 33;
+    private static final int EVENT_UNSOL_OEM_HOOK_RAW               = 34;
     protected static final int EVENT_GET_RADIO_CAPABILITY           = 35;
     protected static final int EVENT_SS                             = 36;
     private static final int EVENT_CONFIG_LCE                       = 37;
@@ -549,6 +550,7 @@
         if (getPhoneType() != PhoneConstants.PHONE_TYPE_SIP) {
             mCi.registerForSrvccStateChanged(this, EVENT_SRVCC_STATE_CHANGED, null);
         }
+        mCi.setOnUnsolOemHookRaw(this, EVENT_UNSOL_OEM_HOOK_RAW, null);
         mCi.startLceService(DEFAULT_REPORT_INTERVAL_MS, LCE_PULL_MODE,
                 obtainMessage(EVENT_CONFIG_LCE));
     }
@@ -683,6 +685,16 @@
                 }
                 break;
 
+            case EVENT_UNSOL_OEM_HOOK_RAW:
+                ar = (AsyncResult)msg.obj;
+                if (ar.exception == null) {
+                    byte[] data = (byte[])ar.result;
+                    mNotifier.notifyOemHookRawEventForSubscriber(getSubId(), data);
+                } else {
+                    Rlog.e(LOG_TAG, "OEM hook raw exception: " + ar.exception);
+                }
+                break;
+
             case EVENT_CONFIG_LCE:
                 ar = (AsyncResult) msg.obj;
                 if (ar.exception != null) {
@@ -1408,8 +1420,6 @@
      */
     public void registerForServiceStateChanged(
             Handler h, int what, Object obj) {
-        checkCorrectThread(h);
-
         mServiceStateRegistrants.add(h, what, obj);
     }
 
@@ -2046,6 +2056,45 @@
     }
 
     /**
+     * Invokes RIL_REQUEST_OEM_HOOK_RAW on RIL implementation.
+     *
+     * @param data The data for the request.
+     * @param response <strong>On success</strong>,
+     * (byte[])(((AsyncResult)response.obj).result)
+     * <strong>On failure</strong>,
+     * (((AsyncResult)response.obj).result) == null and
+     * (((AsyncResult)response.obj).exception) being an instance of
+     * com.android.internal.telephony.gsm.CommandException
+     *
+     * @see #invokeOemRilRequestRaw(byte[], android.os.Message)
+     * @deprecated OEM needs a vendor-extension hal and their apps should use that instead
+     */
+    @Deprecated
+    public void invokeOemRilRequestRaw(byte[] data, Message response) {
+        mCi.invokeOemRilRequestRaw(data, response);
+    }
+
+    /**
+     * Invokes RIL_REQUEST_OEM_HOOK_Strings on RIL implementation.
+     *
+     * @param strings The strings to make available as the request data.
+     * @param response <strong>On success</strong>, "response" bytes is
+     * made available as:
+     * (String[])(((AsyncResult)response.obj).result).
+     * <strong>On failure</strong>,
+     * (((AsyncResult)response.obj).result) == null and
+     * (((AsyncResult)response.obj).exception) being an instance of
+     * com.android.internal.telephony.gsm.CommandException
+     *
+     * @see #invokeOemRilRequestStrings(java.lang.String[], android.os.Message)
+     * @deprecated OEM needs a vendor-extension hal and their apps should use that instead
+     */
+    @Deprecated
+    public void invokeOemRilRequestStrings(String[] strings, Message response) {
+        mCi.invokeOemRilRequestStrings(strings, response);
+    }
+
+    /**
      * Read one of the NV items defined in {@link RadioNVItems} / {@code ril_nv_items.h}.
      * Used for device configuration by some CDMA operators.
      *
@@ -3017,6 +3066,19 @@
         return null;
     }
 
+    public int getCarrierIdListVersion() {
+        return TelephonyManager.UNKNOWN_CARRIER_ID_LIST_VERSION;
+    }
+
+    /**
+     *  Resets the Carrier Keys in the database. This involves 2 steps:
+     *  1. Delete the keys from the database.
+     *  2. Send an intent to download new Certificates.
+     */
+    public void resetCarrierKeysForImsiEncryption() {
+        return;
+    }
+
     /**
      * Return if UT capability of ImsPhone is enabled or not
      */
@@ -3070,6 +3132,13 @@
      * Returns the subscription id.
      */
     public int getSubId() {
+        if (SubscriptionController.getInstance() == null) {
+            // TODO b/78359408 getInstance sometimes returns null in Treehugger tests, which causes
+            // flakiness. Even though we haven't seen this crash in the wild we should keep this
+            // check in until we've figured out the root cause.
+            Rlog.e(LOG_TAG, "SubscriptionController.getInstance = null! Returning default subId");
+            return SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
+        }
         return SubscriptionController.getInstance().getSubIdUsingPhoneId(mPhoneId);
     }
 
@@ -3422,6 +3491,16 @@
         mCi.setAllowedCarriers(carriers, response);
     }
 
+    /** Sets the SignalStrength reporting criteria. */
+    public void setSignalStrengthReportingCriteria(int[] thresholds, int ran) {
+        // no-op default implementation
+    }
+
+    /** Sets the SignalStrength reporting criteria. */
+    public void setLinkCapacityReportingCriteria(int[] dlThresholds, int[] ulThresholds, int ran) {
+        // no-op default implementation
+    }
+
     /**
      * Get allowed carriers
      */
@@ -3578,6 +3657,10 @@
         }
     }
 
+    public void setCarrierTestOverride(String mccmnc, String imsi, String iccid, String gid1,
+            String gid2, String pnn, String spn) {
+    }
+
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("Phone: subId=" + getSubId());
         pw.println(" mPhoneId=" + mPhoneId);
diff --git a/src/java/com/android/internal/telephony/PhoneFactory.java b/src/java/com/android/internal/telephony/PhoneFactory.java
index 3653ea0..ba77747 100644
--- a/src/java/com/android/internal/telephony/PhoneFactory.java
+++ b/src/java/com/android/internal/telephony/PhoneFactory.java
@@ -155,7 +155,7 @@
                 Rlog.i(LOG_TAG, "ImsResolver: defaultImsPackage: " + defaultImsPackage);
                 sImsResolver = new ImsResolver(sContext, defaultImsPackage, numPhones,
                         isDynamicBinding);
-                sImsResolver.populateCacheAndStartBind();
+                sImsResolver.initPopulateCacheAndStartBind();
 
                 int[] networkModes = new int[numPhones];
                 sPhones = new Phone[numPhones];
@@ -307,6 +307,10 @@
         }
     }
 
+    public static SubscriptionInfoUpdater getSubscriptionInfoUpdater() {
+        return sSubInfoRecordUpdater;
+    }
+
     public static ImsResolver getImsResolver() {
         return sImsResolver;
     }
@@ -454,7 +458,10 @@
             pw.println("++++++++++++++++++++++++++++++++");
 
             try {
-                ((UiccProfile) phone.getIccCard()).dump(fd, pw, args);
+                UiccProfile uiccProfile = (UiccProfile) phone.getIccCard();
+                if (uiccProfile != null) {
+                    uiccProfile.dump(fd, pw, args);
+                }
             } catch (Exception e) {
                 e.printStackTrace();
             }
diff --git a/src/java/com/android/internal/telephony/PhoneInternalInterface.java b/src/java/com/android/internal/telephony/PhoneInternalInterface.java
index ae8fdad..91b7b28 100644
--- a/src/java/com/android/internal/telephony/PhoneInternalInterface.java
+++ b/src/java/com/android/internal/telephony/PhoneInternalInterface.java
@@ -894,4 +894,9 @@
      *        decrypt the permanent identity.
      */
     public ImsiEncryptionInfo getCarrierInfoForImsiEncryption(int keyType);
+
+    /**
+     * Resets the Carrier Keys, by deleting them from the database and sending a download intent.
+     */
+    public void resetCarrierKeysForImsiEncryption();
 }
diff --git a/src/java/com/android/internal/telephony/PhoneNotifier.java b/src/java/com/android/internal/telephony/PhoneNotifier.java
index 476394f..5c8faa6 100644
--- a/src/java/com/android/internal/telephony/PhoneNotifier.java
+++ b/src/java/com/android/internal/telephony/PhoneNotifier.java
@@ -68,4 +68,6 @@
     public void notifyDataActivationStateChanged(Phone sender, int activationState);
 
     public void notifyUserMobileDataStateChanged(Phone sender, boolean state);
+
+    public void notifyOemHookRawEventForSubscriber(int subId, byte[] rawData);
 }
diff --git a/src/java/com/android/internal/telephony/PhoneSubInfoController.java b/src/java/com/android/internal/telephony/PhoneSubInfoController.java
index ea17410..23eb0b8 100644
--- a/src/java/com/android/internal/telephony/PhoneSubInfoController.java
+++ b/src/java/com/android/internal/telephony/PhoneSubInfoController.java
@@ -19,6 +19,7 @@
 package com.android.internal.telephony;
 
 import static android.Manifest.permission.CALL_PRIVILEGED;
+import static android.Manifest.permission.MODIFY_PHONE_STATE;
 import static android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE;
 
 import android.app.AppOpsManager;
@@ -59,15 +60,15 @@
     }
 
     public String getDeviceIdForPhone(int phoneId, String callingPackage) {
-        if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
-                mContext, callingPackage, "getDeviceId")) {
-            return null;
-        }
         if (!SubscriptionManager.isValidPhoneId(phoneId)) {
             phoneId = 0;
         }
         final Phone phone = mPhone[phoneId];
         if (phone != null) {
+            if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
+                    mContext, phone.getSubId(), callingPackage, "getDeviceId")) {
+                return null;
+            }
             return phone.getDeviceId();
         } else {
             loge("getDeviceIdForPhone phone " + phoneId + " is null");
@@ -79,7 +80,7 @@
         Phone phone = getPhone(subId);
         if (phone != null) {
             if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
-                    mContext, callingPackage, "getNai")) {
+                    mContext, subId, callingPackage, "getNai")) {
                 return null;
             }
             return phone.getNai();
@@ -93,7 +94,7 @@
         Phone phone = getPhone(subId);
         if (phone != null) {
             if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
-                    mContext, callingPackage, "getImei")) {
+                    mContext, subId, callingPackage, "getImei")) {
                 return null;
             }
             return phone.getImei();
@@ -104,11 +105,12 @@
     }
 
     public ImsiEncryptionInfo getCarrierInfoForImsiEncryption(int subId, int keyType,
-            String callingPackage) {
+                                                              String callingPackage) {
         Phone phone = getPhone(subId);
         if (phone != null) {
             if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
-                    mContext, callingPackage, "getCarrierInfoForImsiEncryption")) {
+                    mContext, subId, callingPackage,
+                    "getCarrierInfoForImsiEncryption")) {
                 return null;
             }
             return phone.getCarrierInfoForImsiEncryption(keyType);
@@ -123,9 +125,10 @@
         Phone phone = getPhone(subId);
         if (phone != null) {
             if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
-                    mContext, callingPackage, "setCarrierInfoForImsiEncryption")) {
+                    mContext, subId, callingPackage, "setCarrierInfoForImsiEncryption")) {
                 return;
             }
+            enforceModifyPermission();
             phone.setCarrierInfoForImsiEncryption(imsiEncryptionInfo);
         } else {
             loge("setCarrierInfoForImsiEncryption phone is null for Subscription:" + subId);
@@ -133,6 +136,25 @@
         }
     }
 
+    /**
+     *  Resets the Carrier Keys in the database. This involves 2 steps:
+     *  1. Delete the keys from the database.
+     *  2. Send an intent to download new Certificates.
+     *  @param subId
+     *  @param callingPackage
+     */
+    public void resetCarrierKeysForImsiEncryption(int subId, String callingPackage) {
+        Phone phone = getPhone(subId);
+        if (phone != null) {
+            enforceModifyPermission();
+            phone.resetCarrierKeysForImsiEncryption();
+            return;
+        } else {
+            loge("resetCarrierKeysForImsiEncryption phone is null for Subscription:" + subId);
+            return;
+        }
+    }
+
 
     public String getDeviceSvn(String callingPackage) {
         return getDeviceSvnUsingSubId(getDefaultSubscription(), callingPackage);
@@ -142,7 +164,7 @@
         Phone phone = getPhone(subId);
         if (phone != null) {
             if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
-                    mContext, callingPackage, "getDeviceSvn")) {
+                    mContext, subId, callingPackage, "getDeviceSvn")) {
                 return null;
             }
             return phone.getDeviceSvn();
@@ -160,7 +182,7 @@
         Phone phone = getPhone(subId);
         if (phone != null) {
             if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
-                    mContext, callingPackage, "getSubscriberId")) {
+                    mContext, subId, callingPackage, "getSubscriberId")) {
                 return null;
             }
             return phone.getSubscriberId();
@@ -181,7 +203,7 @@
         Phone phone = getPhone(subId);
         if (phone != null) {
             if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
-                    mContext, callingPackage, "getIccSerialNumber")) {
+                    mContext, subId, callingPackage, "getIccSerialNumber")) {
                 return null;
             }
             return phone.getIccSerialNumber();
@@ -200,7 +222,7 @@
         if (phone != null) {
             // This is open to apps with WRITE_SMS.
             if (!TelephonyPermissions.checkCallingOrSelfReadPhoneNumber(
-                    mContext, callingPackage, "getLine1Number")) {
+                    mContext, subId, callingPackage, "getLine1Number")) {
                 return null;
             }
             return phone.getLine1Number();
@@ -218,7 +240,7 @@
         Phone phone = getPhone(subId);
         if (phone != null) {
             if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
-                    mContext, callingPackage, "getLine1AlphaTag")) {
+                    mContext, subId, callingPackage, "getLine1AlphaTag")) {
                 return null;
             }
             return phone.getLine1AlphaTag();
@@ -236,7 +258,7 @@
         Phone phone = getPhone(subId);
         if (phone != null) {
             if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
-                    mContext, callingPackage, "getMsisdn")) {
+                    mContext, subId, callingPackage, "getMsisdn")) {
                 return null;
             }
             return phone.getMsisdn();
@@ -254,7 +276,7 @@
         Phone phone = getPhone(subId);
         if (phone != null) {
             if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
-                    mContext, callingPackage, "getVoiceMailNumber")) {
+                    mContext, subId, callingPackage, "getVoiceMailNumber")) {
                 return null;
             }
             String number = PhoneNumberUtils.extractNetworkPortion(phone.getVoiceMailNumber());
@@ -292,7 +314,7 @@
         Phone phone = getPhone(subId);
         if (phone != null) {
             if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
-                    mContext, callingPackage, "getVoiceMailAlphaTag")) {
+                    mContext, subId, callingPackage, "getVoiceMailAlphaTag")) {
                 return null;
             }
             return phone.getVoiceMailAlphaTag();
@@ -328,6 +350,14 @@
         TelephonyPermissions.enforceCallingOrSelfCarrierPrivilege(subId, message);
     }
 
+    /**
+     * Make sure caller has modify phone state permission.
+     */
+    private void enforceModifyPermission() {
+        mContext.enforceCallingOrSelfPermission(MODIFY_PHONE_STATE,
+                "Requires MODIFY_PHONE_STATE");
+    }
+
     private int getDefaultSubscription() {
         return  PhoneFactory.getDefaultSubscription();
     }
@@ -465,15 +495,11 @@
         return uiccApp.getIccRecords().getIccSimChallengeResponse(authType, data);
     }
 
-    public String getGroupIdLevel1(String callingPackage) {
-        return getGroupIdLevel1ForSubscriber(getDefaultSubscription(), callingPackage);
-    }
-
     public String getGroupIdLevel1ForSubscriber(int subId, String callingPackage) {
         Phone phone = getPhone(subId);
         if (phone != null) {
             if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
-                    mContext, callingPackage, "getGroupIdLevel1")) {
+                    mContext, subId, callingPackage, "getGroupIdLevel1")) {
                 return null;
             }
             return phone.getGroupIdLevel1();
diff --git a/src/java/com/android/internal/telephony/RIL.java b/src/java/com/android/internal/telephony/RIL.java
index 4f4d011..9f7ea05 100644
--- a/src/java/com/android/internal/telephony/RIL.java
+++ b/src/java/com/android/internal/telephony/RIL.java
@@ -29,6 +29,7 @@
 import android.hardware.radio.V1_0.CellInfoCdma;
 import android.hardware.radio.V1_0.CellInfoGsm;
 import android.hardware.radio.V1_0.CellInfoLte;
+import android.hardware.radio.V1_0.CellInfoTdscdma;
 import android.hardware.radio.V1_0.CellInfoType;
 import android.hardware.radio.V1_0.CellInfoWcdma;
 import android.hardware.radio.V1_0.DataProfileInfo;
@@ -39,6 +40,7 @@
 import android.hardware.radio.V1_0.IRadio;
 import android.hardware.radio.V1_0.IccIo;
 import android.hardware.radio.V1_0.ImsSmsMessage;
+import android.hardware.radio.V1_0.IndicationFilter;
 import android.hardware.radio.V1_0.LceDataInfo;
 import android.hardware.radio.V1_0.MvnoType;
 import android.hardware.radio.V1_0.NvWriteItem;
@@ -51,6 +53,8 @@
 import android.hardware.radio.V1_0.SimApdu;
 import android.hardware.radio.V1_0.SmsWriteArgs;
 import android.hardware.radio.V1_0.UusInfo;
+import android.hardware.radio.V1_2.AccessNetwork;
+import android.hardware.radio.deprecated.V1_0.IOemHook;
 import android.net.ConnectivityManager;
 import android.net.KeepalivePacketData;
 import android.net.LinkProperties;
@@ -184,6 +188,9 @@
     RadioResponse mRadioResponse;
     RadioIndication mRadioIndication;
     volatile IRadio mRadioProxy = null;
+    OemHookResponse mOemHookResponse;
+    OemHookIndication mOemHookIndication;
+    volatile IOemHook mOemHookProxy = null;
     final AtomicLong mRadioProxyCookie = new AtomicLong(0);
     final RadioProxyDeathRecipient mRadioProxyDeathRecipient;
     final RilHandler mRilHandler;
@@ -326,6 +333,7 @@
 
     private void resetProxyAndRequestList() {
         mRadioProxy = null;
+        mOemHookProxy = null;
 
         // increment the cookie so that death notification can be ignored
         mRadioProxyCookie.incrementAndGet();
@@ -337,6 +345,7 @@
         clearRequestList(RADIO_NOT_AVAILABLE, false);
 
         getRadioProxy(null);
+        getOemHookProxy(null);
     }
 
     /** Returns a {@link IRadio} instance or null if the service is not available. */
@@ -384,6 +393,49 @@
         return mRadioProxy;
     }
 
+    /** Returns an {@link IOemHook} instance or null if the service is not available. */
+    @VisibleForTesting
+    public IOemHook getOemHookProxy(Message result) {
+        if (!mIsMobileNetworkSupported) {
+            if (RILJ_LOGV) riljLog("getOemHookProxy: Not calling getService(): wifi-only");
+            if (result != null) {
+                AsyncResult.forMessage(result, null,
+                        CommandException.fromRilErrno(RADIO_NOT_AVAILABLE));
+                result.sendToTarget();
+            }
+            return null;
+        }
+
+        if (mOemHookProxy != null) {
+            return mOemHookProxy;
+        }
+
+        try {
+            mOemHookProxy = IOemHook.getService(
+                    HIDL_SERVICE_NAME[mPhoneId == null ? 0 : mPhoneId], true);
+            if (mOemHookProxy != null) {
+                // not calling linkToDeath() as ril service runs in the same process and death
+                // notification for that should be sufficient
+                mOemHookProxy.setResponseFunctions(mOemHookResponse, mOemHookIndication);
+            } else {
+                riljLoge("getOemHookProxy: mOemHookProxy == null");
+            }
+        } catch (RemoteException | RuntimeException e) {
+            mOemHookProxy = null;
+            riljLoge("OemHookProxy getService/setResponseFunctions: " + e);
+        }
+
+        if (mOemHookProxy == null) {
+            if (result != null) {
+                AsyncResult.forMessage(result, null,
+                        CommandException.fromRilErrno(RADIO_NOT_AVAILABLE));
+                result.sendToTarget();
+            }
+        }
+
+        return mOemHookProxy;
+    }
+
     //***** Constructors
 
     public RIL(Context context, int preferredNetworkType, int cdmaSubscription) {
@@ -410,6 +462,8 @@
 
         mRadioResponse = new RadioResponse(this);
         mRadioIndication = new RadioIndication(this);
+        mOemHookResponse = new OemHookResponse(this);
+        mOemHookIndication = new OemHookIndication(this);
         mRilHandler = new RilHandler();
         mRadioProxyDeathRecipient = new RadioProxyDeathRecipient();
 
@@ -432,6 +486,7 @@
         // set radio callback; needed to set RadioIndication callback (should be done after
         // wakelock stuff is initialized above as callbacks are received on separate binder threads)
         getRadioProxy(null);
+        getOemHookProxy(null);
     }
 
     @Override
@@ -1134,18 +1189,27 @@
             try {
                 if (radioProxy12 == null) {
                     // IRadio V1.0
+
+                    // Getting data RAT here is just a workaround to support the older 1.0 vendor
+                    // RIL. The new data service interface passes access network type instead of
+                    // RAT for setup data request. It is impossible to convert access network
+                    // type back to RAT here, so we directly get the data RAT from phone.
+                    int dataRat = ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN;
+                    Phone phone = PhoneFactory.getPhone(mPhoneId);
+                    if (phone != null) {
+                        ServiceState ss = phone.getServiceState();
+                        if (ss != null) {
+                            dataRat = ss.getRilDataRadioTechnology();
+                        }
+                    }
                     if (RILJ_LOGD) {
                         riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
-                                + ",radioTechnology=unknown,isRoaming=" + isRoaming
+                                + ",dataRat=" + dataRat + ",isRoaming=" + isRoaming
                                 + ",allowRoaming=" + allowRoaming + "," + dataProfile);
                     }
-                    // The RAT field in setup data call request was never used before. Starting from
-                    // P, the new API passes in access network type instead of RAT. Since it's
-                    // not possible to convert access network type back to RAT, but we still need to
-                    // support the 1.0 API, we passed in unknown RAT to the modem. And modem must
-                    // setup the data call on its current camped network.
-                    radioProxy.setupDataCall(rr.mSerial, ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN,
-                            dpi, dataProfile.isModemCognitive(), allowRoaming, isRoaming);
+
+                    radioProxy.setupDataCall(rr.mSerial, dataRat, dpi,
+                            dataProfile.isModemCognitive(), allowRoaming, isRoaming);
                 } else {
                     // IRadio V1.2
                     ArrayList<String> addresses = new ArrayList<>();
@@ -1930,6 +1994,59 @@
     }
 
     @Override
+    public void invokeOemRilRequestRaw(byte[] data, Message response) {
+        IOemHook oemHookProxy = getOemHookProxy(response);
+        if (oemHookProxy != null) {
+            RILRequest rr = obtainRequest(RIL_REQUEST_OEM_HOOK_RAW, response,
+                    mRILDefaultWorkSource);
+
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+                        + "[" + IccUtils.bytesToHexString(data) + "]");
+            }
+
+            try {
+                oemHookProxy.sendRequestRaw(rr.mSerial, primitiveArrayToArrayList(data));
+            } catch (RemoteException | RuntimeException e) {
+                handleRadioProxyExceptionForRR(rr, "invokeOemRilRequestRaw", e);
+            }
+        } else {
+            // OEM Hook service is disabled for P and later devices.
+            // Deprecated OEM Hook APIs will perform dummy before being removed.
+            if (RILJ_LOGD) riljLog("Radio Oem Hook Service is disabled for P and later devices. ");
+        }
+    }
+
+    @Override
+    public void invokeOemRilRequestStrings(String[] strings, Message result) {
+        IOemHook oemHookProxy = getOemHookProxy(result);
+        if (oemHookProxy != null) {
+            RILRequest rr = obtainRequest(RIL_REQUEST_OEM_HOOK_STRINGS, result,
+                    mRILDefaultWorkSource);
+
+            String logStr = "";
+            for (int i = 0; i < strings.length; i++) {
+                logStr = logStr + strings[i] + " ";
+            }
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest) + " strings = "
+                        + logStr);
+            }
+
+            try {
+                oemHookProxy.sendRequestStrings(rr.mSerial,
+                        new ArrayList<String>(Arrays.asList(strings)));
+            } catch (RemoteException | RuntimeException e) {
+                handleRadioProxyExceptionForRR(rr, "invokeOemRilRequestStrings", e);
+            }
+        } else {
+            // OEM Hook service is disabled for P and later devices.
+            // Deprecated OEM Hook APIs will perform dummy before being removed.
+            if (RILJ_LOGD) riljLog("Radio Oem Hook Service is disabled for P and later devices. ");
+        }
+    }
+
+    @Override
     public void setSuppServiceNotifications(boolean enable, Message result) {
         IRadio radioProxy = getRadioProxy(result);
         if (radioProxy != null) {
@@ -2982,7 +3099,7 @@
             try {
                 radioProxy.sendImsSms(rr.mSerial, msg);
                 mMetrics.writeRilSendSms(mPhoneId, rr.mSerial, SmsSession.Event.Tech.SMS_IMS,
-                        SmsSession.Event.Format.SMS_FORMAT_3GPP);
+                        SmsSession.Event.Format.SMS_FORMAT_3GPP2);
             } catch (RemoteException | RuntimeException e) {
                 handleRadioProxyExceptionForRR(rr, "sendImsCdmaSms", e);
             }
@@ -3602,15 +3719,105 @@
                 riljLog(rr.serialString() + "> " + requestToString(rr.mRequest) + " " + filter);
             }
 
-            try {
-                radioProxy.setIndicationFilter(rr.mSerial, filter);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(rr, "setIndicationFilter", e);
+            android.hardware.radio.V1_2.IRadio radioProxy12 =
+                    android.hardware.radio.V1_2.IRadio.castFrom(radioProxy);
+
+            if (radioProxy12 != null) {
+                try {
+                    radioProxy12.setIndicationFilter_1_2(rr.mSerial, filter);
+                } catch (RemoteException | RuntimeException e) {
+                    handleRadioProxyExceptionForRR(rr, "setIndicationFilter_1_2", e);
+                }
+            } else {
+                try {
+                    int filter10 = filter & IndicationFilter.ALL;
+                    radioProxy.setIndicationFilter(rr.mSerial, filter10);
+                } catch (RemoteException | RuntimeException e) {
+                    handleRadioProxyExceptionForRR(rr, "setIndicationFilter", e);
+                }
             }
         }
     }
 
     @Override
+    public void setSignalStrengthReportingCriteria(int hysteresisMs, int hysteresisDb,
+            int[] thresholdsDbm, int ran, Message result) {
+        IRadio radioProxy = getRadioProxy(result);
+        if (radioProxy != null) {
+            android.hardware.radio.V1_2.IRadio radioProxy12 =
+                    android.hardware.radio.V1_2.IRadio.castFrom(radioProxy);
+            if (radioProxy12 == null) {
+                riljLoge("setSignalStrengthReportingCriteria ignored. RadioProxy 1.2 is null!");
+                return;
+            }
+
+            RILRequest rr = obtainRequest(RIL_REQUEST_SET_SIGNAL_STRENGTH_REPORTING_CRITERIA,
+                    result, mRILDefaultWorkSource);
+
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+            }
+
+            try {
+                radioProxy12.setSignalStrengthReportingCriteria(rr.mSerial, hysteresisMs,
+                        hysteresisDb, primitiveArrayToArrayList(thresholdsDbm),
+                        convertRanToHalRan(ran));
+            } catch (RemoteException | RuntimeException e) {
+                handleRadioProxyExceptionForRR(rr, "setSignalStrengthReportingCriteria", e);
+            }
+        }
+    }
+
+    @Override
+    public void setLinkCapacityReportingCriteria(int hysteresisMs, int hysteresisDlKbps,
+            int hysteresisUlKbps, int[] thresholdsDlKbps, int[] thresholdsUlKbps, int ran,
+            Message result) {
+        IRadio radioProxy = getRadioProxy(result);
+        if (radioProxy != null) {
+            android.hardware.radio.V1_2.IRadio radioProxy12 =
+                    android.hardware.radio.V1_2.IRadio.castFrom(radioProxy);
+            if (radioProxy12 == null) {
+                riljLoge("setLinkCapacityReportingCriteria ignored. RadioProxy 1.2 is null!");
+                return;
+            }
+
+            RILRequest rr = obtainRequest(RIL_REQUEST_SET_LINK_CAPACITY_REPORTING_CRITERIA,
+                    result, mRILDefaultWorkSource);
+
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+            }
+
+            try {
+                radioProxy12.setLinkCapacityReportingCriteria(rr.mSerial, hysteresisMs,
+                        hysteresisDlKbps, hysteresisUlKbps,
+                        primitiveArrayToArrayList(thresholdsDlKbps),
+                        primitiveArrayToArrayList(thresholdsUlKbps), convertRanToHalRan(ran));
+            } catch (RemoteException | RuntimeException e) {
+                handleRadioProxyExceptionForRR(rr, "setLinkCapacityReportingCriteria", e);
+            }
+        }
+    }
+
+    private static int convertRanToHalRan(int radioAccessNetwork) {
+        switch (radioAccessNetwork) {
+            case AccessNetworkType.GERAN:
+                return AccessNetwork.GERAN;
+            case AccessNetworkType.UTRAN:
+                return AccessNetwork.UTRAN;
+            case AccessNetworkType.EUTRAN:
+                return AccessNetwork.EUTRAN;
+            case AccessNetworkType.CDMA2000:
+                return AccessNetwork.CDMA2000;
+            case AccessNetworkType.IWLAN:
+                return AccessNetwork.IWLAN;
+            case AccessNetworkType.UNKNOWN:
+            default:
+                return 0;
+        }
+    }
+
+    @Override
     public void setSimCardPower(int state, Message result) {
         IRadio radioProxy = getRadioProxy(result);
         if (radioProxy != null) {
@@ -3745,6 +3952,7 @@
             appendPrimitiveArrayToArrayList(
                     packetData.dstAddress.getAddress(), req.destinationAddress);
             req.destinationPort = packetData.dstPort;
+            req.maxKeepaliveIntervalMillis = intervalMillis;
 
             radioProxy11.startKeepalive(rr.mSerial, req);
         } catch (RemoteException | RuntimeException e) {
@@ -4545,6 +4753,10 @@
                 return "DATA_CALL_LIST";
             case RIL_REQUEST_RESET_RADIO:
                 return "RESET_RADIO";
+            case RIL_REQUEST_OEM_HOOK_RAW:
+                return "OEM_HOOK_RAW";
+            case RIL_REQUEST_OEM_HOOK_STRINGS:
+                return "OEM_HOOK_STRINGS";
             case RIL_REQUEST_SCREEN_STATE:
                 return "SCREEN_STATE";
             case RIL_REQUEST_SET_SUPP_SVC_NOTIFICATION:
@@ -4716,6 +4928,10 @@
                 return "RIL_REQUEST_START_KEEPALIVE";
             case RIL_REQUEST_STOP_KEEPALIVE:
                 return "RIL_REQUEST_STOP_KEEPALIVE";
+            case RIL_REQUEST_SET_SIGNAL_STRENGTH_REPORTING_CRITERIA:
+                return "RIL_REQUEST_SET_SIGNAL_STRENGTH_REPORTING_CRITERIA";
+            case RIL_REQUEST_SET_LINK_CAPACITY_REPORTING_CRITERIA:
+                return "RIL_REQUEST_SET_LINK_CAPACITY_REPORTING_CRITERIA";
             default: return "<unknown request>";
         }
     }
@@ -4778,6 +4994,8 @@
                 return "UNSOL_CDMA_OTA_PROVISION_STATUS";
             case RIL_UNSOL_CDMA_INFO_REC:
                 return "UNSOL_CDMA_INFO_REC";
+            case RIL_UNSOL_OEM_HOOK_RAW:
+                return "UNSOL_OEM_HOOK_RAW";
             case RIL_UNSOL_RINGBACK_TONE:
                 return "UNSOL_RINGBACK_TONE";
             case RIL_UNSOL_RESEND_INCALL_MUTE:
@@ -4822,6 +5040,8 @@
                 return "RIL_UNSOL_ICC_SLOT_STATUS";
             case RIL_UNSOL_KEEPALIVE_STATUS:
                 return "RIL_UNSOL_KEEPALIVE_STATUS";
+            case RIL_UNSOL_PHYSICAL_CHANNEL_CONFIG:
+                return "RIL_UNSOL_PHYSICAL_CHANNEL_CONFIG";
             default:
                 return "<unknown response>";
         }
@@ -4917,6 +5137,15 @@
         return arrayList;
     }
 
+    /** Convert a primitive int array to an ArrayList<Integer>. */
+    public static ArrayList<Integer> primitiveArrayToArrayList(int[] arr) {
+        ArrayList<Integer> arrayList = new ArrayList<>(arr.length);
+        for (int i : arr) {
+            arrayList.add(i);
+        }
+        return arrayList;
+    }
+
     /** Convert an ArrayList of Bytes to an exactly-sized primitive array */
     public static byte[] arrayListToPrimitiveArray(ArrayList<Byte> bytes) {
         byte[] ret = new byte[bytes.size()];
@@ -5010,12 +5239,12 @@
         p.writeInt(CellIdentity.TYPE_GSM);
         p.writeString(mcc);
         p.writeString(mnc);
+        p.writeString(al);
+        p.writeString(as);
         p.writeInt(lac);
         p.writeInt(cid);
         p.writeInt(arfcn);
         p.writeInt(bsic);
-        p.writeString(al);
-        p.writeString(as);
         p.writeInt(ss);
         p.writeInt(ber);
         p.writeInt(ta);
@@ -5034,13 +5263,13 @@
         p.writeInt(CellIdentity.TYPE_LTE);
         p.writeString(mcc);
         p.writeString(mnc);
+        p.writeString(al);
+        p.writeString(as);
         p.writeInt(ci);
         p.writeInt(pci);
         p.writeInt(tac);
         p.writeInt(earfcn);
         p.writeInt(bandwidth);
-        p.writeString(al);
-        p.writeString(as);
         p.writeInt(ss);
         p.writeInt(rsrp);
         p.writeInt(rsrq);
@@ -5051,18 +5280,37 @@
 
     private static void writeToParcelForWcdma(
             Parcel p, int lac, int cid, int psc, int uarfcn, String mcc, String mnc,
-            String al, String as, int ss, int ber) {
+            String al, String as, int ss, int ber, int rscp, int ecno) {
         p.writeInt(CellIdentity.TYPE_WCDMA);
         p.writeString(mcc);
         p.writeString(mnc);
+        p.writeString(al);
+        p.writeString(as);
         p.writeInt(lac);
         p.writeInt(cid);
         p.writeInt(psc);
         p.writeInt(uarfcn);
-        p.writeString(al);
-        p.writeString(as);
         p.writeInt(ss);
         p.writeInt(ber);
+        p.writeInt(rscp);
+        p.writeInt(ecno);
+    }
+
+    private static void writeToParcelForTdscdma(
+            Parcel p, int lac, int cid, int cpid, int uarfcn, String mcc, String mnc,
+            String al, String as, int ss, int ber, int rscp) {
+        p.writeInt(CellIdentity.TYPE_TDSCDMA);
+        p.writeString(mcc);
+        p.writeString(mnc);
+        p.writeString(al);
+        p.writeString(as);
+        p.writeInt(lac);
+        p.writeInt(cid);
+        p.writeInt(cpid);
+        p.writeInt(uarfcn);
+        p.writeInt(ss);
+        p.writeInt(ber);
+        p.writeInt(rscp);
     }
 
     /**
@@ -5156,10 +5404,29 @@
                             EMPTY_ALPHA_LONG,
                             EMPTY_ALPHA_SHORT,
                             cellInfoWcdma.signalStrengthWcdma.signalStrength,
-                            cellInfoWcdma.signalStrengthWcdma.bitErrorRate);
+                            cellInfoWcdma.signalStrengthWcdma.bitErrorRate,
+                            Integer.MAX_VALUE,
+                            Integer.MAX_VALUE);
                     break;
                 }
 
+                case CellInfoType.TD_SCDMA: {
+                    CellInfoTdscdma cellInfoTdscdma = record.tdscdma.get(0);
+                    writeToParcelForTdscdma(
+                            p,
+                            cellInfoTdscdma.cellIdentityTdscdma.lac,
+                            cellInfoTdscdma.cellIdentityTdscdma.cid,
+                            cellInfoTdscdma.cellIdentityTdscdma.cpid,
+                            Integer.MAX_VALUE,
+                            cellInfoTdscdma.cellIdentityTdscdma.mcc,
+                            cellInfoTdscdma.cellIdentityTdscdma.mnc,
+                            EMPTY_ALPHA_LONG,
+                            EMPTY_ALPHA_SHORT,
+                            Integer.MAX_VALUE,
+                            Integer.MAX_VALUE,
+                            convertTdscdmaRscpTo1_2(cellInfoTdscdma.signalStrengthTdscdma.rscp));
+                    break;
+                }
                 default:
                     throw new RuntimeException("unexpected cellinfotype: " + record.cellInfoType);
             }
@@ -5264,7 +5531,28 @@
                             cellInfoWcdma.cellIdentityWcdma.operatorNames.alphaLong,
                             cellInfoWcdma.cellIdentityWcdma.operatorNames.alphaShort,
                             cellInfoWcdma.signalStrengthWcdma.base.signalStrength,
-                            cellInfoWcdma.signalStrengthWcdma.base.bitErrorRate);
+                            cellInfoWcdma.signalStrengthWcdma.base.bitErrorRate,
+                            cellInfoWcdma.signalStrengthWcdma.rscp,
+                            cellInfoWcdma.signalStrengthWcdma.ecno);
+                    break;
+                }
+
+                case CellInfoType.TD_SCDMA: {
+                    android.hardware.radio.V1_2.CellInfoTdscdma cellInfoTdscdma =
+                            record.tdscdma.get(0);
+                    writeToParcelForTdscdma(
+                            p,
+                            cellInfoTdscdma.cellIdentityTdscdma.base.lac,
+                            cellInfoTdscdma.cellIdentityTdscdma.base.cid,
+                            cellInfoTdscdma.cellIdentityTdscdma.base.cpid,
+                            cellInfoTdscdma.cellIdentityTdscdma.uarfcn,
+                            cellInfoTdscdma.cellIdentityTdscdma.base.mcc,
+                            cellInfoTdscdma.cellIdentityTdscdma.base.mnc,
+                            cellInfoTdscdma.cellIdentityTdscdma.operatorNames.alphaLong,
+                            cellInfoTdscdma.cellIdentityTdscdma.operatorNames.alphaShort,
+                            cellInfoTdscdma.signalStrengthTdscdma.signalStrength,
+                            cellInfoTdscdma.signalStrengthTdscdma.bitErrorRate,
+                            cellInfoTdscdma.signalStrengthTdscdma.rscp);
                     break;
                 }
 
@@ -5281,19 +5569,22 @@
         return response;
     }
 
+    private static int convertTdscdmaRscpTo1_2(int rscp) {
+        // The HAL 1.0 range is 25..120; the ASU/ HAL 1.2 range is 0..96;
+        // yes, this means the range in 1.0 cannot express -24dBm = 96
+        if (rscp >= 25 && rscp <= 120) {
+            // First we flip the sign to convert from the HALs -rscp to the actual RSCP value.
+            int rscpDbm = -rscp;
+            // Then to convert from RSCP to ASU, we apply the offset which aligns 0 ASU to -120dBm.
+            return rscpDbm + 120;
+        }
+        return Integer.MAX_VALUE;
+    }
+
     /** Convert HAL 1.0 Signal Strength to android SignalStrength */
     @VisibleForTesting
     public static SignalStrength convertHalSignalStrength(
             android.hardware.radio.V1_0.SignalStrength signalStrength) {
-        int tdscdmaRscp_1_2 = 255; // 255 is the value for unknown/unreported ASU.
-        // The HAL 1.0 range is 25..120; the ASU/ HAL 1.2 range is 0..96;
-        // yes, this means the range in 1.0 cannot express -24dBm = 96
-        if (signalStrength.tdScdma.rscp >= 25 && signalStrength.tdScdma.rscp <= 120) {
-            // First we flip the sign to convert from the HALs -rscp to the actual RSCP value.
-            int rscpDbm = -signalStrength.tdScdma.rscp;
-            // Then to convert from RSCP to ASU, we apply the offset which aligns 0 ASU to -120dBm.
-            tdscdmaRscp_1_2 = rscpDbm + 120;
-        }
         return new SignalStrength(
                 signalStrength.gw.signalStrength,
                 signalStrength.gw.bitErrorRate,
@@ -5307,7 +5598,7 @@
                 signalStrength.lte.rsrq,
                 signalStrength.lte.rssnr,
                 signalStrength.lte.cqi,
-                tdscdmaRscp_1_2);
+                convertTdscdmaRscpTo1_2(signalStrength.tdScdma.rscp));
     }
 
     /** Convert HAL 1.2 Signal Strength to android SignalStrength */
diff --git a/src/java/com/android/internal/telephony/RILRequest.java b/src/java/com/android/internal/telephony/RILRequest.java
index c0a0b70..d20ce22 100644
--- a/src/java/com/android/internal/telephony/RILRequest.java
+++ b/src/java/com/android/internal/telephony/RILRequest.java
@@ -86,7 +86,8 @@
             rr = new RILRequest();
         }
 
-        rr.mSerial = sNextSerial.getAndIncrement();
+        // Increment serial number. Wrap to 0 when reaching Integer.MAX_VALUE.
+        rr.mSerial = sNextSerial.getAndUpdate(n -> ((n + 1) % Integer.MAX_VALUE));
 
         rr.mRequest = request;
         rr.mResult = result;
@@ -151,9 +152,9 @@
     }
 
     static void resetSerial() {
-        // use a random so that on recovery we probably don't mix old requests
+        // Use a non-negative random number so that on recovery we probably don't mix old requests
         // with new.
-        sNextSerial.set(sRandom.nextInt());
+        sNextSerial.set(sRandom.nextInt(Integer.MAX_VALUE));
     }
 
     String serialString() {
@@ -161,9 +162,9 @@
         StringBuilder sb = new StringBuilder(8);
         String sn;
 
-        long adjustedSerial = (((long) mSerial) - Integer.MIN_VALUE) % 10000;
-
-        sn = Long.toString(adjustedSerial);
+        // Truncate mSerial to a number with maximum 4 digits.
+        int adjustedSerial = mSerial % 10000;
+        sn = Integer.toString(adjustedSerial);
 
         //sb.append("J[");
         sb.append('[');
diff --git a/src/java/com/android/internal/telephony/RadioConfig.java b/src/java/com/android/internal/telephony/RadioConfig.java
index 02c7177..764fc10 100644
--- a/src/java/com/android/internal/telephony/RadioConfig.java
+++ b/src/java/com/android/internal/telephony/RadioConfig.java
@@ -39,6 +39,7 @@
 import com.android.internal.telephony.uicc.IccSlotStatus;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.concurrent.atomic.AtomicLong;
 
 /**
@@ -267,7 +268,8 @@
                     mDefaultWorkSource);
 
             if (DBG) {
-                logd(rr.serialString() + "> " + requestToString(rr.mRequest));
+                logd(rr.serialString() + "> " + requestToString(rr.mRequest)
+                        + " " + Arrays.toString(physicalSlots));
             }
 
             try {
@@ -287,7 +289,7 @@
         return arrayList;
     }
 
-    private static String requestToString(int request) {
+    static String requestToString(int request) {
         switch (request) {
             case RIL_REQUEST_GET_SLOT_STATUS:
                 return "GET_SLOT_STATUS";
diff --git a/src/java/com/android/internal/telephony/RadioConfigIndication.java b/src/java/com/android/internal/telephony/RadioConfigIndication.java
index b817f30..686282c 100644
--- a/src/java/com/android/internal/telephony/RadioConfigIndication.java
+++ b/src/java/com/android/internal/telephony/RadioConfigIndication.java
@@ -19,6 +19,7 @@
 import android.hardware.radio.config.V1_0.IRadioConfigIndication;
 import android.hardware.radio.config.V1_0.SimSlotStatus;
 import android.os.AsyncResult;
+import android.telephony.Rlog;
 
 import com.android.internal.telephony.uicc.IccSlotStatus;
 
@@ -29,6 +30,7 @@
  */
 public class RadioConfigIndication extends IRadioConfigIndication.Stub {
     private final RadioConfig mRadioConfig;
+    private static final String TAG = "RadioConfigIndication";
 
     public RadioConfigIndication(RadioConfig radioConfig) {
         mRadioConfig = radioConfig;
@@ -39,7 +41,7 @@
      */
     public void simSlotsStatusChanged(int indicationType, ArrayList<SimSlotStatus> slotStatus) {
         ArrayList<IccSlotStatus> ret = RadioConfig.convertHalSlotStatus(slotStatus);
-
+        Rlog.d(TAG, "[UNSL]< " + " UNSOL_SIM_SLOT_STATUS_CHANGED " + ret.toString());
         if (mRadioConfig.mSimSlotStatusRegistrant != null) {
             mRadioConfig.mSimSlotStatusRegistrant.notifyRegistrant(
                     new AsyncResult(null, ret, null));
diff --git a/src/java/com/android/internal/telephony/RadioConfigResponse.java b/src/java/com/android/internal/telephony/RadioConfigResponse.java
index f3d48ad..8177b2d 100644
--- a/src/java/com/android/internal/telephony/RadioConfigResponse.java
+++ b/src/java/com/android/internal/telephony/RadioConfigResponse.java
@@ -20,6 +20,7 @@
 import android.hardware.radio.V1_0.RadioResponseInfo;
 import android.hardware.radio.config.V1_0.IRadioConfigResponse;
 import android.hardware.radio.config.V1_0.SimSlotStatus;
+import android.telephony.Rlog;
 
 import com.android.internal.telephony.uicc.IccSlotStatus;
 
@@ -30,6 +31,7 @@
  */
 public class RadioConfigResponse extends IRadioConfigResponse.Stub {
     private final RadioConfig mRadioConfig;
+    private static final String TAG = "RadioConfigResponse";
 
     public RadioConfigResponse(RadioConfig radioConfig) {
         mRadioConfig = radioConfig;
@@ -47,9 +49,17 @@
             if (responseInfo.error == RadioError.NONE) {
                 // send response
                 RadioResponse.sendMessageResponse(rr.mResult, ret);
+                Rlog.d(TAG, rr.serialString() + "< "
+                        + mRadioConfig.requestToString(rr.mRequest) + " " + ret.toString());
             } else {
                 rr.onError(responseInfo.error, ret);
+                Rlog.e(TAG, rr.serialString() + "< "
+                        + mRadioConfig.requestToString(rr.mRequest) + " error "
+                        + responseInfo.error);
             }
+
+        } else {
+            Rlog.e(TAG, "getSimSlotsStatusResponse: Error " + responseInfo.toString());
         }
     }
 
@@ -63,9 +73,18 @@
             if (responseInfo.error == RadioError.NONE) {
                 // send response
                 RadioResponse.sendMessageResponse(rr.mResult, null);
+                Rlog.d(TAG, rr.serialString() + "< "
+                        + mRadioConfig.requestToString(rr.mRequest));
             } else {
                 rr.onError(responseInfo.error, null);
+                Rlog.e(TAG, rr.serialString() + "< "
+                        + mRadioConfig.requestToString(rr.mRequest) + " error "
+                        + responseInfo.error);
             }
+        } else {
+            Rlog.e(TAG, "setSimSlotsMappingResponse: Error " + responseInfo.toString());
         }
     }
+
+
 }
diff --git a/src/java/com/android/internal/telephony/RadioIndication.java b/src/java/com/android/internal/telephony/RadioIndication.java
index e80e5e7..f7a7943 100644
--- a/src/java/com/android/internal/telephony/RadioIndication.java
+++ b/src/java/com/android/internal/telephony/RadioIndication.java
@@ -36,6 +36,7 @@
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_ON_SS;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_ON_USSD;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_PCO_DATA;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_PHYSICAL_CHANNEL_CONFIG;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_RADIO_CAPABILITY;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_RESEND_INCALL_MUTE;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED;
@@ -295,6 +296,8 @@
             response.add(new PhysicalChannelConfig(status, config.cellBandwidthDownlink));
         }
 
+        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_PHYSICAL_CHANNEL_CONFIG, response);
+
         mRil.mPhysicalChannelConfigurationRegistrants.notifyRegistrants(
                 new AsyncResult(null, response, null));
     }
diff --git a/src/java/com/android/internal/telephony/RadioResponse.java b/src/java/com/android/internal/telephony/RadioResponse.java
index c1e7b1b..0fc5279 100644
--- a/src/java/com/android/internal/telephony/RadioResponse.java
+++ b/src/java/com/android/internal/telephony/RadioResponse.java
@@ -270,7 +270,7 @@
         responseSignalStrength_1_2(responseInfo, signalStrength);
     }
 
-    /*
+    /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      * @param voiceRegResponse Current Voice registration response as defined by VoiceRegStateResult
      *        in types.hal
@@ -289,6 +289,23 @@
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param voiceRegResponse Current Voice registration response as defined by VoiceRegStateResult
+     *        in 1.2/types.hal
+     */
+    public void getVoiceRegistrationStateResponse_1_2(RadioResponseInfo responseInfo,
+            android.hardware.radio.V1_2.VoiceRegStateResult voiceRegResponse) {
+        RILRequest rr = mRil.processResponse(responseInfo);
+
+        if (rr != null) {
+            if (responseInfo.error == RadioError.NONE) {
+                sendMessageResponse(rr.mResult, voiceRegResponse);
+            }
+            mRil.processResponseDone(rr, responseInfo, voiceRegResponse);
+        }
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
      * @param dataRegResponse Current Data registration response as defined by DataRegStateResult in
      *        types.hal
      */
@@ -306,6 +323,23 @@
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param dataRegResponse Current Data registration response as defined by DataRegStateResult in
+     *        1.2/types.hal
+     */
+    public void getDataRegistrationStateResponse_1_2(RadioResponseInfo responseInfo,
+            android.hardware.radio.V1_2.DataRegStateResult dataRegResponse) {
+        RILRequest rr = mRil.processResponse(responseInfo);
+
+        if (rr != null) {
+            if (responseInfo.error == RadioError.NONE) {
+                sendMessageResponse(rr.mResult, dataRegResponse);
+            }
+            mRil.processResponseDone(rr, responseInfo, dataRegResponse);
+        }
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
      * @param longName is long alpha ONS or EONS or empty string if unregistered
      * @param shortName is short alpha ONS or EONS or empty string if unregistered
      * @param numeric is 5 or 6 digit numeric code (MCC + MNC) or empty string if unregistered
@@ -603,6 +637,9 @@
         responseDataCallList(responseInfo, dataCallResultList);
     }
 
+    public void sendOemRilRequestRawResponse(RadioResponseInfo responseInfo,
+                                             ArrayList<Byte> var2) {}
+
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
@@ -1291,12 +1328,12 @@
                 } else {
                     ret = new KeepaliveStatus(keepaliveStatus.sessionHandle, convertedStatus);
                 }
+                // If responseInfo.error is NONE, response function sends the response message
+                // even if result is actually an error.
+                sendMessageResponse(rr.mResult, ret);
                 break;
             case RadioError.REQUEST_NOT_SUPPORTED:
                 ret = new KeepaliveStatus(KeepaliveStatus.ERROR_UNSUPPORTED);
-                // The request is unsupported, which is ok. We'll report it to the higher
-                // layer and treat it as acceptable in the RIL.
-                responseInfo.error = RadioError.NONE;
                 break;
             case RadioError.NO_RESOURCES:
                 ret = new KeepaliveStatus(KeepaliveStatus.ERROR_NO_RESOURCES);
@@ -1305,7 +1342,7 @@
                 ret = new KeepaliveStatus(KeepaliveStatus.ERROR_UNKNOWN);
                 break;
         }
-        sendMessageResponse(rr.mResult, ret);
+        // If responseInfo.error != NONE, the processResponseDone sends the response message.
         mRil.processResponseDone(rr, responseInfo, ret);
     }
 
diff --git a/src/java/com/android/internal/telephony/RatRatcheter.java b/src/java/com/android/internal/telephony/RatRatcheter.java
index e6032a5..3745182 100644
--- a/src/java/com/android/internal/telephony/RatRatcheter.java
+++ b/src/java/com/android/internal/telephony/RatRatcheter.java
@@ -22,11 +22,12 @@
 import android.os.PersistableBundle;
 import android.os.UserHandle;
 import android.telephony.CarrierConfigManager;
-import android.telephony.Rlog;
 import android.telephony.ServiceState;
+import android.telephony.Rlog;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 
 /**
@@ -48,6 +49,8 @@
     private final SparseArray<SparseIntArray> mRatFamilyMap = new SparseArray<>();
 
     private final Phone mPhone;
+    private boolean mVoiceRatchetEnabled = true;
+    private boolean mDataRatchetEnabled = true;
 
     /**
      * Updates the ServiceState with a new set of cell bandwidths IFF the new bandwidth list has a
@@ -98,27 +101,46 @@
     }
 
     /** Ratchets RATs and cell bandwidths if oldSS and newSS have the same RAT family. */
-    public void ratchet(ServiceState oldSS, ServiceState newSS) {
-        int newVoiceRat = ratchetRat(oldSS.getRilVoiceRadioTechnology(),
-                newSS.getRilVoiceRadioTechnology());
-        int newDataRat = ratchetRat(oldSS.getRilDataRadioTechnology(),
-                newSS.getRilDataRadioTechnology());
-
-        if (isSameRatFamily(oldSS, newSS)) {
+    public void ratchet(ServiceState oldSS, ServiceState newSS, boolean locationChange) {
+        if (!locationChange && isSameRatFamily(oldSS, newSS)) {
             updateBandwidths(oldSS.getCellBandwidths(), newSS);
         }
+        // temporarily disable rat ratchet on location change.
+        if (locationChange) {
+            mVoiceRatchetEnabled = false;
+            mDataRatchetEnabled = false;
+            return;
+        }
+        if (mVoiceRatchetEnabled) {
+            int newVoiceRat = ratchetRat(oldSS.getRilVoiceRadioTechnology(),
+                    newSS.getRilVoiceRadioTechnology());
+            newSS.setRilVoiceRadioTechnology(newVoiceRat);
+        } else if (oldSS.getRilVoiceRadioTechnology() != newSS.getRilVoiceRadioTechnology()) {
+            // resume rat ratchet on following rat change within the same location
+            mVoiceRatchetEnabled = true;
+        }
+
+        if (mDataRatchetEnabled) {
+            int newDataRat = ratchetRat(oldSS.getRilDataRadioTechnology(),
+                    newSS.getRilDataRadioTechnology());
+            newSS.setRilDataRadioTechnology(newDataRat);
+        } else if (oldSS.getRilVoiceRadioTechnology() != newSS.getRilVoiceRadioTechnology()) {
+            // resume rat ratchet on following rat change within the same location
+            mVoiceRatchetEnabled = true;
+        }
 
         boolean newUsingCA = oldSS.isUsingCarrierAggregation()
                 || newSS.isUsingCarrierAggregation()
                 || newSS.getCellBandwidths().length > 1;
-
-        newSS.setRilVoiceRadioTechnology(newVoiceRat);
-        newSS.setRilDataRadioTechnology(newDataRat);
         newSS.setIsUsingCarrierAggregation(newUsingCA);
     }
 
     private boolean isSameRatFamily(ServiceState ss1, ServiceState ss2) {
         synchronized (mRatFamilyMap) {
+            // Either the two technologies are the same or their families must be non-null
+            // and the same.
+            if (ss1.getRilDataRadioTechnology() == ss2.getRilDataRadioTechnology()) return true;
+            if (mRatFamilyMap.get(ss1.getRilDataRadioTechnology()) == null) return false;
             return mRatFamilyMap.get(ss1.getRilDataRadioTechnology())
                     == mRatFamilyMap.get(ss2.getRilDataRadioTechnology());
         }
diff --git a/src/java/com/android/internal/telephony/RetryManager.java b/src/java/com/android/internal/telephony/RetryManager.java
index 23c3498..0b4bc3c 100644
--- a/src/java/com/android/internal/telephony/RetryManager.java
+++ b/src/java/com/android/internal/telephony/RetryManager.java
@@ -22,11 +22,10 @@
 import android.os.SystemProperties;
 import android.telephony.CarrierConfigManager;
 import android.telephony.Rlog;
+import android.telephony.data.ApnSetting;
 import android.text.TextUtils;
 import android.util.Pair;
 
-import com.android.internal.telephony.dataconnection.ApnSetting;
-
 import java.util.ArrayList;
 import java.util.Random;
 
@@ -506,7 +505,9 @@
             if (++index == mWaitingApns.size()) index = 0;
 
             // Stop if we find the non-failed APN.
-            if (mWaitingApns.get(index).permanentFailed == false) break;
+            if (!mWaitingApns.get(index).getPermanentFailed()) {
+                break;
+            }
 
             // If we've already cycled through all the APNs, that means there is no APN we can try
             if (index == mCurrentApnIndex) return null;
@@ -553,7 +554,9 @@
             if (++index >= mWaitingApns.size()) index = 0;
 
             // Stop if we find the non-failed APN.
-            if (mWaitingApns.get(index).permanentFailed == false) break;
+            if (!mWaitingApns.get(index).getPermanentFailed()) {
+                break;
+            }
 
             // If we've already cycled through all the APNs, that means all APNs have
             // permanently failed
@@ -594,7 +597,7 @@
      * */
     public void markApnPermanentFailed(ApnSetting apn) {
         if (apn != null) {
-            apn.permanentFailed = true;
+            apn.setPermanentFailed(true);
         }
     }
 
@@ -627,7 +630,7 @@
         configureRetry();
 
         for (ApnSetting apn : mWaitingApns) {
-            apn.permanentFailed = false;
+            apn.setPermanentFailed(false);
         }
 
         log("Setting " + mWaitingApns.size() + " waiting APNs.");
diff --git a/src/java/com/android/internal/telephony/SMSDispatcher.java b/src/java/com/android/internal/telephony/SMSDispatcher.java
index 5952c19..1e5afc7 100644
--- a/src/java/com/android/internal/telephony/SMSDispatcher.java
+++ b/src/java/com/android/internal/telephony/SMSDispatcher.java
@@ -628,7 +628,7 @@
      * Send an SMS PDU. Usually just calls {@link sendRawPdu}.
      */
     private void sendSubmitPdu(SmsTracker tracker) {
-        if (shouldBlockSms()) {
+        if (shouldBlockSmsForEcbm()) {
             Rlog.d(TAG, "Block SMS in Emergency Callback mode");
             tracker.onFailed(mContext, SmsManager.RESULT_ERROR_NO_SERVICE, 0/*errorCode*/);
         } else {
@@ -637,9 +637,9 @@
     }
 
     /**
-     * @return true if MO SMS should be blocked.
+     * @return true if MO SMS should be blocked for Emergency Callback Mode.
      */
-    protected abstract boolean shouldBlockSms();
+    protected abstract boolean shouldBlockSmsForEcbm();
 
     /**
      * Called when SMS send completes. Broadcasts a sentIntent on success.
@@ -1454,7 +1454,13 @@
         // fields need to be public for derived SmsDispatchers
         private final HashMap<String, Object> mData;
         public int mRetryCount;
-        public int mImsRetry; // nonzero indicates initial message was sent over Ims
+        // IMS retry counter. Nonzero indicates initial message was sent over IMS channel in RIL and
+        // counts how many retries have been made on the IMS channel.
+        // Used in older implementations where the message is sent over IMS using the RIL.
+        public int mImsRetry;
+        // Tag indicating that this SMS is being handled by the ImsSmsDispatcher. This tracker
+        // should not try to use SMS over IMS over the RIL interface in this case when falling back.
+        public boolean mUsesImsServiceForIms;
         public int mMessageRef;
         public boolean mExpectMore;
         public int mValidityPeriod;
@@ -1504,6 +1510,7 @@
             mFormat = format;
             mExpectMore = expectMore;
             mImsRetry = 0;
+            mUsesImsServiceForIms = false;
             mMessageRef = 0;
             mUnsentPartCount = unsentPartCount;
             mAnyPartFailed = anyPartFailed;
diff --git a/src/java/com/android/internal/telephony/ServiceStateTracker.java b/src/java/com/android/internal/telephony/ServiceStateTracker.java
index 053d231..b572934 100644
--- a/src/java/com/android/internal/telephony/ServiceStateTracker.java
+++ b/src/java/com/android/internal/telephony/ServiceStateTracker.java
@@ -37,6 +37,7 @@
 import android.os.BaseBundle;
 import android.os.Build;
 import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.Message;
 import android.os.PersistableBundle;
 import android.os.Registrant;
@@ -48,6 +49,7 @@
 import android.preference.PreferenceManager;
 import android.provider.Settings;
 import android.telephony.AccessNetworkConstants;
+import android.telephony.AccessNetworkConstants.AccessNetworkType;
 import android.telephony.CarrierConfigManager;
 import android.telephony.CellIdentity;
 import android.telephony.CellIdentityCdma;
@@ -78,6 +80,7 @@
 import android.util.Pair;
 import android.util.SparseArray;
 import android.util.TimeUtils;
+import android.util.TimestampedValue;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.cdma.CdmaSubscriptionSourceManager;
@@ -92,7 +95,6 @@
 import com.android.internal.telephony.uicc.UiccCardApplication;
 import com.android.internal.telephony.uicc.UiccController;
 import com.android.internal.telephony.util.NotificationChannelController;
-import com.android.internal.telephony.util.TimeStampedValue;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.IndentingPrintWriter;
 
@@ -128,6 +130,7 @@
     private static final long LAST_CELL_INFO_LIST_MAX_AGE_MS = 2000;
     private long mLastCellInfoListTime;
     private List<CellInfo> mLastCellInfoList = null;
+    private List<PhysicalChannelConfig> mLastPhysicalChannelConfigList = null;
 
     private SignalStrength mSignalStrength;
 
@@ -139,7 +142,8 @@
      * and ignore stale responses.  The value is a count-down of
      * expected responses in this pollingContext.
      */
-    private int[] mPollingContext;
+    @VisibleForTesting
+    public int[] mPollingContext;
     private boolean mDesiredPowerState;
 
     /**
@@ -160,6 +164,7 @@
     private RegistrantList mNetworkDetachedRegistrants = new RegistrantList();
     private RegistrantList mPsRestrictEnabledRegistrants = new RegistrantList();
     private RegistrantList mPsRestrictDisabledRegistrants = new RegistrantList();
+    private RegistrantList mImsCapabilityChangedRegistrants = new RegistrantList();
 
     /* Radio power off pending flag and tag counter */
     private boolean mPendingRadioPowerOffAfterDataOff = false;
@@ -243,7 +248,9 @@
     private String mCurPlmn = null;
     private boolean mCurShowPlmn = false;
     private boolean mCurShowSpn = false;
-    private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+    @VisibleForTesting
+    public int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+    private int mPrevSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
 
     private boolean mImsRegistered = false;
 
@@ -255,6 +262,9 @@
 
     private final RatRatcheter mRatRatcheter;
 
+    private final HandlerThread mHandlerThread;
+    private final LocaleTracker mLocaleTracker;
+
     private final LocalLog mRoamingLog = new LocalLog(10);
     private final LocalLog mAttachLog = new LocalLog(10);
     private final LocalLog mPhoneTypeLog = new LocalLog(10);
@@ -274,6 +284,7 @@
             if (DBG) log("SubscriptionListener.onSubscriptionInfoChanged");
             // Set the network type, in case the radio does not restore it.
             int subId = mPhone.getSubId();
+            ServiceStateTracker.this.mPrevSubId = mPreviousSubId.get();
             if (mPreviousSubId.getAndSet(subId) != subId) {
                 if (SubscriptionManager.isValidSubscriptionId(subId)) {
                     Context context = mPhone.getContext();
@@ -410,7 +421,7 @@
         public void onReceive(Context context, Intent intent) {
             if (intent.getAction().equals(
                     CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)) {
-                updateLteEarfcnLists();
+                onCarrierConfigChanged();
                 return;
             }
 
@@ -503,6 +514,13 @@
                     this, EVENT_NETWORK_STATE_CHANGED, null);
         }
 
+        // Create a new handler thread dedicated for locale tracker because the blocking
+        // getAllCellInfo call requires clients calling from a different thread.
+        mHandlerThread = new HandlerThread(LocaleTracker.class.getSimpleName());
+        mHandlerThread.start();
+        mLocaleTracker = TelephonyComponentFactory.getInstance().makeLocaleTracker(
+                mPhone, mHandlerThread.getLooper());
+
         mCi.registerForImsNetworkStateChanged(this, EVENT_IMS_STATE_CHANGED, null);
         mCi.registerForRadioStateChanged(this, EVENT_RADIO_STATE_CHANGED, null);
         mCi.setOnNITZTime(this, EVENT_NITZ_TIME, null);
@@ -548,6 +566,8 @@
                 CarrierServiceStateTracker.CARRIER_EVENT_DATA_REGISTRATION, null);
         registerForDataConnectionDetached(mCSST,
                 CarrierServiceStateTracker.CARRIER_EVENT_DATA_DEREGISTRATION, null);
+        registerForImsCapabilityChanged(mCSST,
+                CarrierServiceStateTracker.CARRIER_EVENT_IMS_CAPABILITIES_CHANGED, null);
     }
 
     @VisibleForTesting
@@ -565,11 +585,17 @@
         }
 
         // If we are previously in service, we need to notify that we are out of service now.
+        if (mSS != null && mSS.getVoiceRegState() == ServiceState.STATE_IN_SERVICE) {
+            mNetworkDetachedRegistrants.notifyRegistrants();
+        }
+
+        // If we are previously in service, we need to notify that we are out of service now.
         if (mSS != null && mSS.getDataRegState() == ServiceState.STATE_IN_SERVICE) {
             mDetachedRegistrants.notifyRegistrants();
         }
 
         mSS = new ServiceState();
+        mSS.setStateOutOfService();
         mNewSS = new ServiceState();
         mLastCellInfoListTime = 0;
         mLastCellInfoList = null;
@@ -650,9 +676,14 @@
         mCi.unregisterForPhysicalChannelConfiguration(this);
         mSubscriptionManager
             .removeOnSubscriptionsChangedListener(mOnSubscriptionsChangedListener);
+        mHandlerThread.quit();
         mCi.unregisterForImsNetworkStateChanged(this);
         mPhone.getCarrierActionAgent().unregisterForCarrierAction(this,
                 CARRIER_ACTION_SET_RADIO_ENABLED);
+        if (mCSST != null) {
+            mCSST.dispose();
+            mCSST = null;
+        }
     }
 
     public boolean getDesiredPowerState() {
@@ -1059,7 +1090,9 @@
             case EVENT_SIM_READY:
                 // Reset the mPreviousSubId so we treat a SIM power bounce
                 // as a first boot.  See b/19194287
-                mOnSubscriptionsChangedListener.mPreviousSubId.set(-1);
+                mOnSubscriptionsChangedListener.mPreviousSubId.set(
+                        SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+                mPrevSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
                 mIsSimReady = true;
                 pollState();
                 // Signal strength polling stops when radio is off
@@ -1273,6 +1306,7 @@
             case EVENT_IMS_CAPABILITY_CHANGED:
                 if (DBG) log("EVENT_IMS_CAPABILITY_CHANGED");
                 updateSpnDisplay();
+                mImsCapabilityChangedRegistrants.notifyRegistrants();
                 break;
 
             case EVENT_IMS_SERVICE_STATE_CHANGED:
@@ -1430,6 +1464,7 @@
                                 + list);
                     }
                     mPhone.notifyPhysicalChannelConfiguration(list);
+                    mLastPhysicalChannelConfigList = list;
 
                     // only notify if bandwidths changed
                     if (RatRatcheter.updateBandwidths(getBandwidthsFromConfigs(list), mSS)) {
@@ -1602,6 +1637,10 @@
         if (ar.exception != null) {
             CommandException.Error err=null;
 
+            if (ar.exception instanceof IllegalStateException) {
+                log("handlePollStateResult exception " + ar.exception);
+            }
+
             if (ar.exception instanceof CommandException) {
                 err = ((CommandException)(ar.exception)).getCommandError();
             }
@@ -1625,12 +1664,12 @@
         mPollingContext[0]--;
 
         if (mPollingContext[0] == 0) {
+            mNewSS.setEmergencyOnly(mEmergencyOnly);
             if (mPhone.isPhoneTypeGsm()) {
                 updateRoamingState();
-                mNewSS.setEmergencyOnly(mEmergencyOnly);
             } else {
                 boolean namMatch = false;
-                if (!isSidsAllZeros() && isHomeSid(mNewSS.getSystemId())) {
+                if (!isSidsAllZeros() && isHomeSid(mNewSS.getCdmaSystemId())) {
                     namMatch = true;
                 }
 
@@ -1765,9 +1804,11 @@
                 mNewSS.setCssIndicator(cssIndicator);
                 mNewSS.setRilVoiceRadioTechnology(newVoiceRat);
                 mNewSS.addNetworkRegistrationState(networkRegState);
+                setPhyCellInfoFromCellIdentity(mNewSS, networkRegState.getCellIdentity());
 
                 //Denial reason if registrationState = 3
-                int reasonForDenial = networkRegState.getReasonForDenial();
+                int reasonForDenial = networkRegState.getRejectCause();
+                mEmergencyOnly = networkRegState.isEmergencyEnabled();
                 if (mPhone.isPhoneTypeGsm()) {
 
                     mGsmRoaming = regCodeIsRoaming(registrationState);
@@ -1775,7 +1816,6 @@
 
                     boolean isVoiceCapable = mPhone.getContext().getResources()
                             .getBoolean(com.android.internal.R.bool.config_voice_capable);
-                    mEmergencyOnly = networkRegState.isEmergencyEnabled();
                 } else {
                     int roamingIndicator = voiceSpecificStates.roamingIndicator;
 
@@ -1805,7 +1845,7 @@
                         systemId = ((CellIdentityCdma) cellIdentity).getSystemId();
                         networkId = ((CellIdentityCdma) cellIdentity).getNetworkId();
                     }
-                    mNewSS.setSystemAndNetworkId(systemId, networkId);
+                    mNewSS.setCdmaSystemAndNetworkId(systemId, networkId);
 
                     if (reasonForDenial == 0) {
                         mRegistrationDeniedReason = ServiceStateTracker.REGISTRATION_DENIED_GEN;
@@ -1841,9 +1881,17 @@
                 mNewSS.setRilDataRadioTechnology(newDataRat);
                 mNewSS.addNetworkRegistrationState(networkRegState);
 
+                // When we receive OOS reset the PhyChanConfig list so that non-return-to-idle
+                // implementers of PhyChanConfig unsol will not carry forward a CA report
+                // (2 or more cells) to a new cell if they camp for emergency service only.
+                if (serviceState == ServiceState.STATE_OUT_OF_SERVICE) {
+                    mLastPhysicalChannelConfigList = null;
+                }
+                setPhyCellInfoFromCellIdentity(mNewSS, networkRegState.getCellIdentity());
+
                 if (mPhone.isPhoneTypeGsm()) {
 
-                    mNewReasonDataDenied = networkRegState.getReasonForDenial();
+                    mNewReasonDataDenied = networkRegState.getRejectCause();
                     mNewMaxDataCalls = dataSpecificStates.maxDataCalls;
                     mDataRoaming = regCodeIsRoaming(registrationState);
                     // Save the data roaming state reported by modem registration before resource
@@ -1978,6 +2026,78 @@
         }
     }
 
+    private static boolean isValidLteBandwidthKhz(int bandwidth) {
+        // Valid bandwidths, see 3gpp 36.101 sec. 5.6
+        switch (bandwidth) {
+            case 1400:
+            case 3000:
+            case 5000:
+            case 10000:
+            case 15000:
+            case 20000:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    private void setPhyCellInfoFromCellIdentity(ServiceState ss, CellIdentity cellIdentity) {
+        if (cellIdentity == null) {
+            if (DBG) {
+                log("Could not set ServiceState channel number. CellIdentity null");
+            }
+            return;
+        }
+
+        ss.setChannelNumber(cellIdentity.getChannelNumber());
+        if (VDBG) {
+            log("Setting channel number: " + cellIdentity.getChannelNumber());
+        }
+
+        if (cellIdentity instanceof CellIdentityLte) {
+            CellIdentityLte cl = (CellIdentityLte) cellIdentity;
+            int[] bandwidths = null;
+            // Prioritize the PhysicalChannelConfig list because we might already be in carrier
+            // aggregation by the time poll state is performed.
+            if (!ArrayUtils.isEmpty(mLastPhysicalChannelConfigList)) {
+                bandwidths = getBandwidthsFromConfigs(mLastPhysicalChannelConfigList);
+                for (int bw : bandwidths) {
+                    if (!isValidLteBandwidthKhz(bw)) {
+                        loge("Invalid LTE Bandwidth in RegistrationState, " + bw);
+                        bandwidths = null;
+                        break;
+                    }
+                }
+            }
+            // If we don't have a PhysicalChannelConfig[] list, then pull from CellIdentityLte.
+            // This is normal if we're in idle mode and the PhysicalChannelConfig[] has already
+            // been updated. This is also a fallback in case the PhysicalChannelConfig info
+            // is invalid (ie, broken).
+            // Also, for vendor implementations that do not report return-to-idle, we should
+            // prioritize the bandwidth report in the CellIdentity, because the physical channel
+            // config report may be stale in the case where a single carrier was used previously
+            // and we transition to camped-for-emergency (since we never have a physical
+            // channel active). In the normal case of single-carrier non-return-to-idle, the
+            // values *must* be the same, so it doesn't matter which is chosen.
+            if (bandwidths == null || bandwidths.length == 1) {
+                final int cbw = cl.getBandwidth();
+                if (isValidLteBandwidthKhz(cbw)) {
+                    bandwidths = new int[] {cbw};
+                } else if (cbw == Integer.MAX_VALUE) {
+                    // Bandwidth is unreported; c'est la vie. This is not an error because
+                    // pre-1.2 HAL implementations do not support bandwidth reporting.
+                } else {
+                    loge("Invalid LTE Bandwidth in RegistrationState, " + cbw);
+                }
+            }
+            if (bandwidths != null) {
+                ss.setCellBandwidths(bandwidths);
+            }
+        } else {
+            if (VDBG) log("Skipping bandwidth update for Non-LTE cell.");
+        }
+    }
+
     /**
      * Determine whether a roaming indicator is in the carrier-specified list of ERIs for
      * home system
@@ -2072,7 +2192,7 @@
             if (configLoader != null) {
                 try {
                     PersistableBundle b = configLoader.getConfigForSubId(mPhone.getSubId());
-                    String systemId = Integer.toString(mNewSS.getSystemId());
+                    String systemId = Integer.toString(mNewSS.getCdmaSystemId());
 
                     if (alwaysOnHomeNetwork(b)) {
                         log("updateRoamingState: carrier config override always on home network");
@@ -2115,8 +2235,32 @@
         mNewSS.setCdmaEriIconIndex(EriInfo.ROAMING_INDICATOR_OFF);
     }
 
+    private void updateOperatorNameFromCarrierConfig() {
+        // Brand override gets a priority over carrier config. If brand override is not available,
+        // override the operator name in home network. Also do this only for CDMA. This is temporary
+        // and should be fixed in a proper way in a later release.
+        if (!mPhone.isPhoneTypeGsm() && !mSS.getRoaming()) {
+            boolean hasBrandOverride = mUiccController.getUiccCard(getPhoneId()) != null
+                    && mUiccController.getUiccCard(getPhoneId()).getOperatorBrandOverride() != null;
+            if (!hasBrandOverride) {
+                PersistableBundle config = getCarrierConfig();
+                if (config.getBoolean(
+                        CarrierConfigManager.KEY_CDMA_HOME_REGISTERED_PLMN_NAME_OVERRIDE_BOOL)) {
+                    String operator = config.getString(
+                            CarrierConfigManager.KEY_CDMA_HOME_REGISTERED_PLMN_NAME_STRING);
+                    log("updateOperatorNameFromCarrierConfig: changing from "
+                            + mSS.getOperatorAlpha() + " to " + operator);
+                    // override long and short operator name, keeping numeric the same
+                    mSS.setOperatorName(operator, operator, mSS.getOperatorNumeric());
+                }
+            }
+        }
+    }
+
     protected void updateSpnDisplay() {
         updateOperatorNameFromEri();
+        // carrier config gets a priority over ERI
+        updateOperatorNameFromCarrierConfig();
 
         String wfcVoiceSpnFormat = null;
         String wfcDataSpnFormat = null;
@@ -2635,8 +2779,8 @@
         // ratchet the new tech up through its rat family but don't drop back down
         // until cell change or device is OOS
         boolean isDataInService = mNewSS.getDataRegState() == ServiceState.STATE_IN_SERVICE;
-        if (!hasLocationChanged && isDataInService) {
-            mRatRatcheter.ratchet(mSS, mNewSS);
+        if (isDataInService) {
+            mRatRatcheter.ratchet(mSS, mNewSS, hasLocationChanged);
         }
 
         boolean hasRilVoiceRadioTechnologyChanged =
@@ -2806,17 +2950,19 @@
             if (!mPhone.isPhoneTypeGsm()) {
                 // try to fix the invalid Operator Numeric
                 if (isInvalidOperatorNumeric(operatorNumeric)) {
-                    int sid = mSS.getSystemId();
+                    int sid = mSS.getCdmaSystemId();
                     operatorNumeric = fixUnknownMcc(operatorNumeric, sid);
                 }
             }
 
             tm.setNetworkOperatorNumericForPhone(mPhone.getPhoneId(), operatorNumeric);
-            updateCarrierMccMncConfiguration(operatorNumeric,
-                    prevOperatorNumeric, mPhone.getContext());
+
             if (isInvalidOperatorNumeric(operatorNumeric)) {
                 if (DBG) log("operatorNumeric " + operatorNumeric + " is invalid");
-                tm.setNetworkCountryIsoForPhone(mPhone.getPhoneId(), "");
+                // 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.
+                mLocaleTracker.updateOperatorNumericAsync("");
                 mNitzState.handleNetworkUnavailable();
             } else if (mSS.getRilDataRadioTechnology() != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN) {
                 // If the device is on IWLAN, modems manufacture a ServiceState with the MCC/MNC of
@@ -2828,15 +2974,8 @@
                     setOperatorIdd(operatorNumeric);
                 }
 
-                // Update ISO.
-                String countryIsoCode = "";
-                try {
-                    String mcc = operatorNumeric.substring(0, 3);
-                    countryIsoCode = MccTable.countryCodeForMcc(Integer.parseInt(mcc));
-                } catch (NumberFormatException | StringIndexOutOfBoundsException ex) {
-                    loge("pollStateDone: countryCodeForMcc error: " + ex);
-                }
-                tm.setNetworkCountryIsoForPhone(mPhone.getPhoneId(), countryIsoCode);
+                mLocaleTracker.updateOperatorNumericSync(operatorNumeric);
+                String countryIsoCode = mLocaleTracker.getCurrentCountry();
 
                 // Update Time Zone.
                 boolean iccCardExists = iccCardExists();
@@ -2895,6 +3034,9 @@
 
         if (hasRilDataRadioTechnologyChanged || hasRilVoiceRadioTechnologyChanged) {
             logRatChange();
+
+            updateRatTypeForSignalStrength();
+            notifySignalStrength();
         }
 
         if (hasDataRegStateChanged || hasRilDataRadioTechnologyChanged) {
@@ -2971,8 +3113,7 @@
             if (!hasBrandOverride && (mCi.getRadioState().isOn()) && (mPhone.isEriFileLoaded()) &&
                     (!ServiceState.isLte(mSS.getRilVoiceRadioTechnology()) ||
                             mPhone.getContext().getResources().getBoolean(com.android.internal.R.
-                                    bool.config_LTE_eri_for_network_name)) &&
-                                    (!mIsSubscriptionFromRuim)) {
+                                    bool.config_LTE_eri_for_network_name))) {
                 // Only when CDMA is in service, ERI will take effect
                 String eriText = mSS.getOperatorAlpha();
                 // Now the Phone sees the new ServiceState so it can get the new ERI text
@@ -3004,9 +3145,9 @@
                         ((RuimRecords) mIccRecords).getCsimSpnDisplayCondition();
                 int iconIndex = mSS.getCdmaEriIconIndex();
 
-                if (showSpn && (iconIndex == EriInfo.ROAMING_INDICATOR_OFF) &&
-                        isInHomeSidNid(mSS.getSystemId(), mSS.getNetworkId()) &&
-                        mIccRecords != null) {
+                if (showSpn && (iconIndex == EriInfo.ROAMING_INDICATOR_OFF)
+                        && isInHomeSidNid(mSS.getCdmaSystemId(), mSS.getCdmaNetworkId())
+                        && mIccRecords != null) {
                     mSS.setOperatorAlphaLong(mIccRecords.getServiceProviderName());
                 }
             }
@@ -3421,8 +3562,8 @@
         NitzData newNitzData = NitzData.parse(nitzString);
         if (newNitzData != null) {
             try {
-                TimeStampedValue<NitzData> nitzSignal =
-                        new TimeStampedValue<>(newNitzData, nitzReceiveTime);
+                TimestampedValue<NitzData> nitzSignal =
+                        new TimestampedValue<>(nitzReceiveTime, newNitzData);
                 mNitzState.handleNitzReceived(nitzSignal);
             } finally {
                 if (DBG) {
@@ -3434,17 +3575,19 @@
     }
 
     /**
-     * Cancels all notifications posted to NotificationManager. These notifications for restricted
-     * state and rejection cause for cs registration are no longer valid after the SIM has been
-     * removed.
+     * Cancels all notifications posted to NotificationManager for this subId. These notifications
+     * for restricted state and rejection cause for cs registration are no longer valid after the
+     * SIM has been removed.
      */
     private void cancelAllNotifications() {
-        if (DBG) log("setNotification: cancelAllNotifications");
+        if (DBG) log("cancelAllNotifications: mPrevSubId=" + mPrevSubId);
         NotificationManager notificationManager = (NotificationManager)
                 mPhone.getContext().getSystemService(Context.NOTIFICATION_SERVICE);
-        notificationManager.cancel(PS_NOTIFICATION);
-        notificationManager.cancel(CS_NOTIFICATION);
-        notificationManager.cancel(CS_REJECT_CAUSE_NOTIFICATION);
+        if (SubscriptionManager.isValidSubscriptionId(mPrevSubId)) {
+            notificationManager.cancel(Integer.toString(mPrevSubId), PS_NOTIFICATION);
+            notificationManager.cancel(Integer.toString(mPrevSubId), CS_NOTIFICATION);
+            notificationManager.cancel(Integer.toString(mPrevSubId), CS_REJECT_CAUSE_NOTIFICATION);
+        }
     }
 
     /**
@@ -3457,6 +3600,12 @@
     public void setNotification(int notifyType) {
         if (DBG) log("setNotification: create notification " + notifyType);
 
+        if (!SubscriptionManager.isValidSubscriptionId(mSubId)) {
+            // notifications are posted per-sub-id, so return if current sub-id is invalid
+            loge("cannot setNotification on invalid subid mSubId=" + mSubId);
+            return;
+        }
+
         // Needed because sprout RIL sends these when they shouldn't?
         boolean isSetNotification = mPhone.getContext().getResources().getBoolean(
                 com.android.internal.R.bool.config_user_notification_of_restrictied_mobile_access);
@@ -3488,6 +3637,10 @@
         int notificationId = CS_NOTIFICATION;
         int icon = com.android.internal.R.drawable.stat_sys_warning;
 
+        final boolean multipleSubscriptions = (((TelephonyManager) mPhone.getContext()
+                  .getSystemService(Context.TELEPHONY_SERVICE)).getPhoneCount() > 1);
+        final int simNumber = mSubscriptionController.getSlotIndex(mSubId) + 1;
+
         switch (notifyType) {
             case PS_ENABLED:
                 long dataSubId = SubscriptionManager.getDefaultDataSubscriptionId();
@@ -3496,37 +3649,52 @@
                 }
                 notificationId = PS_NOTIFICATION;
                 title = context.getText(com.android.internal.R.string.RestrictedOnDataTitle);
-                details = context.getText(com.android.internal.R.string.RestrictedStateContent);
+                details = multipleSubscriptions
+                        ? context.getString(
+                                com.android.internal.R.string.RestrictedStateContentMsimTemplate,
+                                simNumber) :
+                        context.getText(com.android.internal.R.string.RestrictedStateContent);
                 break;
             case PS_DISABLED:
                 notificationId = PS_NOTIFICATION;
                 break;
             case CS_ENABLED:
                 title = context.getText(com.android.internal.R.string.RestrictedOnAllVoiceTitle);
-                details = context.getText(
-                        com.android.internal.R.string.RestrictedStateContent);
+                details = multipleSubscriptions
+                        ? context.getString(
+                                com.android.internal.R.string.RestrictedStateContentMsimTemplate,
+                                simNumber) :
+                        context.getText(com.android.internal.R.string.RestrictedStateContent);
                 break;
             case CS_NORMAL_ENABLED:
                 title = context.getText(com.android.internal.R.string.RestrictedOnNormalTitle);
-                details = context.getText(com.android.internal.R.string.RestrictedStateContent);
+                details = multipleSubscriptions
+                        ? context.getString(
+                                com.android.internal.R.string.RestrictedStateContentMsimTemplate,
+                                simNumber) :
+                        context.getText(com.android.internal.R.string.RestrictedStateContent);
                 break;
             case CS_EMERGENCY_ENABLED:
                 title = context.getText(com.android.internal.R.string.RestrictedOnEmergencyTitle);
-                details = context.getText(
-                        com.android.internal.R.string.RestrictedStateContent);
+                details = multipleSubscriptions
+                        ? context.getString(
+                                com.android.internal.R.string.RestrictedStateContentMsimTemplate,
+                                simNumber) :
+                        context.getText(com.android.internal.R.string.RestrictedStateContent);
                 break;
             case CS_DISABLED:
                 // do nothing and cancel the notification later
                 break;
             case CS_REJECT_CAUSE_ENABLED:
                 notificationId = CS_REJECT_CAUSE_NOTIFICATION;
-                int resId = selectResourceForRejectCode(mRejectCode);
+                int resId = selectResourceForRejectCode(mRejectCode, multipleSubscriptions);
                 if (0 == resId) {
                     loge("setNotification: mRejectCode=" + mRejectCode + " is not handled.");
                     return;
                 } else {
                     icon = com.android.internal.R.drawable.stat_notify_mmcc_indication_icn;
-                    title = Resources.getSystem().getString(resId);
+                    // if using the single SIM resource, simNumber will be ignored
+                    title = context.getString(resId, simNumber);
                     details = null;
                 }
                 break;
@@ -3534,7 +3702,7 @@
 
         if (DBG) {
             log("setNotification, create notification, notifyType: " + notifyType
-                    + ", title: " + title + ", details: " + details);
+                    + ", title: " + title + ", details: " + details + ", subId: " + mSubId);
         }
 
         mNotification = new Notification.Builder(context)
@@ -3555,10 +3723,25 @@
 
         if (notifyType == PS_DISABLED || notifyType == CS_DISABLED) {
             // cancel previous post notification
-            notificationManager.cancel(notificationId);
+            notificationManager.cancel(Integer.toString(mSubId), notificationId);
         } else {
-            // update restricted state notification
-            notificationManager.notify(notificationId, mNotification);
+            boolean show = false;
+            if (mSS.isEmergencyOnly() && notifyType == CS_EMERGENCY_ENABLED) {
+                // if reg state is emergency only, always show restricted emergency notification.
+                show = true;
+            } else if (notifyType == CS_REJECT_CAUSE_ENABLED) {
+                // always show notification due to CS reject irrespective of service state.
+                show = true;
+            } else if (mSS.getState() == ServiceState.STATE_IN_SERVICE) {
+                // for non in service states, we have system UI and signal bar to indicate limited
+                // service. No need to show notification again. This also helps to mitigate the
+                // issue if phone go to OOS and camp to other networks and received restricted ind.
+                show = true;
+            }
+            // update restricted state notification for this subId
+            if (show) {
+                notificationManager.notify(Integer.toString(mSubId), notificationId, mNotification);
+            }
         }
     }
 
@@ -3568,20 +3751,28 @@
      *
      * @param rejCode should be compatible with TS 24.008.
      */
-    private int selectResourceForRejectCode(int rejCode) {
+    private int selectResourceForRejectCode(int rejCode, boolean multipleSubscriptions) {
         int rejResourceId = 0;
         switch (rejCode) {
             case 1:// Authentication reject
-                rejResourceId = com.android.internal.R.string.mmcc_authentication_reject;
+                rejResourceId = multipleSubscriptions
+                        ? com.android.internal.R.string.mmcc_authentication_reject_msim_template :
+                        com.android.internal.R.string.mmcc_authentication_reject;
                 break;
             case 2:// IMSI unknown in HLR
-                rejResourceId = com.android.internal.R.string.mmcc_imsi_unknown_in_hlr;
+                rejResourceId = multipleSubscriptions
+                        ? com.android.internal.R.string.mmcc_imsi_unknown_in_hlr_msim_template :
+                        com.android.internal.R.string.mmcc_imsi_unknown_in_hlr;
                 break;
             case 3:// Illegal MS
-                rejResourceId = com.android.internal.R.string.mmcc_illegal_ms;
+                rejResourceId = multipleSubscriptions
+                        ? com.android.internal.R.string.mmcc_illegal_ms_msim_template :
+                        com.android.internal.R.string.mmcc_illegal_ms;
                 break;
             case 6:// Illegal ME
-                rejResourceId = com.android.internal.R.string.mmcc_illegal_me;
+                rejResourceId = multipleSubscriptions
+                        ? com.android.internal.R.string.mmcc_illegal_me_msim_template :
+                        com.android.internal.R.string.mmcc_illegal_me;
                 break;
             default:
                 // The other codes are not defined or not required by operators till now.
@@ -3756,6 +3947,25 @@
     }
 
     /**
+     * Registers for IMS capability changed.
+     * @param h handler to notify
+     * @param what what code of message when delivered
+     * @param obj placed in Message.obj
+     */
+    public void registerForImsCapabilityChanged(Handler h, int what, Object obj) {
+        Registrant r = new Registrant(h, what, obj);
+        mImsCapabilityChangedRegistrants.add(r);
+    }
+
+    /**
+     * Unregisters for IMS capability changed.
+     * @param h handler to notify
+     */
+    public void unregisterForImsCapabilityChanged(Handler h) {
+        mImsCapabilityChangedRegistrants.remove(h);
+    }
+
+    /**
      * Clean up existing voice and data connection then turn off radio power.
      *
      * Hang up the existing voice calls to decrease call drop rate.
@@ -3892,19 +4102,37 @@
 
         return earfcnPairList;
     }
-    private void updateLteEarfcnLists() {
+
+    private void onCarrierConfigChanged() {
         CarrierConfigManager configManager = (CarrierConfigManager)
                 mPhone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
-        PersistableBundle b = configManager.getConfigForSubId(mPhone.getSubId());
+        PersistableBundle config = configManager.getConfigForSubId(mPhone.getSubId());
+
+        if (config != null) {
+            updateLteEarfcnLists(config);
+            updateReportingCriteria(config);
+        }
+    }
+
+    private void updateLteEarfcnLists(PersistableBundle config) {
         synchronized (mLteRsrpBoostLock) {
-            mLteRsrpBoost = b.getInt(CarrierConfigManager.KEY_LTE_EARFCNS_RSRP_BOOST_INT, 0);
-            String[] earfcnsStringArrayForRsrpBoost = b.getStringArray(
+            mLteRsrpBoost = config.getInt(CarrierConfigManager.KEY_LTE_EARFCNS_RSRP_BOOST_INT, 0);
+            String[] earfcnsStringArrayForRsrpBoost = config.getStringArray(
                     CarrierConfigManager.KEY_BOOSTED_LTE_EARFCNS_STRING_ARRAY);
             mEarfcnPairListForRsrpBoost = convertEarfcnStringArrayToPairList(
                     earfcnsStringArrayForRsrpBoost);
         }
     }
 
+    private void updateReportingCriteria(PersistableBundle config) {
+        mPhone.setSignalStrengthReportingCriteria(
+                config.getIntArray(CarrierConfigManager.KEY_LTE_RSRP_THRESHOLDS_INT_ARRAY),
+                AccessNetworkType.EUTRAN);
+        mPhone.setSignalStrengthReportingCriteria(
+                config.getIntArray(CarrierConfigManager.KEY_WCDMA_RSCP_THRESHOLDS_INT_ARRAY),
+                AccessNetworkType.UTRAN);
+    }
+
     private void updateServiceStateLteEarfcnBoost(ServiceState serviceState, int lteEarfcn) {
         synchronized (mLteRsrpBoostLock) {
             if ((lteEarfcn != INVALID_LTE_EARFCN)
@@ -3923,18 +4151,6 @@
      * @return true if the signal strength changed and a notification was sent.
      */
     protected boolean onSignalStrengthResult(AsyncResult ar) {
-        boolean isGsm = false;
-        int dataRat = mSS.getRilDataRadioTechnology();
-        int voiceRat = mSS.getRilVoiceRadioTechnology();
-
-        // Override isGsm based on currently camped data and voice RATs
-        // Set isGsm to true if the RAT belongs to GSM family and not IWLAN
-        if ((dataRat != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN
-                && ServiceState.isGsm(dataRat))
-                || (voiceRat != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN
-                && ServiceState.isGsm(voiceRat))) {
-            isGsm = true;
-        }
 
         // This signal is used for both voice and data radio signal so parse
         // all fields
@@ -3942,12 +4158,6 @@
         if ((ar.exception == null) && (ar.result != null)) {
             mSignalStrength = (SignalStrength) ar.result;
             mSignalStrength.validateInput();
-            if (dataRat == ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN
-                    && voiceRat == ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN) {
-                mSignalStrength.fixType();
-            } else {
-                mSignalStrength.setGsm(isGsm);
-            }
             mSignalStrength.setLteRsrpBoost(mSS.getLteEarfcnRsrpBoost());
 
             PersistableBundle config = getCarrierConfig();
@@ -3961,14 +4171,39 @@
                     CarrierConfigManager.KEY_WCDMA_RSCP_THRESHOLDS_INT_ARRAY));
         } else {
             log("onSignalStrengthResult() Exception from RIL : " + ar.exception);
-            mSignalStrength = new SignalStrength(isGsm);
+            mSignalStrength = new SignalStrength(true);
         }
 
+        updateRatTypeForSignalStrength();
         boolean ssChanged = notifySignalStrength();
 
         return ssChanged;
     }
 
+    private void updateRatTypeForSignalStrength() {
+        if (mSignalStrength != null) {
+            boolean isGsm = false;
+            int dataRat = mSS.getRilDataRadioTechnology();
+            int voiceRat = mSS.getRilVoiceRadioTechnology();
+
+            // Override isGsm based on currently camped data and voice RATs
+            // Set isGsm to true if the RAT belongs to GSM family and not IWLAN
+            if ((dataRat != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN
+                    && ServiceState.isGsm(dataRat))
+                    || (voiceRat != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN
+                    && ServiceState.isGsm(voiceRat))) {
+                isGsm = true;
+            }
+
+            if (dataRat == ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN
+                    && voiceRat == ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN) {
+                mSignalStrength.fixType();
+            } else {
+                mSignalStrength.setGsm(isGsm);
+            }
+        }
+    }
+
     /**
      * Hang up all voice call and turn off radio. Implemented by derived class.
      */
@@ -4231,6 +4466,8 @@
         pw.println(" mLteRsrpBoost=" + mLteRsrpBoost);
         dumpEarfcnPairList(pw);
 
+        mLocaleTracker.dump(fd, pw, args);
+
         pw.println(" Roaming Log:");
         IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
         ipw.increaseIndent();
@@ -4283,15 +4520,6 @@
         return value;
     }
 
-    protected void updateCarrierMccMncConfiguration(String newOp, String oldOp, Context context) {
-        // if we have a change in operator, notify wifi (even to/from none)
-        if (((newOp == null) && (TextUtils.isEmpty(oldOp) == false)) ||
-                ((newOp != null) && (newOp.equals(oldOp) == false))) {
-            log("update mccmnc=" + newOp + " fromServiceState=true");
-            MccTable.updateMccMncConfiguration(context, newOp, true);
-        }
-    }
-
     /**
      * Check ISO country by MCC to see if phone is roaming in same registered country
      */
@@ -4308,8 +4536,8 @@
         boolean inSameCountry = true;
         final String networkMCC = operatorNumeric.substring(0, 3);
         final String homeMCC = homeNumeric.substring(0, 3);
-        final String networkCountry = MccTable.countryCodeForMcc(Integer.parseInt(networkMCC));
-        final String homeCountry = MccTable.countryCodeForMcc(Integer.parseInt(homeMCC));
+        final String networkCountry = MccTable.countryCodeForMcc(networkMCC);
+        final String homeCountry = MccTable.countryCodeForMcc(homeMCC);
         if (networkCountry.isEmpty() || homeCountry.isEmpty()) {
             // Not a valid country
             return false;
@@ -4550,4 +4778,8 @@
         // Return static default defined in CarrierConfigManager.
         return CarrierConfigManager.getDefaultConfig();
     }
+
+    public LocaleTracker getLocaleTracker() {
+        return mLocaleTracker;
+    }
 }
diff --git a/src/java/com/android/internal/telephony/SmsBroadcastUndelivered.java b/src/java/com/android/internal/telephony/SmsBroadcastUndelivered.java
index f2f2aa3..4fb02f0 100644
--- a/src/java/com/android/internal/telephony/SmsBroadcastUndelivered.java
+++ b/src/java/com/android/internal/telephony/SmsBroadcastUndelivered.java
@@ -23,9 +23,11 @@
 import android.content.IntentFilter;
 import android.database.Cursor;
 import android.database.SQLException;
-import android.os.UserHandle;
+import android.os.PersistableBundle;
 import android.os.UserManager;
+import android.telephony.CarrierConfigManager;
 import android.telephony.Rlog;
+import android.telephony.SubscriptionManager;
 
 import com.android.internal.telephony.cdma.CdmaInboundSmsHandler;
 import com.android.internal.telephony.gsm.GsmInboundSmsHandler;
@@ -45,7 +47,7 @@
     private static final boolean DBG = InboundSmsHandler.DBG;
 
     /** Delete any partial message segments older than 30 days. */
-    static final long PARTIAL_SEGMENT_EXPIRE_AGE = (long) (60 * 60 * 1000) * 24 * 30;
+    static final long DEFAULT_PARTIAL_SEGMENT_EXPIRE_AGE = (long) (60 * 60 * 1000) * 24 * 30;
 
     /**
      * Query projection for dispatching pending messages at boot time.
@@ -97,7 +99,7 @@
 
         @Override
         public void run() {
-            scanRawTable();
+            scanRawTable(context);
             InboundSmsHandler.cancelNewMessageNotification(context);
         }
     }
@@ -140,7 +142,7 @@
     /**
      * Scan the raw table for complete SMS messages to broadcast, and old PDUs to delete.
      */
-    private void scanRawTable() {
+    private void scanRawTable(Context context) {
         if (DBG) Rlog.d(TAG, "scanning raw table for undelivered messages");
         long startTime = System.nanoTime();
         HashMap<SmsReferenceKey, Integer> multiPartReceivedCount =
@@ -176,8 +178,9 @@
                     Integer receivedCount = multiPartReceivedCount.get(reference);
                     if (receivedCount == null) {
                         multiPartReceivedCount.put(reference, 1);    // first segment seen
+                        long expirationTime = getUndeliveredSmsExpirationTime(context);
                         if (tracker.getTimestamp() <
-                                (System.currentTimeMillis() - PARTIAL_SEGMENT_EXPIRE_AGE)) {
+                                (System.currentTimeMillis() - expirationTime)) {
                             // older than 30 days; delete if we don't find all the segments
                             oldMultiPartMessages.add(reference);
                         }
@@ -236,6 +239,20 @@
         }
     }
 
+    private long getUndeliveredSmsExpirationTime(Context context) {
+        int subId = SubscriptionManager.getDefaultSmsSubscriptionId();
+        CarrierConfigManager configManager =
+                (CarrierConfigManager) context.getSystemService(Context.CARRIER_CONFIG_SERVICE);
+        PersistableBundle bundle = configManager.getConfigForSubId(subId);
+
+        if (bundle != null) {
+            return bundle.getLong(CarrierConfigManager.KEY_UNDELIVERED_SMS_MESSAGE_EXPIRATION_TIME,
+                    DEFAULT_PARTIAL_SEGMENT_EXPIRE_AGE);
+        } else {
+            return DEFAULT_PARTIAL_SEGMENT_EXPIRE_AGE;
+        }
+    }
+
     /**
      * Used as the HashMap key for matching concatenated message segments.
      */
diff --git a/src/java/com/android/internal/telephony/SmsDispatchersController.java b/src/java/com/android/internal/telephony/SmsDispatchersController.java
index 9eae37d..d8906e7 100644
--- a/src/java/com/android/internal/telephony/SmsDispatchersController.java
+++ b/src/java/com/android/internal/telephony/SmsDispatchersController.java
@@ -32,6 +32,7 @@
 import android.provider.Telephony.Sms.Intents;
 import android.telephony.Rlog;
 import android.telephony.SmsManager;
+import android.telephony.SmsMessage;
 import android.util.Pair;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -40,6 +41,8 @@
 import com.android.internal.telephony.gsm.GsmInboundSmsHandler;
 import com.android.internal.telephony.gsm.GsmSMSDispatcher;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.HashMap;
 
@@ -186,13 +189,17 @@
      */
     @VisibleForTesting
     public void injectSmsPdu(byte[] pdu, String format, SmsInjectionCallback callback) {
-        injectSmsPdu(pdu, format, callback, false /* ignoreClass */);
+        // TODO We need to decide whether we should allow injecting GSM(3gpp)
+        // SMS pdus when the phone is camping on CDMA(3gpp2) network and vice versa.
+        android.telephony.SmsMessage msg =
+                android.telephony.SmsMessage.createFromPdu(pdu, format);
+        injectSmsPdu(msg, format, callback, false /* ignoreClass */);
     }
 
     /**
      * Inject an SMS PDU into the android platform.
      *
-     * @param pdu is the byte array of pdu to be injected into android telephony layer
+     * @param msg is the {@link SmsMessage} to be injected into android telephony layer
      * @param format is the format of SMS pdu (3gpp or 3gpp2)
      * @param callback if not NULL this callback is triggered when the message is successfully
      *                 received by the android telephony layer. This callback is triggered at
@@ -200,15 +207,10 @@
      * @param ignoreClass if set to false, this method will inject class 1 sms only.
      */
     @VisibleForTesting
-    public void injectSmsPdu(byte[] pdu, String format, SmsInjectionCallback callback,
+    public void injectSmsPdu(SmsMessage msg, String format, SmsInjectionCallback callback,
             boolean ignoreClass) {
         Rlog.d(TAG, "SmsDispatchersController:injectSmsPdu");
         try {
-            // TODO We need to decide whether we should allow injecting GSM(3gpp)
-            // SMS pdus when the phone is camping on CDMA(3gpp2) network and vice versa.
-            android.telephony.SmsMessage msg =
-                    android.telephony.SmsMessage.createFromPdu(pdu, format);
-
             if (msg == null) {
                 Rlog.e(TAG, "injectSmsPdu: createFromPdu returned null");
                 callback.onSmsInjectedResult(Intents.RESULT_SMS_GENERIC_ERROR);
@@ -449,7 +451,7 @@
                             PendingIntent sentIntent, PendingIntent deliveryIntent, Uri messageUri,
                             String callingPkg, boolean persistMessage, int priority,
                             boolean expectMore, int validityPeriod) {
-        if (mImsSmsDispatcher.isAvailable()) {
+        if (mImsSmsDispatcher.isAvailable() || mImsSmsDispatcher.isEmergencySmsSupport(destAddr)) {
             mImsSmsDispatcher.sendText(destAddr, scAddr, text, sentIntent, deliveryIntent,
                     messageUri, callingPkg, persistMessage, SMS_MESSAGE_PRIORITY_NOT_SPECIFIED,
                     false /*expectMore*/, SMS_MESSAGE_PERIOD_NOT_SPECIFIED);
@@ -627,4 +629,9 @@
     public interface SmsInjectionCallback {
         void onSmsInjectedResult(int result);
     }
+
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        mGsmInboundSmsHandler.dump(fd, pw, args);
+        mCdmaInboundSmsHandler.dump(fd, pw, args);
+    }
 }
diff --git a/src/java/com/android/internal/telephony/SubscriptionController.java b/src/java/com/android/internal/telephony/SubscriptionController.java
index 43faef5..1c5b35e 100644
--- a/src/java/com/android/internal/telephony/SubscriptionController.java
+++ b/src/java/com/android/internal/telephony/SubscriptionController.java
@@ -64,7 +64,6 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.atomic.AtomicReference;
 import java.util.stream.Collectors;
 
 /**
@@ -94,7 +93,7 @@
     private ScLocalLog mLocalLog = new ScLocalLog(MAX_LOCAL_LOG_LINES);
 
     /* The Cache of Active SubInfoRecord(s) list of currently in use SubInfoRecord(s) */
-    private AtomicReference<List<SubscriptionInfo>> mCacheActiveSubInfoList = new AtomicReference();
+    private final List<SubscriptionInfo> mCacheActiveSubInfoList = new ArrayList<>();
 
     /**
      * Copied from android.util.LocalLog with flush() adding flush and line number
@@ -163,6 +162,7 @@
     private static int mDefaultPhoneId = SubscriptionManager.DEFAULT_PHONE_INDEX;
 
     private int[] colorArr;
+    private long mLastISubServiceRegTime;
 
     public static SubscriptionController init(Phone phone) {
         synchronized (SubscriptionController.class) {
@@ -208,7 +208,8 @@
         mAppOps = (AppOpsManager)mContext.getSystemService(Context.APP_OPS_SERVICE);
 
         if(ServiceManager.getService("isub") == null) {
-                ServiceManager.addService("isub", this);
+            ServiceManager.addService("isub", this);
+            mLastISubServiceRegTime = System.currentTimeMillis();
         }
 
         if (DBG) logdl("[SubscriptionController] init by Context");
@@ -289,10 +290,10 @@
         // Get the blank bitmap for this SubInfoRecord
         Bitmap iconBitmap = BitmapFactory.decodeResource(mContext.getResources(),
                 com.android.internal.R.drawable.ic_sim_card_multi_24px_clr);
-        int mcc = cursor.getInt(cursor.getColumnIndexOrThrow(
-                SubscriptionManager.MCC));
-        int mnc = cursor.getInt(cursor.getColumnIndexOrThrow(
-                SubscriptionManager.MNC));
+        String mcc = cursor.getString(cursor.getColumnIndexOrThrow(
+                SubscriptionManager.MCC_STRING));
+        String mnc = cursor.getString(cursor.getColumnIndexOrThrow(
+                SubscriptionManager.MNC_STRING));
         String cardId = cursor.getString(cursor.getColumnIndexOrThrow(
                 SubscriptionManager.CARD_ID));
         // FIXME: consider stick this into database too
@@ -368,7 +369,7 @@
                             subList = new ArrayList<SubscriptionInfo>();
                         }
                         subList.add(subInfo);
-                }
+                    }
                 }
             } else {
                 if (DBG) logd("Query fail");
@@ -418,7 +419,7 @@
     @Override
     public SubscriptionInfo getActiveSubscriptionInfo(int subId, String callingPackage) {
         if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
-                mContext, callingPackage, "getActiveSubscriptionInfo")) {
+                mContext, subId, callingPackage, "getActiveSubscriptionInfo")) {
             return null;
         }
 
@@ -457,12 +458,29 @@
      */
     @Override
     public SubscriptionInfo getActiveSubscriptionInfoForIccId(String iccId, String callingPackage) {
+        // Query the subscriptions unconditionally, and then check whether the caller has access to
+        // the given subscription.
+        final SubscriptionInfo si = getActiveSubscriptionInfoForIccIdInternal(iccId);
+
+        final int subId = si != null
+                ? si.getSubscriptionId() : SubscriptionManager.INVALID_SUBSCRIPTION_ID;
         if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
-                mContext, callingPackage, "getActiveSubscriptionInfoForIccId") || iccId == null) {
+                mContext, subId, callingPackage, "getActiveSubscriptionInfoForIccId")) {
             return null;
         }
 
-        // Now that all security checks passes, perform the operation as ourselves.
+        return si;
+    }
+
+    /**
+     * Get the active SubscriptionInfo associated with the given iccId. The caller *must* perform
+     * permission checks when using this method.
+     */
+    private SubscriptionInfo getActiveSubscriptionInfoForIccIdInternal(String iccId) {
+        if (iccId == null) {
+            return null;
+        }
+
         final long identity = Binder.clearCallingIdentity();
         try {
             List<SubscriptionInfo> subList = getActiveSubscriptionInfoList(
@@ -496,8 +514,16 @@
     @Override
     public SubscriptionInfo getActiveSubscriptionInfoForSimSlotIndex(int slotIndex,
             String callingPackage) {
+        Phone phone = PhoneFactory.getPhone(slotIndex);
+        if (phone == null) {
+            if (DBG) {
+                loge("[getActiveSubscriptionInfoForSimSlotIndex] no phone, slotIndex=" + slotIndex);
+            }
+            return null;
+        }
         if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
-                mContext, callingPackage, "getActiveSubscriptionInfoForSimSlotIndex")) {
+                mContext, phone.getSubId(), callingPackage,
+                "getActiveSubscriptionInfoForSimSlotIndex")) {
             return null;
         }
 
@@ -542,8 +568,11 @@
     public List<SubscriptionInfo> getAllSubInfoList(String callingPackage) {
         if (DBG) logd("[getAllSubInfoList]+");
 
+        // This API isn't public, so no need to provide a valid subscription ID - we're not worried
+        // about carrier-privileged callers not having access.
         if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
-                mContext, callingPackage, "getAllSubInfoList")) {
+                mContext, SubscriptionManager.INVALID_SUBSCRIPTION_ID, callingPackage,
+                "getAllSubInfoList")) {
             return null;
         }
 
@@ -570,81 +599,71 @@
      */
     @Override
     public List<SubscriptionInfo> getActiveSubscriptionInfoList(String callingPackage) {
-
-        if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
-                mContext, callingPackage, "getActiveSubscriptionInfoList")) {
+        if (!isSubInfoReady()) {
+            if (DBG) logdl("[getActiveSubInfoList] Sub Controller not ready");
             return null;
         }
 
-        // Now that all security checks passes, perform the operation as ourselves.
-        final long identity = Binder.clearCallingIdentity();
+        boolean canReadAllPhoneState;
         try {
-            if (!isSubInfoReady()) {
-                if (DBG) logdl("[getActiveSubInfoList] Sub Controller not ready");
-                return null;
+            canReadAllPhoneState = TelephonyPermissions.checkReadPhoneState(mContext,
+                    SubscriptionManager.INVALID_SUBSCRIPTION_ID, Binder.getCallingPid(),
+                    Binder.getCallingUid(), callingPackage, "getActiveSubscriptionInfoList");
+        } catch (SecurityException e) {
+            canReadAllPhoneState = false;
+        }
+
+        synchronized (mCacheActiveSubInfoList) {
+            // If the caller can read all phone state, just return the full list.
+            if (canReadAllPhoneState) {
+                return new ArrayList<>(mCacheActiveSubInfoList);
             }
 
-            // Get the active subscription info list from the cache if the cache is not null
-            List<SubscriptionInfo> tmpCachedSubList = mCacheActiveSubInfoList.get();
-            if (tmpCachedSubList != null) {
-                if (DBG_CACHE) {
-                    for (SubscriptionInfo si : tmpCachedSubList) {
-                        logd("[getActiveSubscriptionInfoList] Getting Cached subInfo=" + si);
-                    }
-                }
-                return new ArrayList<SubscriptionInfo>(tmpCachedSubList);
-            } else {
-                if (DBG_CACHE) {
-                    logd("[getActiveSubscriptionInfoList] Cached subInfo is null");
-                }
-                return null;
-            }
-        } finally {
-            Binder.restoreCallingIdentity(identity);
+            // Filter the list to only include subscriptions which the caller can manage.
+            return mCacheActiveSubInfoList.stream()
+                    .filter(subscriptionInfo -> {
+                        try {
+                            return TelephonyPermissions.checkCallingOrSelfReadPhoneState(mContext,
+                                    subscriptionInfo.getSubscriptionId(), callingPackage,
+                                    "getActiveSubscriptionInfoList");
+                        } catch (SecurityException e) {
+                            return false;
+                        }
+                    })
+                    .collect(Collectors.toList());
         }
     }
 
     /**
      * Refresh the cache of SubInfoRecord(s) of the currently inserted SIM(s)
      */
-    @VisibleForTesting
-    protected void refreshCachedActiveSubscriptionInfoList() {
-
-        // Now that all security checks passes, perform the operation as ourselves.
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            if (!isSubInfoReady()) {
-                if (DBG_CACHE) {
-                    logdl("[refreshCachedActiveSubscriptionInfoList] "
-                            + "Sub Controller not ready ");
-                }
-                return;
-            }
-
-            List<SubscriptionInfo> subList = getSubInfo(
-                    SubscriptionManager.SIM_SLOT_INDEX + ">=0", null);
-
-            if (subList != null) {
-                // FIXME: Unnecessary when an insertion sort is used!
-                subList.sort(SUBSCRIPTION_INFO_COMPARATOR);
-
-                if (DBG_CACHE) {
-                    logdl("[refreshCachedActiveSubscriptionInfoList]- " + subList.size()
-                            + " infos return");
-                }
-            } else {
-                if (DBG_CACHE) logdl("[refreshCachedActiveSubscriptionInfoList]- no info return");
-            }
-
+    @VisibleForTesting  // For mockito to mock this method
+    public void refreshCachedActiveSubscriptionInfoList() {
+        if (!isSubInfoReady()) {
             if (DBG_CACHE) {
-                for (SubscriptionInfo si : subList) {
-                    logd("[refreshCachedActiveSubscriptionInfoList] Setting Cached subInfo=" + si);
+                logdl("[refreshCachedActiveSubscriptionInfoList] "
+                        + "Sub Controller not ready ");
+            }
+            return;
+        }
+
+        synchronized (mCacheActiveSubInfoList) {
+            mCacheActiveSubInfoList.clear();
+            List<SubscriptionInfo> activeSubscriptionInfoList = getSubInfo(
+                    SubscriptionManager.SIM_SLOT_INDEX + ">=0", null);
+            if (activeSubscriptionInfoList != null) {
+                mCacheActiveSubInfoList.addAll(activeSubscriptionInfoList);
+            }
+            if (DBG_CACHE) {
+                if (!mCacheActiveSubInfoList.isEmpty()) {
+                    for (SubscriptionInfo si : mCacheActiveSubInfoList) {
+                        logd("[refreshCachedActiveSubscriptionInfoList] Setting Cached info="
+                                + si);
+                    }
+                } else {
+                    logdl("[refreshCachedActiveSubscriptionInfoList]- no info return");
                 }
             }
-            mCacheActiveSubInfoList.set(subList);
-
-        } finally {
-            Binder.restoreCallingIdentity(identity);
         }
     }
 
@@ -655,25 +674,14 @@
      */
     @Override
     public int getActiveSubInfoCount(String callingPackage) {
-        if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
-                mContext, callingPackage, "getActiveSubInfoCount")) {
+        // Let getActiveSubscriptionInfoList perform permission checks / filtering.
+        List<SubscriptionInfo> records = getActiveSubscriptionInfoList(callingPackage);
+        if (records == null) {
+            if (VDBG) logd("[getActiveSubInfoCount] records null");
             return 0;
         }
-
-        // Now that all security checks passes, perform the operation as ourselves.
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            List<SubscriptionInfo> records = getActiveSubscriptionInfoList(
-                    mContext.getOpPackageName());
-            if (records == null) {
-                if (VDBG) logd("[getActiveSubInfoCount] records null");
-                return 0;
-            }
-            if (VDBG) logd("[getActiveSubInfoCount]- count: " + records.size());
-            return records.size();
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
+        if (VDBG) logd("[getActiveSubInfoCount]- count: " + records.size());
+        return records.size();
     }
 
     /**
@@ -685,8 +693,11 @@
     public int getAllSubInfoCount(String callingPackage) {
         if (DBG) logd("[getAllSubInfoCount]+");
 
+        // This API isn't public, so no need to provide a valid subscription ID - we're not worried
+        // about carrier-privileged callers not having access.
         if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
-                mContext, callingPackage, "getAllSubInfoCount")) {
+                mContext, SubscriptionManager.INVALID_SUBSCRIPTION_ID, callingPackage,
+                "getAllSubInfoCount")) {
             return 0;
         }
 
@@ -725,8 +736,11 @@
 
     @Override
     public List<SubscriptionInfo> getAvailableSubscriptionInfoList(String callingPackage) {
+        // This API isn't public, so no need to provide a valid subscription ID - we're not worried
+        // about carrier-privileged callers not having access.
         if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
-                mContext, callingPackage, "getAvailableSubscriptionInfoList")) {
+                mContext, SubscriptionManager.INVALID_SUBSCRIPTION_ID, callingPackage,
+                "getAvailableSubscriptionInfoList")) {
             throw new SecurityException("Need READ_PHONE_STATE to call "
                     + " getAvailableSubscriptionInfoList");
         }
@@ -1359,18 +1373,22 @@
      * @return the number of records updated
      */
     public int setMccMnc(String mccMnc, int subId) {
+        String mccString = mccMnc.substring(0, 3);
+        String mncString = mccMnc.substring(3);
         int mcc = 0;
         int mnc = 0;
         try {
-            mcc = Integer.parseInt(mccMnc.substring(0,3));
-            mnc = Integer.parseInt(mccMnc.substring(3));
+            mcc = Integer.parseInt(mccString);
+            mnc = Integer.parseInt(mncString);
         } catch (NumberFormatException e) {
             loge("[setMccMnc] - couldn't parse mcc/mnc: " + mccMnc);
         }
         if (DBG) logd("[setMccMnc]+ mcc/mnc:" + mcc + "/" + mnc + " subId:" + subId);
-        ContentValues value = new ContentValues(2);
+        ContentValues value = new ContentValues(4);
         value.put(SubscriptionManager.MCC, mcc);
         value.put(SubscriptionManager.MNC, mnc);
+        value.put(SubscriptionManager.MCC_STRING, mccString);
+        value.put(SubscriptionManager.MNC_STRING, mncString);
 
         int result = mContext.getContentResolver().update(SubscriptionManager.CONTENT_URI, value,
                 SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" + Long.toString(subId), null);
@@ -1781,7 +1799,7 @@
                 mDefaultFallbackSubId = subId;
                 // Update MCC MNC device configuration information
                 String defaultMccMnc = mTelephonyManager.getSimOperatorNumericForPhone(phoneId);
-                MccTable.updateMccMncConfiguration(mContext, defaultMccMnc, false);
+                MccTable.updateMccMncConfiguration(mContext, defaultMccMnc);
 
                 // Broadcast an Intent for default sub change
                 Intent intent = new Intent(TelephonyIntents.ACTION_DEFAULT_SUBSCRIPTION_CHANGED);
@@ -1862,58 +1880,48 @@
         return subIds[0];
     }
 
-    public List<SubscriptionInfo> getSubInfoUsingSlotIndexWithCheck(int slotIndex,
-                                                                    boolean needCheck,
-                                                                    String callingPackage) {
-        if (DBG) logd("[getSubInfoUsingSlotIndexWithCheck]+ slotIndex:" + slotIndex);
-        if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
-                mContext, callingPackage, "getSubInfoUsingSlotIndexWithCheck")) {
+    /** Must be public for access from instrumentation tests. */
+    @VisibleForTesting
+    public List<SubscriptionInfo> getSubInfoUsingSlotIndexPrivileged(int slotIndex,
+            boolean needCheck) {
+        if (DBG) logd("[getSubInfoUsingSlotIndexPrivileged]+ slotIndex:" + slotIndex);
+        if (slotIndex == SubscriptionManager.DEFAULT_SIM_SLOT_INDEX) {
+            slotIndex = getSlotIndex(getDefaultSubId());
+        }
+        if (!SubscriptionManager.isValidSlotIndex(slotIndex)) {
+            if (DBG) logd("[getSubInfoUsingSlotIndexPrivileged]- invalid slotIndex");
             return null;
         }
 
-        // Now that all security checks passes, perform the operation as ourselves.
-        final long identity = Binder.clearCallingIdentity();
+        if (needCheck && !isSubInfoReady()) {
+            if (DBG) logd("[getSubInfoUsingSlotIndexPrivileged]- not ready");
+            return null;
+        }
+
+        Cursor cursor = mContext.getContentResolver().query(SubscriptionManager.CONTENT_URI,
+                null, SubscriptionManager.SIM_SLOT_INDEX + "=?",
+                new String[]{String.valueOf(slotIndex)}, null);
+        ArrayList<SubscriptionInfo> subList = null;
         try {
-            if (slotIndex == SubscriptionManager.DEFAULT_SIM_SLOT_INDEX) {
-                slotIndex = getSlotIndex(getDefaultSubId());
-            }
-            if (!SubscriptionManager.isValidSlotIndex(slotIndex)) {
-                if (DBG) logd("[getSubInfoUsingSlotIndexWithCheck]- invalid slotIndex");
-                return null;
-            }
-
-            if (needCheck && !isSubInfoReady()) {
-                if (DBG) logd("[getSubInfoUsingSlotIndexWithCheck]- not ready");
-                return null;
-            }
-
-            Cursor cursor = mContext.getContentResolver().query(SubscriptionManager.CONTENT_URI,
-                    null, SubscriptionManager.SIM_SLOT_INDEX + "=?",
-                    new String[]{String.valueOf(slotIndex)}, null);
-            ArrayList<SubscriptionInfo> subList = null;
-            try {
-                if (cursor != null) {
-                    while (cursor.moveToNext()) {
-                        SubscriptionInfo subInfo = getSubInfoRecord(cursor);
-                        if (subInfo != null) {
-                            if (subList == null) {
-                                subList = new ArrayList<SubscriptionInfo>();
-                            }
-                            subList.add(subInfo);
+            if (cursor != null) {
+                while (cursor.moveToNext()) {
+                    SubscriptionInfo subInfo = getSubInfoRecord(cursor);
+                    if (subInfo != null) {
+                        if (subList == null) {
+                            subList = new ArrayList<SubscriptionInfo>();
                         }
+                        subList.add(subInfo);
                     }
                 }
-            } finally {
-                if (cursor != null) {
-                    cursor.close();
-                }
             }
-            if (DBG) logd("[getSubInfoUsingSlotIndex]- null info return");
-
-            return subList;
         } finally {
-            Binder.restoreCallingIdentity(identity);
+            if (cursor != null) {
+                cursor.close();
+            }
         }
+        if (DBG) logd("[getSubInfoUsingSlotIndex]- null info return");
+
+        return subList;
     }
 
     private void validateSubId(int subId) {
@@ -2059,7 +2067,7 @@
     @Override
     public String getSubscriptionProperty(int subId, String propKey, String callingPackage) {
         if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
-                mContext, callingPackage, "getSubInfoUsingSlotIndexWithCheck")) {
+                mContext, subId, callingPackage, "getSubscriptionProperty")) {
             return null;
         }
         String resultValue = null;
@@ -2133,6 +2141,7 @@
         final long token = Binder.clearCallingIdentity();
         try {
             pw.println("SubscriptionController:");
+            pw.println(" mLastISubServiceRegTime=" + mLastISubServiceRegTime);
             pw.println(" defaultSubId=" + getDefaultSubId());
             pw.println(" defaultDataSubId=" + getDefaultDataSubId());
             pw.println(" defaultVoiceSubId=" + getDefaultVoiceSubId());
diff --git a/src/java/com/android/internal/telephony/SubscriptionInfoUpdater.java b/src/java/com/android/internal/telephony/SubscriptionInfoUpdater.java
index 723311b..c16ba7d 100644
--- a/src/java/com/android/internal/telephony/SubscriptionInfoUpdater.java
+++ b/src/java/com/android/internal/telephony/SubscriptionInfoUpdater.java
@@ -20,12 +20,10 @@
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.UserSwitchObserver;
-import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
-import android.content.IntentFilter;
 import android.content.SharedPreferences;
 import android.content.pm.IPackageManager;
 import android.os.AsyncResult;
@@ -55,7 +53,6 @@
 import com.android.internal.telephony.euicc.EuiccController;
 import com.android.internal.telephony.uicc.IccRecords;
 import com.android.internal.telephony.uicc.IccUtils;
-import com.android.internal.telephony.uicc.UiccProfile;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -69,6 +66,7 @@
     private static final String LOG_TAG = "SubscriptionInfoUpdater";
     private static final int PROJECT_SIM_NUM = TelephonyManager.getDefault().getPhoneCount();
 
+    private static final int EVENT_INVALID = -1;
     private static final int EVENT_GET_NETWORK_SELECTION_MODE_DONE = 2;
     private static final int EVENT_SIM_LOADED = 3;
     private static final int EVENT_SIM_ABSENT = 4;
@@ -133,9 +131,6 @@
         mEuiccManager = (EuiccManager) mContext.getSystemService(Context.EUICC_SERVICE);
         mPackageManager = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
 
-        IntentFilter intentFilter = new IntentFilter(UiccProfile.ACTION_INTERNAL_SIM_STATE_CHANGED);
-        mContext.registerReceiver(sReceiver, intentFilter);
-
         mCarrierServiceBindHelper = new CarrierServiceBindHelper(mContext);
         initializeCarrierApps();
     }
@@ -173,56 +168,31 @@
                 mCurrentlyActiveUserId);
     }
 
-    private final BroadcastReceiver sReceiver = new  BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            logd("[Receiver]+");
-            String action = intent.getAction();
-            logd("Action: " + action);
-
-            if (!action.equals(UiccProfile.ACTION_INTERNAL_SIM_STATE_CHANGED)) {
-                return;
-            }
-
-            int slotIndex = intent.getIntExtra(PhoneConstants.PHONE_KEY,
-                    SubscriptionManager.INVALID_SIM_SLOT_INDEX);
-            logd("slotIndex: " + slotIndex);
-            if (!SubscriptionManager.isValidSlotIndex(slotIndex)) {
-                logd("ACTION_INTERNAL_SIM_STATE_CHANGED contains invalid slotIndex: " + slotIndex);
-                return;
-            }
-
-            String simStatus = intent.getStringExtra(IccCardConstants.INTENT_KEY_ICC_STATE);
-            logd("simStatus: " + simStatus);
-
-            if (action.equals(UiccProfile.ACTION_INTERNAL_SIM_STATE_CHANGED)) {
-                if (IccCardConstants.INTENT_VALUE_ICC_ABSENT.equals(simStatus)) {
-                    sendMessage(obtainMessage(EVENT_SIM_ABSENT, slotIndex, -1));
-                } else if (IccCardConstants.INTENT_VALUE_ICC_UNKNOWN.equals(simStatus)) {
-                    sendMessage(obtainMessage(EVENT_SIM_UNKNOWN, slotIndex, -1));
-                } else if (IccCardConstants.INTENT_VALUE_ICC_CARD_IO_ERROR.equals(simStatus)) {
-                    sendMessage(obtainMessage(EVENT_SIM_IO_ERROR, slotIndex, -1));
-                } else if (IccCardConstants.INTENT_VALUE_ICC_CARD_RESTRICTED.equals(simStatus)) {
-                    sendMessage(obtainMessage(EVENT_SIM_RESTRICTED, slotIndex, -1));
-                } else if (IccCardConstants.INTENT_VALUE_ICC_NOT_READY.equals(simStatus)) {
-                    sendEmptyMessage(EVENT_SIM_NOT_READY);
-                } else if (IccCardConstants.INTENT_VALUE_ICC_LOCKED.equals(simStatus)) {
-                    String reason = intent.getStringExtra(
-                        IccCardConstants.INTENT_KEY_LOCKED_REASON);
-                    sendMessage(obtainMessage(EVENT_SIM_LOCKED, slotIndex, -1, reason));
-                } else if (IccCardConstants.INTENT_VALUE_ICC_LOADED.equals(simStatus)) {
-                    sendMessage(obtainMessage(EVENT_SIM_LOADED, slotIndex, -1));
-                } else if (IccCardConstants.INTENT_VALUE_ICC_READY.equals(simStatus)) {
-                    sendMessage(obtainMessage(EVENT_SIM_READY, slotIndex, -1));
-                } else if (IccCardConstants.INTENT_VALUE_ICC_IMSI.equals(simStatus)) {
-                    sendMessage(obtainMessage(EVENT_SIM_IMSI, slotIndex, -1));
-                } else {
-                    logd("Ignoring simStatus: " + simStatus);
-                }
-            }
-            logd("[Receiver]-");
+    public void updateInternalIccState(String simStatus, String reason, int slotId) {
+        logd("updateInternalIccState to simStatus " + simStatus + " reason " + reason
+                + " slotId " + slotId);
+        int message = internalIccStateToMessage(simStatus);
+        if (message != EVENT_INVALID) {
+            sendMessage(obtainMessage(message, slotId, -1, reason));
         }
-    };
+    }
+
+    private int internalIccStateToMessage(String simStatus) {
+        switch(simStatus) {
+            case IccCardConstants.INTENT_VALUE_ICC_ABSENT: return EVENT_SIM_ABSENT;
+            case IccCardConstants.INTENT_VALUE_ICC_UNKNOWN: return EVENT_SIM_UNKNOWN;
+            case IccCardConstants.INTENT_VALUE_ICC_CARD_IO_ERROR: return EVENT_SIM_IO_ERROR;
+            case IccCardConstants.INTENT_VALUE_ICC_CARD_RESTRICTED: return EVENT_SIM_RESTRICTED;
+            case IccCardConstants.INTENT_VALUE_ICC_NOT_READY: return EVENT_SIM_NOT_READY;
+            case IccCardConstants.INTENT_VALUE_ICC_LOCKED: return EVENT_SIM_LOCKED;
+            case IccCardConstants.INTENT_VALUE_ICC_LOADED: return EVENT_SIM_LOADED;
+            case IccCardConstants.INTENT_VALUE_ICC_READY: return EVENT_SIM_READY;
+            case IccCardConstants.INTENT_VALUE_ICC_IMSI: return EVENT_SIM_IMSI;
+            default:
+                logd("Ignoring simStatus: " + simStatus);
+                return EVENT_INVALID;
+        }
+    }
 
     private boolean isAllIccIdQueryDone() {
         for (int i = 0; i < PROJECT_SIM_NUM; i++) {
@@ -325,16 +295,6 @@
         sendMessage(obtainMessage(EVENT_REFRESH_EMBEDDED_SUBSCRIPTIONS, callback));
     }
 
-    private static class QueryIccIdUserObj {
-        public String reason;
-        public int slotId;
-
-        QueryIccIdUserObj(String reason, int slotId) {
-            this.reason = reason;
-            this.slotId = slotId;
-        }
-    };
-
     private 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");
@@ -345,12 +305,12 @@
         if (iccId == null) {
             IccCard iccCard = mPhone[slotId].getIccCard();
             if (iccCard == null) {
-                logd("handleSimLoaded: IccCard null");
+                logd("handleSimLocked: IccCard null");
                 return;
             }
             IccRecords records = iccCard.getIccRecords();
             if (records == null) {
-                logd("handleSimLoaded: IccRecords null");
+                logd("handleSimLocked: IccRecords null");
                 return;
             }
             if (IccUtils.stripTrailingFs(records.getFullIccId()) == null) {
@@ -422,7 +382,7 @@
 
                 if (!TextUtils.isEmpty(operator)) {
                     if (subId == SubscriptionController.getInstance().getDefaultSubId()) {
-                        MccTable.updateMccMncConfiguration(mContext, operator, false);
+                        MccTable.updateMccMncConfiguration(mContext, operator);
                     }
                     SubscriptionController.getInstance().setMccMnc(operator, subId);
                 } else {
@@ -603,9 +563,8 @@
         String[] decIccId = new String[PROJECT_SIM_NUM];
         for (int i = 0; i < PROJECT_SIM_NUM; i++) {
             oldIccId[i] = null;
-            List<SubscriptionInfo> oldSubInfo =
-                    SubscriptionController.getInstance().getSubInfoUsingSlotIndexWithCheck(i, false,
-                    mContext.getOpPackageName());
+            List<SubscriptionInfo> oldSubInfo = SubscriptionController.getInstance()
+                    .getSubInfoUsingSlotIndexPrivileged(i, false);
             decIccId[i] = IccUtils.getDecimalSubstring(mIccId[i]);
             if (oldSubInfo != null && oldSubInfo.size() > 0) {
                 oldIccId[i] = oldSubInfo.get(0).getIccId();
@@ -642,8 +601,6 @@
         }
 
         //check if the inserted SIM is new SIM
-        int nNewCardCount = 0;
-        int nNewSimStatus = 0;
         for (int i = 0; i < PROJECT_SIM_NUM; i++) {
             if (mInsertSimState[i] == SIM_NOT_INSERT) {
                 logd("updateSubscriptionInfoByIccId: No SIM inserted in slot " + i + " this time");
@@ -655,25 +612,11 @@
                             + Integer.toString(mInsertSimState[i]), i);
                     logd("SUB" + (i + 1) + " has invalid IccId");
                 } else /*if (sInsertSimState[i] != SIM_NOT_INSERT)*/ {
+                    logd("updateSubscriptionInfoByIccId: adding subscription info record: iccid: "
+                            + mIccId[i] + "slot: " + i);
                     mSubscriptionManager.addSubscriptionInfoRecord(mIccId[i], i);
                 }
                 if (isNewSim(mIccId[i], decIccId[i], oldIccId)) {
-                    nNewCardCount++;
-                    switch (i) {
-                        case PhoneConstants.SUB1:
-                            nNewSimStatus |= STATUS_SIM1_INSERTED;
-                            break;
-                        case PhoneConstants.SUB2:
-                            nNewSimStatus |= STATUS_SIM2_INSERTED;
-                            break;
-                        case PhoneConstants.SUB3:
-                            nNewSimStatus |= STATUS_SIM3_INSERTED;
-                            break;
-                        //case PhoneConstants.SUB3:
-                        //    nNewSimStatus |= STATUS_SIM4_INSERTED;
-                        //    break;
-                    }
-
                     mInsertSimState[i] = SIM_NEW;
                 }
             }
@@ -880,6 +823,7 @@
             sSimCardState[phoneId] = state;
             Intent i = new Intent(TelephonyManager.ACTION_SIM_CARD_STATE_CHANGED);
             i.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+            i.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
             i.putExtra(TelephonyManager.EXTRA_SIM_STATE, state);
             SubscriptionManager.putPhoneIdAndSubIdExtra(i, phoneId);
             logd("Broadcasting intent ACTION_SIM_CARD_STATE_CHANGED " + simStateString(state)
@@ -897,6 +841,7 @@
                 && sSimApplicationState[phoneId] == TelephonyManager.SIM_STATE_UNKNOWN))) {
             sSimApplicationState[phoneId] = state;
             Intent i = new Intent(TelephonyManager.ACTION_SIM_APPLICATION_STATE_CHANGED);
+            i.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
             i.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
             i.putExtra(TelephonyManager.EXTRA_SIM_STATE, state);
             SubscriptionManager.putPhoneIdAndSubIdExtra(i, phoneId);
@@ -937,11 +882,6 @@
         }
     }
 
-    public void dispose() {
-        logd("[dispose]");
-        mContext.unregisterReceiver(sReceiver);
-    }
-
     private void logd(String message) {
         Rlog.d(LOG_TAG, message);
     }
diff --git a/src/java/com/android/internal/telephony/TelephonyComponentFactory.java b/src/java/com/android/internal/telephony/TelephonyComponentFactory.java
index e379a69..dc8d9fa 100644
--- a/src/java/com/android/internal/telephony/TelephonyComponentFactory.java
+++ b/src/java/com/android/internal/telephony/TelephonyComponentFactory.java
@@ -20,6 +20,7 @@
 import android.database.Cursor;
 import android.os.Handler;
 import android.os.IDeviceIdleController;
+import android.os.Looper;
 import android.os.ServiceManager;
 import android.telephony.AccessNetworkConstants.TransportType;
 
@@ -29,7 +30,6 @@
 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.uicc.IccCardProxy;
 import com.android.internal.telephony.uicc.IccCardStatus;
 import com.android.internal.telephony.uicc.UiccCard;
 import com.android.internal.telephony.uicc.UiccProfile;
@@ -66,10 +66,18 @@
     }
 
     /**
+     * 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 new NitzStateMachine(phone);
+        return USE_NEW_NITZ_STATE_MACHINE
+                ? new NewNitzStateMachine(phone)
+                : new OldNitzStateMachine(phone);
     }
 
     public SimActivationTracker makeSimActivationTracker(Phone phone) {
@@ -100,16 +108,12 @@
         return new IccSmsInterfaceManager(phone);
     }
 
-    public IccCardProxy makeIccCardProxy(Context context, CommandsInterface ci, int phoneId) {
-        return new IccCardProxy(context, ci, phoneId);
-    }
-
     /**
      * Create a new UiccProfile object.
      */
     public UiccProfile makeUiccProfile(Context context, CommandsInterface ci, IccCardStatus ics,
-                                       int phoneId, UiccCard uiccCard) {
-        return new UiccProfile(context, ci, ics, phoneId, uiccCard);
+                                       int phoneId, UiccCard uiccCard, Object lock) {
+        return new UiccProfile(context, ci, ics, phoneId, uiccCard, lock);
     }
 
     public EriManager makeEriManager(Phone phone, Context context, int eriFileSource) {
@@ -177,4 +181,7 @@
         return IDeviceIdleController.Stub.asInterface(
                 ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
     }
+    public LocaleTracker makeLocaleTracker(Phone phone, Looper looper) {
+        return new LocaleTracker(phone, looper);
+    }
 }
diff --git a/src/java/com/android/internal/telephony/TelephonyTester.java b/src/java/com/android/internal/telephony/TelephonyTester.java
index d4e0c6b..52235e4 100644
--- a/src/java/com/android/internal/telephony/TelephonyTester.java
+++ b/src/java/com/android/internal/telephony/TelephonyTester.java
@@ -23,7 +23,6 @@
 import android.net.Uri;
 import android.os.BadParcelableException;
 import android.os.Build;
-import android.telephony.NetworkRegistrationState;
 import android.telephony.Rlog;
 import android.telephony.ServiceState;
 import android.telephony.ims.ImsCallProfile;
@@ -115,6 +114,7 @@
     private static final String EXTRA_DATA_REG_STATE = "data_reg_state";
     private static final String EXTRA_VOICE_ROAMING_TYPE = "voice_roaming_type";
     private static final String EXTRA_DATA_ROAMING_TYPE = "data_roaming_type";
+    private static final String EXTRA_OPERATOR = "operator";
 
     private static final String ACTION_RESET = "reset";
 
@@ -341,13 +341,13 @@
         }
         if (mServiceStateTestIntent.hasExtra(EXTRA_VOICE_REG_STATE)) {
             ss.setVoiceRegState(mServiceStateTestIntent.getIntExtra(EXTRA_VOICE_REG_STATE,
-                    NetworkRegistrationState.REG_STATE_UNKNOWN));
-            log("Override voice reg state with " + ss.getVoiceRegState());
+                    ServiceState.STATE_OUT_OF_SERVICE));
+            log("Override voice service state with " + ss.getVoiceRegState());
         }
         if (mServiceStateTestIntent.hasExtra(EXTRA_DATA_REG_STATE)) {
             ss.setDataRegState(mServiceStateTestIntent.getIntExtra(EXTRA_DATA_REG_STATE,
-                    NetworkRegistrationState.REG_STATE_UNKNOWN));
-            log("Override data reg state with " + ss.getDataRegState());
+                    ServiceState.STATE_OUT_OF_SERVICE));
+            log("Override data service state with " + ss.getDataRegState());
         }
         if (mServiceStateTestIntent.hasExtra(EXTRA_VOICE_RAT)) {
             ss.setRilVoiceRadioTechnology(mServiceStateTestIntent.getIntExtra(EXTRA_VOICE_RAT,
@@ -369,5 +369,10 @@
                     ServiceState.ROAMING_TYPE_UNKNOWN));
             log("Override data roaming type with " + ss.getDataRoamingType());
         }
+        if (mServiceStateTestIntent.hasExtra(EXTRA_OPERATOR)) {
+            String operator = mServiceStateTestIntent.getStringExtra(EXTRA_OPERATOR);
+            ss.setOperatorName(operator, operator, "");
+            log("Override operator with " + operator);
+        }
     }
 }
\ No newline at end of file
diff --git a/src/java/com/android/internal/telephony/UiccSmsController.java b/src/java/com/android/internal/telephony/UiccSmsController.java
index 59449b8..5fb2ff3 100644
--- a/src/java/com/android/internal/telephony/UiccSmsController.java
+++ b/src/java/com/android/internal/telephony/UiccSmsController.java
@@ -24,7 +24,6 @@
 import android.content.Context;
 import android.net.Uri;
 import android.os.Binder;
-import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.provider.Telephony.Sms.Intents;
 import android.telephony.Rlog;
@@ -33,13 +32,16 @@
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.util.List;
 
 /**
- * UiccSmsController to provide an inter-process communication to
- * access Sms in Icc.
+ * Implements the ISmsImplBase interface used in the SmsManager API.
  */
-public class UiccSmsController extends ISms.Stub {
+public class UiccSmsController extends ISmsImplBase {
     static final String LOG_TAG = "RIL_UiccSmsController";
 
     protected UiccSmsController() {
@@ -57,9 +59,8 @@
     }
 
     @Override
-    public boolean
-    updateMessageOnIccEfForSubscriber(int subId, String callingPackage, int index, int status,
-                byte[] pdu) throws android.os.RemoteException {
+    public boolean updateMessageOnIccEfForSubscriber(int subId, String callingPackage, int index,
+            int status, byte[] pdu) {
         IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId);
         if (iccSmsIntMgr != null) {
             return iccSmsIntMgr.updateMessageOnIccEf(callingPackage, index, status, pdu);
@@ -72,7 +73,7 @@
 
     @Override
     public boolean copyMessageToIccEfForSubscriber(int subId, String callingPackage, int status,
-            byte[] pdu, byte[] smsc) throws android.os.RemoteException {
+            byte[] pdu, byte[] smsc) {
         IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId);
         if (iccSmsIntMgr != null) {
             return iccSmsIntMgr.copyMessageToIccEf(callingPackage, status, pdu, smsc);
@@ -84,8 +85,7 @@
     }
 
     @Override
-    public List<SmsRawData> getAllMessagesFromIccEfForSubscriber(int subId, String callingPackage)
-                throws android.os.RemoteException {
+    public List<SmsRawData> getAllMessagesFromIccEfForSubscriber(int subId, String callingPackage) {
         IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId);
         if (iccSmsIntMgr != null) {
             return iccSmsIntMgr.getAllMessagesFromIccEf(callingPackage);
@@ -112,6 +112,7 @@
         }
     }
 
+    @Override
     public void sendDataForSubscriberWithSelfPermissions(int subId, String callingPackage,
             String destAddr, String scAddr, int destPort, byte[] data, PendingIntent sentIntent,
             PendingIntent deliveryIntent) {
@@ -125,12 +126,6 @@
         }
     }
 
-    public void sendText(String callingPackage, String destAddr, String scAddr,
-            String text, PendingIntent sentIntent, PendingIntent deliveryIntent) {
-        sendTextForSubscriber(getPreferredSmsSubscription(), callingPackage, destAddr, scAddr,
-            text, sentIntent, deliveryIntent, true /* persistMessageForNonDefaultSmsApp*/);
-    }
-
     @Override
     public void sendTextForSubscriber(int subId, String callingPackage, String destAddr,
             String scAddr, String text, PendingIntent sentIntent, PendingIntent deliveryIntent,
@@ -146,6 +141,7 @@
         }
     }
 
+    @Override
     public void sendTextForSubscriberWithSelfPermissions(int subId, String callingPackage,
             String destAddr, String scAddr, String text, PendingIntent sentIntent,
             PendingIntent deliveryIntent, boolean persistMessage) {
@@ -174,19 +170,10 @@
         }
     }
 
-    public void sendMultipartText(String callingPackage, String destAddr, String scAddr,
-            List<String> parts, List<PendingIntent> sentIntents,
-            List<PendingIntent> deliveryIntents) throws android.os.RemoteException {
-         sendMultipartTextForSubscriber(getPreferredSmsSubscription(), callingPackage, destAddr,
-                 scAddr, parts, sentIntents, deliveryIntents,
-                 true /* persistMessageForNonDefaultSmsApp */);
-    }
-
     @Override
     public void sendMultipartTextForSubscriber(int subId, String callingPackage, String destAddr,
             String scAddr, List<String> parts, List<PendingIntent> sentIntents,
-            List<PendingIntent> deliveryIntents, boolean persistMessageForNonDefaultSmsApp)
-            throws android.os.RemoteException {
+            List<PendingIntent> deliveryIntents, boolean persistMessageForNonDefaultSmsApp) {
         IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId);
         if (iccSmsIntMgr != null ) {
             iccSmsIntMgr.sendMultipartText(callingPackage, destAddr, scAddr, parts, sentIntents,
@@ -215,15 +202,14 @@
     }
 
     @Override
-    public boolean enableCellBroadcastForSubscriber(int subId, int messageIdentifier, int ranType)
-                throws android.os.RemoteException {
+    public boolean enableCellBroadcastForSubscriber(int subId, int messageIdentifier, int ranType) {
         return enableCellBroadcastRangeForSubscriber(subId, messageIdentifier, messageIdentifier,
                 ranType);
     }
 
     @Override
     public boolean enableCellBroadcastRangeForSubscriber(int subId, int startMessageId,
-            int endMessageId, int ranType) throws android.os.RemoteException {
+            int endMessageId, int ranType) {
         IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId);
         if (iccSmsIntMgr != null ) {
             return iccSmsIntMgr.enableCellBroadcastRange(startMessageId, endMessageId, ranType);
@@ -235,15 +221,15 @@
     }
 
     @Override
-    public boolean disableCellBroadcastForSubscriber(int subId, int messageIdentifier, int ranType)
-                throws android.os.RemoteException {
+    public boolean disableCellBroadcastForSubscriber(int subId,
+            int messageIdentifier, int ranType) {
         return disableCellBroadcastRangeForSubscriber(subId, messageIdentifier, messageIdentifier,
                 ranType);
     }
 
     @Override
     public boolean disableCellBroadcastRangeForSubscriber(int subId, int startMessageId,
-            int endMessageId, int ranType) throws android.os.RemoteException {
+            int endMessageId, int ranType) {
         IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId);
         if (iccSmsIntMgr != null ) {
             return iccSmsIntMgr.disableCellBroadcastRange(startMessageId, endMessageId, ranType);
@@ -355,14 +341,6 @@
     }
 
     /**
-     * Get sms interface manager object based on subscription.
-     * @return ICC SMS manager
-     */
-    private @Nullable IccSmsInterfaceManager getIccSmsInterfaceManager(int subId) {
-        return getPhone(subId).getIccSmsInterfaceManager();
-    }
-
-    /**
      * Get User preferred SMS subscription
      * @return User preferred SMS subscription
      */
@@ -382,7 +360,7 @@
 
     @Override
     public void sendStoredText(int subId, String callingPkg, Uri messageUri, String scAddress,
-            PendingIntent sentIntent, PendingIntent deliveryIntent) throws RemoteException {
+            PendingIntent sentIntent, PendingIntent deliveryIntent) {
         IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId);
         if (iccSmsIntMgr != null) {
             iccSmsIntMgr.sendStoredText(callingPkg, messageUri, scAddress, sentIntent,
@@ -395,8 +373,8 @@
 
     @Override
     public void sendStoredMultipartText(int subId, String callingPkg, Uri messageUri,
-            String scAddress, List<PendingIntent> sentIntents, List<PendingIntent> deliveryIntents)
-            throws RemoteException {
+            String scAddress, List<PendingIntent> sentIntents,
+            List<PendingIntent> deliveryIntents) {
         IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId);
         if (iccSmsIntMgr != null ) {
             iccSmsIntMgr.sendStoredMultipartText(callingPkg, messageUri, scAddress, sentIntents,
@@ -413,6 +391,36 @@
         return getPhone(subId).getAppSmsManager().createAppSpecificSmsToken(callingPkg, intent);
     }
 
+    @Override
+    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        IndentingPrintWriter indentingPW =
+                new IndentingPrintWriter(pw, "    " /* singleIndent */);
+        for (Phone phone : PhoneFactory.getPhones()) {
+            int subId = phone.getSubId();
+            indentingPW.println(String.format("SmsManager for subId = %d:", subId));
+            indentingPW.increaseIndent();
+            if (getIccSmsInterfaceManager(subId) != null) {
+                getIccSmsInterfaceManager(subId).dump(fd, indentingPW, args);
+            }
+            indentingPW.decreaseIndent();
+        }
+        indentingPW.flush();
+    }
+
+    public void sendText(String callingPackage, String destAddr, String scAddr,
+            String text, PendingIntent sentIntent, PendingIntent deliveryIntent) {
+        sendTextForSubscriber(getPreferredSmsSubscription(), callingPackage, destAddr, scAddr,
+                text, sentIntent, deliveryIntent, true /* persistMessageForNonDefaultSmsApp*/);
+    }
+
+    public void sendMultipartText(String callingPackage, String destAddr, String scAddr,
+            List<String> parts, List<PendingIntent> sentIntents,
+            List<PendingIntent> deliveryIntents) {
+        sendMultipartTextForSubscriber(getPreferredSmsSubscription(), callingPackage, destAddr,
+                scAddr, parts, sentIntents, deliveryIntents,
+                true /* persistMessageForNonDefaultSmsApp */);
+    }
+
     private void sendErrorInPendingIntent(@Nullable PendingIntent intent, int errorCode) {
         if (intent != null) {
             try {
@@ -427,4 +435,12 @@
             sendErrorInPendingIntent(intent, errorCode);
         }
     }
+
+    /**
+     * Get sms interface manager object based on subscription.
+     * @return ICC SMS manager
+     */
+    private @Nullable IccSmsInterfaceManager getIccSmsInterfaceManager(int subId) {
+        return getPhone(subId).getIccSmsInterfaceManager();
+    }
 }
diff --git a/src/java/com/android/internal/telephony/cat/AppInterface.java b/src/java/com/android/internal/telephony/cat/AppInterface.java
index 1f2d3a0..9410b98 100755
--- a/src/java/com/android/internal/telephony/cat/AppInterface.java
+++ b/src/java/com/android/internal/telephony/cat/AppInterface.java
@@ -78,6 +78,7 @@
         SEND_SS(0x11),
         SEND_USSD(0x12),
         SEND_SMS(0x13),
+        RUN_AT(0x34),
         SEND_DTMF(0x14),
         SET_UP_EVENT_LIST(0x05),
         SET_UP_IDLE_MODE_TEXT(0x28),
diff --git a/src/java/com/android/internal/telephony/cat/CatCmdMessage.java b/src/java/com/android/internal/telephony/cat/CatCmdMessage.java
index 62d8869..cbad866 100644
--- a/src/java/com/android/internal/telephony/cat/CatCmdMessage.java
+++ b/src/java/com/android/internal/telephony/cat/CatCmdMessage.java
@@ -82,6 +82,8 @@
         case SET_UP_IDLE_MODE_TEXT:
         case SEND_DTMF:
         case SEND_SMS:
+        case REFRESH:
+        case RUN_AT:
         case SEND_SS:
         case SEND_USSD:
             mTextMsg = ((DisplayTextParams) cmdParams).mTextMsg;
@@ -121,7 +123,6 @@
             mSetupEventListSettings.eventList = ((SetEventListParams) cmdParams).mEventInfo;
             break;
         case PROVIDE_LOCAL_INFORMATION:
-        case REFRESH:
         default:
             break;
         }
diff --git a/src/java/com/android/internal/telephony/cat/CatService.java b/src/java/com/android/internal/telephony/cat/CatService.java
index 91d01e6..1084454 100644
--- a/src/java/com/android/internal/telephony/cat/CatService.java
+++ b/src/java/com/android/internal/telephony/cat/CatService.java
@@ -20,6 +20,8 @@
         .IDLE_SCREEN_AVAILABLE_EVENT;
 import static com.android.internal.telephony.cat.CatCmdMessage.SetupEventListConstants
         .LANGUAGE_SELECTION_EVENT;
+import static com.android.internal.telephony.cat.CatCmdMessage.SetupEventListConstants
+        .USER_ACTIVITY_EVENT;
 
 import android.app.ActivityManagerNative;
 import android.app.IActivityManager;
@@ -350,6 +352,7 @@
                  * Language Selection.  */
                 case IDLE_SCREEN_AVAILABLE_EVENT:
                 case LANGUAGE_SELECTION_EVENT:
+                case USER_ACTIVITY_EVENT:
                     break;
                 default:
                     flag = false;
@@ -392,11 +395,6 @@
                 break;
             case DISPLAY_TEXT:
                 break;
-            case REFRESH:
-                // ME side only handles refresh commands which meant to remove IDLE
-                // MODE TEXT.
-                cmdParams.mCmdDet.typeOfCommand = CommandType.SET_UP_IDLE_MODE_TEXT.value();
-                break;
             case SET_UP_IDLE_MODE_TEXT:
                 resultCode = cmdParams.mLoadIconFailed ? ResultCode.PRFRMD_ICON_NOT_DISPLAYED
                                                                             : ResultCode.OK;
@@ -437,6 +435,13 @@
             case GET_INPUT:
             case GET_INKEY:
                 break;
+            case REFRESH:
+            case RUN_AT:
+                if (STK_DEFAULT.equals(((DisplayTextParams)cmdParams).mTextMsg.text)) {
+                    // Remove the default text which was temporarily added and shall not be shown
+                    ((DisplayTextParams)cmdParams).mTextMsg.text = null;
+                }
+                break;
             case SEND_DTMF:
             case SEND_SMS:
             case SEND_SS:
@@ -763,6 +768,8 @@
                 // Language length should be 2 byte
                 buf.write(0x02);
                 break;
+            case USER_ACTIVITY_EVENT:
+                break;
             default:
                 break;
         }
diff --git a/src/java/com/android/internal/telephony/cat/CommandParamsFactory.java b/src/java/com/android/internal/telephony/cat/CommandParamsFactory.java
index 232f808..049e668 100644
--- a/src/java/com/android/internal/telephony/cat/CommandParamsFactory.java
+++ b/src/java/com/android/internal/telephony/cat/CommandParamsFactory.java
@@ -61,12 +61,6 @@
     static final int LOAD_SINGLE_ICON       = 1;
     static final int LOAD_MULTI_ICONS       = 2;
 
-    // Command Qualifier values for refresh command
-    static final int REFRESH_NAA_INIT_AND_FULL_FILE_CHANGE  = 0x00;
-    static final int REFRESH_NAA_INIT_AND_FILE_CHANGE       = 0x02;
-    static final int REFRESH_NAA_INIT                       = 0x03;
-    static final int REFRESH_UICC_RESET                     = 0x04;
-
     // Command Qualifier values for PLI command
     static final int DTTZ_SETTING                           = 0x03;
     static final int LANGUAGE_SETTING                       = 0x04;
@@ -188,6 +182,8 @@
                  break;
              case SEND_DTMF:
              case SEND_SMS:
+             case REFRESH:
+             case RUN_AT:
              case SEND_SS:
              case SEND_USSD:
                  cmdPending = processEventNotify(cmdDet, ctlvs);
@@ -196,10 +192,6 @@
              case SET_UP_CALL:
                  cmdPending = processSetupCall(cmdDet, ctlvs);
                  break;
-             case REFRESH:
-                processRefresh(cmdDet, ctlvs);
-                cmdPending = false;
-                break;
              case LAUNCH_BROWSER:
                  cmdPending = processLaunchBrowser(cmdDet, ctlvs);
                  break;
@@ -585,32 +577,6 @@
     }
 
     /**
-     * Processes REFRESH proactive command from the SIM card.
-     *
-     * @param cmdDet Command Details container object.
-     * @param ctlvs List of ComprehensionTlv objects following Command Details
-     *        object and Device Identities object within the proactive command
-     */
-    private boolean processRefresh(CommandDetails cmdDet,
-            List<ComprehensionTlv> ctlvs) {
-
-        CatLog.d(this, "process Refresh");
-
-        // REFRESH proactive command is rerouted by the baseband and handled by
-        // the telephony layer. IDLE TEXT should be removed for a REFRESH command
-        // with "initialization" or "reset"
-        switch (cmdDet.commandQualifier) {
-        case REFRESH_NAA_INIT_AND_FULL_FILE_CHANGE:
-        case REFRESH_NAA_INIT_AND_FILE_CHANGE:
-        case REFRESH_NAA_INIT:
-        case REFRESH_UICC_RESET:
-            mCmdParams = new DisplayTextParams(cmdDet, null);
-            break;
-        }
-        return false;
-    }
-
-    /**
      * Processes SELECT_ITEM proactive command from the SIM card.
      *
      * @param cmdDet Command Details container object.
diff --git a/src/java/com/android/internal/telephony/cdma/CdmaInboundSmsHandler.java b/src/java/com/android/internal/telephony/cdma/CdmaInboundSmsHandler.java
index 5a40c4e..e2c178a 100644
--- a/src/java/com/android/internal/telephony/cdma/CdmaInboundSmsHandler.java
+++ b/src/java/com/android/internal/telephony/cdma/CdmaInboundSmsHandler.java
@@ -20,7 +20,6 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.os.Message;
-import android.os.SystemProperties;
 import android.provider.Telephony.Sms.Intents;
 import android.telephony.SmsCbMessage;
 
@@ -33,7 +32,6 @@
 import com.android.internal.telephony.SmsMessageBase;
 import com.android.internal.telephony.SmsStorageMonitor;
 import com.android.internal.telephony.TelephonyComponentFactory;
-import com.android.internal.telephony.TelephonyProperties;
 import com.android.internal.telephony.WspTypeDecoder;
 import com.android.internal.telephony.cdma.sms.SmsEnvelope;
 import com.android.internal.util.HexDump;
diff --git a/src/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java b/src/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
index f000caa..bbaa3ca 100755
--- a/src/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
+++ b/src/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
@@ -16,12 +16,7 @@
 
 package com.android.internal.telephony.cdma;
 
-import android.app.Activity;
-import android.app.PendingIntent;
-import android.app.PendingIntent.CanceledException;
-import android.content.Intent;
 import android.os.Message;
-import android.provider.Telephony.Sms;
 import android.telephony.Rlog;
 import android.telephony.ServiceState;
 import android.telephony.TelephonyManager;
@@ -73,8 +68,9 @@
     }
 
     @Override
-    protected boolean shouldBlockSms() {
-        return SMSDispatcherUtil.shouldBlockSms(isCdmaMo(), mPhone);
+    protected boolean shouldBlockSmsForEcbm() {
+        // We only block outgoing SMS during ECBM when using CDMA.
+        return mPhone.isInEcm() && isCdmaMo() && !isIms();
     }
 
     @Override
@@ -123,6 +119,7 @@
                 + " mRetryCount=" + tracker.mRetryCount
                 + " mImsRetry=" + tracker.mImsRetry
                 + " mMessageRef=" + tracker.mMessageRef
+                + " mUsesImsServiceForIms=" + tracker.mUsesImsServiceForIms
                 + " SS=" + mPhone.getServiceState().getState());
 
         int ss = mPhone.getServiceState().getState();
@@ -146,8 +143,11 @@
         // sms over cdma is used:
         //   if sms over IMS is not supported AND
         //   this is not a retry case after sms over IMS failed
-        //     indicated by mImsRetry > 0
-        if (0 == tracker.mImsRetry && !isIms() || imsSmsDisabled) {
+        //     indicated by mImsRetry > 0 OR
+        //   SMS over IMS is disabled because of the network type OR
+        //   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);
         } else {
             mCi.sendImsCdmaSms(pdu, tracker.mImsRetry, tracker.mMessageRef, reply);
diff --git a/src/java/com/android/internal/telephony/dataconnection/ApnContext.java b/src/java/com/android/internal/telephony/dataconnection/ApnContext.java
index 1b42d4a..f068acd 100644
--- a/src/java/com/android/internal/telephony/dataconnection/ApnContext.java
+++ b/src/java/com/android/internal/telephony/dataconnection/ApnContext.java
@@ -22,6 +22,8 @@
 import android.net.NetworkConfig;
 import android.net.NetworkRequest;
 import android.telephony.Rlog;
+import android.telephony.data.ApnSetting;
+import android.telephony.data.ApnSetting.ApnType;
 import android.text.TextUtils;
 import android.util.LocalLog;
 import android.util.SparseIntArray;
@@ -29,7 +31,6 @@
 import com.android.internal.R;
 import com.android.internal.telephony.DctConstants;
 import com.android.internal.telephony.Phone;
-import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.RetryManager;
 import com.android.internal.util.IndentingPrintWriter;
 
@@ -129,6 +130,14 @@
     }
 
     /**
+     * Gets the APN type bitmask.
+     * @return The APN type bitmask
+     */
+    public int getApnTypeBitmask() {
+        return ApnSetting.getApnTypesBitmaskFromString(mApnType);
+    }
+
+    /**
      * Get the data call async channel.
      * @return The data call async channel
      */
@@ -392,8 +401,8 @@
         String provisioningApn = mPhone.getContext().getResources()
                 .getString(R.string.mobile_provisioning_apn);
         if (!TextUtils.isEmpty(provisioningApn) &&
-                (mApnSetting != null) && (mApnSetting.apn != null)) {
-            return (mApnSetting.apn.equals(provisioningApn));
+                (mApnSetting != null) && (mApnSetting.getApnName() != null)) {
+            return (mApnSetting.getApnName().equals(provisioningApn));
         } else {
             return false;
         }
@@ -418,7 +427,7 @@
             } else {
                 mLocalLogs.add(log);
                 mNetworkRequests.add(networkRequest);
-                mDcTracker.setEnabled(apnIdForApnName(mApnType), true);
+                mDcTracker.setEnabled(ApnSetting.getApnTypesBitmaskFromString(mApnType), true);
             }
         }
     }
@@ -438,7 +447,7 @@
                 log.log("ApnContext.releaseNetwork left with " + mNetworkRequests.size() +
                         " requests.");
                 if (mNetworkRequests.size() == 0) {
-                    mDcTracker.setEnabled(apnIdForApnName(mApnType), false);
+                    mDcTracker.setEnabled(ApnSetting.getApnTypesBitmaskFromString(mApnType), false);
                 }
             }
         }
@@ -540,88 +549,78 @@
         return mRetryManager.getRetryAfterDisconnectDelay();
     }
 
-    public static int apnIdForType(int networkType) {
+    public static int getApnTypeFromNetworkType(int networkType) {
         switch (networkType) {
-        case ConnectivityManager.TYPE_MOBILE:
-            return DctConstants.APN_DEFAULT_ID;
-        case ConnectivityManager.TYPE_MOBILE_MMS:
-            return DctConstants.APN_MMS_ID;
-        case ConnectivityManager.TYPE_MOBILE_SUPL:
-            return DctConstants.APN_SUPL_ID;
-        case ConnectivityManager.TYPE_MOBILE_DUN:
-            return DctConstants.APN_DUN_ID;
-        case ConnectivityManager.TYPE_MOBILE_FOTA:
-            return DctConstants.APN_FOTA_ID;
-        case ConnectivityManager.TYPE_MOBILE_IMS:
-            return DctConstants.APN_IMS_ID;
-        case ConnectivityManager.TYPE_MOBILE_CBS:
-            return DctConstants.APN_CBS_ID;
-        case ConnectivityManager.TYPE_MOBILE_IA:
-            return DctConstants.APN_IA_ID;
-        case ConnectivityManager.TYPE_MOBILE_EMERGENCY:
-            return DctConstants.APN_EMERGENCY_ID;
-        default:
-            return DctConstants.APN_INVALID_ID;
+            case ConnectivityManager.TYPE_MOBILE:
+                return ApnSetting.TYPE_DEFAULT;
+            case ConnectivityManager.TYPE_MOBILE_MMS:
+                return ApnSetting.TYPE_MMS;
+            case ConnectivityManager.TYPE_MOBILE_SUPL:
+                return ApnSetting.TYPE_SUPL;
+            case ConnectivityManager.TYPE_MOBILE_DUN:
+                return ApnSetting.TYPE_DUN;
+            case ConnectivityManager.TYPE_MOBILE_FOTA:
+                return ApnSetting.TYPE_FOTA;
+            case ConnectivityManager.TYPE_MOBILE_IMS:
+                return ApnSetting.TYPE_IMS;
+            case ConnectivityManager.TYPE_MOBILE_CBS:
+                return ApnSetting.TYPE_CBS;
+            case ConnectivityManager.TYPE_MOBILE_IA:
+                return ApnSetting.TYPE_IA;
+            case ConnectivityManager.TYPE_MOBILE_EMERGENCY:
+                return ApnSetting.TYPE_EMERGENCY;
+            default:
+                return ApnSetting.TYPE_NONE;
         }
     }
 
-    public static int apnIdForNetworkRequest(NetworkRequest nr) {
+    static @ApnType int getApnTypeFromNetworkRequest(NetworkRequest nr) {
         NetworkCapabilities nc = nr.networkCapabilities;
         // For now, ignore the bandwidth stuff
         if (nc.getTransportTypes().length > 0 &&
                 nc.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) == false) {
-            return DctConstants.APN_INVALID_ID;
+            return ApnSetting.TYPE_NONE;
         }
 
         // in the near term just do 1-1 matches.
         // TODO - actually try to match the set of capabilities
-        int apnId = DctConstants.APN_INVALID_ID;
+        int apnType = ApnSetting.TYPE_NONE;
         boolean error = false;
 
         if (nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
-            apnId = DctConstants.APN_DEFAULT_ID;
+            apnType = ApnSetting.TYPE_DEFAULT;
         }
         if (nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_MMS)) {
-            if (apnId != DctConstants.APN_INVALID_ID) error = true;
-            apnId = DctConstants.APN_MMS_ID;
+            if (apnType != ApnSetting.TYPE_NONE) error = true;
+            apnType = ApnSetting.TYPE_MMS;
         }
         if (nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_SUPL)) {
-            if (apnId != DctConstants.APN_INVALID_ID) error = true;
-            apnId = DctConstants.APN_SUPL_ID;
+            if (apnType != ApnSetting.TYPE_NONE) error = true;
+            apnType = ApnSetting.TYPE_SUPL;
         }
         if (nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_DUN)) {
-            if (apnId != DctConstants.APN_INVALID_ID) error = true;
-            apnId = DctConstants.APN_DUN_ID;
+            if (apnType != ApnSetting.TYPE_NONE) error = true;
+            apnType = ApnSetting.TYPE_DUN;
         }
         if (nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_FOTA)) {
-            if (apnId != DctConstants.APN_INVALID_ID) error = true;
-            apnId = DctConstants.APN_FOTA_ID;
+            if (apnType != ApnSetting.TYPE_NONE) error = true;
+            apnType = ApnSetting.TYPE_FOTA;
         }
         if (nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_IMS)) {
-            if (apnId != DctConstants.APN_INVALID_ID) error = true;
-            apnId = DctConstants.APN_IMS_ID;
+            if (apnType != ApnSetting.TYPE_NONE) error = true;
+            apnType = ApnSetting.TYPE_IMS;
         }
         if (nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_CBS)) {
-            if (apnId != DctConstants.APN_INVALID_ID) error = true;
-            apnId = DctConstants.APN_CBS_ID;
+            if (apnType != ApnSetting.TYPE_NONE) error = true;
+            apnType = ApnSetting.TYPE_CBS;
         }
         if (nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_IA)) {
-            if (apnId != DctConstants.APN_INVALID_ID) error = true;
-            apnId = DctConstants.APN_IA_ID;
-        }
-        if (nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_RCS)) {
-            if (apnId != DctConstants.APN_INVALID_ID) error = true;
-
-            Rlog.d(SLOG_TAG, "RCS APN type not yet supported");
-        }
-        if (nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_XCAP)) {
-            if (apnId != DctConstants.APN_INVALID_ID) error = true;
-
-            Rlog.d(SLOG_TAG, "XCAP APN type not yet supported");
+            if (apnType != ApnSetting.TYPE_NONE) error = true;
+            apnType = ApnSetting.TYPE_IA;
         }
         if (nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_EIMS)) {
-            if (apnId != DctConstants.APN_INVALID_ID) error = true;
-            apnId = DctConstants.APN_EMERGENCY_ID;
+            if (apnType != ApnSetting.TYPE_NONE) error = true;
+            apnType = ApnSetting.TYPE_EMERGENCY;
         }
         if (error) {
             // TODO: If this error condition is removed, the framework's handling of
@@ -630,66 +629,10 @@
             // NetworkCapabilities.maybeMarkCapabilitiesRestricted currently works.
             Rlog.d(SLOG_TAG, "Multiple apn types specified in request - result is unspecified!");
         }
-        if (apnId == DctConstants.APN_INVALID_ID) {
+        if (apnType == ApnSetting.TYPE_NONE) {
             Rlog.d(SLOG_TAG, "Unsupported NetworkRequest in Telephony: nr=" + nr);
         }
-        return apnId;
-    }
-
-    // TODO - kill The use of these strings
-    public static int apnIdForApnName(String type) {
-        switch (type) {
-            case PhoneConstants.APN_TYPE_DEFAULT:
-                return DctConstants.APN_DEFAULT_ID;
-            case PhoneConstants.APN_TYPE_MMS:
-                return DctConstants.APN_MMS_ID;
-            case PhoneConstants.APN_TYPE_SUPL:
-                return DctConstants.APN_SUPL_ID;
-            case PhoneConstants.APN_TYPE_DUN:
-                return DctConstants.APN_DUN_ID;
-            case PhoneConstants.APN_TYPE_HIPRI:
-                return DctConstants.APN_HIPRI_ID;
-            case PhoneConstants.APN_TYPE_IMS:
-                return DctConstants.APN_IMS_ID;
-            case PhoneConstants.APN_TYPE_FOTA:
-                return DctConstants.APN_FOTA_ID;
-            case PhoneConstants.APN_TYPE_CBS:
-                return DctConstants.APN_CBS_ID;
-            case PhoneConstants.APN_TYPE_IA:
-                return DctConstants.APN_IA_ID;
-            case PhoneConstants.APN_TYPE_EMERGENCY:
-                return DctConstants.APN_EMERGENCY_ID;
-            default:
-                return DctConstants.APN_INVALID_ID;
-        }
-    }
-
-    private static String apnNameForApnId(int id) {
-        switch (id) {
-            case DctConstants.APN_DEFAULT_ID:
-                return PhoneConstants.APN_TYPE_DEFAULT;
-            case DctConstants.APN_MMS_ID:
-                return PhoneConstants.APN_TYPE_MMS;
-            case DctConstants.APN_SUPL_ID:
-                return PhoneConstants.APN_TYPE_SUPL;
-            case DctConstants.APN_DUN_ID:
-                return PhoneConstants.APN_TYPE_DUN;
-            case DctConstants.APN_HIPRI_ID:
-                return PhoneConstants.APN_TYPE_HIPRI;
-            case DctConstants.APN_IMS_ID:
-                return PhoneConstants.APN_TYPE_IMS;
-            case DctConstants.APN_FOTA_ID:
-                return PhoneConstants.APN_TYPE_FOTA;
-            case DctConstants.APN_CBS_ID:
-                return PhoneConstants.APN_TYPE_CBS;
-            case DctConstants.APN_IA_ID:
-                return PhoneConstants.APN_TYPE_IA;
-            case DctConstants.APN_EMERGENCY_ID:
-                return PhoneConstants.APN_TYPE_EMERGENCY;
-            default:
-                Rlog.d(SLOG_TAG, "Unknown id (" + id + ") in apnIdToType");
-                return PhoneConstants.APN_TYPE_DEFAULT;
-        }
+        return apnType;
     }
 
     @Override
diff --git a/src/java/com/android/internal/telephony/dataconnection/ApnSetting.java b/src/java/com/android/internal/telephony/dataconnection/ApnSetting.java
deleted file mode 100644
index 9394ecb..0000000
--- a/src/java/com/android/internal/telephony/dataconnection/ApnSetting.java
+++ /dev/null
@@ -1,785 +0,0 @@
-/*
- * Copyright (C) 2006 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.telephony.dataconnection;
-
-import android.content.Context;
-import android.hardware.radio.V1_0.ApnTypes;
-import android.os.PersistableBundle;
-import android.telephony.CarrierConfigManager;
-import android.telephony.Rlog;
-import android.telephony.ServiceState;
-import android.text.TextUtils;
-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.RILConstants;
-import com.android.internal.telephony.uicc.IccRecords;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Objects;
-
-/**
- * This class represents a apn setting for create PDP link
- */
-public class ApnSetting {
-
-    static final String LOG_TAG = "ApnSetting";
-
-    private static final boolean DBG = false;
-    private static final boolean VDBG = false;
-
-    static final String V2_FORMAT_REGEX = "^\\[ApnSettingV2\\]\\s*";
-    static final String V3_FORMAT_REGEX = "^\\[ApnSettingV3\\]\\s*";
-    static final String V4_FORMAT_REGEX = "^\\[ApnSettingV4\\]\\s*";
-    static final String TAG = "ApnSetting";
-
-    public final String carrier;
-    public final String apn;
-    public final String proxy;
-    public final String port;
-    public final String mmsc;
-    public final String mmsProxy;
-    public final String mmsPort;
-    public final String user;
-    public final String password;
-    public final int authType;
-    public final String[] types;
-    public final int typesBitmap;
-    public final int id;
-    public final String numeric;
-    public final String protocol;
-    public final String roamingProtocol;
-    public final int mtu;
-
-    /**
-      * Current status of APN
-      * true : enabled APN, false : disabled APN.
-      */
-    public final boolean carrierEnabled;
-    /**
-     * Radio Access Technology info
-     * To check what values can hold, refer to ServiceState.java.
-     * This should be spread to other technologies,
-     * but currently only used for LTE(14) and EHRPD(13).
-     *
-     * @deprecated use {@code networkTypeBitmask} instead
-     */
-    @Deprecated
-    private final int bearer;
-    /**
-      * Radio Access Technology info
-      * To check what values can hold, refer to ServiceState.java. This is a bitmask of radio
-      * technologies in ServiceState.
-      * This should be spread to other technologies,
-      * but currently only used for LTE(14) and EHRPD(13).
-      *
-      * @deprecated use {@code networkTypeBitmask} instead
-      */
-    @Deprecated
-    public final int bearerBitmask;
-
-    /**
-     * Radio Technology (Network Type) info
-     * To check what values can hold, refer to TelephonyManager.java. This is a bitmask of radio
-     * technologies ({@code NETWORK_TYPE_} constants) in {@link TelephonyManager}.
-     */
-    public final int networkTypeBitmask;
-
-    /* ID of the profile in the modem */
-    public final int profileId;
-    public final boolean modemCognitive;
-    public final int maxConns;
-    public final int waitTime;
-    public final int maxConnsTime;
-
-    /**
-      * MVNO match type. Possible values:
-      *   "spn": Service provider name.
-      *   "imsi": IMSI.
-      *   "gid": Group identifier level 1.
-      *   "iccid": ICCID
-      */
-    public final String mvnoType;
-    /**
-      * MVNO data. Examples:
-      *   "spn": A MOBILE, BEN NL
-      *   "imsi": 302720x94, 2060188
-      *   "gid": 4E, 33
-      *   "iccid": 898603 etc.
-      */
-    public final String mvnoMatchData;
-
-    /**
-     * Indicates this APN setting is permanently failed and cannot be
-     * retried by the retry manager anymore.
-     * */
-    public boolean permanentFailed = false;
-
-    /**
-     * @deprecated this constructor is no longer supported. Use the other constructor which takes
-     * a network type bitmask instead of the deprecated bearer bitmask and bearer field.
-     * */
-    @Deprecated
-    public ApnSetting(int id, String numeric, String carrier, String apn,
-                      String proxy, String port,
-                      String mmsc, String mmsProxy, String mmsPort,
-                      String user, String password, int authType, String[] types,
-                      String protocol, String roamingProtocol, boolean carrierEnabled, int bearer,
-                      int bearerBitmask, int profileId, boolean modemCognitive, int maxConns,
-                      int waitTime, int maxConnsTime, int mtu, String mvnoType,
-                      String mvnoMatchData) {
-        this.id = id;
-        this.numeric = numeric;
-        this.carrier = carrier;
-        this.apn = apn;
-        this.proxy = proxy;
-        this.port = port;
-        this.mmsc = mmsc;
-        this.mmsProxy = mmsProxy;
-        this.mmsPort = mmsPort;
-        this.user = user;
-        this.password = password;
-        this.authType = authType;
-        this.types = new String[types.length];
-        int apnBitmap = 0;
-        for (int i = 0; i < types.length; i++) {
-            this.types[i] = types[i].toLowerCase();
-            apnBitmap |= getApnBitmask(this.types[i]);
-        }
-        this.typesBitmap = apnBitmap;
-        this.protocol = protocol;
-        this.roamingProtocol = roamingProtocol;
-        this.carrierEnabled = carrierEnabled;
-        this.bearer = bearer;
-        this.bearerBitmask = (bearerBitmask | ServiceState.getBitmaskForTech(bearer));
-        this.profileId = profileId;
-        this.modemCognitive = modemCognitive;
-        this.maxConns = maxConns;
-        this.waitTime = waitTime;
-        this.maxConnsTime = maxConnsTime;
-        this.mtu = mtu;
-        this.mvnoType = mvnoType;
-        this.mvnoMatchData = mvnoMatchData;
-        this.networkTypeBitmask = ServiceState.convertBearerBitmaskToNetworkTypeBitmask(
-                this.bearerBitmask);
-    }
-
-    public ApnSetting(int id, String numeric, String carrier, String apn,
-                      String proxy, String port,
-                      String mmsc, String mmsProxy, String mmsPort,
-                      String user, String password, int authType, String[] types,
-                      String protocol, String roamingProtocol, boolean carrierEnabled,
-                      int networkTypeBitmask, int profileId, boolean modemCognitive, int maxConns,
-                      int waitTime, int maxConnsTime, int mtu, String mvnoType,
-                      String mvnoMatchData) {
-        this.id = id;
-        this.numeric = numeric;
-        this.carrier = carrier;
-        this.apn = apn;
-        this.proxy = proxy;
-        this.port = port;
-        this.mmsc = mmsc;
-        this.mmsProxy = mmsProxy;
-        this.mmsPort = mmsPort;
-        this.user = user;
-        this.password = password;
-        this.authType = authType;
-        this.types = new String[types.length];
-        int apnBitmap = 0;
-        for (int i = 0; i < types.length; i++) {
-            this.types[i] = types[i].toLowerCase();
-            apnBitmap |= getApnBitmask(this.types[i]);
-        }
-        this.typesBitmap = apnBitmap;
-        this.protocol = protocol;
-        this.roamingProtocol = roamingProtocol;
-        this.carrierEnabled = carrierEnabled;
-        this.bearer = 0;
-        this.bearerBitmask =
-                ServiceState.convertNetworkTypeBitmaskToBearerBitmask(networkTypeBitmask);
-        this.networkTypeBitmask = networkTypeBitmask;
-        this.profileId = profileId;
-        this.modemCognitive = modemCognitive;
-        this.maxConns = maxConns;
-        this.waitTime = waitTime;
-        this.maxConnsTime = maxConnsTime;
-        this.mtu = mtu;
-        this.mvnoType = mvnoType;
-        this.mvnoMatchData = mvnoMatchData;
-    }
-
-    public ApnSetting(ApnSetting apn) {
-        this(apn.id, apn.numeric, apn.carrier, apn.apn, apn.proxy, apn.port, apn.mmsc, apn.mmsProxy,
-                apn.mmsPort, apn.user, apn.password, apn.authType, apn.types, apn.protocol,
-                apn.roamingProtocol, apn.carrierEnabled, apn.networkTypeBitmask, apn.profileId,
-                apn.modemCognitive, apn.maxConns, apn.waitTime, apn.maxConnsTime,
-                apn.mtu, apn.mvnoType, apn.mvnoMatchData);
-    }
-
-    /**
-     * Creates an ApnSetting object from a string.
-     *
-     * @param data the string to read.
-     *
-     * The string must be in one of two formats (newlines added for clarity,
-     * spaces are optional):
-     *
-     * v1 format:
-     *   <carrier>, <apn>, <proxy>, <port>, <user>, <password>, <server>,
-     *   <mmsc>, <mmsproxy>, <mmsport>, <mcc>, <mnc>, <authtype>,
-     *   <type>[| <type>...],
-     *
-     * v2 format:
-     *   [ApnSettingV2] <carrier>, <apn>, <proxy>, <port>, <user>, <password>, <server>,
-     *   <mmsc>, <mmsproxy>, <mmsport>, <mcc>, <mnc>, <authtype>,
-     *   <type>[| <type>...], <protocol>, <roaming_protocol>, <carrierEnabled>, <bearerBitmask>,
-     *
-     * v3 format:
-     *   [ApnSettingV3] <carrier>, <apn>, <proxy>, <port>, <user>, <password>, <server>,
-     *   <mmsc>, <mmsproxy>, <mmsport>, <mcc>, <mnc>, <authtype>,
-     *   <type>[| <type>...], <protocol>, <roaming_protocol>, <carrierEnabled>, <bearerBitmask>,
-     *   <profileId>, <modemCognitive>, <maxConns>, <waitTime>, <maxConnsTime>, <mtu>,
-     *   <mvnoType>, <mvnoMatchData>
-     *
-     * v4 format:
-     *   [ApnSettingV4] <carrier>, <apn>, <proxy>, <port>, <user>, <password>, <server>,
-     *   <mmsc>, <mmsproxy>, <mmsport>, <mcc>, <mnc>, <authtype>,
-     *   <type>[| <type>...], <protocol>, <roaming_protocol>, <carrierEnabled>, <bearerBitmask>,
-     *   <profileId>, <modemCognitive>, <maxConns>, <waitTime>, <maxConnsTime>, <mtu>,
-     *   <mvnoType>, <mvnoMatchData>, <networkTypeBitmask>
-     *
-     * Note that the strings generated by toString() do not contain the username
-     * and password and thus cannot be read by this method.
-     */
-    public static ApnSetting fromString(String data) {
-        if (data == null) return null;
-
-        int version;
-        // matches() operates on the whole string, so append .* to the regex.
-        if (data.matches(V4_FORMAT_REGEX + ".*")) {
-            version = 4;
-            data = data.replaceFirst(V4_FORMAT_REGEX, "");
-        } else if (data.matches(V3_FORMAT_REGEX + ".*")) {
-            version = 3;
-            data = data.replaceFirst(V3_FORMAT_REGEX, "");
-        } else if (data.matches(V2_FORMAT_REGEX + ".*")) {
-            version = 2;
-            data = data.replaceFirst(V2_FORMAT_REGEX, "");
-        } else {
-            version = 1;
-        }
-
-        String[] a = data.split("\\s*,\\s*");
-        if (a.length < 14) {
-            return null;
-        }
-
-        int authType;
-        try {
-            authType = Integer.parseInt(a[12]);
-        } catch (NumberFormatException e) {
-            authType = 0;
-        }
-
-        String[] typeArray;
-        String protocol, roamingProtocol;
-        boolean carrierEnabled;
-        int bearerBitmask = 0;
-        int networkTypeBitmask = 0;
-        int profileId = 0;
-        boolean modemCognitive = false;
-        int maxConns = 0;
-        int waitTime = 0;
-        int maxConnsTime = 0;
-        int mtu = PhoneConstants.UNSET_MTU;
-        String mvnoType = "";
-        String mvnoMatchData = "";
-        if (version == 1) {
-            typeArray = new String[a.length - 13];
-            System.arraycopy(a, 13, typeArray, 0, a.length - 13);
-            protocol = RILConstants.SETUP_DATA_PROTOCOL_IP;
-            roamingProtocol = RILConstants.SETUP_DATA_PROTOCOL_IP;
-            carrierEnabled = true;
-        } else {
-            if (a.length < 18) {
-                return null;
-            }
-            typeArray = a[13].split("\\s*\\|\\s*");
-            protocol = a[14];
-            roamingProtocol = a[15];
-            carrierEnabled = Boolean.parseBoolean(a[16]);
-
-            bearerBitmask = ServiceState.getBitmaskFromString(a[17]);
-
-            if (a.length > 22) {
-                modemCognitive = Boolean.parseBoolean(a[19]);
-                try {
-                    profileId = Integer.parseInt(a[18]);
-                    maxConns = Integer.parseInt(a[20]);
-                    waitTime = Integer.parseInt(a[21]);
-                    maxConnsTime = Integer.parseInt(a[22]);
-                } catch (NumberFormatException e) {
-                }
-            }
-            if (a.length > 23) {
-                try {
-                    mtu = Integer.parseInt(a[23]);
-                } catch (NumberFormatException e) {
-                }
-            }
-            if (a.length > 25) {
-                mvnoType = a[24];
-                mvnoMatchData = a[25];
-            }
-            if (a.length > 26) {
-                networkTypeBitmask = ServiceState.getBitmaskFromString(a[26]);
-            }
-        }
-
-        // If both bearerBitmask and networkTypeBitmask were specified, bearerBitmask would be
-        // ignored.
-        if (networkTypeBitmask == 0) {
-            networkTypeBitmask =
-                    ServiceState.convertBearerBitmaskToNetworkTypeBitmask(bearerBitmask);
-        }
-        return new ApnSetting(-1, a[10] + a[11], a[0], a[1], a[2], a[3], a[7], a[8], a[9], a[4],
-                a[5], authType, typeArray, protocol, roamingProtocol, carrierEnabled,
-                networkTypeBitmask, profileId, modemCognitive, maxConns, waitTime, maxConnsTime,
-                mtu, mvnoType, mvnoMatchData);
-    }
-
-    /**
-     * Creates an array of ApnSetting objects from a string.
-     *
-     * @param data the string to read.
-     *
-     * Builds on top of the same format used by fromString, but allows for multiple entries
-     * separated by "; ".
-     */
-    public static List<ApnSetting> arrayFromString(String data) {
-        List<ApnSetting> retVal = new ArrayList<ApnSetting>();
-        if (TextUtils.isEmpty(data)) {
-            return retVal;
-        }
-        String[] apnStrings = data.split("\\s*;\\s*");
-        for (String apnString : apnStrings) {
-            ApnSetting apn = fromString(apnString);
-            if (apn != null) {
-                retVal.add(apn);
-            }
-        }
-        return retVal;
-    }
-
-    @Override
-    public String toString() {
-        StringBuilder sb = new StringBuilder();
-        sb.append("[ApnSettingV4] ")
-        .append(carrier)
-        .append(", ").append(id)
-        .append(", ").append(numeric)
-        .append(", ").append(apn)
-        .append(", ").append(proxy)
-        .append(", ").append(mmsc)
-        .append(", ").append(mmsProxy)
-        .append(", ").append(mmsPort)
-        .append(", ").append(port)
-        .append(", ").append(authType).append(", ");
-        for (int i = 0; i < types.length; i++) {
-            sb.append(types[i]);
-            if (i < types.length - 1) {
-                sb.append(" | ");
-            }
-        }
-        sb.append(", ").append(protocol);
-        sb.append(", ").append(roamingProtocol);
-        sb.append(", ").append(carrierEnabled);
-        sb.append(", ").append(bearer);
-        sb.append(", ").append(bearerBitmask);
-        sb.append(", ").append(profileId);
-        sb.append(", ").append(modemCognitive);
-        sb.append(", ").append(maxConns);
-        sb.append(", ").append(waitTime);
-        sb.append(", ").append(maxConnsTime);
-        sb.append(", ").append(mtu);
-        sb.append(", ").append(mvnoType);
-        sb.append(", ").append(mvnoMatchData);
-        sb.append(", ").append(permanentFailed);
-        sb.append(", ").append(networkTypeBitmask);
-        return sb.toString();
-    }
-
-    /**
-     * Returns true if there are MVNO params specified.
-     */
-    public boolean hasMvnoParams() {
-        return !TextUtils.isEmpty(mvnoType) && !TextUtils.isEmpty(mvnoMatchData);
-    }
-
-    public boolean canHandleType(String type) {
-        if (!carrierEnabled) return false;
-        boolean wildcardable = true;
-        if (PhoneConstants.APN_TYPE_IA.equalsIgnoreCase(type)) wildcardable = false;
-        for (String t : types) {
-            // DEFAULT handles all, and HIPRI is handled by DEFAULT
-            if (t.equalsIgnoreCase(type) ||
-                    (wildcardable && t.equalsIgnoreCase(PhoneConstants.APN_TYPE_ALL)) ||
-                    (t.equalsIgnoreCase(PhoneConstants.APN_TYPE_DEFAULT) &&
-                    type.equalsIgnoreCase(PhoneConstants.APN_TYPE_HIPRI))) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private static boolean iccidMatches(String mvnoData, String iccId) {
-        String[] mvnoIccidList = mvnoData.split(",");
-        for (String mvnoIccid : mvnoIccidList) {
-            if (iccId.startsWith(mvnoIccid)) {
-                Log.d(TAG, "mvno icc id match found");
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private static boolean imsiMatches(String imsiDB, String imsiSIM) {
-        // Note: imsiDB value has digit number or 'x' character for seperating USIM information
-        // for MVNO operator. And then digit number is matched at same order and 'x' character
-        // could replace by any digit number.
-        // ex) if imsiDB inserted '310260x10xxxxxx' for GG Operator,
-        //     that means first 6 digits, 8th and 9th digit
-        //     should be set in USIM for GG Operator.
-        int len = imsiDB.length();
-        int idxCompare = 0;
-
-        if (len <= 0) return false;
-        if (len > imsiSIM.length()) return false;
-
-        for (int idx=0; idx<len; idx++) {
-            char c = imsiDB.charAt(idx);
-            if ((c == 'x') || (c == 'X') || (c == imsiSIM.charAt(idx))) {
-                continue;
-            } else {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    public static boolean mvnoMatches(IccRecords r, String mvnoType, String mvnoMatchData) {
-        if (mvnoType.equalsIgnoreCase("spn")) {
-            if ((r.getServiceProviderName() != null) &&
-                    r.getServiceProviderName().equalsIgnoreCase(mvnoMatchData)) {
-                return true;
-            }
-        } else if (mvnoType.equalsIgnoreCase("imsi")) {
-            String imsiSIM = r.getIMSI();
-            if ((imsiSIM != null) && imsiMatches(mvnoMatchData, imsiSIM)) {
-                return true;
-            }
-        } else if (mvnoType.equalsIgnoreCase("gid")) {
-            String gid1 = r.getGid1();
-            int mvno_match_data_length = mvnoMatchData.length();
-            if ((gid1 != null) && (gid1.length() >= mvno_match_data_length) &&
-                    gid1.substring(0, mvno_match_data_length).equalsIgnoreCase(mvnoMatchData)) {
-                return true;
-            }
-        } else if (mvnoType.equalsIgnoreCase("iccid")) {
-            String iccId = r.getIccId();
-            if ((iccId != null) && iccidMatches(mvnoMatchData, iccId)) {
-                return true;
-            }
-        }
-
-        return false;
-    }
-
-    /**
-     * Check if this APN type is metered.
-     *
-     * @param type The APN type
-     * @param phone The phone object
-     * @return True if the APN type is metered, otherwise false.
-     */
-    public static boolean isMeteredApnType(String type, Phone phone) {
-        if (phone == null) {
-            return true;
-        }
-
-        boolean isRoaming = phone.getServiceState().getDataRoaming();
-        boolean isIwlan = phone.getServiceState().getRilDataRadioTechnology()
-                == ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN;
-        int subId = phone.getSubId();
-
-        String carrierConfig;
-        // First check if the device is in IWLAN mode. If yes, use the IWLAN metered APN list. Then
-        // check if the device is roaming. If yes, use the roaming metered APN list. Otherwise, use
-        // the normal metered APN list.
-        if (isIwlan) {
-            carrierConfig = CarrierConfigManager.KEY_CARRIER_METERED_IWLAN_APN_TYPES_STRINGS;
-        } else if (isRoaming) {
-            carrierConfig = CarrierConfigManager.KEY_CARRIER_METERED_ROAMING_APN_TYPES_STRINGS;
-        } else {
-            carrierConfig = CarrierConfigManager.KEY_CARRIER_METERED_APN_TYPES_STRINGS;
-        }
-
-        if (DBG) {
-            Rlog.d(LOG_TAG, "isMeteredApnType: isRoaming=" + isRoaming + ", isIwlan=" + isIwlan);
-        }
-
-        CarrierConfigManager configManager = (CarrierConfigManager)
-                phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
-        if (configManager == null) {
-            Rlog.e(LOG_TAG, "Carrier config service is not available");
-            return true;
-        }
-
-        PersistableBundle b = configManager.getConfigForSubId(subId);
-        if (b == null) {
-            Rlog.e(LOG_TAG, "Can't get the config. subId = " + subId);
-            return true;
-        }
-
-        String[] meteredApnTypes = b.getStringArray(carrierConfig);
-        if (meteredApnTypes == null) {
-            Rlog.e(LOG_TAG, carrierConfig +  " is not available. " + "subId = " + subId);
-            return true;
-        }
-
-        HashSet<String> meteredApnSet = new HashSet<>(Arrays.asList(meteredApnTypes));
-        if (DBG) {
-            Rlog.d(LOG_TAG, "For subId = " + subId + ", metered APN types are "
-                    + Arrays.toString(meteredApnSet.toArray()));
-        }
-
-        // If all types of APN are metered, then this APN setting must be metered.
-        if (meteredApnSet.contains(PhoneConstants.APN_TYPE_ALL)) {
-            if (DBG) Rlog.d(LOG_TAG, "All APN types are metered.");
-            return true;
-        }
-
-        if (meteredApnSet.contains(type)) {
-            if (DBG) Rlog.d(LOG_TAG, type + " is metered.");
-            return true;
-        } else if (type.equals(PhoneConstants.APN_TYPE_ALL)) {
-            // Assuming no configuration error, if at least one APN type is
-            // metered, then this APN setting is metered.
-            if (meteredApnSet.size() > 0) {
-                if (DBG) Rlog.d(LOG_TAG, "APN_TYPE_ALL APN is metered.");
-                return true;
-            }
-        }
-
-        if (DBG) Rlog.d(LOG_TAG, type + " is not metered.");
-        return false;
-    }
-
-    /**
-     * Check if this APN setting is metered.
-     *
-     * @param phone The phone object
-     * @return True if this APN setting is metered, otherwise false.
-     */
-    public boolean isMetered(Phone phone) {
-        if (phone == null) {
-            return true;
-        }
-
-        for (String type : types) {
-            // If one of the APN type is metered, then this APN setting is metered.
-            if (isMeteredApnType(type, phone)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    // TODO - if we have this function we should also have hashCode.
-    // Also should handle changes in type order and perhaps case-insensitivity
-    @Override
-    public boolean equals(Object o) {
-        if (o instanceof ApnSetting == false) {
-            return false;
-        }
-
-        ApnSetting other = (ApnSetting) o;
-
-        return carrier.equals(other.carrier)
-                && id == other.id
-                && numeric.equals(other.numeric)
-                && apn.equals(other.apn)
-                && proxy.equals(other.proxy)
-                && mmsc.equals(other.mmsc)
-                && mmsProxy.equals(other.mmsProxy)
-                && TextUtils.equals(mmsPort, other.mmsPort)
-                && port.equals(other.port)
-                && TextUtils.equals(user, other.user)
-                && TextUtils.equals(password, other.password)
-                && authType == other.authType
-                && Arrays.deepEquals(types, other.types)
-                && typesBitmap == other.typesBitmap
-                && protocol.equals(other.protocol)
-                && roamingProtocol.equals(other.roamingProtocol)
-                && carrierEnabled == other.carrierEnabled
-                && bearer == other.bearer
-                && bearerBitmask == other.bearerBitmask
-                && profileId == other.profileId
-                && modemCognitive == other.modemCognitive
-                && maxConns == other.maxConns
-                && waitTime == other.waitTime
-                && maxConnsTime == other.maxConnsTime
-                && mtu == other.mtu
-                && mvnoType.equals(other.mvnoType)
-                && mvnoMatchData.equals(other.mvnoMatchData)
-                && networkTypeBitmask == other.networkTypeBitmask;
-    }
-
-    /**
-     * Compare two APN settings
-     *
-     * Note: This method does not compare 'id', 'bearer', 'bearerBitmask', 'networkTypeBitmask'.
-     * We only use this for determining if tearing a data call is needed when conditions change. See
-     * cleanUpConnectionsOnUpdatedApns in DcTracker.
-     *
-     * @param o the other object to compare
-     * @param isDataRoaming True if the device is on data roaming
-     * @return True if the two APN settings are same
-     */
-    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-    public boolean equals(Object o, boolean isDataRoaming) {
-        if (!(o instanceof ApnSetting)) {
-            return false;
-        }
-
-        ApnSetting other = (ApnSetting) o;
-
-        return carrier.equals(other.carrier)
-                && numeric.equals(other.numeric)
-                && apn.equals(other.apn)
-                && proxy.equals(other.proxy)
-                && mmsc.equals(other.mmsc)
-                && mmsProxy.equals(other.mmsProxy)
-                && TextUtils.equals(mmsPort, other.mmsPort)
-                && port.equals(other.port)
-                && TextUtils.equals(user, other.user)
-                && TextUtils.equals(password, other.password)
-                && authType == other.authType
-                && Arrays.deepEquals(types, other.types)
-                && typesBitmap == other.typesBitmap
-                && (isDataRoaming || protocol.equals(other.protocol))
-                && (!isDataRoaming || roamingProtocol.equals(other.roamingProtocol))
-                && carrierEnabled == other.carrierEnabled
-                && profileId == other.profileId
-                && modemCognitive == other.modemCognitive
-                && maxConns == other.maxConns
-                && waitTime == other.waitTime
-                && maxConnsTime == other.maxConnsTime
-                && mtu == other.mtu
-                && mvnoType.equals(other.mvnoType)
-                && mvnoMatchData.equals(other.mvnoMatchData);
-    }
-
-    /**
-     * Check if neither mention DUN and are substantially similar
-     *
-     * @param other The other APN settings to compare
-     * @return True if two APN settings are similar
-     */
-    public boolean similar(ApnSetting other) {
-        return (!this.canHandleType(PhoneConstants.APN_TYPE_DUN)
-                && !other.canHandleType(PhoneConstants.APN_TYPE_DUN)
-                && Objects.equals(this.apn, other.apn)
-                && !typeSameAny(this, other)
-                && xorEquals(this.proxy, other.proxy)
-                && xorEquals(this.port, other.port)
-                && xorEquals(this.protocol, other.protocol)
-                && xorEquals(this.roamingProtocol, other.roamingProtocol)
-                && this.carrierEnabled == other.carrierEnabled
-                && this.bearerBitmask == other.bearerBitmask
-                && this.profileId == other.profileId
-                && Objects.equals(this.mvnoType, other.mvnoType)
-                && Objects.equals(this.mvnoMatchData, other.mvnoMatchData)
-                && xorEquals(this.mmsc, other.mmsc)
-                && xorEquals(this.mmsProxy, other.mmsProxy)
-                && xorEquals(this.mmsPort, other.mmsPort))
-                && this.networkTypeBitmask == other.networkTypeBitmask;
-    }
-
-    // check whether the types of two APN same (even only one type of each APN is same)
-    private boolean typeSameAny(ApnSetting first, ApnSetting second) {
-        if (VDBG) {
-            StringBuilder apnType1 = new StringBuilder(first.apn + ": ");
-            for (int index1 = 0; index1 < first.types.length; index1++) {
-                apnType1.append(first.types[index1]);
-                apnType1.append(",");
-            }
-
-            StringBuilder apnType2 = new StringBuilder(second.apn + ": ");
-            for (int index1 = 0; index1 < second.types.length; index1++) {
-                apnType2.append(second.types[index1]);
-                apnType2.append(",");
-            }
-            Rlog.d(LOG_TAG, "APN1: is " + apnType1);
-            Rlog.d(LOG_TAG, "APN2: is " + apnType2);
-        }
-
-        for (int index1 = 0; index1 < first.types.length; index1++) {
-            for (int index2 = 0; index2 < second.types.length; index2++) {
-                if (first.types[index1].equals(PhoneConstants.APN_TYPE_ALL)
-                        || second.types[index2].equals(PhoneConstants.APN_TYPE_ALL)
-                        || first.types[index1].equals(second.types[index2])) {
-                    if (VDBG) Rlog.d(LOG_TAG, "typeSameAny: return true");
-                    return true;
-                }
-            }
-        }
-
-        if (VDBG) Rlog.d(LOG_TAG, "typeSameAny: return false");
-        return false;
-    }
-
-    // equal or one is not specified
-    private boolean xorEquals(String first, String second) {
-        return (Objects.equals(first, second)
-                || TextUtils.isEmpty(first)
-                || TextUtils.isEmpty(second));
-    }
-
-    // Helper function to convert APN string into a 32-bit bitmask.
-    private static int getApnBitmask(String apn) {
-        switch (apn) {
-            case PhoneConstants.APN_TYPE_DEFAULT: return ApnTypes.DEFAULT;
-            case PhoneConstants.APN_TYPE_MMS: return ApnTypes.MMS;
-            case PhoneConstants.APN_TYPE_SUPL: return ApnTypes.SUPL;
-            case PhoneConstants.APN_TYPE_DUN: return ApnTypes.DUN;
-            case PhoneConstants.APN_TYPE_HIPRI: return ApnTypes.HIPRI;
-            case PhoneConstants.APN_TYPE_FOTA: return ApnTypes.FOTA;
-            case PhoneConstants.APN_TYPE_IMS: return ApnTypes.IMS;
-            case PhoneConstants.APN_TYPE_CBS: return ApnTypes.CBS;
-            case PhoneConstants.APN_TYPE_IA: return ApnTypes.IA;
-            case PhoneConstants.APN_TYPE_EMERGENCY: return ApnTypes.EMERGENCY;
-            case PhoneConstants.APN_TYPE_ALL: return ApnTypes.ALL;
-            default: return ApnTypes.NONE;
-        }
-    }
-}
diff --git a/src/java/com/android/internal/telephony/dataconnection/ApnSettingUtils.java b/src/java/com/android/internal/telephony/dataconnection/ApnSettingUtils.java
new file mode 100644
index 0000000..e9a6fc8
--- /dev/null
+++ b/src/java/com/android/internal/telephony/dataconnection/ApnSettingUtils.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony.dataconnection;
+
+import android.content.Context;
+import android.os.PersistableBundle;
+import android.telephony.CarrierConfigManager;
+import android.telephony.Rlog;
+import android.telephony.ServiceState;
+import android.telephony.data.ApnSetting;
+import android.util.Log;
+
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.uicc.IccRecords;
+
+import java.util.Arrays;
+import java.util.HashSet;
+
+/**
+ * This class represents a apn setting for create PDP link
+ */
+public class ApnSettingUtils {
+
+    static final String LOG_TAG = "ApnSetting";
+
+    private static final boolean DBG = false;
+
+    private static boolean iccidMatches(String mvnoData, String iccId) {
+        String[] mvnoIccidList = mvnoData.split(",");
+        for (String mvnoIccid : mvnoIccidList) {
+            if (iccId.startsWith(mvnoIccid)) {
+                Log.d(LOG_TAG, "mvno icc id match found");
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static boolean imsiMatches(String imsiDB, String imsiSIM) {
+        // Note: imsiDB value has digit number or 'x' character for seperating USIM information
+        // for MVNO operator. And then digit number is matched at same order and 'x' character
+        // could replace by any digit number.
+        // ex) if imsiDB inserted '310260x10xxxxxx' for GG Operator,
+        //     that means first 6 digits, 8th and 9th digit
+        //     should be set in USIM for GG Operator.
+        int len = imsiDB.length();
+
+        if (len <= 0) return false;
+        if (len > imsiSIM.length()) return false;
+
+        for (int idx = 0; idx < len; idx++) {
+            char c = imsiDB.charAt(idx);
+            if ((c == 'x') || (c == 'X') || (c == imsiSIM.charAt(idx))) {
+                continue;
+            } else {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Check if MVNO type and data match IccRecords.
+     *
+     * @param r the IccRecords
+     * @param mvnoType the MVNO type
+     * @param mvnoMatchData the MVNO match data
+     * @return {@code true} if MVNO type and data match IccRecords, {@code false} otherwise.
+     */
+    public static boolean mvnoMatches(IccRecords r, int mvnoType, String mvnoMatchData) {
+        if (mvnoType == ApnSetting.MVNO_TYPE_SPN) {
+            if ((r.getServiceProviderName() != null)
+                    && r.getServiceProviderName().equalsIgnoreCase(mvnoMatchData)) {
+                return true;
+            }
+        } else if (mvnoType == ApnSetting.MVNO_TYPE_IMSI) {
+            String imsiSIM = r.getIMSI();
+            if ((imsiSIM != null) && imsiMatches(mvnoMatchData, imsiSIM)) {
+                return true;
+            }
+        } else if (mvnoType == ApnSetting.MVNO_TYPE_GID) {
+            String gid1 = r.getGid1();
+            int mvno_match_data_length = mvnoMatchData.length();
+            if ((gid1 != null) && (gid1.length() >= mvno_match_data_length)
+                    && gid1.substring(0, mvno_match_data_length).equalsIgnoreCase(mvnoMatchData)) {
+                return true;
+            }
+        } else if (mvnoType == ApnSetting.MVNO_TYPE_ICCID) {
+            String iccId = r.getIccId();
+            if ((iccId != null) && iccidMatches(mvnoMatchData, iccId)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Check if this APN type is metered.
+     *
+     * @param type the APN type
+     * @param phone the phone object
+     * @return {@code true} if the APN type is metered, {@code false} otherwise.
+     */
+    public static boolean isMeteredApnType(String type, Phone phone) {
+        if (phone == null) {
+            return true;
+        }
+
+        boolean isRoaming = phone.getServiceState().getDataRoaming();
+        boolean isIwlan = phone.getServiceState().getRilDataRadioTechnology()
+                == ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN;
+        int subId = phone.getSubId();
+
+        String carrierConfig;
+        // First check if the device is in IWLAN mode. If yes, use the IWLAN metered APN list. Then
+        // check if the device is roaming. If yes, use the roaming metered APN list. Otherwise, use
+        // the normal metered APN list.
+        if (isIwlan) {
+            carrierConfig = CarrierConfigManager.KEY_CARRIER_METERED_IWLAN_APN_TYPES_STRINGS;
+        } else if (isRoaming) {
+            carrierConfig = CarrierConfigManager.KEY_CARRIER_METERED_ROAMING_APN_TYPES_STRINGS;
+        } else {
+            carrierConfig = CarrierConfigManager.KEY_CARRIER_METERED_APN_TYPES_STRINGS;
+        }
+
+        if (DBG) {
+            Rlog.d(LOG_TAG, "isMeteredApnType: isRoaming=" + isRoaming + ", isIwlan=" + isIwlan);
+        }
+
+        CarrierConfigManager configManager = (CarrierConfigManager)
+                phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
+        if (configManager == null) {
+            Rlog.e(LOG_TAG, "Carrier config service is not available");
+            return true;
+        }
+
+        PersistableBundle b = configManager.getConfigForSubId(subId);
+        if (b == null) {
+            Rlog.e(LOG_TAG, "Can't get the config. subId = " + subId);
+            return true;
+        }
+
+        String[] meteredApnTypes = b.getStringArray(carrierConfig);
+        if (meteredApnTypes == null) {
+            Rlog.e(LOG_TAG, carrierConfig +  " is not available. " + "subId = " + subId);
+            return true;
+        }
+
+        HashSet<String> meteredApnSet = new HashSet<>(Arrays.asList(meteredApnTypes));
+        if (DBG) {
+            Rlog.d(LOG_TAG, "For subId = " + subId + ", metered APN types are "
+                    + Arrays.toString(meteredApnSet.toArray()));
+        }
+
+        // If all types of APN are metered, then this APN setting must be metered.
+        if (meteredApnSet.contains(PhoneConstants.APN_TYPE_ALL)) {
+            if (DBG) Rlog.d(LOG_TAG, "All APN types are metered.");
+            return true;
+        }
+
+        if (meteredApnSet.contains(type)) {
+            if (DBG) Rlog.d(LOG_TAG, type + " is metered.");
+            return true;
+        } else if (type.equals(PhoneConstants.APN_TYPE_ALL)) {
+            // Assuming no configuration error, if at least one APN type is
+            // metered, then this APN setting is metered.
+            if (meteredApnSet.size() > 0) {
+                if (DBG) Rlog.d(LOG_TAG, "APN_TYPE_ALL APN is metered.");
+                return true;
+            }
+        }
+
+        if (DBG) Rlog.d(LOG_TAG, type + " is not metered.");
+        return false;
+    }
+
+    /**
+     * Check if this APN setting is metered.
+     *
+     * @param phone The phone object
+     * @return True if this APN setting is metered, otherwise false.
+     */
+    public static boolean isMetered(ApnSetting apn, Phone phone) {
+        if (phone == null) {
+            return true;
+        }
+
+        String[] types = ApnSetting.getApnTypesStringFromBitmask(
+                apn.getApnTypeBitmask()).split(",");
+
+        for (String type : types) {
+            // If one of the APN type is metered, then this APN setting is metered.
+            if (isMeteredApnType(type, phone)) {
+                return true;
+            }
+        }
+        return false;
+    }
+}
diff --git a/src/java/com/android/internal/telephony/dataconnection/DataConnection.java b/src/java/com/android/internal/telephony/dataconnection/DataConnection.java
index 8a101b7..00af9fe 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DataConnection.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DataConnection.java
@@ -39,9 +39,11 @@
 import android.os.Message;
 import android.os.SystemClock;
 import android.os.SystemProperties;
+import android.telephony.AccessNetworkConstants;
 import android.telephony.Rlog;
 import android.telephony.ServiceState;
 import android.telephony.TelephonyManager;
+import android.telephony.data.ApnSetting;
 import android.telephony.data.DataCallResponse;
 import android.telephony.data.DataProfile;
 import android.telephony.data.DataService;
@@ -297,10 +299,22 @@
         return new LinkProperties(mLinkProperties);
     }
 
-    boolean getIsInactive() {
+    boolean isInactive() {
         return getCurrentState() == mInactiveState;
     }
 
+    boolean isDisconnecting() {
+        return getCurrentState() == mDisconnectingState;
+    }
+
+    boolean isActive() {
+        return getCurrentState() == mActiveState;
+    }
+
+    boolean isActivating() {
+        return getCurrentState() == mActivatingState;
+    }
+
     int getCid() {
         return mCid;
     }
@@ -430,9 +444,9 @@
             return;
         }
 
-        if (apn != null && apn.mtu != PhoneConstants.UNSET_MTU) {
-            lp.setMtu(apn.mtu);
-            if (DBG) log("MTU set by APN to: " + apn.mtu);
+        if (apn != null && apn.getMtu() != PhoneConstants.UNSET_MTU) {
+            lp.setMtu(apn.getMtu());
+            if (DBG) log("MTU set by APN to: " + apn.getMtu());
             return;
         }
 
@@ -489,9 +503,12 @@
      * @param cp is the connection parameters
      */
     private void onConnect(ConnectionParams cp) {
-        if (DBG) log("onConnect: carrier='" + mApnSetting.carrier
-                + "' APN='" + mApnSetting.apn
-                + "' proxy='" + mApnSetting.proxy + "' port='" + mApnSetting.port + "'");
+        if (DBG) {
+            log("onConnect: carrier='" + mApnSetting.getEntryName()
+                    + "' APN='" + mApnSetting.getApnName()
+                    + "' proxy='" + mApnSetting.getProxyAddressAsString()
+                    + "' port='" + mApnSetting.getProxyPort() + "'");
+        }
         if (cp.mApnContext != null) cp.mApnContext.requestLog("DataConnection.onConnect");
 
         // Check if we should fake an error.
@@ -770,12 +787,12 @@
             // Do not apply the race condition workaround for MMS APN
             // if Proxy is an IP-address.
             // Otherwise, the default APN will not be restored anymore.
-            if (!mApnSetting.types[0].equals(PhoneConstants.APN_TYPE_MMS)
-                || !isIpAddress(mApnSetting.mmsProxy)) {
+            if (!isIpAddress(mApnSetting.getMmsProxyAddressAsString())) {
                 log(String.format(
-                        "isDnsOk: return false apn.types[0]=%s APN_TYPE_MMS=%s isIpAddress(%s)=%s",
-                        mApnSetting.types[0], PhoneConstants.APN_TYPE_MMS, mApnSetting.mmsProxy,
-                        isIpAddress(mApnSetting.mmsProxy)));
+                        "isDnsOk: return false apn.types=%d APN_TYPE_MMS=%s isIpAddress(%s)=%s",
+                        mApnSetting.getApnTypeBitmask(), PhoneConstants.APN_TYPE_MMS,
+                        mApnSetting.getMmsProxyAddressAsString(),
+                        isIpAddress(mApnSetting.getMmsProxyAddressAsString())));
                 return false;
             }
         }
@@ -909,7 +926,7 @@
 
         // Do we need a restricted network to satisfy the request?
         // Is this network metered?  If not, then don't add restricted
-        if (!mApnSetting.isMetered(mPhone)) {
+        if (!ApnSettingUtils.isMetered(mApnSetting, mPhone)) {
             return;
         }
 
@@ -922,11 +939,12 @@
         result.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
 
         if (mApnSetting != null) {
-            ApnSetting securedDunApn = mDct.fetchDunApn();
-            for (String type : mApnSetting.types) {
+            final String[] types = ApnSetting.getApnTypesStringFromBitmask(
+                mApnSetting.getApnTypeBitmask()).split(",");
+            for (String type : types) {
                 if (!mRestrictedNetworkOverride
                         && (mConnectionParams != null && mConnectionParams.mUnmeteredUseOnly)
-                        && ApnSetting.isMeteredApnType(type, mPhone)) {
+                        && ApnSettingUtils.isMeteredApnType(type, mPhone)) {
                     log("Dropped the metered " + type + " for the unmetered data call.");
                     continue;
                 }
@@ -939,11 +957,7 @@
                         result.addCapability(NetworkCapabilities.NET_CAPABILITY_IMS);
                         result.addCapability(NetworkCapabilities.NET_CAPABILITY_CBS);
                         result.addCapability(NetworkCapabilities.NET_CAPABILITY_IA);
-                        // check if this is the DUN apn as well as returned by fetchDunApn().
-                        // If yes, add DUN capability too.
-                        if (mApnSetting.equals(securedDunApn)) {
-                            result.addCapability(NetworkCapabilities.NET_CAPABILITY_DUN);
-                        }
+                        result.addCapability(NetworkCapabilities.NET_CAPABILITY_DUN);
                         break;
                     }
                     case PhoneConstants.APN_TYPE_DEFAULT: {
@@ -959,9 +973,7 @@
                         break;
                     }
                     case PhoneConstants.APN_TYPE_DUN: {
-                        if (securedDunApn == null || securedDunApn.equals(mApnSetting)) {
-                            result.addCapability(NetworkCapabilities.NET_CAPABILITY_DUN);
-                        }
+                        result.addCapability(NetworkCapabilities.NET_CAPABILITY_DUN);
                         break;
                     }
                     case PhoneConstants.APN_TYPE_FOTA: {
@@ -993,7 +1005,7 @@
             // 2. The non-restricted data and is intended for unmetered use only.
             if (((mConnectionParams != null && mConnectionParams.mUnmeteredUseOnly)
                     && !mRestrictedNetworkOverride)
-                    || !mApnSetting.isMetered(mPhone)) {
+                    || !ApnSettingUtils.isMetered(mApnSetting, mPhone)) {
                 result.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
             } else {
                 result.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
@@ -1164,7 +1176,7 @@
             // only NOT be set only if we're in DcInactiveState.
             mApnSetting = apnContext.getApnSetting();
         }
-        if (mApnSetting == null || !mApnSetting.canHandleType(apnContext.getApnType())) {
+        if (mApnSetting == null || !mApnSetting.canHandleType(apnContext.getApnTypeBitmask())) {
             if (DBG) {
                 log("initConnection: incompatible apnSetting in ConnectionParams cp=" + cp
                         + " dc=" + DataConnection.this);
@@ -1271,7 +1283,7 @@
                     break;
                 }
                 case DcAsyncChannel.REQ_IS_INACTIVE: {
-                    boolean val = getIsInactive();
+                    boolean val = isInactive();
                     if (VDBG) log("REQ_IS_INACTIVE  isInactive=" + val);
                     mAc.replyToMessage(msg, DcAsyncChannel.RSP_IS_INACTIVE, val ? 1 : 0);
                     break;
@@ -1677,7 +1689,7 @@
 
             mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTED,
                     mNetworkInfo.getReason(), null);
-            mNetworkInfo.setExtraInfo(mApnSetting.apn);
+            mNetworkInfo.setExtraInfo(mApnSetting.getApnName());
             updateTcpBufferSizes(mRilRat);
 
             final NetworkMisc misc = new NetworkMisc();
@@ -1690,6 +1702,7 @@
             misc.subscriberId = mPhone.getSubscriberId();
 
             setNetworkRestriction();
+            if (DBG) log("mRestrictedNetworkOverride = " + mRestrictedNetworkOverride);
             mNetworkAgent = new DcNetworkAgent(getHandler().getLooper(), mPhone.getContext(),
                     "DcNetworkAgent", mNetworkInfo, getNetworkCapabilities(), mLinkProperties,
                     50, misc);
@@ -1838,10 +1851,26 @@
                     KeepalivePacketData pkt = (KeepalivePacketData) msg.obj;
                     int slotId = msg.arg1;
                     int intervalMillis = msg.arg2 * 1000;
-                    mPhone.mCi.startNattKeepalive(
-                            DataConnection.this.mCid, pkt, intervalMillis,
-                            DataConnection.this.obtainMessage(
-                                    EVENT_KEEPALIVE_STARTED, slotId, 0, null));
+                    if (mDataServiceManager.getTransportType()
+                            == AccessNetworkConstants.TransportType.WWAN) {
+                        mPhone.mCi.startNattKeepalive(
+                                DataConnection.this.mCid, pkt, intervalMillis,
+                                DataConnection.this.obtainMessage(
+                                        EVENT_KEEPALIVE_STARTED, slotId, 0, null));
+                    } else {
+                        // We currently do not support NATT Keepalive requests using the
+                        // DataService API, so unless the request is WWAN (always bound via
+                        // the CommandsInterface), the request cannot be honored.
+                        //
+                        // TODO: b/72331356 to add support for Keepalive to the DataService
+                        // so that keepalive requests can be handled (if supported) by the
+                        // underlying transport.
+                        if (mNetworkAgent != null) {
+                            mNetworkAgent.onPacketKeepaliveEvent(
+                                    msg.arg1,
+                                    ConnectivityManager.PacketKeepalive.ERROR_INVALID_NETWORK);
+                        }
+                    }
                     retVal = HANDLED;
                     break;
                 }
@@ -2100,7 +2129,6 @@
                 String logStr = "Changed from " + mNetworkCapabilities + " to "
                         + networkCapabilities + ", Data RAT="
                         + mPhone.getServiceState().getRilDataRadioTechnology()
-                        + ", DUN APN=\"" + mDct.fetchDunApn() + "\""
                         + ", mApnSetting=" + mApnSetting;
                 mNetCapsLocalLog.log(logStr);
                 log(logStr);
diff --git a/src/java/com/android/internal/telephony/dataconnection/DcAsyncChannel.java b/src/java/com/android/internal/telephony/dataconnection/DcAsyncChannel.java
index 8273dee..06bb7de 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DcAsyncChannel.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DcAsyncChannel.java
@@ -20,6 +20,7 @@
 import android.net.NetworkCapabilities;
 import android.net.ProxyInfo;
 import android.os.Message;
+import android.telephony.data.ApnSetting;
 
 import com.android.internal.telephony.dataconnection.DataConnection.ConnectionParams;
 import com.android.internal.telephony.dataconnection.DataConnection.DisconnectParams;
@@ -153,7 +154,7 @@
                 value = false;
             }
         } else {
-            value = mDc.getIsInactive();
+            value = mDc.isInactive();
         }
         return value;
     }
diff --git a/src/java/com/android/internal/telephony/dataconnection/DcRequest.java b/src/java/com/android/internal/telephony/dataconnection/DcRequest.java
index 79cfcec..bf2a6af 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DcRequest.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DcRequest.java
@@ -15,22 +15,10 @@
  */
 package com.android.internal.telephony.dataconnection;
 
-import static com.android.internal.telephony.DctConstants.APN_CBS_ID;
-import static com.android.internal.telephony.DctConstants.APN_DEFAULT_ID;
-import static com.android.internal.telephony.DctConstants.APN_DUN_ID;
-import static com.android.internal.telephony.DctConstants.APN_EMERGENCY_ID;
-import static com.android.internal.telephony.DctConstants.APN_FOTA_ID;
-import static com.android.internal.telephony.DctConstants.APN_IA_ID;
-import static com.android.internal.telephony.DctConstants.APN_IMS_ID;
-import static com.android.internal.telephony.DctConstants.APN_INVALID_ID;
-import static com.android.internal.telephony.DctConstants.APN_MMS_ID;
-import static com.android.internal.telephony.DctConstants.APN_SUPL_ID;
-
 import android.content.Context;
-import android.net.NetworkCapabilities;
 import android.net.NetworkConfig;
 import android.net.NetworkRequest;
-import android.telephony.Rlog;
+import android.telephony.data.ApnSetting.ApnType;
 
 import java.util.HashMap;
 
@@ -39,17 +27,17 @@
 
     public final NetworkRequest networkRequest;
     public final int priority;
-    public final int apnId;
+    public final @ApnType int apnType;
 
     public DcRequest(NetworkRequest nr, Context context) {
         initApnPriorities(context);
         networkRequest = nr;
-        apnId = apnIdForNetworkRequest(networkRequest);
-        priority = priorityForApnId(apnId);
+        apnType = ApnContext.getApnTypeFromNetworkRequest(networkRequest);
+        priority = priorityForApnType(apnType);
     }
 
     public String toString() {
-        return networkRequest.toString() + ", priority=" + priority + ", apnId=" + apnId;
+        return networkRequest.toString() + ", priority=" + priority + ", apnType=" + apnType;
     }
 
     public int hashCode() {
@@ -67,78 +55,6 @@
         return o.priority - priority;
     }
 
-    private int apnIdForNetworkRequest(NetworkRequest nr) {
-        NetworkCapabilities nc = nr.networkCapabilities;
-        // For now, ignore the bandwidth stuff
-        if (nc.getTransportTypes().length > 0 &&
-                nc.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) == false) {
-            return APN_INVALID_ID;
-        }
-
-        // in the near term just do 1-1 matches.
-        // TODO - actually try to match the set of capabilities
-        int apnId = APN_INVALID_ID;
-
-        boolean error = false;
-        if (nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
-            if (apnId != APN_INVALID_ID) error = true;
-            apnId = APN_DEFAULT_ID;
-        }
-        if (nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_MMS)) {
-            if (apnId != APN_INVALID_ID) error = true;
-            apnId = APN_MMS_ID;
-        }
-        if (nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_SUPL)) {
-            if (apnId != APN_INVALID_ID) error = true;
-            apnId = APN_SUPL_ID;
-        }
-        if (nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_DUN)) {
-            if (apnId != APN_INVALID_ID) error = true;
-            apnId = APN_DUN_ID;
-        }
-        if (nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_FOTA)) {
-            if (apnId != APN_INVALID_ID) error = true;
-            apnId = APN_FOTA_ID;
-        }
-        if (nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_IMS)) {
-            if (apnId != APN_INVALID_ID) error = true;
-            apnId = APN_IMS_ID;
-        }
-        if (nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_CBS)) {
-            if (apnId != APN_INVALID_ID) error = true;
-            apnId = APN_CBS_ID;
-        }
-        if (nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_IA)) {
-            if (apnId != APN_INVALID_ID) error = true;
-            apnId = APN_IA_ID;
-        }
-        if (nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_RCS)) {
-            if (apnId != APN_INVALID_ID) error = true;
-            apnId = APN_INVALID_ID;
-            loge("RCS APN type not yet supported");
-        }
-        if (nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_XCAP)) {
-            if (apnId != APN_INVALID_ID) error = true;
-            apnId = APN_INVALID_ID;
-            loge("XCAP APN type not yet supported");
-        }
-        if (nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_EIMS)) {
-            if (apnId != APN_INVALID_ID) error = true;
-            apnId = APN_EMERGENCY_ID;
-        }
-        if (error) {
-            // TODO: If this error condition is removed, the framework's handling of
-            // NET_CAPABILITY_NOT_RESTRICTED will need to be updated so requests for
-            // say FOTA and INTERNET are marked as restricted.  This is not how
-            // NetworkCapabilities.maybeMarkCapabilitiesRestricted currently works.
-            loge("Multiple apn types specified in request - result is unspecified!");
-        }
-        if (apnId == APN_INVALID_ID) {
-            loge("Unsupported NetworkRequest in Telephony: nr=" + nr);
-        }
-        return apnId;
-    }
-
     private static final HashMap<Integer, Integer> sApnPriorityMap =
             new HashMap<Integer, Integer>();
 
@@ -149,19 +65,15 @@
                         com.android.internal.R.array.networkAttributes);
                 for (String networkConfigString : networkConfigStrings) {
                     NetworkConfig networkConfig = new NetworkConfig(networkConfigString);
-                    final int apnId = ApnContext.apnIdForType(networkConfig.type);
-                    sApnPriorityMap.put(apnId, networkConfig.priority);
+                    final int apnType = ApnContext.getApnTypeFromNetworkType(networkConfig.type);
+                    sApnPriorityMap.put(apnType, networkConfig.priority);
                 }
             }
         }
     }
 
-    private int priorityForApnId(int apnId) {
-        Integer priority = sApnPriorityMap.get(apnId);
+    private int priorityForApnType(int apnType) {
+        Integer priority = sApnPriorityMap.get(apnType);
         return (priority != null ? priority.intValue() : 0);
     }
-
-    private void loge(String s) {
-        Rlog.e(LOG_TAG, s);
-    }
 }
diff --git a/src/java/com/android/internal/telephony/dataconnection/DcTracker.java b/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
index 381a66e..50130c8 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.telephony.dataconnection;
 
+import android.annotation.NonNull;
 import android.app.AlarmManager;
 import android.app.PendingIntent;
 import android.app.ProgressDialog;
@@ -35,7 +36,6 @@
 import android.net.NetworkCapabilities;
 import android.net.NetworkConfig;
 import android.net.NetworkRequest;
-import android.net.NetworkUtils;
 import android.net.ProxyInfo;
 import android.net.TrafficStats;
 import android.net.Uri;
@@ -64,6 +64,7 @@
 import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
 import android.telephony.TelephonyManager;
 import android.telephony.cdma.CdmaCellLocation;
+import android.telephony.data.ApnSetting;
 import android.telephony.data.DataProfile;
 import android.telephony.gsm.GsmCellLocation;
 import android.text.TextUtils;
@@ -97,7 +98,6 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.Map.Entry;
@@ -122,7 +122,7 @@
     private final AlarmManager mAlarmManager;
 
     /* Currently requested APN type (TODO: This should probably be a parameter not a member) */
-    private String mRequestedApnType = PhoneConstants.APN_TYPE_DEFAULT;
+    private int mRequestedApnType = ApnSetting.TYPE_DEFAULT;
 
     // All data enabling/disabling related settings
     private final DataEnabledSettings mDataEnabledSettings;
@@ -216,6 +216,7 @@
     private AsyncChannel mReplyAc = new AsyncChannel();
 
     private final LocalLog mDataRoamingLeakageLog = new LocalLog(50);
+    private final LocalLog mApnSettingsInitializationLog = new LocalLog(50);
 
     private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver () {
         @Override
@@ -353,10 +354,21 @@
             return "{txSum=" + txPkts + " rxSum=" + rxPkts + "}";
         }
 
-        public void updateTxRxSum() {
+        /**
+         * Get Tcp Tx/Rx packet count from TrafficStats
+         */
+        public void updateTcpTxRxSum() {
             this.txPkts = TrafficStats.getMobileTcpTxPackets();
             this.rxPkts = TrafficStats.getMobileTcpRxPackets();
         }
+
+        /**
+         * Get total Tx/Rx packet count from TrafficStats
+         */
+        public void updateTotalTxRxSum() {
+            this.txPkts = TrafficStats.getMobileTxPackets();
+            this.rxPkts = TrafficStats.getMobileRxPackets();
+        }
     }
 
     private void onActionIntentReconnectAlarm(Intent intent) {
@@ -503,7 +515,7 @@
     private final ConcurrentHashMap<String, ApnContext> mApnContexts =
             new ConcurrentHashMap<String, ApnContext>();
 
-    private final SparseArray<ApnContext> mApnContextsById = new SparseArray<ApnContext>();
+    private final SparseArray<ApnContext> mApnContextsByType = new SparseArray<ApnContext>();
 
     private int mDisconnectPendingCount = 0;
 
@@ -731,7 +743,7 @@
 
         mPhone.getContext().getContentResolver().unregisterContentObserver(mApnObserver);
         mApnContexts.clear();
-        mApnContextsById.clear();
+        mApnContextsByType.clear();
         mPrioritySortedApnContexts.clear();
         unregisterForAllEvents();
 
@@ -824,7 +836,7 @@
                             // data connection.
                             apnContext.setReason(Phone.REASON_DATA_ENABLED);
                             cleanUpConnection(true, apnContext);
-                        } else if (apnContext.getApnSetting().isMetered(mPhone)
+                        } else if (ApnSettingUtils.isMetered(apnContext.getApnSetting(), mPhone)
                                 && (netCaps != null && netCaps.hasCapability(
                                         NetworkCapabilities.NET_CAPABILITY_NOT_METERED))) {
                             if (DBG) {
@@ -868,40 +880,19 @@
     }
 
     public void requestNetwork(NetworkRequest networkRequest, LocalLog log) {
-        final int apnId = ApnContext.apnIdForNetworkRequest(networkRequest);
-        final ApnContext apnContext = mApnContextsById.get(apnId);
+        final int apnType = ApnContext.getApnTypeFromNetworkRequest(networkRequest);
+        final ApnContext apnContext = mApnContextsByType.get(apnType);
         log.log("DcTracker.requestNetwork for " + networkRequest + " found " + apnContext);
         if (apnContext != null) apnContext.requestNetwork(networkRequest, log);
     }
 
     public void releaseNetwork(NetworkRequest networkRequest, LocalLog log) {
-        final int apnId = ApnContext.apnIdForNetworkRequest(networkRequest);
-        final ApnContext apnContext = mApnContextsById.get(apnId);
+        final int apnType = ApnContext.getApnTypeFromNetworkRequest(networkRequest);
+        final ApnContext apnContext = mApnContextsByType.get(apnType);
         log.log("DcTracker.releaseNetwork for " + networkRequest + " found " + apnContext);
         if (apnContext != null) apnContext.releaseNetwork(networkRequest, log);
     }
 
-    public boolean isApnSupported(String name) {
-        if (name == null) {
-            loge("isApnSupported: name=null");
-            return false;
-        }
-        ApnContext apnContext = mApnContexts.get(name);
-        if (apnContext == null) {
-            loge("Request for unsupported mobile name: " + name);
-            return false;
-        }
-        return true;
-    }
-
-    public int getApnPriority(String name) {
-        ApnContext apnContext = mApnContexts.get(name);
-        if (apnContext == null) {
-            loge("Request for unsupported mobile name: " + name);
-        }
-        return apnContext.priority;
-    }
-
     // Turn telephony radio on or off.
     private void setRadio(boolean on) {
         final ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
@@ -972,7 +963,7 @@
     private ApnContext addApnContext(String type, NetworkConfig networkConfig) {
         ApnContext apnContext = new ApnContext(mPhone, type, LOG_TAG, networkConfig, this);
         mApnContexts.put(type, apnContext);
-        mApnContextsById.put(ApnContext.apnIdForApnName(type), apnContext);
+        mApnContextsByType.put(ApnSetting.getApnTypesBitmaskFromString(type), apnContext);
         mPrioritySortedApnContexts.add(apnContext);
         return apnContext;
     }
@@ -1076,19 +1067,37 @@
         if (apnContext != null) {
             ApnSetting apnSetting = apnContext.getApnSetting();
             if (apnSetting != null) {
-                return apnSetting.apn;
+                return apnSetting.getApnName();
             }
         }
         return null;
     }
 
-    // Return state of specific apn type
+    /**
+     * Returns {@link DctConstants.State} based on the state of the {@link DataConnection} that
+     * contains a {@link ApnSetting} that supported the given apn type {@code anpType}.
+     *
+     * <p>
+     * Assumes there is less than one {@link ApnSetting} can support the given apn type.
+     */
     public DctConstants.State getState(String apnType) {
-        ApnContext apnContext = mApnContexts.get(apnType);
-        if (apnContext != null) {
-            return apnContext.getState();
+        final int apnTypeBitmask = ApnSetting.getApnTypesBitmaskFromString(apnType);
+        for (DataConnection dc : mDataConnections.values()) {
+            ApnSetting apnSetting = dc.getApnSetting();
+            if (apnSetting != null && apnSetting.canHandleType(apnTypeBitmask)) {
+                if (dc.isActive()) {
+                    return DctConstants.State.CONNECTED;
+                } else if (dc.isActivating()) {
+                    return DctConstants.State.CONNECTING;
+                } else if (dc.isInactive()) {
+                    return DctConstants.State.IDLE;
+                } else if (dc.isDisconnecting()) {
+                    return DctConstants.State.DISCONNECTING;
+                }
+            }
         }
-        return DctConstants.State.FAILED;
+
+        return DctConstants.State.IDLE;
     }
 
     // Return if apn type is a provisioning apn.
@@ -1240,7 +1249,7 @@
                 SubscriptionManager.getDefaultDataSubscriptionId());
 
         boolean isMeteredApnType = apnContext == null
-                || ApnSetting.isMeteredApnType(apnContext.getApnType(), mPhone);
+                || ApnSettingUtils.isMeteredApnType(apnContext.getApnType(), mPhone);
 
         PhoneConstants.State phoneState = PhoneConstants.State.IDLE;
         // Note this is explicitly not using mPhone.getState.  See b/19090488.
@@ -1337,11 +1346,11 @@
             reasons.add(DataAllowedReasonType.UNMETERED_APN);
         }
 
-        // If the request is restricted and there are only soft disallowed reasons (e.g. data
-        // disabled, data roaming disabled) existing, we should allow the data.
+        // If the request is restricted and there are only disallowed reasons due to data
+        // disabled, we should allow the data.
         if (apnContext != null
                 && !apnContext.hasNoRestrictedRequests(true)
-                && !reasons.allowed()) {
+                && reasons.contains(DataDisallowedReasonType.DATA_DISABLED)) {
             reasons.add(DataAllowedReasonType.RESTRICTED_REQUEST);
         }
 
@@ -1544,7 +1553,7 @@
                 // Use ApnSetting to decide metered or non-metered.
                 // Tear down all metered data connections.
                 ApnSetting apnSetting = apnContext.getApnSetting();
-                if (apnSetting != null && apnSetting.isMetered(mPhone)) {
+                if (apnSetting != null && ApnSettingUtils.isMetered(apnSetting, mPhone)) {
                     if (apnContext.isDisconnected() == false) didDisconnect = true;
                     if (DBG) log("clean up metered ApnContext Type: " + apnContext.getApnType());
                     apnContext.setReason(reason);
@@ -1567,7 +1576,7 @@
         stopDataStallAlarm();
 
         // TODO: Do we need mRequestedApnType?
-        mRequestedApnType = PhoneConstants.APN_TYPE_DEFAULT;
+        mRequestedApnType = ApnSetting.TYPE_DEFAULT;
 
         log("cleanUpConnection: mDisconnectPendingCount = " + mDisconnectPendingCount);
         if (tearDown && mDisconnectPendingCount == 0) {
@@ -1689,27 +1698,27 @@
     }
 
     /**
-     * Fetch dun apn
-     * @return ApnSetting to be used for dun
+     * Fetch the DUN apns
+     * @return a list of DUN ApnSetting objects
      */
     @VisibleForTesting
-    public ApnSetting fetchDunApn() {
+    public @NonNull ArrayList<ApnSetting> fetchDunApns() {
         if (SystemProperties.getBoolean("net.tethering.noprovisioning", false)) {
-            log("fetchDunApn: net.tethering.noprovisioning=true ret: null");
-            return null;
+            log("fetchDunApns: net.tethering.noprovisioning=true ret: empty list");
+            return new ArrayList<ApnSetting>(0);
         }
         int bearer = mPhone.getServiceState().getRilDataRadioTechnology();
         IccRecords r = mIccRecords.get();
         String operator = (r != null) ? r.getOperatorNumeric() : "";
         ArrayList<ApnSetting> dunCandidates = new ArrayList<ApnSetting>();
-        ApnSetting retDunSetting = null;
+        ArrayList<ApnSetting> retDunSettings = new ArrayList<ApnSetting>();
 
         // Places to look for tether APN in order: TETHER_DUN_APN setting (to be deprecated soon),
         // APN database, and config_tether_apndata resource (to be deprecated soon).
         String apnData = Settings.Global.getString(mResolver, Settings.Global.TETHER_DUN_APN);
         if (!TextUtils.isEmpty(apnData)) {
             dunCandidates.addAll(ApnSetting.arrayFromString(apnData));
-            if (VDBG) log("fetchDunApn: dunCandidates from Setting: " + dunCandidates);
+            if (VDBG) log("fetchDunApns: dunCandidates from Setting: " + dunCandidates);
         }
 
         // todo: remove this and config_tether_apndata after APNs are moved from overlay to apns xml
@@ -1724,48 +1733,60 @@
                     // apn may be null if apnString isn't valid or has error parsing
                     if (apn != null) dunCandidates.add(apn);
                 }
-                if (VDBG) log("fetchDunApn: dunCandidates from resource: " + dunCandidates);
+                if (VDBG) log("fetchDunApns: dunCandidates from resource: " + dunCandidates);
             }
         }
 
         if (dunCandidates.isEmpty()) {
             if (!ArrayUtils.isEmpty(mAllApnSettings)) {
                 for (ApnSetting apn : mAllApnSettings) {
-                    if (apn.canHandleType(PhoneConstants.APN_TYPE_DUN)) {
+                    if (apn.canHandleType(ApnSetting.TYPE_DUN)) {
                         dunCandidates.add(apn);
                     }
                 }
-                if (VDBG) log("fetchDunApn: dunCandidates from database: " + dunCandidates);
+                if (VDBG) log("fetchDunApns: dunCandidates from database: " + dunCandidates);
             }
         }
 
         for (ApnSetting dunSetting : dunCandidates) {
-            if (!ServiceState.bitmaskHasTech(dunSetting.networkTypeBitmask,
+            if (!ServiceState.bitmaskHasTech(dunSetting.getNetworkTypeBitmask(),
                     ServiceState.rilRadioTechnologyToNetworkType(bearer))) {
                 continue;
             }
-            if (dunSetting.numeric.equals(operator)) {
+            if (dunSetting.getOperatorNumeric().equals(operator)) {
                 if (dunSetting.hasMvnoParams()) {
-                    if (r != null && ApnSetting.mvnoMatches(r, dunSetting.mvnoType,
-                            dunSetting.mvnoMatchData)) {
-                        retDunSetting = dunSetting;
-                        break;
+                    if (r != null && ApnSettingUtils.mvnoMatches(r, dunSetting.getMvnoType(),
+                            dunSetting.getMvnoMatchData())) {
+                        retDunSettings.add(dunSetting);
                     }
                 } else if (mMvnoMatched == false) {
-                    retDunSetting = dunSetting;
-                    break;
+                    retDunSettings.add(dunSetting);
                 }
             }
         }
 
-        if (VDBG) log("fetchDunApn: dunSetting=" + retDunSetting);
-        return retDunSetting;
+        if (VDBG) log("fetchDunApns: dunSettings=" + retDunSettings);
+        return retDunSettings;
+    }
+
+    private int getPreferredApnSetId() {
+        Cursor c = mPhone.getContext().getContentResolver()
+                .query(Uri.withAppendedPath(Telephony.Carriers.CONTENT_URI,
+                    "preferapnset/subId/" + mPhone.getSubId()),
+                        new String[] {Telephony.Carriers.APN_SET_ID}, null, null, null);
+        if (c.getCount() < 1) {
+            loge("getPreferredApnSetId: no APNs found");
+            return Telephony.Carriers.NO_SET_SET;
+        } else {
+            c.moveToFirst();
+            return c.getInt(0 /* index of Telephony.Carriers.APN_SET_ID */);
+        }
     }
 
     public boolean hasMatchedTetherApnSetting() {
-        ApnSetting matched = fetchDunApn();
-        log("hasMatchedTetherApnSetting: APN=" + matched);
-        return matched != null;
+        ArrayList<ApnSetting> matches = fetchDunApns();
+        log("hasMatchedTetherApnSetting: APNs=" + matches);
+        return matches.size() > 0;
     }
 
     /**
@@ -1776,7 +1797,8 @@
         final int rilRat = mPhone.getServiceState().getRilDataRadioTechnology();
         if (ServiceState.isCdma(rilRat)) return true;
 
-        return (fetchDunApn() != null);
+        ArrayList<ApnSetting> apns = fetchDunApns();
+        return apns.size() > 0;
     }
 
     /**
@@ -1797,72 +1819,11 @@
         }
     }
 
-    /**
-     * @param types comma delimited list of APN types
-     * @return array of APN types
-     */
-    private String[] parseTypes(String types) {
-        String[] result;
-        // If unset, set to DEFAULT.
-        if (types == null || types.equals("")) {
-            result = new String[1];
-            result[0] = PhoneConstants.APN_TYPE_ALL;
-        } else {
-            result = types.split(",");
-        }
-        return result;
-    }
-
     boolean isPermanentFailure(DcFailCause dcFailCause) {
         return (dcFailCause.isPermanentFailure(mPhone.getContext(), mPhone.getSubId()) &&
                 (mAttached.get() == false || dcFailCause != DcFailCause.SIGNAL_LOST));
     }
 
-    private ApnSetting makeApnSetting(Cursor cursor) {
-        String[] types = parseTypes(
-                cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.TYPE)));
-        int networkTypeBitmask = cursor.getInt(
-                cursor.getColumnIndexOrThrow(Telephony.Carriers.NETWORK_TYPE_BITMASK));
-
-        ApnSetting apn = new ApnSetting(
-                cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers._ID)),
-                cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.NUMERIC)),
-                cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.NAME)),
-                cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.APN)),
-                NetworkUtils.trimV4AddrZeros(
-                        cursor.getString(
-                        cursor.getColumnIndexOrThrow(Telephony.Carriers.PROXY))),
-                cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.PORT)),
-                NetworkUtils.trimV4AddrZeros(
-                        cursor.getString(
-                        cursor.getColumnIndexOrThrow(Telephony.Carriers.MMSC))),
-                NetworkUtils.trimV4AddrZeros(
-                        cursor.getString(
-                        cursor.getColumnIndexOrThrow(Telephony.Carriers.MMSPROXY))),
-                cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.MMSPORT)),
-                cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.USER)),
-                cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.PASSWORD)),
-                cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.AUTH_TYPE)),
-                types,
-                cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.PROTOCOL)),
-                cursor.getString(cursor.getColumnIndexOrThrow(
-                        Telephony.Carriers.ROAMING_PROTOCOL)),
-                cursor.getInt(cursor.getColumnIndexOrThrow(
-                        Telephony.Carriers.CARRIER_ENABLED)) == 1,
-                networkTypeBitmask,
-                cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.PROFILE_ID)),
-                cursor.getInt(cursor.getColumnIndexOrThrow(
-                        Telephony.Carriers.MODEM_COGNITIVE)) == 1,
-                cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.MAX_CONNS)),
-                cursor.getInt(cursor.getColumnIndexOrThrow(
-                        Telephony.Carriers.WAIT_TIME)),
-                cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.MAX_CONNS_TIME)),
-                cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.MTU)),
-                cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.MVNO_TYPE)),
-                cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.MVNO_MATCH_DATA)));
-        return apn;
-    }
-
     private ArrayList<ApnSetting> createApnList(Cursor cursor) {
         ArrayList<ApnSetting> mnoApns = new ArrayList<ApnSetting>();
         ArrayList<ApnSetting> mvnoApns = new ArrayList<ApnSetting>();
@@ -1870,13 +1831,14 @@
 
         if (cursor.moveToFirst()) {
             do {
-                ApnSetting apn = makeApnSetting(cursor);
+                ApnSetting apn = ApnSetting.makeApnSetting(cursor);
                 if (apn == null) {
                     continue;
                 }
 
                 if (apn.hasMvnoParams()) {
-                    if (r != null && ApnSetting.mvnoMatches(r, apn.mvnoType, apn.mvnoMatchData)) {
+                    if (r != null && ApnSettingUtils.mvnoMatches(r, apn.getMvnoType(),
+                            apn.getMvnoMatchData())) {
                         mvnoApns.add(apn);
                     }
                 } else {
@@ -1950,7 +1912,7 @@
             return false;
         }
 
-        int profileId = apnSetting.profileId;
+        int profileId = apnSetting.getProfileId();
         if (profileId == 0) {
             profileId = getApnProfileID(apnContext.getApnType());
         }
@@ -1959,8 +1921,8 @@
         // a dun-profiled connection so we can't share an existing one
         // On GSM/LTE we can share existing apn connections provided they support
         // this type.
-        if (apnContext.getApnType() != PhoneConstants.APN_TYPE_DUN ||
-                teardownForDun() == false) {
+        if (!apnContext.getApnType().equals(PhoneConstants.APN_TYPE_DUN)
+                || ServiceState.isGsm(mPhone.getServiceState().getRilDataRadioTechnology())) {
             dcac = checkForCompatibleConnectedApnContext(apnContext);
             if (dcac != null) {
                 // Get the dcacApnSetting for the connection we want to share.
@@ -2035,7 +1997,7 @@
 
         log("setInitialApn: E mPreferredApn=" + mPreferredApn);
 
-        if (mPreferredApn != null && mPreferredApn.canHandleType(PhoneConstants.APN_TYPE_IA)) {
+        if (mPreferredApn != null && mPreferredApn.canHandleType(ApnSetting.TYPE_IA)) {
               iaApnSetting = mPreferredApn;
         } else if (mAllApnSettings != null && !mAllApnSettings.isEmpty()) {
             firstApnSetting = mAllApnSettings.get(0);
@@ -2043,13 +2005,13 @@
 
             // Search for Initial APN setting and the first apn that can handle default
             for (ApnSetting apn : mAllApnSettings) {
-                if (apn.canHandleType(PhoneConstants.APN_TYPE_IA)) {
+                if (apn.canHandleType(ApnSetting.TYPE_IA)) {
                     // The Initial Attach APN is highest priority so use it if there is one
                     log("setInitialApn: iaApnSetting=" + apn);
                     iaApnSetting = apn;
                     break;
                 } else if ((defaultApnSetting == null)
-                        && (apn.canHandleType(PhoneConstants.APN_TYPE_DEFAULT))) {
+                        && (apn.canHandleType(ApnSetting.TYPE_DEFAULT))) {
                     // Use the first default apn if no better choice
                     log("setInitialApn: defaultApnSetting=" + apn);
                     defaultApnSetting = apn;
@@ -2325,24 +2287,6 @@
         mAutoAttachOnCreation.set(false);
     }
 
-    private void onSetDependencyMet(String apnType, boolean met) {
-        // don't allow users to tweak hipri to work around default dependency not met
-        if (PhoneConstants.APN_TYPE_HIPRI.equals(apnType)) return;
-
-        ApnContext apnContext = mApnContexts.get(apnType);
-        if (apnContext == null) {
-            loge("onSetDependencyMet: ApnContext not found in onSetDependencyMet(" +
-                    apnType + ", " + met + ")");
-            return;
-        }
-        applyNewState(apnContext, apnContext.isEnabled(), met);
-        if (PhoneConstants.APN_TYPE_DEFAULT.equals(apnType)) {
-            // tie actions on default to similar actions on HIPRI regarding dependencyMet
-            apnContext = mApnContexts.get(PhoneConstants.APN_TYPE_HIPRI);
-            if (apnContext != null) applyNewState(apnContext, apnContext.isEnabled(), met);
-        }
-    }
-
     public void setPolicyDataEnabled(boolean enabled) {
         if (DBG) log("setPolicyDataEnabled: " + enabled);
         Message msg = obtainMessage(DctConstants.CMD_SET_POLICY_DATA_ENABLE);
@@ -2447,11 +2391,11 @@
     }
 
     private DcAsyncChannel checkForCompatibleConnectedApnContext(ApnContext apnContext) {
-        String apnType = apnContext.getApnType();
-        ApnSetting dunSetting = null;
+        int apnType = apnContext.getApnTypeBitmask();
+        ArrayList<ApnSetting> dunSettings = null;
 
-        if (PhoneConstants.APN_TYPE_DUN.equals(apnType)) {
-            dunSetting = fetchDunApn();
+        if (ApnSetting.TYPE_DUN == apnType) {
+            dunSettings = sortApnListByPreferred(fetchDunApns());
         }
         if (DBG) {
             log("checkForCompatibleConnectedApnContext: apnContext=" + apnContext );
@@ -2464,23 +2408,26 @@
             if (curDcac != null) {
                 ApnSetting apnSetting = curApnCtx.getApnSetting();
                 log("apnSetting: " + apnSetting);
-                if (dunSetting != null) {
-                    if (dunSetting.equals(apnSetting)) {
-                        switch (curApnCtx.getState()) {
-                            case CONNECTED:
-                                if (DBG) {
-                                    log("checkForCompatibleConnectedApnContext:"
-                                            + " found dun conn=" + curDcac
-                                            + " curApnCtx=" + curApnCtx);
-                                }
-                                return curDcac;
-                            case RETRYING:
-                            case CONNECTING:
-                                potentialDcac = curDcac;
-                                potentialApnCtx = curApnCtx;
-                            default:
-                                // Not connected, potential unchanged
-                                break;
+                if (dunSettings != null && dunSettings.size() > 0) {
+                    for (ApnSetting dunSetting : dunSettings) {
+                        if (dunSetting.equals(apnSetting)) {
+                            switch (curApnCtx.getState()) {
+                                case CONNECTED:
+                                    if (DBG) {
+                                        log("checkForCompatibleConnectedApnContext:"
+                                                + " found dun conn=" + curDcac
+                                                + " curApnCtx=" + curApnCtx);
+                                    }
+                                    return curDcac;
+                                case RETRYING:
+                                case CONNECTING:
+                                    potentialDcac = curDcac;
+                                    potentialApnCtx = curApnCtx;
+                                    break;
+                                default:
+                                    // Not connected, potential unchanged
+                                    break;
+                            }
                         }
                     }
                 } else if (apnSetting != null && apnSetting.canHandleType(apnType)) {
@@ -2496,6 +2443,7 @@
                         case CONNECTING:
                             potentialDcac = curDcac;
                             potentialApnCtx = curApnCtx;
+                            break;
                         default:
                             // Not connected, potential unchanged
                             break;
@@ -2519,17 +2467,17 @@
         return null;
     }
 
-    public void setEnabled(int id, boolean enable) {
+    public void setEnabled(int apnType, boolean enable) {
         Message msg = obtainMessage(DctConstants.EVENT_ENABLE_NEW_APN);
-        msg.arg1 = id;
+        msg.arg1 = apnType;
         msg.arg2 = (enable ? DctConstants.ENABLED : DctConstants.DISABLED);
         sendMessage(msg);
     }
 
-    private void onEnableApn(int apnId, int enabled) {
-        ApnContext apnContext = mApnContextsById.get(apnId);
+    private void onEnableApn(int apnType, int enabled) {
+        ApnContext apnContext = mApnContextsByType.get(apnType);
         if (apnContext == null) {
-            loge("onEnableApn(" + apnId + ", " + enabled + "): NO ApnContext");
+            loge("onEnableApn(" + apnType + ", " + enabled + "): NO ApnContext");
             return;
         }
         // TODO change our retry manager to use the appropriate numbers for the new APN
@@ -2874,18 +2822,20 @@
             } else {
                 ApnSetting apn = apnContext.getApnSetting();
                 if (DBG) {
-                    log("onDataSetupComplete: success apn=" + (apn == null ? "unknown" : apn.apn));
+                    log("onDataSetupComplete: success apn=" + (apn == null ? "unknown"
+                            : apn.getApnName()));
                 }
-                if (apn != null && apn.proxy != null && apn.proxy.length() != 0) {
+                if (apn != null && !TextUtils.isEmpty(apn.getProxyAddressAsString())) {
                     try {
-                        String port = apn.port;
-                        if (TextUtils.isEmpty(port)) port = "8080";
-                        ProxyInfo proxy = new ProxyInfo(apn.proxy,
-                                Integer.parseInt(port), null);
+                        int port = apn.getProxyPort();
+                        if (port == -1) {
+                            port = 8080;
+                        }
+                        ProxyInfo proxy = new ProxyInfo(apn.getProxyAddressAsString(), port, null);
                         dcac.setLinkPropertiesHttpProxySync(proxy);
                     } catch (NumberFormatException e) {
-                        loge("onDataSetupComplete: NumberFormatException making ProxyProperties (" +
-                                apn.port + "): " + e);
+                        loge("onDataSetupComplete: NumberFormatException making ProxyProperties ("
+                                + apn.getProxyPort() + "): " + e);
                     }
                 }
 
@@ -2900,7 +2850,7 @@
                         if (DBG) log("onDataSetupComplete: PREFERRED APN is null");
                         mPreferredApn = apn;
                         if (mPreferredApn != null) {
-                            setPreferredApn(mPreferredApn.id);
+                            setPreferredApn(mPreferredApn.getId());
                         }
                     }
                 } else {
@@ -2983,7 +2933,7 @@
             if (DBG) {
                 ApnSetting apn = apnContext.getApnSetting();
                 log(String.format("onDataSetupComplete: error apn=%s cause=%s",
-                        (apn == null ? "unknown" : apn.apn), cause));
+                        (apn == null ? "unknown" : apn.getApnName()), cause));
             }
             if (cause.isEventLoggable()) {
                 // Log this failure to the Event Logs.
@@ -2993,7 +2943,8 @@
             }
             ApnSetting apn = apnContext.getApnSetting();
             mPhone.notifyPreciseDataConnectionFailed(apnContext.getReason(),
-                    apnContext.getApnType(), apn != null ? apn.apn : "unknown", cause.toString());
+                    apnContext.getApnType(), apn != null ? apn.getApnName()
+                    : "unknown", cause.toString());
 
             // Compose broadcast intent send to the specific carrier signaling receivers
             Intent intent = new Intent(TelephonyIntents
@@ -3221,9 +3172,9 @@
         setupDataOnConnectableApns(Phone.REASON_VOICE_CALL_ENDED);
     }
 
-    private void onCleanUpConnection(boolean tearDown, int apnId, String reason) {
+    private void onCleanUpConnection(boolean tearDown, int apnType, String reason) {
         if (DBG) log("onCleanUpConnection");
-        ApnContext apnContext = mApnContextsById.get(apnId);
+        ApnContext apnContext = mApnContextsByType.get(apnType);
         if (apnContext != null) {
             apnContext.setReason(reason);
             cleanUpConnection(tearDown, apnContext);
@@ -3269,7 +3220,7 @@
         if (mAllApnSettings != null && !mAllApnSettings.isEmpty()) {
             ArrayList<DataProfile> dps = new ArrayList<DataProfile>();
             for (ApnSetting apn : mAllApnSettings) {
-                if (apn.modemCognitive) {
+                if (apn.getModemCognitive()) {
                     DataProfile dp = createDataProfile(apn);
                     if (!dps.contains(dp)) {
                         dps.add(dp);
@@ -3307,8 +3258,14 @@
             if (cursor != null) {
                 if (cursor.getCount() > 0) {
                     mAllApnSettings = createApnList(cursor);
+                } else {
+                    if (DBG) log("createAllApnList: cursor count is 0");
+                    mApnSettingsInitializationLog.log("no APN in db for carrier: " + operator);
                 }
                 cursor.close();
+            } else {
+                if (DBG) log("createAllApnList: cursor is null");
+                mApnSettingsInitializationLog.log("cursor is null for carrier: " + operator);
             }
         }
 
@@ -3318,12 +3275,13 @@
 
         if (mAllApnSettings.isEmpty()) {
             if (DBG) log("createAllApnList: No APN found for carrier: " + operator);
+            mApnSettingsInitializationLog.log("no APN found for carrier: " + operator);
             mPreferredApn = null;
             // TODO: What is the right behavior?
             //notifyNoData(DataConnection.FailCause.MISSING_UNKNOWN_APN);
         } else {
             mPreferredApn = getPreferredApn();
-            if (mPreferredApn != null && !mPreferredApn.numeric.equals(operator)) {
+            if (mPreferredApn != null && !mPreferredApn.getOperatorNumeric().equals(operator)) {
                 mPreferredApn = null;
                 setPreferredApn(-1);
             }
@@ -3360,36 +3318,33 @@
     }
 
     private ApnSetting mergeApns(ApnSetting dest, ApnSetting src) {
-        int id = dest.id;
-        ArrayList<String> resultTypes = new ArrayList<String>();
-        resultTypes.addAll(Arrays.asList(dest.types));
-        for (String srcType : src.types) {
-            if (resultTypes.contains(srcType) == false) resultTypes.add(srcType);
-            if (srcType.equals(PhoneConstants.APN_TYPE_DEFAULT)) id = src.id;
+        int id = dest.getId();
+        if ((src.getApnTypeBitmask() & ApnSetting.TYPE_DEFAULT) == ApnSetting.TYPE_DEFAULT) {
+            id = src.getId();
         }
-        String mmsc = (TextUtils.isEmpty(dest.mmsc) ? src.mmsc : dest.mmsc);
-        String mmsProxy = (TextUtils.isEmpty(dest.mmsProxy) ? src.mmsProxy : dest.mmsProxy);
-        String mmsPort = (TextUtils.isEmpty(dest.mmsPort) ? src.mmsPort : dest.mmsPort);
-        String proxy = (TextUtils.isEmpty(dest.proxy) ? src.proxy : dest.proxy);
-        String port = (TextUtils.isEmpty(dest.port) ? src.port : dest.port);
-        String protocol = src.protocol.equals("IPV4V6") ? src.protocol : dest.protocol;
-        String roamingProtocol = src.roamingProtocol.equals("IPV4V6") ? src.roamingProtocol :
-                dest.roamingProtocol;
-        int networkTypeBitmask = (dest.networkTypeBitmask == 0 || src.networkTypeBitmask == 0)
-                ? 0 : (dest.networkTypeBitmask | src.networkTypeBitmask);
-        if (networkTypeBitmask == 0) {
-            int bearerBitmask = (dest.bearerBitmask == 0 || src.bearerBitmask == 0)
-                    ? 0 : (dest.bearerBitmask | src.bearerBitmask);
-            networkTypeBitmask = ServiceState.convertBearerBitmaskToNetworkTypeBitmask(
-                    bearerBitmask);
-        }
+        final int resultApnType = src.getApnTypeBitmask() | dest.getApnTypeBitmask();
+        Uri mmsc = (dest.getMmsc() == null ? src.getMmsc() : dest.getMmsc());
+        String mmsProxy = TextUtils.isEmpty(dest.getMmsProxyAddressAsString())
+                ? src.getMmsProxyAddressAsString() : dest.getMmsProxyAddressAsString();
+        int mmsPort = dest.getMmsProxyPort() == -1 ? src.getMmsProxyPort() : dest.getMmsProxyPort();
+        String proxy = TextUtils.isEmpty(dest.getProxyAddressAsString())
+                ? src.getProxyAddressAsString() : dest.getProxyAddressAsString();
+        int port = dest.getProxyPort() == -1 ? src.getProxyPort() : dest.getProxyPort();
+        int protocol = src.getProtocol() == ApnSetting.PROTOCOL_IPV4V6 ? src.getProtocol()
+                : dest.getProtocol();
+        int roamingProtocol = src.getRoamingProtocol() == ApnSetting.PROTOCOL_IPV4V6
+                ? src.getRoamingProtocol() : dest.getRoamingProtocol();
+        int networkTypeBitmask = (dest.getNetworkTypeBitmask() == 0
+                || src.getNetworkTypeBitmask() == 0)
+                ? 0 : (dest.getNetworkTypeBitmask() | src.getNetworkTypeBitmask());
 
-        return new ApnSetting(id, dest.numeric, dest.carrier, dest.apn,
-                proxy, port, mmsc, mmsProxy, mmsPort, dest.user, dest.password,
-                dest.authType, resultTypes.toArray(new String[0]), protocol,
-                roamingProtocol, dest.carrierEnabled, networkTypeBitmask, dest.profileId,
-                (dest.modemCognitive || src.modemCognitive), dest.maxConns, dest.waitTime,
-                dest.maxConnsTime, dest.mtu, dest.mvnoType, dest.mvnoMatchData);
+        return ApnSetting.makeApnSetting(id, dest.getOperatorNumeric(), dest.getEntryName(),
+            dest.getApnName(), proxy, port, mmsc, mmsProxy, mmsPort, dest.getUser(),
+            dest.getPassword(), dest.getAuthType(), resultApnType, protocol, roamingProtocol,
+            dest.isEnabled(), networkTypeBitmask, dest.getProfileId(),
+            (dest.getModemCognitive() || src.getModemCognitive()), dest.getMaxConns(),
+            dest.getWaitTime(), dest.getMaxConnsTime(), dest.getMtu(), dest.getMvnoType(),
+            dest.getMvnoMatchData(), dest.getApnSetId());
     }
 
     /** Return the DC AsyncChannel for the new data connection */
@@ -3432,12 +3387,15 @@
         if (DBG) log("buildWaitingApns: E requestedApnType=" + requestedApnType);
         ArrayList<ApnSetting> apnList = new ArrayList<ApnSetting>();
 
-        if (requestedApnType.equals(PhoneConstants.APN_TYPE_DUN)) {
-            ApnSetting dun = fetchDunApn();
-            if (dun != null) {
-                apnList.add(dun);
-                if (DBG) log("buildWaitingApns: X added APN_TYPE_DUN apnList=" + apnList);
-                return apnList;
+        int requestedApnTypeBitmask = ApnSetting.getApnTypesBitmaskFromString(requestedApnType);
+        if (requestedApnTypeBitmask == ApnSetting.TYPE_DUN) {
+            ArrayList<ApnSetting> dunApns = fetchDunApns();
+            if (dunApns.size() > 0) {
+                for (ApnSetting dun : dunApns) {
+                    apnList.add(dun);
+                    if (DBG) log("buildWaitingApns: X added APN_TYPE_DUN apnList=" + apnList);
+                }
+                return sortApnListByPreferred(apnList);
             }
         }
 
@@ -3469,15 +3427,16 @@
         }
 
         if (usePreferred && mCanSetPreferApn && mPreferredApn != null &&
-                mPreferredApn.canHandleType(requestedApnType)) {
+                mPreferredApn.canHandleType(requestedApnTypeBitmask)) {
             if (DBG) {
                 log("buildWaitingApns: Preferred APN:" + operator + ":"
-                        + mPreferredApn.numeric + ":" + mPreferredApn);
+                        + mPreferredApn.getOperatorNumeric() + ":" + mPreferredApn);
             }
-            if (mPreferredApn.numeric.equals(operator)) {
-                if (ServiceState.bitmaskHasTech(mPreferredApn.networkTypeBitmask,
+            if (mPreferredApn.getOperatorNumeric().equals(operator)) {
+                if (ServiceState.bitmaskHasTech(mPreferredApn.getNetworkTypeBitmask(),
                         ServiceState.rilRadioTechnologyToNetworkType(radioTech))) {
                     apnList.add(mPreferredApn);
+                    apnList = sortApnListByPreferred(apnList);
                     if (DBG) log("buildWaitingApns: X added preferred apnList=" + apnList);
                     return apnList;
                 } else {
@@ -3494,15 +3453,15 @@
         if (mAllApnSettings != null) {
             if (DBG) log("buildWaitingApns: mAllApnSettings=" + mAllApnSettings);
             for (ApnSetting apn : mAllApnSettings) {
-                if (apn.canHandleType(requestedApnType)) {
-                    if (ServiceState.bitmaskHasTech(apn.networkTypeBitmask,
+                if (apn.canHandleType(requestedApnTypeBitmask)) {
+                    if (ServiceState.bitmaskHasTech(apn.getNetworkTypeBitmask(),
                             ServiceState.rilRadioTechnologyToNetworkType(radioTech))) {
                         if (DBG) log("buildWaitingApns: adding apn=" + apn);
                         apnList.add(apn);
                     } else {
                         if (DBG) {
-                            log("buildWaitingApns: bearerBitmask:" + apn.bearerBitmask
-                                    + " or " + "networkTypeBitmask:" + apn.networkTypeBitmask
+                            log("buildWaitingApns: networkTypeBitmask:"
+                                    + apn.getNetworkTypeBitmask()
                                     + "do not include radioTech:" + radioTech);
                         }
                     }
@@ -3514,10 +3473,45 @@
         } else {
             loge("mAllApnSettings is null!");
         }
+
+        apnList = sortApnListByPreferred(apnList);
         if (DBG) log("buildWaitingApns: " + apnList.size() + " APNs in the list: " + apnList);
         return apnList;
     }
 
+    /**
+     * Sort a list of ApnSetting objects, with the preferred APNs at the front of the list
+     *
+     * e.g. if the preferred APN set = 2 and we have
+     *   1. APN with apn_set_id = 0 = Carriers.NO_SET_SET (no set is set)
+     *   2. APN with apn_set_id = 1 (not preferred set)
+     *   3. APN with apn_set_id = 2 (preferred set)
+     * Then the return order should be (3, 1, 2) or (3, 2, 1)
+     *
+     * e.g. if the preferred APN set = Carriers.NO_SET_SET (no preferred set) then the
+     * return order can be anything
+     */
+    @VisibleForTesting
+    public ArrayList<ApnSetting> sortApnListByPreferred(ArrayList<ApnSetting> list) {
+        if (list == null || list.size() <= 1) return list;
+        int preferredApnSetId = getPreferredApnSetId();
+        if (preferredApnSetId != Telephony.Carriers.NO_SET_SET) {
+            list.sort(new Comparator<ApnSetting>() {
+                @Override
+                public int compare(ApnSetting apn1, ApnSetting apn2) {
+                    if (apn1.getApnSetId() == preferredApnSetId) {
+                        return -1;
+                    }
+                    if (apn2.getApnSetId() == preferredApnSetId) {
+                        return 1;
+                    }
+                    return 0;
+                }
+            });
+        }
+        return list;
+    }
+
     private String apnListToString (ArrayList<ApnSetting> apns) {
         StringBuilder result = new StringBuilder();
         for (int i = 0, size = apns.size(); i < size; i++) {
@@ -3574,7 +3568,7 @@
             pos = cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers._ID));
             for(ApnSetting p : mAllApnSettings) {
                 log("getPreferredApn: apnSetting=" + p);
-                if (p.id == pos && p.canHandleType(mRequestedApnType)) {
+                if (p.getId() == pos && p.canHandleType(mRequestedApnType)) {
                     log("getPreferredApn: X found apnSetting" + p);
                     cursor.close();
                     return p;
@@ -3651,7 +3645,7 @@
                         cleanUpAllConnections(false, Phone.REASON_PS_RESTRICT_ENABLED);
                         mReregisterOnReconnectFailure = false;
                     }
-                    ApnContext apnContext = mApnContextsById.get(DctConstants.APN_DEFAULT_ID);
+                    ApnContext apnContext = mApnContextsByType.get(ApnSetting.TYPE_DEFAULT);
                     if (apnContext != null) {
                         apnContext.setReason(Phone.REASON_PS_RESTRICT_ENABLED);
                         trySetupData(apnContext);
@@ -3789,19 +3783,6 @@
                 onSetUserDataEnabled(enabled);
                 break;
             }
-            // TODO - remove
-            case DctConstants.CMD_SET_DEPENDENCY_MET: {
-                boolean met = (msg.arg1 == DctConstants.ENABLED) ? true : false;
-                if (DBG) log("CMD_SET_DEPENDENCY_MET met=" + met);
-                Bundle bundle = msg.getData();
-                if (bundle != null) {
-                    String apnType = (String)bundle.get(DctConstants.APN_TYPE_KEY);
-                    if (apnType != null) {
-                        onSetDependencyMet(apnType, met);
-                    }
-                }
-                break;
-            }
             case DctConstants.CMD_SET_POLICY_DATA_ENABLE: {
                 final boolean enabled = (msg.arg1 == DctConstants.ENABLED) ? true : false;
                 onSetPolicyDataEnabled(enabled);
@@ -3867,7 +3848,7 @@
             }
             case DctConstants.EVENT_PROVISIONING_APN_ALARM: {
                 if (DBG) log("EVENT_PROVISIONING_APN_ALARM");
-                ApnContext apnCtx = mApnContextsById.get(DctConstants.APN_DEFAULT_ID);
+                ApnContext apnCtx = mApnContextsByType.get(ApnSetting.TYPE_DEFAULT);
                 if (apnCtx.isProvisioningApn() && apnCtx.isConnectedOrConnecting()) {
                     if (mProvisioningApnAlarmTag == msg.arg1) {
                         if (DBG) log("EVENT_PROVISIONING_APN_ALARM: Disconnecting");
@@ -3939,6 +3920,7 @@
                 break;
             case DctConstants.EVENT_DATA_SERVICE_BINDING_CHANGED:
                 onDataServiceBindingChanged((Boolean) ((AsyncResult) msg.obj).result);
+                break;
             default:
                 Rlog.e("DcTracker", "Unhandled event=" + msg);
                 break;
@@ -4141,6 +4123,8 @@
         pw.println(" mUniqueIdGenerator=" + mUniqueIdGenerator);
         pw.println(" mDataRoamingLeakageLog= ");
         mDataRoamingLeakageLog.dump(fd, pw, args);
+        pw.println(" mApnSettingsInitializationLog= ");
+        mApnSettingsInitializationLog.dump(fd, pw, args);
         pw.flush();
         pw.println(" ***************************************");
         DcController dcc = mDcc;
@@ -4221,9 +4205,9 @@
         }
 
         if (TextUtils.equals(apnType, PhoneConstants.APN_TYPE_EMERGENCY)) {
-            apnContext = mApnContextsById.get(DctConstants.APN_EMERGENCY_ID);
+            apnContext = mApnContextsByType.get(ApnSetting.TYPE_EMERGENCY);
         } else if (TextUtils.equals(apnType, PhoneConstants.APN_TYPE_IMS)) {
-            apnContext = mApnContextsById.get(DctConstants.APN_IMS_ID);
+            apnContext = mApnContextsByType.get(ApnSetting.TYPE_IMS);
         } else {
             log("apnType is invalid, return null");
             return null;
@@ -4268,7 +4252,7 @@
         if (cursor != null) {
             if (cursor.getCount() > 0) {
                 if (cursor.moveToFirst()) {
-                    mEmergencyApn = makeApnSetting(cursor);
+                    mEmergencyApn = ApnSetting.makeApnSetting(cursor);
                 }
             }
             cursor.close();
@@ -4285,7 +4269,7 @@
             } else {
                 boolean hasEmergencyApn = false;
                 for (ApnSetting apn : mAllApnSettings) {
-                    if (ArrayUtils.contains(apn.types, PhoneConstants.APN_TYPE_EMERGENCY)) {
+                    if ((apn.getApnTypeBitmask() & ApnSetting.TYPE_EMERGENCY) > 0) {
                         hasEmergencyApn = true;
                         break;
                     }
@@ -4357,7 +4341,7 @@
             stopDataStallAlarm();
         }
 
-        mRequestedApnType = PhoneConstants.APN_TYPE_DEFAULT;
+        mRequestedApnType = ApnSetting.TYPE_DEFAULT;
 
         if (DBG) log("mDisconnectPendingCount = " + mDisconnectPendingCount);
         if (tearDown && mDisconnectPendingCount == 0) {
@@ -4436,7 +4420,7 @@
 
         TxRxSum preTxRxSum = new TxRxSum(mTxPkts, mRxPkts);
         TxRxSum curTxRxSum = new TxRxSum();
-        curTxRxSum.updateTxRxSum();
+        curTxRxSum.updateTotalTxRxSum();
         mTxPkts = curTxRxSum.txPkts;
         mRxPkts = curTxRxSum.rxPkts;
 
@@ -4600,7 +4584,7 @@
         long sent, received;
 
         TxRxSum preTxRxSum = new TxRxSum(mDataStallTxRxSum);
-        mDataStallTxRxSum.updateTxRxSum();
+        mDataStallTxRxSum.updateTcpTxRxSum();
 
         if (VDBG_STALL) {
             log("updateDataStallInfo: mDataStallTxRxSum=" + mDataStallTxRxSum +
@@ -4792,7 +4776,7 @@
     }
 
     private static DataProfile createDataProfile(ApnSetting apn) {
-        return createDataProfile(apn, apn.profileId);
+        return createDataProfile(apn, apn.getProfileId());
     }
 
     @VisibleForTesting
@@ -4801,7 +4785,7 @@
 
         int bearerBitmap = 0;
         bearerBitmap = ServiceState.convertNetworkTypeBitmaskToBearerBitmask(
-                apn.networkTypeBitmask);
+                apn.getNetworkTypeBitmask());
 
         if (bearerBitmap == 0) {
             profileType = DataProfile.TYPE_COMMON;
@@ -4811,11 +4795,13 @@
             profileType = DataProfile.TYPE_3GPP;
         }
 
-        return new DataProfile(profileId, apn.apn, apn.protocol,
-                apn.authType, apn.user, apn.password, profileType,
-                apn.maxConnsTime, apn.maxConns, apn.waitTime, apn.carrierEnabled, apn.typesBitmap,
-                apn.roamingProtocol, bearerBitmap, apn.mtu, apn.mvnoType, apn.mvnoMatchData,
-                apn.modemCognitive);
+        return new DataProfile(profileId, apn.getApnName(),
+            ApnSetting.getProtocolStringFromInt(apn.getProtocol()), apn.getAuthType(),
+            apn.getUser(), apn.getPassword(), profileType, apn.getMaxConnsTime(),
+            apn.getMaxConns(), apn.getWaitTime(), apn.isEnabled(), apn.getApnTypeBitmask(),
+            ApnSetting.getProtocolStringFromInt(apn.getRoamingProtocol()), bearerBitmap,
+            apn.getMtu(), ApnSetting.getMvnoTypeStringFromInt(apn.getMvnoType()),
+            apn.getMvnoMatchData(), apn.getModemCognitive());
     }
 
     private void onDataServiceBindingChanged(boolean bound) {
diff --git a/src/java/com/android/internal/telephony/euicc/EuiccCardController.java b/src/java/com/android/internal/telephony/euicc/EuiccCardController.java
index 2326091..d3c59a3 100644
--- a/src/java/com/android/internal/telephony/euicc/EuiccCardController.java
+++ b/src/java/com/android/internal/telephony/euicc/EuiccCardController.java
@@ -39,6 +39,7 @@
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.SubscriptionController;
 import com.android.internal.telephony.uicc.UiccController;
 import com.android.internal.telephony.uicc.UiccSlot;
 import com.android.internal.telephony.uicc.euicc.EuiccCard;
@@ -179,7 +180,7 @@
                 return (EuiccCard) controller.getUiccCardForSlot(slotId);
             }
         }
-        Log.e(TAG, "EuiccCard is null. CardId : " + cardId);
+        loge("EuiccCard is null. CardId : " + cardId);
         return null;
     }
 
@@ -200,7 +201,7 @@
             try {
                 callback.onComplete(EuiccCardManager.RESULT_EUICC_NOT_FOUND, null);
             } catch (RemoteException exception) {
-                throw exception.rethrowFromSystemServer();
+                loge("getAllProfiles callback failure.", exception);
             }
             return;
         }
@@ -212,7 +213,7 @@
                 try {
                     callback.onComplete(EuiccCardManager.RESULT_OK, result);
                 } catch (RemoteException exception) {
-                    throw exception.rethrowFromSystemServer();
+                    loge("getAllProfiles callback failure.", exception);
                 }
             }
 
@@ -221,7 +222,7 @@
                 try {
                     callback.onComplete(getResultCode(e), null);
                 } catch (RemoteException exception) {
-                    throw exception.rethrowFromSystemServer();
+                    loge("getAllProfiles callback failure.", exception);
                 }
             }
         };
@@ -239,7 +240,7 @@
             try {
                 callback.onComplete(EuiccCardManager.RESULT_EUICC_NOT_FOUND, null);
             } catch (RemoteException exception) {
-                throw exception.rethrowFromSystemServer();
+                loge("getProfile callback failure.", exception);
             }
             return;
         }
@@ -250,7 +251,7 @@
                         try {
                             callback.onComplete(EuiccCardManager.RESULT_OK, result);
                         } catch (RemoteException exception) {
-                            throw exception.rethrowFromSystemServer();
+                            loge("getProfile callback failure.", exception);
                         }
                     }
 
@@ -259,7 +260,7 @@
                         try {
                             callback.onComplete(getResultCode(e), null);
                         } catch (RemoteException exception) {
-                            throw exception.rethrowFromSystemServer();
+                            loge("getProfile callback failure.", exception);
                         }
                     }
                 };
@@ -277,7 +278,7 @@
             try {
                 callback.onComplete(EuiccCardManager.RESULT_EUICC_NOT_FOUND);
             } catch (RemoteException exception) {
-                throw exception.rethrowFromSystemServer();
+                loge("disableProfile callback failure.", exception);
             }
             return;
         }
@@ -288,7 +289,7 @@
                 try {
                     callback.onComplete(EuiccCardManager.RESULT_OK);
                 } catch (RemoteException exception) {
-                    throw exception.rethrowFromSystemServer();
+                    loge("disableProfile callback failure.", exception);
                 }
             }
 
@@ -297,7 +298,7 @@
                 try {
                     callback.onComplete(getResultCode(e));
                 } catch (RemoteException exception) {
-                    throw exception.rethrowFromSystemServer();
+                    loge("disableProfile callback failure.", exception);
                 }
             }
         };
@@ -315,7 +316,7 @@
             try {
                 callback.onComplete(EuiccCardManager.RESULT_EUICC_NOT_FOUND, null);
             } catch (RemoteException exception) {
-                throw exception.rethrowFromSystemServer();
+                loge("switchToProfile callback failure.", exception);
             }
             return;
         }
@@ -330,7 +331,7 @@
                         try {
                             callback.onComplete(EuiccCardManager.RESULT_OK, profile);
                         } catch (RemoteException exception) {
-                            throw exception.rethrowFromSystemServer();
+                            loge("switchToProfile callback failure.", exception);
                         }
                     }
 
@@ -339,7 +340,7 @@
                         try {
                             callback.onComplete(getResultCode(e), profile);
                         } catch (RemoteException exception) {
-                            throw exception.rethrowFromSystemServer();
+                            loge("switchToProfile callback failure.", exception);
                         }
                     }
                 };
@@ -352,7 +353,7 @@
                 try {
                     callback.onComplete(getResultCode(e), null);
                 } catch (RemoteException exception) {
-                    throw exception.rethrowFromSystemServer();
+                    loge("switchToProfile callback failure.", exception);
                 }
             }
         };
@@ -370,7 +371,7 @@
             try {
                 callback.onComplete(EuiccCardManager.RESULT_EUICC_NOT_FOUND);
             } catch (RemoteException exception) {
-                throw exception.rethrowFromSystemServer();
+                loge("setNickname callback failure.", exception);
             }
             return;
         }
@@ -381,7 +382,7 @@
                 try {
                     callback.onComplete(EuiccCardManager.RESULT_OK);
                 } catch (RemoteException exception) {
-                    throw exception.rethrowFromSystemServer();
+                    loge("setNickname callback failure.", exception);
                 }
             }
 
@@ -390,7 +391,7 @@
                 try {
                     callback.onComplete(getResultCode(e));
                 } catch (RemoteException exception) {
-                    throw exception.rethrowFromSystemServer();
+                    loge("setNickname callback failure.", exception);
                 }
             }
         };
@@ -408,7 +409,7 @@
             try {
                 callback.onComplete(EuiccCardManager.RESULT_EUICC_NOT_FOUND);
             } catch (RemoteException exception) {
-                throw exception.rethrowFromSystemServer();
+                loge("deleteProfile callback failure.", exception);
             }
             return;
         }
@@ -416,19 +417,22 @@
         AsyncResultCallback<Void> cardCb = new AsyncResultCallback<Void>() {
             @Override
             public void onResult(Void result) {
-                try {
-                    callback.onComplete(EuiccCardManager.RESULT_OK);
-                } catch (RemoteException exception) {
-                    throw exception.rethrowFromSystemServer();
-                }
-            }
+                SubscriptionController.getInstance().requestEmbeddedSubscriptionInfoListRefresh(
+                        () -> {
+                            try {
+                                callback.onComplete(EuiccCardManager.RESULT_OK);
+                            } catch (RemoteException exception) {
+                                loge("deleteProfile callback failure.", exception);
+                            }
+                        });
+            };
 
             @Override
             public void onException(Throwable e) {
                 try {
                     callback.onComplete(getResultCode(e));
                 } catch (RemoteException exception) {
-                    throw exception.rethrowFromSystemServer();
+                    loge("deleteProfile callback failure.", exception);
                 }
             }
         };
@@ -446,7 +450,7 @@
             try {
                 callback.onComplete(EuiccCardManager.RESULT_EUICC_NOT_FOUND);
             } catch (RemoteException exception) {
-                throw exception.rethrowFromSystemServer();
+                loge("resetMemory callback failure.", exception);
             }
             return;
         }
@@ -454,11 +458,14 @@
         AsyncResultCallback<Void> cardCb = new AsyncResultCallback<Void>() {
             @Override
             public void onResult(Void result) {
-                try {
-                    callback.onComplete(EuiccCardManager.RESULT_OK);
-                } catch (RemoteException exception) {
-                    throw exception.rethrowFromSystemServer();
-                }
+                SubscriptionController.getInstance().requestEmbeddedSubscriptionInfoListRefresh(
+                        () -> {
+                            try {
+                                callback.onComplete(EuiccCardManager.RESULT_OK);
+                            } catch (RemoteException exception) {
+                                loge("resetMemory callback failure.", exception);
+                            }
+                        });
             }
 
             @Override
@@ -466,7 +473,7 @@
                 try {
                     callback.onComplete(getResultCode(e));
                 } catch (RemoteException exception) {
-                    throw exception.rethrowFromSystemServer();
+                    loge("resetMemory callback failure.", exception);
                 }
             }
         };
@@ -484,7 +491,7 @@
             try {
                 callback.onComplete(EuiccCardManager.RESULT_EUICC_NOT_FOUND, null);
             } catch (RemoteException exception) {
-                throw exception.rethrowFromSystemServer();
+                loge("getDefaultSmdpAddress callback failure.", exception);
             }
             return;
         }
@@ -495,7 +502,7 @@
                 try {
                     callback.onComplete(EuiccCardManager.RESULT_OK, result);
                 } catch (RemoteException exception) {
-                    throw exception.rethrowFromSystemServer();
+                    loge("getDefaultSmdpAddress callback failure.", exception);
                 }
             }
 
@@ -504,7 +511,7 @@
                 try {
                     callback.onComplete(getResultCode(e), null);
                 } catch (RemoteException exception) {
-                    throw exception.rethrowFromSystemServer();
+                    loge("getDefaultSmdpAddress callback failure.", exception);
                 }
             }
         };
@@ -522,7 +529,7 @@
             try {
                 callback.onComplete(EuiccCardManager.RESULT_EUICC_NOT_FOUND, null);
             } catch (RemoteException exception) {
-                throw exception.rethrowFromSystemServer();
+                loge("getSmdsAddress callback failure.", exception);
             }
             return;
         }
@@ -533,7 +540,7 @@
                 try {
                     callback.onComplete(EuiccCardManager.RESULT_OK, result);
                 } catch (RemoteException exception) {
-                    throw exception.rethrowFromSystemServer();
+                    loge("getSmdsAddress callback failure.", exception);
                 }
             }
 
@@ -542,7 +549,7 @@
                 try {
                     callback.onComplete(getResultCode(e), null);
                 } catch (RemoteException exception) {
-                    throw exception.rethrowFromSystemServer();
+                    loge("getSmdsAddress callback failure.", exception);
                 }
             }
         };
@@ -560,7 +567,7 @@
             try {
                 callback.onComplete(EuiccCardManager.RESULT_EUICC_NOT_FOUND);
             } catch (RemoteException exception) {
-                throw exception.rethrowFromSystemServer();
+                loge("setDefaultSmdpAddress callback failure.", exception);
             }
             return;
         }
@@ -571,7 +578,7 @@
                 try {
                     callback.onComplete(EuiccCardManager.RESULT_OK);
                 } catch (RemoteException exception) {
-                    throw exception.rethrowFromSystemServer();
+                    loge("setDefaultSmdpAddress callback failure.", exception);
                 }
             }
 
@@ -580,7 +587,7 @@
                 try {
                     callback.onComplete(getResultCode(e));
                 } catch (RemoteException exception) {
-                    throw exception.rethrowFromSystemServer();
+                    loge("setDefaultSmdpAddress callback failure.", exception);
                 }
             }
         };
@@ -598,7 +605,7 @@
             try {
                 callback.onComplete(EuiccCardManager.RESULT_EUICC_NOT_FOUND, null);
             } catch (RemoteException exception) {
-                throw exception.rethrowFromSystemServer();
+                loge("getRulesAuthTable callback failure.", exception);
             }
             return;
         }
@@ -610,7 +617,7 @@
                 try {
                     callback.onComplete(EuiccCardManager.RESULT_OK, result);
                 } catch (RemoteException exception) {
-                    throw exception.rethrowFromSystemServer();
+                    loge("getRulesAuthTable callback failure.", exception);
                 }
             }
 
@@ -619,7 +626,7 @@
                 try {
                     callback.onComplete(getResultCode(e), null);
                 } catch (RemoteException exception) {
-                    throw exception.rethrowFromSystemServer();
+                    loge("getRulesAuthTable callback failure.", exception);
                 }
             }
         };
@@ -637,7 +644,7 @@
             try {
                 callback.onComplete(EuiccCardManager.RESULT_EUICC_NOT_FOUND, null);
             } catch (RemoteException exception) {
-                throw exception.rethrowFromSystemServer();
+                loge("getEuiccChallenge callback failure.", exception);
             }
             return;
         }
@@ -648,7 +655,7 @@
                 try {
                     callback.onComplete(EuiccCardManager.RESULT_OK, result);
                 } catch (RemoteException exception) {
-                    throw exception.rethrowFromSystemServer();
+                    loge("getEuiccChallenge callback failure.", exception);
                 }
             }
 
@@ -657,7 +664,7 @@
                 try {
                     callback.onComplete(getResultCode(e), null);
                 } catch (RemoteException exception) {
-                    throw exception.rethrowFromSystemServer();
+                    loge("getEuiccChallenge callback failure.", exception);
                 }
             }
         };
@@ -675,7 +682,7 @@
             try {
                 callback.onComplete(EuiccCardManager.RESULT_EUICC_NOT_FOUND, null);
             } catch (RemoteException exception) {
-                throw exception.rethrowFromSystemServer();
+                loge("getEuiccInfo1 callback failure.", exception);
             }
             return;
         }
@@ -686,7 +693,7 @@
                 try {
                     callback.onComplete(EuiccCardManager.RESULT_OK, result);
                 } catch (RemoteException exception) {
-                    throw exception.rethrowFromSystemServer();
+                    loge("getEuiccInfo1 callback failure.", exception);
                 }
             }
 
@@ -695,7 +702,7 @@
                 try {
                     callback.onComplete(getResultCode(e), null);
                 } catch (RemoteException exception) {
-                    throw exception.rethrowFromSystemServer();
+                    loge("getEuiccInfo1 callback failure.", exception);
                 }
             }
         };
@@ -713,7 +720,7 @@
             try {
                 callback.onComplete(EuiccCardManager.RESULT_EUICC_NOT_FOUND, null);
             } catch (RemoteException exception) {
-                throw exception.rethrowFromSystemServer();
+                loge("getEuiccInfo2 callback failure.", exception);
             }
             return;
         }
@@ -724,7 +731,7 @@
                 try {
                     callback.onComplete(EuiccCardManager.RESULT_OK, result);
                 } catch (RemoteException exception) {
-                    throw exception.rethrowFromSystemServer();
+                    loge("getEuiccInfo2 callback failure.", exception);
                 }
             }
 
@@ -733,7 +740,7 @@
                 try {
                     callback.onComplete(getResultCode(e), null);
                 } catch (RemoteException exception) {
-                    throw exception.rethrowFromSystemServer();
+                    loge("getEuiccInfo2 callback failure.", exception);
                 }
             }
         };
@@ -752,7 +759,7 @@
             try {
                 callback.onComplete(EuiccCardManager.RESULT_EUICC_NOT_FOUND, null);
             } catch (RemoteException exception) {
-                throw exception.rethrowFromSystemServer();
+                loge("authenticateServer callback failure.", exception);
             }
             return;
         }
@@ -763,7 +770,7 @@
                 try {
                     callback.onComplete(EuiccCardManager.RESULT_OK, result);
                 } catch (RemoteException exception) {
-                    throw exception.rethrowFromSystemServer();
+                    loge("authenticateServer callback failure.", exception);
                 }
             }
 
@@ -772,7 +779,7 @@
                 try {
                     callback.onComplete(getResultCode(e), null);
                 } catch (RemoteException exception) {
-                    throw exception.rethrowFromSystemServer();
+                    loge("authenticateServer callback failure.", exception);
                 }
             }
         };
@@ -792,7 +799,7 @@
             try {
                 callback.onComplete(EuiccCardManager.RESULT_EUICC_NOT_FOUND, null);
             } catch (RemoteException exception) {
-                throw exception.rethrowFromSystemServer();
+                loge("prepareDownload callback failure.", exception);
             }
             return;
         }
@@ -803,7 +810,7 @@
                 try {
                     callback.onComplete(EuiccCardManager.RESULT_OK, result);
                 } catch (RemoteException exception) {
-                    throw exception.rethrowFromSystemServer();
+                    loge("prepareDownload callback failure.", exception);
                 }
             }
 
@@ -812,7 +819,7 @@
                 try {
                     callback.onComplete(getResultCode(e), null);
                 } catch (RemoteException exception) {
-                    throw exception.rethrowFromSystemServer();
+                    loge("prepareDownload callback failure.", exception);
                 }
             }
         };
@@ -831,7 +838,7 @@
             try {
                 callback.onComplete(EuiccCardManager.RESULT_EUICC_NOT_FOUND, null);
             } catch (RemoteException exception) {
-                throw exception.rethrowFromSystemServer();
+                loge("loadBoundProfilePackage callback failure.", exception);
             }
             return;
         }
@@ -839,11 +846,14 @@
         AsyncResultCallback<byte[]> cardCb = new AsyncResultCallback<byte[]>() {
             @Override
             public void onResult(byte[] result) {
-                try {
-                    callback.onComplete(EuiccCardManager.RESULT_OK, result);
-                } catch (RemoteException exception) {
-                    throw exception.rethrowFromSystemServer();
-                }
+                SubscriptionController.getInstance().requestEmbeddedSubscriptionInfoListRefresh(
+                        () -> {
+                            try {
+                                callback.onComplete(EuiccCardManager.RESULT_OK, result);
+                            } catch (RemoteException exception) {
+                                loge("loadBoundProfilePackage callback failure.", exception);
+                            }
+                        });
             }
 
             @Override
@@ -851,7 +861,7 @@
                 try {
                     callback.onComplete(getResultCode(e), null);
                 } catch (RemoteException exception) {
-                    throw exception.rethrowFromSystemServer();
+                    loge("loadBoundProfilePackage callback failure.", exception);
                 }
             }
         };
@@ -869,7 +879,7 @@
             try {
                 callback.onComplete(EuiccCardManager.RESULT_EUICC_NOT_FOUND, null);
             } catch (RemoteException exception) {
-                throw exception.rethrowFromSystemServer();
+                loge("cancelSession callback failure.", exception);
             }
             return;
         }
@@ -880,7 +890,7 @@
                 try {
                     callback.onComplete(EuiccCardManager.RESULT_OK, result);
                 } catch (RemoteException exception) {
-                    throw exception.rethrowFromSystemServer();
+                    loge("cancelSession callback failure.", exception);
                 }
             }
 
@@ -889,7 +899,7 @@
                 try {
                     callback.onComplete(getResultCode(e), null);
                 } catch (RemoteException exception) {
-                    throw exception.rethrowFromSystemServer();
+                    loge("cancelSession callback failure.", exception);
                 }
             }
         };
@@ -907,7 +917,7 @@
             try {
                 callback.onComplete(EuiccCardManager.RESULT_EUICC_NOT_FOUND, null);
             } catch (RemoteException exception) {
-                throw exception.rethrowFromSystemServer();
+                loge("listNotifications callback failure.", exception);
             }
             return;
         }
@@ -919,7 +929,7 @@
                 try {
                     callback.onComplete(EuiccCardManager.RESULT_OK, result);
                 } catch (RemoteException exception) {
-                    throw exception.rethrowFromSystemServer();
+                    loge("listNotifications callback failure.", exception);
                 }
             }
 
@@ -928,7 +938,7 @@
                 try {
                     callback.onComplete(getResultCode(e), null);
                 } catch (RemoteException exception) {
-                    throw exception.rethrowFromSystemServer();
+                    loge("listNotifications callback failure.", exception);
                 }
             }
         };
@@ -946,7 +956,7 @@
             try {
                 callback.onComplete(EuiccCardManager.RESULT_EUICC_NOT_FOUND, null);
             } catch (RemoteException exception) {
-                throw exception.rethrowFromSystemServer();
+                loge("retrieveNotificationList callback failure.", exception);
             }
             return;
         }
@@ -958,7 +968,7 @@
                         try {
                             callback.onComplete(EuiccCardManager.RESULT_OK, result);
                         } catch (RemoteException exception) {
-                            throw exception.rethrowFromSystemServer();
+                            loge("retrieveNotificationList callback failure.", exception);
                         }
                     }
 
@@ -967,7 +977,7 @@
                         try {
                             callback.onComplete(getResultCode(e), null);
                         } catch (RemoteException exception) {
-                            throw exception.rethrowFromSystemServer();
+                            loge("retrieveNotificationList callback failure.", exception);
                         }
                     }
                 };
@@ -985,7 +995,7 @@
             try {
                 callback.onComplete(EuiccCardManager.RESULT_EUICC_NOT_FOUND, null);
             } catch (RemoteException exception) {
-                throw exception.rethrowFromSystemServer();
+                loge("retrieveNotification callback failure.", exception);
             }
             return;
         }
@@ -997,7 +1007,7 @@
                         try {
                             callback.onComplete(EuiccCardManager.RESULT_OK, result);
                         } catch (RemoteException exception) {
-                            throw exception.rethrowFromSystemServer();
+                            loge("retrieveNotification callback failure.", exception);
                         }
                     }
 
@@ -1006,7 +1016,7 @@
                         try {
                             callback.onComplete(getResultCode(e), null);
                         } catch (RemoteException exception) {
-                            throw exception.rethrowFromSystemServer();
+                            loge("retrieveNotification callback failure.", exception);
                         }
                     }
                 };
@@ -1024,7 +1034,7 @@
             try {
                 callback.onComplete(EuiccCardManager.RESULT_EUICC_NOT_FOUND);
             } catch (RemoteException exception) {
-                throw exception.rethrowFromSystemServer();
+                loge("removeNotificationFromList callback failure.", exception);
             }
             return;
         }
@@ -1035,7 +1045,7 @@
                         try {
                             callback.onComplete(EuiccCardManager.RESULT_OK);
                         } catch (RemoteException exception) {
-                            throw exception.rethrowFromSystemServer();
+                            loge("removeNotificationFromList callback failure.", exception);
                         }
 
                     }
@@ -1045,7 +1055,7 @@
                         try {
                             callback.onComplete(getResultCode(e));
                         } catch (RemoteException exception) {
-                            throw exception.rethrowFromSystemServer();
+                            loge("removeNotificationFromList callback failure.", exception);
                         }
                     }
                 };
@@ -1065,4 +1075,12 @@
 
         Binder.restoreCallingIdentity(token);
     }
+
+    private static void loge(String message) {
+        Log.e(TAG, message);
+    }
+
+    private static void loge(String message, Throwable tr) {
+        Log.e(TAG, message, tr);
+    }
 }
diff --git a/src/java/com/android/internal/telephony/euicc/EuiccController.java b/src/java/com/android/internal/telephony/euicc/EuiccController.java
index 27018b5..8c1b81e 100644
--- a/src/java/com/android/internal/telephony/euicc/EuiccController.java
+++ b/src/java/com/android/internal/telephony/euicc/EuiccController.java
@@ -69,6 +69,8 @@
             EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR;
     private static final int ERROR =
             EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_ERROR;
+    private static final String EXTRA_EMBEDDED_SUBSCRIPTION_DOWNLOADABLE_SUBSCRIPTION =
+            EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DOWNLOADABLE_SUBSCRIPTION;
 
     private static EuiccController sInstance;
 
@@ -263,7 +265,7 @@
                 case EuiccService.RESULT_OK:
                     resultCode = OK;
                     extrasIntent.putExtra(
-                            EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DOWNLOADABLE_SUBSCRIPTION,
+                            EXTRA_EMBEDDED_SUBSCRIPTION_DOWNLOADABLE_SUBSCRIPTION,
                             result.getDownloadableSubscription());
                     break;
                 case EuiccService.RESULT_MUST_DEACTIVATE_SIM:
@@ -446,6 +448,9 @@
                                         mContext.getContentResolver(),
                                         Settings.Global.EUICC_PROVISIONED,
                                         1);
+                                extrasIntent.putExtra(
+                                        EXTRA_EMBEDDED_SUBSCRIPTION_DOWNLOADABLE_SUBSCRIPTION,
+                                        subscription);
                                 if (!switchAfterDownload) {
                                     // Since we're not switching, nothing will trigger a
                                     // subscription list refresh on its own, so request one here.
diff --git a/src/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java b/src/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
index 8c4e9aa..99082ee 100644
--- a/src/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
+++ b/src/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
@@ -16,13 +16,8 @@
 
 package com.android.internal.telephony.gsm;
 
-import android.app.Activity;
-import android.app.PendingIntent;
-import android.app.PendingIntent.CanceledException;
-import android.content.Intent;
 import android.os.AsyncResult;
 import android.os.Message;
-import android.provider.Telephony.Sms;
 import android.provider.Telephony.Sms.Intents;
 import android.telephony.Rlog;
 import android.telephony.ServiceState;
@@ -106,8 +101,9 @@
     }
 
     @Override
-    protected boolean shouldBlockSms() {
-        return SMSDispatcherUtil.shouldBlockSms(isCdmaMo(), mPhone);
+    protected boolean shouldBlockSmsForEcbm() {
+        // There is no such thing as ECBM for GSM. This only applies to CDMA.
+        return false;
     }
 
     @Override
@@ -187,6 +183,7 @@
                 + " mRetryCount=" + tracker.mRetryCount
                 + " mImsRetry=" + tracker.mImsRetry
                 + " mMessageRef=" + tracker.mMessageRef
+                + " mUsesImsServiceForIms=" + tracker.mUsesImsServiceForIms
                 + " SS=" + mPhone.getServiceState().getState());
 
         int ss = mPhone.getServiceState().getState();
@@ -202,8 +199,11 @@
         // sms over gsm is used:
         //   if sms over IMS is not supported AND
         //   this is not a retry case after sms over IMS failed
-        //     indicated by mImsRetry > 0
-        if (0 == tracker.mImsRetry && !isIms()) {
+        //     indicated by mImsRetry > 0 OR
+        //   this tracker uses ImsSmsDispatcher to handle SMS over IMS. This dispatcher has received
+        //     this message because the ImsSmsDispatcher has indicated that the message needs to
+        //     fall back to sending over CS.
+        if (0 == tracker.mImsRetry && !isIms() || tracker.mUsesImsServiceForIms) {
             if (tracker.mRetryCount == 0 && tracker.mExpectMore) {
                 mCi.sendSMSExpectMore(IccUtils.bytesToHexString(smsc),
                         IccUtils.bytesToHexString(pdu), reply);
diff --git a/src/java/com/android/internal/telephony/ims/ImsResolver.java b/src/java/com/android/internal/telephony/ims/ImsResolver.java
index 081d41c..f0aee01 100644
--- a/src/java/com/android/internal/telephony/ims/ImsResolver.java
+++ b/src/java/com/android/internal/telephony/ims/ImsResolver.java
@@ -28,6 +28,8 @@
 import android.content.pm.ServiceInfo;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.Message;
+import android.os.PersistableBundle;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.telephony.CarrierConfigManager;
@@ -38,21 +40,21 @@
 import android.telephony.ims.aidl.IImsRcsFeature;
 import android.telephony.ims.aidl.IImsRegistration;
 import android.telephony.ims.feature.ImsFeature;
+import android.telephony.ims.stub.ImsFeatureConfiguration;
 import android.text.TextUtils;
-import android.util.ArraySet;
 import android.util.Log;
-import android.util.Pair;
 import android.util.SparseArray;
 
 import com.android.ims.internal.IImsServiceFeatureCallback;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.os.SomeArgs;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
-import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
@@ -78,6 +80,10 @@
             "android.telephony.ims.EMERGENCY_MMTEL_FEATURE";
     public static final String METADATA_MMTEL_FEATURE = "android.telephony.ims.MMTEL_FEATURE";
     public static final String METADATA_RCS_FEATURE = "android.telephony.ims.RCS_FEATURE";
+    // Overrides the sanity permission check of android.permission.BIND_IMS_SERVICE for any
+    // ImsService that is connecting to the platform.
+    // This should ONLY be used for testing and should not be used in production ImsServices.
+    private static final String METADATA_OVERRIDE_PERM_CHECK = "override_bind_check";
 
     // Based on updates from PackageManager
     private static final int HANDLER_ADD_PACKAGE = 0;
@@ -85,6 +91,16 @@
     private static final int HANDLER_REMOVE_PACKAGE = 1;
     // Based on updates from CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED
     private static final int HANDLER_CONFIG_CHANGED = 2;
+    // A query has been started for an ImsService to relay the features they support.
+    private static final int HANDLER_START_DYNAMIC_FEATURE_QUERY = 3;
+    // A query to request ImsService features has completed or the ImsService has updated features.
+    private static final int HANDLER_DYNAMIC_FEATURE_CHANGE = 4;
+    // Testing: Overrides the current configuration for ImsService binding
+    private static final int HANDLER_OVERRIDE_IMS_SERVICE_CONFIG = 5;
+
+    // Delay between dynamic ImsService queries.
+    private static final int DELAY_DYNAMIC_QUERY_MS = 5000;
+
 
     /**
      * Stores information about an ImsService, including the package name, class name, and features
@@ -93,10 +109,36 @@
     @VisibleForTesting
     public static class ImsServiceInfo {
         public ComponentName name;
-        public Set<Integer> supportedFeatures;
-        public boolean supportsEmergencyMmTel = false;
+        // Determines if features were created from metadata in the manifest or through dynamic
+        // query.
+        public boolean featureFromMetadata = true;
         public ImsServiceControllerFactory controllerFactory;
 
+        // Map slotId->Feature
+        private final HashSet<ImsFeatureConfiguration.FeatureSlotPair> mSupportedFeatures;
+        private final int mNumSlots;
+
+        public ImsServiceInfo(int numSlots) {
+            mNumSlots = numSlots;
+            mSupportedFeatures = new HashSet<>();
+        }
+
+        void addFeatureForAllSlots(int feature) {
+            for (int i = 0; i < mNumSlots; i++) {
+                mSupportedFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(i, feature));
+            }
+        }
+
+        void replaceFeatures(Set<ImsFeatureConfiguration.FeatureSlotPair> newFeatures) {
+            mSupportedFeatures.clear();
+            mSupportedFeatures.addAll(newFeatures);
+        }
+
+        @VisibleForTesting
+        public HashSet<ImsFeatureConfiguration.FeatureSlotPair> getSupportedFeatures() {
+            return mSupportedFeatures;
+        }
+
         @Override
         public boolean equals(Object o) {
             if (this == o) return true;
@@ -104,10 +146,8 @@
 
             ImsServiceInfo that = (ImsServiceInfo) o;
 
-            if (supportsEmergencyMmTel != that.supportsEmergencyMmTel) return false;
             if (name != null ? !name.equals(that.name) : that.name != null) return false;
-            if (supportedFeatures != null ? !supportedFeatures.equals(that.supportedFeatures)
-                    : that.supportedFeatures != null) {
+            if (!mSupportedFeatures.equals(that.mSupportedFeatures)) {
                 return false;
             }
             return controllerFactory != null ? controllerFactory.equals(that.controllerFactory)
@@ -116,12 +156,28 @@
 
         @Override
         public int hashCode() {
+            // We do not include mSupportedFeatures in hashcode because the internal structure
+            // changes after adding.
             int result = name != null ? name.hashCode() : 0;
-            result = 31 * result + (supportedFeatures != null ? supportedFeatures.hashCode() : 0);
-            result = 31 * result + (supportsEmergencyMmTel ? 1 : 0);
             result = 31 * result + (controllerFactory != null ? controllerFactory.hashCode() : 0);
             return result;
         }
+
+        @Override
+        public String toString() {
+            StringBuilder res = new StringBuilder();
+            res.append("[ImsServiceInfo] name=");
+            res.append(name);
+            res.append(", supportedFeatures=[ ");
+            for (ImsFeatureConfiguration.FeatureSlotPair feature : mSupportedFeatures) {
+                res.append("(");
+                res.append(feature.slotId);
+                res.append(",");
+                res.append(feature.featureType);
+                res.append(") ");
+            }
+            return res.toString();
+        }
     }
 
     // Receives broadcasts from the system involving changes to the installed applications. If
@@ -134,6 +190,8 @@
             switch (action) {
                 case Intent.ACTION_PACKAGE_ADDED:
                     // intentional fall-through
+                case Intent.ACTION_PACKAGE_REPLACED:
+                    // intentional fall-through
                 case Intent.ACTION_PACKAGE_CHANGED:
                     mHandler.obtainMessage(HANDLER_ADD_PACKAGE, packageName).sendToTarget();
                     break;
@@ -152,17 +210,17 @@
         @Override
         public void onReceive(Context context, Intent intent) {
 
-            int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
-                    SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+            int slotId = intent.getIntExtra(CarrierConfigManager.EXTRA_SLOT_INDEX,
+                    SubscriptionManager.INVALID_SIM_SLOT_INDEX);
 
-            if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
-                Log.i(TAG, "Received SIM change for invalid sub id.");
+            if (slotId == SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
+                Log.i(TAG, "Received SIM change for invalid slot id.");
                 return;
             }
 
-            Log.i(TAG, "Received Carrier Config Changed for SubId: " + subId);
+            Log.i(TAG, "Received Carrier Config Changed for SlotId: " + slotId);
 
-            mHandler.obtainMessage(HANDLER_CONFIG_CHANGED, subId).sendToTarget();
+            mHandler.obtainMessage(HANDLER_CONFIG_CHANGED, slotId).sendToTarget();
         }
     };
 
@@ -229,6 +287,15 @@
         }
     };
 
+    /**
+     * Used for testing.
+     */
+    @VisibleForTesting
+    public interface ImsDynamicQueryManagerFactory {
+        ImsServiceFeatureQueryManager create(Context context,
+                ImsServiceFeatureQueryManager.Listener listener);
+    }
+
     private ImsServiceControllerFactory mImsServiceControllerFactoryCompat =
             new ImsServiceControllerFactory() {
                 @Override
@@ -258,6 +325,9 @@
                 }
             };
 
+    private ImsDynamicQueryManagerFactory mDynamicQueryManagerFactory =
+            ImsServiceFeatureQueryManager::new;
+
     private final CarrierConfigManager mCarrierConfigManager;
     private final Context mContext;
     // Locks mBoundImsServicesByFeature only. Be careful to avoid deadlocks from
@@ -265,6 +335,8 @@
     private final Object mBoundServicesLock = new Object();
     private final int mNumSlots;
     private final boolean mIsDynamicBinding;
+    // Package name of the default device service.
+    private String mDeviceService;
 
     // Synchronize all messages on a handler to ensure that the cache includes the most recent
     // version of the installed ImsServices.
@@ -281,8 +353,52 @@
                 break;
             }
             case HANDLER_CONFIG_CHANGED: {
-                int subId = (Integer) msg.obj;
-                maybeRebindService(subId);
+                int slotId = (Integer) msg.obj;
+                carrierConfigChanged(slotId);
+                break;
+            }
+            case HANDLER_START_DYNAMIC_FEATURE_QUERY: {
+                ImsServiceInfo info = (ImsServiceInfo) msg.obj;
+                startDynamicQuery(info);
+                break;
+            }
+            case HANDLER_DYNAMIC_FEATURE_CHANGE: {
+                SomeArgs args = (SomeArgs) msg.obj;
+                ComponentName name = (ComponentName) args.arg1;
+                Set<ImsFeatureConfiguration.FeatureSlotPair> features =
+                        (Set<ImsFeatureConfiguration.FeatureSlotPair>) args.arg2;
+                args.recycle();
+                dynamicQueryComplete(name, features);
+                break;
+            }
+            case HANDLER_OVERRIDE_IMS_SERVICE_CONFIG: {
+                int slotId = msg.arg1;
+                // arg2 will be equal to 1 if it is a carrier service.
+                boolean isCarrierImsService = (msg.arg2 == 1);
+                String packageName = (String) msg.obj;
+                if (isCarrierImsService) {
+                    Log.i(TAG, "overriding carrier ImsService - slot=" + slotId + " packageName="
+                            + packageName);
+                    maybeRebindService(slotId, packageName);
+                } else {
+                    Log.i(TAG, "overriding device ImsService -  packageName=" + packageName);
+                    if (packageName == null || packageName.isEmpty()) {
+                        unbindImsService(getImsServiceInfoFromCache(mDeviceService));
+                    }
+                    mDeviceService = packageName;
+                    ImsServiceInfo deviceInfo = getImsServiceInfoFromCache(mDeviceService);
+                    if (deviceInfo == null) {
+                        // The package name is either "" or does not exist on the device.
+                        break;
+                    }
+                    if (deviceInfo.featureFromMetadata) {
+                        bindImsService(deviceInfo);
+                    } else {
+                        // newly added ImsServiceInfo that has not had features queried yet. Start
+                        // async bind and query features.
+                        scheduleQueryForFeatures(deviceInfo);
+                    }
+                }
                 break;
             }
             default:
@@ -291,19 +407,37 @@
         return true;
     });
 
-    // Package name of the default device service.
-    private String mDeviceService;
+    // Results from dynamic queries to ImsService regarding the features they support.
+    private ImsServiceFeatureQueryManager.Listener mDynamicQueryListener =
+            new ImsServiceFeatureQueryManager.Listener() {
+
+                @Override
+                public void onComplete(ComponentName name,
+                        Set<ImsFeatureConfiguration.FeatureSlotPair> features) {
+                    Log.d(TAG, "onComplete called for name: " + name + "features:"
+                            + printFeatures(features));
+                    handleFeaturesChanged(name, features);
+                }
+
+                @Override
+                public void onError(ComponentName name) {
+                    Log.w(TAG, "onError: " + name + "returned with an error result");
+                    scheduleQueryForFeatures(name, DELAY_DYNAMIC_QUERY_MS);
+                }
+            };
+
     // Array index corresponds to slot Id associated with the service package name.
     private String[] mCarrierServices;
     // List index corresponds to Slot Id, Maps ImsFeature.FEATURE->bound ImsServiceController
     // Locked on mBoundServicesLock
     private List<SparseArray<ImsServiceController>> mBoundImsServicesByFeature;
     // not locked, only accessed on a handler thread.
-    private Set<ImsServiceInfo> mInstalledServicesCache = new ArraySet<>();
+    private Map<ComponentName, ImsServiceInfo> mInstalledServicesCache = new HashMap<>();
     // not locked, only accessed on a handler thread.
-    private Set<ImsServiceController> mActiveControllers = new ArraySet<>();
-    // Only used as the
+    private Map<ComponentName, ImsServiceController> mActiveControllers = new HashMap<>();
+    // Only used as the Component name for legacy ImsServices that did not use dynamic binding.
     private final ComponentName mStaticComponent;
+    private ImsServiceFeatureQueryManager mFeatureQueryManager;
 
     public ImsResolver(Context context, String defaultImsPackageName, int numSlots,
             boolean isDynamicBinding) {
@@ -353,14 +487,21 @@
         return mHandler;
     }
 
+    @VisibleForTesting
+    public void setImsDynamicQueryManagerFactory(ImsDynamicQueryManagerFactory m) {
+        mDynamicQueryManagerFactory = m;
+    }
+
     /**
      * Needs to be called after the constructor to first populate the cache and possibly bind to
      * ImsServices.
      */
-    public void populateCacheAndStartBind() {
+    public void initPopulateCacheAndStartBind() {
         Log.i(TAG, "Initializing cache and binding.");
+        mFeatureQueryManager = mDynamicQueryManagerFactory.create(mContext, mDynamicQueryListener);
         // Populates the CarrierConfig override package names for each slot
-        mHandler.obtainMessage(HANDLER_CONFIG_CHANGED, -1).sendToTarget();
+        mHandler.obtainMessage(HANDLER_CONFIG_CHANGED,
+                SubscriptionManager.INVALID_SIM_SLOT_INDEX).sendToTarget();
         // Starts first bind to the system.
         mHandler.obtainMessage(HANDLER_ADD_PACKAGE, null).sendToTarget();
     }
@@ -449,19 +590,6 @@
         return null;
     }
 
-    /**
-     * @return true if the ImsService associated with this slot supports emergency calling over IMS,
-     * false if the call should be placed over circuit switch instead.
-     */
-    public boolean isEmergencyMmTelAvailable(int slotId) {
-        ImsServiceController controller = getImsServiceController(slotId, ImsFeature.FEATURE_MMTEL);
-        if (controller != null) {
-            return controller.canPlaceEmergencyCalls();
-        }
-        Log.w(TAG, "isEmergencyMmTelAvailable: No controller found for slot " + slotId);
-        return false;
-    }
-
     @VisibleForTesting
     public ImsServiceController getImsServiceController(int slotId, int feature) {
         if (slotId < 0 || slotId >= mNumSlots) {
@@ -497,12 +625,42 @@
         ImsServiceController controller = getImsServiceController(slotId, feature);
 
         if (controller != null) {
-            controller.addImsServiceFeatureListener(callback);
+            controller.addImsServiceFeatureCallback(callback);
             return controller;
         }
         return null;
     }
 
+    // Used for testing only.
+    public boolean overrideImsServiceConfiguration(int slotId, boolean isCarrierService,
+            String packageName) {
+        if (slotId < 0 || slotId >= mNumSlots) {
+            Log.w(TAG, "overrideImsServiceConfiguration: invalid slotId!");
+            return false;
+        }
+
+        if (packageName == null) {
+            Log.w(TAG, "overrideImsServiceConfiguration: null packageName!");
+            return false;
+        }
+
+        // encode boolean to int for Message.
+        int carrierService = isCarrierService ? 1 : 0;
+        Message.obtain(mHandler, HANDLER_OVERRIDE_IMS_SERVICE_CONFIG, slotId, carrierService,
+                packageName).sendToTarget();
+        return true;
+    }
+
+    // used for testing only.
+    public String getImsServiceConfiguration(int slotId, boolean isCarrierService) {
+        if (slotId < 0 || slotId >= mNumSlots) {
+            Log.w(TAG, "getImsServiceConfiguration: invalid slotId!");
+            return "";
+        }
+
+        return isCarrierService ? mCarrierServices[slotId] : mDeviceService;
+    }
+
     private void putImsController(int slotId, int feature, ImsServiceController controller) {
         if (slotId < 0 || slotId >= mNumSlots || feature <= ImsFeature.FEATURE_INVALID
                 || feature >= ImsFeature.FEATURE_MAX) {
@@ -554,21 +712,32 @@
         for (ImsServiceInfo info : infos) {
             // Checking to see if the ComponentName is the same, so we can update the supported
             // features. Will only be one (if it exists), since it is a set.
-            Optional<ImsServiceInfo> match = getInfoByComponentName(mInstalledServicesCache,
-                    info.name);
-            if (match.isPresent()) {
-                // update features in the cache
-                Log.i(TAG, "Updating features in cached ImsService: " + info.name);
-                Log.d(TAG, "Updating features - Old features: " + match.get().supportedFeatures
-                        + " new features: " + info.supportedFeatures
-                        + ", supports emergency: " + info.supportsEmergencyMmTel);
-                match.get().supportsEmergencyMmTel = info.supportsEmergencyMmTel;
-                match.get().supportedFeatures = info.supportedFeatures;
-                updateImsServiceFeatures(info);
+            ImsServiceInfo match = getInfoByComponentName(mInstalledServicesCache, info.name);
+            if (match != null) {
+                // for dynamic query the new "info" will have no supported features yet. Don't wipe
+                // out the cache for the existing features or update yet. Instead start a query
+                // for features dynamically.
+                if (info.featureFromMetadata) {
+                    // update features in the cache
+                    Log.i(TAG, "Updating features in cached ImsService: " + info.name);
+                    Log.d(TAG, "Updating features - Old features: " + match + " new features: "
+                            + info);
+                    match.replaceFeatures(info.getSupportedFeatures());
+                    updateImsServiceFeatures(info);
+                } else {
+                    // start a query to get ImsService features
+                    scheduleQueryForFeatures(info);
+                }
             } else {
                 Log.i(TAG, "Adding newly added ImsService to cache: " + info.name);
-                mInstalledServicesCache.add(info);
-                newlyAddedInfos.add(info);
+                mInstalledServicesCache.put(info.name, info);
+                if (info.featureFromMetadata) {
+                    newlyAddedInfos.add(info);
+                } else {
+                    // newly added ImsServiceInfo that has not had features queried yet. Start async
+                    // bind and query features.
+                    scheduleQueryForFeatures(info);
+                }
             }
         }
         // Loop through the newly created ServiceInfos in a separate loop to make sure the cache
@@ -577,12 +746,12 @@
             if (isActiveCarrierService(info)) {
                 // New ImsService is registered to active carrier services and must be newly
                 // bound.
-                bindNewImsService(info);
+                bindImsService(info);
                 // Update existing device service features
                 updateImsServiceFeatures(getImsServiceInfoFromCache(mDeviceService));
             } else if (isDeviceService(info)) {
                 // New ImsService is registered as device default and must be newly bound.
-                bindNewImsService(info);
+                bindImsService(info);
             }
         }
     }
@@ -591,11 +760,11 @@
     // killed.
     // Called from the handler ONLY
     private boolean maybeRemovedImsService(String packageName) {
-        Optional<ImsServiceInfo> match = getInfoByPackageName(mInstalledServicesCache, packageName);
-        if (match.isPresent()) {
-            mInstalledServicesCache.remove(match.get());
-            Log.i(TAG, "Removing ImsService: " + match.get().name);
-            unbindImsService(match.get());
+        ImsServiceInfo match = getInfoByPackageName(mInstalledServicesCache, packageName);
+        if (match != null) {
+            mInstalledServicesCache.remove(match.name);
+            Log.i(TAG, "Removing ImsService: " + match.name);
+            unbindImsService(match);
             updateImsServiceFeatures(getImsServiceInfoFromCache(mDeviceService));
             return true;
         }
@@ -624,25 +793,26 @@
                 return i;
             }
         }
-        return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+        return SubscriptionManager.INVALID_SIM_SLOT_INDEX;
     }
 
-    private Optional<ImsServiceController> getControllerByServiceInfo(
-            Set<ImsServiceController> searchSet, ImsServiceInfo matchValue) {
-        return searchSet.stream()
-                .filter(c -> Objects.equals(c.getComponentName(), matchValue.name)).findFirst();
+    private ImsServiceController getControllerByServiceInfo(
+            Map<ComponentName, ImsServiceController> searchMap, ImsServiceInfo matchValue) {
+        return searchMap.values().stream()
+                .filter(c -> Objects.equals(c.getComponentName(), matchValue.name))
+                .findFirst().orElse(null);
     }
 
-    private Optional<ImsServiceInfo> getInfoByPackageName(Set<ImsServiceInfo> searchSet,
+    private ImsServiceInfo getInfoByPackageName(Map<ComponentName, ImsServiceInfo> searchMap,
             String matchValue) {
-        return searchSet.stream()
-                .filter((i) -> Objects.equals(i.name.getPackageName(), matchValue)).findFirst();
+        return searchMap.values().stream()
+                .filter((i) -> Objects.equals(i.name.getPackageName(), matchValue))
+                .findFirst().orElse(null);
     }
 
-    private Optional<ImsServiceInfo> getInfoByComponentName(Set<ImsServiceInfo> searchSet,
-            ComponentName matchValue) {
-        return searchSet.stream()
-                .filter((i) -> Objects.equals(i.name, matchValue)).findFirst();
+    private ImsServiceInfo getInfoByComponentName(
+            Map<ComponentName, ImsServiceInfo> searchMap, ComponentName matchValue) {
+        return searchMap.get(matchValue);
     }
 
     // Creates new features in active ImsServices and removes obsolete cached features. If
@@ -652,51 +822,71 @@
         if (newInfo == null) {
             return;
         }
-        Optional<ImsServiceController> o = getControllerByServiceInfo(mActiveControllers,
-                newInfo);
-        if (o.isPresent()) {
-            Log.d(TAG, "Updating canPlaceEmergencyCalls: " + newInfo.supportsEmergencyMmTel);
-            o.get().setCanPlaceEmergencyCalls(newInfo.supportsEmergencyMmTel);
-            Log.i(TAG, "Updating features for ImsService: " + o.get().getComponentName());
-            HashSet<Pair<Integer, Integer>> features = calculateFeaturesToCreate(newInfo);
+        ImsServiceController controller = getControllerByServiceInfo(mActiveControllers, newInfo);
+        // Will return zero if these features are overridden or it should not currently have any
+        // features because it is not carrier/device.
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> features =
+                calculateFeaturesToCreate(newInfo);
+        if (shouldFeaturesCauseBind(features)) {
             try {
-                if (features.size() > 0) {
+                if (controller != null) {
+                    Log.i(TAG, "Updating features for ImsService: "
+                            + controller.getComponentName());
                     Log.d(TAG, "Updating Features - New Features: " + features);
-                    o.get().changeImsServiceFeatures(features);
-
-                    // If the carrier service features have changed, the device features will also
-                    // need to be recalculated.
-                    if (isActiveCarrierService(newInfo)
-                            // Prevent infinite recursion from bad behavior
-                            && !TextUtils.equals(newInfo.name.getPackageName(), mDeviceService)) {
-                        Log.i(TAG, "Updating device default");
-                        updateImsServiceFeatures(getImsServiceInfoFromCache(mDeviceService));
-                    }
+                    controller.changeImsServiceFeatures(features);
                 } else {
-                    Log.i(TAG, "Unbinding: features = 0 for ImsService: "
-                            + o.get().getComponentName());
-                    o.get().unbind();
+                    Log.i(TAG, "updateImsServiceFeatures: unbound with active features, rebinding");
+                    bindImsServiceWithFeatures(newInfo, features);
+                }
+                // If the carrier service features have changed, the device features will also
+                // need to be recalculated.
+                if (isActiveCarrierService(newInfo)
+                        // Prevent infinite recursion from bad behavior
+                        && !TextUtils.equals(newInfo.name.getPackageName(), mDeviceService)) {
+                    Log.i(TAG, "Updating device default");
+                    updateImsServiceFeatures(getImsServiceInfoFromCache(mDeviceService));
                 }
             } catch (RemoteException e) {
                 Log.e(TAG, "updateImsServiceFeatures: Remote Exception: " + e.getMessage());
             }
+        // Don't stay bound if the ImsService is providing no features.
+        } else if (controller != null) {
+            Log.i(TAG, "Unbinding: features = 0 for ImsService: " + controller.getComponentName());
+            unbindImsService(newInfo);
         }
     }
 
-    // Bind to a new ImsService and wait for the service to be connected to create ImsFeatures.
-    private void bindNewImsService(ImsServiceInfo info) {
+    // Bind to an ImsService and wait for the service to be connected to create ImsFeatures.
+    private void bindImsService(ImsServiceInfo info) {
         if (info == null) {
             return;
         }
-        ImsServiceController controller = info.controllerFactory.create(mContext, info.name, this);
-        HashSet<Pair<Integer, Integer>> features = calculateFeaturesToCreate(info);
-        controller.setCanPlaceEmergencyCalls(info.supportsEmergencyMmTel);
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> features = calculateFeaturesToCreate(info);
+        bindImsServiceWithFeatures(info, features);
+    }
+
+    private void bindImsServiceWithFeatures(ImsServiceInfo info,
+            HashSet<ImsFeatureConfiguration.FeatureSlotPair> features) {
         // Only bind if there are features that will be created by the service.
-        if (features.size() > 0) {
-            Log.i(TAG, "Binding ImsService: " + controller.getComponentName() + " with features: "
-                    + features + ", supports emergency calling: " + info.supportsEmergencyMmTel);
-            controller.bind(features);
-            mActiveControllers.add(controller);
+        if (shouldFeaturesCauseBind(features)) {
+            // Check to see if an active controller already exists
+            ImsServiceController controller = getControllerByServiceInfo(mActiveControllers, info);
+            if (controller != null) {
+                Log.i(TAG, "ImsService connection exists, updating features " + features);
+                try {
+                    controller.changeImsServiceFeatures(features);
+                    // Features have been set, there was an error adding/removing. When the
+                    // controller recovers, it will add/remove again.
+                } catch (RemoteException e) {
+                    Log.w(TAG, "bindImsService: error=" + e.getMessage());
+                }
+            } else {
+                controller = info.controllerFactory.create(mContext, info.name, this);
+                Log.i(TAG, "Binding ImsService: " + controller.getComponentName()
+                        + " with features: " + features);
+                controller.bind(features);
+            }
+            mActiveControllers.put(info.name, controller);
         }
     }
 
@@ -705,16 +895,16 @@
         if (info == null) {
             return;
         }
-        Optional<ImsServiceController> o = getControllerByServiceInfo(mActiveControllers, info);
-        if (o.isPresent()) {
+        ImsServiceController controller = getControllerByServiceInfo(mActiveControllers, info);
+        if (controller != null) {
             // Calls imsServiceFeatureRemoved on all features in the controller
             try {
-                Log.i(TAG, "Unbinding ImsService: " + o.get().getComponentName());
-                o.get().unbind();
+                Log.i(TAG, "Unbinding ImsService: " + controller.getComponentName());
+                controller.unbind();
             } catch (RemoteException e) {
                 Log.e(TAG, "unbindImsService: Remote Exception: " + e.getMessage());
             }
-            mActiveControllers.remove(o.get());
+            mActiveControllers.remove(info.name);
         }
     }
 
@@ -722,13 +912,16 @@
     // ImsServiceController, it will be granted all of the features it requests on the associated
     // slot. If it is the device ImsService, it will get all of the features not covered by the
     // carrier implementation.
-    private HashSet<Pair<Integer, Integer>> calculateFeaturesToCreate(ImsServiceInfo info) {
-        HashSet<Pair<Integer, Integer>> imsFeaturesBySlot = new HashSet<>();
+    private HashSet<ImsFeatureConfiguration.FeatureSlotPair> calculateFeaturesToCreate(
+            ImsServiceInfo info) {
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> imsFeaturesBySlot = new HashSet<>();
         // Check if the info is a carrier service
         int slotId = getSlotForActiveCarrierService(info);
-        if (slotId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
-            imsFeaturesBySlot.addAll(info.supportedFeatures.stream().map(
-                    feature -> new Pair<>(slotId, feature)).collect(Collectors.toList()));
+        if (slotId != SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
+            imsFeaturesBySlot.addAll(info.getSupportedFeatures().stream()
+                    // Match slotId with feature slotId.
+                    .filter(feature -> slotId == feature.slotId)
+                    .collect(Collectors.toList()));
         } else if (isDeviceService(info)) {
             // For all slots that are not currently using a carrier ImsService, enable all features
             // for the device default.
@@ -736,17 +929,19 @@
                 final int currSlotId = i;
                 ImsServiceInfo carrierImsInfo = getImsServiceInfoFromCache(mCarrierServices[i]);
                 if (carrierImsInfo == null) {
-                    // No Carrier override, add all features
-                    imsFeaturesBySlot.addAll(info.supportedFeatures.stream().map(
-                            feature -> new Pair<>(currSlotId, feature)).collect(
-                            Collectors.toList()));
+                    // No Carrier override, add all features for this slot
+                    imsFeaturesBySlot.addAll(info.getSupportedFeatures().stream()
+                            .filter(feature -> currSlotId == feature.slotId)
+                            .collect(Collectors.toList()));
                 } else {
                     // Add all features to the device service that are not currently covered by
                     // the carrier ImsService.
-                    Set<Integer> deviceFeatures = new HashSet<>(info.supportedFeatures);
-                    deviceFeatures.removeAll(carrierImsInfo.supportedFeatures);
-                    imsFeaturesBySlot.addAll(deviceFeatures.stream().map(
-                            feature -> new Pair<>(currSlotId, feature)).collect(
+                    HashSet<ImsFeatureConfiguration.FeatureSlotPair> deviceFeatures =
+                            new HashSet<>(info.getSupportedFeatures());
+                    deviceFeatures.removeAll(carrierImsInfo.getSupportedFeatures());
+                    // only add features for current slot
+                    imsFeaturesBySlot.addAll(deviceFeatures.stream()
+                            .filter(feature -> currSlotId == feature.slotId).collect(
                             Collectors.toList()));
                 }
             }
@@ -772,28 +967,64 @@
         removeImsController(slotId, feature);
     }
 
+    /**
+     * Implementation of
+     * {@link ImsServiceController.ImsServiceControllerCallbacks#imsServiceFeaturesChanged, which
+     * notify the ImsResolver of a change to the supported ImsFeatures of a connected ImsService.
+     */
+    public void imsServiceFeaturesChanged(ImsFeatureConfiguration config,
+            ImsServiceController controller) {
+        if (controller == null || config == null) {
+            return;
+        }
+        Log.i(TAG, "imsServiceFeaturesChanged: config=" + config.getServiceFeatures()
+                + ", ComponentName=" + controller.getComponentName());
+        handleFeaturesChanged(controller.getComponentName(), config.getServiceFeatures());
+    }
+
+    /**
+     * Determines if the features specified should cause a bind or keep a binding active to an
+     * ImsService.
+     * @return true if MMTEL or RCS features are present, false if they are not or only
+     * EMERGENCY_MMTEL is specified.
+     */
+    private boolean shouldFeaturesCauseBind(
+            HashSet<ImsFeatureConfiguration.FeatureSlotPair> features) {
+        long bindableFeatures = features.stream()
+                // remove all emergency features
+                .filter(f -> f.featureType != ImsFeature.FEATURE_EMERGENCY_MMTEL).count();
+        return bindableFeatures > 0;
+    }
+
     // Possibly rebind to another ImsService if currently installed ImsServices were changed or if
     // the SIM card has changed.
     // Called from the handler ONLY
-    private void maybeRebindService(int subId) {
-        if (subId <= SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+    private void maybeRebindService(int slotId, String newPackageName) {
+        if (slotId <= SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
             // not specified, replace package on all slots.
             for (int i = 0; i < mNumSlots; i++) {
-                // get Sub id from Slot Id
-                subId = mSubscriptionManagerProxy.getSubId(i);
-                updateBoundCarrierServices(subId);
+                updateBoundCarrierServices(i, newPackageName);
             }
         } else {
-            updateBoundCarrierServices(subId);
+            updateBoundCarrierServices(slotId, newPackageName);
         }
 
     }
 
-    private void updateBoundCarrierServices(int subId) {
-        int slotId = mSubscriptionManagerProxy.getSlotIndex(subId);
-        String newPackageName = mCarrierConfigManager.getConfigForSubId(subId).getString(
-                CarrierConfigManager.KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING, null);
-        if (slotId != SubscriptionManager.INVALID_SIM_SLOT_INDEX && slotId < mNumSlots) {
+    private void carrierConfigChanged(int slotId) {
+        int subId = mSubscriptionManagerProxy.getSubId(slotId);
+        PersistableBundle config = mCarrierConfigManager.getConfigForSubId(subId);
+        if (config != null) {
+            String newPackageName = config.getString(
+                    CarrierConfigManager.KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING, null);
+            maybeRebindService(slotId, newPackageName);
+        } else {
+            Log.w(TAG, "carrierConfigChanged: CarrierConfig is null!");
+        }
+    }
+
+    private void updateBoundCarrierServices(int slotId, String newPackageName) {
+        if (slotId > SubscriptionManager.INVALID_SIM_SLOT_INDEX && slotId < mNumSlots) {
             String oldPackageName = mCarrierServices[slotId];
             mCarrierServices[slotId] = newPackageName;
             if (!TextUtils.equals(newPackageName, oldPackageName)) {
@@ -802,14 +1033,132 @@
                 // ImsService is retrieved from the cache. If the cache hasn't been populated yet,
                 // the calls to unbind/bind will fail (intended during initial start up).
                 unbindImsService(getImsServiceInfoFromCache(oldPackageName));
-                bindNewImsService(getImsServiceInfoFromCache(newPackageName));
-                // Recalculate the device ImsService features to reflect changes.
-                updateImsServiceFeatures(getImsServiceInfoFromCache(mDeviceService));
+                ImsServiceInfo newInfo = getImsServiceInfoFromCache(newPackageName);
+                // if there is no carrier ImsService, newInfo is null. This we still want to update
+                // bindings for device ImsService to pick up the missing features.
+                if (newInfo == null || newInfo.featureFromMetadata) {
+                    bindImsService(newInfo);
+                    // Recalculate the device ImsService features to reflect changes.
+                    updateImsServiceFeatures(getImsServiceInfoFromCache(mDeviceService));
+                } else {
+                    // ImsServiceInfo that has not had features queried yet. Start async
+                    // bind and query features.
+                    scheduleQueryForFeatures(newInfo);
+                }
             }
         }
     }
 
     /**
+     * Schedules a query for dynamic ImsService features.
+     */
+    private void scheduleQueryForFeatures(ImsServiceInfo service, int delayMs) {
+        // if not current device/carrier service, don't perform query. If this changes, this method
+        // will be called again.
+        if (!isDeviceService(service) && getSlotForActiveCarrierService(service)
+                == SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
+            Log.i(TAG, "scheduleQueryForFeatures: skipping query for ImsService that is not"
+                    + " set as carrier/device ImsService.");
+            return;
+        }
+        Message msg = Message.obtain(mHandler, HANDLER_START_DYNAMIC_FEATURE_QUERY, service);
+        if (mHandler.hasMessages(HANDLER_START_DYNAMIC_FEATURE_QUERY, service)) {
+            Log.d(TAG, "scheduleQueryForFeatures: dynamic query for " + service.name
+                    + " already scheduled");
+            return;
+        }
+        Log.d(TAG, "scheduleQueryForFeatures: starting dynamic query for " + service.name
+                + " in " + delayMs + "ms.");
+        mHandler.sendMessageDelayed(msg, delayMs);
+    }
+
+    private void scheduleQueryForFeatures(ComponentName name, int delayMs) {
+        ImsServiceInfo service = getImsServiceInfoFromCache(name.getPackageName());
+        if (service == null) {
+            Log.w(TAG, "scheduleQueryForFeatures: Couldn't find cached info for name: " + name);
+            return;
+        }
+        scheduleQueryForFeatures(service, delayMs);
+    }
+
+    private void scheduleQueryForFeatures(ImsServiceInfo service) {
+        scheduleQueryForFeatures(service, 0);
+    }
+
+    /**
+     * Schedules the processing of a completed query.
+     */
+    private void handleFeaturesChanged(ComponentName name,
+            Set<ImsFeatureConfiguration.FeatureSlotPair> features) {
+        SomeArgs args = SomeArgs.obtain();
+        args.arg1 = name;
+        args.arg2 = features;
+        mHandler.obtainMessage(HANDLER_DYNAMIC_FEATURE_CHANGE, args).sendToTarget();
+    }
+
+    // Starts a dynamic query. Called from handler ONLY.
+    private void startDynamicQuery(ImsServiceInfo service) {
+        boolean queryStarted = mFeatureQueryManager.startQuery(service.name,
+                service.controllerFactory.getServiceInterface());
+        if (!queryStarted) {
+            Log.w(TAG, "startDynamicQuery: service could not connect. Retrying after delay.");
+            scheduleQueryForFeatures(service, DELAY_DYNAMIC_QUERY_MS);
+        } else {
+            Log.d(TAG, "startDynamicQuery: Service queried, waiting for response.");
+        }
+    }
+
+    // process complete dynamic query. Called from handler ONLY.
+    private void dynamicQueryComplete(ComponentName name,
+            Set<ImsFeatureConfiguration.FeatureSlotPair> features) {
+        ImsServiceInfo service = getImsServiceInfoFromCache(name.getPackageName());
+        if (service == null) {
+            Log.w(TAG, "handleFeaturesChanged: Couldn't find cached info for name: "
+                    + name);
+            return;
+        }
+        // Add features to service
+        service.replaceFeatures(features);
+        if (isActiveCarrierService(service)) {
+            // New ImsService is registered to active carrier services and must be newly
+            // bound.
+            bindImsService(service);
+            // Update existing device service features
+            updateImsServiceFeatures(getImsServiceInfoFromCache(mDeviceService));
+        } else if (isDeviceService(service)) {
+            // New ImsService is registered as device default and must be newly bound.
+            bindImsService(service);
+        }
+    }
+
+    /**
+     * @return true if the ImsResolver is in the process of resolving a dynamic query and should not
+     * be considered available, false if the ImsResolver is idle.
+     */
+    public boolean isResolvingBinding() {
+        return mHandler.hasMessages(HANDLER_START_DYNAMIC_FEATURE_QUERY)
+                // We haven't processed this message yet, so it is still resolving.
+                || mHandler.hasMessages(HANDLER_DYNAMIC_FEATURE_CHANGE)
+                || mFeatureQueryManager.isQueryInProgress();
+    }
+
+    private String printFeatures(Set<ImsFeatureConfiguration.FeatureSlotPair> features) {
+        StringBuilder featureString = new StringBuilder();
+        featureString.append("features: [");
+        if (features != null) {
+            for (ImsFeatureConfiguration.FeatureSlotPair feature : features) {
+                featureString.append("{");
+                featureString.append(feature.slotId);
+                featureString.append(",");
+                featureString.append(feature.featureType);
+                featureString.append("} ");
+            }
+            featureString.append("]");
+        }
+        return featureString.toString();
+    }
+
+    /**
      * Returns the ImsServiceInfo that matches the provided packageName. Visible for testing
      * the ImsService caching functionality.
      */
@@ -818,10 +1167,9 @@
         if (TextUtils.isEmpty(packageName)) {
             return null;
         }
-        Optional<ImsServiceInfo> infoFilter = getInfoByPackageName(mInstalledServicesCache,
-                packageName);
-        if (infoFilter.isPresent()) {
-            return infoFilter.get();
+        ImsServiceInfo infoFilter = getInfoByPackageName(mInstalledServicesCache, packageName);
+        if (infoFilter != null) {
+            return infoFilter;
         } else {
             return null;
         }
@@ -846,12 +1194,11 @@
     private List<ImsServiceInfo> getStaticImsService() {
         List<ImsServiceInfo> infos = new ArrayList<>();
 
-        ImsServiceInfo info = new ImsServiceInfo();
+        ImsServiceInfo info = new ImsServiceInfo(mNumSlots);
         info.name = mStaticComponent;
-        info.supportedFeatures = new HashSet<>(ImsFeature.FEATURE_MAX);
         info.controllerFactory = mImsServiceControllerFactoryStaticBindingCompat;
-        info.supportsEmergencyMmTel = true;
-        info.supportedFeatures.add(ImsFeature.FEATURE_MMTEL);
+        info.addFeatureForAllSlots(ImsFeature.FEATURE_EMERGENCY_MMTEL);
+        info.addFeatureForAllSlots(ImsFeature.FEATURE_MMTEL);
         infos.add(info);
         return infos;
     }
@@ -871,31 +1218,51 @@
             ServiceInfo serviceInfo = entry.serviceInfo;
 
             if (serviceInfo != null) {
-                ImsServiceInfo info = new ImsServiceInfo();
+                ImsServiceInfo info = new ImsServiceInfo(mNumSlots);
                 info.name = new ComponentName(serviceInfo.packageName, serviceInfo.name);
-                info.supportedFeatures = new HashSet<>(ImsFeature.FEATURE_MAX);
                 info.controllerFactory = controllerFactory;
-                // Add all supported features
-                if (serviceInfo.metaData != null) {
-                    if (serviceInfo.metaData.getBoolean(METADATA_EMERGENCY_MMTEL_FEATURE, false)) {
-                        info.supportsEmergencyMmTel = true;
+
+                // we will allow the manifest method of declaring manifest features in two cases:
+                // 1) it is the device overlay "default" ImsService, where the features do not
+                // change (the new method can still be used if the default does not define manifest
+                // entries).
+                // 2) using the "compat" ImsService, which only supports manifest query.
+                if (isDeviceService(info)
+                        || mImsServiceControllerFactoryCompat == controllerFactory) {
+                    if (serviceInfo.metaData != null) {
+                        if (serviceInfo.metaData.getBoolean(METADATA_EMERGENCY_MMTEL_FEATURE,
+                                false)) {
+                            info.addFeatureForAllSlots(ImsFeature.FEATURE_EMERGENCY_MMTEL);
+                        }
+                        if (serviceInfo.metaData.getBoolean(METADATA_MMTEL_FEATURE, false)) {
+                            info.addFeatureForAllSlots(ImsFeature.FEATURE_MMTEL);
+                        }
+                        if (serviceInfo.metaData.getBoolean(METADATA_RCS_FEATURE, false)) {
+                            info.addFeatureForAllSlots(ImsFeature.FEATURE_RCS);
+                        }
                     }
-                    if (serviceInfo.metaData.getBoolean(METADATA_MMTEL_FEATURE, false)) {
-                        info.supportedFeatures.add(ImsFeature.FEATURE_MMTEL);
+                    // Only dynamic query if we are not a compat version of ImsService and the
+                    // default service.
+                    if (mImsServiceControllerFactoryCompat != controllerFactory
+                            && info.getSupportedFeatures().isEmpty()) {
+                        // metadata empty, try dynamic query instead
+                        info.featureFromMetadata = false;
                     }
-                    if (serviceInfo.metaData.getBoolean(METADATA_RCS_FEATURE, false)) {
-                        info.supportedFeatures.add(ImsFeature.FEATURE_RCS);
-                    }
+                } else {
+                    // We are a carrier service and not using the compat version of ImsService.
+                    info.featureFromMetadata = false;
                 }
+                Log.i(TAG, "service name: " + info.name + ", manifest query: "
+                        + info.featureFromMetadata);
                 // Check manifest permission to be sure that the service declares the correct
-                // permissions.
-                if (TextUtils.equals(serviceInfo.permission,
-                        Manifest.permission.BIND_IMS_SERVICE)) {
-                    Log.d(TAG, "ImsService (" + serviceIntent + ") added to cache: "
-                            + info.name + " with features: " + info.supportedFeatures);
+                // permissions. Overridden if the METADATA_OVERRIDE_PERM_CHECK metadata is set to
+                // true.
+                // NOTE: METADATA_OVERRIDE_PERM_CHECK should only be set for testing.
+                if (TextUtils.equals(serviceInfo.permission, Manifest.permission.BIND_IMS_SERVICE)
+                        || serviceInfo.metaData.getBoolean(METADATA_OVERRIDE_PERM_CHECK, false)) {
                     infos.add(info);
                 } else {
-                    Log.w(TAG, "ImsService does not have BIND_IMS_SERVICE permission: "
+                    Log.w(TAG, "ImsService is not protected with BIND_IMS_SERVICE permission: "
                             + info.name);
                 }
             }
diff --git a/src/java/com/android/internal/telephony/ims/ImsServiceController.java b/src/java/com/android/internal/telephony/ims/ImsServiceController.java
index 30de179..60f11c6 100644
--- a/src/java/com/android/internal/telephony/ims/ImsServiceController.java
+++ b/src/java/com/android/internal/telephony/ims/ImsServiceController.java
@@ -34,8 +34,8 @@
 import android.telephony.ims.aidl.IImsRegistration;
 import android.telephony.ims.aidl.IImsServiceController;
 import android.telephony.ims.feature.ImsFeature;
+import android.telephony.ims.stub.ImsFeatureConfiguration;
 import android.util.Log;
-import android.util.Pair;
 
 import com.android.ims.internal.IImsFeatureStatusCallback;
 import com.android.ims.internal.IImsServiceFeatureCallback;
@@ -45,21 +45,21 @@
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 
 /**
  * Manages the Binding lifecycle of one ImsService as well as the relevant ImsFeatures that the
  * ImsService will support.
  *
- * When the ImsService is first bound, {@link IImsServiceController#createImsFeature} will be
- * called
+ * When the ImsService is first bound, {@link ImsService#createMmTelFeature(int)} and
+ * {@link ImsService#createRcsFeature(int)} will be called
  * on each feature that the service supports. For each ImsFeature that is created,
  * {@link ImsServiceControllerCallbacks#imsServiceFeatureCreated} will be called to notify the
  * listener that the ImsService now supports that feature.
  *
  * When {@link #changeImsServiceFeatures} is called with a set of features that is different from
- * the original set, {@link IImsServiceController#createImsFeature} and
- * {@link IImsServiceController#removeImsFeature} will be called for each feature that is
- * created/removed.
+ * the original set, create and {@link IImsServiceController#removeImsFeature} will be called for
+ * each feature that is created/removed.
  */
 public class ImsServiceController {
 
@@ -74,7 +74,11 @@
         @Override
         public void binderDied() {
             Log.e(LOG_TAG, "ImsService(" + mComponentName + ") died. Restarting...");
-            notifyAllFeaturesRemoved();
+            synchronized (mLock) {
+                mIsBinding = false;
+                mIsBound = false;
+            }
+            cleanupAllFeatures();
             cleanUpService();
             startDelayedRebindToService();
         }
@@ -88,7 +92,6 @@
             synchronized (mLock) {
                 mIsBound = true;
                 mIsBinding = false;
-                grantPermissionsToService();
                 Log.d(LOG_TAG, "ImsService(" + name + "): onServiceConnected with binder: "
                         + service);
                 if (service != null) {
@@ -97,8 +100,9 @@
                         service.linkToDeath(mImsDeathRecipient, 0);
                         mImsServiceControllerBinder = service;
                         setServiceController(service);
+                        notifyImsServiceReady();
                         // create all associated features in the ImsService
-                        for (Pair<Integer, Integer> i : mImsFeatures) {
+                        for (ImsFeatureConfiguration.FeatureSlotPair i : mImsFeatures) {
                             addImsServiceFeature(i);
                         }
                     } catch (RemoteException e) {
@@ -120,29 +124,61 @@
             synchronized (mLock) {
                 mIsBinding = false;
             }
+            cleanupConnection();
+            Log.w(LOG_TAG, "ImsService(" + name + "): onServiceDisconnected. Waiting...");
+            // Service disconnected, but we are still technically bound. Waiting for reconnect.
+        }
+
+        @Override
+        public void onBindingDied(ComponentName name) {
+            synchronized (mLock) {
+                mIsBinding = false;
+                mIsBound = false;
+            }
+            cleanupConnection();
+            Log.w(LOG_TAG, "ImsService(" + name + "): onBindingDied. Starting rebind...");
+            startDelayedRebindToService();
+        }
+
+        private void cleanupConnection() {
             if (isServiceControllerAvailable()) {
                 mImsServiceControllerBinder.unlinkToDeath(mImsDeathRecipient, 0);
             }
-            notifyAllFeaturesRemoved();
+            cleanupAllFeatures();
             cleanUpService();
-            Log.w(LOG_TAG, "ImsService(" + name + "): onServiceDisconnected. Rebinding...");
-            startDelayedRebindToService();
         }
     }
 
+    private ImsService.Listener mFeatureChangedListener = new ImsService.Listener() {
+        @Override
+        public void onUpdateSupportedImsFeatures(ImsFeatureConfiguration c) {
+            if (mCallbacks == null) {
+                return;
+            }
+            mCallbacks.imsServiceFeaturesChanged(c, ImsServiceController.this);
+        }
+    };
+
     /**
      * Defines callbacks that are used by the ImsServiceController to notify when an ImsService
      * has created or removed a new feature as well as the associated ImsServiceController.
      */
     public interface ImsServiceControllerCallbacks {
         /**
-         * Called by ImsServiceController when a new feature has been created.
+         * Called by ImsServiceController when a new MMTEL or RCS feature has been created.
          */
         void imsServiceFeatureCreated(int slotId, int feature, ImsServiceController controller);
         /**
-         * Called by ImsServiceController when a new feature has been removed.
+         * Called by ImsServiceController when a new MMTEL or RCS feature has been removed.
          */
         void imsServiceFeatureRemoved(int slotId, int feature, ImsServiceController controller);
+
+        /**
+         * Called by the ImsServiceController when the ImsService has notified the framework that
+         * its features have changed.
+         */
+        void imsServiceFeaturesChanged(ImsFeatureConfiguration config,
+                ImsServiceController controller);
     }
 
     /**
@@ -175,18 +211,16 @@
     private boolean mIsBound = false;
     private boolean mIsBinding = false;
     // Set of a pair of slotId->feature
-    private HashSet<Pair<Integer, Integer>> mImsFeatures;
+    private HashSet<ImsFeatureConfiguration.FeatureSlotPair> mImsFeatures;
     // Binder interfaces to the features set in mImsFeatures;
     private HashSet<ImsFeatureContainer> mImsFeatureBinders = new HashSet<>();
     private IImsServiceController mIImsServiceController;
     private IBinder mImsServiceControllerBinder;
     private ImsServiceConnection mImsServiceConnection;
     private ImsDeathRecipient mImsDeathRecipient;
-    private Set<IImsServiceFeatureCallback> mImsStatusCallbacks = new HashSet<>();
+    private Set<IImsServiceFeatureCallback> mImsStatusCallbacks = ConcurrentHashMap.newKeySet();
     // Only added or removed, never accessed on purpose.
     private Set<ImsFeatureStatusCallback> mFeatureStatusCallbacks = new HashSet<>();
-    // Determines whether or not emergency calls can be placed through this ImsService.
-    private boolean mCanPlaceEmergencyCalls = false;
 
     protected final Object mLock = new Object();
     protected final Context mContext;
@@ -315,19 +349,20 @@
     }
 
     /**
-     * Sends request to bind to ImsService designated by the {@ComponentName} with the feature set
-     * imsFeatureSet
+     * Sends request to bind to ImsService designated by the {@link ComponentName} with the feature
+     * set imsFeatureSet.
      *
      * @param imsFeatureSet a Set of Pairs that designate the slotId->featureId that need to be
      *                      created once the service is bound.
      * @return {@link true} if the service is in the process of being bound, {@link false} if it
      * has failed.
      */
-    public boolean bind(HashSet<Pair<Integer, Integer>> imsFeatureSet) {
+    public boolean bind(HashSet<ImsFeatureConfiguration.FeatureSlotPair> imsFeatureSet) {
         synchronized (mLock) {
             if (!mIsBound && !mIsBinding) {
                 mIsBinding = true;
                 mImsFeatures = imsFeatureSet;
+                grantPermissionsToService();
                 Intent imsServiceIntent = new Intent(getServiceInterface()).setComponent(
                         mComponentName);
                 mImsServiceConnection = new ImsServiceConnection();
@@ -338,6 +373,7 @@
                     boolean bindSucceeded = startBindToService(imsServiceIntent,
                             mImsServiceConnection, serviceFlags);
                     if (!bindSucceeded) {
+                        mIsBinding = false;
                         mBackoff.notifyFailed();
                     }
                     return bindSucceeded;
@@ -375,7 +411,7 @@
             }
             // Clean up all features
             changeImsServiceFeatures(new HashSet<>());
-            removeImsServiceFeatureListener();
+            removeImsServiceFeatureCallbacks();
             mImsServiceControllerBinder.unlinkToDeath(mImsDeathRecipient, 0);
             Log.i(LOG_TAG, "Unbinding ImsService: " + mComponentName);
             mContext.unbindService(mImsServiceConnection);
@@ -387,50 +423,32 @@
      * For every feature that is added, the service calls the associated create. For every
      * ImsFeature that is removed, {@link IImsServiceController#removeImsFeature} is called.
      */
-    public void changeImsServiceFeatures(HashSet<Pair<Integer, Integer>> newImsFeatures)
+    public void changeImsServiceFeatures(
+            HashSet<ImsFeatureConfiguration.FeatureSlotPair> newImsFeatures)
             throws RemoteException {
         synchronized (mLock) {
+            Log.i(LOG_TAG, "Features changed (" + mImsFeatures + "->" + newImsFeatures + ") for "
+                    + "ImsService: " + mComponentName);
+            HashSet<ImsFeatureConfiguration.FeatureSlotPair> oldImsFeatures =
+                    new HashSet<>(mImsFeatures);
+            // Set features first in case we lose binding and need to rebind later.
+            mImsFeatures = newImsFeatures;
             if (mIsBound) {
                 // add features to service.
-                HashSet<Pair<Integer, Integer>> newFeatures = new HashSet<>(newImsFeatures);
-                newFeatures.removeAll(mImsFeatures);
-                for (Pair<Integer, Integer> i : newFeatures) {
+                HashSet<ImsFeatureConfiguration.FeatureSlotPair> newFeatures =
+                        new HashSet<>(mImsFeatures);
+                newFeatures.removeAll(oldImsFeatures);
+                for (ImsFeatureConfiguration.FeatureSlotPair i : newFeatures) {
                     addImsServiceFeature(i);
                 }
                 // remove old features
-                HashSet<Pair<Integer, Integer>> oldFeatures = new HashSet<>(mImsFeatures);
-                oldFeatures.removeAll(newImsFeatures);
-                for (Pair<Integer, Integer> i : oldFeatures) {
+                HashSet<ImsFeatureConfiguration.FeatureSlotPair> oldFeatures =
+                        new HashSet<>(oldImsFeatures);
+                oldFeatures.removeAll(mImsFeatures);
+                for (ImsFeatureConfiguration.FeatureSlotPair i : oldFeatures) {
                     removeImsServiceFeature(i);
                 }
             }
-            Log.i(LOG_TAG, "Features changed (" + mImsFeatures + "->" + newImsFeatures + ") for "
-                    + "ImsService: " + mComponentName);
-            mImsFeatures = newImsFeatures;
-        }
-    }
-
-    /**
-     * Sets the ability for the ImsService to place emergency calls. This is controlled by the
-     * {@link ImsFeature#FEATURE_EMERGENCY_MMTEL} feature attribute, which is set either as metadata
-     * in the AndroidManifest service definition or via dynamic query in
-     * {@link ImsService#querySupportedImsFeatures()}.
-     */
-    public void setCanPlaceEmergencyCalls(boolean canPlaceEmergencyCalls) {
-        synchronized (mLock) {
-            mCanPlaceEmergencyCalls = canPlaceEmergencyCalls;
-        }
-    }
-
-    /**
-     * Whether or not the ImsService connected to this controller is able to place emergency calls
-     * over IMS.
-     * @return true if this ImsService can place emergency calls over IMS, false if the framework
-     * should instead place the emergency call over circuit switch.
-     */
-    public boolean canPlaceEmergencyCalls() {
-        synchronized (mLock) {
-            return mCanPlaceEmergencyCalls;
         }
     }
 
@@ -456,15 +474,30 @@
     /**
      * Add a callback to ImsManager that signals a new feature that the ImsServiceProxy can handle.
      */
-    public void addImsServiceFeatureListener(IImsServiceFeatureCallback callback) {
+    public void addImsServiceFeatureCallback(IImsServiceFeatureCallback callback) {
+        mImsStatusCallbacks.add(callback);
         synchronized (mLock) {
-            mImsStatusCallbacks.add(callback);
+            if (mImsFeatures == null || mImsFeatures.isEmpty()) {
+                return;
+            }
+            // notify the new status callback of the features that are available.
+            try {
+                for (ImsFeatureConfiguration.FeatureSlotPair i : mImsFeatures) {
+                    callback.imsFeatureCreated(i.slotId, i.featureType);
+                }
+            } catch (RemoteException e) {
+                Log.w(LOG_TAG, "addImsServiceFeatureCallback: exception notifying callback");
+            }
         }
     }
 
     public void enableIms(int slotId) {
         try {
-            mIImsServiceController.enableIms(slotId);
+            synchronized (mLock) {
+                if (isServiceControllerAvailable()) {
+                    mIImsServiceController.enableIms(slotId);
+                }
+            }
         } catch (RemoteException e) {
             Log.w(LOG_TAG, "Couldn't enable IMS: " + e.getMessage());
         }
@@ -472,7 +505,11 @@
 
     public void disableIms(int slotId) {
         try {
-            mIImsServiceController.disableIms(slotId);
+            synchronized (mLock) {
+                if (isServiceControllerAvailable()) {
+                    mIImsServiceController.disableIms(slotId);
+                }
+            }
         } catch (RemoteException e) {
             Log.w(LOG_TAG, "Couldn't disable IMS: " + e.getMessage());
         }
@@ -512,7 +549,8 @@
      */
     public IImsRegistration getRegistration(int slotId) throws RemoteException {
         synchronized (mLock) {
-            return mIImsServiceController.getRegistration(slotId);
+            return isServiceControllerAvailable()
+                    ? mIImsServiceController.getRegistration(slotId) : null;
         }
     }
 
@@ -521,7 +559,20 @@
      */
     public IImsConfig getConfig(int slotId) throws RemoteException {
         synchronized (mLock) {
-            return mIImsServiceController.getConfig(slotId);
+            return isServiceControllerAvailable() ? mIImsServiceController.getConfig(slotId) : null;
+        }
+    }
+
+    /**
+     * notify the ImsService that the ImsService is ready for feature creation.
+     */
+    protected void notifyImsServiceReady() throws RemoteException {
+        synchronized (mLock) {
+            if (isServiceControllerAvailable()) {
+                Log.d(LOG_TAG, "notifyImsServiceReady");
+                mIImsServiceController.setListener(mFeatureChangedListener);
+                mIImsServiceController.notifyImsServiceReadyForFeatureCreation();
+            }
         }
     }
 
@@ -538,6 +589,15 @@
     }
 
     /**
+     * @return true if the controller is currently bound.
+     */
+    public boolean isBound() {
+        synchronized (mLock) {
+            return mIsBound;
+        }
+    }
+
+    /**
      * Check to see if the service controller is available, overridden for compat versions,
      * @return true if available, false otherwise;
      */
@@ -545,10 +605,9 @@
         return mIImsServiceController != null;
     }
 
-    private void removeImsServiceFeatureListener() {
-        synchronized (mLock) {
+    @VisibleForTesting
+    public void removeImsServiceFeatureCallbacks() {
             mImsStatusCallbacks.clear();
-        }
     }
 
     // Only add a new rebind if there are no pending rebinds waiting.
@@ -572,97 +631,110 @@
     }
 
     private void sendImsFeatureCreatedCallback(int slot, int feature) {
-        synchronized (mLock) {
-            for (Iterator<IImsServiceFeatureCallback> i = mImsStatusCallbacks.iterator();
-                    i.hasNext(); ) {
-                IImsServiceFeatureCallback callbacks = i.next();
-                try {
-                    callbacks.imsFeatureCreated(slot, feature);
-                } catch (RemoteException e) {
-                    // binder died, remove callback.
-                    Log.w(LOG_TAG, "sendImsFeatureCreatedCallback: Binder died, removing "
-                            + "callback. Exception:" + e.getMessage());
-                    i.remove();
-                }
+        for (Iterator<IImsServiceFeatureCallback> i = mImsStatusCallbacks.iterator();
+                i.hasNext(); ) {
+            IImsServiceFeatureCallback callbacks = i.next();
+            try {
+                callbacks.imsFeatureCreated(slot, feature);
+            } catch (RemoteException e) {
+                // binder died, remove callback.
+                Log.w(LOG_TAG, "sendImsFeatureCreatedCallback: Binder died, removing "
+                        + "callback. Exception:" + e.getMessage());
+                i.remove();
             }
         }
     }
 
     private void sendImsFeatureRemovedCallback(int slot, int feature) {
-        synchronized (mLock) {
-            for (Iterator<IImsServiceFeatureCallback> i = mImsStatusCallbacks.iterator();
-                    i.hasNext(); ) {
-                IImsServiceFeatureCallback callbacks = i.next();
-                try {
-                    callbacks.imsFeatureRemoved(slot, feature);
-                } catch (RemoteException e) {
-                    // binder died, remove callback.
-                    Log.w(LOG_TAG, "sendImsFeatureRemovedCallback: Binder died, removing "
-                            + "callback. Exception:" + e.getMessage());
-                    i.remove();
-                }
+        for (Iterator<IImsServiceFeatureCallback> i = mImsStatusCallbacks.iterator();
+                i.hasNext(); ) {
+            IImsServiceFeatureCallback callbacks = i.next();
+            try {
+                callbacks.imsFeatureRemoved(slot, feature);
+            } catch (RemoteException e) {
+                // binder died, remove callback.
+                Log.w(LOG_TAG, "sendImsFeatureRemovedCallback: Binder died, removing "
+                        + "callback. Exception:" + e.getMessage());
+                i.remove();
             }
         }
     }
 
     private void sendImsFeatureStatusChanged(int slot, int feature, int status) {
-        synchronized (mLock) {
-            for (Iterator<IImsServiceFeatureCallback> i = mImsStatusCallbacks.iterator();
-                    i.hasNext(); ) {
-                IImsServiceFeatureCallback callbacks = i.next();
-                try {
-                    callbacks.imsStatusChanged(slot, feature, status);
-                } catch (RemoteException e) {
-                    // binder died, remove callback.
-                    Log.w(LOG_TAG, "sendImsFeatureStatusChanged: Binder died, removing "
-                            + "callback. Exception:" + e.getMessage());
-                    i.remove();
-                }
+        for (Iterator<IImsServiceFeatureCallback> i = mImsStatusCallbacks.iterator();
+                i.hasNext(); ) {
+            IImsServiceFeatureCallback callbacks = i.next();
+            try {
+                callbacks.imsStatusChanged(slot, feature, status);
+            } catch (RemoteException e) {
+                // binder died, remove callback.
+                Log.w(LOG_TAG, "sendImsFeatureStatusChanged: Binder died, removing "
+                        + "callback. Exception:" + e.getMessage());
+                i.remove();
             }
         }
     }
 
     // This method should only be called when synchronized on mLock
-    private void addImsServiceFeature(Pair<Integer, Integer> featurePair) throws RemoteException {
+    private void addImsServiceFeature(ImsFeatureConfiguration.FeatureSlotPair featurePair)
+            throws RemoteException {
         if (!isServiceControllerAvailable() || mCallbacks == null) {
             Log.w(LOG_TAG, "addImsServiceFeature called with null values.");
             return;
         }
-        ImsFeatureStatusCallback c = new ImsFeatureStatusCallback(featurePair.first,
-                featurePair.second);
-        mFeatureStatusCallbacks.add(c);
-        IInterface f = createImsFeature(featurePair.first, featurePair.second, c.getCallback());
-        addImsFeatureBinder(featurePair.first, featurePair.second, f);
-        // Signal ImsResolver to change supported ImsFeatures for this ImsServiceController
-        mCallbacks.imsServiceFeatureCreated(featurePair.first, featurePair.second, this);
-        // Send callback to ImsServiceProxy to change supported ImsFeatures
-        sendImsFeatureCreatedCallback(featurePair.first, featurePair.second);
+        if (featurePair.featureType != ImsFeature.FEATURE_EMERGENCY_MMTEL) {
+            ImsFeatureStatusCallback c = new ImsFeatureStatusCallback(featurePair.slotId,
+                    featurePair.featureType);
+            mFeatureStatusCallbacks.add(c);
+            IInterface f = createImsFeature(featurePair.slotId, featurePair.featureType,
+                    c.getCallback());
+            addImsFeatureBinder(featurePair.slotId, featurePair.featureType, f);
+            // Signal ImsResolver to change supported ImsFeatures for this ImsServiceController
+            mCallbacks.imsServiceFeatureCreated(featurePair.slotId, featurePair.featureType, this);
+        } else {
+            // Don't update ImsService for emergency MMTEL feature.
+            Log.i(LOG_TAG, "supports emergency calling on slot " + featurePair.slotId);
+        }
+        // Send callback to ImsServiceProxy to change supported ImsFeatures including emergency
+        // MMTEL state.
+        sendImsFeatureCreatedCallback(featurePair.slotId, featurePair.featureType);
     }
 
     // This method should only be called when synchronized on mLock
-    private void removeImsServiceFeature(Pair<Integer, Integer> featurePair)
-            throws RemoteException {
+    private void removeImsServiceFeature(ImsFeatureConfiguration.FeatureSlotPair featurePair) {
         if (!isServiceControllerAvailable() || mCallbacks == null) {
             Log.w(LOG_TAG, "removeImsServiceFeature called with null values.");
             return;
         }
-        ImsFeatureStatusCallback callbackToRemove = mFeatureStatusCallbacks.stream().filter(c ->
-                c.mSlotId == featurePair.first && c.mFeatureType == featurePair.second)
-                .findFirst().orElse(null);
-        // Remove status callbacks from list.
-        if (callbackToRemove != null) {
-            mFeatureStatusCallbacks.remove(callbackToRemove);
+        if (featurePair.featureType != ImsFeature.FEATURE_EMERGENCY_MMTEL) {
+            ImsFeatureStatusCallback callbackToRemove = mFeatureStatusCallbacks.stream().filter(c ->
+                    c.mSlotId == featurePair.slotId && c.mFeatureType == featurePair.featureType)
+                    .findFirst().orElse(null);
+            // Remove status callbacks from list.
+            if (callbackToRemove != null) {
+                mFeatureStatusCallbacks.remove(callbackToRemove);
+            }
+            removeImsFeatureBinder(featurePair.slotId, featurePair.featureType);
+            // Signal ImsResolver to change supported ImsFeatures for this ImsServiceController
+            mCallbacks.imsServiceFeatureRemoved(featurePair.slotId, featurePair.featureType, this);
+            try {
+                removeImsFeature(featurePair.slotId, featurePair.featureType,
+                        (callbackToRemove != null ? callbackToRemove.getCallback() : null));
+            } catch (RemoteException e) {
+                // The connection to this ImsService doesn't exist. This may happen if the service
+                // has died and we are removing features.
+                Log.i(LOG_TAG, "Couldn't remove feature {" + featurePair.featureType
+                        + "}, connection is down: " + e.getMessage());
+            }
+        } else {
+            // Don't update ImsService for emergency MMTEL feature.
+            Log.i(LOG_TAG, "doesn't support emergency calling on slot " + featurePair.slotId);
         }
-        removeImsFeature(featurePair.first, featurePair.second,
-                (callbackToRemove != null ? callbackToRemove.getCallback() : null));
-        removeImsFeatureBinder(featurePair.first, featurePair.second);
-        // Signal ImsResolver to change supported ImsFeatures for this ImsServiceController
-        mCallbacks.imsServiceFeatureRemoved(featurePair.first, featurePair.second, this);
         // Send callback to ImsServiceProxy to change supported ImsFeatures
         // Ensure that ImsServiceProxy callback occurs after ImsResolver callback. If an
         // ImsManager requests the ImsService while it is being removed in ImsResolver, this
         // callback will clean it up after.
-        sendImsFeatureRemovedCallback(featurePair.first, featurePair.second);
+        sendImsFeatureRemovedCallback(featurePair.slotId, featurePair.featureType);
     }
 
     // This method should only be called when already synchronized on mLock.
@@ -708,16 +780,15 @@
                 .findFirst().orElse(null);
     }
 
-    private void notifyAllFeaturesRemoved() {
-        if (mCallbacks == null) {
-            Log.w(LOG_TAG, "notifyAllFeaturesRemoved called with invalid callbacks.");
-            return;
-        }
+    private void cleanupAllFeatures() {
         synchronized (mLock) {
-            for (Pair<Integer, Integer> feature : mImsFeatures) {
-                mCallbacks.imsServiceFeatureRemoved(feature.first, feature.second, this);
-                sendImsFeatureRemovedCallback(feature.first, feature.second);
+            // Remove all features and clean up all associated Binders.
+            for (ImsFeatureConfiguration.FeatureSlotPair i : mImsFeatures) {
+                removeImsServiceFeature(i);
             }
+            // remove all MmTelFeatureConnection callbacks, since we have already sent removed
+            // callback.
+            removeImsServiceFeatureCallbacks();
         }
     }
 
@@ -727,7 +798,6 @@
             mImsServiceConnection = null;
             mImsServiceControllerBinder = null;
             setServiceController(null);
-            mIsBound = false;
         }
     }
 }
diff --git a/src/java/com/android/internal/telephony/ims/ImsServiceControllerCompat.java b/src/java/com/android/internal/telephony/ims/ImsServiceControllerCompat.java
index 094be71..4e0aea0 100644
--- a/src/java/com/android/internal/telephony/ims/ImsServiceControllerCompat.java
+++ b/src/java/com/android/internal/telephony/ims/ImsServiceControllerCompat.java
@@ -61,7 +61,7 @@
     }
 
     @Override
-    protected String getServiceInterface() {
+    protected final String getServiceInterface() {
         // Return compatibility version of String.
         return ImsService.SERVICE_INTERFACE;
     }
@@ -70,7 +70,7 @@
      * Converts the new command to {@link MMTelFeature#turnOnIms()}.
      */
     @Override
-    public void enableIms(int slotId) {
+    public final void enableIms(int slotId) {
         MmTelFeatureCompatAdapter adapter = mMmTelCompatAdapters.get(slotId);
         if (adapter == null) {
             Log.w(TAG, "enableIms: adapter null for slot :" + slotId);
@@ -87,7 +87,7 @@
      * Converts the new command to {@link MMTelFeature#turnOffIms()}.
      */
     @Override
-    public void disableIms(int slotId) {
+    public final void disableIms(int slotId) {
         MmTelFeatureCompatAdapter adapter = mMmTelCompatAdapters.get(slotId);
         if (adapter == null) {
             Log.w(TAG, "enableIms: adapter null for slot :" + slotId);
@@ -103,7 +103,8 @@
     /**
      * @return the IImsRegistration that corresponds to the slot id specified.
      */
-    public IImsRegistration getRegistration(int slotId) throws RemoteException {
+    @Override
+    public final IImsRegistration getRegistration(int slotId) {
         ImsRegistrationCompatAdapter adapter = mRegCompatAdapters.get(slotId);
         if (adapter == null) {
             Log.w(TAG, "getRegistration: Registration does not exist for slot " + slotId);
@@ -115,7 +116,8 @@
     /**
      * @return the IImsConfig that corresponds to the slot id specified.
      */
-    public IImsConfig getConfig(int slotId) throws RemoteException {
+    @Override
+    public final IImsConfig getConfig(int slotId) {
         ImsConfigCompatAdapter adapter = mConfigCompatAdapters.get(slotId);
         if (adapter == null) {
             Log.w(TAG, "getConfig: Config does not exist for slot " + slotId);
@@ -125,7 +127,14 @@
     }
 
     @Override
-    protected IInterface createImsFeature(int slotId, int featureType, IImsFeatureStatusCallback c)
+    protected final void notifyImsServiceReady() {
+        Log.d(TAG, "notifyImsServiceReady");
+        // don't do anything for compat impl.
+    }
+
+    @Override
+    protected final IInterface createImsFeature(int slotId, int featureType,
+            IImsFeatureStatusCallback c)
             throws RemoteException {
         switch (featureType) {
             case ImsFeature.MMTEL: {
@@ -140,14 +149,16 @@
     }
 
     @Override
-    protected void removeImsFeature(int slotId, int featureType, IImsFeatureStatusCallback c)
+    protected final void removeImsFeature(int slotId, int featureType, IImsFeatureStatusCallback c)
             throws RemoteException {
         if (featureType == ImsFeature.MMTEL) {
             mMmTelCompatAdapters.remove(slotId);
             mRegCompatAdapters.remove(slotId);
             mConfigCompatAdapters.remove(slotId);
         }
-        mServiceController.removeImsFeature(slotId, featureType, c);
+        if (mServiceController != null) {
+            mServiceController.removeImsFeature(slotId, featureType, c);
+        }
     }
 
     @Override
diff --git a/src/java/com/android/internal/telephony/ims/ImsServiceControllerStaticCompat.java b/src/java/com/android/internal/telephony/ims/ImsServiceControllerStaticCompat.java
index 5b080d1..e39aa52 100644
--- a/src/java/com/android/internal/telephony/ims/ImsServiceControllerStaticCompat.java
+++ b/src/java/com/android/internal/telephony/ims/ImsServiceControllerStaticCompat.java
@@ -20,7 +20,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.os.IBinder;
-import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.util.Log;
 
@@ -66,8 +65,13 @@
     }
 
     @Override
-    protected MmTelInterfaceAdapter getInterface(int slotId, IImsFeatureStatusCallback c)
-            throws RemoteException {
+    // used for add/remove features and cleanup in ImsServiceController.
+    protected boolean isServiceControllerAvailable() {
+        return mImsServiceCompat != null;
+    }
+
+    @Override
+    protected MmTelInterfaceAdapter getInterface(int slotId, IImsFeatureStatusCallback c) {
         if (mImsServiceCompat == null) {
             Log.w(TAG, "getInterface: IImsService returned null.");
             return null;
diff --git a/src/java/com/android/internal/telephony/ims/ImsServiceFeatureQueryManager.java b/src/java/com/android/internal/telephony/ims/ImsServiceFeatureQueryManager.java
new file mode 100644
index 0000000..24ec0be
--- /dev/null
+++ b/src/java/com/android/internal/telephony/ims/ImsServiceFeatureQueryManager.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony.ims;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.telephony.ims.aidl.IImsServiceController;
+import android.telephony.ims.stub.ImsFeatureConfiguration;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Manages the querying of multiple ImsServices asynchronously in order to retrieve the ImsFeatures
+ * they support.
+ */
+
+public class ImsServiceFeatureQueryManager {
+
+    private final class ImsServiceFeatureQuery implements ServiceConnection {
+
+        private static final String LOG_TAG = "ImsServiceFeatureQuery";
+
+        private final ComponentName mName;
+        private final String mIntentFilter;
+
+        ImsServiceFeatureQuery(ComponentName name, String intentFilter) {
+            mName = name;
+            mIntentFilter = intentFilter;
+        }
+
+        /**
+         * Starts the bind to the ImsService specified ComponentName.
+         * @return true if binding started, false if it failed and will not recover.
+         */
+        public boolean start() {
+            Log.d(LOG_TAG, "start: intent filter=" + mIntentFilter + ", name=" + mName);
+            Intent imsServiceIntent = new Intent(mIntentFilter).setComponent(mName);
+            int serviceFlags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
+                    | Context.BIND_IMPORTANT;
+            boolean bindStarted = mContext.bindService(imsServiceIntent, this, serviceFlags);
+            if (!bindStarted) {
+                // Docs say to unbind if this fails.
+                cleanup();
+            }
+            return bindStarted;
+        }
+
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            Log.i(LOG_TAG, "onServiceConnected for component: " + name);
+            if (service != null) {
+                queryImsFeatures(IImsServiceController.Stub.asInterface(service));
+            } else {
+                Log.w(LOG_TAG, "onServiceConnected: " + name + " binder null, cleaning up.");
+                cleanup();
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            Log.w(LOG_TAG, "onServiceDisconnected for component: " + name);
+        }
+
+        private void queryImsFeatures(IImsServiceController controller) {
+            ImsFeatureConfiguration config;
+            try {
+                config = controller.querySupportedImsFeatures();
+            } catch (RemoteException e) {
+                Log.w(LOG_TAG, "queryImsFeatures - error: " + e);
+                cleanup();
+                mListener.onError(mName);
+                return;
+            }
+            Set<ImsFeatureConfiguration.FeatureSlotPair> servicePairs = config.getServiceFeatures();
+            // Complete, remove from active queries and notify.
+            cleanup();
+            mListener.onComplete(mName, servicePairs);
+        }
+
+        private void cleanup() {
+            mContext.unbindService(this);
+            synchronized (mLock) {
+                mActiveQueries.remove(mName);
+            }
+        }
+    }
+
+    public interface Listener {
+        /**
+         * Called when a query has completed.
+         * @param name The Package Name of the query
+         * @param features A Set of slotid->feature pairs that the ImsService supports.
+         */
+        void onComplete(ComponentName name, Set<ImsFeatureConfiguration.FeatureSlotPair> features);
+
+        /**
+         * Called when a query has failed and should be retried.
+         */
+        void onError(ComponentName name);
+    }
+
+    // Maps an active ImsService query (by Package Name String) its query.
+    private final Map<ComponentName, ImsServiceFeatureQuery> mActiveQueries = new HashMap<>();
+    private final Context mContext;
+    private final Listener mListener;
+    private final Object mLock = new Object();
+
+    public ImsServiceFeatureQueryManager(Context context, Listener listener) {
+        mContext = context;
+        mListener = listener;
+    }
+
+    /**
+     * Starts an ImsService feature query for the ComponentName and Intent specified.
+     * @param name The ComponentName of the ImsService being queried.
+     * @param intentFilter The Intent filter that the ImsService specified.
+     * @return true if the query started, false if it was unable to start.
+     */
+    public boolean startQuery(ComponentName name, String intentFilter) {
+        synchronized (mLock) {
+            if (mActiveQueries.containsKey(name)) {
+                // We already have an active query, wait for it to return.
+                return true;
+            }
+            ImsServiceFeatureQuery query = new ImsServiceFeatureQuery(name, intentFilter);
+            mActiveQueries.put(name, query);
+            return query.start();
+        }
+    }
+
+    /**
+     * @return true if there are any active queries, false if the manager is idle.
+     */
+    public boolean isQueryInProgress() {
+        synchronized (mLock) {
+            return !mActiveQueries.isEmpty();
+        }
+    }
+}
diff --git a/src/java/com/android/internal/telephony/ims/MmTelFeatureCompatAdapter.java b/src/java/com/android/internal/telephony/ims/MmTelFeatureCompatAdapter.java
index a9653d5..7b0619b 100644
--- a/src/java/com/android/internal/telephony/ims/MmTelFeatureCompatAdapter.java
+++ b/src/java/com/android/internal/telephony/ims/MmTelFeatureCompatAdapter.java
@@ -155,6 +155,10 @@
 
         @Override
         public void registrationDisconnected(ImsReasonInfo imsReasonInfo) throws RemoteException {
+            // At de-registration, notify the framework that no IMS capabilities are currently
+            // available.
+            Log.i(TAG, "registrationDisconnected: resetting MMTEL capabilities.");
+            notifyCapabilitiesStatusChanged(new MmTelCapabilities());
             // Implemented in the Registration Adapter
         }
 
@@ -510,7 +514,7 @@
 
     private PendingIntent createIncomingCallPendingIntent() {
         Intent intent = new Intent(ImsManager.ACTION_IMS_INCOMING_CALL);
-        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+        intent.setPackage(TelephonyManager.PHONE_PROCESS_NAME);
         return PendingIntent.getBroadcast(mContext, 0, intent,
                 PendingIntent.FLAG_UPDATE_CURRENT);
     }
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhone.java b/src/java/com/android/internal/telephony/imsphone/ImsPhone.java
index 69c03f6..0a86867 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhone.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhone.java
@@ -689,7 +689,7 @@
             return mCT.dial(dialString, imsDialArgsBuilder.build());
         } else if (mmi.isTemporaryModeCLIR()) {
             imsDialArgsBuilder.setClirMode(mmi.getCLIRMode());
-            return mCT.dial(dialString, imsDialArgsBuilder.build());
+            return mCT.dial(mmi.getDialingNumber(), imsDialArgsBuilder.build());
         } else if (!mmi.isSupportedOverImsPhone()) {
             // If the mmi is not supported by IMS service,
             // try to initiate dialing with default phone
@@ -1279,12 +1279,12 @@
 
     private CallForwardInfo getCallForwardInfo(ImsCallForwardInfo info) {
         CallForwardInfo cfInfo = new CallForwardInfo();
-        cfInfo.status = info.mStatus;
-        cfInfo.reason = getCFReasonFromCondition(info.mCondition);
+        cfInfo.status = info.getStatus();
+        cfInfo.reason = getCFReasonFromCondition(info.getCondition());
         cfInfo.serviceClass = SERVICE_CLASS_VOICE;
-        cfInfo.toa = info.mToA;
-        cfInfo.number = info.mNumber;
-        cfInfo.timeSeconds = info.mTimeSeconds;
+        cfInfo.toa = info.getToA();
+        cfInfo.number = info.getNumber();
+        cfInfo.timeSeconds = info.getTimeSeconds();
         return cfInfo;
     }
 
@@ -1308,10 +1308,10 @@
             }
         } else {
             for (int i = 0, s = infos.length; i < s; i++) {
-                if (infos[i].mCondition == ImsUtInterface.CDIV_CF_UNCONDITIONAL) {
+                if (infos[i].getCondition() == ImsUtInterface.CDIV_CF_UNCONDITIONAL) {
                     if (r != null) {
-                        setVoiceCallForwardingFlag(r, 1, (infos[i].mStatus == 1),
-                            infos[i].mNumber);
+                        setVoiceCallForwardingFlag(r, 1, (infos[i].getStatus() == 1),
+                                infos[i].getNumber());
                     }
                 }
                 cfInfos[i] = getCallForwardInfo(infos[i]);
@@ -1325,7 +1325,7 @@
         int[] cbInfos = new int[1];
         cbInfos[0] = SERVICE_CLASS_NONE;
 
-        if (infos[0].mStatus == 1) {
+        if (infos[0].getStatus() == 1) {
             cbInfos[0] = SERVICE_CLASS_VOICE;
         }
 
@@ -1336,7 +1336,7 @@
         int[] cwInfos = new int[2];
         cwInfos[0] = 0;
 
-        if (infos[0].mStatus == 1) {
+        if (infos[0].getStatus() == 1) {
             cwInfos[0] = 1;
             cwInfos[1] = SERVICE_CLASS_VOICE;
         }
@@ -1432,8 +1432,14 @@
                 ServiceState newServiceState = (ServiceState) ar.result;
                 // only update if roaming status changed
                 if (mRoaming != newServiceState.getRoaming()) {
-                    if (DBG) logd("Roaming state changed");
-                    updateRoamingState(newServiceState.getRoaming());
+                    if (DBG) logd("Roaming state changed - " + mRoaming);
+                    // Update WFC mode only if voice or data is in service.
+                    // The STATE_IN_SERVICE is checked to prevent wifi calling mode change
+                    // when phone moves from roaming to no service.
+                    boolean isInService =
+                            (newServiceState.getVoiceRegState() == ServiceState.STATE_IN_SERVICE ||
+                            newServiceState.getDataRegState() == ServiceState.STATE_IN_SERVICE);
+                    updateRoamingState(newServiceState.getRoaming(), isInService);
                 }
                 break;
             case EVENT_VOICE_CALL_ENDED:
@@ -1442,7 +1448,7 @@
                 // only update if roaming status changed
                 boolean newRoaming = getCurrentRoaming();
                 if (mRoaming != newRoaming) {
-                    updateRoamingState(newRoaming);
+                    updateRoamingState(newRoaming, true);
                 }
                 break;
 
@@ -1804,12 +1810,16 @@
         return mCT.getVtDataUsage(perUidStats);
     }
 
-    private void updateRoamingState(boolean newRoaming) {
+    private void updateRoamingState(boolean newRoaming, boolean isInService) {
         if (mCT.getState() == PhoneConstants.State.IDLE) {
             if (DBG) logd("updateRoamingState now: " + newRoaming);
             mRoaming = newRoaming;
-            ImsManager imsManager = ImsManager.getInstance(mContext, mPhoneId);
-            imsManager.setWfcMode(imsManager.getWfcMode(newRoaming), newRoaming);
+            if (isInService) {
+                ImsManager imsManager = ImsManager.getInstance(mContext, mPhoneId);
+                imsManager.setWfcMode(imsManager.getWfcMode(newRoaming), newRoaming);
+            } else {
+                if (DBG) Rlog.d(LOG_TAG, "updateRoamingState service state is OUT_OF_SERVICE");
+            }
         } else {
             if (DBG) logd("updateRoamingState postponed: " + newRoaming);
             mCT.registerForVoiceCallEnded(this,
@@ -1820,7 +1830,7 @@
     private boolean getCurrentRoaming() {
         TelephonyManager tm = (TelephonyManager) mContext
                 .getSystemService(Context.TELEPHONY_SERVICE);
-        return tm.isNetworkRoaming();
+        return tm.isNetworkRoaming(getSubId());
     }
 
     @Override
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
index ce6822e..460ad95 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
@@ -1537,6 +1537,16 @@
         return mDesiredMute;
     }
 
+    /**
+     * Sends a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2833</a>,
+     * event 0 ~ 9 maps to decimal value 0 ~ 9, '*' to 10, '#' to 11, event 'A' ~ 'D' to 12 ~ 15,
+     * and event flash to 16. Currently, event flash is not supported.
+     *
+     * @param c that represents the DTMF to send. '0' ~ '9', 'A' ~ 'D', '*', '#' are valid inputs.
+     * @param result the result message to send when done. If non-null, the {@link Message} must
+     *         contain a valid {@link android.os.Messenger} in the {@link Message#replyTo} field,
+     *         since this can be used across IPC boundaries.
+     */
     public void sendDtmf(char c, Message result) {
         if (DBG) log("sendDtmf");
 
@@ -2258,7 +2268,8 @@
                 if (mRingingCall.getState().isRinging()) {
                     // Drop pending MO. We should address incoming call first
                     mPendingMO = null;
-                } else if (mPendingMO != null) {
+                } else if (mPendingMO != null
+                        && mPendingMO.getDisconnectCause() == DisconnectCause.NOT_DISCONNECTED) {
                     sendEmptyMessage(EVENT_DIAL_PENDINGMO);
                 }
             }
@@ -2373,6 +2384,9 @@
                         mPendingMO.setDisconnectCause(DisconnectCause.ERROR_UNSPECIFIED);
                         sendEmptyMessageDelayed(EVENT_HANGUP_PENDINGMO, TIMEOUT_HANGUP_PENDINGMO);
                     }
+                    if (imsCall != mCallExpectedToResume) {
+                        mCallExpectedToResume = null;
+                    }
                 }
                 mPhone.notifySuppServiceFailed(Phone.SuppService.HOLD);
             }
@@ -2545,6 +2559,8 @@
             // based on the user facing UI.
             mPhone.notifySuppServiceFailed(Phone.SuppService.CONFERENCE);
 
+            call.resetIsMergeRequestedByConf(false);
+
             // Start plumbing this even through Telecom so other components can take
             // appropriate action.
             ImsPhoneConnection conn = findConnection(call);
@@ -2670,10 +2686,6 @@
             ImsPhoneConnection conn = findConnection(imsCall);
             if (conn != null) {
                 conn.onRttModifyResponseReceived(status);
-                if (status ==
-                        android.telecom.Connection.RttModifyStatus.SESSION_MODIFY_REQUEST_SUCCESS) {
-                    conn.startRttTextProcessing();
-                }
             }
         }
 
@@ -2793,7 +2805,6 @@
                 @Override
                 public void onDeregistered(ImsReasonInfo imsReasonInfo) {
                     if (DBG) log("onImsDisconnected imsReasonInfo=" + imsReasonInfo);
-                    resetImsCapabilities();
                     mPhone.setServiceState(ServiceState.STATE_OUT_OF_SERVICE);
                     mPhone.setImsRegistered(false);
                     mPhone.processDisconnectReason(imsReasonInfo);
@@ -3580,7 +3591,8 @@
         if (imsCall != null) {
             if (conn.hasCapabilities(
                     Connection.Capability.SUPPORTS_DOWNGRADE_TO_VOICE_LOCAL |
-                            Connection.Capability.SUPPORTS_DOWNGRADE_TO_VOICE_REMOTE)) {
+                            Connection.Capability.SUPPORTS_DOWNGRADE_TO_VOICE_REMOTE)
+                            && !mSupportPauseVideo) {
 
                 // If the carrier supports downgrading to voice, then we can simply issue a
                 // downgrade to voice instead of terminating the call.
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCommandInterface.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCommandInterface.java
index 76cc26d..93a6d3e 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCommandInterface.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCommandInterface.java
@@ -404,6 +404,14 @@
     }
 
     @Override
+    public void invokeOemRilRequestRaw(byte[] data, Message response) {
+    }
+
+    @Override
+    public void invokeOemRilRequestStrings(String[] strings, Message response) {
+    }
+
+    @Override
     public void setBandMode (int bandMode, Message response) {
     }
 
@@ -645,6 +653,17 @@
     }
 
     @Override
+    public void setSignalStrengthReportingCriteria(int hysteresisMs, int hysteresisDb,
+            int[] thresholdsDbm, int ran, Message result) {
+    }
+
+    @Override
+    public void setLinkCapacityReportingCriteria(int hysteresisMs, int hysteresisDlKbps,
+            int hysteresisUlKbps, int[] thresholdsDlKbps, int[] thresholdsUlKbps, int ran,
+            Message result) {
+    }
+
+    @Override
     public void setSimCardPower(int state, Message result) {
     }
 
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java
index 2448ea6..d63b9c2 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java
@@ -23,6 +23,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.os.Messenger;
 import android.os.PersistableBundle;
 import android.os.PowerManager;
 import android.os.Registrant;
@@ -33,12 +34,12 @@
 import android.telephony.PhoneNumberUtils;
 import android.telephony.Rlog;
 import android.telephony.ServiceState;
+import android.telephony.ims.ImsCallProfile;
+import android.telephony.ims.ImsStreamMediaProfile;
 import android.text.TextUtils;
 
 import com.android.ims.ImsCall;
-import android.telephony.ims.ImsCallProfile;
 import com.android.ims.ImsException;
-import android.telephony.ims.ImsStreamMediaProfile;
 import com.android.ims.internal.ImsVideoCallProviderWrapper;
 import com.android.internal.telephony.CallStateException;
 import com.android.internal.telephony.Connection;
@@ -79,6 +80,7 @@
 
     private UUSInfo mUusInfo;
     private Handler mHandler;
+    private Messenger mHandlerMessenger;
 
     private PowerManager.WakeLock mPartialWakeLock;
 
@@ -171,6 +173,7 @@
 
         mOwner = ct;
         mHandler = new MyHandler(mOwner.getLooper());
+        mHandlerMessenger = new Messenger(mHandler);
         mImsCall = imsCall;
 
         if ((imsCall != null) && (imsCall.getCallProfile() != null)) {
@@ -495,7 +498,9 @@
     private boolean
     processPostDialChar(char c) {
         if (PhoneNumberUtils.is12Key(c)) {
-            mOwner.sendDtmf(c, mHandler.obtainMessage(EVENT_DTMF_DONE));
+            Message dtmfComplete = mHandler.obtainMessage(EVENT_DTMF_DONE);
+            dtmfComplete.replyTo = mHandlerMessenger;
+            mOwner.sendDtmf(c, dtmfComplete);
         } else if (c == PhoneNumberUtils.PAUSE) {
             // From TS 22.101:
             // It continues...
@@ -985,18 +990,35 @@
         imsCall.sendRttModifyResponse(accept);
         if (accept) {
             setCurrentRttTextStream(textStream);
-            startRttTextProcessing();
         } else {
             Rlog.e(LOG_TAG, "sendRttModifyResponse: foreground call has no connections");
         }
     }
 
     public void onRttMessageReceived(String message) {
-        getOrCreateRttTextHandler().sendToInCall(message);
+        synchronized (this) {
+            if (mRttTextHandler == null) {
+                Rlog.w(LOG_TAG, "onRttMessageReceived: RTT text handler not available."
+                        + " Attempting to create one.");
+                if (mRttTextStream == null) {
+                    Rlog.e(LOG_TAG, "onRttMessageReceived:"
+                            + " Unable to process incoming message. No textstream available");
+                    return;
+                }
+                createRttTextHandler();
+            }
+        }
+        mRttTextHandler.sendToInCall(message);
     }
 
     public void setCurrentRttTextStream(android.telecom.Connection.RttTextStream rttTextStream) {
-        mRttTextStream = rttTextStream;
+        synchronized (this) {
+            mRttTextStream = rttTextStream;
+            if (mRttTextHandler == null && mIsRttEnabledForCall) {
+                Rlog.i(LOG_TAG, "setCurrentRttTextStream: Creating a text handler");
+                createRttTextHandler();
+            }
+        }
     }
 
     public boolean hasRttTextStream() {
@@ -1008,20 +1030,24 @@
     }
 
     public void startRttTextProcessing() {
-        if (mRttTextStream == null) {
-            Rlog.w(LOG_TAG, "startRttTextProcessing: no RTT text stream. Ignoring.");
-            return;
+        synchronized (this) {
+            if (mRttTextStream == null) {
+                Rlog.w(LOG_TAG, "startRttTextProcessing: no RTT text stream. Ignoring.");
+                return;
+            }
+            if (mRttTextHandler != null) {
+                Rlog.w(LOG_TAG, "startRttTextProcessing: RTT text handler already exists");
+                return;
+            }
+            createRttTextHandler();
         }
-        getOrCreateRttTextHandler().initialize(mRttTextStream);
     }
 
-    private ImsRttTextHandler getOrCreateRttTextHandler() {
-        if (mRttTextHandler != null) {
-            return mRttTextHandler;
-        }
+    // Make sure to synchronize on ImsPhoneConnection.this before calling.
+    private void createRttTextHandler() {
         mRttTextHandler = new ImsRttTextHandler(Looper.getMainLooper(),
                 (message) -> getImsCall().sendRttMessage(message));
-        return mRttTextHandler;
+        mRttTextHandler.initialize(mRttTextStream);
     }
 
     /**
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneMmiCode.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneMmiCode.java
index a72b904..87e51ec 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneMmiCode.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneMmiCode.java
@@ -36,13 +36,13 @@
 import android.os.ResultReceiver;
 import android.telephony.PhoneNumberUtils;
 import android.telephony.Rlog;
+import android.telephony.ims.ImsReasonInfo;
+import android.telephony.ims.ImsSsData;
+import android.telephony.ims.ImsSsInfo;
 import android.text.SpannableStringBuilder;
 import android.text.TextUtils;
 
 import com.android.ims.ImsException;
-import android.telephony.ims.ImsReasonInfo;
-import android.telephony.ims.ImsSsData;
-import android.telephony.ims.ImsSsInfo;
 import com.android.ims.ImsUtInterface;
 import com.android.internal.telephony.CallForwardInfo;
 import com.android.internal.telephony.CallStateException;
@@ -50,7 +50,6 @@
 import com.android.internal.telephony.CommandsInterface;
 import com.android.internal.telephony.MmiCode;
 import com.android.internal.telephony.Phone;
-import com.android.internal.telephony.RILConstants;
 import com.android.internal.telephony.uicc.IccRecords;
 
 import java.util.regex.Matcher;
@@ -1278,10 +1277,18 @@
                 sb.append(mContext.getText(
                         com.android.internal.R.string.serviceEnabled));
             }
+            // Record CLIR setting
+            if (mSc.equals(SC_CLIR)) {
+                mPhone.saveClirSetting(CommandsInterface.CLIR_INVOCATION);
+            }
         } else if (isDeactivate()) {
             mState = State.COMPLETE;
             sb.append(mContext.getText(
                     com.android.internal.R.string.serviceDisabled));
+            // Record CLIR setting
+            if (mSc.equals(SC_CLIR)) {
+                mPhone.saveClirSetting(CommandsInterface.CLIR_SUPPRESSION);
+            }
         } else if (isRegister()) {
             mState = State.COMPLETE;
             sb.append(mContext.getText(
@@ -1472,11 +1479,11 @@
                 ssInfo = (ImsSsInfo) ssInfoResp.getParcelable(UT_BUNDLE_KEY_SSINFO);
                 if (ssInfo != null) {
                     Rlog.d(LOG_TAG,
-                            "onSuppSvcQueryComplete: ImsSsInfo mStatus = " + ssInfo.mStatus);
-                    if (ssInfo.mStatus == ImsSsInfo.DISABLED) {
+                            "onSuppSvcQueryComplete: ImsSsInfo mStatus = " + ssInfo.getStatus());
+                    if (ssInfo.getStatus() == ImsSsInfo.DISABLED) {
                         sb.append(mContext.getText(com.android.internal.R.string.serviceDisabled));
                         mState = State.COMPLETE;
-                    } else if (ssInfo.mStatus == ImsSsInfo.ENABLED) {
+                    } else if (ssInfo.getStatus() == ImsSsInfo.ENABLED) {
                         sb.append(mContext.getText(com.android.internal.R.string.serviceEnabled));
                         mState = State.COMPLETE;
                     } else {
@@ -1524,10 +1531,10 @@
                 sb.append(mContext.getText(com.android.internal.R.string.serviceDisabled));
             } else {
                 for (int i = 0, s = infos.length; i < s ; i++) {
-                    if (infos[i].mIcbNum !=null) {
-                        sb.append("Num: " + infos[i].mIcbNum + " status: "
-                                + infos[i].mStatus + "\n");
-                    } else if (infos[i].mStatus == 1) {
+                    if (infos[i].getIcbNum() != null) {
+                        sb.append("Num: " + infos[i].getIcbNum() + " status: "
+                                + infos[i].getStatus() + "\n");
+                    } else if (infos[i].getStatus() == 1) {
                         sb.append(mContext.getText(com.android.internal
                                 .R.string.serviceEnabled));
                     } else {
@@ -1718,7 +1725,7 @@
     }
 
     void parseSsData(ImsSsData ssData) {
-        ImsException ex = (ssData.result != RILConstants.SUCCESS)
+        ImsException ex = (ssData.result != ImsSsData.RESULT_SUCCESS)
                 ? new ImsException(null, ssData.result) : null;
         mSc = getScStringFromScType(ssData.serviceType);
         mAction = getActionStringFromReqType(ssData.requestType);
@@ -1729,7 +1736,7 @@
             case ImsSsData.SS_DEACTIVATION:
             case ImsSsData.SS_REGISTRATION:
             case ImsSsData.SS_ERASURE:
-                if ((ssData.result == RILConstants.SUCCESS)
+                if ((ssData.result == ImsSsData.RESULT_SUCCESS)
                         && ssData.isTypeUnConditional()) {
                     /*
                      * When ssData.serviceType is unconditional (SS_CFU or SS_CF_ALL) and
@@ -1750,30 +1757,31 @@
                         Rlog.e(LOG_TAG, "setCallForwardingFlag aborted. sim records is null.");
                     }
                 }
-                onSetComplete(null, new AsyncResult(null, ssData.cfInfo, ex));
+                onSetComplete(null, new AsyncResult(null, ssData.getCallForwardInfo(), ex));
                 break;
             case ImsSsData.SS_INTERROGATION:
                 if (ssData.isTypeClir()) {
                     Rlog.d(LOG_TAG, "CLIR INTERROGATION");
                     Bundle clirInfo = new Bundle();
-                    clirInfo.putIntArray(UT_BUNDLE_KEY_CLIR, ssData.ssInfo);
+                    clirInfo.putIntArray(UT_BUNDLE_KEY_CLIR, ssData.getSuppServiceInfo());
                     onQueryClirComplete(new AsyncResult(null, clirInfo, ex));
                 } else if (ssData.isTypeCF()) {
                     Rlog.d(LOG_TAG, "CALL FORWARD INTERROGATION");
                     onQueryCfComplete(new AsyncResult(null, mPhone
-                            .handleCfQueryResult(ssData.cfInfo), ex));
+                            .handleCfQueryResult(ssData.getCallForwardInfo()), ex));
                 } else if (ssData.isTypeBarring()) {
-                    onSuppSvcQueryComplete(new AsyncResult(null, ssData.ssInfo, ex));
+                    onSuppSvcQueryComplete(new AsyncResult(null, ssData.getSuppServiceInfo(), ex));
                 } else if (ssData.isTypeColr() || ssData.isTypeClip() || ssData.isTypeColp()) {
-                    ImsSsInfo ssInfo = new ImsSsInfo();
-                    ssInfo.mStatus = ssData.ssInfo[0];
+                    int[] suppServiceInfo = ssData.getSuppServiceInfo();
+                    ImsSsInfo ssInfo = new ImsSsInfo(suppServiceInfo[0], null);
                     Bundle clInfo = new Bundle();
                     clInfo.putParcelable(UT_BUNDLE_KEY_SSINFO, ssInfo);
                     onSuppSvcQueryComplete(new AsyncResult(null, clInfo, ex));
                 } else if (ssData.isTypeIcb()) {
-                    onIcbQueryComplete(new AsyncResult(null, ssData.imsSsInfo, ex));
+                    onIcbQueryComplete(new AsyncResult(null, ssData.getImsSpecificSuppServiceInfo(),
+                            ex));
                 } else {
-                    onQueryComplete(new AsyncResult(null, ssData.ssInfo, ex));
+                    onQueryComplete(new AsyncResult(null, ssData.getSuppServiceInfo(), ex));
                 }
                 break;
             default:
diff --git a/src/java/com/android/internal/telephony/metrics/SmsSessionEventBuilder.java b/src/java/com/android/internal/telephony/metrics/SmsSessionEventBuilder.java
index 530be1d..5004ce7 100644
--- a/src/java/com/android/internal/telephony/metrics/SmsSessionEventBuilder.java
+++ b/src/java/com/android/internal/telephony/metrics/SmsSessionEventBuilder.java
@@ -88,4 +88,9 @@
         mEvent.format = format;
         return this;
     }
+
+    public SmsSessionEventBuilder setCellBroadcastMessage(SmsSession.Event.CBMessage msg) {
+        mEvent.cellBroadcastMessage = msg;
+        return this;
+    }
 }
diff --git a/src/java/com/android/internal/telephony/metrics/TelephonyMetrics.java b/src/java/com/android/internal/telephony/metrics/TelephonyMetrics.java
index 11ca080..8b9d580 100644
--- a/src/java/com/android/internal/telephony/metrics/TelephonyMetrics.java
+++ b/src/java/com/android/internal/telephony/metrics/TelephonyMetrics.java
@@ -1705,7 +1705,7 @@
      * @param tech SMS RAT
      * @param format SMS format. Either 3GPP or 3GPP2.
      */
-    public void writeRilSendSms(int phoneId, int rilSerial, int tech, int format) {
+    public synchronized void writeRilSendSms(int phoneId, int rilSerial, int tech, int format) {
         InProgressSmsSession smsSession = startNewSmsSessionIfNeeded(phoneId);
 
         smsSession.addEvent(new SmsSessionEventBuilder(SmsSession.Event.Type.SMS_SEND)
@@ -1724,7 +1724,7 @@
      * @param tech SMS RAT
      * @param format SMS format. Either 3GPP or 3GPP2.
      */
-    public void writeRilNewSms(int phoneId, int tech, int format) {
+    public synchronized void writeRilNewSms(int phoneId, int tech, int format) {
         InProgressSmsSession smsSession = startNewSmsSessionIfNeeded(phoneId);
 
         smsSession.addEvent(new SmsSessionEventBuilder(SmsSession.Event.Type.SMS_RECEIVED)
@@ -1736,6 +1736,42 @@
     }
 
     /**
+     * Write incoming Broadcast SMS event
+     *
+     * @param phoneId Phone id
+     * @param format CB msg format
+     * @param priority CB msg priority
+     * @param isCMAS true if msg is CMAS
+     * @param isETWS true if msg is ETWS
+     * @param serviceCategory Service category of CB msg
+     */
+    public synchronized void writeNewCBSms(int phoneId, int format, int priority, boolean isCMAS,
+                                           boolean isETWS, int serviceCategory) {
+        InProgressSmsSession smsSession = startNewSmsSessionIfNeeded(phoneId);
+
+        int type;
+        if (isCMAS) {
+            type = SmsSession.Event.CBMessageType.CMAS;
+        } else if (isETWS) {
+            type = SmsSession.Event.CBMessageType.ETWS;
+        } else {
+            type = SmsSession.Event.CBMessageType.OTHER;
+        }
+
+        SmsSession.Event.CBMessage cbm = new SmsSession.Event.CBMessage();
+        cbm.msgFormat = format;
+        cbm.msgPriority = priority + 1;
+        cbm.msgType = type;
+        cbm.serviceCategory = serviceCategory;
+
+        smsSession.addEvent(new SmsSessionEventBuilder(SmsSession.Event.Type.CB_SMS_RECEIVED)
+                .setCellBroadcastMessage(cbm)
+        );
+
+        finishSmsSessionIfNeeded(smsSession);
+    }
+
+    /**
      * Write NITZ event
      *
      * @param phoneId Phone id
@@ -1782,12 +1818,18 @@
         final CarrierIdMatchingResult carrierIdMatchingResult = new CarrierIdMatchingResult();
 
         if (cid != TelephonyManager.UNKNOWN_CARRIER_ID) {
+            // Successful matching event if result only has carrierId
             carrierIdMatchingResult.carrierId = cid;
+            // Unknown Gid1 event if result only has carrierId, gid1 and mccmnc
             if (gid1 != null) {
+                carrierIdMatchingResult.mccmnc = mccmnc;
                 carrierIdMatchingResult.gid1 = gid1;
             }
         } else {
-            carrierIdMatchingResult.mccmnc = mccmnc;
+            // Unknown mccmnc event if result only has mccmnc
+            if (mccmnc != null) {
+                carrierIdMatchingResult.mccmnc = mccmnc;
+            }
         }
 
         carrierIdMatching.cidTableVersion = version;
diff --git a/src/java/com/android/internal/telephony/sip/SipCommandInterface.java b/src/java/com/android/internal/telephony/sip/SipCommandInterface.java
index 0cd8fab..396fd4b 100644
--- a/src/java/com/android/internal/telephony/sip/SipCommandInterface.java
+++ b/src/java/com/android/internal/telephony/sip/SipCommandInterface.java
@@ -405,6 +405,14 @@
     }
 
     @Override
+    public void invokeOemRilRequestRaw(byte[] data, Message response) {
+    }
+
+    @Override
+    public void invokeOemRilRequestStrings(String[] strings, Message response) {
+    }
+
+    @Override
     public void setBandMode (int bandMode, Message response) {
     }
 
@@ -647,6 +655,17 @@
     }
 
     @Override
+    public void setSignalStrengthReportingCriteria(int hysteresisMs, int hysteresisDb,
+            int[] thresholdsDbm, int ran, Message result) {
+    }
+
+    @Override
+    public void setLinkCapacityReportingCriteria(int hysteresisMs, int hysteresisDlKbps,
+            int hysteresisUlKbps, int[] thresholdsDlKbps, int[] thresholdsUlKbps, int ran,
+            Message result) {
+    }
+
+    @Override
     public void setSimCardPower(int state, Message result) {
     }
 
diff --git a/src/java/com/android/internal/telephony/test/SimulatedCommands.java b/src/java/com/android/internal/telephony/test/SimulatedCommands.java
index 8598403..1fc9a26 100644
--- a/src/java/com/android/internal/telephony/test/SimulatedCommands.java
+++ b/src/java/com/android/internal/telephony/test/SimulatedCommands.java
@@ -1509,6 +1509,15 @@
     }
 
     @Override
+    public void invokeOemRilRequestRaw(byte[] data, Message response) {
+        // Just echo back data
+        if (response != null) {
+            AsyncResult.forMessage(response).result = data;
+            response.sendToTarget();
+        }
+    }
+
+    @Override
     public void setCarrierInfoForImsiEncryption(ImsiEncryptionInfo imsiEncryptionInfo,
                                                 Message response) {
         // Just echo back data
@@ -1518,6 +1527,15 @@
         }
     }
 
+    @Override
+    public void invokeOemRilRequestStrings(String[] strings, Message response) {
+        // Just echo back data
+        if (response != null) {
+            AsyncResult.forMessage(response).result = strings;
+            response.sendToTarget();
+        }
+    }
+
     //***** SimulatedRadioControl
 
 
@@ -2201,6 +2219,17 @@
     }
 
     @Override
+    public void setSignalStrengthReportingCriteria(int hysteresisMs, int hysteresisDb,
+            int[] thresholdsDbm, int ran, Message result) {
+    }
+
+    @Override
+    public void setLinkCapacityReportingCriteria(int hysteresisMs, int hysteresisDlKbps,
+            int hysteresisUlKbps, int[] thresholdsDlKbps, int[] thresholdsUlKbps, int ran,
+            Message result) {
+    }
+
+    @Override
     public void setSimCardPower(int state, Message result) {
     }
 
diff --git a/src/java/com/android/internal/telephony/test/SimulatedCommandsVerifier.java b/src/java/com/android/internal/telephony/test/SimulatedCommandsVerifier.java
index 0a62268..f43cee0 100644
--- a/src/java/com/android/internal/telephony/test/SimulatedCommandsVerifier.java
+++ b/src/java/com/android/internal/telephony/test/SimulatedCommandsVerifier.java
@@ -1058,6 +1058,26 @@
     }
 
     @Override
+    public void invokeOemRilRequestRaw(byte[] data, Message response) {
+
+    }
+
+    @Override
+    public void invokeOemRilRequestStrings(String[] strings, Message response) {
+
+    }
+
+    @Override
+    public void setOnUnsolOemHookRaw(Handler h, int what, Object obj) {
+
+    }
+
+    @Override
+    public void unSetOnUnsolOemHookRaw(Handler h) {
+
+    }
+
+    @Override
     public void sendTerminalResponse(String contents, Message response) {
 
     }
@@ -1394,6 +1414,17 @@
     }
 
     @Override
+    public void setSignalStrengthReportingCriteria(int hysteresisMs, int hysteresisDb,
+            int[] thresholdsDbm, int ran, Message result) {
+    }
+
+    @Override
+    public void setLinkCapacityReportingCriteria(int hysteresisMs, int hysteresisDlKbps,
+            int hysteresisUlKbps, int[] thresholdsDlKbps, int[] thresholdsUlKbps, int ran,
+            Message result) {
+    }
+
+    @Override
     public void setSimCardPower(int state, Message result) {
     }
 
diff --git a/src/java/com/android/internal/telephony/uicc/CarrierTestOverride.java b/src/java/com/android/internal/telephony/uicc/CarrierTestOverride.java
index 2e72500..61379d2 100644
--- a/src/java/com/android/internal/telephony/uicc/CarrierTestOverride.java
+++ b/src/java/com/android/internal/telephony/uicc/CarrierTestOverride.java
@@ -43,6 +43,7 @@
      * Sample xml:
      * <carrierTestOverrides>
        <carrierTestOverride key="isInTestMode" value="true"/>
+       <carrierTestOverride key="mccmnc" value="310010" />
        <carrierTestOverride key="gid1" value="bae0000000000000"/>
        <carrierTestOverride key="gid2" value="ffffffffffffffff"/>
        <carrierTestOverride key="imsi" value="310010123456789"/>
@@ -58,6 +59,7 @@
     static final String CARRIER_TEST_XML_ITEM_KEY = "key";
     static final String CARRIER_TEST_XML_ITEM_VALUE = "value";
     static final String CARRIER_TEST_XML_ITEM_KEY_STRING_ISINTESTMODE = "isInTestMode";
+    static final String CARRIER_TEST_XML_ITEM_KEY_STRING_MCCMNC = "mccmnc";
     static final String CARRIER_TEST_XML_ITEM_KEY_STRING_GID1 = "gid1";
     static final String CARRIER_TEST_XML_ITEM_KEY_STRING_GID2 = "gid2";
     static final String CARRIER_TEST_XML_ITEM_KEY_STRING_IMSI = "imsi";
@@ -144,6 +146,29 @@
         }
     }
 
+    String getFakeMccMnc() {
+        try {
+            String mccmnc = mCarrierTestParamMap.get(CARRIER_TEST_XML_ITEM_KEY_STRING_MCCMNC);
+            Rlog.d(LOG_TAG, "reading mccmnc from CarrierTestConfig file: " + mccmnc);
+            return mccmnc;
+        } catch (NullPointerException e) {
+            Rlog.w(LOG_TAG, "No mccmnc in CarrierTestConfig file ");
+            return null;
+        }
+    }
+
+    void override(String mccmnc, String imsi, String iccid, String gid1, String gid2, String pnn,
+            String spn) {
+        mCarrierTestParamMap.put(CARRIER_TEST_XML_ITEM_KEY_STRING_ISINTESTMODE, "true");
+        mCarrierTestParamMap.put(CARRIER_TEST_XML_ITEM_KEY_STRING_MCCMNC, mccmnc);
+        mCarrierTestParamMap.put(CARRIER_TEST_XML_ITEM_KEY_STRING_IMSI, imsi);
+        mCarrierTestParamMap.put(CARRIER_TEST_XML_ITEM_KEY_STRING_ICCID, iccid);
+        mCarrierTestParamMap.put(CARRIER_TEST_XML_ITEM_KEY_STRING_GID1, gid1);
+        mCarrierTestParamMap.put(CARRIER_TEST_XML_ITEM_KEY_STRING_GID2, gid2);
+        mCarrierTestParamMap.put(CARRIER_TEST_XML_ITEM_KEY_STRING_PNN, pnn);
+        mCarrierTestParamMap.put(CARRIER_TEST_XML_ITEM_KEY_STRING_SPN, spn);
+    }
+
     private void loadCarrierTestOverrides() {
 
         FileReader carrierTestConfigReader;
diff --git a/src/java/com/android/internal/telephony/uicc/IccCardProxy.java b/src/java/com/android/internal/telephony/uicc/IccCardProxy.java
deleted file mode 100644
index f9629e7..0000000
--- a/src/java/com/android/internal/telephony/uicc/IccCardProxy.java
+++ /dev/null
@@ -1,778 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.telephony.uicc;
-
-import android.app.ActivityManager;
-import android.content.Context;
-import android.content.Intent;
-import android.os.AsyncResult;
-import android.os.Handler;
-import android.os.Message;
-import android.os.Registrant;
-import android.os.RegistrantList;
-import android.os.UserHandle;
-import android.telephony.Rlog;
-import android.telephony.ServiceState;
-import android.telephony.SubscriptionManager;
-import android.telephony.TelephonyManager;
-import android.text.TextUtils;
-
-import com.android.internal.telephony.CommandsInterface;
-import com.android.internal.telephony.CommandsInterface.RadioState;
-import com.android.internal.telephony.IccCard;
-import com.android.internal.telephony.IccCardConstants;
-import com.android.internal.telephony.IccCardConstants.State;
-import com.android.internal.telephony.MccTable;
-import com.android.internal.telephony.PhoneConstants;
-import com.android.internal.telephony.RILConstants;
-import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppState;
-import com.android.internal.telephony.uicc.IccCardApplicationStatus.PersoSubState;
-import com.android.internal.telephony.uicc.IccCardStatus.CardState;
-import com.android.internal.telephony.uicc.IccCardStatus.PinState;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-
-/**
- * @Deprecated use {@link UiccController}.getUiccCard instead.
- *
- * The Phone App assumes that there is only one icc card, and one icc application
- * available at a time. Moreover, it assumes such object (represented with IccCard)
- * is available all the time (whether {@link RILConstants#RIL_REQUEST_GET_SIM_STATUS} returned
- * or not, whether card has desired application or not, whether there really is a card in the
- * slot or not).
- *
- * UiccController, however, can handle multiple instances of icc objects (multiple
- * {@link UiccCardApplication}, multiple {@link IccFileHandler}, multiple {@link IccRecords})
- * created and destroyed dynamically during phone operation.
- *
- * This class implements the IccCard interface that is always available (right after default
- * phone object is constructed) to expose the current (based on voice radio technology)
- * application on the uicc card, so that external apps won't break.
- */
-
-public class IccCardProxy extends Handler implements IccCard {
-    private static final boolean DBG = true;
-    private static final String LOG_TAG = "IccCardProxy";
-
-    private static final int EVENT_RADIO_OFF_OR_UNAVAILABLE = 1;
-    private static final int EVENT_RADIO_ON = 2;
-    private static final int EVENT_ICC_CHANGED = 3;
-    private static final int EVENT_ICC_ABSENT = 4;
-    private static final int EVENT_ICC_LOCKED = 5;
-    private static final int EVENT_APP_READY = 6;
-    private static final int EVENT_RECORDS_LOADED = 7;
-    private static final int EVENT_IMSI_READY = 8;
-    private static final int EVENT_NETWORK_LOCKED = 9;
-
-    private static final int EVENT_ICC_RECORD_EVENTS = 500;
-    private static final int EVENT_CARRIER_PRIVILEGES_LOADED = 503;
-
-    private Integer mPhoneId = null;
-
-    private final Object mLock = new Object();
-    private Context mContext;
-    private CommandsInterface mCi;
-    private TelephonyManager mTelephonyManager;
-
-    private RegistrantList mNetworkLockedRegistrants = new RegistrantList();
-
-    private int mCurrentAppType = UiccController.APP_FAM_3GPP; //default to 3gpp?
-    private UiccController mUiccController = null;
-    private UiccSlot mUiccSlot = null;
-    private UiccCard mUiccCard = null;
-    private UiccCardApplication mUiccApplication = null;
-    private IccRecords mIccRecords = null;
-    private RadioState mRadioState = RadioState.RADIO_UNAVAILABLE;
-    private boolean mInitialized = false;
-    private State mExternalState = State.UNKNOWN;
-
-    public static final String ACTION_INTERNAL_SIM_STATE_CHANGED = "android.intent.action.internal_sim_state_changed";
-
-    public IccCardProxy(Context context, CommandsInterface ci, int phoneId) {
-        if (DBG) log("ctor: ci=" + ci + " phoneId=" + phoneId);
-        mContext = context;
-        mCi = ci;
-        mPhoneId = phoneId;
-        mTelephonyManager = (TelephonyManager) mContext.getSystemService(
-                Context.TELEPHONY_SERVICE);
-        mUiccController = UiccController.getInstance();
-        mUiccController.registerForIccChanged(this, EVENT_ICC_CHANGED, null);
-        ci.registerForOn(this,EVENT_RADIO_ON, null);
-        ci.registerForOffOrNotAvailable(this, EVENT_RADIO_OFF_OR_UNAVAILABLE, null);
-
-        resetProperties();
-    }
-
-    public void dispose() {
-        synchronized (mLock) {
-            log("Disposing");
-            //Cleanup icc references
-            mUiccController.unregisterForIccChanged(this);
-            mUiccController = null;
-            mCi.unregisterForOn(this);
-            mCi.unregisterForOffOrNotAvailable(this);
-        }
-    }
-
-    /*
-     * The card application that the external world sees will be based on the
-     * voice radio technology only!
-     */
-    public void setVoiceRadioTech(int radioTech) {
-        synchronized (mLock) {
-            if (DBG) {
-                log("Setting radio tech " + ServiceState.rilRadioTechnologyToString(radioTech));
-            }
-            if (ServiceState.isGsm(radioTech)) {
-                mCurrentAppType = UiccController.APP_FAM_3GPP;
-            } else {
-                mCurrentAppType = UiccController.APP_FAM_3GPP2;
-            }
-            updateCurrentAppType();
-        }
-    }
-
-    /**
-     * Update current app type and post EVENT_ICC_CHANGED.
-     */
-    private void updateCurrentAppType() {
-        synchronized (mLock) {
-            boolean isLteOnCdmaMode = TelephonyManager.getLteOnCdmaModeStatic()
-                    == PhoneConstants.LTE_ON_CDMA_TRUE;
-            if (mCurrentAppType == UiccController.APP_FAM_3GPP2) {
-                if (isLteOnCdmaMode) {
-                    log("updateCurrentAppType: is cdma/lte device, force IccCardProxy into 3gpp"
-                            + " mode");
-                    mCurrentAppType = UiccController.APP_FAM_3GPP;
-                }
-
-                if (DBG) {
-                    log("updateCurrentAppType: "
-                            + " mCurrentAppType=" + mCurrentAppType
-                            + " isLteOnCdmaMode=" + isLteOnCdmaMode);
-                }
-            }
-
-            mInitialized = true;
-            sendMessage(obtainMessage(EVENT_ICC_CHANGED));
-        }
-    }
-
-    @Override
-    public void handleMessage(Message msg) {
-        switch (msg.what) {
-            case EVENT_RADIO_OFF_OR_UNAVAILABLE:
-                mRadioState = mCi.getRadioState();
-                updateExternalState();
-                break;
-            case EVENT_RADIO_ON:
-                mRadioState = RadioState.RADIO_ON;
-                if (!mInitialized) {
-                    updateCurrentAppType();
-                } else {
-                    // updateCurrentAppType() triggers ICC_CHANGED, which eventually
-                    // calls updateExternalState; thus, we don't need this in the
-                    // above case
-                    updateExternalState();
-                }
-                break;
-            case EVENT_ICC_CHANGED:
-                if (mInitialized) {
-                    updateIccAvailability();
-                }
-                break;
-            case EVENT_ICC_ABSENT:
-                setExternalState(State.ABSENT);
-                break;
-            case EVENT_ICC_LOCKED:
-                processLockedState();
-                break;
-            case EVENT_APP_READY:
-                setExternalState(State.READY);
-                break;
-            case EVENT_RECORDS_LOADED:
-                // Update the MCC/MNC.
-                if (mIccRecords != null) {
-                    String operator = mIccRecords.getOperatorNumeric();
-                    log("operator=" + operator + " mPhoneId=" + mPhoneId);
-
-                    if (!TextUtils.isEmpty(operator)) {
-                        mTelephonyManager.setSimOperatorNumericForPhone(mPhoneId, operator);
-                        String countryCode = operator.substring(0,3);
-                        if (countryCode != null) {
-                            mTelephonyManager.setSimCountryIsoForPhone(mPhoneId,
-                                    MccTable.countryCodeForMcc(Integer.parseInt(countryCode)));
-                        } else {
-                            loge("EVENT_RECORDS_LOADED Country code is null");
-                        }
-                    } else {
-                        loge("EVENT_RECORDS_LOADED Operator name is null");
-                    }
-                }
-                if (mUiccCard != null && !mUiccCard.areCarrierPriviligeRulesLoaded()) {
-                    mUiccCard.registerForCarrierPrivilegeRulesLoaded(
-                            this, EVENT_CARRIER_PRIVILEGES_LOADED, null);
-                } else {
-                    setExternalState(State.LOADED);
-                }
-                break;
-            case EVENT_IMSI_READY:
-                broadcastInternalIccStateChangedIntent(IccCardConstants.INTENT_VALUE_ICC_IMSI,
-                        null);
-                break;
-            case EVENT_NETWORK_LOCKED:
-                mNetworkLockedRegistrants.notifyRegistrants();
-                setExternalState(State.NETWORK_LOCKED);
-                break;
-            case EVENT_ICC_RECORD_EVENTS:
-                if ((mCurrentAppType == UiccController.APP_FAM_3GPP) && (mIccRecords != null)) {
-                    AsyncResult ar = (AsyncResult)msg.obj;
-                    int eventCode = (Integer) ar.result;
-                    if (eventCode == SIMRecords.EVENT_SPN) {
-                        mTelephonyManager.setSimOperatorNameForPhone(
-                                mPhoneId, mIccRecords.getServiceProviderName());
-                    }
-                }
-                break;
-
-            case EVENT_CARRIER_PRIVILEGES_LOADED:
-                log("EVENT_CARRIER_PRIVILEGES_LOADED");
-                if (mUiccCard != null) {
-                    mUiccCard.unregisterForCarrierPrivilegeRulesLoaded(this);
-                }
-                setExternalState(State.LOADED);
-                break;
-
-            default:
-                loge("Unhandled message with number: " + msg.what);
-                break;
-        }
-    }
-
-    private void updateIccAvailability() {
-        synchronized (mLock) {
-            UiccSlot newSlot = mUiccController.getUiccSlotForPhone(mPhoneId);
-            UiccCard newCard = mUiccController.getUiccCard(mPhoneId);
-            UiccCardApplication newApp = null;
-            IccRecords newRecords = null;
-            if (newCard != null) {
-                newApp = newCard.getApplication(mCurrentAppType);
-                if (newApp != null) {
-                    newRecords = newApp.getIccRecords();
-                }
-            }
-
-            if (mIccRecords != newRecords || mUiccApplication != newApp || mUiccCard != newCard
-                    || mUiccSlot != newSlot) {
-                if (DBG) log("Icc changed. Reregistering.");
-                unregisterUiccCardEvents();
-                mUiccSlot = newSlot;
-                mUiccCard = newCard;
-                mUiccApplication = newApp;
-                mIccRecords = newRecords;
-                registerUiccCardEvents();
-            }
-            updateExternalState();
-        }
-    }
-
-    void resetProperties() {
-        if (mCurrentAppType == UiccController.APP_FAM_3GPP) {
-            log("update icc_operator_numeric=" + "");
-            mTelephonyManager.setSimOperatorNumericForPhone(mPhoneId, "");
-            mTelephonyManager.setSimCountryIsoForPhone(mPhoneId, "");
-            mTelephonyManager.setSimOperatorNameForPhone(mPhoneId, "");
-         }
-    }
-
-    private void HandleDetectedState() {
-    // CAF_MSIM SAND
-//        setExternalState(State.DETECTED, false);
-    }
-
-    private void updateExternalState() {
-
-        // mUiccCard could be null at bootup, before valid card states have
-        // been received from UiccController.
-        if (mUiccCard == null) {
-            setExternalState(State.UNKNOWN);
-            return;
-        }
-
-        if (mUiccCard.getCardState() == CardState.CARDSTATE_ABSENT) {
-            /*
-             * Both IccCardProxy and UiccController are registered for
-             * RadioState changes. When the UiccController receives a radio
-             * state changed to Unknown it will dispose of all of the IccCard
-             * objects, which will then notify the IccCardProxy and the null
-             * object will force the state to unknown. However, because the
-             * IccCardProxy is also registered for RadioState changes, it will
-             * recieve that signal first. By triggering on radio state changes
-             * directly, we reduce the time window during which the modem is
-             * UNAVAILABLE but the IccStatus is reported as something valid.
-             * This is not ideal.
-             */
-            if (mRadioState == RadioState.RADIO_UNAVAILABLE) {
-                setExternalState(State.UNKNOWN);
-            } else {
-                setExternalState(State.ABSENT);
-            }
-            return;
-        }
-
-        if (mUiccCard.getCardState() == CardState.CARDSTATE_ERROR) {
-            setExternalState(State.CARD_IO_ERROR);
-            return;
-        }
-
-        if (mUiccCard.getCardState() == CardState.CARDSTATE_RESTRICTED) {
-            setExternalState(State.CARD_RESTRICTED);
-            return;
-        }
-
-        if (mUiccApplication == null) {
-            setExternalState(State.NOT_READY);
-            return;
-        }
-
-        // By process of elimination, the UICC Card State = PRESENT
-        switch (mUiccApplication.getState()) {
-            case APPSTATE_UNKNOWN:
-                /*
-                 * APPSTATE_UNKNOWN is a catch-all state reported whenever the app
-                 * is not explicitly in one of the other states. To differentiate the
-                 * case where we know that there is a card present, but the APP is not
-                 * ready, we choose NOT_READY here instead of unknown. This is possible
-                 * in at least two cases:
-                 * 1) A transient during the process of the SIM bringup
-                 * 2) There is no valid App on the SIM to load, which can be the case with an
-                 *    eSIM/soft SIM.
-                 */
-                setExternalState(State.NOT_READY);
-                break;
-            case APPSTATE_DETECTED:
-                HandleDetectedState();
-                break;
-            case APPSTATE_SUBSCRIPTION_PERSO:
-                if (mUiccApplication.getPersoSubState() ==
-                        PersoSubState.PERSOSUBSTATE_SIM_NETWORK) {
-                    setExternalState(State.NETWORK_LOCKED);
-                }
-                // Otherwise don't change external SIM state.
-                break;
-            case APPSTATE_READY:
-                setExternalState(State.READY);
-                break;
-        }
-    }
-
-    private void registerUiccCardEvents() {
-        if (mUiccApplication != null) {
-            mUiccApplication.registerForReady(this, EVENT_APP_READY, null);
-        }
-        if (mIccRecords != null) {
-            mIccRecords.registerForImsiReady(this, EVENT_IMSI_READY, null);
-            mIccRecords.registerForRecordsLoaded(this, EVENT_RECORDS_LOADED, null);
-            mIccRecords.registerForLockedRecordsLoaded(this, EVENT_ICC_LOCKED, null);
-            mIccRecords.registerForNetworkLockedRecordsLoaded(this, EVENT_NETWORK_LOCKED, null);
-            mIccRecords.registerForRecordsEvents(this, EVENT_ICC_RECORD_EVENTS, null);
-        }
-    }
-
-    private void unregisterUiccCardEvents() {
-        if (mUiccCard != null) mUiccCard.unregisterForCarrierPrivilegeRulesLoaded(this);
-        if (mUiccApplication != null) {
-            mUiccApplication.unregisterForReady(this);
-        }
-        if (mIccRecords != null) {
-            mIccRecords.unregisterForImsiReady(this);
-            mIccRecords.unregisterForRecordsLoaded(this);
-            mIccRecords.unregisterForLockedRecordsLoaded(this);
-            mIccRecords.unregisterForNetworkLockedRecordsLoaded(this);
-            mIccRecords.unregisterForRecordsEvents(this);
-        }
-    }
-
-    private void broadcastInternalIccStateChangedIntent(String value, String reason) {
-        synchronized (mLock) {
-            if (mPhoneId == null) {
-                loge("broadcastInternalIccStateChangedIntent: Card Index is not set; Return!!");
-                return;
-            }
-
-            Intent intent = new Intent(ACTION_INTERNAL_SIM_STATE_CHANGED);
-            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
-                    | Intent.FLAG_RECEIVER_FOREGROUND);
-            intent.putExtra(PhoneConstants.PHONE_NAME_KEY, "Phone");
-            intent.putExtra(IccCardConstants.INTENT_KEY_ICC_STATE, value);
-            intent.putExtra(IccCardConstants.INTENT_KEY_LOCKED_REASON, reason);
-            intent.putExtra(PhoneConstants.PHONE_KEY, mPhoneId);  // SubId may not be valid.
-            log("broadcastInternalIccStateChangedIntent: Sending intent "
-                    + "ACTION_INTERNAL_SIM_STATE_CHANGED value = " + value
-                    + " for mPhoneId : " + mPhoneId);
-            ActivityManager.broadcastStickyIntent(intent, UserHandle.USER_ALL);
-        }
-    }
-
-    private void setExternalState(State newState, boolean override) {
-        synchronized (mLock) {
-            if (mPhoneId == null || !SubscriptionManager.isValidSlotIndex(mPhoneId)) {
-                loge("setExternalState: mPhoneId=" + mPhoneId + " is invalid; Return!!");
-                return;
-            }
-
-            if (!override && newState == mExternalState) {
-                log("setExternalState: !override and newstate unchanged from " + newState);
-                return;
-            }
-            mExternalState = newState;
-            log("setExternalState: set mPhoneId=" + mPhoneId + " mExternalState=" + mExternalState);
-            mTelephonyManager.setSimStateForPhone(mPhoneId, getState().toString());
-
-            broadcastInternalIccStateChangedIntent(getIccStateIntentString(mExternalState),
-                    getIccStateReason(mExternalState));
-        }
-    }
-
-    private void processLockedState() {
-        synchronized (mLock) {
-            if (mUiccApplication == null) {
-                //Don't need to do anything if non-existent application is locked
-                return;
-            }
-            PinState pin1State = mUiccApplication.getPin1State();
-            if (pin1State == PinState.PINSTATE_ENABLED_PERM_BLOCKED) {
-                setExternalState(State.PERM_DISABLED);
-                return;
-            }
-
-            AppState appState = mUiccApplication.getState();
-            switch (appState) {
-                case APPSTATE_PIN:
-                    setExternalState(State.PIN_REQUIRED);
-                    break;
-                case APPSTATE_PUK:
-                    setExternalState(State.PUK_REQUIRED);
-                    break;
-                case APPSTATE_DETECTED:
-                case APPSTATE_READY:
-                case APPSTATE_SUBSCRIPTION_PERSO:
-                case APPSTATE_UNKNOWN:
-                    // Neither required
-                    break;
-            }
-        }
-    }
-
-    private void setExternalState(State newState) {
-        setExternalState(newState, false);
-    }
-
-    public boolean getIccRecordsLoaded() {
-        synchronized (mLock) {
-            if (mIccRecords != null) {
-                return mIccRecords.getRecordsLoaded();
-            }
-            return false;
-        }
-    }
-
-    private String getIccStateIntentString(State state) {
-        switch (state) {
-            case ABSENT: return IccCardConstants.INTENT_VALUE_ICC_ABSENT;
-            case PIN_REQUIRED: return IccCardConstants.INTENT_VALUE_ICC_LOCKED;
-            case PUK_REQUIRED: return IccCardConstants.INTENT_VALUE_ICC_LOCKED;
-            case NETWORK_LOCKED: return IccCardConstants.INTENT_VALUE_ICC_LOCKED;
-            case READY: return IccCardConstants.INTENT_VALUE_ICC_READY;
-            case NOT_READY: return IccCardConstants.INTENT_VALUE_ICC_NOT_READY;
-            case PERM_DISABLED: return IccCardConstants.INTENT_VALUE_ICC_LOCKED;
-            case CARD_IO_ERROR: return IccCardConstants.INTENT_VALUE_ICC_CARD_IO_ERROR;
-            case CARD_RESTRICTED: return IccCardConstants.INTENT_VALUE_ICC_CARD_RESTRICTED;
-            case LOADED: return IccCardConstants.INTENT_VALUE_ICC_LOADED;
-            default: return IccCardConstants.INTENT_VALUE_ICC_UNKNOWN;
-        }
-    }
-
-    /**
-     * Locked state have a reason (PIN, PUK, NETWORK, PERM_DISABLED, CARD_IO_ERROR)
-     * @return reason
-     */
-    private String getIccStateReason(State state) {
-        switch (state) {
-            case PIN_REQUIRED: return IccCardConstants.INTENT_VALUE_LOCKED_ON_PIN;
-            case PUK_REQUIRED: return IccCardConstants.INTENT_VALUE_LOCKED_ON_PUK;
-            case NETWORK_LOCKED: return IccCardConstants.INTENT_VALUE_LOCKED_NETWORK;
-            case PERM_DISABLED: return IccCardConstants.INTENT_VALUE_ABSENT_ON_PERM_DISABLED;
-            case CARD_IO_ERROR: return IccCardConstants.INTENT_VALUE_ICC_CARD_IO_ERROR;
-            case CARD_RESTRICTED: return IccCardConstants.INTENT_VALUE_ICC_CARD_RESTRICTED;
-            default: return null;
-       }
-    }
-
-    /* IccCard interface implementation */
-    @Override
-    public State getState() {
-        synchronized (mLock) {
-            return mExternalState;
-        }
-    }
-
-    @Override
-    public IccRecords getIccRecords() {
-        synchronized (mLock) {
-            return mIccRecords;
-        }
-    }
-
-    /**
-     * Notifies handler of any transition into State.NETWORK_LOCKED
-     */
-    @Override
-    public void registerForNetworkLocked(Handler h, int what, Object obj) {
-        synchronized (mLock) {
-            Registrant r = new Registrant (h, what, obj);
-
-            mNetworkLockedRegistrants.add(r);
-
-            if (getState() == State.NETWORK_LOCKED) {
-                r.notifyRegistrant();
-            }
-        }
-    }
-
-    @Override
-    public void unregisterForNetworkLocked(Handler h) {
-        synchronized (mLock) {
-            mNetworkLockedRegistrants.remove(h);
-        }
-    }
-
-    @Override
-    public void supplyPin(String pin, Message onComplete) {
-        synchronized (mLock) {
-            if (mUiccApplication != null) {
-                mUiccApplication.supplyPin(pin, onComplete);
-            } else if (onComplete != null) {
-                Exception e = new RuntimeException("ICC card is absent.");
-                AsyncResult.forMessage(onComplete).exception = e;
-                onComplete.sendToTarget();
-                return;
-            }
-        }
-    }
-
-    @Override
-    public void supplyPuk(String puk, String newPin, Message onComplete) {
-        synchronized (mLock) {
-            if (mUiccApplication != null) {
-                mUiccApplication.supplyPuk(puk, newPin, onComplete);
-            } else if (onComplete != null) {
-                Exception e = new RuntimeException("ICC card is absent.");
-                AsyncResult.forMessage(onComplete).exception = e;
-                onComplete.sendToTarget();
-                return;
-            }
-        }
-    }
-
-    @Override
-    public void supplyPin2(String pin2, Message onComplete) {
-        synchronized (mLock) {
-            if (mUiccApplication != null) {
-                mUiccApplication.supplyPin2(pin2, onComplete);
-            } else if (onComplete != null) {
-                Exception e = new RuntimeException("ICC card is absent.");
-                AsyncResult.forMessage(onComplete).exception = e;
-                onComplete.sendToTarget();
-                return;
-            }
-        }
-    }
-
-    @Override
-    public void supplyPuk2(String puk2, String newPin2, Message onComplete) {
-        synchronized (mLock) {
-            if (mUiccApplication != null) {
-                mUiccApplication.supplyPuk2(puk2, newPin2, onComplete);
-            } else if (onComplete != null) {
-                Exception e = new RuntimeException("ICC card is absent.");
-                AsyncResult.forMessage(onComplete).exception = e;
-                onComplete.sendToTarget();
-                return;
-            }
-        }
-    }
-
-    @Override
-    public void supplyNetworkDepersonalization(String pin, Message onComplete) {
-        synchronized (mLock) {
-            if (mUiccApplication != null) {
-                mUiccApplication.supplyNetworkDepersonalization(pin, onComplete);
-            } else if (onComplete != null) {
-                Exception e = new RuntimeException("CommandsInterface is not set.");
-                AsyncResult.forMessage(onComplete).exception = e;
-                onComplete.sendToTarget();
-                return;
-            }
-        }
-    }
-
-    @Override
-    public boolean getIccLockEnabled() {
-        synchronized (mLock) {
-            /* defaults to false, if ICC is absent/deactivated */
-            Boolean retValue = mUiccApplication != null ?
-                    mUiccApplication.getIccLockEnabled() : false;
-            return retValue;
-        }
-    }
-
-    @Override
-    public boolean getIccFdnEnabled() {
-        synchronized (mLock) {
-            Boolean retValue = mUiccApplication != null ?
-                    mUiccApplication.getIccFdnEnabled() : false;
-            return retValue;
-        }
-    }
-
-    public boolean getIccPin2Blocked() {
-        /* defaults to disabled */
-        Boolean retValue = mUiccApplication != null ? mUiccApplication.getIccPin2Blocked() : false;
-        return retValue;
-    }
-
-    public boolean getIccPuk2Blocked() {
-        /* defaults to disabled */
-        Boolean retValue = mUiccApplication != null ? mUiccApplication.getIccPuk2Blocked() : false;
-        return retValue;
-    }
-
-    @Override
-    public void setIccLockEnabled(boolean enabled, String password, Message onComplete) {
-        synchronized (mLock) {
-            if (mUiccApplication != null) {
-                mUiccApplication.setIccLockEnabled(enabled, password, onComplete);
-            } else if (onComplete != null) {
-                Exception e = new RuntimeException("ICC card is absent.");
-                AsyncResult.forMessage(onComplete).exception = e;
-                onComplete.sendToTarget();
-                return;
-            }
-        }
-    }
-
-    @Override
-    public void setIccFdnEnabled(boolean enabled, String password, Message onComplete) {
-        synchronized (mLock) {
-            if (mUiccApplication != null) {
-                mUiccApplication.setIccFdnEnabled(enabled, password, onComplete);
-            } else if (onComplete != null) {
-                Exception e = new RuntimeException("ICC card is absent.");
-                AsyncResult.forMessage(onComplete).exception = e;
-                onComplete.sendToTarget();
-                return;
-            }
-        }
-    }
-
-    @Override
-    public void changeIccLockPassword(String oldPassword, String newPassword, Message onComplete) {
-        synchronized (mLock) {
-            if (mUiccApplication != null) {
-                mUiccApplication.changeIccLockPassword(oldPassword, newPassword, onComplete);
-            } else if (onComplete != null) {
-                Exception e = new RuntimeException("ICC card is absent.");
-                AsyncResult.forMessage(onComplete).exception = e;
-                onComplete.sendToTarget();
-                return;
-            }
-        }
-    }
-
-    @Override
-    public void changeIccFdnPassword(String oldPassword, String newPassword, Message onComplete) {
-        synchronized (mLock) {
-            if (mUiccApplication != null) {
-                mUiccApplication.changeIccFdnPassword(oldPassword, newPassword, onComplete);
-            } else if (onComplete != null) {
-                Exception e = new RuntimeException("ICC card is absent.");
-                AsyncResult.forMessage(onComplete).exception = e;
-                onComplete.sendToTarget();
-                return;
-            }
-        }
-    }
-
-    @Override
-    public String getServiceProviderName() {
-        synchronized (mLock) {
-            if (mIccRecords != null) {
-                return mIccRecords.getServiceProviderName();
-            }
-            return null;
-        }
-    }
-
-    @Override
-    public boolean isApplicationOnIcc(IccCardApplicationStatus.AppType type) {
-        synchronized (mLock) {
-            Boolean retValue = mUiccCard != null ? mUiccCard.isApplicationOnIcc(type) : false;
-            return retValue;
-        }
-    }
-
-    @Override
-    public boolean hasIccCard() {
-        synchronized (mLock) {
-            if (mUiccCard != null && mUiccCard.getCardState() != CardState.CARDSTATE_ABSENT) {
-                return true;
-            }
-            return false;
-        }
-    }
-
-    private void log(String s) {
-        Rlog.d(LOG_TAG, s);
-    }
-
-    private void loge(String msg) {
-        Rlog.e(LOG_TAG, msg);
-    }
-
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        pw.println("IccCardProxy: " + this);
-        pw.println(" mContext=" + mContext);
-        pw.println(" mCi=" + mCi);
-        pw.println(" mNetworkLockedRegistrants: size=" + mNetworkLockedRegistrants.size());
-        for (int i = 0; i < mNetworkLockedRegistrants.size(); i++) {
-            pw.println("  mNetworkLockedRegistrants[" + i + "]="
-                    + ((Registrant)mNetworkLockedRegistrants.get(i)).getHandler());
-        }
-        pw.println(" mCurrentAppType=" + mCurrentAppType);
-        pw.println(" mUiccController=" + mUiccController);
-        pw.println(" mUiccCard=" + mUiccCard);
-        pw.println(" mUiccApplication=" + mUiccApplication);
-        pw.println(" mIccRecords=" + mIccRecords);
-        pw.println(" mRadioState=" + mRadioState);
-        pw.println(" mInitialized=" + mInitialized);
-        pw.println(" mExternalState=" + mExternalState);
-
-        pw.flush();
-    }
-}
diff --git a/src/java/com/android/internal/telephony/uicc/IccIoResult.java b/src/java/com/android/internal/telephony/uicc/IccIoResult.java
index 4a35e14..b1b6e75 100644
--- a/src/java/com/android/internal/telephony/uicc/IccIoResult.java
+++ b/src/java/com/android/internal/telephony/uicc/IccIoResult.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.telephony.uicc;
 
+import android.os.Build;
 
 /**
  * {@hide}
@@ -154,6 +155,12 @@
                             + "CHV blocked"
                             + "UNBLOCK CHV blocked";
                     case 0x50: return "increase cannot be performed, Max value reached";
+                    // The definition for these status codes can be found in TS 31.102 7.3.1
+                    case 0x62: return "authentication error, application specific";
+                    case 0x64: return "authentication error, security context not supported";
+                    case 0x65: return "key freshness failure";
+                    case 0x66: return "authentication error, no memory space available";
+                    case 0x67: return "authentication error, no memory space available in EF_MUK";
                 }
                 break;
             case 0x9E: return null; // success
@@ -181,7 +188,9 @@
     @Override
     public String toString() {
         return "IccIoResult sw1:0x" + Integer.toHexString(sw1) + " sw2:0x"
-                + Integer.toHexString(sw2) + ((!success()) ? " Error: " + getErrorString() : "");
+                + Integer.toHexString(sw2) + " Payload: "
+                + ((Build.IS_DEBUGGABLE && Build.IS_ENG) ? payload : "*******")
+                + ((!success()) ? " Error: " + getErrorString() : "");
     }
 
     /**
diff --git a/src/java/com/android/internal/telephony/uicc/IccRecords.java b/src/java/com/android/internal/telephony/uicc/IccRecords.java
index a298548..6a630d6 100644
--- a/src/java/com/android/internal/telephony/uicc/IccRecords.java
+++ b/src/java/com/android/internal/telephony/uicc/IccRecords.java
@@ -80,7 +80,6 @@
     protected int mLockedRecordsReqReason = LOCKED_RECORDS_REQ_REASON_NONE;
 
     protected String mIccId;  // Includes only decimals (no hex)
-    protected String mFakeIccId;
 
     protected String mFullIccId;  // Includes hex characters in ICCID
     protected String mMsisdn = null;  // My mobile number
@@ -93,22 +92,17 @@
     protected String mNewVoiceMailTag = null;
     protected boolean mIsVoiceMailFixed = false;
     protected String mImsi;
-    protected String mFakeImsi;
     private IccIoResult auth_rsp;
 
     protected int mMncLength = UNINITIALIZED;
     protected int mMailboxIndex = 0; // 0 is no mailbox dailing number associated
 
     private String mSpn;
-    private String mFakeSpn;
 
     protected String mGid1;
-    protected String mFakeGid1;
     protected String mGid2;
-    protected String mFakeGid2;
 
     protected String mPnnHomeName;
-    protected String mFakePnnHomeName;
 
     protected String mPrefLang;
 
@@ -170,7 +164,8 @@
                 + " recordsRequested=" + mRecordsRequested
                 + " lockedRecordsReqReason=" + mLockedRecordsReqReason
                 + " iccid=" + iccIdToPrint
-                + (mCarrierTestOverride.isInTestMode() ? "mFakeIccid=" + mFakeIccId : "")
+                + (mCarrierTestOverride.isInTestMode() ? "mFakeIccid="
+                + mCarrierTestOverride.getFakeIccid() : "")
                 + " msisdnTag=" + mMsisdnTag
                 + " voiceMailNum=" + Rlog.pii(VDBG, mVoiceMailNum)
                 + " voiceMailTag=" + mVoiceMailTag
@@ -179,11 +174,13 @@
                 + " isVoiceMailFixed=" + mIsVoiceMailFixed
                 + " mImsi=" + ((mImsi != null) ?
                 mImsi.substring(0, 6) + Rlog.pii(VDBG, mImsi.substring(6)) : "null")
-                + (mCarrierTestOverride.isInTestMode() ? " mFakeImsi=" + mFakeImsi : "")
+                + (mCarrierTestOverride.isInTestMode() ? " mFakeImsi="
+                + mCarrierTestOverride.getFakeIMSI() : "")
                 + " mncLength=" + mMncLength
                 + " mailboxIndex=" + mMailboxIndex
                 + " spn=" + mSpn
-                + (mCarrierTestOverride.isInTestMode() ? " mFakeSpn=" + mFakeSpn : "");
+                + (mCarrierTestOverride.isInTestMode() ? " mFakeSpn="
+                + mCarrierTestOverride.getFakeSpn() : "");
     }
 
     /**
@@ -212,29 +209,18 @@
                 Context.TELEPHONY_SERVICE);
 
         mCarrierTestOverride = new CarrierTestOverride();
-
-        if (mCarrierTestOverride.isInTestMode()) {
-            mFakeImsi = mCarrierTestOverride.getFakeIMSI();
-            log("load mFakeImsi: " + mFakeImsi);
-
-            mFakeGid1 = mCarrierTestOverride.getFakeGid1();
-            log("load mFakeGid1: " + mFakeGid1);
-
-            mFakeGid2 = mCarrierTestOverride.getFakeGid2();
-            log("load mFakeGid2: " + mFakeGid2);
-
-            mFakeSpn = mCarrierTestOverride.getFakeSpn();
-            log("load mFakeSpn: " + mFakeSpn);
-
-            mFakePnnHomeName = mCarrierTestOverride.getFakePnnHomeName();
-            log("load mFakePnnHomeName: " + mFakePnnHomeName);
-
-            mFakeIccId = mCarrierTestOverride.getFakeIccid();
-            log("load mFakeIccId: " + mFakeIccId);
-        }
         mCi.registerForIccRefresh(this, EVENT_REFRESH, null);
     }
 
+    // Override IccRecords for testing
+    public void setCarrierTestOverride(String mccmnc, String imsi, String iccid, String gid1,
+            String gid2, String pnn, String spn)  {
+        mCarrierTestOverride.override(mccmnc, imsi, iccid, gid1, gid2, pnn, spn);
+        mTelephonyManager.setSimOperatorNameForPhone(mParentApp.getPhoneId(), spn);
+        mTelephonyManager.setSimOperatorNumericForPhone(mParentApp.getPhoneId(), mccmnc);
+        mRecordsLoadedRegistrants.notifyRegistrants();
+    }
+
     /**
      * Call when the IccRecords object is no longer going to be used.
      */
@@ -295,8 +281,8 @@
      * @return ICC ID without hex digits
      */
     public String getIccId() {
-        if (mCarrierTestOverride.isInTestMode() && mFakeIccId != null) {
-            return mFakeIccId;
+        if (mCarrierTestOverride.isInTestMode() && mCarrierTestOverride.getFakeIccid() != null) {
+            return mCarrierTestOverride.getFakeIccid();
         } else {
             return mIccId;
         }
@@ -442,8 +428,8 @@
      * @return null if SIM is not yet ready or unavailable
      */
     public String getIMSI() {
-        if (mCarrierTestOverride.isInTestMode() && mFakeImsi != null) {
-            return mFakeImsi;
+        if (mCarrierTestOverride.isInTestMode() && mCarrierTestOverride.getFakeIMSI() != null) {
+            return mCarrierTestOverride.getFakeIMSI();
         } else {
             return mImsi;
         }
@@ -477,8 +463,8 @@
      * @return null if SIM is not yet ready
      */
     public String getGid1() {
-        if (mCarrierTestOverride.isInTestMode() && mFakeGid1 != null) {
-            return mFakeGid1;
+        if (mCarrierTestOverride.isInTestMode() && mCarrierTestOverride.getFakeGid1() != null) {
+            return mCarrierTestOverride.getFakeGid1();
         } else {
             return mGid1;
         }
@@ -489,8 +475,8 @@
      * @return null if SIM is not yet ready
      */
     public String getGid2() {
-        if (mCarrierTestOverride.isInTestMode() && mFakeGid2 != null) {
-            return mFakeGid2;
+        if (mCarrierTestOverride.isInTestMode() && mCarrierTestOverride.getFakeGid2() != null) {
+            return mCarrierTestOverride.getFakeGid2();
         } else {
             return mGid2;
         }
@@ -501,8 +487,9 @@
      * @return null if SIM is not yet ready
      */
     public String getPnnHomeName() {
-        if (mCarrierTestOverride.isInTestMode() && mFakePnnHomeName != null) {
-            return mFakePnnHomeName;
+        if (mCarrierTestOverride.isInTestMode()
+                && mCarrierTestOverride.getFakePnnHomeName() != null) {
+            return mCarrierTestOverride.getFakePnnHomeName();
         } else {
             return mPnnHomeName;
         }
@@ -531,8 +518,8 @@
      * @return null if SIM is not yet ready or no RUIM entry
      */
     public String getServiceProviderName() {
-        if (mCarrierTestOverride.isInTestMode() && mFakeSpn != null) {
-            return mFakeSpn;
+        if (mCarrierTestOverride.isInTestMode() && mCarrierTestOverride.getFakeSpn() != null) {
+            return mCarrierTestOverride.getFakeSpn();
         }
         String providerName = mSpn;
 
@@ -981,13 +968,13 @@
         pw.println(" mImsi=" + ((mImsi != null) ?
                 mImsi.substring(0, 6) + Rlog.pii(VDBG, mImsi.substring(6)) : "null"));
         if (mCarrierTestOverride.isInTestMode()) {
-            pw.println(" mFakeImsi=" + mFakeImsi);
+            pw.println(" mFakeImsi=" + mCarrierTestOverride.getFakeIMSI());
         }
         pw.println(" mMncLength=" + mMncLength);
         pw.println(" mMailboxIndex=" + mMailboxIndex);
         pw.println(" mSpn=" + mSpn);
         if (mCarrierTestOverride.isInTestMode()) {
-            pw.println(" mFakeSpn=" + mFakeSpn);
+            pw.println(" mFakeSpn=" + mCarrierTestOverride.getFakeSpn());
         }
         pw.flush();
     }
diff --git a/src/java/com/android/internal/telephony/uicc/RuimRecords.java b/src/java/com/android/internal/telephony/uicc/RuimRecords.java
index 88ac071..fd4debf 100644
--- a/src/java/com/android/internal/telephony/uicc/RuimRecords.java
+++ b/src/java/com/android/internal/telephony/uicc/RuimRecords.java
@@ -645,7 +645,7 @@
                     if (operatorNumeric != null) {
                         if (operatorNumeric.length() <= 6) {
                             log("update mccmnc=" + operatorNumeric);
-                            MccTable.updateMccMncConfiguration(mContext, operatorNumeric, false);
+                            MccTable.updateMccMncConfiguration(mContext, operatorNumeric);
                         }
                     }
                 } else {
@@ -794,10 +794,8 @@
 
             if (!TextUtils.isEmpty(imsi)) {
                 log("onAllRecordsLoaded set mcc imsi=" + (VDBG ? ("=" + imsi) : ""));
-                mTelephonyManager.setSimCountryIsoForPhone(
-                        mParentApp.getPhoneId(),
-                        MccTable.countryCodeForMcc(
-                        Integer.parseInt(imsi.substring(0, 3))));
+                mTelephonyManager.setSimCountryIsoForPhone(mParentApp.getPhoneId(),
+                        MccTable.countryCodeForMcc(imsi.substring(0, 3)));
             } else {
                 log("onAllRecordsLoaded empty imsi skipping setting mcc");
             }
diff --git a/src/java/com/android/internal/telephony/uicc/SIMRecords.java b/src/java/com/android/internal/telephony/uicc/SIMRecords.java
index 67c0900..ca5e562 100755
--- a/src/java/com/android/internal/telephony/uicc/SIMRecords.java
+++ b/src/java/com/android/internal/telephony/uicc/SIMRecords.java
@@ -16,10 +16,7 @@
 
 package com.android.internal.telephony.uicc;
 
-import android.content.BroadcastReceiver;
 import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
 import android.content.res.Resources;
 import android.os.AsyncResult;
 import android.os.Message;
@@ -30,7 +27,6 @@
 import android.telephony.ServiceState;
 import android.telephony.SmsMessage;
 import android.telephony.SubscriptionInfo;
-import android.telephony.SubscriptionManager;
 import android.text.TextUtils;
 
 import com.android.internal.telephony.CommandsInterface;
@@ -172,7 +168,6 @@
 
     // TODO: Possibly move these to IccRecords.java
     private static final int SYSTEM_EVENT_BASE = 0x100;
-    private static final int EVENT_CARRIER_CONFIG_CHANGED = 1 + SYSTEM_EVENT_BASE;
     private static final int EVENT_APP_LOCKED = 2 + SYSTEM_EVENT_BASE;
     private static final int EVENT_APP_NETWORK_LOCKED = 3 + SYSTEM_EVENT_BASE;
 
@@ -225,21 +220,8 @@
         mParentApp.registerForLocked(this, EVENT_APP_LOCKED, null);
         mParentApp.registerForNetworkLocked(this, EVENT_APP_NETWORK_LOCKED, null);
         if (DBG) log("SIMRecords X ctor this=" + this);
-
-        IntentFilter intentfilter = new IntentFilter();
-        intentfilter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
-        c.registerReceiver(mReceiver, intentfilter);
     }
 
-    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (intent.getAction().equals(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)) {
-                sendMessage(obtainMessage(EVENT_CARRIER_CONFIG_CHANGED));
-            }
-        }
-    };
-
     @Override
     public void dispose() {
         if (DBG) log("Disposing SIMRecords this=" + this);
@@ -248,7 +230,6 @@
         mParentApp.unregisterForReady(this);
         mParentApp.unregisterForLocked(this);
         mParentApp.unregisterForNetworkLocked(this);
-        mContext.unregisterReceiver(mReceiver);
         resetRecords();
         super.dispose();
     }
@@ -732,7 +713,7 @@
                         // finally have both the imsi and the mncLength and
                         // can parse the imsi properly
                         MccTable.updateMccMncConfiguration(mContext,
-                                imsi.substring(0, 3 + mMncLength), false);
+                                imsi.substring(0, 3 + mMncLength));
                     }
                     mImsiReadyRegistrants.notifyRegistrants();
                     break;
@@ -1009,7 +990,7 @@
                             // the imsi properly
                             log("update mccmnc=" + imsi.substring(0, 3 + mMncLength));
                             MccTable.updateMccMncConfiguration(mContext,
-                                    imsi.substring(0, 3 + mMncLength), false);
+                                    imsi.substring(0, 3 + mMncLength));
                         }
                     }
                     break;
@@ -1365,10 +1346,6 @@
                     }
                     break;
 
-                case EVENT_CARRIER_CONFIG_CHANGED:
-                    handleCarrierNameOverride();
-                    break;
-
                 default:
                     super.handleMessage(msg);   // IccRecords handles generic record load responses
             }
@@ -1594,8 +1571,7 @@
         if (!TextUtils.isEmpty(imsi) && imsi.length() >= 3) {
             log("onAllRecordsLoaded set mcc imsi" + (VDBG ? ("=" + imsi) : ""));
             mTelephonyManager.setSimCountryIsoForPhone(
-                    mParentApp.getPhoneId(), MccTable.countryCodeForMcc(
-                    Integer.parseInt(imsi.substring(0, 3))));
+                    mParentApp.getPhoneId(), MccTable.countryCodeForMcc(imsi.substring(0, 3)));
         } else {
             log("onAllRecordsLoaded empty imsi skipping setting mcc");
         }
@@ -1607,63 +1583,6 @@
 
     //***** Private methods
 
-    /**
-     * Override the carrier name with either carrier config or SPN
-     * if an override is provided.
-     */
-    private void handleCarrierNameOverride() {
-        final int phoneId = mParentApp.getPhoneId();
-        SubscriptionController subCon = SubscriptionController.getInstance();
-        final int subId = subCon.getSubIdUsingPhoneId(phoneId);
-        if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
-            loge("subId not valid for Phone " + phoneId);
-            return;
-        }
-
-        CarrierConfigManager configLoader = (CarrierConfigManager)
-                mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
-        if (configLoader == null) {
-            loge("Failed to load a Carrier Config");
-            return;
-        }
-
-        PersistableBundle config = configLoader.getConfigForSubId(subId);
-        boolean preferCcName = config.getBoolean(
-                CarrierConfigManager.KEY_CARRIER_NAME_OVERRIDE_BOOL, false);
-        String ccName = config.getString(CarrierConfigManager.KEY_CARRIER_NAME_STRING);
-        // 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
-        // a name in carrier config, use the carrier config name as a backup.
-        if (preferCcName || (TextUtils.isEmpty(getServiceProviderName())
-                             && !TextUtils.isEmpty(ccName))) {
-            setServiceProviderName(ccName);
-            mTelephonyManager.setSimOperatorNameForPhone(phoneId, ccName);
-        }
-
-        updateCarrierNameForSubscription(subCon, subId);
-    }
-
-    private void updateCarrierNameForSubscription(SubscriptionController subCon, int subId) {
-        /* update display name with carrier override */
-        SubscriptionInfo subInfo = subCon.getActiveSubscriptionInfo(
-                subId, mContext.getOpPackageName());
-
-        if (subInfo == null || subInfo.getNameSource()
-                == SubscriptionManager.NAME_SOURCE_USER_INPUT) {
-            // either way, there is no subinfo to update
-            return;
-        }
-
-        CharSequence oldSubName = subInfo.getDisplayName();
-        String newCarrierName = mTelephonyManager.getSimOperatorName(subId);
-
-        if (!TextUtils.isEmpty(newCarrierName) && !newCarrierName.equals(oldSubName)) {
-            log("sim name[" + mParentApp.getPhoneId() + "] = " + newCarrierName);
-            subCon.setDisplayName(newCarrierName, subId);
-        }
-    }
-
     private void setVoiceMailByCountry (String spn) {
         if (mVmConfig.containsCarrier(spn)) {
             mIsVoiceMailFixed = true;
@@ -2214,15 +2133,15 @@
         pw.println(" mUsimServiceTable=" + mUsimServiceTable);
         pw.println(" mGid1=" + mGid1);
         if (mCarrierTestOverride.isInTestMode()) {
-            pw.println(" mFakeGid1=" + mFakeGid1);
+            pw.println(" mFakeGid1=" + mCarrierTestOverride.getFakeGid1());
         }
         pw.println(" mGid2=" + mGid2);
         if (mCarrierTestOverride.isInTestMode()) {
-            pw.println(" mFakeGid2=" + mFakeGid2);
+            pw.println(" mFakeGid2=" + mCarrierTestOverride.getFakeGid2());
         }
         pw.println(" mPnnHomeName=" + mPnnHomeName);
         if (mCarrierTestOverride.isInTestMode()) {
-            pw.println(" mFakePnnHomeName=" + mFakePnnHomeName);
+            pw.println(" mFakePnnHomeName=" + mCarrierTestOverride.getFakePnnHomeName());
         }
         pw.println(" mPlmnActRecords[]=" + Arrays.toString(mPlmnActRecords));
         pw.println(" mOplmnActRecords[]=" + Arrays.toString(mOplmnActRecords));
diff --git a/src/java/com/android/internal/telephony/uicc/UiccCard.java b/src/java/com/android/internal/telephony/uicc/UiccCard.java
index a664a08..361f70b 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccCard.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccCard.java
@@ -25,7 +25,6 @@
 import android.os.Message;
 import android.telephony.Rlog;
 import android.telephony.TelephonyManager;
-import android.util.LocalLog;
 
 import com.android.internal.telephony.CommandsInterface;
 import com.android.internal.telephony.TelephonyComponentFactory;
@@ -47,22 +46,22 @@
     public static final String EXTRA_ICC_CARD_ADDED =
             "com.android.internal.telephony.uicc.ICC_CARD_ADDED";
 
-    private static final String OPERATOR_BRAND_OVERRIDE_PREFIX = "operator_branding_";
-
-    private final Object mLock = new Object();
+    // The lock object is created by UiccSlot that owns this UiccCard - this is to share the lock
+    // between UiccSlot, UiccCard and UiccProfile for now.
+    private final Object mLock;
     private CardState mCardState;
     private String mIccid;
     protected String mCardId;
     private UiccProfile mUiccProfile;
     private Context mContext;
     private CommandsInterface mCi;
-    private static final LocalLog mLocalLog = new LocalLog(100);
     private final int mPhoneId;
 
-    public UiccCard(Context c, CommandsInterface ci, IccCardStatus ics, int phoneId) {
+    public UiccCard(Context c, CommandsInterface ci, IccCardStatus ics, int phoneId, Object lock) {
         if (DBG) log("Creating");
         mCardState = ics.mCardState;
         mPhoneId = phoneId;
+        mLock = lock;
         update(c, ci, ics);
     }
 
@@ -87,7 +86,7 @@
             if (mCardState != CardState.CARDSTATE_ABSENT) {
                 if (mUiccProfile == null) {
                     mUiccProfile = TelephonyComponentFactory.getInstance().makeUiccProfile(
-                            mContext, mCi, ics, mPhoneId, this);
+                            mContext, mCi, ics, mPhoneId, this, mLock);
                 } else {
                     mUiccProfile.update(mContext, mCi, ics);
                 }
@@ -231,13 +230,13 @@
      *
      * A null aid implies a card level reset - all applications must be reset.
      *
-     * @deprecated Please use {@link UiccProfile#resetAppWithAid(String)} instead.
+     * @deprecated Please use {@link UiccProfile#resetAppWithAid(String, boolean)} instead.
      */
     @Deprecated
-    public boolean resetAppWithAid(String aid) {
+    public boolean resetAppWithAid(String aid, boolean reset) {
         synchronized (mLock) {
             if (mUiccProfile != null) {
-                return mUiccProfile.resetAppWithAid(aid);
+                return mUiccProfile.resetAppWithAid(aid, reset);
             } else {
                 return false;
             }
@@ -506,10 +505,6 @@
         Rlog.e(LOG_TAG, msg);
     }
 
-    private void loglocal(String msg) {
-        if (DBG) mLocalLog.log(msg);
-    }
-
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("UiccCard:");
         pw.println(" mCi=" + mCi);
diff --git a/src/java/com/android/internal/telephony/uicc/UiccCardApplication.java b/src/java/com/android/internal/telephony/uicc/UiccCardApplication.java
index 918e635..7a361e3 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccCardApplication.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccCardApplication.java
@@ -73,6 +73,9 @@
     private boolean       mDesiredFdnEnabled;
     private boolean       mIccLockEnabled;
     private boolean       mDesiredPinLocked;
+
+    // App state will be ignored while deciding whether the card is ready or not.
+    private boolean       mIgnoreApp;
     private boolean       mIccFdnAvailable = true; // Default is enabled.
 
     private CommandsInterface mCi;
@@ -101,6 +104,7 @@
         mPin1Replaced = (as.pin1_replaced != 0);
         mPin1State = as.pin1;
         mPin2State = as.pin2;
+        mIgnoreApp = false;
 
         mContext = c;
         mCi = ci;
@@ -874,6 +878,14 @@
         return mUiccProfile.getPhoneId();
     }
 
+    public boolean isAppIgnored() {
+        return mIgnoreApp;
+    }
+
+    public void setAppIgnoreState(boolean ignore) {
+        mIgnoreApp = ignore;
+    }
+
     protected UiccProfile getUiccProfile() {
         return mUiccProfile;
     }
diff --git a/src/java/com/android/internal/telephony/uicc/UiccController.java b/src/java/com/android/internal/telephony/uicc/UiccController.java
index 27fe2f7..783d82f 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccController.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccController.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.os.AsyncResult;
 import android.os.Handler;
 import android.os.Message;
@@ -27,20 +28,22 @@
 import android.telephony.CarrierConfigManager;
 import android.telephony.Rlog;
 import android.telephony.TelephonyManager;
-import android.text.format.Time;
+import android.util.LocalLog;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.CommandException;
 import com.android.internal.telephony.CommandsInterface;
 import com.android.internal.telephony.IccCardConstants;
 import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.RadioConfig;
+import com.android.internal.telephony.SubscriptionInfoUpdater;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashSet;
-import java.util.LinkedList;
 import java.util.Set;
 
 /**
@@ -113,6 +116,7 @@
     private UiccSlot[] mUiccSlots;
     private int[] mPhoneIdToSlotId;
     private boolean mIsSlotStatusSupported = true;
+    private boolean mIsCdmaSupported = true;
 
     private static final Object mLock = new Object();
     private static UiccController mInstance;
@@ -125,14 +129,13 @@
     private UiccStateChangedLauncher mLauncher;
     private RadioConfig mRadioConfig;
 
-    // Logging for dumpsys. Useful in cases when the cards run into errors.
-    private static final int MAX_PROACTIVE_COMMANDS_TO_LOG = 20;
-    private LinkedList<String> mCardLogs = new LinkedList<String>();
+    // LocalLog buffer to hold important SIM related events for debugging
+    static LocalLog sLocalLog = new LocalLog(100);
 
     public static UiccController make(Context c, CommandsInterface[] ci) {
         synchronized (mLock) {
             if (mInstance != null) {
-                throw new RuntimeException("MSimUiccController.make() should only be called once");
+                throw new RuntimeException("UiccController.make() should only be called once");
             }
             mInstance = new UiccController(c, ci);
             return mInstance;
@@ -143,9 +146,11 @@
         if (DBG) log("Creating UiccController");
         mContext = c;
         mCis = ci;
-        if (VDBG) {
-            log("config_num_physical_slots = " + c.getResources().getInteger(
-                    com.android.internal.R.integer.config_num_physical_slots));
+        if (DBG) {
+            String logStr = "config_num_physical_slots = " + c.getResources().getInteger(
+                    com.android.internal.R.integer.config_num_physical_slots);
+            log(logStr);
+            sLocalLog.log(logStr);
         }
         int numPhysicalSlots = c.getResources().getInteger(
                 com.android.internal.R.integer.config_num_physical_slots);
@@ -179,6 +184,11 @@
         }
 
         mLauncher = new UiccStateChangedLauncher(c, this);
+
+        // set mIsCdmaSupported based on PackageManager.FEATURE_TELEPHONY_CDMA
+        PackageManager packageManager = c.getPackageManager();
+        mIsCdmaSupported =
+                packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_CDMA);
     }
 
     private int getSlotIdFromPhoneId(int phoneId) {
@@ -371,6 +381,8 @@
                 return;
             }
 
+            sLocalLog.log("handleMessage: Received " + msg.what + " for phoneId " + phoneId);
+
             AsyncResult ar = (AsyncResult)msg.obj;
             switch (msg.what) {
                 case EVENT_ICC_STATUS_CHANGED:
@@ -459,6 +471,15 @@
         }
     }
 
+    static void updateInternalIccState(String value, String reason, int phoneId) {
+        SubscriptionInfoUpdater subInfoUpdator = PhoneFactory.getSubscriptionInfoUpdater();
+        if (subInfoUpdator != null) {
+            subInfoUpdator.updateInternalIccState(value, reason, phoneId);
+        } else {
+            Rlog.e(LOG_TAG, "subInfoUpdate is null.");
+        }
+    }
+
     private synchronized void onGetIccCardStatusDone(AsyncResult ar, Integer index) {
         if (ar.exception != null) {
             Rlog.e(LOG_TAG,"Error getting ICC status. "
@@ -473,12 +494,14 @@
 
         IccCardStatus status = (IccCardStatus)ar.result;
 
+        sLocalLog.log("onGetIccCardStatusDone: phoneId " + index + " IccCardStatus: " + status);
+
         int slotId = status.physicalSlotIndex;
         if (VDBG) log("onGetIccCardStatusDone: phoneId " + index + " physicalSlotIndex " + slotId);
         if (slotId == INVALID_SLOT_ID) {
             slotId = index;
-            mPhoneIdToSlotId[index] = slotId;
         }
+        mPhoneIdToSlotId[index] = slotId;
 
         if (VDBG) logPhoneIdToSlotIdMapping();
 
@@ -503,15 +526,20 @@
         }
         Throwable e = ar.exception;
         if (e != null) {
+            String logStr;
             if (!(e instanceof CommandException) || ((CommandException) e).getCommandError()
                     != CommandException.Error.REQUEST_NOT_SUPPORTED) {
                 // this is not expected; there should be no exception other than
                 // REQUEST_NOT_SUPPORTED
-                Rlog.e(LOG_TAG, "Unexpected error getting slot status: " + ar.exception);
+                logStr = "Unexpected error getting slot status: " + ar.exception;
+                Rlog.e(LOG_TAG, logStr);
+                sLocalLog.log(logStr);
             } else {
                 // REQUEST_NOT_SUPPORTED
-                log("onGetSlotStatusDone: request not supported; marking mIsSlotStatusSupported "
-                        + "to false");
+                logStr = "onGetSlotStatusDone: request not supported; marking "
+                        + "mIsSlotStatusSupported to false";
+                log(logStr);
+                sLocalLog.log(logStr);
                 mIsSlotStatusSupported = false;
             }
             return;
@@ -524,6 +552,8 @@
             return;
         }
 
+        sLastSlotStatus = status;
+
         int numActiveSlots = 0;
         for (int i = 0; i < status.size(); i++) {
             IccSlotStatus iss = status.get(i);
@@ -568,6 +598,8 @@
 
         // broadcast slot status changed
         Intent intent = new Intent(TelephonyManager.ACTION_SIM_SLOT_STATUS_CHANGED);
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+        intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
         mContext.sendBroadcast(intent, android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
     }
 
@@ -589,22 +621,10 @@
             log("    phoneId " + i + " slotId " + mPhoneIdToSlotId[i]);
         }
     }
-    /**
-     * Slots are initialized when none of them are null.
-     * todo: is this even needed?
-     */
-    private synchronized boolean areSlotsInitialized() {
-        for (UiccSlot slot : mUiccSlots) {
-            if (slot == null) {
-                return false;
-            }
-        }
-        return true;
-    }
 
     private void onSimRefresh(AsyncResult ar, Integer index) {
         if (ar.exception != null) {
-            Rlog.e(LOG_TAG, "Sim REFRESH with exception: " + ar.exception);
+            Rlog.e(LOG_TAG, "onSimRefresh: Sim REFRESH with exception: " + ar.exception);
             return;
         }
 
@@ -615,6 +635,7 @@
 
         IccRefreshResponse resp = (IccRefreshResponse) ar.result;
         log("onSimRefresh: " + resp);
+        sLocalLog.log("onSimRefresh: " + resp);
 
         if (resp == null) {
             Rlog.e(LOG_TAG, "onSimRefresh: received without input");
@@ -627,17 +648,19 @@
             return;
         }
 
-        log("Handling refresh: " + resp);
         boolean changed = false;
         switch(resp.refreshResult) {
+            // Reset the required apps when we know about the refresh so that
+            // anyone interested does not get stale state.
             case IccRefreshResponse.REFRESH_RESULT_RESET:
+                changed = uiccCard.resetAppWithAid(resp.aid, true /* reset */);
+                break;
             case IccRefreshResponse.REFRESH_RESULT_INIT:
-                 // Reset the required apps when we know about the refresh so that
-                 // anyone interested does not get stale state.
-                 changed = uiccCard.resetAppWithAid(resp.aid);
-                 break;
+                // don't dispose CatService on SIM REFRESH of type INIT
+                changed = uiccCard.resetAppWithAid(resp.aid, false /* initialize */);
+                break;
             default:
-                 return;
+                return;
         }
 
         if (changed && resp.refreshResult == IccRefreshResponse.REFRESH_RESULT_RESET) {
@@ -658,6 +681,10 @@
         mCis[index].getIccCardStatus(obtainMessage(EVENT_GET_ICC_STATUS_DONE, index));
     }
 
+    public boolean isCdmaSupported() {
+        return mIsCdmaSupported;
+    }
+
     private boolean isValidPhoneIndex(int index) {
         return (index >= 0 && index < TelephonyManager.getDefault().getPhoneCount());
     }
@@ -670,14 +697,8 @@
         Rlog.d(LOG_TAG, string);
     }
 
-    // TODO: This is hacky. We need a better way of saving the logs.
     public void addCardLog(String data) {
-        Time t = new Time();
-        t.setToNow();
-        mCardLogs.addLast(t.format("%m-%d %H:%M:%S") + " " + data);
-        if (mCardLogs.size() > MAX_PROACTIVE_COMMANDS_TO_LOG) {
-            mCardLogs.removeFirst();
-        }
+        sLocalLog.log(data);
     }
 
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
@@ -691,6 +712,7 @@
         }
         pw.println();
         pw.flush();
+        pw.println(" mIsCdmaSupported=" + mIsCdmaSupported);
         pw.println(" mUiccSlots: size=" + mUiccSlots.length);
         for (int i = 0; i < mUiccSlots.length; i++) {
             if (mUiccSlots[i] == null) {
@@ -700,9 +722,7 @@
                 mUiccSlots[i].dump(fd, pw, args);
             }
         }
-        pw.println("mCardLogs: ");
-        for (int i = 0; i < mCardLogs.size(); ++i) {
-            pw.println("  " + mCardLogs.get(i));
-        }
+        pw.println(" sLocalLog= ");
+        sLocalLog.dump(fd, pw, args);
     }
 }
diff --git a/src/java/com/android/internal/telephony/uicc/UiccProfile.java b/src/java/com/android/internal/telephony/uicc/UiccProfile.java
index 3c23d66..f7aeaa1 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccProfile.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccProfile.java
@@ -16,12 +16,13 @@
 
 package com.android.internal.telephony.uicc;
 
-import android.app.ActivityManager;
 import android.app.AlertDialog;
 import android.app.usage.UsageStatsManager;
+import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.SharedPreferences;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
@@ -35,7 +36,6 @@
 import android.os.PersistableBundle;
 import android.os.Registrant;
 import android.os.RegistrantList;
-import android.os.UserHandle;
 import android.preference.PreferenceManager;
 import android.provider.Settings;
 import android.telephony.CarrierConfigManager;
@@ -45,10 +45,10 @@
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
-import android.util.LocalLog;
 import android.view.WindowManager;
 
 import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.CommandsInterface;
 import com.android.internal.telephony.IccCard;
 import com.android.internal.telephony.IccCardConstants;
@@ -81,15 +81,16 @@
  *
  * {@hide}
  */
-public class UiccProfile extends Handler implements IccCard {
+public class UiccProfile extends IccCard {
     protected static final String LOG_TAG = "UiccProfile";
     protected static final boolean DBG = true;
     private static final boolean VDBG = false; //STOPSHIP if true
-    private static final boolean ICC_CARD_PROXY_REMOVED = true;
 
     private static final String OPERATOR_BRAND_OVERRIDE_PREFIX = "operator_branding_";
 
-    private final Object mLock = new Object();
+    // The lock object is created by UiccSlot that owns the UiccCard that owns this UiccProfile.
+    // This is to share the lock between UiccSlot, UiccCard and UiccProfile for now.
+    private final Object mLock;
     private PinState mUniversalPinState;
     private int mGsmUmtsSubscriptionAppIndex;
     private int mCdmaSubscriptionAppIndex;
@@ -98,34 +99,31 @@
             new UiccCardApplication[IccCardStatus.CARD_MAX_APPS];
     private Context mContext;
     private CommandsInterface mCi;
-    private UiccCard mUiccCard; //parent
+    private final UiccCard mUiccCard; //parent
     private CatService mCatService;
     private UiccCarrierPrivilegeRules mCarrierPrivilegeRules;
     private boolean mDisposed = false;
 
     private RegistrantList mCarrierPrivilegeRegistrants = new RegistrantList();
-
-    private static final int EVENT_OPEN_LOGICAL_CHANNEL_DONE = 15;
-    private static final int EVENT_CLOSE_LOGICAL_CHANNEL_DONE = 16;
-    private static final int EVENT_TRANSMIT_APDU_LOGICAL_CHANNEL_DONE = 17;
-    private static final int EVENT_TRANSMIT_APDU_BASIC_CHANNEL_DONE = 18;
-    private static final int EVENT_SIM_IO_DONE = 19;
-    private static final int EVENT_CARRIER_PRIVILEGES_LOADED = 20;
-
-    private static final LocalLog sLocalLog = new LocalLog(100);
+    private RegistrantList mOperatorBrandOverrideRegistrants = new RegistrantList();
 
     private final int mPhoneId;
 
-    /*----------------------------------------------------*/
-    // logic moved over from IccCardProxy
     private static final int EVENT_RADIO_OFF_OR_UNAVAILABLE = 1;
-    private static final int EVENT_ICC_LOCKED = 5;
-    private static final int EVENT_APP_READY = 6;
-    private static final int EVENT_RECORDS_LOADED = 7;
-    private static final int EVENT_NETWORK_LOCKED = 9;
-    private static final int EVENT_EID_READY = 10;
-
-    private static final int EVENT_ICC_RECORD_EVENTS = 500;
+    private static final int EVENT_ICC_LOCKED = 2;
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    public static final int EVENT_APP_READY = 3;
+    private static final int EVENT_RECORDS_LOADED = 4;
+    private static final int EVENT_NETWORK_LOCKED = 5;
+    private static final int EVENT_EID_READY = 6;
+    private static final int EVENT_ICC_RECORD_EVENTS = 7;
+    private static final int EVENT_OPEN_LOGICAL_CHANNEL_DONE = 8;
+    private static final int EVENT_CLOSE_LOGICAL_CHANNEL_DONE = 9;
+    private static final int EVENT_TRANSMIT_APDU_LOGICAL_CHANNEL_DONE = 10;
+    private static final int EVENT_TRANSMIT_APDU_BASIC_CHANNEL_DONE = 11;
+    private static final int EVENT_SIM_IO_DONE = 12;
+    private static final int EVENT_CARRIER_PRIVILEGES_LOADED = 13;
+    private static final int EVENT_CARRIER_CONFIG_CHANGED = 14;
 
     private TelephonyManager mTelephonyManager;
 
@@ -136,55 +134,130 @@
     private IccRecords mIccRecords = null;
     private IccCardConstants.State mExternalState = IccCardConstants.State.UNKNOWN;
 
-    public static final String ACTION_INTERNAL_SIM_STATE_CHANGED =
-            "android.intent.action.internal_sim_state_changed";
 
-    /*----------------------------------------------------*/
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (intent.getAction().equals(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)) {
+                mHandler.sendMessage(mHandler.obtainMessage(EVENT_CARRIER_CONFIG_CHANGED));
+            }
+        }
+    };
+
+    @VisibleForTesting
+    public final Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            // We still need to handle the following response messages even the UiccProfile has been
+            // disposed because whoever sent the request may be still waiting for the response.
+            if (mDisposed && msg.what != EVENT_OPEN_LOGICAL_CHANNEL_DONE
+                    && msg.what != EVENT_CLOSE_LOGICAL_CHANNEL_DONE
+                    && msg.what != EVENT_TRANSMIT_APDU_LOGICAL_CHANNEL_DONE
+                    && msg.what != EVENT_TRANSMIT_APDU_BASIC_CHANNEL_DONE
+                    && msg.what != EVENT_SIM_IO_DONE) {
+                loge("handleMessage: Received " + msg.what
+                        + " after dispose(); ignoring the message");
+                return;
+            }
+            loglocal("handleMessage: Received " + msg.what + " for phoneId " + mPhoneId);
+            switch (msg.what) {
+                case EVENT_NETWORK_LOCKED:
+                    mNetworkLockedRegistrants.notifyRegistrants();
+                    // intentional fall through
+                case EVENT_RADIO_OFF_OR_UNAVAILABLE:
+                case EVENT_ICC_LOCKED:
+                case EVENT_APP_READY:
+                case EVENT_RECORDS_LOADED:
+                case EVENT_EID_READY:
+                    if (VDBG) log("handleMessage: Received " + msg.what);
+                    updateExternalState();
+                    break;
+
+                case EVENT_ICC_RECORD_EVENTS:
+                    if ((mCurrentAppType == UiccController.APP_FAM_3GPP) && (mIccRecords != null)) {
+                        AsyncResult ar = (AsyncResult) msg.obj;
+                        int eventCode = (Integer) ar.result;
+                        if (eventCode == SIMRecords.EVENT_SPN) {
+                            mTelephonyManager.setSimOperatorNameForPhone(
+                                    mPhoneId, mIccRecords.getServiceProviderName());
+                        }
+                    }
+                    break;
+
+                case EVENT_CARRIER_PRIVILEGES_LOADED:
+                    if (VDBG) log("handleMessage: EVENT_CARRIER_PRIVILEGES_LOADED");
+                    onCarrierPriviligesLoadedMessage();
+                    updateExternalState();
+                    break;
+
+                case EVENT_CARRIER_CONFIG_CHANGED:
+                    handleCarrierNameOverride();
+                    break;
+
+                case EVENT_OPEN_LOGICAL_CHANNEL_DONE:
+                case EVENT_CLOSE_LOGICAL_CHANNEL_DONE:
+                case EVENT_TRANSMIT_APDU_LOGICAL_CHANNEL_DONE:
+                case EVENT_TRANSMIT_APDU_BASIC_CHANNEL_DONE:
+                case EVENT_SIM_IO_DONE:
+                    AsyncResult ar = (AsyncResult) msg.obj;
+                    if (ar.exception != null) {
+                        loglocal("handleMessage: Exception " + ar.exception);
+                        log("handleMessage: Error in SIM access with exception" + ar.exception);
+                    }
+                    AsyncResult.forMessage((Message) ar.userObj, ar.result, ar.exception);
+                    ((Message) ar.userObj).sendToTarget();
+                    break;
+
+                default:
+                    loge("handleMessage: Unhandled message with number: " + msg.what);
+                    break;
+            }
+        }
+    };
 
     public UiccProfile(Context c, CommandsInterface ci, IccCardStatus ics, int phoneId,
-            UiccCard uiccCard) {
+            UiccCard uiccCard, Object lock) {
         if (DBG) log("Creating profile");
+        mLock = lock;
         mUiccCard = uiccCard;
         mPhoneId = phoneId;
-        if (ICC_CARD_PROXY_REMOVED) {
-            // set current app type based on phone type - do this before calling update() as that
-            // calls updateIccAvailability() which uses mCurrentAppType
-            Phone phone = PhoneFactory.getPhone(phoneId);
-            if (phone != null) {
-                setCurrentAppType(phone.getPhoneType() == PhoneConstants.PHONE_TYPE_GSM);
-            }
+        // set current app type based on phone type - do this before calling update() as that
+        // calls updateIccAvailability() which uses mCurrentAppType
+        Phone phone = PhoneFactory.getPhone(phoneId);
+        if (phone != null) {
+            setCurrentAppType(phone.getPhoneType() == PhoneConstants.PHONE_TYPE_GSM);
         }
 
         if (mUiccCard instanceof EuiccCard) {
-            ((EuiccCard) mUiccCard).registerForEidReady(this, EVENT_EID_READY, null);
+            ((EuiccCard) mUiccCard).registerForEidReady(mHandler, EVENT_EID_READY, null);
         }
 
         update(c, ci, ics);
+        ci.registerForOffOrNotAvailable(mHandler, EVENT_RADIO_OFF_OR_UNAVAILABLE, null);
+        resetProperties();
 
-        if (ICC_CARD_PROXY_REMOVED) {
-            ci.registerForOffOrNotAvailable(this, EVENT_RADIO_OFF_OR_UNAVAILABLE, null);
-
-            resetProperties();
-        }
+        IntentFilter intentfilter = new IntentFilter();
+        intentfilter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
+        c.registerReceiver(mReceiver, intentfilter);
     }
 
     /**
      * Dispose the UiccProfile.
      */
     public void dispose() {
-        synchronized (mLock) {
-            if (DBG) log("Disposing profile");
+        if (DBG) log("Disposing profile");
 
+        // mUiccCard is outside of mLock in order to prevent deadlocking. This is safe because
+        // EuiccCard#unregisterForEidReady handles its own lock
+        if (mUiccCard instanceof EuiccCard) {
+            ((EuiccCard) mUiccCard).unregisterForEidReady(mHandler);
+        }
+        synchronized (mLock) {
             unregisterAllAppEvents();
             unregisterCurrAppEvents();
 
-            if (mUiccCard instanceof EuiccCard) {
-                ((EuiccCard) mUiccCard).unregisterForEidReady(this);
-            }
-
-            if (ICC_CARD_PROXY_REMOVED) {
-                mCi.unregisterForOffOrNotAvailable(this);
-            }
+            mCi.unregisterForOffOrNotAvailable(mHandler);
+            mContext.unregisterReceiver(mReceiver);
 
             if (mCatService != null) mCatService.dispose();
             for (UiccCardApplication app : mUiccApplications) {
@@ -216,9 +289,7 @@
     private void setCurrentAppType(boolean isGsm) {
         if (VDBG) log("setCurrentAppType");
         synchronized (mLock) {
-            boolean isLteOnCdmaMode = TelephonyManager.getLteOnCdmaModeStatic()
-                    == PhoneConstants.LTE_ON_CDMA_TRUE;
-            if (isGsm || isLteOnCdmaMode) {
+            if (isGsm) {
                 mCurrentAppType = UiccController.APP_FAM_3GPP;
             } else {
                 mCurrentAppType = UiccController.APP_FAM_3GPP2;
@@ -226,59 +297,62 @@
         }
     }
 
-    @Override
-    public void handleMessage(Message msg) {
-        if (mDisposed) {
-            loge("handleMessage: Received " + msg.what + " after dispose(); ignoring the message");
+    /**
+     * Override the carrier name with either carrier config or SPN
+     * if an override is provided.
+     */
+    private void handleCarrierNameOverride() {
+        SubscriptionController subCon = SubscriptionController.getInstance();
+        final int subId = subCon.getSubIdUsingPhoneId(mPhoneId);
+        if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+            loge("subId not valid for Phone " + mPhoneId);
             return;
         }
-        switch (msg.what) {
-            case EVENT_NETWORK_LOCKED:
-                mNetworkLockedRegistrants.notifyRegistrants();
-                // intentional fall through
-            case EVENT_RADIO_OFF_OR_UNAVAILABLE:
-            case EVENT_ICC_LOCKED:
-            case EVENT_APP_READY:
-            case EVENT_RECORDS_LOADED:
-            case EVENT_EID_READY:
-                if (VDBG) log("handleMessage: Received " + msg.what);
-                updateExternalState();
-                break;
 
-            case EVENT_ICC_RECORD_EVENTS:
-                if ((mCurrentAppType == UiccController.APP_FAM_3GPP) && (mIccRecords != null)) {
-                    AsyncResult ar = (AsyncResult) msg.obj;
-                    int eventCode = (Integer) ar.result;
-                    if (eventCode == SIMRecords.EVENT_SPN) {
-                        mTelephonyManager.setSimOperatorNameForPhone(
-                                mPhoneId, mIccRecords.getServiceProviderName());
-                    }
-                }
-                break;
+        CarrierConfigManager configLoader = (CarrierConfigManager)
+                mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
+        if (configLoader == null) {
+            loge("Failed to load a Carrier Config");
+            return;
+        }
 
-            case EVENT_CARRIER_PRIVILEGES_LOADED:
-                if (VDBG) log("EVENT_CARRIER_PRIVILEGES_LOADED");
-                onCarrierPriviligesLoadedMessage();
-                updateExternalState();
-                break;
+        PersistableBundle config = configLoader.getConfigForSubId(subId);
+        boolean preferCcName = config.getBoolean(
+                CarrierConfigManager.KEY_CARRIER_NAME_OVERRIDE_BOOL, false);
+        String ccName = config.getString(CarrierConfigManager.KEY_CARRIER_NAME_STRING);
+        // 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
+        // a name in carrier config, use the carrier config name as a backup.
+        if (preferCcName || (TextUtils.isEmpty(getServiceProviderName())
+                && !TextUtils.isEmpty(ccName))) {
+            if (mIccRecords != null) {
+                mIccRecords.setServiceProviderName(ccName);
+            }
+            mTelephonyManager.setSimOperatorNameForPhone(mPhoneId, ccName);
+            mOperatorBrandOverrideRegistrants.notifyRegistrants();
+        }
 
-            case EVENT_OPEN_LOGICAL_CHANNEL_DONE:
-            case EVENT_CLOSE_LOGICAL_CHANNEL_DONE:
-            case EVENT_TRANSMIT_APDU_LOGICAL_CHANNEL_DONE:
-            case EVENT_TRANSMIT_APDU_BASIC_CHANNEL_DONE:
-            case EVENT_SIM_IO_DONE:
-                AsyncResult ar = (AsyncResult) msg.obj;
-                if (ar.exception != null) {
-                    loglocal("Exception: " + ar.exception);
-                    log("Error in SIM access with exception" + ar.exception);
-                }
-                AsyncResult.forMessage((Message) ar.userObj, ar.result, ar.exception);
-                ((Message) ar.userObj).sendToTarget();
-                break;
+        updateCarrierNameForSubscription(subCon, subId);
+    }
 
-            default:
-                loge("Unhandled message with number: " + msg.what);
-                break;
+    private void updateCarrierNameForSubscription(SubscriptionController subCon, int subId) {
+        /* update display name with carrier override */
+        SubscriptionInfo subInfo = subCon.getActiveSubscriptionInfo(
+                subId, mContext.getOpPackageName());
+
+        if (subInfo == null || subInfo.getNameSource()
+                == SubscriptionManager.NAME_SOURCE_USER_INPUT) {
+            // either way, there is no subinfo to update
+            return;
+        }
+
+        CharSequence oldSubName = subInfo.getDisplayName();
+        String newCarrierName = mTelephonyManager.getSimOperatorName(subId);
+
+        if (!TextUtils.isEmpty(newCarrierName) && !newCarrierName.equals(oldSubName)) {
+            log("sim name[" + mPhoneId + "] = " + newCarrierName);
+            subCon.setDisplayName(newCarrierName, subId);
         }
     }
 
@@ -316,7 +390,11 @@
         }
     }
 
-    private void updateExternalState() {
+    /**
+     * Update the external SIM state
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    public void updateExternalState() {
         // First check if card state is IO_ERROR or RESTRICTED
         if (mUiccCard.getCardState() == IccCardStatus.CardState.CARDSTATE_ERROR) {
             setExternalState(IccCardConstants.State.CARD_IO_ERROR);
@@ -409,6 +487,7 @@
                 setExternalState(IccCardConstants.State.NOT_READY);
                 break;
             case APPSTATE_READY:
+                checkAndUpdateIfAnyAppToBeIgnored();
                 if (areAllApplicationsReady()) {
                     if (areAllRecordsLoaded() && areCarrierPriviligeRulesLoaded()) {
                         if (VDBG) log("updateExternalState: setting state to LOADED");
@@ -437,12 +516,12 @@
         for (UiccCardApplication app : mUiccApplications) {
             if (app != null) {
                 if (VDBG) log("registerUiccCardEvents: registering for EVENT_APP_READY");
-                app.registerForReady(this, EVENT_APP_READY, null);
+                app.registerForReady(mHandler, EVENT_APP_READY, null);
                 IccRecords ir = app.getIccRecords();
                 if (ir != null) {
                     if (VDBG) log("registerUiccCardEvents: registering for EVENT_RECORDS_LOADED");
-                    ir.registerForRecordsLoaded(this, EVENT_RECORDS_LOADED, null);
-                    ir.registerForRecordsEvents(this, EVENT_ICC_RECORD_EVENTS, null);
+                    ir.registerForRecordsLoaded(mHandler, EVENT_RECORDS_LOADED, null);
+                    ir.registerForRecordsEvents(mHandler, EVENT_ICC_RECORD_EVENTS, null);
                 }
             }
         }
@@ -451,11 +530,11 @@
     private void unregisterAllAppEvents() {
         for (UiccCardApplication app : mUiccApplications) {
             if (app != null) {
-                app.unregisterForReady(this);
+                app.unregisterForReady(mHandler);
                 IccRecords ir = app.getIccRecords();
                 if (ir != null) {
-                    ir.unregisterForRecordsLoaded(this);
-                    ir.unregisterForRecordsEvents(this);
+                    ir.unregisterForRecordsLoaded(mHandler);
+                    ir.unregisterForRecordsEvents(mHandler);
                 }
             }
         }
@@ -464,31 +543,18 @@
     private void registerCurrAppEvents() {
         // In case of locked, only listen to the current application.
         if (mIccRecords != null) {
-            mIccRecords.registerForLockedRecordsLoaded(this, EVENT_ICC_LOCKED, null);
-            mIccRecords.registerForNetworkLockedRecordsLoaded(this, EVENT_NETWORK_LOCKED, null);
+            mIccRecords.registerForLockedRecordsLoaded(mHandler, EVENT_ICC_LOCKED, null);
+            mIccRecords.registerForNetworkLockedRecordsLoaded(mHandler, EVENT_NETWORK_LOCKED, null);
         }
     }
 
     private void unregisterCurrAppEvents() {
         if (mIccRecords != null) {
-            mIccRecords.unregisterForLockedRecordsLoaded(this);
-            mIccRecords.unregisterForNetworkLockedRecordsLoaded(this);
+            mIccRecords.unregisterForLockedRecordsLoaded(mHandler);
+            mIccRecords.unregisterForNetworkLockedRecordsLoaded(mHandler);
         }
     }
 
-    static void broadcastInternalIccStateChangedIntent(String value, String reason, int phoneId) {
-        Intent intent = new Intent(ACTION_INTERNAL_SIM_STATE_CHANGED);
-        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
-                | Intent.FLAG_RECEIVER_FOREGROUND);
-        intent.putExtra(PhoneConstants.PHONE_NAME_KEY, "Phone");
-        intent.putExtra(IccCardConstants.INTENT_KEY_ICC_STATE, value);
-        intent.putExtra(IccCardConstants.INTENT_KEY_LOCKED_REASON, reason);
-        intent.putExtra(PhoneConstants.PHONE_KEY, phoneId);  // SubId may not be valid.
-        log("Sending intent ACTION_INTERNAL_SIM_STATE_CHANGED value=" + value
-                + " for mPhoneId : " + phoneId);
-        ActivityManager.broadcastStickyIntent(intent, UserHandle.USER_ALL);
-    }
-
     private void setExternalState(IccCardConstants.State newState, boolean override) {
         synchronized (mLock) {
             if (!SubscriptionManager.isValidSlotIndex(mPhoneId)) {
@@ -505,26 +571,26 @@
                 // Update the MCC/MNC.
                 if (mIccRecords != null) {
                     String operator = mIccRecords.getOperatorNumeric();
-                    log("operator=" + operator + " mPhoneId=" + mPhoneId);
+                    log("setExternalState: operator=" + operator + " mPhoneId=" + mPhoneId);
 
                     if (!TextUtils.isEmpty(operator)) {
                         mTelephonyManager.setSimOperatorNumericForPhone(mPhoneId, operator);
                         String countryCode = operator.substring(0, 3);
                         if (countryCode != null) {
                             mTelephonyManager.setSimCountryIsoForPhone(mPhoneId,
-                                    MccTable.countryCodeForMcc(Integer.parseInt(countryCode)));
+                                    MccTable.countryCodeForMcc(countryCode));
                         } else {
-                            loge("EVENT_RECORDS_LOADED Country code is null");
+                            loge("setExternalState: state LOADED; Country code is null");
                         }
                     } else {
-                        loge("EVENT_RECORDS_LOADED Operator name is null");
+                        loge("setExternalState: state LOADED; Operator name is null");
                     }
                 }
             }
             log("setExternalState: set mPhoneId=" + mPhoneId + " mExternalState=" + mExternalState);
             mTelephonyManager.setSimStateForPhone(mPhoneId, getState().toString());
 
-            broadcastInternalIccStateChangedIntent(getIccStateIntentString(mExternalState),
+            UiccController.updateInternalIccState(getIccStateIntentString(mExternalState),
                     getIccStateReason(mExternalState), mPhoneId);
         }
     }
@@ -702,6 +768,13 @@
     }
 
     @Override
+    public boolean getIccFdnAvailable() {
+        synchronized (mLock) {
+            return mUiccApplication != null && mUiccApplication.getIccFdnAvailable();
+        }
+    }
+
+    @Override
     public boolean getIccPin2Blocked() {
         /* defaults to disabled */
         return mUiccApplication != null && mUiccApplication.getIccPin2Blocked();
@@ -781,15 +854,14 @@
 
     @Override
     public boolean hasIccCard() {
-        synchronized (mLock) {
-            if (mUiccCard != null && mUiccCard.getCardState()
-                    != IccCardStatus.CardState.CARDSTATE_ABSENT) {
-                return true;
-            }
-            loge("hasIccCard: UiccProfile is not null but UiccCard is null or card state is "
-                    + "ABSENT");
-            return false;
+        // mUiccCard is initialized in constructor, so won't be null
+        if (mUiccCard.getCardState()
+                != IccCardStatus.CardState.CARDSTATE_ABSENT) {
+            return true;
         }
+        loge("hasIccCard: UiccProfile is not null but UiccCard is null or card state is "
+                + "ABSENT");
+        return false;
     }
 
     /**
@@ -831,7 +903,7 @@
             log("Before privilege rules: " + mCarrierPrivilegeRules + " : " + ics.mCardState);
             if (mCarrierPrivilegeRules == null && ics.mCardState == CardState.CARDSTATE_PRESENT) {
                 mCarrierPrivilegeRules = new UiccCarrierPrivilegeRules(this,
-                        obtainMessage(EVENT_CARRIER_PRIVILEGES_LOADED));
+                        mHandler.obtainMessage(EVENT_CARRIER_PRIVILEGES_LOADED));
             } else if (mCarrierPrivilegeRules != null
                     && ics.mCardState != CardState.CARDSTATE_PRESENT) {
                 mCarrierPrivilegeRules = null;
@@ -884,24 +956,48 @@
      * this only checks for SIM/USIM and CSIM/RUIM apps. ISIM is considered not supported for this
      * purpose as there are cards that have ISIM app that is never read (there are SIMs for which
      * the state of ISIM goes to DETECTED but never to READY).
+     * CSIM/RUIM apps are considered not supported if CDMA is not supported.
      */
     private boolean isSupportedApplication(UiccCardApplication app) {
         // TODO: 2/15/18 Add check to see if ISIM app will go to READY state, and if yes, check for
         // ISIM also (currently ISIM is considered as not supported in this function)
-        if (app.getType() != AppType.APPTYPE_USIM && app.getType() != AppType.APPTYPE_CSIM
-                && app.getType() != AppType.APPTYPE_SIM && app.getType() != AppType.APPTYPE_RUIM) {
-            return false;
+        if (app.getType() == AppType.APPTYPE_USIM || app.getType() == AppType.APPTYPE_SIM
+                || (UiccController.getInstance().isCdmaSupported()
+                && (app.getType() == AppType.APPTYPE_CSIM
+                || app.getType() == AppType.APPTYPE_RUIM))) {
+            return true;
         }
-        return true;
+        return false;
+    }
+
+    private void checkAndUpdateIfAnyAppToBeIgnored() {
+        boolean[] appReadyStateTracker = new boolean[AppType.APPTYPE_ISIM.ordinal() + 1];
+        for (UiccCardApplication app : mUiccApplications) {
+            if (app != null && isSupportedApplication(app) && app.isReady()) {
+                appReadyStateTracker[app.getType().ordinal()] = true;
+            }
+        }
+
+        for (UiccCardApplication app : mUiccApplications) {
+            if (app != null && isSupportedApplication(app) && !app.isReady()) {
+                /* Checks if the  appReadyStateTracker has already an entry in ready state
+                   with same type as app */
+                if (appReadyStateTracker[app.getType().ordinal()]) {
+                    app.setAppIgnoreState(true);
+                }
+            }
+        }
     }
 
     private boolean areAllApplicationsReady() {
         for (UiccCardApplication app : mUiccApplications) {
-            if (app != null && isSupportedApplication(app) && !app.isReady()) {
+            if (app != null && isSupportedApplication(app) && !app.isReady()
+                    && !app.isAppIgnored()) {
                 if (VDBG) log("areAllApplicationsReady: return false");
                 return false;
             }
         }
+
         if (VDBG) {
             log("areAllApplicationsReady: outside loop, return " + (mUiccApplication != null));
         }
@@ -910,7 +1006,7 @@
 
     private boolean areAllRecordsLoaded() {
         for (UiccCardApplication app : mUiccApplications) {
-            if (app != null && isSupportedApplication(app)) {
+            if (app != null && isSupportedApplication(app) && !app.isAppIgnored()) {
                 IccRecords ir = app.getIccRecords();
                 if (ir == null || !ir.isLoaded()) {
                     if (VDBG) log("areAllRecordsLoaded: return false");
@@ -947,6 +1043,20 @@
     }
 
     /**
+     * Registers the handler when operator brand name is overridden.
+     *
+     * @param h Handler for notification message.
+     * @param what User-defined message code.
+     * @param obj User object.
+     */
+    public void registerForOpertorBrandOverride(Handler h, int what, Object obj) {
+        synchronized (mLock) {
+            Registrant r = new Registrant(h, what, obj);
+            mOperatorBrandOverrideRegistrants.add(r);
+        }
+    }
+
+    /**
      * Registers the handler when carrier privilege rules are loaded.
      *
      * @param h Handler for notification message.
@@ -975,6 +1085,17 @@
             mCarrierPrivilegeRegistrants.remove(h);
         }
     }
+    
+     /**
+     * Unregister for notifications when operator brand name is overriden.
+     *
+     * @param h Handler to be removed from the registrant list.
+     */
+    public void unregisterForOperatorBrandOverride(Handler h) {
+        synchronized (mLock) {
+            mOperatorBrandOverrideRegistrants.remove(h);
+        }
+    }
 
     private boolean isPackageInstalled(String pkgName) {
         PackageManager pm = mContext.getPackageManager();
@@ -1144,8 +1265,12 @@
      * Resets the application with the input AID. Returns true if any changes were made.
      *
      * A null aid implies a card level reset - all applications must be reset.
+     *
+     * @param aid aid of the application which should be reset; null imples all applications
+     * @param reset true if reset is required. false for initialization.
+     * @return boolean indicating if there was any change made as part of the reset
      */
-    public boolean resetAppWithAid(String aid) {
+    public boolean resetAppWithAid(String aid, boolean reset) {
         synchronized (mLock) {
             boolean changed = false;
             for (int i = 0; i < mUiccApplications.length; i++) {
@@ -1157,11 +1282,12 @@
                     changed = true;
                 }
             }
-            if (TextUtils.isEmpty(aid)) {
+            if (reset && TextUtils.isEmpty(aid)) {
                 if (mCarrierPrivilegeRules != null) {
                     mCarrierPrivilegeRules = null;
                     changed = true;
                 }
+                // CatService shall be disposed only when a card level reset happens.
                 if (mCatService != null) {
                     mCatService.dispose();
                     mCatService = null;
@@ -1176,19 +1302,19 @@
      * Exposes {@link CommandsInterface#iccOpenLogicalChannel}
      */
     public void iccOpenLogicalChannel(String aid, int p2, Message response) {
-        loglocal("Open Logical Channel: " + aid + " , " + p2 + " by pid:" + Binder.getCallingPid()
+        loglocal("iccOpenLogicalChannel: " + aid + " , " + p2 + " by pid:" + Binder.getCallingPid()
                 + " uid:" + Binder.getCallingUid());
         mCi.iccOpenLogicalChannel(aid, p2,
-                obtainMessage(EVENT_OPEN_LOGICAL_CHANNEL_DONE, response));
+                mHandler.obtainMessage(EVENT_OPEN_LOGICAL_CHANNEL_DONE, response));
     }
 
     /**
      * Exposes {@link CommandsInterface#iccCloseLogicalChannel}
      */
     public void iccCloseLogicalChannel(int channel, Message response) {
-        loglocal("Close Logical Channel: " + channel);
+        loglocal("iccCloseLogicalChannel: " + channel);
         mCi.iccCloseLogicalChannel(channel,
-                obtainMessage(EVENT_CLOSE_LOGICAL_CHANNEL_DONE, response));
+                mHandler.obtainMessage(EVENT_CLOSE_LOGICAL_CHANNEL_DONE, response));
     }
 
     /**
@@ -1197,7 +1323,7 @@
     public void iccTransmitApduLogicalChannel(int channel, int cla, int command,
             int p1, int p2, int p3, String data, Message response) {
         mCi.iccTransmitApduLogicalChannel(channel, cla, command, p1, p2, p3,
-                data, obtainMessage(EVENT_TRANSMIT_APDU_LOGICAL_CHANNEL_DONE, response));
+                data, mHandler.obtainMessage(EVENT_TRANSMIT_APDU_LOGICAL_CHANNEL_DONE, response));
     }
 
     /**
@@ -1206,7 +1332,7 @@
     public void iccTransmitApduBasicChannel(int cla, int command,
             int p1, int p2, int p3, String data, Message response) {
         mCi.iccTransmitApduBasicChannel(cla, command, p1, p2, p3,
-                data, obtainMessage(EVENT_TRANSMIT_APDU_BASIC_CHANNEL_DONE, response));
+                data, mHandler.obtainMessage(EVENT_TRANSMIT_APDU_BASIC_CHANNEL_DONE, response));
     }
 
     /**
@@ -1215,7 +1341,7 @@
     public void iccExchangeSimIO(int fileID, int command, int p1, int p2, int p3,
             String pathID, Message response) {
         mCi.iccIO(command, fileID, pathID, p1, p2, p3, null, null,
-                obtainMessage(EVENT_SIM_IO_DONE, response));
+                mHandler.obtainMessage(EVENT_SIM_IO_DONE, response));
     }
 
     /**
@@ -1351,6 +1477,7 @@
         } else {
             spEditor.putString(key, brand).commit();
         }
+        mOperatorBrandOverrideRegistrants.notifyRegistrants();
         return true;
     }
 
@@ -1363,21 +1490,7 @@
             return null;
         }
         SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
-        String brandName = sp.getString(OPERATOR_BRAND_OVERRIDE_PREFIX + iccId, null);
-        if (brandName == null) {
-            // Check if  CarrierConfig sets carrier name
-            CarrierConfigManager manager = (CarrierConfigManager)
-                    mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
-            int subId = SubscriptionController.getInstance().getSubIdUsingPhoneId(mPhoneId);
-            if (manager != null) {
-                PersistableBundle bundle = manager.getConfigForSubId(subId);
-                if (bundle != null && bundle.getBoolean(
-                        CarrierConfigManager.KEY_CARRIER_NAME_OVERRIDE_BOOL)) {
-                    brandName = bundle.getString(CarrierConfigManager.KEY_CARRIER_NAME_STRING);
-                }
-            }
-        }
-        return brandName;
+        return sp.getString(OPERATOR_BRAND_OVERRIDE_PREFIX + iccId, null);
     }
 
     /**
@@ -1405,7 +1518,7 @@
     }
 
     private void loglocal(String msg) {
-        if (DBG) sLocalLog.log(msg);
+        if (DBG) UiccController.sLocalLog.log("UiccProfile[" + mPhoneId + "]: " + msg);
     }
 
     /**
@@ -1419,6 +1532,10 @@
             pw.println("  mCarrierPrivilegeRegistrants[" + i + "]="
                     + ((Registrant) mCarrierPrivilegeRegistrants.get(i)).getHandler());
         }
+        for (int i = 0; i < mOperatorBrandOverrideRegistrants.size(); i++) {
+            pw.println("  mOperatorBrandOverrideRegistrants[" + i + "]="
+                    + ((Registrant) mOperatorBrandOverrideRegistrants.get(i)).getHandler());
+        }
         pw.println(" mUniversalPinState=" + mUniversalPinState);
         pw.println(" mGsmUmtsSubscriptionAppIndex=" + mGsmUmtsSubscriptionAppIndex);
         pw.println(" mCdmaSubscriptionAppIndex=" + mCdmaSubscriptionAppIndex);
@@ -1464,22 +1581,16 @@
         }
         pw.flush();
 
-        if (ICC_CARD_PROXY_REMOVED) {
-            pw.println(" mNetworkLockedRegistrants: size=" + mNetworkLockedRegistrants.size());
-            for (int i = 0; i < mNetworkLockedRegistrants.size(); i++) {
-                pw.println("  mNetworkLockedRegistrants[" + i + "]="
-                        + ((Registrant) mNetworkLockedRegistrants.get(i)).getHandler());
-            }
-            pw.println(" mCurrentAppType=" + mCurrentAppType);
-            pw.println(" mUiccCard=" + mUiccCard);
-            pw.println(" mUiccApplication=" + mUiccApplication);
-            pw.println(" mIccRecords=" + mIccRecords);
-            pw.println(" mExternalState=" + mExternalState);
-            pw.flush();
+        pw.println(" mNetworkLockedRegistrants: size=" + mNetworkLockedRegistrants.size());
+        for (int i = 0; i < mNetworkLockedRegistrants.size(); i++) {
+            pw.println("  mNetworkLockedRegistrants[" + i + "]="
+                    + ((Registrant) mNetworkLockedRegistrants.get(i)).getHandler());
         }
-
-        pw.println("sLocalLog:");
-        sLocalLog.dump(fd, pw, args);
+        pw.println(" mCurrentAppType=" + mCurrentAppType);
+        pw.println(" mUiccCard=" + mUiccCard);
+        pw.println(" mUiccApplication=" + mUiccApplication);
+        pw.println(" mIccRecords=" + mIccRecords);
+        pw.println(" mExternalState=" + mExternalState);
         pw.flush();
     }
 }
diff --git a/src/java/com/android/internal/telephony/uicc/UiccSlot.java b/src/java/com/android/internal/telephony/uicc/UiccSlot.java
index aa141ab..25ef637 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccSlot.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccSlot.java
@@ -27,7 +27,6 @@
 import android.os.Message;
 import android.os.PowerManager;
 import android.telephony.Rlog;
-import android.util.LocalLog;
 import android.view.WindowManager;
 
 import com.android.internal.R;
@@ -53,6 +52,7 @@
 
     private final Object mLock = new Object();
     private boolean mActive;
+    private boolean mStateIsUnknown = true;
     private CardState mCardState;
     private Context mContext;
     private CommandsInterface mCi;
@@ -66,22 +66,22 @@
     private static final int EVENT_CARD_REMOVED = 13;
     private static final int EVENT_CARD_ADDED = 14;
 
-    private static final LocalLog sLocalLog = new LocalLog(100);
-
     public UiccSlot(Context c, boolean isActive) {
         if (DBG) log("Creating");
         mContext = c;
         mActive = isActive;
-        mCardState = CardState.CARDSTATE_ABSENT;
+        mCardState = null;
     }
 
     /**
      * Update slot. The main trigger for this is a change in the ICC Card status.
      */
     public void update(CommandsInterface ci, IccCardStatus ics, int phoneId) {
+        if (DBG) log("cardStatus update: " + ics.toString());
         synchronized (mLock) {
             CardState oldState = mCardState;
             mCardState = ics.mCardState;
+            mIccId = ics.iccid;
             mPhoneId = phoneId;
             parseAtr(ics.atr);
             mCi = ci;
@@ -91,22 +91,14 @@
                 log("update: radioState=" + radioState + " mLastRadioState=" + mLastRadioState);
             }
 
-            if (oldState != CardState.CARDSTATE_ABSENT
-                    && mCardState == CardState.CARDSTATE_ABSENT) {
-                // No notifications while radio is off or we just powering up
-                if (radioState == RadioState.RADIO_ON && mLastRadioState == RadioState.RADIO_ON) {
-                    if (DBG) log("update: notify card removed");
-                    sendMessage(obtainMessage(EVENT_CARD_REMOVED, null));
-                }
-
-                UiccProfile.broadcastInternalIccStateChangedIntent(
-                        IccCardConstants.INTENT_VALUE_ICC_ABSENT, null, mPhoneId);
-
-                // no card present in the slot now; dispose card and make mUiccCard null
-                mUiccCard.dispose();
-                mUiccCard = null;
-            } else if (oldState == CardState.CARDSTATE_ABSENT
-                    && mCardState != CardState.CARDSTATE_ABSENT) {
+            if (absentStateUpdateNeeded(oldState)) {
+                updateCardStateAbsent();
+            // Because mUiccCard may be updated in both IccCardStatus and IccSlotStatus, we need to
+            // create a new UiccCard instance in two scenarios:
+            //   1. mCardState is changing from ABSENT to non ABSENT.
+            //   2. The latest mCardState is not ABSENT, but there is no UiccCard instance.
+            } else if ((oldState == null || oldState == CardState.CARDSTATE_ABSENT
+                    || mUiccCard == null) && mCardState != CardState.CARDSTATE_ABSENT) {
                 // No notifications while radio is off or we just powering up
                 if (radioState == RadioState.RADIO_ON && mLastRadioState == RadioState.RADIO_ON) {
                     if (DBG) log("update: notify card added");
@@ -120,9 +112,9 @@
                 }
 
                 if (!mIsEuicc) {
-                    mUiccCard = new UiccCard(mContext, mCi, ics, mPhoneId);
+                    mUiccCard = new UiccCard(mContext, mCi, ics, mPhoneId, mLock);
                 } else {
-                    mUiccCard = new EuiccCard(mContext, mCi, ics, phoneId);
+                    mUiccCard = new EuiccCard(mContext, mCi, ics, phoneId, mLock);
                 }
             } else {
                 if (mUiccCard != null) {
@@ -137,32 +129,73 @@
      * Update slot based on IccSlotStatus.
      */
     public void update(CommandsInterface ci, IccSlotStatus iss) {
-        log("slotStatus update");
+        if (DBG) log("slotStatus update: " + iss.toString());
         synchronized (mLock) {
+            CardState oldState = mCardState;
             mCi = ci;
+            parseAtr(iss.atr);
+            mCardState = iss.cardState;
+            mIccId = iss.iccid;
             if (iss.slotState == IccSlotStatus.SlotState.SLOTSTATE_INACTIVE) {
+                // TODO: (b/79432584) evaluate whether should broadcast card state change
+                // even if it's inactive.
                 if (mActive) {
                     mActive = false;
                     mLastRadioState = RadioState.RADIO_UNAVAILABLE;
                     mPhoneId = INVALID_PHONE_ID;
                     if (mUiccCard != null) mUiccCard.dispose();
-                    mUiccCard = null;
+                    nullifyUiccCard(true /* sim state is unknown */);
                 }
-                parseAtr(iss.atr);
-                mCardState = iss.cardState;
-                mIccId = iss.iccid;
-            } else if (!mActive && iss.slotState == IccSlotStatus.SlotState.SLOTSTATE_ACTIVE) {
+            } else {
                 mActive = true;
-                parseAtr(iss.atr);
-                // todo - ignoring these fields for now; relying on sim state changed to update
-                // these
-                //      iss.cardState;
-                //      iss.iccid;
-                //      iss.logicalSlotIndex;
+                mPhoneId = iss.logicalSlotIndex;
+                if (absentStateUpdateNeeded(oldState)) {
+                    updateCardStateAbsent();
+                }
+                // TODO: (b/79432584) Create UiccCard or EuiccCard object here.
+                // Right now It's OK not creating it because Card status update will do it.
+                // But we should really make them symmetric.
             }
         }
     }
 
+    private boolean absentStateUpdateNeeded(CardState oldState) {
+        return (oldState != CardState.CARDSTATE_ABSENT || mUiccCard != null)
+                && mCardState == CardState.CARDSTATE_ABSENT;
+    }
+
+    private void updateCardStateAbsent() {
+        RadioState radioState =
+                (mCi == null) ? RadioState.RADIO_UNAVAILABLE : mCi.getRadioState();
+        // No notifications while radio is off or we just powering up
+        if (radioState == RadioState.RADIO_ON && mLastRadioState == RadioState.RADIO_ON) {
+            if (DBG) log("update: notify card removed");
+            sendMessage(obtainMessage(EVENT_CARD_REMOVED, null));
+        }
+
+        UiccController.updateInternalIccState(
+                IccCardConstants.INTENT_VALUE_ICC_ABSENT, null, mPhoneId);
+
+        // no card present in the slot now; dispose card and make mUiccCard null
+        if (mUiccCard != null) {
+            mUiccCard.dispose();
+        }
+        nullifyUiccCard(false /* sim state is not unknown */);
+        mLastRadioState = radioState;
+    }
+
+    // whenever we set mUiccCard to null, we lose the ability to differentiate between absent and
+    // unknown states. To mitigate this, we will us mStateIsUnknown to keep track. The sim is only
+    // unknown if we haven't heard from the radio or if the radio has become unavailable.
+    private void nullifyUiccCard(boolean stateUnknown) {
+        mStateIsUnknown = stateUnknown;
+        mUiccCard = null;
+    }
+
+    public boolean isStateUnknown() {
+        return (mCardState == null || mCardState == CardState.CARDSTATE_ABSENT) && mStateIsUnknown;
+    }
+
     private void checkIsEuiccSupported() {
         if (mAtr != null && mAtr.isEuiccSupported()) {
             mIsEuicc = true;
@@ -201,6 +234,10 @@
         }
     }
 
+    public boolean isExtendedApduSupported() {
+        return  (mAtr != null && mAtr.isExtendedApduSupported());
+    }
+
     @Override
     protected void finalize() {
         if (DBG) log("UiccSlot finalized");
@@ -300,7 +337,11 @@
      */
     public CardState getCardState() {
         synchronized (mLock) {
-            return mCardState;
+            if (mCardState == null) {
+                return CardState.CARDSTATE_ABSENT;
+            } else {
+                return mCardState;
+            }
         }
     }
 
@@ -320,10 +361,10 @@
         if (mUiccCard != null) {
             mUiccCard.dispose();
         }
-        mUiccCard = null;
+        nullifyUiccCard(true /* sim state is unknown */);
 
         if (mPhoneId != INVALID_PHONE_ID) {
-            UiccProfile.broadcastInternalIccStateChangedIntent(
+            UiccController.updateInternalIccState(
                     IccCardConstants.INTENT_VALUE_ICC_UNKNOWN, null, mPhoneId);
         }
 
@@ -339,10 +380,6 @@
         Rlog.e(TAG, msg);
     }
 
-    private void loglocal(String msg) {
-        if (DBG) sLocalLog.log(msg);
-    }
-
     /**
      * Dump
      */
@@ -360,8 +397,6 @@
         }
         pw.println();
         pw.flush();
-        pw.println("sLocalLog:");
-        sLocalLog.dump(fd, pw, args);
         pw.flush();
     }
 }
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 6acfb80..103c75b 100644
--- a/src/java/com/android/internal/telephony/uicc/euicc/EuiccCard.java
+++ b/src/java/com/android/internal/telephony/uicc/euicc/EuiccCard.java
@@ -116,8 +116,8 @@
     private EuiccSpecVersion mSpecVersion;
     private volatile String mEid;
 
-    public EuiccCard(Context c, CommandsInterface ci, IccCardStatus ics, int phoneId) {
-        super(c, ci, ics, phoneId);
+    public EuiccCard(Context c, CommandsInterface ci, IccCardStatus ics, int phoneId, Object lock) {
+        super(c, ci, ics, phoneId, lock);
         // TODO: Set supportExtendedApdu based on ATR.
         mApduSender = new ApduSender(ci, ISD_R_AID, false /* supportExtendedApdu */);
 
diff --git a/src/java/com/android/internal/telephony/util/NotificationChannelController.java b/src/java/com/android/internal/telephony/util/NotificationChannelController.java
index 54d7d1a..3f6da54 100644
--- a/src/java/com/android/internal/telephony/util/NotificationChannelController.java
+++ b/src/java/com/android/internal/telephony/util/NotificationChannelController.java
@@ -57,6 +57,8 @@
                 NotificationManager.IMPORTANCE_DEFAULT);
         alertChannel.setSound(Settings.System.DEFAULT_NOTIFICATION_URI,
                 new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_NOTIFICATION).build());
+        // allow users to block notifications from system
+        alertChannel.setBlockableSystem(true);
 
         final NotificationChannel mobileDataStatusChannel = new NotificationChannel(
                 CHANNEL_ID_MOBILE_DATA_STATUS,
diff --git a/src/java/com/android/internal/telephony/util/SMSDispatcherUtil.java b/src/java/com/android/internal/telephony/util/SMSDispatcherUtil.java
index a3c7926..17f2611 100644
--- a/src/java/com/android/internal/telephony/util/SMSDispatcherUtil.java
+++ b/src/java/com/android/internal/telephony/util/SMSDispatcherUtil.java
@@ -36,17 +36,6 @@
     private SMSDispatcherUtil() {}
 
     /**
-     * Whether to block SMS or not.
-     *
-     * @param isCdma true if cdma format should be used.
-     * @param phone the Phone to use
-     * @return true to block sms; false otherwise.
-     */
-    public static boolean shouldBlockSms(boolean isCdma, Phone phone) {
-        return isCdma && phone.isInEcm();
-    }
-
-    /**
      * Trigger the proper implementation for getting submit pdu for text sms based on format.
      *
      * @param isCdma true if cdma format should be used.
diff --git a/src/java/com/android/internal/telephony/util/TimeStampedValue.java b/src/java/com/android/internal/telephony/util/TimeStampedValue.java
deleted file mode 100644
index e2628f6..0000000
--- a/src/java/com/android/internal/telephony/util/TimeStampedValue.java
+++ /dev/null
@@ -1,73 +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.util;
-
-import android.os.SystemClock;
-
-/**
- * A pair containing a value and an associated time stamp.
- *
- * @param <T> The type of the value.
- */
-public final class TimeStampedValue<T> {
-
-    /** The value. */
-    public final T mValue;
-
-    /**
-     * The value of {@link SystemClock#elapsedRealtime} or equivalent when value was
-     * determined.
-     */
-    public final long mElapsedRealtime;
-
-    public TimeStampedValue(T value, long elapsedRealtime) {
-        this.mValue = value;
-        this.mElapsedRealtime = elapsedRealtime;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (o == null || getClass() != o.getClass()) {
-            return false;
-        }
-
-        TimeStampedValue<?> that = (TimeStampedValue<?>) o;
-
-        if (mElapsedRealtime != that.mElapsedRealtime) {
-            return false;
-        }
-        return mValue != null ? mValue.equals(that.mValue) : that.mValue == null;
-    }
-
-    @Override
-    public int hashCode() {
-        int result = mValue != null ? mValue.hashCode() : 0;
-        result = 31 * result + (int) (mElapsedRealtime ^ (mElapsedRealtime >>> 32));
-        return result;
-    }
-
-    @Override
-    public String toString() {
-        return "TimeStampedValue{"
-                + "mValue=" + mValue
-                + ", elapsedRealtime=" + mElapsedRealtime
-                + '}';
-    }
-}
diff --git a/src/java/com/google/android/mms/pdu/PduComposer.java b/src/java/com/google/android/mms/pdu/PduComposer.java
index bb44bab..582caec 100644
--- a/src/java/com/google/android/mms/pdu/PduComposer.java
+++ b/src/java/com/google/android/mms/pdu/PduComposer.java
@@ -155,7 +155,8 @@
         /* make the message */
         switch (type) {
             case PduHeaders.MESSAGE_TYPE_SEND_REQ:
-                if (makeSendReqPdu() != PDU_COMPOSE_SUCCESS) {
+            case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF:
+                if (makeSendRetrievePdu(type) != PDU_COMPOSE_SUCCESS) {
                     return null;
                 }
                 break;
@@ -564,6 +565,7 @@
             case PduHeaders.PRIORITY:
             case PduHeaders.DELIVERY_REPORT:
             case PduHeaders.READ_REPORT:
+            case PduHeaders.RETRIEVE_STATUS:
                 int octet = mPduHeader.getOctet(field);
                 if (0 == octet) {
                     return PDU_COMPOSE_FIELD_NOT_SET;
@@ -584,6 +586,7 @@
                 break;
 
             case PduHeaders.SUBJECT:
+            case PduHeaders.RETRIEVE_TEXT:
                 EncodedStringValue enString =
                     mPduHeader.getEncodedStringValue(field);
                 if (null == enString) {
@@ -757,7 +760,7 @@
     /**
      * Make Send.req.
      */
-    private int makeSendReqPdu() {
+    private int makeSendRetrievePdu(int type) {
         if (mMessage == null) {
             mMessage = new ByteArrayOutputStream();
             mPosition = 0;
@@ -765,7 +768,7 @@
 
         // X-Mms-Message-Type
         appendOctet(PduHeaders.MESSAGE_TYPE);
-        appendOctet(PduHeaders.MESSAGE_TYPE_SEND_REQ);
+        appendOctet(type);
 
         // X-Mms-Transaction-ID
         appendOctet(PduHeaders.TRANSACTION_ID);
@@ -831,17 +834,25 @@
         // X-Mms-Read-Report Optional
         appendHeader(PduHeaders.READ_REPORT);
 
+        if (type == PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF) {
+            // X-Mms-Retrieve-Status Optional
+            appendHeader(PduHeaders.RETRIEVE_STATUS);
+            // X-Mms-Retrieve-Text Optional
+            appendHeader(PduHeaders.RETRIEVE_TEXT);
+        }
+
+
         //    Content-Type
         appendOctet(PduHeaders.CONTENT_TYPE);
 
         //  Message body
-        return makeMessageBody();
+        return makeMessageBody(type);
     }
 
     /**
      * Make message body.
      */
-    private int makeMessageBody() {
+    private int makeMessageBody(int type) {
         // 1. add body informations
         mStack.newbuf();  // Switching buffer because we need to
 
@@ -858,7 +869,12 @@
         appendShortInteger(contentTypeIdentifier.intValue());
 
         // content-type parameter: start
-        PduBody body = ((SendReq) mPdu).getBody();
+        PduBody body;
+        if (type == PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF) {
+            body = ((RetrieveConf) mPdu).getBody();
+        } else {
+            body = ((SendReq) mPdu).getBody();
+        }
         if (null == body || body.getPartsNum() == 0) {
             // empty message
             appendUintvarInteger(0);
diff --git a/tests/telephonytests/Android.mk b/tests/telephonytests/Android.mk
index 3356e68..6709d46 100644
--- a/tests/telephonytests/Android.mk
+++ b/tests/telephonytests/Android.mk
@@ -20,7 +20,4 @@
 
 LOCAL_COMPATIBILITY_SUITE := device-tests
 
-# b/72575505
-LOCAL_ERROR_PRONE_FLAGS := -Xep:JUnit4TestNotRun:WARN
-
 include $(BUILD_PACKAGE)
diff --git a/tests/telephonytests/src/android/telephony/ims/ImsRegistrationTests.java b/tests/telephonytests/src/android/telephony/ims/ImsRegistrationTests.java
index 65cb5f7..3810f76 100644
--- a/tests/telephonytests/src/android/telephony/ims/ImsRegistrationTests.java
+++ b/tests/telephonytests/src/android/telephony/ims/ImsRegistrationTests.java
@@ -68,8 +68,8 @@
     @Test
     public void testRegistrationConfigParcel() {
         ImsFeatureConfiguration testConfig = new ImsFeatureConfiguration.Builder()
-                .addFeature(ImsFeature.FEATURE_MMTEL)
-                .addFeature(ImsFeature.FEATURE_RCS)
+                .addFeature(/*slotId*/ 0, ImsFeature.FEATURE_MMTEL)
+                .addFeature(/*slotId*/ 0, ImsFeature.FEATURE_RCS)
                 .build();
         Parcel p = Parcel.obtain();
         testConfig.writeToParcel(p, 0);
@@ -85,14 +85,14 @@
     @Test
     public void testRegistrationConfigPermutationEqual() {
         ImsFeatureConfiguration testConfig = new ImsFeatureConfiguration.Builder()
-                .addFeature(ImsFeature.FEATURE_MMTEL)
-                .addFeature(ImsFeature.FEATURE_RCS)
+                .addFeature(/*slotId*/ 0, ImsFeature.FEATURE_MMTEL)
+                .addFeature(/*slotId*/ 0, ImsFeature.FEATURE_RCS)
                 .build();
 
         // Permute field insertion ordering to ensure order doesn't matter.
         ImsFeatureConfiguration testConfig2 = new ImsFeatureConfiguration.Builder()
-                .addFeature(ImsFeature.FEATURE_RCS)
-                .addFeature(ImsFeature.FEATURE_MMTEL)
+                .addFeature(/*slotId*/ 0, ImsFeature.FEATURE_RCS)
+                .addFeature(/*slotId*/ 0, ImsFeature.FEATURE_MMTEL)
                 .build();
 
         assertEquals(testConfig, testConfig2);
@@ -101,13 +101,16 @@
     @SmallTest
     @Test
     public void testRegistrationConfigConstructorsEqual() {
-        ImsFeatureConfiguration testConfig = new ImsFeatureConfiguration(
-                new int[] {ImsFeature.FEATURE_MMTEL, ImsFeature.FEATURE_RCS});
+        // Permute field insertion ordering to ensure order doesn't matter.
+        ImsFeatureConfiguration testConfig = new ImsFeatureConfiguration.Builder()
+                .addFeature(/*slotId*/ 0, ImsFeature.FEATURE_MMTEL)
+                .addFeature(/*slotId*/ 0, ImsFeature.FEATURE_RCS)
+                .build();
 
         // Permute field insertion ordering to ensure order doesn't matter.
         ImsFeatureConfiguration testConfig2 = new ImsFeatureConfiguration.Builder()
-                .addFeature(ImsFeature.FEATURE_RCS)
-                .addFeature(ImsFeature.FEATURE_MMTEL)
+                .addFeature(/*slotId*/ 0, ImsFeature.FEATURE_RCS)
+                .addFeature(/*slotId*/ 0, ImsFeature.FEATURE_MMTEL)
                 .build();
 
         assertEquals(testConfig, testConfig2);
diff --git a/tests/telephonytests/src/android/telephony/ims/ImsServiceTest.java b/tests/telephonytests/src/android/telephony/ims/ImsServiceTest.java
index 37fe7b7..34febcb 100644
--- a/tests/telephonytests/src/android/telephony/ims/ImsServiceTest.java
+++ b/tests/telephonytests/src/android/telephony/ims/ImsServiceTest.java
@@ -175,8 +175,8 @@
     @SmallTest
     public void testQuerySupportedImsFeatures() throws RemoteException {
         ImsFeatureConfiguration config = new ImsFeatureConfiguration.Builder()
-                .addFeature(ImsFeature.FEATURE_MMTEL)
-                .addFeature(ImsFeature.FEATURE_RCS)
+                .addFeature(0, ImsFeature.FEATURE_MMTEL)
+                .addFeature(0, ImsFeature.FEATURE_RCS)
                 .build();
         mTestImsService.testFeatureConfig = config;
 
diff --git a/tests/telephonytests/src/android/telephony/ims/MmTelFeatureTests.java b/tests/telephonytests/src/android/telephony/ims/MmTelFeatureTests.java
index ccbc251..6be2fb0 100644
--- a/tests/telephonytests/src/android/telephony/ims/MmTelFeatureTests.java
+++ b/tests/telephonytests/src/android/telephony/ims/MmTelFeatureTests.java
@@ -17,14 +17,19 @@
 package android.telephony.ims;
 
 import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertTrue;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 
-import android.os.RemoteException;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
 import android.support.test.runner.AndroidJUnit4;
+import android.telecom.TelecomManager;
 import android.telephony.ims.aidl.IImsMmTelFeature;
 import android.telephony.ims.feature.ImsFeature;
 import android.telephony.ims.feature.MmTelFeature;
@@ -32,6 +37,7 @@
 import android.test.suitebuilder.annotation.SmallTest;
 
 import com.android.ims.internal.IImsCallSession;
+import com.android.internal.telephony.ims.ImsTestBase;
 
 import org.junit.After;
 import org.junit.Before;
@@ -41,23 +47,55 @@
 import org.mockito.Mockito;
 
 @RunWith(AndroidJUnit4.class)
-public class MmTelFeatureTests {
+public class MmTelFeatureTests extends ImsTestBase {
 
     private static final int TEST_CAPABILITY = 1;
     private static final int TEST_RADIO_TECH = 0;
 
+    private static final int TEST_TTY_RESULT = 0;
+    private static final int TEST_SEND_DTMF_RESULT = 1;
+    private static final int TEST_RESULT_MAX = 2;
+
+    private static final int TEST_RESULT_DELAY_MS = 5000;
+
     private android.telephony.ims.TestMmTelFeature mFeature;
     private IImsMmTelFeature mFeatureBinder;
     private ImsFeature.CapabilityCallback mCapabilityCallback;
     private MmTelFeature.Listener mListener;
 
+    // set to true when the handler receives a message back from the Feature.
+    private boolean[] mHandlerResults;
+
+    private class TestHandler extends Handler {
+
+        TestHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case TEST_TTY_RESULT:
+                    mHandlerResults[TEST_TTY_RESULT] = true;
+                    break;
+                case TEST_SEND_DTMF_RESULT:
+                    mHandlerResults[TEST_SEND_DTMF_RESULT] = true;
+                    break;
+            }
+        }
+    }
+    private final Handler mHandler = new TestHandler(Looper.getMainLooper());
+    private final Messenger mHandlerMessenger = new Messenger(mHandler);
+
     @Before
-    public void setup() throws RemoteException {
+    public void setup() throws Exception {
+        super.setUp();
         mFeature = new TestMmTelFeature();
         mFeatureBinder = mFeature.getBinder();
         mCapabilityCallback = spy(new ImsFeature.CapabilityCallback());
         mListener = spy(new MmTelFeature.Listener());
         mFeatureBinder.setListener(mListener);
+        mHandlerResults = new boolean[TEST_RESULT_MAX];
     }
 
     @After
@@ -91,4 +129,25 @@
 
         assertEquals(sessionBinder, captor.getValue());
     }
+
+    @SmallTest
+    @Test
+    public void testSetTtyMessageMessenger() throws Exception {
+        Message resultMessage = Message.obtain(mHandler, TEST_TTY_RESULT);
+        resultMessage.replyTo = mHandlerMessenger;
+        mFeatureBinder.setUiTtyMode(TelecomManager.TTY_MODE_FULL, resultMessage);
+        waitForHandlerAction(mHandler, TEST_RESULT_DELAY_MS);
+        assertTrue(mHandlerResults[TEST_TTY_RESULT]);
+    }
+
+    @SmallTest
+    @Test
+    public void testSendDtmfMessageMessenger() throws Exception {
+        Message resultMessage = Message.obtain(mHandler, TEST_SEND_DTMF_RESULT);
+        resultMessage.replyTo = mHandlerMessenger;
+        IImsCallSession callSession = mFeatureBinder.createCallSession(null);
+        callSession.sendDtmf('0', resultMessage);
+        waitForHandlerAction(mHandler, TEST_RESULT_DELAY_MS);
+        assertTrue(mHandlerResults[TEST_SEND_DTMF_RESULT]);
+    }
 }
diff --git a/tests/telephonytests/src/android/telephony/ims/TestMmTelFeature.java b/tests/telephonytests/src/android/telephony/ims/TestMmTelFeature.java
index 795d1c0..6414403 100644
--- a/tests/telephonytests/src/android/telephony/ims/TestMmTelFeature.java
+++ b/tests/telephonytests/src/android/telephony/ims/TestMmTelFeature.java
@@ -16,6 +16,7 @@
 
 package android.telephony.ims;
 
+import android.os.Message;
 import android.os.RemoteException;
 import android.telephony.ims.feature.CapabilityChangeRequest;
 import android.telephony.ims.feature.ImsFeature;
@@ -33,6 +34,22 @@
     public CapabilityChangeRequest lastRequest;
     public boolean isUtInterfaceCalled = false;
 
+    private final TestImsCallSession mCallSession = new TestImsCallSession();
+    private class TestImsCallSession extends ImsCallSessionImplBase {
+
+        @Override
+        public void sendDtmf(char c, Message result) {
+            // Just call result to signify complete for test.
+            if (result.replyTo != null) {
+                try {
+                    result.replyTo.send(result);
+                } catch (RemoteException e) {
+                    // eat error, test will fail.
+                }
+            }
+        }
+    }
+
     public void incomingCall(ImsCallSessionImplBase c) throws RemoteException {
         notifyIncomingCall(c, null);
     }
@@ -44,7 +61,7 @@
 
     @Override
     public ImsCallSessionImplBase createCallSession(ImsCallProfile profile) {
-        return super.createCallSession(profile);
+        return mCallSession;
     }
 
     @Override
@@ -64,6 +81,16 @@
     }
 
     @Override
+    public void setUiTtyMode(int mode, Message onCompleteMessage) {
+        try {
+            // just send complete message.
+            onCompleteMessage.replyTo.send(onCompleteMessage);
+        } catch (RemoteException e) {
+            // do nothing, test will fail.
+        }
+    }
+
+    @Override
     public boolean queryCapabilityConfiguration(@MmTelCapabilities.MmTelCapability int capability,
             @ImsRegistrationImplBase.ImsRegistrationTech int radioTech) {
         // Base implementation - Override to provide functionality
diff --git a/tests/telephonytests/src/android/telephony/mbms/MbmsReceiverTest.java b/tests/telephonytests/src/android/telephony/mbms/MbmsReceiverTest.java
new file mode 100644
index 0000000..46cf4f7
--- /dev/null
+++ b/tests/telephonytests/src/android/telephony/mbms/MbmsReceiverTest.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.mbms;
+
+import static org.junit.Assert.assertEquals;
+
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+
+@RunWith(AndroidJUnit4.class)
+public class MbmsReceiverTest {
+    @Test
+    @SmallTest
+    public void testFileHierarchyRecreation() throws Exception {
+        String rootPath = "http://www.example.com/files/";
+        assertEquals("subdir1/file.txt",
+                MbmsDownloadReceiver.getFileRelativePath(rootPath, rootPath + "/subdir1/file.txt"));
+        assertEquals("subdir1/subdir2/file.txt",
+                MbmsDownloadReceiver.getFileRelativePath(
+                        rootPath,
+                        rootPath + "/subdir1/subdir2/file.txt"));
+        assertEquals("file.txt",
+                MbmsDownloadReceiver.getFileRelativePath(
+                        rootPath + "/subdir1/file.*",
+                        rootPath + "/subdir1/file.txt"));
+        assertEquals("file.txt",
+                MbmsDownloadReceiver.getFileRelativePath(
+                        rootPath + "/subdir1/*",
+                        rootPath + "/subdir1/file.txt"));
+        assertEquals("subdir1/file.txt",
+                MbmsDownloadReceiver.getFileRelativePath(
+                        rootPath + "/subdir*",
+                        rootPath + "/subdir1/file.txt"));
+        assertEquals("file.txt",
+                MbmsDownloadReceiver.getFileRelativePath(
+                        rootPath,
+                        rootPath + "/file.txt"));
+        assertEquals("file.txt",
+                MbmsDownloadReceiver.getFileRelativePath(
+                        rootPath + "/*",
+                        rootPath + "/file.txt"));
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CarrierKeyDownloadMgrTest.java b/tests/telephonytests/src/com/android/internal/telephony/CarrierKeyDownloadMgrTest.java
index 4b99cbe..623cf13 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CarrierKeyDownloadMgrTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CarrierKeyDownloadMgrTest.java
@@ -45,6 +45,7 @@
 import java.util.GregorianCalendar;
 
 import static android.preference.PreferenceManager.getDefaultSharedPreferences;
+
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -124,7 +125,7 @@
 
     /**
      * Checks if the expiration date is calculated correctly
-     * In this case the expiration date should be the expiration date of the key.
+     * In this case the expiration date should be within the window (7 to 21 days).
      **/
     @Test
     @SmallTest
@@ -133,16 +134,20 @@
         mCarrierKeyDM.mKeyAvailability = 3;
         SimpleDateFormat dt = new SimpleDateFormat("yyyy-mm-dd");
         Calendar cal = new GregorianCalendar();
-        cal.add(Calendar.DATE, 10);
+        cal.add(Calendar.DATE, 30);
         Date date = cal.getTime();
-        Calendar expectedCal = new GregorianCalendar();
-        expectedCal.add(Calendar.DATE, 3);
-        String dateExpected = dt.format(expectedCal.getTime());
+        Calendar minExpirationCal = new GregorianCalendar();
+        Calendar maxExpirationCal = new GregorianCalendar();
+        minExpirationCal.add(Calendar.DATE, 23);
+        maxExpirationCal.add(Calendar.DATE, 9);
+        Date minExpirationDate = minExpirationCal.getTime();
+        Date maxExpirationDate = maxExpirationCal.getTime();
         ImsiEncryptionInfo imsiEncryptionInfo = new ImsiEncryptionInfo("mcc", "mnc", 1,
                 "keyIdentifier", publicKey, date);
         when(mPhone.getCarrierInfoForImsiEncryption(anyInt())).thenReturn(imsiEncryptionInfo);
         Date expirationDate = new Date(mCarrierKeyDM.getExpirationDate());
-        assertTrue(dt.format(expirationDate).equals(dateExpected));
+        assertTrue(expirationDate.before(minExpirationDate));
+        assertTrue(expirationDate.after(maxExpirationDate));
     }
 
     /**
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CarrierServiceStateTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/CarrierServiceStateTrackerTest.java
index 8be2f60..59d8872 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CarrierServiceStateTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CarrierServiceStateTrackerTest.java
@@ -16,22 +16,10 @@
 
 package com.android.internal.telephony;
 
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.res.Resources;
-import android.os.HandlerThread;
-import android.os.Message;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import static com.android.internal.telephony.TelephonyTestUtils.waitForMs;
 
 import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.isA;
@@ -39,6 +27,28 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.content.Intent;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.HandlerThread;
+import android.os.Message;
+import android.os.PersistableBundle;
+import android.provider.Settings;
+import android.telephony.CarrierConfigManager;
+import android.telephony.ServiceState;
+import android.test.mock.MockContentResolver;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Map;
+
 /**
  * Unit tests for {@link com.android.internal.telephony.CarrierServiceStateTracker}.
  */
@@ -47,25 +57,34 @@
     public static final String LOG_TAG = "CSST";
     public static final int TEST_TIMEOUT = 5000;
 
+    private CarrierServiceStateTracker mSpyCarrierSST;
     private CarrierServiceStateTracker mCarrierSST;
     private CarrierServiceStateTrackerTestHandler mCarrierServiceStateTrackerTestHandler;
-    private  CarrierServiceStateTracker.PrefNetworkNotification mPrefNetworkNotification;
-    private  CarrierServiceStateTracker.EmergencyNetworkNotification mEmergencyNetworkNotification;
+    private FakeContentResolver mFakeContentResolver;
 
-    @Mock Context mContext;
-    @Mock ServiceStateTracker mServiceStateTracker;
-    @Mock NotificationManager mNotificationManager;
-    @Mock Resources mResources;
+    NotificationManager mNotificationManager;
+    PersistableBundle mBundle;
+
+    private class FakeContentResolver extends MockContentResolver {
+        @Override
+        public void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork) {
+            super.notifyChange(uri, observer, syncToNetwork);
+            logd("onChanged(uri=" + uri + ")" + observer);
+            if (observer != null) {
+                observer.dispatchChange(false, uri);
+            }
+        }
+    }
 
     private class CarrierServiceStateTrackerTestHandler extends HandlerThread {
-
         private CarrierServiceStateTrackerTestHandler(String name) {
             super(name);
         }
 
         @Override
         public void onLooperPrepared() {
-            mCarrierSST = spy(new CarrierServiceStateTracker(mPhone, mServiceStateTracker));
+            mCarrierSST = new CarrierServiceStateTracker(mPhone, mSST);
+            mSpyCarrierSST = spy(mCarrierSST);
             setReady(true);
         }
     }
@@ -75,15 +94,29 @@
         MockitoAnnotations.initMocks(this);
         logd(LOG_TAG + "Setup!");
         super.setUp(getClass().getSimpleName());
+        mBundle = mContextFixture.getCarrierConfigBundle();
+        when(mPhone.getSubId()).thenReturn(1);
         mCarrierServiceStateTrackerTestHandler =
                 new CarrierServiceStateTrackerTestHandler(getClass().getSimpleName());
         mCarrierServiceStateTrackerTestHandler.start();
-        when(mContext.getResources()).thenReturn(mResources);
-        when(mContext.getPackageManager()).thenReturn(mPackageManager);
-        when(mContext.getApplicationInfo()).thenReturn(new ApplicationInfo());
+        mFakeContentResolver = new CarrierServiceStateTrackerTest.FakeContentResolver();
+
+        when(mPhone.getContext().getContentResolver()).thenReturn(mFakeContentResolver);
+
+        mNotificationManager = (NotificationManager) mContext.getSystemService(
+                Context.NOTIFICATION_SERVICE);
+
+        setDefaultValues();
         waitUntilReady();
     }
 
+    private void setDefaultValues() {
+        mBundle.putInt(CarrierConfigManager.KEY_PREF_NETWORK_NOTIFICATION_DELAY_INT,
+                0);
+        mBundle.putInt(CarrierConfigManager.KEY_EMERGENCY_NOTIFICATION_DELAY_INT,
+                0);
+    }
+
     @After
     public void tearDown() throws Exception {
         mCarrierServiceStateTrackerTestHandler.quit();
@@ -94,12 +127,12 @@
     @SmallTest
     public void testCancelBothNotifications() {
         logd(LOG_TAG + ":testCancelBothNotifications()");
-        Message notificationMsg = mCarrierSST.obtainMessage(
+        Message notificationMsg = mSpyCarrierSST.obtainMessage(
                 CarrierServiceStateTracker.CARRIER_EVENT_DATA_REGISTRATION, null);
-        doReturn(false).when(mCarrierSST).evaluateSendingMessage(any());
-        doReturn(mNotificationManager).when(mCarrierSST).getNotificationManager(any());
-        mCarrierSST.handleMessage(notificationMsg);
-        waitForHandlerAction(mCarrierSST, TEST_TIMEOUT);
+        doReturn(false).when(mSpyCarrierSST).evaluateSendingMessage(any());
+        doReturn(mNotificationManager).when(mSpyCarrierSST).getNotificationManager(any());
+        mSpyCarrierSST.handleMessage(notificationMsg);
+        waitForHandlerAction(mSpyCarrierSST, TEST_TIMEOUT);
         verify(mNotificationManager).cancel(
                 CarrierServiceStateTracker.NOTIFICATION_EMERGENCY_NETWORK);
         verify(mNotificationManager).cancel(
@@ -111,18 +144,95 @@
     public void testSendBothNotifications() {
         logd(LOG_TAG + ":testSendBothNotifications()");
         Notification.Builder mNotificationBuilder = new Notification.Builder(mContext);
-        Message notificationMsg = mCarrierSST.obtainMessage(
+        Message notificationMsg = mSpyCarrierSST.obtainMessage(
                 CarrierServiceStateTracker.CARRIER_EVENT_DATA_DEREGISTRATION, null);
-        doReturn(true).when(mCarrierSST).evaluateSendingMessage(any());
-        doReturn(false).when(mCarrierSST).isRadioOffOrAirplaneMode();
-        doReturn(0).when(mCarrierSST).getDelay(any());
-        doReturn(mNotificationBuilder).when(mCarrierSST).getNotificationBuilder(any());
-        doReturn(mNotificationManager).when(mCarrierSST).getNotificationManager(any());
-        mCarrierSST.handleMessage(notificationMsg);
-        waitForHandlerAction(mCarrierSST, TEST_TIMEOUT);
+        doReturn(true).when(mSpyCarrierSST).evaluateSendingMessage(any());
+        doReturn(false).when(mSpyCarrierSST).isRadioOffOrAirplaneMode();
+        doReturn(0).when(mSpyCarrierSST).getDelay(any());
+        doReturn(mNotificationBuilder).when(mSpyCarrierSST).getNotificationBuilder(any());
+        doReturn(mNotificationManager).when(mSpyCarrierSST).getNotificationManager(any());
+        mSpyCarrierSST.handleMessage(notificationMsg);
+        waitForHandlerAction(mSpyCarrierSST, TEST_TIMEOUT);
         verify(mNotificationManager).notify(
                 eq(CarrierServiceStateTracker.NOTIFICATION_PREF_NETWORK), isA(Notification.class));
         verify(mNotificationManager).notify(
                 eq(CarrierServiceStateTracker.NOTIFICATION_EMERGENCY_NETWORK), any());
     }
+
+    @Test
+    @SmallTest
+    public void testSendPrefNetworkNotification() {
+        logd(LOG_TAG + ":testSendPrefNetworkNotification()");
+        Intent intent = new Intent().setAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
+        mContext.sendBroadcast(intent);
+        waitForMs(300);
+
+        Map<Integer, CarrierServiceStateTracker.NotificationType> notificationTypeMap =
+                mCarrierSST.getNotificationTypeMap();
+        CarrierServiceStateTracker.NotificationType prefNetworkNotification =
+                notificationTypeMap.get(CarrierServiceStateTracker.NOTIFICATION_PREF_NETWORK);
+        CarrierServiceStateTracker.NotificationType spyPrefNetworkNotification = spy(
+                prefNetworkNotification);
+        notificationTypeMap.put(CarrierServiceStateTracker.NOTIFICATION_PREF_NETWORK,
+                spyPrefNetworkNotification);
+        Notification.Builder mNotificationBuilder = new Notification.Builder(mContext);
+        doReturn(ServiceState.STATE_OUT_OF_SERVICE).when(mSST.mSS).getVoiceRegState();
+        doReturn(ServiceState.STATE_OUT_OF_SERVICE).when(mSST.mSS).getDataRegState();
+        doReturn(true).when(mSST).isRadioOn();
+        doReturn(mNotificationBuilder).when(spyPrefNetworkNotification).getNotificationBuilder();
+
+        String prefNetworkMode = Settings.Global.PREFERRED_NETWORK_MODE + mPhone.getSubId();
+        Settings.Global.putInt(mFakeContentResolver, prefNetworkMode,
+                RILConstants.NETWORK_MODE_LTE_CDMA_EVDO);
+        mFakeContentResolver.notifyChange(
+                Settings.Global.getUriFor(prefNetworkMode), mSpyCarrierSST.getContentObserver());
+        waitForMs(500);
+        verify(mNotificationManager).notify(
+                eq(CarrierServiceStateTracker.NOTIFICATION_PREF_NETWORK), isA(Notification.class));
+
+        Settings.Global.putInt(mFakeContentResolver, prefNetworkMode,
+                RILConstants.NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA);
+        mFakeContentResolver.notifyChange(
+                Settings.Global.getUriFor(prefNetworkMode), mSpyCarrierSST.getContentObserver());
+        waitForMs(500);
+        verify(mNotificationManager, atLeast(1)).cancel(
+                CarrierServiceStateTracker.NOTIFICATION_PREF_NETWORK);
+    }
+
+    @Test
+    @SmallTest
+    public void testSendEmergencyNetworkNotification() {
+        logd(LOG_TAG + ":testSendEmergencyNetworkNotification()");
+        Intent intent = new Intent().setAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
+        mContext.sendBroadcast(intent);
+        waitForMs(300);
+
+        Map<Integer, CarrierServiceStateTracker.NotificationType> notificationTypeMap =
+                mCarrierSST.getNotificationTypeMap();
+        CarrierServiceStateTracker.NotificationType emergencyNetworkNotification =
+                notificationTypeMap.get(CarrierServiceStateTracker.NOTIFICATION_EMERGENCY_NETWORK);
+        CarrierServiceStateTracker.NotificationType spyEmergencyNetworkNotification = spy(
+                emergencyNetworkNotification);
+        notificationTypeMap.put(CarrierServiceStateTracker.NOTIFICATION_EMERGENCY_NETWORK,
+                spyEmergencyNetworkNotification);
+        Notification.Builder mNotificationBuilder = new Notification.Builder(mContext);
+        doReturn(ServiceState.STATE_OUT_OF_SERVICE).when(mSST.mSS).getVoiceRegState();
+        doReturn(mNotificationBuilder).when(spyEmergencyNetworkNotification)
+                .getNotificationBuilder();
+
+        doReturn(true).when(mPhone).isWifiCallingEnabled();
+        Message notificationMsg = mSpyCarrierSST.obtainMessage(
+                CarrierServiceStateTracker.CARRIER_EVENT_IMS_CAPABILITIES_CHANGED, null);
+        mSpyCarrierSST.handleMessage(notificationMsg);
+        waitForHandlerAction(mSpyCarrierSST, TEST_TIMEOUT);
+        verify(mNotificationManager).notify(
+                eq(CarrierServiceStateTracker.NOTIFICATION_EMERGENCY_NETWORK),
+                isA(Notification.class));
+
+        doReturn(false).when(mPhone).isWifiCallingEnabled();
+        mSpyCarrierSST.handleMessage(notificationMsg);
+        waitForHandlerAction(mSpyCarrierSST, TEST_TIMEOUT);
+        verify(mNotificationManager, atLeast(2)).cancel(
+                CarrierServiceStateTracker.NOTIFICATION_EMERGENCY_NETWORK);
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CellIdentityGsmTest.java b/tests/telephonytests/src/com/android/internal/telephony/CellIdentityGsmTest.java
index e826709..cb003b7 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CellIdentityGsmTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CellIdentityGsmTest.java
@@ -51,6 +51,7 @@
         assertEquals(LAC, ci.getLac());
         assertEquals(CID, ci.getCid());
         assertEquals(ARFCN, ci.getArfcn());
+        assertEquals(ARFCN, ci.getChannelNumber());
         assertEquals(BSIC, ci.getBsic());
         assertEquals(MCC, ci.getMcc());
         assertEquals(MNC, ci.getMnc());
@@ -180,12 +181,15 @@
 
     @SmallTest
     public void testParcelWithUnknowMccMnc() {
-        CellIdentityGsm ci = new CellIdentityGsm(LAC, CID, ARFCN, BSIC, null, null, null, null);
+        CellIdentityGsm ci =
+                new CellIdentityGsm(LAC, CID, ARFCN, BSIC, null, null, ALPHA_LONG, ALPHA_SHORT);
 
         Parcel p = Parcel.obtain();
         p.writeInt(CellIdentityGsm.TYPE_GSM);
         p.writeString(String.valueOf(Integer.MAX_VALUE));
         p.writeString(String.valueOf(Integer.MAX_VALUE));
+        p.writeString(ALPHA_LONG);
+        p.writeString(ALPHA_SHORT);
         p.writeInt(LAC);
         p.writeInt(CID);
         p.writeInt(ARFCN);
@@ -200,12 +204,15 @@
     public void testParcelWithInvalidMccMnc() {
         final String invalidMcc = "randomStuff";
         final String invalidMnc = "randomStuff";
-        CellIdentityGsm ci = new CellIdentityGsm(LAC, CID, ARFCN, BSIC, null, null, null, null);
+        CellIdentityGsm ci =
+                new CellIdentityGsm(LAC, CID, ARFCN, BSIC, null, null, ALPHA_LONG, ALPHA_SHORT);
 
         Parcel p = Parcel.obtain();
         p.writeInt(CellIdentity.TYPE_GSM);
         p.writeString(invalidMcc);
         p.writeString(invalidMnc);
+        p.writeString(ALPHA_LONG);
+        p.writeString(ALPHA_SHORT);
         p.writeInt(LAC);
         p.writeInt(CID);
         p.writeInt(ARFCN);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CellIdentityLteTest.java b/tests/telephonytests/src/com/android/internal/telephony/CellIdentityLteTest.java
index 56575ca..bbd9078 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CellIdentityLteTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CellIdentityLteTest.java
@@ -52,6 +52,7 @@
         assertEquals(PCI, ci.getPci());
         assertEquals(TAC, ci.getTac());
         assertEquals(EARFCN, ci.getEarfcn());
+        assertEquals(EARFCN, ci.getChannelNumber());
         assertEquals(BANDWIDTH, ci.getBandwidth());
         assertEquals(MCC, ci.getMcc());
         assertEquals(MNC, ci.getMnc());
@@ -189,12 +190,14 @@
     @SmallTest
     public void testParcelWithUnknownMccMnc() {
         CellIdentityLte ci = new CellIdentityLte(
-                CI, PCI, TAC, EARFCN, BANDWIDTH, null, null, null, null);
+                CI, PCI, TAC, EARFCN, BANDWIDTH, null, null, ALPHA_LONG, ALPHA_SHORT);
 
         Parcel p = Parcel.obtain();
         p.writeInt(CellIdentity.TYPE_LTE);
         p.writeString(String.valueOf(Integer.MAX_VALUE));
         p.writeString(String.valueOf(Integer.MAX_VALUE));
+        p.writeString(ALPHA_LONG);
+        p.writeString(ALPHA_SHORT);
         p.writeInt(CI);
         p.writeInt(PCI);
         p.writeInt(TAC);
@@ -211,12 +214,14 @@
         final String invalidMcc = "randomStuff";
         final String invalidMnc = "randomStuff";
         CellIdentityLte ci = new CellIdentityLte(
-                CI, PCI, TAC, EARFCN, BANDWIDTH, null, null, null, null);
+                CI, PCI, TAC, EARFCN, BANDWIDTH, null, null, ALPHA_LONG, ALPHA_SHORT);
 
         Parcel p = Parcel.obtain();
         p.writeInt(CellIdentity.TYPE_LTE);
         p.writeString(invalidMcc);
         p.writeString(invalidMnc);
+        p.writeString(ALPHA_LONG);
+        p.writeString(ALPHA_SHORT);
         p.writeInt(CI);
         p.writeInt(PCI);
         p.writeInt(TAC);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CellIdentityTdscdmaTest.java b/tests/telephonytests/src/com/android/internal/telephony/CellIdentityTdscdmaTest.java
index 949d99b..5915062 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CellIdentityTdscdmaTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CellIdentityTdscdmaTest.java
@@ -33,7 +33,7 @@
     // Tracking area code ranges from 0 to 65535.
     private static final int TAC = 65535;
     // Absolute RF Channel Number ranges from 0 to 262140.
-    private static final int EARFCN = 262140;
+    private static final int UARFCN = 262140;
     private static final int MCC = 120;
     private static final int MNC = 260;
     private static final String MCC_STR = "120";
@@ -52,33 +52,38 @@
     @SmallTest
     public void testDefaultConstructor() {
         CellIdentityTdscdma ci =
-                new CellIdentityTdscdma(MCC_STR, MNC_STR, LAC, CID, CPID);
+                new CellIdentityTdscdma(
+                        MCC_STR, MNC_STR, LAC, CID, CPID, UARFCN, ALPHA_LONG, ALPHA_SHORT);
 
         assertEquals(MCC_STR, ci.getMccString());
         assertEquals(MNC_STR, ci.getMncString());
         assertEquals(LAC, ci.getLac());
         assertEquals(CID, ci.getCid());
         assertEquals(CPID, ci.getCpid());
+        assertEquals(UARFCN, ci.getChannelNumber());
+        assertEquals(ALPHA_LONG, ci.getOperatorAlphaLong());
+        assertEquals(ALPHA_SHORT, ci.getOperatorAlphaShort());
     }
 
     @SmallTest
     public void testConstructorWithEmptyMccMnc() {
-        CellIdentityTdscdma ci = new CellIdentityTdscdma(null, null, LAC, CID, CPID);
+        CellIdentityTdscdma ci = new CellIdentityTdscdma(
+                null, null, LAC, CID, CPID, UARFCN, "", "");
 
         assertNull(ci.getMccString());
         assertNull(ci.getMncString());
 
-        ci = new CellIdentityTdscdma(MCC_STR, null, LAC, CID, CPID);
+        ci = new CellIdentityTdscdma(MCC_STR, null, LAC, CID, CPID, UARFCN, "", "");
 
         assertEquals(MCC_STR, ci.getMccString());
         assertNull(ci.getMncString());
 
-        ci = new CellIdentityTdscdma(null, MNC_STR, LAC, CID, CPID);
+        ci = new CellIdentityTdscdma(null, MNC_STR, LAC, CID, CPID, UARFCN, "", "");
 
         assertEquals(MNC_STR, ci.getMncString());
         assertNull(ci.getMccString());
 
-        ci = new CellIdentityTdscdma("", "", LAC, CID, CPID);
+        ci = new CellIdentityTdscdma("", "", LAC, CID, CPID, UARFCN, "", "");
 
         assertNull(ci.getMccString());
         assertNull(ci.getMncString());
@@ -86,7 +91,8 @@
 
     @SmallTest
     public void testParcel() {
-        CellIdentityTdscdma ci = new CellIdentityTdscdma(MCC_STR, MNC_STR, LAC, CID, CPID);
+        CellIdentityTdscdma ci = new CellIdentityTdscdma(
+                MCC_STR, MNC_STR, LAC, CID, UARFCN, CPID, ALPHA_LONG, ALPHA_SHORT);
 
         Parcel p = Parcel.obtain();
         ci.writeToParcel(p, 0);
@@ -98,15 +104,20 @@
 
     @SmallTest
     public void testParcelWithUnknowMccMnc() {
-        CellIdentityTdscdma ci = new CellIdentityTdscdma(null, null, LAC, CID, CPID);
+        CellIdentityTdscdma ci =
+                new CellIdentityTdscdma(
+                        null, null, LAC, CID, CPID, UARFCN, ALPHA_LONG, ALPHA_SHORT);
 
         Parcel p = Parcel.obtain();
         p.writeInt(CellIdentity.TYPE_TDSCDMA);
         p.writeString(String.valueOf(Integer.MAX_VALUE));
         p.writeString(String.valueOf(Integer.MAX_VALUE));
+        p.writeString(ALPHA_LONG);
+        p.writeString(ALPHA_SHORT);
         p.writeInt(LAC);
         p.writeInt(CID);
         p.writeInt(CPID);
+        p.writeInt(UARFCN);
         p.setDataPosition(0);
 
         CellIdentityTdscdma newCi = CellIdentityTdscdma.CREATOR.createFromParcel(p);
@@ -117,15 +128,20 @@
     public void testParcelWithInvalidMccMnc() {
         final String invalidMcc = "randomStuff";
         final String invalidMnc = "randomStuff";
-        CellIdentityTdscdma ci = new CellIdentityTdscdma(null, null, LAC, CID, CPID);
+        CellIdentityTdscdma ci =
+                new CellIdentityTdscdma(
+                        null, null, LAC, CID, CPID, UARFCN, ALPHA_LONG, ALPHA_SHORT);
 
         Parcel p = Parcel.obtain();
         p.writeInt(CellIdentity.TYPE_TDSCDMA);
         p.writeString(invalidMcc);
         p.writeString(invalidMnc);
+        p.writeString(ALPHA_LONG);
+        p.writeString(ALPHA_SHORT);
         p.writeInt(LAC);
         p.writeInt(CID);
         p.writeInt(CPID);
+        p.writeInt(UARFCN);
         p.setDataPosition(0);
 
         CellIdentityTdscdma newCi = CellIdentityTdscdma.CREATOR.createFromParcel(p);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CellIdentityWcdmaTest.java b/tests/telephonytests/src/com/android/internal/telephony/CellIdentityWcdmaTest.java
index f5065d9..d0e30b4 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CellIdentityWcdmaTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CellIdentityWcdmaTest.java
@@ -50,6 +50,8 @@
         assertEquals(LAC, ci.getLac());
         assertEquals(CID, ci.getCid());
         assertEquals(PSC, ci.getPsc());
+        assertEquals(UARFCN, ci.getUarfcn());
+        assertEquals(UARFCN, ci.getChannelNumber());
         assertEquals(MCC, ci.getMcc());
         assertEquals(MNC, ci.getMnc());
         assertEquals(MCC_STR, ci.getMccString());
@@ -177,12 +179,15 @@
 
     @SmallTest
     public void testParcelWithUnknowMccMnc() {
-        CellIdentityWcdma ci = new CellIdentityWcdma(LAC, CID, PSC, UARFCN, null, null, null, null);
+        CellIdentityWcdma ci =
+                new CellIdentityWcdma(LAC, CID, PSC, UARFCN, null, null, ALPHA_LONG, ALPHA_SHORT);
 
         Parcel p = Parcel.obtain();
         p.writeInt(CellIdentity.TYPE_WCDMA);
         p.writeString(String.valueOf(Integer.MAX_VALUE));
         p.writeString(String.valueOf(Integer.MAX_VALUE));
+        p.writeString(ALPHA_LONG);
+        p.writeString(ALPHA_SHORT);
         p.writeInt(LAC);
         p.writeInt(CID);
         p.writeInt(PSC);
@@ -197,12 +202,15 @@
     public void testParcelWithInvalidMccMnc() {
         final String invalidMcc = "randomStuff";
         final String invalidMnc = "randomStuff";
-        CellIdentityWcdma ci = new CellIdentityWcdma(LAC, CID, PSC, UARFCN, null, null, null, null);
+        CellIdentityWcdma ci =
+                new CellIdentityWcdma(LAC, CID, PSC, UARFCN, null, null, ALPHA_LONG, ALPHA_SHORT);
 
         Parcel p = Parcel.obtain();
         p.writeInt(CellIdentity.TYPE_WCDMA);
         p.writeString(invalidMcc);
         p.writeString(invalidMnc);
+        p.writeString(ALPHA_LONG);
+        p.writeString(ALPHA_SHORT);
         p.writeInt(LAC);
         p.writeInt(CID);
         p.writeInt(PSC);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CellularNetworkServiceTest.java b/tests/telephonytests/src/com/android/internal/telephony/CellularNetworkServiceTest.java
index 16bc535..a595c36 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CellularNetworkServiceTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CellularNetworkServiceTest.java
@@ -54,7 +54,7 @@
         mCellularNetworkService = new CellularNetworkService();
         ServiceInfo serviceInfo =  new ServiceInfo();
         serviceInfo.packageName = "com.android.phone";
-        serviceInfo.permission = "android.permission.BIND_NETWORK_SERVICE";
+        serviceInfo.permission = "android.permission.BIND_TELEPHONY_NETWORK_SERVICE";
         IntentFilter filter = new IntentFilter();
         mContextFixture.addService(
                 NetworkService.NETWORK_SERVICE_INTERFACE,
@@ -132,7 +132,7 @@
         waitForMs(1000);
 
         NetworkRegistrationState expectedState = new NetworkRegistrationState(
-                AccessNetworkConstants.TransportType.WWAN, domain, voiceRegState,
+                domain, AccessNetworkConstants.TransportType.WWAN, voiceRegState,
                 ServiceState.rilRadioTechnologyToNetworkType(voiceRadioTech), reasonForDenial,
                 false, availableServices, null, cssSupported,
                 roamingIndicator, systemIsInPrl, defaultRoamingIndicator);
@@ -155,7 +155,7 @@
         waitForMs(1000);
 
         expectedState = new NetworkRegistrationState(
-                AccessNetworkConstants.TransportType.WWAN, domain, voiceRegState,
+                domain, AccessNetworkConstants.TransportType.WWAN, voiceRegState,
                 ServiceState.rilRadioTechnologyToNetworkType(voiceRadioTech), reasonForDenial,
                 false, availableServices, null, maxDataCalls);
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java b/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java
index c274dea..d2e28ca 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java
@@ -40,9 +40,11 @@
 import android.content.IntentFilter;
 import android.content.ServiceConnection;
 import android.content.SharedPreferences;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
+import android.content.res.AssetManager;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.database.Cursor;
@@ -267,11 +269,21 @@
         }
 
         @Override
+        public AssetManager getAssets() {
+            return mAssetManager;
+        }
+
+        @Override
         public Resources getResources() {
             return mResources;
         }
 
         @Override
+        public ApplicationInfo getApplicationInfo() {
+            return mApplicationInfo;
+        }
+
+        @Override
         public String getOpPackageName() {
             return "com.android.internal.telephony";
         }
@@ -282,6 +294,11 @@
         }
 
         @Override
+        public Resources.Theme getTheme() {
+            return null;
+        }
+
+        @Override
         public void unregisterReceiver(BroadcastReceiver receiver) {
         }
 
@@ -515,6 +532,7 @@
     // when(...) logic to be used to add specific little responses where needed.
 
     private final Resources mResources = mock(Resources.class);
+    private final ApplicationInfo mApplicationInfo = mock(ApplicationInfo.class);
     private final PackageManager mPackageManager = mock(PackageManager.class);
     private final TelephonyManager mTelephonyManager = mock(TelephonyManager.class);
     private final DownloadManager mDownloadManager = mock(DownloadManager.class);
@@ -524,6 +542,7 @@
     private final CarrierConfigManager mCarrierConfigManager = mock(CarrierConfigManager.class);
     private final SubscriptionManager mSubscriptionManager = mock(SubscriptionManager.class);
     private final AlarmManager mAlarmManager = mock(AlarmManager.class);
+    private final AssetManager mAssetManager = new AssetManager();
     private final ConnectivityManager mConnectivityManager = mock(ConnectivityManager.class);
     private final UsageStatsManager mUsageStatManager = null;
     private final WifiManager mWifiManager = mock(WifiManager.class);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java b/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java
index fadfaf2..77eb7dc 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java
@@ -31,6 +31,7 @@
 import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
@@ -57,8 +58,11 @@
 import android.test.suitebuilder.annotation.SmallTest;
 
 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.UiccProfile;
+import com.android.internal.telephony.uicc.UiccSlot;
 
 import org.junit.After;
 import org.junit.Before;
@@ -79,6 +83,7 @@
 
     private static final int EVENT_EMERGENCY_CALLBACK_MODE_EXIT = 1;
     private static final int EVENT_EMERGENCY_CALL_TOGGLE = 2;
+    private static final int EVENT_SET_ICC_LOCK_ENABLED = 3;
 
     private class GsmCdmaPhoneTestHandler extends HandlerThread {
 
@@ -398,7 +403,7 @@
         // voicemail number from sharedPreference
         mPhoneUT.setVoiceMailNumber("alphaTag", voiceMailNumber, null);
         ArgumentCaptor<Message> messageArgumentCaptor = ArgumentCaptor.forClass(Message.class);
-        verify(mRuimRecords).setVoiceMailNumber(eq("alphaTag"), eq(voiceMailNumber),
+        verify(mSimRecords).setVoiceMailNumber(eq("alphaTag"), eq(voiceMailNumber),
                 messageArgumentCaptor.capture());
 
         Message msg = messageArgumentCaptor.getValue();
@@ -475,8 +480,8 @@
         doReturn(imsi).when(mSimRecords).getIMSI();
         mPhoneUT.getCallForwardingOption(CF_REASON_UNCONDITIONAL, null);
         verify(mSimulatedCommandsVerifier).queryCallForwardStatus(
-                eq(CF_REASON_UNCONDITIONAL), anyInt(), nullable(String.class),
-                nullable(Message.class));
+                eq(CF_REASON_UNCONDITIONAL), eq(CommandsInterface.SERVICE_CLASS_VOICE),
+                nullable(String.class), nullable(Message.class));
         waitForMs(50);
         verify(mSimRecords).setVoiceCallForwardingFlag(anyInt(), anyBoolean(),
                 nullable(String.class));
@@ -812,4 +817,60 @@
         waitForMs(100);
         verify(mEriManager, times(1)).loadEriFile();
     }
+
+    @Test
+    @SmallTest
+    public void testGetIccCardUnknownAndAbsent() {
+        // If UiccSlot.isStateUnknown is true, we should return a dummy IccCard with the state
+        // set to UNKNOWN
+        doReturn(null).when(mUiccController).getUiccProfileForPhone(anyInt());
+        UiccSlot mockSlot = mock(UiccSlot.class);
+        doReturn(mockSlot).when(mUiccController).getUiccSlotForPhone(anyInt());
+        doReturn(true).when(mockSlot).isStateUnknown();
+
+        IccCard iccCard = mPhoneUT.getIccCard();
+        assertEquals(IccCardConstants.State.UNKNOWN, iccCard.getState());
+
+        // if isStateUnknown is false, we should return a dummy IccCard with the state set to
+        // ABSENT
+        doReturn(false).when(mockSlot).isStateUnknown();
+        iccCard = mPhoneUT.getIccCard();
+        assertEquals(IccCardConstants.State.ABSENT, iccCard.getState());
+    }
+
+    @Test
+    @SmallTest
+    public void testGetEmptyIccCard() {
+        doReturn(null).when(mUiccController).getUiccProfileForPhone(anyInt());
+
+        IccCard iccCard = mPhoneUT.getIccCard();
+
+        // The iccCard should be a dummy object, not null.
+        assertTrue(!(iccCard instanceof UiccProfile));
+
+        assertTrue(iccCard != null);
+        assertEquals(IccCardConstants.State.UNKNOWN, iccCard.getState());
+        assertEquals(null, iccCard.getIccRecords());
+        assertEquals(false, iccCard.getIccLockEnabled());
+        assertEquals(false, iccCard.getIccFdnEnabled());
+        assertEquals(false, iccCard.isApplicationOnIcc(
+                IccCardApplicationStatus.AppType.APPTYPE_SIM));
+        assertEquals(false, iccCard.hasIccCard());
+        assertEquals(false, iccCard.getIccPin2Blocked());
+        assertEquals(false, iccCard.getIccPuk2Blocked());
+
+        Message onComplete = mTestHandler.obtainMessage(EVENT_SET_ICC_LOCK_ENABLED);
+        iccCard.setIccLockEnabled(true, "password", onComplete);
+
+        waitForMs(100);
+
+        ArgumentCaptor<Message> messageArgumentCaptor = ArgumentCaptor.forClass(Message.class);
+        // Verify that message is sent back with exception.
+        verify(mTestHandler, times(1)).sendMessageAtTime(messageArgumentCaptor.capture(),
+                anyLong());
+        Message message = messageArgumentCaptor.getAllValues().get(0);
+        AsyncResult ret = (AsyncResult) message.obj;
+        assertEquals(EVENT_SET_ICC_LOCK_ENABLED, message.what);
+        assertTrue(ret.exception != null);
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/IccSmsInterfaceManagerTest.java b/tests/telephonytests/src/com/android/internal/telephony/IccSmsInterfaceManagerTest.java
new file mode 100644
index 0000000..3bc2c1b
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/IccSmsInterfaceManagerTest.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.telephony;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.Manifest;
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.UserManager;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class IccSmsInterfaceManagerTest {
+    private static final String PACKAGE = "com.example.package";
+    private static final String MESSAGE = "msg";
+
+    private HandlerThread mHandlerThread;
+
+    @Mock
+    private Phone mMockPhone;
+    @Mock
+    private Context mMockContext;
+    @Mock
+    private AppOpsManager mMockAppOps;
+    @Mock
+    private UserManager mMockUserManager;
+    @Mock
+    private SmsDispatchersController mMockDispatchersController;
+
+    private IccSmsInterfaceManager mIccSmsInterfaceManager;
+
+    private boolean mCallerHasCarrierPrivileges;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mHandlerThread = new HandlerThread("IccSmsInterfaceManagerTest");
+        mHandlerThread.start();
+        final CountDownLatch initialized = new CountDownLatch(1);
+        new Handler(mHandlerThread.getLooper()).post(() -> {
+            mIccSmsInterfaceManager = new IccSmsInterfaceManager(
+                    mMockPhone, mMockContext, mMockAppOps, mMockUserManager,
+                    mMockDispatchersController) {
+                @Override
+                public void enforceCallerIsImsAppOrCarrierApp(String message) {
+                    if (!mCallerHasCarrierPrivileges) {
+                        throw new SecurityException(message);
+                    }
+                }
+            };
+            initialized.countDown();
+        });
+        // Wait for object to initialize.
+        if (!initialized.await(30, TimeUnit.SECONDS)) {
+            fail("Could not initialize IccSmsInterfaceManager");
+        }
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mHandlerThread.quit();
+    }
+
+    @Test
+    public void testCheckCallingSendTextPermissions_persist_grant() {
+        assertTrue(mIccSmsInterfaceManager.checkCallingSendTextPermissions(
+                true /* persistMessageForNonDefaultSmsApp */, PACKAGE, MESSAGE));
+    }
+
+    @Test
+    public void testCheckCallingSendTextPermissions_persist_noGrant() {
+        Mockito.doThrow(new SecurityException(MESSAGE)).when(mMockContext)
+                .enforceCallingPermission(Manifest.permission.SEND_SMS, MESSAGE);
+        try {
+            mIccSmsInterfaceManager.checkCallingSendTextPermissions(
+                    true /* persistMessageForNonDefaultSmsApp */, PACKAGE, MESSAGE);
+            fail();
+        } catch (SecurityException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testCheckCallingSendTextPermissions_persist_noAppOps() {
+        Mockito.when(mMockAppOps.noteOp(
+                AppOpsManager.OP_SEND_SMS, Binder.getCallingUid(), PACKAGE))
+                .thenReturn(AppOpsManager.MODE_ERRORED);
+        assertFalse(mIccSmsInterfaceManager.checkCallingSendTextPermissions(
+                true /* persistMessageForNonDefaultSmsApp */, PACKAGE, MESSAGE));
+    }
+
+    @Test
+    public void testCheckCallingSendTextPermissions_noPersist_grantViaCarrierApp() {
+        mCallerHasCarrierPrivileges = true;
+        // Other permissions shouldn't matter.
+        Mockito.doThrow(new SecurityException(MESSAGE)).when(mMockContext)
+                .enforceCallingPermission(Manifest.permission.MODIFY_PHONE_STATE, MESSAGE);
+        Mockito.doThrow(new SecurityException(MESSAGE)).when(mMockContext)
+                .enforceCallingPermission(Manifest.permission.SEND_SMS, MESSAGE);
+        Mockito.when(mMockAppOps.noteOp(
+                AppOpsManager.OP_SEND_SMS, Binder.getCallingUid(), PACKAGE))
+                .thenReturn(AppOpsManager.MODE_ERRORED);
+
+        assertTrue(mIccSmsInterfaceManager.checkCallingSendTextPermissions(
+                false /* persistMessageForNonDefaultSmsApp */, PACKAGE, MESSAGE));
+    }
+
+    @Test
+    public void testCheckCallingSendTextPermissions_noPersist_grantViaModifyAndSend() {
+        assertTrue(mIccSmsInterfaceManager.checkCallingSendTextPermissions(
+                false /* persistMessageForNonDefaultSmsApp */, PACKAGE, MESSAGE));
+    }
+
+    @Test
+    public void testCheckCallingSendTextPermissions_noPersist_noModify() {
+        Mockito.doThrow(new SecurityException(MESSAGE)).when(mMockContext)
+                .enforceCallingPermission(Manifest.permission.MODIFY_PHONE_STATE, MESSAGE);
+        try {
+            mIccSmsInterfaceManager.checkCallingSendTextPermissions(
+                    false /* persistMessageForNonDefaultSmsApp */, PACKAGE, MESSAGE);
+            fail();
+        } catch (SecurityException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testCheckCallingSendTextPermissions_noPersist_noSendSmsPermission() {
+        Mockito.doThrow(new SecurityException(MESSAGE)).when(mMockContext)
+                .enforceCallingPermission(Manifest.permission.SEND_SMS, MESSAGE);
+        try {
+            mIccSmsInterfaceManager.checkCallingSendTextPermissions(
+                    false /* persistMessageForNonDefaultSmsApp */, PACKAGE, MESSAGE);
+            fail();
+        } catch (SecurityException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testCheckCallingSendTextPermissions_noPersist_noAppOps() {
+        Mockito.when(mMockAppOps.noteOp(
+                AppOpsManager.OP_SEND_SMS, Binder.getCallingUid(), PACKAGE))
+                .thenReturn(AppOpsManager.MODE_ERRORED);
+        assertFalse(mIccSmsInterfaceManager.checkCallingSendTextPermissions(
+                false /* persistMessageForNonDefaultSmsApp */, PACKAGE, MESSAGE));
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/LocaleTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/LocaleTrackerTest.java
new file mode 100644
index 0000000..bcc1730
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/LocaleTrackerTest.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony;
+
+import static com.android.internal.telephony.TelephonyTestUtils.waitForMs;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.isNull;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.net.wifi.WifiManager;
+import android.os.AsyncResult;
+import android.os.HandlerThread;
+import android.telephony.CellIdentityGsm;
+import android.telephony.CellInfoGsm;
+import android.telephony.ServiceState;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Arrays;
+
+public class LocaleTrackerTest extends TelephonyTest {
+
+    private static final String US_MCC = "310";
+    private static final String FAKE_MNC = "123";
+    private static final String US_COUNTRY_CODE = "us";
+    private static final String COUNTRY_CODE_UNAVAILABLE = "";
+
+    private LocaleTracker mLocaleTracker;
+    private LocaleTrackerTestHandler mLocaleTrackerTestHandler;
+
+    private CellInfoGsm mCellInfo;
+    private WifiManager mWifiManager;
+
+    private class LocaleTrackerTestHandler extends HandlerThread {
+
+        private LocaleTrackerTestHandler(String name) {
+            super(name);
+        }
+
+        @Override
+        public void onLooperPrepared() {
+            mLocaleTracker = new LocaleTracker(mPhone, this.getLooper());
+            setReady(true);
+        }
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        logd("LocaleTrackerTest +Setup!");
+        super.setUp(getClass().getSimpleName());
+
+        // This is a workaround to bypass setting system properties, which causes access violation.
+        doReturn(-1).when(mPhone).getPhoneId();
+        mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
+
+        mCellInfo = new CellInfoGsm();
+        mCellInfo.setCellIdentity(new CellIdentityGsm(Integer.parseInt(US_MCC),
+                Integer.parseInt(FAKE_MNC), 0, 0));
+        doReturn(Arrays.asList(mCellInfo)).when(mPhone).getAllCellInfo(isNull());
+        doReturn(true).when(mSST).getDesiredPowerState();
+
+        mLocaleTrackerTestHandler = new LocaleTrackerTestHandler(getClass().getSimpleName());
+        mLocaleTrackerTestHandler.start();
+        waitUntilReady();
+        logd("LocaleTrackerTest -Setup!");
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mLocaleTracker.removeCallbacksAndMessages(null);
+        mLocaleTrackerTestHandler.quit();
+        super.tearDown();
+    }
+
+    @Test
+    @SmallTest
+    public void testUpdateOperatorNumericSync() throws Exception {
+        mLocaleTracker.updateOperatorNumericSync(US_MCC + FAKE_MNC);
+        assertEquals(US_COUNTRY_CODE, mLocaleTracker.getCurrentCountry());
+        verify(mWifiManager).setCountryCode(US_COUNTRY_CODE, false);
+    }
+
+    @Test
+    @SmallTest
+    public void testUpdateOperatorNumericAsync() throws Exception {
+        mLocaleTracker.updateOperatorNumericAsync(US_MCC + FAKE_MNC);
+        waitForMs(100);
+        assertEquals(US_COUNTRY_CODE, mLocaleTracker.getCurrentCountry());
+        verify(mWifiManager).setCountryCode(US_COUNTRY_CODE, false);
+    }
+
+    @Test
+    @SmallTest
+    public void testNoSim() throws Exception {
+        mLocaleTracker.updateOperatorNumericAsync("");
+        waitForHandlerAction(mLocaleTracker, 100);
+        assertEquals(US_COUNTRY_CODE, mLocaleTracker.getCurrentCountry());
+        verify(mWifiManager).setCountryCode(US_COUNTRY_CODE, false);
+    }
+
+    @Test
+    @SmallTest
+    public void testBootupInAirplaneModeOn() throws Exception {
+        doReturn(false).when(mSST).getDesiredPowerState();
+        mLocaleTracker.updateOperatorNumericAsync("");
+        waitForHandlerAction(mLocaleTracker, 100);
+        assertEquals(COUNTRY_CODE_UNAVAILABLE, mLocaleTracker.getCurrentCountry());
+        verify(mWifiManager).setCountryCode(COUNTRY_CODE_UNAVAILABLE, false);
+    }
+
+    @Test
+    @SmallTest
+    public void testTogglingAirplaneMode() throws Exception {
+        mLocaleTracker.updateOperatorNumericSync(US_MCC + FAKE_MNC);
+        assertEquals(US_COUNTRY_CODE, mLocaleTracker.getCurrentCountry());
+        verify(mWifiManager).setCountryCode(US_COUNTRY_CODE, false);
+
+        doReturn(false).when(mSST).getDesiredPowerState();
+        mLocaleTracker.updateOperatorNumericAsync("");
+        waitForHandlerAction(mLocaleTracker, 100);
+        assertEquals(COUNTRY_CODE_UNAVAILABLE, mLocaleTracker.getCurrentCountry());
+        verify(mWifiManager).setCountryCode(COUNTRY_CODE_UNAVAILABLE, false);
+
+        doReturn(true).when(mSST).getDesiredPowerState();
+        mLocaleTracker.updateOperatorNumericSync(US_MCC + FAKE_MNC);
+        assertEquals(US_COUNTRY_CODE, mLocaleTracker.getCurrentCountry());
+        verify(mWifiManager, times(2)).setCountryCode(US_COUNTRY_CODE, false);
+    }
+
+    @Test
+    @SmallTest
+    public void testCellInfoUnavailableRetry() throws Exception {
+        doReturn(null).when(mPhone).getAllCellInfo(isNull());
+        mLocaleTracker.updateOperatorNumericAsync("");
+        waitForHandlerAction(mLocaleTracker, 100);
+        assertEquals(COUNTRY_CODE_UNAVAILABLE, mLocaleTracker.getCurrentCountry());
+        verify(mWifiManager).setCountryCode(COUNTRY_CODE_UNAVAILABLE, false);
+
+        doReturn(Arrays.asList(mCellInfo)).when(mPhone).getAllCellInfo(isNull());
+        waitForHandlerActionDelayed(mLocaleTracker, 100, 2500);
+        assertEquals(US_COUNTRY_CODE, mLocaleTracker.getCurrentCountry());
+        verify(mWifiManager).setCountryCode(US_COUNTRY_CODE, false);
+    }
+
+    @Test
+    @SmallTest
+    public void testOutOfAirplaneMode() throws Exception {
+        doReturn(null).when(mPhone).getAllCellInfo(isNull());
+        mLocaleTracker.updateOperatorNumericAsync("");
+        waitForHandlerAction(mLocaleTracker, 100);
+        assertEquals(COUNTRY_CODE_UNAVAILABLE, mLocaleTracker.getCurrentCountry());
+        verify(mWifiManager).setCountryCode(COUNTRY_CODE_UNAVAILABLE, false);
+
+        doReturn(Arrays.asList(mCellInfo)).when(mPhone).getAllCellInfo(isNull());
+        ServiceState ss = new ServiceState();
+        ss.setState(ServiceState.STATE_IN_SERVICE);
+        AsyncResult ar = new AsyncResult(null, ss, null);
+        mLocaleTracker.sendMessage(mLocaleTracker.obtainMessage(3, ar));
+        waitForHandlerAction(mLocaleTracker, 100);
+        assertEquals(US_COUNTRY_CODE, mLocaleTracker.getCurrentCountry());
+        verify(mWifiManager).setCountryCode(US_COUNTRY_CODE, false);
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/NetworkRegistrationStateTest.java b/tests/telephonytests/src/com/android/internal/telephony/NetworkRegistrationStateTest.java
new file mode 100644
index 0000000..eb52e7c
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/NetworkRegistrationStateTest.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony;
+
+import static junit.framework.Assert.assertEquals;
+
+import android.os.Parcel;
+import android.support.test.filters.SmallTest;
+import android.telephony.AccessNetworkConstants.TransportType;
+import android.telephony.CellIdentityLte;
+import android.telephony.NetworkRegistrationState;
+import android.telephony.TelephonyManager;
+
+import org.junit.Test;
+
+/** Unit tests for {@link NetworkRegistrationState}. */
+
+public class NetworkRegistrationStateTest {
+
+    @Test
+    @SmallTest
+    public void testParcel() {
+        NetworkRegistrationState nrs = new NetworkRegistrationState(
+                NetworkRegistrationState.DOMAIN_CS,
+                TransportType.WWAN,
+                NetworkRegistrationState.REG_STATE_HOME,
+                TelephonyManager.NETWORK_TYPE_LTE,
+                0,
+                false,
+                new int[]{NetworkRegistrationState.SERVICE_TYPE_DATA},
+                new CellIdentityLte());
+
+        Parcel p = Parcel.obtain();
+        nrs.writeToParcel(p, 0);
+        p.setDataPosition(0);
+
+        NetworkRegistrationState newNrs = NetworkRegistrationState.CREATOR.createFromParcel(p);
+        assertEquals(nrs, newNrs);
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/NewNitzStateMachineTest.java b/tests/telephonytests/src/com/android/internal/telephony/NewNitzStateMachineTest.java
new file mode 100644
index 0000000..028cbcc
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/NewNitzStateMachineTest.java
@@ -0,0 +1,970 @@
+/*
+ * 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.eq;
+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.Mock;
+
+public class NewNitzStateMachineTest 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 NewNitzStateMachine.DeviceState mDeviceState;
+
+    @Mock
+    private NewTimeServiceHelper mTimeServiceHelper;
+
+    private TimeZoneLookupHelper mRealTimeZoneLookupHelper;
+
+    private NewNitzStateMachine 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 NewNitzStateMachine(
+                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_timeZoneEnabled_countryThenNitz() throws Exception {
+        Scenario scenario = UNIQUE_US_ZONE_SCENARIO;
+        Device device = new DeviceBuilder()
+                .setClocksFromScenario(scenario)
+                .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())
+                // Country + NITZ is enough for both time + time zone detection.
+                .verifyTimeSuggestedAndZoneSetAndReset(
+                        scenario.getNitzSignal(), scenario.getTimeZoneId());
+
+        // Check NitzStateMachine state.
+        assertTrue(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
+        assertEquals(scenario.getNitzSignal().getValue(), mNitzStateMachine.getCachedNitzData());
+        assertEquals(scenario.getTimeZoneId(), mNitzStateMachine.getSavedTimeZoneId());
+    }
+
+    @Test
+    public void test_unitedKingdom_timeZoneEnabled_countryThenNitz() throws Exception {
+        Scenario scenario = UNITED_KINGDOM_SCENARIO;
+        Device device = new DeviceBuilder()
+                .setClocksFromScenario(scenario)
+                .setTimeZoneDetectionEnabled(true)
+                .setTimeZoneSettingInitialized(false)
+                .initialize();
+        Script script = new Script(device);
+
+        script.countryReceived(scenario.getNetworkCountryIsoCode())
+                // Country alone is enough to guess the time zone.
+                .verifyOnlyTimeZoneWasSetAndReset(scenario.getTimeZoneId())
+                .nitzReceived(scenario.getNitzSignal())
+                // Country + NITZ is enough for both time + time zone detection.
+                .verifyTimeSuggestedAndZoneSetAndReset(
+                        scenario.getNitzSignal(), scenario.getTimeZoneId());
+
+        // Check NitzStateMachine state.
+        assertTrue(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
+        assertEquals(scenario.getNitzSignal().getValue(), mNitzStateMachine.getCachedNitzData());
+        assertEquals(scenario.getTimeZoneId(), mNitzStateMachine.getSavedTimeZoneId());
+    }
+
+    @Test
+    public void test_uniqueUsZone_timeZoneDisabled_countryThenNitz() throws Exception {
+        Scenario scenario = UNIQUE_US_ZONE_SCENARIO;
+        Device device = new DeviceBuilder()
+                .setClocksFromScenario(scenario)
+                .setTimeZoneDetectionEnabled(false)
+                .setTimeZoneSettingInitialized(false)
+                .initialize();
+        Script script = new Script(device);
+
+        script.countryReceived(scenario.getNetworkCountryIsoCode())
+                // Country is not enough to guess the time zone and time zone detection is disabled.
+                .verifyNothingWasSetAndReset()
+                .nitzReceived(scenario.getNitzSignal())
+                // Time zone detection is disabled, but time should be suggested from NITZ.
+                .verifyOnlyTimeWasSuggestedAndReset(scenario.getNitzSignal());
+
+        // Check NitzStateMachine state.
+        assertTrue(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
+        assertEquals(scenario.getNitzSignal().getValue(), mNitzStateMachine.getCachedNitzData());
+        assertEquals(scenario.getTimeZoneId(), mNitzStateMachine.getSavedTimeZoneId());
+    }
+
+    @Test
+    public void test_unitedKingdom_timeZoneDisabled_countryThenNitz() throws Exception {
+        Scenario scenario = UNITED_KINGDOM_SCENARIO;
+        Device device = new DeviceBuilder()
+                .setClocksFromScenario(scenario)
+                .setTimeZoneDetectionEnabled(false)
+                .setTimeZoneSettingInitialized(false)
+                .initialize();
+        Script script = new Script(device);
+
+        script.countryReceived(scenario.getNetworkCountryIsoCode())
+                // Country alone would be enough for time zone detection, but it's disabled.
+                .verifyNothingWasSetAndReset()
+                .nitzReceived(scenario.getNitzSignal())
+                // Time zone detection is disabled, but time should be suggested from NITZ.
+                .verifyOnlyTimeWasSuggestedAndReset(scenario.getNitzSignal());
+
+        // Check NitzStateMachine state.
+        assertTrue(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
+        assertEquals(scenario.getNitzSignal().getValue(), mNitzStateMachine.getCachedNitzData());
+        assertEquals(scenario.getTimeZoneId(), mNitzStateMachine.getSavedTimeZoneId());
+    }
+
+    @Test
+    public void test_uniqueUsZone_timeZoneEnabled_nitzThenCountry() throws Exception {
+        Scenario scenario = UNIQUE_US_ZONE_SCENARIO;
+        Device device = new DeviceBuilder()
+                .setClocksFromScenario(scenario)
+                .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.
+                .verifyOnlyTimeWasSuggestedAndReset(scenario.getNitzSignal());
+
+        // 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_timeZoneEnabled_nitzThenCountry() throws Exception {
+        Scenario scenario = UNITED_KINGDOM_SCENARIO;
+        Device device = new DeviceBuilder()
+                .setClocksFromScenario(scenario)
+                .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.
+                .verifyOnlyTimeWasSuggestedAndReset(scenario.getNitzSignal());
+
+        // 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 time zone 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)
+                .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.
+                .verifyOnlyTimeWasSuggestedAndReset(scenario.getNitzSignal());
+
+        // 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)
+                .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 suggested from the NITZ signal.
+                // The combination of NITZ + country will cause the time zone to be set.
+                .verifyTimeSuggestedAndZoneSetAndReset(
+                        scenario.getNitzSignal(), 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)
+                .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 it is bogus.
+                .verifyOnlyTimeWasSuggestedAndReset(scenario.getNitzSignal());
+
+        // 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)
+                .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 suggested.
+                .verifyOnlyTimeWasSuggestedAndReset(scenario.getNitzSignal());
+
+        // 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)
+                .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.
+                .verifyOnlyTimeWasSuggestedAndReset(scenario.getNitzSignal());
+
+        // 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)
+                .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 suggested.
+                .verifyOnlyTimeWasSuggestedAndReset(scenario.getNitzSignal());
+
+        // 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)
+                .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)
+                .verifyTimeSuggestedAndZoneSetAndReset(
+                        scenario.getNitzSignal(), 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)
+                .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())
+                .verifyTimeSuggestedAndZoneSetAndReset(scenario.getNitzSignal(), 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)
+                .setTimeZoneDetectionEnabled(true)
+                .setTimeZoneSettingInitialized(true)
+                .initialize();
+        Script script = new Script(device);
+
+        String expectedZoneId = checkNitzOnlyLookupIsAmbiguousAndReturnZoneId(scenario);
+
+        // Simulate receiving the NITZ signal.
+        script.nitzReceived(scenario.getNitzSignal())
+                .verifyOnlyTimeWasSuggestedAndReset(scenario.getNitzSignal());
+
+        // 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 verifyNothingWasSetAndReset() {
+            mDevice.verifyTimeZoneWasNotSet();
+            mDevice.verifyTimeWasNotSuggested();
+            mDevice.checkNoUnverifiedSetOperations();
+            mDevice.resetInvocations();
+            return this;
+        }
+
+        Script verifyOnlyTimeZoneWasSetAndReset(String timeZoneId, int times) {
+            mDevice.verifyTimeZoneWasSet(timeZoneId, times);
+            mDevice.verifyTimeWasNotSuggested();
+            mDevice.checkNoUnverifiedSetOperations();
+            mDevice.resetInvocations();
+            return this;
+        }
+
+        Script verifyOnlyTimeZoneWasSetAndReset(String timeZoneId) {
+            return verifyOnlyTimeZoneWasSetAndReset(timeZoneId, 1);
+        }
+
+        Script verifyOnlyTimeWasSuggestedAndReset(TimestampedValue<NitzData> nitzSignal) {
+            mDevice.verifyTimeZoneWasNotSet();
+
+            TimestampedValue<Long> time = new TimestampedValue<>(
+                    nitzSignal.getReferenceTimeMillis(),
+                    nitzSignal.getValue().getCurrentTimeInMillis());
+            mDevice.verifyTimeWasSuggested(time);
+            mDevice.checkNoUnverifiedSetOperations();
+            mDevice.resetInvocations();
+            return this;
+        }
+
+        Script verifyTimeSuggestedAndZoneSetAndReset(
+                TimestampedValue<NitzData> nitzSignal, String timeZoneId) {
+            mDevice.verifyTimeZoneWasSet(timeZoneId);
+
+            TimestampedValue<Long> time = new TimestampedValue<>(
+                    nitzSignal.getReferenceTimeMillis(),
+                    nitzSignal.getValue().getCurrentTimeInMillis());
+            mDevice.verifyTimeWasSuggested(time);
+            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 mTimeZoneDetectionEnabled;
+        private final boolean mTimeZoneSettingInitialized;
+
+        Device(long initialSystemClockMillis, long initialRealtimeMillis,
+                boolean timeZoneDetectionEnabled, boolean timeZoneSettingInitialized) {
+            mInitialSystemClockMillis = initialSystemClockMillis;
+            mInitialRealtimeMillis = initialRealtimeMillis;
+            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.isTimeZoneDetectionEnabled())
+                    .thenReturn(mTimeZoneDetectionEnabled);
+            when(mTimeServiceHelper.isTimeZoneSettingInitialized())
+                    .thenReturn(mTimeZoneSettingInitialized);
+        }
+
+        void networkCountryKnown(String countryIsoCode) {
+            when(mDeviceState.getNetworkCountryIsoForPhone()).thenReturn(countryIsoCode);
+            mNitzStateMachine.handleNetworkCountryCodeSet(true);
+        }
+
+        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 verifyTimeWasNotSuggested() {
+            verify(mTimeServiceHelper, times(0)).suggestDeviceTime(any());
+        }
+
+        void verifyTimeWasSuggested(TimestampedValue<Long> expectedTime) {
+            verify(mTimeServiceHelper, times(1)).suggestDeviceTime(eq(expectedTime));
+        }
+
+        /**
+         * Used after calling verify... methods to reset expectations.
+         */
+        void resetInvocations() {
+            clearInvocations(mTimeServiceHelper);
+        }
+
+        void checkNoUnverifiedSetOperations() {
+            NewNitzStateMachineTest.checkNoUnverifiedSetOperations(mTimeServiceHelper);
+        }
+    }
+
+    /** A class used to construct a Device. */
+    class DeviceBuilder {
+
+        private long mInitialSystemClock;
+        private long mInitialRealtimeMillis;
+        private boolean mTimeZoneDetectionEnabled;
+        private boolean mTimeZoneSettingInitialized;
+
+        Device initialize() {
+            Device device = new Device(mInitialSystemClock, mInitialRealtimeMillis,
+                    mTimeZoneDetectionEnabled, mTimeZoneSettingInitialized);
+            device.initialize();
+            return device;
+        }
+
+        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(NewTimeServiceHelper 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)).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/NitzStateMachineTest.java b/tests/telephonytests/src/com/android/internal/telephony/NitzStateMachineTest.java
deleted file mode 100644
index c327581..0000000
--- a/tests/telephonytests/src/com/android/internal/telephony/NitzStateMachineTest.java
+++ /dev/null
@@ -1,739 +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 com.android.internal.telephony.TimeZoneLookupHelper.CountryResult;
-import com.android.internal.telephony.TimeZoneLookupHelper.OffsetResult;
-import com.android.internal.telephony.util.TimeStampedValue;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-
-public class NitzStateMachineTest extends TelephonyTest {
-
-    @Mock
-    private NitzStateMachine.DeviceState mDeviceState;
-
-    @Mock
-    private TimeServiceHelper mTimeServiceHelper;
-
-    private TimeZoneLookupHelper mRealTimeZoneLookupHelper;
-
-    private NitzStateMachine 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 NitzStateMachine(
-                mPhone, mTimeServiceHelper, mDeviceState, mRealTimeZoneLookupHelper);
-
-        logd("ServiceStateTrackerTest -Setup!");
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        checkNoUnverifiedSetOperations(mTimeServiceHelper);
-
-        super.tearDown();
-    }
-
-    // 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.
-    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();
-
-    @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().mValue,
-                UNIQUE_US_ZONE_SCENARIO.getNetworkCountryIsoCode());
-        assertEquals(expectedLookupResult, actualLookupResult);
-    }
-
-    // A country with a single zone : the zone can be guessed from the country.
-    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();
-
-    @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().mValue,
-                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().mValue, 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().mValue, 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().mValue, 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().mValue, 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().mValue, 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().mValue, 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().mValue, 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().mValue, 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().mValue, 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.
-        // TODO(nfuller): The following line should probably be assertTrue but the logic under test
-        // may be buggy. Look at whether it needs to change.
-        assertFalse(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
-        assertEquals(scenario.getNitzSignal().mValue, 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().mValue, 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.
-        // TODO(nfuller): The following line should probably be assertTrue but the logic under test
-        // may be buggy. Look at whether it needs to change.
-        assertFalse(mNitzStateMachine.getNitzTimeZoneDetectionSuccessful());
-        assertEquals(scenario.getNitzSignal().mValue, mNitzStateMachine.getCachedNitzData());
-        assertEquals(scenario.getTimeZoneId(), mNitzStateMachine.getSavedTimeZoneId());
-    }
-
-    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() {
-            NitzStateMachineTest.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);
-                mNitzSignal = new TimeStampedValue<>(nitzData, mInitialDeviceRealtimeMillis);
-            }
-            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(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());
-        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/OldNitzStateMachineTest.java b/tests/telephonytests/src/com/android/internal/telephony/OldNitzStateMachineTest.java
new file mode 100644
index 0000000..2fc864a
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/OldNitzStateMachineTest.java
@@ -0,0 +1,1110 @@
+/*
+ * 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 ec52c79..49ab00b 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/PhoneSubInfoControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/PhoneSubInfoControllerTest.java
@@ -15,15 +15,10 @@
  */
 package com.android.internal.telephony;
 
-import android.app.AppOpsManager;
-import android.content.Context;
-import org.junit.After;
-import org.junit.Assert;
-import org.junit.Before;
-
 import static android.Manifest.permission.READ_PHONE_STATE;
 import static android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE;
 import static android.Manifest.permission.READ_SMS;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
@@ -31,12 +26,17 @@
 import static org.mockito.Mockito.anyString;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.eq;
+
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
 import org.junit.Test;
 import org.mockito.Mock;
 
-
-import android.test.suitebuilder.annotation.SmallTest;
-
 public class PhoneSubInfoControllerTest extends TelephonyTest {
     private PhoneSubInfoController mPhoneSubInfoControllerUT;
     private AppOpsManager mAppOsMgr;
@@ -92,7 +92,7 @@
             Assert.fail("expected Security Exception Thrown");
         } catch (Exception ex) {
             assertTrue(ex instanceof SecurityException);
-            assertEquals(READ_PHONE_STATE + " denied: getDeviceId", ex.getMessage());
+            assertTrue(ex.getMessage().contains("getDeviceId"));
         }
 
         try {
@@ -100,7 +100,7 @@
             Assert.fail("expected Security Exception Thrown");
         } catch (Exception ex) {
             assertTrue(ex instanceof SecurityException);
-            assertEquals(READ_PHONE_STATE + " denied: getDeviceId", ex.getMessage());
+            assertTrue(ex.getMessage().contains("getDeviceId"));
         }
 
         //case 2: no READ_PRIVILEGED_PHONE_STATE & appOsMgr READ_PHONE_PERMISSION
@@ -142,7 +142,7 @@
             Assert.fail("expected Security Exception Thrown");
         } catch (Exception ex) {
             assertTrue(ex instanceof SecurityException);
-            assertEquals(READ_PHONE_STATE + " denied: getNai", ex.getMessage());
+            assertTrue(ex.getMessage().contains("getNai"));
         }
 
         try {
@@ -150,7 +150,7 @@
             Assert.fail("expected Security Exception Thrown");
         } catch (Exception ex) {
             assertTrue(ex instanceof SecurityException);
-            assertEquals(READ_PHONE_STATE + " denied: getNai", ex.getMessage());
+            assertTrue(ex.getMessage().contains("getNai"));
         }
 
         //case 2: no READ_PRIVILEGED_PHONE_STATE & appOsMgr READ_PHONE_PERMISSION
@@ -192,7 +192,7 @@
             Assert.fail("expected Security Exception Thrown");
         } catch (Exception ex) {
             assertTrue(ex instanceof SecurityException);
-            assertEquals(READ_PHONE_STATE + " denied: getImei", ex.getMessage());
+            assertTrue(ex.getMessage().contains("getImei"));
         }
 
         try {
@@ -200,7 +200,7 @@
             Assert.fail("expected Security Exception Thrown");
         } catch (Exception ex) {
             assertTrue(ex instanceof SecurityException);
-            assertEquals(READ_PHONE_STATE + " denied: getImei", ex.getMessage());
+            assertTrue(ex.getMessage().contains("getImei"));
         }
 
         //case 2: no READ_PRIVILEGED_PHONE_STATE & appOsMgr READ_PHONE_PERMISSION
@@ -242,7 +242,7 @@
             Assert.fail("expected Security Exception Thrown");
         } catch (Exception ex) {
             assertTrue(ex instanceof SecurityException);
-            assertEquals(READ_PHONE_STATE + " denied: getDeviceSvn", ex.getMessage());
+            assertTrue(ex.getMessage().contains("getDeviceSvn"));
         }
 
         try {
@@ -250,7 +250,7 @@
             Assert.fail("expected Security Exception Thrown");
         } catch (Exception ex) {
             assertTrue(ex instanceof SecurityException);
-            assertEquals(READ_PHONE_STATE + " denied: getDeviceSvn", ex.getMessage());
+            assertTrue(ex.getMessage().contains("getDeviceSvn"));
         }
 
         //case 2: no READ_PRIVILEGED_PHONE_STATE & appOsMgr READ_PHONE_PERMISSION
@@ -295,7 +295,7 @@
             Assert.fail("expected Security Exception Thrown");
         } catch (Exception ex) {
             assertTrue(ex instanceof SecurityException);
-            assertEquals(READ_PHONE_STATE + " denied: getSubscriberId", ex.getMessage());
+            assertTrue(ex.getMessage().contains("getSubscriberId"));
         }
 
         try {
@@ -303,7 +303,7 @@
             Assert.fail("expected Security Exception Thrown");
         } catch (Exception ex) {
             assertTrue(ex instanceof SecurityException);
-            assertEquals(READ_PHONE_STATE + " denied: getSubscriberId", ex.getMessage());
+            assertTrue(ex.getMessage().contains("getSubscriberId"));
         }
 
         //case 2: no READ_PRIVILEGED_PHONE_STATE & appOsMgr READ_PHONE_PERMISSION
@@ -350,7 +350,7 @@
             Assert.fail("expected Security Exception Thrown");
         } catch (Exception ex) {
             assertTrue(ex instanceof SecurityException);
-            assertEquals(READ_PHONE_STATE + " denied: getIccSerialNumber", ex.getMessage());
+            assertTrue(ex.getMessage().contains("getIccSerialNumber"));
         }
 
         try {
@@ -358,7 +358,7 @@
             Assert.fail("expected Security Exception Thrown");
         } catch (Exception ex) {
             assertTrue(ex instanceof SecurityException);
-            assertEquals(READ_PHONE_STATE + " denied: getIccSerialNumber", ex.getMessage());
+            assertTrue(ex.getMessage().contains("getIccSerialNumber"));
         }
 
         //case 2: no READ_PRIVILEGED_PHONE_STATE & appOsMgr READ_PHONE_PERMISSION
@@ -483,7 +483,7 @@
             Assert.fail("expected Security Exception Thrown");
         } catch (Exception ex) {
             assertTrue(ex instanceof SecurityException);
-            assertEquals(READ_PHONE_STATE + " denied: getLine1AlphaTag", ex.getMessage());
+            assertTrue(ex.getMessage().contains("getLine1AlphaTag"));
         }
 
         try {
@@ -491,7 +491,7 @@
             Assert.fail("expected Security Exception Thrown");
         } catch (Exception ex) {
             assertTrue(ex instanceof SecurityException);
-            assertEquals(READ_PHONE_STATE + " denied: getLine1AlphaTag", ex.getMessage());
+            assertTrue(ex.getMessage().contains("getLine1AlphaTag"));
         }
 
         //case 2: no READ_PRIVILEGED_PHONE_STATE & appOsMgr READ_PHONE_PERMISSION
@@ -535,7 +535,7 @@
             Assert.fail("expected Security Exception Thrown");
         } catch (Exception ex) {
             assertTrue(ex instanceof SecurityException);
-            assertEquals(READ_PHONE_STATE + " denied: getMsisdn", ex.getMessage());
+            assertTrue(ex.getMessage().contains("getMsisdn"));
         }
 
         try {
@@ -543,7 +543,7 @@
             Assert.fail("expected Security Exception Thrown");
         } catch (Exception ex) {
             assertTrue(ex instanceof SecurityException);
-            assertEquals(READ_PHONE_STATE + " denied: getMsisdn", ex.getMessage());
+            assertTrue(ex.getMessage().contains("getMsisdn"));
         }
 
         //case 2: no READ_PRIVILEGED_PHONE_STATE & appOsMgr READ_PHONE_PERMISSION
@@ -587,7 +587,7 @@
             Assert.fail("expected Security Exception Thrown");
         } catch (Exception ex) {
             assertTrue(ex instanceof SecurityException);
-            assertEquals(READ_PHONE_STATE + " denied: getVoiceMailNumber", ex.getMessage());
+            assertTrue(ex.getMessage().contains("getVoiceMailNumber"));
         }
 
         try {
@@ -595,7 +595,7 @@
             Assert.fail("expected Security Exception Thrown");
         } catch (Exception ex) {
             assertTrue(ex instanceof SecurityException);
-            assertEquals(READ_PHONE_STATE + " denied: getVoiceMailNumber", ex.getMessage());
+            assertTrue(ex.getMessage().contains("getVoiceMailNumber"));
         }
 
         //case 2: no READ_PRIVILEGED_PHONE_STATE & appOsMgr READ_PHONE_PERMISSION
@@ -641,7 +641,7 @@
             Assert.fail("expected Security Exception Thrown");
         } catch (Exception ex) {
             assertTrue(ex instanceof SecurityException);
-            assertEquals(READ_PHONE_STATE + " denied: getVoiceMailAlphaTag", ex.getMessage());
+            assertTrue(ex.getMessage().contains("getVoiceMailAlphaTag"));
         }
 
         try {
@@ -649,7 +649,7 @@
             Assert.fail("expected Security Exception Thrown");
         } catch (Exception ex) {
             assertTrue(ex instanceof SecurityException);
-            assertEquals(READ_PHONE_STATE + " denied: getVoiceMailAlphaTag", ex.getMessage());
+            assertTrue(ex.getMessage().contains("getVoiceMailAlphaTag"));
         }
 
         //case 2: no READ_PRIVILEGED_PHONE_STATE & appOsMgr READ_PHONE_PERMISSION
diff --git a/tests/telephonytests/src/com/android/internal/telephony/RILTest.java b/tests/telephonytests/src/com/android/internal/telephony/RILTest.java
index 0bedc75..10dd6f2 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/RILTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/RILTest.java
@@ -57,6 +57,7 @@
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SEND_DEVICE_STATE;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SEND_SMS;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SEND_SMS_EXPECT_MORE;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SETUP_DATA_CALL;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_INITIAL_ATTACH_APN;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_SIM_CARD_POWER;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_SMSC_ADDRESS;
@@ -101,6 +102,7 @@
 import android.hardware.radio.V1_0.RadioResponseInfo;
 import android.hardware.radio.V1_0.RadioResponseType;
 import android.hardware.radio.V1_0.SmsWriteArgs;
+import android.hardware.radio.deprecated.V1_0.IOemHook;
 import android.net.ConnectivityManager;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -109,25 +111,29 @@
 import android.os.PowerManager;
 import android.os.WorkSource;
 import android.support.test.filters.FlakyTest;
+import android.telephony.AccessNetworkConstants;
 import android.telephony.CellIdentityCdma;
 import android.telephony.CellIdentityGsm;
 import android.telephony.CellIdentityLte;
+import android.telephony.CellIdentityTdscdma;
 import android.telephony.CellIdentityWcdma;
 import android.telephony.CellInfo;
 import android.telephony.CellInfoCdma;
 import android.telephony.CellInfoGsm;
 import android.telephony.CellInfoLte;
+import android.telephony.CellInfoTdscdma;
 import android.telephony.CellInfoWcdma;
 import android.telephony.CellSignalStrengthCdma;
 import android.telephony.CellSignalStrengthGsm;
 import android.telephony.CellSignalStrengthLte;
+import android.telephony.CellSignalStrengthTdscdma;
 import android.telephony.CellSignalStrengthWcdma;
 import android.telephony.SmsManager;
 import android.telephony.TelephonyManager;
+import android.telephony.data.ApnSetting;
 import android.telephony.data.DataProfile;
 
 import com.android.internal.telephony.RIL.RilHandler;
-import com.android.internal.telephony.dataconnection.ApnSetting;
 import com.android.internal.telephony.dataconnection.DcTracker;
 
 import org.junit.After;
@@ -138,6 +144,7 @@
 import org.mockito.MockitoAnnotations;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 
 public class RILTest extends TelephonyTest {
 
@@ -151,6 +158,8 @@
     private ConnectivityManager mConnectionManager;
     @Mock
     private IRadio mRadioProxy;
+    @Mock
+    private IOemHook mOemHookProxy;
 
     private RilHandler mRilHandler;
     private RIL mRILInstance;
@@ -188,6 +197,8 @@
     private static final int RSSNR = 2147483647;
     private static final int RSRP = 96;
     private static final int RSRQ = 10;
+    private static final int RSCP = 94;
+    private static final int ECNO = 5;
     private static final int SIGNAL_NOISE_RATIO = 6;
     private static final int SIGNAL_STRENGTH = 24;
     private static final int SYSTEM_ID = 65533;
@@ -199,6 +210,26 @@
     private static final int TYPE_GSM = 1;
     private static final int TYPE_LTE = 3;
     private static final int TYPE_WCDMA = 4;
+    private static final int TYPE_TD_SCDMA = 5;
+
+    private static final int PROFILE_ID = 0;
+    private static final String APN = "apn";
+    private static final String PROTOCOL = "IPV6";
+    private static final int AUTH_TYPE = 0;
+    private static final String USER_NAME = "username";
+    private static final String PASSWORD = "password";
+    private static final int TYPE = 0;
+    private static final int MAX_CONNS_TIME = 1;
+    private static final int MAX_CONNS = 3;
+    private static final int WAIT_TIME = 10;
+    private static final boolean APN_ENABLED = true;
+    private static final int SUPPORTED_APNT_YPES_BITMAP = 123456;
+    private static final String ROAMING_PROTOCOL = "IPV6";
+    private static final int BEARER_BITMAP = 123123;
+    private static final int MTU = 1234;
+    private static final String MVNO_TYPE = "";
+    private static final String MVNO_MATCH_DATA = "";
+    private static final boolean MODEM_COGNITIVE = true;
 
     private class RILTestHandler extends HandlerThread {
 
@@ -223,6 +254,7 @@
                     Phone.PREFERRED_CDMA_SUBSCRIPTION, 0);
             mRILUnderTest = spy(mRILInstance);
             doReturn(mRadioProxy).when(mRILUnderTest).getRadioProxy(any());
+            doReturn(mOemHookProxy).when(mRILUnderTest).getOemHookProxy(any());
 
             mRilHandler = mRILUnderTest.getRilHandler();
 
@@ -242,7 +274,8 @@
     }
 
     @Before
-    public void setUp() {
+    public void setUp() throws Exception {
+        super.setUp(RILTest.class.getSimpleName());
         MockitoAnnotations.initMocks(this);
         mTestHandler = new RILTestHandler(getClass().getSimpleName());
         mTestHandler.start();
@@ -252,6 +285,7 @@
     @After
     public void tearDown() throws Exception {
         mTestHandler.quit();
+        super.tearDown();
     }
 
     @FlakyTest
@@ -664,11 +698,12 @@
     @FlakyTest
     @Test
     public void testSetInitialAttachApn() throws Exception {
-        ApnSetting apnSetting = new ApnSetting(
-                -1, "22210", "Vodafone IT", "web.omnitel.it", "", "",
-                "", "", "", "", "", 0, new String[]{"DUN"}, "IP", "IP", true, 0, 0,
-                0, false, 0, 0, 0, 0, "", "");
-        DataProfile dataProfile = DcTracker.createDataProfile(apnSetting, apnSetting.profileId);
+        ApnSetting apnSetting = ApnSetting.makeApnSetting(
+                -1, "22210", "Vodafone IT", "web.omnitel.it", null, -1,
+                null, null, -1, "", "", 0, ApnSetting.TYPE_DUN, ApnSetting.PROTOCOL_IP,
+                ApnSetting.PROTOCOL_IP, true, 0, 0, false, 0, 0, 0, 0, -1, "");
+        DataProfile dataProfile = DcTracker.createDataProfile(
+                apnSetting, apnSetting.getProfileId());
         boolean isRoaming = false;
 
         mRILUnderTest.setInitialAttachApn(dataProfile, isRoaming, obtainMessage());
@@ -995,6 +1030,22 @@
         assertFalse(mRILInstance.getWakeLock(RIL.FOR_WAKELOCK).isHeld());
     }
 
+    @Test
+    public void testInvokeOemRilRequestStrings() throws Exception {
+        String[] strings = new String[]{"a", "b", "c"};
+        mRILUnderTest.invokeOemRilRequestStrings(strings, obtainMessage());
+        verify(mOemHookProxy).sendRequestStrings(
+                mSerialNumberCaptor.capture(), eq(new ArrayList<>(Arrays.asList(strings))));
+    }
+
+    @Test
+    public void testInvokeOemRilRequestRaw() throws Exception {
+        byte[] data = new byte[]{1, 2, 3};
+        mRILUnderTest.invokeOemRilRequestRaw(data, obtainMessage());
+        verify(mOemHookProxy).sendRequestRaw(
+                mSerialNumberCaptor.capture(), eq(mRILUnderTest.primitiveArrayToArrayList(data)));
+    }
+
     private Message obtainMessage() {
         return mTestHandler.getThreadHandler().obtainMessage();
     }
@@ -1139,7 +1190,8 @@
         expected.setTimeStampType(RIL_TIMESTAMP_TYPE_OEM_RIL);
         CellIdentityWcdma ci = new CellIdentityWcdma(
                 LAC, CID, PSC, UARFCN, MCC_STR, MNC_STR, EMPTY_ALPHA_LONG, EMPTY_ALPHA_SHORT);
-        CellSignalStrengthWcdma cs = new CellSignalStrengthWcdma(SIGNAL_STRENGTH, BIT_ERROR_RATE);
+        CellSignalStrengthWcdma cs = new CellSignalStrengthWcdma(
+                SIGNAL_STRENGTH, BIT_ERROR_RATE, Integer.MAX_VALUE, Integer.MAX_VALUE);
         expected.setCellIdentity(ci);
         expected.setCellSignalStrength(cs);
         expected.setCellConnectionStatus(CellInfo.CONNECTION_UNKNOWN);
@@ -1147,6 +1199,47 @@
     }
 
     @Test
+    public void testConvertHalCellInfoListForTdscdma() throws Exception {
+        android.hardware.radio.V1_2.CellInfoTdscdma cellinfo =
+                new android.hardware.radio.V1_2.CellInfoTdscdma();
+        cellinfo.cellIdentityTdscdma.base.lac = LAC;
+        cellinfo.cellIdentityTdscdma.base.cid = CID;
+        cellinfo.cellIdentityTdscdma.base.cpid = PSC;
+        cellinfo.cellIdentityTdscdma.uarfcn = UARFCN;
+        cellinfo.cellIdentityTdscdma.base.mcc = MCC_STR;
+        cellinfo.cellIdentityTdscdma.base.mnc = MNC_STR;
+        cellinfo.signalStrengthTdscdma.signalStrength = SIGNAL_STRENGTH;
+        cellinfo.signalStrengthTdscdma.bitErrorRate = BIT_ERROR_RATE;
+        cellinfo.signalStrengthTdscdma.rscp = RSCP;
+        android.hardware.radio.V1_2.CellInfo record = new android.hardware.radio.V1_2.CellInfo();
+        record.cellInfoType = TYPE_TD_SCDMA;
+        record.registered = false;
+        record.timeStampType = RIL_TIMESTAMP_TYPE_OEM_RIL;
+        record.timeStamp = TIMESTAMP;
+        record.tdscdma.add(cellinfo);
+        ArrayList<android.hardware.radio.V1_2.CellInfo> records =
+                new ArrayList<android.hardware.radio.V1_2.CellInfo>();
+        records.add(record);
+
+        ArrayList<CellInfo> ret = RIL.convertHalCellInfoList_1_2(records);
+
+        assertEquals(1, ret.size());
+        CellInfoTdscdma cellInfoTdscdma = (CellInfoTdscdma) ret.get(0);
+        CellInfoTdscdma expected = new CellInfoTdscdma();
+        expected.setRegistered(false);
+        expected.setTimeStamp(TIMESTAMP);
+        expected.setTimeStampType(RIL_TIMESTAMP_TYPE_OEM_RIL);
+        expected.setCellConnectionStatus(CellInfo.CONNECTION_NONE);
+        CellIdentityTdscdma ci = new CellIdentityTdscdma(
+                MCC_STR, MNC_STR, LAC, CID, PSC, UARFCN, EMPTY_ALPHA_LONG, EMPTY_ALPHA_SHORT);
+        CellSignalStrengthTdscdma cs = new CellSignalStrengthTdscdma(
+                SIGNAL_STRENGTH, BIT_ERROR_RATE, RSCP);
+        expected.setCellIdentity(ci);
+        expected.setCellSignalStrength(cs);
+        assertEquals(expected, cellInfoTdscdma);
+    }
+
+    @Test
     public void testConvertHalCellInfoListForCdma() throws Exception {
         android.hardware.radio.V1_0.CellInfoCdma cellinfo =
                 new android.hardware.radio.V1_0.CellInfoCdma();
@@ -1330,7 +1423,8 @@
         expected.setTimeStampType(RIL_TIMESTAMP_TYPE_OEM_RIL);
         CellIdentityWcdma ci = new CellIdentityWcdma(
                 LAC, CID, PSC, UARFCN, MCC_STR, MNC_STR, ALPHA_LONG, ALPHA_SHORT);
-        CellSignalStrengthWcdma cs = new CellSignalStrengthWcdma(SIGNAL_STRENGTH, BIT_ERROR_RATE);
+        CellSignalStrengthWcdma cs =
+                new CellSignalStrengthWcdma(SIGNAL_STRENGTH, BIT_ERROR_RATE, RSCP, ECNO);
         expected.setCellIdentity(ci);
         expected.setCellSignalStrength(cs);
         expected.setCellConnectionStatus(CellInfo.CONNECTION_NONE);
@@ -1350,7 +1444,8 @@
         expected.setTimeStampType(RIL_TIMESTAMP_TYPE_OEM_RIL);
         CellIdentityWcdma ci = new CellIdentityWcdma(
                 LAC, CID, PSC, UARFCN, MCC_STR, MNC_STR, EMPTY_ALPHA_LONG, EMPTY_ALPHA_SHORT);
-        CellSignalStrengthWcdma cs = new CellSignalStrengthWcdma(SIGNAL_STRENGTH, BIT_ERROR_RATE);
+        CellSignalStrengthWcdma cs = new CellSignalStrengthWcdma(
+                SIGNAL_STRENGTH, BIT_ERROR_RATE, RSCP, ECNO);
         expected.setCellIdentity(ci);
         expected.setCellSignalStrength(cs);
         expected.setCellConnectionStatus(CellInfo.CONNECTION_NONE);
@@ -1372,7 +1467,8 @@
         expected.setTimeStampType(RIL_TIMESTAMP_TYPE_OEM_RIL);
         CellIdentityWcdma ci = new CellIdentityWcdma(
                 LAC, CID, PSC, UARFCN, null, null, ALPHA_LONG, ALPHA_SHORT);
-        CellSignalStrengthWcdma cs = new CellSignalStrengthWcdma(SIGNAL_STRENGTH, BIT_ERROR_RATE);
+        CellSignalStrengthWcdma cs = new CellSignalStrengthWcdma(
+                SIGNAL_STRENGTH, BIT_ERROR_RATE, RSCP, ECNO);
         expected.setCellIdentity(ci);
         expected.setCellSignalStrength(cs);
         expected.setCellConnectionStatus(CellInfo.CONNECTION_NONE);
@@ -1495,8 +1591,8 @@
         cellinfo.cellIdentityWcdma.operatorNames.alphaShort = alphaShort;
         cellinfo.signalStrengthWcdma.base.signalStrength = SIGNAL_STRENGTH;
         cellinfo.signalStrengthWcdma.base.bitErrorRate = BIT_ERROR_RATE;
-        cellinfo.signalStrengthWcdma.rscp = 10;
-        cellinfo.signalStrengthWcdma.ecno = 5;
+        cellinfo.signalStrengthWcdma.rscp = RSCP;
+        cellinfo.signalStrengthWcdma.ecno = ECNO;
         android.hardware.radio.V1_2.CellInfo record = new android.hardware.radio.V1_2.CellInfo();
         record.cellInfoType = TYPE_WCDMA;
         record.registered = false;
@@ -1597,4 +1693,37 @@
         assertEquals(getTdScdmaSignalStrength_1_0(-1), getTdScdmaSignalStrength_1_2(255));
     }
 
+    @Test
+    public void testSetupDataCall() throws Exception {
+
+        DataProfile dp = new DataProfile(PROFILE_ID, APN, PROTOCOL, AUTH_TYPE, USER_NAME, PASSWORD,
+                TYPE, MAX_CONNS_TIME, MAX_CONNS, WAIT_TIME, APN_ENABLED, SUPPORTED_APNT_YPES_BITMAP,
+                ROAMING_PROTOCOL, BEARER_BITMAP, MTU, MVNO_TYPE, MVNO_MATCH_DATA, MODEM_COGNITIVE);
+        mRILUnderTest.setupDataCall(AccessNetworkConstants.AccessNetworkType.EUTRAN, dp, false,
+                false, 0, null, obtainMessage());
+        ArgumentCaptor<DataProfileInfo> dpiCaptor = ArgumentCaptor.forClass(DataProfileInfo.class);
+        verify(mRadioProxy).setupDataCall(
+                mSerialNumberCaptor.capture(), eq(AccessNetworkConstants.AccessNetworkType.EUTRAN),
+                dpiCaptor.capture(), eq(true), eq(false), eq(false));
+        verifyRILResponse(
+                mRILUnderTest, mSerialNumberCaptor.getValue(), RIL_REQUEST_SETUP_DATA_CALL);
+        DataProfileInfo dpi = dpiCaptor.getValue();
+        assertEquals(PROFILE_ID, dpi.profileId);
+        assertEquals(APN, dpi.apn);
+        assertEquals(PROTOCOL, dpi.protocol);
+        assertEquals(AUTH_TYPE, dpi.authType);
+        assertEquals(USER_NAME, dpi.user);
+        assertEquals(PASSWORD, dpi.password);
+        assertEquals(TYPE, dpi.type);
+        assertEquals(MAX_CONNS_TIME, dpi.maxConnsTime);
+        assertEquals(MAX_CONNS, dpi.maxConns);
+        assertEquals(WAIT_TIME, dpi.waitTime);
+        assertEquals(APN_ENABLED, dpi.enabled);
+        assertEquals(SUPPORTED_APNT_YPES_BITMAP, dpi.supportedApnTypesBitmap);
+        assertEquals(ROAMING_PROTOCOL, dpi.protocol);
+        assertEquals(BEARER_BITMAP, dpi.bearerBitmap);
+        assertEquals(MTU, dpi.mtu);
+        assertEquals(0, dpi.mvnoType);
+        assertEquals(MVNO_MATCH_DATA, dpi.mvnoMatchData);
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTest.java b/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTest.java
index a6bbfe2..55fde49 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTest.java
@@ -28,6 +28,7 @@
 import junit.framework.TestCase;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 
 public class ServiceStateTest extends TestCase {
 
@@ -165,6 +166,17 @@
             }
         }
     }
+    @SmallTest
+    public void testGetCellBandwidths() {
+        ServiceState ss = new ServiceState();
+
+        ss.setCellBandwidths(null);
+        assertTrue(Arrays.equals(ss.getCellBandwidths(), new int[0]));
+
+        int[] cellBandwidths = new int[]{5000, 10000};
+        ss.setCellBandwidths(cellBandwidths);
+        assertTrue(Arrays.equals(ss.getCellBandwidths(), cellBandwidths));
+    }
 
     @SmallTest
     public void testOperatorName() {
@@ -199,9 +211,9 @@
         ss.setIsManualSelection(true);
         assertTrue(ss.getIsManualSelection());
 
-        ss.setSystemAndNetworkId(123, 456);
-        assertEquals(123, ss.getSystemId());
-        assertEquals(456, ss.getNetworkId());
+        ss.setCdmaSystemAndNetworkId(123, 456);
+        assertEquals(123, ss.getCdmaSystemId());
+        assertEquals(456, ss.getCdmaNetworkId());
 
         ss.setEmergencyOnly(true);
         assertTrue(ss.isEmergencyOnly());
@@ -220,7 +232,7 @@
         ss.setRilVoiceRadioTechnology(ServiceState.RIL_RADIO_TECHNOLOGY_1xRTT);
         ss.setRilDataRadioTechnology(ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_0);
         ss.setCssIndicator(1);
-        ss.setSystemAndNetworkId(2, 3);
+        ss.setCdmaSystemAndNetworkId(2, 3);
         ss.setCdmaRoamingIndicator(4);
         ss.setCdmaDefaultRoamingIndicator(5);
         ss.setCdmaEriIconIndex(6);
@@ -250,7 +262,7 @@
         ss.setRilVoiceRadioTechnology(ServiceState.RIL_RADIO_TECHNOLOGY_1xRTT);
         ss.setRilDataRadioTechnology(ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_0);
         ss.setCssIndicator(1);
-        ss.setSystemAndNetworkId(2, 3);
+        ss.setCdmaSystemAndNetworkId(2, 3);
         ss.setCdmaRoamingIndicator(4);
         ss.setCdmaDefaultRoamingIndicator(5);
         ss.setCdmaEriIconIndex(6);
@@ -270,18 +282,18 @@
     @SmallTest
     public void testNetworkRegistrationState() {
         NetworkRegistrationState wwanVoiceRegState = new NetworkRegistrationState(
-                AccessNetworkConstants.TransportType.WWAN, NetworkRegistrationState.DOMAIN_CS,
+                NetworkRegistrationState.DOMAIN_CS, AccessNetworkConstants.TransportType.WWAN,
                 0, 0, 0, false,
                 null, null, true, 0, 0, 0);
 
 
         NetworkRegistrationState wwanDataRegState = new NetworkRegistrationState(
-                AccessNetworkConstants.TransportType.WWAN, NetworkRegistrationState.DOMAIN_PS,
+                NetworkRegistrationState.DOMAIN_PS, AccessNetworkConstants.TransportType.WWAN,
                 0, 0, 0, false,
                 null, null, 0);
 
         NetworkRegistrationState wlanRegState = new NetworkRegistrationState(
-                AccessNetworkConstants.TransportType.WLAN, NetworkRegistrationState.DOMAIN_PS,
+                NetworkRegistrationState.DOMAIN_PS, AccessNetworkConstants.TransportType.WLAN,
                 0, 0, 0, false,
                 null, null);
 
@@ -291,19 +303,59 @@
         ss.addNetworkRegistrationState(wwanDataRegState);
         ss.addNetworkRegistrationState(wlanRegState);
 
-        assertEquals(ss.getNetworkRegistrationStates(AccessNetworkConstants.TransportType.WWAN,
-                NetworkRegistrationState.DOMAIN_CS), wwanVoiceRegState);
-        assertEquals(ss.getNetworkRegistrationStates(AccessNetworkConstants.TransportType.WWAN,
-                NetworkRegistrationState.DOMAIN_PS), wwanDataRegState);
-        assertEquals(ss.getNetworkRegistrationStates(AccessNetworkConstants.TransportType.WLAN,
-                NetworkRegistrationState.DOMAIN_PS), wlanRegState);
+        assertEquals(ss.getNetworkRegistrationStates(NetworkRegistrationState.DOMAIN_CS,
+                AccessNetworkConstants.TransportType.WWAN), wwanVoiceRegState);
+        assertEquals(ss.getNetworkRegistrationStates(NetworkRegistrationState.DOMAIN_PS,
+                AccessNetworkConstants.TransportType.WWAN), wwanDataRegState);
+        assertEquals(ss.getNetworkRegistrationStates(NetworkRegistrationState.DOMAIN_PS,
+                AccessNetworkConstants.TransportType.WLAN), wlanRegState);
 
         wwanDataRegState = new NetworkRegistrationState(
-                AccessNetworkConstants.TransportType.WWAN, NetworkRegistrationState.DOMAIN_PS,
+                NetworkRegistrationState.DOMAIN_PS, AccessNetworkConstants.TransportType.WWAN,
                 0, 0, 0, true,
                 null, null, 0);
         ss.addNetworkRegistrationState(wwanDataRegState);
-        assertEquals(ss.getNetworkRegistrationStates(AccessNetworkConstants.TransportType.WWAN,
-                NetworkRegistrationState.DOMAIN_PS), wwanDataRegState);
+        assertEquals(ss.getNetworkRegistrationStates(NetworkRegistrationState.DOMAIN_PS,
+                AccessNetworkConstants.TransportType.WWAN), wwanDataRegState);
+    }
+
+    @SmallTest
+    public void testDuplexMode_notLte() {
+        ServiceState ss = new ServiceState();
+        ss.setRilDataRadioTechnology(ServiceState.RIL_RADIO_TECHNOLOGY_GSM);
+        ss.setChannelNumber(2400);
+
+        assertEquals(ss.getDuplexMode(), ServiceState.DUPLEX_MODE_UNKNOWN);
+    }
+
+    @SmallTest
+    public void testDuplexMode_invalidEarfcn() {
+        ServiceState ss = new ServiceState();
+        ss.setRilDataRadioTechnology(ServiceState.RIL_RADIO_TECHNOLOGY_LTE);
+        ss.setChannelNumber(-1);
+
+        assertEquals(ss.getDuplexMode(), ServiceState.DUPLEX_MODE_UNKNOWN);
+
+        ss.setChannelNumber(Integer.MAX_VALUE);
+
+        assertEquals(ss.getDuplexMode(), ServiceState.DUPLEX_MODE_UNKNOWN);
+    }
+
+    @SmallTest
+    public void testDuplexMode_FddChannel() {
+        ServiceState ss = new ServiceState();
+        ss.setRilDataRadioTechnology(ServiceState.RIL_RADIO_TECHNOLOGY_LTE);
+        ss.setChannelNumber(2400); // band 5
+
+        assertEquals(ss.getDuplexMode(), ServiceState.DUPLEX_MODE_FDD);
+    }
+
+    @SmallTest
+    public void testDuplexMode_TddChannel() {
+        ServiceState ss = new ServiceState();
+        ss.setRilDataRadioTechnology(ServiceState.RIL_RADIO_TECHNOLOGY_LTE);
+        ss.setChannelNumber(36000); // band 33
+
+        assertEquals(ss.getDuplexMode(), ServiceState.DUPLEX_MODE_TDD);
     }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java
index 74aec16..603f800 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java
@@ -25,17 +25,26 @@
 import static org.mockito.Matchers.anyLong;
 import static org.mockito.Matchers.nullable;
 import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyString;
 import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.app.IAlarmManager;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
 import android.os.AsyncResult;
 import android.os.Bundle;
 import android.os.Handler;
@@ -48,26 +57,30 @@
 import android.os.UserHandle;
 import android.os.WorkSource;
 import android.support.test.filters.FlakyTest;
+import android.telephony.AccessNetworkConstants.AccessNetworkType;
 import android.telephony.CarrierConfigManager;
 import android.telephony.CellIdentityGsm;
+import android.telephony.CellIdentityLte;
 import android.telephony.CellInfo;
 import android.telephony.CellInfoGsm;
 import android.telephony.NetworkRegistrationState;
 import android.telephony.NetworkService;
+import android.telephony.PhysicalChannelConfig;
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
 import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
 import android.telephony.gsm.GsmCellLocation;
 import android.test.suitebuilder.annotation.MediumTest;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Pair;
+import android.util.TimestampedValue;
 
 import com.android.internal.R;
 import com.android.internal.telephony.cdma.CdmaSubscriptionSourceManager;
 import com.android.internal.telephony.dataconnection.DcTracker;
 import com.android.internal.telephony.test.SimulatedCommands;
 import com.android.internal.telephony.uicc.IccCardApplicationStatus;
-import com.android.internal.telephony.util.TimeStampedValue;
 
 import org.junit.After;
 import org.junit.Before;
@@ -75,8 +88,10 @@
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
 
@@ -126,7 +141,7 @@
         mCellularNetworkService = new CellularNetworkService();
         ServiceInfo serviceInfo =  new ServiceInfo();
         serviceInfo.packageName = "com.android.phone";
-        serviceInfo.permission = "android.permission.BIND_NETWORK_SERVICE";
+        serviceInfo.permission = "android.permission.BIND_TELEPHONY_NETWORK_SERVICE";
         IntentFilter filter = new IntentFilter();
         mContextFixture.addService(
                 NetworkService.NETWORK_SERVICE_INTERFACE,
@@ -158,6 +173,10 @@
         mBundle.putStringArray(
                 CarrierConfigManager.KEY_NON_ROAMING_OPERATOR_STRING_ARRAY, new String[]{"123456"});
 
+        mBundle.putStringArray(CarrierConfigManager.KEY_RATCHET_RAT_FAMILIES,
+                // UMTS < GPRS < EDGE
+                new String[]{"3,1,2"});
+
         mSimulatedCommands.setVoiceRegState(NetworkRegistrationState.REG_STATE_HOME);
         mSimulatedCommands.setVoiceRadioTech(ServiceState.RIL_RADIO_TECHNOLOGY_HSPA);
         mSimulatedCommands.setDataRegState(NetworkRegistrationState.REG_STATE_HOME);
@@ -428,6 +447,41 @@
     }
 
     @Test
+    public void testSetsNewSignalStrengthReportingCriteria() {
+        int[] wcdmaThresholds = {
+                -110, /* SIGNAL_STRENGTH_POOR */
+                -100, /* SIGNAL_STRENGTH_MODERATE */
+                -90, /* SIGNAL_STRENGTH_GOOD */
+                -80  /* SIGNAL_STRENGTH_GREAT */
+        };
+        mBundle.putIntArray(CarrierConfigManager.KEY_WCDMA_RSCP_THRESHOLDS_INT_ARRAY,
+                wcdmaThresholds);
+
+        int[] lteThresholds = {
+                -130, /* SIGNAL_STRENGTH_POOR */
+                -120, /* SIGNAL_STRENGTH_MODERATE */
+                -110, /* SIGNAL_STRENGTH_GOOD */
+                -100,  /* SIGNAL_STRENGTH_GREAT */
+        };
+        mBundle.putIntArray(CarrierConfigManager.KEY_LTE_RSRP_THRESHOLDS_INT_ARRAY,
+                lteThresholds);
+
+        CarrierConfigManager mockConfigManager = Mockito.mock(CarrierConfigManager.class);
+        when(mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE))
+                .thenReturn(mockConfigManager);
+        when(mockConfigManager.getConfigForSubId(anyInt())).thenReturn(mBundle);
+
+        Intent intent = new Intent().setAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
+        mContext.sendBroadcast(intent);
+        waitForMs(300);
+
+        verify(mPhone).setSignalStrengthReportingCriteria(eq(wcdmaThresholds),
+                eq(AccessNetworkType.UTRAN));
+        verify(mPhone).setSignalStrengthReportingCriteria(eq(lteThresholds),
+                eq(AccessNetworkType.EUTRAN));
+    }
+
+    @Test
     @MediumTest
     public void testSignalLevelWithWcdmaRscpThresholds() {
         mBundle.putIntArray(CarrierConfigManager.KEY_WCDMA_RSCP_THRESHOLDS_INT_ARRAY,
@@ -1160,6 +1214,127 @@
         assertEquals(intArgumentCaptor.getValue().intValue(), restrictedState[1]);
     }
 
+    private boolean notificationHasTitleSet(Notification n) {
+        // Notification has no methods to check the actual title, but #toString() includes the
+        // word "tick" if the title is set so we check this as a workaround
+        return n.toString().contains("tick");
+    }
+
+    private String getNotificationTitle(Notification n) {
+        return n.extras.getString(Notification.EXTRA_TITLE);
+    }
+
+    @Test
+    @SmallTest
+    public void testSetPsNotifications() {
+        sst.mSubId = 1;
+        final NotificationManager nm = (NotificationManager)
+                mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+        mContextFixture.putBooleanResource(
+                R.bool.config_user_notification_of_restrictied_mobile_access, true);
+        doReturn(new ApplicationInfo()).when(mContext).getApplicationInfo();
+        Drawable mockDrawable = mock(Drawable.class);
+        Resources mockResources = mContext.getResources();
+        when(mockResources.getDrawable(anyInt(), any())).thenReturn(mockDrawable);
+
+        mContextFixture.putResource(com.android.internal.R.string.RestrictedOnDataTitle, "test1");
+        sst.setNotification(ServiceStateTracker.PS_ENABLED);
+        ArgumentCaptor<Notification> notificationArgumentCaptor =
+                ArgumentCaptor.forClass(Notification.class);
+        verify(nm).notify(anyString(), anyInt(), notificationArgumentCaptor.capture());
+        // if the postedNotification has title set then it must have been the correct notification
+        Notification postedNotification = notificationArgumentCaptor.getValue();
+        assertTrue(notificationHasTitleSet(postedNotification));
+        assertEquals("test1", getNotificationTitle(postedNotification));
+
+        sst.setNotification(ServiceStateTracker.PS_DISABLED);
+        verify(nm).cancel(anyString(), anyInt());
+    }
+
+    @Test
+    @SmallTest
+    public void testSetCsNotifications() {
+        sst.mSubId = 1;
+        final NotificationManager nm = (NotificationManager)
+                mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+        mContextFixture.putBooleanResource(
+                R.bool.config_user_notification_of_restrictied_mobile_access, true);
+        doReturn(new ApplicationInfo()).when(mContext).getApplicationInfo();
+        Drawable mockDrawable = mock(Drawable.class);
+        Resources mockResources = mContext.getResources();
+        when(mockResources.getDrawable(anyInt(), any())).thenReturn(mockDrawable);
+
+        mContextFixture.putResource(com.android.internal.R.string.RestrictedOnAllVoiceTitle,
+                "test2");
+        sst.setNotification(ServiceStateTracker.CS_ENABLED);
+        ArgumentCaptor<Notification> notificationArgumentCaptor =
+                ArgumentCaptor.forClass(Notification.class);
+        verify(nm).notify(anyString(), anyInt(), notificationArgumentCaptor.capture());
+        // if the postedNotification has title set then it must have been the correct notification
+        Notification postedNotification = notificationArgumentCaptor.getValue();
+        assertTrue(notificationHasTitleSet(postedNotification));
+        assertEquals("test2", getNotificationTitle(postedNotification));
+
+        sst.setNotification(ServiceStateTracker.CS_DISABLED);
+        verify(nm).cancel(anyString(), anyInt());
+    }
+
+    @Test
+    @SmallTest
+    public void testSetCsNormalNotifications() {
+        sst.mSubId = 1;
+        final NotificationManager nm = (NotificationManager)
+                mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+        mContextFixture.putBooleanResource(
+                R.bool.config_user_notification_of_restrictied_mobile_access, true);
+        doReturn(new ApplicationInfo()).when(mContext).getApplicationInfo();
+        Drawable mockDrawable = mock(Drawable.class);
+        Resources mockResources = mContext.getResources();
+        when(mockResources.getDrawable(anyInt(), any())).thenReturn(mockDrawable);
+
+        mContextFixture.putResource(com.android.internal.R.string.RestrictedOnNormalTitle, "test3");
+        sst.setNotification(ServiceStateTracker.CS_NORMAL_ENABLED);
+        ArgumentCaptor<Notification> notificationArgumentCaptor =
+                ArgumentCaptor.forClass(Notification.class);
+        verify(nm).notify(anyString(), anyInt(), notificationArgumentCaptor.capture());
+        // if the postedNotification has title set then it must have been the correct notification
+        Notification postedNotification = notificationArgumentCaptor.getValue();
+        assertTrue(notificationHasTitleSet(postedNotification));
+        assertEquals("test3", getNotificationTitle(postedNotification));
+
+        sst.setNotification(ServiceStateTracker.CS_DISABLED);
+        verify(nm).cancel(anyString(), anyInt());
+    }
+
+    @Test
+    @SmallTest
+    public void testSetCsEmergencyNotifications() {
+        sst.mSubId = 1;
+        final NotificationManager nm = (NotificationManager)
+                mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+        mContextFixture.putBooleanResource(
+                R.bool.config_user_notification_of_restrictied_mobile_access, true);
+        doReturn(new ApplicationInfo()).when(mContext).getApplicationInfo();
+        Drawable mockDrawable = mock(Drawable.class);
+        Resources mockResources = mContext.getResources();
+        when(mockResources.getDrawable(anyInt(), any())).thenReturn(mockDrawable);
+
+        mContextFixture.putResource(com.android.internal.R.string.RestrictedOnEmergencyTitle,
+                "test4");
+        sst.setNotification(ServiceStateTracker.CS_EMERGENCY_ENABLED);
+        ArgumentCaptor<Notification> notificationArgumentCaptor =
+                ArgumentCaptor.forClass(Notification.class);
+        verify(nm).notify(anyString(), anyInt(), notificationArgumentCaptor.capture());
+        // if the postedNotification has title set then it must have been the correct notification
+        Notification postedNotification = notificationArgumentCaptor.getValue();
+        assertTrue(notificationHasTitleSet(postedNotification));
+        assertEquals("test4", getNotificationTitle(postedNotification));
+
+        sst.setNotification(ServiceStateTracker.CS_DISABLED);
+        verify(nm).cancel(anyString(), anyInt());
+        sst.setNotification(ServiceStateTracker.CS_REJECT_CAUSE_ENABLED);
+    }
+
     @Test
     @MediumTest
     public void testRegisterForSubscriptionInfoReady() {
@@ -1319,15 +1494,234 @@
             mSimulatedCommands.triggerNITZupdate(nitzStr);
             waitForMs(200);
 
-            ArgumentCaptor<TimeStampedValue<NitzData>> argumentsCaptor =
-                    ArgumentCaptor.forClass(TimeStampedValue.class);
+            ArgumentCaptor<TimestampedValue<NitzData>> argumentsCaptor =
+                    ArgumentCaptor.forClass(TimestampedValue.class);
             verify(mNitzStateMachine, times(1))
                     .handleNitzReceived(argumentsCaptor.capture());
 
             // Confirm the argument was what we expected.
-            TimeStampedValue<NitzData> actualNitzSignal = argumentsCaptor.getValue();
-            assertEquals(expectedNitzData, actualNitzSignal.mValue);
-            assertTrue(actualNitzSignal.mElapsedRealtime <= SystemClock.elapsedRealtime());
+            TimestampedValue<NitzData> actualNitzSignal = argumentsCaptor.getValue();
+            assertEquals(expectedNitzData, actualNitzSignal.getValue());
+            assertTrue(actualNitzSignal.getReferenceTimeMillis() <= SystemClock.elapsedRealtime());
         }
     }
+
+    // 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
+    public void testRatRatchet() throws Exception {
+        CellIdentityGsm cellIdentity = new CellIdentityGsm(-1, -1, -1, -1, -1, -1);
+        NetworkRegistrationState dataResult = new NetworkRegistrationState(
+                0, 0, 1, 2, 0, false, null, cellIdentity, 1);
+        sst.mPollingContext[0] = 2;
+        // update data reg state to be in service
+        sst.sendMessage(sst.obtainMessage(ServiceStateTracker.EVENT_POLL_STATE_GPRS,
+                new AsyncResult(sst.mPollingContext, dataResult, null)));
+        waitForMs(200);
+        assertEquals(ServiceState.STATE_IN_SERVICE, mSST.getCurrentDataConnectionState());
+
+        NetworkRegistrationState voiceResult = new NetworkRegistrationState(
+                0, 0, 1, 2, 0, false, null, cellIdentity, false, 0, 0, 0);
+        sst.sendMessage(sst.obtainMessage(ServiceStateTracker.EVENT_POLL_STATE_REGISTRATION,
+                new AsyncResult(sst.mPollingContext, voiceResult, null)));
+        waitForMs(200);
+        assertEquals(ServiceState.RIL_RADIO_TECHNOLOGY_EDGE, sst.mSS.getRilVoiceRadioTechnology());
+
+        // EDGE -> GPRS
+        voiceResult = new NetworkRegistrationState(
+                0, 0, 1, 1, 0, false, null,
+                cellIdentity, false, 0, 0, 0);
+        sst.mPollingContext[0] = 2;
+        sst.sendMessage(sst.obtainMessage(ServiceStateTracker.EVENT_POLL_STATE_GPRS,
+                new AsyncResult(sst.mPollingContext, dataResult, null)));
+        waitForMs(200);
+        assertEquals(ServiceState.STATE_IN_SERVICE, mSST.getCurrentDataConnectionState());
+
+        sst.sendMessage(sst.obtainMessage(ServiceStateTracker.EVENT_POLL_STATE_REGISTRATION,
+                new AsyncResult(sst.mPollingContext, voiceResult, null)));
+        waitForMs(200);
+        assertEquals(ServiceState.RIL_RADIO_TECHNOLOGY_EDGE, sst.mSS.getRilVoiceRadioTechnology());
+    }
+
+    // Edge and GPRS are grouped under the same family and Edge has higher rate than GPRS.
+    // Bypass rat rachet when cell id changed. Expect rat update from E to G
+    @Test
+    public void testRatRatchetWithCellChange() throws Exception {
+        CellIdentityGsm cellIdentity = new CellIdentityGsm(-1, -1, -1, -1, -1, -1);
+        NetworkRegistrationState dataResult = new NetworkRegistrationState(
+                0, 0, 1, 2, 0, false, null, cellIdentity, 1);
+        sst.mPollingContext[0] = 2;
+        // update data reg state to be in service
+        sst.sendMessage(sst.obtainMessage(ServiceStateTracker.EVENT_POLL_STATE_GPRS,
+                new AsyncResult(sst.mPollingContext, dataResult, null)));
+        waitForMs(200);
+        assertEquals(ServiceState.STATE_IN_SERVICE, mSST.getCurrentDataConnectionState());
+
+        NetworkRegistrationState voiceResult = new NetworkRegistrationState(
+                0, 0, 1, 2, 0, false, null, cellIdentity, false, 0, 0, 0);
+        sst.sendMessage(sst.obtainMessage(ServiceStateTracker.EVENT_POLL_STATE_REGISTRATION,
+                new AsyncResult(sst.mPollingContext, voiceResult, null)));
+        waitForMs(200);
+        assertEquals(ServiceState.RIL_RADIO_TECHNOLOGY_EDGE, sst.mSS.getRilVoiceRadioTechnology());
+
+        // RAT: EDGE -> GPRS cell ID: -1 -> 5
+        cellIdentity = new CellIdentityGsm(-1, -1, -1, 5, -1, -1);
+        voiceResult = new NetworkRegistrationState(
+                0, 0, 1, 1, 0, false, null, cellIdentity, false, 0, 0, 0);
+        sst.mPollingContext[0] = 2;
+        sst.sendMessage(sst.obtainMessage(ServiceStateTracker.EVENT_POLL_STATE_GPRS,
+                new AsyncResult(sst.mPollingContext, dataResult, null)));
+        waitForMs(200);
+        assertEquals(ServiceState.STATE_IN_SERVICE, mSST.getCurrentDataConnectionState());
+
+        sst.sendMessage(sst.obtainMessage(ServiceStateTracker.EVENT_POLL_STATE_REGISTRATION,
+                new AsyncResult(sst.mPollingContext, voiceResult, null)));
+        waitForMs(200);
+        assertEquals(ServiceState.RIL_RADIO_TECHNOLOGY_GPRS, sst.mSS.getRilVoiceRadioTechnology());
+    }
+
+    // Edge, GPRS and UMTS are grouped under the same family where Edge > GPRS > UMTS  .
+    // Expect no rat update from E to G immediately following cell id change.
+    // Expect ratratchet (from G to UMTS) for the following rat update within the cell location.
+    @Test
+    public void testRatRatchetWithCellChangeBeforeRatChange() throws Exception {
+        // cell ID update
+        CellIdentityGsm cellIdentity = new CellIdentityGsm(-1, -1, -1, 5, -1, -1);
+        NetworkRegistrationState dataResult = new NetworkRegistrationState(
+                0, 0, 1, 2, 0, false, null, cellIdentity, 1);
+        sst.mPollingContext[0] = 2;
+        // update data reg state to be in service
+        sst.sendMessage(sst.obtainMessage(ServiceStateTracker.EVENT_POLL_STATE_GPRS,
+                new AsyncResult(sst.mPollingContext, dataResult, null)));
+        waitForMs(200);
+        assertEquals(ServiceState.STATE_IN_SERVICE, mSST.getCurrentDataConnectionState());
+
+        NetworkRegistrationState voiceResult = new NetworkRegistrationState(
+                0, 0, 1, 2, 0, false, null, cellIdentity, false, 0, 0, 0);
+        sst.sendMessage(sst.obtainMessage(ServiceStateTracker.EVENT_POLL_STATE_REGISTRATION,
+                new AsyncResult(sst.mPollingContext, voiceResult, null)));
+        waitForMs(200);
+        assertEquals(ServiceState.RIL_RADIO_TECHNOLOGY_EDGE, sst.mSS.getRilVoiceRadioTechnology());
+
+        // RAT: EDGE -> GPRS, cell ID unchanged. Expect no rat ratchet following cell Id change.
+        voiceResult = new NetworkRegistrationState(
+                0, 0, 1, 1, 0, false, null, cellIdentity, false, 0, 0, 0);
+        sst.mPollingContext[0] = 2;
+        sst.sendMessage(sst.obtainMessage(ServiceStateTracker.EVENT_POLL_STATE_GPRS,
+                new AsyncResult(sst.mPollingContext, dataResult, null)));
+        waitForMs(200);
+        assertEquals(ServiceState.STATE_IN_SERVICE, mSST.getCurrentDataConnectionState());
+
+        sst.sendMessage(sst.obtainMessage(ServiceStateTracker.EVENT_POLL_STATE_REGISTRATION,
+                new AsyncResult(sst.mPollingContext, voiceResult, null)));
+        waitForMs(200);
+        assertEquals(ServiceState.RIL_RADIO_TECHNOLOGY_GPRS, sst.mSS.getRilVoiceRadioTechnology());
+
+        // RAT: GPRS -> UMTS
+        voiceResult = new NetworkRegistrationState(
+                0, 0, 1, 3, 0, false, null, cellIdentity, false, 0, 0, 0);
+        sst.mPollingContext[0] = 2;
+        sst.sendMessage(sst.obtainMessage(ServiceStateTracker.EVENT_POLL_STATE_GPRS,
+                new AsyncResult(sst.mPollingContext, dataResult, null)));
+        waitForMs(200);
+        assertEquals(ServiceState.STATE_IN_SERVICE, mSST.getCurrentDataConnectionState());
+
+        sst.sendMessage(sst.obtainMessage(ServiceStateTracker.EVENT_POLL_STATE_REGISTRATION,
+                new AsyncResult(sst.mPollingContext, voiceResult, null)));
+        waitForMs(200);
+        assertEquals(ServiceState.RIL_RADIO_TECHNOLOGY_GPRS, sst.mSS.getRilVoiceRadioTechnology());
+    }
+
+    private void sendPhyChanConfigChange(int[] bandwidths) {
+        ArrayList<PhysicalChannelConfig> pc = new ArrayList<>();
+        int ssType = PhysicalChannelConfig.CONNECTION_PRIMARY_SERVING;
+        for (int bw : bandwidths) {
+            pc.add(new PhysicalChannelConfig(ssType, bw));
+
+            // All cells after the first are secondary serving cells.
+            ssType = PhysicalChannelConfig.CONNECTION_SECONDARY_SERVING;
+        }
+        sst.sendMessage(sst.obtainMessage(ServiceStateTracker.EVENT_PHYSICAL_CHANNEL_CONFIG,
+                new AsyncResult(null, pc, null)));
+        waitForMs(100);
+    }
+
+    private void sendRegStateUpdateForLteCellId(CellIdentityLte cellId) {
+        NetworkRegistrationState dataResult = new NetworkRegistrationState(
+                2, 1, 1, TelephonyManager.NETWORK_TYPE_LTE, 0, false, null, cellId, 1);
+        NetworkRegistrationState voiceResult = new NetworkRegistrationState(
+                1, 1, 1, TelephonyManager.NETWORK_TYPE_LTE, 0, false, null, cellId,
+                false, 0, 0, 0);
+        sst.mPollingContext[0] = 2;
+        // update data reg state to be in service
+        sst.sendMessage(sst.obtainMessage(ServiceStateTracker.EVENT_POLL_STATE_GPRS,
+                new AsyncResult(sst.mPollingContext, dataResult, null)));
+        waitForMs(200);
+        sst.sendMessage(sst.obtainMessage(ServiceStateTracker.EVENT_POLL_STATE_REGISTRATION,
+                new AsyncResult(sst.mPollingContext, voiceResult, null)));
+        waitForMs(200);
+    }
+
+    @Test
+    public void testPhyChanBandwidthUpdatedOnDataRegState() throws Exception {
+        // Cell ID change should trigger hasLocationChanged.
+        CellIdentityLte cellIdentity5 =
+                new CellIdentityLte(1, 1, 5, 1, 5000, "001", "01", "test", "tst");
+
+        sendPhyChanConfigChange(new int[] {10000});
+        sendRegStateUpdateForLteCellId(cellIdentity5);
+        assertTrue(Arrays.equals(new int[] {5000}, sst.mSS.getCellBandwidths()));
+    }
+
+    @Test
+    public void testPhyChanBandwidthNotUpdatedWhenInvalidInCellIdentity() throws Exception {
+        // Cell ID change should trigger hasLocationChanged.
+        CellIdentityLte cellIdentityInv =
+                new CellIdentityLte(1, 1, 5, 1, 12345, "001", "01", "test", "tst");
+
+        sendPhyChanConfigChange(new int[] {10000});
+        sendRegStateUpdateForLteCellId(cellIdentityInv);
+        assertTrue(Arrays.equals(new int[] {10000}, sst.mSS.getCellBandwidths()));
+    }
+
+    @Test
+    public void testPhyChanBandwidthPrefersCarrierAggregationReport() throws Exception {
+        // Cell ID change should trigger hasLocationChanged.
+        CellIdentityLte cellIdentity10 =
+                new CellIdentityLte(1, 1, 5, 1, 10000, "001", "01", "test", "tst");
+
+        sendPhyChanConfigChange(new int[] {10000, 5000});
+        sendRegStateUpdateForLteCellId(cellIdentity10);
+        assertTrue(Arrays.equals(new int[] {10000, 5000}, sst.mSS.getCellBandwidths()));
+    }
+
+    @Test
+    public void testPhyChanBandwidthRatchetedOnPhyChanBandwidth() throws Exception {
+        // LTE Cell with bandwidth = 10000
+        CellIdentityLte cellIdentity10 =
+                new CellIdentityLte(1, 1, 1, 1, 10000, "1", "1", "test", "tst");
+
+        sendRegStateUpdateForLteCellId(cellIdentity10);
+        assertTrue(Arrays.equals(new int[] {10000}, sst.mSS.getCellBandwidths()));
+        sendPhyChanConfigChange(new int[] {10000, 5000});
+        assertTrue(Arrays.equals(new int[] {10000, 5000}, sst.mSS.getCellBandwidths()));
+    }
+
+    @Test
+    public void testPhyChanBandwidthResetsOnOos() throws Exception {
+        testPhyChanBandwidthRatchetedOnPhyChanBandwidth();
+        NetworkRegistrationState dataResult = new NetworkRegistrationState(
+                2, 1, 0, TelephonyManager.NETWORK_TYPE_UNKNOWN, 0, false, null, null, 1);
+        NetworkRegistrationState voiceResult = new NetworkRegistrationState(
+                1, 1, 0, TelephonyManager.NETWORK_TYPE_UNKNOWN, 0, false, null, null,
+                false, 0, 0, 0);
+        sst.mPollingContext[0] = 2;
+        sst.sendMessage(sst.obtainMessage(ServiceStateTracker.EVENT_POLL_STATE_GPRS,
+                new AsyncResult(sst.mPollingContext, dataResult, null)));
+        waitForMs(200);
+        sst.sendMessage(sst.obtainMessage(ServiceStateTracker.EVENT_POLL_STATE_REGISTRATION,
+                new AsyncResult(sst.mPollingContext, voiceResult, null)));
+        waitForMs(200);
+        assertTrue(Arrays.equals(new int[0], sst.mSS.getCellBandwidths()));
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SubscriptionControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/SubscriptionControllerTest.java
index ef9f7d4..a237010 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SubscriptionControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SubscriptionControllerTest.java
@@ -65,7 +65,9 @@
                 SubscriptionManager.NAME_SOURCE, SubscriptionManager.COLOR,
                 SubscriptionManager.NUMBER, SubscriptionManager.DISPLAY_NUMBER_FORMAT,
                 SubscriptionManager.DATA_ROAMING, SubscriptionManager.MCC,
-                SubscriptionManager.MNC, SubscriptionManager.CB_EXTREME_THREAT_ALERT,
+                SubscriptionManager.MNC, SubscriptionManager.MCC_STRING,
+                SubscriptionManager.MNC_STRING,
+                SubscriptionManager.CB_EXTREME_THREAT_ALERT,
                 SubscriptionManager.CB_SEVERE_THREAT_ALERT, SubscriptionManager.CB_AMBER_ALERT,
                 SubscriptionManager.CB_ALERT_SOUND_DURATION,
                 SubscriptionManager.CB_ALERT_REMINDER_INTERVAL,
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoTest.java b/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoTest.java
index ac97937..6770e9a 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoTest.java
@@ -15,15 +15,15 @@
  */
 package com.android.internal.telephony;
 
-import android.telephony.SubscriptionManager;
+import static org.junit.Assert.assertEquals;
+
+import android.telephony.SubscriptionInfo;
 import android.test.suitebuilder.annotation.SmallTest;
-import static org.junit.Assert.*;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
-import android.telephony.SubscriptionInfo;
-
 public class SubscriptionInfoTest {
     private SubscriptionInfo mSubscriptionInfoUT;
 
@@ -35,7 +35,7 @@
     @Before
     public void setUp() throws Exception {
         mSubscriptionInfoUT = new SubscriptionInfo(1, "890126042XXXXXXXXXXX", 0, "T-mobile",
-                "T-mobile", 0, 255, "12345", 0, null, 310, 260, "156");
+                "T-mobile", 0, 255, "12345", 0, null, "310", "260", "156");
     }
 
     @Test
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoUpdaterTest.java b/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoUpdaterTest.java
index 9efed51..33782b2 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoUpdaterTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoUpdaterTest.java
@@ -52,7 +52,6 @@
 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.UiccProfile;
 
 import org.junit.After;
 import org.junit.Before;
@@ -167,14 +166,10 @@
     @SmallTest
     public void testSimAbsent() throws Exception {
         doReturn(Arrays.asList(mSubInfo)).when(mSubscriptionController)
-                .getSubInfoUsingSlotIndexWithCheck(eq(FAKE_SUB_ID_1), anyBoolean(), anyString());
+                .getSubInfoUsingSlotIndexPrivileged(eq(FAKE_SUB_ID_1), anyBoolean());
         doReturn(new int[]{FAKE_SUB_ID_1}).when(mSubscriptionController).getActiveSubIdList();
-        Intent mIntent = new Intent(UiccProfile.ACTION_INTERNAL_SIM_STATE_CHANGED);
-        mIntent.putExtra(IccCardConstants.INTENT_KEY_ICC_STATE,
-                IccCardConstants.INTENT_VALUE_ICC_ABSENT);
-        mIntent.putExtra(PhoneConstants.PHONE_KEY, FAKE_SUB_ID_1);
-
-        mContext.sendBroadcast(mIntent);
+        mUpdater.updateInternalIccState(
+                IccCardConstants.INTENT_VALUE_ICC_ABSENT, null, FAKE_SUB_ID_1);
 
         waitForMs(100);
         verify(mSubscriptionContent).put(eq(SubscriptionManager.SIM_SLOT_INDEX),
@@ -191,12 +186,8 @@
     @Test
     @SmallTest
     public void testSimUnknown() throws Exception {
-        Intent mIntent = new Intent(UiccProfile.ACTION_INTERNAL_SIM_STATE_CHANGED);
-        mIntent.putExtra(IccCardConstants.INTENT_KEY_ICC_STATE,
-                IccCardConstants.INTENT_VALUE_ICC_UNKNOWN);
-        mIntent.putExtra(PhoneConstants.PHONE_KEY, FAKE_SUB_ID_1);
-
-        mContext.sendBroadcast(mIntent);
+        mUpdater.updateInternalIccState(
+                IccCardConstants.INTENT_VALUE_ICC_UNKNOWN, null, FAKE_SUB_ID_1);
 
         waitForMs(100);
         verify(mSubscriptionContent, times(0)).put(anyString(), any());
@@ -211,17 +202,14 @@
     @Test
     @SmallTest
     public void testSimError() throws Exception {
-        Intent mIntent = new Intent(UiccProfile.ACTION_INTERNAL_SIM_STATE_CHANGED);
-        mIntent.putExtra(IccCardConstants.INTENT_KEY_ICC_STATE,
-                IccCardConstants.INTENT_VALUE_ICC_CARD_IO_ERROR);
-        mIntent.putExtra(PhoneConstants.PHONE_KEY, 0);
+        mUpdater.updateInternalIccState(
+                IccCardConstants.INTENT_VALUE_ICC_CARD_IO_ERROR, null, FAKE_SUB_ID_1);
 
-        mContext.sendBroadcast(mIntent);
         waitForMs(100);
         verify(mSubscriptionContent, times(0)).put(anyString(), any());
         CarrierConfigManager mConfigManager = (CarrierConfigManager)
                 mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
-        verify(mConfigManager).updateConfigForPhoneId(eq(0),
+        verify(mConfigManager).updateConfigForPhoneId(eq(FAKE_SUB_ID_1),
                 eq(IccCardConstants.INTENT_VALUE_ICC_CARD_IO_ERROR));
         verify(mSubscriptionController, times(0)).clearSubInfo();
         verify(mSubscriptionController, times(1)).notifySubscriptionInfoChanged();
@@ -230,12 +218,9 @@
     @Test
     @SmallTest
     public void testWrongSimState() throws Exception {
-        Intent mIntent = new Intent(UiccProfile.ACTION_INTERNAL_SIM_STATE_CHANGED);
-        mIntent.putExtra(IccCardConstants.INTENT_KEY_ICC_STATE,
-                IccCardConstants.INTENT_VALUE_ICC_IMSI);
-        mIntent.putExtra(PhoneConstants.PHONE_KEY, 2);
+        mUpdater.updateInternalIccState(
+                IccCardConstants.INTENT_VALUE_ICC_IMSI, null, 2);
 
-        mContext.sendBroadcast(mIntent);
         waitForMs(100);
         verify(mSubscriptionContent, times(0)).put(anyString(), any());
         CarrierConfigManager mConfigManager = (CarrierConfigManager)
@@ -251,16 +236,13 @@
     public void testSimLoaded() throws Exception {
         /* mock new sim got loaded and there is no sim loaded before */
         doReturn(null).when(mSubscriptionController)
-                .getSubInfoUsingSlotIndexWithCheck(eq(FAKE_SUB_ID_1), anyBoolean(), anyString());
+                .getSubInfoUsingSlotIndexPrivileged(eq(FAKE_SUB_ID_1), anyBoolean());
         doReturn("89012604200000000000").when(mIccRecord).getFullIccId();
         doReturn(FAKE_MCC_MNC_1).when(mTelephonyManager).getSimOperatorNumeric(FAKE_SUB_ID_1);
-        Intent intentInternalSimStateChanged =
-                new Intent(UiccProfile.ACTION_INTERNAL_SIM_STATE_CHANGED);
-        intentInternalSimStateChanged.putExtra(IccCardConstants.INTENT_KEY_ICC_STATE,
-                IccCardConstants.INTENT_VALUE_ICC_LOADED);
-        intentInternalSimStateChanged.putExtra(PhoneConstants.PHONE_KEY, FAKE_SUB_ID_1);
 
-        mContext.sendBroadcast(intentInternalSimStateChanged);
+        mUpdater.updateInternalIccState(
+                IccCardConstants.INTENT_VALUE_ICC_LOADED, null, FAKE_SUB_ID_1);
+
         waitForMs(100);
 
         // verify SIM_STATE_CHANGED broadcast. It should be broadcast twice, once for
@@ -319,16 +301,13 @@
     public void testSimLoadedEmptyOperatorNumeric() throws Exception {
         /* mock new sim got loaded and there is no sim loaded before */
         doReturn(null).when(mSubscriptionController)
-                .getSubInfoUsingSlotIndexWithCheck(eq(FAKE_SUB_ID_1), anyBoolean(), anyString());
+                .getSubInfoUsingSlotIndexPrivileged(eq(FAKE_SUB_ID_1), anyBoolean());
         doReturn("89012604200000000000").when(mIccRecord).getFullIccId();
         // operator numeric is empty
         doReturn("").when(mTelephonyManager).getSimOperatorNumeric(FAKE_SUB_ID_1);
-        Intent mIntent = new Intent(UiccProfile.ACTION_INTERNAL_SIM_STATE_CHANGED);
-        mIntent.putExtra(IccCardConstants.INTENT_KEY_ICC_STATE,
-                IccCardConstants.INTENT_VALUE_ICC_LOADED);
-        mIntent.putExtra(PhoneConstants.PHONE_KEY, FAKE_SUB_ID_1);
+        mUpdater.updateInternalIccState(
+                IccCardConstants.INTENT_VALUE_ICC_LOADED, null, FAKE_SUB_ID_1);
 
-        mContext.sendBroadcast(mIntent);
         waitForMs(100);
         SubscriptionManager mSubscriptionManager = SubscriptionManager.from(mContext);
         verify(mTelephonyManager).getSimOperatorNumeric(FAKE_SUB_ID_1);
@@ -351,15 +330,11 @@
         doReturn("98106240020000000000").when(mIccRecord).getFullIccId();
 
         doReturn(Arrays.asList(mSubInfo)).when(mSubscriptionController)
-                .getSubInfoUsingSlotIndexWithCheck(eq(FAKE_SUB_ID_1), anyBoolean(), anyString());
+                .getSubInfoUsingSlotIndexPrivileged(eq(FAKE_SUB_ID_1), anyBoolean());
 
-        Intent mIntent = new Intent(UiccProfile.ACTION_INTERNAL_SIM_STATE_CHANGED);
-        mIntent.putExtra(IccCardConstants.INTENT_KEY_ICC_STATE,
-                IccCardConstants.INTENT_VALUE_ICC_LOCKED);
-        mIntent.putExtra(IccCardConstants.INTENT_KEY_LOCKED_REASON, "TESTING");
-        mIntent.putExtra(PhoneConstants.PHONE_KEY, FAKE_SUB_ID_1);
+        mUpdater.updateInternalIccState(
+                IccCardConstants.INTENT_VALUE_ICC_LOCKED, "TESTING", FAKE_SUB_ID_1);
 
-        mContext.sendBroadcast(mIntent);
         waitForMs(100);
 
         /* old IccId != new queried IccId */
@@ -404,16 +379,14 @@
         doReturn(FAKE_MCC_MNC_2).when(mTelephonyManager).getSimOperatorNumeric(eq(FAKE_SUB_ID_2));
         // Mock there is no sim inserted before
         doReturn(null).when(mSubscriptionController)
-                .getSubInfoUsingSlotIndexWithCheck(anyInt(), anyBoolean(), anyString());
+                .getSubInfoUsingSlotIndexPrivileged(anyInt(), anyBoolean());
         verify(mSubscriptionController, times(0)).clearSubInfo();
         doReturn("89012604200000000000").when(mIccRecord).getFullIccId();
 
         // Mock sending a sim loaded for SIM 1
-        Intent mIntent = new Intent(UiccProfile.ACTION_INTERNAL_SIM_STATE_CHANGED);
-        mIntent.putExtra(IccCardConstants.INTENT_KEY_ICC_STATE,
-                IccCardConstants.INTENT_VALUE_ICC_LOADED);
-        mIntent.putExtra(PhoneConstants.PHONE_KEY, FAKE_SUB_ID_1);
-        mContext.sendBroadcast(mIntent);
+        mUpdater.updateInternalIccState(
+                IccCardConstants.INTENT_VALUE_ICC_LOADED, null, FAKE_SUB_ID_1);
+
         waitForMs(100);
 
         SubscriptionManager mSubscriptionManager = SubscriptionManager.from(mContext);
@@ -423,11 +396,10 @@
 
         // Mock sending a sim loaded for SIM 2
         doReturn("89012604200000000001").when(mIccRecord).getFullIccId();
-        mIntent = new Intent(UiccProfile.ACTION_INTERNAL_SIM_STATE_CHANGED);
-        mIntent.putExtra(IccCardConstants.INTENT_KEY_ICC_STATE,
-                IccCardConstants.INTENT_VALUE_ICC_LOADED);
-        mIntent.putExtra(PhoneConstants.PHONE_KEY, FAKE_SUB_ID_2);
-        mContext.sendBroadcast(mIntent);
+
+        mUpdater.updateInternalIccState(
+                IccCardConstants.INTENT_VALUE_ICC_LOADED, null, FAKE_SUB_ID_2);
+
         waitForMs(100);
 
         verify(mSubscriptionManager, times(1)).addSubscriptionInfoRecord(eq("89012604200000000000"),
@@ -448,13 +420,9 @@
         replaceInstance(SubscriptionInfoUpdater.class, "mIccId", null,
                 new String[]{"89012604200000000000"});
 
-        Intent mIntent = new Intent(UiccProfile.ACTION_INTERNAL_SIM_STATE_CHANGED);
-        mIntent.putExtra(IccCardConstants.INTENT_KEY_ICC_STATE,
-                IccCardConstants.INTENT_VALUE_ICC_LOCKED);
-        mIntent.putExtra(IccCardConstants.INTENT_KEY_LOCKED_REASON, "TESTING");
-        mIntent.putExtra(PhoneConstants.PHONE_KEY, FAKE_SUB_ID_1);
+        mUpdater.updateInternalIccState(
+                IccCardConstants.INTENT_VALUE_ICC_LOCKED, "TESTING", FAKE_SUB_ID_1);
 
-        mContext.sendBroadcast(mIntent);
         waitForMs(100);
 
         SubscriptionManager mSubscriptionManager = SubscriptionManager.from(mContext);
@@ -485,11 +453,11 @@
         List<SubscriptionInfo> subInfoList = new ArrayList<>();
         // 1: not embedded, but has matching iccid with an embedded subscription.
         subInfoList.add(new SubscriptionInfo(
-                        0, "1", 0, "", "", 0, 0, "", 0, null, 0, 0, "", false /* isEmbedded */,
+                        0, "1", 0, "", "", 0, 0, "", 0, null, "0", "0", "", false /* isEmbedded */,
                         null /* accessRules */));
         // 2: embedded but no longer present.
         subInfoList.add(new SubscriptionInfo(
-                0, "2", 0, "", "", 0, 0, "", 0, null, 0, 0, "", true /* isEmbedded */,
+                0, "2", 0, "", "", 0, 0, "", 0, null, "0", "0", "", true /* isEmbedded */,
                 null /* accessRules */));
 
         when(mSubscriptionController.getSubscriptionInfoListForEmbeddedSubscriptionUpdate(
@@ -536,11 +504,11 @@
         List<SubscriptionInfo> subInfoList = new ArrayList<>();
         // 1: not embedded, but has matching iccid with an embedded subscription.
         subInfoList.add(new SubscriptionInfo(
-                0, "1", 0, "", "", 0, 0, "", 0, null, 0, 0, "", false /* isEmbedded */,
+                0, "1", 0, "", "", 0, 0, "", 0, null, "0", "0", "", false /* isEmbedded */,
                 null /* accessRules */));
         // 2: embedded.
         subInfoList.add(new SubscriptionInfo(
-                0, "2", 0, "", "", 0, 0, "", 0, null, 0, 0, "", true /* isEmbedded */,
+                0, "2", 0, "", "", 0, 0, "", 0, null, "0", "0", "", true /* isEmbedded */,
                 null /* accessRules */));
 
         when(mSubscriptionController.getSubscriptionInfoListForEmbeddedSubscriptionUpdate(
@@ -578,7 +546,7 @@
         List<SubscriptionInfo> subInfoList = new ArrayList<>();
         // 1: not embedded.
         subInfoList.add(new SubscriptionInfo(
-                0, "1", 0, "", "", 0, 0, "", 0, null, 0, 0, "", false /* isEmbedded */,
+                0, "1", 0, "", "", 0, 0, "", 0, null, "0", "0", "", false /* isEmbedded */,
                 null /* accessRules */));
 
         when(mSubscriptionController.getSubscriptionInfoListForEmbeddedSubscriptionUpdate(
@@ -598,16 +566,14 @@
     @SmallTest
     public void testHexIccIdSuffix() throws Exception {
         doReturn(null).when(mSubscriptionController)
-                .getSubInfoUsingSlotIndexWithCheck(anyInt(), anyBoolean(), anyString());
+                .getSubInfoUsingSlotIndexPrivileged(anyInt(), anyBoolean());
         verify(mSubscriptionController, times(0)).clearSubInfo();
         doReturn("890126042000000000Ff").when(mIccRecord).getFullIccId();
 
         // Mock sending a sim loaded for SIM 1
-        Intent mIntent = new Intent(UiccProfile.ACTION_INTERNAL_SIM_STATE_CHANGED);
-        mIntent.putExtra(IccCardConstants.INTENT_KEY_ICC_STATE,
-                IccCardConstants.INTENT_VALUE_ICC_LOADED);
-        mIntent.putExtra(PhoneConstants.PHONE_KEY, FAKE_SUB_ID_1);
-        mContext.sendBroadcast(mIntent);
+        mUpdater.updateInternalIccState(
+                IccCardConstants.INTENT_VALUE_ICC_LOADED, "TESTING", FAKE_SUB_ID_1);
+
         waitForMs(100);
 
         SubscriptionManager mSubscriptionManager = SubscriptionManager.from(mContext);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/TelephonyPermissionsTest.java b/tests/telephonytests/src/com/android/internal/telephony/TelephonyPermissionsTest.java
index 7c2057f..3a0d375 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/TelephonyPermissionsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/TelephonyPermissionsTest.java
@@ -27,6 +27,7 @@
 
 import android.app.AppOpsManager;
 import android.content.Context;
+import android.telephony.TelephonyManager;
 import android.test.suitebuilder.annotation.SmallTest;
 
 import org.junit.Before;
@@ -37,6 +38,7 @@
 @SmallTest
 public class TelephonyPermissionsTest {
 
+    private static final int SUB_ID = 55555;
     private static final int PID = 12345;
     private static final int UID = 54321;
     private static final String PACKAGE = "com.example";
@@ -46,9 +48,11 @@
     private Context mMockContext;
     @Mock
     private AppOpsManager mMockAppOps;
+    @Mock
+    private ITelephony mMockTelephony;
 
     @Before
-    public void setUp() {
+    public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         when(mMockContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mMockAppOps);
 
@@ -59,12 +63,15 @@
                 .enforcePermission(anyString(), eq(PID), eq(UID), eq(MSG));
         when(mMockAppOps.noteOp(anyInt(), eq(UID), eq(PACKAGE)))
                 .thenReturn(AppOpsManager.MODE_ERRORED);
+        when(mMockTelephony.getCarrierPrivilegeStatusForUid(eq(SUB_ID), eq(UID)))
+                .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
     }
 
     @Test
     public void testCheckReadPhoneState_noPermissions() {
         try {
-            TelephonyPermissions.checkReadPhoneState(mMockContext, PID, UID, PACKAGE, MSG);
+            TelephonyPermissions.checkReadPhoneState(
+                    mMockContext, () -> mMockTelephony, SUB_ID, PID, UID, PACKAGE, MSG);
             fail("Should have thrown SecurityException");
         } catch (SecurityException e) {
             // expected
@@ -75,7 +82,8 @@
     public void testCheckReadPhoneState_hasPrivilegedPermission() {
         doNothing().when(mMockContext).enforcePermission(
                 android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, PID, UID, MSG);
-        assertTrue(TelephonyPermissions.checkReadPhoneState(mMockContext, PID, UID, PACKAGE, MSG));
+        assertTrue(TelephonyPermissions.checkReadPhoneState(
+                mMockContext, () -> mMockTelephony, SUB_ID, PID, UID, PACKAGE, MSG));
     }
 
     @Test
@@ -84,20 +92,31 @@
                 android.Manifest.permission.READ_PHONE_STATE, PID, UID, MSG);
         when(mMockAppOps.noteOp(AppOpsManager.OP_READ_PHONE_STATE, UID, PACKAGE))
                 .thenReturn(AppOpsManager.MODE_ALLOWED);
-        assertTrue(TelephonyPermissions.checkReadPhoneState(mMockContext, PID, UID, PACKAGE, MSG));
+        assertTrue(TelephonyPermissions.checkReadPhoneState(
+                mMockContext, () -> mMockTelephony, SUB_ID, PID, UID, PACKAGE, MSG));
     }
 
     @Test
     public void testCheckReadPhoneState_hasPermissionWithoutAppOp() {
         doNothing().when(mMockContext).enforcePermission(
                 android.Manifest.permission.READ_PHONE_STATE, PID, UID, MSG);
-        assertFalse(TelephonyPermissions.checkReadPhoneState(mMockContext, PID, UID, PACKAGE, MSG));
+        assertFalse(TelephonyPermissions.checkReadPhoneState(
+                mMockContext, () -> mMockTelephony, SUB_ID, PID, UID, PACKAGE, MSG));
+    }
+
+    @Test
+    public void testCheckReadPhoneState_hasCarrierPrivileges() throws Exception {
+        when(mMockTelephony.getCarrierPrivilegeStatusForUid(eq(SUB_ID), eq(UID)))
+                .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS);
+        assertTrue(TelephonyPermissions.checkReadPhoneState(
+                mMockContext, () -> mMockTelephony, SUB_ID, PID, UID, PACKAGE, MSG));
     }
 
     @Test
     public void testCheckReadPhoneNumber_noPermissions() {
         try {
-            TelephonyPermissions.checkReadPhoneNumber(mMockContext, PID, UID, PACKAGE, MSG);
+            TelephonyPermissions.checkReadPhoneNumber(
+                    mMockContext, () -> mMockTelephony, SUB_ID, PID, UID, PACKAGE, MSG);
             fail("Should have thrown SecurityException");
         } catch (SecurityException e) {
             // expected
@@ -108,14 +127,16 @@
     public void testCheckReadPhoneNumber_defaultSmsApp() {
         when(mMockAppOps.noteOp(AppOpsManager.OP_WRITE_SMS, UID, PACKAGE))
                 .thenReturn(AppOpsManager.MODE_ALLOWED);
-        assertTrue(TelephonyPermissions.checkReadPhoneNumber(mMockContext, PID, UID, PACKAGE, MSG));
+        assertTrue(TelephonyPermissions.checkReadPhoneNumber(
+                mMockContext, () -> mMockTelephony, SUB_ID, PID, UID, PACKAGE, MSG));
     }
 
     @Test
     public void testCheckReadPhoneNumber_hasPrivilegedPhoneStatePermission() {
         doNothing().when(mMockContext).enforcePermission(
                 android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, PID, UID, MSG);
-        assertTrue(TelephonyPermissions.checkReadPhoneNumber(mMockContext, PID, UID, PACKAGE, MSG));
+        assertTrue(TelephonyPermissions.checkReadPhoneNumber(
+                mMockContext, () -> mMockTelephony, SUB_ID, PID, UID, PACKAGE, MSG));
     }
 
     @Test
@@ -124,7 +145,8 @@
                 android.Manifest.permission.READ_SMS, PID, UID, MSG);
         when(mMockAppOps.noteOp(AppOpsManager.OP_READ_SMS, UID, PACKAGE))
                 .thenReturn(AppOpsManager.MODE_ALLOWED);
-        assertTrue(TelephonyPermissions.checkReadPhoneNumber(mMockContext, PID, UID, PACKAGE, MSG));
+        assertTrue(TelephonyPermissions.checkReadPhoneNumber(
+                mMockContext, () -> mMockTelephony, SUB_ID, PID, UID, PACKAGE, MSG));
     }
 
     @Test
@@ -133,6 +155,7 @@
                 android.Manifest.permission.READ_PHONE_NUMBERS, PID, UID, MSG);
         when(mMockAppOps.noteOp(AppOpsManager.OP_READ_PHONE_NUMBERS, UID, PACKAGE))
                 .thenReturn(AppOpsManager.MODE_ALLOWED);
-        assertTrue(TelephonyPermissions.checkReadPhoneNumber(mMockContext, PID, UID, PACKAGE, MSG));
+        assertTrue(TelephonyPermissions.checkReadPhoneNumber(
+                mMockContext, () -> mMockTelephony, SUB_ID, PID, UID, PACKAGE, MSG));
     }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java b/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
index 0f75a94..89d8143 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
@@ -39,6 +39,7 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.IDeviceIdleController;
+import android.os.Looper;
 import android.os.RegistrantList;
 import android.os.ServiceManager;
 import android.provider.BlockedNumberContract;
@@ -64,7 +65,6 @@
 import com.android.internal.telephony.imsphone.ImsPhoneCallTracker;
 import com.android.internal.telephony.test.SimulatedCommands;
 import com.android.internal.telephony.test.SimulatedCommandsVerifier;
-import com.android.internal.telephony.uicc.IccCardProxy;
 import com.android.internal.telephony.uicc.IccCardStatus;
 import com.android.internal.telephony.uicc.IccRecords;
 import com.android.internal.telephony.uicc.IsimUiccRecords;
@@ -109,8 +109,6 @@
     @Mock
     protected UiccController mUiccController;
     @Mock
-    protected IccCardProxy mIccCardProxy;
-    @Mock
     protected UiccProfile mUiccProfile;
     @Mock
     protected CallManager mCallManager;
@@ -196,6 +194,10 @@
     protected NitzStateMachine mNitzStateMachine;
     @Mock
     protected RadioConfig mMockRadioConfig;
+    @Mock
+    protected SubscriptionInfoUpdater mSubInfoRecordUpdater;
+    @Mock
+    protected LocaleTracker mLocaleTracker;
 
     protected ImsCallProfile mImsCallProfile;
     protected TelephonyManager mTelephonyManager;
@@ -328,12 +330,10 @@
         doReturn(mSST).when(mTelephonyComponentFactory)
                 .makeServiceStateTracker(nullable(GsmCdmaPhone.class),
                         nullable(CommandsInterface.class));
-        doReturn(mIccCardProxy).when(mTelephonyComponentFactory)
-                .makeIccCardProxy(nullable(Context.class), nullable(CommandsInterface.class),
-                        anyInt());
         doReturn(mUiccProfile).when(mTelephonyComponentFactory)
                 .makeUiccProfile(nullable(Context.class), nullable(CommandsInterface.class),
-                        nullable(IccCardStatus.class), anyInt(), nullable(UiccCard.class));
+                        nullable(IccCardStatus.class), anyInt(), nullable(UiccCard.class),
+                        nullable(Object.class));
         doReturn(mCT).when(mTelephonyComponentFactory)
                 .makeGsmCdmaCallTracker(nullable(GsmCdmaPhone.class));
         doReturn(mIccPhoneBookIntManager).when(mTelephonyComponentFactory)
@@ -372,6 +372,8 @@
                 .makeDeviceStateMonitor(nullable(Phone.class));
         doReturn(mNitzStateMachine).when(mTelephonyComponentFactory)
                 .makeNitzStateMachine(nullable(GsmCdmaPhone.class));
+        doReturn(mLocaleTracker).when(mTelephonyComponentFactory)
+                .makeLocaleTracker(nullable(Phone.class), nullable(Looper.class));
 
         //mPhone
         doReturn(mContext).when(mPhone).getContext();
@@ -419,7 +421,7 @@
         doReturn(mRuimRecords).when(mUiccCardApplication3gpp2).getIccRecords();
         doReturn(mIsimUiccRecords).when(mUiccCardApplicationIms).getIccRecords();
 
-        //mIccCardProxy
+        //mUiccProfile
         doReturn(mSimRecords).when(mUiccProfile).getIccRecords();
         doAnswer(new Answer<IccRecords>() {
             public IccRecords answer(InvocationOnMock invocation) {
@@ -494,6 +496,7 @@
         replaceInstance(PhoneFactory.class, "sMadeDefaults", null, true);
         replaceInstance(PhoneFactory.class, "sPhone", null, mPhone);
         replaceInstance(PhoneFactory.class, "sPhones", null, mPhones);
+        replaceInstance(PhoneFactory.class, "sSubInfoRecordUpdater", null, mSubInfoRecordUpdater);
         replaceInstance(RadioConfig.class, "sRadioConfig", null, mMockRadioConfig);
 
         setReady(false);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/ApnContextTest.java b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/ApnContextTest.java
index 8c49c21..6722691 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/ApnContextTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/ApnContextTest.java
@@ -16,9 +16,19 @@
 
 package com.android.internal.telephony.dataconnection;
 
-import android.net.NetworkCapabilities;
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
 import android.net.NetworkConfig;
 import android.net.NetworkRequest;
+import android.telephony.data.ApnSetting;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.LocalLog;
 
@@ -32,16 +42,6 @@
 import org.junit.Test;
 import org.mockito.Mock;
 
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertNull;
-import static junit.framework.Assert.assertTrue;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
 public class ApnContextTest extends TelephonyTest {
 
     @Mock
@@ -143,14 +143,14 @@
         NetworkRequest nr = new NetworkRequest.Builder().build();
         mApnContext.requestNetwork(nr, log);
 
-        verify(mDcTracker, times(1)).setEnabled(eq(DctConstants.APN_DEFAULT_ID), eq(true));
+        verify(mDcTracker, times(1)).setEnabled(eq(ApnSetting.TYPE_DEFAULT), eq(true));
         mApnContext.requestNetwork(nr, log);
-        verify(mDcTracker, times(1)).setEnabled(eq(DctConstants.APN_DEFAULT_ID), eq(true));
+        verify(mDcTracker, times(1)).setEnabled(eq(ApnSetting.TYPE_DEFAULT), eq(true));
 
         mApnContext.releaseNetwork(nr, log);
-        verify(mDcTracker, times(1)).setEnabled(eq(DctConstants.APN_DEFAULT_ID), eq(false));
+        verify(mDcTracker, times(1)).setEnabled(eq(ApnSetting.TYPE_DEFAULT), eq(false));
         mApnContext.releaseNetwork(nr, log);
-        verify(mDcTracker, times(1)).setEnabled(eq(DctConstants.APN_DEFAULT_ID), eq(false));
+        verify(mDcTracker, times(1)).setEnabled(eq(ApnSetting.TYPE_DEFAULT), eq(false));
     }
 
     @Test
@@ -176,32 +176,31 @@
     public void testProvisionApn() throws Exception {
         mContextFixture.putResource(R.string.mobile_provisioning_apn, "fake_apn");
 
-        ApnSetting myApn = new ApnSetting(
+        ApnSetting myApn = ApnSetting.makeApnSetting(
                 2163,                   // id
                 "44010",                // numeric
                 "sp-mode",              // name
                 "fake_apn",             // apn
-                "",                     // proxy
-                "",                     // port
-                "",                     // mmsc
-                "",                     // mmsproxy
-                "",                     // mmsport
+                null,                     // proxy
+                -1,                     // port
+                null,                     // mmsc
+                null,                     // mmsproxy
+                -1,                     // mmsport
                 "",                     // user
                 "",                     // password
                 -1,                     // authtype
-                new String[]{"default", "supl"},     // types
-                "IP",                   // protocol
-                "IP",                   // roaming_protocol
+                ApnSetting.TYPE_DEFAULT | ApnSetting.TYPE_SUPL,     // types
+                ApnSetting.PROTOCOL_IP,                   // protocol
+                ApnSetting.PROTOCOL_IP,                   // roaming_protocol
                 true,                   // carrier_enabled
-                0,                      // bearer
-                0,                      // bearer_bitmask
+                0,                      // networktype_bismask
                 0,                      // profile_id
                 false,                  // modem_cognitive
                 0,                      // max_conns
                 0,                      // wait_time
                 0,                      // max_conns_time
                 0,                      // mtu
-                "",                     // mvno_type
+                -1,                     // mvno_type
                 "");                    // mnvo_match_data
 
         mApnContext.setApnSetting(myApn);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/ApnSettingTest.java b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/ApnSettingTest.java
index 748ddf9..b9738e5 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/ApnSettingTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/ApnSettingTest.java
@@ -16,13 +16,6 @@
 
 package com.android.internal.telephony.dataconnection;
 
-import static com.android.internal.telephony.PhoneConstants.APN_TYPE_ALL;
-import static com.android.internal.telephony.PhoneConstants.APN_TYPE_DEFAULT;
-import static com.android.internal.telephony.PhoneConstants.APN_TYPE_HIPRI;
-import static com.android.internal.telephony.PhoneConstants.APN_TYPE_IA;
-import static com.android.internal.telephony.PhoneConstants.APN_TYPE_MMS;
-import static com.android.internal.telephony.PhoneConstants.APN_TYPE_SUPL;
-
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 import static junit.framework.Assert.fail;
@@ -30,9 +23,11 @@
 import static org.junit.Assert.assertEquals;
 import static org.mockito.Mockito.doReturn;
 
+import android.net.Uri;
 import android.os.PersistableBundle;
 import android.telephony.CarrierConfigManager;
 import android.telephony.ServiceState;
+import android.telephony.data.ApnSetting;
 import android.test.suitebuilder.annotation.SmallTest;
 
 import com.android.internal.telephony.PhoneConstants;
@@ -44,6 +39,7 @@
 
 import java.lang.reflect.Field;
 import java.lang.reflect.Modifier;
+import java.net.InetAddress;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -62,41 +58,40 @@
         super.tearDown();
     }
 
-    static ApnSetting createApnSetting(String[] apnTypes) {
-        return createApnSettingInternal(apnTypes, true);
+    static ApnSetting createApnSetting(int apnTypesBitmask) {
+        return createApnSettingInternal(apnTypesBitmask, true);
     }
 
-    private static ApnSetting createDisabledApnSetting(String[] apnTypes) {
-        return createApnSettingInternal(apnTypes, false);
+    private static ApnSetting createDisabledApnSetting(int apnTypesBitmask) {
+        return createApnSettingInternal(apnTypesBitmask, false);
     }
 
-    private static ApnSetting createApnSettingInternal(String[] apnTypes, boolean carrierEnabled) {
-        return new ApnSetting(
+    private static ApnSetting createApnSettingInternal(int apnTypeBitmask, boolean carrierEnabled) {
+        return ApnSetting.makeApnSetting(
                 2163,                   // id
                 "44010",                // numeric
                 "sp-mode",              // name
                 "spmode.ne.jp",         // apn
-                "",                     // proxy
-                "",                     // port
-                "",                     // mmsc
-                "",                     // mmsproxy
-                "",                     // mmsport
+                null,                     // proxy
+                -1,                     // port
+                null,                     // mmsc
+                null,                     // mmsproxy
+                -1,                     // mmsport
                 "",                     // user
                 "",                     // password
                 -1,                     // authtype
-                apnTypes,               // types
-                "IP",                   // protocol
-                "IP",                   // roaming_protocol
+                apnTypeBitmask,               // types
+                ApnSetting.PROTOCOL_IP,                   // protocol
+                ApnSetting.PROTOCOL_IP,                   // roaming_protocol
                 carrierEnabled,         // carrier_enabled
-                0,                      // bearer
-                0,                      // bearer_bitmask
+                0,                      // networktype_bitmask
                 0,                      // profile_id
                 false,                  // modem_cognitive
                 0,                      // max_conns
                 0,                      // wait_time
                 0,                      // max_conns_time
                 0,                      // mtu
-                "",                     // mvno_type
+                -1,                     // mvno_type
                 "");                    // mnvo_match_data
     }
 
@@ -108,90 +103,84 @@
     }
 
     private static void assertApnSettingEqual(ApnSetting a1, ApnSetting a2) {
-        assertEquals(a1.carrier, a2.carrier);
-        assertEquals(a1.apn, a2.apn);
-        assertEquals(a1.proxy, a2.proxy);
-        assertEquals(a1.port, a2.port);
-        assertEquals(a1.mmsc, a2.mmsc);
-        assertEquals(a1.mmsProxy, a2.mmsProxy);
-        assertEquals(a1.mmsPort, a2.mmsPort);
-        assertEquals(a1.user, a2.user);
-        assertEquals(a1.password, a2.password);
-        assertEquals(a1.authType, a2.authType);
-        assertEquals(a1.id, a2.id);
-        assertEquals(a1.numeric, a2.numeric);
-        assertEquals(a1.protocol, a2.protocol);
-        assertEquals(a1.roamingProtocol, a2.roamingProtocol);
-        assertEquals(a1.types.length, a2.types.length);
-        int i;
-        for (i = 0; i < a1.types.length; i++) {
-            assertEquals(a1.types[i], a2.types[i]);
-        }
-        assertEquals(a1.carrierEnabled, a2.carrierEnabled);
-        assertEquals(a1.bearerBitmask, a2.bearerBitmask);
-        assertEquals(a1.profileId, a2.profileId);
-        assertEquals(a1.modemCognitive, a2.modemCognitive);
-        assertEquals(a1.maxConns, a2.maxConns);
-        assertEquals(a1.waitTime, a2.waitTime);
-        assertEquals(a1.maxConnsTime, a2.maxConnsTime);
-        assertEquals(a1.mtu, a2.mtu);
-        assertEquals(a1.mvnoType, a2.mvnoType);
-        assertEquals(a1.mvnoMatchData, a2.mvnoMatchData);
-        assertEquals(a1.networkTypeBitmask, a2.networkTypeBitmask);
+        assertEquals(a1.getEntryName(), a2.getEntryName());
+        assertEquals(a1.getApnName(), a2.getApnName());
+        assertEquals(a1.getProxyAddressAsString(), a2.getProxyAddressAsString());
+        assertEquals(a1.getProxyPort(), a2.getProxyPort());
+        assertEquals(a1.getMmsc(), a2.getMmsc());
+        assertEquals(a1.getMmsProxyAddressAsString(), a2.getMmsProxyAddressAsString());
+        assertEquals(a1.getMmsProxyPort(), a2.getMmsProxyPort());
+        assertEquals(a1.getUser(), a2.getUser());
+        assertEquals(a1.getPassword(), a2.getPassword());
+        assertEquals(a1.getAuthType(), a2.getAuthType());
+        assertEquals(a1.getId(), a2.getId());
+        assertEquals(a1.getOperatorNumeric(), a2.getOperatorNumeric());
+        assertEquals(a1.getProtocol(), a2.getProtocol());
+        assertEquals(a1.getRoamingProtocol(), a2.getRoamingProtocol());
+        assertEquals(a1.getApnTypeBitmask(), a2.getApnTypeBitmask());
+        assertEquals(a1.isEnabled(), a2.isEnabled());
+        assertEquals(a1.getProfileId(), a2.getProfileId());
+        assertEquals(a1.getModemCognitive(), a2.getModemCognitive());
+        assertEquals(a1.getMaxConns(), a2.getMaxConns());
+        assertEquals(a1.getWaitTime(), a2.getWaitTime());
+        assertEquals(a1.getMaxConnsTime(), a2.getMaxConnsTime());
+        assertEquals(a1.getMtu(), a2.getMtu());
+        assertEquals(a1.getMvnoType(), a2.getMvnoType());
+        assertEquals(a1.getMvnoMatchData(), a2.getMvnoMatchData());
+        assertEquals(a1.getNetworkTypeBitmask(), a2.getNetworkTypeBitmask());
+        assertEquals(a1.getApnSetId(), a2.getApnSetId());
     }
 
     @Test
     @SmallTest
     public void testFromString() throws Exception {
-        String[] dunTypes = {"DUN"};
-        String[] mmsTypes = {"mms", "*"};
+        final int dunTypesBitmask = ApnSetting.TYPE_DUN;
+        final int mmsTypesBitmask = ApnSetting.TYPE_MMS | ApnSetting.TYPE_ALL;
 
         ApnSetting expectedApn;
         String testString;
 
         // A real-world v1 example string.
         testString = "Vodafone IT,web.omnitel.it,,,,,,,,,222,10,,DUN";
-        expectedApn = new ApnSetting(
-                -1, "22210", "Vodafone IT", "web.omnitel.it", "", "",
-                "", "", "", "", "", 0, dunTypes, "IP", "IP", true, 0, 0,
-                0, false, 0, 0, 0, 0, "", "");
+        expectedApn = ApnSetting.makeApnSetting(
+                -1, "22210", "Vodafone IT", "web.omnitel.it", "", -1, null, "", -1, "", "", 0,
+                dunTypesBitmask, ApnSetting.PROTOCOL_IP, ApnSetting.PROTOCOL_IP, true,
+                0, 0, false, 0, 0, 0, 0, -1, "");
         assertApnSettingEqual(expectedApn, ApnSetting.fromString(testString));
 
         // A v2 string.
         testString = "[ApnSettingV2] Name,apn,,,,,,,,,123,45,,mms|*,IPV6,IP,true,14";
-        expectedApn = new ApnSetting(
-                -1, "12345", "Name", "apn", "", "",
-                "", "", "", "", "", 0, mmsTypes, "IPV6", "IP", true, 14, 0,
-                0, false, 0, 0, 0, 0, "", "");
+        int networkTypeBitmask = 1 << (13 - 1);
+        expectedApn = ApnSetting.makeApnSetting(
+                -1, "12345", "Name", "apn", "", -1, null, "", -1, "", "", 0,
+                mmsTypesBitmask, ApnSetting.PROTOCOL_IPV6, ApnSetting.PROTOCOL_IP, true,
+                networkTypeBitmask, 0, false, 0, 0, 0, 0, -1, "");
         assertApnSettingEqual(expectedApn, ApnSetting.fromString(testString));
 
         // A v2 string with spaces.
         testString = "[ApnSettingV2] Name,apn, ,,,,,,,,123,45,,mms|*,IPV6, IP,true,14";
-        expectedApn = new ApnSetting(
-                -1, "12345", "Name", "apn", "", "",
-                "", "", "", "", "", 0, mmsTypes, "IPV6", "IP", true, 14, 0,
-                0, false, 0, 0, 0, 0, "", "");
-        assertApnSettingEqual(expectedApn, ApnSetting.fromString(testString));
-        int networkTypeBitmask = 1 << (13 - 1);
-        expectedApn = new ApnSetting(
-                -1, "12345", "Name", "apn", "", "", "", "", "", "", "", 0, mmsTypes, "IPV6",
-                "IP", true, networkTypeBitmask, 0, false, 0, 0, 0, 0, "", "");
+        expectedApn = ApnSetting.makeApnSetting(
+                -1, "12345", "Name", "apn", "", -1, null, "", -1, "", "", 0,
+                mmsTypesBitmask, ApnSetting.PROTOCOL_IPV6, ApnSetting.PROTOCOL_IP, true,
+                networkTypeBitmask, 0, false, 0, 0, 0, 0, -1, "");
         assertApnSettingEqual(expectedApn, ApnSetting.fromString(testString));
 
         // A v3 string.
         testString = "[ApnSettingV3] Name,apn,,,,,,,,,123,45,,mms|*,IPV6,IP,true,14,,,,,,,spn,testspn";
-        expectedApn = new ApnSetting(
-                -1, "12345", "Name", "apn", "", "", "", "", "", "", "", 0, mmsTypes, "IPV6",
-                "IP", true, 14, 0, 0, false, 0, 0, 0, 0, "spn", "testspn");
+        expectedApn = ApnSetting.makeApnSetting(
+                -1, "12345", "Name", "apn", "", -1, null, "", -1, "", "", 0,
+                mmsTypesBitmask, ApnSetting.PROTOCOL_IPV6, ApnSetting.PROTOCOL_IP, true,
+                networkTypeBitmask, 0, false, 0, 0, 0, 0, ApnSetting.MVNO_TYPE_SPN, "testspn");
         assertApnSettingEqual(expectedApn, ApnSetting.fromString(testString));
 
         // A v4 string with network type bitmask.
         testString =
                 "[ApnSettingV4] Name,apn,,,,,,,,,123,45,,mms|*,IPV6,IP,true,0,,,,,,,spn,testspn,6";
         networkTypeBitmask = 1 << (6 - 1);
-        expectedApn = new ApnSetting(
-                -1, "12345", "Name", "apn", "", "", "", "", "", "", "", 0, mmsTypes, "IPV6",
-                "IP", true, networkTypeBitmask, 0, false, 0, 0, 0, 0, "spn", "testspn");
+        expectedApn = ApnSetting.makeApnSetting(
+                -1, "12345", "Name", "apn", "", -1, null, "", -1, "", "", 0,
+                mmsTypesBitmask, ApnSetting.PROTOCOL_IPV6, ApnSetting.PROTOCOL_IP, true,
+                networkTypeBitmask, 0, false, 0, 0, 0, 0, ApnSetting.MVNO_TYPE_SPN, "testspn");
         assertApnSettingEqual(expectedApn, ApnSetting.fromString(testString));
 
         testString =
@@ -199,35 +188,48 @@
                         + "4|5|6|7|8|12|13|14|19";
         // The value was calculated by adding "4|5|6|7|8|12|13|14|19".
         networkTypeBitmask = 276728;
-        expectedApn = new ApnSetting(
-                -1, "12345", "Name", "apn", "", "", "", "", "", "", "", 0, mmsTypes, "IPV6",
-                "IP", true, networkTypeBitmask, 0, false, 0, 0, 0, 0, "spn", "testspn");
+        expectedApn = ApnSetting.makeApnSetting(
+                -1, "12345", "Name", "apn", "", -1, null, "", -1, "", "", 0,
+                mmsTypesBitmask, ApnSetting.PROTOCOL_IPV6, ApnSetting.PROTOCOL_IP, true,
+                networkTypeBitmask, 0, false, 0, 0, 0, 0, ApnSetting.MVNO_TYPE_SPN, "testspn");
         assertApnSettingEqual(expectedApn, ApnSetting.fromString(testString));
 
         // A v4 string with network type bitmask and compatible bearer bitmask.
         testString =
                 "[ApnSettingV4] Name,apn,,,,,,,,,123,45,,mms|*,IPV6,IP,true,8,,,,,,,spn,testspn, 6";
         networkTypeBitmask = 1 << (6 - 1);
-        int bearerBitmask = 1 << (8 - 1);
-        expectedApn = new ApnSetting(
-                -1, "12345", "Name", "apn", "", "", "", "", "", "", "", 0, mmsTypes, "IPV6",
-                "IP", true, 0, bearerBitmask, 0, false, 0, 0, 0, 0, "spn",
-                "testspn");
-        assertApnSettingEqual(expectedApn, ApnSetting.fromString(testString));
-        expectedApn = new ApnSetting(
-                -1, "12345", "Name", "apn", "", "", "", "", "", "", "", 0, mmsTypes, "IPV6",
-                "IP", true, networkTypeBitmask, 0, false, 0, 0, 0, 0, "spn",
-                "testspn");
+        expectedApn = ApnSetting.makeApnSetting(
+                -1, "12345", "Name", "apn", "", -1, null, "", -1, "", "", 0,
+                mmsTypesBitmask, ApnSetting.PROTOCOL_IPV6, ApnSetting.PROTOCOL_IP, true,
+                networkTypeBitmask, 0, false, 0, 0, 0, 0, ApnSetting.MVNO_TYPE_SPN, "testspn");
         assertApnSettingEqual(expectedApn, ApnSetting.fromString(testString));
 
         // A v4 string with network type bitmask and incompatible bearer bitmask.
         testString =
                 "[ApnSettingV4] Name,apn,,,,,,,,,123,45,,mms|*,IPV6,IP,true,9,,,,,,,spn,testspn, 6";
-        bearerBitmask = 1 << (8 - 1);
-        expectedApn = new ApnSetting(
-                -1, "12345", "Name", "apn", "", "", "", "", "", "", "", 0, mmsTypes, "IPV6",
-                "IP", true, 0, bearerBitmask, 0, false, 0, 0, 0, 0, "spn",
-                "testspn");
+        expectedApn = ApnSetting.makeApnSetting(
+                -1, "12345", "Name", "apn", "", -1, null, "", -1, "", "", 0,
+                mmsTypesBitmask, ApnSetting.PROTOCOL_IPV6, ApnSetting.PROTOCOL_IP, true,
+                networkTypeBitmask, 0, false, 0,
+                0, 0, 0, ApnSetting.MVNO_TYPE_SPN, "testspn");
+        assertApnSettingEqual(expectedApn, ApnSetting.fromString(testString));
+
+        // A v5 string with apnSetId=0
+        testString =
+                "[ApnSettingV5] Name,apn,,,,,,,,,123,45,,mms|*,IPV6,IP,true,0,,,,,,,spn,testspn,0,0";
+        expectedApn = ApnSetting.makeApnSetting(
+                -1, "12345", "Name", "apn", "", -1, null, "", -1, "", "", 0,
+                mmsTypesBitmask, ApnSetting.PROTOCOL_IPV6, ApnSetting.PROTOCOL_IP, true,
+                0, 0, false, 0, 0, 0, 0, ApnSetting.MVNO_TYPE_SPN, "testspn");
+        assertApnSettingEqual(expectedApn, ApnSetting.fromString(testString));
+
+        // A v5 string with apnSetId=3
+        testString =
+                "[ApnSettingV5] Name,apn,,,,,,,,,123,45,,mms|*,IPV6,IP,true,0,,,,,,,spn,testspn,0,3";
+        expectedApn = ApnSetting.makeApnSetting(
+                -1, "12345", "Name", "apn", "", -1, null, "", -1, "", "", 0,
+                mmsTypesBitmask, ApnSetting.PROTOCOL_IPV6, ApnSetting.PROTOCOL_IP, true,
+                0, 0, false, 0, 0, 0, 0, ApnSetting.MVNO_TYPE_SPN, "testspn", 3);
         assertApnSettingEqual(expectedApn, ApnSetting.fromString(testString));
 
         // Return no apn if insufficient fields given.
@@ -241,6 +243,7 @@
     @Test
     @SmallTest
     public void testArrayFromString() throws Exception {
+        final int mmsTypesBitmask = ApnSetting.TYPE_MMS;
         // Test a multiple v3 string.
         String testString =
                 "[ApnSettingV3] Name,apn,,,,,,,,,123,45,,mms,IPV6,IP,true,14,,,,,,,spn,testspn";
@@ -248,30 +251,51 @@
                 " ;[ApnSettingV3] Name1,apn1,,,,,,,,,123,46,,mms,IPV6,IP,true,12,,,,,,,gid,testGid";
         testString +=
                 " ;[ApnSettingV3] Name1,apn2,,,,,,,,,123,46,,mms,IPV6,IP,true,12,,,,,,,,";
+        testString +=
+                " ;[ApnSettingV5] Name1,apn2,,,,,,,,,123,46,,mms,IPV6,IP,true,0,,,,,,,,,,3";
         List<ApnSetting> expectedApns = new ArrayList<ApnSetting>();
-        expectedApns.add(new ApnSetting(
-                -1, "12345", "Name", "apn", "", "", "", "", "", "", "", 0, new String[]{"mms"}, "IPV6",
-                "IP", true, 14, 0, 0, false, 0, 0, 0, 0, "spn", "testspn"));
-        expectedApns.add(new ApnSetting(
-                -1, "12346", "Name1", "apn1", "", "", "", "", "", "", "", 0, new String[]{"mms"}, "IPV6",
-                "IP", true, 12, 0, 0, false, 0, 0, 0, 0, "gid", "testGid"));
-        expectedApns.add(new ApnSetting(
-                -1, "12346", "Name1", "apn2", "", "", "", "", "", "", "", 0, new String[]{"mms"}, "IPV6",
-                "IP", true, 12, 0, 0, false, 0, 0, 0, 0, "", ""));
+        expectedApns.add(ApnSetting.makeApnSetting(
+                -1, "12345", "Name", "apn", "", -1, null, "", -1, "", "", 0,
+                mmsTypesBitmask, ApnSetting.PROTOCOL_IPV6, ApnSetting.PROTOCOL_IP, true,
+                1 << (13 - 1), 0, false, 0, 0, 0, 0, ApnSetting.MVNO_TYPE_SPN, "testspn"));
+        expectedApns.add(ApnSetting.makeApnSetting(
+                -1, "12346", "Name1", "apn1", "", -1, null, "", -1, "", "", 0,
+                mmsTypesBitmask, ApnSetting.PROTOCOL_IPV6, ApnSetting.PROTOCOL_IP, true,
+                1 << (12 - 1), 0, false, 0, 0, 0, 0, ApnSetting.MVNO_TYPE_GID, "testGid"));
+        expectedApns.add(ApnSetting.makeApnSetting(
+                -1, "12346", "Name1", "apn2", "", -1, null, "", -1, "", "", 0,
+                mmsTypesBitmask, ApnSetting.PROTOCOL_IPV6, ApnSetting.PROTOCOL_IP, true,
+                1 << (12 - 1), 0, false, 0, 0, 0, 0, -1, ""));
+        expectedApns.add(ApnSetting.makeApnSetting(
+                -1, "12346", "Name1", "apn2", "", -1, null, "", -1, "", "", 0,
+                mmsTypesBitmask, ApnSetting.PROTOCOL_IPV6, ApnSetting.PROTOCOL_IP, true,
+                0, 0, false, 0, 0, 0, 0, -1, "", 3));
         assertApnSettingsEqual(expectedApns, ApnSetting.arrayFromString(testString));
     }
 
     @Test
     @SmallTest
     public void testToString() throws Exception {
-        String[] types = {"default", "*"};
-        ApnSetting apn = new ApnSetting(
-                99, "12345", "Name", "apn", "proxy", "port",
-                "mmsc", "mmsproxy", "mmsport", "user", "password", 0,
-                types, "IPV6", "IP", true, 14, 0, 0, false, 0, 0, 0, 0, "", "");
-        String expected = "[ApnSettingV4] Name, 99, 12345, apn, proxy, "
-                + "mmsc, mmsproxy, mmsport, port, 0, default | *, "
-                + "IPV6, IP, true, 14, 8192, 0, false, 0, 0, 0, 0, , , false, 4096";
+        // Use default apn_set_id constructor.
+        ApnSetting apn = ApnSetting.makeApnSetting(
+                99, "12345", "Name", "apn", null, 10,
+                null, null, -1, "user", "password", 0,
+                ApnSetting.TYPE_DEFAULT, ApnSetting.PROTOCOL_IPV6, ApnSetting.PROTOCOL_IP, true,
+                4096, 0, false, 0, 0, 0, 0, ApnSetting.MVNO_TYPE_SPN, "");
+        String expected = "[ApnSettingV5] Name, 99, 12345, apn, null, "
+                + "null, null, null, 10, 0, default, "
+                + "IPV6, IP, true, 0, false, 0, 0, 0, 0, spn, , false, 4096, 0";
+        assertEquals(expected, apn.toString());
+
+        final int networkTypeBitmask = 1 << (14 - 1);
+        apn = ApnSetting.makeApnSetting(
+                99, "12345", "Name", "apn", null, 10,
+                null, null, -1, "user", "password", 0,
+                ApnSetting.TYPE_DEFAULT, ApnSetting.PROTOCOL_IPV6, ApnSetting.PROTOCOL_IP, true,
+                networkTypeBitmask, 0, false, 0, 0, 0, 0, ApnSetting.MVNO_TYPE_SPN, "", 3);
+        expected = "[ApnSettingV5] Name, 99, 12345, apn, null, "
+                + "null, null, null, 10, 0, default, "
+                + "IPV6, IP, true, 0, false, 0, 0, 0, 0, spn, , false, 8192, 3";
         assertEquals(expected, apn.toString());
     }
 
@@ -283,50 +307,43 @@
 
         doReturn(false).when(mServiceState).getDataRoaming();
         doReturn(1).when(mPhone).getSubId();
-        assertTrue(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_DEFAULT}).isMetered(mPhone));
 
-        assertTrue(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_DEFAULT, PhoneConstants.APN_TYPE_MMS}).
-                isMetered(mPhone));
+        assertTrue(ApnSettingUtils.isMetered(createApnSetting(ApnSetting.TYPE_DEFAULT), mPhone));
 
-        assertTrue(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_MMS}).isMetered(mPhone));
+        assertTrue(ApnSettingUtils.isMetered(
+                createApnSetting(ApnSetting.TYPE_DEFAULT | ApnSetting.TYPE_MMS), mPhone));
 
-        assertTrue(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_MMS, PhoneConstants.APN_TYPE_SUPL}).
-                isMetered(mPhone));
+        assertTrue(ApnSettingUtils.isMetered(createApnSetting(ApnSetting.TYPE_MMS), mPhone));
 
-        assertTrue(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_DEFAULT, PhoneConstants.APN_TYPE_DUN}).
-                isMetered(mPhone));
+        assertTrue(ApnSettingUtils.isMetered(
+                createApnSetting(ApnSetting.TYPE_SUPL | ApnSetting.TYPE_MMS), mPhone));
 
-        assertTrue(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_ALL}).isMetered(mPhone));
+        assertTrue(ApnSettingUtils.isMetered(
+                createApnSetting(ApnSetting.TYPE_DEFAULT | ApnSetting.TYPE_DUN), mPhone));
 
-        assertFalse(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_FOTA, PhoneConstants.APN_TYPE_SUPL}).
-                isMetered(mPhone));
+        assertTrue(ApnSettingUtils.isMetered(createApnSetting(ApnSetting.TYPE_ALL), mPhone));
 
-        assertFalse(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_IA, PhoneConstants.APN_TYPE_CBS}).
-                isMetered(mPhone));
+        assertFalse(ApnSettingUtils.isMetered(
+                createApnSetting(ApnSetting.TYPE_SUPL | ApnSetting.TYPE_FOTA), mPhone));
 
-        assertTrue(ApnSetting.isMeteredApnType(PhoneConstants.APN_TYPE_DEFAULT, mPhone));
-        assertTrue(ApnSetting.isMeteredApnType(PhoneConstants.APN_TYPE_MMS, mPhone));
-        assertFalse(ApnSetting.isMeteredApnType(PhoneConstants.APN_TYPE_SUPL, mPhone));
-        assertFalse(ApnSetting.isMeteredApnType(PhoneConstants.APN_TYPE_CBS, mPhone));
-        assertFalse(ApnSetting.isMeteredApnType(PhoneConstants.APN_TYPE_DUN, mPhone));
-        assertFalse(ApnSetting.isMeteredApnType(PhoneConstants.APN_TYPE_FOTA, mPhone));
-        assertFalse(ApnSetting.isMeteredApnType(PhoneConstants.APN_TYPE_IA, mPhone));
-        assertFalse(ApnSetting.isMeteredApnType(PhoneConstants.APN_TYPE_HIPRI, mPhone));
+        assertFalse(ApnSettingUtils.isMetered(
+                createApnSetting(ApnSetting.TYPE_IA | ApnSetting.TYPE_CBS), mPhone));
+
+        assertTrue(ApnSettingUtils.isMeteredApnType(PhoneConstants.APN_TYPE_DEFAULT, mPhone));
+        assertTrue(ApnSettingUtils.isMeteredApnType(PhoneConstants.APN_TYPE_MMS, mPhone));
+        assertFalse(ApnSettingUtils.isMeteredApnType(PhoneConstants.APN_TYPE_SUPL, mPhone));
+        assertFalse(ApnSettingUtils.isMeteredApnType(PhoneConstants.APN_TYPE_CBS, mPhone));
+        assertFalse(ApnSettingUtils.isMeteredApnType(PhoneConstants.APN_TYPE_DUN, mPhone));
+        assertFalse(ApnSettingUtils.isMeteredApnType(PhoneConstants.APN_TYPE_FOTA, mPhone));
+        assertFalse(ApnSettingUtils.isMeteredApnType(PhoneConstants.APN_TYPE_IA, mPhone));
+        assertFalse(ApnSettingUtils.isMeteredApnType(PhoneConstants.APN_TYPE_HIPRI, mPhone));
 
         // Carrier config settings changes.
         mBundle.putStringArray(CarrierConfigManager.KEY_CARRIER_METERED_APN_TYPES_STRINGS,
                 new String[]{PhoneConstants.APN_TYPE_DEFAULT});
 
-        assertTrue(ApnSetting.isMeteredApnType(PhoneConstants.APN_TYPE_DEFAULT, mPhone));
-        assertFalse(ApnSetting.isMeteredApnType(PhoneConstants.APN_TYPE_MMS, mPhone));
+        assertTrue(ApnSettingUtils.isMeteredApnType(PhoneConstants.APN_TYPE_DEFAULT, mPhone));
+        assertFalse(ApnSettingUtils.isMeteredApnType(PhoneConstants.APN_TYPE_MMS, mPhone));
     }
 
     @Test
@@ -337,42 +354,34 @@
         doReturn(true).when(mServiceState).getDataRoaming();
         doReturn(1).when(mPhone).getSubId();
 
-        assertTrue(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_DEFAULT}).isMetered(mPhone));
+        assertTrue(ApnSettingUtils.isMetered(createApnSetting(ApnSetting.TYPE_DEFAULT), mPhone));
 
-        assertTrue(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_DEFAULT, PhoneConstants.APN_TYPE_MMS}).
-                isMetered(mPhone));
+        assertTrue(ApnSettingUtils.isMetered(
+                createApnSetting(ApnSetting.TYPE_DEFAULT | ApnSetting.TYPE_MMS), mPhone));
 
-        assertTrue(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_MMS}).isMetered(mPhone));
+        assertTrue(ApnSettingUtils.isMetered(createApnSetting(ApnSetting.TYPE_MMS), mPhone));
 
-        assertTrue(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_MMS, PhoneConstants.APN_TYPE_SUPL}).
-                isMetered(mPhone));
+        assertTrue(ApnSettingUtils.isMetered(
+                createApnSetting(ApnSetting.TYPE_SUPL | ApnSetting.TYPE_MMS), mPhone));
 
-        assertTrue(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_DEFAULT, PhoneConstants.APN_TYPE_DUN}).
-                isMetered(mPhone));
+        assertTrue(ApnSettingUtils.isMetered(
+                createApnSetting(ApnSetting.TYPE_DEFAULT | ApnSetting.TYPE_DUN), mPhone));
 
-        assertTrue(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_ALL}).isMetered(mPhone));
+        assertTrue(ApnSettingUtils.isMetered(createApnSetting(ApnSetting.TYPE_ALL), mPhone));
 
-        assertFalse(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_FOTA, PhoneConstants.APN_TYPE_SUPL}).
-                isMetered(mPhone));
+        assertFalse(ApnSettingUtils.isMetered(
+                createApnSetting(ApnSetting.TYPE_SUPL | ApnSetting.TYPE_FOTA), mPhone));
 
-        assertFalse(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_IA, PhoneConstants.APN_TYPE_CBS}).
-                isMetered(mPhone));
+        assertFalse(ApnSettingUtils.isMetered(
+                createApnSetting(ApnSetting.TYPE_IA | ApnSetting.TYPE_CBS), mPhone));
 
         // Carrier config settings changes.
         mBundle.putStringArray(CarrierConfigManager.KEY_CARRIER_METERED_ROAMING_APN_TYPES_STRINGS,
                 new String[]{PhoneConstants.APN_TYPE_FOTA});
 
-        assertFalse(ApnSetting.isMeteredApnType(PhoneConstants.APN_TYPE_DEFAULT, mPhone));
-        assertFalse(ApnSetting.isMeteredApnType(PhoneConstants.APN_TYPE_MMS, mPhone));
-        assertTrue(ApnSetting.isMeteredApnType(PhoneConstants.APN_TYPE_FOTA, mPhone));
+        assertFalse(ApnSettingUtils.isMeteredApnType(PhoneConstants.APN_TYPE_DEFAULT, mPhone));
+        assertFalse(ApnSettingUtils.isMeteredApnType(PhoneConstants.APN_TYPE_MMS, mPhone));
+        assertTrue(ApnSettingUtils.isMeteredApnType(PhoneConstants.APN_TYPE_FOTA, mPhone));
     }
 
     @Test
@@ -385,42 +394,34 @@
                 .getRilDataRadioTechnology();
         doReturn(1).when(mPhone).getSubId();
 
-        assertTrue(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_DEFAULT}).isMetered(mPhone));
+        assertTrue(ApnSettingUtils.isMetered(createApnSetting(ApnSetting.TYPE_DEFAULT), mPhone));
 
-        assertTrue(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_DEFAULT, PhoneConstants.APN_TYPE_MMS})
-                .isMetered(mPhone));
+        assertTrue(ApnSettingUtils.isMetered(
+                createApnSetting(ApnSetting.TYPE_DEFAULT | ApnSetting.TYPE_MMS), mPhone));
 
-        assertTrue(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_MMS}).isMetered(mPhone));
+        assertTrue(ApnSettingUtils.isMetered(createApnSetting(ApnSetting.TYPE_MMS), mPhone));
 
-        assertTrue(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_MMS, PhoneConstants.APN_TYPE_SUPL})
-                .isMetered(mPhone));
+        assertTrue(ApnSettingUtils.isMetered(
+                createApnSetting(ApnSetting.TYPE_SUPL | ApnSetting.TYPE_MMS), mPhone));
 
-        assertTrue(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_DEFAULT, PhoneConstants.APN_TYPE_DUN})
-                .isMetered(mPhone));
+        assertTrue(ApnSettingUtils.isMetered(
+                createApnSetting(ApnSetting.TYPE_DEFAULT | ApnSetting.TYPE_DUN), mPhone));
 
-        assertTrue(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_ALL}).isMetered(mPhone));
+        assertTrue(ApnSettingUtils.isMetered(createApnSetting(ApnSetting.TYPE_ALL), mPhone));
 
-        assertFalse(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_FOTA, PhoneConstants.APN_TYPE_SUPL})
-                .isMetered(mPhone));
+        assertFalse(ApnSettingUtils.isMetered(
+                createApnSetting(ApnSetting.TYPE_SUPL | ApnSetting.TYPE_FOTA), mPhone));
 
-        assertFalse(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_IA, PhoneConstants.APN_TYPE_CBS})
-                .isMetered(mPhone));
+        assertFalse(ApnSettingUtils.isMetered(
+                createApnSetting(ApnSetting.TYPE_IA | ApnSetting.TYPE_CBS), mPhone));
 
         // Carrier config settings changes.
         mBundle.putStringArray(CarrierConfigManager.KEY_CARRIER_METERED_IWLAN_APN_TYPES_STRINGS,
                 new String[]{PhoneConstants.APN_TYPE_FOTA});
 
-        assertFalse(ApnSetting.isMeteredApnType(PhoneConstants.APN_TYPE_DEFAULT, mPhone));
-        assertFalse(ApnSetting.isMeteredApnType(PhoneConstants.APN_TYPE_MMS, mPhone));
-        assertTrue(ApnSetting.isMeteredApnType(PhoneConstants.APN_TYPE_FOTA, mPhone));
+        assertFalse(ApnSettingUtils.isMeteredApnType(PhoneConstants.APN_TYPE_DEFAULT, mPhone));
+        assertFalse(ApnSettingUtils.isMeteredApnType(PhoneConstants.APN_TYPE_MMS, mPhone));
+        assertTrue(ApnSettingUtils.isMeteredApnType(PhoneConstants.APN_TYPE_FOTA, mPhone));
     }
 
     @Test
@@ -431,33 +432,26 @@
 
         doReturn(false).when(mServiceState).getDataRoaming();
         doReturn(1).when(mPhone).getSubId();
-        assertTrue(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_SUPL, PhoneConstants.APN_TYPE_CBS}).
-                isMetered(mPhone));
 
-        assertTrue(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_SUPL}).isMetered(mPhone));
+        assertTrue(ApnSettingUtils.isMetered(
+                createApnSetting(ApnSetting.TYPE_SUPL | ApnSetting.TYPE_CBS), mPhone));
 
-        assertTrue(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_CBS}).isMetered(mPhone));
+        assertTrue(ApnSettingUtils.isMetered(createApnSetting(ApnSetting.TYPE_SUPL), mPhone));
 
-        assertTrue(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_FOTA, PhoneConstants.APN_TYPE_CBS}).
-                isMetered(mPhone));
+        assertTrue(ApnSettingUtils.isMetered(createApnSetting(ApnSetting.TYPE_CBS), mPhone));
 
-        assertTrue(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_SUPL, PhoneConstants.APN_TYPE_IA}).
-                isMetered(mPhone));
+        assertTrue(ApnSettingUtils.isMetered(
+                createApnSetting(ApnSetting.TYPE_FOTA | ApnSetting.TYPE_CBS), mPhone));
 
-        assertTrue(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_ALL}).isMetered(mPhone));
+        assertTrue(ApnSettingUtils.isMetered(
+                createApnSetting(ApnSetting.TYPE_SUPL | ApnSetting.TYPE_IA), mPhone));
 
-        assertFalse(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_DEFAULT, PhoneConstants.APN_TYPE_IMS}).
-                isMetered(mPhone));
+        assertTrue(ApnSettingUtils.isMetered(createApnSetting(ApnSetting.TYPE_ALL), mPhone));
 
-        assertFalse(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_IMS}).isMetered(mPhone));
+        assertFalse(ApnSettingUtils.isMetered(
+                createApnSetting(ApnSetting.TYPE_DEFAULT | ApnSetting.TYPE_IMS), mPhone));
+
+        assertFalse(ApnSettingUtils.isMetered(createApnSetting(ApnSetting.TYPE_IMS), mPhone));
     }
 
     @Test
@@ -467,42 +461,35 @@
                 new String[]{PhoneConstants.APN_TYPE_SUPL, PhoneConstants.APN_TYPE_CBS});
         doReturn(true).when(mServiceState).getDataRoaming();
         doReturn(2).when(mPhone).getSubId();
-        assertTrue(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_SUPL, PhoneConstants.APN_TYPE_CBS}).
-                isMetered(mPhone));
 
-        assertTrue(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_SUPL}).isMetered(mPhone));
+        assertTrue(ApnSettingUtils.isMetered(
+                createApnSetting(ApnSetting.TYPE_SUPL | ApnSetting.TYPE_CBS), mPhone));
 
-        assertTrue(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_CBS}).isMetered(mPhone));
+        assertTrue(ApnSettingUtils.isMetered(createApnSetting(ApnSetting.TYPE_SUPL), mPhone));
 
-        assertTrue(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_FOTA, PhoneConstants.APN_TYPE_CBS}).
-                isMetered(mPhone));
+        assertTrue(ApnSettingUtils.isMetered(createApnSetting(ApnSetting.TYPE_CBS), mPhone));
 
-        assertTrue(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_SUPL, PhoneConstants.APN_TYPE_IA}).
-                isMetered(mPhone));
+        assertTrue(ApnSettingUtils.isMetered(
+                createApnSetting(ApnSetting.TYPE_FOTA | ApnSetting.TYPE_CBS), mPhone));
 
-        assertTrue(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_ALL}).isMetered(mPhone));
+        assertTrue(ApnSettingUtils.isMetered(
+                createApnSetting(ApnSetting.TYPE_SUPL | ApnSetting.TYPE_IA), mPhone));
 
-        assertFalse(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_DEFAULT, PhoneConstants.APN_TYPE_IMS}).
-                isMetered(mPhone));
+        assertTrue(ApnSettingUtils.isMetered(createApnSetting(ApnSetting.TYPE_ALL), mPhone));
 
-        assertFalse(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_IMS}).isMetered(mPhone));
+        assertFalse(ApnSettingUtils.isMetered(
+                createApnSetting(ApnSetting.TYPE_DEFAULT | ApnSetting.TYPE_IMS), mPhone));
 
-        assertTrue(ApnSetting.isMeteredApnType(PhoneConstants.APN_TYPE_SUPL, mPhone));
-        assertTrue(ApnSetting.isMeteredApnType(PhoneConstants.APN_TYPE_CBS, mPhone));
-        assertFalse(ApnSetting.isMeteredApnType(PhoneConstants.APN_TYPE_DEFAULT, mPhone));
-        assertFalse(ApnSetting.isMeteredApnType(PhoneConstants.APN_TYPE_MMS, mPhone));
-        assertFalse(ApnSetting.isMeteredApnType(PhoneConstants.APN_TYPE_DUN, mPhone));
-        assertFalse(ApnSetting.isMeteredApnType(PhoneConstants.APN_TYPE_FOTA, mPhone));
-        assertFalse(ApnSetting.isMeteredApnType(PhoneConstants.APN_TYPE_IA, mPhone));
-        assertFalse(ApnSetting.isMeteredApnType(PhoneConstants.APN_TYPE_HIPRI, mPhone));
+        assertFalse(ApnSettingUtils.isMetered(createApnSetting(ApnSetting.TYPE_IMS), mPhone));
+
+        assertTrue(ApnSettingUtils.isMeteredApnType(PhoneConstants.APN_TYPE_SUPL, mPhone));
+        assertTrue(ApnSettingUtils.isMeteredApnType(PhoneConstants.APN_TYPE_CBS, mPhone));
+        assertFalse(ApnSettingUtils.isMeteredApnType(PhoneConstants.APN_TYPE_DEFAULT, mPhone));
+        assertFalse(ApnSettingUtils.isMeteredApnType(PhoneConstants.APN_TYPE_MMS, mPhone));
+        assertFalse(ApnSettingUtils.isMeteredApnType(PhoneConstants.APN_TYPE_DUN, mPhone));
+        assertFalse(ApnSettingUtils.isMeteredApnType(PhoneConstants.APN_TYPE_FOTA, mPhone));
+        assertFalse(ApnSettingUtils.isMeteredApnType(PhoneConstants.APN_TYPE_IA, mPhone));
+        assertFalse(ApnSettingUtils.isMeteredApnType(PhoneConstants.APN_TYPE_HIPRI, mPhone));
     }
 
     @Test
@@ -514,42 +501,35 @@
         doReturn(ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN).when(mServiceState)
                 .getRilDataRadioTechnology();
         doReturn(2).when(mPhone).getSubId();
-        assertTrue(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_SUPL, PhoneConstants.APN_TYPE_CBS})
-                .isMetered(mPhone));
 
-        assertTrue(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_SUPL}).isMetered(mPhone));
+        assertTrue(ApnSettingUtils.isMetered(
+                createApnSetting(ApnSetting.TYPE_SUPL | ApnSetting.TYPE_CBS), mPhone));
 
-        assertTrue(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_CBS}).isMetered(mPhone));
+        assertTrue(ApnSettingUtils.isMetered(createApnSetting(ApnSetting.TYPE_SUPL), mPhone));
 
-        assertTrue(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_FOTA, PhoneConstants.APN_TYPE_CBS})
-                .isMetered(mPhone));
+        assertTrue(ApnSettingUtils.isMetered(createApnSetting(ApnSetting.TYPE_CBS), mPhone));
 
-        assertTrue(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_SUPL, PhoneConstants.APN_TYPE_IA})
-                .isMetered(mPhone));
+        assertTrue(ApnSettingUtils.isMetered(
+                createApnSetting(ApnSetting.TYPE_FOTA | ApnSetting.TYPE_CBS), mPhone));
 
-        assertTrue(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_ALL}).isMetered(mPhone));
+        assertTrue(ApnSettingUtils.isMetered(
+                createApnSetting(ApnSetting.TYPE_SUPL | ApnSetting.TYPE_IA), mPhone));
 
-        assertFalse(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_DEFAULT, PhoneConstants.APN_TYPE_IMS})
-                .isMetered(mPhone));
+        assertTrue(ApnSettingUtils.isMetered(createApnSetting(ApnSetting.TYPE_ALL), mPhone));
 
-        assertFalse(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_IMS}).isMetered(mPhone));
+        assertFalse(ApnSettingUtils.isMetered(
+                createApnSetting(ApnSetting.TYPE_DEFAULT | ApnSetting.TYPE_IMS), mPhone));
 
-        assertTrue(ApnSetting.isMeteredApnType(PhoneConstants.APN_TYPE_SUPL, mPhone));
-        assertTrue(ApnSetting.isMeteredApnType(PhoneConstants.APN_TYPE_CBS, mPhone));
-        assertFalse(ApnSetting.isMeteredApnType(PhoneConstants.APN_TYPE_DEFAULT, mPhone));
-        assertFalse(ApnSetting.isMeteredApnType(PhoneConstants.APN_TYPE_MMS, mPhone));
-        assertFalse(ApnSetting.isMeteredApnType(PhoneConstants.APN_TYPE_DUN, mPhone));
-        assertFalse(ApnSetting.isMeteredApnType(PhoneConstants.APN_TYPE_FOTA, mPhone));
-        assertFalse(ApnSetting.isMeteredApnType(PhoneConstants.APN_TYPE_IA, mPhone));
-        assertFalse(ApnSetting.isMeteredApnType(PhoneConstants.APN_TYPE_HIPRI, mPhone));
+        assertFalse(ApnSettingUtils.isMetered(createApnSetting(ApnSetting.TYPE_IMS), mPhone));
+
+        assertTrue(ApnSettingUtils.isMeteredApnType(PhoneConstants.APN_TYPE_SUPL, mPhone));
+        assertTrue(ApnSettingUtils.isMeteredApnType(PhoneConstants.APN_TYPE_CBS, mPhone));
+        assertFalse(ApnSettingUtils.isMeteredApnType(PhoneConstants.APN_TYPE_DEFAULT, mPhone));
+        assertFalse(ApnSettingUtils.isMeteredApnType(PhoneConstants.APN_TYPE_MMS, mPhone));
+        assertFalse(ApnSettingUtils.isMeteredApnType(PhoneConstants.APN_TYPE_DUN, mPhone));
+        assertFalse(ApnSettingUtils.isMeteredApnType(PhoneConstants.APN_TYPE_FOTA, mPhone));
+        assertFalse(ApnSettingUtils.isMeteredApnType(PhoneConstants.APN_TYPE_IA, mPhone));
+        assertFalse(ApnSettingUtils.isMeteredApnType(PhoneConstants.APN_TYPE_HIPRI, mPhone));
     }
 
     @Test
@@ -561,19 +541,16 @@
         doReturn(false).when(mServiceState).getDataRoaming();
         doReturn(3).when(mPhone).getSubId();
 
-        assertFalse(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_IMS}).isMetered(mPhone));
+        assertFalse(ApnSettingUtils.isMetered(createApnSetting(ApnSetting.TYPE_IMS), mPhone));
 
-        assertFalse(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_IMS, PhoneConstants.APN_TYPE_MMS})
-                .isMetered(mPhone));
+        assertFalse(ApnSettingUtils.isMetered(
+                createApnSetting(ApnSetting.TYPE_IMS | ApnSetting.TYPE_MMS), mPhone));
 
-        assertFalse(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_DEFAULT, PhoneConstants.APN_TYPE_FOTA})
-                .isMetered(mPhone));
+        assertFalse(ApnSettingUtils.isMetered(
+                createApnSetting(ApnSetting.TYPE_DEFAULT | ApnSetting.TYPE_FOTA), mPhone));
 
-        assertFalse(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_ALL}).isMetered(mPhone));
+        assertFalse(ApnSettingUtils.isMetered(
+                createApnSetting(ApnSetting.TYPE_ALL), mPhone));
     }
 
     @Test
@@ -584,20 +561,16 @@
         doReturn(true).when(mServiceState).getDataRoaming();
         doReturn(3).when(mPhone).getSubId();
 
-        assertFalse(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_IMS}).isMetered(mPhone));
+        assertFalse(ApnSettingUtils.isMetered(createApnSetting(ApnSetting.TYPE_IMS), mPhone));
 
-        assertFalse(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_IMS, PhoneConstants.APN_TYPE_MMS}).
-                isMetered(mPhone));
+        assertFalse(ApnSettingUtils.isMetered(
+                createApnSetting(ApnSetting.TYPE_IMS | ApnSetting.TYPE_MMS), mPhone));
 
-        assertFalse(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_DEFAULT, PhoneConstants.APN_TYPE_FOTA}).
-                isMetered(mPhone));
+        assertFalse(ApnSettingUtils.isMetered(
+                createApnSetting(ApnSetting.TYPE_DEFAULT | ApnSetting.TYPE_FOTA), mPhone));
 
-        assertFalse(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_ALL}).
-                isMetered(mPhone));
+        assertFalse(ApnSettingUtils.isMetered(
+                createApnSetting(ApnSetting.TYPE_ALL), mPhone));
     }
 
     @Test
@@ -610,19 +583,16 @@
                 .getRilDataRadioTechnology();
         doReturn(3).when(mPhone).getSubId();
 
-        assertFalse(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_IMS}).isMetered(mPhone));
+        assertFalse(ApnSettingUtils.isMetered(createApnSetting(ApnSetting.TYPE_IMS), mPhone));
 
-        assertFalse(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_IMS, PhoneConstants.APN_TYPE_MMS})
-                .isMetered(mPhone));
+        assertFalse(ApnSettingUtils.isMetered(
+                createApnSetting(ApnSetting.TYPE_IMS | ApnSetting.TYPE_MMS), mPhone));
 
-        assertFalse(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_DEFAULT, PhoneConstants.APN_TYPE_FOTA})
-                .isMetered(mPhone));
+        assertFalse(ApnSettingUtils.isMetered(
+                createApnSetting(ApnSetting.TYPE_DEFAULT | ApnSetting.TYPE_FOTA), mPhone));
 
-        assertFalse(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_ALL}).isMetered(mPhone));
+        assertFalse(ApnSettingUtils.isMetered(
+                createApnSetting(ApnSetting.TYPE_ALL), mPhone));
     }
 
     @Test
@@ -634,22 +604,17 @@
         doReturn(false).when(mServiceState).getDataRoaming();
         doReturn(4).when(mPhone).getSubId();
 
-        assertTrue(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_ALL}).
-                isMetered(mPhone));
+        assertTrue(ApnSettingUtils.isMetered(
+                createApnSetting(ApnSetting.TYPE_ALL), mPhone));
 
-        assertTrue(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_DEFAULT, PhoneConstants.APN_TYPE_MMS}).
-                isMetered(mPhone));
+        assertTrue(ApnSettingUtils.isMetered(
+                createApnSetting(ApnSetting.TYPE_DEFAULT | ApnSetting.TYPE_MMS), mPhone));
 
-        assertTrue(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_FOTA, PhoneConstants.APN_TYPE_CBS}).
-                isMetered(mPhone));
+        assertTrue(ApnSettingUtils.isMetered(
+                createApnSetting(ApnSetting.TYPE_FOTA | ApnSetting.TYPE_CBS), mPhone));
 
-        assertTrue(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_IA, PhoneConstants.APN_TYPE_DUN}).
-                isMetered(mPhone));
-
+        assertTrue(ApnSettingUtils.isMetered(
+                createApnSetting(ApnSetting.TYPE_IA | ApnSetting.TYPE_DUN), mPhone));
     }
 
     @Test
@@ -661,20 +626,17 @@
         doReturn(true).when(mServiceState).getDataRoaming();
         doReturn(4).when(mPhone).getSubId();
 
-        assertTrue(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_ALL}).isMetered(mPhone));
+        assertTrue(ApnSettingUtils.isMetered(
+                createApnSetting(ApnSetting.TYPE_ALL), mPhone));
 
-        assertTrue(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_DEFAULT, PhoneConstants.APN_TYPE_MMS})
-                .isMetered(mPhone));
+        assertTrue(ApnSettingUtils.isMetered(
+                createApnSetting(ApnSetting.TYPE_DEFAULT | ApnSetting.TYPE_MMS), mPhone));
 
-        assertTrue(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_FOTA, PhoneConstants.APN_TYPE_CBS})
-                .isMetered(mPhone));
+        assertTrue(ApnSettingUtils.isMetered(
+                createApnSetting(ApnSetting.TYPE_FOTA | ApnSetting.TYPE_CBS), mPhone));
 
-        assertTrue(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_IA, PhoneConstants.APN_TYPE_DUN})
-                .isMetered(mPhone));
+        assertTrue(ApnSettingUtils.isMetered(
+                createApnSetting(ApnSetting.TYPE_IA | ApnSetting.TYPE_DUN), mPhone));
     }
 
     @Test
@@ -688,20 +650,17 @@
                 .getRilDataRadioTechnology();
         doReturn(4).when(mPhone).getSubId();
 
-        assertTrue(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_ALL}).isMetered(mPhone));
+        assertTrue(ApnSettingUtils.isMetered(
+                createApnSetting(ApnSetting.TYPE_ALL), mPhone));
 
-        assertTrue(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_DEFAULT, PhoneConstants.APN_TYPE_MMS}).
-                isMetered(mPhone));
+        assertTrue(ApnSettingUtils.isMetered(
+                createApnSetting(ApnSetting.TYPE_DEFAULT | ApnSetting.TYPE_MMS), mPhone));
 
-        assertTrue(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_FOTA, PhoneConstants.APN_TYPE_CBS}).
-                isMetered(mPhone));
+        assertTrue(ApnSettingUtils.isMetered(
+                createApnSetting(ApnSetting.TYPE_FOTA | ApnSetting.TYPE_CBS), mPhone));
 
-        assertTrue(createApnSetting(
-                new String[]{PhoneConstants.APN_TYPE_IA, PhoneConstants.APN_TYPE_DUN}).
-                isMetered(mPhone));
+        assertTrue(ApnSettingUtils.isMetered(
+                createApnSetting(ApnSetting.TYPE_IA | ApnSetting.TYPE_DUN), mPhone));
     }
 
     @Test
@@ -709,59 +668,52 @@
     public void testCanHandleType() throws Exception {
         String types[] = {"mms"};
 
-        // empty string replaced with ALL ('*') when loaded to db
-        assertFalse(createApnSetting(new String[]{}).
-                canHandleType(APN_TYPE_MMS));
+        assertTrue(createApnSetting(ApnSetting.TYPE_ALL)
+                .canHandleType(ApnSetting.TYPE_MMS));
 
-        assertTrue(createApnSetting(new String[]{APN_TYPE_ALL}).
-                canHandleType(APN_TYPE_MMS));
+        assertFalse(createApnSetting(ApnSetting.TYPE_DEFAULT)
+                .canHandleType(ApnSetting.TYPE_MMS));
 
-        assertFalse(createApnSetting(new String[]{APN_TYPE_DEFAULT}).
-                canHandleType(APN_TYPE_MMS));
-
-        assertTrue(createApnSetting(new String[]{"DEfAULT"}).
-                canHandleType("defAult"));
+        assertTrue(createApnSetting(ApnSetting.TYPE_DEFAULT)
+                .canHandleType(ApnSetting.TYPE_DEFAULT));
 
         // Hipri is asymmetric
-        assertTrue(createApnSetting(new String[]{APN_TYPE_DEFAULT}).
-                canHandleType(APN_TYPE_HIPRI));
-        assertFalse(createApnSetting(new String[]{APN_TYPE_HIPRI}).
-                canHandleType(APN_TYPE_DEFAULT));
+        assertTrue(createApnSetting(ApnSetting.TYPE_DEFAULT)
+                .canHandleType(ApnSetting.TYPE_HIPRI));
+        assertFalse(createApnSetting(ApnSetting.TYPE_HIPRI)
+                .canHandleType(ApnSetting.TYPE_DEFAULT));
 
 
-        assertTrue(createApnSetting(new String[]{APN_TYPE_DEFAULT, APN_TYPE_MMS}).
-                canHandleType(APN_TYPE_DEFAULT));
+        assertTrue(createApnSetting(ApnSetting.TYPE_DEFAULT | ApnSetting.TYPE_MMS)
+                .canHandleType(ApnSetting.TYPE_DEFAULT));
 
-        assertTrue(createApnSetting(new String[]{APN_TYPE_DEFAULT, APN_TYPE_MMS}).
-                canHandleType(APN_TYPE_MMS));
+        assertTrue(createApnSetting(ApnSetting.TYPE_DEFAULT | ApnSetting.TYPE_MMS)
+                .canHandleType(ApnSetting.TYPE_MMS));
 
-        assertFalse(createApnSetting(new String[]{APN_TYPE_DEFAULT, APN_TYPE_MMS}).
-                canHandleType(APN_TYPE_SUPL));
+        assertFalse(createApnSetting(ApnSetting.TYPE_DEFAULT | ApnSetting.TYPE_MMS)
+                .canHandleType(ApnSetting.TYPE_SUPL));
 
         // special IA case - doesn't match wildcards
-        assertFalse(createApnSetting(new String[]{APN_TYPE_DEFAULT, APN_TYPE_MMS}).
-                canHandleType(APN_TYPE_IA));
-        assertFalse(createApnSetting(new String[]{APN_TYPE_ALL}).
-                canHandleType(APN_TYPE_IA));
-        assertFalse(createApnSetting(new String[]{APN_TYPE_ALL}).
-                canHandleType("iA"));
-        assertTrue(createApnSetting(new String[]{APN_TYPE_DEFAULT, APN_TYPE_MMS, APN_TYPE_IA}).
-                canHandleType(APN_TYPE_IA));
+        assertFalse(createApnSetting(ApnSetting.TYPE_DEFAULT | ApnSetting.TYPE_MMS)
+                .canHandleType(ApnSetting.TYPE_IA));
+        assertTrue(createApnSetting(
+                ApnSetting.TYPE_DEFAULT | ApnSetting.TYPE_MMS | ApnSetting.TYPE_IA)
+                .canHandleType(ApnSetting.TYPE_IA));
 
         // check carrier disabled
-        assertFalse(createDisabledApnSetting(new String[]{APN_TYPE_ALL}).
-                canHandleType(APN_TYPE_MMS));
-        assertFalse(createDisabledApnSetting(new String[]{"DEfAULT"}).
-                canHandleType("defAult"));
-        assertFalse(createDisabledApnSetting(new String[]{APN_TYPE_DEFAULT}).
-                canHandleType(APN_TYPE_HIPRI));
-        assertFalse(createDisabledApnSetting(new String[]{APN_TYPE_DEFAULT, APN_TYPE_MMS}).
-                canHandleType(APN_TYPE_DEFAULT));
-        assertFalse(createDisabledApnSetting(new String[]{APN_TYPE_DEFAULT, APN_TYPE_MMS}).
-                canHandleType(APN_TYPE_MMS));
-        assertFalse(createDisabledApnSetting(new String[]
-                {APN_TYPE_DEFAULT, APN_TYPE_MMS, APN_TYPE_IA}).
-                canHandleType(APN_TYPE_IA));
+        assertFalse(createDisabledApnSetting(ApnSetting.TYPE_ALL)
+                .canHandleType(ApnSetting.TYPE_MMS));
+        assertFalse(createDisabledApnSetting(ApnSetting.TYPE_DEFAULT)
+                .canHandleType(ApnSetting.TYPE_DEFAULT));
+        assertFalse(createDisabledApnSetting(ApnSetting.TYPE_DEFAULT)
+                .canHandleType(ApnSetting.TYPE_HIPRI));
+        assertFalse(createDisabledApnSetting(ApnSetting.TYPE_DEFAULT | ApnSetting.TYPE_MMS)
+                .canHandleType(ApnSetting.TYPE_DEFAULT));
+        assertFalse(createDisabledApnSetting(ApnSetting.TYPE_DEFAULT | ApnSetting.TYPE_MMS)
+                .canHandleType(ApnSetting.TYPE_MMS));
+        assertFalse(createDisabledApnSetting(
+                ApnSetting.TYPE_DEFAULT | ApnSetting.TYPE_MMS | ApnSetting.TYPE_IA)
+                .canHandleType(ApnSetting.TYPE_IA));
     }
 
     @Test
@@ -770,8 +722,10 @@
         final int dummyInt = 1;
         final String dummyString = "dummy";
         final String[] dummyStringArr = new String[] {"dummy"};
+        final InetAddress dummyProxyAddress = InetAddress.getByAddress(new byte[]{0, 0, 0, 0});
+        final Uri dummyUri = Uri.parse("www.google.com");
         // base apn
-        ApnSetting baseApn = createApnSetting(new String[] {"mms", "default"});
+        ApnSetting baseApn = createApnSetting(ApnSetting.TYPE_MMS | ApnSetting.TYPE_DEFAULT);
         Field[] fields = ApnSetting.class.getDeclaredFields();
         for (Field f : fields) {
             int modifiers = f.getModifiers();
@@ -781,17 +735,23 @@
             f.setAccessible(true);
             ApnSetting testApn = null;
             if (int.class.equals(f.getType())) {
-                testApn = new ApnSetting(baseApn);
+                testApn = ApnSetting.makeApnSetting(baseApn);
                 f.setInt(testApn, dummyInt + f.getInt(testApn));
             } else if (boolean.class.equals(f.getType())) {
-                testApn = new ApnSetting(baseApn);
+                testApn = ApnSetting.makeApnSetting(baseApn);
                 f.setBoolean(testApn, !f.getBoolean(testApn));
             } else if (String.class.equals(f.getType())) {
-                testApn = new ApnSetting(baseApn);
+                testApn = ApnSetting.makeApnSetting(baseApn);
                 f.set(testApn, dummyString);
             } else if (String[].class.equals(f.getType())) {
-                testApn = new ApnSetting(baseApn);
+                testApn = ApnSetting.makeApnSetting(baseApn);
                 f.set(testApn, dummyStringArr);
+            } else if (InetAddress.class.equals(f.getType())) {
+                testApn = ApnSetting.makeApnSetting(baseApn);
+                f.set(testApn, dummyProxyAddress);
+            } else if (Uri.class.equals(f.getType())) {
+                testApn = ApnSetting.makeApnSetting(baseApn);
+                f.set(testApn, dummyUri);
             } else {
                 fail("Unsupported field:" + f.getName());
             }
@@ -804,63 +764,61 @@
     @Test
     @SmallTest
     public void testEqualsRoamingProtocol() throws Exception {
-        ApnSetting apn1 = new ApnSetting(
+        ApnSetting apn1 = ApnSetting.makeApnSetting(
                 1234,
                 "310260",
                 "",
                 "ims",
-                "",
-                "",
-                "",
-                "",
-                "",
+                null,
+                -1,
+                null,
+                null,
+                -1,
                 "",
                 "",
                 -1,
-                 new String[]{"ims"},
-                "IPV6",
-                "",
+                ApnSetting.TYPE_IMS,
+                ApnSetting.PROTOCOL_IPV6,
+                -1,
                 true,
-                0,
-                131071,
+                ServiceState.convertBearerBitmaskToNetworkTypeBitmask(131071),
                 0,
                 false,
                 0,
                 0,
                 0,
                 1440,
-                "",
+                -1,
                 "");
 
-        ApnSetting apn2 = new ApnSetting(
+        ApnSetting apn2 = ApnSetting.makeApnSetting(
                 1235,
                 "310260",
                 "",
                 "ims",
-                "",
-                "",
-                "",
-                "",
-                "",
+                null,
+                -1,
+                null,
+                null,
+                -1,
                 "",
                 "",
                 -1,
-                new String[]{"ims"},
-                "IPV6",
-                "IPV6",
+                ApnSetting.TYPE_IMS,
+                ApnSetting.PROTOCOL_IPV6,
+                ApnSetting.PROTOCOL_IPV6,
                 true,
-                0,
-                131072,
+                ServiceState.convertBearerBitmaskToNetworkTypeBitmask(131072),
                 0,
                 false,
                 0,
                 0,
                 0,
                 1440,
-                "",
+                -1,
                 "");
 
         assertTrue(apn1.equals(apn2, false));
         assertFalse(apn1.equals(apn2, true));
     }
-}
\ No newline at end of file
+}
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 f13d2c2..ce5eaeb 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DataConnectionTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DataConnectionTest.java
@@ -35,6 +35,7 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
@@ -54,6 +55,7 @@
 import android.telephony.AccessNetworkConstants.TransportType;
 import android.telephony.CarrierConfigManager;
 import android.telephony.ServiceState;
+import android.telephony.data.ApnSetting;
 import android.telephony.data.DataCallResponse;
 import android.telephony.data.DataProfile;
 import android.telephony.data.DataService;
@@ -97,32 +99,58 @@
     private DataConnectionTestHandler mDataConnectionTestHandler;
     private DcController mDcc;
 
-    private ApnSetting mApn1 = new ApnSetting(
+    private ApnSetting mApn1 = ApnSetting.makeApnSetting(
             2163,                   // id
             "44010",                // numeric
             "sp-mode",              // name
             "spmode.ne.jp",         // apn
-            "",                     // proxy
-            "",                     // port
-            "",                     // mmsc
-            "",                     // mmsproxy
-            "",                     // mmsport
+            null,                     // proxy
+            -1,                     // port
+            null,                     // mmsc
+            null,                     // mmsproxy
+            -1,                     // mmsport
             "",                     // user
             "",                     // password
             -1,                     // authtype
-            new String[]{"default", "supl"},     // types
-            "IP",                   // protocol
-            "IP",                   // roaming_protocol
+            ApnSetting.TYPE_DEFAULT | ApnSetting.TYPE_SUPL,     // types
+            ApnSetting.PROTOCOL_IP,                   // protocol
+            ApnSetting.PROTOCOL_IP,                   // roaming_protocol
             true,                   // carrier_enabled
-            0,                      // bearer
-            0,                      // bearer_bitmask
+            0,                      // networktype_bitmask
             0,                      // profile_id
             false,                  // modem_cognitive
             0,                      // max_conns
             0,                      // wait_time
             0,                      // max_conns_time
             0,                      // mtu
-            "",                     // mvno_type
+            -1,                     // mvno_type
+            "");                    // mnvo_match_data
+
+    private ApnSetting mApn2 = ApnSetting.makeApnSetting(
+            2164,                   // id
+            "44010",                // numeric
+            "sp-mode",              // name
+            "spmode.ne.jp",         // apn
+            null,                     // proxy
+            -1,                     // port
+            null,                     // mmsc
+            null,                     // mmsproxy
+            -1,                     // mmsport
+            "",                     // user
+            "",                     // password
+            -1,                     // authtype
+            ApnSetting.TYPE_DEFAULT | ApnSetting.TYPE_DUN,     // types
+            ApnSetting.PROTOCOL_IP,                   // protocol
+            ApnSetting.PROTOCOL_IP,                   // roaming_protocol
+            true,                   // carrier_enabled
+            0,                      // networktype_bitmask
+            0,                      // profile_id
+            false,                  // modem_cognitive
+            0,                      // max_conns
+            0,                      // wait_time
+            0,                      // max_conns_time
+            0,                      // mtu
+            -1,                     // mvno_type
             "");                    // mnvo_match_data
 
     private class DataConnectionTestHandler extends HandlerThread {
@@ -147,7 +175,7 @@
         CellularDataService cellularDataService = new CellularDataService();
         ServiceInfo serviceInfo = new ServiceInfo();
         serviceInfo.packageName = "com.android.phone";
-        serviceInfo.permission = "android.permission.BIND_DATA_SERVICE";
+        serviceInfo.permission = "android.permission.BIND_TELEPHONY_DATA_SERVICE";
         IntentFilter filter = new IntentFilter();
         mContextFixture.addService(
                 DataService.DATA_SERVICE_INTERFACE,
@@ -170,6 +198,7 @@
                 ServiceState.RIL_RADIO_TECHNOLOGY_UMTS);
         doReturn(mApn1).when(mApnContext).getApnSetting();
         doReturn(PhoneConstants.APN_TYPE_DEFAULT).when(mApnContext).getApnType();
+        doReturn(true).when(mDcTracker).isDataEnabled();
 
         mDcFailBringUp.saveParameters(0, 0, -2);
         doReturn(mDcFailBringUp).when(mDcTesterFailBringUpAll).getDcFailBringUp();
@@ -372,6 +401,36 @@
 
     @Test
     @SmallTest
+    public void testNetworkCapability() throws Exception {
+        mContextFixture.getCarrierConfigBundle().putStringArray(
+                CarrierConfigManager.KEY_CARRIER_METERED_APN_TYPES_STRINGS,
+                new String[] { "default" });
+        doReturn(mApn2).when(mApnContext).getApnSetting();
+        testConnectEvent();
+
+        assertTrue("capabilities: " + getNetworkCapabilities(), getNetworkCapabilities()
+                .hasCapability(NetworkCapabilities.NET_CAPABILITY_DUN));
+        assertTrue("capabilities: " + getNetworkCapabilities(), getNetworkCapabilities()
+                .hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET));
+        assertFalse("capabilities: " + getNetworkCapabilities(), getNetworkCapabilities()
+                .hasCapability(NetworkCapabilities.NET_CAPABILITY_MMS));
+
+        mDc.sendMessage(DataConnection.EVENT_DISCONNECT, mDcp);
+        waitForMs(100);
+        doReturn(mApn1).when(mApnContext).getApnSetting();
+        mDc.sendMessage(DataConnection.EVENT_CONNECT, mCp);
+        waitForMs(200);
+
+        assertFalse("capabilities: " + getNetworkCapabilities(), getNetworkCapabilities()
+                .hasCapability(NetworkCapabilities.NET_CAPABILITY_DUN));
+        assertTrue("capabilities: " + getNetworkCapabilities(), getNetworkCapabilities()
+                .hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET));
+        assertTrue("capabilities: " + getNetworkCapabilities(), getNetworkCapabilities()
+                .hasCapability(NetworkCapabilities.NET_CAPABILITY_SUPL));
+    }
+
+    @Test
+    @SmallTest
     public void testMeteredCapability() throws Exception {
 
         mContextFixture.getCarrierConfigBundle().
@@ -441,6 +500,7 @@
         assertTrue(getNetworkCapabilities().hasCapability(NET_CAPABILITY_NOT_CONGESTED));
     }
 
+    @Test
     @SmallTest
     public void testIsIpAddress() throws Exception {
         // IPv4
@@ -524,6 +584,36 @@
         assertEquals(SetupResult.SUCCESS, setLinkProperties(response, linkProperties));
     }
 
+    @Test
+    @SmallTest
+    public void testStartKeepaliveWLAN() throws Exception {
+        testConnectEvent();
+        waitForMs(200);
+
+        DataServiceManager mockDsm = mock(DataServiceManager.class);
+        doReturn(TransportType.WLAN).when(mockDsm).getTransportType();
+        replaceInstance(DataConnection.class, "mDataServiceManager", mDc, mockDsm);
+
+        final int sessionHandle = 0xF00;
+        final int slotId = 3;
+        final int interval = 10; // seconds
+        // Construct a new KeepalivePacketData request as we would receive from a Network Agent,
+        // and check that the packet is sent to the RIL.
+        KeepalivePacketData kd = KeepalivePacketData.nattKeepalivePacket(
+                NetworkUtils.numericToInetAddress("1.2.3.4"),
+                1234,
+                NetworkUtils.numericToInetAddress("8.8.8.8"),
+                4500);
+        mDc.obtainMessage(
+                DataConnection.EVENT_KEEPALIVE_START_REQUEST, slotId, interval, kd).sendToTarget();
+        waitForMs(100);
+        // testStartStopNattKeepalive() verifies that this request is passed with WWAN.
+        // Thus, even though we can't see the response in NetworkAgent, we can verify that the
+        // CommandsInterface never receives a request and infer that it was dropped due to WLAN.
+        verify(mSimulatedCommandsVerifier, times(0))
+                .startNattKeepalive(anyInt(), eq(kd), eq(interval * 1000), any(Message.class));
+    }
+
     public void checkStartStopNattKeepalive(boolean useCondensedFlow) throws Exception {
         testConnectEvent();
         waitForMs(200);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DataProfileTest.java b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DataProfileTest.java
index 5dbe995..97fcc75 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DataProfileTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DataProfileTest.java
@@ -17,6 +17,7 @@
 package com.android.internal.telephony.dataconnection;
 
 import android.telephony.ServiceState;
+import android.telephony.data.ApnSetting;
 import android.telephony.data.DataProfile;
 import android.test.suitebuilder.annotation.SmallTest;
 
@@ -26,132 +27,130 @@
 
 public class DataProfileTest extends TestCase {
 
-    private ApnSetting mApn1 = new ApnSetting(
+    private ApnSetting mApn1 = ApnSetting.makeApnSetting(
             2163,                   // id
             "44010",                // numeric
             "sp-mode",              // name
             "fake_apn",             // apn
-            "",                     // proxy
-            "",                     // port
-            "",                     // mmsc
-            "",                     // mmsproxy
-            "",                     // mmsport
+            null,                     // proxy
+            -1,                     // port
+            null,                     // mmsc
+            null,                     // mmsproxy
+            -1,                     // mmsport
             "user",                 // user
             "passwd",               // password
             -1,                     // authtype
-            new String[]{"default", "supl"},     // types
-            "IPV6",                 // protocol
-            "IP",                   // roaming_protocol
+            ApnSetting.TYPE_DEFAULT | ApnSetting.TYPE_SUPL,     // types
+            ApnSetting.PROTOCOL_IPV6,                 // protocol
+            ApnSetting.PROTOCOL_IP,                   // roaming_protocol
             true,                   // carrier_enabled
-            0,                      // bearer
-            0,                      // bearer_bitmask
+            0,                      // networktype_bitmask
             1234,                   // profile_id
             false,                  // modem_cognitive
             321,                    // max_conns
             456,                    // wait_time
             789,                    // max_conns_time
             0,                      // mtu
-            "",                     // mvno_type
+            -1,                     // mvno_type
             "");                    // mnvo_match_data
 
-    private ApnSetting mApn2 = new ApnSetting(
+    private ApnSetting mApn2 = ApnSetting.makeApnSetting(
             2163,                   // id
             "44010",                // numeric
             "sp-mode",              // name
             "fake_apn",             // apn
-            "",                     // proxy
-            "",                     // port
-            "",                     // mmsc
-            "",                     // mmsproxy
-            "",                     // mmsport
+            null,                     // proxy
+            -1,                     // port
+            null,                     // mmsc
+            null,                     // mmsproxy
+            -1,                     // mmsport
             "user",                 // user
             "passwd",               // password
             -1,                     // authtype
-            new String[]{"default", "supl"},     // types
-            "IP",                   // protocol
-            "IP",                   // roaming_protocol
+            ApnSetting.TYPE_DEFAULT | ApnSetting.TYPE_SUPL,     // types
+            ApnSetting.PROTOCOL_IP,                 // protocol
+            ApnSetting.PROTOCOL_IP,                   // roaming_protocol
             true,                   // carrier_enabled
-            0,                      // bearer
-            0,                      // bearer_bitmask
+            0,                      // networktype_bitmask
             1234,                   // profile_id
             false,                  // modem_cognitive
             111,                    // max_conns
             456,                    // wait_time
             789,                    // max_conns_time
             0,                      // mtu
-            "",                     // mvno_type
+            -1,                     // mvno_type
             "");                    // mnvo_match_data
 
-    private ApnSetting mApn3 = new ApnSetting(
+    private ApnSetting mApn3 = ApnSetting.makeApnSetting(
             2163,                   // id
             "44010",                // numeric
             "sp-mode",              // name
             "fake_apn",             // apn
-            "",                     // proxy
-            "",                     // port
-            "",                     // mmsc
-            "",                     // mmsproxy
-            "",                     // mmsport
+            null,                     // proxy
+            -1,                     // port
+            null,                     // mmsc
+            null,                     // mmsproxy
+            -1,                     // mmsport
             "user",                 // user
             "passwd",               // password
             -1,                     // authtype
-            new String[]{"default", "supl"},     // types
-            "IP",                   // protocol
-            "IP",                   // roaming_protocol
+            ApnSetting.TYPE_DEFAULT | ApnSetting.TYPE_SUPL,     // types
+            ApnSetting.PROTOCOL_IP,                 // protocol
+            ApnSetting.PROTOCOL_IP,                   // roaming_protocol
             true,                   // carrier_enabled
-            276600,                    // network_type_bitmask
+            276600,                      // networktype_bitmask
             1234,                   // profile_id
             false,                  // modem_cognitive
             111,                    // max_conns
             456,                    // wait_time
             789,                    // max_conns_time
             0,                      // mtu
-            "",                     // mvno_type
-            ""                   // mnvo_match_data
-            );
+            -1,                     // mvno_type
+            "");                    // mnvo_match_data
 
     @SmallTest
     public void testCreateFromApnSetting() throws Exception {
-        DataProfile dp = DcTracker.createDataProfile(mApn1, mApn1.profileId);
-        assertEquals(mApn1.profileId, dp.getProfileId());
-        assertEquals(mApn1.apn, dp.getApn());
-        assertEquals(mApn1.protocol, dp.getProtocol());
+        DataProfile dp = DcTracker.createDataProfile(mApn1, mApn1.getProfileId());
+        assertEquals(mApn1.getProfileId(), dp.getProfileId());
+        assertEquals(mApn1.getApnName(), dp.getApn());
+        assertEquals(ApnSetting.getProtocolStringFromInt(mApn1.getProtocol()), dp.getProtocol());
         assertEquals(RILConstants.SETUP_DATA_AUTH_PAP_CHAP, dp.getAuthType());
-        assertEquals(mApn1.user, dp.getUserName());
-        assertEquals(mApn1.password, dp.getPassword());
+        assertEquals(mApn1.getUser(), dp.getUserName());
+        assertEquals(mApn1.getPassword(), dp.getPassword());
         assertEquals(0, dp.getType());  // TYPE_COMMON
-        assertEquals(mApn1.maxConnsTime, dp.getMaxConnsTime());
-        assertEquals(mApn1.maxConns, dp.getMaxConns());
-        assertEquals(mApn1.waitTime, dp.getWaitTime());
-        assertEquals(mApn1.carrierEnabled, dp.isEnabled());
+        assertEquals(mApn1.getMaxConnsTime(), dp.getMaxConnsTime());
+        assertEquals(mApn1.getMaxConns(), dp.getMaxConns());
+        assertEquals(mApn1.getWaitTime(), dp.getWaitTime());
+        assertEquals(mApn1.isEnabled(), dp.isEnabled());
     }
 
     @SmallTest
     public void testCreateFromApnSettingWithNetworkTypeBitmask() throws Exception {
-        DataProfile dp = DcTracker.createDataProfile(mApn3, mApn3.profileId);
-        assertEquals(mApn3.profileId, dp.getProfileId());
-        assertEquals(mApn3.apn, dp.getApn());
-        assertEquals(mApn3.protocol, dp.getProtocol());
+        DataProfile dp = DcTracker.createDataProfile(mApn3, mApn3.getProfileId());
+        assertEquals(mApn3.getProfileId(), dp.getProfileId());
+        assertEquals(mApn3.getApnName(), dp.getApn());
+        assertEquals(ApnSetting.getProtocolStringFromInt(mApn3.getProtocol()), dp.getProtocol());
         assertEquals(RILConstants.SETUP_DATA_AUTH_PAP_CHAP, dp.getAuthType());
-        assertEquals(mApn3.user, dp.getUserName());
-        assertEquals(mApn3.password, dp.getPassword());
+        assertEquals(mApn3.getUser(), dp.getUserName());
+        assertEquals(mApn3.getPassword(), dp.getPassword());
         assertEquals(2, dp.getType());  // TYPE_3GPP2
-        assertEquals(mApn3.maxConnsTime, dp.getMaxConnsTime());
-        assertEquals(mApn3.maxConns, dp.getMaxConns());
-        assertEquals(mApn3.waitTime, dp.getWaitTime());
-        assertEquals(mApn3.carrierEnabled, dp.isEnabled());
+        assertEquals(mApn3.getMaxConnsTime(), dp.getMaxConnsTime());
+        assertEquals(mApn3.getMaxConns(), dp.getMaxConns());
+        assertEquals(mApn3.getWaitTime(), dp.getWaitTime());
+        assertEquals(mApn3.isEnabled(), dp.isEnabled());
         int expectedBearerBitmap =
-                ServiceState.convertNetworkTypeBitmaskToBearerBitmask(mApn3.networkTypeBitmask);
+                ServiceState.convertNetworkTypeBitmaskToBearerBitmask(
+                    mApn3.getNetworkTypeBitmask());
         assertEquals(expectedBearerBitmap, dp.getBearerBitmap());
     }
 
     @SmallTest
     public void testEquals() throws Exception {
-        DataProfile dp1 = DcTracker.createDataProfile(mApn1, mApn1.profileId);
-        DataProfile dp2 = DcTracker.createDataProfile(mApn1, mApn1.profileId);
+        DataProfile dp1 = DcTracker.createDataProfile(mApn1, mApn1.getProfileId());
+        DataProfile dp2 = DcTracker.createDataProfile(mApn1, mApn1.getProfileId());
         assertEquals(dp1, dp2);
 
-        dp2 = DcTracker.createDataProfile(mApn2, mApn2.profileId);
+        dp2 = DcTracker.createDataProfile(mApn2, mApn2.getProfileId());
         assertFalse(dp1.equals(dp2));
     }
 }
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 f7eed07..451571e 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DcTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DcTrackerTest.java
@@ -38,6 +38,7 @@
 import android.app.AlarmManager;
 import android.app.PendingIntent;
 import android.content.ContentResolver;
+import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -63,12 +64,14 @@
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
+import android.telephony.data.ApnSetting;
 import android.telephony.data.DataProfile;
 import android.telephony.data.DataService;
 import android.test.mock.MockContentProvider;
 import android.test.mock.MockContentResolver;
 import android.test.suitebuilder.annotation.MediumTest;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.text.TextUtils;
 import android.util.LocalLog;
 
 import com.android.internal.R;
@@ -88,6 +91,7 @@
 import org.mockito.stubbing.Answer;
 
 import java.lang.reflect.Method;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import java.util.regex.Matcher;
@@ -121,6 +125,8 @@
             1 << (TelephonyManager.NETWORK_TYPE_LTE - 1);
     private static final int NETWORK_TYPE_EHRPD_BITMASK =
             1 << (TelephonyManager.NETWORK_TYPE_EHRPD - 1);
+    private static final Uri PREFERAPN_URI = Uri.parse(
+            Telephony.Carriers.CONTENT_URI + "/preferapn");
 
     @Mock
     ISub mIsub;
@@ -153,7 +159,7 @@
         CellularDataService cellularDataService = new CellularDataService();
         ServiceInfo serviceInfo = new ServiceInfo();
         serviceInfo.packageName = "com.android.phone";
-        serviceInfo.permission = "android.permission.BIND_DATA_SERVICE";
+        serviceInfo.permission = "android.permission.BIND_TELEPHONY_DATA_SERVICE";
         IntentFilter filter = new IntentFilter();
         mContextFixture.addService(
                 DataService.DATA_SERVICE_INTERFACE,
@@ -178,6 +184,7 @@
     }
 
     private class ApnSettingContentProvider extends MockContentProvider {
+        private int mPreferredApnSet = 0;
 
         @Override
         public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
@@ -222,7 +229,8 @@
                                     Telephony.Carriers.MAX_CONNS_TIME, Telephony.Carriers.MTU,
                                     Telephony.Carriers.MVNO_TYPE,
                                     Telephony.Carriers.MVNO_MATCH_DATA,
-                                    Telephony.Carriers.NETWORK_TYPE_BITMASK});
+                                    Telephony.Carriers.NETWORK_TYPE_BITMASK,
+                                    Telephony.Carriers.APN_SET_ID});
 
                     mc.addRow(new Object[]{
                             2163,                   // id
@@ -251,7 +259,8 @@
                             0,                      // mtu
                             "",                     // mvno_type
                             "",                     // mnvo_match_data
-                            NETWORK_TYPE_LTE_BITMASK // network_type_bitmask
+                            NETWORK_TYPE_LTE_BITMASK, // network_type_bitmask
+                            0                       // apn_set_id
                     });
 
                     mc.addRow(new Object[]{
@@ -281,7 +290,8 @@
                             0,                      // mtu
                             "",                     // mvno_type
                             "",                     // mnvo_match_data
-                            NETWORK_TYPE_LTE_BITMASK // network_type_bitmask
+                            NETWORK_TYPE_LTE_BITMASK, // network_type_bitmask
+                            0                       // apn_set_id
                     });
 
                     mc.addRow(new Object[]{
@@ -311,7 +321,8 @@
                             0,                      // mtu
                             "",                     // mvno_type
                             "",                     // mnvo_match_data
-                            0                       // network_type_bitmask
+                            0,                      // network_type_bitmask
+                            0                       // apn_set_id
                     });
 
                     mc.addRow(new Object[]{
@@ -341,7 +352,8 @@
                             0,                      // mtu
                             "",                     // mvno_type
                             "",                     // mnvo_match_data
-                            NETWORK_TYPE_EHRPD_BITMASK // network_type_bitmask
+                            NETWORK_TYPE_EHRPD_BITMASK, // network_type_bitmask
+                            0                       // apn_set_id
                     });
 
                     mc.addRow(new Object[]{
@@ -371,14 +383,30 @@
                             0,                      // mtu
                             "",                     // mvno_type
                             "",                     // mnvo_match_data
-                            0                       // network_type_bitmask
+                            0,                      // network_type_bitmask
+                            0                       // apn_set_id
                     });
+
                     return mc;
                 }
+            } else if (uri.isPathPrefixMatch(
+                    Uri.withAppendedPath(Telephony.Carriers.CONTENT_URI, "preferapnset"))) {
+                MatrixCursor mc = new MatrixCursor(
+                        new String[]{Telephony.Carriers.APN_SET_ID});
+                // apn_set_id is the only field used with this URL
+                mc.addRow(new Object[]{ mPreferredApnSet });
+                mc.addRow(new Object[]{ 0 });
+                return mc;
             }
 
             return null;
         }
+
+        @Override
+        public int update(Uri url, ContentValues values, String where, String[] whereArgs) {
+            mPreferredApnSet = values.getAsInteger(Telephony.Carriers.APN_SET_ID);
+            return 1;
+        }
     }
 
     @Before
@@ -577,7 +605,7 @@
 
         logd("Sending EVENT_ENABLE_NEW_APN");
         // APN id 0 is APN_TYPE_DEFAULT
-        mDct.setEnabled(0, true);
+        mDct.setEnabled(ApnSetting.TYPE_DEFAULT, true);
         waitForMs(200);
 
         dataConnectionReasons = new DataConnectionReasons();
@@ -651,7 +679,7 @@
 
         logd("Sending EVENT_ENABLE_NEW_APN");
         // APN id 0 is APN_TYPE_DEFAULT
-        mDct.setEnabled(0, true);
+        mDct.setEnabled(ApnSetting.TYPE_DEFAULT, true);
         waitForMs(200);
 
 
@@ -710,8 +738,8 @@
         boolean dataEnabled = mDct.isUserDataEnabled();
         mBundle.putStringArray(CarrierConfigManager.KEY_CARRIER_METERED_APN_TYPES_STRINGS,
                 new String[]{PhoneConstants.APN_TYPE_DEFAULT, PhoneConstants.APN_TYPE_MMS});
-        mDct.setEnabled(5, true);
-        mDct.setEnabled(0, true);
+        mDct.setEnabled(ApnSetting.TYPE_IMS, true);
+        mDct.setEnabled(ApnSetting.TYPE_DEFAULT, true);
 
         logd("Sending EVENT_RECORDS_LOADED");
         mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_RECORDS_LOADED, null));
@@ -764,8 +792,9 @@
 
         mBundle.putStringArray(CarrierConfigManager.KEY_CARRIER_METERED_ROAMING_APN_TYPES_STRINGS,
                 new String[]{PhoneConstants.APN_TYPE_DEFAULT, PhoneConstants.APN_TYPE_MMS});
-        mDct.setEnabled(5, true);
-        mDct.setEnabled(0, true);
+
+        mDct.setEnabled(ApnSetting.TYPE_IMS, true);
+        mDct.setEnabled(ApnSetting.TYPE_DEFAULT, true);
 
         logd("Sending EVENT_RECORDS_LOADED");
         mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_RECORDS_LOADED, null));
@@ -822,8 +851,8 @@
         //set Default and MMS to be metered in the CarrierConfigManager
         mBundle.putStringArray(CarrierConfigManager.KEY_CARRIER_METERED_ROAMING_APN_TYPES_STRINGS,
                 new String[]{PhoneConstants.APN_TYPE_DEFAULT, PhoneConstants.APN_TYPE_MMS});
-        mDct.setEnabled(5, true);
-        mDct.setEnabled(0, true);
+        mDct.setEnabled(ApnSetting.TYPE_IMS, true);
+        mDct.setEnabled(ApnSetting.TYPE_DEFAULT, true);
 
         logd("Sending DATA_ENABLED_CMD");
         mDct.setUserDataEnabled(true);
@@ -937,8 +966,8 @@
         boolean dataEnabled = mDct.isUserDataEnabled();
         mDct.setUserDataEnabled(true);
 
-        mDct.setEnabled(5, true);
-        mDct.setEnabled(0, true);
+        mDct.setEnabled(ApnSetting.TYPE_IMS, true);
+        mDct.setEnabled(ApnSetting.TYPE_DEFAULT, true);
 
         logd("Sending EVENT_RECORDS_LOADED");
         mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_RECORDS_LOADED, null));
@@ -978,7 +1007,8 @@
     private void initApns(String targetApn, String[] canHandleTypes) {
         doReturn(targetApn).when(mApnContext).getApnType();
         doReturn(true).when(mApnContext).isConnectable();
-        ApnSetting apnSetting = createApnSetting(canHandleTypes);
+        ApnSetting apnSetting = createApnSetting(ApnSetting.getApnTypesBitmaskFromString(
+                TextUtils.join(",", canHandleTypes)));
         doReturn(apnSetting).when(mApnContext).getNextApnSetting();
         doReturn(apnSetting).when(mApnContext).getApnSetting();
         doReturn(mDcac).when(mApnContext).getDcAc();
@@ -1003,6 +1033,33 @@
                 any(Message.class));
     }
 
+    @Test
+    @SmallTest
+    public void testGetDataConnectionState() throws Exception {
+        initApns(PhoneConstants.APN_TYPE_SUPL,
+                new String[]{PhoneConstants.APN_TYPE_SUPL, PhoneConstants.APN_TYPE_DEFAULT});
+        mDct.setUserDataEnabled(false);
+
+        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);
+
+        // Assert that both APN_TYPE_SUPL & APN_TYPE_DEFAULT are connected even we only setup data
+        // for APN_TYPE_SUPL
+        assertEquals(DctConstants.State.CONNECTED, mDct.getState(PhoneConstants.APN_TYPE_SUPL));
+        assertEquals(DctConstants.State.CONNECTED, mDct.getState(PhoneConstants.APN_TYPE_DEFAULT));
+    }
+
     // Test the unmetered APN setup when data is disabled.
     @Test
     @SmallTest
@@ -1085,6 +1142,37 @@
                 any(Message.class));
     }
 
+    // Test the restricted data request when roaming is disabled.
+    @Test
+    @SmallTest
+    public void testTrySetupRestrictedRoamingDisabled() throws Exception {
+        initApns(PhoneConstants.APN_TYPE_DEFAULT, new String[]{PhoneConstants.APN_TYPE_ALL});
+        doReturn(false).when(mApnContext).hasNoRestrictedRequests(eq(true));
+
+        mDct.setUserDataEnabled(true);
+        mDct.setDataRoamingEnabledByUser(false);
+        mBundle.putStringArray(CarrierConfigManager.KEY_CARRIER_METERED_APN_TYPES_STRINGS,
+                new String[]{PhoneConstants.APN_TYPE_DEFAULT});
+        //user is in roaming
+        doReturn(true).when(mServiceState).getDataRoaming();
+
+        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);
+
+        // expect no restricted data connection
+        verify(mSimulatedCommandsVerifier, times(0)).setupDataCall(anyInt(), any(DataProfile.class),
+                eq(false), eq(false), eq(DataService.REQUEST_REASON_NORMAL), any(),
+                any(Message.class));
+    }
+
     // Test the default data when data is not connectable.
     @Test
     @SmallTest
@@ -1175,7 +1263,7 @@
                 .getRilDataRadioTechnology();
         mBundle.putStringArray(CarrierConfigManager.KEY_CARRIER_METERED_APN_TYPES_STRINGS,
                 new String[]{PhoneConstants.APN_TYPE_DEFAULT});
-        mDct.setEnabled(0, true);
+        mDct.setEnabled(ApnSetting.TYPE_DEFAULT, true);
         mDct.setUserDataEnabled(true);
         initApns(PhoneConstants.APN_TYPE_DEFAULT, new String[]{PhoneConstants.APN_TYPE_ALL});
 
@@ -1230,7 +1318,7 @@
         assertEquals(DctConstants.State.CONNECTED, mDct.getOverallState());
     }
 
-// Test for fetchDunApn()
+    // Test for fetchDunApns()
     @Test
     @SmallTest
     public void testFetchDunApn() {
@@ -1245,15 +1333,48 @@
         Settings.Global.putString(mContext.getContentResolver(),
                 Settings.Global.TETHER_DUN_APN, dunApnString);
         // should return APN from Setting
-        ApnSetting dunApn = mDct.fetchDunApn();
+        ApnSetting dunApn = mDct.fetchDunApns().get(0);
         assertTrue(dunApnExpected.equals(dunApn));
 
         Settings.Global.putString(mContext.getContentResolver(),
                 Settings.Global.TETHER_DUN_APN, null);
         // should return APN from db
-        dunApn = mDct.fetchDunApn();
-        assertEquals(FAKE_APN5, dunApn.apn);
+        dunApn = mDct.fetchDunApns().get(0);
+        assertEquals(FAKE_APN5, dunApn.getApnName());
     }
+
+    // Test for fetchDunApns() with apn set id
+    @Test
+    @SmallTest
+    public void testFetchDunApnWithPreferredApnSet() {
+        logd("Sending EVENT_RECORDS_LOADED");
+        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_RECORDS_LOADED, null));
+        waitForMs(200);
+
+        // apnSetId=1
+        String dunApnString1 = "[ApnSettingV5]HOT mobile PC,pc.hotm,,,,,,,,,440,10,,DUN,,,true,"
+                + "0,,,,,,,,,,1";
+        // apnSetId=0
+        String dunApnString2 = "[ApnSettingV5]HOT mobile PC,pc.coldm,,,,,,,,,440,10,,DUN,,,true,"
+                + "0,,,,,,,,,,0";
+
+        ApnSetting dunApnExpected = ApnSetting.fromString(dunApnString1);
+
+        ContentResolver cr = mContext.getContentResolver();
+        Settings.Global.putString(cr, Settings.Global.TETHER_DUN_APN,
+                dunApnString1 + ";" + dunApnString2);
+
+        // set that we prefer apn set 1
+        ContentValues values = new ContentValues();
+        values.put(Telephony.Carriers.APN_SET_ID, 1);
+        cr.update(PREFERAPN_URI, values, null, null);
+
+        // return APN from Setting with apnSetId=1
+        ArrayList<ApnSetting> dunApns = mDct.sortApnListByPreferred(mDct.fetchDunApns());
+        assertEquals(2, dunApns.size());
+        assertTrue(dunApnExpected.equals(dunApns.get(0)));
+    }
+
     // Test oos
     @Test
     @SmallTest
@@ -1262,7 +1383,7 @@
                 .getRilDataRadioTechnology();
         mBundle.putStringArray(CarrierConfigManager.KEY_CARRIER_METERED_APN_TYPES_STRINGS,
                 new String[]{PhoneConstants.APN_TYPE_DEFAULT});
-        mDct.setEnabled(0, true);
+        mDct.setEnabled(ApnSetting.TYPE_DEFAULT, true);
         mDct.setUserDataEnabled(true);
         initApns(PhoneConstants.APN_TYPE_DEFAULT, new String[]{PhoneConstants.APN_TYPE_ALL});
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/RetryManagerTest.java b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/RetryManagerTest.java
index 6b00289..2f452f6 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/RetryManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/RetryManagerTest.java
@@ -16,8 +16,12 @@
 
 package com.android.internal.telephony.dataconnection;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
 import android.os.PersistableBundle;
 import android.telephony.CarrierConfigManager;
+import android.telephony.data.ApnSetting;
 import android.test.suitebuilder.annotation.SmallTest;
 
 import com.android.internal.telephony.RetryManager;
@@ -31,97 +35,91 @@
 
 import java.util.ArrayList;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
 /**
  * APN retry manager tests
  */
 public class RetryManagerTest extends TelephonyTest {
 
     // This is the real APN data for the Japanese carrier NTT Docomo.
-    private ApnSetting mApn1 = new ApnSetting(
+    private ApnSetting mApn1 = ApnSetting.makeApnSetting(
             2163,                   // id
             "44010",                // numeric
             "sp-mode",              // name
             "spmode.ne.jp",         // apn
-            "",                     // proxy
-            "",                     // port
-            "",                     // mmsc
-            "",                     // mmsproxy
-            "",                     // mmsport
+            null,                     // proxy
+            -1,                     // port
+            null,                     // mmsc
+            null,                     // mmsproxy
+            -1,                     // mmsport
             "",                     // user
             "",                     // password
             -1,                     // authtype
-            new String[]{"default", "supl"},     // types
-            "IP",                   // protocol
-            "IP",                   // roaming_protocol
+            ApnSetting.TYPE_DEFAULT | ApnSetting.TYPE_SUPL, // types
+            ApnSetting.PROTOCOL_IP, // protocol
+            ApnSetting.PROTOCOL_IP, // roaming_protocol
             true,                   // carrier_enabled
-            0,                      // bearer
-            0,                      // bearer_bitmask
+            0,                      // networktype_bitmask
             0,                      // profile_id
             false,                  // modem_cognitive
             0,                      // max_conns
             0,                      // wait_time
             0,                      // max_conns_time
             0,                      // mtu
-            "",                     // mvno_type
+            -1,                     // mvno_type
             "");                    // mnvo_match_data
 
-    private ApnSetting mApn2 = new ApnSetting(
+    private ApnSetting mApn2 = ApnSetting.makeApnSetting(
             2164,                   // id
             "44010",                // numeric
             "mopera U",             // name
             "mopera.net",           // apn
-            "",                     // proxy
-            "",                     // port
-            "",                     // mmsc
-            "",                     // mmsproxy
-            "",                     // mmsport
+            null,                     // proxy
+            -1,                     // port
+            null,                     // mmsc
+            null,                     // mmsproxy
+            -1,                     // mmsport
             "",                     // user
             "",                     // password
             -1,                     // authtype
-            new String[]{"default", "supl"},     // types
-            "IP",                   // protocol
-            "IP",                   // roaming_protocol
+            ApnSetting.TYPE_DEFAULT | ApnSetting.TYPE_SUPL,     // types
+            ApnSetting.PROTOCOL_IP,                   // protocol
+            ApnSetting.PROTOCOL_IP,                   // roaming_protocol
             true,                   // carrier_enabled
-            0,                      // bearer
-            0,                      // bearer_bitmask
+            0,                      // networktype_bitmask
             0,                      // profile_id
             false,                  // modem_cognitive
             0,                      // max_conns
             0,                      // wait_time
             0,                      // max_conns_time
             0,                      // mtu
-            "",                     // mvno_type
+            -1,                     // mvno_type
             "");                    // mnvo_match_data
 
-    private ApnSetting mApn3 = new ApnSetting(
+    private ApnSetting mApn3 = ApnSetting.makeApnSetting(
             2165,                   // id
             "44010",                // numeric
             "b-mobile for Nexus",   // name
             "bmobile.ne.jp",        // apn
-            "",                     // proxy
-            "",                     // port
-            "",                     // mmsc
-            "",                     // mmsproxy
-            "",                     // mmsport
+            null,                     // proxy
+            -1,                     // port
+            null,                     // mmsc
+            null,                     // mmsproxy
+            -1,                     // mmsport
             "",                     // user
             "",                     // password
             3,                      // authtype
-            new String[]{"default", "supl"},     // types
-            "IP",                   // protocol
-            "IP",                   // roaming_protocol
+            ApnSetting.TYPE_DEFAULT | ApnSetting.TYPE_SUPL,     // types
+            ApnSetting.PROTOCOL_IP,                   // protocol
+            ApnSetting.PROTOCOL_IP,                   // roaming_protocol
             true,                   // carrier_enabled
-            0,                      // bearer
-            0,                      // bearer_bitmask
+            0,                      // networktype_bitmask
             0,                      // profile_id
             false,                  // modem_cognitive
             0,                      // max_conns
             0,                      // wait_time
             0,                      // max_conns_time
             0,                      // mtu
-            "",                     // mvno_type
+            -1,                     // mvno_type
             "");                    // mnvo_match_data
 
     private PersistableBundle mBundle;
@@ -173,7 +171,7 @@
                 new String[]{"default:"});
 
         ArrayList<ApnSetting> waitingApns = new ArrayList<ApnSetting>();
-        waitingApns.add(new ApnSetting(mApn1));
+        waitingApns.add(ApnSetting.makeApnSetting(mApn1));
 
         RetryManager rm = new RetryManager(mPhone, "default");
         rm.setWaitingApns(waitingApns);
@@ -195,7 +193,7 @@
                 new String[]{"supl:2000,3000"});
 
         ArrayList<ApnSetting> waitingApns = new ArrayList<ApnSetting>();
-        waitingApns.add(new ApnSetting(mApn1));
+        waitingApns.add(ApnSetting.makeApnSetting(mApn1));
 
         RetryManager rm = new RetryManager(mPhone, "supl");
         rm.setWaitingApns(waitingApns);
@@ -249,8 +247,8 @@
                 new String[]{"others:2000"});
 
         ArrayList<ApnSetting> waitingApns = new ArrayList<ApnSetting>();
-        waitingApns.add(new ApnSetting(mApn1));
-        waitingApns.add(new ApnSetting(mApn2));
+        waitingApns.add(ApnSetting.makeApnSetting(mApn1));
+        waitingApns.add(ApnSetting.makeApnSetting(mApn2));
 
         RetryManager rm = new RetryManager(mPhone, "default");
         rm.setWaitingApns(waitingApns);
@@ -287,8 +285,8 @@
                 new String[]{"dun:2000,5000"});
 
         ArrayList<ApnSetting> waitingApns = new ArrayList<ApnSetting>();
-        waitingApns.add(new ApnSetting(mApn1));
-        waitingApns.add(new ApnSetting(mApn2));
+        waitingApns.add(ApnSetting.makeApnSetting(mApn1));
+        waitingApns.add(ApnSetting.makeApnSetting(mApn2));
 
         RetryManager rm = new RetryManager(mPhone, "dun");
         rm.setWaitingApns(waitingApns);
@@ -335,8 +333,8 @@
                 new String[]{"mms:      3000,6000"});
 
         ArrayList<ApnSetting> waitingApns = new ArrayList<ApnSetting>();
-        waitingApns.add(new ApnSetting(mApn1));
-        waitingApns.add(new ApnSetting(mApn2));
+        waitingApns.add(ApnSetting.makeApnSetting(mApn1));
+        waitingApns.add(ApnSetting.makeApnSetting(mApn2));
 
         RetryManager rm = new RetryManager(mPhone, "mms");
         rm.setWaitingApns(waitingApns);
@@ -383,7 +381,7 @@
                 new String[]{"fota:1000,4000,7000"});
 
         ArrayList<ApnSetting> waitingApns = new ArrayList<ApnSetting>();
-        ApnSetting apn = new ApnSetting(mApn1);
+        ApnSetting apn = ApnSetting.makeApnSetting(mApn1);
         waitingApns.add(apn);
 
         RetryManager rm = new RetryManager(mPhone, "fota");
@@ -416,8 +414,8 @@
                 new String[]{"xyz  :   1000,4000,7000"});
 
         ArrayList<ApnSetting> waitingApns = new ArrayList<ApnSetting>();
-        ApnSetting myApn1 = new ApnSetting(mApn1);
-        ApnSetting myApn2 = new ApnSetting(mApn2);
+        ApnSetting myApn1 = ApnSetting.makeApnSetting(mApn1);
+        ApnSetting myApn2 = ApnSetting.makeApnSetting(mApn2);
         waitingApns.add(myApn1);
         waitingApns.add(myApn2);
 
@@ -473,9 +471,9 @@
                 new String[]{"default:2000:2000,3000:3000", "ims:1000,4000"});
 
         ArrayList<ApnSetting> waitingApns = new ArrayList<ApnSetting>();
-        ApnSetting myApn1 = new ApnSetting(mApn1);
-        ApnSetting myApn2 = new ApnSetting(mApn2);
-        ApnSetting myApn3 = new ApnSetting(mApn3);
+        ApnSetting myApn1 = ApnSetting.makeApnSetting(mApn1);
+        ApnSetting myApn2 = ApnSetting.makeApnSetting(mApn2);
+        ApnSetting myApn3 = ApnSetting.makeApnSetting(mApn3);
         waitingApns.add(myApn1);
         waitingApns.add(myApn2);
         waitingApns.add(myApn3);
@@ -532,8 +530,8 @@
                 new String[]{"default:1000,4000,7000,9000", "mms:1234,4123"});
 
         ArrayList<ApnSetting> waitingApns = new ArrayList<ApnSetting>();
-        ApnSetting myApn1 = new ApnSetting(mApn1);
-        ApnSetting myApn2 = new ApnSetting(mApn2);
+        ApnSetting myApn1 = ApnSetting.makeApnSetting(mApn1);
+        ApnSetting myApn2 = ApnSetting.makeApnSetting(mApn2);
         waitingApns.add(myApn1);
         waitingApns.add(myApn2);
 
@@ -587,7 +585,7 @@
                 new String[]{"default:default_randomization=1000,3000:2000,6000:3000,10000"});
 
         ArrayList<ApnSetting> waitingApns = new ArrayList<ApnSetting>();
-        waitingApns.add(new ApnSetting(mApn1));
+        waitingApns.add(ApnSetting.makeApnSetting(mApn1));
 
         RetryManager rm = new RetryManager(mPhone, "default");
         rm.setWaitingApns(waitingApns);
@@ -624,8 +622,8 @@
                 new String[]{"default:max_retries=infinite,1000,2000"});
 
         ArrayList<ApnSetting> waitingApns = new ArrayList<ApnSetting>();
-        waitingApns.add(new ApnSetting(mApn1));
-        waitingApns.add(new ApnSetting(mApn2));
+        waitingApns.add(ApnSetting.makeApnSetting(mApn1));
+        waitingApns.add(ApnSetting.makeApnSetting(mApn2));
 
         RetryManager rm = new RetryManager(mPhone, "default");
         rm.setWaitingApns(waitingApns);
@@ -682,8 +680,8 @@
                 new String[]{"hipri:  max_retries=4,1000,2000"});
 
         ArrayList<ApnSetting> waitingApns = new ArrayList<ApnSetting>();
-        waitingApns.add(new ApnSetting(mApn1));
-        waitingApns.add(new ApnSetting(mApn2));
+        waitingApns.add(ApnSetting.makeApnSetting(mApn1));
+        waitingApns.add(ApnSetting.makeApnSetting(mApn2));
 
         RetryManager rm = new RetryManager(mPhone, "hipri");
         rm.setWaitingApns(waitingApns);
@@ -752,8 +750,8 @@
         mBundle.putLong(CarrierConfigManager.KEY_CARRIER_DATA_CALL_APN_DELAY_FASTER_LONG, 2000);
 
         ArrayList<ApnSetting> waitingApns = new ArrayList<ApnSetting>();
-        waitingApns.add(new ApnSetting(mApn1));
-        waitingApns.add(new ApnSetting(mApn2));
+        waitingApns.add(ApnSetting.makeApnSetting(mApn1));
+        waitingApns.add(ApnSetting.makeApnSetting(mApn2));
 
         RetryManager rm = new RetryManager(mPhone, "default");
         rm.setWaitingApns(waitingApns);
@@ -800,8 +798,8 @@
                 new String[]{"dun:1000,4000,7000,9000"});
 
         ArrayList<ApnSetting> waitingApns = new ArrayList<ApnSetting>();
-        ApnSetting myApn1 = new ApnSetting(mApn1);
-        ApnSetting myApn2 = new ApnSetting(mApn2);
+        ApnSetting myApn1 = ApnSetting.makeApnSetting(mApn1);
+        ApnSetting myApn2 = ApnSetting.makeApnSetting(mApn2);
         waitingApns.add(myApn1);
         waitingApns.add(myApn2);
 
@@ -845,7 +843,7 @@
 
         // reset the retry manager
 
-        ApnSetting myApn3 = new ApnSetting(mApn3);
+        ApnSetting myApn3 = ApnSetting.makeApnSetting(mApn3);
         waitingApns.clear();
         waitingApns.add(myApn3);
 
@@ -881,8 +879,8 @@
                 new String[]{"others:1000,4000,7000,9000"});
 
         ArrayList<ApnSetting> waitingApns = new ArrayList<ApnSetting>();
-        ApnSetting myApn1 = new ApnSetting(mApn1);
-        ApnSetting myApn2 = new ApnSetting(mApn2);
+        ApnSetting myApn1 = ApnSetting.makeApnSetting(mApn1);
+        ApnSetting myApn2 = ApnSetting.makeApnSetting(mApn2);
         waitingApns.add(myApn1);
         waitingApns.add(myApn2);
 
@@ -937,8 +935,8 @@
                 new String[]{"default:1000,4000,7000,9000"});
 
         ArrayList<ApnSetting> waitingApns = new ArrayList<ApnSetting>();
-        ApnSetting myApn1 = new ApnSetting(mApn1);
-        ApnSetting myApn2 = new ApnSetting(mApn2);
+        ApnSetting myApn1 = ApnSetting.makeApnSetting(mApn1);
+        ApnSetting myApn2 = ApnSetting.makeApnSetting(mApn2);
         waitingApns.add(myApn1);
         waitingApns.add(myApn2);
 
@@ -980,8 +978,8 @@
                 new String[]{"mms:2000,3000", "default:1000,4000,7000,9000"});
 
         ArrayList<ApnSetting> waitingApns = new ArrayList<ApnSetting>();
-        ApnSetting myApn1 = new ApnSetting(mApn1);
-        ApnSetting myApn2 = new ApnSetting(mApn2);
+        ApnSetting myApn1 = ApnSetting.makeApnSetting(mApn1);
+        ApnSetting myApn2 = ApnSetting.makeApnSetting(mApn2);
         waitingApns.add(myApn1);
         waitingApns.add(myApn2);
 
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 88a7d9d..f7d8671 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccControllerTest.java
@@ -837,7 +837,7 @@
     private void setHasCarrierPrivilegesOnActiveSubscription(boolean hasPrivileges)
             throws Exception {
         SubscriptionInfo subInfo = new SubscriptionInfo(
-                0, "", 0, "", "", 0, 0, "", 0, null, 0, 0, "", true /* isEmbedded */,
+                0, "", 0, "", "", 0, 0, "", 0, null, "0", "0", "", true /* isEmbedded */,
                 hasPrivileges ? new UiccAccessRule[] { ACCESS_RULE } : null);
         when(mSubscriptionManager.canManageSubscription(subInfo, PACKAGE_NAME)).thenReturn(
                 hasPrivileges);
@@ -847,7 +847,7 @@
 
     private void prepareOperationSubscription(boolean hasPrivileges) throws Exception {
         SubscriptionInfo subInfo = new SubscriptionInfo(
-                SUBSCRIPTION_ID, ICC_ID, 0, "", "", 0, 0, "", 0, null, 0, 0, "",
+                SUBSCRIPTION_ID, ICC_ID, 0, "", "", 0, 0, "", 0, null, "0", "0", "",
                 true /* isEmbedded */, hasPrivileges ? new UiccAccessRule[] { ACCESS_RULE } : null);
         when(mSubscriptionManager.canManageSubscription(subInfo, PACKAGE_NAME)).thenReturn(
                 hasPrivileges);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/gsm/GSMPhoneTest.java.broken b/tests/telephonytests/src/com/android/internal/telephony/gsm/GSMPhoneTest.java.broken
index 33836ac..2e9b88c 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/gsm/GSMPhoneTest.java.broken
+++ b/tests/telephonytests/src/com/android/internal/telephony/gsm/GSMPhoneTest.java.broken
@@ -59,6 +59,7 @@
     private static final int EVENT_IN_SERVICE = 10;
     private static final int SUPP_SERVICE_FAILED = 11;
     private static final int SERVICE_STATE_CHANGED = 12;
+    private static final int EVENT_OEM_RIL_MESSAGE = 13;
     public static final int ANY_MESSAGE = -1;
 
     @Override
@@ -1781,6 +1782,95 @@
         assertEquals(MmiCode.State.CANCELLED, mmi.getState());
     }
 
+    public void testRilHooks() throws Exception {
+        //
+        // These test cases all assume the RIL OEM hooks
+        // just echo back their input
+        //
+
+        Message msg;
+        AsyncResult ar;
+
+        // null byte array
+
+        mGSMPhone.invokeOemRilRequestRaw(null, mHandler.obtainMessage(EVENT_OEM_RIL_MESSAGE));
+
+        msg = mGSMTestHandler.waitForMessage(EVENT_OEM_RIL_MESSAGE);
+        assertNotNull("Message Time Out", msg);
+
+        ar = ((AsyncResult) msg.obj);
+
+        assertNull(ar.result);
+        assertNull(ar.exception);
+
+        // empty byte array
+
+        mGSMPhone.invokeOemRilRequestRaw(new byte[0], mHandler.obtainMessage(EVENT_OEM_RIL_MESSAGE));
+
+        msg = mGSMTestHandler.waitForMessage(EVENT_OEM_RIL_MESSAGE);
+        assertNotNull("Message Time Out", msg);
+
+        ar = ((AsyncResult) msg.obj);
+
+        assertEquals(0, ((byte[]) (ar.result)).length);
+        assertNull(ar.exception);
+
+        // byte array with data
+
+        mGSMPhone.invokeOemRilRequestRaw("Hello".getBytes("utf-8"),
+                mHandler.obtainMessage(EVENT_OEM_RIL_MESSAGE));
+
+        msg = mGSMTestHandler.waitForMessage(EVENT_OEM_RIL_MESSAGE);
+        assertNotNull("Message Time Out", msg);
+
+        ar = ((AsyncResult) msg.obj);
+
+        assertEquals("Hello", new String(((byte[]) (ar.result)), "utf-8"));
+        assertNull(ar.exception);
+
+        // null strings
+
+        mGSMPhone.invokeOemRilRequestStrings(null, mHandler.obtainMessage(EVENT_OEM_RIL_MESSAGE));
+
+        msg = mGSMTestHandler.waitForMessage(EVENT_OEM_RIL_MESSAGE);
+        assertNotNull("Message Time Out", msg);
+
+        ar = ((AsyncResult) msg.obj);
+
+        assertNull(ar.result);
+        assertNull(ar.exception);
+
+        // empty byte array
+
+        mGSMPhone.invokeOemRilRequestStrings(new String[0],
+                mHandler.obtainMessage(EVENT_OEM_RIL_MESSAGE));
+
+        msg = mGSMTestHandler.waitForMessage(EVENT_OEM_RIL_MESSAGE);
+        assertNotNull("Message Time Out", msg);
+
+        ar = ((AsyncResult) msg.obj);
+
+        assertEquals(0, ((String[]) (ar.result)).length);
+        assertNull(ar.exception);
+
+        // Strings with data
+
+        String s[] = new String[1];
+
+        s[0] = "Hello";
+
+        mGSMPhone.invokeOemRilRequestStrings(s, mHandler.obtainMessage(EVENT_OEM_RIL_MESSAGE));
+
+        msg = mGSMTestHandler.waitForMessage(EVENT_OEM_RIL_MESSAGE);
+        assertNotNull("Message Time Out", msg);
+
+        ar = ((AsyncResult) msg.obj);
+
+        assertEquals("Hello", ((String[]) (ar.result))[0]);
+        assertEquals(1, ((String[]) (ar.result)).length);
+        assertNull(ar.exception);
+    }
+
     public void testMmi() throws Exception {
         mRadioControl.setAutoProgressConnectingCall(false);
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/gsm/UsimDataDownloadCommands.java.broken b/tests/telephonytests/src/com/android/internal/telephony/gsm/UsimDataDownloadCommands.java.broken
index 325aa67..3db8adc 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/gsm/UsimDataDownloadCommands.java.broken
+++ b/tests/telephonytests/src/com/android/internal/telephony/gsm/UsimDataDownloadCommands.java.broken
@@ -520,6 +520,14 @@
     }
 
     @Override
+    public void invokeOemRilRequestRaw(byte[] data, Message response) {
+    }
+
+    @Override
+    public void invokeOemRilRequestStrings(String[] strings, Message response) {
+    }
+
+    @Override
     public void sendTerminalResponse(String contents, Message response) {
     }
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ims/ImsManagerTest.java b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsManagerTest.java
index f217c7d..fd19f80 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ims/ImsManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsManagerTest.java
@@ -22,6 +22,7 @@
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.anyString;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -35,6 +36,7 @@
 
 import com.android.ims.ImsConfig;
 import com.android.ims.ImsManager;
+import com.android.ims.MmTelFeatureConnection;
 import com.android.internal.telephony.TelephonyTest;
 
 import org.junit.After;
@@ -47,13 +49,16 @@
 public class ImsManagerTest extends TelephonyTest {
     private static final String UNSET_PROVISIONED_STRING = "unset";
     private static final boolean ENHANCED_4G_MODE_DEFAULT_VAL = true;
-    private static final boolean ENHANCED_4G_ENABLE_DEFAULT_VAL = true;
     private static final boolean ENHANCED_4G_MODE_EDITABLE = true;
     private static final boolean WFC_IMS_ENABLE_DEFAULT_VAL = false;
     private static final boolean WFC_IMS_ROAMING_ENABLE_DEFAULT_VAL = true;
     private static final boolean VT_IMS_ENABLE_DEFAULT_VAL = true;
-    private static final int WFC_IMS_MODE_DEFAULT_VAL = 2;
-    private static final int WFC_IMS_ROAMING_MODE_DEFAULT_VAL = 3;
+    private static final boolean WFC_IMS_EDITABLE_VAL = true;
+    private static final boolean WFC_IMS_NOT_EDITABLE_VAL = false;
+    private static final int WFC_IMS_MODE_DEFAULT_VAL =
+            ImsConfig.WfcModeFeatureValueConstants.CELLULAR_PREFERRED;
+    private static final int WFC_IMS_ROAMING_MODE_DEFAULT_VAL =
+            ImsConfig.WfcModeFeatureValueConstants.WIFI_PREFERRED;
 
     PersistableBundle mBundle;
 
@@ -64,7 +69,7 @@
     Hashtable<Integer, Integer> mProvisionedIntVals = new Hashtable<>();
     Hashtable<Integer, String> mProvisionedStringVals = new Hashtable<>();
     ImsConfigImplBase.ImsConfigStub mImsConfigStub;
-    ImsConfig mImsConfig;
+    @Mock MmTelFeatureConnection mMmTelFeatureConnection;
 
     private final int[] mSubId = {0};
     private int mPhoneId;
@@ -80,6 +85,8 @@
         doReturn(mSubscriptionController).when(mBinder).queryLocalInterface(anyString());
         mServiceManagerMockedServices.put("isub", mBinder);
 
+        doReturn(true).when(mMmTelFeatureConnection).isBinderAlive();
+
         mImsManagerInstances.remove(mPhoneId);
 
         setDefaultValues();
@@ -92,7 +99,9 @@
 
     private void setDefaultValues() {
         mBundle.putBoolean(CarrierConfigManager.KEY_EDITABLE_ENHANCED_4G_LTE_BOOL,
-                ENHANCED_4G_ENABLE_DEFAULT_VAL);
+                ENHANCED_4G_MODE_EDITABLE);
+        mBundle.putBoolean(CarrierConfigManager.KEY_EDITABLE_WFC_MODE_BOOL,
+                WFC_IMS_EDITABLE_VAL);
         mBundle.putBoolean(CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_ENABLED_BOOL,
                 WFC_IMS_ENABLE_DEFAULT_VAL);
         mBundle.putBoolean(CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_ROAMING_ENABLED_BOOL,
@@ -119,7 +128,7 @@
                 eq(SubscriptionManager.WFC_IMS_ENABLED),
                 anyString());
 
-        assertEquals(ENHANCED_4G_ENABLE_DEFAULT_VAL,
+        assertEquals(ENHANCED_4G_MODE_DEFAULT_VAL,
                 imsManager.isEnhanced4gLteModeSettingEnabledByUser());
         verify(mSubscriptionController, times(1)).getSubscriptionProperty(
                 anyInt(),
@@ -239,7 +248,126 @@
 
     }
 
-    private ImsManager initializeProvisionedValues() {
+    /**
+     * Tests that when a WFC mode is set for home/roaming, that setting is sent to the ImsService
+     * correctly.
+     *
+     * Preconditions:
+     *  - CarrierConfigManager.KEY_EDITABLE_WFC_MODE_BOOL = true
+     */
+    @Test @SmallTest
+    public void testSetWfcSetting_true_shouldSetWfcModeWrtRoamingState() throws Exception {
+        // First, Set WFC home/roaming mode that is not the Carrier Config default.
+        doReturn(String.valueOf(ImsConfig.WfcModeFeatureValueConstants.WIFI_PREFERRED))
+                .when(mSubscriptionController).getSubscriptionProperty(
+                        anyInt(),
+                        eq(SubscriptionManager.WFC_IMS_MODE),
+                        anyString());
+        doReturn(String.valueOf(ImsConfig.WfcModeFeatureValueConstants.CELLULAR_PREFERRED))
+                .when(mSubscriptionController).getSubscriptionProperty(
+                        anyInt(),
+                        eq(SubscriptionManager.WFC_IMS_ROAMING_MODE),
+                        anyString());
+        ImsManager imsManager = initializeProvisionedValues();
+
+        // Roaming
+        doReturn(true).when(mTelephonyManager).isNetworkRoaming(eq(mSubId[0]));
+        // Turn on WFC
+        imsManager.setWfcSetting(true);
+        // Roaming mode (CELLULAR_PREFERRED) should be set. With 1000 ms timeout.
+        verify(mImsConfigImplBaseMock, timeout(1000)).setConfig(
+                eq(ImsConfig.ConfigConstants.VOICE_OVER_WIFI_MODE),
+                eq(ImsConfig.WfcModeFeatureValueConstants.CELLULAR_PREFERRED));
+
+        // Not roaming
+        doReturn(false).when(mTelephonyManager).isNetworkRoaming(eq(mSubId[0]));
+        // Turn on WFC
+        imsManager.setWfcSetting(true);
+        // Home mode (WIFI_PREFERRED) should be set. With 1000 ms timeout.
+        verify(mImsConfigImplBaseMock, timeout(1000)).setConfig(
+                eq(ImsConfig.ConfigConstants.VOICE_OVER_WIFI_MODE),
+                eq(ImsConfig.WfcModeFeatureValueConstants.WIFI_PREFERRED));
+    }
+
+    /**
+     * Tests that the settings for WFC mode are ignored if the Carrier sets the settings to not
+     * editable.
+     *
+     * Preconditions:
+     *  - CarrierConfigManager.KEY_EDITABLE_WFC_MODE_BOOL = false
+     */
+    @Test @SmallTest
+    public void testSetWfcSetting_wfcNotEditable() throws Exception {
+        mBundle.putBoolean(CarrierConfigManager.KEY_EDITABLE_WFC_MODE_BOOL,
+                WFC_IMS_NOT_EDITABLE_VAL);
+        // Set some values that are different than the defaults for WFC mode.
+        doReturn(String.valueOf(ImsConfig.WfcModeFeatureValueConstants.WIFI_ONLY))
+                .when(mSubscriptionController).getSubscriptionProperty(
+                anyInt(),
+                eq(SubscriptionManager.WFC_IMS_MODE),
+                anyString());
+        doReturn(String.valueOf(ImsConfig.WfcModeFeatureValueConstants.WIFI_ONLY))
+                .when(mSubscriptionController).getSubscriptionProperty(
+                anyInt(),
+                eq(SubscriptionManager.WFC_IMS_ROAMING_MODE),
+                anyString());
+        ImsManager imsManager = initializeProvisionedValues();
+
+        // Roaming
+        doReturn(true).when(mTelephonyManager).isNetworkRoaming(eq(mSubId[0]));
+        // Turn on WFC
+        imsManager.setWfcSetting(true);
+        // User defined setting for Roaming mode (WIFI_ONLY) should be set independent of whether or
+        // not WFC mode is editable. With 1000 ms timeout.
+        verify(mImsConfigImplBaseMock, timeout(1000)).setConfig(
+                eq(ImsConfig.ConfigConstants.VOICE_OVER_WIFI_MODE),
+                eq(ImsConfig.WfcModeFeatureValueConstants.WIFI_ONLY));
+
+        // Not roaming
+        doReturn(false).when(mTelephonyManager).isNetworkRoaming(eq(mSubId[0]));
+        // Turn on WFC
+        imsManager.setWfcSetting(true);
+        // Default Home mode (CELLULAR_PREFERRED) should be set. With 1000 ms timeout.
+        verify(mImsConfigImplBaseMock, timeout(1000)).setConfig(
+                eq(ImsConfig.ConfigConstants.VOICE_OVER_WIFI_MODE),
+                eq(WFC_IMS_MODE_DEFAULT_VAL));
+    }
+
+    /**
+     * Tests that the CarrierConfig defaults will be used if no setting is set in the Subscription
+     * Manager.
+     *
+     * Preconditions:
+     *  - CarrierConfigManager.KEY_EDITABLE_WFC_MODE_BOOL = true
+     *  - CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_MODE_INT = Carrier preferred
+     *  - CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_ROAMING_MODE_INT = WiFi preferred
+     */
+    @Test @SmallTest
+    public void testSetWfcSetting_noUserSettingSet() throws Exception {
+        ImsManager imsManager = initializeProvisionedValues();
+
+        // Roaming
+        doReturn(true).when(mTelephonyManager).isNetworkRoaming(eq(mSubId[0]));
+        // Turn on WFC
+        imsManager.setWfcSetting(true);
+
+        // Default Roaming mode (WIFI_PREFERRED) for carrier should be set. With 1000 ms timeout.
+        verify(mImsConfigImplBaseMock, timeout(1000)).setConfig(
+                eq(ImsConfig.ConfigConstants.VOICE_OVER_WIFI_MODE),
+                eq(WFC_IMS_ROAMING_MODE_DEFAULT_VAL));
+
+        // Not roaming
+        doReturn(false).when(mTelephonyManager).isNetworkRoaming(eq(mSubId[0]));
+        // Turn on WFC
+        imsManager.setWfcSetting(true);
+
+        // Default Home mode (CELLULAR_PREFERRED) for carrier should be set. With 1000 ms timeout.
+        verify(mImsConfigImplBaseMock, timeout(1000)).setConfig(
+                eq(ImsConfig.ConfigConstants.VOICE_OVER_WIFI_MODE),
+                eq(WFC_IMS_MODE_DEFAULT_VAL));
+    }
+
+    private ImsManager initializeProvisionedValues() throws Exception {
         when(mImsConfigImplBaseMock.getConfigInt(anyInt()))
                 .thenAnswer(invocation ->  {
                     return getProvisionedInt((Integer) (invocation.getArguments()[0]));
@@ -255,15 +383,13 @@
 
         // Configure ImsConfigStub
         mImsConfigStub = new ImsConfigImplBase.ImsConfigStub(mImsConfigImplBaseMock);
-        doReturn(mImsConfigStub).when(mImsConfigImplBaseMock).getIImsConfig();
-
-        // Configure ImsConfig
-        mImsConfig = new ImsConfig(mImsConfigStub, mContext);
+        doReturn(mImsConfigStub).when(mMmTelFeatureConnection).getConfigInterface();
 
         // Configure ImsManager
         ImsManager imsManager = ImsManager.getInstance(mContext, mPhoneId);
         try {
-            replaceInstance(ImsManager.class, "mConfig", imsManager, mImsConfig);
+            replaceInstance(ImsManager.class, "mMmTelFeatureConnection", imsManager,
+                    mMmTelFeatureConnection);
         } catch (Exception ex) {
             fail("failed with " + ex);
         }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ims/ImsResolverTest.java b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsResolverTest.java
index f6aafe3..3587b8b 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ims/ImsResolverTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsResolverTest.java
@@ -20,6 +20,7 @@
 import static junit.framework.Assert.assertNotNull;
 import static junit.framework.Assert.assertNull;
 import static junit.framework.Assert.assertTrue;
+import static junit.framework.TestCase.assertFalse;
 
 import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.Matchers.any;
@@ -47,10 +48,8 @@
 import android.telephony.CarrierConfigManager;
 import android.telephony.ims.ImsService;
 import android.telephony.ims.feature.ImsFeature;
+import android.telephony.ims.stub.ImsFeatureConfiguration;
 import android.test.suitebuilder.annotation.SmallTest;
-import android.util.Pair;
-
-import com.android.internal.telephony.PhoneConstants;
 
 import org.junit.After;
 import org.junit.Before;
@@ -58,6 +57,7 @@
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -80,13 +80,22 @@
     private static final ComponentName TEST_CARRIER_2_DEFAULT_NAME = new ComponentName(
             "TestCarrier2Pkg", "Carrier2ImsService");
 
-    @Mock Context mMockContext;
-    @Mock PackageManager mMockPM;
-    @Mock ImsResolver.SubscriptionManagerProxy mTestSubscriptionManagerProxy;
-    @Mock CarrierConfigManager mMockCarrierConfigManager;
+    @Mock
+    Context mMockContext;
+    @Mock
+    PackageManager mMockPM;
+    @Mock
+    ImsResolver.SubscriptionManagerProxy mTestSubscriptionManagerProxy;
+    @Mock
+    CarrierConfigManager mMockCarrierConfigManager;
+    @Mock
+    ImsResolver.ImsDynamicQueryManagerFactory mMockQueryManagerFactory;
+    @Mock
+    ImsServiceFeatureQueryManager mMockQueryManager;
     private ImsResolver mTestImsResolver;
     private BroadcastReceiver mTestPackageBroadcastReceiver;
     private BroadcastReceiver mTestCarrierConfigReceiver;
+    private ImsServiceFeatureQueryManager.Listener mDynamicQueryListener;
     private PersistableBundle[] mCarrierConfigs;
 
     @Before
@@ -104,26 +113,22 @@
 
     /**
      * Add a package to the package manager and make sure it is added to the cache of available
-     * ImsServices in the ImsResolver
+     * ImsServices in the ImsResolver.
      */
     @Test
     @SmallTest
-    public void testAddPackageToCache() {
+    public void testAddDevicePackageToCache() {
         setupResolver(1/*numSlots*/);
-        List<ResolveInfo> info = new ArrayList<>();
-        Set<String> features = new HashSet<>();
+        HashSet<String> features = new HashSet<>();
+        features.add(ImsResolver.METADATA_EMERGENCY_MMTEL_FEATURE);
         features.add(ImsResolver.METADATA_MMTEL_FEATURE);
         features.add(ImsResolver.METADATA_RCS_FEATURE);
-        info.add(getResolveInfo(TEST_DEVICE_DEFAULT_NAME, features, true));
-        // Only return info if not using the compat argument
-        when(mMockPM.queryIntentServicesAsUser(
-                argThat(argument -> ImsService.SERVICE_INTERFACE.equals(argument.getAction())),
-                anyInt(), anyInt())).thenReturn(info);
-        setConfigCarrierString(0, TEST_CARRIER_DEFAULT_NAME.getPackageName());
+        setupPackageQuery(TEST_DEVICE_DEFAULT_NAME, features, true);
+        setupController();
 
-        mTestImsResolver.populateCacheAndStartBind();
+        // Complete package manager lookup and cache.
+        startBind();
 
-        waitForHandlerAction(mTestImsResolver.getHandler(), TEST_TIMEOUT);
         ImsResolver.ImsServiceInfo testCachedService =
                 mTestImsResolver.getImsServiceInfoFromCache(
                         TEST_DEVICE_DEFAULT_NAME.getPackageName());
@@ -132,6 +137,35 @@
     }
 
     /**
+     * Add a carrier ImsService to the package manager and make sure the features declared here are
+     * ignored. We should only allow dynamic query for these services.
+     */
+    @Test
+    @SmallTest
+    public void testAddCarrierPackageToCache() {
+        setupResolver(1/*numSlots*/);
+        HashSet<String> features = new HashSet<>();
+        features.add(ImsResolver.METADATA_EMERGENCY_MMTEL_FEATURE);
+        features.add(ImsResolver.METADATA_MMTEL_FEATURE);
+        features.add(ImsResolver.METADATA_RCS_FEATURE);
+        setConfigCarrierString(0, TEST_CARRIER_DEFAULT_NAME.getPackageName());
+        setupPackageQuery(TEST_CARRIER_DEFAULT_NAME, features, true);
+        setupController();
+
+        // Complete package manager lookup and cache.
+        startBind();
+
+        ImsResolver.ImsServiceInfo testCachedService =
+                mTestImsResolver.getImsServiceInfoFromCache(
+                        TEST_CARRIER_DEFAULT_NAME.getPackageName());
+        assertNotNull(testCachedService);
+        // none of the manifest features we added above should be reported for carrier package.
+        assertTrue(testCachedService.getSupportedFeatures().isEmpty());
+        // we should report that the service does not use metadata to report features.
+        assertFalse(testCachedService.featureFromMetadata);
+    }
+
+    /**
      * Set the carrier config override value and ensure that ImsResolver calls .bind on that
      * package name with the correct ImsFeatures.
      */
@@ -139,45 +173,28 @@
     @SmallTest
     public void testCarrierPackageBind() throws RemoteException {
         setupResolver(1/*numSlots*/);
-        // Set CarrierConfig default package name and make it available to the package manager
+        // Setup the carrier features
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> features = new HashSet<>();
+        features.add(new ImsFeatureConfiguration.FeatureSlotPair(0, ImsFeature.FEATURE_MMTEL));
+        features.add(new ImsFeatureConfiguration.FeatureSlotPair(0, ImsFeature.FEATURE_RCS));
+        // Set CarrierConfig default package name and make it available as the CarrierConfig.
         setConfigCarrierString(0, TEST_CARRIER_DEFAULT_NAME.getPackageName());
-        List<ResolveInfo> info = new ArrayList<>();
-        Set<String> features = new HashSet<>();
-        features.add(ImsResolver.METADATA_MMTEL_FEATURE);
-        features.add(ImsResolver.METADATA_RCS_FEATURE);
-        info.add(getResolveInfo(TEST_CARRIER_DEFAULT_NAME, features, true));
-        // Only return info if not using the compat argument
-        when(mMockPM.queryIntentServicesAsUser(
-                argThat(argument -> ImsService.SERVICE_INTERFACE.equals(argument.getAction())),
-                anyInt(), anyInt())).thenReturn(info);
-        ImsServiceController controller = mock(ImsServiceController.class);
-        mTestImsResolver.setImsServiceControllerFactory(
-                new ImsResolver.ImsServiceControllerFactory() {
-                    @Override
-                    public String getServiceInterface() {
-                        return ImsService.SERVICE_INTERFACE;
-                    }
+        setupPackageQuery(TEST_CARRIER_DEFAULT_NAME, new HashSet<>(), true);
+        ImsServiceController controller = setupController();
 
-                    @Override
-                    public ImsServiceController create(Context context, ComponentName componentName,
-                            ImsServiceController.ImsServiceControllerCallbacks callbacks) {
-                        when(controller.getComponentName()).thenReturn(componentName);
-                        return controller;
-                    }
-                });
+        // Start bind to carrier service
+        startBind();
+        // setup features response
+        setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, features, 1);
 
-
-        mTestImsResolver.populateCacheAndStartBind();
-
-        waitForHandlerAction(mTestImsResolver.getHandler(), TEST_TIMEOUT);
-        verify(controller).bind(convertToHashSet(features, 0));
+        verify(controller).bind(features);
         verify(controller, never()).unbind();
         assertEquals(TEST_CARRIER_DEFAULT_NAME, controller.getComponentName());
     }
 
     /**
-     * Creates a carrier ImsService with a manifest that defines METADATA_EMERGENCY_MMTEL_FEATURE,
-     * ensure that the controller sets this capability.
+     * Creates a carrier ImsService that defines FEATURE_EMERGENCY_MMTEL and ensure that the
+     * controller sets this capability.
      */
     @Test
     @SmallTest
@@ -185,46 +202,23 @@
         setupResolver(1/*numSlots*/);
         // Set CarrierConfig default package name and make it available to the package manager
         setConfigCarrierString(0, TEST_CARRIER_DEFAULT_NAME.getPackageName());
-        List<ResolveInfo> info = new ArrayList<>();
-        Set<String> features = new HashSet<>();
-        features.add(ImsResolver.METADATA_EMERGENCY_MMTEL_FEATURE);
-        features.add(ImsResolver.METADATA_MMTEL_FEATURE);
-        features.add(ImsResolver.METADATA_RCS_FEATURE);
-        info.add(getResolveInfo(TEST_CARRIER_DEFAULT_NAME, features, true));
-        // Only return info if not using the compat argument
-        when(mMockPM.queryIntentServicesAsUser(
-                argThat(argument -> ImsService.SERVICE_INTERFACE.equals(argument.getAction())),
-                anyInt(), anyInt())).thenReturn(info);
-        ImsServiceController controller = mock(ImsServiceController.class);
-        mTestImsResolver.setImsServiceControllerFactory(
-                new ImsResolver.ImsServiceControllerFactory() {
-                    @Override
-                    public String getServiceInterface() {
-                        return ImsService.SERVICE_INTERFACE;
-                    }
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> features = new HashSet<>();
+        features.add(new ImsFeatureConfiguration.FeatureSlotPair(0, ImsFeature.FEATURE_MMTEL));
+        features.add(new ImsFeatureConfiguration.FeatureSlotPair(0, ImsFeature.FEATURE_RCS));
+        setupPackageQuery(TEST_CARRIER_DEFAULT_NAME, new HashSet<>(), true);
+        ImsServiceController controller = setupController();
 
-                    @Override
-                    public ImsServiceController create(Context context, ComponentName componentName,
-                            ImsServiceController.ImsServiceControllerCallbacks callbacks) {
-                        when(controller.getComponentName()).thenReturn(componentName);
-                        return controller;
-                    }
-                });
+        startBind();
+        setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, features, 1);
 
-
-        mTestImsResolver.populateCacheAndStartBind();
-        waitForHandlerAction(mTestImsResolver.getHandler(), TEST_TIMEOUT);
-        verify(controller).bind(convertToHashSet(features, 0));
+        verify(controller).bind(features);
         verify(controller, never()).unbind();
         assertEquals(TEST_CARRIER_DEFAULT_NAME, controller.getComponentName());
-
-        verify(controller).setCanPlaceEmergencyCalls(eq(true));
     }
 
     /**
-     * Creates a carrier ImsService with a manifest that doesn't define
-     * METADATA_EMERGENCY_MMTEL_FEATURE and then update the ImsService to define it. Ensure that the
-     * controller sets this capability once enabled.
+     * Creates a carrier ImsService that does not report FEATURE_EMERGENCY_MMTEL and then update the
+     * ImsService to define it. Ensure that the controller sets this capability once enabled.
      */
     @Test
     @SmallTest
@@ -232,96 +226,48 @@
         setupResolver(1/*numSlots*/);
         // Set CarrierConfig default package name and make it available to the package manager
         setConfigCarrierString(0, TEST_CARRIER_DEFAULT_NAME.getPackageName());
-        List<ResolveInfo> info = new ArrayList<>();
-        Set<String> features = new HashSet<>();
-        features.add(ImsResolver.METADATA_MMTEL_FEATURE);
-        info.add(getResolveInfo(TEST_CARRIER_DEFAULT_NAME, features, true));
-        // Only return info if not using the compat argument
-        when(mMockPM.queryIntentServicesAsUser(
-                argThat(argument -> ImsService.SERVICE_INTERFACE.equals(argument.getAction())),
-                anyInt(), anyInt())).thenReturn(info);
-        ImsServiceController controller = mock(ImsServiceController.class);
-        mTestImsResolver.setImsServiceControllerFactory(
-                new ImsResolver.ImsServiceControllerFactory() {
-                    @Override
-                    public String getServiceInterface() {
-                        return ImsService.SERVICE_INTERFACE;
-                    }
-
-                    @Override
-                    public ImsServiceController create(Context context, ComponentName componentName,
-                            ImsServiceController.ImsServiceControllerCallbacks callbacks) {
-                        when(controller.getComponentName()).thenReturn(componentName);
-                        return controller;
-                    }
-                });
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> features = new HashSet<>();
+        features.add(new ImsFeatureConfiguration.FeatureSlotPair(0, ImsFeature.FEATURE_MMTEL));
+        setupPackageQuery(TEST_CARRIER_DEFAULT_NAME, new HashSet<>(), true);
+        ImsServiceController controller = setupController();
 
         // Bind without emergency calling
-        mTestImsResolver.populateCacheAndStartBind();
-        waitForHandlerAction(mTestImsResolver.getHandler(), TEST_TIMEOUT);
-        verify(controller).bind(convertToHashSet(features, 0));
+        startBind();
+        setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, features, 1);
+        verify(controller).bind(features);
         verify(controller, never()).unbind();
         assertEquals(TEST_CARRIER_DEFAULT_NAME, controller.getComponentName());
-        // ensure emergency calling is disabled
-        verify(controller, never()).setCanPlaceEmergencyCalls(eq(true));
 
-        // Tell package manager that app has changed and service now supports emergency calling
-        Set<String> newFeatures = new HashSet<>();
-        newFeatures.add(ImsResolver.METADATA_EMERGENCY_MMTEL_FEATURE);
-        newFeatures.add(ImsResolver.METADATA_MMTEL_FEATURE);
-        info.clear();
-        info.add(getResolveInfo(TEST_CARRIER_DEFAULT_NAME, newFeatures, true));
-
-        Intent addPackageIntent = new Intent();
-        addPackageIntent.setAction(Intent.ACTION_PACKAGE_ADDED);
-        addPackageIntent.setData(new Uri.Builder().scheme("package")
-                .opaquePart(TEST_CARRIER_DEFAULT_NAME.getPackageName()).build());
-        mTestPackageBroadcastReceiver.onReceive(null, addPackageIntent);
-        waitForHandlerAction(mTestImsResolver.getHandler(), TEST_TIMEOUT);
+        packageChanged(TEST_CARRIER_DEFAULT_NAME.getPackageName());
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> newFeatures = new HashSet<>();
+        newFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(0,
+                ImsFeature.FEATURE_MMTEL));
+        newFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(0,
+                ImsFeature.FEATURE_EMERGENCY_MMTEL));
+        setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, newFeatures, 2);
 
         //Verify new feature is added to the carrier override.
         // add all features for slot 0
-        HashSet<Pair<Integer, Integer>> newCarrierFeatureSet =
-                convertToHashSet(newFeatures, 0);
-        verify(controller, atLeastOnce()).changeImsServiceFeatures(newCarrierFeatureSet);
-        verify(controller).setCanPlaceEmergencyCalls(eq(true));
+        verify(controller, atLeastOnce()).changeImsServiceFeatures(newFeatures);
     }
 
     /**
-     * Ensure that no ImsService is bound if there is no carrier or device package explictly set.
+     * Ensure that no ImsService is bound if there is no carrier or device package explicitly set.
      */
     @Test
     @SmallTest
     public void testDontBindWhenNullCarrierPackage() throws RemoteException {
         setupResolver(1/*numSlots*/);
-        List<ResolveInfo> info = new ArrayList<>();
-        Set<String> features = new HashSet<>();
-        features.add(ImsResolver.METADATA_MMTEL_FEATURE);
-        features.add(ImsResolver.METADATA_RCS_FEATURE);
-        info.add(getResolveInfo(TEST_CARRIER_DEFAULT_NAME, features, true));
-        when(mMockPM.queryIntentServicesAsUser(any(), anyInt(), anyInt())).thenReturn(info);
-        ImsServiceController controller = mock(ImsServiceController.class);
-        mTestImsResolver.setImsServiceControllerFactory(
-                new ImsResolver.ImsServiceControllerFactory() {
-                    @Override
-                    public String getServiceInterface() {
-                        return ImsService.SERVICE_INTERFACE;
-                    }
-
-                    @Override
-                    public ImsServiceController create(Context context, ComponentName componentName,
-                            ImsServiceController.ImsServiceControllerCallbacks callbacks) {
-                        when(controller.getComponentName()).thenReturn(componentName);
-                        return controller;
-                    }
-                });
+        setupPackageQuery(TEST_CARRIER_DEFAULT_NAME, new HashSet<>(), true);
+        ImsServiceController controller = setupController();
 
         // Set the CarrierConfig string to null so that ImsResolver will not bind to the available
         // Services
         setConfigCarrierString(0, null);
-        mTestImsResolver.populateCacheAndStartBind();
+        startBind();
 
         waitForHandlerAction(mTestImsResolver.getHandler(), TEST_TIMEOUT);
+        verify(mMockQueryManager, never()).startQuery(any(), any());
         verify(controller, never()).bind(any());
         verify(controller, never()).unbind();
     }
@@ -340,36 +286,21 @@
         features.add(ImsResolver.METADATA_RCS_FEATURE);
         // Use device default package, which will load the ImsService that the device provides
         info.add(getResolveInfo(TEST_DEVICE_DEFAULT_NAME, features, true));
-        info.add(getResolveInfo(TEST_CARRIER_DEFAULT_NAME, features, true));
-        // Only return info if not using the compat argument
-        when(mMockPM.queryIntentServicesAsUser(
-                argThat(argument -> ImsService.SERVICE_INTERFACE.equals(argument.getAction())),
-                anyInt(), anyInt())).thenReturn(info);
-        ImsServiceController controller = mock(ImsServiceController.class);
-        mTestImsResolver.setImsServiceControllerFactory(
-                new ImsResolver.ImsServiceControllerFactory() {
-                    @Override
-                    public String getServiceInterface() {
-                        return ImsService.SERVICE_INTERFACE;
-                    }
-
-                    @Override
-                    public ImsServiceController create(Context context, ComponentName componentName,
-                            ImsServiceController.ImsServiceControllerCallbacks callbacks) {
-                        when(controller.getComponentName()).thenReturn(componentName);
-                        return controller;
-                    }
-                });
+        info.add(getResolveInfo(TEST_CARRIER_DEFAULT_NAME, new HashSet<>(), true));
+        setupPackageQuery(info);
+        ImsServiceController controller = setupController();
 
 
-        mTestImsResolver.populateCacheAndStartBind();
-
+        startBind();
+        // Wait to make sure that there are no dynamic queries that are being completed.
         waitForHandlerAction(mTestImsResolver.getHandler(), TEST_TIMEOUT);
+
         // There is no carrier override set, so make sure that the ImsServiceController binds
         // to all SIMs.
-        HashSet<Pair<Integer, Integer>> featureSet = convertToHashSet(features, 0);
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> featureSet = convertToHashSet(features, 0);
         verify(controller).bind(featureSet);
         verify(controller, never()).unbind();
+        verify(mMockQueryManager, never()).startQuery(any(), any());
         assertEquals(TEST_DEVICE_DEFAULT_NAME, controller.getComponentName());
     }
 
@@ -388,34 +319,29 @@
         deviceFeatures.add(ImsResolver.METADATA_RCS_FEATURE);
         // Set the carrier override package for slot 0
         setConfigCarrierString(0, TEST_CARRIER_DEFAULT_NAME.getPackageName());
-        Set<String> carrierFeatures = new HashSet<>();
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> carrierFeatures = new HashSet<>();
         // Carrier service doesn't support the voice feature.
-        carrierFeatures.add(ImsResolver.METADATA_RCS_FEATURE);
+        carrierFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(0, ImsFeature.FEATURE_RCS));
         // Use device default package, which will load the ImsService that the device provides
         info.add(getResolveInfo(TEST_DEVICE_DEFAULT_NAME, deviceFeatures, true));
-        info.add(getResolveInfo(TEST_CARRIER_DEFAULT_NAME, carrierFeatures, true));
+        info.add(getResolveInfo(TEST_CARRIER_DEFAULT_NAME, new HashSet<>(), true));
         // Only return info if not using the compat argument
-        when(mMockPM.queryIntentServicesAsUser(
-                argThat(argument -> ImsService.SERVICE_INTERFACE.equals(argument.getAction())),
-                anyInt(), anyInt())).thenReturn(info);
+        setupPackageQuery(info);
         ImsServiceController deviceController = mock(ImsServiceController.class);
         ImsServiceController carrierController = mock(ImsServiceController.class);
         setImsServiceControllerFactory(deviceController, carrierController);
 
+        startBind();
+        setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, carrierFeatures, 1);
 
-        mTestImsResolver.populateCacheAndStartBind();
-
-        waitForHandlerAction(mTestImsResolver.getHandler(), TEST_TIMEOUT);
         // Verify that all features that have been defined for the carrier override are bound
-        HashSet<Pair<Integer, Integer>> carrierFeatureSet = convertToHashSet(carrierFeatures, 0);
-        carrierFeatureSet.addAll(convertToHashSet(carrierFeatures, 0));
-        verify(carrierController).bind(carrierFeatureSet);
+        verify(carrierController).bind(carrierFeatures);
         verify(carrierController, never()).unbind();
         assertEquals(TEST_CARRIER_DEFAULT_NAME, carrierController.getComponentName());
         // Verify that all features that are not defined in the carrier override are bound in the
         // device controller (including emergency voice for slot 0)
-        HashSet<Pair<Integer, Integer>> deviceFeatureSet = convertToHashSet(deviceFeatures, 0);
-        deviceFeatureSet.removeAll(carrierFeatureSet);
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> deviceFeatureSet =
+                convertToHashSet(deviceFeatures, 0);
         verify(deviceController).bind(deviceFeatureSet);
         verify(deviceController, never()).unbind();
         assertEquals(TEST_DEVICE_DEFAULT_NAME, deviceController.getComponentName());
@@ -431,7 +357,6 @@
         setupResolver(2/*numSlots*/);
         ImsServiceController deviceController = mock(ImsServiceController.class);
         ImsServiceController carrierController = mock(ImsServiceController.class);
-        mTestImsResolver.populateCacheAndStartBind();
 
         // Callback from mock ImsServiceControllers
         // All features on slot 1 should be the device default
@@ -464,30 +389,12 @@
         features.add(ImsResolver.METADATA_MMTEL_FEATURE);
         // Doesn't include RCS feature by default
         info.add(getResolveInfo(TEST_DEVICE_DEFAULT_NAME, features, true));
-        when(mMockPM.queryIntentServicesAsUser(
-                argThat(argument -> ImsService.SERVICE_INTERFACE.equals(
-                        argument.getAction())), anyInt(), anyInt()))
-                .thenReturn(info);
-        ImsServiceController controller = mock(ImsServiceController.class);
-        mTestImsResolver.setImsServiceControllerFactory(
-                new ImsResolver.ImsServiceControllerFactory() {
-                    @Override
-                    public String getServiceInterface() {
-                        return ImsService.SERVICE_INTERFACE;
-                    }
-
-                    @Override
-                    public ImsServiceController create(Context context, ComponentName componentName,
-                            ImsServiceController.ImsServiceControllerCallbacks callbacks) {
-                        when(controller.getComponentName()).thenReturn(componentName);
-                        return controller;
-                    }
-                });
-
+        setupPackageQuery(info);
+        ImsServiceController controller = setupController();
         // Bind using default features
-        mTestImsResolver.populateCacheAndStartBind();
-        waitForHandlerAction(mTestImsResolver.getHandler(), TEST_TIMEOUT);
-        HashSet<Pair<Integer, Integer>> featureSet = convertToHashSet(features, 0);
+        startBind();
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> featureSet =
+                convertToHashSet(features, 0);
         featureSet.addAll(convertToHashSet(features, 1));
         verify(controller).bind(featureSet);
 
@@ -497,16 +404,11 @@
         info.clear();
         info.add(getResolveInfo(TEST_DEVICE_DEFAULT_NAME, newFeatures, true));
 
-        // Tell the package manager that a new device feature is installed
-        Intent addPackageIntent = new Intent();
-        addPackageIntent.setAction(Intent.ACTION_PACKAGE_ADDED);
-        addPackageIntent.setData(new Uri.Builder().scheme("package")
-                .opaquePart(TEST_DEVICE_DEFAULT_NAME.getPackageName()).build());
-        mTestPackageBroadcastReceiver.onReceive(null, addPackageIntent);
-        waitForHandlerAction(mTestImsResolver.getHandler(), TEST_TIMEOUT);
+        packageChanged(TEST_DEVICE_DEFAULT_NAME.getPackageName());
 
         //Verify new feature is added to the device default.
-        HashSet<Pair<Integer, Integer>> newFeatureSet = convertToHashSet(newFeatures, 0);
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> newFeatureSet =
+                convertToHashSet(newFeatures, 0);
         newFeatureSet.addAll(convertToHashSet(newFeatures, 1));
         verify(controller).changeImsServiceFeatures(newFeatureSet);
     }
@@ -524,36 +426,36 @@
         deviceFeatures.add(ImsResolver.METADATA_MMTEL_FEATURE);
         // Set the carrier override package for slot 0
         setConfigCarrierString(0, TEST_CARRIER_DEFAULT_NAME.getPackageName());
-        Set<String> carrierFeatures = new HashSet<>();
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> carrierFeatures = new HashSet<>();
         // Carrier service doesn't support the emergency voice feature.
-        carrierFeatures.add(ImsResolver.METADATA_MMTEL_FEATURE);
-        carrierFeatures.add(ImsResolver.METADATA_RCS_FEATURE);
+        carrierFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(0,
+                ImsFeature.FEATURE_MMTEL));
+        carrierFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(0,
+                ImsFeature.FEATURE_RCS));
         // Use device default package, which will load the ImsService that the device provides
         info.add(getResolveInfo(TEST_DEVICE_DEFAULT_NAME, deviceFeatures, true));
-        info.add(getResolveInfo(TEST_CARRIER_DEFAULT_NAME, carrierFeatures, true));
-        when(mMockPM.queryIntentServicesAsUser(
-                argThat(argument -> ImsService.SERVICE_INTERFACE.equals(
-                        argument.getAction())), anyInt(), anyInt()))
-                .thenReturn(info);
+        info.add(getResolveInfo(TEST_CARRIER_DEFAULT_NAME, new HashSet<>(), true));
+        setupPackageQuery(info);
         ImsServiceController deviceController = mock(ImsServiceController.class);
         ImsServiceController carrierController = mock(ImsServiceController.class);
         setImsServiceControllerFactory(deviceController, carrierController);
 
-        mTestImsResolver.populateCacheAndStartBind();
+        startBind();
+        setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, carrierFeatures, 1);
 
-        waitForHandlerAction(mTestImsResolver.getHandler(), TEST_TIMEOUT);
         // Verify that all features that have been defined for the carrier override are bound
-        HashSet<Pair<Integer, Integer>> carrierFeatureSet = convertToHashSet(carrierFeatures, 0);
-        carrierFeatureSet.addAll(convertToHashSet(carrierFeatures, 0));
-        verify(carrierController).bind(carrierFeatureSet);
+        verify(carrierController).bind(carrierFeatures);
         verify(carrierController, never()).unbind();
         assertEquals(TEST_CARRIER_DEFAULT_NAME, carrierController.getComponentName());
         // Verify that all features that are not defined in the carrier override are bound in the
         // device controller (including emergency voice for slot 0)
-        HashSet<Pair<Integer, Integer>> deviceFeatureSet = convertToHashSet(deviceFeatures, 1);
-        deviceFeatures.removeAll(carrierFeatures);
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> deviceFeatureSet =
+                convertToHashSet(deviceFeatures, 1);
         deviceFeatureSet.addAll(convertToHashSet(deviceFeatures, 0));
-        verify(deviceController).bind(deviceFeatureSet);
+        deviceFeatureSet.removeAll(carrierFeatures);
+        // we will first have bound to device and then the features will change once the dynamic
+        // returns. So, instead of checking the bind parameters, we will check the change parameters
+        verify(deviceController).changeImsServiceFeatures(deviceFeatureSet);
         verify(deviceController, never()).unbind();
         assertEquals(TEST_DEVICE_DEFAULT_NAME, deviceController.getComponentName());
 
@@ -565,20 +467,15 @@
         info.add(getResolveInfo(TEST_DEVICE_DEFAULT_NAME, newDeviceFeatures, true));
 
         // Tell the package manager that a new device feature is installed
-        Intent addPackageIntent = new Intent();
-        addPackageIntent.setAction(Intent.ACTION_PACKAGE_ADDED);
-        addPackageIntent.setData(new Uri.Builder().scheme("package")
-                .opaquePart(TEST_DEVICE_DEFAULT_NAME.getPackageName()).build());
-        mTestPackageBroadcastReceiver.onReceive(null, addPackageIntent);
-        waitForHandlerAction(mTestImsResolver.getHandler(), TEST_TIMEOUT);
+        packageChanged(TEST_DEVICE_DEFAULT_NAME.getPackageName());
 
         //Verify new feature is added to the device default.
         // add all features for slot 1
-        HashSet<Pair<Integer, Integer>> newDeviceFeatureSet =
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> newDeviceFeatureSet =
                 convertToHashSet(newDeviceFeatures, 1);
-        // remove carrier overrides for slot 0
-        newDeviceFeatures.removeAll(carrierFeatures);
         newDeviceFeatureSet.addAll(convertToHashSet(newDeviceFeatures, 0));
+        // remove carrier overrides for slot 0
+        newDeviceFeatureSet.removeAll(carrierFeatures);
         verify(deviceController).changeImsServiceFeatures(newDeviceFeatureSet);
         verify(carrierController, never()).changeImsServiceFeatures(any());
     }
@@ -597,59 +494,47 @@
         deviceFeatures.add(ImsResolver.METADATA_RCS_FEATURE);
         // Set the carrier override package for slot 0
         setConfigCarrierString(0, TEST_CARRIER_DEFAULT_NAME.getPackageName());
-        Set<String> carrierFeatures = new HashSet<>();
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> carrierFeatures = new HashSet<>();
         // Carrier service doesn't support the emergency voice feature.
-        carrierFeatures.add(ImsResolver.METADATA_MMTEL_FEATURE);
+        carrierFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(0,
+                ImsFeature.FEATURE_MMTEL));
         // Use device default package, which will load the ImsService that the device provides
         info.add(getResolveInfo(TEST_DEVICE_DEFAULT_NAME, deviceFeatures, true));
-        info.add(getResolveInfo(TEST_CARRIER_DEFAULT_NAME, carrierFeatures, true));
-        when(mMockPM.queryIntentServicesAsUser(
-                argThat(argument -> ImsService.SERVICE_INTERFACE.equals(
-                        argument.getAction())), anyInt(), anyInt()))
-                .thenReturn(info);
+        info.add(getResolveInfo(TEST_CARRIER_DEFAULT_NAME, new HashSet<>(), true));
+        setupPackageQuery(info);
         ImsServiceController deviceController = mock(ImsServiceController.class);
         ImsServiceController carrierController = mock(ImsServiceController.class);
         setImsServiceControllerFactory(deviceController, carrierController);
 
-        mTestImsResolver.populateCacheAndStartBind();
-
-        waitForHandlerAction(mTestImsResolver.getHandler(), TEST_TIMEOUT);
+        startBind();
+        setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, carrierFeatures, 1);
         // Verify that all features that have been defined for the carrier override are bound
-        HashSet<Pair<Integer, Integer>> carrierFeatureSet = convertToHashSet(carrierFeatures, 0);
-        carrierFeatureSet.addAll(convertToHashSet(carrierFeatures, 0));
-        verify(carrierController).bind(carrierFeatureSet);
+        verify(carrierController).bind(carrierFeatures);
         verify(carrierController, never()).unbind();
         assertEquals(TEST_CARRIER_DEFAULT_NAME, carrierController.getComponentName());
         // Verify that all features that are not defined in the carrier override are bound in the
         // device controller (including emergency voice for slot 0)
-        HashSet<Pair<Integer, Integer>> deviceFeatureSet = convertToHashSet(deviceFeatures, 1);
-        deviceFeatures.removeAll(carrierFeatures);
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> deviceFeatureSet =
+                convertToHashSet(deviceFeatures, 1);
         deviceFeatureSet.addAll(convertToHashSet(deviceFeatures, 0));
-        verify(deviceController).bind(deviceFeatureSet);
+        deviceFeatureSet.removeAll(carrierFeatures);
+        // device ImsService will bind with all of its defined features first and then when the
+        // carrier query comes back, it will change. So, checking change instead of bind here.
+        verify(deviceController).changeImsServiceFeatures(deviceFeatureSet);
         verify(deviceController, never()).unbind();
         assertEquals(TEST_DEVICE_DEFAULT_NAME, deviceController.getComponentName());
 
         // add RCS to carrier features list
-        Set<String> newCarrierFeatures = new HashSet<>();
-        newCarrierFeatures.add(ImsResolver.METADATA_MMTEL_FEATURE);
-        newCarrierFeatures.add(ImsResolver.METADATA_RCS_FEATURE);
-        info.clear();
-        info.add(getResolveInfo(TEST_CARRIER_DEFAULT_NAME, newCarrierFeatures, true));
+        carrierFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(0, ImsFeature.FEATURE_RCS));
 
         // Tell the package manager that a new device feature is installed
-        Intent addPackageIntent = new Intent();
-        addPackageIntent.setAction(Intent.ACTION_PACKAGE_ADDED);
-        addPackageIntent.setData(new Uri.Builder().scheme("package")
-                .opaquePart(TEST_CARRIER_DEFAULT_NAME.getPackageName()).build());
-        mTestPackageBroadcastReceiver.onReceive(null, addPackageIntent);
-        waitForHandlerAction(mTestImsResolver.getHandler(), TEST_TIMEOUT);
+        packageChanged(TEST_CARRIER_DEFAULT_NAME.getPackageName());
+        setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, carrierFeatures, 2);
 
         //Verify new feature is added to the carrier override.
         // add all features for slot 0
-        HashSet<Pair<Integer, Integer>> newCarrierFeatureSet =
-                convertToHashSet(newCarrierFeatures, 0);
-        verify(carrierController).changeImsServiceFeatures(newCarrierFeatureSet);
-        deviceFeatureSet.removeAll(newCarrierFeatureSet);
+        verify(carrierController).changeImsServiceFeatures(carrierFeatures);
+        deviceFeatureSet.removeAll(carrierFeatures);
         verify(deviceController).changeImsServiceFeatures(deviceFeatureSet);
     }
 
@@ -668,64 +553,52 @@
         deviceFeatures.add(ImsResolver.METADATA_RCS_FEATURE);
         // Set the carrier override package for slot 0
         setConfigCarrierString(0, TEST_CARRIER_DEFAULT_NAME.getPackageName());
-        Set<String> carrierFeatures = new HashSet<>();
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> carrierFeatures = new HashSet<>();
         // Carrier service doesn't support the voice feature.
-        carrierFeatures.add(ImsResolver.METADATA_RCS_FEATURE);
+        carrierFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(0, ImsFeature.FEATURE_RCS));
         // Use device default package, which will load the ImsService that the device provides
         info.add(getResolveInfo(TEST_DEVICE_DEFAULT_NAME, deviceFeatures, true));
-        info.add(getResolveInfo(TEST_CARRIER_DEFAULT_NAME, carrierFeatures, true));
-        when(mMockPM.queryIntentServicesAsUser(
-                argThat(argument -> ImsService.SERVICE_INTERFACE.equals(
-                        argument.getAction())), anyInt(), anyInt()))
-                .thenReturn(info);
+        info.add(getResolveInfo(TEST_CARRIER_DEFAULT_NAME, new HashSet<>(), true));
+        setupPackageQuery(info);
         ImsServiceController deviceController = mock(ImsServiceController.class);
         ImsServiceController carrierController = mock(ImsServiceController.class);
         setImsServiceControllerFactory(deviceController, carrierController);
 
-        mTestImsResolver.populateCacheAndStartBind();
-
-        waitForHandlerAction(mTestImsResolver.getHandler(), TEST_TIMEOUT);
+        startBind();
+        setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, carrierFeatures, 1);
         // Verify that all features that have been defined for the carrier override are bound
-        HashSet<Pair<Integer, Integer>> carrierFeatureSet = convertToHashSet(carrierFeatures, 0);
-        carrierFeatureSet.addAll(convertToHashSet(carrierFeatures, 0));
-        verify(carrierController).bind(carrierFeatureSet);
+        verify(carrierController).bind(carrierFeatures);
         verify(carrierController, never()).unbind();
         assertEquals(TEST_CARRIER_DEFAULT_NAME, carrierController.getComponentName());
         // Verify that all features that are not defined in the carrier override are bound in the
         // device controller (including emergency voice for slot 0)
-        HashSet<Pair<Integer, Integer>> deviceFeatureSet = convertToHashSet(deviceFeatures, 1);
-        deviceFeatures.removeAll(carrierFeatures);
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> deviceFeatureSet =
+                convertToHashSet(deviceFeatures, 1);
         deviceFeatureSet.addAll(convertToHashSet(deviceFeatures, 0));
-        verify(deviceController).bind(deviceFeatureSet);
+        deviceFeatureSet.removeAll(carrierFeatures);
+        // we will first have bound to device and then the features will change once the dynamic
+        // returns. So, instead of checking the bind parameters, we will check the change parameters
+        verify(deviceController).changeImsServiceFeatures(deviceFeatureSet);
         verify(deviceController, never()).unbind();
         assertEquals(TEST_DEVICE_DEFAULT_NAME, deviceController.getComponentName());
 
-        // remove RCS from carrier features list
-        Set<String> newCarrierFeatures = new HashSet<>();
-        newCarrierFeatures.add(ImsResolver.METADATA_MMTEL_FEATURE);
-        info.clear();
-        info.add(getResolveInfo(TEST_CARRIER_DEFAULT_NAME, newCarrierFeatures, true));
-
+        // change supported feature to MMTEL only
+        carrierFeatures.clear();
+        carrierFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(0,
+                ImsFeature.FEATURE_MMTEL));
         // Tell the package manager that a new device feature is installed
-        Intent addPackageIntent = new Intent();
-        addPackageIntent.setAction(Intent.ACTION_PACKAGE_ADDED);
-        addPackageIntent.setData(new Uri.Builder().scheme("package")
-                .opaquePart(TEST_CARRIER_DEFAULT_NAME.getPackageName()).build());
-        mTestPackageBroadcastReceiver.onReceive(null, addPackageIntent);
-        waitForHandlerAction(mTestImsResolver.getHandler(), TEST_TIMEOUT);
+        packageChanged(TEST_CARRIER_DEFAULT_NAME.getPackageName());
+        setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, carrierFeatures, 2);
 
         //Verify new feature is added to the carrier override.
-        // add all features for slot 0
-        HashSet<Pair<Integer, Integer>> newCarrierFeatureSet =
-                convertToHashSet(newCarrierFeatures, 0);
-        verify(carrierController).changeImsServiceFeatures(newCarrierFeatureSet);
+        verify(carrierController).changeImsServiceFeatures(carrierFeatures);
         Set<String> newDeviceFeatures = new HashSet<>();
         newDeviceFeatures.add(ImsResolver.METADATA_MMTEL_FEATURE);
         newDeviceFeatures.add(ImsResolver.METADATA_RCS_FEATURE);
-        HashSet<Pair<Integer, Integer>> newDeviceFeatureSet = convertToHashSet(newDeviceFeatures,
-                1);
-        newDeviceFeatures.removeAll(newCarrierFeatures);
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> newDeviceFeatureSet =
+                convertToHashSet(newDeviceFeatures, 1);
         newDeviceFeatureSet.addAll(convertToHashSet(newDeviceFeatures, 0));
+        newDeviceFeatureSet.removeAll(carrierFeatures);
         verify(deviceController).changeImsServiceFeatures(newDeviceFeatureSet);
     }
 
@@ -744,42 +617,29 @@
         setConfigCarrierString(0, TEST_CARRIER_DEFAULT_NAME.getPackageName());
         // Use device default package, which will load the ImsService that the device provides
         info.add(getResolveInfo(TEST_DEVICE_DEFAULT_NAME, deviceFeatures, true));
-        when(mMockPM.queryIntentServicesAsUser(
-                argThat(argument -> (argument == null || ImsService.SERVICE_INTERFACE
-                        .equals(argument.getAction()))), anyInt(), anyInt()))
-                .thenReturn(info);
+        setupPackageQuery(info);
         ImsServiceController deviceController = mock(ImsServiceController.class);
         ImsServiceController carrierController = mock(ImsServiceController.class);
         setImsServiceControllerFactory(deviceController, carrierController);
 
-        mTestImsResolver.populateCacheAndStartBind();
-        waitForHandlerAction(mTestImsResolver.getHandler(), TEST_TIMEOUT);
+        startBind();
 
-        Set<String> carrierFeatures = new HashSet<>();
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> carrierFeatures = new HashSet<>();
         // Carrier service doesn't support the voice feature.
-        carrierFeatures.add(ImsResolver.METADATA_RCS_FEATURE);
-        info.add(getResolveInfo(TEST_CARRIER_DEFAULT_NAME, carrierFeatures, true));
-        when(mMockPM.queryIntentServicesAsUser(
-                argThat(argument -> (argument == null || ImsService.SERVICE_INTERFACE
-                        .equals(argument.getAction()))), anyInt(), anyInt()))
-                .thenReturn(info);
+        carrierFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(0, ImsFeature.FEATURE_RCS));
+        info.add(getResolveInfo(TEST_CARRIER_DEFAULT_NAME, new HashSet<>(), true));
 
         // Tell the package manager that a new carrier app is installed
-        Intent addPackageIntent = new Intent();
-        addPackageIntent.setAction(Intent.ACTION_PACKAGE_ADDED);
-        addPackageIntent.setData(new Uri.Builder().scheme("package")
-                .opaquePart(TEST_CARRIER_DEFAULT_NAME.getPackageName()).build());
-        mTestPackageBroadcastReceiver.onReceive(null, addPackageIntent);
-        waitForHandlerAction(mTestImsResolver.getHandler(), TEST_TIMEOUT);
+        packageChanged(TEST_CARRIER_DEFAULT_NAME.getPackageName());
+        setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, carrierFeatures, 1);
 
         // Verify that all features that have been defined for the carrier override are bound
-        HashSet<Pair<Integer, Integer>> carrierFeatureSet = convertToHashSet(carrierFeatures, 0);
-        carrierFeatureSet.addAll(convertToHashSet(carrierFeatures, 0));
-        verify(carrierController).bind(carrierFeatureSet);
+        verify(carrierController).bind(carrierFeatures);
         // device features change
-        HashSet<Pair<Integer, Integer>> deviceFeatureSet = convertToHashSet(deviceFeatures, 1);
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> deviceFeatureSet =
+                convertToHashSet(deviceFeatures, 1);
         deviceFeatureSet.addAll(convertToHashSet(deviceFeatures, 0));
-        deviceFeatureSet.removeAll(carrierFeatureSet);
+        deviceFeatureSet.removeAll(carrierFeatures);
         verify(deviceController).changeImsServiceFeatures(deviceFeatureSet);
     }
 
@@ -797,37 +657,32 @@
         deviceFeatures.add(ImsResolver.METADATA_RCS_FEATURE);
         // Set the carrier override package for slot 0
         setConfigCarrierString(0, TEST_CARRIER_DEFAULT_NAME.getPackageName());
-        Set<String> carrierFeatures = new HashSet<>();
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> carrierFeatures = new HashSet<>();
         // Carrier service doesn't support the voice feature.
-        carrierFeatures.add(ImsResolver.METADATA_RCS_FEATURE);
-        info.add(getResolveInfo(TEST_CARRIER_DEFAULT_NAME, carrierFeatures, true));
+        carrierFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(0, ImsFeature.FEATURE_RCS));
+        info.add(getResolveInfo(TEST_CARRIER_DEFAULT_NAME, new HashSet<>(), true));
         // Use device default package, which will load the ImsService that the device provides
         info.add(getResolveInfo(TEST_DEVICE_DEFAULT_NAME, deviceFeatures, true));
-        when(mMockPM.queryIntentServicesAsUser(any(), anyInt(), anyInt())).thenReturn(info);
+        setupPackageQuery(info);
         ImsServiceController deviceController = mock(ImsServiceController.class);
         ImsServiceController carrierController = mock(ImsServiceController.class);
         setImsServiceControllerFactory(deviceController, carrierController);
 
-        mTestImsResolver.populateCacheAndStartBind();
-        waitForHandlerAction(mTestImsResolver.getHandler(), TEST_TIMEOUT);
+        startBind();
+        setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, carrierFeatures, 1);
 
         // Tell the package manager that carrier app is uninstalled
-        Intent removePackageIntent = new Intent();
-        removePackageIntent.setAction(Intent.ACTION_PACKAGE_REMOVED);
-        removePackageIntent.setData(new Uri.Builder().scheme("package")
-                .opaquePart(TEST_CARRIER_DEFAULT_NAME.getPackageName()).build());
         info.clear();
         info.add(getResolveInfo(TEST_DEVICE_DEFAULT_NAME, deviceFeatures, true));
-        when(mMockPM.queryIntentServicesAsUser(any(), anyInt(), anyInt())).thenReturn(info);
-        mTestPackageBroadcastReceiver.onReceive(null, removePackageIntent);
-        waitForHandlerAction(mTestImsResolver.getHandler(), TEST_TIMEOUT);
+        packageRemoved(TEST_CARRIER_DEFAULT_NAME.getPackageName());
 
         // Verify that the carrier controller is unbound
         verify(carrierController).unbind();
         assertNull(mTestImsResolver.getImsServiceInfoFromCache(
                 TEST_CARRIER_DEFAULT_NAME.getPackageName()));
         // device features change to include all supported functionality
-        HashSet<Pair<Integer, Integer>> deviceFeatureSet = convertToHashSet(deviceFeatures, 1);
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> deviceFeatureSet =
+                convertToHashSet(deviceFeatures, 1);
         deviceFeatureSet.addAll(convertToHashSet(deviceFeatures, 0));
         verify(deviceController).changeImsServiceFeatures(deviceFeatureSet);
     }
@@ -846,23 +701,23 @@
         deviceFeatures.add(ImsResolver.METADATA_RCS_FEATURE);
         // Set the carrier override package for slot 0
         setConfigCarrierString(0, TEST_CARRIER_DEFAULT_NAME.getPackageName());
-        Set<String> carrierFeatures = new HashSet<>();
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> carrierFeatures = new HashSet<>();
         // Carrier service doesn't support the voice feature.
-        carrierFeatures.add(ImsResolver.METADATA_RCS_FEATURE);
-        info.add(getResolveInfo(TEST_CARRIER_DEFAULT_NAME, carrierFeatures, true));
+        carrierFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(0, ImsFeature.FEATURE_RCS));
+        info.add(getResolveInfo(TEST_CARRIER_DEFAULT_NAME, new HashSet<>(), true));
         // Use device default package, which will load the ImsService that the device provides
         info.add(getResolveInfo(TEST_DEVICE_DEFAULT_NAME, deviceFeatures, true));
-        when(mMockPM.queryIntentServicesAsUser(any(), anyInt(), anyInt())).thenReturn(info);
+        setupPackageQuery(info);
         ImsServiceController deviceController = mock(ImsServiceController.class);
         ImsServiceController carrierController = mock(ImsServiceController.class);
         setImsServiceControllerFactory(deviceController, carrierController);
 
-        mTestImsResolver.populateCacheAndStartBind();
-        waitForHandlerAction(mTestImsResolver.getHandler(), TEST_TIMEOUT);
+        startBind();
+        setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, carrierFeatures, 1);
 
         setConfigCarrierString(0, null);
         Intent carrierConfigIntent = new Intent();
-        carrierConfigIntent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, 0);
+        carrierConfigIntent.putExtra(CarrierConfigManager.EXTRA_SLOT_INDEX, 0);
         mTestCarrierConfigReceiver.onReceive(null, carrierConfigIntent);
         waitForHandlerAction(mTestImsResolver.getHandler(), TEST_TIMEOUT);
 
@@ -871,7 +726,8 @@
         assertNotNull(mTestImsResolver.getImsServiceInfoFromCache(
                 TEST_CARRIER_DEFAULT_NAME.getPackageName()));
         // device features change
-        HashSet<Pair<Integer, Integer>> deviceFeatureSet = convertToHashSet(deviceFeatures, 1);
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> deviceFeatureSet =
+                convertToHashSet(deviceFeatures, 1);
         deviceFeatureSet.addAll(convertToHashSet(deviceFeatures, 0));
         verify(deviceController).changeImsServiceFeatures(deviceFeatureSet);
     }
@@ -890,45 +746,49 @@
         deviceFeatures.add(ImsResolver.METADATA_RCS_FEATURE);
         // Set the carrier override package for slot 0
         setConfigCarrierString(0, TEST_CARRIER_DEFAULT_NAME.getPackageName());
-        Set<String> carrierFeatures1 = new HashSet<>();
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> carrierFeatures1 = new HashSet<>();
         // Carrier service 1
-        carrierFeatures1.add(ImsResolver.METADATA_MMTEL_FEATURE);
-        carrierFeatures1.add(ImsResolver.METADATA_RCS_FEATURE);
-        Set<String> carrierFeatures2 = new HashSet<>();
+        carrierFeatures1.add(new ImsFeatureConfiguration.FeatureSlotPair(0,
+                ImsFeature.FEATURE_MMTEL));
+        carrierFeatures1.add(
+                new ImsFeatureConfiguration.FeatureSlotPair(0, ImsFeature.FEATURE_RCS));
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> carrierFeatures2 = new HashSet<>();
         // Carrier service 2 doesn't support the voice feature.
-        carrierFeatures2.add(ImsResolver.METADATA_RCS_FEATURE);
-        info.add(getResolveInfo(TEST_CARRIER_2_DEFAULT_NAME, carrierFeatures2, true));
-        info.add(getResolveInfo(TEST_CARRIER_DEFAULT_NAME, carrierFeatures1, true));
+        carrierFeatures2.add(
+                new ImsFeatureConfiguration.FeatureSlotPair(0, ImsFeature.FEATURE_RCS));
+        info.add(getResolveInfo(TEST_CARRIER_2_DEFAULT_NAME, new HashSet<>(), true));
+        info.add(getResolveInfo(TEST_CARRIER_DEFAULT_NAME, new HashSet<>(), true));
         // Use device default package, which will load the ImsService that the device provides
         info.add(getResolveInfo(TEST_DEVICE_DEFAULT_NAME, deviceFeatures, true));
-        when(mMockPM.queryIntentServicesAsUser(any(), anyInt(), anyInt())).thenReturn(info);
+        setupPackageQuery(info);
         ImsServiceController deviceController = mock(ImsServiceController.class);
         ImsServiceController carrierController1 = mock(ImsServiceController.class);
         ImsServiceController carrierController2 = mock(ImsServiceController.class);
         setImsServiceControllerFactory(deviceController, carrierController1, carrierController2);
 
-        mTestImsResolver.populateCacheAndStartBind();
-        waitForHandlerAction(mTestImsResolver.getHandler(), TEST_TIMEOUT);
+        startBind();
+        setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, carrierFeatures1, 1);
 
         setConfigCarrierString(0, TEST_CARRIER_2_DEFAULT_NAME.getPackageName());
         Intent carrierConfigIntent = new Intent();
-        carrierConfigIntent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, 0);
+        carrierConfigIntent.putExtra(CarrierConfigManager.EXTRA_SLOT_INDEX, 0);
         mTestCarrierConfigReceiver.onReceive(null, carrierConfigIntent);
         waitForHandlerAction(mTestImsResolver.getHandler(), TEST_TIMEOUT);
+        setupDynamicQueryFeatures(TEST_CARRIER_2_DEFAULT_NAME, carrierFeatures2, 1);
 
         // Verify that carrier 1 is unbound
         verify(carrierController1).unbind();
         assertNotNull(mTestImsResolver.getImsServiceInfoFromCache(
                 TEST_CARRIER_DEFAULT_NAME.getPackageName()));
         // Verify that carrier 2 is bound
-        HashSet<Pair<Integer, Integer>> carrier2FeatureSet = convertToHashSet(carrierFeatures2, 0);
-        verify(carrierController2).bind(carrier2FeatureSet);
+        verify(carrierController2).bind(carrierFeatures2);
         assertNotNull(mTestImsResolver.getImsServiceInfoFromCache(
-                TEST_CARRIER_DEFAULT_NAME.getPackageName()));
+                TEST_CARRIER_2_DEFAULT_NAME.getPackageName()));
         // device features change to accommodate for the features carrier 2 lacks
-        HashSet<Pair<Integer, Integer>> deviceFeatureSet = convertToHashSet(deviceFeatures, 1);
-        deviceFeatures.removeAll(carrierFeatures2);
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> deviceFeatureSet =
+                convertToHashSet(deviceFeatures, 1);
         deviceFeatureSet.addAll(convertToHashSet(deviceFeatures, 0));
+        deviceFeatureSet.removeAll(carrierFeatures2);
         verify(deviceController).changeImsServiceFeatures(deviceFeatureSet);
     }
 
@@ -958,6 +818,86 @@
         mTestCarrierConfigReceiver = carrierConfigCaptor.getValue();
         mTestPackageBroadcastReceiver = packageBroadcastCaptor.getValue();
         mTestImsResolver.setSubscriptionManagerProxy(mTestSubscriptionManagerProxy);
+        when(mMockQueryManagerFactory.create(any(Context.class),
+                any(ImsServiceFeatureQueryManager.Listener.class))).thenReturn(mMockQueryManager);
+        mTestImsResolver.setImsDynamicQueryManagerFactory(mMockQueryManagerFactory);
+    }
+
+    private void setupPackageQuery(List<ResolveInfo> infos) {
+        // Only return info if not using the compat argument
+        when(mMockPM.queryIntentServicesAsUser(
+                argThat(argument -> ImsService.SERVICE_INTERFACE.equals(argument.getAction())),
+                anyInt(), anyInt())).thenReturn(infos);
+    }
+
+    private void setupPackageQuery(ComponentName name, Set<String> features,
+            boolean isPermissionGranted) {
+        List<ResolveInfo> info = new ArrayList<>();
+        info.add(getResolveInfo(name, features, isPermissionGranted));
+        // Only return info if not using the compat argument
+        when(mMockPM.queryIntentServicesAsUser(
+                argThat(argument -> ImsService.SERVICE_INTERFACE.equals(argument.getAction())),
+                anyInt(), anyInt())).thenReturn(info);
+    }
+
+    private ImsServiceController setupController() {
+        ImsServiceController controller = mock(ImsServiceController.class);
+        mTestImsResolver.setImsServiceControllerFactory(
+                new ImsResolver.ImsServiceControllerFactory() {
+                    @Override
+                    public String getServiceInterface() {
+                        return ImsService.SERVICE_INTERFACE;
+                    }
+
+                    @Override
+                    public ImsServiceController create(Context context, ComponentName componentName,
+                            ImsServiceController.ImsServiceControllerCallbacks callbacks) {
+                        when(controller.getComponentName()).thenReturn(componentName);
+                        return controller;
+                    }
+                });
+        return controller;
+    }
+
+    private void startBind() {
+        mTestImsResolver.initPopulateCacheAndStartBind();
+        ArgumentCaptor<ImsServiceFeatureQueryManager.Listener> queryManagerCaptor =
+                ArgumentCaptor.forClass(ImsServiceFeatureQueryManager.Listener.class);
+        verify(mMockQueryManagerFactory).create(any(Context.class), queryManagerCaptor.capture());
+        mDynamicQueryListener = queryManagerCaptor.getValue();
+        waitForHandlerAction(mTestImsResolver.getHandler(), TEST_TIMEOUT);
+    }
+
+    private void setupDynamicQueryFeatures(ComponentName name,
+            HashSet<ImsFeatureConfiguration.FeatureSlotPair> features, int times) {
+        // wait for schedule to happen
+        waitForHandlerAction(mTestImsResolver.getHandler(), TEST_TIMEOUT);
+        // ensure that startQuery was called
+        when(mMockQueryManager.startQuery(any(ComponentName.class), any(String.class)))
+                .thenReturn(true);
+        verify(mMockQueryManager, Mockito.times(times)).startQuery(eq(name), any(String.class));
+        mDynamicQueryListener.onComplete(name, features);
+        // wait for handling of onComplete
+        waitForHandlerAction(mTestImsResolver.getHandler(), TEST_TIMEOUT);
+    }
+
+    public void packageChanged(String packageName) {
+        // Tell the package manager that a new device feature is installed
+        Intent addPackageIntent = new Intent();
+        addPackageIntent.setAction(Intent.ACTION_PACKAGE_CHANGED);
+        addPackageIntent.setData(new Uri.Builder().scheme("package").opaquePart(packageName)
+                .build());
+        mTestPackageBroadcastReceiver.onReceive(null, addPackageIntent);
+        waitForHandlerAction(mTestImsResolver.getHandler(), TEST_TIMEOUT);
+    }
+
+    public void packageRemoved(String packageName) {
+        Intent removePackageIntent = new Intent();
+        removePackageIntent.setAction(Intent.ACTION_PACKAGE_REMOVED);
+        removePackageIntent.setData(new Uri.Builder().scheme("package")
+                .opaquePart(TEST_CARRIER_DEFAULT_NAME.getPackageName()).build());
+        mTestPackageBroadcastReceiver.onReceive(null, removePackageIntent);
+        waitForHandlerAction(mTestImsResolver.getHandler(), TEST_TIMEOUT);
     }
 
     private void setImsServiceControllerFactory(ImsServiceController deviceController,
@@ -1022,13 +962,14 @@
                 CarrierConfigManager.KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING, packageName);
     }
 
-    private HashSet<Pair<Integer, Integer>> convertToHashSet(Set<String> features, int subId) {
-        HashSet<Pair<Integer, Integer>> featureSet = features.stream()
+    private HashSet<ImsFeatureConfiguration.FeatureSlotPair> convertToHashSet(
+            Set<String> features, int slotId) {
+        return features.stream()
                 // We do not count this as a valid feature set member.
                 .filter(f -> !ImsResolver.METADATA_EMERGENCY_MMTEL_FEATURE.equals(f))
-                .map(f -> new Pair<>(subId, metadataStringToFeature(f)))
+                .map(f -> new ImsFeatureConfiguration.FeatureSlotPair(slotId,
+                        metadataStringToFeature(f)))
                 .collect(Collectors.toCollection(HashSet::new));
-        return featureSet;
     }
 
     private int metadataStringToFeature(String f) {
@@ -1041,6 +982,8 @@
         return -1;
     }
 
+    // Make sure the metadata provided in the service definition creates the associated features in
+    // the ImsServiceInfo. Note: this only tests for one slot.
     private boolean isImsServiceInfoEqual(ComponentName name, Set<String> features,
             ImsResolver.ImsServiceInfo sInfo) {
         if (!Objects.equals(sInfo.name, name)) {
@@ -1049,17 +992,23 @@
         for (String f : features) {
             switch (f) {
                 case ImsResolver.METADATA_EMERGENCY_MMTEL_FEATURE:
-                    if (!sInfo.supportedFeatures.contains(ImsFeature.FEATURE_EMERGENCY_MMTEL)) {
+                    if (!sInfo.getSupportedFeatures().contains(
+                            new ImsFeatureConfiguration.FeatureSlotPair(0,
+                                    ImsFeature.FEATURE_EMERGENCY_MMTEL))) {
                         return false;
                     }
                     break;
                 case ImsResolver.METADATA_MMTEL_FEATURE:
-                    if (!sInfo.supportedFeatures.contains(ImsFeature.FEATURE_MMTEL)) {
+                    if (!sInfo.getSupportedFeatures().contains(
+                            new ImsFeatureConfiguration.FeatureSlotPair(0,
+                                    ImsFeature.FEATURE_MMTEL))) {
                         return false;
                     }
                     break;
                 case ImsResolver.METADATA_RCS_FEATURE:
-                    if (!sInfo.supportedFeatures.contains(ImsFeature.FEATURE_RCS)) {
+                    if (!sInfo.getSupportedFeatures().contains(
+                            new ImsFeatureConfiguration.FeatureSlotPair(0,
+                                    ImsFeature.FEATURE_RCS))) {
                         return false;
                     }
                     break;
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ims/ImsServiceControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsServiceControllerTest.java
index 9a7f111..af2f81b 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ims/ImsServiceControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsServiceControllerTest.java
@@ -39,7 +39,7 @@
 import android.support.test.filters.FlakyTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.telephony.ims.ImsService;
-import android.util.Pair;
+import android.telephony.ims.stub.ImsFeatureConfiguration;
 
 import com.android.ims.internal.IImsServiceFeatureCallback;
 
@@ -75,7 +75,6 @@
     };
 
     @Spy TestImsServiceControllerAdapter mMockServiceControllerBinder;
-    @Mock IBinder mMockBinder;
     @Mock ImsServiceController.ImsServiceControllerCallbacks mMockCallbacks;
     @Mock IImsServiceFeatureCallback mMockProxyCallbacks;
     @Mock Context mMockContext;
@@ -90,7 +89,7 @@
         super.setUp();
         mTestImsServiceController = new ImsServiceController(mMockContext, mTestComponentName,
                 mMockCallbacks, mHandler, REBIND_RETRY);
-        mTestImsServiceController.addImsServiceFeatureListener(mMockProxyCallbacks);
+        mTestImsServiceController.addImsServiceFeatureCallback(mMockProxyCallbacks);
         when(mMockContext.bindService(any(), any(), anyInt())).thenReturn(true);
     }
 
@@ -108,11 +107,11 @@
     @FlakyTest
     @Test
     public void testBindService() {
-        HashSet<Pair<Integer, Integer>> testFeatures = new HashSet<>();
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> testFeatures = new HashSet<>();
         // Slot 1, MMTel
-        testFeatures.add(new Pair<>(1, 1));
+        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, 1));
         // Slot 1, RCS
-        testFeatures.add(new Pair<>(1, 2));
+        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, 2));
         ArgumentCaptor<Intent> intentCaptor =
                 ArgumentCaptor.forClass(Intent.class);
 
@@ -132,9 +131,9 @@
     @FlakyTest
     @Test
     public void testBindFailureWhenBound() {
-        HashSet<Pair<Integer, Integer>> testFeatures = new HashSet<>();
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> testFeatures = new HashSet<>();
         // Slot 1, MMTel
-        testFeatures.add(new Pair<>(1, 1));
+        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, 1));
         bindAndConnectService(testFeatures);
 
         // already bound, should return false
@@ -150,11 +149,11 @@
     @FlakyTest
     @Test
     public void testBindServiceAndConnected() throws RemoteException {
-        HashSet<Pair<Integer, Integer>> testFeatures = new HashSet<>();
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> testFeatures = new HashSet<>();
         // Slot 1, MMTel
-        testFeatures.add(new Pair<>(1, 1));
+        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, 1));
         // Slot 1, RCS
-        testFeatures.add(new Pair<>(1, 2));
+        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, 2));
 
         bindAndConnectService(testFeatures);
 
@@ -173,17 +172,73 @@
     }
 
     /**
+     * Tests Emergency MMTEL ImsServiceController callbacks are properly called when an ImsService
+     * is bound and connected.
+     */
+    @FlakyTest
+    @Test
+    public void testBindEmergencyMmTel() throws RemoteException {
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> testFeatures = new HashSet<>();
+        // Slot 1, Emergency MMTel
+        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, 0));
+        // Slot 1, MmTel
+        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, 1));
+
+        bindAndConnectService(testFeatures);
+
+        IBinder binder = mMockServiceControllerBinder.getBinder().asBinder();
+        verify(binder).linkToDeath(any(), anyInt());
+        verify(mMockServiceControllerBinder).createMMTelFeature(eq(1));
+        // We do not want this callback to happen for emergency MMTEL
+        verify(mMockCallbacks, never()).imsServiceFeatureCreated(eq(1), eq(0),
+                eq(mTestImsServiceController));
+        verify(mMockCallbacks).imsServiceFeatureCreated(eq(1), eq(1),
+                eq(mTestImsServiceController));
+        // Make sure this callback happens, which will notify the framework of emergency calling
+        // availability.
+        verify(mMockProxyCallbacks).imsFeatureCreated(eq(1), eq(0));
+        verify(mMockProxyCallbacks).imsFeatureCreated(eq(1), eq(1));
+        assertEquals(mMockServiceControllerBinder.getBinder(),
+                mTestImsServiceController.getImsServiceControllerBinder());
+    }
+
+    /**
+     * Tests that if a callback is added after the ImsServiceController is already bound, we get a
+     * imsFeatureCreated callback.
+     */
+    @FlakyTest
+    @Test
+    public void testCallbacksHappenWhenAddedAfterBind() throws RemoteException {
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> testFeatures = new HashSet<>();
+        // Slot 1, Emergency MMTel
+        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, 0));
+        // Slot 1, MmTel
+        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, 1));
+        mTestImsServiceController.removeImsServiceFeatureCallbacks();
+
+        bindAndConnectService(testFeatures);
+        // add the callback after bind
+        mTestImsServiceController.addImsServiceFeatureCallback(mMockProxyCallbacks);
+
+        // Make sure this callback happens for Emergency MMTEL and MMTEL
+        verify(mMockProxyCallbacks).imsFeatureCreated(eq(1), eq(0));
+        verify(mMockProxyCallbacks).imsFeatureCreated(eq(1), eq(1));
+        assertEquals(mMockServiceControllerBinder.getBinder(),
+                mTestImsServiceController.getImsServiceControllerBinder());
+    }
+
+    /**
      * Tests ImsServiceController callbacks are properly called when an ImsService is bound and
      * connected.
      */
     @FlakyTest
     @Test
     public void testBindServiceAndConnectedDisconnected() throws RemoteException {
-        HashSet<Pair<Integer, Integer>> testFeatures = new HashSet<>();
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> testFeatures = new HashSet<>();
         // Slot 1, MMTel
-        testFeatures.add(new Pair<>(1, 1));
+        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, 1));
         // Slot 1, RCS
-        testFeatures.add(new Pair<>(1, 2));
+        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, 2));
         ServiceConnection conn = bindAndConnectService(testFeatures);
 
         conn.onServiceDisconnected(mTestComponentName);
@@ -207,11 +262,11 @@
     @FlakyTest
     @Test
     public void testBindServiceBindUnbind() throws RemoteException {
-        HashSet<Pair<Integer, Integer>> testFeatures = new HashSet<>();
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> testFeatures = new HashSet<>();
         // Slot 1, MMTel
-        testFeatures.add(new Pair<>(1, 1));
+        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, 1));
         // Slot 1, RCS
-        testFeatures.add(new Pair<>(1, 2));
+        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, 2));
         ServiceConnection conn = bindAndConnectService(testFeatures);
 
         mTestImsServiceController.unbind();
@@ -235,11 +290,11 @@
     @FlakyTest
     @Test
     public void testBindServiceAndBinderDied() throws RemoteException {
-        HashSet<Pair<Integer, Integer>> testFeatures = new HashSet<>();
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> testFeatures = new HashSet<>();
         // Slot 1, MMTel
-        testFeatures.add(new Pair<>(1, 1));
+        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, 1));
         // Slot 1, RCS
-        testFeatures.add(new Pair<>(1, 2));
+        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, 2));
         bindAndConnectService(testFeatures);
         ArgumentCaptor<IBinder.DeathRecipient> deathCaptor =
                 ArgumentCaptor.forClass(IBinder.DeathRecipient.class);
@@ -262,17 +317,18 @@
     @FlakyTest
     @Test
     public void testBindServiceAndAddFeature() throws RemoteException {
-        HashSet<Pair<Integer, Integer>> testFeatures = new HashSet<>();
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> testFeatures = new HashSet<>();
         // Slot 1, MMTel
-        testFeatures.add(new Pair<>(1, 1));
+        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, 1));
         bindAndConnectService(testFeatures);
         verify(mMockServiceControllerBinder).createMMTelFeature(eq(1));
         verify(mMockCallbacks).imsServiceFeatureCreated(eq(1), eq(1),
                 eq(mTestImsServiceController));
         verify(mMockProxyCallbacks).imsFeatureCreated(eq(1), eq(1));
         // Create a new list with an additional item
-        HashSet<Pair<Integer, Integer>> testFeaturesWithAddition = new HashSet<>(testFeatures);
-        testFeaturesWithAddition.add(new Pair<>(2, 1));
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> testFeaturesWithAddition = new HashSet<>(
+                testFeatures);
+        testFeaturesWithAddition.add(new ImsFeatureConfiguration.FeatureSlotPair(2, 1));
 
         mTestImsServiceController.changeImsServiceFeatures(testFeaturesWithAddition);
 
@@ -288,11 +344,11 @@
     @FlakyTest
     @Test
     public void testBindServiceAndRemoveFeature() throws RemoteException {
-        HashSet<Pair<Integer, Integer>> testFeatures = new HashSet<>();
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> testFeatures = new HashSet<>();
         // Slot 1, MMTel
-        testFeatures.add(new Pair<>(1, 1));
+        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, 1));
         // Slot 2, MMTel
-        testFeatures.add(new Pair<>(2, 1));
+        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(2, 1));
         bindAndConnectService(testFeatures);
         verify(mMockServiceControllerBinder).createMMTelFeature(eq(1));
         verify(mMockCallbacks).imsServiceFeatureCreated(eq(1), eq(1),
@@ -303,8 +359,9 @@
                 eq(mTestImsServiceController));
         verify(mMockProxyCallbacks).imsFeatureCreated(eq(2), eq(1));
         // Create a new list with one less item
-        HashSet<Pair<Integer, Integer>> testFeaturesWithSubtraction = new HashSet<>(testFeatures);
-        testFeaturesWithSubtraction.remove(new Pair<>(2, 1));
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> testFeaturesWithSubtraction =
+                new HashSet<>(testFeatures);
+        testFeaturesWithSubtraction.remove(new ImsFeatureConfiguration.FeatureSlotPair(2, 1));
 
         mTestImsServiceController.changeImsServiceFeatures(testFeaturesWithSubtraction);
 
@@ -320,11 +377,11 @@
     @FlakyTest
     @Test
     public void testBindServiceAndRemoveAllFeatures() throws RemoteException {
-        HashSet<Pair<Integer, Integer>> testFeatures = new HashSet<>();
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> testFeatures = new HashSet<>();
         // slot 1, MMTel
-        testFeatures.add(new Pair<>(1, 1));
+        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, 1));
         // slot 2, MMTel
-        testFeatures.add(new Pair<>(2, 1));
+        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(2, 1));
         bindAndConnectService(testFeatures);
         verify(mMockServiceControllerBinder).createMMTelFeature(eq(1));
         verify(mMockCallbacks).imsServiceFeatureCreated(eq(1), eq(1),
@@ -354,15 +411,16 @@
     @FlakyTest
     @Test
     public void testBindUnbindServiceAndAddFeature() throws RemoteException {
-        HashSet<Pair<Integer, Integer>> testFeatures = new HashSet<>();
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> testFeatures = new HashSet<>();
         // Slot 1, MMTel
-        testFeatures.add(new Pair<>(1, 1));
+        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, 1));
         bindAndConnectService(testFeatures);
         mTestImsServiceController.unbind();
         // Create a new list with an additional item
-        HashSet<Pair<Integer, Integer>> testFeaturesWithAddition = new HashSet<>(testFeatures);
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> testFeaturesWithAddition = new HashSet<>(
+                testFeatures);
         // Try to create an RCS feature
-        testFeaturesWithAddition.add(new Pair<>(1, 2));
+        testFeaturesWithAddition.add(new ImsFeatureConfiguration.FeatureSlotPair(1, 2));
 
         mTestImsServiceController.changeImsServiceFeatures(testFeaturesWithAddition);
 
@@ -379,11 +437,11 @@
     @FlakyTest
     @Test
     public void testAutoBindAfterBinderDied() throws RemoteException {
-        HashSet<Pair<Integer, Integer>> testFeatures = new HashSet<>();
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> testFeatures = new HashSet<>();
         // Slot 1, MMTel
-        testFeatures.add(new Pair<>(1, 1));
+        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, 1));
         // Slot 1, RCS
-        testFeatures.add(new Pair<>(1, 2));
+        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, 2));
         bindAndConnectService(testFeatures);
 
         getDeathRecipient().binderDied();
@@ -400,11 +458,11 @@
     @FlakyTest
     @Test
     public void testNoAutoBindBeforeTimeout() throws RemoteException {
-        HashSet<Pair<Integer, Integer>> testFeatures = new HashSet<>();
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> testFeatures = new HashSet<>();
         // Slot 1, MMTel
-        testFeatures.add(new Pair<>(1, 1));
+        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, 1));
         // Slot 1, RCS
-        testFeatures.add(new Pair<>(1, 2));
+        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, 2));
         bindAndConnectService(testFeatures);
 
         getDeathRecipient().binderDied();
@@ -419,11 +477,11 @@
     @FlakyTest
     @Test
     public void testUnbindCauseAutoBindCancelAfterBinderDied() throws RemoteException {
-        HashSet<Pair<Integer, Integer>> testFeatures = new HashSet<>();
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> testFeatures = new HashSet<>();
         // Slot 1, MMTel
-        testFeatures.add(new Pair<>(1, 1));
+        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, 1));
         // Slot 1, RCS
-        testFeatures.add(new Pair<>(1, 2));
+        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, 2));
         bindAndConnectService(testFeatures);
 
         getDeathRecipient().binderDied();
@@ -443,11 +501,11 @@
     @FlakyTest
     @Test
     public void testBindCauseAutoBindCancelAfterBinderDied() throws RemoteException {
-        HashSet<Pair<Integer, Integer>> testFeatures = new HashSet<>();
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> testFeatures = new HashSet<>();
         // Slot 1, MMTel
-        testFeatures.add(new Pair<>(1, 1));
+        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, 1));
         // Slot 1, RCS
-        testFeatures.add(new Pair<>(1, 2));
+        testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, 2));
         bindAndConnectService(testFeatures);
         getDeathRecipient().binderDied();
         mTestImsServiceController.bind(testFeatures);
@@ -458,7 +516,8 @@
         verify(mMockContext, times(2)).bindService(any(), any(), anyInt());
     }
 
-    private ServiceConnection bindAndConnectService(HashSet<Pair<Integer, Integer>> testFeatures) {
+    private ServiceConnection bindAndConnectService(
+            HashSet<ImsFeatureConfiguration.FeatureSlotPair> testFeatures) {
         ArgumentCaptor<ServiceConnection> serviceCaptor =
                 ArgumentCaptor.forClass(ServiceConnection.class);
         assertTrue(mTestImsServiceController.bind(testFeatures));
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ims/TestImsServiceControllerAdapter.java b/tests/telephonytests/src/com/android/internal/telephony/ims/TestImsServiceControllerAdapter.java
index 294d6f2..38b3203 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ims/TestImsServiceControllerAdapter.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ims/TestImsServiceControllerAdapter.java
@@ -71,11 +71,6 @@
         }
 
         @Override
-        public void notifyImsFeatureReady(int slotId, int featureType)
-                throws RemoteException {
-        }
-
-        @Override
         public IImsConfig getConfig(int slotId) throws RemoteException {
             return new ImsConfigImplBase().getIImsConfig();
         }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/metrics/TelephonyMetricsTest.java b/tests/telephonytests/src/com/android/internal/telephony/metrics/TelephonyMetricsTest.java
index 41698ce..71d0794 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/metrics/TelephonyMetricsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/TelephonyMetricsTest.java
@@ -218,7 +218,7 @@
         assertEquals(TelephonyEvent.Type.CARRIER_ID_MATCHING, log.events[0].type);
         assertEquals(1, log.events[0].carrierIdMatching.cidTableVersion);
         assertEquals(1, log.events[0].carrierIdMatching.result.carrierId);
-        assertTrue(log.events[0].carrierIdMatching.result.mccmnc.isEmpty());
+        assertEquals("mccmncTest", log.events[0].carrierIdMatching.result.mccmnc);
         assertEquals("gid1Test", log.events[0].carrierIdMatching.result.gid1);
     }
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/mocks/DcTrackerMock.java b/tests/telephonytests/src/com/android/internal/telephony/mocks/DcTrackerMock.java
index a09c4eb..c02f68b 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/mocks/DcTrackerMock.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/mocks/DcTrackerMock.java
@@ -53,14 +53,6 @@
         throw new RuntimeException("Not Implemented");
     }
     @Override
-    public boolean isApnSupported(String name) {
-        throw new RuntimeException("Not Implemented");
-    }
-    @Override
-    public int getApnPriority(String name) {
-        throw new RuntimeException("Not Implemented");
-    }
-    @Override
     public LinkProperties getLinkProperties(String apnType) {
         throw new RuntimeException("Not Implemented");
     }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/mocks/PhoneMock.java b/tests/telephonytests/src/com/android/internal/telephony/mocks/PhoneMock.java
index a768914..1b20833 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/mocks/PhoneMock.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/mocks/PhoneMock.java
@@ -525,6 +525,14 @@
         throw new RuntimeException("not implemented");
     }
 
+    public void invokeOemRilRequestRaw(byte[] data, Message response) {
+        throw new RuntimeException("not implemented");
+    }
+
+    public void invokeOemRilRequestStrings(String[] strings, Message response) {
+        throw new RuntimeException("not implemented");
+    }
+
     public void nvReadItem(int itemID, Message response) {
         throw new RuntimeException("not implemented");
     }
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 0200963..b14fc10 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/mocks/SubscriptionControllerMock.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/mocks/SubscriptionControllerMock.java
@@ -250,11 +250,6 @@
         throw new RuntimeException("not implemented");
     }
     @Override
-    public List<SubscriptionInfo> getSubInfoUsingSlotIndexWithCheck(int slotId, boolean needCheck,
-                                                                    String callingPackage) {
-        throw new RuntimeException("not implemented");
-    }
-    @Override
     public void updatePhonesAvailability(Phone[] phones) {
         throw new RuntimeException("not implemented");
     }
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 7ee0053..2c73efa 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/mocks/TelephonyRegistryMock.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/mocks/TelephonyRegistryMock.java
@@ -316,6 +316,11 @@
     }
 
     @Override
+    public void notifyOemHookRawEventForSubscriber(int subId, byte[] rawData) {
+        throw new RuntimeException("Not implemented");
+    }
+
+    @Override
     public void notifyCarrierNetworkChange(boolean active) {
         throw new RuntimeException("Not implemented");
     }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/IccCardProxyTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/IccCardProxyTest.java
deleted file mode 100644
index 479acb7..0000000
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/IccCardProxyTest.java
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.internal.telephony.uicc;
-
-import static com.android.internal.telephony.TelephonyTestUtils.waitForMs;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Mockito.anyInt;
-import static org.mockito.Mockito.doReturn;
-
-import android.os.HandlerThread;
-import android.support.test.filters.FlakyTest;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import com.android.internal.telephony.CommandsInterface;
-import com.android.internal.telephony.IccCardConstants.State;
-import com.android.internal.telephony.TelephonyTest;
-import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppState;
-import com.android.internal.telephony.uicc.IccCardStatus.CardState;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.mockito.Mock;
-
-public class IccCardProxyTest extends TelephonyTest {
-    private IccCardProxy mIccCardProxyUT;
-    // private UiccCard mUiccCard;
-    private IccCardProxyHandlerThread mIccCardProxyHandlerThread;
-    private static final int PHONE_ID = 0;
-    private static final int PHONE_COUNT = 1;
-
-    private static final int SCARY_SLEEP_MS = 200;
-    // Must match IccCardProxy.EVENT_ICC_CHANGED
-    private static final int EVENT_ICC_CHANGED = 3;
-
-    @Mock private IccCardStatus mIccCardStatus;
-    @Mock private UiccCard mUiccCard;
-    @Mock private UiccCardApplication mUiccCardApplication;
-
-    private class IccCardProxyHandlerThread extends HandlerThread {
-
-        private IccCardProxyHandlerThread(String name) {
-            super(name);
-        }
-
-        @Override
-        public void onLooperPrepared() {
-            /* create a new UICC Controller associated with the simulated Commands */
-            mIccCardProxyUT = new IccCardProxy(mContext, mSimulatedCommands, PHONE_ID);
-            setReady(true);
-        }
-    }
-
-    @Before
-    public void setUp() throws Exception {
-        super.setUp(this.getClass().getSimpleName());
-        doReturn(PHONE_COUNT).when(mTelephonyManager).getPhoneCount();
-        doReturn(PHONE_COUNT).when(mTelephonyManager).getSimCount();
-        mSimulatedCommands.setIccCardStatus(mIccCardStatus);
-        mIccCardProxyHandlerThread = new IccCardProxyHandlerThread(TAG);
-        mIccCardProxyHandlerThread.start();
-        waitUntilReady();
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        mIccCardProxyHandlerThread.quitSafely();
-        super.tearDown();
-    }
-
-    @Test
-    @SmallTest
-    public void testInitialCardState() {
-        assertEquals(mIccCardProxyUT.getState(), State.UNKNOWN);
-    }
-
-    @Test
-    @Ignore
-    @FlakyTest
-    @SmallTest
-    public void testPowerOn() {
-        mSimulatedCommands.setRadioPower(true, null);
-        mSimulatedCommands.notifyRadioOn();
-        doReturn(mUiccCard).when(mUiccController).getUiccCard(anyInt());
-        mIccCardProxyUT.sendMessage(mIccCardProxyUT.obtainMessage(EVENT_ICC_CHANGED));
-
-        waitForMs(SCARY_SLEEP_MS);
-        assertEquals(CommandsInterface.RadioState.RADIO_ON, mSimulatedCommands.getRadioState());
-        assertEquals(mIccCardProxyUT.getState(), State.NOT_READY);
-        logd("IccCardProxy state = " + mIccCardProxyUT.getState());
-    }
-
-    @Test
-    @Ignore
-    @FlakyTest
-    @SmallTest
-    public void testCardLoaded() {
-        testPowerOn();
-        doReturn(CardState.CARDSTATE_PRESENT).when(mUiccCard).getCardState();
-        mIccCardProxyUT.sendMessage(mIccCardProxyUT.obtainMessage(EVENT_ICC_CHANGED));
-
-        waitForMs(SCARY_SLEEP_MS);
-        assertEquals(mIccCardProxyUT.getState(), State.NOT_READY);
-    }
-
-    @Test
-    @Ignore
-    @FlakyTest
-    @SmallTest
-    public void testAppNotLoaded() {
-        testPowerOn();
-        doReturn(CardState.CARDSTATE_PRESENT).when(mUiccCard).getCardState();
-        mIccCardProxyUT.sendMessage(mIccCardProxyUT.obtainMessage(EVENT_ICC_CHANGED));
-        doReturn(AppState.APPSTATE_UNKNOWN).when(mUiccCardApplication).getState();
-        doReturn(mUiccCardApplication).when(mUiccCard).getApplication(anyInt());
-
-        waitForMs(SCARY_SLEEP_MS);
-        assertEquals(mIccCardProxyUT.getState(), State.NOT_READY);
-    }
-
-    @Test
-    @Ignore
-    @FlakyTest
-    @SmallTest
-    public void testAppReady() {
-        testPowerOn();
-        doReturn(CardState.CARDSTATE_PRESENT).when(mUiccCard).getCardState();
-        mIccCardProxyUT.sendMessage(mIccCardProxyUT.obtainMessage(EVENT_ICC_CHANGED));
-        doReturn(AppState.APPSTATE_READY).when(mUiccCardApplication).getState();
-        doReturn(mUiccCardApplication).when(mUiccCard).getApplication(anyInt());
-
-        waitForMs(SCARY_SLEEP_MS);
-        assertEquals(mIccCardProxyUT.getState(), State.READY);
-    }
-}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCardTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCardTest.java
index dc21b70..27b8531 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCardTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCardTest.java
@@ -63,7 +63,8 @@
         @Override
         public void onLooperPrepared() {
             mUicccard = new UiccCard(mContextFixture.getTestDouble(),
-                                     mSimulatedCommands, mIccCardStatus, 0 /* phoneId */);
+                                     mSimulatedCommands, mIccCardStatus, 0 /* phoneId */,
+                                     new Object());
             /* create a custom handler for the Handler Thread */
             mHandler = new Handler(mTestHandlerThread.getLooper()) {
                 @Override
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 9dd1af0..404754c 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccProfileTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccProfileTest.java
@@ -16,6 +16,7 @@
 package com.android.internal.telephony.uicc;
 
 import static com.android.internal.telephony.TelephonyTestUtils.waitForMs;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
@@ -23,18 +24,23 @@
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.anyString;
 import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.isA;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
+import android.content.Intent;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Message;
-import android.test.suitebuilder.annotation.SmallTest;
+import android.os.PersistableBundle;
+import android.support.test.filters.SmallTest;
+import android.telephony.CarrierConfigManager;
 
 import com.android.internal.telephony.IccCardConstants.State;
 import com.android.internal.telephony.TelephonyTest;
 import com.android.internal.telephony.cat.CatService;
+import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppState;
 
 import org.junit.After;
 import org.junit.Before;
@@ -50,8 +56,6 @@
     }
 
     private IccIoResult mIccIoResult;
-    // Must match UiccProfile.EVENT_APP_READY
-    private static final int EVENT_APP_READY = 6;
     private static final int SCARY_SLEEP_MS = 200;
 
     private UiccProfileHandlerThread mTestHandlerThread;
@@ -79,7 +83,7 @@
         public void onLooperPrepared() {
             mUiccProfile = new UiccProfile(mContextFixture.getTestDouble(),
                                            mSimulatedCommands, mIccCardStatus, 0 /* phoneId */,
-                                           mUiccCard);
+                                           mUiccCard, new Object());
             /* create a custom handler for the Handler Thread */
             mHandler = new Handler(mTestHandlerThread.getLooper()) {
                 @Override
@@ -283,7 +287,8 @@
         waitForMs(50);
         assertEquals(3, mUiccProfile.getNumApplications());
 
-        mUiccProfile.sendMessage(mUiccProfile.obtainMessage(EVENT_APP_READY));
+        mUiccProfile.mHandler.sendMessage(
+                mUiccProfile.mHandler.obtainMessage(UiccProfile.EVENT_APP_READY));
         waitForMs(SCARY_SLEEP_MS);
         assertEquals(mUiccProfile.getState(), State.NOT_READY);
     }
@@ -315,7 +320,8 @@
         waitForMs(50);
         assertEquals(3, mUiccProfile.getNumApplications());
 
-        mUiccProfile.sendMessage(mUiccProfile.obtainMessage(EVENT_APP_READY));
+        mUiccProfile.mHandler.sendMessage(
+                mUiccProfile.mHandler.obtainMessage(UiccProfile.EVENT_APP_READY));
         waitForMs(SCARY_SLEEP_MS);
         // state is loaded as all records are loaded right away as SimulatedCommands returns
         // response for them right away. Ideally applications and records should be mocked.
@@ -349,7 +355,86 @@
         waitForMs(50);
         assertEquals(3, mUiccProfile.getNumApplications());
 
-        mUiccProfile.sendMessage(mUiccProfile.obtainMessage(EVENT_APP_READY));
+        mUiccProfile.mHandler.sendMessage(
+                mUiccProfile.mHandler.obtainMessage(UiccProfile.EVENT_APP_READY));
+        waitForMs(SCARY_SLEEP_MS);
+        // state is loaded as all records are loaded right away as SimulatedCommands returns
+        // response for them right away. Ideally applications and records should be mocked.
+        assertEquals(State.LOADED, mUiccProfile.getState());
+    }
+
+    @Test
+    @SmallTest
+    public void testUpdateUiccProfileApplicationWithDuplicateApps() {
+        /* update app status and index */
+        IccCardApplicationStatus umtsApp = composeUiccApplicationStatus(
+                IccCardApplicationStatus.AppType.APPTYPE_USIM,
+                IccCardApplicationStatus.AppState.APPSTATE_READY, "0xA2");
+        IccCardApplicationStatus imsApp = composeUiccApplicationStatus(
+                IccCardApplicationStatus.AppType.APPTYPE_ISIM,
+                IccCardApplicationStatus.AppState.APPSTATE_READY, "0xA1");
+        IccCardApplicationStatus unknownApp = composeUiccApplicationStatus(
+                IccCardApplicationStatus.AppType.APPTYPE_UNKNOWN,
+                IccCardApplicationStatus.AppState.APPSTATE_UNKNOWN, "0xA2");
+        IccCardApplicationStatus umtsAppDup = composeUiccApplicationStatus(
+                IccCardApplicationStatus.AppType.APPTYPE_USIM,
+                AppState.APPSTATE_DETECTED, "0xA2");
+        mIccCardStatus.mApplications = new IccCardApplicationStatus[]{imsApp, umtsApp, unknownApp,
+                umtsAppDup};
+        mIccCardStatus.mCdmaSubscriptionAppIndex = -1;
+        mIccCardStatus.mImsSubscriptionAppIndex = 0;
+        mIccCardStatus.mGsmUmtsSubscriptionAppIndex = 1;
+        Message mProfileUpdate = mHandler.obtainMessage(UICCPROFILE_UPDATE_APPLICATION_EVENT);
+        setReady(false);
+        mProfileUpdate.sendToTarget();
+
+        waitUntilReady();
+
+        /* wait for the carrier privilege rules to be loaded */
+        waitForMs(50);
+        assertEquals(4, mUiccProfile.getNumApplications());
+
+        mUiccProfile.mHandler.sendMessage(
+                mUiccProfile.mHandler.obtainMessage(UiccProfile.EVENT_APP_READY));
+        waitForMs(SCARY_SLEEP_MS);
+        // state is loaded as all records are loaded right away as SimulatedCommands returns
+        // response for them right away. Ideally applications and records should be mocked.
+        assertEquals(State.LOADED, mUiccProfile.getState());
+    }
+
+    @Test
+    @SmallTest
+    public void testUpdateUiccProfileApplicationWithDuplicateAppsInDifferentOrder() {
+        /* update app status and index */
+        IccCardApplicationStatus umtsApp = composeUiccApplicationStatus(
+                IccCardApplicationStatus.AppType.APPTYPE_USIM,
+                IccCardApplicationStatus.AppState.APPSTATE_READY, "0xA2");
+        IccCardApplicationStatus imsApp = composeUiccApplicationStatus(
+                IccCardApplicationStatus.AppType.APPTYPE_ISIM,
+                IccCardApplicationStatus.AppState.APPSTATE_READY, "0xA1");
+        IccCardApplicationStatus unknownApp = composeUiccApplicationStatus(
+                IccCardApplicationStatus.AppType.APPTYPE_UNKNOWN,
+                IccCardApplicationStatus.AppState.APPSTATE_UNKNOWN, "0xA2");
+        IccCardApplicationStatus umtsAppDup = composeUiccApplicationStatus(
+                IccCardApplicationStatus.AppType.APPTYPE_USIM,
+                AppState.APPSTATE_DETECTED, "0xA2");
+        mIccCardStatus.mApplications = new IccCardApplicationStatus[]{umtsAppDup, imsApp, umtsApp,
+                unknownApp};
+        mIccCardStatus.mCdmaSubscriptionAppIndex = -1;
+        mIccCardStatus.mImsSubscriptionAppIndex = 0;
+        mIccCardStatus.mGsmUmtsSubscriptionAppIndex = 2;
+        Message mProfileUpdate = mHandler.obtainMessage(UICCPROFILE_UPDATE_APPLICATION_EVENT);
+        setReady(false);
+        mProfileUpdate.sendToTarget();
+
+        waitUntilReady();
+
+        /* wait for the carrier privilege rules to be loaded */
+        waitForMs(50);
+        assertEquals(4, mUiccProfile.getNumApplications());
+
+        mUiccProfile.mHandler.sendMessage(
+                mUiccProfile.mHandler.obtainMessage(UiccProfile.EVENT_APP_READY));
         waitForMs(SCARY_SLEEP_MS);
         // state is loaded as all records are loaded right away as SimulatedCommands returns
         // response for them right away. Ideally applications and records should be mocked.
@@ -373,7 +458,8 @@
         waitForMs(50);
         assertEquals(0, mUiccProfile.getNumApplications());
 
-        mUiccProfile.sendMessage(mUiccProfile.obtainMessage(EVENT_APP_READY));
+        mUiccProfile.mHandler.sendMessage(
+                mUiccProfile.mHandler.obtainMessage(UiccProfile.EVENT_APP_READY));
         waitForMs(SCARY_SLEEP_MS);
         // state is loaded since there is no applications.
         assertEquals(State.NOT_READY, mUiccProfile.getState());
@@ -399,9 +485,121 @@
         waitForMs(50);
         assertEquals(1, mUiccProfile.getNumApplications());
 
-        mUiccProfile.sendMessage(mUiccProfile.obtainMessage(EVENT_APP_READY));
+        mUiccProfile.mHandler.sendMessage(
+                mUiccProfile.mHandler.obtainMessage(UiccProfile.EVENT_APP_READY));
         waitForMs(SCARY_SLEEP_MS);
         // state is loaded since there is no applications.
         assertEquals(State.NOT_READY, mUiccProfile.getState());
     }
+
+    private void testWithCsimApp() {
+        /* update app status and index */
+        IccCardApplicationStatus umtsApp = composeUiccApplicationStatus(
+                IccCardApplicationStatus.AppType.APPTYPE_USIM,
+                AppState.APPSTATE_READY, "0xA2");
+        IccCardApplicationStatus imsApp = composeUiccApplicationStatus(
+                IccCardApplicationStatus.AppType.APPTYPE_ISIM,
+                AppState.APPSTATE_READY, "0xA1");
+        IccCardApplicationStatus cdmaApp = composeUiccApplicationStatus(
+                IccCardApplicationStatus.AppType.APPTYPE_CSIM,
+                AppState.APPSTATE_DETECTED, "0xA2");
+        mIccCardStatus.mApplications = new IccCardApplicationStatus[]{imsApp, umtsApp, cdmaApp};
+        mIccCardStatus.mCdmaSubscriptionAppIndex = 2;
+        mIccCardStatus.mImsSubscriptionAppIndex = 0;
+        mIccCardStatus.mGsmUmtsSubscriptionAppIndex = 1;
+
+        Message mProfileUpdate = mHandler.obtainMessage(UICCPROFILE_UPDATE_APPLICATION_EVENT);
+        setReady(false);
+        mProfileUpdate.sendToTarget();
+
+        waitUntilReady();
+
+        /* wait for the carrier privilege rules to be loaded */
+        waitForMs(50);
+        assertEquals(3, mUiccProfile.getNumApplications());
+
+        mUiccProfile.mHandler.sendMessage(
+                mUiccProfile.mHandler.obtainMessage(UiccProfile.EVENT_APP_READY));
+        waitForMs(SCARY_SLEEP_MS);
+    }
+
+    @Test
+    @SmallTest
+    public void testUpdateUiccProfileApplicationCdmaSupported() {
+        // CDMA supported
+        doReturn(true).when(mUiccController).isCdmaSupported();
+
+        testWithCsimApp();
+
+        // CDMA is supported and CSIM app is not ready, so state should be NOT_READY
+        assertEquals(State.NOT_READY, mUiccProfile.getState());
+    }
+
+    @Test
+    @SmallTest
+    public void testUpdateUiccProfileApplicationCdmaNotSupported() {
+        // CDMA supported
+        doReturn(false).when(mUiccController).isCdmaSupported();
+
+        testWithCsimApp();
+
+        // state is loaded as all records are loaded right away as SimulatedCommands returns
+        // response for them right away. Ideally applications and records should be mocked.
+        // CSIM is not ready but that should not matter since CDMA is not supported.
+        assertEquals(State.LOADED, mUiccProfile.getState());
+    }
+
+    @Test
+    @SmallTest
+    public void testUpdateExternalState() {
+        // IO_ERROR
+        doReturn(IccCardStatus.CardState.CARDSTATE_ERROR).when(mUiccCard).getCardState();
+        mUiccProfile.updateExternalState();
+        assertEquals(State.CARD_IO_ERROR, mUiccProfile.getState());
+
+        // RESTRICTED
+        doReturn(IccCardStatus.CardState.CARDSTATE_RESTRICTED).when(mUiccCard).getCardState();
+        mUiccProfile.updateExternalState();
+        assertEquals(State.CARD_RESTRICTED, mUiccProfile.getState());
+
+        // CARD PRESENT; no mUiccApplication - state should be NOT_READY
+        doReturn(IccCardStatus.CardState.CARDSTATE_PRESENT).when(mUiccCard).getCardState();
+        mUiccProfile.updateExternalState();
+        assertEquals(State.NOT_READY, mUiccProfile.getState());
+
+        // set mUiccApplication
+        testUpdateUiccProfileApplicationAllReady();
+        mUiccProfile.updateExternalState();
+        assertEquals(State.LOADED, mUiccProfile.getState());
+    }
+
+    @Test
+    @SmallTest
+    public void testCarrierConfigHandling() {
+        testUpdateUiccProfileApplication();
+
+        // Fake carrier name
+        String fakeCarrierName = "fakeCarrierName";
+        PersistableBundle carrierConfigBundle = mContextFixture.getCarrierConfigBundle();
+        carrierConfigBundle.putBoolean(CarrierConfigManager.KEY_CARRIER_NAME_OVERRIDE_BOOL, true);
+        carrierConfigBundle.putString(CarrierConfigManager.KEY_CARRIER_NAME_STRING,
+                fakeCarrierName);
+
+        // broadcast CARRIER_CONFIG_CHANGED
+        mContext.sendBroadcast(new Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED));
+        waitForMs(200);
+
+        // verify that setSimOperatorNameForPhone() is called with fakeCarrierName
+        ArgumentCaptor<String> stringArgumentCaptor = ArgumentCaptor.forClass(String.class);
+        verify(mTelephonyManager, atLeast(1)).setSimOperatorNameForPhone(anyInt(),
+                stringArgumentCaptor.capture());
+        boolean carrierFound = false;
+        for (String carrierName : stringArgumentCaptor.getAllValues()) {
+            if (fakeCarrierName.equals(carrierName)) {
+                carrierFound = true;
+                break;
+            }
+        }
+        assertTrue(carrierFound);
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccSlotTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccSlotTest.java
index a001cac..457f021 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccSlotTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccSlotTest.java
@@ -17,14 +17,20 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyObject;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.verify;
 
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Message;
 import android.support.test.filters.SmallTest;
 
+import com.android.internal.telephony.IccCardConstants;
 import com.android.internal.telephony.TelephonyTest;
 
 import org.junit.After;
@@ -74,6 +80,7 @@
     @Before
     public void setUp() throws Exception {
         super.setUp(getClass().getSimpleName());
+        mContextFixture.putBooleanResource(com.android.internal.R.bool.config_hotswapCapable, true);
         /* initially there are no application available */
         mIccCardStatus.mApplications = new IccCardApplicationStatus[]{};
         mIccCardStatus.mCdmaSubscriptionAppIndex =
@@ -95,8 +102,9 @@
 
     @Test
     @SmallTest
-    public void testUpdateSlotStatus() {
+    public void testUpdateInactiveSlotStatus() {
         IccSlotStatus iss = new IccSlotStatus();
+        iss.logicalSlotIndex = 0;
         iss.slotState = IccSlotStatus.SlotState.SLOTSTATE_INACTIVE;
         iss.cardState = IccCardStatus.CardState.CARDSTATE_PRESENT;
         iss.iccid = "fake-iccid";
@@ -115,8 +123,35 @@
         assertNull(mUiccSlot.getUiccCard());
         assertEquals(IccCardStatus.CardState.CARDSTATE_PRESENT, mUiccSlot.getCardState());
         assertEquals(iss.iccid, mUiccSlot.getIccId());
+    }
 
+    @Test
+    @SmallTest
+    public void testUpdateActiveSlotStatus() {
+        // initial state
+        assertTrue(mUiccSlot.isActive());
+        assertNull(mUiccSlot.getUiccCard());
+        assertEquals(IccCardStatus.CardState.CARDSTATE_ABSENT, mUiccSlot.getCardState());
+        assertNull(mUiccSlot.getIccId());
+
+        mSimulatedCommands.setRadioPower(true, null);
+        int phoneId = 0;
+        IccSlotStatus iss = new IccSlotStatus();
+        iss.logicalSlotIndex = phoneId;
         iss.slotState = IccSlotStatus.SlotState.SLOTSTATE_ACTIVE;
+        iss.cardState = IccCardStatus.CardState.CARDSTATE_ABSENT;
+        iss.iccid = "fake-iccid";
+
+        // update slot to inactive
+        mUiccSlot.update(mSimulatedCommands, iss);
+
+        // assert on updated values
+        assertTrue(mUiccSlot.isActive());
+        assertNull(mUiccSlot.getUiccCard());
+        assertEquals(IccCardStatus.CardState.CARDSTATE_ABSENT, mUiccSlot.getCardState());
+        assertEquals(iss.iccid, mUiccSlot.getIccId());
+        verify(mSubInfoRecordUpdater).updateInternalIccState(
+                IccCardConstants.INTENT_VALUE_ICC_ABSENT, null, phoneId);
 
         // update slot to active
         mUiccSlot.update(mSimulatedCommands, iss);
@@ -129,6 +164,7 @@
     @SmallTest
     public void testUpdateSlotStatusEuiccIsSupported() {
         IccSlotStatus iss = new IccSlotStatus();
+        iss.logicalSlotIndex = 0;
         iss.slotState = IccSlotStatus.SlotState.SLOTSTATE_INACTIVE;
         iss.cardState = IccCardStatus.CardState.CARDSTATE_PRESENT;
         iss.iccid = "fake-iccid";
@@ -163,6 +199,7 @@
     @SmallTest
     public void testUpdateSlotStatusEuiccIsNotSupported() {
         IccSlotStatus iss = new IccSlotStatus();
+        iss.logicalSlotIndex = 0;
         iss.slotState = IccSlotStatus.SlotState.SLOTSTATE_INACTIVE;
         iss.cardState = IccCardStatus.CardState.CARDSTATE_PRESENT;
         iss.iccid = "fake-iccid";
@@ -192,4 +229,41 @@
         assertTrue(mUiccSlot.isActive());
         assertFalse(mUiccSlot.isEuicc());
     }
+
+    @Test
+    @SmallTest
+    public void testUpdateAbsentState() {
+        int phoneId = 0;
+        // Make sure when received CARDSTATE_ABSENT state in the first time,
+        mIccCardStatus.mCardState = IccCardStatus.CardState.CARDSTATE_ABSENT;
+        mUiccSlot.update(mSimulatedCommands, mIccCardStatus, phoneId);
+        verify(mSubInfoRecordUpdater).updateInternalIccState(
+                IccCardConstants.INTENT_VALUE_ICC_ABSENT, null, phoneId);
+        assertEquals(IccCardStatus.CardState.CARDSTATE_ABSENT, mUiccSlot.getCardState());
+        assertNull(mUiccSlot.getUiccCard());
+    }
+
+    @Test
+    @SmallTest
+    public void testUiccSlotCreateAndDispose() {
+        int phoneId = 0;
+        // Simulate when SIM is added, UiccCard and UiccProfile should be created.
+        mIccCardStatus.mCardState = IccCardStatus.CardState.CARDSTATE_PRESENT;
+        mUiccSlot.update(mSimulatedCommands, mIccCardStatus, phoneId);
+        verify(mTelephonyComponentFactory).makeUiccProfile(
+                anyObject(), eq(mSimulatedCommands), eq(mIccCardStatus), anyInt(), anyObject(),
+                anyObject());
+        assertEquals(IccCardStatus.CardState.CARDSTATE_PRESENT, mUiccSlot.getCardState());
+        assertNotNull(mUiccSlot.getUiccCard());
+
+        // Simulate when SIM is removed, UiccCard and UiccProfile should be disposed and ABSENT
+        // state is sent to SubscriptionInfoUpdater.
+        mIccCardStatus.mCardState = IccCardStatus.CardState.CARDSTATE_ABSENT;
+        mUiccSlot.update(mSimulatedCommands, mIccCardStatus, phoneId);
+        verify(mSubInfoRecordUpdater).updateInternalIccState(
+                IccCardConstants.INTENT_VALUE_ICC_ABSENT, null, phoneId);
+        verify(mUiccProfile).dispose();
+        assertEquals(IccCardStatus.CardState.CARDSTATE_ABSENT, mUiccSlot.getCardState());
+        assertNull(mUiccSlot.getUiccCard());
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccStateChangedLauncherTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccStateChangedLauncherTest.java
index a90e947..f933596 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccStateChangedLauncherTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccStateChangedLauncherTest.java
@@ -101,7 +101,7 @@
 
         // The first broadcast should be sent after initialization.
         UiccCard card = new UiccCard(mContext, mSimulatedCommands,
-                makeCardStatus(CardState.CARDSTATE_PRESENT), 0 /* phoneId */);
+                makeCardStatus(CardState.CARDSTATE_PRESENT), 0 /* phoneId */, new Object());
         when(UiccController.getInstance().getUiccCardForPhone(0)).thenReturn(card);
         uiccLauncher.handleMessage(msg);
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/EuiccCardTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/EuiccCardTest.java
index 831d0b8..dc621a5 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
@@ -103,7 +103,7 @@
         public void onLooperPrepared() {
             mEuiccCard =
                     new EuiccCard(mContextFixture.getTestDouble(), mMockCi, mMockIccCardStatus,
-                            0 /* phoneId */) {
+                            0 /* phoneId */, new Object()) {
                         @Override
                         protected byte[] getDeviceId() {
                             return IccUtils.bcdToBytes("987654321012345");
@@ -173,7 +173,7 @@
             final CountDownLatch latch = new CountDownLatch(1);
             mHandler.post(() -> {
                 mEuiccCard = new EuiccCard(mContextFixture.getTestDouble(), mMockCi,
-                        mMockIccCardStatus, 0 /* phoneId */);
+                        mMockIccCardStatus, 0 /* phoneId */, new Object());
                 latch.countDown();
             });
             assertTrue(latch.await(WAIT_TIMEOUT_MLLIS, TimeUnit.MILLISECONDS));