DO NOT MERGE - Merge pie-platform-release (PPRL.181205.001) into master

Bug: 120502534
Change-Id: I396269cd1cf8b17c82fbbf59241b3db3fc257cfa
diff --git a/Android.bp b/Android.bp
new file mode 100644
index 0000000..80070b7
--- /dev/null
+++ b/Android.bp
@@ -0,0 +1,70 @@
+// Copyright 2018 Google Inc. All rights reserved.
+//
+// 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.
+
+
+filegroup {
+    name: "opt-telephony-srcs",
+    srcs: [
+        "src/java/android/telephony/**/*.java",
+    ],
+}
+
+filegroup {
+    name: "opt-telephony-htmls",
+    srcs: [
+        "src/java/android/telephony/**/*.html",
+    ],
+}
+
+java_library {
+    name: "telephony-common",
+    installable: true,
+
+    aidl: {
+        local_include_dirs: ["src/java"],
+    },
+    srcs: [
+        "src/java/**/*.java",
+        "src/java/**/I*.aidl",
+        "src/java/**/*.logtags",
+    ],
+
+    libs: [
+        "voip-common",
+        "ims-common",
+        "services",
+    ],
+    static_libs: [
+        "telephony-protos",
+        "ecc-protos-lite",
+        "android.hardware.radio-V1.0-java",
+        "android.hardware.radio-V1.1-java",
+        "android.hardware.radio-V1.2-java",
+        "android.hardware.radio-V1.3-java",
+        "android.hardware.radio-V1.4-java",
+        "android.hardware.radio.config-V1.0-java",
+        "android.hardware.radio.config-V1.1-java",
+        "android.hardware.radio.config-V1.2-java",
+        "android.hardware.radio.deprecated-V1.0-java",
+        "android.hidl.base-V1.0-java",
+    ],
+
+    product_variables: {
+        pdk: {
+            // enable this build only when platform library is available
+            enabled: false,
+        },
+    },
+}
+
diff --git a/Android.mk b/Android.mk
deleted file mode 100644
index 561aed0..0000000
--- a/Android.mk
+++ /dev/null
@@ -1,52 +0,0 @@
-# Copyright (C) 2011 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.
-
-# enable this build only when platform library is available
-ifneq ($(TARGET_BUILD_PDK), true)
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_AIDL_INCLUDES := $(LOCAL_PATH)/src/java
-LOCAL_SRC_FILES := $(call all-java-files-under, src/java) \
-	$(call all-Iaidl-files-under, src/java) \
-	$(call all-logtags-files-under, src/java)
-
-LOCAL_JAVA_LIBRARIES := voip-common ims-common services bouncycastle
-LOCAL_STATIC_JAVA_LIBRARIES := \
-    telephony-protos \
-    android.hardware.radio-V1.0-java \
-    android.hardware.radio-V1.1-java \
-    android.hardware.radio-V1.2-java \
-    android.hardware.radio.config-V1.0-java \
-    android.hardware.radio.deprecated-V1.0-java \
-    android.hidl.base-V1.0-java
-
-LOCAL_MODULE_TAGS := optional
-LOCAL_MODULE := telephony-common
-
-LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
-
-ifeq ($(EMMA_INSTRUMENT_FRAMEWORK),true)
-LOCAL_EMMA_INSTRUMENT := true
-endif
-
-include $(BUILD_JAVA_LIBRARY)
-
-# Include subdirectory makefiles
-# ============================================================
-include $(call all-makefiles-under,$(LOCAL_PATH))
-
-endif # non-PDK build
diff --git a/OWNERS b/OWNERS
index 7276a1a..284f0ec 100644
--- a/OWNERS
+++ b/OWNERS
@@ -9,3 +9,5 @@
 mpq@google.com
 shuoq@google.com
 refuhoo@google.com
+paulye@google.com
+nazaninb@google.com
diff --git a/proto/src/carrierId.proto b/proto/src/carrierId.proto
index f67e9f6..0f5eba8 100644
--- a/proto/src/carrierId.proto
+++ b/proto/src/carrierId.proto
@@ -39,6 +39,11 @@
 
   // [Optional] Carrier attributes to match a carrier. At least one value is required.
   repeated CarrierAttribute carrier_attribute = 3;
+
+  // [Optional] A unique canonical number to represent its parent carrier. The parent-child
+  // relationship can be used to differentiate a single carrier by different networks,
+  // by prepaid v.s. postpaid  or even by 4G v.s. 3G plan.
+  optional int32 parent_canonical_id = 4;
 };
 
 // Attributes used to match a carrier.
@@ -79,5 +84,9 @@
   // [Optional] Prefix of Integrated Circuit Card Identifier. Read from subscription EF_ICCID.
   // Sample values: 894430, 894410
   repeated string iccid_prefix = 8;
+
+  // [Optional] Carrier Privilege Access Rule in hex string.
+  // Sample values: 61ed377e85d386a8dfee6b864bd85b0bfaa5af88
+  repeated string privilege_access_rule = 9;
 };
 
diff --git a/proto/src/telephony.proto b/proto/src/telephony.proto
index 36f1883..91baed5 100644
--- a/proto/src/telephony.proto
+++ b/proto/src/telephony.proto
@@ -571,6 +571,21 @@
   RIL_E_INVALID_RESPONSE = 67;
 }
 
+// Errors returned by ImsService
+enum ImsServiceErrno {
+
+  // The operation error is unknown
+  IMS_E_UNKNOWN = 0;
+  // The operation has succeeded
+  IMS_E_SUCCESS = 1;
+  // Sending an SMS over IMS has failed. Do not retry over IMS again or fallback to CS.
+  IMS_E_SMS_SEND_STATUS_ERROR = 2;
+  // Sending an SMS over IMS has failed. Retry over IMS again.
+  IMS_E_SMS_SEND_STATUS_ERROR_RETRY = 3;
+  // Sending an SMS over IMS has failed. Fallback to sending the SMS over CS.
+  IMS_E_SMS_SEND_STATUS_ERROR_FALLBACK = 4;
+}
+
 // PDP_type values in TS 27.007 section 10.1.1.
 enum PdpType {
 
@@ -1070,6 +1085,9 @@
 
       // System time overwritten by NITZ (Network time)
       NITZ_TIME = 21;
+
+      // Change of audio codec
+      AUDIO_CODEC = 22;
     }
 
     enum RilRequest {
@@ -1162,6 +1180,53 @@
       CALL_DISCONNECTING = 9;
     }
 
+    // Audio codecs
+    enum AudioCodec {
+
+      // Unknown codec
+      AUDIO_CODEC_UNKNOWN = 0;
+
+      AUDIO_CODEC_AMR = 1;
+
+      AUDIO_CODEC_AMR_WB = 2;
+
+      AUDIO_CODEC_QCELP13K = 3;
+
+      AUDIO_CODEC_EVRC = 4;
+
+      AUDIO_CODEC_EVRC_B = 5;
+
+      AUDIO_CODEC_EVRC_WB = 6;
+
+      AUDIO_CODEC_EVRC_NW = 7;
+
+      AUDIO_CODEC_GSM_EFR = 8;
+
+      AUDIO_CODEC_GSM_FR = 9;
+
+      AUDIO_CODEC_GSM_HR = 10;
+
+      AUDIO_CODEC_G711U = 11;
+
+      AUDIO_CODEC_G723 = 12;
+
+      AUDIO_CODEC_G711A = 13;
+
+      AUDIO_CODEC_G722 = 14;
+
+      AUDIO_CODEC_G711AB = 15;
+
+      AUDIO_CODEC_G729 = 16;
+
+      AUDIO_CODEC_EVS_NB = 17;
+
+      AUDIO_CODEC_EVS_WB = 18;
+
+      AUDIO_CODEC_EVS_SWB = 19;
+
+      AUDIO_CODEC_EVS_FB = 20;
+    }
+
     // The information about a voice call
     message RilCall {
 
@@ -1269,6 +1334,9 @@
 
     // NITZ time in milliseconds
     optional int64 nitz_timestamp_millis = 21;
+
+    // Audio codec at the beginning of the session or when changed
+    optional AudioCodec audio_codec = 22;
   }
 
   // Time when call has started, in minutes since epoch,
@@ -1310,17 +1378,27 @@
       // or old data call has removed.
       DATA_CALL_LIST_CHANGED = 5;
 
-      // Send a SMS message
+      // Send a SMS message over RIL
       SMS_SEND = 6;
 
-      // Message has been sent to network
+      // Message has been sent to network using RIL
       SMS_SEND_RESULT = 7;
 
-      // Notification about received SMS
+      // Notification about received SMS using RIL
       SMS_RECEIVED = 8;
 
       // CB message received
       CB_SMS_RECEIVED = 9;
+
+      // Send an SMS message over ImsService
+      SMS_SEND_IMS_SERVICE = 10;
+
+      // Receive an SMS message result over ImsService
+      SMS_SEND_RESULT_IMS_SERVICE = 11;
+
+      // Receive an SMS message over ImsService
+      SMS_RECEIVED_IMS_SERVICE = 12;
+
     }
 
     // Formats used to encode SMS messages
@@ -1426,6 +1504,7 @@
 
     // See 3GPP 27.005, 3.2.5 for GSM/UMTS,
     // 3GPP2 N.S0005 (IS-41C) Table 171 for CDMA,
+    // or ImsService for IMS specific error codes (using ImsService specific Event.Type)
     // -1 if unknown or not applicable
     optional int32 error_code = 10;
 
@@ -1437,6 +1516,9 @@
 
     // Cellbroadcast message content
     optional CBMessage cell_broadcast_message = 13;
+
+    // ImsService error code.
+    optional ImsServiceErrno imsError = 14;
   }
 
   // Time when session has started, in minutes since epoch,
@@ -1483,4 +1565,20 @@
 
   // Amount of time modem is in tx (ms)
   repeated int64 tx_time_ms = 9;
+
+  // Number of bytes sent (tx)
+  optional int64 num_bytes_tx = 10;
+
+  // Number of packets received (rx)
+  optional int64 num_packets_rx = 11;
+
+  // Number of bytes received (rx)
+  optional int64 num_bytes_rx = 12;
+
+  // Amount of time phone spends in various Radio Access Technologies (ms)
+  repeated int64 time_in_rat_ms = 13;
+
+  // Amount of time phone spends in various cellular
+  // rx signal strength levels (ms)
+  repeated int64 time_in_rx_signal_strength_level_ms = 14;
 }
diff --git a/src/java/android/telephony/CellBroadcastMessage.java b/src/java/android/telephony/CellBroadcastMessage.java
index cbb9e34..3cb364d 100644
--- a/src/java/android/telephony/CellBroadcastMessage.java
+++ b/src/java/android/telephony/CellBroadcastMessage.java
@@ -16,6 +16,7 @@
 
 package android.telephony;
 
+import android.annotation.UnsupportedAppUsage;
 import android.content.ContentValues;
 import android.content.Context;
 import android.database.Cursor;
@@ -74,6 +75,7 @@
         return mSubId;
     }
 
+    @UnsupportedAppUsage
     public CellBroadcastMessage(SmsCbMessage message) {
         mSmsCbMessage = message;
         mDeliveryTime = System.currentTimeMillis();
@@ -126,6 +128,7 @@
      * @return the new CellBroadcastMessage
      * @throws IllegalArgumentException if one of the required columns is missing
      */
+    @UnsupportedAppUsage
     public static CellBroadcastMessage createFromCursor(Cursor cursor) {
         int geoScope = cursor.getInt(
                 cursor.getColumnIndexOrThrow(Telephony.CellBroadcasts.GEOGRAPHICAL_SCOPE));
@@ -250,6 +253,7 @@
      * Return a ContentValues object for insertion into the database.
      * @return a new ContentValues object containing this object's data
      */
+    @UnsupportedAppUsage
     public ContentValues getContentValues() {
         ContentValues cv = new ContentValues(16);
         SmsCbMessage msg = mSmsCbMessage;
@@ -299,26 +303,32 @@
         mIsRead = isRead;
     }
 
+    @UnsupportedAppUsage
     public String getLanguageCode() {
         return mSmsCbMessage.getLanguageCode();
     }
 
+    @UnsupportedAppUsage
     public int getServiceCategory() {
         return mSmsCbMessage.getServiceCategory();
     }
 
+    @UnsupportedAppUsage
     public long getDeliveryTime() {
         return mDeliveryTime;
     }
 
+    @UnsupportedAppUsage
     public String getMessageBody() {
         return mSmsCbMessage.getMessageBody();
     }
 
+    @UnsupportedAppUsage
     public boolean isRead() {
         return mIsRead;
     }
 
+    @UnsupportedAppUsage
     public int getSerialNumber() {
         return mSmsCbMessage.getSerialNumber();
     }
@@ -327,6 +337,7 @@
         return mSmsCbMessage.getCmasWarningInfo();
     }
 
+    @UnsupportedAppUsage
     public SmsCbEtwsInfo getEtwsWarningInfo() {
         return mSmsCbMessage.getEtwsWarningInfo();
     }
@@ -350,6 +361,7 @@
      *
      * @return true if the message is PWS type (ETWS or CMAS)
      */
+    @UnsupportedAppUsage
     public boolean isEmergencyAlertMessage() {
         return mSmsCbMessage.isEmergencyMessage();
     }
@@ -358,6 +370,7 @@
      * Return whether the broadcast is an ETWS emergency message type.
      * @return true if the message is ETWS emergency type; false otherwise
      */
+    @UnsupportedAppUsage
     public boolean isEtwsMessage() {
         return mSmsCbMessage.isEtwsMessage();
     }
@@ -366,6 +379,7 @@
      * Return whether the broadcast is a CMAS emergency message type.
      * @return true if the message is CMAS emergency type; false otherwise
      */
+    @UnsupportedAppUsage
     public boolean isCmasMessage() {
         return mSmsCbMessage.isCmasMessage();
     }
@@ -430,6 +444,7 @@
      * @param context the context object
      * @return a String for populating the list item AccessibilityEvent for TTS
      */
+    @UnsupportedAppUsage
     public String getSpokenDateString(Context context) {
         int flags = DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_DATE;
         return DateUtils.formatDateTime(context, mDeliveryTime, flags);
diff --git a/src/java/com/android/internal/telephony/BaseCommands.java b/src/java/com/android/internal/telephony/BaseCommands.java
index c8cea5a..2695951 100644
--- a/src/java/com/android/internal/telephony/BaseCommands.java
+++ b/src/java/com/android/internal/telephony/BaseCommands.java
@@ -31,7 +31,7 @@
 public abstract class BaseCommands implements CommandsInterface {
     //***** Instance Variables
     protected Context mContext;
-    protected RadioState mState = RadioState.RADIO_UNAVAILABLE;
+    protected int mState = TelephonyManager.RADIO_POWER_UNAVAILABLE;
     protected Object mStateMonitor = new Object();
 
     protected RegistrantList mRadioStateChangedRegistrants = new RegistrantList();
@@ -78,6 +78,7 @@
     protected RegistrantList mNattKeepaliveStatusRegistrants = new RegistrantList();
     protected RegistrantList mPhysicalChannelConfigurationRegistrants = new RegistrantList();
     protected RegistrantList mLceInfoRegistrants = new RegistrantList();
+    protected RegistrantList mEmergencyNumberListRegistrants = new RegistrantList();
 
     protected Registrant mGsmSmsRegistrant;
     protected Registrant mCdmaSmsRegistrant;
@@ -117,7 +118,7 @@
     //***** CommandsInterface implementation
 
     @Override
-    public RadioState getRadioState() {
+    public @TelephonyManager.RadioPowerState int getRadioState() {
         return mState;
     }
 
@@ -154,7 +155,7 @@
         synchronized (mStateMonitor) {
             mOnRegistrants.add(r);
 
-            if (mState.isOn()) {
+            if (mState == TelephonyManager.RADIO_POWER_ON) {
                 r.notifyRegistrant(new AsyncResult(null, null, null));
             }
         }
@@ -174,7 +175,7 @@
         synchronized (mStateMonitor) {
             mAvailRegistrants.add(r);
 
-            if (mState.isAvailable()) {
+            if (mState != TelephonyManager.RADIO_POWER_UNAVAILABLE) {
                 r.notifyRegistrant(new AsyncResult(null, null, null));
             }
         }
@@ -194,7 +195,7 @@
         synchronized (mStateMonitor) {
             mNotAvailRegistrants.add(r);
 
-            if (!mState.isAvailable()) {
+            if (mState == TelephonyManager.RADIO_POWER_UNAVAILABLE) {
                 r.notifyRegistrant(new AsyncResult(null, null, null));
             }
         }
@@ -214,7 +215,8 @@
         synchronized (mStateMonitor) {
             mOffOrNotAvailRegistrants.add(r);
 
-            if (mState == RadioState.RADIO_OFF || !mState.isAvailable()) {
+            if (mState == TelephonyManager.RADIO_POWER_OFF
+                    || mState == TelephonyManager.RADIO_POWER_UNAVAILABLE) {
                 r.notifyRegistrant(new AsyncResult(null, null, null));
             }
         }
@@ -782,6 +784,17 @@
         mSubscriptionStatusRegistrants.remove(h);
     }
 
+    @Override
+    public void registerForEmergencyNumberList(Handler h, int what, Object obj) {
+        Registrant r = new Registrant(h, what, obj);
+        mEmergencyNumberListRegistrants.add(r);
+    }
+
+    @Override
+    public void unregisterForEmergencyNumberList(Handler h) {
+        mEmergencyNumberListRegistrants.remove(h);
+    }
+
     //***** Protected Methods
     /**
      * Store new RadioState and send notification based on the changes
@@ -791,37 +804,43 @@
      *
      * RadioState has 3 values : RADIO_OFF, RADIO_UNAVAILABLE, RADIO_ON.
      *
-     * @param newState new RadioState decoded from RIL_UNSOL_RADIO_STATE_CHANGED
+     * @param newState new radio power state decoded from RIL_UNSOL_RADIO_STATE_CHANGED
+     * @param forceNotifyRegistrants boolean indicating if registrants should be notified even if
+     * there is no change in state
      */
-    protected void setRadioState(RadioState newState) {
-        RadioState oldState;
+    protected void setRadioState(@TelephonyManager.RadioPowerState int newState,
+                                 boolean forceNotifyRegistrants) {
+        int oldState;
 
         synchronized (mStateMonitor) {
             oldState = mState;
             mState = newState;
 
-            if (oldState == mState) {
+            if (oldState == mState && !forceNotifyRegistrants) {
                 // no state transition
                 return;
             }
 
             mRadioStateChangedRegistrants.notifyRegistrants();
 
-            if (mState.isAvailable() && !oldState.isAvailable()) {
+            if (mState != TelephonyManager.RADIO_POWER_UNAVAILABLE
+                    && oldState == TelephonyManager.RADIO_POWER_UNAVAILABLE) {
                 mAvailRegistrants.notifyRegistrants();
             }
 
-            if (!mState.isAvailable() && oldState.isAvailable()) {
+            if (mState == TelephonyManager.RADIO_POWER_UNAVAILABLE
+                    && oldState != TelephonyManager.RADIO_POWER_UNAVAILABLE) {
                 mNotAvailRegistrants.notifyRegistrants();
             }
 
-            if (mState.isOn() && !oldState.isOn()) {
+            if (mState == TelephonyManager.RADIO_POWER_ON
+                    && oldState != TelephonyManager.RADIO_POWER_ON) {
                 mOnRegistrants.notifyRegistrants();
             }
 
-            if ((!mState.isOn() || !mState.isAvailable())
-                && !((!oldState.isOn() || !oldState.isAvailable()))
-            ) {
+            if ((mState == TelephonyManager.RADIO_POWER_OFF
+                    || mState == TelephonyManager.RADIO_POWER_UNAVAILABLE)
+                    && (oldState == TelephonyManager.RADIO_POWER_ON)) {
                 mOffOrNotAvailRegistrants.notifyRegistrants();
             }
         }
diff --git a/src/java/com/android/internal/telephony/BlockChecker.java b/src/java/com/android/internal/telephony/BlockChecker.java
index 456be56..1a63db6 100644
--- a/src/java/com/android/internal/telephony/BlockChecker.java
+++ b/src/java/com/android/internal/telephony/BlockChecker.java
@@ -41,14 +41,36 @@
      * @return {@code true} if the number is blocked. {@code false} otherwise.
      */
     public static boolean isBlocked(Context context, String phoneNumber, Bundle extras) {
-        boolean isBlocked = false;
+        return getBlockStatus(context, phoneNumber, extras)
+                != BlockedNumberContract.STATUS_NOT_BLOCKED;
+    }
+
+    /**
+     * Returns the call blocking status for the {@code phoneNumber}.
+     * <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.
+     * @param extras the extra attribute of the number.
+     * @return result code indicating if the number should be blocked, and if so why.
+     *         Valid values are: {@link BlockedNumberContract#STATUS_NOT_BLOCKED},
+     *         {@link BlockedNumberContract#STATUS_BLOCKED_IN_LIST},
+     *         {@link BlockedNumberContract#STATUS_BLOCKED_NOT_IN_CONTACTS},
+     *         {@link BlockedNumberContract#STATUS_BLOCKED_PAYPHONE},
+     *         {@link BlockedNumberContract#STATUS_BLOCKED_RESTRICTED},
+     *         {@link BlockedNumberContract#STATUS_BLOCKED_UNKNOWN_NUMBER}.
+     */
+    public static int getBlockStatus(Context context, String phoneNumber, Bundle extras) {
+        int blockStatus = BlockedNumberContract.STATUS_NOT_BLOCKED;
         long startTimeNano = System.nanoTime();
 
         try {
-            if (BlockedNumberContract.SystemContract.shouldSystemBlockNumber(
-                    context, phoneNumber, extras)) {
+            blockStatus = BlockedNumberContract.SystemContract.shouldSystemBlockNumber(
+                    context, phoneNumber, extras);
+            if (blockStatus != BlockedNumberContract.STATUS_NOT_BLOCKED) {
                 Rlog.d(TAG, phoneNumber + " is blocked.");
-                isBlocked = true;
             }
         } catch (Exception e) {
             Rlog.e(TAG, "Exception checking for blocked number: " + e);
@@ -58,6 +80,6 @@
         if (durationMillis > 500 || VDBG) {
             Rlog.d(TAG, "Blocked number lookup took: " + durationMillis + " ms.");
         }
-        return isBlocked;
+        return blockStatus;
     }
 }
diff --git a/src/java/com/android/internal/telephony/CallFailCause.java b/src/java/com/android/internal/telephony/CallFailCause.java
index acc1432..6e4e01c 100644
--- a/src/java/com/android/internal/telephony/CallFailCause.java
+++ b/src/java/com/android/internal/telephony/CallFailCause.java
@@ -149,6 +149,9 @@
     int DIAL_MODIFIED_TO_SS   = 245;
     int DIAL_MODIFIED_TO_DIAL = 246;
 
+    //Access class blocked - TS 31.121 5.2.1
+    int ACCESS_CLASS_BLOCKED = 260;
+
     //Emergency Redial
     int EMERGENCY_TEMP_FAILURE = 325;
     int EMERGENCY_PERM_FAILURE = 326;
diff --git a/src/java/com/android/internal/telephony/CallManager.java b/src/java/com/android/internal/telephony/CallManager.java
index b90629d..3a49a12 100644
--- a/src/java/com/android/internal/telephony/CallManager.java
+++ b/src/java/com/android/internal/telephony/CallManager.java
@@ -16,20 +16,19 @@
 
 package com.android.internal.telephony;
 
-import com.android.internal.telephony.sip.SipPhone;
-
 import android.content.Context;
 import android.os.AsyncResult;
 import android.os.Handler;
 import android.os.Message;
-import android.os.RegistrantList;
 import android.os.Registrant;
-import android.telecom.VideoProfile;
+import android.os.RegistrantList;
 import android.telephony.PhoneNumberUtils;
-import android.telephony.TelephonyManager;
 import android.telephony.PhoneStateListener;
-import android.telephony.ServiceState;
 import android.telephony.Rlog;
+import android.telephony.ServiceState;
+import android.telephony.TelephonyManager;
+
+import com.android.internal.telephony.sip.SipPhone;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -203,14 +202,6 @@
     }
 
     /**
-     * Returns all the registered phone objects.
-     * @return all the registered phone objects.
-     */
-    public List<Phone> getAllPhones() {
-        return Collections.unmodifiableList(mPhones);
-    }
-
-    /**
      * get Phone object corresponds to subId
      * @return Phone
      */
@@ -347,19 +338,6 @@
         return phone;
     }
 
-    public Phone getPhoneInCall(int subId) {
-        Phone phone = null;
-        if (!getFirstActiveRingingCall(subId).isIdle()) {
-            phone = getFirstActiveRingingCall(subId).getPhone();
-        } else if (!getActiveFgCall(subId).isIdle()) {
-            phone = getActiveFgCall(subId).getPhone();
-        } else {
-            // If BG call is idle, we return default phone
-            phone = getFirstActiveBgCall(subId).getPhone();
-        }
-        return phone;
-    }
-
     /**
      * Register phone to CallManager
      * @param phone to be registered
@@ -448,14 +426,6 @@
     }
 
     /**
-     * @return the phone associated with the background call
-     * of a particular subId
-     */
-    public Phone getBgPhone(int subId) {
-        return getFirstActiveBgCall(subId).getPhone();
-    }
-
-    /**
      * @return the phone associated with the ringing call
      */
     public Phone getRingingPhone() {
@@ -666,54 +636,6 @@
     }
 
     /**
-     * Answers a ringing or waiting call.
-     *
-     * Active call, if any, go on hold.
-     * If active call can't be held, i.e., a background call of the same channel exists,
-     * the active call will be hang up.
-     *
-     * Answering occurs asynchronously, and final notification occurs via
-     * {@link #registerForPreciseCallStateChanged(android.os.Handler, int,
-     * java.lang.Object) registerForPreciseCallStateChanged()}.
-     *
-     * @exception CallStateException when call is not ringing or waiting
-     */
-    public void acceptCall(Call ringingCall) throws CallStateException {
-        Phone ringingPhone = ringingCall.getPhone();
-
-        if (VDBG) {
-            Rlog.d(LOG_TAG, "acceptCall(" +ringingCall + " from " + ringingCall.getPhone() + ")");
-            Rlog.d(LOG_TAG, toString());
-        }
-
-        if ( hasActiveFgCall() ) {
-            Phone activePhone = getActiveFgCall().getPhone();
-            boolean hasBgCall = ! (activePhone.getBackgroundCall().isIdle());
-            boolean sameChannel = (activePhone == ringingPhone);
-
-            if (VDBG) {
-                Rlog.d(LOG_TAG, "hasBgCall: "+ hasBgCall + "sameChannel:" + sameChannel);
-            }
-
-            if (sameChannel && hasBgCall) {
-                getActiveFgCall().hangup();
-            } else if (!sameChannel && !hasBgCall) {
-                activePhone.switchHoldingAndActive();
-            } else if (!sameChannel && hasBgCall) {
-                getActiveFgCall().hangup();
-            }
-        }
-
-        // We only support the AUDIO_ONLY video state in this scenario.
-        ringingPhone.acceptCall(VideoProfile.STATE_AUDIO_ONLY);
-
-        if (VDBG) {
-            Rlog.d(LOG_TAG, "End acceptCall(" +ringingCall + ")");
-            Rlog.d(LOG_TAG, toString());
-        }
-    }
-
-    /**
      * Reject (ignore) a ringing call. In GSM, this means UDUB
      * (User Determined User Busy). Reject occurs asynchronously,
      * and final notification occurs via
@@ -724,7 +646,6 @@
      */
     public void rejectCall(Call ringingCall) throws CallStateException {
         if (VDBG) {
-            Rlog.d(LOG_TAG, "rejectCall(" +ringingCall + ")");
             Rlog.d(LOG_TAG, toString());
         }
 
@@ -739,92 +660,6 @@
     }
 
     /**
-     * Places active call on hold, and makes held call active.
-     * Switch occurs asynchronously and may fail.
-     *
-     * There are 4 scenarios
-     * 1. only active call but no held call, aka, hold
-     * 2. no active call but only held call, aka, unhold
-     * 3. both active and held calls from same phone, aka, swap
-     * 4. active and held calls from different phones, aka, phone swap
-     *
-     * Final notification occurs via
-     * {@link #registerForPreciseCallStateChanged(android.os.Handler, int,
-     * java.lang.Object) registerForPreciseCallStateChanged()}.
-     *
-     * @exception CallStateException if active call is ringing, waiting, or
-     * dialing/alerting, or heldCall can't be active.
-     * In these cases, this operation may not be performed.
-     */
-    public void switchHoldingAndActive(Call heldCall) throws CallStateException {
-        Phone activePhone = null;
-        Phone heldPhone = null;
-
-        if (VDBG) {
-            Rlog.d(LOG_TAG, "switchHoldingAndActive(" +heldCall + ")");
-            Rlog.d(LOG_TAG, toString());
-        }
-
-        if (hasActiveFgCall()) {
-            activePhone = getActiveFgCall().getPhone();
-        }
-
-        if (heldCall != null) {
-            heldPhone = heldCall.getPhone();
-        }
-
-        if (activePhone != null) {
-            activePhone.switchHoldingAndActive();
-        }
-
-        if (heldPhone != null && heldPhone != activePhone) {
-            heldPhone.switchHoldingAndActive();
-        }
-
-        if (VDBG) {
-            Rlog.d(LOG_TAG, "End switchHoldingAndActive(" +heldCall + ")");
-            Rlog.d(LOG_TAG, toString());
-        }
-    }
-
-    /**
-     * Hangup foreground call and resume the specific background call
-     *
-     * Note: this is noop if there is no foreground call or the heldCall is null
-     *
-     * @param heldCall to become foreground
-     * @throws CallStateException
-     */
-    public void hangupForegroundResumeBackground(Call heldCall) throws CallStateException {
-        Phone foregroundPhone = null;
-        Phone backgroundPhone = null;
-
-        if (VDBG) {
-            Rlog.d(LOG_TAG, "hangupForegroundResumeBackground(" +heldCall + ")");
-            Rlog.d(LOG_TAG, toString());
-        }
-
-        if (hasActiveFgCall()) {
-            foregroundPhone = getFgPhone();
-            if (heldCall != null) {
-                backgroundPhone = heldCall.getPhone();
-                if (foregroundPhone == backgroundPhone) {
-                    getActiveFgCall().hangup();
-                } else {
-                // the call to be hangup and resumed belongs to different phones
-                    getActiveFgCall().hangup();
-                    switchHoldingAndActive(heldCall);
-                }
-            }
-        }
-
-        if (VDBG) {
-            Rlog.d(LOG_TAG, "End hangupForegroundResumeBackground(" +heldCall + ")");
-            Rlog.d(LOG_TAG, toString());
-        }
-    }
-
-    /**
      * Whether or not the phone can conference in the current phone
      * state--that is, one call holding and one call active.
      * @return true if the phone can conference; false otherwise.
@@ -2062,42 +1897,6 @@
     }
 
     /**
-     * @return the connections of active background call
-     * return empty list if there is no active background call
-     */
-    public List<Connection> getBgCallConnections(int subId) {
-        Call bgCall = getFirstActiveBgCall(subId);
-        if ( bgCall != null) {
-            return bgCall.getConnections();
-        }
-        return mEmptyConnections;
-    }
-
-    /**
-     * @return the latest connection of active foreground call
-     * return null if there is no active foreground call
-     */
-    public Connection getFgCallLatestConnection() {
-        Call fgCall = getActiveFgCall();
-        if ( fgCall != null) {
-            return fgCall.getLatestConnection();
-        }
-        return null;
-    }
-
-    /**
-     * @return the latest connection of active foreground call
-     * return null if there is no active foreground call
-     */
-    public Connection getFgCallLatestConnection(int subId) {
-        Call fgCall = getActiveFgCall(subId);
-        if ( fgCall != null) {
-            return fgCall.getLatestConnection();
-        }
-        return null;
-    }
-
-    /**
      * @return true if there is at least one Foreground call in disconnected state
      */
     public boolean hasDisconnectedFgCall() {
@@ -2228,22 +2027,6 @@
         return false;
     }
 
-    /* FIXME Taken from klp-sprout-dev but setAudioMode was removed in L.
-    private boolean isServiceStateInService() {
-        boolean bInService = false;
-
-        for (Phone phone : mPhones) {
-            bInService = (phone.getServiceState().getState() == ServiceState.STATE_IN_SERVICE);
-            if (bInService) {
-                break;
-            }
-        }
-
-        if (VDBG) Rlog.d(LOG_TAG, "[isServiceStateInService] bInService = " + bInService);
-        return bInService;
-    }
-    */
-
     private class CallManagerHandler extends Handler {
         @Override
         public void handleMessage(Message msg) {
@@ -2372,52 +2155,4 @@
             }
         }
     };
-
-    @Override
-    public String toString() {
-        Call call;
-        StringBuilder b = new StringBuilder();
-        for (int i = 0; i < TelephonyManager.getDefault().getPhoneCount(); i++) {
-            b.append("CallManager {");
-            b.append("\nstate = " + getState(i));
-            call = getActiveFgCall(i);
-            if (call != null) {
-                b.append("\n- Foreground: " + getActiveFgCallState(i));
-                b.append(" from " + call.getPhone());
-                b.append("\n  Conn: ").append(getFgCallConnections(i));
-            }
-            call = getFirstActiveBgCall(i);
-            if (call != null) {
-                b.append("\n- Background: " + call.getState());
-                b.append(" from " + call.getPhone());
-                b.append("\n  Conn: ").append(getBgCallConnections(i));
-            }
-            call = getFirstActiveRingingCall(i);
-            if (call != null) {
-                b.append("\n- Ringing: " +call.getState());
-                b.append(" from " + call.getPhone());
-            }
-        }
-
-        for (Phone phone : getAllPhones()) {
-            if (phone != null) {
-                b.append("\nPhone: " + phone + ", name = " + phone.getPhoneName()
-                        + ", state = " + phone.getState());
-                call = phone.getForegroundCall();
-                if (call != null) {
-                    b.append("\n- Foreground: ").append(call);
-                }
-                call = phone.getBackgroundCall();
-                if (call != null) {
-                    b.append(" Background: ").append(call);
-                }
-                call = phone.getRingingCall();
-                if (call != null) {
-                    b.append(" Ringing: ").append(call);
-                }
-            }
-        }
-        b.append("\n}");
-        return b.toString();
-    }
 }
diff --git a/src/java/com/android/internal/telephony/CallStateException.java b/src/java/com/android/internal/telephony/CallStateException.java
index 8429146..a712d95 100644
--- a/src/java/com/android/internal/telephony/CallStateException.java
+++ b/src/java/com/android/internal/telephony/CallStateException.java
@@ -28,6 +28,11 @@
 
     public static final int ERROR_OUT_OF_SERVICE = 1;
     public static final int ERROR_POWER_OFF = 2;
+    public static final int ERROR_ALREADY_DIALING = 3;
+    public static final int ERROR_CALL_RINGING = 4;
+    public static final int ERROR_CALLING_DISABLED = 5;
+    public static final int ERROR_TOO_MANY_CALLS = 6;
+    public static final int ERROR_OTASP_PROVISIONING_IN_PROCESS = 7;
 
     public
     CallStateException()
diff --git a/src/java/com/android/internal/telephony/CallTracker.java b/src/java/com/android/internal/telephony/CallTracker.java
index 23874e2..e48b3e5 100644
--- a/src/java/com/android/internal/telephony/CallTracker.java
+++ b/src/java/com/android/internal/telephony/CallTracker.java
@@ -209,7 +209,7 @@
         String[] convertMaps = null;
         CarrierConfigManager configManager = (CarrierConfigManager)
                 phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
-        PersistableBundle bundle = configManager.getConfig();
+        PersistableBundle bundle = configManager.getConfigForSubId(phone.getSubId());
         if (bundle != null) {
             convertMaps =
                     bundle.getStringArray(CarrierConfigManager.KEY_DIAL_STRING_REPLACE_STRING_ARRAY);
diff --git a/src/java/com/android/internal/telephony/CarrierIdentifier.java b/src/java/com/android/internal/telephony/CarrierIdentifier.java
deleted file mode 100644
index 1ff8971..0000000
--- a/src/java/com/android/internal/telephony/CarrierIdentifier.java
+++ /dev/null
@@ -1,641 +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 android.provider.Telephony.CarrierId;
-
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Intent;
-import android.database.ContentObserver;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.Message;
-import android.provider.Telephony;
-import android.telephony.Rlog;
-import android.telephony.SubscriptionManager;
-import android.telephony.TelephonyManager;
-import android.text.TextUtils;
-import android.util.LocalLog;
-import android.util.Log;
-
-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;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.atomic.AtomicInteger;
-
-/**
- * CarrierIdentifier identifies the subscription carrier and returns a canonical carrier Id
- * and a user friendly carrier name. CarrierIdentifier reads subscription info and check against
- * all carrier matching rules stored in CarrierIdProvider. It is msim aware, each phone has a
- * dedicated CarrierIdentifier.
- */
-public class CarrierIdentifier extends Handler {
-    private static final String LOG_TAG = CarrierIdentifier.class.getSimpleName();
-    private static final boolean DBG = true;
-    private static final boolean VDBG = Rlog.isLoggable(LOG_TAG, Log.VERBOSE);
-
-    // events to trigger carrier identification
-    private static final int SIM_LOAD_EVENT             = 1;
-    private static final int SIM_ABSENT_EVENT           = 2;
-    private static final int SPN_OVERRIDE_EVENT         = 3;
-    private static final int ICC_CHANGED_EVENT          = 4;
-    private static final int PREFER_APN_UPDATE_EVENT    = 5;
-    private static final int CARRIER_ID_DB_UPDATE_EVENT = 6;
-
-    private static final Uri CONTENT_URL_PREFER_APN = Uri.withAppendedPath(
-            Telephony.Carriers.CONTENT_URI, "preferapn");
-    private static final String OPERATOR_BRAND_OVERRIDE_PREFIX = "operator_branding_";
-
-    // cached matching rules based mccmnc to speed up resolution
-    private List<CarrierMatchingRule> mCarrierMatchingRulesOnMccMnc = new ArrayList<>();
-    // cached carrier Id
-    private int mCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
-    // cached carrier name
-    private String mCarrierName;
-    // cached preferapn name
-    private String mPreferApn;
-    // cached service provider name. telephonyManager API returns empty string as default value.
-    // some carriers need to target devices with Empty SPN. In that case, carrier matching rule
-    // should specify "" spn explicitly.
-    private String mSpn = "";
-
-    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 ContentObserver mContentObserver = new ContentObserver(this) {
-        @Override
-        public void onChange(boolean selfChange, Uri uri) {
-            if (CONTENT_URL_PREFER_APN.equals(uri.getLastPathSegment())) {
-                logd("onChange URI: " + uri);
-                sendEmptyMessage(PREFER_APN_UPDATE_EVENT);
-            } else if (CarrierId.All.CONTENT_URI.equals(uri)) {
-                logd("onChange URI: " + uri);
-                sendEmptyMessage(CARRIER_ID_DB_UPDATE_EVENT);
-            }
-        }
-    };
-
-    private class SubscriptionsChangedListener
-            extends SubscriptionManager.OnSubscriptionsChangedListener {
-        final AtomicInteger mPreviousSubId =
-                new AtomicInteger(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
-        /**
-         * Callback invoked when there is any change to any SubscriptionInfo. Typically
-         * this method would invoke {@link SubscriptionManager#getActiveSubscriptionInfoList}
-         */
-        @Override
-        public void onSubscriptionsChanged() {
-            int subId = mPhone.getSubId();
-            if (mPreviousSubId.getAndSet(subId) != subId) {
-                if (DBG) {
-                    logd("SubscriptionListener.onSubscriptionInfoChanged subId: "
-                            + mPreviousSubId);
-                }
-                if (SubscriptionManager.isValidSubscriptionId(subId)) {
-                    sendEmptyMessage(SIM_LOAD_EVENT);
-                } else {
-                    sendEmptyMessage(SIM_ABSENT_EVENT);
-                }
-            }
-        }
-    }
-
-    public CarrierIdentifier(Phone phone) {
-        logd("Creating CarrierIdentifier[" + phone.getPhoneId() + "]");
-        mContext = phone.getContext();
-        mPhone = phone;
-        mTelephonyMgr = TelephonyManager.from(mContext);
-
-        // register events
-        mContext.getContentResolver().registerContentObserver(CONTENT_URL_PREFER_APN, false,
-                mContentObserver);
-        mContext.getContentResolver().registerContentObserver(
-                CarrierId.All.CONTENT_URI, false, mContentObserver);
-        SubscriptionManager.from(mContext).addOnSubscriptionsChangedListener(
-                mOnSubscriptionsChangedListener);
-        UiccController.getInstance().registerForIccChanged(this, ICC_CHANGED_EVENT, null);
-    }
-
-    /**
-     * Entry point for the carrier identification.
-     *
-     *    1. SIM_LOAD_EVENT
-     *        This indicates that all SIM records has been loaded and its first entry point for the
-     *        carrier identification. Note, there are other attributes could be changed on the fly
-     *        like APN and SPN. We cached all carrier matching rules based on MCCMNC to speed
-     *        up carrier resolution on following trigger events.
-     *
-     *    2. PREFER_APN_UPDATE_EVENT
-     *        This indicates prefer apn has been changed. It could be triggered when user modified
-     *        APN settings or when default data connection first establishes on the current carrier.
-     *        We follow up on this by querying prefer apn sqlite and re-issue carrier identification
-     *        with the updated prefer apn name.
-     *
-     *    3. SPN_OVERRIDE_EVENT
-     *        This indicates that SPN value as been changed. It could be triggered from EF_SPN
-     *        record loading, carrier config override
-     *        {@link android.telephony.CarrierConfigManager#KEY_CARRIER_NAME_STRING}
-     *        or carrier app override {@link TelephonyManager#setOperatorBrandOverride(String)}.
-     *        we follow up this by checking the cached mSPN against the latest value and issue
-     *        carrier identification only if spn changes.
-     *
-     *    4. CARRIER_ID_DB_UPDATE_EVENT
-     *        This indicates that carrierIdentification database which stores all matching rules
-     *        has been updated. It could be triggered from OTA or assets update.
-     */
-    @Override
-    public void handleMessage(Message msg) {
-        if (VDBG) logd("handleMessage: " + msg.what);
-        switch (msg.what) {
-            case SIM_LOAD_EVENT:
-            case CARRIER_ID_DB_UPDATE_EVENT:
-                mSpn = mTelephonyMgr.getSimOperatorNameForPhone(mPhone.getPhoneId());
-                mPreferApn = getPreferApn();
-                loadCarrierMatchingRulesOnMccMnc();
-                break;
-            case SIM_ABSENT_EVENT:
-                mCarrierMatchingRulesOnMccMnc.clear();
-                mSpn = null;
-                mPreferApn = null;
-                updateCarrierIdAndName(TelephonyManager.UNKNOWN_CARRIER_ID, null);
-                break;
-            case PREFER_APN_UPDATE_EVENT:
-                String preferApn = getPreferApn();
-                if (!equals(mPreferApn, preferApn, true)) {
-                    logd("[updatePreferApn] from:" + mPreferApn + " to:" + preferApn);
-                    mPreferApn = preferApn;
-                    matchCarrier();
-                }
-                break;
-            case SPN_OVERRIDE_EVENT:
-                String spn = mTelephonyMgr.getSimOperatorNameForPhone(mPhone.getPhoneId());
-                if (!equals(mSpn, spn, true)) {
-                    logd("[updateSpn] from:" + mSpn + " to:" + spn);
-                    mSpn = spn;
-                    matchCarrier();
-                }
-                break;
-            case ICC_CHANGED_EVENT:
-                // 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.unregisterForRecordsLoaded(this);
-                        mIccRecords.unregisterForRecordsOverride(this);
-                        mIccRecords = null;
-                    }
-                    if (newIccRecords != null) {
-                        logd("new Icc object");
-                        newIccRecords.registerForRecordsLoaded(this, SIM_LOAD_EVENT, null);
-                        newIccRecords.registerForRecordsOverride(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);
-                break;
-        }
-    }
-
-    private void loadCarrierMatchingRulesOnMccMnc() {
-        try {
-            String mccmnc = mTelephonyMgr.getSimOperatorNumericForPhone(mPhone.getPhoneId());
-            Cursor cursor = mContext.getContentResolver().query(
-                    CarrierId.All.CONTENT_URI,
-                    /* projection */ null,
-                    /* selection */ CarrierId.All.MCCMNC + "=?",
-                    /* selectionArgs */ new String[]{mccmnc}, null);
-            try {
-                if (cursor != null) {
-                    if (VDBG) {
-                        logd("[loadCarrierMatchingRules]- " + cursor.getCount()
-                                + " Records(s) in DB" + " mccmnc: " + mccmnc);
-                    }
-                    mCarrierMatchingRulesOnMccMnc.clear();
-                    while (cursor.moveToNext()) {
-                        mCarrierMatchingRulesOnMccMnc.add(makeCarrierMatchingRule(cursor));
-                    }
-                    matchCarrier();
-                }
-            } finally {
-                if (cursor != null) {
-                    cursor.close();
-                }
-            }
-        } catch (Exception ex) {
-            loge("[loadCarrierMatchingRules]- ex: " + ex);
-        }
-    }
-
-    private String getPreferApn() {
-        Cursor cursor = mContext.getContentResolver().query(
-                Uri.withAppendedPath(Telephony.Carriers.CONTENT_URI, "preferapn/subId/"
-                + mPhone.getSubId()), /* projection */ new String[]{Telephony.Carriers.APN},
-                /* selection */ null, /* selectionArgs */ null, /* sortOrder */ null);
-        try {
-            if (cursor != null) {
-                if (VDBG) {
-                    logd("[getPreferApn]- " + cursor.getCount() + " Records(s) in DB");
-                }
-                while (cursor.moveToNext()) {
-                    String apn = cursor.getString(cursor.getColumnIndexOrThrow(
-                            Telephony.Carriers.APN));
-                    logd("[getPreferApn]- " + apn);
-                    return apn;
-                }
-            }
-        } catch (Exception ex) {
-            loge("[getPreferApn]- exception: " + ex);
-        } finally {
-            if (cursor != null) {
-                cursor.close();
-            }
-        }
-        return null;
-    }
-
-    private void updateCarrierIdAndName(int cid, String name) {
-        boolean update = false;
-        if (!equals(name, mCarrierName, true)) {
-            logd("[updateCarrierName] from:" + mCarrierName + " to:" + name);
-            mCarrierName = name;
-            update = true;
-        }
-        if (cid != mCarrierId) {
-            logd("[updateCarrierId] from:" + mCarrierId + " to:" + cid);
-            mCarrierId = cid;
-            update = true;
-        }
-        if (update) {
-            mCarrierIdLocalLog.log("[updateCarrierIdAndName] cid:" + mCarrierId + " name:"
-                    + mCarrierName);
-            final Intent intent = new Intent(TelephonyManager
-                    .ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED);
-            intent.putExtra(TelephonyManager.EXTRA_CARRIER_ID, mCarrierId);
-            intent.putExtra(TelephonyManager.EXTRA_CARRIER_NAME, mCarrierName);
-            intent.putExtra(TelephonyManager.EXTRA_SUBSCRIPTION_ID, mPhone.getSubId());
-            mContext.sendBroadcast(intent);
-
-            // update current subscriptions
-            ContentValues cv = new ContentValues();
-            cv.put(CarrierId.CARRIER_ID, mCarrierId);
-            cv.put(CarrierId.CARRIER_NAME, mCarrierName);
-            mContext.getContentResolver().update(
-                    Uri.withAppendedPath(CarrierId.CONTENT_URI,
-                    Integer.toString(mPhone.getSubId())), cv, null, null);
-        }
-    }
-
-    private CarrierMatchingRule makeCarrierMatchingRule(Cursor cursor) {
-        return new CarrierMatchingRule(
-                cursor.getString(cursor.getColumnIndexOrThrow(CarrierId.All.MCCMNC)),
-                cursor.getString(cursor.getColumnIndexOrThrow(
-                        CarrierId.All.IMSI_PREFIX_XPATTERN)),
-                cursor.getString(cursor.getColumnIndexOrThrow(
-                        CarrierId.All.ICCID_PREFIX)),
-                cursor.getString(cursor.getColumnIndexOrThrow(CarrierId.All.GID1)),
-                cursor.getString(cursor.getColumnIndexOrThrow(CarrierId.All.GID2)),
-                cursor.getString(cursor.getColumnIndexOrThrow(CarrierId.All.PLMN)),
-                cursor.getString(cursor.getColumnIndexOrThrow(CarrierId.All.SPN)),
-                cursor.getString(cursor.getColumnIndexOrThrow(CarrierId.All.APN)),
-                cursor.getInt(cursor.getColumnIndexOrThrow(CarrierId.CARRIER_ID)),
-                cursor.getString(cursor.getColumnIndexOrThrow(CarrierId.CARRIER_NAME)));
-    }
-
-    /**
-     * carrier matching attributes with corresponding cid
-     */
-    private static class CarrierMatchingRule {
-        /**
-         * These scores provide the hierarchical relationship between the attributes, intended to
-         * resolve conflicts in a deterministic way. The scores are constructed such that a match
-         * from a higher tier will beat any subsequent match which does not match at that tier,
-         * so MCCMNC beats everything else. This avoids problems when two (or more) carriers rule
-         * matches as the score helps to find the best match uniquely. e.g.,
-         * rule 1 {mccmnc, imsi} rule 2 {mccmnc, imsi, gid1} and rule 3 {mccmnc, imsi, gid2} all
-         * matches with subscription data. rule 2 wins with the highest matching score.
-         */
-        private static final int SCORE_MCCMNC          = 1 << 7;
-        private static final int SCORE_IMSI_PREFIX     = 1 << 6;
-        private static final int SCORE_ICCID_PREFIX    = 1 << 5;
-        private static final int SCORE_GID1            = 1 << 4;
-        private static final int SCORE_GID2            = 1 << 3;
-        private static final int SCORE_PLMN            = 1 << 2;
-        private static final int SCORE_SPN             = 1 << 1;
-        private static final int SCORE_APN             = 1 << 0;
-
-        private static final int SCORE_INVALID         = -1;
-
-        // carrier matching attributes
-        private String mMccMnc;
-        private String mImsiPrefixPattern;
-        private String mIccidPrefix;
-        private String mGid1;
-        private String mGid2;
-        private String mPlmn;
-        private String mSpn;
-        private String mApn;
-
-        // user-facing carrier name
-        private String mName;
-        // unique carrier id
-        private int mCid;
-
-        private int mScore = 0;
-
-        CarrierMatchingRule(String mccmnc, String imsiPrefixPattern, String iccidPrefix,
-                String gid1, String gid2, String plmn, String spn, String apn, int cid,
-                String name) {
-            mMccMnc = mccmnc;
-            mImsiPrefixPattern = imsiPrefixPattern;
-            mIccidPrefix = iccidPrefix;
-            mGid1 = gid1;
-            mGid2 = gid2;
-            mPlmn = plmn;
-            mSpn = spn;
-            mApn = apn;
-            mCid = cid;
-            mName = name;
-        }
-
-        // Calculate matching score. Values which aren't set in the rule are considered "wild".
-        // All values in the rule must match in order for the subscription to be considered part of
-        // the carrier. Otherwise, a invalid score -1 will be assigned. A match from a higher tier
-        // will beat any subsequent match which does not match at that tier. When there are multiple
-        // matches at the same tier, the match with highest score will be used.
-        public void match(CarrierMatchingRule subscriptionRule) {
-            mScore = 0;
-            if (mMccMnc != null) {
-                if (!CarrierIdentifier.equals(subscriptionRule.mMccMnc, mMccMnc, false)) {
-                    mScore = SCORE_INVALID;
-                    return;
-                }
-                mScore += SCORE_MCCMNC;
-            }
-            if (mImsiPrefixPattern != null) {
-                if (!imsiPrefixMatch(subscriptionRule.mImsiPrefixPattern, mImsiPrefixPattern)) {
-                    mScore = SCORE_INVALID;
-                    return;
-                }
-                mScore += SCORE_IMSI_PREFIX;
-            }
-            if (mIccidPrefix != null) {
-                if (!iccidPrefixMatch(subscriptionRule.mIccidPrefix, mIccidPrefix)) {
-                    mScore = SCORE_INVALID;
-                    return;
-                }
-                mScore += SCORE_ICCID_PREFIX;
-            }
-            if (mGid1 != null) {
-                // full string match. carrier matching should cover the corner case that gid1
-                // with garbage tail due to SIM manufacture issues.
-                if (!CarrierIdentifier.equals(subscriptionRule.mGid1, mGid1, true)) {
-                    mScore = SCORE_INVALID;
-                    return;
-                }
-                mScore += SCORE_GID1;
-            }
-            if (mGid2 != null) {
-                // full string match. carrier matching should cover the corner case that gid2
-                // with garbage tail due to SIM manufacture issues.
-                if (!CarrierIdentifier.equals(subscriptionRule.mGid2, mGid2, true)) {
-                    mScore = SCORE_INVALID;
-                    return;
-                }
-                mScore += SCORE_GID2;
-            }
-            if (mPlmn != null) {
-                if (!CarrierIdentifier.equals(subscriptionRule.mPlmn, mPlmn, true)) {
-                    mScore = SCORE_INVALID;
-                    return;
-                }
-                mScore += SCORE_PLMN;
-            }
-            if (mSpn != null) {
-                if (!CarrierIdentifier.equals(subscriptionRule.mSpn, mSpn, true)) {
-                    mScore = SCORE_INVALID;
-                    return;
-                }
-                mScore += SCORE_SPN;
-            }
-            if (mApn != null) {
-                if (!CarrierIdentifier.equals(subscriptionRule.mApn, mApn, true)) {
-                    mScore = SCORE_INVALID;
-                    return;
-                }
-                mScore += SCORE_APN;
-            }
-        }
-
-        private boolean imsiPrefixMatch(String imsi, String prefixXPattern) {
-            if (TextUtils.isEmpty(prefixXPattern)) return true;
-            if (TextUtils.isEmpty(imsi)) return false;
-            if (imsi.length() < prefixXPattern.length()) {
-                return false;
-            }
-            for (int i = 0; i < prefixXPattern.length(); i++) {
-                if ((prefixXPattern.charAt(i) != 'x') && (prefixXPattern.charAt(i) != 'X')
-                        && (prefixXPattern.charAt(i) != imsi.charAt(i))) {
-                    return false;
-                }
-            }
-            return true;
-        }
-
-        private boolean iccidPrefixMatch(String iccid, String prefix) {
-            if (iccid == null || prefix == null) {
-                return false;
-            }
-            return iccid.startsWith(prefix);
-        }
-
-        public String toString() {
-            return "[CarrierMatchingRule] -"
-                    + " mccmnc: " + mMccMnc
-                    + " gid1: " + mGid1
-                    + " gid2: " + mGid2
-                    + " plmn: " + mPlmn
-                    + " imsi_prefix: " + mImsiPrefixPattern
-                    + " iccid_prefix" + mIccidPrefix
-                    + " spn: " + mSpn
-                    + " apn: " + mApn
-                    + " name: " + mName
-                    + " cid: " + mCid
-                    + " score: " + mScore;
-        }
-    }
-
-    /**
-     * find the best matching carrier from candidates with matched MCCMNC and notify
-     * all interested parties on carrier id change.
-     */
-    private void matchCarrier() {
-        if (!SubscriptionManager.isValidSubscriptionId(mPhone.getSubId())) {
-            logd("[matchCarrier]" + "skip before sim records loaded");
-            return;
-        }
-        final String mccmnc = mTelephonyMgr.getSimOperatorNumericForPhone(mPhone.getPhoneId());
-        final String iccid = mPhone.getIccSerialNumber();
-        final String gid1 = mPhone.getGroupIdLevel1();
-        final String gid2 = mPhone.getGroupIdLevel2();
-        final String imsi = mPhone.getSubscriberId();
-        final String plmn = mPhone.getPlmn();
-        final String spn = mSpn;
-        final String apn = mPreferApn;
-
-        if (VDBG) {
-            logd("[matchCarrier]"
-                    + " mnnmnc:" + mccmnc
-                    + " gid1: " + gid1
-                    + " gid2: " + gid2
-                    + " imsi: " + Rlog.pii(LOG_TAG, imsi)
-                    + " iccid: " + Rlog.pii(LOG_TAG, iccid)
-                    + " plmn: " + plmn
-                    + " spn: " + spn
-                    + " apn: " + apn);
-        }
-
-        CarrierMatchingRule subscriptionRule = new CarrierMatchingRule(
-                mccmnc, imsi, iccid, gid1, gid2, plmn,  spn, apn,
-                TelephonyManager.UNKNOWN_CARRIER_ID, null);
-
-        int maxScore = CarrierMatchingRule.SCORE_INVALID;
-        CarrierMatchingRule maxRule = null;
-
-        for (CarrierMatchingRule rule : mCarrierMatchingRulesOnMccMnc) {
-            rule.match(subscriptionRule);
-            if (rule.mScore > maxScore) {
-                maxScore = rule.mScore;
-                maxRule = rule;
-            }
-        }
-
-        if (maxScore == CarrierMatchingRule.SCORE_INVALID) {
-            logd("[matchCarrier - no match] cid: " + TelephonyManager.UNKNOWN_CARRIER_ID
-                    + " name: " + null);
-            updateCarrierIdAndName(TelephonyManager.UNKNOWN_CARRIER_ID, null);
-        } else {
-            logd("[matchCarrier] cid: " + maxRule.mCid + " name: " + maxRule.mName);
-            updateCarrierIdAndName(maxRule.mCid, maxRule.mName);
-        }
-
-        /*
-         * Write Carrier Identification Matching event, logging with the
-         * 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,
-         * but the read gid1 is not matched within the highest-scored rule.
-         * 3) successfully found a matched carrier id in the provider.
-         * 4) use carrier list version to compare the unknown carrier ratio between each version.
-         */
-        String unknownGid1ToLog = ((maxScore & CarrierMatchingRule.SCORE_GID1) == 0
-                && !TextUtils.isEmpty(subscriptionRule.mGid1)) ? subscriptionRule.mGid1 : null;
-        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);
-    }
-
-    public int getCarrierListVersion() {
-        final Cursor cursor = mContext.getContentResolver().query(
-                Uri.withAppendedPath(CarrierId.All.CONTENT_URI,
-                "get_version"), null, null, null);
-        cursor.moveToFirst();
-        return cursor.getInt(0);
-    }
-
-    public int getCarrierId() {
-        return mCarrierId;
-    }
-
-    public String getCarrierName() {
-        return mCarrierName;
-    }
-
-    private static boolean equals(String a, String b, boolean ignoreCase) {
-        if (a == null && b == null) return true;
-        if (a != null && b != null) {
-            return (ignoreCase) ? a.equalsIgnoreCase(b) : a.equals(b);
-        }
-        return false;
-    }
-
-    private static void logd(String str) {
-        Rlog.d(LOG_TAG, str);
-    }
-    private static void loge(String str) {
-        Rlog.e(LOG_TAG, str);
-    }
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
-        ipw.println("mCarrierIdLocalLogs:");
-        ipw.increaseIndent();
-        mCarrierIdLocalLog.dump(fd, pw, args);
-        ipw.decreaseIndent();
-
-        ipw.println("mCarrierId: " + mCarrierId);
-        ipw.println("mCarrierName: " + mCarrierName);
-        ipw.println("version: " + getCarrierListVersion());
-
-        ipw.println("mCarrierMatchingRules on mccmnc: "
-                + mTelephonyMgr.getSimOperatorNumericForPhone(mPhone.getPhoneId()));
-        ipw.increaseIndent();
-        for (CarrierMatchingRule rule : mCarrierMatchingRulesOnMccMnc) {
-            ipw.println(rule.toString());
-        }
-        ipw.decreaseIndent();
-
-        ipw.println("mSpn: " + mSpn);
-        ipw.println("mPreferApn: " + mPreferApn);
-        ipw.flush();
-    }
-}
diff --git a/src/java/com/android/internal/telephony/CarrierResolver.java b/src/java/com/android/internal/telephony/CarrierResolver.java
new file mode 100644
index 0000000..0c7950f
--- /dev/null
+++ b/src/java/com/android/internal/telephony/CarrierResolver.java
@@ -0,0 +1,983 @@
+/*
+ * 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 android.provider.Telephony.CarrierId;
+
+import android.annotation.NonNull;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Message;
+import android.provider.Telephony;
+import android.service.carrier.CarrierIdentifier;
+import android.telephony.Rlog;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.LocalLog;
+import android.util.Log;
+
+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.util.IndentingPrintWriter;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * CarrierResolver identifies the subscription carrier and returns a canonical carrier Id
+ * and a user friendly carrier name. CarrierResolver reads subscription info and check against
+ * all carrier matching rules stored in CarrierIdProvider. It is msim aware, each phone has a
+ * dedicated CarrierResolver.
+ */
+public class CarrierResolver extends Handler {
+    private static final String LOG_TAG = CarrierResolver.class.getSimpleName();
+    private static final boolean DBG = true;
+    private static final boolean VDBG = Rlog.isLoggable(LOG_TAG, Log.VERBOSE);
+
+    // events to trigger carrier identification
+    private static final int SIM_LOAD_EVENT             = 1;
+    private static final int ICC_CHANGED_EVENT          = 2;
+    private static final int PREFER_APN_UPDATE_EVENT    = 3;
+    private static final int CARRIER_ID_DB_UPDATE_EVENT = 4;
+
+    private static final Uri CONTENT_URL_PREFER_APN = Uri.withAppendedPath(
+            Telephony.Carriers.CONTENT_URI, "preferapn");
+
+    // cached matching rules based mccmnc to speed up resolution
+    private List<CarrierMatchingRule> mCarrierMatchingRulesOnMccMnc = new ArrayList<>();
+    // cached carrier Id
+    private int mCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
+    // cached precise carrier Id
+    private int mPreciseCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
+    // cached MNO carrier Id. mno carrier shares the same mccmnc as cid and can be solely
+    // identified by mccmnc only. If there is no such mno carrier, mno carrier id equals to
+    // the cid.
+    private int mMnoCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
+    // cached carrier name
+    private String mCarrierName;
+    private String mPreciseCarrierName;
+    // cached preferapn name
+    private String mPreferApn;
+    // cached service provider name. telephonyManager API returns empty string as default value.
+    // some carriers need to target devices with Empty SPN. In that case, carrier matching rule
+    // should specify "" spn explicitly.
+    private String mSpn = "";
+
+    private Context mContext;
+    private Phone mPhone;
+    private IccRecords mIccRecords;
+    private final LocalLog mCarrierIdLocalLog = new LocalLog(20);
+    private final TelephonyManager mTelephonyMgr;
+
+    private final ContentObserver mContentObserver = new ContentObserver(this) {
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            if (CONTENT_URL_PREFER_APN.equals(uri.getLastPathSegment())) {
+                logd("onChange URI: " + uri);
+                sendEmptyMessage(PREFER_APN_UPDATE_EVENT);
+            } else if (CarrierId.All.CONTENT_URI.equals(uri)) {
+                logd("onChange URI: " + uri);
+                sendEmptyMessage(CARRIER_ID_DB_UPDATE_EVENT);
+            }
+        }
+    };
+
+    public CarrierResolver(Phone phone) {
+        logd("Creating CarrierResolver[" + phone.getPhoneId() + "]");
+        mContext = phone.getContext();
+        mPhone = phone;
+        mTelephonyMgr = TelephonyManager.from(mContext);
+
+        // register events
+        mContext.getContentResolver().registerContentObserver(CONTENT_URL_PREFER_APN, false,
+                mContentObserver);
+        mContext.getContentResolver().registerContentObserver(
+                CarrierId.All.CONTENT_URI, false, mContentObserver);
+        UiccController.getInstance().registerForIccChanged(this, ICC_CHANGED_EVENT, null);
+    }
+
+    /**
+     * This is triggered from SubscriptionInfoUpdater after sim state change.
+     * The sequence of sim loading would be
+     *  1. ACTION_SUBINFO_CONTENT_CHANGE
+     *  2. ACTION_SIM_STATE_CHANGED/ACTION_SIM_CARD_STATE_CHANGED
+     *  /ACTION_SIM_APPLICATION_STATE_CHANGED
+     *  3. ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED
+     *
+     *  For SIM refresh either reset or file update, SubscriptionInfoUpdater will re-trigger
+     *  carrier identification with sim loaded state.
+     */
+    public void resolveSubscriptionCarrierId(String simState) {
+        logd("[resolveSubscriptionCarrierId] simState: " + simState);
+        switch (simState) {
+            case IccCardConstants.INTENT_VALUE_ICC_ABSENT:
+                // only clear carrier id on absent to avoid transition to unknown carrier id during
+                // intermediate states of sim refresh
+                handleSimAbsent();
+                break;
+            case IccCardConstants.INTENT_VALUE_ICC_LOCKED:
+                // intentional fall through from above case, treat locked same as loaded
+            case IccCardConstants.INTENT_VALUE_ICC_LOADED:
+                handleSimLoaded();
+                break;
+        }
+    }
+
+    private void handleSimLoaded() {
+        if (mIccRecords != null) {
+            /**
+             * returns empty string to be consistent with
+             * {@link TelephonyManager#getSimOperatorName()}
+             */
+            mSpn = (mIccRecords.getServiceProviderName() == null) ? ""
+                    : mIccRecords.getServiceProviderName();
+        } else {
+            loge("mIccRecords is null on SIM_LOAD_EVENT, could not get SPN");
+        }
+        mPreferApn = getPreferApn();
+        loadCarrierMatchingRulesOnMccMnc();
+    }
+
+    private void handleSimAbsent() {
+        mCarrierMatchingRulesOnMccMnc.clear();
+        mSpn = null;
+        mPreferApn = null;
+        updateCarrierIdAndName(TelephonyManager.UNKNOWN_CARRIER_ID, null,
+                TelephonyManager.UNKNOWN_CARRIER_ID, null,
+                TelephonyManager.UNKNOWN_CARRIER_ID);
+    }
+
+    /**
+     * Entry point for the carrier identification.
+     *
+     *    1. SIM_LOAD_EVENT
+     *        This indicates that all SIM records has been loaded and its first entry point for the
+     *        carrier identification. Note, there are other attributes could be changed on the fly
+     *        like APN. We cached all carrier matching rules based on MCCMNC to speed
+     *        up carrier resolution on following trigger events.
+     *
+     *    2. PREFER_APN_UPDATE_EVENT
+     *        This indicates prefer apn has been changed. It could be triggered when user modified
+     *        APN settings or when default data connection first establishes on the current carrier.
+     *        We follow up on this by querying prefer apn sqlite and re-issue carrier identification
+     *        with the updated prefer apn name.
+     *
+     *    3. CARRIER_ID_DB_UPDATE_EVENT
+     *        This indicates that carrierIdentification database which stores all matching rules
+     *        has been updated. It could be triggered from OTA or assets update.
+     */
+    @Override
+    public void handleMessage(Message msg) {
+        if (DBG) logd("handleMessage: " + msg.what);
+        switch (msg.what) {
+            case SIM_LOAD_EVENT:
+                handleSimLoaded();
+                break;
+            case CARRIER_ID_DB_UPDATE_EVENT:
+                loadCarrierMatchingRulesOnMccMnc();
+                break;
+            case PREFER_APN_UPDATE_EVENT:
+                String preferApn = getPreferApn();
+                if (!equals(mPreferApn, preferApn, true)) {
+                    logd("[updatePreferApn] from:" + mPreferApn + " to:" + preferApn);
+                    mPreferApn = preferApn;
+                    matchSubscriptionCarrier();
+                }
+                break;
+            case ICC_CHANGED_EVENT:
+                // 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.unregisterForRecordsLoaded(this);
+                        mIccRecords.unregisterForRecordsOverride(this);
+                        mIccRecords = null;
+                    }
+                    if (newIccRecords != null) {
+                        logd("new Icc object");
+                        newIccRecords.registerForRecordsLoaded(this, SIM_LOAD_EVENT, null);
+                        newIccRecords.registerForRecordsOverride(this, SIM_LOAD_EVENT, null);
+                        mIccRecords = newIccRecords;
+                    }
+                }
+                break;
+            default:
+                loge("invalid msg: " + msg.what);
+                break;
+        }
+    }
+
+    private void loadCarrierMatchingRulesOnMccMnc() {
+        try {
+            String mccmnc = mTelephonyMgr.getSimOperatorNumericForPhone(mPhone.getPhoneId());
+            Cursor cursor = mContext.getContentResolver().query(
+                    CarrierId.All.CONTENT_URI,
+                    /* projection */ null,
+                    /* selection */ CarrierId.All.MCCMNC + "=?",
+                    /* selectionArgs */ new String[]{mccmnc}, null);
+            try {
+                if (cursor != null) {
+                    if (VDBG) {
+                        logd("[loadCarrierMatchingRules]- " + cursor.getCount()
+                                + " Records(s) in DB" + " mccmnc: " + mccmnc);
+                    }
+                    mCarrierMatchingRulesOnMccMnc.clear();
+                    while (cursor.moveToNext()) {
+                        mCarrierMatchingRulesOnMccMnc.add(makeCarrierMatchingRule(cursor));
+                    }
+                    matchSubscriptionCarrier();
+                }
+            } finally {
+                if (cursor != null) {
+                    cursor.close();
+                }
+            }
+        } catch (Exception ex) {
+            loge("[loadCarrierMatchingRules]- ex: " + ex);
+        }
+    }
+
+    private String getCarrierNameFromId(int cid) {
+        try {
+            Cursor cursor = mContext.getContentResolver().query(
+                    CarrierId.All.CONTENT_URI,
+                    /* projection */ null,
+                    /* selection */ CarrierId.CARRIER_ID + "=?",
+                    /* selectionArgs */ new String[]{cid + ""}, null);
+            try {
+                if (cursor != null) {
+                    if (VDBG) {
+                        logd("[getCarrierNameFromId]- " + cursor.getCount()
+                                + " Records(s) in DB" + " cid: " + cid);
+                    }
+                    while (cursor.moveToNext()) {
+                        return cursor.getString(cursor.getColumnIndex(CarrierId.CARRIER_NAME));
+                    }
+                }
+            } finally {
+                if (cursor != null) {
+                    cursor.close();
+                }
+            }
+        } catch (Exception ex) {
+            loge("[getCarrierNameFromId]- ex: " + ex);
+        }
+        return null;
+    }
+
+    private static List<CarrierMatchingRule> getCarrierMatchingRulesFromMccMnc(
+            @NonNull Context context, String mccmnc) {
+        List<CarrierMatchingRule> rules = new ArrayList<>();
+        try {
+            Cursor cursor = context.getContentResolver().query(
+                    CarrierId.All.CONTENT_URI,
+                    /* projection */ null,
+                    /* selection */ CarrierId.All.MCCMNC + "=?",
+                    /* selectionArgs */ new String[]{mccmnc}, null);
+            try {
+                if (cursor != null) {
+                    if (VDBG) {
+                        logd("[loadCarrierMatchingRules]- " + cursor.getCount()
+                                + " Records(s) in DB" + " mccmnc: " + mccmnc);
+                    }
+                    rules.clear();
+                    while (cursor.moveToNext()) {
+                        rules.add(makeCarrierMatchingRule(cursor));
+                    }
+                }
+            } finally {
+                if (cursor != null) {
+                    cursor.close();
+                }
+            }
+        } catch (Exception ex) {
+            loge("[loadCarrierMatchingRules]- ex: " + ex);
+        }
+        return rules;
+    }
+
+    private String getPreferApn() {
+        Cursor cursor = mContext.getContentResolver().query(
+                Uri.withAppendedPath(Telephony.Carriers.CONTENT_URI, "preferapn/subId/"
+                + mPhone.getSubId()), /* projection */ new String[]{Telephony.Carriers.APN},
+                /* selection */ null, /* selectionArgs */ null, /* sortOrder */ null);
+        try {
+            if (cursor != null) {
+                if (VDBG) {
+                    logd("[getPreferApn]- " + cursor.getCount() + " Records(s) in DB");
+                }
+                while (cursor.moveToNext()) {
+                    String apn = cursor.getString(cursor.getColumnIndexOrThrow(
+                            Telephony.Carriers.APN));
+                    logd("[getPreferApn]- " + apn);
+                    return apn;
+                }
+            }
+        } catch (Exception ex) {
+            loge("[getPreferApn]- exception: " + ex);
+        } finally {
+            if (cursor != null) {
+                cursor.close();
+            }
+        }
+        return null;
+    }
+
+    private void updateCarrierIdAndName(int cid, String name,
+                                        int preciseCarrierId, String preciseCarrierName,
+                                        int mnoCid) {
+        boolean update = false;
+        if (!equals(name, mCarrierName, true)) {
+            logd("[updateCarrierName] from:" + mCarrierName + " to:" + name);
+            mCarrierName = name;
+            update = true;
+        }
+        if (cid != mCarrierId) {
+            logd("[updateCarrierId] from:" + mCarrierId + " to:" + cid);
+            mCarrierId = cid;
+            update = true;
+        }
+        if (mnoCid != mMnoCarrierId) {
+            logd("[updateMnoCarrierId] from:" + mMnoCarrierId + " to:" + mnoCid);
+            mMnoCarrierId = mnoCid;
+            update = true;
+        }
+        if (update) {
+            mCarrierIdLocalLog.log("[updateCarrierIdAndName] cid:" + mCarrierId + " name:"
+                    + mCarrierName + " mnoCid:" + mMnoCarrierId);
+            final Intent intent = new Intent(TelephonyManager
+                    .ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED);
+            intent.putExtra(TelephonyManager.EXTRA_CARRIER_ID, mCarrierId);
+            intent.putExtra(TelephonyManager.EXTRA_CARRIER_NAME, mCarrierName);
+            intent.putExtra(TelephonyManager.EXTRA_MNO_CARRIER_ID, mMnoCarrierId);
+            intent.putExtra(TelephonyManager.EXTRA_SUBSCRIPTION_ID, mPhone.getSubId());
+            mContext.sendBroadcast(intent);
+
+            // notify content observers for carrier id change event
+            ContentValues cv = new ContentValues();
+            cv.put(CarrierId.CARRIER_ID, mCarrierId);
+            cv.put(CarrierId.CARRIER_NAME, mCarrierName);
+            cv.put(CarrierId.MNO_CARRIER_ID, mMnoCarrierId);
+            mContext.getContentResolver().update(
+                    Uri.withAppendedPath(CarrierId.CONTENT_URI,
+                            Integer.toString(mPhone.getSubId())), cv, null, null);
+        }
+
+        update = false;
+        if (preciseCarrierId != mPreciseCarrierId) {
+            logd("[updatePreciseCarrierId] from:" + mPreciseCarrierId + " to:"
+                    + preciseCarrierId);
+            mPreciseCarrierId = preciseCarrierId;
+            update = true;
+        }
+        if (preciseCarrierName != mPreciseCarrierName) {
+            logd("[updatePreciseCarrierName] from:" + mPreciseCarrierName + " to:"
+                    + preciseCarrierName);
+            mPreciseCarrierName = preciseCarrierName;
+            update = true;
+        }
+        if (update) {
+            mCarrierIdLocalLog.log("[updatePreciseCarrierIdAndName] cid:" + mPreciseCarrierId
+                    + " name:" + mPreciseCarrierName);
+            final Intent intent = new Intent(TelephonyManager
+                    .ACTION_SUBSCRIPTION_PRECISE_CARRIER_IDENTITY_CHANGED);
+            intent.putExtra(TelephonyManager.EXTRA_PRECISE_CARRIER_ID, mPreciseCarrierId);
+            intent.putExtra(TelephonyManager.EXTRA_PRECISE_CARRIER_NAME, mPreciseCarrierName);
+            intent.putExtra(TelephonyManager.EXTRA_SUBSCRIPTION_ID, mPhone.getSubId());
+            mContext.sendBroadcast(intent);
+
+            // notify content observers for precise carrier id change event.
+            ContentValues cv = new ContentValues();
+            cv.put(CarrierId.PRECISE_CARRIER_ID, mPreciseCarrierId);
+            cv.put(CarrierId.PRECISE_CARRIER_ID_NAME, mPreciseCarrierName);
+            mContext.getContentResolver().update(
+                    Telephony.CarrierId.getPreciseCarrierIdUriForSubscriptionId(mPhone.getSubId()),
+                    cv, null, null);
+        }
+    }
+
+    private static CarrierMatchingRule makeCarrierMatchingRule(Cursor cursor) {
+        String certs = cursor.getString(
+                cursor.getColumnIndexOrThrow(CarrierId.All.PRIVILEGE_ACCESS_RULE));
+        return new CarrierMatchingRule(
+                cursor.getString(cursor.getColumnIndexOrThrow(CarrierId.All.MCCMNC)),
+                cursor.getString(cursor.getColumnIndexOrThrow(
+                        CarrierId.All.IMSI_PREFIX_XPATTERN)),
+                cursor.getString(cursor.getColumnIndexOrThrow(
+                        CarrierId.All.ICCID_PREFIX)),
+                cursor.getString(cursor.getColumnIndexOrThrow(CarrierId.All.GID1)),
+                cursor.getString(cursor.getColumnIndexOrThrow(CarrierId.All.GID2)),
+                cursor.getString(cursor.getColumnIndexOrThrow(CarrierId.All.PLMN)),
+                cursor.getString(cursor.getColumnIndexOrThrow(CarrierId.All.SPN)),
+                cursor.getString(cursor.getColumnIndexOrThrow(CarrierId.All.APN)),
+                (TextUtils.isEmpty(certs) ? null : new ArrayList<>(Arrays.asList(certs))),
+                cursor.getInt(cursor.getColumnIndexOrThrow(CarrierId.CARRIER_ID)),
+                cursor.getString(cursor.getColumnIndexOrThrow(CarrierId.CARRIER_NAME)),
+                cursor.getInt(cursor.getColumnIndexOrThrow(CarrierId.PARENT_CARRIER_ID)));
+    }
+
+    /**
+     * carrier matching attributes with corresponding cid
+     */
+    private static class CarrierMatchingRule {
+        /**
+         * These scores provide the hierarchical relationship between the attributes, intended to
+         * resolve conflicts in a deterministic way. The scores are constructed such that a match
+         * from a higher tier will beat any subsequent match which does not match at that tier,
+         * so MCCMNC beats everything else. This avoids problems when two (or more) carriers rule
+         * matches as the score helps to find the best match uniquely. e.g.,
+         * rule 1 {mccmnc, imsi} rule 2 {mccmnc, imsi, gid1} and rule 3 {mccmnc, imsi, gid2} all
+         * matches with subscription data. rule 2 wins with the highest matching score.
+         */
+        private static final int SCORE_MCCMNC                   = 1 << 8;
+        private static final int SCORE_IMSI_PREFIX              = 1 << 7;
+        private static final int SCORE_ICCID_PREFIX             = 1 << 6;
+        private static final int SCORE_GID1                     = 1 << 5;
+        private static final int SCORE_GID2                     = 1 << 4;
+        private static final int SCORE_PLMN                     = 1 << 3;
+        private static final int SCORE_PRIVILEGE_ACCESS_RULE    = 1 << 2;
+        private static final int SCORE_SPN                      = 1 << 1;
+        private static final int SCORE_APN                      = 1 << 0;
+
+        private static final int SCORE_INVALID                  = -1;
+
+        // carrier matching attributes
+        private final String mMccMnc;
+        private final String mImsiPrefixPattern;
+        private final String mIccidPrefix;
+        private final String mGid1;
+        private final String mGid2;
+        private final String mPlmn;
+        private final String mSpn;
+        private final String mApn;
+        // there can be multiple certs configured in the UICC
+        private final List<String> mPrivilegeAccessRule;
+
+        // user-facing carrier name
+        private String mName;
+        // unique carrier id
+        private int mCid;
+        // unique parent carrier id
+        private int mParentCid;
+
+        private int mScore = 0;
+
+        private CarrierMatchingRule(String mccmnc, String imsiPrefixPattern, String iccidPrefix,
+                String gid1, String gid2, String plmn, String spn, String apn,
+                List<String> privilegeAccessRule, int cid, String name, int parentCid) {
+            mMccMnc = mccmnc;
+            mImsiPrefixPattern = imsiPrefixPattern;
+            mIccidPrefix = iccidPrefix;
+            mGid1 = gid1;
+            mGid2 = gid2;
+            mPlmn = plmn;
+            mSpn = spn;
+            mApn = apn;
+            mPrivilegeAccessRule = privilegeAccessRule;
+            mCid = cid;
+            mName = name;
+            mParentCid = parentCid;
+        }
+
+        private CarrierMatchingRule(CarrierMatchingRule rule) {
+            mMccMnc = rule.mMccMnc;
+            mImsiPrefixPattern = rule.mImsiPrefixPattern;
+            mIccidPrefix = rule.mIccidPrefix;
+            mGid1 = rule.mGid1;
+            mGid2 = rule.mGid2;
+            mPlmn = rule.mPlmn;
+            mSpn = rule.mSpn;
+            mApn = rule.mApn;
+            mPrivilegeAccessRule = rule.mPrivilegeAccessRule;
+            mCid = rule.mCid;
+            mName = rule.mName;
+            mParentCid = rule.mParentCid;
+        }
+
+        // Calculate matching score. Values which aren't set in the rule are considered "wild".
+        // All values in the rule must match in order for the subscription to be considered part of
+        // the carrier. Otherwise, a invalid score -1 will be assigned. A match from a higher tier
+        // will beat any subsequent match which does not match at that tier. When there are multiple
+        // matches at the same tier, the match with highest score will be used.
+        public void match(CarrierMatchingRule subscriptionRule) {
+            mScore = 0;
+            if (mMccMnc != null) {
+                if (!CarrierResolver.equals(subscriptionRule.mMccMnc, mMccMnc, false)) {
+                    mScore = SCORE_INVALID;
+                    return;
+                }
+                mScore += SCORE_MCCMNC;
+            }
+            if (mImsiPrefixPattern != null) {
+                if (!imsiPrefixMatch(subscriptionRule.mImsiPrefixPattern, mImsiPrefixPattern)) {
+                    mScore = SCORE_INVALID;
+                    return;
+                }
+                mScore += SCORE_IMSI_PREFIX;
+            }
+            if (mIccidPrefix != null) {
+                if (!iccidPrefixMatch(subscriptionRule.mIccidPrefix, mIccidPrefix)) {
+                    mScore = SCORE_INVALID;
+                    return;
+                }
+                mScore += SCORE_ICCID_PREFIX;
+            }
+            if (mGid1 != null) {
+                if (!gidMatch(subscriptionRule.mGid1, mGid1)) {
+                    mScore = SCORE_INVALID;
+                    return;
+                }
+                mScore += SCORE_GID1;
+            }
+            if (mGid2 != null) {
+                if (!gidMatch(subscriptionRule.mGid2, mGid2)) {
+                    mScore = SCORE_INVALID;
+                    return;
+                }
+                mScore += SCORE_GID2;
+            }
+            if (mPlmn != null) {
+                if (!CarrierResolver.equals(subscriptionRule.mPlmn, mPlmn, true)) {
+                    mScore = SCORE_INVALID;
+                    return;
+                }
+                mScore += SCORE_PLMN;
+            }
+            if (mSpn != null) {
+                if (!CarrierResolver.equals(subscriptionRule.mSpn, mSpn, true)) {
+                    mScore = SCORE_INVALID;
+                    return;
+                }
+                mScore += SCORE_SPN;
+            }
+
+            if (mPrivilegeAccessRule != null && !mPrivilegeAccessRule.isEmpty()) {
+                if (!carrierPrivilegeRulesMatch(subscriptionRule.mPrivilegeAccessRule,
+                        mPrivilegeAccessRule)) {
+                    mScore = SCORE_INVALID;
+                    return;
+                }
+                mScore += SCORE_PRIVILEGE_ACCESS_RULE;
+            }
+
+            if (mApn != null) {
+                if (!CarrierResolver.equals(subscriptionRule.mApn, mApn, true)) {
+                    mScore = SCORE_INVALID;
+                    return;
+                }
+                mScore += SCORE_APN;
+            }
+        }
+
+        private boolean imsiPrefixMatch(String imsi, String prefixXPattern) {
+            if (TextUtils.isEmpty(prefixXPattern)) return true;
+            if (TextUtils.isEmpty(imsi)) return false;
+            if (imsi.length() < prefixXPattern.length()) {
+                return false;
+            }
+            for (int i = 0; i < prefixXPattern.length(); i++) {
+                if ((prefixXPattern.charAt(i) != 'x') && (prefixXPattern.charAt(i) != 'X')
+                        && (prefixXPattern.charAt(i) != imsi.charAt(i))) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        private boolean iccidPrefixMatch(String iccid, String prefix) {
+            if (iccid == null || prefix == null) {
+                return false;
+            }
+            return iccid.startsWith(prefix);
+        }
+
+        // We are doing prefix and case insensitive match.
+        // Ideally we should do full string match. However due to SIM manufacture issues
+        // gid from some SIM might has garbage tail.
+        private boolean gidMatch(String gidFromSim, String gid) {
+            return (gidFromSim != null) && gidFromSim.toLowerCase().startsWith(gid.toLowerCase());
+        }
+
+        private boolean carrierPrivilegeRulesMatch(List<String> certsFromSubscription,
+                                                   List<String> certs) {
+            if (certsFromSubscription == null || certsFromSubscription.isEmpty()) {
+                return false;
+            }
+            for (String cert : certs) {
+                for (String certFromSubscription : certsFromSubscription) {
+                    if (!TextUtils.isEmpty(cert)
+                            && cert.equalsIgnoreCase(certFromSubscription)) {
+                        return true;
+                    }
+                }
+            }
+            return false;
+        }
+
+        public String toString() {
+            return "[CarrierMatchingRule] -"
+                    + " mccmnc: " + mMccMnc
+                    + " gid1: " + mGid1
+                    + " gid2: " + mGid2
+                    + " plmn: " + mPlmn
+                    + " imsi_prefix: " + mImsiPrefixPattern
+                    + " iccid_prefix" + mIccidPrefix
+                    + " spn: " + mSpn
+                    + " privilege_access_rule: " + mPrivilegeAccessRule
+                    + " apn: " + mApn
+                    + " name: " + mName
+                    + " cid: " + mCid
+                    + " score: " + mScore;
+        }
+    }
+
+    private CarrierMatchingRule getSubscriptionMatchingRule() {
+        final String mccmnc = mTelephonyMgr.getSimOperatorNumericForPhone(mPhone.getPhoneId());
+        final String iccid = mPhone.getIccSerialNumber();
+        final String gid1 = mPhone.getGroupIdLevel1();
+        final String gid2 = mPhone.getGroupIdLevel2();
+        final String imsi = mPhone.getSubscriberId();
+        final String plmn = mPhone.getPlmn();
+        final String spn = mSpn;
+        final String apn = mPreferApn;
+        final List<String> accessRules = mTelephonyMgr.getCertsFromCarrierPrivilegeAccessRules();
+
+        if (VDBG) {
+            logd("[matchSubscriptionCarrier]"
+                    + " mnnmnc:" + mccmnc
+                    + " gid1: " + gid1
+                    + " gid2: " + gid2
+                    + " imsi: " + Rlog.pii(LOG_TAG, imsi)
+                    + " iccid: " + Rlog.pii(LOG_TAG, iccid)
+                    + " plmn: " + plmn
+                    + " spn: " + spn
+                    + " apn: " + apn
+                    + " accessRules: " + ((accessRules != null) ? accessRules : null));
+        }
+        return new CarrierMatchingRule(
+                mccmnc, imsi, iccid, gid1, gid2, plmn, spn, apn, accessRules,
+                TelephonyManager.UNKNOWN_CARRIER_ID, null,
+                TelephonyManager.UNKNOWN_CARRIER_ID);
+    }
+
+    /**
+     * find the best matching carrier from candidates with matched subscription MCCMNC.
+     */
+    private void matchSubscriptionCarrier() {
+        if (!SubscriptionManager.isValidSubscriptionId(mPhone.getSubId())) {
+            logd("[matchSubscriptionCarrier]" + "skip before sim records loaded");
+            return;
+        }
+        int maxScore = CarrierMatchingRule.SCORE_INVALID;
+        /**
+         * For child-parent relationship. either child and parent have the same matching
+         * score, or child's matching score > parents' matching score.
+         */
+        CarrierMatchingRule maxRule = null;
+        CarrierMatchingRule maxRuleParent = null;
+        /**
+         * matching rule with mccmnc only. If mnoRule is found, then mno carrier id equals to the
+         * cid from mnoRule. otherwise, mno carrier id is same as cid.
+         */
+        CarrierMatchingRule mnoRule = null;
+        CarrierMatchingRule subscriptionRule = getSubscriptionMatchingRule();
+
+        for (CarrierMatchingRule rule : mCarrierMatchingRulesOnMccMnc) {
+            rule.match(subscriptionRule);
+            if (rule.mScore > maxScore) {
+                maxScore = rule.mScore;
+                maxRule = rule;
+                maxRuleParent = rule;
+            } else if (maxScore > CarrierMatchingRule.SCORE_INVALID && rule.mScore == maxScore) {
+                // to handle the case that child parent has the same matching score, we need to
+                // differentiate who is child who is parent.
+                if (rule.mParentCid == maxRule.mCid) {
+                    maxRule = rule;
+                } else if (maxRule.mParentCid == rule.mCid) {
+                    maxRuleParent = rule;
+                }
+            }
+            if (rule.mScore == CarrierMatchingRule.SCORE_MCCMNC) {
+                mnoRule = rule;
+            }
+        }
+        if (maxScore == CarrierMatchingRule.SCORE_INVALID) {
+            logd("[matchSubscriptionCarrier - no match] cid: " + TelephonyManager.UNKNOWN_CARRIER_ID
+                    + " name: " + null);
+            updateCarrierIdAndName(TelephonyManager.UNKNOWN_CARRIER_ID, null,
+                    TelephonyManager.UNKNOWN_CARRIER_ID, null,
+                    TelephonyManager.UNKNOWN_CARRIER_ID);
+        } else {
+            // if there is a single matching result, check if this rule has parent cid assigned.
+            if ((maxRule == maxRuleParent)
+                    && maxRule.mParentCid != TelephonyManager.UNKNOWN_CARRIER_ID) {
+                maxRuleParent = new CarrierMatchingRule(maxRule);
+                maxRuleParent.mCid = maxRuleParent.mParentCid;
+                maxRuleParent.mName = getCarrierNameFromId(maxRuleParent.mCid);
+            }
+            logd("[matchSubscriptionCarrier] precise cid: " + maxRule.mCid + " precise name: "
+                    + maxRule.mName +" cid: " + maxRuleParent.mCid
+                    + " name: " + maxRuleParent.mName);
+            updateCarrierIdAndName(maxRuleParent.mCid, maxRuleParent.mName,
+                    maxRule.mCid, maxRule.mName,
+                    (mnoRule == null) ? maxRule.mCid : mnoRule.mCid);
+        }
+
+        /*
+         * Write Carrier Identification Matching event, logging with the
+         * 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,
+         * but the read gid1 is not matched within the highest-scored rule.
+         * 3) successfully found a matched carrier id in the provider.
+         * 4) use carrier list version to compare the unknown carrier ratio between each version.
+         */
+        String unknownGid1ToLog = ((maxScore & CarrierMatchingRule.SCORE_GID1) == 0
+                && !TextUtils.isEmpty(subscriptionRule.mGid1)) ? subscriptionRule.mGid1 : null;
+        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);
+    }
+
+    public int getCarrierListVersion() {
+        final Cursor cursor = mContext.getContentResolver().query(
+                Uri.withAppendedPath(CarrierId.All.CONTENT_URI,
+                "get_version"), null, null, null);
+        cursor.moveToFirst();
+        return cursor.getInt(0);
+    }
+
+    public int getCarrierId() {
+        return mCarrierId;
+    }
+    /**
+     * Returns fine-grained carrier id of the current subscription. Carrier ids with a valid parent
+     * id are precise carrier ids.
+     * The precise carrier id can be used to further differentiate a carrier by different
+     * networks, by prepaid v.s.postpaid or even by 4G v.s.3G plan. Each carrier has a unique
+     * carrier id but can have multiple precise carrier id. e.g,
+     * {@link #getCarrierId()} will always return Tracfone (id 2022) for a Tracfone SIM, while
+     * {@link #getPreciseCarrierId()} can return Tracfone AT&T or Tracfone T-Mobile based on the
+     * current underlying network.
+     * For carriers without any fine-grained carrier ids, return {@link #getCarrierId()}
+     */
+    public int getPreciseCarrierId() {
+        return mPreciseCarrierId;
+    }
+
+    public String getCarrierName() {
+        return mCarrierName;
+    }
+
+    public String getPreciseCarrierName() {
+        return mPreciseCarrierName;
+    }
+
+    public int getMnoCarrierId() {
+        return mMnoCarrierId;
+    }
+
+    /**
+     * a util function to convert carrierIdentifier to the best matching carrier id.
+     *
+     * @return the best matching carrier id.
+     */
+    public static int getCarrierIdFromIdentifier(@NonNull Context context,
+                                                 @NonNull CarrierIdentifier carrierIdentifier) {
+        final String mccmnc = carrierIdentifier.getMcc() + carrierIdentifier.getMnc();
+        final String gid1 = carrierIdentifier.getGid1();
+        final String gid2 = carrierIdentifier.getGid2();
+        final String imsi = carrierIdentifier.getImsi();
+        final String spn = carrierIdentifier.getSpn();
+        if (VDBG) {
+            logd("[getCarrierIdFromIdentifier]"
+                    + " mnnmnc:" + mccmnc
+                    + " gid1: " + gid1
+                    + " gid2: " + gid2
+                    + " imsi: " + Rlog.pii(LOG_TAG, imsi)
+                    + " spn: " + spn);
+        }
+        // assign null to other fields which are not supported by carrierIdentifier.
+        CarrierMatchingRule targetRule =
+                new CarrierMatchingRule(mccmnc, imsi, null, gid1, gid2, null,
+                        spn, null, null,
+                        TelephonyManager.UNKNOWN_CARRIER_ID_LIST_VERSION, null,
+                        TelephonyManager.UNKNOWN_CARRIER_ID);
+
+        int carrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
+        int maxScore = CarrierMatchingRule.SCORE_INVALID;
+        List<CarrierMatchingRule> rules = getCarrierMatchingRulesFromMccMnc(
+                context, targetRule.mMccMnc);
+        for (CarrierMatchingRule rule : rules) {
+            rule.match(targetRule);
+            if (rule.mScore > maxScore) {
+                maxScore = rule.mScore;
+                carrierId = rule.mCid;
+            }
+        }
+        return carrierId;
+    }
+
+    /**
+     * a util function to convert {mccmnc, mvno_type, mvno_data} to all matching carrier ids.
+     *
+     * @return a list of id with matching {mccmnc, mvno_type, mvno_data}
+     */
+    public static List<Integer> getCarrierIdsFromApnQuery(@NonNull Context context,
+                                                          String mccmnc, String mvnoCase,
+                                                          String mvnoData) {
+        String selection = CarrierId.All.MCCMNC + "=" + mccmnc;
+        // build the proper query
+        if ("spn".equals(mvnoCase) && mvnoData != null) {
+            selection += " AND " + CarrierId.All.SPN + "='" + mvnoData + "'";
+        } else if ("imsi".equals(mvnoCase) && mvnoData != null) {
+            selection += " AND " + CarrierId.All.IMSI_PREFIX_XPATTERN + "='" + mvnoData + "'";
+        } else if ("gid1".equals(mvnoCase) && mvnoData != null) {
+            selection += " AND " + CarrierId.All.GID1 + "='" + mvnoData + "'";
+        } else if ("gid2".equals(mvnoCase) && mvnoData != null) {
+            selection += " AND " + CarrierId.All.GID2 + "='" + mvnoData +"'";
+        } else {
+            logd("mvno case empty or other invalid values");
+        }
+
+        List<Integer> ids = new ArrayList<>();
+        try {
+            Cursor cursor = context.getContentResolver().query(
+                    CarrierId.All.CONTENT_URI,
+                    /* projection */ null,
+                    /* selection */ selection,
+                    /* selectionArgs */ null, null);
+            try {
+                if (cursor != null) {
+                    if (VDBG) {
+                        logd("[getCarrierIdsFromApnQuery]- " + cursor.getCount()
+                                + " Records(s) in DB");
+                    }
+                    while (cursor.moveToNext()) {
+                        int cid = cursor.getInt(cursor.getColumnIndex(CarrierId.CARRIER_ID));
+                        if (!ids.contains(cid)) {
+                            ids.add(cid);
+                        }
+                    }
+                }
+            } finally {
+                if (cursor != null) {
+                    cursor.close();
+                }
+            }
+        } catch (Exception ex) {
+            loge("[getCarrierIdsFromApnQuery]- ex: " + ex);
+        }
+        logd(selection + " " + ids);
+        return ids;
+    }
+
+    // static helper function to get carrier id from mccmnc
+    public static int getCarrierIdFromMccMnc(@NonNull Context context, String mccmnc) {
+        try {
+            Cursor cursor = context.getContentResolver().query(
+                    CarrierId.All.CONTENT_URI,
+                    /* projection */ null,
+                    /* selection */ CarrierId.All.MCCMNC + "=? AND "
+                            + CarrierId.All.GID1 + " is NULL AND "
+                            + CarrierId.All.GID2 + " is NULL AND "
+                            + CarrierId.All.IMSI_PREFIX_XPATTERN + " is NULL AND "
+                            + CarrierId.All.SPN + " is NULL AND "
+                            + CarrierId.All.ICCID_PREFIX + " is NULL AND "
+                            + CarrierId.All.PLMN + " is NULL AND "
+                            + CarrierId.All.PRIVILEGE_ACCESS_RULE + " is NULL AND "
+                            + CarrierId.All.APN + " is NULL",
+                    /* selectionArgs */ new String[]{mccmnc},
+                    null);
+            try {
+                if (cursor != null) {
+                    if (VDBG) {
+                        logd("[getCarrierIdFromMccMnc]- " + cursor.getCount()
+                                + " Records(s) in DB" + " mccmnc: " + mccmnc);
+                    }
+                    while (cursor.moveToNext()) {
+                        return cursor.getInt(cursor.getColumnIndex(CarrierId.CARRIER_ID));
+                    }
+                }
+            } finally {
+                if (cursor != null) {
+                    cursor.close();
+                }
+            }
+        } catch (Exception ex) {
+            loge("[getCarrierIdFromMccMnc]- ex: " + ex);
+        }
+        return TelephonyManager.UNKNOWN_CARRIER_ID;
+    }
+
+    private static boolean equals(String a, String b, boolean ignoreCase) {
+        if (a == null && b == null) return true;
+        if (a != null && b != null) {
+            return (ignoreCase) ? a.equalsIgnoreCase(b) : a.equals(b);
+        }
+        return false;
+    }
+
+    private static void logd(String str) {
+        Rlog.d(LOG_TAG, str);
+    }
+    private static void loge(String str) {
+        Rlog.e(LOG_TAG, str);
+    }
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
+        ipw.println("mCarrierResolverLocalLogs:");
+        ipw.increaseIndent();
+        mCarrierIdLocalLog.dump(fd, pw, args);
+        ipw.decreaseIndent();
+
+        ipw.println("mCarrierId: " + mCarrierId);
+        ipw.println("mPreciseCarrierId: " + mPreciseCarrierId);
+        ipw.println("mMnoCarrierId: " + mMnoCarrierId);
+        ipw.println("mCarrierName: " + mCarrierName);
+        ipw.println("mPreciseCarrierName: " + mPreciseCarrierName);
+        ipw.println("version: " + getCarrierListVersion());
+
+        ipw.println("mCarrierMatchingRules on mccmnc: "
+                + mTelephonyMgr.getSimOperatorNumericForPhone(mPhone.getPhoneId()));
+        ipw.increaseIndent();
+        for (CarrierMatchingRule rule : mCarrierMatchingRulesOnMccMnc) {
+            ipw.println(rule.toString());
+        }
+        ipw.decreaseIndent();
+
+        ipw.println("mSpn: " + mSpn);
+        ipw.println("mPreferApn: " + mPreferApn);
+        ipw.flush();
+    }
+}
diff --git a/src/java/com/android/internal/telephony/CarrierServiceStateTracker.java b/src/java/com/android/internal/telephony/CarrierServiceStateTracker.java
index 7f49f28..c4c5a30 100644
--- a/src/java/com/android/internal/telephony/CarrierServiceStateTracker.java
+++ b/src/java/com/android/internal/telephony/CarrierServiceStateTracker.java
@@ -54,6 +54,8 @@
     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;
@@ -139,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);
@@ -219,6 +224,14 @@
         }
     }
 
+    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);
diff --git a/src/java/com/android/internal/telephony/CarrierServicesSmsFilter.java b/src/java/com/android/internal/telephony/CarrierServicesSmsFilter.java
index f3bc1fd..5a63613 100644
--- a/src/java/com/android/internal/telephony/CarrierServicesSmsFilter.java
+++ b/src/java/com/android/internal/telephony/CarrierServicesSmsFilter.java
@@ -21,6 +21,8 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.os.Binder;
+import android.os.Handler;
+import android.os.Message;
 import android.os.RemoteException;
 import android.service.carrier.CarrierMessagingService;
 import android.service.carrier.ICarrierMessagingCallback;
@@ -35,8 +37,10 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Optional;
+import java.util.Set;
 
 /**
  * Filters incoming SMS with carrier services.
@@ -44,6 +48,11 @@
  */
 public class CarrierServicesSmsFilter {
     protected static final boolean DBG = true;
+    /** onFilterComplete is not called. */
+    public static final int EVENT_ON_FILTER_COMPLETE_NOT_CALLED = 1;
+
+    /** onFilterComplete timeout. */
+    public static final int FILTER_COMPLETE_TIMEOUT_MS = 60000; //10 minutes
 
     private final Context mContext;
     private final Phone mPhone;
@@ -52,6 +61,9 @@
     private final String mPduFormat;
     private final CarrierServicesSmsFilterCallbackInterface mCarrierServicesSmsFilterCallback;
     private final String mLogTag;
+    private final CallbackTimeoutHandler mCallbackTimeoutHandler;
+    private FilterAggregator mFilterAggregator;
+
 
     @VisibleForTesting
     public CarrierServicesSmsFilter(
@@ -69,6 +81,7 @@
         mPduFormat = pduFormat;
         mCarrierServicesSmsFilterCallback = carrierServicesSmsFilterCallback;
         mLogTag = logTag;
+        mCallbackTimeoutHandler = new CallbackTimeoutHandler();
     }
 
     /**
@@ -86,9 +99,19 @@
         if (carrierImsPackage != null) {
             smsFilterPackages.add(carrierImsPackage);
         }
-        FilterAggregator filterAggregator = new FilterAggregator(smsFilterPackages.size());
+
+        if (mFilterAggregator != null) {
+            String errMsg = "Cannot reuse the same CarrierServiceSmsFilter object for filtering.";
+            loge(errMsg);
+            throw new RuntimeException(errMsg);
+        }
+
+        mFilterAggregator = new FilterAggregator(smsFilterPackages.size());
+        //start the timer
+        mCallbackTimeoutHandler.sendMessageDelayed(mCallbackTimeoutHandler
+                .obtainMessage(EVENT_ON_FILTER_COMPLETE_NOT_CALLED), FILTER_COMPLETE_TIMEOUT_MS);
         for (String smsFilterPackage : smsFilterPackages) {
-            filterWithPackage(smsFilterPackage, filterAggregator);
+            filterWithPackage(smsFilterPackage, mFilterAggregator);
         }
         boolean handled = smsFilterPackages.size() > 0;
         return handled;
@@ -127,6 +150,8 @@
         CarrierSmsFilter smsFilter = new CarrierSmsFilter(mPdus, mDestPort, mPduFormat);
         CarrierSmsFilterCallback smsFilterCallback =
                 new CarrierSmsFilterCallback(filterAggregator, smsFilter);
+        filterAggregator.addToCallbacks(smsFilterCallback);
+
         smsFilter.filterSms(packageName, smsFilterCallback);
     }
 
@@ -228,11 +253,13 @@
     private final class CarrierSmsFilterCallback extends ICarrierMessagingCallback.Stub {
         private final FilterAggregator mFilterAggregator;
         private final CarrierMessagingServiceManager mCarrierMessagingServiceManager;
+        private boolean mIsOnFilterCompleteCalled;
 
         CarrierSmsFilterCallback(FilterAggregator filterAggregator,
-                                 CarrierMessagingServiceManager carrierMessagingServiceManager) {
+                CarrierMessagingServiceManager carrierMessagingServiceManager) {
             mFilterAggregator = filterAggregator;
             mCarrierMessagingServiceManager = carrierMessagingServiceManager;
+            mIsOnFilterCompleteCalled = false;
         }
 
         /**
@@ -240,8 +267,13 @@
          */
         @Override
         public void onFilterComplete(int result) {
-            mCarrierMessagingServiceManager.disposeConnection(mContext);
-            mFilterAggregator.onFilterComplete(result);
+            // in the case that timeout has already passed and triggered, but the initial callback
+            // is run afterwards, we should not follow through
+            if (!mIsOnFilterCompleteCalled) {
+                mIsOnFilterCompleteCalled = true;
+                mCarrierMessagingServiceManager.disposeConnection(mContext);
+                mFilterAggregator.onFilterComplete(result);
+            }
         }
 
         @Override
@@ -268,10 +300,12 @@
     private final class FilterAggregator {
         private final Object mFilterLock = new Object();
         private int mNumPendingFilters;
+        private final Set<CarrierSmsFilterCallback> mCallbacks;
         private int mFilterResult;
 
         FilterAggregator(int numFilters) {
             mNumPendingFilters = numFilters;
+            mCallbacks = new HashSet<>();
             mFilterResult = CarrierMessagingService.RECEIVE_OPTIONS_DEFAULT;
         }
 
@@ -289,6 +323,9 @@
                         // return back to the CarrierMessagingService, restore the calling identity.
                         Binder.restoreCallingIdentity(token);
                     }
+                    //all onFilterCompletes called before timeout has triggered
+                    //remove the pending message
+                    mCallbackTimeoutHandler.removeMessages(EVENT_ON_FILTER_COMPLETE_NOT_CALLED);
                 }
             }
         }
@@ -296,5 +333,34 @@
         private void combine(int result) {
             mFilterResult = mFilterResult | result;
         }
+
+        private void addToCallbacks(CarrierSmsFilterCallback callback) {
+            mCallbacks.add(callback);
+        }
+
+    }
+
+    protected final class CallbackTimeoutHandler extends Handler {
+
+        private static final boolean DBG = true;
+
+        @Override
+        public void handleMessage(Message msg) {
+            if (DBG) {
+                log("CallbackTimeoutHandler handleMessage(" + msg.what + ")");
+            }
+
+            switch(msg.what) {
+                case EVENT_ON_FILTER_COMPLETE_NOT_CALLED:
+                    handleFilterCallbacksTimeout();
+                    break;
+            }
+        }
+
+        private void handleFilterCallbacksTimeout() {
+            for (CarrierSmsFilterCallback callback : mFilterAggregator.mCallbacks) {
+                callback.onFilterComplete(CarrierMessagingService.RECEIVE_OPTIONS_DEFAULT);
+            }
+        }
     }
 }
diff --git a/src/java/com/android/internal/telephony/CarrierSignalAgent.java b/src/java/com/android/internal/telephony/CarrierSignalAgent.java
index f2dd2aa..a7e0d8d 100644
--- a/src/java/com/android/internal/telephony/CarrierSignalAgent.java
+++ b/src/java/com/android/internal/telephony/CarrierSignalAgent.java
@@ -28,8 +28,10 @@
 import android.os.Handler;
 import android.os.Message;
 import android.os.PersistableBundle;
+import android.os.UserHandle;
 import android.telephony.CarrierConfigManager;
 import android.telephony.Rlog;
+import android.telephony.SubscriptionManager;
 import android.text.TextUtils;
 import android.util.LocalLog;
 import android.util.Log;
@@ -313,12 +315,13 @@
                 return;
             }
 
+            signal.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, mPhone.getSubId());
             signal.putExtra(PhoneConstants.SUBSCRIPTION_KEY, mPhone.getSubId());
             signal.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
             if (!wakeup) signal.setFlags(Intent.FLAG_EXCLUDE_STOPPED_PACKAGES);
 
             try {
-                mPhone.getContext().sendBroadcast(signal);
+                mPhone.getContext().sendBroadcastAsUser(signal, UserHandle.ALL);
                 if (DBG) {
                     log("Sending signal " + signal.getAction() + ((signal.getComponent() != null)
                             ? " to the carrier signal receiver: " + signal.getComponent() : ""));
diff --git a/src/java/com/android/internal/telephony/CellBroadcastHandler.java b/src/java/com/android/internal/telephony/CellBroadcastHandler.java
index 55e0643..4c4148d 100644
--- a/src/java/com/android/internal/telephony/CellBroadcastHandler.java
+++ b/src/java/com/android/internal/telephony/CellBroadcastHandler.java
@@ -30,15 +30,21 @@
 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
  * completes and our result receiver is called.
  */
 public class CellBroadcastHandler extends WakeLockStateMachine {
 
+    private final LocalLog mLocalLog = new LocalLog(100);
+
     private CellBroadcastHandler(Context context, Phone phone) {
         this("CellBroadcastHandler", context, phone);
     }
@@ -91,17 +97,24 @@
                 message.getServiceCategory(), message.getSerialNumber(),
                 System.currentTimeMillis());
 
+        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);
+            //Emergency alerts need to be delivered with high priority
+            intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
             // Explicitly send the intent to the default cell broadcast receiver.
             intent.setPackage(mContext.getResources().getString(
                     com.android.internal.R.string.config_defaultCellBroadcastReceiverPkg));
             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.
@@ -130,4 +143,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 e5b0ae0..edbbcda 100644
--- a/src/java/com/android/internal/telephony/CellularNetworkService.java
+++ b/src/java/com/android/internal/telephony/CellularNetworkService.java
@@ -228,7 +228,7 @@
                 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);
@@ -248,7 +248,7 @@
                 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);
@@ -258,8 +258,8 @@
         }
 
         private NetworkRegistrationState createRegistrationStateFromDataRegState(Object result) {
-            int transportType = TransportType.WWAN;
             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 =
@@ -273,9 +273,11 @@
                 CellIdentity cellIdentity =
                         convertHalCellIdentityToCellIdentity(dataRegState.cellIdentity);
 
-                return new NetworkRegistrationState(transportType, domain, regState,
+                return new NetworkRegistrationState(domain, transportType, regState,
                         accessNetworkTechnology, reasonForDenial, emergencyOnly, availableServices,
-                        cellIdentity, maxDataCalls);
+                        cellIdentity, maxDataCalls, false /* isDcNrRestricted */,
+                        false /* isNrAvailable */, false /* isEnDcAvailable */);
+
             } else if (result instanceof android.hardware.radio.V1_2.DataRegStateResult) {
                 android.hardware.radio.V1_2.DataRegStateResult dataRegState =
                         (android.hardware.radio.V1_2.DataRegStateResult) result;
@@ -288,11 +290,29 @@
                 CellIdentity cellIdentity =
                         convertHalCellIdentityToCellIdentity(dataRegState.cellIdentity);
 
-                return new NetworkRegistrationState(transportType, domain, regState,
+                return new NetworkRegistrationState(domain, transportType, regState,
                         accessNetworkTechnology, reasonForDenial, emergencyOnly, availableServices,
-                        cellIdentity, maxDataCalls);
-            }
+                        cellIdentity, maxDataCalls, false /* isDcNrRestricted */,
+                        false /* isNrAvailable */, false /* isEnDcAvailable */);
+            } else if (result instanceof android.hardware.radio.V1_4.DataRegStateResult) {
+                android.hardware.radio.V1_4.DataRegStateResult dataRegState =
+                        (android.hardware.radio.V1_4.DataRegStateResult) result;
+                int regState = getRegStateFromHalRegState(dataRegState.base.regState);
+                int accessNetworkTechnology =
+                        getAccessNetworkTechnologyFromRat(dataRegState.base.rat);
+                int reasonForDenial = dataRegState.base.reasonDataDenied;
+                boolean emergencyOnly = isEmergencyOnly(dataRegState.base.regState);
+                int maxDataCalls = dataRegState.base.maxDataCalls;
+                int[] availableServices = getAvailableServices(regState, domain, emergencyOnly);
+                CellIdentity cellIdentity =
+                        convertHalCellIdentityToCellIdentity(dataRegState.base.cellIdentity);
+                android.hardware.radio.V1_4.NrIndicators nrIndicators = dataRegState.nrIndicators;
 
+                return new NetworkRegistrationState(domain, transportType, regState,
+                        accessNetworkTechnology, reasonForDenial, emergencyOnly, availableServices,
+                        cellIdentity, maxDataCalls, nrIndicators.isDcNrRestricted,
+                        nrIndicators.isNrAvailable, nrIndicators.isEndcAvailable);
+            }
             return null;
         }
 
@@ -330,7 +350,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;
                 }
@@ -417,6 +438,7 @@
                                 cellIdentityTdscdma.base.lac,
                                 cellIdentityTdscdma.base.cid,
                                 cellIdentityTdscdma.base.cpid,
+                                cellIdentityTdscdma.uarfcn,
                                 cellIdentityTdscdma.operatorNames.alphaLong,
                                 cellIdentityTdscdma.operatorNames.alphaShort);
                     }
diff --git a/src/java/com/android/internal/telephony/CommandsInterface.java b/src/java/com/android/internal/telephony/CommandsInterface.java
index 9f6ce5e..cda5cd7 100644
--- a/src/java/com/android/internal/telephony/CommandsInterface.java
+++ b/src/java/com/android/internal/telephony/CommandsInterface.java
@@ -25,9 +25,11 @@
 import android.telephony.ClientRequestStats;
 import android.telephony.ImsiEncryptionInfo;
 import android.telephony.NetworkScanRequest;
+import android.telephony.TelephonyManager;
 import android.telephony.data.DataProfile;
 
 import com.android.internal.telephony.cdma.CdmaSmsBroadcastConfigInfo;
+import com.android.internal.telephony.dataconnection.TransportManager;
 import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
 import com.android.internal.telephony.uicc.IccCardStatus;
 
@@ -37,19 +39,6 @@
  * {@hide}
  */
 public interface CommandsInterface {
-    enum RadioState {
-        RADIO_OFF,         /* Radio explicitly powered off (eg CFUN=0) */
-        RADIO_UNAVAILABLE, /* Radio unavailable (eg, resetting or not booted) */
-        RADIO_ON;          /* Radio is on */
-
-        public boolean isOn() /* and available...*/ {
-            return this == RADIO_ON;
-        }
-
-        public boolean isAvailable() {
-            return this != RADIO_UNAVAILABLE;
-        }
-    }
 
     //***** Constants
 
@@ -121,7 +110,12 @@
     static final int CDMA_SMS_FAIL_CAUSE_ENCODING_PROBLEM           = 96;
 
     //***** Methods
-    RadioState getRadioState();
+
+    /**
+     * get latest radio power state from modem
+     * @return
+     */
+    @TelephonyManager.RadioPowerState int getRadioState();
 
     /**
      * response.obj.result is an int[2]
@@ -1410,14 +1404,6 @@
     void getPreferredNetworkType(Message response);
 
     /**
-     * Query neighboring cell ids
-     *
-     * @param response s callback message to cell ids
-     * @param workSource calling WorkSource
-     */
-    default void getNeighboringCids(Message response, WorkSource workSource){}
-
-    /**
      * Request to enable/disable network state change notifications when
      * location information (lac and/or cid) has changed.
      *
@@ -1903,8 +1889,9 @@
      *
      * @param itemID the ID of the item to read
      * @param response callback message with the String response in the obj field
+     * @param workSource calling WorkSource
      */
-    void nvReadItem(int itemID, Message response);
+    default void nvReadItem(int itemID, Message response, WorkSource workSource) {}
 
     /**
      * Write one of the NV items defined in {@link RadioNVItems} / {@code ril_nv_items.h}.
@@ -1913,8 +1900,10 @@
      * @param itemID the ID of the item to read
      * @param itemValue the value to write, as a String
      * @param response Callback message.
+     * @param workSource calling WorkSource
      */
-    void nvWriteItem(int itemID, String itemValue, Message response);
+    default void nvWriteItem(int itemID, String itemValue, Message response,
+            WorkSource workSource) {}
 
     /**
      * Update the CDMA Preferred Roaming List (PRL) in the radio NV storage.
@@ -2067,23 +2056,27 @@
      * Get modem activity info and stats
      *
      * @param result Callback message contains the modem activity information
+     * @param workSource calling WorkSource
      */
-    public void getModemActivityInfo(Message result);
+    default void getModemActivityInfo(Message result, WorkSource workSource) {}
 
     /**
      * Set allowed carriers
      *
      * @param carriers Allowed carriers
      * @param result Callback message contains the number of carriers set successfully
+     * @param workSource calling WorkSource
      */
-    public void setAllowedCarriers(List<CarrierIdentifier> carriers, Message result);
+    default void setAllowedCarriers(List<CarrierIdentifier> carriers, Message result,
+            WorkSource workSource) {}
 
     /**
      * Get allowed carriers
      *
      * @param result Callback message contains the allowed carriers
+     * @param workSource calling WorkSource
      */
-    public void getAllowedCarriers(Message result);
+    default void getAllowedCarriers(Message result, WorkSource workSource) {}
 
     /**
      * Register for unsolicited PCO data.  This information is carrier-specific,
@@ -2175,8 +2168,9 @@
      * - {@link android.telephony.TelephonyManager#CARD_POWER_UP}
      * - {@link android.telephony.TelephonyManager#CARD_POWER_UP_PASS_THROUGH}
      * @param result callback message contains the information of SUCCESS/FAILURE
+     * @param workSource calling WorkSource
      */
-    void setSimCardPower(int state, Message result);
+    default void setSimCardPower(int state, Message result, WorkSource workSource) {}
 
     /**
      * Register for unsolicited Carrier Public Key.
@@ -2227,6 +2221,22 @@
     void unregisterForNattKeepaliveStatus(Handler h);
 
     /**
+     * Register for unsolicited Emergency Number List Indications
+     *
+     * @param h Handler for notification message.
+     * @param what User-defined message code.
+     * @param obj User object.
+     */
+    void registerForEmergencyNumberList(Handler h, int what, Object obj);
+
+    /**
+     * Deregister for unsolicited Emergency Number List Indications
+     *
+     * @param h Handler for notification message.
+     */
+    void unregisterForEmergencyNumberList(Handler h);
+
+    /**
      * Start sending NATT Keepalive packets on a specified data connection
      *
      * @param contextId cid that identifies the data connection for this keepalive
@@ -2245,7 +2255,11 @@
      */
     void stopNattKeepalive(int sessionHandle, Message result);
 
-    default public List<ClientRequestStats> getClientRequestStats() {
+    default List<ClientRequestStats> getClientRequestStats() {
         return null;
     }
+
+    default int getIwlanOperationMode() {
+        return TransportManager.IWLAN_OPERATION_MODE_DEFAULT;
+    }
 }
diff --git a/src/java/com/android/internal/telephony/Connection.java b/src/java/com/android/internal/telephony/Connection.java
old mode 100644
new mode 100755
index 62fc5b0..05a0081
--- a/src/java/com/android/internal/telephony/Connection.java
+++ b/src/java/com/android/internal/telephony/Connection.java
@@ -22,9 +22,9 @@
 import android.telecom.ConferenceParticipant;
 import android.telephony.DisconnectCause;
 import android.telephony.Rlog;
+import android.telephony.ServiceState;
 import android.util.Log;
 
-import java.lang.Override;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
@@ -89,7 +89,7 @@
     public interface Listener {
         public void onVideoStateChanged(int videoState);
         public void onConnectionCapabilitiesChanged(int capability);
-        public void onWifiChanged(boolean isWifi);
+        public void onCallRadioTechChanged(@ServiceState.RilRadioTechnology int vrat);
         public void onVideoProviderChanged(
                 android.telecom.Connection.VideoProvider videoProvider);
         public void onAudioQualityChanged(int audioQuality);
@@ -108,6 +108,7 @@
         public void onRttInitiated();
         public void onRttTerminated();
         public void onOriginalConnectionReplaced(Connection newConnection);
+        public void onIsNetworkEmergencyCallChanged(boolean isEmergencyCall);
     }
 
     /**
@@ -119,7 +120,7 @@
         @Override
         public void onConnectionCapabilitiesChanged(int capability) {}
         @Override
-        public void onWifiChanged(boolean isWifi) {}
+        public void onCallRadioTechChanged(@ServiceState.RilRadioTechnology int vrat) {}
         @Override
         public void onVideoProviderChanged(
                 android.telecom.Connection.VideoProvider videoProvider) {}
@@ -155,6 +156,8 @@
         public void onRttTerminated() {}
         @Override
         public void onOriginalConnectionReplaced(Connection newConnection) {}
+        @Override
+        public void onIsNetworkEmergencyCallChanged(boolean isEmergencyCall) {}
     }
 
     public static final int AUDIO_QUALITY_STANDARD = 1;
@@ -206,7 +209,13 @@
     Object mUserData;
     private int mVideoState;
     private int mConnectionCapabilities;
-    private boolean mIsWifi;
+    /**
+     * Determines the call radio technology for current connection.
+     *
+     * This is used to propagate the call radio technology to upper layer.
+     */
+    private @ServiceState.RilRadioTechnology int mCallRadioTech =
+            ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN;
     private boolean mAudioModeIsVoip;
     private int mAudioQuality;
     private int mCallSubstate;
@@ -218,6 +227,11 @@
     private boolean mAllowAddCallDuringVideoCall;
 
     /**
+     * When {@code true}, the network has indicated that this is an emergency call.
+     */
+    private boolean mIsNetworkIdentifiedEmergencyCall;
+
+    /**
      * Used to indicate that this originated from pulling a {@link android.telecom.Connection} with
      * {@link android.telecom.Connection#PROPERTY_IS_EXTERNAL_CALL}.
      */
@@ -742,7 +756,17 @@
      * @return {@code True} if the connection is using a wifi network.
      */
     public boolean isWifi() {
-        return mIsWifi;
+        return getCallRadioTech() == ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN;
+    }
+
+    /**
+     * Returns radio technology is used for the connection.
+     *
+     * @return the RIL Voice Radio Technology used for current connection,
+     *         see {@code RIL_RADIO_TECHNOLOGY_*} in {@link android.telephony.ServiceState}.
+     */
+    public @ServiceState.RilRadioTechnology int getCallRadioTech() {
+        return mCallRadioTech;
     }
 
     /**
@@ -813,14 +837,18 @@
     }
 
     /**
-     * Sets whether a wifi network is used for the connection.
+     * Sets RIL voice radio technology used for current connection.
      *
-     * @param isWifi {@code True} if wifi is being used.
+     * @param vrat the RIL voice radio technology for current connection,
+     *             see {@code RIL_RADIO_TECHNOLOGY_*} in {@link android.telephony.ServiceState}.
      */
-    public void setWifi(boolean isWifi) {
-        mIsWifi = isWifi;
+    public void setCallRadioTech(@ServiceState.RilRadioTechnology int vrat) {
+        if (mCallRadioTech == vrat) {
+            return;
+        }
+        mCallRadioTech = vrat;
         for (Listener l : mListeners) {
-            l.onWifiChanged(mIsWifi);
+            l.onCallRadioTechChanged(vrat);
         }
     }
 
@@ -972,6 +1000,21 @@
     }
 
     /**
+     * Changes the address and presentation for this call.
+     * @param newAddress The new address.
+     * @param numberPresentation The number presentation for the address.
+     */
+    public void setAddress(String newAddress, int numberPresentation) {
+        Rlog.i(TAG, "setAddress = " + newAddress);
+        mAddress = newAddress;
+        mNumberPresentation = numberPresentation;
+    }
+
+    public void setDialString(String newDialString) {
+        mDialString = newDialString;
+    }
+
+    /**
      * Notifies listeners of a change to conference participant(s).
      *
      * @param conferenceParticipants The participant(s).
@@ -1107,6 +1150,37 @@
     }
 
     /**
+     * Reset the Connection time and Duration
+     */
+    public void resetConnectionTime() {
+        if (mPhoneType == PhoneConstants.PHONE_TYPE_CDMA_LTE ||
+                mPhoneType == PhoneConstants.PHONE_TYPE_CDMA) {
+            mConnectTime = System.currentTimeMillis();
+            mConnectTimeReal = SystemClock.elapsedRealtime();
+            mDuration = 0;
+        }
+    }
+
+    /**
+     * Sets whether this {@link Connection} has been identified by the network as an emergency call.
+     * @param isNetworkIdentifiedEmergencyCall {@code true} if ecall, {@code false} otherwise.
+     */
+    public void setIsNetworkIdentifiedEmergencyCall(boolean isNetworkIdentifiedEmergencyCall) {
+        mIsNetworkIdentifiedEmergencyCall = isNetworkIdentifiedEmergencyCall;
+        for (Listener l : mListeners) {
+            l.onIsNetworkEmergencyCallChanged(isNetworkIdentifiedEmergencyCall);
+        }
+    }
+
+    /**
+     * @return Whether this {@link Connection} has been identified by the network as an emergency
+     * call.
+     */
+    public boolean isNetworkIdentifiedEmergencyCall() {
+        return mIsNetworkIdentifiedEmergencyCall;
+    }
+
+    /**
      * Build a human representation of a connection instance, suitable for debugging.
      * Don't log personal stuff unless in debug mode.
      * @return a string representing the internal state of this connection.
diff --git a/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java b/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java
index 8f1f078..ee2a9bb 100644
--- a/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java
+++ b/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java
@@ -22,13 +22,16 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.telephony.CellInfo;
+import android.telephony.CellLocation;
+import android.telephony.PhoneCapability;
 import android.telephony.PhysicalChannelConfig;
 import android.telephony.PreciseCallState;
 import android.telephony.Rlog;
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
-import android.telephony.VoLteServiceState;
+import android.telephony.data.ApnSetting;
+import android.telephony.emergency.EmergencyNumber;
 
 import java.util.List;
 
@@ -181,7 +184,8 @@
             if (mRegistry != null) {
                 mRegistry.notifyDataConnectionForSubscriber(subId,
                     PhoneConstantConversions.convertDataState(state),
-                        sender.isDataAllowed(), reason,
+                        sender.isDataAllowed(ApnSetting.getApnTypesBitmaskFromString(apnType)),
+                        reason,
                         sender.getActiveApnHost(apnType),
                         apnType,
                         linkProperties,
@@ -207,10 +211,10 @@
     }
 
     @Override
-    public void notifyCellLocation(Phone sender) {
+    public void notifyCellLocation(Phone sender, CellLocation cl) {
         int subId = sender.getSubId();
         Bundle data = new Bundle();
-        sender.getCellLocation().fillInNotifierBundle(data);
+        cl.fillInNotifierBundle(data);
         try {
             if (mRegistry != null) {
                 mRegistry.notifyCellLocationForSubscriber(subId, data);
@@ -294,10 +298,9 @@
     }
 
     @Override
-    public void notifyVoLteServiceStateChanged(Phone sender, VoLteServiceState lteState) {
-        // FIXME: subID
+    public void notifySrvccStateChanged(Phone sender, @TelephonyManager.SrvccState int state) {
         try {
-            mRegistry.notifyVoLteServiceStateChanged(lteState);
+            mRegistry.notifySrvccStateChanged(sender.getSubId(), state);
         } catch (RemoteException ex) {
             // system process is dead
         }
@@ -342,6 +345,37 @@
         }
     }
 
+    @Override
+    public void notifyPhoneCapabilityChanged(PhoneCapability capability) {
+        try {
+            mRegistry.notifyPhoneCapabilityChanged(capability);
+        } catch (RemoteException ex) {
+            // system process is dead
+        }
+    }
+
+    @Override
+    public void notifyRadioPowerStateChanged(@TelephonyManager.RadioPowerState int state) {
+        try {
+            mRegistry.notifyRadioPowerStateChanged(state);
+        } catch (RemoteException ex) {
+            // system process is dead
+        }
+    }
+
+    @Override
+    public void notifyEmergencyNumberList(Phone sender,
+                                          List<EmergencyNumber> emergencyNumberList) {
+        int subId = sender.getSubId();
+        try {
+            if (mRegistry != null) {
+                mRegistry.notifyEmergencyNumberList(emergencyNumberList);
+            }
+        } 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 92a78f7..358f15f 100644
--- a/src/java/com/android/internal/telephony/DeviceStateMonitor.java
+++ b/src/java/com/android/internal/telephony/DeviceStateMonitor.java
@@ -27,6 +27,9 @@
 import android.hardware.display.DisplayManager;
 import android.hardware.radio.V1_2.IndicationFilter;
 import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
 import android.os.BatteryManager;
 import android.os.Handler;
 import android.os.Message;
@@ -39,11 +42,14 @@
 import android.util.SparseIntArray;
 import android.view.Display;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.IndentingPrintWriter;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
 
 /**
  * The device state monitor monitors the device state such as charging state, power saving sate,
@@ -58,21 +64,65 @@
     protected static final boolean DBG = false;      /* STOPSHIP if true */
     protected static final String TAG = DeviceStateMonitor.class.getSimpleName();
 
-    private static final int EVENT_RIL_CONNECTED                = 0;
-    private static final int EVENT_UPDATE_MODE_CHANGED          = 1;
-    private static final int EVENT_SCREEN_STATE_CHANGED         = 2;
-    private static final int EVENT_POWER_SAVE_MODE_CHANGED      = 3;
-    private static final int EVENT_CHARGING_STATE_CHANGED       = 4;
-    private static final int EVENT_TETHERING_STATE_CHANGED      = 5;
-    private static final int EVENT_RADIO_AVAILABLE              = 6;
+    static final int EVENT_RIL_CONNECTED                = 0;
+    static final int EVENT_UPDATE_MODE_CHANGED          = 1;
+    @VisibleForTesting
+    static final int EVENT_SCREEN_STATE_CHANGED         = 2;
+    static final int EVENT_POWER_SAVE_MODE_CHANGED      = 3;
+    @VisibleForTesting
+    static final int EVENT_CHARGING_STATE_CHANGED       = 4;
+    static final int EVENT_TETHERING_STATE_CHANGED      = 5;
+    static final int EVENT_RADIO_AVAILABLE              = 6;
+    @VisibleForTesting
+    static final int EVENT_WIFI_CONNECTION_CHANGED      = 7;
 
     // TODO(b/74006656) load hysteresis values from a property when DeviceStateMonitor starts
     private static final int HYSTERESIS_KBPS = 50;
 
+    private static final int WIFI_UNAVAILABLE = 0;
+    private static final int WIFI_AVAILABLE = 1;
+
     private final Phone mPhone;
 
     private final LocalLog mLocalLog = new LocalLog(100);
 
+    private final NetworkRequest mWifiNetworkRequest =
+            new NetworkRequest.Builder()
+            .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+            .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+            .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+            .build();
+
+    private final ConnectivityManager.NetworkCallback mNetworkCallback =
+            new ConnectivityManager.NetworkCallback() {
+        Set<Network> mWifiNetworks = new HashSet<>();
+
+        @Override
+        public void onAvailable(Network network) {
+            synchronized (mWifiNetworks) {
+                if (mWifiNetworks.size() == 0) {
+                    // We just connected to Wifi, so send an update.
+                    obtainMessage(EVENT_WIFI_CONNECTION_CHANGED, WIFI_AVAILABLE, 0).sendToTarget();
+                    log("Wifi (default) connected", true);
+                }
+                mWifiNetworks.add(network);
+            }
+        }
+
+        @Override
+        public void onLost(Network network) {
+            synchronized (mWifiNetworks) {
+                mWifiNetworks.remove(network);
+                if (mWifiNetworks.size() == 0) {
+                    // We just disconnected from the last connected wifi, so send an update.
+                    obtainMessage(
+                            EVENT_WIFI_CONNECTION_CHANGED, WIFI_UNAVAILABLE, 0).sendToTarget();
+                    log("Wifi (default) disconnected", true);
+                }
+            }
+        }
+    };
+
     /**
      * Flag for wifi/usb/bluetooth tethering turned on or not
      */
@@ -105,6 +155,22 @@
      */
     private boolean mIsLowDataExpected;
 
+    /**
+     * Wifi is connected. True means both that cellular is likely to be asleep when the screen is
+     * on and that in most cases the device location is relatively close to the WiFi AP. This means
+     * that fewer location updates should be provided by cellular.
+     */
+    private boolean mIsWifiConnected;
+
+    @VisibleForTesting
+    static final int CELL_INFO_INTERVAL_SHORT_MS = 2000;
+    @VisibleForTesting
+    static final int CELL_INFO_INTERVAL_LONG_MS = 10000;
+
+    /** The minimum required wait time between cell info requests to the modem */
+    private int mCellInfoMinInterval = CELL_INFO_INTERVAL_SHORT_MS;
+
+
     private SparseIntArray mUpdateModes = new SparseIntArray();
 
     /**
@@ -202,6 +268,10 @@
 
         mPhone.mCi.registerForRilConnected(this, EVENT_RIL_CONNECTED, null);
         mPhone.mCi.registerForAvailable(this, EVENT_RADIO_AVAILABLE, null);
+
+        ConnectivityManager cm = (ConnectivityManager) phone.getContext().getSystemService(
+                Context.CONNECTIVITY_SERVICE);
+        cm.registerNetworkCallback(mWifiNetworkRequest, mNetworkCallback);
     }
 
     /**
@@ -212,6 +282,27 @@
     }
 
     /**
+     * @return The minimum period between CellInfo requests to the modem
+     */
+    @VisibleForTesting
+    public int computeCellInfoMinInterval() {
+        // The screen is on and we're either on cellular or charging. Screen on + Charging is
+        // a likely vehicular scenario, even if there is a nomadic AP.
+        if (mIsScreenOn && !mIsWifiConnected) {
+            // Screen on without WiFi - We are in a high power likely mobile situation.
+            return CELL_INFO_INTERVAL_SHORT_MS;
+        } else if (mIsScreenOn && mIsCharging) {
+            // Screen is on and we're charging, so we favor accuracy over power.
+            return CELL_INFO_INTERVAL_SHORT_MS;
+        } else {
+            // If the screen is off, apps should not need cellular location at rapid intervals.
+            // If the screen is on but we are on wifi and not charging then cellular location
+            // accuracy is not crucial, so favor modem power saving over high accuracy.
+            return CELL_INFO_INTERVAL_LONG_MS;
+        }
+    }
+
+    /**
      * @return True if signal strength update should be turned off.
      */
     private boolean shouldTurnOffSignalStrength() {
@@ -358,6 +449,9 @@
             case EVENT_TETHERING_STATE_CHANGED:
                 onUpdateDeviceState(msg.what, msg.arg1 != 0);
                 break;
+            case EVENT_WIFI_CONNECTION_CHANGED:
+                onUpdateDeviceState(msg.what, msg.arg1 != WIFI_UNAVAILABLE);
+                break;
             default:
                 throw new IllegalStateException("Unexpected message arrives. msg = " + msg.what);
         }
@@ -389,10 +483,22 @@
                 mIsPowerSaveOn = state;
                 sendDeviceState(POWER_SAVE_MODE, mIsPowerSaveOn);
                 break;
+            case EVENT_WIFI_CONNECTION_CHANGED:
+                if (mIsWifiConnected == state) return;
+                mIsWifiConnected = state;
+
+                break;
             default:
                 return;
         }
 
+        final int newCellInfoMinInterval = computeCellInfoMinInterval();
+        if (mCellInfoMinInterval != newCellInfoMinInterval) {
+            mCellInfoMinInterval = newCellInfoMinInterval;
+            setCellInfoMinInterval(mCellInfoMinInterval);
+            log("CellInfo Min Interval Updated to " + newCellInfoMinInterval, true);
+        }
+
         if (mIsLowDataExpected != isLowDataExpected()) {
             mIsLowDataExpected = !mIsLowDataExpected;
             sendDeviceState(LOW_DATA_EXPECTED, mIsLowDataExpected);
@@ -437,6 +543,7 @@
         setUnsolResponseFilter(mUnsolicitedResponseFilter, true);
         setSignalStrengthReportingCriteria();
         setLinkCapacityReportingCriteria();
+        setCellInfoMinInterval(mCellInfoMinInterval);
     }
 
     /**
@@ -501,6 +608,10 @@
                 LINK_CAPACITY_UPLINK_THRESHOLDS, AccessNetworkType.CDMA2000);
     }
 
+    private void setCellInfoMinInterval(int rate) {
+        mPhone.setCellInfoMinInterval(rate);
+    }
+
     /**
      * @return True if the device is currently in power save mode.
      * See {@link android.os.BatteryManager#isPowerSaveMode BatteryManager.isPowerSaveMode()}.
@@ -579,6 +690,7 @@
         ipw.println("mIsPowerSaveOn=" + mIsPowerSaveOn);
         ipw.println("mIsLowDataExpected=" + mIsLowDataExpected);
         ipw.println("mUnsolicitedResponseFilter=" + mUnsolicitedResponseFilter);
+        ipw.println("mIsWifiConnected=" + mIsWifiConnected);
         ipw.println("Local logs:");
         ipw.increaseIndent();
         mLocalLog.dump(fd, ipw, args);
@@ -623,12 +735,10 @@
          * 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 */
         };
 
         /**
diff --git a/src/java/com/android/internal/telephony/GsmCdmaCallTracker.java b/src/java/com/android/internal/telephony/GsmCdmaCallTracker.java
index f4c561c..e282392 100755
--- a/src/java/com/android/internal/telephony/GsmCdmaCallTracker.java
+++ b/src/java/com/android/internal/telephony/GsmCdmaCallTracker.java
@@ -28,6 +28,7 @@
 import android.os.Registrant;
 import android.os.RegistrantList;
 import android.os.SystemProperties;
+import android.telephony.AccessNetworkConstants.TransportType;
 import android.telephony.CarrierConfigManager;
 import android.telephony.CellLocation;
 import android.telephony.DisconnectCause;
@@ -172,8 +173,9 @@
             mCi.unregisterForCallWaitingInfo(this);
             // Prior to phone switch to GSM, if CDMA has any emergency call
             // data will be in disabled state, after switching to GSM enable data.
-            if (mIsInEmergencyCall) {
-                mPhone.mDcTracker.setInternalDataEnabled(true);
+            if (mIsInEmergencyCall && mPhone.getDcTracker(TransportType.WWAN) != null) {
+                mPhone.getDcTracker(TransportType.WWAN).setInternalDataEnabled(true);
+
             }
         } else {
             mConnections = new GsmCdmaConnection[MAX_CONNECTIONS_CDMA];
@@ -197,6 +199,8 @@
         }
 
         if (mPendingMO != null) {
+            // Send the notification that the pending call was disconnected to the higher layers.
+            mPendingMO.onDisconnect(DisconnectCause.ERROR_UNSPECIFIED);
             mPendingMO.dispose();
         }
 
@@ -272,9 +276,10 @@
         // note that this triggers call state changed notif
         clearDisconnected();
 
-        if (!canDial()) {
-            throw new CallStateException("cannot dial in current state");
-        }
+        // Check for issues which would preclude dialing and throw a CallStateException.
+        boolean isEmergencyCall = PhoneNumberUtils.isLocalEmergencyNumber(mPhone.getContext(),
+                dialString);
+        checkForDialIssues(isEmergencyCall);
 
         String origNumber = dialString;
         dialString = convertNumberIfNecessary(mPhone, dialString);
@@ -308,8 +313,7 @@
             //we should have failed in !canDial() above before we get here
             throw new CallStateException("cannot dial in current state");
         }
-        boolean isEmergencyCall = PhoneNumberUtils.isLocalEmergencyNumber(mPhone.getContext(),
-                dialString);
+
         mPendingMO = new GsmCdmaConnection(mPhone, checkForTestEmergencyNumber(dialString),
                 this, mForegroundCall, isEmergencyCall);
         mHangupPendingMO = false;
@@ -370,7 +374,9 @@
     //CDMA
     public void setIsInEmergencyCall() {
         mIsInEmergencyCall = true;
-        mPhone.mDcTracker.setInternalDataEnabled(false);
+        if (mPhone.getDcTracker(TransportType.WWAN) != null) {
+            mPhone.getDcTracker(TransportType.WWAN).setInternalDataEnabled(false);
+        }
         mPhone.notifyEmergencyCallRegistrants(true);
         mPhone.sendEmergencyCallStateChange(true);
     }
@@ -383,9 +389,11 @@
         // note that this triggers call state changed notif
         clearDisconnected();
 
-        if (!canDial()) {
-            throw new CallStateException("cannot dial in current state");
-        }
+        boolean isEmergencyCall =
+                PhoneNumberUtils.isLocalEmergencyNumber(mPhone.getContext(), dialString);
+
+        // Check for issues which would preclude dialing and throw a CallStateException.
+        checkForDialIssues(isEmergencyCall);
 
         TelephonyManager tm =
                 (TelephonyManager) mPhone.getContext().getSystemService(Context.TELEPHONY_SERVICE);
@@ -407,8 +415,6 @@
         }
 
         boolean isPhoneInEcmMode = mPhone.isInEcm();
-        boolean isEmergencyCall =
-                PhoneNumberUtils.isLocalEmergencyNumber(mPhone.getContext(), dialString);
 
         // Cancel Ecm timer if a second emergency call is originating in Ecm mode
         if (isPhoneInEcmMode && isEmergencyCall) {
@@ -476,7 +482,7 @@
             // Some networks need an empty flash before sending the normal one
             CarrierConfigManager configManager = (CarrierConfigManager)
                     mPhone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
-            PersistableBundle bundle = configManager.getConfig();
+            PersistableBundle bundle = configManager.getConfigForSubId(mPhone.getSubId());
             if (bundle != null) {
                 m3WayCallFlashDelay =
                         bundle.getInt(CarrierConfigManager.KEY_CDMA_3WAYCALL_FLASH_DELAY_INT);
@@ -611,41 +617,50 @@
                 && !mForegroundCall.isFull();
     }
 
-    private boolean canDial() {
-        boolean ret;
-        int serviceState = mPhone.getServiceState().getState();
+    /**
+     * Determines if there are issues which would preclude dialing an outgoing call.  Throws a
+     * {@link CallStateException} if there is an issue.
+     * @throws CallStateException
+     */
+    public void checkForDialIssues(boolean isEmergencyCall) throws CallStateException {
         String disableCall = SystemProperties.get(
                 TelephonyProperties.PROPERTY_DISABLE_CALL, "false");
 
-        ret = (serviceState != ServiceState.STATE_POWER_OFF)
-                && mPendingMO == null
-                && !mRingingCall.isRinging()
-                && !disableCall.equals("true")
-                && (!mForegroundCall.getState().isAlive()
-                    || !mBackgroundCall.getState().isAlive()
-                    || (!isPhoneTypeGsm()
-                        && mForegroundCall.getState() == GsmCdmaCall.State.ACTIVE));
-
-        if (!ret) {
-            log(String.format("canDial is false\n" +
-                            "((serviceState=%d) != ServiceState.STATE_POWER_OFF)::=%s\n" +
-                            "&& pendingMO == null::=%s\n" +
-                            "&& !ringingCall.isRinging()::=%s\n" +
-                            "&& !disableCall.equals(\"true\")::=%s\n" +
-                            "&& (!foregroundCall.getState().isAlive()::=%s\n" +
-                            "   || foregroundCall.getState() == GsmCdmaCall.State.ACTIVE::=%s\n" +
-                            "   ||!backgroundCall.getState().isAlive())::=%s)",
-                    serviceState,
-                    serviceState != ServiceState.STATE_POWER_OFF,
-                    mPendingMO == null,
-                    !mRingingCall.isRinging(),
-                    !disableCall.equals("true"),
-                    !mForegroundCall.getState().isAlive(),
-                    mForegroundCall.getState() == GsmCdmaCall.State.ACTIVE,
-                    !mBackgroundCall.getState().isAlive()));
+        if (mCi.getRadioState() != TelephonyManager.RADIO_POWER_ON) {
+            throw new CallStateException(CallStateException.ERROR_POWER_OFF,
+                    "Modem not powered");
         }
+        if (disableCall.equals("true")) {
+            throw new CallStateException(CallStateException.ERROR_CALLING_DISABLED,
+                    "Calling disabled via ro.telephony.disable-call property");
+        }
+        if (mPendingMO != null) {
+            throw new CallStateException(CallStateException.ERROR_ALREADY_DIALING,
+                    "A call is already dialing.");
+        }
+        if (mRingingCall.isRinging()) {
+            throw new CallStateException(CallStateException.ERROR_CALL_RINGING,
+                    "Can't call while a call is ringing.");
+        }
+        if (isPhoneTypeGsm()
+                && mForegroundCall.getState().isAlive() && mBackgroundCall.getState().isAlive()) {
+            throw new CallStateException(CallStateException.ERROR_TOO_MANY_CALLS,
+                    "There is already a foreground and background call.");
+        }
+        if (!isPhoneTypeGsm()
+                // Essentially foreground call state is one of:
+                // HOLDING, DIALING, ALERTING, INCOMING, WAITING
+                && mForegroundCall.getState().isAlive()
+                && mForegroundCall.getState() != GsmCdmaCall.State.ACTIVE
 
-        return ret;
+                && mBackgroundCall.getState().isAlive()) {
+            throw new CallStateException(CallStateException.ERROR_TOO_MANY_CALLS,
+                    "There is already a foreground and background call.");
+        }
+        if (!isEmergencyCall && isInOtaspCall()) {
+            throw new CallStateException(CallStateException.ERROR_OTASP_PROVISIONING_IN_PROCESS,
+                    "OTASP provisioning is in process.");
+        }
     }
 
     public boolean canTransfer() {
@@ -1431,11 +1446,36 @@
                 operationComplete();
 
                 if (ar.exception != null) {
-                    // An exception occurred...just treat the disconnect
-                    // cause as "normal"
-                    causeCode = CallFailCause.NORMAL_CLEARING;
-                    Rlog.i(LOG_TAG,
-                            "Exception during getLastCallFailCause, assuming normal disconnect");
+                    if (ar.exception instanceof CommandException) {
+                        // If we get a CommandException, there are some modem-reported command
+                        // errors which are truly exceptional.  We shouldn't treat these as
+                        // NORMAL_CLEARING, so we'll re-map to ERROR_UNSPECIFIED.
+                        CommandException commandException = (CommandException) ar.exception;
+                        switch (commandException.getCommandError()) {
+                            case RADIO_NOT_AVAILABLE:
+                                // Intentional fall-through.
+                            case NO_MEMORY:
+                                // Intentional fall-through.
+                            case INTERNAL_ERR:
+                                // Intentional fall-through.
+                            case NO_RESOURCES:
+                                causeCode = CallFailCause.ERROR_UNSPECIFIED;
+
+                                // Report the actual internal command error as the vendor cause;
+                                // this will ensure it gets bubbled up into the Telecom logs.
+                                vendorCause = commandException.getCommandError().toString();
+                                break;
+                            default:
+                                causeCode = CallFailCause.NORMAL_CLEARING;
+                        }
+                    } else {
+                        // An exception occurred...just treat the disconnect
+                        // cause as "normal"
+                        causeCode = CallFailCause.NORMAL_CLEARING;
+                        Rlog.i(LOG_TAG,
+                                "Exception during getLastCallFailCause, assuming normal "
+                                        + "disconnect");
+                    }
                 } else {
                     LastCallFailCause failCause = (LastCallFailCause)ar.result;
                     causeCode = failCause.causeCode;
@@ -1560,6 +1600,24 @@
         }
     }
 
+    /**
+     * Dispatches the CS call radio technology to all exist connections.
+     *
+     * @param vrat the RIL voice radio technology for CS calls,
+     *             see {@code RIL_RADIO_TECHNOLOGY_*} in {@link android.telephony.ServiceState}.
+     */
+    public void dispatchCsCallRadioTech(@ServiceState.RilRadioTechnology int vrat) {
+        if (mConnections == null) {
+            log("dispatchCsCallRadioTech: mConnections is null");
+            return;
+        }
+        for (GsmCdmaConnection gsmCdmaConnection : mConnections) {
+            if (gsmCdmaConnection != null) {
+                gsmCdmaConnection.setCallRadioTech(vrat);
+            }
+        }
+    }
+
     //CDMA
     /**
      * Check and enable data call after an emergency call is dropped if it's
@@ -1574,7 +1632,9 @@
             }
             if (!inEcm) {
                 // Re-initiate data connection
-                mPhone.mDcTracker.setInternalDataEnabled(true);
+                if (mPhone.getDcTracker(TransportType.WWAN) != null) {
+                    mPhone.getDcTracker(TransportType.WWAN).setInternalDataEnabled(true);
+                }
                 mPhone.notifyEmergencyCallRegistrants(false);
             }
             mPhone.sendEmergencyCallStateChange(false);
@@ -1624,6 +1684,18 @@
         return mIsInEmergencyCall;
     }
 
+    /**
+     * @return {@code true} if the pending outgoing call or active call is an OTASP call,
+     * {@code false} otherwise.
+     */
+    public boolean isInOtaspCall() {
+        return mPendingMO != null && mPendingMO.isOtaspCall()
+                || (mForegroundCall.getConnections().stream()
+                .filter(connection -> ((connection instanceof GsmCdmaConnection)
+                        && (((GsmCdmaConnection) connection).isOtaspCall())))
+                .count() > 0);
+    }
+
     private boolean isPhoneTypeGsm() {
         return mPhone.getPhoneType() == PhoneConstants.PHONE_TYPE_GSM;
     }
diff --git a/src/java/com/android/internal/telephony/GsmCdmaConnection.java b/src/java/com/android/internal/telephony/GsmCdmaConnection.java
index c0f67e2..84c9e0a 100644
--- a/src/java/com/android/internal/telephony/GsmCdmaConnection.java
+++ b/src/java/com/android/internal/telephony/GsmCdmaConnection.java
@@ -33,6 +33,7 @@
 
 import com.android.internal.telephony.cdma.CdmaCallWaitingNotification;
 import com.android.internal.telephony.cdma.CdmaSubscriptionSourceManager;
+import com.android.internal.telephony.metrics.TelephonyMetrics;
 import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppState;
 import com.android.internal.telephony.uicc.UiccCardApplication;
 
@@ -44,6 +45,8 @@
     private static final boolean DBG = true;
     private static final boolean VDBG = false;
 
+    public static final String OTASP_NUMBER = "*22899";
+
     //***** Instance Variables
 
     GsmCdmaCallTracker mOwner;
@@ -75,6 +78,11 @@
     // The cached delay to be used between DTMF tones fetched from carrier config.
     private int mDtmfToneDelay = 0;
 
+    // Store the current audio codec
+    private int mAudioCodec = DriverCall.AUDIO_QUALITY_UNSPECIFIED;
+
+    private TelephonyMetrics mMetrics = TelephonyMetrics.getInstance();
+
     //***** Event Constants
     static final int EVENT_DTMF_DONE = 1;
     static final int EVENT_PAUSE_DONE = 2;
@@ -143,6 +151,8 @@
         fetchDtmfToneDelay(phone);
 
         setAudioQuality(getAudioQualityFromDC(dc.audioQuality));
+
+        setCallRadioTech(mOwner.getPhone().getCsCallRadioTech());
     }
 
     /** This is an MO call, created when dialing */
@@ -155,9 +165,8 @@
         mOwner = ct;
         mHandler = new MyHandler(mOwner.getLooper());
 
-        if (isPhoneTypeGsm()) {
-            mDialString = dialString;
-        } else {
+        mDialString = dialString;
+        if (!isPhoneTypeGsm()) {
             Rlog.d(LOG_TAG, "[GsmCdmaConn] GsmCdmaConnection: dialString=" +
                     maskDialString(dialString));
             dialString = formatDialString(dialString);
@@ -194,6 +203,8 @@
         }
 
         fetchDtmfToneDelay(phone);
+
+        setCallRadioTech(mOwner.getPhone().getCsCallRadioTech());
     }
 
     //CDMA
@@ -216,6 +227,8 @@
         mConnectTime = 0;
         mParent = parent;
         parent.attachFake(this, GsmCdmaCall.State.WAITING);
+
+        setCallRadioTech(mOwner.getPhone().getCsCallRadioTech());
     }
 
 
@@ -515,6 +528,7 @@
             case CallFailCause.USER_ALERTING_NO_ANSWER:
                 return DisconnectCause.TIMED_OUT;
 
+            case CallFailCause.ACCESS_CLASS_BLOCKED:
             case CallFailCause.ERROR_UNSPECIFIED:
             case CallFailCause.NORMAL_CLEARING:
             default:
@@ -549,7 +563,8 @@
                     }
                 }
                 if (isPhoneTypeGsm()) {
-                    if (causeCode == CallFailCause.ERROR_UNSPECIFIED) {
+                    if (causeCode == CallFailCause.ERROR_UNSPECIFIED ||
+                                   causeCode == CallFailCause.ACCESS_CLASS_BLOCKED ) {
                         if (phone.mSST.mRestrictedState.isCsRestricted()) {
                             return DisconnectCause.CS_RESTRICTED;
                         } else if (phone.mSST.mRestrictedState.isCsEmergencyRestricted()) {
@@ -654,6 +669,12 @@
             changed = true;
         }
 
+        // Metrics for audio codec
+        if (dc.audioQuality != mAudioCodec) {
+            mAudioCodec = dc.audioQuality;
+            mMetrics.writeAudioCodecGsmCdma(mOwner.getPhone().getPhoneId(), dc.audioQuality);
+        }
+
         // A null cnapName should be the same as ""
         if (TextUtils.isEmpty(dc.name)) {
             if (!TextUtils.isEmpty(mCnapName)) {
@@ -1152,4 +1173,11 @@
 
         return false;
     }
+
+    /**
+     * @return {@code true} if this call is an OTASP activation call, {@code false} otherwise.
+     */
+    public boolean isOtaspCall() {
+        return mAddress != null && OTASP_NUMBER.equals(mAddress);
+    }
 }
diff --git a/src/java/com/android/internal/telephony/GsmCdmaPhone.java b/src/java/com/android/internal/telephony/GsmCdmaPhone.java
index 2a94fc5..4ce5a8c 100644
--- a/src/java/com/android/internal/telephony/GsmCdmaPhone.java
+++ b/src/java/com/android/internal/telephony/GsmCdmaPhone.java
@@ -55,6 +55,7 @@
 import android.provider.Settings;
 import android.provider.Telephony;
 import android.telecom.VideoProfile;
+import android.telephony.AccessNetworkConstants.TransportType;
 import android.telephony.CarrierConfigManager;
 import android.telephony.CellLocation;
 import android.telephony.ImsiEncryptionInfo;
@@ -66,15 +67,19 @@
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.telephony.UssdResponse;
-import android.telephony.cdma.CdmaCellLocation;
+import android.telephony.data.ApnSetting;
 import android.text.TextUtils;
 import android.util.Log;
+import android.util.Pair;
 
 import com.android.ims.ImsManager;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.cdma.CdmaMmiCode;
 import com.android.internal.telephony.cdma.CdmaSubscriptionSourceManager;
 import com.android.internal.telephony.cdma.EriManager;
+import com.android.internal.telephony.dataconnection.DcTracker;
+import com.android.internal.telephony.dataconnection.TransportManager;
+import com.android.internal.telephony.emergency.EmergencyNumberTracker;
 import com.android.internal.telephony.gsm.GsmMmiCode;
 import com.android.internal.telephony.gsm.SuppServiceNotification;
 import com.android.internal.telephony.test.SimulatedRadioControl;
@@ -91,6 +96,7 @@
 import com.android.internal.telephony.uicc.UiccController;
 import com.android.internal.telephony.uicc.UiccProfile;
 import com.android.internal.telephony.uicc.UiccSlot;
+import com.android.internal.util.ArrayUtils;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -165,10 +171,11 @@
     private IsimUiccRecords mIsimUiccRecords;
     public GsmCdmaCallTracker mCT;
     public ServiceStateTracker mSST;
+    public EmergencyNumberTracker mEmergencyNumberTracker;
     private ArrayList <MmiCode> mPendingMMIs = new ArrayList<MmiCode>();
     private IccPhoneBookInterfaceManager mIccPhoneBookIntManager;
     // Used for identify the carrier of current subscription
-    private CarrierIdentifier mCarrerIdentifier;
+    private CarrierResolver mCarrierResolver;
 
     private int mPrecisePhoneType;
 
@@ -220,15 +227,31 @@
         initRatSpecific(precisePhoneType);
         // CarrierSignalAgent uses CarrierActionAgent in construction so it needs to be created
         // after CarrierActionAgent.
-        mCarrierActionAgent = mTelephonyComponentFactory.makeCarrierActionAgent(this);
-        mCarrierSignalAgent = mTelephonyComponentFactory.makeCarrierSignalAgent(this);
-        mSST = mTelephonyComponentFactory.makeServiceStateTracker(this, this.mCi);
+        mCarrierActionAgent = mTelephonyComponentFactory.inject(CarrierActionAgent.class.getName())
+                .makeCarrierActionAgent(this);
+        mCarrierSignalAgent = mTelephonyComponentFactory.inject(CarrierSignalAgent.class.getName())
+                .makeCarrierSignalAgent(this);
+        mTransportManager = mTelephonyComponentFactory.inject(TransportManager.class.getName())
+                .makeTransportManager(this);
+        mSST = mTelephonyComponentFactory.inject(ServiceStateTracker.class.getName())
+                .makeServiceStateTracker(this, this.mCi);
+        mEmergencyNumberTracker = mTelephonyComponentFactory
+                .inject(EmergencyNumberTracker.class.getName()).makeEmergencyNumberTracker(
+                this, this.mCi);
         // DcTracker uses SST so needs to be created after it is instantiated
-        mDcTracker = mTelephonyComponentFactory.makeDcTracker(this);
-        mCarrerIdentifier = mTelephonyComponentFactory.makeCarrierIdentifier(this);
+        for (int transport : mTransportManager.getAvailableTransports()) {
+            mDcTrackers.put(transport, mTelephonyComponentFactory.inject(DcTracker.class.getName())
+                    .makeDcTracker(this, transport));
+        }
+
+        mCarrierResolver = mTelephonyComponentFactory.inject(CarrierResolver.class.getName())
+                .makeCarrierResolver(this);
 
         mSST.registerForNetworkAttached(this, EVENT_REGISTERED_TO_NETWORK, null);
-        mDeviceStateMonitor = mTelephonyComponentFactory.makeDeviceStateMonitor(this);
+        mDeviceStateMonitor = mTelephonyComponentFactory.inject(DeviceStateMonitor.class.getName())
+                .makeDeviceStateMonitor(this);
+
+        mSST.registerForVoiceRegStateOrRatChanged(this, EVENT_VRS_OR_RAT_CHANGED, null);
         logd("GsmCdmaPhone: constructor: sub = " + mPhoneId);
     }
 
@@ -247,16 +270,22 @@
             mSimulatedRadioControl = (SimulatedRadioControl) ci;
         }
 
-        mCT = mTelephonyComponentFactory.makeGsmCdmaCallTracker(this);
-        mIccPhoneBookIntManager = mTelephonyComponentFactory.makeIccPhoneBookInterfaceManager(this);
+        mCT = mTelephonyComponentFactory.inject(GsmCdmaCallTracker.class.getName())
+                .makeGsmCdmaCallTracker(this);
+        mIccPhoneBookIntManager = mTelephonyComponentFactory
+                .inject(IccPhoneBookInterfaceManager.class.getName())
+                .makeIccPhoneBookInterfaceManager(this);
         PowerManager pm
                 = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
         mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, LOG_TAG);
-        mIccSmsInterfaceManager = mTelephonyComponentFactory.makeIccSmsInterfaceManager(this);
+        mIccSmsInterfaceManager = mTelephonyComponentFactory
+                .inject(IccSmsInterfaceManager.class.getName())
+                .makeIccSmsInterfaceManager(this);
 
         mCi.registerForAvailable(this, EVENT_RADIO_AVAILABLE, null);
         mCi.registerForOffOrNotAvailable(this, EVENT_RADIO_OFF_OR_NOT_AVAILABLE, null);
         mCi.registerForOn(this, EVENT_RADIO_ON, null);
+        mCi.registerForRadioStateChanged(this, EVENT_RADIO_STATE_CHANGED, null);
         mCi.setOnSuppServiceNotification(this, EVENT_SSN, null);
 
         //GSM
@@ -264,10 +293,11 @@
         mCi.setOnSs(this, EVENT_SS, null);
 
         //CDMA
-        mCdmaSSM = mTelephonyComponentFactory.getCdmaSubscriptionSourceManagerInstance(mContext,
+        mCdmaSSM = mTelephonyComponentFactory.inject(CdmaSubscriptionSourceManager.class.getName())
+                .getCdmaSubscriptionSourceManagerInstance(mContext,
                 mCi, this, EVENT_CDMA_SUBSCRIPTION_SOURCE_CHANGED, null);
-        mEriManager = mTelephonyComponentFactory.makeEriManager(this, mContext,
-                EriManager.ERI_FROM_XML);
+        mEriManager = mTelephonyComponentFactory.inject(EriManager.class.getName())
+                .makeEriManager(this, mContext, EriManager.ERI_FROM_XML);
         mCi.setEmergencyCallbackMode(this, EVENT_EMERGENCY_CALLBACK_MODE_ENTER, null);
         mCi.registerForExitEmergencyCallbackMode(this, EVENT_EXIT_EMERGENCY_CALLBACK_RESPONSE,
                 null);
@@ -339,7 +369,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
@@ -357,19 +387,18 @@
         if (TextUtils.isEmpty(operatorNumeric)) {
             logd("setIsoCountryProperty: clear 'gsm.sim.operator.iso-country'");
             tm.setSimCountryIsoForPhone(mPhoneId, "");
+            SubscriptionController.getInstance().setCountryIso("", getSubId());
         } else {
             String iso = "";
             try {
-                iso = MccTable.countryCodeForMcc(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);
             }
 
             logd("setIsoCountryProperty: set 'gsm.sim.operator.iso-country' to iso=" + iso);
             tm.setSimCountryIsoForPhone(mPhoneId, iso);
+            SubscriptionController.getInstance().setCountryIso(iso, getSubId());
         }
     }
 
@@ -395,14 +424,14 @@
         onUpdateIccAvailability();
         mCT.updatePhoneType();
 
-        CommandsInterface.RadioState radioState = mCi.getRadioState();
-        if (radioState.isAvailable()) {
+        int radioState = mCi.getRadioState();
+        if (radioState != TelephonyManager.RADIO_POWER_UNAVAILABLE) {
             handleRadioAvailable();
-            if (radioState.isOn()) {
+            if (radioState == TelephonyManager.RADIO_POWER_ON) {
                 handleRadioOn();
             }
         }
-        if (!radioState.isAvailable() || !radioState.isOn()) {
+        if (radioState != TelephonyManager.RADIO_POWER_ON) {
             handleRadioOffOrNotAvailable();
         }
     }
@@ -420,8 +449,7 @@
     public ServiceState getServiceState() {
         if (mSST == null || mSST.mSS.getState() != ServiceState.STATE_IN_SERVICE) {
             if (mImsPhone != null) {
-                return ServiceState.mergeServiceStates(
-                        (mSST == null) ? new ServiceState() : mSST.mSS,
+                return mergeServiceStates((mSST == null) ? new ServiceState() : mSST.mSS,
                         mImsPhone.getServiceState());
             }
         }
@@ -435,25 +463,8 @@
     }
 
     @Override
-    public CellLocation getCellLocation(WorkSource workSource) {
-        if (isPhoneTypeGsm()) {
-            return mSST.getCellLocation(workSource);
-        } else {
-            CdmaCellLocation loc = (CdmaCellLocation)mSST.mCellLoc;
-
-            int mode = Settings.Secure.getInt(getContext().getContentResolver(),
-                    Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_OFF);
-            if (mode == Settings.Secure.LOCATION_MODE_OFF) {
-                // clear lat/long values for location privacy
-                CdmaCellLocation privateLoc = new CdmaCellLocation();
-                privateLoc.setCellLocationData(loc.getBaseStationId(),
-                        CdmaCellLocation.INVALID_LAT_LONG,
-                        CdmaCellLocation.INVALID_LAT_LONG,
-                        loc.getSystemId(), loc.getNetworkId());
-                loc = privateLoc;
-            }
-            return loc;
-        }
+    public void getCellLocation(WorkSource workSource, Message rspMsg) {
+        mSST.requestCellLocation(workSource, rspMsg);
     }
 
     @Override
@@ -483,11 +494,21 @@
     }
 
     @Override
+    public EmergencyNumberTracker getEmergencyNumberTracker() {
+        return mEmergencyNumberTracker;
+    }
+
+    @Override
     public CallTracker getCallTracker() {
         return mCT;
     }
 
     @Override
+    public TransportManager getTransportManager() {
+        return mTransportManager;
+    }
+
+    @Override
     public void updateVoiceMail() {
         if (isPhoneTypeGsm()) {
             int countVoiceMessages = 0;
@@ -533,21 +554,25 @@
 
             ret = PhoneConstants.DataState.DISCONNECTED;
         } else { /* mSST.gprsState == ServiceState.STATE_IN_SERVICE */
-            switch (mDcTracker.getState(apnType)) {
-                case CONNECTED:
-                case DISCONNECTING:
-                    if ( mCT.mState != PhoneConstants.State.IDLE
-                            && !mSST.isConcurrentVoiceAndDataAllowed()) {
-                        ret = PhoneConstants.DataState.SUSPENDED;
-                    } else {
-                        ret = PhoneConstants.DataState.CONNECTED;
-                    }
-                    break;
-                case CONNECTING:
-                    ret = PhoneConstants.DataState.CONNECTING;
-                    break;
-                default:
-                    ret = PhoneConstants.DataState.DISCONNECTED;
+            int currentTransport = mTransportManager.getCurrentTransport(
+                    ApnSetting.getApnTypesBitmaskFromString(apnType));
+            if (getDcTracker(currentTransport) != null) {
+                switch (getDcTracker(currentTransport).getState(apnType)) {
+                    case CONNECTED:
+                    case DISCONNECTING:
+                        if (mCT.mState != PhoneConstants.State.IDLE
+                                && !mSST.isConcurrentVoiceAndDataAllowed()) {
+                            ret = PhoneConstants.DataState.SUSPENDED;
+                        } else {
+                            ret = PhoneConstants.DataState.CONNECTED;
+                        }
+                        break;
+                    case CONNECTING:
+                        ret = PhoneConstants.DataState.CONNECTING;
+                        break;
+                    default:
+                        ret = PhoneConstants.DataState.DISCONNECTED;
+                }
             }
         }
 
@@ -559,8 +584,9 @@
     public DataActivityState getDataActivityState() {
         DataActivityState ret = DataActivityState.NONE;
 
-        if (mSST.getCurrentDataConnectionState() == ServiceState.STATE_IN_SERVICE) {
-            switch (mDcTracker.getActivity()) {
+        if (mSST.getCurrentDataConnectionState() == ServiceState.STATE_IN_SERVICE
+                && getDcTracker(TransportType.WWAN) != null) {
+            switch (getDcTracker(TransportType.WWAN).getActivity()) {
                 case DATAIN:
                     ret = DataActivityState.DATAIN;
                 break;
@@ -641,11 +667,16 @@
         intent.putExtra(PhoneConstants.PHONE_IN_ECM_STATE, isInEcm());
         SubscriptionManager.putPhoneIdAndSubIdExtra(intent, getPhoneId());
         ActivityManager.broadcastStickyIntent(intent, UserHandle.USER_ALL);
-        if (DBG) logd("sendEmergencyCallbackModeChange");
+        logi("sendEmergencyCallbackModeChange");
     }
 
     @Override
     public void sendEmergencyCallStateChange(boolean callActive) {
+        if (!isPhoneTypeCdma()) {
+            // It possible that this method got called from ImsPhoneCallTracker#
+            logi("sendEmergencyCallbackModeChange - skip for non-cdma");
+            return;
+        }
         if (mBroadcastEmergencyCallStateChanges) {
             Intent intent = new Intent(TelephonyIntents.ACTION_EMERGENCY_CALL_STATE_CHANGED);
             intent.putExtra(PhoneConstants.PHONE_IN_EMERGENCY_CALL, callActive);
@@ -668,8 +699,13 @@
         super.notifyServiceStateChangedP(ss);
     }
 
-    public void notifyLocationChanged() {
-        mNotifier.notifyCellLocation(this);
+    /**
+     * Notify that the CellLocation has changed.
+     *
+     * @param cl the new CellLocation
+     */
+    public void notifyLocationChanged(CellLocation cl) {
+        mNotifier.notifyCellLocation(this, cl);
     }
 
     @Override
@@ -840,6 +876,32 @@
         return mCT.mRingingCall;
     }
 
+    /**
+     * ImsService reports "IN_SERVICE" for its voice registration state even if the device
+     * has lost the physical link to the tower. This helper method merges the IMS and modem
+     * ServiceState, only overriding the voice registration state when we are registered to IMS over
+     * IWLAN. In this case the voice registration state will always be "OUT_OF_SERVICE", so override
+     * the voice registration state with the data registration state.
+     */
+    private ServiceState mergeServiceStates(ServiceState baseSs, ServiceState imsSs) {
+        // "IN_SERVICE" in this case means IMS is registered.
+        if (imsSs.getVoiceRegState() != ServiceState.STATE_IN_SERVICE) {
+            return baseSs;
+        }
+
+        if (imsSs.getRilDataRadioTechnology() == ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN) {
+            ServiceState newSs = new ServiceState(baseSs);
+            // Voice override for IWLAN. In this case, voice registration is OUT_OF_SERVICE, but
+            // the data RAT is IWLAN, so use that as a basis for determining whether or not the
+            // physical link is available.
+            newSs.setVoiceRegState(baseSs.getDataRegState());
+            newSs.setEmergencyOnly(false); // only get here if voice is IN_SERVICE
+            return newSs;
+        }
+
+        return baseSs;
+    }
+
     private boolean handleCallDeflectionIncallSupplementaryService(
             String dialString) {
         if (dialString.length() > 1) {
@@ -1104,7 +1166,7 @@
                     logi("IMS call failed with Exception: " + e.getMessage() + ". Falling back "
                             + "to CS.");
                 } else {
-                    CallStateException ce = new CallStateException(e.getMessage());
+                    CallStateException ce = new CallStateException(e.getError(), e.getMessage());
                     ce.setStackTrace(e.getStackTrace());
                     throw ce;
                 }
@@ -1118,7 +1180,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");
@@ -1386,7 +1449,7 @@
         if (TextUtils.isEmpty(number)) {
             CarrierConfigManager configManager = (CarrierConfigManager)
                     getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
-            PersistableBundle b = configManager.getConfig();
+            PersistableBundle b = configManager.getConfigForSubId(getSubId());
             if (b != null) {
                 String defaultVmNumber =
                         b.getString(CarrierConfigManager.KEY_DEFAULT_VM_NUMBER_STRING);
@@ -1394,7 +1457,7 @@
                         b.getString(CarrierConfigManager.KEY_DEFAULT_VM_NUMBER_ROAMING_STRING);
                 if (!TextUtils.isEmpty(defaultVmNumberRoaming) && mSST.mSS.getRoaming()) {
                     number = defaultVmNumberRoaming;
-                } else {
+                } else if (!TextUtils.isEmpty(defaultVmNumber)) {
                     number = defaultVmNumber;
                 }
             }
@@ -1404,7 +1467,7 @@
             // Read platform settings for dynamic voicemail number
             CarrierConfigManager configManager = (CarrierConfigManager)
                     getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
-            PersistableBundle b = configManager.getConfig();
+            PersistableBundle b = configManager.getConfigForSubId(getSubId());
             if (b != null && b.getBoolean(
                     CarrierConfigManager.KEY_CONFIG_TELEPHONY_USE_OWN_NUMBER_FOR_VOICEMAIL_BOOL)) {
                 number = getLine1Number();
@@ -1539,17 +1602,37 @@
 
     @Override
     public int getCarrierId() {
-        return mCarrerIdentifier.getCarrierId();
+        return mCarrierResolver.getCarrierId();
     }
 
     @Override
     public String getCarrierName() {
-        return mCarrerIdentifier.getCarrierName();
+        return mCarrierResolver.getCarrierName();
+    }
+
+    @Override
+    public int getMNOCarrierId() {
+        return mCarrierResolver.getMnoCarrierId();
+    }
+
+    @Override
+    public int getPreciseCarrierId() {
+        return mCarrierResolver.getPreciseCarrierId();
+    }
+
+    @Override
+    public String getPreciseCarrierName() {
+        return mCarrierResolver.getPreciseCarrierName();
+    }
+
+    @Override
+    public void resolveSubscriptionCarrierId(String simState) {
+        mCarrierResolver.resolveSubscriptionCarrierId(simState);
     }
 
     @Override
     public int getCarrierIdListVersion() {
-        return mCarrerIdentifier.getCarrierListVersion();
+        return mCarrierResolver.getCarrierListVersion();
     }
 
     @Override
@@ -1732,9 +1815,15 @@
         return (action == CF_ACTION_ENABLE) || (action == CF_ACTION_REGISTRATION);
     }
 
+    private boolean isImsUtEnabledOverCdma() {
+        return isPhoneTypeCdmaLte()
+            && mImsPhone != null
+            && mImsPhone.isUtEnabled();
+    }
+
     @Override
     public void getCallForwardingOption(int commandInterfaceCFReason, Message onComplete) {
-        if (isPhoneTypeGsm()) {
+        if (isPhoneTypeGsm() || isImsUtEnabledOverCdma()) {
             Phone imsPhone = mImsPhone;
             if ((imsPhone != null)
                     && ((imsPhone.getServiceState().getState() == ServiceState.STATE_IN_SERVICE)
@@ -1755,7 +1844,7 @@
                         CommandsInterface.SERVICE_CLASS_VOICE, null, resp);
             }
         } else {
-            loge("getCallForwardingOption: not possible in CDMA");
+            loge("getCallForwardingOption: not possible in CDMA without IMS");
         }
     }
 
@@ -1765,7 +1854,7 @@
             String dialingNumber,
             int timerSeconds,
             Message onComplete) {
-        if (isPhoneTypeGsm()) {
+        if (isPhoneTypeGsm() || isImsUtEnabledOverCdma()) {
             Phone imsPhone = mImsPhone;
             if ((imsPhone != null)
                     && ((imsPhone.getServiceState().getState() == ServiceState.STATE_IN_SERVICE)
@@ -1794,7 +1883,7 @@
                         resp);
             }
         } else {
-            loge("setCallForwardingOption: not possible in CDMA");
+            loge("setCallForwardingOption: not possible in CDMA without IMS");
         }
     }
 
@@ -1803,9 +1892,7 @@
             int serviceClass) {
         if (isPhoneTypeGsm()) {
             Phone imsPhone = mImsPhone;
-            if ((imsPhone != null)
-                    && ((imsPhone.getServiceState().getState() == ServiceState.STATE_IN_SERVICE)
-                    || imsPhone.isUtEnabled())) {
+            if ((imsPhone != null) && imsPhone.isUtEnabled()) {
                 imsPhone.getCallBarring(facility, password, onComplete, serviceClass);
                 return;
             }
@@ -1820,9 +1907,7 @@
             Message onComplete, int serviceClass) {
         if (isPhoneTypeGsm()) {
             Phone imsPhone = mImsPhone;
-            if ((imsPhone != null)
-                    && ((imsPhone.getServiceState().getState() == ServiceState.STATE_IN_SERVICE)
-                    || imsPhone.isUtEnabled())) {
+            if ((imsPhone != null) && imsPhone.isUtEnabled()) {
                 imsPhone.setCallBarring(facility, lockState, password, onComplete, serviceClass);
                 return;
             }
@@ -1887,7 +1972,7 @@
 
     @Override
     public void getCallWaiting(Message onComplete) {
-        if (isPhoneTypeGsm()) {
+        if (isPhoneTypeGsm() || isImsUtEnabledOverCdma()) {
             Phone imsPhone = mImsPhone;
             if ((imsPhone != null)
                     && ((imsPhone.getServiceState().getState() == ServiceState.STATE_IN_SERVICE)
@@ -1906,7 +1991,7 @@
 
     @Override
     public void setCallWaiting(boolean enable, Message onComplete) {
-        if (isPhoneTypeGsm()) {
+        if (isPhoneTypeGsm() || isImsUtEnabledOverCdma()) {
             Phone imsPhone = mImsPhone;
             if ((imsPhone != null)
                     && ((imsPhone.getServiceState().getState() == ServiceState.STATE_IN_SERVICE)
@@ -1917,7 +2002,7 @@
 
             mCi.setCallWaiting(enable, CommandsInterface.SERVICE_CLASS_VOICE, onComplete);
         } else {
-            loge("method setCallWaiting is NOT supported in CDMA!");
+            loge("method setCallWaiting is NOT supported in CDMA without IMS!");
         }
     }
 
@@ -1941,28 +2026,6 @@
     }
 
     @Override
-    public void getNeighboringCids(Message response, WorkSource workSource) {
-        if (isPhoneTypeGsm()) {
-            mCi.getNeighboringCids(response, workSource);
-        } else {
-            /*
-             * This is currently not implemented.  At least as of June
-             * 2009, there is no neighbor cell information available for
-             * CDMA because some party is resisting making this
-             * information readily available.  Consequently, calling this
-             * function can have no useful effect.  This situation may
-             * (and hopefully will) change in the future.
-             */
-            if (response != null) {
-                CommandException ce = new CommandException(
-                        CommandException.Error.REQUEST_NOT_SUPPORTED);
-                AsyncResult.forMessage(response).exception = ce;
-                response.sendToTarget();
-            }
-        }
-    }
-
-    @Override
     public void setTTYMode(int ttyMode, Message onComplete) {
         // Send out the TTY Mode change over RIL as well
         super.setTTYMode(ttyMode, onComplete);
@@ -2005,12 +2068,17 @@
 
     @Override
     public boolean getDataRoamingEnabled() {
-        return mDcTracker.getDataRoamingEnabled();
+        if (getDcTracker(TransportType.WWAN) != null) {
+            return getDcTracker(TransportType.WWAN).getDataRoamingEnabled();
+        }
+        return false;
     }
 
     @Override
     public void setDataRoamingEnabled(boolean enable) {
-        mDcTracker.setDataRoamingEnabledByUser(enable);
+        if (getDcTracker(TransportType.WWAN) != null) {
+            getDcTracker(TransportType.WWAN).setDataRoamingEnabledByUser(enable);
+        }
     }
 
     @Override
@@ -2055,17 +2123,25 @@
 
     @Override
     public boolean isUserDataEnabled() {
-        return mDcTracker.isUserDataEnabled();
+        if (getDcTracker(TransportType.WWAN) != null) {
+            return getDcTracker(TransportType.WWAN).isUserDataEnabled();
+        }
+        return false;
     }
 
     @Override
     public boolean isDataEnabled() {
-        return mDcTracker.isDataEnabled();
+        if (getDcTracker(TransportType.WWAN) != null) {
+            return getDcTracker(TransportType.WWAN).isDataEnabled();
+        }
+        return false;
     }
 
     @Override
     public void setUserDataEnabled(boolean enable) {
-        mDcTracker.setUserDataEnabled(enable);
+        if (getDcTracker(TransportType.WWAN) != null) {
+            getDcTracker(TransportType.WWAN).setUserDataEnabled(enable);
+        }
     }
 
     /**
@@ -2102,7 +2178,7 @@
     public boolean supports3gppCallForwardingWhileRoaming() {
         CarrierConfigManager configManager = (CarrierConfigManager)
                 getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
-        PersistableBundle b = configManager.getConfig();
+        PersistableBundle b = configManager.getConfigForSubId(getSubId());
         if (b != null) {
             return b.getBoolean(
                     CarrierConfigManager.KEY_SUPPORT_3GPP_CALL_FORWARDING_WHILE_ROAMING_BOOL, true);
@@ -2222,6 +2298,11 @@
         mRadioOffOrNotAvailableRegistrants.notifyRegistrants();
     }
 
+    private void handleRadioPowerStateChange() {
+        Rlog.d(LOG_TAG, "handleRadioPowerStateChange, state= " + mCi.getRadioState());
+        mNotifier.notifyRadioPowerStateChanged(mCi.getRadioState());
+    }
+
     @Override
     public void handleMessage(Message msg) {
         AsyncResult ar;
@@ -2418,6 +2499,8 @@
                     setVmSimImsi(null);
                 }
 
+                updateVoiceMail();
+
                 mSimRecordsLoadedRegistrants.notifyRegistrants();
                 break;
 
@@ -2473,6 +2556,12 @@
                 break;
             }
 
+            case EVENT_RADIO_STATE_CHANGED: {
+                logd("EVENT EVENT_RADIO_STATE_CHANGED");
+                handleRadioPowerStateChange();
+                break;
+            }
+
             case EVENT_SSN:
                 logd("Event EVENT_SSN Received");
                 if (isPhoneTypeGsm()) {
@@ -2574,6 +2663,11 @@
                 }
                 Rlog.d(LOG_TAG, "EVENT_GET_RADIO_CAPABILITY: phone rc: " + rc);
                 break;
+            case EVENT_VRS_OR_RAT_CHANGED:
+                ar = (AsyncResult) msg.obj;
+                Pair<Integer, Integer> vrsRatPair = (Pair<Integer, Integer>) ar.result;
+                onVoiceRegStateOrRatChanged(vrsRatPair.first, vrsRatPair.second);
+                break;
 
             default:
                 super.handleMessage(msg);
@@ -2654,10 +2748,22 @@
                 if (DBG) {
                     logd("New Uicc application found. type = " + newUiccApplication.getType());
                 }
+                final IccRecords iccRecords = newUiccApplication.getIccRecords();
                 mUiccApplication.set(newUiccApplication);
-                mIccRecords.set(newUiccApplication.getIccRecords());
+                mIccRecords.set(iccRecords);
                 registerForIccRecordEvents();
-                mIccPhoneBookIntManager.updateIccRecords(mIccRecords.get());
+                mIccPhoneBookIntManager.updateIccRecords(iccRecords);
+                if (iccRecords != null) {
+                    final String simOperatorNumeric = iccRecords.getOperatorNumeric();
+                    if (DBG) {
+                        logd("New simOperatorNumeric = " + simOperatorNumeric);
+                    }
+                    if (!TextUtils.isEmpty(simOperatorNumeric)) {
+                        TelephonyManager.from(mContext).setSimOperatorNumericForPhone(mPhoneId,
+                                simOperatorNumeric);
+                    }
+                }
+                updateDataConnectionTracker();
             }
         }
     }
@@ -2720,7 +2826,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) {
@@ -2868,8 +2974,8 @@
          *  selection is disallowed. So we should force auto select mode.
          */
         if (isManualSelProhibitedInGlobalMode()
-                && ((nwMode == Phone.NT_MODE_LTE_CDMA_EVDO_GSM_WCDMA)
-                        || (nwMode == Phone.NT_MODE_GLOBAL)) ){
+                && ((nwMode == TelephonyManager.NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA)
+                        || (nwMode == TelephonyManager.NETWORK_MODE_GLOBAL)) ){
             logd("Should force auto network select mode = " + nwMode);
             return true;
         } else {
@@ -3004,7 +3110,9 @@
             // send an Intent
             sendEmergencyCallbackModeChange();
             // Re-initiate data connection
-            mDcTracker.setInternalDataEnabled(true);
+            if (getDcTracker(TransportType.WWAN) != null) {
+                getDcTracker(TransportType.WWAN).setInternalDataEnabled(true);
+            }
             notifyEmergencyCallRegistrants(false);
         }
     }
@@ -3270,6 +3378,14 @@
         }
     }
 
+    //return true if either CSIM or RUIM app is present
+    private boolean isCdmaSubscriptionAppPresent(){
+        UiccCardApplication cdmaApplication =
+                mUiccController.getUiccCardApplication(mPhoneId, UiccController.APP_FAM_3GPP2);
+        return cdmaApplication != null && (cdmaApplication.getType() == AppType.APPTYPE_CSIM ||
+                cdmaApplication.getType() == AppType.APPTYPE_RUIM);
+    }
+
     private void phoneObjectUpdater(int newVoiceRadioTech) {
         logd("phoneObjectUpdater: newVoiceRadioTech=" + newVoiceRadioTech);
 
@@ -3283,7 +3399,10 @@
                 int volteReplacementRat =
                         b.getInt(CarrierConfigManager.KEY_VOLTE_REPLACEMENT_RAT_INT);
                 logd("phoneObjectUpdater: volteReplacementRat=" + volteReplacementRat);
-                if (volteReplacementRat != ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN) {
+                if (volteReplacementRat != ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN &&
+                           //In cdma case, replace rat only if csim or ruim app present
+                           (ServiceState.isGsm(volteReplacementRat) ||
+                           isCdmaSubscriptionAppPresent())) {
                     newVoiceRadioTech = volteReplacementRat;
                 }
             } else {
@@ -3343,7 +3462,7 @@
 
         boolean oldPowerState = false; // old power state to off
         if (mResetModemOnRadioTechnologyChange) {
-            if (mCi.getRadioState().isOn()) {
+            if (mCi.getRadioState() == TelephonyManager.RADIO_POWER_ON) {
                 oldPowerState = true;
                 logd("phoneObjectUpdater: Setting Radio Power to Off");
                 mCi.setRadioPower(false, null);
@@ -3572,6 +3691,70 @@
         mEcmTimerResetRegistrants.notifyResult(flag);
     }
 
+    private static final int[] VOICE_PS_CALL_RADIO_TECHNOLOGY = {
+            ServiceState.RIL_RADIO_TECHNOLOGY_LTE,
+            ServiceState.RIL_RADIO_TECHNOLOGY_LTE_CA,
+            ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN
+    };
+
+    /**
+     * Calculates current RIL voice radio technology for CS calls.
+     *
+     * This function should only be used in {@link com.android.internal.telephony.GsmCdmaConnection}
+     * to indicate current CS call radio technology.
+     *
+     * @return the RIL voice radio technology used for CS calls,
+     *         see {@code RIL_RADIO_TECHNOLOGY_*} in {@link android.telephony.ServiceState}.
+     */
+    public @ServiceState.RilRadioTechnology int getCsCallRadioTech() {
+        int calcVrat = ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN;
+        if (mSST != null) {
+            calcVrat = getCsCallRadioTech(mSST.mSS.getVoiceRegState(),
+                    mSST.mSS.getRilVoiceRadioTechnology());
+        }
+
+        return calcVrat;
+    }
+
+    /**
+     * Calculates current RIL voice radio technology for CS calls based on current voice
+     * registration state and technology.
+     *
+     * Mark current RIL voice radio technology as unknow when any of below condtion is met:
+     *  1) Current RIL voice registration state is not in-service.
+     *  2) Current RIL voice radio technology is PS call technology, which means CSFB will
+     *     happen later after call connection is established.
+     *     It is inappropriate to notify upper layer the PS call technology while current call
+     *     is CS call, so before CSFB happens, mark voice radio technology as unknow.
+     *     After CSFB happens, {@link #onVoiceRegStateOrRatChanged} will update voice call radio
+     *     technology with correct value.
+     *
+     * @param vrs the voice registration state
+     * @param vrat the RIL voice radio technology
+     *
+     * @return the RIL voice radio technology used for CS calls,
+     *         see {@code RIL_RADIO_TECHNOLOGY_*} in {@link android.telephony.ServiceState}.
+     */
+    private @ServiceState.RilRadioTechnology int getCsCallRadioTech(int vrs, int vrat) {
+        logd("getCsCallRadioTech, current vrs=" + vrs + ", vrat=" + vrat);
+        int calcVrat = vrat;
+        if (vrs != ServiceState.STATE_IN_SERVICE
+                || ArrayUtils.contains(VOICE_PS_CALL_RADIO_TECHNOLOGY, vrat)) {
+            calcVrat = ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN;
+        }
+
+        logd("getCsCallRadioTech, result calcVrat=" + calcVrat);
+        return calcVrat;
+    }
+
+    /**
+     * Handler of RIL Voice Radio Technology changed event.
+     */
+    private void onVoiceRegStateOrRatChanged(int vrs, int vrat) {
+        logd("onVoiceRegStateOrRatChanged");
+        mCT.dispatchCsCallRadioTech(getCsCallRadioTech(vrs, vrat));
+    }
+
     /**
      * Registration point for Ecm timer reset
      *
diff --git a/src/java/com/android/internal/telephony/HalVersion.java b/src/java/com/android/internal/telephony/HalVersion.java
new file mode 100644
index 0000000..c05179b
--- /dev/null
+++ b/src/java/com/android/internal/telephony/HalVersion.java
@@ -0,0 +1,95 @@
+/*
+ * 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 java.util.Objects;
+
+/**
+ * Represent the version of underlying vendor HAL service.
+ * @see <a href="https://source.android.com/devices/architecture/hidl/versioning">
+ * HIDL versioning</a>.
+ */
+public class HalVersion implements Comparable<HalVersion> {
+    public final int major;
+
+    public final int minor;
+
+    public HalVersion(int major, int minor) {
+        this.major = major;
+        this.minor = minor;
+    }
+
+    @Override
+    public int compareTo(HalVersion ver) {
+        if (ver == null) {
+            return 1;
+        }
+        if (this.major > ver.major) {
+            return 1;
+        } else if (this.major < ver.major) {
+            return -1;
+        } else if (this.minor > ver.minor) {
+            return 1;
+        } else if (this.minor < ver.minor) {
+            return -1;
+        }
+        return 0;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(major, minor);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        return ((o instanceof HalVersion) && (o == this || compareTo((HalVersion) o) == 0));
+    }
+
+    /**
+     * @return True if the version is greater than the compared version.
+     */
+    public boolean greater(HalVersion ver) {
+        return compareTo(ver) > 0;
+    }
+
+    /**
+     * @return True if the version is less than the compared version.
+     */
+    public boolean less(HalVersion ver) {
+        return compareTo(ver) < 0;
+    }
+
+    /**
+     * @return True if the version is greater than or equal to the compared version.
+     */
+    public boolean greaterOrEqual(HalVersion ver) {
+        return greater(ver) || equals(ver);
+    }
+
+    /**
+     * @return True if the version is less than or equal to the compared version.
+     */
+    public boolean lessOrEqual(HalVersion ver) {
+        return less(ver) || equals(ver);
+    }
+
+    @Override
+    public String toString() {
+        return major + "." + minor;
+    }
+}
diff --git a/src/java/com/android/internal/telephony/IccCard.java b/src/java/com/android/internal/telephony/IccCard.java
index 6c27d7e..75f3377 100644
--- a/src/java/com/android/internal/telephony/IccCard.java
+++ b/src/java/com/android/internal/telephony/IccCard.java
@@ -143,6 +143,15 @@
     }
 
     /**
+     * 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
      * This is a sync call which returns the cached pin enabled state
      *
@@ -266,6 +275,14 @@
         return false;
     }
 
+    /**
+     * @return whether the card is an empty profile, meaning there's no UiccCardApplication,
+     * and that we don't need to wait for LOADED state.
+     */
+    public boolean isEmptyProfile() {
+        return false;
+    }
+
     private void sendMessageWithCardAbsentException(Message onComplete) {
         AsyncResult ret = AsyncResult.forMessage(onComplete);
         ret.exception = new RuntimeException("No valid IccCard");
diff --git a/src/java/com/android/internal/telephony/IccPhoneBookInterfaceManager.java b/src/java/com/android/internal/telephony/IccPhoneBookInterfaceManager.java
index 2a35370..42a156a 100644
--- a/src/java/com/android/internal/telephony/IccPhoneBookInterfaceManager.java
+++ b/src/java/com/android/internal/telephony/IccPhoneBookInterfaceManager.java
@@ -75,6 +75,8 @@
                             logd("GET_RECORD_SIZE Size " + mRecordSize[0] +
                                     " total " + mRecordSize[1] +
                                     " #record " + mRecordSize[2]);
+                        } else {
+                            loge("EVENT_GET_SIZE_DONE: failed; ex=" + ar.exception);
                         }
                         notifyPending(ar);
                     }
@@ -83,6 +85,9 @@
                     ar = (AsyncResult) msg.obj;
                     synchronized (mLock) {
                         mSuccess = (ar.exception == null);
+                        if (!mSuccess) {
+                            loge("EVENT_UPDATE_DONE - failed; ex=" + ar.exception);
+                        }
                         notifyPending(ar);
                     }
                     break;
@@ -92,7 +97,8 @@
                         if (ar.exception == null) {
                             mRecords = (List<AdnRecord>) ar.result;
                         } else {
-                            if(DBG) logd("Cannot load ADN records");
+                            loge("EVENT_LOAD_DONE: Cannot load ADN records; ex="
+                                    + ar.exception);
                             mRecords = null;
                         }
                         notifyPending(ar);
diff --git a/src/java/com/android/internal/telephony/IccProvider.java b/src/java/com/android/internal/telephony/IccProvider.java
index 8feec94..76af170 100644
--- a/src/java/com/android/internal/telephony/IccProvider.java
+++ b/src/java/com/android/internal/telephony/IccProvider.java
@@ -294,7 +294,7 @@
         String[] emails = null;
         String pin2 = null;
 
-        String[] tokens = where.split("AND");
+        String[] tokens = where.split(" AND ");
         int n = tokens.length;
 
         while (--n >= 0) {
diff --git a/src/java/com/android/internal/telephony/IccSmsInterfaceManager.java b/src/java/com/android/internal/telephony/IccSmsInterfaceManager.java
index f2383f5..280ab9a 100644
--- a/src/java/com/android/internal/telephony/IccSmsInterfaceManager.java
+++ b/src/java/com/android/internal/telephony/IccSmsInterfaceManager.java
@@ -40,6 +40,7 @@
 import android.telephony.Rlog;
 import android.telephony.SmsManager;
 import android.telephony.SmsMessage;
+import android.util.LocalLog;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -50,6 +51,8 @@
 import com.android.internal.telephony.uicc.IccUtils;
 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;
@@ -83,8 +86,10 @@
     protected Phone mPhone;
     final protected Context mContext;
     final protected AppOpsManager mAppOps;
-    final private UserManager mUserManager;
-    protected SmsDispatchersController mDispatchersController;
+    @VisibleForTesting
+    public SmsDispatchersController mDispatchersController;
+
+    private final LocalLog mCellBroadcastLocalLog = new LocalLog(100);
 
     protected Handler mHandler = new Handler() {
         @Override
@@ -142,7 +147,6 @@
         mPhone = phone;
         mContext = context;
         mAppOps = appOps;
-        mUserManager = userManager;
         mDispatchersController = dispatchersController;
     }
 
@@ -179,11 +183,6 @@
         }
     }
 
-    protected void updatePhoneObject(Phone phone) {
-        mPhone = phone;
-        mDispatchersController.updatePhoneObject(phone);
-    }
-
     protected void enforceReceiveAndSend(String message) {
         mContext.enforceCallingOrSelfPermission(
                 Manifest.permission.RECEIVE_SMS, message);
@@ -217,7 +216,7 @@
             mSuccess = false;
             Message response = mHandler.obtainMessage(EVENT_UPDATE_DONE);
 
-            if (status == STATUS_ON_ICC_FREE) {
+            if ((status & 0x01) == STATUS_ON_ICC_FREE) {
                 // RIL_REQUEST_DELETE_SMS_ON_SIM vs RIL_REQUEST_CDMA_DELETE_SMS_ON_RUIM
                 // Special case FREE: call deleteSmsOnSim/Ruim instead of
                 // manipulating the record
@@ -333,6 +332,7 @@
     public void sendDataWithSelfPermissions(String callingPackage, String destAddr, String scAddr,
             int destPort, byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) {
         if (!checkCallingOrSelfSendSmsPermission(callingPackage, "Sending SMS message")) {
+            returnUnspecifiedFailure(sentIntent);
             return;
         }
         sendDataInternal(destAddr, scAddr, destPort, data, sentIntent, deliveryIntent);
@@ -345,6 +345,7 @@
     public void sendData(String callingPackage, String destAddr, String scAddr, int destPort,
             byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) {
         if (!checkCallingSendSmsPermission(callingPackage, "Sending SMS message")) {
+            returnUnspecifiedFailure(sentIntent);
             return;
         }
         sendDataInternal(destAddr, scAddr, destPort, data, sentIntent, deliveryIntent);
@@ -397,6 +398,7 @@
             boolean persistMessageForNonDefaultSmsApp) {
         if (!checkCallingSendTextPermissions(
                 persistMessageForNonDefaultSmsApp, callingPackage, "Sending SMS message")) {
+            returnUnspecifiedFailure(sentIntent);
             return;
         }
         sendTextInternal(callingPackage, destAddr, scAddr, text, sentIntent, deliveryIntent,
@@ -412,6 +414,7 @@
             String text, PendingIntent sentIntent, PendingIntent deliveryIntent,
             boolean persistMessage) {
         if (!checkCallingOrSelfSendSmsPermission(callingPackage, "Sending SMS message")) {
+            returnUnspecifiedFailure(sentIntent);
             return;
         }
         sendTextInternal(callingPackage, destAddr, scAddr, text, sentIntent, deliveryIntent,
@@ -533,6 +536,7 @@
             boolean persistMessageForNonDefaultSmsApp, int priority, boolean expectMore,
             int validityPeriod) {
         if (!checkCallingOrSelfSendSmsPermission(callingPackage, "Sending SMS message")) {
+            returnUnspecifiedFailure(sentIntent);
             return;
         }
         sendTextInternal(callingPackage, destAddr, scAddr, text, sentIntent, deliveryIntent,
@@ -661,6 +665,7 @@
             int priority, boolean expectMore, int validityPeriod) {
         if (!checkCallingSendTextPermissions(
                 persistMessageForNonDefaultSmsApp, callingPackage, "Sending SMS message")) {
+            returnUnspecifiedFailure(sentIntents);
             return;
         }
         if (Rlog.isLoggable("SMS", Log.VERBOSE)) {
@@ -736,7 +741,7 @@
 
         for (int i = 0; i < count; i++) {
             byte[] ba = messages.get(i);
-            if (ba[0] == STATUS_ON_ICC_FREE) {
+            if ((ba[0] & 0x01) == STATUS_ON_ICC_FREE) {
                 ret.add(null);
             } else {
                 ret.add(new SmsRawData(messages.get(i)));
@@ -788,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");
         }
     }
 
@@ -798,7 +803,7 @@
         } 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");
         }
     }
 
@@ -811,15 +816,21 @@
         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());
 
@@ -835,15 +846,21 @@
         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());
 
@@ -859,15 +876,20 @@
         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());
 
@@ -883,15 +905,20 @@
         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());
 
@@ -1073,6 +1100,7 @@
     public void sendStoredText(String callingPkg, Uri messageUri, String scAddress,
             PendingIntent sentIntent, PendingIntent deliveryIntent) {
         if (!checkCallingSendSmsPermission(callingPkg, "Sending SMS message")) {
+            returnUnspecifiedFailure(sentIntent);
             return;
         }
         if (Rlog.isLoggable("SMS", Log.VERBOSE)) {
@@ -1101,6 +1129,7 @@
     public void sendStoredMultipartText(String callingPkg, Uri messageUri, String scAddress,
             List<PendingIntent> sentIntents, List<PendingIntent> deliveryIntents) {
         if (!checkCallingSendSmsPermission(callingPkg, "Sending SMS message")) {
+            returnUnspecifiedFailure(sentIntents);
             return;
         }
         final ContentResolver resolver = mContext.getContentResolver();
@@ -1335,4 +1364,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 79ea8d2..70db301 100644
--- a/src/java/com/android/internal/telephony/ImsSmsDispatcher.java
+++ b/src/java/com/android/internal/telephony/ImsSmsDispatcher.java
@@ -16,12 +16,16 @@
 
 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.PhoneNumberUtils;
 import android.telephony.Rlog;
+import android.telephony.ServiceState;
 import android.telephony.ims.ImsReasonInfo;
 import android.telephony.ims.aidl.IImsSmsListener;
-import android.telephony.ims.feature.ImsFeature;
 import android.telephony.ims.feature.MmTelFeature;
 import android.telephony.ims.stub.ImsRegistrationImplBase;
 import android.telephony.ims.stub.ImsSmsImplBase;
@@ -32,6 +36,7 @@
 import com.android.ims.ImsManager;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
+import com.android.internal.telephony.metrics.TelephonyMetrics;
 import com.android.internal.telephony.util.SMSDispatcherUtil;
 
 import java.util.HashMap;
@@ -57,12 +62,15 @@
     private volatile boolean mIsImsServiceUp;
     private volatile boolean mIsRegistered;
     private final ImsManager.Connector mImsManagerConnector;
+    /** Telephony metrics instance for logging metrics event */
+    private TelephonyMetrics mMetrics = TelephonyMetrics.getInstance();
+
     /**
      * Listen to the IMS service state change
      *
      */
-    private ImsRegistrationImplBase.Callback mRegistrationCallback =
-            new ImsRegistrationImplBase.Callback() {
+    private android.telephony.ims.ImsMmTelManager.RegistrationCallback mRegistrationCallback =
+            new android.telephony.ims.ImsMmTelManager.RegistrationCallback() {
                 @Override
                 public void onRegistered(
                         @ImsRegistrationImplBase.ImsRegistrationTech int imsRadioTech) {
@@ -90,12 +98,13 @@
                 }
             };
 
-    private ImsFeature.CapabilityCallback mCapabilityCallback =
-            new ImsFeature.CapabilityCallback() {
+    private android.telephony.ims.ImsMmTelManager.CapabilityCallback mCapabilityCallback =
+            new android.telephony.ims.ImsMmTelManager.CapabilityCallback() {
                 @Override
-                public void onCapabilitiesStatusChanged(ImsFeature.Capabilities config) {
+                public void onCapabilitiesStatusChanged(
+                        MmTelFeature.MmTelCapabilities capabilities) {
                     synchronized (mLock) {
-                        mIsSmsCapable = config.isCapable(
+                        mIsSmsCapable = capabilities.isCapable(
                                 MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_SMS);
                     }
                 }
@@ -107,6 +116,7 @@
                 int reason) throws RemoteException {
             Rlog.d(TAG, "onSendSmsResult token=" + token + " messageRef=" + messageRef
                     + " status=" + status + " reason=" + reason);
+            mMetrics.writeOnImsServiceSmsSolicitedResponse(mPhone.getPhoneId(), status, reason);
             SmsTracker tracker = mTrackers.get(token);
             if (tracker == null) {
                 throw new IllegalArgumentException("Invalid token.");
@@ -125,6 +135,7 @@
                     sendSms(tracker);
                     break;
                 case ImsSmsImplBase.SEND_STATUS_ERROR_FALLBACK:
+                    tracker.mRetryCount += 1;
                     fallbackToPstn(token, tracker);
                     break;
                 default:
@@ -159,8 +170,7 @@
         }
 
         @Override
-        public void onSmsReceived(int token, String format, byte[] pdu)
-                throws RemoteException {
+        public void onSmsReceived(int token, String format, byte[] pdu) {
             Rlog.d(TAG, "SMS received.");
             android.telephony.SmsMessage message =
                     android.telephony.SmsMessage.createFromPdu(pdu, format);
@@ -193,6 +203,7 @@
                     Rlog.e(TAG, "Failed to acknowledgeSms(). Error: " + e.getMessage());
                 }
             }, true);
+            mMetrics.writeImsServiceNewSms(mPhone.getPhoneId(), format);
         }
     };
 
@@ -224,10 +235,54 @@
     private void setListeners() throws ImsException {
         getImsManager().addRegistrationCallback(mRegistrationCallback);
         getImsManager().addCapabilitiesCallback(mCapabilityCallback);
-        getImsManager().setSmsListener(mImsSmsListener);
+        getImsManager().setSmsListener(getSmsListener());
         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
@@ -289,8 +344,9 @@
         byte[] pdu = (byte[]) map.get(MAP_KEY_PDU);
         byte smsc[] = (byte[]) map.get(MAP_KEY_SMSC);
         boolean isRetry = tracker.mRetryCount > 0;
+        String format = getFormat();
 
-        if (SmsConstants.FORMAT_3GPP.equals(getFormat()) && tracker.mRetryCount > 0) {
+        if (SmsConstants.FORMAT_3GPP.equals(format) && tracker.mRetryCount > 0) {
             // per TS 23.040 Section 9.2.3.6:  If TP-MTI SMS-SUBMIT (0x01) type
             //   TP-RD (bit 2) is 1 for retry
             //   and TP-MR is set to previously failed sms TP-MR
@@ -306,10 +362,11 @@
             getImsManager().sendSms(
                     token,
                     tracker.mMessageRef,
-                    getFormat(),
+                    format,
                     smsc != null ? new String(smsc) : null,
                     isRetry,
                     pdu);
+            mMetrics.writeImsServiceSendSms(mPhone.getPhoneId(), format);
         } catch (ImsException e) {
             Rlog.e(TAG, "sendSms failed. Falling back to PSTN. Error: " + e.getMessage());
             fallbackToPstn(token, tracker);
@@ -330,4 +387,9 @@
     protected boolean isCdmaMo() {
         return mSmsDispatchersController.isCdmaFormat(getFormat());
     }
+
+    @VisibleForTesting
+    public IImsSmsListener getSmsListener() {
+        return mImsSmsListener;
+    }
 }
diff --git a/src/java/com/android/internal/telephony/InboundSmsHandler.java b/src/java/com/android/internal/telephony/InboundSmsHandler.java
index 195259a..5124748 100644
--- a/src/java/com/android/internal/telephony/InboundSmsHandler.java
+++ b/src/java/com/android/internal/telephony/InboundSmsHandler.java
@@ -60,6 +60,8 @@
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
+import android.util.LocalLog;
+import android.util.Pair;
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
@@ -69,6 +71,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;
@@ -96,13 +100,21 @@
  */
 public abstract class InboundSmsHandler extends StateMachine {
     protected static final boolean DBG = true;
-    private static final boolean VDBG = false; // STOPSHIP if true, logs user data
+    protected static final boolean VDBG = false; // STOPSHIP if true, logs user data
 
     /** Query projection for checking for duplicate message segments. */
-    private static final String[] PDU_PROJECTION = {
-            "pdu"
+    private static final String[] PDU_DELETED_FLAG_PROJECTION = {
+            "pdu",
+            "deleted"
     };
 
+    /** Mapping from DB COLUMN to PDU_SEQUENCE_PORT PROJECTION index */
+    private static final Map<Integer, Integer> PDU_DELETED_FLAG_PROJECTION_INDEX_MAPPING =
+            new HashMap<Integer, Integer>() {{
+            put(PDU_COLUMN, 0);
+            put(DELETED_FLAG_COLUMN, 1);
+            }};
+
     /** Query projection for combining concatenated message segments. */
     private static final String[] PDU_SEQUENCE_PORT_PROJECTION = {
             "pdu",
@@ -130,6 +142,7 @@
     public static final int ID_COLUMN = 7;
     public static final int MESSAGE_BODY_COLUMN = 8;
     public static final int DISPLAY_ADDRESS_COLUMN = 9;
+    public static final int DELETED_FLAG_COLUMN = 10;
 
     public static final String SELECT_BY_ID = "_id=?";
 
@@ -151,11 +164,11 @@
     /** Sent by {@link SmsBroadcastUndelivered} after cleaning the raw table. */
     public static final int EVENT_START_ACCEPTING_SMS = 6;
 
-    /** Update phone object */
-    private static final int EVENT_UPDATE_PHONE_OBJECT = 7;
-
     /** New SMS received as an AsyncResult. */
-    public static final int EVENT_INJECT_SMS = 8;
+    public static final int EVENT_INJECT_SMS = 7;
+
+    /** Update the sms tracker */
+    public static final int EVENT_UPDATE_TRACKER = 8;
 
     /** Wakelock release delay when returning to idle state. */
     private static final int WAKELOCK_TIMEOUT = 3000;
@@ -205,6 +218,8 @@
 
     private UserManager mUserManager;
 
+    private LocalLog mLocalLog = new LocalLog(64);
+
     IDeviceIdleController mDeviceIdleController;
 
     // Delete permanently from raw table
@@ -244,7 +259,8 @@
         mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, name);
         mWakeLock.acquire();    // wake lock released after we enter idle state
         mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
-        mDeviceIdleController = TelephonyComponentFactory.getInstance().getIDeviceIdleController();
+        mDeviceIdleController = TelephonyComponentFactory.getInstance()
+                .inject(IDeviceIdleController.class.getName()).getIDeviceIdleController();
 
         addState(mDefaultState);
         addState(mStartupState, mDefaultState);
@@ -264,13 +280,6 @@
     }
 
     /**
-     * Update the phone object when it changes.
-     */
-    public void updatePhoneObject(Phone phone) {
-        sendMessage(EVENT_UPDATE_PHONE_OBJECT, phone);
-    }
-
-    /**
      * Dispose of the WAP push object and release the wakelock.
      */
     @Override
@@ -295,10 +304,6 @@
         @Override
         public boolean processMessage(Message msg) {
             switch (msg.what) {
-                case EVENT_UPDATE_PHONE_OBJECT: {
-                    onUpdatePhoneObject((Phone) msg.obj);
-                    break;
-                }
                 default: {
                     String errorText = "processMessage: unhandled message type " + msg.what +
                         " currState=" + getCurrentState().getName();
@@ -449,6 +454,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 +480,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 +506,8 @@
      */
     private class WaitingState extends State {
 
+        private InboundSmsTracker mLastDeliveredSmsTracker;
+
         @Override
         public void enter() {
             if (DBG) log("entering Waiting state");
@@ -504,6 +519,8 @@
             // Before moving to idle state, set wakelock timeout to WAKE_LOCK_TIMEOUT milliseconds
             // to give any receivers time to take their own wake locks
             setWakeLockTimeout(WAKELOCK_TIMEOUT);
+            mPhone.getIccSmsInterfaceManager().mDispatchersController.sendEmptyMessage(
+                    SmsDispatchersController.EVENT_SMS_HANDLER_EXITING_WAITING_STATE);
         }
 
         @Override
@@ -512,10 +529,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 +552,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;
@@ -639,19 +673,6 @@
             int result, Message response);
 
     /**
-     * Called when the phone changes the default method updates mPhone
-     * mStorageMonitor and mCellBroadcastHandler.updatePhoneObject.
-     * Override if different or other behavior is desired.
-     *
-     * @param phone
-     */
-    protected void onUpdatePhoneObject(Phone phone) {
-        mPhone = phone;
-        mStorageMonitor = mPhone.mSmsStorageMonitor;
-        log("onUpdatePhoneObject: phone=" + mPhone.getClass().getSimpleName());
-    }
-
-    /**
      * Notify interested apps if the framework has rejected an incoming SMS,
      * and send an acknowledge message to the network.
      * @param success indicates that last message was successfully received.
@@ -699,8 +720,9 @@
                 destPort = smsHeader.portAddrs.destPort;
                 if (DBG) log("destination port: " + destPort);
             }
-
-            tracker = TelephonyComponentFactory.getInstance().makeInboundSmsTracker(sms.getPdu(),
+            tracker = TelephonyComponentFactory.getInstance()
+                    .inject(InboundSmsTracker.class.getName())
+                    .makeInboundSmsTracker(sms.getPdu(),
                     sms.getTimestampMillis(), destPort, is3gpp2(), false,
                     sms.getOriginatingAddress(), sms.getDisplayOriginatingAddress(),
                     sms.getMessageBody());
@@ -709,8 +731,9 @@
             SmsHeader.ConcatRef concatRef = smsHeader.concatRef;
             SmsHeader.PortAddrs portAddrs = smsHeader.portAddrs;
             int destPort = (portAddrs != null ? portAddrs.destPort : -1);
-
-            tracker = TelephonyComponentFactory.getInstance().makeInboundSmsTracker(sms.getPdu(),
+            tracker = TelephonyComponentFactory.getInstance()
+                    .inject(InboundSmsTracker.class.getName())
+                    .makeInboundSmsTracker(sms.getPdu(),
                     sms.getTimestampMillis(), destPort, is3gpp2(), sms.getOriginatingAddress(),
                     sms.getDisplayOriginatingAddress(), concatRef.refNumber, concatRef.seqNumber,
                     concatRef.msgCount, false, sms.getMessageBody());
@@ -758,6 +781,7 @@
         byte[][] pdus;
         int destPort = tracker.getDestPort();
         boolean block = false;
+        String address = tracker.getAddress();
 
         // Do not process when the message count is invalid.
         if (messageCount <= 0) {
@@ -775,7 +799,6 @@
             Cursor cursor = null;
             try {
                 // used by several query selection arguments
-                String address = tracker.getAddress();
                 String refNumber = Integer.toString(tracker.getReferenceNumber());
                 String count = Integer.toString(tracker.getMessageCount());
 
@@ -853,8 +876,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;
         }
 
@@ -880,7 +905,8 @@
                 }
                 output.write(pdu, 0, pdu.length);
             }
-            int result = mWapPush.dispatchWapPdu(output.toByteArray(), resultReceiver, this);
+            int result = mWapPush.dispatchWapPdu(output.toByteArray(), resultReceiver,
+                    this, address);
             if (DBG) log("dispatchWapPdu() returned " + result);
             // result is Activity.RESULT_OK if an ordered broadcast was sent
             if (result == Activity.RESULT_OK) {
@@ -1172,55 +1198,47 @@
     }
 
     /**
-     * Function to check if message should be dropped because same message has already been
-     * received. In certain cases it checks for similar messages instead of exact same (cases where
-     * keeping both messages in db can cause ambiguity)
-     * @return true if duplicate exists, false otherwise
+     * Function to detect and handle duplicate messages. If the received message should replace an
+     * existing message in the raw db, this function deletes the existing message. If an existing
+     * message takes priority (for eg, existing message has already been broadcast), then this new
+     * message should be dropped.
+     * @return true if the message represented by the passed in tracker should be dropped,
+     * false otherwise
      */
-    private boolean duplicateExists(InboundSmsTracker tracker) throws SQLException {
-        String address = tracker.getAddress();
-        // convert to strings for query
-        String refNumber = Integer.toString(tracker.getReferenceNumber());
-        String count = Integer.toString(tracker.getMessageCount());
-        // sequence numbers are 1-based except for CDMA WAP, which is 0-based
-        int sequence = tracker.getSequenceNumber();
-        String seqNumber = Integer.toString(sequence);
-        String date = Long.toString(tracker.getTimestamp());
-        String messageBody = tracker.getMessageBody();
-        String where;
-        if (tracker.getMessageCount() == 1) {
-            where = "address=? AND reference_number=? AND count=? AND sequence=? AND " +
-                    "date=? AND message_body=?";
-        } else {
-            // for multi-part messages, deduping should also be done against undeleted
-            // segments that can cause ambiguity when contacenating the segments, that is,
-            // segments with same address, reference_number, count, sequence and message type.
-            where = tracker.getQueryForMultiPartDuplicates();
-        }
+    private boolean checkAndHandleDuplicate(InboundSmsTracker tracker) throws SQLException {
+        Pair<String, String[]> exactMatchQuery = tracker.getExactMatchDupDetectQuery();
 
         Cursor cursor = null;
         try {
             // Check for duplicate message segments
-            cursor = mResolver.query(sRawUri, PDU_PROJECTION, where,
-                    new String[]{address, refNumber, count, seqNumber, date, messageBody},
-                    null);
+            cursor = mResolver.query(sRawUri, PDU_DELETED_FLAG_PROJECTION, exactMatchQuery.first,
+                    exactMatchQuery.second, null);
 
             // moveToNext() returns false if no duplicates were found
             if (cursor != null && cursor.moveToNext()) {
-                loge("Discarding duplicate message segment, refNumber=" + refNumber
-                        + " seqNumber=" + seqNumber + " count=" + count);
-                if (VDBG) {
-                    loge("address=" + address + " date=" + date + " messageBody=" +
-                            messageBody);
+                if (cursor.getCount() != 1) {
+                    loge("Exact match query returned " + cursor.getCount() + " rows");
                 }
-                String oldPduString = cursor.getString(PDU_COLUMN);
-                byte[] pdu = tracker.getPdu();
-                byte[] oldPdu = HexDump.hexStringToByteArray(oldPduString);
-                if (!Arrays.equals(oldPdu, tracker.getPdu())) {
-                    loge("Warning: dup message segment PDU of length " + pdu.length
-                            + " is different from existing PDU of length " + oldPdu.length);
+
+                // if the exact matching row is marked deleted, that means this message has already
+                // been received and processed, and can be discarded as dup
+                if (cursor.getInt(
+                        PDU_DELETED_FLAG_PROJECTION_INDEX_MAPPING.get(DELETED_FLAG_COLUMN)) == 1) {
+                    loge("Discarding duplicate message segment: " + tracker);
+                    logDupPduMismatch(cursor, tracker);
+                    return true;   // reject message
+                } else {
+                    // exact match duplicate is not marked deleted. If it is a multi-part segment,
+                    // the code below for inexact match will take care of it. If it is a single
+                    // part message, handle it here.
+                    if (tracker.getMessageCount() == 1) {
+                        // delete the old message segment permanently
+                        deleteFromRawTable(exactMatchQuery.first, exactMatchQuery.second,
+                                DELETE_PERMANENTLY);
+                        loge("Replacing duplicate message: " + tracker);
+                        logDupPduMismatch(cursor, tracker);
+                    }
                 }
-                return true;   // reject message
             }
         } finally {
             if (cursor != null) {
@@ -1228,9 +1246,49 @@
             }
         }
 
+        // The code above does an exact match. Multi-part message segments need an additional check
+        // on top of that: if there is a message segment that conflicts this new one (may not be an
+        // exact match), replace the old message segment with this one.
+        if (tracker.getMessageCount() > 1) {
+            Pair<String, String[]> inexactMatchQuery = tracker.getInexactMatchDupDetectQuery();
+            cursor = null;
+            try {
+                // Check for duplicate message segments
+                cursor = mResolver.query(sRawUri, PDU_DELETED_FLAG_PROJECTION,
+                        inexactMatchQuery.first, inexactMatchQuery.second, null);
+
+                // moveToNext() returns false if no duplicates were found
+                if (cursor != null && cursor.moveToNext()) {
+                    if (cursor.getCount() != 1) {
+                        loge("Inexact match query returned " + cursor.getCount() + " rows");
+                    }
+                    // delete the old message segment permanently
+                    deleteFromRawTable(inexactMatchQuery.first, inexactMatchQuery.second,
+                            DELETE_PERMANENTLY);
+                    loge("Replacing duplicate message segment: " + tracker);
+                    logDupPduMismatch(cursor, tracker);
+                }
+            } finally {
+                if (cursor != null) {
+                    cursor.close();
+                }
+            }
+        }
+
         return false;
     }
 
+    private void logDupPduMismatch(Cursor cursor, InboundSmsTracker tracker) {
+        String oldPduString = cursor.getString(
+                PDU_DELETED_FLAG_PROJECTION_INDEX_MAPPING.get(PDU_COLUMN));
+        byte[] pdu = tracker.getPdu();
+        byte[] oldPdu = HexDump.hexStringToByteArray(oldPduString);
+        if (!Arrays.equals(oldPdu, tracker.getPdu())) {
+            loge("Warning: dup message PDU of length " + pdu.length
+                    + " is different from existing PDU of length " + oldPdu.length);
+        }
+    }
+
     /**
      * Insert a message PDU into the raw table so we can acknowledge it immediately.
      * If the device crashes before the broadcast to listeners completes, it will be delivered
@@ -1244,7 +1302,7 @@
     private int addTrackerToRawTable(InboundSmsTracker tracker, boolean deDup) {
         if (deDup) {
             try {
-                if (duplicateExists(tracker)) {
+                if (checkAndHandleDuplicate(tracker)) {
                     return Intents.RESULT_SMS_DUPLICATED;   // reject message
                 }
             } catch (SQLException e) {
@@ -1478,14 +1536,10 @@
             loge("Failed to parse SMS pdu");
             return null;
         }
-        // Sometimes, SmsMessage.mWrappedSmsMessage is null causing NPE when we access
-        // the methods on it although the SmsMessage itself is not null. So do this check
-        // before we do anything on the parsed SmsMessages.
+        // Sometimes, SmsMessage is null if it can’t be parsed correctly.
         for (final SmsMessage sms : messages) {
-            try {
-                sms.getDisplayMessageBody();
-            } catch (NullPointerException e) {
-                loge("NPE inside SmsMessage");
+            if (sms == null) {
+                loge("Can’t write null SmsMessage");
                 return null;
             }
         }
@@ -1547,6 +1601,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/InboundSmsTracker.java b/src/java/com/android/internal/telephony/InboundSmsTracker.java
index 36c6996..50c84c8 100644
--- a/src/java/com/android/internal/telephony/InboundSmsTracker.java
+++ b/src/java/com/android/internal/telephony/InboundSmsTracker.java
@@ -18,6 +18,7 @@
 
 import android.content.ContentValues;
 import android.database.Cursor;
+import android.util.Pair;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.HexDump;
@@ -88,19 +89,6 @@
             + "AND count=? AND (destination_port & "
             + DEST_PORT_FLAG_3GPP2_WAP_PDU + "=" + DEST_PORT_FLAG_3GPP2_WAP_PDU + ") AND deleted=0";
 
-    @VisibleForTesting
-    public static final String SELECT_BY_DUPLICATE_REFERENCE = "address=? AND "
-            + "reference_number=? AND count=? AND sequence=? AND "
-            + "((date=? AND message_body=?) OR deleted=0) AND (destination_port & "
-            + DEST_PORT_FLAG_3GPP2_WAP_PDU + "=0)";
-
-    @VisibleForTesting
-    public static final String SELECT_BY_DUPLICATE_REFERENCE_3GPP2WAP = "address=? AND "
-            + "reference_number=? " + "AND count=? AND sequence=? AND "
-            + "((date=? AND message_body=?) OR deleted=0) AND "
-            + "(destination_port & " + DEST_PORT_FLAG_3GPP2_WAP_PDU + "="
-            + DEST_PORT_FLAG_3GPP2_WAP_PDU + ")";
-
     /**
      * Create a tracker for a single-part SMS.
      *
@@ -284,13 +272,15 @@
         builder.append(new Date(mTimestamp));
         builder.append(" destPort=").append(mDestPort);
         builder.append(" is3gpp2=").append(mIs3gpp2);
-        if (mAddress != null) {
+        if (InboundSmsHandler.VDBG) {
             builder.append(" address=").append(mAddress);
-            builder.append(" display_originating_addr=").append(mDisplayAddress);
-            builder.append(" refNumber=").append(mReferenceNumber);
-            builder.append(" seqNumber=").append(mSequenceNumber);
-            builder.append(" msgCount=").append(mMessageCount);
+            builder.append(" timestamp=").append(mTimestamp);
+            builder.append(" messageBody=").append(mMessageBody);
         }
+        builder.append(" display_originating_addr=").append(mDisplayAddress);
+        builder.append(" refNumber=").append(mReferenceNumber);
+        builder.append(" seqNumber=").append(mSequenceNumber);
+        builder.append(" msgCount=").append(mMessageCount);
         if (mDeleteWhere != null) {
             builder.append(" deleteWhere(").append(mDeleteWhere);
             builder.append(") deleteArgs=(").append(Arrays.toString(mDeleteWhereArgs));
@@ -324,9 +314,62 @@
         return mIs3gpp2WapPdu ? SELECT_BY_REFERENCE_3GPP2WAP : SELECT_BY_REFERENCE;
     }
 
-    public String getQueryForMultiPartDuplicates() {
-        return mIs3gpp2WapPdu ? SELECT_BY_DUPLICATE_REFERENCE_3GPP2WAP :
-                SELECT_BY_DUPLICATE_REFERENCE;
+    /**
+     * Get the query to find the exact same message/message segment in the db.
+     * @return Pair with where as Pair.first and whereArgs as Pair.second
+     */
+    public Pair<String, String[]> getExactMatchDupDetectQuery() {
+        // convert to strings for query
+        String address = getAddress();
+        String refNumber = Integer.toString(getReferenceNumber());
+        String count = Integer.toString(getMessageCount());
+        String seqNumber = Integer.toString(getSequenceNumber());
+        String date = Long.toString(getTimestamp());
+        String messageBody = getMessageBody();
+
+        String where = "address=? AND reference_number=? AND count=? AND sequence=? AND "
+                + "date=? AND message_body=?";
+        where = addDestPortQuery(where);
+        String[] whereArgs = new String[]{address, refNumber, count, seqNumber, date, messageBody};
+
+        return new Pair<>(where, whereArgs);
+    }
+
+    /**
+     * The key differences here compared to exact match are:
+     * - this is applicable only for multi-part message segments
+     * - this does not match date or message_body
+     * - this matches deleted=0 (undeleted segments)
+     * The only difference as compared to getQueryForSegments() is that this checks for sequence as
+     * well.
+     * @return Pair with where as Pair.first and whereArgs as Pair.second
+     */
+    public Pair<String, String[]> getInexactMatchDupDetectQuery() {
+        if (getMessageCount() == 1) return null;
+
+        // convert to strings for query
+        String address = getAddress();
+        String refNumber = Integer.toString(getReferenceNumber());
+        String count = Integer.toString(getMessageCount());
+        String seqNumber = Integer.toString(getSequenceNumber());
+
+        String where = "address=? AND reference_number=? AND count=? AND sequence=? AND "
+                + "deleted=0";
+        where = addDestPortQuery(where);
+        String[] whereArgs = new String[]{address, refNumber, count, seqNumber};
+
+        return new Pair<>(where, whereArgs);
+    }
+
+    private String addDestPortQuery(String where) {
+        String whereDestPort;
+        if (mIs3gpp2WapPdu) {
+            whereDestPort = "destination_port & " + DEST_PORT_FLAG_3GPP2_WAP_PDU + "="
+                + DEST_PORT_FLAG_3GPP2_WAP_PDU;
+        } else {
+            whereDestPort = "destination_port & " + DEST_PORT_FLAG_3GPP2_WAP_PDU + "=0";
+        }
+        return where + " AND (" + whereDestPort + ")";
     }
 
     /**
diff --git a/src/java/com/android/internal/telephony/LocaleTracker.java b/src/java/com/android/internal/telephony/LocaleTracker.java
old mode 100644
new mode 100755
index 996e288..d582762
--- a/src/java/com/android/internal/telephony/LocaleTracker.java
+++ b/src/java/com/android/internal/telephony/LocaleTracker.java
@@ -40,7 +40,7 @@
 import android.text.TextUtils;
 import android.util.LocalLog;
 
-import com.android.internal.util.CollectionUtils;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.IndentingPrintWriter;
 
 import java.io.FileDescriptor;
@@ -58,13 +58,19 @@
     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;
+    private static final int EVENT_REQUEST_CELL_INFO = 1;
 
     /** Event for service state changed */
-    private static final int EVENT_SERVICE_STATE_CHANGED        = 3;
+    private static final int EVENT_SERVICE_STATE_CHANGED = 2;
+
+    /** Event for sim state changed */
+    private static final int EVENT_SIM_STATE_CHANGED = 3;
+
+    /** Event for incoming unsolicited cell info */
+    private static final int EVENT_UNSOL_CELL_INFO = 4;
+
+    /** Event for incoming cell info */
+    private static final int EVENT_RESPONSE_CELL_INFO = 5;
 
     // Todo: Read this from Settings.
     /** The minimum delay to get cell info from the modem */
@@ -78,8 +84,13 @@
     /** The delay for periodically getting cell info from the modem */
     private static final long CELL_INFO_PERIODIC_POLLING_DELAY_MS = 10 * MINUTE_IN_MILLIS;
 
+    /** The maximum fail count to prevent delay time overflow */
+    private static final int MAX_FAIL_COUNT = 30;
+
     private final Phone mPhone;
 
+    private final NitzStateMachine mNitzStateMachine;
+
     /** SIM card state. Must be one of TelephonyManager.SIM_STATE_XXX */
     private int mSimState;
 
@@ -99,7 +110,9 @@
     private String mCurrentCountryIso;
 
     /** Current service state. Must be one of ServiceState.STATE_XXX. */
-    private int mLastServiceState = -1;
+    private int mLastServiceState = ServiceState.STATE_POWER_OFF;
+
+    private boolean mIsTracking = false;
 
     private final LocalLog mLocalLog = new LocalLog(50);
 
@@ -110,8 +123,9 @@
             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));
+                    obtainMessage(EVENT_SIM_STATE_CHANGED,
+                            intent.getIntExtra(TelephonyManager.EXTRA_SIM_STATE,
+                                    TelephonyManager.SIM_STATE_UNKNOWN), 0).sendToTarget();
                 }
             }
         }
@@ -125,19 +139,32 @@
     @Override
     public void handleMessage(Message msg) {
         switch (msg.what) {
-            case EVENT_GET_CELL_INFO:
-                synchronized (this) {
-                    getCellInfo();
-                    updateLocale();
-                }
+            case EVENT_REQUEST_CELL_INFO:
+                mPhone.requestCellInfoUpdate(null, obtainMessage(EVENT_RESPONSE_CELL_INFO));
                 break;
-            case EVENT_UPDATE_OPERATOR_NUMERIC:
-                updateOperatorNumericSync((String) msg.obj);
+
+            case EVENT_UNSOL_CELL_INFO:
+                processCellInfo((AsyncResult) msg.obj);
+                // If the unsol happened to be useful, use it; otherwise, pretend it didn't happen.
+                if (mCellInfo != null && mCellInfo.size() > 0) requestNextCellInfo(true);
                 break;
+
+            case EVENT_RESPONSE_CELL_INFO:
+                processCellInfo((AsyncResult) msg.obj);
+                // If the cellInfo was non-empty then it's business as usual. Either way, this
+                // cell info was requested by us, so it's our trigger to schedule another one.
+                requestNextCellInfo(mCellInfo != null && mCellInfo.size() > 0);
+                break;
+
             case EVENT_SERVICE_STATE_CHANGED:
                 AsyncResult ar = (AsyncResult) msg.obj;
                 onServiceStateChanged((ServiceState) ar.result);
                 break;
+
+            case EVENT_SIM_STATE_CHANGED:
+                onSimCardStateChanged(msg.arg1);
+                break;
+
             default:
                 throw new IllegalStateException("Unexpected message arrives. msg = " + msg.what);
         }
@@ -147,11 +174,13 @@
      * Constructor
      *
      * @param phone The phone object
+     * @param nitzStateMachine NITZ state machine
      * @param looper The looper message handler
      */
-    public LocaleTracker(Phone phone, Looper looper)  {
+    public LocaleTracker(Phone phone, NitzStateMachine nitzStateMachine, Looper looper)  {
         super(looper);
         mPhone = phone;
+        mNitzStateMachine = nitzStateMachine;
         mSimState = TelephonyManager.SIM_STATE_UNKNOWN;
 
         final IntentFilter filter = new IntentFilter();
@@ -159,6 +188,7 @@
         mPhone.getContext().registerReceiver(mBroadcastReceiver, filter);
 
         mPhone.registerForServiceStateChanged(this, EVENT_SERVICE_STATE_CHANGED, null);
+        mPhone.registerForCellInfo(this, EVENT_UNSOL_CELL_INFO, null);
     }
 
     /**
@@ -167,7 +197,7 @@
      * @return The device's current country. Empty string if the information is not available.
      */
     @NonNull
-    public synchronized String getCurrentCountry() {
+    public String getCurrentCountry() {
         return (mCurrentCountryIso != null) ? mCurrentCountryIso : "";
     }
 
@@ -217,12 +247,9 @@
      * @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;
+        updateLocale();
+        updateTrackingStatus();
     }
 
     /**
@@ -231,71 +258,57 @@
      * @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;
-        }
+        mLastServiceState = serviceState.getState();
+        updateLocale();
+        updateTrackingStatus();
     }
 
     /**
-     * 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.
+     * Update MCC/MNC from network service state.
      *
      * @param operatorNumeric MCC/MNC of the operator
      */
-    public synchronized void updateOperatorNumericSync(String operatorNumeric) {
+    public void updateOperatorNumeric(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));
+    private void processCellInfo(AsyncResult ar) {
+        if (ar == null || ar.exception != null) {
+            mCellInfo = null;
+            return;
+        }
+        mCellInfo = (List<CellInfo>) ar.result;
+        String msg = "getCellInfo: cell info=" + mCellInfo;
+        if (DBG) log(msg);
+        mLocalLog.log(msg);
+        updateLocale();
+    }
+
+    private void requestNextCellInfo(boolean succeeded) {
+        if (!mIsTracking) return;
+
+        removeMessages(EVENT_REQUEST_CELL_INFO);
+        if (succeeded) {
+            resetCellInfoRetry();
+            // 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.
+            removeMessages(EVENT_UNSOL_CELL_INFO);
+            removeMessages(EVENT_RESPONSE_CELL_INFO);
+            sendMessageDelayed(obtainMessage(EVENT_REQUEST_CELL_INFO),
+                    CELL_INFO_PERIODIC_POLLING_DELAY_MS);
+        } else {
+            // 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.");
+            sendMessageDelayed(obtainMessage(EVENT_REQUEST_CELL_INFO), delay);
+        }
     }
 
     /**
@@ -305,75 +318,68 @@
      * @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;
+    @VisibleForTesting
+    public static long getCellInfoDelayTime(int failCount) {
+        // Exponentially grow the delay time. Note we limit the fail count to MAX_FAIL_COUNT to
+        // prevent overflow in Math.pow().
+        long delay = CELL_INFO_MIN_DELAY_MS
+                * (long) Math.pow(2, Math.min(failCount, MAX_FAIL_COUNT) - 1);
+        return Math.min(Math.max(delay, CELL_INFO_MIN_DELAY_MS), CELL_INFO_MAX_DELAY_MS);
     }
 
     /**
      * Stop retrying getting cell info from the modem. It cancels any scheduled cell info retrieving
      * request.
      */
-    private void stopCellInfoRetry() {
+    private void resetCellInfoRetry() {
         mFailCellInfoCount = 0;
-        removeMessages(EVENT_GET_CELL_INFO);
+        removeMessages(EVENT_REQUEST_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;
+    private void updateTrackingStatus() {
+        boolean shouldTrackLocale =
+                (mSimState == TelephonyManager.SIM_STATE_ABSENT
+                        || TextUtils.isEmpty(mOperatorNumeric))
+                && (mLastServiceState == ServiceState.STATE_OUT_OF_SERVICE
+                        || mLastServiceState == ServiceState.STATE_EMERGENCY_ONLY);
+        if (shouldTrackLocale) {
+            startTracking();
+        } else {
+            stopTracking();
         }
+    }
 
-        // 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;
+    private void stopTracking() {
+        if (!mIsTracking) return;
+        mIsTracking = false;
+        String msg = "Stopping LocaleTracker";
         if (DBG) log(msg);
         mLocalLog.log(msg);
-        if (CollectionUtils.isEmpty(mCellInfo)) {
-            // 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();
+        mCellInfo = null;
+        resetCellInfoRetry();
+    }
 
-            // 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);
-        }
+    private void startTracking() {
+        if (mIsTracking) return;
+        String msg = "Starting LocaleTracker";
+        mLocalLog.log(msg);
+        if (DBG) log(msg);
+        mIsTracking = true;
+        sendMessage(obtainMessage(EVENT_REQUEST_CELL_INFO));
     }
 
     /**
      * Update the device's current locale
      */
-    private void updateLocale() {
+    private synchronized 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(Integer.parseInt(mcc));
-            } catch (StringIndexOutOfBoundsException | NumberFormatException ex) {
+                countryIso = MccTable.countryCodeForMcc(mcc);
+            } catch (StringIndexOutOfBoundsException ex) {
                 loge("updateLocale: Can't get country from operator numeric. mcc = "
                         + mcc + ". ex=" + ex);
             }
@@ -383,19 +389,13 @@
         // info.
         if (TextUtils.isEmpty(countryIso)) {
             mcc = getMccFromCellInfo();
-            if (!TextUtils.isEmpty(mcc)) {
-                try {
-                    countryIso = MccTable.countryCodeForMcc(Integer.parseInt(mcc));
-                } catch (NumberFormatException ex) {
-                    loge("updateLocale: Can't get country from cell info. mcc = "
-                            + mcc + ". ex=" + ex);
-                }
-            }
+            countryIso = MccTable.countryCodeForMcc(mcc);
         }
 
         String msg = "updateLocale: mcc = " + mcc + ", country = " + countryIso;
         log(msg);
         mLocalLog.log(msg);
+        boolean countryChanged = false;
         if (!Objects.equals(countryIso, mCurrentCountryIso)) {
             msg = "updateLocale: Change the current country to " + countryIso;
             log(msg);
@@ -410,7 +410,19 @@
             // broadcast on forbidden channels.
             ((WifiManager) mPhone.getContext().getSystemService(Context.WIFI_SERVICE))
                     .setCountryCode(countryIso);
+            countryChanged = true;
         }
+
+        if (TextUtils.isEmpty(countryIso)) {
+            mNitzStateMachine.handleNetworkCountryCodeUnavailable();
+        } else {
+            mNitzStateMachine.handleNetworkCountryCodeSet(countryChanged);
+        }
+    }
+
+    /** Exposed for testing purposes */
+    public boolean isTracking() {
+        return mIsTracking;
     }
 
     private void log(String msg) {
@@ -432,6 +444,7 @@
         final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
         pw.println("LocaleTracker:");
         ipw.increaseIndent();
+        ipw.println("mIsTracking = " + mIsTracking);
         ipw.println("mOperatorNumeric = " + mOperatorNumeric);
         ipw.println("mSimState = " + mSimState);
         ipw.println("mCellInfo = " + mCellInfo);
diff --git a/src/java/com/android/internal/telephony/MccTable.java b/src/java/com/android/internal/telephony/MccTable.java
index a010618..b1cfb7b 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;
 
@@ -31,7 +29,7 @@
 import com.android.internal.app.LocaleStore.LocaleInfo;
 
 import libcore.icu.ICU;
-import libcore.util.TimeZoneFinder;
+import libcore.timezone.TimeZoneFinder;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -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);
-    }
-
     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 ae7ede0..04d09a5 100644
--- a/src/java/com/android/internal/telephony/NetworkRegistrationManager.java
+++ b/src/java/com/android/internal/telephony/NetworkRegistrationManager.java
@@ -127,7 +127,8 @@
     private class NetworkServiceConnection implements ServiceConnection {
         @Override
         public void onServiceConnected(ComponentName name, IBinder service) {
-            logd("service connected.");
+            logd("service " + name + " for transport "
+                    + TransportType.toString(mTransportType) + " is now connected.");
             mServiceBinder = (INetworkService.Stub) service;
             mDeathRecipient = new RegManagerDeathRecipient(name);
             try {
@@ -144,7 +145,8 @@
 
         @Override
         public void onServiceDisconnected(ComponentName name) {
-            logd("onServiceDisconnected " + name);
+            logd("service " + name + " for transport "
+                    + TransportType.toString(mTransportType) + " is now disconnected.");
             if (mServiceBinder != null) {
                 mServiceBinder.unlinkToDeath(mDeathRecipient, 0);
             }
@@ -180,6 +182,8 @@
         try {
             // We bind this as a foreground service because it is operating directly on the SIM,
             // and we do not want it subjected to power-savings restrictions while doing so.
+            logd("Trying to bind " + getPackageName() + " for transport "
+                    + TransportType.toString(mTransportType));
             return mPhone.getContext().bindService(intent, new NetworkServiceConnection(),
                     Context.BIND_AUTO_CREATE);
         } catch (SecurityException e) {
@@ -219,9 +223,6 @@
             packageName = b.getString(carrierConfig, packageName);
         }
 
-        logd("Binding to packageName " + packageName + " for transport type"
-                + mTransportType);
-
         return packageName;
     }
 
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..74d9862
--- /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 #handleNetworkCountryCodeUnavailable()}. The flag can be used when historic NITZ
+     * data may no longer be valid. {@code false} indicates it is reasonable to try to set the time
+     * zone using less reliable algorithms than NITZ-based detection such as by just using network
+     * country code.
+     */
+    private boolean mNitzTimeZoneDetectionSuccessful = false;
+
+    // Miscellaneous dependencies and helpers not related to detection state.
+    private final LocalLog mTimeLog = new LocalLog(15);
+    private final LocalLog mTimeZoneLog = new LocalLog(15);
+    private final GsmCdmaPhone mPhone;
+    private final DeviceState mDeviceState;
+    private final 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 handleNetworkCountryCodeUnavailable() {
+        if (DBG) {
+            Rlog.d(LOG_TAG, "handleNetworkCountryCodeUnavailable");
+        }
+
+        mGotCountryCode = false;
+        mNitzTimeZoneDetectionSuccessful = false;
+    }
+
+    @Override
+    public void handleNitzReceived(TimestampedValue<NitzData> nitzSignal) {
+        // Always store the latest NITZ signal received.
+        mLatestNitzSignal = nitzSignal;
+
+        updateTimeZoneFromCountryAndNitz();
+        updateTimeFromNitz();
+    }
+
+    private void updateTimeFromNitz() {
+        TimestampedValue<NitzData> nitzSignal = mLatestNitzSignal;
+        try {
+            boolean ignoreNitz = mDeviceState.getIgnoreNitz();
+            if (ignoreNitz) {
+                if (DBG) {
+                    Rlog.d(LOG_TAG, "updateTimeFromNitz: Not 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/NitzData.java b/src/java/com/android/internal/telephony/NitzData.java
index 80f1c4a..6c310f6 100644
--- a/src/java/com/android/internal/telephony/NitzData.java
+++ b/src/java/com/android/internal/telephony/NitzData.java
@@ -35,6 +35,7 @@
 public final class NitzData {
     private static final String LOG_TAG = ServiceStateTracker.LOG_TAG;
     private static final int MS_PER_QUARTER_HOUR = 15 * 60 * 1000;
+    private static final int MS_PER_HOUR = 60 * 60 * 1000;
 
     /* Time stamp after 19 January 2038 is not supported under 32 bit */
     private static final int MAX_NITZ_YEAR = 2037;
@@ -111,11 +112,11 @@
 
             // DST correction is already applied to the UTC offset. We could subtract it if we
             // wanted the raw offset.
-            Integer dstAdjustmentQuarterHours =
+            Integer dstAdjustmentHours =
                     (nitzSubs.length >= 8) ? Integer.parseInt(nitzSubs[7]) : null;
             Integer dstAdjustmentMillis = null;
-            if (dstAdjustmentQuarterHours != null) {
-                dstAdjustmentMillis = dstAdjustmentQuarterHours * MS_PER_QUARTER_HOUR;
+            if (dstAdjustmentHours != null) {
+                dstAdjustmentMillis = dstAdjustmentHours * MS_PER_HOUR;
             }
 
             // As a special extension, the Android emulator appends the name of
diff --git a/src/java/com/android/internal/telephony/NitzStateMachine.java b/src/java/com/android/internal/telephony/NitzStateMachine.java
index 1a365ee..9b91573 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,73 @@
 
 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 country code from network has become
+     * unavailable.
+     */
+    void handleNetworkCountryCodeUnavailable();
+
+    /**
+     * 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 +137,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/OldNitzStateMachine.java b/src/java/com/android/internal/telephony/OldNitzStateMachine.java
new file mode 100644
index 0000000..916dc65
--- /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 #handleNetworkCountryCodeUnavailable()}. The flag can be used when historic NITZ
+     * data may no longer be valid. {@code false} indicates it is reasonable to try to set the time
+     * zone using less reliable algorithms than NITZ-based detection such as by just using network
+     * country code.
+     */
+    private boolean mNitzTimeZoneDetectionSuccessful = false;
+
+    // Miscellaneous dependencies and helpers not related to detection state.
+    private final LocalLog mTimeLog = new LocalLog(15);
+    private final LocalLog mTimeZoneLog = new LocalLog(15);
+    private final GsmCdmaPhone mPhone;
+    private final DeviceState mDeviceState;
+    private final OldTimeServiceHelper mTimeServiceHelper;
+    private final TimeZoneLookupHelper mTimeZoneLookupHelper;
+    /** Wake lock used while setting time of day. */
+    private final PowerManager.WakeLock mWakeLock;
+    private static final String WAKELOCK_TAG = "NitzStateMachine";
+
+    public OldNitzStateMachine(GsmCdmaPhone phone) {
+        this(phone,
+                new OldTimeServiceHelper(phone.getContext()),
+                new DeviceState(phone),
+                new TimeZoneLookupHelper());
+    }
+
+    @VisibleForTesting
+    public OldNitzStateMachine(GsmCdmaPhone phone, OldTimeServiceHelper timeServiceHelper,
+            DeviceState deviceState, TimeZoneLookupHelper timeZoneLookupHelper) {
+        mPhone = phone;
+
+        Context context = phone.getContext();
+        PowerManager powerManager =
+                (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+        mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG);
+
+        mDeviceState = deviceState;
+        mTimeZoneLookupHelper = timeZoneLookupHelper;
+        mTimeServiceHelper = timeServiceHelper;
+        mTimeServiceHelper.setListener(new OldTimeServiceHelper.Listener() {
+            @Override
+            public void onTimeDetectionChange(boolean enabled) {
+                if (enabled) {
+                    handleAutoTimeEnabled();
+                }
+            }
+
+            @Override
+            public void onTimeZoneDetectionChange(boolean enabled) {
+                if (enabled) {
+                    handleAutoTimeZoneEnabled();
+                }
+            }
+        });
+    }
+
+    @Override
+    public void handleNetworkCountryCodeSet(boolean countryChanged) {
+        boolean hadCountryCode = mGotCountryCode;
+        mGotCountryCode = true;
+
+        String isoCountryCode = mDeviceState.getNetworkCountryIsoForPhone();
+        if (!TextUtils.isEmpty(isoCountryCode) && !mNitzTimeZoneDetectionSuccessful) {
+            updateTimeZoneFromNetworkCountryCode(isoCountryCode);
+        }
+
+        if (mLatestNitzSignal != null && (countryChanged || !hadCountryCode)) {
+            updateTimeZoneFromCountryAndNitz();
+        }
+    }
+
+    private void updateTimeZoneFromCountryAndNitz() {
+        String isoCountryCode = mDeviceState.getNetworkCountryIsoForPhone();
+        TimestampedValue<NitzData> nitzSignal = mLatestNitzSignal;
+
+        // TimeZone.getDefault() returns a default zone (GMT) even when time zone have never
+        // been set which makes it difficult to tell if it's what the user / time zone detection
+        // has chosen. isTimeZoneSettingInitialized() tells us whether the time zone of the
+        // device has ever been explicit set by the user or code.
+        final boolean isTimeZoneSettingInitialized =
+                mTimeServiceHelper.isTimeZoneSettingInitialized();
+
+        if (DBG) {
+            Rlog.d(LOG_TAG, "updateTimeZoneFromCountryAndNitz:"
+                    + " isTimeZoneSettingInitialized=" + isTimeZoneSettingInitialized
+                    + " nitzSignal=" + nitzSignal
+                    + " isoCountryCode=" + isoCountryCode);
+        }
+
+        try {
+            NitzData nitzData = nitzSignal.getValue();
+
+            String zoneId;
+            if (nitzData.getEmulatorHostTimeZone() != null) {
+                zoneId = nitzData.getEmulatorHostTimeZone().getID();
+            } else if (!mGotCountryCode) {
+                // We don't have a country code so we won't try to look up the time zone.
+                zoneId = null;
+            } else if (TextUtils.isEmpty(isoCountryCode)) {
+                // We have a country code but it's empty. This is most likely because we're on a
+                // test network that's using a bogus MCC (eg, "001"). Obtain a TimeZone based only
+                // on the NITZ parameters: it's only going to be correct in a few cases but it
+                // should at least have the correct offset.
+                OffsetResult lookupResult = mTimeZoneLookupHelper.lookupByNitz(nitzData);
+                String logMsg = "updateTimeZoneFromCountryAndNitz: lookupByNitz returned"
+                        + " lookupResult=" + lookupResult;
+                if (DBG) {
+                    Rlog.d(LOG_TAG, logMsg);
+                }
+                // We log this in the time zone log because it has been a source of bugs.
+                mTimeZoneLog.log(logMsg);
+
+                zoneId = lookupResult != null ? lookupResult.zoneId : null;
+            } else if (mLatestNitzSignal == null) {
+                if (DBG) {
+                    Rlog.d(LOG_TAG,
+                            "updateTimeZoneFromCountryAndNitz: No cached NITZ data available,"
+                                    + " not setting zone");
+                }
+                zoneId = null;
+            } else if (isNitzSignalOffsetInfoBogus(nitzSignal, isoCountryCode)) {
+                String logMsg = "updateTimeZoneFromCountryAndNitz: Received NITZ looks bogus, "
+                        + " isoCountryCode=" + isoCountryCode
+                        + " nitzSignal=" + nitzSignal;
+                if (DBG) {
+                    Rlog.d(LOG_TAG, logMsg);
+                }
+                // We log this in the time zone log because it has been a source of bugs.
+                mTimeZoneLog.log(logMsg);
+
+                zoneId = null;
+            } else {
+                OffsetResult lookupResult = mTimeZoneLookupHelper.lookupByNitzCountry(
+                        nitzData, isoCountryCode);
+                if (DBG) {
+                    Rlog.d(LOG_TAG, "updateTimeZoneFromCountryAndNitz: using"
+                            + " lookupByNitzCountry(nitzData, isoCountryCode),"
+                            + " nitzData=" + nitzData
+                            + " isoCountryCode=" + isoCountryCode
+                            + " lookupResult=" + lookupResult);
+                }
+                zoneId = lookupResult != null ? lookupResult.zoneId : null;
+            }
+
+            // Log the action taken to the dedicated time zone log.
+            final String tmpLog = "updateTimeZoneFromCountryAndNitz:"
+                    + " isTimeZoneSettingInitialized=" + isTimeZoneSettingInitialized
+                    + " isoCountryCode=" + isoCountryCode
+                    + " nitzSignal=" + nitzSignal
+                    + " zoneId=" + zoneId
+                    + " isTimeZoneDetectionEnabled()="
+                    + mTimeServiceHelper.isTimeZoneDetectionEnabled();
+            mTimeZoneLog.log(tmpLog);
+
+            // Set state as needed.
+            if (zoneId != null) {
+                if (DBG) {
+                    Rlog.d(LOG_TAG, "updateTimeZoneFromCountryAndNitz: zoneId=" + zoneId);
+                }
+                if (mTimeServiceHelper.isTimeZoneDetectionEnabled()) {
+                    setAndBroadcastNetworkSetTimeZone(zoneId);
+                } else {
+                    if (DBG) {
+                        Rlog.d(LOG_TAG, "updateTimeZoneFromCountryAndNitz: skip changing zone"
+                                + " as isTimeZoneDetectionEnabled() is false");
+                    }
+                }
+                mSavedTimeZoneId = zoneId;
+                mNitzTimeZoneDetectionSuccessful = true;
+            } else {
+                if (DBG) {
+                    Rlog.d(LOG_TAG,
+                            "updateTimeZoneFromCountryAndNitz: zoneId == null, do nothing");
+                }
+            }
+        } catch (RuntimeException ex) {
+            Rlog.e(LOG_TAG, "updateTimeZoneFromCountryAndNitz: Processing NITZ data"
+                    + " nitzSignal=" + nitzSignal
+                    + " isoCountryCode=" + isoCountryCode
+                    + " isTimeZoneSettingInitialized=" + isTimeZoneSettingInitialized
+                    + " ex=" + ex);
+        }
+    }
+
+    /**
+     * Returns true if the NITZ signal is definitely bogus, assuming that the country is correct.
+     */
+    private boolean isNitzSignalOffsetInfoBogus(
+            TimestampedValue<NitzData> nitzSignal, String isoCountryCode) {
+
+        if (TextUtils.isEmpty(isoCountryCode)) {
+            // We cannot say for sure.
+            return false;
+        }
+
+        NitzData newNitzData = nitzSignal.getValue();
+        boolean zeroOffsetNitz = newNitzData.getLocalOffsetMillis() == 0 && !newNitzData.isDst();
+        return zeroOffsetNitz && !countryUsesUtc(isoCountryCode, nitzSignal);
+    }
+
+    private boolean countryUsesUtc(
+            String isoCountryCode, TimestampedValue<NitzData> nitzSignal) {
+        return mTimeZoneLookupHelper.countryUsesUtc(
+                isoCountryCode,
+                nitzSignal.getValue().getCurrentTimeInMillis());
+    }
+
+    @Override
+    public void handleNetworkAvailable() {
+        if (DBG) {
+            Rlog.d(LOG_TAG, "handleNetworkAvailable: mNitzTimeZoneDetectionSuccessful="
+                    + mNitzTimeZoneDetectionSuccessful
+                    + ", Setting mNitzTimeZoneDetectionSuccessful=false");
+        }
+        mNitzTimeZoneDetectionSuccessful = false;
+    }
+
+    @Override
+    public void handleNetworkCountryCodeUnavailable() {
+        if (DBG) {
+            Rlog.d(LOG_TAG, "handleNetworkCountryCodeUnavailable");
+        }
+
+        mGotCountryCode = false;
+        mNitzTimeZoneDetectionSuccessful = false;
+    }
+
+    @Override
+    public void handleNitzReceived(TimestampedValue<NitzData> nitzSignal) {
+        // Always store the latest NITZ signal received.
+        mLatestNitzSignal = nitzSignal;
+
+        updateTimeZoneFromCountryAndNitz();
+        updateTimeFromNitz();
+    }
+
+    private void updateTimeFromNitz() {
+        TimestampedValue<NitzData> nitzSignal = mLatestNitzSignal;
+        try {
+            boolean ignoreNitz = mDeviceState.getIgnoreNitz();
+            if (ignoreNitz) {
+                if (DBG) {
+                    Rlog.d(LOG_TAG,
+                            "updateTimeFromNitz: Not setting clock because gsm.ignore-nitz is set");
+                }
+                return;
+            }
+
+            try {
+                // Acquire the wake lock as we are reading the elapsed realtime clock and system
+                // clock.
+                mWakeLock.acquire();
+
+                // Validate the nitzTimeSignal to reject obviously bogus elapsedRealtime values.
+                long elapsedRealtime = mTimeServiceHelper.elapsedRealtime();
+                long millisSinceNitzReceived =
+                        elapsedRealtime - nitzSignal.getReferenceTimeMillis();
+                if (millisSinceNitzReceived < 0 || millisSinceNitzReceived > Integer.MAX_VALUE) {
+                    if (DBG) {
+                        Rlog.d(LOG_TAG, "updateTimeFromNitz: not setting time, unexpected"
+                                + " elapsedRealtime=" + elapsedRealtime
+                                + " nitzSignal=" + nitzSignal);
+                    }
+                    return;
+                }
+
+                // Adjust the NITZ time by the delay since it was received to get the time now.
+                long adjustedCurrentTimeMillis =
+                        nitzSignal.getValue().getCurrentTimeInMillis() + millisSinceNitzReceived;
+                long gained = adjustedCurrentTimeMillis - mTimeServiceHelper.currentTimeMillis();
+
+                if (mTimeServiceHelper.isTimeDetectionEnabled()) {
+                    String logMsg = "updateTimeFromNitz:"
+                            + " nitzSignal=" + nitzSignal
+                            + " adjustedCurrentTimeMillis=" + adjustedCurrentTimeMillis
+                            + " millisSinceNitzReceived= " + millisSinceNitzReceived
+                            + " gained=" + gained;
+
+                    if (mSavedNitzTime == null) {
+                        logMsg += ": First update received.";
+                        setAndBroadcastNetworkSetTime(logMsg, adjustedCurrentTimeMillis);
+                    } else {
+                        long elapsedRealtimeSinceLastSaved = mTimeServiceHelper.elapsedRealtime()
+                                - mSavedNitzTime.getReferenceTimeMillis();
+                        int nitzUpdateSpacing = mDeviceState.getNitzUpdateSpacingMillis();
+                        int nitzUpdateDiff = mDeviceState.getNitzUpdateDiffMillis();
+                        if (elapsedRealtimeSinceLastSaved > nitzUpdateSpacing
+                                || Math.abs(gained) > nitzUpdateDiff) {
+                            // Either it has been a while since we received an update, or the gain
+                            // is sufficiently large that we want to act on it.
+                            logMsg += ": New update received.";
+                            setAndBroadcastNetworkSetTime(logMsg, adjustedCurrentTimeMillis);
+                        } else {
+                            if (DBG) {
+                                Rlog.d(LOG_TAG, logMsg + ": Update throttled.");
+                            }
+
+                            // Return early. This means that we don't reset the
+                            // mSavedNitzTime for next time and that we may act on more
+                            // NITZ time signals overall but should end up with a system clock that
+                            // tracks NITZ more closely than if we saved throttled values (which
+                            // would reset mSavedNitzTime.elapsedRealtime used to calculate time
+                            // since the last NITZ signal was received).
+                            return;
+                        }
+                    }
+                }
+
+                // Save the last NITZ time signal used so we can return to it later
+                // if auto-time detection is toggled.
+                mSavedNitzTime = new TimestampedValue<>(
+                        nitzSignal.getReferenceTimeMillis(),
+                        nitzSignal.getValue().getCurrentTimeInMillis());
+            } finally {
+                mWakeLock.release();
+            }
+        } catch (RuntimeException ex) {
+            Rlog.e(LOG_TAG, "updateTimeFromNitz: Processing NITZ data"
+                    + " nitzSignal=" + nitzSignal
+                    + " ex=" + ex);
+        }
+    }
+
+    private void setAndBroadcastNetworkSetTimeZone(String zoneId) {
+        if (DBG) {
+            Rlog.d(LOG_TAG, "setAndBroadcastNetworkSetTimeZone: zoneId=" + zoneId);
+        }
+        mTimeServiceHelper.setDeviceTimeZone(zoneId);
+        if (DBG) {
+            Rlog.d(LOG_TAG,
+                    "setAndBroadcastNetworkSetTimeZone: called setDeviceTimeZone()"
+                            + " zoneId=" + zoneId);
+        }
+    }
+
+    private void setAndBroadcastNetworkSetTime(String msg, long time) {
+        if (!mWakeLock.isHeld()) {
+            Rlog.w(LOG_TAG, "setAndBroadcastNetworkSetTime: Wake lock not held while setting device"
+                    + " time (msg=" + msg + ")");
+        }
+
+        msg = "setAndBroadcastNetworkSetTime: [Setting time to time=" + time + "]:" + msg;
+        if (DBG) {
+            Rlog.d(LOG_TAG, msg);
+        }
+        mTimeLog.log(msg);
+        mTimeServiceHelper.setDeviceTime(time);
+        TelephonyMetrics.getInstance().writeNITZEvent(mPhone.getPhoneId(), time);
+    }
+
+    private void handleAutoTimeEnabled() {
+        if (DBG) {
+            Rlog.d(LOG_TAG, "handleAutoTimeEnabled: Reverting to NITZ Time:"
+                    + " mSavedNitzTime=" + mSavedNitzTime);
+        }
+        if (mSavedNitzTime != null) {
+            try {
+                // Acquire the wakelock as we're reading the elapsed realtime clock here.
+                mWakeLock.acquire();
+
+                long elapsedRealtime = mTimeServiceHelper.elapsedRealtime();
+                String msg = "mSavedNitzTime: Reverting to NITZ time"
+                        + " elapsedRealtime=" + elapsedRealtime
+                        + " mSavedNitzTime=" + mSavedNitzTime;
+                long adjustedCurrentTimeMillis = mSavedNitzTime.getValue()
+                        + (elapsedRealtime - mSavedNitzTime.getReferenceTimeMillis());
+                setAndBroadcastNetworkSetTime(msg, adjustedCurrentTimeMillis);
+            } finally {
+                mWakeLock.release();
+            }
+        }
+    }
+
+    private void handleAutoTimeZoneEnabled() {
+        String tmpLog = "handleAutoTimeZoneEnabled: Reverting to NITZ TimeZone:"
+                + " mSavedTimeZoneId=" + mSavedTimeZoneId;
+        if (DBG) {
+            Rlog.d(LOG_TAG, tmpLog);
+        }
+        mTimeZoneLog.log(tmpLog);
+        if (mSavedTimeZoneId != null) {
+            setAndBroadcastNetworkSetTimeZone(mSavedTimeZoneId);
+        }
+    }
+
+    @Override
+    public void dumpState(PrintWriter pw) {
+        // Time Detection State
+        pw.println(" mSavedTime=" + mSavedNitzTime);
+
+        // Time Zone Detection State
+        pw.println(" mLatestNitzSignal=" + mLatestNitzSignal);
+        pw.println(" mGotCountryCode=" + mGotCountryCode);
+        pw.println(" mSavedTimeZoneId=" + mSavedTimeZoneId);
+        pw.println(" mNitzTimeZoneDetectionSuccessful=" + mNitzTimeZoneDetectionSuccessful);
+
+        // Miscellaneous
+        pw.println(" mWakeLock=" + mWakeLock);
+        pw.flush();
+    }
+
+    @Override
+    public void dumpLogs(FileDescriptor fd, IndentingPrintWriter ipw, String[] args) {
+        ipw.println(" Time Logs:");
+        ipw.increaseIndent();
+        mTimeLog.dump(fd, ipw, args);
+        ipw.decreaseIndent();
+
+        ipw.println(" Time zone Logs:");
+        ipw.increaseIndent();
+        mTimeZoneLog.dump(fd, ipw, args);
+        ipw.decreaseIndent();
+    }
+
+    /**
+     * Update time zone by network country code, works well on countries which only have one time
+     * zone or multiple zones with the same offset.
+     *
+     * @param iso Country code from network MCC
+     */
+    private void updateTimeZoneFromNetworkCountryCode(String iso) {
+        CountryResult lookupResult = mTimeZoneLookupHelper.lookupByCountry(
+                iso, mTimeServiceHelper.currentTimeMillis());
+        if (lookupResult != null && lookupResult.allZonesHaveSameOffset) {
+            String logMsg = "updateTimeZoneFromNetworkCountryCode: tz result found"
+                    + " iso=" + iso
+                    + " lookupResult=" + lookupResult;
+            if (DBG) {
+                Rlog.d(LOG_TAG, logMsg);
+            }
+            mTimeZoneLog.log(logMsg);
+            String zoneId = lookupResult.zoneId;
+            if (mTimeServiceHelper.isTimeZoneDetectionEnabled()) {
+                setAndBroadcastNetworkSetTimeZone(zoneId);
+            }
+            mSavedTimeZoneId = zoneId;
+        } else {
+            if (DBG) {
+                Rlog.d(LOG_TAG, "updateTimeZoneFromNetworkCountryCode: no good zone for"
+                        + " iso=" + iso
+                        + " lookupResult=" + lookupResult);
+            }
+        }
+    }
+
+    public boolean getNitzTimeZoneDetectionSuccessful() {
+        return mNitzTimeZoneDetectionSuccessful;
+    }
+
+    @Override
+    public NitzData getCachedNitzData() {
+        return mLatestNitzSignal != null ? mLatestNitzSignal.getValue() : null;
+    }
+
+    @Override
+    public String getSavedTimeZoneId() {
+        return mSavedTimeZoneId;
+    }
+
+}
diff --git a/src/java/com/android/internal/telephony/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 0f431ef..e6ec174 100644
--- a/src/java/com/android/internal/telephony/Phone.java
+++ b/src/java/com/android/internal/telephony/Phone.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.telephony;
 
+import android.annotation.Nullable;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -40,10 +41,9 @@
 import android.provider.Settings;
 import android.service.carrier.CarrierIdentifier;
 import android.telecom.VideoProfile;
+import android.telephony.AccessNetworkConstants.TransportType;
 import android.telephony.CarrierConfigManager;
-import android.telephony.CellIdentityCdma;
 import android.telephony.CellInfo;
-import android.telephony.CellInfoCdma;
 import android.telephony.CellLocation;
 import android.telephony.ClientRequestStats;
 import android.telephony.ImsiEncryptionInfo;
@@ -55,9 +55,12 @@
 import android.telephony.SignalStrength;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
-import android.telephony.VoLteServiceState;
+import android.telephony.data.ApnSetting;
+import android.telephony.data.ApnSetting.ApnType;
+import android.telephony.emergency.EmergencyNumber;
 import android.telephony.ims.stub.ImsRegistrationImplBase;
 import android.text.TextUtils;
+import android.util.SparseArray;
 
 import com.android.ims.ImsCall;
 import com.android.ims.ImsConfig;
@@ -65,6 +68,8 @@
 import com.android.internal.R;
 import com.android.internal.telephony.dataconnection.DataConnectionReasons;
 import com.android.internal.telephony.dataconnection.DcTracker;
+import com.android.internal.telephony.dataconnection.TransportManager;
+import com.android.internal.telephony.emergency.EmergencyNumberTracker;
 import com.android.internal.telephony.imsphone.ImsPhoneCall;
 import com.android.internal.telephony.test.SimulatedRadioControl;
 import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppType;
@@ -79,6 +84,7 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
@@ -162,6 +168,7 @@
     protected static final int EVENT_GET_CALL_FORWARD_DONE       = 13;
     protected static final int EVENT_CALL_RING                   = 14;
     private static final int EVENT_CALL_RING_CONTINUE            = 15;
+    private static final int EVENT_ALL_DATA_DISCONNECTED         = 16;
 
     // Used to intercept the carrier selection calls so that
     // we can save the values.
@@ -199,8 +206,11 @@
     // Carrier's CDMA prefer mode setting
     protected static final int EVENT_SET_ROAMING_PREFERENCE_DONE    = 44;
     protected static final int EVENT_MODEM_RESET                    = 45;
+    protected static final int EVENT_VRS_OR_RAT_CHANGED             = 46;
+    // Radio state change
+    protected static final int EVENT_RADIO_STATE_CHANGED            = 47;
 
-    protected static final int EVENT_LAST                       = EVENT_MODEM_RESET;
+    protected static final int EVENT_LAST                       = EVENT_RADIO_STATE_CHANGED;
 
     // For shared prefs.
     private static final String GSM_ROAMING_LIST_OVERRIDE_PREFIX = "gsm_roaming_list_";
@@ -250,7 +260,11 @@
     public CommandsInterface mCi;
     protected int mVmCount = 0;
     private boolean mDnsCheckDisabled;
-    public DcTracker mDcTracker;
+    // Data connection trackers. For each transport type (e.g. WWAN, WLAN), there will be a
+    // corresponding DcTracker. The WWAN DcTracker is for cellular data connections while
+    // WLAN DcTracker is for IWLAN data connection. For IWLAN legacy mode, only one (WWAN) DcTracker
+    // will be created.
+    protected final SparseArray<DcTracker> mDcTrackers = new SparseArray<>();
     /* Used for dispatching signals to configured carrier apps */
     protected CarrierSignalAgent mCarrierSignalAgent;
     /* Used for dispatching carrier action from carrier apps */
@@ -279,6 +293,7 @@
     private final String mActionDetached;
     private final String mActionAttached;
     protected DeviceStateMonitor mDeviceStateMonitor;
+    protected TransportManager mTransportManager;
 
     protected int mPhoneId;
 
@@ -305,47 +320,37 @@
     public static final String EXTRA_KEY_ALERT_SHOW = "alertShow";
     public static final String EXTRA_KEY_NOTIFICATION_MESSAGE = "notificationMessage";
 
-    private final RegistrantList mPreciseCallStateRegistrants
-            = new RegistrantList();
+    private final RegistrantList mPreciseCallStateRegistrants = new RegistrantList();
 
-    private final RegistrantList mHandoverRegistrants
-            = new RegistrantList();
+    private final RegistrantList mHandoverRegistrants = new RegistrantList();
 
-    private final RegistrantList mNewRingingConnectionRegistrants
-            = new RegistrantList();
+    private final RegistrantList mNewRingingConnectionRegistrants = new RegistrantList();
 
-    private final RegistrantList mIncomingRingRegistrants
-            = new RegistrantList();
+    private final RegistrantList mIncomingRingRegistrants = new RegistrantList();
 
-    protected final RegistrantList mDisconnectRegistrants
-            = new RegistrantList();
+    protected final RegistrantList mDisconnectRegistrants = new RegistrantList();
 
-    private final RegistrantList mServiceStateRegistrants
-            = new RegistrantList();
+    private final RegistrantList mServiceStateRegistrants = new RegistrantList();
 
-    protected final RegistrantList mMmiCompleteRegistrants
-            = new RegistrantList();
+    protected final RegistrantList mMmiCompleteRegistrants = new RegistrantList();
 
-    protected final RegistrantList mMmiRegistrants
-            = new RegistrantList();
+    protected final RegistrantList mMmiRegistrants = new RegistrantList();
 
-    protected final RegistrantList mUnknownConnectionRegistrants
-            = new RegistrantList();
+    protected final RegistrantList mUnknownConnectionRegistrants = new RegistrantList();
 
-    protected final RegistrantList mSuppServiceFailedRegistrants
-            = new RegistrantList();
+    protected final RegistrantList mSuppServiceFailedRegistrants = new RegistrantList();
 
-    protected final RegistrantList mRadioOffOrNotAvailableRegistrants
-            = new RegistrantList();
+    protected final RegistrantList mRadioOffOrNotAvailableRegistrants = new RegistrantList();
 
-    protected final RegistrantList mSimRecordsLoadedRegistrants
-            = new RegistrantList();
+    protected final RegistrantList mSimRecordsLoadedRegistrants = new RegistrantList();
 
-    private final RegistrantList mVideoCapabilityChangedRegistrants
-            = new RegistrantList();
+    private final RegistrantList mVideoCapabilityChangedRegistrants = new RegistrantList();
 
-    protected final RegistrantList mEmergencyCallToggledRegistrants
-            = new RegistrantList();
+    protected final RegistrantList mEmergencyCallToggledRegistrants = new RegistrantList();
+
+    private final RegistrantList mAllDataDisconnectedRegistrants = new RegistrantList();
+
+    private final RegistrantList mCellInfoRegistrants = new RegistrantList();
 
     protected Registrant mPostDialHandler;
 
@@ -480,7 +485,8 @@
         mCi = ci;
         mActionDetached = this.getClass().getPackage().getName() + ".action_detached";
         mActionAttached = this.getClass().getPackage().getName() + ".action_attached";
-        mAppSmsManager = telephonyComponentFactory.makeAppSmsManager(context);
+        mAppSmsManager = telephonyComponentFactory.inject(AppSmsManager.class.getName())
+                .makeAppSmsManager(context);
 
         if (Build.IS_DEBUGGABLE) {
             mTelephonyTester = new TelephonyTester(this);
@@ -542,11 +548,15 @@
 
         // Initialize device storage and outgoing SMS usage monitors for SMSDispatchers.
         mTelephonyComponentFactory = telephonyComponentFactory;
-        mSmsStorageMonitor = mTelephonyComponentFactory.makeSmsStorageMonitor(this);
-        mSmsUsageMonitor = mTelephonyComponentFactory.makeSmsUsageMonitor(context);
+        mSmsStorageMonitor = mTelephonyComponentFactory.inject(SmsStorageMonitor.class.getName())
+                .makeSmsStorageMonitor(this);
+        mSmsUsageMonitor = mTelephonyComponentFactory.inject(SmsUsageMonitor.class.getName())
+                .makeSmsUsageMonitor(context);
         mUiccController = UiccController.getInstance();
         mUiccController.registerForIccChanged(this, EVENT_ICC_CHANGED, null);
-        mSimActivationTracker = mTelephonyComponentFactory.makeSimActivationTracker(this);
+        mSimActivationTracker = mTelephonyComponentFactory
+                .inject(SimActivationTracker.class.getName())
+                .makeSimActivationTracker(this);
         if (getPhoneType() != PhoneConstants.PHONE_TYPE_SIP) {
             mCi.registerForSrvccStateChanged(this, EVENT_SRVCC_STATE_CHANGED, null);
         }
@@ -600,7 +610,7 @@
     public boolean supportsConversionOfCdmaCallerIdMmiCodesWhileRoaming() {
         CarrierConfigManager configManager = (CarrierConfigManager)
                 getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
-        PersistableBundle b = configManager.getConfig();
+        PersistableBundle b = configManager.getConfigForSubId(getSubId());
         if (b != null) {
             return b.getBoolean(
                     CarrierConfigManager
@@ -709,6 +719,12 @@
                 onCheckForNetworkSelectionModeAutomatic(msg);
                 break;
             }
+
+            case EVENT_ALL_DATA_DISCONNECTED:
+                if (areAllDataDisconnected()) {
+                    mAllDataDisconnectedRegistrants.notifyRegistrants();
+                }
+                break;
             default:
                 throw new RuntimeException("unexpected event not handled");
         }
@@ -736,7 +752,7 @@
         if (ret != null && ret.length != 0) {
             int state = ret[0];
             switch(state) {
-                case VoLteServiceState.HANDOVER_STARTED:
+                case TelephonyManager.SRVCC_STATE_HANDOVER_STARTED:
                     srvccState = Call.SrvccState.STARTED;
                     if (imsPhone != null) {
                         conn = imsPhone.getHandoverConnection();
@@ -745,7 +761,7 @@
                         Rlog.d(LOG_TAG, "HANDOVER_STARTED: mImsPhone null");
                     }
                     break;
-                case VoLteServiceState.HANDOVER_COMPLETED:
+                case TelephonyManager.SRVCC_STATE_HANDOVER_COMPLETED:
                     srvccState = Call.SrvccState.COMPLETED;
                     if (imsPhone != null) {
                         imsPhone.notifySrvccState(srvccState);
@@ -753,8 +769,8 @@
                         Rlog.d(LOG_TAG, "HANDOVER_COMPLETED: mImsPhone null");
                     }
                     break;
-                case VoLteServiceState.HANDOVER_FAILED:
-                case VoLteServiceState.HANDOVER_CANCELED:
+                case TelephonyManager.SRVCC_STATE_HANDOVER_FAILED:
+                case TelephonyManager.SRVCC_STATE_HANDOVER_CANCELED:
                     srvccState = Call.SrvccState.FAILED;
                     break;
 
@@ -765,8 +781,7 @@
 
             getCallTracker().notifySrvccState(srvccState, conn);
 
-            VoLteServiceState lteState = new VoLteServiceState(state);
-            notifyVoLteServiceStateChanged(lteState);
+            notifySrvccStateChanged(state);
         }
     }
 
@@ -872,6 +887,7 @@
         migrate(mMmiRegistrants, from.mMmiRegistrants);
         migrate(mUnknownConnectionRegistrants, from.mUnknownConnectionRegistrants);
         migrate(mSuppServiceFailedRegistrants, from.mSuppServiceFailedRegistrants);
+        migrate(mCellInfoRegistrants, from.mCellInfoRegistrants);
         if (from.isInEmergencyCall()) {
             setIsInEmergencyCall();
         }
@@ -1485,6 +1501,24 @@
     }
 
     /**
+     * Registers for CellInfo changed.
+     * Message.obj will contain an AsyncResult.
+     * AsyncResult.result will be a List<CellInfo> instance
+     */
+    public void registerForCellInfo(
+            Handler h, int what, Object obj) {
+        mCellInfoRegistrants.add(h, what, obj);
+    }
+
+    /**
+     * Unregisters for CellInfo notification.
+     * Extraneous calls are tolerated silently
+     */
+    public void unregisterForCellInfo(Handler h) {
+        mCellInfoRegistrants.remove(h);
+    }
+
+    /**
      * Enables or disables echo suppression.
      */
     public void setEchoSuppressionEnabled() {
@@ -1602,6 +1636,13 @@
     }
 
     /**
+     * Retrieves the EmergencyNumberTracker of the phone instance.
+     */
+    public EmergencyNumberTracker getEmergencyNumberTracker() {
+        return null;
+    }
+
+    /**
     * Get call tracker
     */
     public CallTracker getCallTracker() {
@@ -1609,6 +1650,13 @@
     }
 
     /**
+     * @return The instance of transport manager
+     */
+    public TransportManager getTransportManager() {
+        return null;
+    }
+
+    /**
      * Update voice activation state
      */
     public void setVoiceActivationState(int state) {
@@ -1688,50 +1736,39 @@
         return (r != null) ? r.getRecordsLoaded() : false;
     }
 
+    /** Set the minimum interval for CellInfo requests to the modem */
+    public void setCellInfoMinInterval(int interval) {
+        getServiceStateTracker().setCellInfoMinInterval(interval);
+    }
+
+    /**
+     * @return the last known CellInfo
+     */
+    public List<CellInfo> getAllCellInfo() {
+        return getServiceStateTracker().getAllCellInfo();
+    }
+
     /**
      * @param workSource calling WorkSource
-     * @return all available cell information or null if none.
+     * @param rspMsg the response message containing the cell info
      */
-    public List<CellInfo> getAllCellInfo(WorkSource workSource) {
-        List<CellInfo> cellInfoList = getServiceStateTracker().getAllCellInfo(workSource);
-        return privatizeCellInfoList(cellInfoList);
-    }
-
-    public CellLocation getCellLocation() {
-        return getCellLocation(null);
+    public void requestCellInfoUpdate(WorkSource workSource, Message rspMsg) {
+        getServiceStateTracker().requestAllCellInfo(workSource, rspMsg);
     }
 
     /**
-     * Clear CDMA base station lat/long values if location setting is disabled.
-     * @param cellInfoList the original cell info list from the RIL
-     * @return the original list with CDMA lat/long cleared if necessary
+     * @return the current cell location if known
      */
-    private List<CellInfo> privatizeCellInfoList(List<CellInfo> cellInfoList) {
-        if (cellInfoList == null) return null;
-        int mode = Settings.Secure.getInt(getContext().getContentResolver(),
-                Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_OFF);
-        if (mode == Settings.Secure.LOCATION_MODE_OFF) {
-            ArrayList<CellInfo> privateCellInfoList = new ArrayList<CellInfo>(cellInfoList.size());
-            // clear lat/lon values for location privacy
-            for (CellInfo c : cellInfoList) {
-                if (c instanceof CellInfoCdma) {
-                    CellInfoCdma cellInfoCdma = (CellInfoCdma) c;
-                    CellIdentityCdma cellIdentity = cellInfoCdma.getCellIdentity();
-                    CellIdentityCdma maskedCellIdentity = new CellIdentityCdma(
-                            cellIdentity.getNetworkId(),
-                            cellIdentity.getSystemId(),
-                            cellIdentity.getBasestationId(),
-                            Integer.MAX_VALUE, Integer.MAX_VALUE);
-                    CellInfoCdma privateCellInfoCdma = new CellInfoCdma(cellInfoCdma);
-                    privateCellInfoCdma.setCellIdentity(maskedCellIdentity);
-                    privateCellInfoList.add(privateCellInfoCdma);
-                } else {
-                    privateCellInfoList.add(c);
-                }
-            }
-            cellInfoList = privateCellInfoList;
-        }
-        return cellInfoList;
+    public CellLocation getCellLocation() {
+        return getServiceStateTracker().getCellLocation();
+    }
+
+    /**
+     * @param workSource calling WorkSource
+     * @param rspMsg the response message containing the cell location
+     */
+    public void getCellLocation(WorkSource workSource, Message rspMsg) {
+        getServiceStateTracker().requestCellLocation(workSource, rspMsg);
     }
 
     /**
@@ -2100,9 +2137,10 @@
      *
      * @param itemID the ID of the item to read
      * @param response callback message with the String response in the obj field
+     * @param workSource calling WorkSource
      */
-    public void nvReadItem(int itemID, Message response) {
-        mCi.nvReadItem(itemID, response);
+    public void nvReadItem(int itemID, Message response, WorkSource workSource) {
+        mCi.nvReadItem(itemID, response, workSource);
     }
 
     /**
@@ -2112,9 +2150,11 @@
      * @param itemID the ID of the item to read
      * @param itemValue the value to write, as a String
      * @param response Callback message.
+     * @param workSource calling WorkSource
      */
-    public void nvWriteItem(int itemID, String itemValue, Message response) {
-        mCi.nvWriteItem(itemID, itemValue, response);
+    public void nvWriteItem(int itemID, String itemValue, Message response,
+            WorkSource workSource) {
+        mCi.nvWriteItem(itemID, itemValue, response, workSource);
     }
 
     /**
@@ -2129,15 +2169,24 @@
     }
 
     /**
-     * Perform the specified type of NV config reset. The radio will be taken offline
-     * and the device must be rebooted after erasing the NV. Used for device
+     * Perform the radio modem reboot. The radio will be taken offline. Used for device
      * configuration by some CDMA operators.
+     * TODO: reuse nvResetConfig for now, should move to separate HAL API.
      *
-     * @param resetType reset type: 1: reload NV reset, 2: erase NV reset, 3: factory NV reset
      * @param response Callback message.
      */
-    public void nvResetConfig(int resetType, Message response) {
-        mCi.nvResetConfig(resetType, response);
+    public void rebootModem(Message response) {
+        mCi.nvResetConfig(1 /* 1: reload NV reset, trigger a modem reboot */, response);
+    }
+
+    /**
+     * Perform the modem configuration reset. Used for device configuration by some CDMA operators.
+     * TODO: reuse nvResetConfig for now, should move to separate HAL API.
+     *
+     * @param response Callback message.
+     */
+    public void resetModemConfig(Message response) {
+        mCi.nvResetConfig(3 /* factory NV reset */, response);
     }
 
     public void notifyDataActivity() {
@@ -2164,8 +2213,11 @@
 
     public void notifyDataConnection(String reason) {
         String types[] = getActiveApnTypes();
-        for (String apnType : types) {
-            mNotifier.notifyDataConnection(this, reason, apnType, getDataConnectionState(apnType));
+        if (types != null) {
+            for (String apnType : types) {
+                mNotifier.notifyDataConnection(this, reason, apnType,
+                        getDataConnectionState(apnType));
+            }
         }
     }
 
@@ -2190,7 +2242,10 @@
     }
 
     public void notifyCellInfo(List<CellInfo> cellInfo) {
-        mNotifier.notifyCellInfo(this, privatizeCellInfoList(cellInfo));
+        AsyncResult ar = new AsyncResult(null, cellInfo, null);
+        mCellInfoRegistrants.notifyRegistrants(ar);
+
+        mNotifier.notifyCellInfo(this, cellInfo);
     }
 
     /** Notify {@link PhysicalChannelConfig} changes. */
@@ -2198,8 +2253,16 @@
         mNotifier.notifyPhysicalChannelConfiguration(this, configs);
     }
 
-    public void notifyVoLteServiceStateChanged(VoLteServiceState lteState) {
-        mNotifier.notifyVoLteServiceStateChanged(this, lteState);
+    /**
+     * Notify listeners that SRVCC state has changed.
+     */
+    public void notifySrvccStateChanged(int state) {
+        mNotifier.notifySrvccStateChanged(this, state);
+    }
+
+    /** Notify the list of {@link EmergencyNumber} changes. */
+    public void notifyEmergencyNumberList(List<EmergencyNumber> emergencyNumberList) {
+        mNotifier.notifyEmergencyNumberList(this, emergencyNumberList);
     }
 
     /**
@@ -2304,6 +2367,15 @@
         } else {
             Rlog.e(LOG_TAG, "setVoiceMessageCount in sharedPreference: invalid subId " + subId);
         }
+        // store voice mail count in SIM
+        IccRecords records = UiccController.getInstance().getIccRecords(
+                mPhoneId, UiccController.APP_FAM_3GPP);
+        if (records != null) {
+            Rlog.d(LOG_TAG, "setVoiceMessageCount: updating SIM Records");
+            records.setVoiceMessageWaiting(1, countWaiting);
+        } else {
+            Rlog.d(LOG_TAG, "setVoiceMessageCount: SIM Records not found");
+        }
         // notify listeners of voice mail
         notifyMessageWaitingIndicator();
     }
@@ -2363,6 +2435,14 @@
                     Uri.parse("android_secret_code://" + code));
             intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
             mContext.sendBroadcast(intent);
+
+            // {@link TelephonyManager.ACTION_SECRET_CODE} will replace {@link
+            // TelephonyIntents#SECRET_CODE_ACTION} in the next Android version. Before
+            // that both of these two actions will be broadcast.
+            Intent secrectCodeIntent = new Intent(TelephonyManager.ACTION_SECRET_CODE,
+                    Uri.parse("android_secret_code://" + code));
+            intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+            mContext.sendBroadcast(secrectCodeIntent);
         }
     }
 
@@ -2772,24 +2852,35 @@
     /**
      * Returns an array of string identifiers for the APN types serviced by the
      * currently active.
-     *  @return The string array will always return at least one entry, Phone.APN_TYPE_DEFAULT.
-     * TODO: Revisit if we always should return at least one entry.
+     *
+     * @return The string array of APN types. Return null if no active APN types.
      */
+    @Nullable
     public String[] getActiveApnTypes() {
-        if (mDcTracker == null) {
-            return null;
+        if (mTransportManager != null) {
+            List<String> typesList = new ArrayList<>();
+            for (int transportType : mTransportManager.getAvailableTransports()) {
+                if (getDcTracker(transportType) != null) {
+                    typesList.addAll(Arrays.asList(
+                            getDcTracker(transportType).getActiveApnTypes()));
+                }
+            }
+
+            return typesList.toArray(new String[typesList.size()]);
         }
 
-        return mDcTracker.getActiveApnTypes();
+        return null;
     }
 
     /**
-     * Check if TETHER_DUN_APN setting or config_tether_apndata includes APN that matches
-     * current operator.
+     * Check if there are matching tethering (i.e DUN) for the carrier.
      * @return true if there is a matching DUN APN.
      */
     public boolean hasMatchedTetherApnSetting() {
-        return mDcTracker.hasMatchedTetherApnSetting();
+        if (getDcTracker(TransportType.WWAN) != null) {
+            return getDcTracker(TransportType.WWAN).hasMatchedTetherApnSetting();
+        }
+        return false;
     }
 
     /**
@@ -2797,40 +2888,71 @@
      *  @return type as a string or null if none.
      */
     public String getActiveApnHost(String apnType) {
-        return mDcTracker.getActiveApnString(apnType);
+        if (mTransportManager != null) {
+            int transportType = mTransportManager.getCurrentTransport(
+                    ApnSetting.getApnTypesBitmaskFromString(apnType));
+            if (getDcTracker(transportType) != null) {
+                return getDcTracker(transportType).getActiveApnString(apnType);
+            }
+        }
+
+        return null;
     }
 
     /**
      * Return the LinkProperties for the named apn or null if not available
      */
     public LinkProperties getLinkProperties(String apnType) {
-        return mDcTracker.getLinkProperties(apnType);
+        if (mTransportManager != null) {
+            int transport = mTransportManager.getCurrentTransport(
+                    ApnSetting.getApnTypesBitmaskFromString(apnType));
+            if (getDcTracker(transport) != null) {
+                return getDcTracker(transport).getLinkProperties(apnType);
+            }
+        }
+        return null;
     }
 
     /**
      * Return the NetworkCapabilities
      */
     public NetworkCapabilities getNetworkCapabilities(String apnType) {
-        return mDcTracker.getNetworkCapabilities(apnType);
+        if (mTransportManager != null) {
+            int transportType = mTransportManager.getCurrentTransport(
+                    ApnSetting.getApnTypesBitmaskFromString(apnType));
+            if (getDcTracker(transportType) != null) {
+                return getDcTracker(transportType).getNetworkCapabilities(apnType);
+            }
+        }
+        return null;
     }
 
     /**
-     * Report on whether data connectivity is allowed.
+     * Report on whether data connectivity is allowed for given APN type.
+     *
+     * @param apnType APN type
      *
      * @return True if data is allowed to be established.
      */
-    public boolean isDataAllowed() {
-        return ((mDcTracker != null) && (mDcTracker.isDataAllowed(null)));
+    public boolean isDataAllowed(@ApnType int apnType) {
+        return isDataAllowed(apnType, null);
     }
 
     /**
      * Report on whether data connectivity is allowed.
      *
+     * @param apnType APN type
      * @param reasons The reasons that data can/can't be established. This is an output param.
      * @return True if data is allowed to be established
      */
-    public boolean isDataAllowed(DataConnectionReasons reasons) {
-        return ((mDcTracker != null) && (mDcTracker.isDataAllowed(reasons)));
+    public boolean isDataAllowed(@ApnType int apnType, DataConnectionReasons reasons) {
+        if (mTransportManager != null) {
+            int transport = mTransportManager.getCurrentTransport(apnType);
+            if (getDcTracker(transport) != null) {
+                return getDcTracker(transport).isDataAllowed(reasons);
+            }
+        }
+        return false;
     }
 
 
@@ -3020,7 +3142,15 @@
      * @param apnType the apnType, "ims" for IMS APN, "emergency" for EMERGENCY APN
      */
     public String[] getPcscfAddress(String apnType) {
-        return mDcTracker.getPcscfAddress(apnType);
+        if (mTransportManager != null) {
+            int transportType = mTransportManager.getCurrentTransport(
+                    ApnSetting.getApnTypesBitmaskFromString(apnType));
+            if (getDcTracker(transportType) != null) {
+                return getDcTracker(transportType).getPcscfAddress(apnType);
+            }
+        }
+
+        return null;
     }
 
     /**
@@ -3066,10 +3196,25 @@
         return null;
     }
 
+    public int getMNOCarrierId() {
+        return TelephonyManager.UNKNOWN_CARRIER_ID;
+    }
+
+    public int getPreciseCarrierId() {
+        return TelephonyManager.UNKNOWN_CARRIER_ID;
+    }
+
+    public String getPreciseCarrierName() {
+        return null;
+    }
+
     public int getCarrierIdListVersion() {
         return TelephonyManager.UNKNOWN_CARRIER_ID_LIST_VERSION;
     }
 
+    public void resolveSubscriptionCarrierId(String simState) {
+    }
+
     /**
      *  Resets the Carrier Keys in the database. This involves 2 steps:
      *  1. Delete the keys from the database.
@@ -3132,6 +3277,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);
     }
 
@@ -3249,6 +3401,20 @@
     }
 
     /**
+     * @return true if the IMS capability for the registration technology specified is available,
+     * false otherwise.
+     */
+    public boolean isImsCapabilityAvailable(int capability, int regTech) {
+        Phone imsPhone = mImsPhone;
+        boolean isAvailable = false;
+        if (imsPhone != null) {
+            isAvailable = imsPhone.isImsCapabilityAvailable(capability, regTech);
+        }
+        Rlog.d(LOG_TAG, "isImsRegistered =" + isAvailable);
+        return isAvailable;
+    }
+
+    /**
      * Get Volte Feature Availability
      */
     public boolean isVolteEnabled() {
@@ -3290,17 +3456,24 @@
     }
 
     /**
+     * @return returns the latest radio state from the modem
+     */
+    public int getRadioPowerState() {
+        return mCi.getRadioState();
+    }
+
+    /**
      * Is Radio Present on the device and is it accessible
      */
     public boolean isRadioAvailable() {
-        return mCi.getRadioState().isAvailable();
+        return mCi.getRadioState() != TelephonyManager.RADIO_POWER_UNAVAILABLE;
     }
 
     /**
      * Is Radio turned on
      */
     public boolean isRadioOn() {
-        return mCi.getRadioState().isOn();
+        return mCi.getRadioState() == TelephonyManager.RADIO_POWER_ON;
     }
 
     /**
@@ -3464,8 +3637,8 @@
     /**
      * Returns the modem activity information
      */
-    public void getModemActivityInfo(Message response)  {
-        mCi.getModemActivityInfo(response);
+    public void getModemActivityInfo(Message response, WorkSource workSource)  {
+        mCi.getModemActivityInfo(response, workSource);
     }
 
     /**
@@ -3480,8 +3653,9 @@
     /**
      * Set allowed carriers
      */
-    public void setAllowedCarriers(List<CarrierIdentifier> carriers, Message response) {
-        mCi.setAllowedCarriers(carriers, response);
+    public void setAllowedCarriers(List<CarrierIdentifier> carriers, Message response,
+            WorkSource workSource) {
+        mCi.setAllowedCarriers(carriers, response, workSource);
     }
 
     /** Sets the SignalStrength reporting criteria. */
@@ -3497,8 +3671,8 @@
     /**
      * Get allowed carriers
      */
-    public void getAllowedCarriers(Message response) {
-        mCi.getAllowedCarriers(response);
+    public void getAllowedCarriers(Message response, WorkSource workSource) {
+        mCi.getAllowedCarriers(response, workSource);
     }
 
     /**
@@ -3515,31 +3689,59 @@
     }
 
     public void updateDataConnectionTracker() {
-        mDcTracker.update();
-    }
-
-    public void setInternalDataEnabled(boolean enable, Message onCompleteMsg) {
-        mDcTracker.setInternalDataEnabled(enable, onCompleteMsg);
+        if (mTransportManager != null) {
+            for (int transport : mTransportManager.getAvailableTransports()) {
+                if (getDcTracker(transport) != null) {
+                    getDcTracker(transport).update();
+                }
+            }
+        }
     }
 
     public boolean updateCurrentCarrierInProvider() {
         return false;
     }
 
-    public void registerForAllDataDisconnected(Handler h, int what, Object obj) {
-        mDcTracker.registerForAllDataDisconnected(h, what, obj);
+    /**
+     * @return True if all data connections are disconnected.
+     */
+    public boolean areAllDataDisconnected() {
+        if (mTransportManager != null) {
+            for (int transport : mTransportManager.getAvailableTransports()) {
+                if (getDcTracker(transport) != null && !getDcTracker(transport).isDisconnected()) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    public void registerForAllDataDisconnected(Handler h, int what) {
+        mAllDataDisconnectedRegistrants.addUnique(h, what, null);
+        if (mTransportManager != null) {
+            for (int transport : mTransportManager.getAvailableTransports()) {
+                if (getDcTracker(transport) != null && !getDcTracker(transport).isDisconnected()) {
+                    getDcTracker(transport).registerForAllDataDisconnected(
+                            this, EVENT_ALL_DATA_DISCONNECTED);
+                }
+            }
+        }
     }
 
     public void unregisterForAllDataDisconnected(Handler h) {
-        mDcTracker.unregisterForAllDataDisconnected(h);
+        mAllDataDisconnectedRegistrants.remove(h);
     }
 
     public void registerForDataEnabledChanged(Handler h, int what, Object obj) {
-        mDcTracker.registerForDataEnabledChanged(h, what, obj);
+        if (getDcTracker(TransportType.WWAN) != null) {
+            getDcTracker(TransportType.WWAN).registerForDataEnabledChanged(h, what, obj);
+        }
     }
 
     public void unregisterForDataEnabledChanged(Handler h) {
-        mDcTracker.unregisterForDataEnabledChanged(h);
+        if (getDcTracker(TransportType.WWAN) != null) {
+            getDcTracker(TransportType.WWAN).unregisterForDataEnabledChanged(h);
+        }
     }
 
     public IccSmsInterfaceManager getIccSmsInterfaceManager(){
@@ -3617,7 +3819,13 @@
      * @param enabled True if enabling the data, otherwise disabling.
      */
     public void setPolicyDataEnabled(boolean enabled) {
-        mDcTracker.setPolicyDataEnabled(enabled);
+        if (mTransportManager != null) {
+            for (int transport : mTransportManager.getAvailableTransports()) {
+                if (getDcTracker(transport) != null) {
+                    getDcTracker(transport).setPolicyDataEnabled(enabled);
+                }
+            }
+        }
     }
 
     /**
@@ -3640,8 +3848,8 @@
      * - {@link android.telephony.TelephonyManager#CARD_POWER_UP}
      * - {@link android.telephony.TelephonyManager#CARD_POWER_UP_PASS_THROUGH}
      **/
-    public void setSimPowerState(int state) {
-        mCi.setSimCardPower(state, null);
+    public void setSimPowerState(int state, WorkSource workSource) {
+        mCi.setSimCardPower(state, null, workSource);
     }
 
     public void setRadioIndicationUpdateMode(int filters, int mode) {
@@ -3654,12 +3862,21 @@
             String gid2, String pnn, String spn) {
     }
 
+    /**
+     * Get data connection tracker based on the transport type
+     *
+     * @param transportType Transport type defined in AccessNetworkConstants.TransportType
+     * @return The data connection tracker. Null if not found.
+     */
+    public @Nullable DcTracker getDcTracker(int transportType) {
+        return mDcTrackers.get(transportType);
+    }
+
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("Phone: subId=" + getSubId());
         pw.println(" mPhoneId=" + mPhoneId);
         pw.println(" mCi=" + mCi);
         pw.println(" mDnsCheckDisabled=" + mDnsCheckDisabled);
-        pw.println(" mDcTracker=" + mDcTracker);
         pw.println(" mDoesRilSendMultipleCallRing=" + mDoesRilSendMultipleCallRing);
         pw.println(" mCallRingContinueToken=" + mCallRingContinueToken);
         pw.println(" mCallRingDelay=" + mCallRingDelay);
@@ -3703,9 +3920,19 @@
             pw.println("++++++++++++++++++++++++++++++++");
         }
 
-        if (mDcTracker != null) {
+        if (mTransportManager != null) {
+            for (int transport : mTransportManager.getAvailableTransports()) {
+                if (getDcTracker(transport) != null) {
+                    getDcTracker(transport).dump(fd, pw, args);
+                    pw.flush();
+                    pw.println("++++++++++++++++++++++++++++++++");
+                }
+            }
+        }
+
+        if (getServiceStateTracker() != null) {
             try {
-                mDcTracker.dump(fd, pw, args);
+                getServiceStateTracker().dump(fd, pw, args);
             } catch (Exception e) {
                 e.printStackTrace();
             }
@@ -3714,9 +3941,9 @@
             pw.println("++++++++++++++++++++++++++++++++");
         }
 
-        if (getServiceStateTracker() != null) {
+        if (getEmergencyNumberTracker() != null) {
             try {
-                getServiceStateTracker().dump(fd, pw, args);
+                getEmergencyNumberTracker().dump(fd, pw, args);
             } catch (Exception e) {
                 e.printStackTrace();
             }
@@ -3775,6 +4002,10 @@
             pw.println("++++++++++++++++++++++++++++++++");
         }
 
+        if (mTransportManager != null) {
+            mTransportManager.dump(fd, pw, args);
+        }
+
         if (mCi != null && mCi instanceof RIL) {
             try {
                 ((RIL)mCi).dump(fd, pw, args);
diff --git a/src/java/com/android/internal/telephony/PhoneConfigurationManager.java b/src/java/com/android/internal/telephony/PhoneConfigurationManager.java
new file mode 100644
index 0000000..bf85c18
--- /dev/null
+++ b/src/java/com/android/internal/telephony/PhoneConfigurationManager.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony;
+
+import android.content.Context;
+import android.telephony.PhoneCapability;
+import android.telephony.Rlog;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+/**
+ * This class manages phone's configuration which defines the potential capability (static) of the
+ * phone and its current activated capability (current).
+ * It gets and monitors static and current phone capability from the modem; send broadcast
+ * if they change, and and sends commands to modem to enable or disable phones.
+ */
+public class PhoneConfigurationManager {
+    private static final String LOG_TAG = "PhoneCfgMgr";
+
+    private static PhoneConfigurationManager sInstance = null;
+    private final Context mContext;
+    private PhoneCapability mStaticCapability;
+    private PhoneCapability mCurrentCapability;
+
+    /**
+     * Init method to instantiate the object
+     * Should only be called once.
+     */
+    public static PhoneConfigurationManager init(Context context) {
+        synchronized (PhoneConfigurationManager.class) {
+            if (sInstance == null) {
+                sInstance = new PhoneConfigurationManager(context);
+            } else {
+                Log.wtf(LOG_TAG, "init() called multiple times!  sInstance = " + sInstance);
+            }
+            return sInstance;
+        }
+    }
+
+    /**
+     * Constructor.
+     * @param context context needed to send broadcast.
+     */
+    private PhoneConfigurationManager(Context context) {
+        mContext = context;
+        // TODO: send commands to modem once interface is ready.
+        TelephonyManager telephonyManager = new TelephonyManager(context);
+        mStaticCapability = telephonyManager.getPhoneCount() == 1
+                ? PhoneConfigurationModels.SSSS_CAPABILITY
+                : PhoneConfigurationModels.DSDS_CAPABILITY;
+        mCurrentCapability = mStaticCapability;
+
+        notifyCapabilityChanged();
+    }
+
+    /**
+     * Static method to get instance.
+     */
+    public static PhoneConfigurationManager getInstance() {
+        if (sInstance == null) {
+            Log.wtf(LOG_TAG, "getInstance null");
+        }
+
+        return sInstance;
+    }
+
+
+    /**
+     * Enable or disable phone
+     *
+     * @param phoneId which phone to operate on
+     * @param enable true or false
+     *
+     */
+    public void enablePhone(int phoneId, boolean enable) {
+        // TODO: send command to modem once interface is ready.
+    }
+
+    /**
+     * Enable or disable phone
+     *
+     * @param phoneId which phone to operate on
+     * @param enable true or false
+     *
+     */
+    public void enablePhone(int[] phoneId, boolean[] enable) {
+        // TODO: send command to modem once interface is ready.
+    }
+
+    /**
+     * Returns how many phone objects the device supports.
+     */
+    public int getPhoneCount() {
+        TelephonyManager tm = new TelephonyManager(mContext);
+        return tm.getPhoneCount();
+    }
+
+    /**
+     * get static overall phone capabilities for all phones.
+     *
+     */
+    public PhoneCapability getStaticPhoneCapability() {
+        return mStaticCapability;
+    }
+
+    /**
+     * get configuration related status of each phone.
+     *
+     */
+    public PhoneCapability getCurrentPhoneCapability() {
+        return mCurrentCapability;
+    }
+
+    public int getNumberOfModemsWithSimultaneousDataConnections() {
+        return mCurrentCapability.maxActiveData;
+    }
+
+    private void notifyCapabilityChanged() {
+        PhoneNotifier notifier = new DefaultPhoneNotifier();
+
+        notifier.notifyPhoneCapabilityChanged(mCurrentCapability);
+    }
+
+    private static void log(String s) {
+        Rlog.d(LOG_TAG, s);
+    }
+}
diff --git a/src/java/com/android/internal/telephony/PhoneConfigurationModels.java b/src/java/com/android/internal/telephony/PhoneConfigurationModels.java
new file mode 100644
index 0000000..ada2d5e
--- /dev/null
+++ b/src/java/com/android/internal/telephony/PhoneConfigurationModels.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony;
+
+import android.telephony.ModemInfo;
+import android.telephony.PhoneCapability;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class is temporary created for CBRS phase 1 demo purpose.
+ * It has two hardcoded phone capability: ssss and dsds.
+ * This file should be removed once modem interface to return capabilities are ready.
+ */
+class PhoneConfigurationModels {
+    // Hardcoded DSDS capability.
+    public static final PhoneCapability DSDS_CAPABILITY;
+    // Hardcoded Single SIM single standby capability.
+    public static final PhoneCapability SSSS_CAPABILITY;
+
+    static {
+        ModemInfo modemInfo1 = new ModemInfo(0, 0, true, true);
+        ModemInfo modemInfo2 = new ModemInfo(1, 0, false, true);
+
+        List<ModemInfo> logicalModemList = new ArrayList<>();
+        logicalModemList.add(modemInfo1);
+        logicalModemList.add(modemInfo2);
+        DSDS_CAPABILITY = new PhoneCapability(1, 2, 0, logicalModemList);
+
+        logicalModemList = new ArrayList<>();
+        logicalModemList.add(modemInfo1);
+        SSSS_CAPABILITY = new PhoneCapability(1, 1, 0, logicalModemList);
+    }
+}
diff --git a/src/java/com/android/internal/telephony/PhoneFactory.java b/src/java/com/android/internal/telephony/PhoneFactory.java
index ba77747..5359941 100644
--- a/src/java/com/android/internal/telephony/PhoneFactory.java
+++ b/src/java/com/android/internal/telephony/PhoneFactory.java
@@ -43,7 +43,6 @@
 import com.android.internal.telephony.sip.SipPhone;
 import com.android.internal.telephony.sip.SipPhoneFactory;
 import com.android.internal.telephony.uicc.UiccController;
-import com.android.internal.telephony.uicc.UiccProfile;
 import com.android.internal.telephony.util.NotificationChannelController;
 import com.android.internal.util.IndentingPrintWriter;
 
@@ -82,6 +81,7 @@
     static private boolean sMadeDefaults = false;
     static private PhoneNotifier sPhoneNotifier;
     static private Context sContext;
+    static private PhoneConfigurationManager sPhoneConfigurationManager;
     static private PhoneSwitcher sPhoneSwitcher;
     static private SubscriptionMonitor sSubscriptionMonitor;
     static private TelephonyNetworkFactory[] sTelephonyNetworkFactories;
@@ -90,9 +90,6 @@
 
     static private final HashMap<String, LocalLog>sLocalLogs = new HashMap<String, LocalLog>();
 
-    // TODO - make this a dynamic property read from the modem
-    public static final int MAX_ACTIVE_PHONES = 1;
-
     //***** Class Methods
 
     public static void makeDefaultPhones(Context context) {
@@ -243,7 +240,12 @@
 
                 sSubscriptionMonitor = new SubscriptionMonitor(tr, sContext, sc, numPhones);
 
-                sPhoneSwitcher = new PhoneSwitcher(MAX_ACTIVE_PHONES, numPhones,
+                sPhoneConfigurationManager = PhoneConfigurationManager.init(sContext);
+
+                int maxActivePhones = sPhoneConfigurationManager
+                        .getNumberOfModemsWithSimultaneousDataConnections();
+
+                sPhoneSwitcher = PhoneSwitcher.make(maxActivePhones, numPhones,
                         sContext, sc, Looper.myLooper(), tr, sCommandsInterfaces,
                         sPhones);
 
@@ -257,8 +259,7 @@
                 sTelephonyNetworkFactories = new TelephonyNetworkFactory[numPhones];
                 for (int i = 0; i < numPhones; i++) {
                     sTelephonyNetworkFactories[i] = new TelephonyNetworkFactory(
-                            sPhoneSwitcher, sc, sSubscriptionMonitor, Looper.myLooper(),
-                            sContext, i, sPhones[i].mDcTracker);
+                            sSubscriptionMonitor, Looper.myLooper(), sPhones[i]);
                 }
             }
         }
@@ -455,17 +456,6 @@
             sTelephonyNetworkFactories[i].dump(fd, pw, args);
 
             pw.flush();
-            pw.println("++++++++++++++++++++++++++++++++");
-
-            try {
-                UiccProfile uiccProfile = (UiccProfile) phone.getIccCard();
-                if (uiccProfile != null) {
-                    uiccProfile.dump(fd, pw, args);
-                }
-            } catch (Exception e) {
-                e.printStackTrace();
-            }
-            pw.flush();
             pw.decreaseIndent();
             pw.println("++++++++++++++++++++++++++++++++");
         }
diff --git a/src/java/com/android/internal/telephony/PhoneInternalInterface.java b/src/java/com/android/internal/telephony/PhoneInternalInterface.java
index 123914e..a304ab6 100644
--- a/src/java/com/android/internal/telephony/PhoneInternalInterface.java
+++ b/src/java/com/android/internal/telephony/PhoneInternalInterface.java
@@ -21,9 +21,7 @@
 import android.os.Handler;
 import android.os.Message;
 import android.os.ResultReceiver;
-import android.os.WorkSource;
 import android.telecom.VideoProfile;
-import android.telephony.CellLocation;
 import android.telephony.ImsiEncryptionInfo;
 import android.telephony.NetworkScanRequest;
 import android.telephony.ServiceState;
@@ -178,36 +176,6 @@
     static final int BM_US_2500M    = RILConstants.BAND_MODE_USA_2500M;
     static final int BM_NUM_BAND_MODES = 19; //Total number of band modes
 
-    // Used for preferred network type
-    // Note NT_* substitute RILConstants.NETWORK_MODE_* above the Phone
-    int NT_MODE_WCDMA_PREF   = RILConstants.NETWORK_MODE_WCDMA_PREF;
-    int NT_MODE_GSM_ONLY     = RILConstants.NETWORK_MODE_GSM_ONLY;
-    int NT_MODE_WCDMA_ONLY   = RILConstants.NETWORK_MODE_WCDMA_ONLY;
-    int NT_MODE_GSM_UMTS     = RILConstants.NETWORK_MODE_GSM_UMTS;
-
-    int NT_MODE_CDMA         = RILConstants.NETWORK_MODE_CDMA;
-
-    int NT_MODE_CDMA_NO_EVDO = RILConstants.NETWORK_MODE_CDMA_NO_EVDO;
-    int NT_MODE_EVDO_NO_CDMA = RILConstants.NETWORK_MODE_EVDO_NO_CDMA;
-    int NT_MODE_GLOBAL       = RILConstants.NETWORK_MODE_GLOBAL;
-
-    int NT_MODE_LTE_CDMA_AND_EVDO        = RILConstants.NETWORK_MODE_LTE_CDMA_EVDO;
-    int NT_MODE_LTE_GSM_WCDMA            = RILConstants.NETWORK_MODE_LTE_GSM_WCDMA;
-    int NT_MODE_LTE_CDMA_EVDO_GSM_WCDMA  = RILConstants.NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA;
-    int NT_MODE_LTE_ONLY                 = RILConstants.NETWORK_MODE_LTE_ONLY;
-    int NT_MODE_LTE_WCDMA                = RILConstants.NETWORK_MODE_LTE_WCDMA;
-
-    int NT_MODE_TDSCDMA_ONLY            = RILConstants.NETWORK_MODE_TDSCDMA_ONLY;
-    int NT_MODE_TDSCDMA_WCDMA           = RILConstants.NETWORK_MODE_TDSCDMA_WCDMA;
-    int NT_MODE_LTE_TDSCDMA             = RILConstants.NETWORK_MODE_LTE_TDSCDMA;
-    int NT_MODE_TDSCDMA_GSM             = RILConstants.NETWORK_MODE_TDSCDMA_GSM;
-    int NT_MODE_LTE_TDSCDMA_GSM         = RILConstants.NETWORK_MODE_LTE_TDSCDMA_GSM;
-    int NT_MODE_TDSCDMA_GSM_WCDMA       = RILConstants.NETWORK_MODE_TDSCDMA_GSM_WCDMA;
-    int NT_MODE_LTE_TDSCDMA_WCDMA       = RILConstants.NETWORK_MODE_LTE_TDSCDMA_WCDMA;
-    int NT_MODE_LTE_TDSCDMA_GSM_WCDMA   = RILConstants.NETWORK_MODE_LTE_TDSCDMA_GSM_WCDMA;
-    int NT_MODE_TDSCDMA_CDMA_EVDO_GSM_WCDMA = RILConstants.NETWORK_MODE_TDSCDMA_CDMA_EVDO_GSM_WCDMA;
-    int NT_MODE_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA = RILConstants.NETWORK_MODE_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA;
-
     int PREFERRED_NT_MODE                = RILConstants.PREFERRED_NETWORK_MODE;
 
     // Used for CDMA roaming mode
@@ -256,12 +224,6 @@
     ServiceState getServiceState();
 
     /**
-     * Get the current CellLocation.
-     * @param workSource calling WorkSource
-     */
-    CellLocation getCellLocation(WorkSource workSource);
-
-    /**
      * Get the current DataState. No change notification exists at this
      * interface -- use
      * {@link android.telephony.PhoneStateListener} instead.
@@ -726,20 +688,6 @@
     void stopNetworkScan(Message response);
 
     /**
-     * Query neighboring cell IDs.  <code>response</code> is dispatched when
-     * this is complete.  <code>response.obj</code> will be an AsyncResult,
-     * and <code>response.obj.exception</code> will be non-null on failure.
-     * On success, <code>AsyncResult.result</code> will be a <code>String[]</code>
-     * containing the neighboring cell IDs.  Index 0 will contain the count
-     * of available cell IDs.  Cell IDs are in hexadecimal format.
-     *
-     * @param response callback message that is dispatched when the query
-     * completes.
-     * @param workSource calling WorkSource
-     */
-    default void getNeighboringCids(Message response, WorkSource workSource){}
-
-    /**
      * Mutes or unmutes the microphone for the active call. The microphone
      * is automatically unmuted if a call is answered, dialed, or resumed
      * from a holding state.
diff --git a/src/java/com/android/internal/telephony/PhoneNotifier.java b/src/java/com/android/internal/telephony/PhoneNotifier.java
index 5c8faa6..30b53d7 100644
--- a/src/java/com/android/internal/telephony/PhoneNotifier.java
+++ b/src/java/com/android/internal/telephony/PhoneNotifier.java
@@ -17,8 +17,11 @@
 package com.android.internal.telephony;
 
 import android.telephony.CellInfo;
+import android.telephony.CellLocation;
+import android.telephony.PhoneCapability;
 import android.telephony.PhysicalChannelConfig;
-import android.telephony.VoLteServiceState;
+import android.telephony.TelephonyManager;
+import android.telephony.emergency.EmergencyNumber;
 
 import java.util.List;
 
@@ -31,7 +34,8 @@
 
     public void notifyServiceState(Phone sender);
 
-    public void notifyCellLocation(Phone sender);
+    /** Notify registrants of the current CellLocation */
+    void notifyCellLocation(Phone sender, CellLocation cl);
 
     public void notifySignalStrength(Phone sender);
 
@@ -61,7 +65,8 @@
     public void notifyPreciseDataConnectionFailed(Phone sender, String reason, String apnType,
             String apn, String failCause);
 
-    public void notifyVoLteServiceStateChanged(Phone sender, VoLteServiceState lteState);
+    /** send a notification that the SRVCC state has changed.*/
+    void notifySrvccStateChanged(Phone sender, @TelephonyManager.SrvccState int state);
 
     public void notifyVoiceActivationStateChanged(Phone sender, int activationState);
 
@@ -70,4 +75,11 @@
     public void notifyUserMobileDataStateChanged(Phone sender, boolean state);
 
     public void notifyOemHookRawEventForSubscriber(int subId, byte[] rawData);
+
+    public void notifyPhoneCapabilityChanged(PhoneCapability capability);
+
+    void notifyRadioPowerStateChanged(@TelephonyManager.RadioPowerState int state);
+
+    /** Notify of change to EmergencyNumberList. */
+    void notifyEmergencyNumberList(Phone sender, List<EmergencyNumber> emergencyNumberList);
 }
diff --git a/src/java/com/android/internal/telephony/PhoneSubInfoController.java b/src/java/com/android/internal/telephony/PhoneSubInfoController.java
index a371191..297dc84 100644
--- a/src/java/com/android/internal/telephony/PhoneSubInfoController.java
+++ b/src/java/com/android/internal/telephony/PhoneSubInfoController.java
@@ -25,6 +25,7 @@
 import android.app.AppOpsManager;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.os.Binder;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.telephony.ImsiEncryptionInfo;
@@ -60,76 +61,35 @@
     }
 
     public String getDeviceIdForPhone(int phoneId, String callingPackage) {
-        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");
-            return null;
-        }
+        return callPhoneMethodForPhoneIdWithReadCheck(phoneId, callingPackage,
+                "getDeviceId", (phone)-> phone.getDeviceId());
     }
 
     public String getNaiForSubscriber(int subId, String callingPackage) {
-        Phone phone = getPhone(subId);
-        if (phone != null) {
-            if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
-                    mContext, subId, callingPackage, "getNai")) {
-                return null;
-            }
-            return phone.getNai();
-        } else {
-            loge("getNai phone is null for Subscription:" + subId);
-            return null;
-        }
+        return callPhoneMethodForSubIdWithReadCheck(subId, callingPackage, "getNai",
+                (phone)-> phone.getNai());
     }
 
     public String getImeiForSubscriber(int subId, String callingPackage) {
-        Phone phone = getPhone(subId);
-        if (phone != null) {
-            if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
-                    mContext, subId, callingPackage, "getImei")) {
-                return null;
-            }
-            return phone.getImei();
-        } else {
-            loge("getDeviceId phone is null for Subscription:" + subId);
-            return null;
-        }
+        return callPhoneMethodForSubIdWithReadCheck(subId, callingPackage, "getImei",
+                (phone)-> phone.getImei());
     }
 
     public ImsiEncryptionInfo getCarrierInfoForImsiEncryption(int subId, int keyType,
                                                               String callingPackage) {
-        Phone phone = getPhone(subId);
-        if (phone != null) {
-            if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
-                    mContext, subId, callingPackage,
-                    "getCarrierInfoForImsiEncryption")) {
-                return null;
-            }
-            return phone.getCarrierInfoForImsiEncryption(keyType);
-        } else {
-            loge("getCarrierInfoForImsiEncryption phone is null for Subscription:" + subId);
-            return null;
-        }
+        return callPhoneMethodForSubIdWithReadCheck(subId, callingPackage,
+                "getCarrierInfoForImsiEncryption",
+                (phone)-> phone.getCarrierInfoForImsiEncryption(keyType));
     }
 
     public void setCarrierInfoForImsiEncryption(int subId, String callingPackage,
                                                 ImsiEncryptionInfo imsiEncryptionInfo) {
-        Phone phone = getPhone(subId);
-        if (phone != null) {
-            enforceModifyPermission();
-            phone.setCarrierInfoForImsiEncryption(imsiEncryptionInfo);
-        } else {
-            loge("setCarrierInfoForImsiEncryption phone is null for Subscription:" + subId);
-            return;
-        }
+        callPhoneMethodForSubIdWithModifyCheck(subId, callingPackage,
+                "setCarrierInfoForImsiEncryption",
+                (phone)-> {
+                    phone.setCarrierInfoForImsiEncryption(imsiEncryptionInfo);
+                    return null;
+                });
     }
 
     /**
@@ -140,15 +100,12 @@
      *  @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;
-        }
+        callPhoneMethodForSubIdWithModifyCheck(subId, callingPackage,
+                "setCarrierInfoForImsiEncryption",
+                (phone)-> {
+                    phone.resetCarrierKeysForImsiEncryption();
+                    return null;
+                });
     }
 
 
@@ -157,17 +114,8 @@
     }
 
     public String getDeviceSvnUsingSubId(int subId, String callingPackage) {
-        Phone phone = getPhone(subId);
-        if (phone != null) {
-            if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
-                    mContext, subId, callingPackage, "getDeviceSvn")) {
-                return null;
-            }
-            return phone.getDeviceSvn();
-        } else {
-            loge("getDeviceSvn phone is null");
-            return null;
-        }
+        return callPhoneMethodForSubIdWithReadCheck(subId, callingPackage, "getDeviceSvn",
+                (phone)-> phone.getDeviceSvn());
     }
 
     public String getSubscriberId(String callingPackage) {
@@ -175,17 +123,8 @@
     }
 
     public String getSubscriberIdForSubscriber(int subId, String callingPackage) {
-        Phone phone = getPhone(subId);
-        if (phone != null) {
-            if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
-                    mContext, subId, callingPackage, "getSubscriberId")) {
-                return null;
-            }
-            return phone.getSubscriberId();
-        } else {
-            loge("getSubscriberId phone is null for Subscription:" + subId);
-            return null;
-        }
+        return callPhoneMethodForSubIdWithReadCheck(subId, callingPackage, "getSubscriberId",
+                (phone)-> phone.getSubscriberId());
     }
 
     /**
@@ -196,17 +135,8 @@
     }
 
     public String getIccSerialNumberForSubscriber(int subId, String callingPackage) {
-        Phone phone = getPhone(subId);
-        if (phone != null) {
-            if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
-                    mContext, subId, callingPackage, "getIccSerialNumber")) {
-                return null;
-            }
-            return phone.getIccSerialNumber();
-        } else {
-            loge("getIccSerialNumber phone is null for Subscription:" + subId);
-            return null;
-        }
+        return callPhoneMethodForSubIdWithReadCheck(subId, callingPackage, "getIccSerialNumber",
+                (phone)-> phone.getIccSerialNumber());
     }
 
     public String getLine1Number(String callingPackage) {
@@ -214,18 +144,9 @@
     }
 
     public String getLine1NumberForSubscriber(int subId, String callingPackage) {
-        Phone phone = getPhone(subId);
-        if (phone != null) {
-            // This is open to apps with WRITE_SMS.
-            if (!TelephonyPermissions.checkCallingOrSelfReadPhoneNumber(
-                    mContext, subId, callingPackage, "getLine1Number")) {
-                return null;
-            }
-            return phone.getLine1Number();
-        } else {
-            loge("getLine1Number phone is null for Subscription:" + subId);
-            return null;
-        }
+        return callPhoneMethodForSubIdWithReadPhoneNumberCheck(
+                subId, callingPackage, "getLine1Number",
+                (phone)-> phone.getLine1Number());
     }
 
     public String getLine1AlphaTag(String callingPackage) {
@@ -233,17 +154,8 @@
     }
 
     public String getLine1AlphaTagForSubscriber(int subId, String callingPackage) {
-        Phone phone = getPhone(subId);
-        if (phone != null) {
-            if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
-                    mContext, subId, callingPackage, "getLine1AlphaTag")) {
-                return null;
-            }
-            return phone.getLine1AlphaTag();
-        } else {
-            loge("getLine1AlphaTag phone is null for Subscription:" + subId);
-            return null;
-        }
+        return callPhoneMethodForSubIdWithReadCheck(subId, callingPackage, "getLine1AlphaTag",
+                (phone)-> phone.getLine1AlphaTag());
     }
 
     public String getMsisdn(String callingPackage) {
@@ -251,17 +163,8 @@
     }
 
     public String getMsisdnForSubscriber(int subId, String callingPackage) {
-        Phone phone = getPhone(subId);
-        if (phone != null) {
-            if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
-                    mContext, subId, callingPackage, "getMsisdn")) {
-                return null;
-            }
-            return phone.getMsisdn();
-        } else {
-            loge("getMsisdn phone is null for Subscription:" + subId);
-            return null;
-        }
+        return callPhoneMethodForSubIdWithReadCheck(subId, callingPackage, "getMsisdn",
+                (phone)-> phone.getMsisdn());
     }
 
     public String getVoiceMailNumber(String callingPackage) {
@@ -269,19 +172,13 @@
     }
 
     public String getVoiceMailNumberForSubscriber(int subId, String callingPackage) {
-        Phone phone = getPhone(subId);
-        if (phone != null) {
-            if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
-                    mContext, subId, callingPackage, "getVoiceMailNumber")) {
-                return null;
-            }
-            String number = PhoneNumberUtils.extractNetworkPortion(phone.getVoiceMailNumber());
-            if (VDBG) log("VM: getVoiceMailNUmber: " + number);
-            return number;
-        } else {
-            loge("getVoiceMailNumber phone is null for Subscription:" + subId);
-            return null;
-        }
+        return callPhoneMethodForSubIdWithReadCheck(subId, callingPackage,
+                "getVoiceMailNumber", (phone)-> {
+                    String number = PhoneNumberUtils.extractNetworkPortion(
+                            phone.getVoiceMailNumber());
+                    if (VDBG) log("VM: getVoiceMailNUmber: " + number);
+                    return number;
+                });
     }
 
     // TODO: change getCompleteVoiceMailNumber() to require READ_PRIVILEGED_PHONE_STATE
@@ -307,17 +204,8 @@
     }
 
     public String getVoiceMailAlphaTagForSubscriber(int subId, String callingPackage) {
-        Phone phone = getPhone(subId);
-        if (phone != null) {
-            if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
-                    mContext, subId, callingPackage, "getVoiceMailAlphaTag")) {
-                return null;
-            }
-            return phone.getVoiceMailAlphaTag();
-        } else {
-            loge("getVoiceMailAlphaTag phone is null for Subscription:" + subId);
-            return null;
-        }
+        return callPhoneMethodForSubIdWithReadCheck(subId, callingPackage,
+                "getVoiceMailAlphaTag", (phone)-> phone.getVoiceMailAlphaTag());
     }
 
     /**
@@ -337,6 +225,9 @@
      * @throws SecurityException if the caller does not have the required permission/privilege
      */
     private void enforcePrivilegedPermissionOrCarrierPrivilege(int subId, String message) {
+        // TODO(b/73660190): Migrate to
+        // TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivileges and delete
+        // this helper method.
         int permissionResult = mContext.checkCallingOrSelfPermission(
                 READ_PRIVILEGED_PHONE_STATE);
         if (permissionResult == PackageManager.PERMISSION_GRANTED) {
@@ -358,151 +249,216 @@
         return  PhoneFactory.getDefaultSubscription();
     }
 
-
     /**
     * get the Isim Impi based on subId
     */
     public String getIsimImpi(int subId) {
-        Phone phone = getPhone(subId);
-        if (phone != null) {
-            mContext.enforceCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE,
-                    "Requires READ_PRIVILEGED_PHONE_STATE");
-            IsimRecords isim = phone.getIsimRecords();
-            if (isim != null) {
-                return isim.getIsimImpi();
-            } else {
-                return null;
-            }
-        } else {
-            loge("getIsimImpi phone is null for Subscription:" + subId);
-            return null;
-        }
+        return callPhoneMethodForSubIdWithPrivilegedCheck(subId, "getIsimImpi",
+                (phone) -> {
+                    IsimRecords isim = phone.getIsimRecords();
+                    if (isim != null) {
+                        return isim.getIsimImpi();
+                    } else {
+                        return null;
+                    }
+                });
     }
 
     /**
     * get the Isim Domain based on subId
     */
     public String getIsimDomain(int subId) {
-        Phone phone = getPhone(subId);
-        if (phone != null) {
-            mContext.enforceCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE,
-                    "Requires READ_PRIVILEGED_PHONE_STATE");
-            IsimRecords isim = phone.getIsimRecords();
-            if (isim != null) {
-                return isim.getIsimDomain();
-            } else {
-                return null;
-            }
-        } else {
-            loge("getIsimDomain phone is null for Subscription:" + subId);
-            return null;
-        }
+        return callPhoneMethodForSubIdWithPrivilegedCheck(subId, "getIsimDomain",
+                (phone) -> {
+                    IsimRecords isim = phone.getIsimRecords();
+                    if (isim != null) {
+                        return isim.getIsimDomain();
+                    } else {
+                        return null;
+                    }
+                });
     }
 
     /**
     * get the Isim Impu based on subId
     */
     public String[] getIsimImpu(int subId) {
-        Phone phone = getPhone(subId);
-        if (phone != null) {
-            mContext.enforceCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE,
-                    "Requires READ_PRIVILEGED_PHONE_STATE");
-            IsimRecords isim = phone.getIsimRecords();
-            if (isim != null) {
-                return isim.getIsimImpu();
-            } else {
-                return null;
-            }
-        } else {
-            loge("getIsimImpu phone is null for Subscription:" + subId);
-            return null;
-        }
+        return callPhoneMethodForSubIdWithPrivilegedCheck(subId, "getIsimImpu",
+                (phone) -> {
+                    IsimRecords isim = phone.getIsimRecords();
+                    if (isim != null) {
+                        return isim.getIsimImpu();
+                    } else {
+                        return null;
+                    }
+                });
     }
 
     /**
     * get the Isim Ist based on subId
     */
     public String getIsimIst(int subId) throws RemoteException {
-        Phone phone = getPhone(subId);
-        if (phone != null) {
-            mContext.enforceCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE,
-                    "Requires READ_PRIVILEGED_PHONE_STATE");
-            IsimRecords isim = phone.getIsimRecords();
-            if (isim != null) {
-                return isim.getIsimIst();
-            } else {
-                return null;
-            }
-        } else {
-            loge("getIsimIst phone is null for Subscription:" + subId);
-            return null;
-        }
+        return callPhoneMethodForSubIdWithPrivilegedCheck(subId, "getIsimIst",
+                (phone) -> {
+                    IsimRecords isim = phone.getIsimRecords();
+                    if (isim != null) {
+                        return isim.getIsimIst();
+                    } else {
+                        return null;
+                    }
+                });
     }
 
     /**
     * get the Isim Pcscf based on subId
     */
     public String[] getIsimPcscf(int subId) throws RemoteException {
-        Phone phone = getPhone(subId);
-        if (phone != null) {
-            mContext.enforceCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE,
-                    "Requires READ_PRIVILEGED_PHONE_STATE");
-            IsimRecords isim = phone.getIsimRecords();
-            if (isim != null) {
-                return isim.getIsimPcscf();
-            } else {
-                return null;
-            }
-        } else {
-            loge("getIsimPcscf phone is null for Subscription:" + subId);
-            return null;
-        }
+        return callPhoneMethodForSubIdWithPrivilegedCheck(subId, "getIsimPcscf",
+                (phone) -> {
+                    IsimRecords isim = phone.getIsimRecords();
+                    if (isim != null) {
+                        return isim.getIsimPcscf();
+                    } else {
+                        return null;
+                    }
+                });
     }
 
     public String getIccSimChallengeResponse(int subId, int appType, int authType, String data)
             throws RemoteException {
-        // TODO(b/73660190): Migrate to
-        // TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivileges and delete
-        // this helper method.
-        enforcePrivilegedPermissionOrCarrierPrivilege(subId, "getIccSimChallengeResponse");
-        Phone phone = getPhone(subId);
-        UiccCard uiccCard = phone.getUiccCard();
-        if (uiccCard == null) {
-            loge("getIccSimChallengeResponse() UiccCard is null");
-            return null;
-        }
+        CallPhoneMethodHelper<String> toExecute = (phone)-> {
+            UiccCard uiccCard = phone.getUiccCard();
+            if (uiccCard == null) {
+                loge("getIccSimChallengeResponse() UiccCard is null");
+                return null;
+            }
 
-        UiccCardApplication uiccApp = uiccCard.getApplicationByType(appType);
-        if (uiccApp == null) {
-            loge("getIccSimChallengeResponse() no app with specified type -- " +
-                    appType);
-            return null;
-        } else {
-            loge("getIccSimChallengeResponse() found app " + uiccApp.getAid()
-                    + " specified type -- " + appType);
-        }
+            UiccCardApplication uiccApp = uiccCard.getApplicationByType(appType);
+            if (uiccApp == null) {
+                loge("getIccSimChallengeResponse() no app with specified type -- " + appType);
+                return null;
+            } else {
+                loge("getIccSimChallengeResponse() found app " + uiccApp.getAid()
+                        + " specified type -- " + appType);
+            }
 
-        if(authType != UiccCardApplication.AUTH_CONTEXT_EAP_SIM &&
-                authType != UiccCardApplication.AUTH_CONTEXT_EAP_AKA) {
-            loge("getIccSimChallengeResponse() unsupported authType: " + authType);
-            return null;
-        }
+            if (authType != UiccCardApplication.AUTH_CONTEXT_EAP_SIM
+                    && authType != UiccCardApplication.AUTH_CONTEXT_EAP_AKA) {
+                loge("getIccSimChallengeResponse() unsupported authType: " + authType);
+                return null;
+            }
+            return uiccApp.getIccRecords().getIccSimChallengeResponse(authType, data);
+        };
 
-        return uiccApp.getIccRecords().getIccSimChallengeResponse(authType, data);
+        return callPhoneMethodWithPermissionCheck(
+                subId, null, "getIccSimChallengeResponse", toExecute,
+                (aContext, aSubId, aCallingPackage, aMessage)-> {
+                    enforcePrivilegedPermissionOrCarrierPrivilege(aSubId, aMessage);
+                    return true;
+                });
     }
 
     public String getGroupIdLevel1ForSubscriber(int subId, String callingPackage) {
-        Phone phone = getPhone(subId);
-        if (phone != null) {
-            if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
-                    mContext, subId, callingPackage, "getGroupIdLevel1")) {
+        return callPhoneMethodForSubIdWithReadCheck(subId, callingPackage,
+                "getGroupIdLevel1", (phone)-> phone.getGroupIdLevel1());
+    }
+
+    /** Below are utility methods that abstracts the flow that many public methods use:
+     *  1. Check permission: pass, throw exception, or fails (returns false).
+     *  2. clearCallingIdentity.
+     *  3. Call a specified phone method and get return value.
+     *  4. restoreCallingIdentity and return.
+     */
+    private interface CallPhoneMethodHelper<T> {
+        T callMethod(Phone phone);
+    }
+
+    private interface PermissionCheckHelper {
+        // Implemented to do whatever permission check it wants.
+        // If passes, it should return true.
+        // If permission is not granted, throws SecurityException.
+        // If permission is revoked by AppOps, return false.
+        boolean checkPermission(Context context, int subId, String callingPackage, String message);
+    }
+
+    // Base utility method that others use.
+    private <T> T callPhoneMethodWithPermissionCheck(int subId, String callingPackage,
+            String message, CallPhoneMethodHelper<T> callMethodHelper,
+            PermissionCheckHelper permissionCheckHelper) {
+        if (!permissionCheckHelper.checkPermission(mContext, subId, callingPackage, message)) {
+            return null;
+        }
+
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            Phone phone = getPhone(subId);
+            if (phone != null) {
+                return callMethodHelper.callMethod(phone);
+            } else {
+                loge(message + " phone is null for Subscription:" + subId);
                 return null;
             }
-            return phone.getGroupIdLevel1();
-        } else {
-            loge("getGroupIdLevel1 phone is null for Subscription:" + subId);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    private <T> T callPhoneMethodForSubIdWithReadCheck(int subId, String callingPackage,
+            String message, CallPhoneMethodHelper<T> callMethodHelper) {
+        return callPhoneMethodWithPermissionCheck(subId, callingPackage, message, callMethodHelper,
+                (aContext, aSubId, aCallingPackage, aMessage)->
+                        TelephonyPermissions.checkCallingOrSelfReadPhoneState(
+                                aContext, aSubId, aCallingPackage, aMessage));
+    }
+
+    private <T> T callPhoneMethodForSubIdWithPrivilegedCheck(
+            int subId, String message, CallPhoneMethodHelper<T> callMethodHelper) {
+        return callPhoneMethodWithPermissionCheck(subId, null, message, callMethodHelper,
+                (aContext, aSubId, aCallingPackage, aMessage)-> {
+                    mContext.enforceCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE, message);
+                    return true;
+                });
+    }
+
+    private <T> T callPhoneMethodForSubIdWithModifyCheck(int subId, String callingPackage,
+            String message, CallPhoneMethodHelper<T> callMethodHelper) {
+        return callPhoneMethodWithPermissionCheck(subId, null, message, callMethodHelper,
+                (aContext, aSubId, aCallingPackage, aMessage)-> {
+                    enforceModifyPermission();
+                    return true;
+                });
+    }
+
+    private <T> T callPhoneMethodForSubIdWithReadPhoneNumberCheck(int subId, String callingPackage,
+            String message, CallPhoneMethodHelper<T> callMethodHelper) {
+        return callPhoneMethodWithPermissionCheck(subId, callingPackage, message, callMethodHelper,
+                (aContext, aSubId, aCallingPackage, aMessage)->
+                        TelephonyPermissions.checkCallingOrSelfReadPhoneNumber(
+                                aContext, aSubId, aCallingPackage, aMessage));
+    }
+
+    private <T> T callPhoneMethodForPhoneIdWithReadCheck(int phoneId, String callingPackage,
+            String message, CallPhoneMethodHelper<T> callMethodHelper) {
+        // Getting subId before doing permission check.
+        if (!SubscriptionManager.isValidPhoneId(phoneId)) {
+            phoneId = 0;
+        }
+        final Phone phone = mPhone[phoneId];
+        if (phone == null) {
             return null;
         }
+
+        if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
+                mContext, phone.getSubId(), callingPackage, message)) {
+            return null;
+        }
+
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            return callMethodHelper.callMethod(phone);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
     }
 
     private void log(String s) {
diff --git a/src/java/com/android/internal/telephony/PhoneSwitcher.java b/src/java/com/android/internal/telephony/PhoneSwitcher.java
index cd28b2b..75dd049 100644
--- a/src/java/com/android/internal/telephony/PhoneSwitcher.java
+++ b/src/java/com/android/internal/telephony/PhoneSwitcher.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.telephony;
 
+import static android.telephony.PhoneStateListener.LISTEN_PHONE_CAPABILITY_CHANGE;
 import static android.telephony.SubscriptionManager.INVALID_PHONE_INDEX;
 import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
 
@@ -35,7 +36,11 @@
 import android.os.Registrant;
 import android.os.RegistrantList;
 import android.os.RemoteException;
+import android.telephony.PhoneCapability;
+import android.telephony.PhoneStateListener;
 import android.telephony.Rlog;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
 import android.util.LocalLog;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -60,9 +65,8 @@
     private final static String LOG_TAG = "PhoneSwitcher";
     private final static boolean VDBG = false;
 
-    private final int mMaxActivePhones;
     private final List<DcRequest> mPrioritizedDcRequests = new ArrayList<DcRequest>();
-    private final RegistrantList[] mActivePhoneRegistrants;
+    private final RegistrantList mActivePhoneRegistrants;
     private final SubscriptionController mSubscriptionController;
     private final int[] mPhoneSubscriptions;
     private final CommandsInterface[] mCommandsInterfaces;
@@ -71,18 +75,67 @@
     private final int mNumPhones;
     private final Phone[] mPhones;
     private final LocalLog mLocalLog;
+    private final PhoneStateListener mPhoneStateListener;
 
-    private int mDefaultDataSubscription;
+    private int mMaxActivePhones;
+    private static PhoneSwitcher sPhoneSwitcher = null;
 
-    private final static int EVENT_DEFAULT_SUBSCRIPTION_CHANGED = 101;
-    private final static int EVENT_SUBSCRIPTION_CHANGED         = 102;
-    private final static int EVENT_REQUEST_NETWORK              = 103;
-    private final static int EVENT_RELEASE_NETWORK              = 104;
-    private final static int EVENT_EMERGENCY_TOGGLE             = 105;
-    private final static int EVENT_RESEND_DATA_ALLOWED          = 106;
+    // Default subscription ID from user setting.
+    private int mDefaultDataSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+
+    // If mPreferredDataSubId is an active subscription, it overrides
+    // mDefaultDataSubId and decides:
+    // 1. In modem layer, which subscription is preferred to have data traffic on.
+    // 2. In TelephonyNetworkFactory, which subscription will apply default network requests, which
+    //    are requests without specifying a subId.
+    private int mPreferredDataSubId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
+
+    @VisibleForTesting
+    // Corresponding phoneId after considerting mPreferredDataSubId and mDefaultDataSubId above.
+    protected int mPreferredDataPhoneId = SubscriptionManager.INVALID_PHONE_INDEX;
+
+    private static final int EVENT_DEFAULT_SUBSCRIPTION_CHANGED   = 101;
+    private static final int EVENT_SUBSCRIPTION_CHANGED           = 102;
+    private static final int EVENT_REQUEST_NETWORK                = 103;
+    private static final int EVENT_RELEASE_NETWORK                = 104;
+    private static final int EVENT_EMERGENCY_TOGGLE               = 105;
+    private static final int EVENT_RADIO_CAPABILITY_CHANGED       = 106;
+    private static final int EVENT_PREFERRED_SUBSCRIPTION_CHANGED = 107;
+    private static final int EVENT_RADIO_AVAILABLE                = 108;
+
+    // Depending on version of IRadioConfig, we need to send either RIL_REQUEST_ALLOW_DATA if it's
+    // 1.0, or RIL_REQUEST_SET_PREFERRED_DATA if it's 1.1 or later. So internally mHalCommandToUse
+    // will be either HAL_COMMAND_ALLOW_DATA or HAL_COMMAND_ALLOW_DATA or HAL_COMMAND_UNKNOWN.
+    private static final int HAL_COMMAND_UNKNOWN        = 0;
+    private static final int HAL_COMMAND_ALLOW_DATA     = 1;
+    private static final int HAL_COMMAND_PREFERRED_DATA = 2;
+    private int mHalCommandToUse = HAL_COMMAND_UNKNOWN;
+
+    private RadioConfig mRadioConfig;
 
     private final static int MAX_LOCAL_LOG_LINES = 30;
 
+    /**
+     * Method to get singleton instance.
+     */
+    public static PhoneSwitcher getInstance() {
+        return sPhoneSwitcher;
+    }
+
+    /**
+     * Method to create singleton instance.
+     */
+    public static PhoneSwitcher make(int maxActivePhones, int numPhones, Context context,
+            SubscriptionController subscriptionController, Looper looper, ITelephonyRegistry tr,
+            CommandsInterface[] cis, Phone[] phones) {
+        if (sPhoneSwitcher == null) {
+            sPhoneSwitcher = new PhoneSwitcher(maxActivePhones, numPhones, context,
+                    subscriptionController, looper, tr, cis, phones);
+        }
+
+        return sPhoneSwitcher;
+    }
+
     @VisibleForTesting
     public PhoneSwitcher(Looper looper) {
         super(looper);
@@ -96,8 +149,15 @@
         mLocalLog = null;
         mActivePhoneRegistrants = null;
         mNumPhones = 0;
+        mRadioConfig = RadioConfig.getInstance(mContext);
+        mPhoneStateListener = new PhoneStateListener(looper) {
+            public void onPhoneCapabilityChanged(PhoneCapability capability) {
+                onPhoneCapabilityChangedInternal(capability);
+            }
+        };
     }
 
+    @VisibleForTesting
     public PhoneSwitcher(int maxActivePhones, int numPhones, Context context,
             SubscriptionController subscriptionController, Looper looper, ITelephonyRegistry tr,
             CommandsInterface[] cis, Phone[] phones) {
@@ -110,11 +170,21 @@
         mLocalLog = new LocalLog(MAX_LOCAL_LOG_LINES);
 
         mSubscriptionController = subscriptionController;
+        mRadioConfig = RadioConfig.getInstance(mContext);
 
-        mActivePhoneRegistrants = new RegistrantList[numPhones];
+        mPhoneStateListener = new PhoneStateListener(looper) {
+            public void onPhoneCapabilityChanged(PhoneCapability capability) {
+                onPhoneCapabilityChangedInternal(capability);
+            }
+        };
+
+        TelephonyManager telephonyManager =
+                (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+        telephonyManager.listen(mPhoneStateListener, LISTEN_PHONE_CAPABILITY_CHANGE);
+
+        mActivePhoneRegistrants = new RegistrantList();
         mPhoneStates = new PhoneState[numPhones];
         for (int i = 0; i < numPhones; i++) {
-            mActivePhoneRegistrants[i] = new RegistrantList();
             mPhoneStates[i] = new PhoneState();
             if (mPhones[i] != null) {
                 mPhones[i].registerForEmergencyCallToggle(this, EVENT_EMERGENCY_TOGGLE, null);
@@ -123,6 +193,7 @@
 
         mCommandsInterfaces = cis;
 
+        mCommandsInterfaces[0].registerForAvailable(this, EVENT_RADIO_AVAILABLE, null);
         try {
             tr.addOnSubscriptionsChangedListener(context.getOpPackageName(),
                     mSubscriptionsChangedListener);
@@ -197,8 +268,17 @@
                 onEvaluate(REQUESTS_CHANGED, "emergencyToggle");
                 break;
             }
-            case EVENT_RESEND_DATA_ALLOWED: {
-                onResendDataAllowed(msg);
+            case EVENT_RADIO_CAPABILITY_CHANGED: {
+                resendRilCommands(msg);
+                break;
+            }
+            case EVENT_PREFERRED_SUBSCRIPTION_CHANGED: {
+                onEvaluate(REQUESTS_UNCHANGED, "preferredDataSubIdChanged");
+                break;
+            }
+            case EVENT_RADIO_AVAILABLE: {
+                updateHalCommandToUse();
+                onEvaluate(REQUESTS_UNCHANGED, "EVENT_RADIO_AVAILABLE");
                 break;
             }
         }
@@ -272,15 +352,17 @@
             return;
         }
 
-        boolean diffDetected = requestsChanged;
-        final int dataSub = mSubscriptionController.getDefaultDataSubId();
-        if (dataSub != mDefaultDataSubscription) {
-            sb.append(" default ").append(mDefaultDataSubscription).append("->").append(dataSub);
-            mDefaultDataSubscription = dataSub;
-            diffDetected = true;
+        // If we use HAL_COMMAND_PREFERRED_DATA,
+        boolean diffDetected = mHalCommandToUse != HAL_COMMAND_PREFERRED_DATA && requestsChanged;
 
+        // Check if user setting of default data sub is changed.
+        final int dataSub = mSubscriptionController.getDefaultDataSubId();
+        if (dataSub != mDefaultDataSubId) {
+            sb.append(" default ").append(mDefaultDataSubId).append("->").append(dataSub);
+            mDefaultDataSubId = dataSub;
         }
 
+        // Check if phoneId to subId mapping is changed.
         for (int i = 0; i < mNumPhones; i++) {
             int sub = mSubscriptionController.getSubIdUsingPhoneId(i);
             if (sub != mPhoneSubscriptions[i]) {
@@ -291,38 +373,72 @@
             }
         }
 
+        // Check if phoneId for preferred data is changed.
+        int oldPreferredDataPhoneId = mPreferredDataPhoneId;
+        updatePhoneIdForDefaultNetworkRequests();
+        if (oldPreferredDataPhoneId != mPreferredDataPhoneId) {
+            sb.append(" preferred phoneId ").append(oldPreferredDataPhoneId)
+                    .append("->").append(mPreferredDataPhoneId);
+            diffDetected = true;
+        }
+
         if (diffDetected) {
             log("evaluating due to " + sb.toString());
-
-            List<Integer> newActivePhones = new ArrayList<Integer>();
-
-            for (DcRequest dcRequest : mPrioritizedDcRequests) {
-                int phoneIdForRequest = phoneIdForRequest(dcRequest.networkRequest);
-                if (phoneIdForRequest == INVALID_PHONE_INDEX) continue;
-                if (newActivePhones.contains(phoneIdForRequest)) continue;
-                newActivePhones.add(phoneIdForRequest);
-                if (newActivePhones.size() >= mMaxActivePhones) break;
-            }
-
-            if (VDBG) {
-                log("default subId = " + mDefaultDataSubscription);
-                for (int i = 0; i < mNumPhones; i++) {
-                    log(" phone[" + i + "] using sub[" + mPhoneSubscriptions[i] + "]");
+            if (mHalCommandToUse == HAL_COMMAND_PREFERRED_DATA) {
+                if (SubscriptionManager.isUsableSubIdValue(mPreferredDataPhoneId)) {
+                    mRadioConfig.setPreferredDataModem(mPreferredDataPhoneId, null);
                 }
-                log(" newActivePhones:");
-                for (Integer i : newActivePhones) log("  " + i);
-            }
+            } else {
+                List<Integer> newActivePhones = new ArrayList<Integer>();
 
-            for (int phoneId = 0; phoneId < mNumPhones; phoneId++) {
-                if (newActivePhones.contains(phoneId) == false) {
-                    deactivate(phoneId);
+                /**
+                 * If all phones can have PS attached, activate all.
+                 * Otherwise, choose to activate phones according to requests. And
+                 * if list is not full, add mPreferredDataPhoneId.
+                 */
+                if (mMaxActivePhones == mPhones.length) {
+                    for (int i = 0; i < mMaxActivePhones; i++) {
+                        newActivePhones.add(mPhones[i].mPhoneId);
+                    }
+                } else {
+                    for (DcRequest dcRequest : mPrioritizedDcRequests) {
+                        int phoneIdForRequest = phoneIdForRequest(dcRequest.networkRequest);
+                        if (phoneIdForRequest == INVALID_PHONE_INDEX) continue;
+                        if (newActivePhones.contains(phoneIdForRequest)) continue;
+                        newActivePhones.add(phoneIdForRequest);
+                        if (newActivePhones.size() >= mMaxActivePhones) break;
+                    }
+
+                    if (newActivePhones.size() < mMaxActivePhones
+                            && newActivePhones.contains(mPreferredDataPhoneId)
+                            && SubscriptionManager.isUsableSubIdValue(mPreferredDataPhoneId)) {
+                        newActivePhones.add(mPreferredDataPhoneId);
+                    }
+                }
+
+                if (VDBG) {
+                    log("default subId = " + mDefaultDataSubId);
+                    log("preferred subId = " + mPreferredDataSubId);
+                    for (int i = 0; i < mNumPhones; i++) {
+                        log(" phone[" + i + "] using sub[" + mPhoneSubscriptions[i] + "]");
+                    }
+                    log(" newActivePhones:");
+                    for (Integer i : newActivePhones) log("  " + i);
+                }
+
+                for (int phoneId = 0; phoneId < mNumPhones; phoneId++) {
+                    if (!newActivePhones.contains(phoneId)) {
+                        deactivate(phoneId);
+                    }
+                }
+
+                // only activate phones up to the limit
+                for (int phoneId : newActivePhones) {
+                    activate(phoneId);
                 }
             }
-
-            // only activate phones up to the limit
-            for (int phoneId : newActivePhones) {
-                activate(phoneId);
-            }
+            // Notify all registrants.
+            mActivePhoneRegistrants.notifyRegistrants();
         }
     }
 
@@ -331,56 +447,70 @@
         public long lastRequested = 0;
     }
 
-    private void deactivate(int phoneId) {
-        PhoneState state = mPhoneStates[phoneId];
-        if (state.active == false) return;
-        state.active = false;
-        log("deactivate " + phoneId);
-        state.lastRequested = System.currentTimeMillis();
-        // Skip ALLOW_DATA for single SIM device
-        if (mNumPhones > 1) {
-            mCommandsInterfaces[phoneId].setDataAllowed(false, null);
-        }
-        mActivePhoneRegistrants[phoneId].notifyRegistrants();
-    }
-
     private void activate(int phoneId) {
-        PhoneState state = mPhoneStates[phoneId];
-        if (state.active == true) return;
-        state.active = true;
-        log("activate " + phoneId);
-        state.lastRequested = System.currentTimeMillis();
-        // Skip ALLOW_DATA for single SIM device
-        if (mNumPhones > 1) {
-            mCommandsInterfaces[phoneId].setDataAllowed(true, null);
-        }
-        mActivePhoneRegistrants[phoneId].notifyRegistrants();
+        switchPhone(phoneId, true);
     }
 
-    // used when the modem may have been rebooted and we want to resend
-    // setDataAllowed
-    public void resendDataAllowed(int phoneId) {
+    private void deactivate(int phoneId) {
+        switchPhone(phoneId, false);
+    }
+
+    private void switchPhone(int phoneId, boolean active) {
+        PhoneState state = mPhoneStates[phoneId];
+        if (state.active == active) return;
+        state.active = active;
+        log((active ? "activate " : "deactivate ") + phoneId);
+        state.lastRequested = System.currentTimeMillis();
+        if (mHalCommandToUse == HAL_COMMAND_ALLOW_DATA || mHalCommandToUse == HAL_COMMAND_UNKNOWN) {
+            // Skip ALLOW_DATA for single SIM device
+            if (mNumPhones > 1) {
+                mCommandsInterfaces[phoneId].setDataAllowed(active, null);
+            }
+        }
+    }
+
+    /**
+     * Used when the modem may have been rebooted and we
+     * want to resend setDataAllowed or setPreferredData
+     */
+    public void onRadioCapChanged(int phoneId) {
         validatePhoneId(phoneId);
-        Message msg = obtainMessage(EVENT_RESEND_DATA_ALLOWED);
+        Message msg = obtainMessage(EVENT_RADIO_CAPABILITY_CHANGED);
         msg.arg1 = phoneId;
         msg.sendToTarget();
     }
 
-    private void onResendDataAllowed(Message msg) {
+    private void resendRilCommands(Message msg) {
         final int phoneId = msg.arg1;
-        // Skip ALLOW_DATA for single SIM device
-        if (mNumPhones > 1) {
-            mCommandsInterfaces[phoneId].setDataAllowed(mPhoneStates[phoneId].active, null);
+        if (mHalCommandToUse == HAL_COMMAND_ALLOW_DATA || mHalCommandToUse == HAL_COMMAND_UNKNOWN) {
+            // Skip ALLOW_DATA for single SIM device
+            if (mNumPhones > 1) {
+                mCommandsInterfaces[phoneId].setDataAllowed(mPhoneStates[phoneId].active, null);
+            }
+        } else {
+            mRadioConfig.setPreferredDataModem(mPreferredDataPhoneId, null);
+        }
+    }
+
+    private void onPhoneCapabilityChangedInternal(PhoneCapability capability) {
+        int newMaxActivePhones = TelephonyManager.getDefault()
+                .getNumberOfModemsWithSimultaneousDataConnections();
+        if (mMaxActivePhones != newMaxActivePhones) {
+            mMaxActivePhones = newMaxActivePhones;
+            log("Max active phones changed to " + mMaxActivePhones);
+            onEvaluate(REQUESTS_UNCHANGED, "phoneCfgChanged");
         }
     }
 
     private int phoneIdForRequest(NetworkRequest netRequest) {
         NetworkSpecifier specifier = netRequest.networkCapabilities.getNetworkSpecifier();
+        if (specifier == null) {
+            return mPreferredDataPhoneId;
+        }
+
         int subId;
 
-        if (specifier == null) {
-            subId = mDefaultDataSubscription;
-        } else if (specifier instanceof StringNetworkSpecifier) {
+        if (specifier instanceof StringNetworkSpecifier) {
             try {
                 subId = Integer.parseInt(((StringNetworkSpecifier) specifier).specifier);
             } catch (NumberFormatException e) {
@@ -404,21 +534,68 @@
         return phoneId;
     }
 
-    public boolean isPhoneActive(int phoneId) {
-        validatePhoneId(phoneId);
-        return mPhoneStates[phoneId].active;
+    private int getSubIdForDefaultNetworkRequests() {
+        if (mSubscriptionController.isActiveSubId(mPreferredDataSubId)) {
+            return mPreferredDataSubId;
+        } else {
+            return mDefaultDataSubId;
+        }
     }
 
-    public void registerForActivePhoneSwitch(int phoneId, Handler h, int what, Object o) {
+    // This updates mPreferredDataPhoneId which decides which phone should
+    // handle default network requests.
+    private void updatePhoneIdForDefaultNetworkRequests() {
+        int subId = getSubIdForDefaultNetworkRequests();
+        int phoneId = SubscriptionManager.INVALID_PHONE_INDEX;
+
+        if (SubscriptionManager.isUsableSubIdValue(subId)) {
+            for (int i = 0; i < mNumPhones; i++) {
+                if (mPhoneSubscriptions[i] == subId) {
+                    phoneId = i;
+                    break;
+                }
+            }
+        }
+
+        mPreferredDataPhoneId = phoneId;
+    }
+
+    /**
+     * Returns whether phone should handle network requests
+     * that don't specify a subId.
+     */
+    public boolean shouldApplyUnspecifiedRequests(int phoneId) {
         validatePhoneId(phoneId);
+        if (mHalCommandToUse == HAL_COMMAND_PREFERRED_DATA) {
+            return phoneId == mPreferredDataPhoneId;
+        } else {
+            return mPhoneStates[phoneId].active && phoneId == mPreferredDataPhoneId;
+        }
+    }
+
+    /**
+     * Returns whether phone should handle network requests
+     * that specify a subId.
+     */
+    public boolean shouldApplySpecifiedRequests(int phoneId) {
+        validatePhoneId(phoneId);
+        // If we use SET_PREFERRED_DATA, always apply specified network requests. Otherwise,
+        // only apply network requests if the phone is active (dataAllowed).
+        return mHalCommandToUse == HAL_COMMAND_PREFERRED_DATA || mPhoneStates[phoneId].active;
+    }
+
+    /**
+     * If preferred phone changes, or phone activation status changes, registrants
+     * will be notified.
+     */
+    public void registerForActivePhoneSwitch(Handler h, int what, Object o) {
         Registrant r = new Registrant(h, what, o);
-        mActivePhoneRegistrants[phoneId].add(r);
+        mActivePhoneRegistrants.add(r);
         r.notifyRegistrant();
     }
 
-    public void unregisterForActivePhoneSwitch(int phoneId, Handler h) {
-        validatePhoneId(phoneId);
-        mActivePhoneRegistrants[phoneId].remove(h);
+    public void unregisterForActivePhoneSwitch(Handler h) {
+        mActivePhoneRegistrants.remove(h);
     }
 
     private void validatePhoneId(int phoneId) {
@@ -427,6 +604,24 @@
         }
     }
 
+    /**
+     * Set a subscription as preferred data subscription.
+     * See {@link SubscriptionManager#setPreferredData(int)} for more details.
+     */
+    public void setPreferredData(int subId) {
+        if (mPreferredDataSubId != subId) {
+            log("setPreferredData subId changed to " + subId);
+            mPreferredDataSubId = subId;
+            Message msg = PhoneSwitcher.this.obtainMessage(EVENT_PREFERRED_SUBSCRIPTION_CHANGED);
+            msg.sendToTarget();
+        }
+    }
+
+    private void updateHalCommandToUse() {
+        mHalCommandToUse = mRadioConfig.isSetPreferredDataCommandSupported()
+                ? HAL_COMMAND_PREFERRED_DATA : HAL_COMMAND_ALLOW_DATA;
+    }
+
     private void log(String l) {
         Rlog.d(LOG_TAG, l);
         mLocalLog.log(l);
diff --git a/src/java/com/android/internal/telephony/ProxyController.java b/src/java/com/android/internal/telephony/ProxyController.java
index 1426bc7..7afdb7e 100644
--- a/src/java/com/android/internal/telephony/ProxyController.java
+++ b/src/java/com/android/internal/telephony/ProxyController.java
@@ -126,7 +126,7 @@
 
         mUiccPhoneBookController = new UiccPhoneBookController(mPhones);
         mPhoneSubInfoController = new PhoneSubInfoController(mContext, mPhones);
-        mUiccSmsController = new UiccSmsController();
+        mUiccSmsController = new UiccSmsController(mContext);
         mSetRadioAccessFamilyStatus = new int[mPhones.length];
         mNewRadioAccessFamily = new int[mPhones.length];
         mOldRadioAccessFamily = new int[mPhones.length];
@@ -147,28 +147,11 @@
         logd("Constructor - Exit");
     }
 
-    public void updateDataConnectionTracker(int sub) {
-        mPhones[sub].updateDataConnectionTracker();
-    }
-
-    public void enableDataConnectivity(int sub) {
-        mPhones[sub].setInternalDataEnabled(true, null);
-    }
-
-    public void disableDataConnectivity(int sub,
-            Message dataCleanedUpMsg) {
-        mPhones[sub].setInternalDataEnabled(false, dataCleanedUpMsg);
-    }
-
-    public void updateCurrentCarrierInProvider(int sub) {
-        mPhones[sub].updateCurrentCarrierInProvider();
-    }
-
-    public void registerForAllDataDisconnected(int subId, Handler h, int what, Object obj) {
+    public void registerForAllDataDisconnected(int subId, Handler h, int what) {
         int phoneId = SubscriptionController.getInstance().getPhoneId(subId);
 
         if (phoneId >= 0 && phoneId < TelephonyManager.getDefault().getPhoneCount()) {
-            mPhones[phoneId].registerForAllDataDisconnected(h, what, obj);
+            mPhones[phoneId].registerForAllDataDisconnected(h, what);
         }
     }
 
@@ -180,17 +163,6 @@
         }
     }
 
-    public boolean isDataDisconnected(int subId) {
-        int phoneId = SubscriptionController.getInstance().getPhoneId(subId);
-
-        if (phoneId >= 0 && phoneId < TelephonyManager.getDefault().getPhoneCount()) {
-            return mPhones[phoneId].mDcTracker.isDisconnected();
-        } else {
-            // if we can't find a phone for the given subId, it is disconnected.
-            return true;
-        }
-    }
-
     /**
      * Get phone radio type and access technology.
      *
@@ -454,7 +426,7 @@
                 logd("onNotificationRadioCapabilityChanged: phoneId=" + id + " status=SUCCESS");
                 mSetRadioAccessFamilyStatus[id] = SET_RC_STATUS_SUCCESS;
                 // The modems may have been restarted and forgotten this
-                mPhoneSwitcher.resendDataAllowed(id);
+                mPhoneSwitcher.onRadioCapChanged(id);
                 mPhones[id].radioCapabilityUpdated(rc);
             }
 
diff --git a/src/java/com/android/internal/telephony/RIL.java b/src/java/com/android/internal/telephony/RIL.java
index 9e74ee2..eb10904 100644
--- a/src/java/com/android/internal/telephony/RIL.java
+++ b/src/java/com/android/internal/telephony/RIL.java
@@ -29,9 +29,10 @@
 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;
+import android.hardware.radio.V1_0.DataProfileId;
 import android.hardware.radio.V1_0.Dial;
 import android.hardware.radio.V1_0.GsmBroadcastSmsConfigInfo;
 import android.hardware.radio.V1_0.GsmSmsMessage;
@@ -71,7 +72,6 @@
 import android.os.WorkSource;
 import android.service.carrier.CarrierIdentifier;
 import android.telephony.AccessNetworkConstants.AccessNetworkType;
-import android.telephony.CellIdentity;
 import android.telephony.CellIdentityCdma;
 import android.telephony.CellInfo;
 import android.telephony.CellSignalStrengthCdma;
@@ -100,6 +100,8 @@
 import com.android.internal.telephony.cat.ComprehensionTlvTag;
 import com.android.internal.telephony.cdma.CdmaInformationRecords;
 import com.android.internal.telephony.cdma.CdmaSmsBroadcastConfigInfo;
+import com.android.internal.telephony.dataconnection.TransportManager;
+import com.android.internal.telephony.dataconnection.TransportManager.IwlanOperationMode;
 import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
 import com.android.internal.telephony.metrics.TelephonyMetrics;
 import com.android.internal.telephony.nano.TelephonyProto.SmsSession;
@@ -115,7 +117,10 @@
 import java.net.InetAddress;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashSet;
 import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Set;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicLong;
 
@@ -150,6 +155,21 @@
     public static final int FOR_ACK_WAKELOCK = 1;
     private final ClientWakelockTracker mClientWakelockTracker = new ClientWakelockTracker();
 
+    private static final HalVersion RADIO_HAL_VERSION_UNKNOWN = new HalVersion(-1, -1);
+
+    private static final HalVersion RADIO_HAL_VERSION_1_0 = new HalVersion(1, 0);
+
+    private static final HalVersion RADIO_HAL_VERSION_1_1 = new HalVersion(1, 1);
+
+    private static final HalVersion RADIO_HAL_VERSION_1_2 = new HalVersion(1, 2);
+
+    private static final HalVersion RADIO_HAL_VERSION_1_3 = new HalVersion(1, 3);
+
+    private static final HalVersion RADIO_HAL_VERSION_1_4 = new HalVersion(1, 4);
+
+    // IRadio version
+    private HalVersion mRadioVersion = RADIO_HAL_VERSION_UNKNOWN;
+
     //***** Instance Variables
 
     final WakeLock mWakeLock;           // Wake lock associated with request/response
@@ -168,13 +188,25 @@
     static SparseArray<TelephonyHistogram> mRilTimeHistograms = new
             SparseArray<TelephonyHistogram>();
 
-    Object[]     mLastNITZTimeInfo;
+    Object[] mLastNITZTimeInfo;
 
     // When we are testing emergency calls
     AtomicBoolean mTestingEmergencyCall = new AtomicBoolean(false);
 
     final Integer mPhoneId;
 
+    /**
+     * A set that records if radio service is disabled in hal for
+     * a specific phone id slot to avoid further getService request.
+     */
+    Set<Integer> mDisabledRadioServices = new HashSet();
+
+    /**
+     * A set that records if oem hook service is disabled in hal for
+     * a specific phone id slot to avoid further getService request.
+     */
+    Set<Integer> mDisabledOemHookServices = new HashSet();
+
     /* default work source which will blame phone process */
     private WorkSource mRILDefaultWorkSource;
 
@@ -338,7 +370,7 @@
         // increment the cookie so that death notification can be ignored
         mRadioProxyCookie.incrementAndGet();
 
-        setRadioState(RadioState.RADIO_UNAVAILABLE);
+        setRadioState(TelephonyManager.RADIO_POWER_UNAVAILABLE, true /* forceNotifyRegistrants */);
 
         RILRequest.resetSerial();
         // Clear request list on close
@@ -350,7 +382,7 @@
 
     /** Returns a {@link IRadio} instance or null if the service is not available. */
     @VisibleForTesting
-    public IRadio getRadioProxy(Message result) {
+    public synchronized IRadio getRadioProxy(Message result) {
         if (!mIsMobileNetworkSupported) {
             if (RILJ_LOGV) riljLog("getRadioProxy: Not calling getService(): wifi-only");
             if (result != null) {
@@ -366,16 +398,64 @@
         }
 
         try {
-            mRadioProxy = IRadio.getService(HIDL_SERVICE_NAME[mPhoneId == null ? 0 : mPhoneId],
-                    true);
-            if (mRadioProxy != null) {
-                mRadioProxy.linkToDeath(mRadioProxyDeathRecipient,
-                        mRadioProxyCookie.incrementAndGet());
-                mRadioProxy.setResponseFunctions(mRadioResponse, mRadioIndication);
+            if (mDisabledRadioServices.contains(mPhoneId)) {
+                riljLoge("getRadioProxy: mRadioProxy for " + HIDL_SERVICE_NAME[mPhoneId]
+                        + " is disabled");
             } else {
-                riljLoge("getRadioProxy: mRadioProxy == null");
+                try {
+                    mRadioProxy = android.hardware.radio.V1_4.IRadio.getService(
+                            HIDL_SERVICE_NAME[mPhoneId], true);
+                    mRadioVersion = RADIO_HAL_VERSION_1_4;
+                } catch (NoSuchElementException e) {
+                }
+
+                if (mRadioProxy == null) {
+                    try {
+                        mRadioProxy = android.hardware.radio.V1_3.IRadio.getService(
+                                HIDL_SERVICE_NAME[mPhoneId], true);
+                        mRadioVersion = RADIO_HAL_VERSION_1_3;
+                    } catch (NoSuchElementException e) {
+                    }
+                }
+
+                if (mRadioProxy == null) {
+                    try {
+                        mRadioProxy = android.hardware.radio.V1_2.IRadio.getService(
+                                HIDL_SERVICE_NAME[mPhoneId], true);
+                        mRadioVersion = RADIO_HAL_VERSION_1_2;
+                    } catch (NoSuchElementException e) {
+                    }
+                }
+
+                if (mRadioProxy == null) {
+                    try {
+                        mRadioProxy = android.hardware.radio.V1_1.IRadio.getService(
+                                HIDL_SERVICE_NAME[mPhoneId], true);
+                        mRadioVersion = RADIO_HAL_VERSION_1_1;
+                    } catch (NoSuchElementException e) {
+                    }
+                }
+
+                if (mRadioProxy == null) {
+                    try {
+                        mRadioProxy = android.hardware.radio.V1_0.IRadio.getService(
+                                HIDL_SERVICE_NAME[mPhoneId], true);
+                        mRadioVersion = RADIO_HAL_VERSION_1_0;
+                    } catch (NoSuchElementException e) {
+                    }
+                }
+
+                if (mRadioProxy != null) {
+                    mRadioProxy.linkToDeath(mRadioProxyDeathRecipient,
+                            mRadioProxyCookie.incrementAndGet());
+                    mRadioProxy.setResponseFunctions(mRadioResponse, mRadioIndication);
+                } else {
+                    mDisabledRadioServices.add(mPhoneId);
+                    riljLoge("getRadioProxy: mRadioProxy for "
+                            + HIDL_SERVICE_NAME[mPhoneId] + " is disabled");
+                }
             }
-        } catch (RemoteException | RuntimeException e) {
+        } catch (RemoteException e) {
             mRadioProxy = null;
             riljLoge("RadioProxy getService/setResponseFunctions: " + e);
         }
@@ -395,7 +475,7 @@
 
     /** Returns an {@link IOemHook} instance or null if the service is not available. */
     @VisibleForTesting
-    public IOemHook getOemHookProxy(Message result) {
+    public synchronized IOemHook getOemHookProxy(Message result) {
         if (!mIsMobileNetworkSupported) {
             if (RILJ_LOGV) riljLog("getOemHookProxy: Not calling getService(): wifi-only");
             if (result != null) {
@@ -411,16 +491,25 @@
         }
 
         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);
+            if (mDisabledOemHookServices.contains(mPhoneId)) {
+                riljLoge("getOemHookProxy: mOemHookProxy for " + HIDL_SERVICE_NAME[mPhoneId]
+                        + " is disabled");
             } else {
-                riljLoge("getOemHookProxy: mOemHookProxy == null");
+                mOemHookProxy = IOemHook.getService(HIDL_SERVICE_NAME[mPhoneId], true);
+                if (mOemHookProxy != null) {
+                    // not calling linkToDeath() as ril service runs in the same process and death
+                    // notification for that should be sufficient
+                    mOemHookProxy.setResponseFunctions(mOemHookResponse, mOemHookIndication);
+                } else {
+                    mDisabledOemHookServices.add(mPhoneId);
+                    riljLoge("getOemHookProxy: mOemHookProxy for " + HIDL_SERVICE_NAME[mPhoneId]
+                            + " is disabled");
+                }
             }
-        } catch (RemoteException | RuntimeException e) {
+        } catch (NoSuchElementException e) {
+            mRadioProxy = null;
+            riljLoge("IOemHook service is not on the device HAL: " + e);
+        }  catch (RemoteException e) {
             mOemHookProxy = null;
             riljLoge("OemHookProxy getService/setResponseFunctions: " + e);
         }
@@ -454,7 +543,7 @@
         mCdmaSubscription  = cdmaSubscription;
         mPreferredNetworkType = preferredNetworkType;
         mPhoneType = RILConstants.NO_PHONE;
-        mPhoneId = instanceId;
+        mPhoneId = instanceId == null ? 0 : instanceId;
 
         ConnectivityManager cm = (ConnectivityManager)context.getSystemService(
                 Context.CONNECTIVITY_SERVICE);
@@ -479,6 +568,7 @@
         mWakeLockCount = 0;
         mRILDefaultWorkSource = new WorkSource(context.getApplicationInfo().uid,
                 context.getPackageName());
+        mActiveWakelockWorkSource = new WorkSource();
 
         TelephonyDevController tdc = TelephonyDevController.getInstance();
         tdc.registerRIL(this);
@@ -487,6 +577,10 @@
         // wakelock stuff is initialized above as callbacks are received on separate binder threads)
         getRadioProxy(null);
         getOemHookProxy(null);
+
+        if (RILJ_LOGD) {
+            riljLog("Radio HAL version: " + mRadioVersion);
+        }
     }
 
     @Override
@@ -1121,12 +1215,14 @@
     }
 
     /**
-     * Convert to DataProfileInfo defined in types.hal
+     * Convert to DataProfileInfo defined in radio/1.0/types.hal
      * @param dp Data profile
      * @return A converted data profile
      */
-    private static DataProfileInfo convertToHalDataProfile(DataProfile dp) {
-        DataProfileInfo dpi = new DataProfileInfo();
+    private static android.hardware.radio.V1_0.DataProfileInfo convertToHalDataProfile10(
+            DataProfile dp) {
+        android.hardware.radio.V1_0.DataProfileInfo dpi =
+                new android.hardware.radio.V1_0.DataProfileInfo();
 
         dpi.profileId = dp.getProfileId();
         dpi.apn = dp.getApn();
@@ -1143,8 +1239,41 @@
         dpi.supportedApnTypesBitmap = dp.getSupportedApnTypesBitmap();
         dpi.bearerBitmap = dp.getBearerBitmap();
         dpi.mtu = dp.getMtu();
-        dpi.mvnoType = convertToHalMvnoType(dp.getMvnoType());
-        dpi.mvnoMatchData = dp.getMvnoMatchData();
+        dpi.mvnoType = MvnoType.NONE;
+        dpi.mvnoMatchData = "";
+
+        return dpi;
+    }
+
+    /**
+     * Convert to DataProfileInfo defined in radio/1.4/types.hal
+     * @param dp Data profile
+     * @return A converted data profile
+     */
+    private static android.hardware.radio.V1_4.DataProfileInfo convertToHalDataProfile14(
+            DataProfile dp) {
+        android.hardware.radio.V1_4.DataProfileInfo dpi =
+                new android.hardware.radio.V1_4.DataProfileInfo();
+
+        dpi.apn = dp.getApn();
+        dpi.protocol = dp.getProtocol();
+        dpi.roamingProtocol = dp.getRoamingProtocol();
+        dpi.authType = dp.getAuthType();
+        dpi.user = dp.getUserName();
+        dpi.password = dp.getPassword();
+        dpi.type = dp.getType();
+        dpi.maxConnsTime = dp.getMaxConnsTime();
+        dpi.maxConns = dp.getMaxConns();
+        dpi.waitTime = dp.getWaitTime();
+        dpi.enabled = dp.isEnabled();
+        dpi.supportedApnTypesBitmap = dp.getSupportedApnTypesBitmap();
+        dpi.bearerBitmap = dp.getBearerBitmap();
+        dpi.mtu = dp.getMtu();
+        dpi.persistent = dp.isPersistent();
+        dpi.preferred = dp.isPreferred();
+
+        // profile id is only meaningful when it's persistent on the modem.
+        dpi.profileId = (dpi.persistent) ? dp.getProfileId() : DataProfileId.INVALID;
 
         return dpi;
     }
@@ -1181,19 +1310,67 @@
             RILRequest rr = obtainRequest(RIL_REQUEST_SETUP_DATA_CALL, result,
                     mRILDefaultWorkSource);
 
-            // Convert to HAL data profile
-            DataProfileInfo dpi = convertToHalDataProfile(dataProfile);
+            ArrayList<String> addresses = new ArrayList<>();
+            ArrayList<String> dnses = new ArrayList<>();
+            if (linkProperties != null) {
+                for (InetAddress address : linkProperties.getAddresses()) {
+                    addresses.add(address.getHostAddress());
+                }
+                for (InetAddress dns : linkProperties.getDnsServers()) {
+                    dnses.add(dns.getHostAddress());
+                }
+            }
 
-            android.hardware.radio.V1_2.IRadio radioProxy12 =
-                    android.hardware.radio.V1_2.IRadio.castFrom(radioProxy);
             try {
-                if (radioProxy12 == null) {
-                    // IRadio V1.0
+                if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_4)) {
+                    // IRadio V1.4
+                    android.hardware.radio.V1_4.IRadio radioProxy14 =
+                            (android.hardware.radio.V1_4.IRadio) radioProxy;
 
-                    // 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.
+                    // Convert to HAL data profile
+                    android.hardware.radio.V1_4.DataProfileInfo dpi =
+                            convertToHalDataProfile14(dataProfile);
+
+                    if (RILJ_LOGD) {
+                        riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+                                + ",accessNetworkType=" + accessNetworkType + ",isRoaming="
+                                + isRoaming + ",allowRoaming=" + allowRoaming + "," + dataProfile
+                                + ",addresses=" + addresses + ",dnses=" + dnses);
+                    }
+
+                    radioProxy14.setupDataCall_1_4(rr.mSerial, accessNetworkType, dpi, allowRoaming,
+                            reason, addresses, dnses);
+                } else if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_2)) {
+                    // IRadio V1.2 and IRadio V1.3
+                    android.hardware.radio.V1_2.IRadio radioProxy12 =
+                            (android.hardware.radio.V1_2.IRadio) radioProxy;
+
+                    // Convert to HAL data profile
+                    android.hardware.radio.V1_0.DataProfileInfo dpi =
+                            convertToHalDataProfile10(dataProfile);
+
+                    if (RILJ_LOGD) {
+                        riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+                                + ",accessNetworkType=" + accessNetworkType + ",isRoaming="
+                                + isRoaming + ",allowRoaming=" + allowRoaming + ","
+                                + dataProfile + ",addresses=" + addresses + ",dnses=" + dnses);
+                    }
+
+                    radioProxy12.setupDataCall_1_2(rr.mSerial, accessNetworkType, dpi,
+                            dataProfile.isPersistent(), allowRoaming, isRoaming, reason,
+                            addresses, dnses);
+                } else if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_0)) {
+                    // IRadio V1.0 and IRadio V1.1
+
+                    // Convert to HAL data profile
+                    android.hardware.radio.V1_0.DataProfileInfo dpi =
+                            convertToHalDataProfile10(dataProfile);
+
+                    // Getting data RAT here is just a workaround to support the older 1.0
+                    // vendor RIL. The new data service interface passes access network type
+                    // 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) {
@@ -1209,30 +1386,7 @@
                     }
 
                     radioProxy.setupDataCall(rr.mSerial, dataRat, dpi,
-                            dataProfile.isModemCognitive(), allowRoaming, isRoaming);
-                } else {
-                    // IRadio V1.2
-                    ArrayList<String> addresses = new ArrayList<>();
-                    ArrayList<String> dnses = new ArrayList<>();
-                    if (linkProperties != null) {
-                        for (InetAddress address : linkProperties.getAddresses()) {
-                            addresses.add(address.getHostAddress());
-                        }
-                        for (InetAddress dns : linkProperties.getDnsServers()) {
-                            dnses.add(dns.getHostAddress());
-                        }
-                    }
-
-                    if (RILJ_LOGD) {
-                        riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
-                                + ",accessNetworkType=" + accessNetworkType + ",isRoaming="
-                                + isRoaming + ",allowRoaming=" + allowRoaming + "," + dataProfile
-                                + ",addresses=" + addresses + ",dnses=" + dnses);
-                    }
-
-                    radioProxy12.setupDataCall_1_2(rr.mSerial, accessNetworkType, dpi,
-                            dataProfile.isModemCognitive(), allowRoaming, isRoaming, reason,
-                            addresses, dnses);
+                            dataProfile.isPersistent(), allowRoaming, isRoaming);
                 }
             } catch (RemoteException | RuntimeException e) {
                 handleRadioProxyExceptionForRR(rr, "setupDataCall", e);
@@ -1517,15 +1671,15 @@
                         + requestToString(rr.mRequest) + " cid = " + cid + " reason = " + reason);
             }
 
-            android.hardware.radio.V1_2.IRadio radioProxy12 =
-                    android.hardware.radio.V1_2.IRadio.castFrom(radioProxy);
-
             try {
-                if (radioProxy12 == null) {
+                if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_2)) {
+                    android.hardware.radio.V1_2.IRadio radioProxy12 =
+                            (android.hardware.radio.V1_2.IRadio) radioProxy;
+
+                    radioProxy12.deactivateDataCall_1_2(rr.mSerial, cid, reason);
+                } else {
                     radioProxy.deactivateDataCall(rr.mSerial, cid,
                             (reason == DataService.REQUEST_REASON_SHUTDOWN));
-                } else {
-                    radioProxy12.deactivateDataCall_1_2(rr.mSerial, cid, reason);
                 }
                 mMetrics.writeRilDeactivateDataCall(mPhoneId, rr.mSerial, cid, reason);
             } catch (RemoteException | RuntimeException e) {
@@ -1736,9 +1890,10 @@
     public void startNetworkScan(NetworkScanRequest nsr, 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) {
+            if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_2)) {
+                android.hardware.radio.V1_2.IRadio radioProxy12 =
+                        (android.hardware.radio.V1_2.IRadio) radioProxy;
+
                 android.hardware.radio.V1_2.NetworkScanRequest request =
                         new android.hardware.radio.V1_2.NetworkScanRequest();
                 request.type = nsr.getScanType();
@@ -1771,43 +1926,40 @@
                 } catch (RemoteException | RuntimeException e) {
                     handleRadioProxyExceptionForRR(rr, "startNetworkScan", e);
                 }
-            } else {
+            } else if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_1)) {
                 android.hardware.radio.V1_1.IRadio radioProxy11 =
-                        android.hardware.radio.V1_1.IRadio.castFrom(radioProxy);
-                if (radioProxy11 == null) {
-                    if (result != null) {
-                        AsyncResult.forMessage(result, null,
-                                CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-                        result.sendToTarget();
-                    }
-                } else {
-                    android.hardware.radio.V1_1.NetworkScanRequest request =
-                            new android.hardware.radio.V1_1.NetworkScanRequest();
-                    request.type = nsr.getScanType();
-                    request.interval = nsr.getSearchPeriodicity();
-                    for (RadioAccessSpecifier ras : nsr.getSpecifiers()) {
-                        android.hardware.radio.V1_1.RadioAccessSpecifier rasInHalFormat =
-                                convertRadioAccessSpecifierToRadioHAL(ras);
-                        if (rasInHalFormat == null) {
-                            return;
-                        }
+                        (android.hardware.radio.V1_1.IRadio) radioProxy;
 
-                        request.specifiers.add(rasInHalFormat);
+                android.hardware.radio.V1_1.NetworkScanRequest request =
+                        new android.hardware.radio.V1_1.NetworkScanRequest();
+                request.type = nsr.getScanType();
+                request.interval = nsr.getSearchPeriodicity();
+                for (RadioAccessSpecifier ras : nsr.getSpecifiers()) {
+                    android.hardware.radio.V1_1.RadioAccessSpecifier rasInHalFormat =
+                            convertRadioAccessSpecifierToRadioHAL(ras);
+                    if (rasInHalFormat == null) {
+                        return;
                     }
 
-                    RILRequest rr = obtainRequest(RIL_REQUEST_START_NETWORK_SCAN, result,
-                            mRILDefaultWorkSource);
-
-                    if (RILJ_LOGD) {
-                        riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
-                    }
-
-                    try {
-                        radioProxy11.startNetworkScan(rr.mSerial, request);
-                    } catch (RemoteException | RuntimeException e) {
-                        handleRadioProxyExceptionForRR(rr, "startNetworkScan", e);
-                    }
+                    request.specifiers.add(rasInHalFormat);
                 }
+
+                RILRequest rr = obtainRequest(RIL_REQUEST_START_NETWORK_SCAN, result,
+                        mRILDefaultWorkSource);
+
+                if (RILJ_LOGD) {
+                    riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+                }
+
+                try {
+                    radioProxy11.startNetworkScan(rr.mSerial, request);
+                } catch (RemoteException | RuntimeException e) {
+                    handleRadioProxyExceptionForRR(rr, "startNetworkScan", e);
+                }
+            } else if (result != null) {
+                AsyncResult.forMessage(result, null,
+                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
+                result.sendToTarget();
             }
         }
     }
@@ -1816,15 +1968,10 @@
     public void stopNetworkScan(Message result) {
         IRadio radioProxy = getRadioProxy(result);
         if (radioProxy != null) {
-            android.hardware.radio.V1_1.IRadio radioProxy11 =
-                    android.hardware.radio.V1_1.IRadio.castFrom(radioProxy);
-            if (radioProxy11 == null) {
-                if (result != null) {
-                    AsyncResult.forMessage(result, null,
-                            CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-                    result.sendToTarget();
-                }
-            } else {
+            if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_1)) {
+                android.hardware.radio.V1_1.IRadio radioProxy11 =
+                        (android.hardware.radio.V1_1.IRadio) radioProxy;
+
                 RILRequest rr = obtainRequest(RIL_REQUEST_STOP_NETWORK_SCAN, result,
                         mRILDefaultWorkSource);
 
@@ -1837,6 +1984,10 @@
                 } catch (RemoteException | RuntimeException e) {
                     handleRadioProxyExceptionForRR(rr, "stopNetworkScan", e);
                 }
+            } else if (result != null) {
+                AsyncResult.forMessage(result, null,
+                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
+                result.sendToTarget();
             }
         }
     }
@@ -2204,7 +2355,7 @@
                         byte[] target = Arrays.copyOfRange(ctlv.getRawValue(), from,
                                 ctlv.getValueIndex() + ctlv.getLength());
                         terminalResponse = terminalResponse.toLowerCase().replace(
-                                IccUtils.bytesToHexString(target), "********");
+                                IccUtils.bytesToHexString(target).toLowerCase(), "********");
                     }
                     // The text string tag and the length field should also be hidden.
                     from = ctlv.getValueIndex() + ctlv.getLength();
@@ -2295,24 +2446,6 @@
     }
 
     @Override
-    public void getNeighboringCids(Message result, WorkSource workSource) {
-        workSource = getDeafultWorkSourceIfInvalid(workSource);
-        IRadio radioProxy = getRadioProxy(result);
-        if (radioProxy != null) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_GET_NEIGHBORING_CELL_IDS, result,
-                    workSource);
-
-            if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
-
-            try {
-                radioProxy.getNeighboringCids(rr.mSerial);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(rr, "getNeighboringCids", e);
-            }
-        }
-    }
-
-    @Override
     public void setLocationUpdates(boolean enable, Message result) {
         IRadio radioProxy = getRadioProxy(result);
         if (radioProxy != null) {
@@ -3005,10 +3138,6 @@
         }
     }
 
-    void setCellInfoListRate() {
-        setCellInfoListRate(Integer.MAX_VALUE, null, mRILDefaultWorkSource);
-    }
-
     @Override
     public void setInitialAttachApn(DataProfile dataProfile, boolean isRoaming, Message result) {
 
@@ -3022,8 +3151,18 @@
             }
 
             try {
-                radioProxy.setInitialAttachApn(rr.mSerial, convertToHalDataProfile(dataProfile),
-                        dataProfile.isModemCognitive(), isRoaming);
+                if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_4)) {
+                    // v1.4
+                    android.hardware.radio.V1_4.IRadio radioProxy14 =
+                            (android.hardware.radio.V1_4.IRadio) radioProxy;
+                    radioProxy14.setInitialAttachApn_1_4(rr.mSerial,
+                            convertToHalDataProfile14(dataProfile));
+                } else {
+                    // v1.3, v1.2, v1.1, and v1.0
+                    radioProxy.setInitialAttachApn(rr.mSerial,
+                            convertToHalDataProfile10(dataProfile), dataProfile.isPersistent(),
+                            isRoaming);
+                }
             } catch (RemoteException | RuntimeException e) {
                 handleRadioProxyExceptionForRR(rr, "setInitialAttachApn", e);
             }
@@ -3225,11 +3364,12 @@
     }
 
     @Override
-    public void nvReadItem(int itemID, Message result) {
+    public void nvReadItem(int itemID, Message result, WorkSource workSource) {
+        workSource = getDeafultWorkSourceIfInvalid(workSource);
         IRadio radioProxy = getRadioProxy(result);
         if (radioProxy != null) {
             RILRequest rr = obtainRequest(RIL_REQUEST_NV_READ_ITEM, result,
-                    mRILDefaultWorkSource);
+                    workSource);
 
             if (RILJ_LOGD) {
                 riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
@@ -3245,11 +3385,12 @@
     }
 
     @Override
-    public void nvWriteItem(int itemId, String itemValue, Message result) {
+    public void nvWriteItem(int itemId, String itemValue, Message result, WorkSource workSource) {
+        workSource = getDeafultWorkSourceIfInvalid(workSource);
         IRadio radioProxy = getRadioProxy(result);
         if (radioProxy != null) {
             RILRequest rr = obtainRequest(RIL_REQUEST_NV_WRITE_ITEM, result,
-                    mRILDefaultWorkSource);
+                    workSource);
 
             if (RILJ_LOGD) {
                 riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
@@ -3407,24 +3548,57 @@
 
         IRadio radioProxy = getRadioProxy(result);
         if (radioProxy != null) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_SET_DATA_PROFILE, result,
-                    mRILDefaultWorkSource);
 
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
-                        + " with data profiles : ");
-                for (DataProfile profile : dps) {
-                    riljLog(profile.toString());
-                }
-            }
-
-            ArrayList<DataProfileInfo> dpis = new ArrayList<>();
-            for (DataProfile dp : dps) {
-                dpis.add(convertToHalDataProfile(dp));
-            }
-
+            RILRequest rr = null;
             try {
-                radioProxy.setDataProfile(rr.mSerial, dpis, isRoaming);
+                if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_4)) {
+                    // V1.4
+                    android.hardware.radio.V1_4.IRadio radioProxy14 =
+                            (android.hardware.radio.V1_4.IRadio) radioProxy;
+
+                    rr = obtainRequest(RIL_REQUEST_SET_DATA_PROFILE, result,
+                            mRILDefaultWorkSource);
+
+                    ArrayList<android.hardware.radio.V1_4.DataProfileInfo> dpis = new ArrayList<>();
+                    for (DataProfile dp : dps) {
+                        dpis.add(convertToHalDataProfile14(dp));
+                    }
+
+                    if (RILJ_LOGD) {
+                        riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+                                + " with data profiles : ");
+                        for (DataProfile profile : dps) {
+                            riljLog(profile.toString());
+                        }
+                    }
+
+                    radioProxy14.setDataProfile_1_4(rr.mSerial, dpis);
+                } else {
+                    // V1.0, 1.1, 1,2 and 1.3
+                    ArrayList<android.hardware.radio.V1_0.DataProfileInfo> dpis = new ArrayList<>();
+                    for (DataProfile dp : dps) {
+                        // For v1.0 to v1.2, we only send data profiles that has the persistent
+                        // (a.k.a modem cognitive) bit set to true.
+                        if (dp.isPersistent()) {
+                            dpis.add(convertToHalDataProfile10(dp));
+                        }
+                    }
+
+                    if (!dpis.isEmpty()) {
+                        rr = obtainRequest(RIL_REQUEST_SET_DATA_PROFILE, result,
+                                mRILDefaultWorkSource);
+
+                        if (RILJ_LOGD) {
+                            riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+                                    + " with data profiles : ");
+                            for (DataProfile profile : dps) {
+                                riljLog(profile.toString());
+                            }
+                        }
+
+                        radioProxy.setDataProfile(rr.mSerial, dpis, isRoaming);
+                    }
+                }
             } catch (RemoteException | RuntimeException e) {
                 handleRadioProxyExceptionForRR(rr, "setDataProfile", e);
             }
@@ -3501,9 +3675,8 @@
     @Override
     public void startLceService(int reportIntervalMs, boolean pullMode, Message result) {
         IRadio radioProxy = getRadioProxy(result);
-        android.hardware.radio.V1_2.IRadio radioProxy12 =
-                android.hardware.radio.V1_2.IRadio.castFrom(radioProxy);
-        if (radioProxy12 != null) {
+
+        if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_2)) {
             // We have a 1.2 or later radio, so the LCE 1.0 LCE service control path is unused.
             // Instead the LCE functionality is always-on and provides unsolicited indications.
             return;
@@ -3529,9 +3702,7 @@
     @Override
     public void stopLceService(Message result) {
         IRadio radioProxy = getRadioProxy(result);
-        android.hardware.radio.V1_2.IRadio radioProxy12 =
-                android.hardware.radio.V1_2.IRadio.castFrom(radioProxy);
-        if (radioProxy12 != null) {
+        if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_2)) {
             // We have a 1.2 or later radio, so the LCE 1.0 LCE service control is unused.
             // Instead the LCE functionality is always-on and provides unsolicited indications.
             return;
@@ -3584,11 +3755,12 @@
     }
 
     @Override
-    public void getModemActivityInfo(Message result) {
+    public void getModemActivityInfo(Message result, WorkSource workSource) {
+        workSource = getDeafultWorkSourceIfInvalid(workSource);
         IRadio radioProxy = getRadioProxy(result);
         if (radioProxy != null) {
             RILRequest rr = obtainRequest(RIL_REQUEST_GET_ACTIVITY_INFO, result,
-                    mRILDefaultWorkSource);
+                    workSource);
 
             if (RILJ_LOGD) {
                 riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
@@ -3610,12 +3782,15 @@
     }
 
     @Override
-    public void setAllowedCarriers(List<CarrierIdentifier> carriers, Message result) {
+    public void setAllowedCarriers(List<CarrierIdentifier> carriers, Message result,
+            WorkSource workSource) {
         checkNotNull(carriers, "Allowed carriers list cannot be null.");
+        workSource = getDeafultWorkSourceIfInvalid(workSource);
+
         IRadio radioProxy = getRadioProxy(result);
         if (radioProxy != null) {
             RILRequest rr = obtainRequest(RIL_REQUEST_SET_ALLOWED_CARRIERS, result,
-                    mRILDefaultWorkSource);
+                    workSource);
 
             if (RILJ_LOGD) {
                 String logStr = "";
@@ -3669,11 +3844,13 @@
     }
 
     @Override
-    public void getAllowedCarriers(Message result) {
+    public void getAllowedCarriers(Message result, WorkSource workSource) {
+        workSource = getDeafultWorkSourceIfInvalid(workSource);
+
         IRadio radioProxy = getRadioProxy(result);
         if (radioProxy != null) {
             RILRequest rr = obtainRequest(RIL_REQUEST_GET_ALLOWED_CARRIERS, result,
-                    mRILDefaultWorkSource);
+                    workSource);
 
             if (RILJ_LOGD) {
                 riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
@@ -3719,11 +3896,11 @@
                 riljLog(rr.serialString() + "> " + requestToString(rr.mRequest) + " " + filter);
             }
 
-            android.hardware.radio.V1_2.IRadio radioProxy12 =
-                    android.hardware.radio.V1_2.IRadio.castFrom(radioProxy);
-
-            if (radioProxy12 != null) {
+            if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_2)) {
                 try {
+                    android.hardware.radio.V1_2.IRadio radioProxy12 =
+                            (android.hardware.radio.V1_2.IRadio) radioProxy;
+
                     radioProxy12.setIndicationFilter_1_2(rr.mSerial, filter);
                 } catch (RemoteException | RuntimeException e) {
                     handleRadioProxyExceptionForRR(rr, "setIndicationFilter_1_2", e);
@@ -3744,10 +3921,9 @@
             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!");
+            if (mRadioVersion.less(RADIO_HAL_VERSION_1_2)) {
+                riljLoge("setSignalStrengthReportingCriteria ignored on IRadio version less "
+                        + "than 1.2");
                 return;
             }
 
@@ -3759,6 +3935,8 @@
             }
 
             try {
+                android.hardware.radio.V1_2.IRadio radioProxy12 =
+                        (android.hardware.radio.V1_2.IRadio) radioProxy;
                 radioProxy12.setSignalStrengthReportingCriteria(rr.mSerial, hysteresisMs,
                         hysteresisDb, primitiveArrayToArrayList(thresholdsDbm),
                         convertRanToHalRan(ran));
@@ -3774,10 +3952,9 @@
             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!");
+            if (mRadioVersion.less(RADIO_HAL_VERSION_1_2)) {
+                riljLoge("setLinkCapacityReportingCriteria ignored on IRadio version less "
+                        + "than 1.2");
                 return;
             }
 
@@ -3789,6 +3966,8 @@
             }
 
             try {
+                android.hardware.radio.V1_2.IRadio radioProxy12 =
+                        (android.hardware.radio.V1_2.IRadio) radioProxy;
                 radioProxy12.setLinkCapacityReportingCriteria(rr.mSerial, hysteresisMs,
                         hysteresisDlKbps, hysteresisUlKbps,
                         primitiveArrayToArrayList(thresholdsDlKbps),
@@ -3818,18 +3997,26 @@
     }
 
     @Override
-    public void setSimCardPower(int state, Message result) {
+    public void setSimCardPower(int state, Message result, WorkSource workSource) {
+        workSource = getDeafultWorkSourceIfInvalid(workSource);
         IRadio radioProxy = getRadioProxy(result);
         if (radioProxy != null) {
             RILRequest rr = obtainRequest(RIL_REQUEST_SET_SIM_CARD_POWER, result,
-                    mRILDefaultWorkSource);
+                    workSource);
 
             if (RILJ_LOGD) {
                 riljLog(rr.serialString() + "> " + requestToString(rr.mRequest) + " " + state);
             }
-            android.hardware.radio.V1_1.IRadio radioProxy11 =
-                    android.hardware.radio.V1_1.IRadio.castFrom(radioProxy);
-            if (radioProxy11 == null) {
+
+            if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_1)) {
+                try {
+                    android.hardware.radio.V1_1.IRadio radioProxy11 =
+                            (android.hardware.radio.V1_1.IRadio) radioProxy;
+                    radioProxy11.setSimCardPower_1_1(rr.mSerial, state);
+                } catch (RemoteException | RuntimeException e) {
+                    handleRadioProxyExceptionForRR(rr, "setSimCardPower", e);
+                }
+            } else {
                 try {
                     switch (state) {
                         case TelephonyManager.CARD_POWER_DOWN: {
@@ -3851,12 +4038,6 @@
                 } catch (RemoteException | RuntimeException e) {
                     handleRadioProxyExceptionForRR(rr, "setSimCardPower", e);
                 }
-            } else {
-                try {
-                    radioProxy11.setSimCardPower_1_1(rr.mSerial, state);
-                } catch (RemoteException | RuntimeException e) {
-                    handleRadioProxyExceptionForRR(rr, "setSimCardPower", e);
-                }
             }
         }
     }
@@ -3867,15 +4048,10 @@
         checkNotNull(imsiEncryptionInfo, "ImsiEncryptionInfo cannot be null.");
         IRadio radioProxy = getRadioProxy(result);
         if (radioProxy != null) {
-            android.hardware.radio.V1_1.IRadio radioProxy11 =
-                    android.hardware.radio.V1_1.IRadio.castFrom(radioProxy);
-            if (radioProxy11 == null) {
-                if (result != null) {
-                    AsyncResult.forMessage(result, null,
-                            CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-                    result.sendToTarget();
-                }
-            } else {
+            if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_1)) {
+                android.hardware.radio.V1_1.IRadio radioProxy11 =
+                        (android.hardware.radio.V1_1.IRadio ) radioProxy;
+
                 RILRequest rr = obtainRequest(RIL_REQUEST_SET_CARRIER_INFO_IMSI_ENCRYPTION, result,
                         mRILDefaultWorkSource);
                 if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
@@ -3899,6 +4075,10 @@
                 } catch (RemoteException | RuntimeException e) {
                     handleRadioProxyExceptionForRR(rr, "setCarrierInfoForImsiEncryption", e);
                 }
+            } else if (result != null) {
+                AsyncResult.forMessage(result, null,
+                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
+                result.sendToTarget();
             }
         }
     }
@@ -3913,9 +4093,7 @@
             return;
         }
 
-        android.hardware.radio.V1_1.IRadio radioProxy11 =
-                android.hardware.radio.V1_1.IRadio.castFrom(radioProxy);
-        if (radioProxy11 == null) {
+        if (mRadioVersion.less(RADIO_HAL_VERSION_1_1)) {
             if (result != null) {
                 AsyncResult.forMessage(result, null,
                         CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
@@ -3924,6 +4102,9 @@
             return;
         }
 
+        android.hardware.radio.V1_1.IRadio radioProxy11 =
+                (android.hardware.radio.V1_1.IRadio) radioProxy;
+
         RILRequest rr = obtainRequest(
                 RIL_REQUEST_START_KEEPALIVE, result, mRILDefaultWorkSource);
 
@@ -3952,6 +4133,7 @@
             appendPrimitiveArrayToArrayList(
                     packetData.dstAddress.getAddress(), req.destinationAddress);
             req.destinationPort = packetData.dstPort;
+            req.maxKeepaliveIntervalMillis = intervalMillis;
 
             radioProxy11.startKeepalive(rr.mSerial, req);
         } catch (RemoteException | RuntimeException e) {
@@ -3967,9 +4149,7 @@
             return;
         }
 
-        android.hardware.radio.V1_1.IRadio radioProxy11 =
-                android.hardware.radio.V1_1.IRadio.castFrom(radioProxy);
-        if (radioProxy11 == null) {
+        if (mRadioVersion.less(RADIO_HAL_VERSION_1_1)) {
             if (result != null) {
                 AsyncResult.forMessage(result, null,
                         CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
@@ -3978,6 +4158,9 @@
             return;
         }
 
+        android.hardware.radio.V1_1.IRadio radioProxy11 =
+                (android.hardware.radio.V1_1.IRadio) radioProxy;
+
         RILRequest rr = obtainRequest(
                 RIL_REQUEST_STOP_KEEPALIVE, result, mRILDefaultWorkSource);
 
@@ -4161,7 +4344,8 @@
                 }
                 break;
             case RIL_REQUEST_SHUTDOWN:
-                setRadioState(RadioState.RADIO_UNAVAILABLE);
+                setRadioState(TelephonyManager.RADIO_POWER_UNAVAILABLE,
+                        false /* forceNotifyRegistrants */);
                 break;
         }
 
@@ -4283,11 +4467,7 @@
 
                         String clientId = rr.getWorkSourceClientId();
                         if (!mClientWakelockTracker.isClientActive(clientId)) {
-                            if (mActiveWakelockWorkSource != null) {
-                                mActiveWakelockWorkSource.add(rr.mWorkSource);
-                            } else {
-                                mActiveWakelockWorkSource = rr.mWorkSource;
-                            }
+                            mActiveWakelockWorkSource.add(rr.mWorkSource);
                             mWakeLock.setWorkSource(mActiveWakelockWorkSource);
                         }
 
@@ -4344,12 +4524,8 @@
                                 rr.mRequest, rr.mSerial,
                                 (mWakeLockCount > 1) ? mWakeLockCount - 1 : 0);
                         String clientId = rr.getWorkSourceClientId();
-                        if (!mClientWakelockTracker.isClientActive(clientId)
-                                && (mActiveWakelockWorkSource != null)) {
+                        if (!mClientWakelockTracker.isClientActive(clientId)) {
                             mActiveWakelockWorkSource.remove(rr.mWorkSource);
-                            if (mActiveWakelockWorkSource.size() == 0) {
-                                mActiveWakelockWorkSource = null;
-                            }
                             mWakeLock.setWorkSource(mActiveWakelockWorkSource);
                         }
 
@@ -4382,7 +4558,7 @@
                 mWakeLockCount = 0;
                 mWakeLock.release();
                 mClientWakelockTracker.stopTrackingAll();
-                mActiveWakelockWorkSource = null;
+                mActiveWakelockWorkSource = new WorkSource();
                 return true;
             }
         } else {
@@ -5040,23 +5216,19 @@
     }
 
     void riljLog(String msg) {
-        Rlog.d(RILJ_LOG_TAG, msg
-                + (mPhoneId != null ? (" [SUB" + mPhoneId + "]") : ""));
+        Rlog.d(RILJ_LOG_TAG, msg + (" [SUB" + mPhoneId + "]"));
     }
 
     void riljLoge(String msg) {
-        Rlog.e(RILJ_LOG_TAG, msg
-                + (mPhoneId != null ? (" [SUB" + mPhoneId + "]") : ""));
+        Rlog.e(RILJ_LOG_TAG, msg + (" [SUB" + mPhoneId + "]"));
     }
 
     void riljLoge(String msg, Exception e) {
-        Rlog.e(RILJ_LOG_TAG, msg
-                + (mPhoneId != null ? (" [SUB" + mPhoneId + "]") : ""), e);
+        Rlog.e(RILJ_LOG_TAG, msg + (" [SUB" + mPhoneId + "]"), e);
     }
 
     void riljLogv(String msg) {
-        Rlog.v(RILJ_LOG_TAG, msg
-                + (mPhoneId != null ? (" [SUB" + mPhoneId + "]") : ""));
+        Rlog.v(RILJ_LOG_TAG, msg + (" [SUB" + mPhoneId + "]"));
     }
 
     void unsljLog(int response) {
@@ -5191,7 +5363,8 @@
             android.hardware.radio.V1_0.RadioCapability rcRil, RIL ril) {
         int session = rcRil.session;
         int phase = rcRil.phase;
-        int rat = rcRil.raf;
+        // convert to public bitmask {@link TelephonyManager.NetworkTypeBitMask}
+        int rat = RadioAccessFamily.convertToNetworkTypeBitMask(rcRil.raf);
         String logicModemUuid = rcRil.logicalModemUuid;
         int status = rcRil.status;
 
@@ -5199,7 +5372,7 @@
                 ", phase=" + phase +
                 ", rat=" + rat +
                 ", logicModemUuid=" + logicModemUuid +
-                ", status=" + status);
+                ", status=" + status + ", rcRil.raf=" + rcRil.raf);
         RadioCapability rc = new RadioCapability(
                 ril.mPhoneId, session, phase, rat, logicModemUuid, status);
         return rc;
@@ -5225,10 +5398,11 @@
         return lce;
     }
 
+    // TODO(b/119224773) refactor the converter of CellInfo.
     private static void writeToParcelForGsm(
             Parcel p, int lac, int cid, int arfcn, int bsic, String mcc, String mnc,
             String al, String as, int ss, int ber, int ta) {
-        p.writeInt(CellIdentity.TYPE_GSM);
+        p.writeInt(CellInfo.TYPE_GSM);
         p.writeString(mcc);
         p.writeString(mnc);
         p.writeString(al);
@@ -5242,6 +5416,7 @@
         p.writeInt(ta);
     }
 
+    // TODO(b/119224773) refactor the converter of CellInfo.
     private static void writeToParcelForCdma(
             Parcel p, int ni, int si, int bsi, int lon, int lat, String al, String as,
             int dbm, int ecio, int eDbm, int eEcio, int eSnr) {
@@ -5249,31 +5424,43 @@
         new CellSignalStrengthCdma(dbm, ecio, eDbm, eEcio, eSnr).writeToParcel(p, 0);
     }
 
+    // TODO(b/119224773) refactor the converter of CellInfo.
     private static void writeToParcelForLte(
             Parcel p, int ci, int pci, int tac, int earfcn, int bandwidth, String mcc, String mnc,
-            String al, String as, int ss, int rsrp, int rsrq, int rssnr, int cqi, int ta) {
-        p.writeInt(CellIdentity.TYPE_LTE);
+            String al, String as, int ss, int rsrp, int rsrq, int rssnr, int cqi, int ta,
+            boolean isEndcAvailable) {
+
+        // General CellInfo
+        p.writeInt(CellInfo.TYPE_LTE);
         p.writeString(mcc);
         p.writeString(mnc);
         p.writeString(al);
         p.writeString(as);
+
+        // CellIdentity
         p.writeInt(ci);
         p.writeInt(pci);
         p.writeInt(tac);
         p.writeInt(earfcn);
         p.writeInt(bandwidth);
+
+        // CellSignalStrength
         p.writeInt(ss);
         p.writeInt(rsrp);
         p.writeInt(rsrq);
         p.writeInt(rssnr);
         p.writeInt(cqi);
         p.writeInt(ta);
+
+        // CellConfigLte
+        p.writeBoolean(isEndcAvailable);
     }
 
+    // TODO(b/119224773) refactor the converter of CellInfo.
     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) {
-        p.writeInt(CellIdentity.TYPE_WCDMA);
+            String al, String as, int ss, int ber, int rscp, int ecno) {
+        p.writeInt(CellInfo.TYPE_WCDMA);
         p.writeString(mcc);
         p.writeString(mnc);
         p.writeString(al);
@@ -5284,6 +5471,26 @@
         p.writeInt(uarfcn);
         p.writeInt(ss);
         p.writeInt(ber);
+        p.writeInt(rscp);
+        p.writeInt(ecno);
+    }
+
+    // TODO(b/119224773) refactor the converter of CellInfo.
+    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(CellInfo.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);
     }
 
     /**
@@ -5301,8 +5508,7 @@
             Parcel p = Parcel.obtain();
             p.writeInt(record.cellInfoType);
             p.writeInt(record.registered ? 1 : 0);
-            p.writeInt(record.timeStampType);
-            p.writeLong(record.timeStamp);
+            p.writeLong(SystemClock.elapsedRealtimeNanos());
             p.writeInt(CellInfo.CONNECTION_UNKNOWN);
             switch (record.cellInfoType) {
                 case CellInfoType.GSM: {
@@ -5360,7 +5566,8 @@
                             cellInfoLte.signalStrengthLte.rsrq,
                             cellInfoLte.signalStrengthLte.rssnr,
                             cellInfoLte.signalStrengthLte.cqi,
-                            cellInfoLte.signalStrengthLte.timingAdvance);
+                            cellInfoLte.signalStrengthLte.timingAdvance,
+                            false /* isEndcAvailable */);
                     break;
                 }
 
@@ -5377,10 +5584,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);
             }
@@ -5409,8 +5635,7 @@
             Parcel p = Parcel.obtain();
             p.writeInt(record.cellInfoType);
             p.writeInt(record.registered ? 1 : 0);
-            p.writeInt(record.timeStampType);
-            p.writeLong(record.timeStamp);
+            p.writeLong(SystemClock.elapsedRealtimeNanos());
             p.writeInt(record.connectionStatus);
             switch (record.cellInfoType) {
                 case CellInfoType.GSM: {
@@ -5468,7 +5693,8 @@
                             cellInfoLte.signalStrengthLte.rsrq,
                             cellInfoLte.signalStrengthLte.rssnr,
                             cellInfoLte.signalStrengthLte.cqi,
-                            cellInfoLte.signalStrengthLte.timingAdvance);
+                            cellInfoLte.signalStrengthLte.timingAdvance,
+                            false /* isEndcAvailable */);
                     break;
                 }
 
@@ -5485,7 +5711,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;
                 }
 
@@ -5502,19 +5749,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,
@@ -5528,7 +5778,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 */
@@ -5552,4 +5802,27 @@
                 signalStrength.wcdma.base.signalStrength,
                 signalStrength.wcdma.rscp);
     }
+
+    /**
+     * @return The {@link IwlanOperationMode IWLAN operation mode}
+     */
+    public @IwlanOperationMode int getIwlanOperationMode() {
+        // Get IWLAN operation mode from the system property. If the system property is missing,
+        // use the default mode.
+        int mode = SystemProperties.getInt(TransportManager.SYSTEM_PROPERTIES_IWLAN_OPERATION_MODE,
+                TransportManager.IWLAN_OPERATION_MODE_DEFAULT);
+
+        // If the operation mode is default, then we use the HAL version to determine it.
+        // On 1.4 or later version of IRadio, it is expected the device to support
+        // IWLAN AP-assisted mode.
+        if (mode == TransportManager.IWLAN_OPERATION_MODE_DEFAULT) {
+            if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_4)) {
+                return TransportManager.IWLAN_OPERATION_MODE_AP_ASSISTED;
+            } else {
+                return TransportManager.IWLAN_OPERATION_MODE_LEGACY;
+            }
+        }
+
+        return mode;
+    }
 }
diff --git a/src/java/com/android/internal/telephony/RILRequest.java b/src/java/com/android/internal/telephony/RILRequest.java
index ffe4a82..174570c 100644
--- a/src/java/com/android/internal/telephony/RILRequest.java
+++ b/src/java/com/android/internal/telephony/RILRequest.java
@@ -88,7 +88,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;
@@ -114,9 +115,8 @@
      */
     // @VisibleForTesting
     public static RILRequest obtain(int request, Message result, WorkSource workSource) {
-        RILRequest rr = null;
+        RILRequest rr = obtain(request, result);
 
-        rr = obtain(request, result);
         if (workSource != null) {
             rr.mWorkSource = workSource;
             rr.mClientId = rr.getWorkSourceClientId();
@@ -176,9 +176,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() {
@@ -186,9 +186,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/RadioCapability.java b/src/java/com/android/internal/telephony/RadioCapability.java
index a7e73fc..d405c92 100644
--- a/src/java/com/android/internal/telephony/RadioCapability.java
+++ b/src/java/com/android/internal/telephony/RadioCapability.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.telephony;
 
+import android.telephony.TelephonyManager;
+
 /**
  * Object to indicate the phone radio capability.
  *
@@ -94,8 +96,9 @@
      * RadioAccessFamily is a bit field of radio access technologies the
      * for the modem is currently supporting. The initial value returned
      * my the modem must the the set of bits that the modem currently supports.
-     * see RadioAccessFamily#RADIO_TECHNOLOGY_XXXX
+     * see {@link android.telephony.TelephonyManager.NetworkTypeBitMask}
      */
+    @TelephonyManager.NetworkTypeBitMask
     private int mRadioAccessFamily;
 
     /**
@@ -118,14 +121,15 @@
      * @param session the request transaction id
      * @param phase the request phase id
      * @param radioAccessFamily the phone radio access family defined in
-     *        RadioAccessFamily. It's a bit mask value to represent
-     *        the support type.
+     * {@link android.telephony.TelephonyManager.NetworkTypeBitMask}
+     *                          It's a bit mask value to represent the support type.
      * @param logicalModemUuid the logicalModem UUID which phone connected to
      * @param status tell modem the action transaction of
      *        set radio capability is success or fail with RC_Phase_FINISH
      */
     public RadioCapability(int phoneId, int session, int phase,
-            int radioAccessFamily, String logicalModemUuid, int status) {
+                           @TelephonyManager.NetworkTypeBitMask int radioAccessFamily,
+                           String logicalModemUuid, int status) {
         mPhoneId = phoneId;
         mSession = session;
         mPhase = phase;
diff --git a/src/java/com/android/internal/telephony/RadioConfig.java b/src/java/com/android/internal/telephony/RadioConfig.java
index c258f6c..3706dc1 100644
--- a/src/java/com/android/internal/telephony/RadioConfig.java
+++ b/src/java/com/android/internal/telephony/RadioConfig.java
@@ -17,14 +17,15 @@
 package com.android.internal.telephony;
 
 import static com.android.internal.telephony.RILConstants.RADIO_NOT_AVAILABLE;
+import static com.android.internal.telephony.RILConstants.REQUEST_NOT_SUPPORTED;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_SLOT_STATUS;
-import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_LOGICAL_TO_PHYSICAL_SLOT_MAPPING;
+import static com.android.internal.telephony.RILConstants
+        .RIL_REQUEST_SET_LOGICAL_TO_PHYSICAL_SLOT_MAPPING;
 
 import android.content.Context;
 import android.hardware.radio.V1_0.RadioResponseInfo;
 import android.hardware.radio.V1_0.RadioResponseType;
 import android.hardware.radio.config.V1_0.IRadioConfig;
-import android.hardware.radio.config.V1_0.SimSlotStatus;
 import android.net.ConnectivityManager;
 import android.os.AsyncResult;
 import android.os.Handler;
@@ -258,7 +259,32 @@
     }
 
     /**
-     * Wrapper function for IRadioConfig.getSimSlotsStatus().
+     * Wrapper function for IRadioConfig.setPreferredDataModem(int modemId).
+     */
+    public void setPreferredDataModem(int modemId, Message result) {
+        if (!isSetPreferredDataCommandSupported()) {
+            if (result != null) {
+                AsyncResult.forMessage(result, null,
+                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
+                result.sendToTarget();
+            }
+        }
+        // TODO: call radioConfigProxy.setPreferredDataModem when it's ready.
+    }
+
+    /**
+     * @return whether current radio config version supports SET_PREFERRED_DATA_MODEM command.
+     * If yes, we'll use RIL_REQUEST_SET_PREFERRED_DATA_MODEM to indicate which modem is preferred.
+     * If not, we shall use RIL_REQUEST_ALLOW_DATA for on-demand PS attach / detach.
+     * See PhoneSwitcher for more details.
+     */
+    public boolean isSetPreferredDataCommandSupported() {
+        // TODO: call radioConfigProxy.isSetPreferredDataCommandSupported when it's ready.
+        return false;
+    }
+
+    /**
+     * Wrapper function for IRadioConfig.setSimSlotsMapping(int32_t serial, vec<uint32_t> slotMap).
      */
     public void setSimSlotsMapping(int[] physicalSlots, Message result) {
         IRadioConfig radioConfigProxy = getRadioConfigProxy(result);
@@ -317,9 +343,9 @@
     }
 
     static ArrayList<IccSlotStatus> convertHalSlotStatus(
-            ArrayList<SimSlotStatus> halSlotStatusList) {
+            ArrayList<android.hardware.radio.config.V1_0.SimSlotStatus> halSlotStatusList) {
         ArrayList<IccSlotStatus> response = new ArrayList<IccSlotStatus>(halSlotStatusList.size());
-        for (SimSlotStatus slotStatus : halSlotStatusList) {
+        for (android.hardware.radio.config.V1_0.SimSlotStatus slotStatus : halSlotStatusList) {
             IccSlotStatus iccSlotStatus = new IccSlotStatus();
             iccSlotStatus.setCardState(slotStatus.cardState);
             iccSlotStatus.setSlotState(slotStatus.slotState);
@@ -331,6 +357,22 @@
         return response;
     }
 
+    static ArrayList<IccSlotStatus> convertHalSlotStatus_1_2(
+            ArrayList<android.hardware.radio.config.V1_2.SimSlotStatus> halSlotStatusList) {
+        ArrayList<IccSlotStatus> response = new ArrayList<IccSlotStatus>(halSlotStatusList.size());
+        for (android.hardware.radio.config.V1_2.SimSlotStatus slotStatus : halSlotStatusList) {
+            IccSlotStatus iccSlotStatus = new IccSlotStatus();
+            iccSlotStatus.setCardState(slotStatus.base.cardState);
+            iccSlotStatus.setSlotState(slotStatus.base.slotState);
+            iccSlotStatus.logicalSlotIndex = slotStatus.base.logicalSlotId;
+            iccSlotStatus.atr = slotStatus.base.atr;
+            iccSlotStatus.iccid = slotStatus.base.iccid;
+            iccSlotStatus.eid = slotStatus.eid;
+            response.add(iccSlotStatus);
+        }
+        return response;
+    }
+
     private static void logd(String log) {
         Rlog.d(TAG, log);
     }
diff --git a/src/java/com/android/internal/telephony/RadioConfigIndication.java b/src/java/com/android/internal/telephony/RadioConfigIndication.java
index 686282c..5774bb1 100644
--- a/src/java/com/android/internal/telephony/RadioConfigIndication.java
+++ b/src/java/com/android/internal/telephony/RadioConfigIndication.java
@@ -17,7 +17,6 @@
 package com.android.internal.telephony;
 
 import android.hardware.radio.config.V1_0.IRadioConfigIndication;
-import android.hardware.radio.config.V1_0.SimSlotStatus;
 import android.os.AsyncResult;
 import android.telephony.Rlog;
 
@@ -39,7 +38,8 @@
     /**
      * Unsolicited indication for slot status changed
      */
-    public void simSlotsStatusChanged(int indicationType, ArrayList<SimSlotStatus> slotStatus) {
+    public void simSlotsStatusChanged(int indicationType,
+            ArrayList<android.hardware.radio.config.V1_0.SimSlotStatus> slotStatus) {
         ArrayList<IccSlotStatus> ret = RadioConfig.convertHalSlotStatus(slotStatus);
         Rlog.d(TAG, "[UNSL]< " + " UNSOL_SIM_SLOT_STATUS_CHANGED " + ret.toString());
         if (mRadioConfig.mSimSlotStatusRegistrant != null) {
@@ -47,4 +47,17 @@
                     new AsyncResult(null, ret, null));
         }
     }
+
+    /**
+     * Unsolicited indication for slot status changed
+     */
+    public void simSlotsStatusChanged_1_2(int indicationType,
+            ArrayList<android.hardware.radio.config.V1_2.SimSlotStatus> slotStatus) {
+        ArrayList<IccSlotStatus> ret = RadioConfig.convertHalSlotStatus_1_2(slotStatus);
+        Rlog.d(TAG, "[UNSL]< " + " UNSOL_SIM_SLOT_STATUS_CHANGED " + ret.toString());
+        if (mRadioConfig.mSimSlotStatusRegistrant != null) {
+            mRadioConfig.mSimSlotStatusRegistrant.notifyRegistrant(
+                    new AsyncResult(null, ret, null));
+        }
+    }
 }
diff --git a/src/java/com/android/internal/telephony/RadioConfigResponse.java b/src/java/com/android/internal/telephony/RadioConfigResponse.java
index 8177b2d..3b333ae 100644
--- a/src/java/com/android/internal/telephony/RadioConfigResponse.java
+++ b/src/java/com/android/internal/telephony/RadioConfigResponse.java
@@ -18,8 +18,8 @@
 
 import android.hardware.radio.V1_0.RadioError;
 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.hardware.radio.config.V1_1.PhoneCapability;
+import android.hardware.radio.config.V1_2.IRadioConfigResponse;
 import android.telephony.Rlog;
 
 import com.android.internal.telephony.uicc.IccSlotStatus;
@@ -41,7 +41,7 @@
      * Response function for IRadioConfig.getSimSlotsStatus().
      */
     public void getSimSlotsStatusResponse(RadioResponseInfo responseInfo,
-                                          ArrayList<SimSlotStatus> slotStatus) {
+            ArrayList<android.hardware.radio.config.V1_0.SimSlotStatus> slotStatus) {
         RILRequest rr = mRadioConfig.processResponse(responseInfo);
 
         if (rr != null) {
@@ -64,6 +64,31 @@
     }
 
     /**
+     * Response function for IRadioConfig.getSimSlotsStatus().
+     */
+    public void getSimSlotsStatusResponse_1_2(RadioResponseInfo responseInfo,
+            ArrayList<android.hardware.radio.config.V1_2.SimSlotStatus> slotStatus) {
+        RILRequest rr = mRadioConfig.processResponse(responseInfo);
+
+        if (rr != null) {
+            ArrayList<IccSlotStatus> ret = RadioConfig.convertHalSlotStatus_1_2(slotStatus);
+            if (responseInfo.error == RadioError.NONE) {
+                // send response
+                RadioResponse.sendMessageResponse(rr.mResult, ret);
+                Rlog.d(TAG, rr.serialString() + "< "
+                        + mRadioConfig.requestToString(rr.mRequest) + " " + ret.toString());
+            } else {
+                rr.onError(responseInfo.error, ret);
+                Rlog.e(TAG, rr.serialString() + "< "
+                        + mRadioConfig.requestToString(rr.mRequest) + " error "
+                        + responseInfo.error);
+            }
+        } else {
+            Rlog.e(TAG, "getSimSlotsStatusResponse_1_2: Error " + responseInfo.toString());
+        }
+    }
+
+    /**
      * Response function for IRadioConfig.setSimSlotsMapping().
      */
     public void setSimSlotsMappingResponse(RadioResponseInfo responseInfo) {
@@ -86,5 +111,16 @@
         }
     }
 
+    /**
+     * Response function for IRadioConfig.getPhoneCapability().
+     */
+    public void getPhoneCapabilityResponse(RadioResponseInfo info,
+            PhoneCapability phoneCapability) {
+    }
 
+    /**
+     * Response function for IRadioConfig.setPreferredDataModem().
+     */
+    public void setPreferredDataModemResponse(RadioResponseInfo info) {
+    }
 }
diff --git a/src/java/com/android/internal/telephony/RadioIndication.java b/src/java/com/android/internal/telephony/RadioIndication.java
index f7a7943..1614a27 100644
--- a/src/java/com/android/internal/telephony/RadioIndication.java
+++ b/src/java/com/android/internal/telephony/RadioIndication.java
@@ -91,6 +91,7 @@
 import android.telephony.PhysicalChannelConfig;
 import android.telephony.SignalStrength;
 import android.telephony.SmsMessage;
+import android.telephony.TelephonyManager;
 
 import com.android.internal.telephony.cdma.CdmaCallWaitingNotification;
 import com.android.internal.telephony.cdma.CdmaInformationRecords;
@@ -120,13 +121,13 @@
     public void radioStateChanged(int indicationType, int radioState) {
         mRil.processIndication(indicationType);
 
-        CommandsInterface.RadioState newState = getRadioStateFromInt(radioState);
+        int state = getRadioStateFromInt(radioState);
         if (RIL.RILJ_LOGD) {
             mRil.unsljLogMore(RIL_UNSOL_RESPONSE_RADIO_STATE_CHANGED, "radioStateChanged: " +
-                    newState);
+                    state);
         }
 
-        mRil.setRadioState(newState);
+        mRil.setRadioState(state, false /* forceNotifyRegistrants */);
     }
 
     public void callStateChanged(int indicationType) {
@@ -293,7 +294,10 @@
                     break;
             }
 
-            response.add(new PhysicalChannelConfig(status, config.cellBandwidthDownlink));
+            response.add(new PhysicalChannelConfig.Builder()
+                    .setCellConnectionStatus(status)
+                    .setCellBandwidthDownlinkKhz(config.cellBandwidthDownlink)
+                    .build());
         }
 
         if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_PHYSICAL_CHANNEL_CONFIG, response);
@@ -668,7 +672,6 @@
         // Initial conditions
         mRil.setRadioPower(false, null);
         mRil.setCdmaSubscriptionSource(mRil.mCdmaSubscription, null);
-        mRil.setCellInfoListRate();
         // todo: this should not require a version number now. Setting it to latest RIL version for
         // now.
         mRil.notifyRegistrantsRilConnectionChanged(15);
@@ -904,18 +907,22 @@
         mRil.mNattKeepaliveStatusRegistrants.notifyRegistrants(new AsyncResult(null, ks, null));
     }
 
-    private CommandsInterface.RadioState getRadioStateFromInt(int stateInt) {
-        CommandsInterface.RadioState state;
+    /**
+     * @param stateInt
+     * @return {@link TelephonyManager.RadioPowerState RadioPowerState}
+     */
+    private @TelephonyManager.RadioPowerState int getRadioStateFromInt(int stateInt) {
+        int state;
 
         switch(stateInt) {
             case android.hardware.radio.V1_0.RadioState.OFF:
-                state = CommandsInterface.RadioState.RADIO_OFF;
+                state = TelephonyManager.RADIO_POWER_OFF;
                 break;
             case android.hardware.radio.V1_0.RadioState.UNAVAILABLE:
-                state = CommandsInterface.RadioState.RADIO_UNAVAILABLE;
+                state = TelephonyManager.RADIO_POWER_UNAVAILABLE;
                 break;
             case android.hardware.radio.V1_0.RadioState.ON:
-                state = CommandsInterface.RadioState.RADIO_ON;
+                state = TelephonyManager.RADIO_POWER_ON;
                 break;
             default:
                 throw new RuntimeException("Unrecognized RadioState: " + stateInt);
diff --git a/src/java/com/android/internal/telephony/RadioResponse.java b/src/java/com/android/internal/telephony/RadioResponse.java
index e790ab6..af6dcc3 100644
--- a/src/java/com/android/internal/telephony/RadioResponse.java
+++ b/src/java/com/android/internal/telephony/RadioResponse.java
@@ -113,6 +113,15 @@
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param cardStatus ICC card status as defined by CardStatus in 1.4/types.hal
+     */
+    public void getIccCardStatusResponse_1_4(RadioResponseInfo responseInfo,
+                                             android.hardware.radio.V1_4.CardStatus cardStatus) {
+        responseIccCardStatus_1_4(responseInfo, cardStatus);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
      * @param remainingAttempts Number of retries remaining, must be equal to -1 if unknown.
      */
     public void supplyIccPinForAppResponse(RadioResponseInfo responseInfo, int remainingAttempts) {
@@ -1313,37 +1322,36 @@
             android.hardware.radio.V1_1.KeepaliveStatus keepaliveStatus) {
 
         RILRequest rr = mRil.processResponse(responseInfo);
-
-        if (rr == null) {
-            return;
-        }
+        if (rr == null) return;
 
         KeepaliveStatus ret = null;
-
-        switch(responseInfo.error) {
-            case RadioError.NONE:
-                int convertedStatus = convertHalKeepaliveStatusCode(keepaliveStatus.code);
-                if (convertedStatus < 0) {
+        try {
+            switch(responseInfo.error) {
+                case RadioError.NONE:
+                    int convertedStatus = convertHalKeepaliveStatusCode(keepaliveStatus.code);
+                    if (convertedStatus < 0) {
+                        ret = new KeepaliveStatus(KeepaliveStatus.ERROR_UNSUPPORTED);
+                    } else {
+                        ret = new KeepaliveStatus(keepaliveStatus.sessionHandle, convertedStatus);
+                    }
+                    // If responseInfo.error is NONE, response function sends the response message
+                    // even if result is actually an error.
+                    sendMessageResponse(rr.mResult, ret);
+                    break;
+                case RadioError.REQUEST_NOT_SUPPORTED:
                     ret = new KeepaliveStatus(KeepaliveStatus.ERROR_UNSUPPORTED);
-                } else {
-                    ret = new KeepaliveStatus(keepaliveStatus.sessionHandle, convertedStatus);
-                }
-                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);
-                break;
-            default:
-                ret = new KeepaliveStatus(KeepaliveStatus.ERROR_UNKNOWN);
-                break;
+                    break;
+                case RadioError.NO_RESOURCES:
+                    ret = new KeepaliveStatus(KeepaliveStatus.ERROR_NO_RESOURCES);
+                    break;
+                default:
+                    ret = new KeepaliveStatus(KeepaliveStatus.ERROR_UNKNOWN);
+                    break;
+            }
+        } finally {
+            // If responseInfo.error != NONE, the processResponseDone sends the response message.
+            mRil.processResponseDone(rr, responseInfo, ret);
         }
-        sendMessageResponse(rr.mResult, ret);
-        mRil.processResponseDone(rr, responseInfo, ret);
     }
 
     /**
@@ -1351,16 +1359,16 @@
      */
     public void stopKeepaliveResponse(RadioResponseInfo responseInfo) {
         RILRequest rr = mRil.processResponse(responseInfo);
+        if (rr == null) return;
 
-        if (rr == null) {
-            return;
-        }
-
-        if (responseInfo.error == RadioError.NONE) {
-            sendMessageResponse(rr.mResult, null);
+        try {
+            if (responseInfo.error == RadioError.NONE) {
+                sendMessageResponse(rr.mResult, null);
+            } else {
+                //TODO: Error code translation
+            }
+        } finally {
             mRil.processResponseDone(rr, responseInfo, null);
-        } else {
-            //TODO: Error code translation
         }
     }
 
@@ -1442,6 +1450,24 @@
         }
     }
 
+    private void responseIccCardStatus_1_4(RadioResponseInfo responseInfo,
+                                           android.hardware.radio.V1_4.CardStatus cardStatus) {
+        RILRequest rr = mRil.processResponse(responseInfo);
+
+        if (rr != null) {
+            IccCardStatus iccCardStatus = convertHalCardStatus(cardStatus.base.base);
+            iccCardStatus.physicalSlotIndex = cardStatus.base.physicalSlotId;
+            iccCardStatus.atr = cardStatus.base.atr;
+            iccCardStatus.iccid = cardStatus.base.iccid;
+            iccCardStatus.eid = cardStatus.eid;
+            mRil.riljLog("responseIccCardStatus: from HIDL: " + iccCardStatus);
+            if (responseInfo.error == RadioError.NONE) {
+                sendMessageResponse(rr.mResult, iccCardStatus);
+            }
+            mRil.processResponseDone(rr, responseInfo, iccCardStatus);
+        }
+    }
+
     private void responseInts(RadioResponseInfo responseInfo, int ...var) {
         final ArrayList<Integer> ints = new ArrayList<>();
         for (int i = 0; i < var.length; i++) {
diff --git a/src/java/com/android/internal/telephony/RatRatcheter.java b/src/java/com/android/internal/telephony/RatRatcheter.java
index 2b4b5e9..59078a4 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;
 
 /**
@@ -110,6 +111,10 @@
             mDataRatchetEnabled = false;
             return;
         }
+
+        boolean newUsingCA = oldSS.isUsingCarrierAggregation()
+                || newSS.isUsingCarrierAggregation()
+                || newSS.getCellBandwidths().length > 1;
         if (mVoiceRatchetEnabled) {
             int newVoiceRat = ratchetRat(oldSS.getRilVoiceRadioTechnology(),
                     newSS.getRilVoiceRadioTechnology());
@@ -128,9 +133,6 @@
             mDataRatchetEnabled = true;
         }
 
-        boolean newUsingCA = oldSS.isUsingCarrierAggregation()
-                || newSS.isUsingCarrierAggregation()
-                || newSS.getCellBandwidths().length > 1;
         newSS.setIsUsingCarrierAggregation(newUsingCA);
     }
 
diff --git a/src/java/com/android/internal/telephony/RcsController.java b/src/java/com/android/internal/telephony/RcsController.java
new file mode 100644
index 0000000..e04b4b6
--- /dev/null
+++ b/src/java/com/android/internal/telephony/RcsController.java
@@ -0,0 +1,70 @@
+/*
+ * 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.content.Context;
+import android.os.ServiceManager;
+import android.telephony.Rlog;
+import android.telephony.rcs.RcsManager;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.rcs.IRcs;
+
+/** Backing implementation of {@link RcsManager}. */
+public class RcsController extends IRcs.Stub {
+    private static final String TAG = "RcsController";
+    private static final String RCS_SERVICE_NAME = "ircs";
+
+    private static RcsController sInstance;
+
+    private final Context mContext;
+
+    /** Initialize the instance. Should only be called once. */
+    public static RcsController init(Context context) {
+        synchronized (RcsController.class) {
+            if (sInstance == null) {
+                sInstance = new RcsController(context);
+            } else {
+                Rlog.e(TAG, "init() called multiple times! sInstance = " + sInstance);
+            }
+        }
+        return sInstance;
+    }
+
+    private RcsController(Context context) {
+        mContext = context;
+        if (ServiceManager.getService(RCS_SERVICE_NAME) == null) {
+            ServiceManager.addService(RCS_SERVICE_NAME, this);
+        }
+    }
+
+    @VisibleForTesting
+    public RcsController(Context context, Void unused) {
+        mContext = context;
+    }
+
+    @Override
+    public void deleteThread(int threadId) {
+        // TODO - add implementation
+    }
+
+    @Override
+    public int getMessageCount(int rcsThreadId) {
+        // TODO - add implementation. Return a magic number for now to test the RPC calls
+        return 1018;
+    }
+}
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 1e5afc7..2420ac4 100644
--- a/src/java/com/android/internal/telephony/SMSDispatcher.java
+++ b/src/java/com/android/internal/telephony/SMSDispatcher.java
@@ -17,8 +17,6 @@
 package com.android.internal.telephony;
 
 import static android.Manifest.permission.SEND_SMS_NO_CONFIRMATION;
-import static com.android.internal.telephony.IccSmsInterfaceManager.SMS_MESSAGE_PERIOD_NOT_SPECIFIED;
-import static com.android.internal.telephony.IccSmsInterfaceManager.SMS_MESSAGE_PRIORITY_NOT_SPECIFIED;
 import static android.telephony.SmsManager.RESULT_ERROR_FDN_CHECK_FAILURE;
 import static android.telephony.SmsManager.RESULT_ERROR_GENERIC_FAILURE;
 import static android.telephony.SmsManager.RESULT_ERROR_LIMIT_EXCEEDED;
@@ -28,6 +26,9 @@
 import static android.telephony.SmsManager.RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED;
 import static android.telephony.SmsManager.RESULT_ERROR_SHORT_CODE_NOT_ALLOWED;
 
+import static com.android.internal.telephony.IccSmsInterfaceManager.SMS_MESSAGE_PERIOD_NOT_SPECIFIED;
+import static com.android.internal.telephony.IccSmsInterfaceManager.SMS_MESSAGE_PRIORITY_NOT_SPECIFIED;
+
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.app.Activity;
@@ -80,8 +81,8 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.telephony.cdma.sms.UserData;
 import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
+import com.android.internal.telephony.cdma.sms.UserData;
 import com.android.internal.telephony.uicc.UiccCard;
 import com.android.internal.telephony.uicc.UiccController;
 
@@ -220,11 +221,6 @@
         }
     }
 
-    protected void updatePhoneObject(Phone phone) {
-        mPhone = phone;
-        Rlog.d(TAG, "Active phone changed to " + mPhone.getPhoneName() );
-    }
-
     /** Unregister for incoming SMS events. */
     public void dispose() {
         mContext.getContentResolver().unregisterContentObserver(mSettingsObserver);
@@ -876,6 +872,16 @@
         }
     }
 
+    private void triggerSentIntentForFailure(List<PendingIntent> sentIntents) {
+        if (sentIntents == null) {
+            return;
+        }
+
+        for (PendingIntent sentIntent : sentIntents) {
+            triggerSentIntentForFailure(sentIntent);
+        }
+    }
+
     private boolean sendSmsByCarrierApp(boolean isDataSms, SmsTracker tracker ) {
         String carrierPackage = getCarrierAppPackageName();
         if (carrierPackage != null) {
@@ -956,14 +962,19 @@
      *  Validity Period(Maximum) -> 635040 mins(i.e.63 weeks).
      *  Any Other values included Negative considered as Invalid Validity Period of the message.
      */
-    protected void sendMultipartText(String destAddr, String scAddr,
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
+    public void sendMultipartText(String destAddr, String scAddr,
             ArrayList<String> parts, ArrayList<PendingIntent> sentIntents,
             ArrayList<PendingIntent> deliveryIntents, Uri messageUri, String callingPkg,
             boolean persistMessage, int priority, boolean expectMore, int validityPeriod) {
         final String fullMessageText = getMultipartMessageText(parts);
         int refNumber = getNextConcatenatedRef() & 0x00FF;
-        int msgCount = parts.size();
         int encoding = SmsConstants.ENCODING_UNKNOWN;
+        int msgCount = parts.size();
+        if (msgCount < 1) {
+            triggerSentIntentForFailure(sentIntents);
+            return;
+        }
 
         TextEncodingDetails[] encodingForParts = new TextEncodingDetails[msgCount];
         for (int i = 0; i < msgCount; i++) {
@@ -1018,15 +1029,13 @@
                         sentIntent, deliveryIntent, (i == (msgCount - 1)),
                         unsentPartCount, anyPartFailed, messageUri,
                         fullMessageText, priority, expectMore, validityPeriod);
+            if (trackers[i] == null) {
+                triggerSentIntentForFailure(sentIntents);
+                return;
+            }
             trackers[i].mPersistMessage = persistMessage;
         }
 
-        if (parts == null || trackers == null || trackers.length == 0
-                || trackers[0] == null) {
-            Rlog.e(TAG, "Cannot send multipart text. parts=" + parts + " trackers=" + trackers);
-            return;
-        }
-
         String carrierPackage = getCarrierAppPackageName();
         if (carrierPackage != null) {
             Rlog.d(TAG, "Found carrier package.");
@@ -1036,11 +1045,7 @@
         } else {
             Rlog.v(TAG, "No carrier package.");
             for (SmsTracker tracker : trackers) {
-                if (tracker != null) {
-                    sendSubmitPdu(tracker);
-                } else {
-                    Rlog.e(TAG, "Null tracker.");
-                }
+                sendSubmitPdu(tracker);
             }
         }
     }
@@ -1073,13 +1078,18 @@
                     com.android.internal.telephony.cdma.SmsMessage.getSubmitPdu(destinationAddress,
                             uData, (deliveryIntent != null) && lastPart, priority);
 
-            HashMap map = getSmsTrackerMap(destinationAddress, scAddress,
-                    message, submitPdu);
-            return getSmsTracker(map, sentIntent, deliveryIntent,
-                    getFormat(), unsentPartCount, anyPartFailed, messageUri, smsHeader,
-                    (!lastPart || expectMore), fullMessageText, true /*isText*/,
-                    true /*persistMessage*/, priority, validityPeriod);
-
+            if (submitPdu != null) {
+                HashMap map = getSmsTrackerMap(destinationAddress, scAddress,
+                        message, submitPdu);
+                return getSmsTracker(map, sentIntent, deliveryIntent,
+                        getFormat(), unsentPartCount, anyPartFailed, messageUri, smsHeader,
+                        (!lastPart || expectMore), fullMessageText, true /*isText*/,
+                        true /*persistMessage*/, priority, validityPeriod);
+            } else {
+                Rlog.e(TAG, "CdmaSMSDispatcher.getNewSubmitPduTracker(): getSubmitPdu() returned "
+                        + "null");
+                return null;
+            }
         } else {
             SmsMessageBase.SubmitPduBase pdu =
                     com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(scAddress,
@@ -1094,7 +1104,8 @@
                         smsHeader, (!lastPart || expectMore), fullMessageText, true /*isText*/,
                         false /*persistMessage*/, priority, validityPeriod);
             } else {
-                Rlog.e(TAG, "GsmSMSDispatcher.sendNewSubmitPdu(): getSubmitPdu() returned null");
+                Rlog.e(TAG, "GsmSMSDispatcher.getNewSubmitPduTracker(): getSubmitPdu() returned "
+                        + "null");
                 return null;
             }
         }
@@ -1196,20 +1207,24 @@
             int rule = mPremiumSmsRule.get();
             int smsCategory = SmsUsageMonitor.CATEGORY_NOT_SHORT_CODE;
             if (rule == PREMIUM_RULE_USE_SIM || rule == PREMIUM_RULE_USE_BOTH) {
-                String simCountryIso = mTelephonyManager.getSimCountryIso();
+                String simCountryIso =
+                        mTelephonyManager.getSimCountryIsoForPhone(mPhone.getPhoneId());
                 if (simCountryIso == null || simCountryIso.length() != 2) {
                     Rlog.e(TAG, "Can't get SIM country Iso: trying network country Iso");
-                    simCountryIso = mTelephonyManager.getNetworkCountryIso();
+                    simCountryIso =
+                            mTelephonyManager.getNetworkCountryIsoForPhone(mPhone.getPhoneId());
                 }
 
                 smsCategory = mSmsDispatchersController.getUsageMonitor().checkDestination(
                         tracker.mDestAddress, simCountryIso);
             }
             if (rule == PREMIUM_RULE_USE_NETWORK || rule == PREMIUM_RULE_USE_BOTH) {
-                String networkCountryIso = mTelephonyManager.getNetworkCountryIso();
+                String networkCountryIso =
+                        mTelephonyManager.getNetworkCountryIsoForPhone(mPhone.getPhoneId());
                 if (networkCountryIso == null || networkCountryIso.length() != 2) {
                     Rlog.e(TAG, "Can't get Network country Iso: trying SIM country Iso");
-                    networkCountryIso = mTelephonyManager.getSimCountryIso();
+                    networkCountryIso =
+                            mTelephonyManager.getSimCountryIsoForPhone(mPhone.getPhoneId());
                 }
 
                 smsCategory = SmsUsageMonitor.mergeShortCodeCategories(smsCategory,
diff --git a/src/java/com/android/internal/telephony/ServiceStateTracker.java b/src/java/com/android/internal/telephony/ServiceStateTracker.java
index 8d1993a..cd1f44a 100644
--- a/src/java/com/android/internal/telephony/ServiceStateTracker.java
+++ b/src/java/com/android/internal/telephony/ServiceStateTracker.java
@@ -21,6 +21,7 @@
 
 import static com.android.internal.telephony.CarrierActionAgent.CARRIER_ACTION_SET_RADIO_ENABLED;
 
+import android.Manifest.permission;
 import android.app.AlarmManager;
 import android.app.Notification;
 import android.app.NotificationManager;
@@ -37,7 +38,6 @@
 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;
@@ -58,9 +58,6 @@
 import android.telephony.CellIdentityTdscdma;
 import android.telephony.CellIdentityWcdma;
 import android.telephony.CellInfo;
-import android.telephony.CellInfoGsm;
-import android.telephony.CellInfoLte;
-import android.telephony.CellInfoWcdma;
 import android.telephony.CellLocation;
 import android.telephony.DataSpecificRegistrationStates;
 import android.telephony.NetworkRegistrationState;
@@ -80,22 +77,22 @@
 import android.util.Pair;
 import android.util.SparseArray;
 import android.util.StatsLog;
-import android.util.TimeUtils;
+import android.util.TimestampedValue;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.cdma.CdmaSubscriptionSourceManager;
 import com.android.internal.telephony.cdma.EriInfo;
-import com.android.internal.telephony.dataconnection.DcTracker;
 import com.android.internal.telephony.dataconnection.TransportManager;
 import com.android.internal.telephony.metrics.TelephonyMetrics;
 import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppState;
+import com.android.internal.telephony.uicc.IccCardStatus.CardState;
 import com.android.internal.telephony.uicc.IccRecords;
 import com.android.internal.telephony.uicc.RuimRecords;
 import com.android.internal.telephony.uicc.SIMRecords;
+import com.android.internal.telephony.uicc.UiccCard;
 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;
 
@@ -103,7 +100,9 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.LinkedList;
 import java.util.List;
+import java.util.Objects;
 import java.util.TimeZone;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.regex.PatternSyntaxException;
@@ -128,8 +127,22 @@
     public ServiceState mSS;
     private ServiceState mNewSS;
 
-    private static final long LAST_CELL_INFO_LIST_MAX_AGE_MS = 2000;
-    private long mLastCellInfoListTime;
+    // This is the minimum interval at which CellInfo requests will be serviced by the modem.
+    // Any requests that arrive within MinInterval of the previous reuqest will simply receive the
+    // cached result. This is a power-saving feature, because requests to the modem may require
+    // wakeup of a separate chip and bus communication. Because the cost of wakeups is
+    // architecture dependent, it would be preferable if this sort of optimization could be
+    // handled in SoC-specific code, but for now, keep it here to ensure that in case further
+    // optimizations are not present elsewhere, there is a power-management scheme of last resort.
+    private int mCellInfoMinIntervalMs =  2000;
+
+    // Maximum time to wait for a CellInfo request before assuming it won't arrive and returning
+    // null to callers. Note, that if a CellInfo response does arrive later, then it will be
+    // treated as an UNSOL, which means it will be cached as well as sent to registrants; thus,
+    // this only impacts the behavior of one-shot requests (be they blocking or non-blocking).
+    private static final long CELL_INFO_LIST_QUERY_TIMEOUT = 2000;
+
+    private long mLastCellInfoReqTime;
     private List<CellInfo> mLastCellInfoList = null;
     private List<PhysicalChannelConfig> mLastPhysicalChannelConfigList = null;
 
@@ -160,16 +173,22 @@
     private RegistrantList mDataRoamingOffRegistrants = new RegistrantList();
     protected RegistrantList mAttachedRegistrants = new RegistrantList();
     protected RegistrantList mDetachedRegistrants = new RegistrantList();
+    private RegistrantList mVoiceRegStateOrRatChangedRegistrants = new RegistrantList();
     private RegistrantList mDataRegStateOrRatChangedRegistrants = new RegistrantList();
     private RegistrantList mNetworkAttachedRegistrants = new RegistrantList();
     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;
     private int mPendingRadioPowerOffAfterDataOffTag = 0;
 
+    // This is a flag for debug purposes only. It it set once the RUIM_RECORDS_LOADED event is
+    // received while the phone type is CDMA-LTE, and is never reset after that.
+    private boolean mRuimRecordsLoaded = false;
+
     /** Signal strength poll rate. */
     private static final int POLL_PERIOD_MILLIS = 20 * 1000;
 
@@ -219,15 +238,14 @@
     protected static final int EVENT_ALL_DATA_DISCONNECTED             = 49;
     protected static final int EVENT_PHONE_TYPE_SWITCHED               = 50;
     protected static final int EVENT_RADIO_POWER_FROM_CARRIER          = 51;
-    protected static final int EVENT_SIM_NOT_INSERTED                  = 52;
     protected static final int EVENT_IMS_SERVICE_STATE_CHANGED         = 53;
     protected static final int EVENT_RADIO_POWER_OFF_DONE              = 54;
     protected static final int EVENT_PHYSICAL_CHANNEL_CONFIG           = 55;
+    protected static final int EVENT_CELL_LOCATION_RESPONSE            = 56;
 
-    private class CellInfoResult {
-        List<CellInfo> list;
-        Object lockObj = new Object();
-    }
+    private List<Message> mPendingCellInfoRequests = new LinkedList<Message>();
+    // @GuardedBy("mPendingCellInfoRequests")
+    private boolean mIsPendingCellInfoRequest = false;
 
     /** Reason for registration denial. */
     protected static final String REGISTRATION_DENIED_GEN  = "General";
@@ -262,7 +280,6 @@
 
     private final RatRatcheter mRatRatcheter;
 
-    private final HandlerThread mHandlerThread;
     private final LocaleTracker mLocaleTracker;
 
     private final LocalLog mRoamingLog = new LocalLog(10);
@@ -270,6 +287,7 @@
     private final LocalLog mPhoneTypeLog = new LocalLog(10);
     private final LocalLog mRatLog = new LocalLog(20);
     private final LocalLog mRadioPowerLog = new LocalLog(20);
+    private final LocalLog mMdnLog = new LocalLog(20);
 
     private class SstSubscriptionsChangedListener extends OnSubscriptionsChangedListener {
         public final AtomicInteger mPreviousSubId =
@@ -340,14 +358,6 @@
                 }
                 // update voicemail count and notify message waiting changed
                 mPhone.updateVoiceMail();
-
-                // cancel notifications if we see SIM_NOT_INSERTED (This happens on bootup before
-                // the SIM is first detected and then subsequently on SIM removals)
-                if (mSubscriptionController.getSlotIndex(subId)
-                        == SubscriptionManager.SIM_NOT_INSERTED) {
-                    // this is handled on the main thread to mitigate racing with setNotification().
-                    sendMessage(obtainMessage(EVENT_SIM_NOT_INSERTED));
-                }
             }
         }
     };
@@ -355,8 +365,8 @@
     //Common
     private final GsmCdmaPhone mPhone;
 
-    public CellLocation mCellLoc;
-    private CellLocation mNewCellLoc;
+    private CellIdentity mCellIdentity;
+    private CellIdentity mNewCellIdentity;
     private static final int MS_PER_HOUR = 60 * 60 * 1000;
     private final NitzStateMachine mNitzState;
     private final ContentResolver mCr;
@@ -435,8 +445,7 @@
                 updateSpnDisplay();
             } else if (intent.getAction().equals(ACTION_RADIO_OFF)) {
                 mAlarmSwitch = false;
-                DcTracker dcTracker = mPhone.mDcTracker;
-                powerOffRadioSafely(dcTracker);
+                powerOffRadioSafely();
             }
         }
     };
@@ -485,7 +494,9 @@
     private static final int INVALID_LTE_EARFCN = -1;
 
     public ServiceStateTracker(GsmCdmaPhone phone, CommandsInterface ci) {
-        mNitzState = TelephonyComponentFactory.getInstance().makeNitzStateMachine(phone);
+        mNitzState = TelephonyComponentFactory.getInstance()
+                .inject(NitzStateMachine.class.getName())
+                .makeNitzStateMachine(phone);
         mPhone = phone;
         mCi = ci;
 
@@ -505,7 +516,7 @@
                 .addOnSubscriptionsChangedListener(mOnSubscriptionsChangedListener);
         mRestrictedState = new RestrictedState();
 
-        mTransportManager = new TransportManager();
+        mTransportManager = mPhone.getTransportManager();
 
         for (int transportType : mTransportManager.getAvailableTransports()) {
             mRegStateManagers.append(transportType, new NetworkRegistrationManager(
@@ -513,13 +524,9 @@
             mRegStateManagers.get(transportType).registerForNetworkRegistrationStateChanged(
                     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());
+        mLocaleTracker = TelephonyComponentFactory.getInstance()
+                .inject(LocaleTracker.class.getName())
+                .makeLocaleTracker(mPhone, mNitzState, getLooper());
 
         mCi.registerForImsNetworkStateChanged(this, EVENT_IMS_STATE_CHANGED, null);
         mCi.registerForRadioStateChanged(this, EVENT_RADIO_STATE_CHANGED, null);
@@ -566,10 +573,13 @@
                 CarrierServiceStateTracker.CARRIER_EVENT_DATA_REGISTRATION, null);
         registerForDataConnectionDetached(mCSST,
                 CarrierServiceStateTracker.CARRIER_EVENT_DATA_DEREGISTRATION, null);
+        registerForImsCapabilityChanged(mCSST,
+                CarrierServiceStateTracker.CARRIER_EVENT_IMS_CAPABILITIES_CHANGED, null);
     }
 
     @VisibleForTesting
     public void updatePhoneType() {
+
         // If we are previously voice roaming, we need to notify that roaming status changed before
         // we change back to non-roaming.
         if (mSS != null && mSS.getVoiceRoaming()) {
@@ -593,17 +603,21 @@
         }
 
         mSS = new ServiceState();
+        mSS.setStateOutOfService();
         mNewSS = new ServiceState();
-        mLastCellInfoListTime = 0;
+        mLastCellInfoReqTime = 0;
         mLastCellInfoList = null;
         mSignalStrength = new SignalStrength();
         mStartedGprsRegCheck = false;
         mReportedGprsNoReg = false;
         mMdn = null;
+        logMdnChange("updatePhoneType: setting mMdn to null");
         mMin = null;
         mPrlVersion = null;
         mIsMinInfoReady = false;
-        mNitzState.handleNetworkUnavailable();
+        mNitzState.handleNetworkCountryCodeUnavailable();
+        mCellIdentity = null;
+        mNewCellIdentity = null;
 
         //cancel any pending pollstate request on voice tech switching
         cancelPollState();
@@ -619,12 +633,8 @@
             mCi.unregisterForCdmaOtaProvision(this);
             mPhone.unregisterForSimRecordsLoaded(this);
 
-            mCellLoc = new GsmCellLocation();
-            mNewCellLoc = new GsmCellLocation();
         } else {
             mPhone.registerForSimRecordsLoaded(this, EVENT_SIM_RECORDS_LOADED, null);
-            mCellLoc = new CdmaCellLocation();
-            mNewCellLoc = new CdmaCellLocation();
             mCdmaSSM = CdmaSubscriptionSourceManager.getInstance(mPhone.getContext(), mCi, this,
                     EVENT_CDMA_SUBSCRIPTION_SOURCE_CHANGED, null);
             mIsSubscriptionFromRuim = (mCdmaSSM.getCdmaSubscriptionSource() ==
@@ -655,6 +665,7 @@
         logPhoneTypeChange();
 
         // Tell everybody that the registration state and RAT have changed.
+        notifyVoiceRegStateRilRadioTechnologyChanged();
         notifyDataRegStateRilRadioTechnologyChanged();
     }
 
@@ -673,7 +684,6 @@
         mCi.unregisterForPhysicalChannelConfiguration(this);
         mSubscriptionManager
             .removeOnSubscriptionsChangedListener(mOnSubscriptionsChangedListener);
-        mHandlerThread.quit();
         mCi.unregisterForImsNetworkStateChanged(this);
         mPhone.getCarrierActionAgent().unregisterForCarrierAction(this,
                 CARRIER_ACTION_SET_RADIO_ENABLED);
@@ -705,6 +715,19 @@
     }
 
     /**
+     * Notify all mVoiceRegStateOrRatChangedRegistrants using an
+     * AsyncResult in msg.obj where AsyncResult#result contains the
+     * new RAT as an Integer Object.
+     */
+    protected void notifyVoiceRegStateRilRadioTechnologyChanged() {
+        int rat = mSS.getRilVoiceRadioTechnology();
+        int vrs = mSS.getVoiceRegState();
+        if (DBG) log("notifyVoiceRegStateRilRadioTechnologyChanged: vrs=" + vrs + " rat=" + rat);
+
+        mVoiceRegStateOrRatChangedRegistrants.notifyResult(new Pair<Integer, Integer>(vrs, rat));
+    }
+
+    /**
      * Notify all mDataConnectionRatChangeRegistrants using an
      * AsyncResult in msg.obj where AsyncResult#result contains the
      * new RAT as an Integer Object.
@@ -903,77 +926,6 @@
         }
     }
 
-    private void processCellLocationInfo(CellLocation cellLocation, CellIdentity cellIdentity) {
-        if (mPhone.isPhoneTypeGsm()) {
-            int psc = -1;
-            int cid = -1;
-            int lac = -1;
-            if (cellIdentity != null) {
-                switch (cellIdentity.getType()) {
-                    case CellInfoType.GSM: {
-                        cid = ((CellIdentityGsm) cellIdentity).getCid();
-                        lac = ((CellIdentityGsm) cellIdentity).getLac();
-                        break;
-                    }
-                    case CellInfoType.WCDMA: {
-                        cid = ((CellIdentityWcdma) cellIdentity).getCid();
-                        lac = ((CellIdentityWcdma) cellIdentity).getLac();
-                        psc = ((CellIdentityWcdma) cellIdentity).getPsc();
-                        break;
-                    }
-                    case CellInfoType.TD_SCDMA: {
-                        cid = ((CellIdentityTdscdma) cellIdentity).getCid();
-                        lac = ((CellIdentityTdscdma) cellIdentity).getLac();
-                        break;
-                    }
-                    case CellInfoType.LTE: {
-                        cid = ((CellIdentityLte) cellIdentity).getCi();
-                        lac = ((CellIdentityLte) cellIdentity).getTac();
-                        break;
-                    }
-                    default: {
-                        break;
-                    }
-                }
-            }
-            // LAC and CID are -1 if not avail
-            ((GsmCellLocation) cellLocation).setLacAndCid(lac, cid);
-            ((GsmCellLocation) cellLocation).setPsc(psc);
-        } else {
-            int baseStationId = -1;
-            int baseStationLatitude = CdmaCellLocation.INVALID_LAT_LONG;
-            int baseStationLongitude = CdmaCellLocation.INVALID_LAT_LONG;
-            int systemId = 0;
-            int networkId = 0;
-
-            if (cellIdentity != null) {
-                switch (cellIdentity.getType()) {
-                    case CellInfoType.CDMA: {
-                        baseStationId = ((CellIdentityCdma) cellIdentity).getBasestationId();
-                        baseStationLatitude = ((CellIdentityCdma) cellIdentity).getLatitude();
-                        baseStationLongitude = ((CellIdentityCdma) cellIdentity).getLongitude();
-                        systemId = ((CellIdentityCdma) cellIdentity).getSystemId();
-                        networkId = ((CellIdentityCdma) cellIdentity).getNetworkId();
-                        break;
-                    }
-                    default: {
-                        break;
-                    }
-                }
-            }
-
-            // Some carriers only return lat-lngs of 0,0
-            if (baseStationLatitude == 0 && baseStationLongitude == 0) {
-                baseStationLatitude  = CdmaCellLocation.INVALID_LAT_LONG;
-                baseStationLongitude = CdmaCellLocation.INVALID_LAT_LONG;
-            }
-
-            // Values are -1 if not available.
-            ((CdmaCellLocation) cellLocation).setCellLocationData(baseStationId,
-                    baseStationLatitude, baseStationLongitude, systemId, networkId);
-        }
-    }
-
     private int getLteEarfcn(CellIdentity cellIdentity) {
         int lteEarfcn = INVALID_LTE_EARFCN;
         if (cellIdentity != null) {
@@ -1015,6 +967,16 @@
                 break;
 
             case EVENT_ICC_CHANGED:
+                if (isSimAbsent()) {
+                    if (DBG) log("EVENT_ICC_CHANGED: SIM absent");
+                    // cancel notifications if SIM is removed/absent
+                    cancelAllNotifications();
+                    // clear cached values on SIM removal
+                    mMdn = null;
+                    logMdnChange("EVENT_ICC_CHANGED: setting mMdn to null");
+                    mMin = null;
+                    mIsMinInfoReady = false;
+                }
                 onUpdateIccAvailability();
                 if (mUiccApplcation != null
                         && mUiccApplcation.getState() != AppState.APPSTATE_READY) {
@@ -1023,40 +985,53 @@
                 }
                 break;
 
-            case EVENT_GET_CELL_INFO_LIST: {
-                ar = (AsyncResult) msg.obj;
-                CellInfoResult result = (CellInfoResult) ar.userObj;
-                synchronized(result.lockObj) {
+            case EVENT_GET_CELL_INFO_LIST: // fallthrough
+            case EVENT_UNSOL_CELL_INFO_LIST: {
+                List<CellInfo> cellInfo = null;
+                Throwable ex = null;
+                if (msg.obj != null) {
+                    ar = (AsyncResult) msg.obj;
                     if (ar.exception != null) {
                         log("EVENT_GET_CELL_INFO_LIST: error ret null, e=" + ar.exception);
-                        result.list = null;
+                        ex = ar.exception;
+                    } else if (ar.result == null) {
+                        loge("Invalid CellInfo result");
                     } else {
-                        result.list = (List<CellInfo>) ar.result;
-
+                        cellInfo = (List<CellInfo>) ar.result;
+                        mLastCellInfoList = cellInfo;
+                        mPhone.notifyCellInfo(cellInfo);
                         if (VDBG) {
-                            log("EVENT_GET_CELL_INFO_LIST: size=" + result.list.size()
-                                    + " list=" + result.list);
+                            log("CELL_INFO_LIST: size=" + cellInfo.size() + " list=" + cellInfo);
                         }
                     }
-                    mLastCellInfoListTime = SystemClock.elapsedRealtime();
-                    mLastCellInfoList = result.list;
-                    result.lockObj.notify();
-                }
-                break;
-            }
-
-            case EVENT_UNSOL_CELL_INFO_LIST: {
-                ar = (AsyncResult) msg.obj;
-                if (ar.exception != null) {
-                    log("EVENT_UNSOL_CELL_INFO_LIST: error ignoring, e=" + ar.exception);
                 } else {
-                    List<CellInfo> list = (List<CellInfo>) ar.result;
-                    if (VDBG) {
-                        log("EVENT_UNSOL_CELL_INFO_LIST: size=" + list.size() + " list=" + list);
+                    // If we receive an empty message, it's probably a timeout; if there is no
+                    // pending request, drop it.
+                    if (!mIsPendingCellInfoRequest) break;
+                    // If there is a request pending, we still need to check whether it's a timeout
+                    // for the current request of whether it's leftover from a previous request.
+                    final long curTime = SystemClock.elapsedRealtime();
+                    if ((curTime - mLastCellInfoReqTime) <  CELL_INFO_LIST_QUERY_TIMEOUT) {
+                        break;
                     }
-                    mLastCellInfoListTime = SystemClock.elapsedRealtime();
-                    mLastCellInfoList = list;
-                    mPhone.notifyCellInfo(list);
+                    // We've received a legitimate timeout, so something has gone terribly wrong.
+                    loge("Timeout waiting for CellInfo; (everybody panic)!");
+                    mLastCellInfoList = null;
+                    // Since the timeout is applicable, fall through and update all synchronous
+                    // callers with the failure.
+                }
+                synchronized (mPendingCellInfoRequests) {
+                    // If we have pending requests, then service them. Note that in case of a
+                    // timeout, we send null responses back to the callers.
+                    if (mIsPendingCellInfoRequest) {
+                        // regardless of timeout or valid response, when something arrives,
+                        mIsPendingCellInfoRequest = false;
+                        for (Message m : mPendingCellInfoRequests) {
+                            AsyncResult.forMessage(m, cellInfo, ex);
+                            m.sendToTarget();
+                        }
+                        mPendingCellInfoRequests.clear();
+                    }
                 }
                 break;
             }
@@ -1075,7 +1050,8 @@
 
             case EVENT_RADIO_POWER_OFF_DONE:
                 if (DBG) log("EVENT_RADIO_POWER_OFF_DONE");
-                if (mDeviceShuttingDown && mCi.getRadioState().isAvailable()) {
+                if (mDeviceShuttingDown && mCi.getRadioState()
+                        != TelephonyManager.RADIO_POWER_UNAVAILABLE) {
                     // during shutdown the modem may not send radio state changed event
                     // as a result of radio power request
                     // Hence, issuing shut down regardless of radio power response
@@ -1099,7 +1075,7 @@
             case EVENT_RADIO_STATE_CHANGED:
             case EVENT_PHONE_TYPE_SWITCHED:
                 if(!mPhone.isPhoneTypeGsm() &&
-                        mCi.getRadioState() == CommandsInterface.RadioState.RADIO_ON) {
+                        mCi.getRadioState() == TelephonyManager.RADIO_POWER_ON) {
                     handleCdmaSubscriptionSource(mCdmaSSM.getCdmaSubscriptionSource());
 
                     // Signal strength polling stops when radio is off.
@@ -1119,7 +1095,7 @@
                 // This callback is called when signal strength is polled
                 // all by itself
 
-                if (!(mCi.getRadioState().isOn())) {
+                if (!(mCi.getRadioState() == TelephonyManager.RADIO_POWER_ON)) {
                     // Polling will continue when radio turns back on
                     return;
                 }
@@ -1134,8 +1110,8 @@
                 if (ar.exception == null) {
                     CellIdentity cellIdentity = ((NetworkRegistrationState) ar.result)
                             .getCellIdentity();
-                    processCellLocationInfo(mCellLoc, cellIdentity);
-                    mPhone.notifyLocationChanged();
+                    mCellIdentity = cellIdentity;
+                    mPhone.notifyLocationChanged(getCellLocation());
                 }
 
                 // Release any temporary cell lock, which could have been
@@ -1251,9 +1227,8 @@
                     // Can't register data service while voice service is ok
                     // i.e. CREG is ok while CGREG is not
                     // possible a network or baseband side error
-                    GsmCellLocation loc = ((GsmCellLocation)mPhone.getCellLocation());
                     EventLog.writeEvent(EventLogTags.DATA_NETWORK_REGISTRATION_FAIL,
-                            mSS.getOperatorNumeric(), loc != null ? loc.getCid() : -1);
+                            mSS.getOperatorNumeric(), getCidFromCellIdentity(mCellIdentity));
                     mReportedGprsNoReg = true;
                 }
                 mStartedGprsRegCheck = false;
@@ -1272,14 +1247,6 @@
                 }
                 break;
 
-            case EVENT_SIM_NOT_INSERTED:
-                if (DBG) log("EVENT_SIM_NOT_INSERTED");
-                cancelAllNotifications();
-                mMdn = null;
-                mMin = null;
-                mIsMinInfoReady = false;
-                break;
-
             case EVENT_ALL_DATA_DISCONNECTED:
                 int dds = SubscriptionManager.getDefaultDataSubscriptionId();
                 ProxyController.getInstance().unregisterForAllDataDisconnected(dds, this);
@@ -1303,6 +1270,7 @@
             case EVENT_IMS_CAPABILITY_CHANGED:
                 if (DBG) log("EVENT_IMS_CAPABILITY_CHANGED");
                 updateSpnDisplay();
+                mImsCapabilityChangedRegistrants.notifyRegistrants();
                 break;
 
             case EVENT_IMS_SERVICE_STATE_CHANGED:
@@ -1354,6 +1322,8 @@
                         String cdmaSubscription[] = (String[]) ar.result;
                         if (cdmaSubscription != null && cdmaSubscription.length >= 5) {
                             mMdn = cdmaSubscription[0];
+                            logMdnChange("EVENT_POLL_STATE_CDMA_SUBSCRIPTION: setting mMdn to "
+                                    + mMdn);
                             parseSidNid(cdmaSubscription[1], cdmaSubscription[2]);
 
                             mMin = cdmaSubscription[3];
@@ -1395,17 +1365,25 @@
                         updateSpnDisplay();
                     } else {
                         RuimRecords ruim = (RuimRecords) mIccRecords;
+                        mRuimRecordsLoaded = true;
                         if (ruim != null) {
                             if (ruim.isProvisioned()) {
                                 mMdn = ruim.getMdn();
+                                logMdnChange("EVENT_RUIM_RECORDS_LOADED: setting mMdn to " + mMdn);
                                 mMin = ruim.getMin();
                                 parseSidNid(ruim.getSid(), ruim.getNid());
                                 mPrlVersion = ruim.getPrlVersion();
                                 mIsMinInfoReady = true;
+                            } else {
+                                logMdnChange("EVENT_RUIM_RECORDS_LOADED: ruim not provisioned; "
+                                        + "not updating mMdn " + mMdn);
                             }
                             updateOtaspState();
                             // Notify apps subscription info is ready
                             notifyCdmaSubscriptionInfoReady();
+                        } else {
+                            logMdnChange("EVENT_RUIM_RECORDS_LOADED: ruim is null; "
+                                    + "not updating mMdn " + mMdn);
                         }
                         // SID/NID/PRL is loaded. Poll service state
                         // again to update to the roaming state with
@@ -1469,12 +1447,42 @@
                 }
                 break;
 
+            case EVENT_CELL_LOCATION_RESPONSE:
+                ar = (AsyncResult) msg.obj;
+                if (ar == null) {
+                    loge("Invalid null response to getCellLocation!");
+                    break;
+                }
+                // This response means that the correct CellInfo is already cached; thus we
+                // can rely on the last cell info to already contain any cell info that is
+                // available, which means that we can return the result of the existing
+                // getCellLocation() function without any additional processing here.
+                Message rspRspMsg = (Message) ar.userObj;
+                AsyncResult.forMessage(rspRspMsg, getCellLocation(), ar.exception);
+                rspRspMsg.sendToTarget();
+                break;
+
             default:
                 log("Unhandled message with number: " + msg.what);
                 break;
         }
     }
 
+    private boolean isSimAbsent() {
+        boolean simAbsent;
+        if (mUiccController == null) {
+            simAbsent = true;
+        } else {
+            UiccCard uiccCard = mUiccController.getUiccCard(mPhone.getPhoneId());
+            if (uiccCard == null) {
+                simAbsent = true;
+            } else {
+                simAbsent = (uiccCard.getCardState() == CardState.CARDSTATE_ABSENT);
+            }
+        }
+        return simAbsent;
+    }
+
     private int[] getBandwidthsFromConfigs(List<PhysicalChannelConfig> list) {
         return list.stream()
                 .map(PhysicalChannelConfig::getCellBandwidthDownlink)
@@ -1508,6 +1516,28 @@
     }
 
     public String getMdnNumber() {
+        // if for CDMA-LTE phone MDN is null, and it has already been updated from RUIM, in some
+        // unknown error scenario mMdn may still have been updated to null. Detect and fix that case
+        if (mMdn == null && mRuimRecordsLoaded && mPhone.isPhoneTypeCdmaLte()) {
+            // query RuimRecords to see if it's not null and the value from there can be used. This
+            // should never be the case except in certain error scenarios/race conditions.
+            RuimRecords ruim = (RuimRecords) mIccRecords;
+            if (ruim != null) {
+                if (ruim.isProvisioned() && ruim.getMdn() != null) {
+                    logeMdnChange("getMdnNumber: mMdn is null when RuimRecords.getMdn() is not");
+
+                    // broadcast intent to indicate an error related to Line1Number has been
+                    // detected
+                    Intent intent = new Intent(TelephonyIntents.ACTION_LINE1_NUMBER_ERROR_DETECTED);
+                    intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+                    mPhone.getContext().sendBroadcast(intent,
+                            permission.READ_PRIVILEGED_PHONE_STATE);
+
+                    // update mdn
+                    mMdn = ruim.getMdn();
+                }
+            }
+        }
         return mMdn;
     }
 
@@ -1660,9 +1690,9 @@
         mPollingContext[0]--;
 
         if (mPollingContext[0] == 0) {
+            mNewSS.setEmergencyOnly(mEmergencyOnly);
             if (mPhone.isPhoneTypeGsm()) {
                 updateRoamingState();
-                mNewSS.setEmergencyOnly(mEmergencyOnly);
             } else {
                 boolean namMatch = false;
                 if (!isSidsAllZeros() && isHomeSid(mNewSS.getCdmaSystemId())) {
@@ -1803,7 +1833,8 @@
                 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);
@@ -1811,7 +1842,6 @@
 
                     boolean isVoiceCapable = mPhone.getContext().getResources()
                             .getBoolean(com.android.internal.R.bool.config_voice_capable);
-                    mEmergencyOnly = networkRegState.isEmergencyEnabled();
                 } else {
                     int roamingIndicator = voiceSpecificStates.roamingIndicator;
 
@@ -1856,7 +1886,7 @@
                     }
                 }
 
-                processCellLocationInfo(mNewCellLoc, networkRegState.getCellIdentity());
+                mNewCellIdentity = networkRegState.getCellIdentity();
 
                 if (DBG) {
                     log("handlPollVoiceRegResultMessage: regState=" + registrationState
@@ -1887,12 +1917,9 @@
 
                 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
-                    // overlay or carrier config possibly overrides it.
-                    mNewSS.setDataRoamingFromRegistration(mDataRoaming);
 
                     if (DBG) {
                         log("handlPollStateResultMessage: GsmSST dataServiceState=" + serviceState
@@ -1903,9 +1930,6 @@
 
                     boolean isDataRoaming = regCodeIsRoaming(registrationState);
                     mNewSS.setDataRoaming(isDataRoaming);
-                    // Save the data roaming state reported by modem registration before resource
-                    // overlay or carrier config possibly overrides it.
-                    mNewSS.setDataRoamingFromRegistration(isDataRoaming);
 
                     if (DBG) {
                         log("handlPollStateResultMessage: cdma dataServiceState=" + serviceState
@@ -1932,9 +1956,6 @@
                     // voice roaming state in done while handling EVENT_POLL_STATE_REGISTRATION_CDMA
                     boolean isDataRoaming = regCodeIsRoaming(registrationState);
                     mNewSS.setDataRoaming(isDataRoaming);
-                    // Save the data roaming state reported by modem registration before resource
-                    // overlay or carrier config possibly overrides it.
-                    mNewSS.setDataRoamingFromRegistration(isDataRoaming);
                     if (DBG) {
                         log("handlPollStateResultMessage: CdmaLteSST dataServiceState="
                                 + serviceState + " registrationState=" + registrationState
@@ -2037,6 +2058,27 @@
         }
     }
 
+    /**
+     * Extract the CID/CI for GSM/UTRA/EUTRA
+     *
+     * @returns the cell ID (unique within a PLMN for a given tech) or -1 if invalid
+     */
+    private static int getCidFromCellIdentity(CellIdentity id) {
+        if (id == null) return -1;
+        int cid = -1;
+        switch(id.getType()) {
+            case CellInfo.TYPE_GSM: cid = ((CellIdentityGsm) id).getCid(); break;
+            case CellInfo.TYPE_WCDMA: cid = ((CellIdentityWcdma) id).getCid(); break;
+            case CellInfo.TYPE_TDSCDMA: cid = ((CellIdentityTdscdma) id).getCid(); break;
+            case CellInfo.TYPE_LTE: cid = ((CellIdentityLte) id).getCi(); break;
+            default: break;
+        }
+        // If the CID is unreported
+        if (cid == Integer.MAX_VALUE) cid = -1;
+
+        return cid;
+    }
+
     private void setPhyCellInfoFromCellIdentity(ServiceState ss, CellIdentity cellIdentity) {
         if (cellIdentity == null) {
             if (DBG) {
@@ -2260,6 +2302,7 @@
 
         String wfcVoiceSpnFormat = null;
         String wfcDataSpnFormat = null;
+        String wfcFlightSpnFormat = null;
         int combinedRegState = getCombinedRegState();
         if (mPhone.getImsPhone() != null && mPhone.getImsPhone().isWifiCallingEnabled()
                 && (combinedRegState == ServiceState.STATE_IN_SERVICE)) {
@@ -2270,10 +2313,10 @@
             //
             // 2) Show PLMN + Wi-Fi Calling if there is no valid SPN in case 1
 
-            String[] wfcSpnFormats = mPhone.getContext().getResources().getStringArray(
-                    com.android.internal.R.array.wfcSpnFormats);
             int voiceIdx = 0;
             int dataIdx = 0;
+            int flightModeIdx = -1;
+            boolean useRootLocale = false;
             CarrierConfigManager configLoader = (CarrierConfigManager)
                     mPhone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
             if (configLoader != null) {
@@ -2283,14 +2326,37 @@
                         voiceIdx = b.getInt(CarrierConfigManager.KEY_WFC_SPN_FORMAT_IDX_INT);
                         dataIdx = b.getInt(
                                 CarrierConfigManager.KEY_WFC_DATA_SPN_FORMAT_IDX_INT);
+                        flightModeIdx = b.getInt(
+                                CarrierConfigManager.KEY_WFC_FLIGHT_MODE_SPN_FORMAT_IDX_INT);
+                        useRootLocale =
+                                b.getBoolean(CarrierConfigManager.KEY_WFC_SPN_USE_ROOT_LOCALE);
                     }
                 } catch (Exception e) {
                     loge("updateSpnDisplay: carrier config error: " + e);
                 }
             }
 
+            String[] wfcSpnFormats = SubscriptionManager.getResourcesForSubId(mPhone.getContext(),
+                    mPhone.getSubId(), useRootLocale)
+                    .getStringArray(com.android.internal.R.array.wfcSpnFormats);
+
+            if (voiceIdx < 0 || voiceIdx >= wfcSpnFormats.length) {
+                loge("updateSpnDisplay: KEY_WFC_SPN_FORMAT_IDX_INT out of bounds: " + voiceIdx);
+                voiceIdx = 0;
+            }
+            if (dataIdx < 0 || dataIdx >= wfcSpnFormats.length) {
+                loge("updateSpnDisplay: KEY_WFC_DATA_SPN_FORMAT_IDX_INT out of bounds: " + dataIdx);
+                dataIdx = 0;
+            }
+            if (flightModeIdx < 0 || flightModeIdx >= wfcSpnFormats.length) {
+                // KEY_WFC_FLIGHT_MODE_SPN_FORMAT_IDX_INT out of bounds. Use the value from
+                // voiceIdx.
+                flightModeIdx = voiceIdx;
+            }
+
             wfcVoiceSpnFormat = wfcSpnFormats[voiceIdx];
             wfcDataSpnFormat = wfcSpnFormats[dataIdx];
+            wfcFlightSpnFormat = wfcSpnFormats[flightModeIdx];
         }
 
         if (mPhone.isPhoneTypeGsm()) {
@@ -2367,6 +2433,11 @@
                 // Show SPN + Wi-Fi Calling If SIM has SPN and SPN display condition
                 // is satisfied or SPN override is enabled for this carrier.
 
+                // Handle Flight Mode
+                if (mSS.getVoiceRegState() == ServiceState.STATE_POWER_OFF) {
+                    wfcVoiceSpnFormat = wfcFlightSpnFormat;
+                }
+
                 String originalSpn = spn.trim();
                 spn = String.format(wfcVoiceSpnFormat, originalSpn);
                 dataSpn = String.format(wfcDataSpnFormat, originalSpn);
@@ -2384,11 +2455,7 @@
                 showSpn = false;
             }
 
-            int subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
-            int[] subIds = SubscriptionManager.getSubId(mPhone.getPhoneId());
-            if (subIds != null && subIds.length > 0) {
-                subId = subIds[0];
-            }
+            int subId = mPhone.getSubId();
 
             // Update SPN_STRINGS_UPDATED_ACTION IFF any value changes
             if (mSubId != subId ||
@@ -2430,18 +2497,14 @@
 
             showPlmn = plmn != null;
 
-            int subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
-            int[] subIds = SubscriptionManager.getSubId(mPhone.getPhoneId());
-            if (subIds != null && subIds.length > 0) {
-                subId = subIds[0];
-            }
+            int subId = mPhone.getSubId();
 
             if (!TextUtils.isEmpty(plmn) && !TextUtils.isEmpty(wfcVoiceSpnFormat)) {
                 // In Wi-Fi Calling mode show SPN+WiFi
 
                 String originalPlmn = plmn.trim();
                 plmn = String.format(wfcVoiceSpnFormat, originalPlmn);
-            } else if (mCi.getRadioState() == CommandsInterface.RadioState.RADIO_OFF) {
+            } else if (mCi.getRadioState() == TelephonyManager.RADIO_POWER_OFF) {
                 // todo: temporary hack; should have a better fix. This is to avoid using operator
                 // name from ServiceState (populated in resetServiceStateInIwlanMode()) until
                 // wifi calling is actually enabled
@@ -2511,9 +2574,10 @@
 
         // If we want it on and it's off, turn it on
         if (mDesiredPowerState && !mRadioDisabledByCarrier
-                && mCi.getRadioState() == CommandsInterface.RadioState.RADIO_OFF) {
+                && mCi.getRadioState() == TelephonyManager.RADIO_POWER_OFF) {
             mCi.setRadioPower(true, null);
-        } else if ((!mDesiredPowerState || mRadioDisabledByCarrier) && mCi.getRadioState().isOn()) {
+        } else if ((!mDesiredPowerState || mRadioDisabledByCarrier) && mCi.getRadioState()
+                == TelephonyManager.RADIO_POWER_ON) {
             // If it's on and available and we want it off gracefully
             if (mPhone.isPhoneTypeGsm() && mPowerOffDelayNeed) {
                 if (mImsRegistrationOnOff && !mAlarmSwitch) {
@@ -2529,14 +2593,13 @@
                     am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                             SystemClock.elapsedRealtime() + 3000, mRadioOffIntent);
                 } else {
-                    DcTracker dcTracker = mPhone.mDcTracker;
-                    powerOffRadioSafely(dcTracker);
+                    powerOffRadioSafely();
                 }
             } else {
-                DcTracker dcTracker = mPhone.mDcTracker;
-                powerOffRadioSafely(dcTracker);
+                powerOffRadioSafely();
             }
-        } else if (mDeviceShuttingDown && mCi.getRadioState().isAvailable()) {
+        } else if (mDeviceShuttingDown
+                && (mCi.getRadioState() != TelephonyManager.RADIO_POWER_UNAVAILABLE)) {
             mCi.requestShutdown(null);
         }
     }
@@ -2593,6 +2656,16 @@
         mRatLog.log(mSS.toString());
     }
 
+    private void logMdnChange(String msg) {
+        mMdnLog.log(msg);
+        log(msg);
+    }
+
+    private void logeMdnChange(String msg) {
+        mMdnLog.log(msg);
+        loge(msg);
+    }
+
     protected final void log(String s) {
         Rlog.d(LOG_TAG, "[" + mPhone.getPhoneId() + "] " + s);
     }
@@ -2653,7 +2726,7 @@
     }
 
     public boolean isRadioOn() {
-        return mCi.getRadioState() == CommandsInterface.RadioState.RADIO_ON;
+        return mCi.getRadioState() == TelephonyManager.RADIO_POWER_ON;
     }
 
     /**
@@ -2683,19 +2756,19 @@
         log("pollState: modemTriggered=" + modemTriggered);
 
         switch (mCi.getRadioState()) {
-            case RADIO_UNAVAILABLE:
+            case TelephonyManager.RADIO_POWER_UNAVAILABLE:
                 mNewSS.setStateOutOfService();
-                mNewCellLoc.setStateInvalid();
+                mNewCellIdentity = null;
                 setSignalStrengthDefaultValues();
-                mNitzState.handleNetworkUnavailable();
+                mNitzState.handleNetworkCountryCodeUnavailable();
                 pollStateDone();
                 break;
 
-            case RADIO_OFF:
+            case TelephonyManager.RADIO_POWER_OFF:
                 mNewSS.setStateOff();
-                mNewCellLoc.setStateInvalid();
+                mNewCellIdentity = null;
                 setSignalStrengthDefaultValues();
-                mNitzState.handleNetworkUnavailable();
+                mNitzState.handleNetworkCountryCodeUnavailable();
                 // don't poll when device is shutting down or the poll was not modemTrigged
                 // (they sent us new radio data) and current network is not IWLAN
                 if (mDeviceShuttingDown ||
@@ -2778,12 +2851,15 @@
         boolean hasVoiceRegStateChanged =
                 mSS.getVoiceRegState() != mNewSS.getVoiceRegState();
 
-        boolean hasLocationChanged = !mNewCellLoc.equals(mCellLoc);
+        // TODO: loosen this restriction to exempt fields that are provided through system
+        // information; otherwise, we will get false positives when things like the operator
+        // alphas are provided later - that's better than missing location changes, but
+        // still not ideal.
+        boolean hasLocationChanged = !Objects.equals(mNewCellIdentity, mCellIdentity);
 
         // 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 (isDataInService) {
             mRatRatcheter.ratchet(mSS, mNewSS, hasLocationChanged);
         }
@@ -2870,9 +2946,7 @@
             // TODO: we may add filtering to reduce the event logged,
             // i.e. check preferred network setting, only switch to 2G, etc
             if (hasRilVoiceRadioTechnologyChanged) {
-                int cid = -1;
-                GsmCellLocation loc = (GsmCellLocation) mNewCellLoc;
-                if (loc != null) cid = loc.getCid();
+                int cid = getCidFromCellIdentity(mNewCellIdentity);
                 // NOTE: this code was previously located after mSS and mNewSS are swapped, so
                 // existing logs were incorrectly using the new state for "network_from"
                 // and STATE_OUT_OF_SERVICE for "network_to". To avoid confusion, use a new log tag
@@ -2899,7 +2973,7 @@
             mRejectCode = mNewRejectCode;
         }
 
-        ServiceState oldMergedSS = mPhone.getServiceState();
+        ServiceState oldMergedSS = new ServiceState(mPhone.getServiceState());
 
         // swap mSS and mNewSS to put new state in mSS
         ServiceState tss = mSS;
@@ -2908,10 +2982,10 @@
         // clean slate for next time
         mNewSS.setStateOutOfService();
 
-        // swap mCellLoc and mNewCellLoc to put new state in mCellLoc
-        CellLocation tcl = mCellLoc;
-        mCellLoc = mNewCellLoc;
-        mNewCellLoc = tcl;
+        // swap mCellIdentity and mNewCellIdentity to put new state in mCellIdentity
+        CellIdentity tempCellId = mCellIdentity;
+        mCellIdentity = mNewCellIdentity;
+        mNewCellIdentity = tempCellId;
 
         if (hasRilVoiceRadioTechnologyChanged) {
             updatePhoneObject();
@@ -2939,7 +3013,6 @@
 
         if (hasDeregistered) {
             mNetworkDetachedRegistrants.notifyRegistrants();
-            mNitzState.handleNetworkUnavailable();
         }
 
         if (hasRejectCauseChanged) {
@@ -2970,8 +3043,7 @@
                 // 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();
+                mLocaleTracker.updateOperatorNumeric("");
             } else if (mSS.getRilDataRadioTechnology() != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN) {
                 // If the device is on IWLAN, modems manufacture a ServiceState with the MCC/MNC of
                 // the SIM as if we were talking to towers. Telephony code then uses that with
@@ -2982,29 +3054,7 @@
                     setOperatorIdd(operatorNumeric);
                 }
 
-                mLocaleTracker.updateOperatorNumericSync(operatorNumeric);
-                String countryIsoCode = mLocaleTracker.getCurrentCountry();
-
-                // Update Time Zone.
-                boolean iccCardExists = iccCardExists();
-                boolean networkIsoChanged =
-                        networkCountryIsoChanged(countryIsoCode, prevCountryIsoCode);
-
-                // Determine countryChanged: networkIso is only reliable if there's an ICC card.
-                boolean countryChanged = iccCardExists && networkIsoChanged;
-                if (DBG) {
-                    long ctm = System.currentTimeMillis();
-                    log("Before handleNetworkCountryCodeKnown:"
-                            + " countryChanged=" + countryChanged
-                            + " iccCardExist=" + iccCardExists
-                            + " countryIsoChanged=" + networkIsoChanged
-                            + " operatorNumeric=" + operatorNumeric
-                            + " prevOperatorNumeric=" + prevOperatorNumeric
-                            + " countryIsoCode=" + countryIsoCode
-                            + " prevCountryIsoCode=" + prevCountryIsoCode
-                            + " ltod=" + TimeUtils.logTimeOfDay(ctm));
-                }
-                mNitzState.handleNetworkCountryCodeSet(countryChanged);
+                mLocaleTracker.updateOperatorNumeric(operatorNumeric);
             }
 
             tm.setNetworkRoamingForPhone(mPhone.getPhoneId(),
@@ -3042,6 +3092,13 @@
 
         if (hasRilDataRadioTechnologyChanged || hasRilVoiceRadioTechnologyChanged) {
             logRatChange();
+
+            updateRatTypeForSignalStrength();
+            notifySignalStrength();
+        }
+
+        if (hasVoiceRegStateChanged || hasRilVoiceRadioTechnologyChanged) {
+            notifyVoiceRegStateRilRadioTechnologyChanged();
         }
 
         if (hasDataRegStateChanged || hasRilDataRadioTechnologyChanged) {
@@ -3076,7 +3133,7 @@
         }
 
         if (hasLocationChanged) {
-            mPhone.notifyLocationChanged();
+            mPhone.notifyLocationChanged(getCellLocation());
         }
 
         if (mPhone.isPhoneTypeGsm()) {
@@ -3099,7 +3156,8 @@
 
     private void updateOperatorNameFromEri() {
         if (mPhone.isPhoneTypeCdma()) {
-            if ((mCi.getRadioState().isOn()) && (!mIsSubscriptionFromRuim)) {
+            if ((mCi.getRadioState() == TelephonyManager.RADIO_POWER_ON)
+                    && (!mIsSubscriptionFromRuim)) {
                 String eriText;
                 // Now the Phone sees the new ServiceState so it can get the new ERI text
                 if (mSS.getVoiceRegState() == ServiceState.STATE_IN_SERVICE) {
@@ -3115,10 +3173,11 @@
         } else if (mPhone.isPhoneTypeCdmaLte()) {
             boolean hasBrandOverride = mUiccController.getUiccCard(getPhoneId()) != null &&
                     mUiccController.getUiccCard(getPhoneId()).getOperatorBrandOverride() != null;
-            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))) {
+            if (!hasBrandOverride && (mCi.getRadioState() == TelephonyManager.RADIO_POWER_ON)
+                    && (mPhone.isEriFileLoaded())
+                    && (!ServiceState.isLte(mSS.getRilVoiceRadioTechnology())
+                    || mPhone.getContext().getResources().getBoolean(com.android.internal.R
+                    .bool.config_LTE_eri_for_network_name))) {
                 // Only when CDMA is in service, ERI will take effect
                 String eriText = mSS.getOperatorAlpha();
                 // Now the Phone sees the new ServiceState so it can get the new ERI text
@@ -3484,75 +3543,77 @@
     }
 
     /**
-     * @param workSource calling WorkSource
-     * @return the current cell location information. Prefer Gsm location
-     * information if available otherwise return LTE location information
+     * Get CellLocation from the ServiceState if available or guess from cached CellInfo
+     *
+     * Get the CellLocation by first checking if ServiceState has a current CID. If so
+     * then return that info. Otherwise, check the latest List<CellInfo> and return the first GSM or
+     * WCDMA result that appears. If no GSM or WCDMA results, then return an LTE result. The
+     * behavior is kept consistent for backwards compatibility; (do not apply logic to determine
+     * why the behavior is this way).
+     *
+     * @return the current cell location if known or a non-null "empty" cell location
      */
-    public CellLocation getCellLocation(WorkSource workSource) {
-        if (((GsmCellLocation)mCellLoc).getLac() >= 0 &&
-                ((GsmCellLocation)mCellLoc).getCid() >= 0) {
-            if (VDBG) log("getCellLocation(): X good mCellLoc=" + mCellLoc);
-            return mCellLoc;
-        } else {
-            List<CellInfo> result = getAllCellInfo(workSource);
-            if (result != null) {
-                // A hack to allow tunneling of LTE information via GsmCellLocation
-                // so that older Network Location Providers can return some information
-                // on LTE only networks, see bug 9228974.
-                //
-                // We'll search the return CellInfo array preferring GSM/WCDMA
-                // data, but if there is none we'll tunnel the first LTE information
-                // in the list.
-                //
-                // The tunnel'd LTE information is returned as follows:
-                //   LAC = TAC field
-                //   CID = CI field
-                //   PSC = 0.
-                GsmCellLocation cellLocOther = new GsmCellLocation();
-                for (CellInfo ci : result) {
-                    if (ci instanceof CellInfoGsm) {
-                        CellInfoGsm cellInfoGsm = (CellInfoGsm)ci;
-                        CellIdentityGsm cellIdentityGsm = cellInfoGsm.getCellIdentity();
-                        cellLocOther.setLacAndCid(cellIdentityGsm.getLac(),
-                                cellIdentityGsm.getCid());
-                        cellLocOther.setPsc(cellIdentityGsm.getPsc());
-                        if (VDBG) log("getCellLocation(): X ret GSM info=" + cellLocOther);
-                        return cellLocOther;
-                    } else if (ci instanceof CellInfoWcdma) {
-                        CellInfoWcdma cellInfoWcdma = (CellInfoWcdma)ci;
-                        CellIdentityWcdma cellIdentityWcdma = cellInfoWcdma.getCellIdentity();
-                        cellLocOther.setLacAndCid(cellIdentityWcdma.getLac(),
-                                cellIdentityWcdma.getCid());
-                        cellLocOther.setPsc(cellIdentityWcdma.getPsc());
-                        if (VDBG) log("getCellLocation(): X ret WCDMA info=" + cellLocOther);
-                        return cellLocOther;
-                    } else if ((ci instanceof CellInfoLte) &&
-                            ((cellLocOther.getLac() < 0) || (cellLocOther.getCid() < 0))) {
-                        // We'll return the first good LTE info we get if there is no better answer
-                        CellInfoLte cellInfoLte = (CellInfoLte)ci;
-                        CellIdentityLte cellIdentityLte = cellInfoLte.getCellIdentity();
-                        if ((cellIdentityLte.getTac() != Integer.MAX_VALUE)
-                                && (cellIdentityLte.getCi() != Integer.MAX_VALUE)) {
-                            cellLocOther.setLacAndCid(cellIdentityLte.getTac(),
-                                    cellIdentityLte.getCi());
-                            cellLocOther.setPsc(0);
-                            if (VDBG) {
-                                log("getCellLocation(): possible LTE cellLocOther=" + cellLocOther);
-                            }
-                        }
-                    }
+    public CellLocation getCellLocation() {
+        if (mCellIdentity != null) return mCellIdentity.asCellLocation();
+
+        CellLocation cl = getCellLocationFromCellInfo(getAllCellInfo());
+        if (cl != null) return cl;
+
+        return mPhone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA
+                ? new CdmaCellLocation() : new GsmCellLocation();
+    }
+
+    /**
+     * Get CellLocation from the ServiceState if available or guess from CellInfo
+     *
+     * Get the CellLocation by first checking if ServiceState has a current CID. If so
+     * then return that info. Otherwise, query AllCellInfo and return the first GSM or
+     * WCDMA result that appears. If no GSM or WCDMA results, then return an LTE result.
+     * The behavior is kept consistent for backwards compatibility; (do not apply logic
+     * to determine why the behavior is this way).
+     *
+     * @param workSource calling WorkSource
+     * @param rspMsg the response message which must be non-null
+     */
+    public void requestCellLocation(WorkSource workSource, Message rspMsg) {
+        if (mCellIdentity != null) {
+            AsyncResult.forMessage(rspMsg, mCellIdentity.asCellLocation(), null);
+            rspMsg.sendToTarget();
+            return;
+        }
+
+        Message cellLocRsp = obtainMessage(EVENT_CELL_LOCATION_RESPONSE, rspMsg);
+        requestAllCellInfo(workSource, cellLocRsp);
+    }
+
+    /* Find and return a CellLocation from CellInfo
+     *
+     * This method returns the first GSM or WCDMA result that appears in List<CellInfo>. If no GSM
+     * or  WCDMA results are found, then it returns an LTE result. The behavior is kept consistent
+     * for backwards compatibility; (do not apply logic to determine why the behavior is this way).
+     *
+     * @return the current cell location from CellInfo or null
+     */
+    private static CellLocation getCellLocationFromCellInfo(List<CellInfo> info) {
+        CellLocation cl = null;
+        if (info != null && info.size() > 0) {
+            CellIdentity fallbackLteCid = null; // We prefer not to use LTE
+            for (CellInfo ci : info) {
+                CellIdentity c = ci.getCellIdentity();
+                if (c instanceof CellIdentityLte && fallbackLteCid == null) {
+                    if (getCidFromCellIdentity(c) != -1) fallbackLteCid = c;
+                    continue;
                 }
-                if (VDBG) {
-                    log("getCellLocation(): X ret best answer cellLocOther=" + cellLocOther);
+                if (getCidFromCellIdentity(c) != -1) {
+                    cl = c.asCellLocation();
+                    break;
                 }
-                return cellLocOther;
-            } else {
-                if (VDBG) {
-                    log("getCellLocation(): X empty mCellLoc and CellInfo mCellLoc=" + mCellLoc);
-                }
-                return mCellLoc;
+            }
+            if (cl == null && fallbackLteCid != null) {
+                cl = fallbackLteCid.asCellLocation();
             }
         }
+        return cl;
     }
 
     /**
@@ -3567,8 +3628,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) {
@@ -3624,7 +3685,7 @@
         CarrierConfigManager configManager = (CarrierConfigManager)
                 context.getSystemService(Context.CARRIER_CONFIG_SERVICE);
         if (configManager != null) {
-            PersistableBundle bundle = configManager.getConfig();
+            PersistableBundle bundle = configManager.getConfigForSubId(mPhone.getSubId());
             if (bundle != null) {
                 boolean disableVoiceBarringNotification = bundle.getBoolean(
                         CarrierConfigManager.KEY_DISABLE_VOICE_BARRING_NOTIFICATION_BOOL, false);
@@ -3698,8 +3759,8 @@
                     return;
                 } else {
                     icon = com.android.internal.R.drawable.stat_notify_mmcc_indication_icn;
-                    // if using the single SIM resource, mSubId will be ignored
-                    title = context.getString(resId, mSubId);
+                    // if using the single SIM resource, simNumber will be ignored
+                    title = context.getString(resId, simNumber);
                     details = null;
                 }
                 break;
@@ -3803,6 +3864,13 @@
             return;
         }
 
+        // if there is no SIM present, do not poll signal strength
+        UiccCard uiccCard = UiccController.getInstance().getUiccCard(getPhoneId());
+        if (uiccCard == null || uiccCard.getCardState() == CardState.CARDSTATE_ABSENT) {
+            log("Not polling signal strength due to absence of SIM");
+            return;
+        }
+
         Message msg;
 
         msg = obtainMessage();
@@ -3858,6 +3926,25 @@
     }
 
     /**
+     * Registration for RIL Voice Radio Technology changing. The
+     * new radio technology will be returned AsyncResult#result as an Integer Object.
+     * The AsyncResult will be in the notification Message#obj.
+     *
+     * @param h handler to notify
+     * @param what what code of message when delivered
+     * @param obj placed in Message.obj
+     */
+    public void registerForVoiceRegStateOrRatChanged(Handler h, int what, Object obj) {
+        Registrant r = new Registrant(h, what, obj);
+        mVoiceRegStateOrRatChangedRegistrants.add(r);
+        notifyVoiceRegStateRilRadioTechnologyChanged();
+    }
+
+    public void unregisterForVoiceRegStateOrRatChanged(Handler h) {
+        mVoiceRegStateOrRatChangedRegistrants.remove(h);
+    }
+
+    /**
      * Registration for DataConnection RIL Data Radio Technology changing. The
      * new radio technology will be returned AsyncResult#result as an Integer Object.
      * The AsyncResult will be in the notification Message#obj.
@@ -3952,22 +4039,36 @@
     }
 
     /**
+     * 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.
      */
-    public void powerOffRadioSafely(DcTracker dcTracker) {
+    public void powerOffRadioSafely() {
         synchronized (this) {
             if (!mPendingRadioPowerOffAfterDataOff) {
-                int dds = SubscriptionManager.getDefaultDataSubscriptionId();
                 // To minimize race conditions we call cleanUpAllConnections on
                 // both if else paths instead of before this isDisconnected test.
-                if (dcTracker.isDisconnected()
-                        && (dds == mPhone.getSubId()
-                        || (dds != mPhone.getSubId()
-                        && ProxyController.getInstance().isDataDisconnected(dds)))) {
+                if (mPhone.areAllDataDisconnected()) {
                     // To minimize race conditions we do this after isDisconnected
-                    dcTracker.cleanUpAllConnections(Phone.REASON_RADIO_TURNED_OFF);
                     if (DBG) log("Data disconnected, turn off radio right away.");
                     hangupAndPowerOff();
                 } else {
@@ -3977,16 +4078,19 @@
                         mPhone.mCT.mBackgroundCall.hangupIfAlive();
                         mPhone.mCT.mForegroundCall.hangupIfAlive();
                     }
-                    dcTracker.cleanUpAllConnections(Phone.REASON_RADIO_TURNED_OFF);
-                    if (dds != mPhone.getSubId()
-                            && !ProxyController.getInstance().isDataDisconnected(dds)) {
-                        if (DBG) log("Data is active on DDS.  Wait for all data disconnect");
-                        // Data is not disconnected on DDS. Wait for the data disconnect complete
-                        // before sending the RADIO_POWER off.
-                        ProxyController.getInstance().registerForAllDataDisconnected(dds, this,
-                                EVENT_ALL_DATA_DISCONNECTED, null);
-                        mPendingRadioPowerOffAfterDataOff = true;
+                    if (DBG) log("Wait for all data disconnect");
+                    // Data is not disconnected. Wait for the data disconnect complete
+                    // before sending the RADIO_POWER off.
+                    ProxyController.getInstance().registerForAllDataDisconnected(
+                            mPhone.getSubId(), this, EVENT_ALL_DATA_DISCONNECTED);
+                    mPendingRadioPowerOffAfterDataOff = true;
+                    for (int transport : mTransportManager.getAvailableTransports()) {
+                        if (mPhone.getDcTracker(transport) != null) {
+                            mPhone.getDcTracker(transport).cleanUpAllConnections(
+                                    Phone.REASON_RADIO_TURNED_OFF);
+                        }
                     }
+
                     Message msg = Message.obtain(this);
                     msg.what = EVENT_SET_RADIO_POWER_OFF;
                     msg.arg1 = ++mPendingRadioPowerOffAfterDataOffTag;
@@ -4137,18 +4241,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
@@ -4156,12 +4248,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();
@@ -4175,14 +4261,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.
      */
@@ -4241,48 +4352,64 @@
         return TelephonyManager.getTelephonyProperty(mPhone.getPhoneId(), property, defValue);
     }
 
+    public List<CellInfo> getAllCellInfo() {
+        return mLastCellInfoList;
+    }
+
+    /** Set the minimum time between CellInfo requests to the modem, in milliseconds */
+    public void setCellInfoMinInterval(int interval) {
+        mCellInfoMinIntervalMs = interval;
+    }
+
     /**
-     * @return all available cell information or null if none.
+     * Request the latest CellInfo from the modem.
+     *
+     * If sufficient time has elapsed, then this request will be sent to the modem. Otherwise
+     * the latest cached List<CellInfo> will be returned.
+     *
+     * @param workSource of the caller for power accounting
+     * @param rspMsg an optional response message to get the response to the CellInfo request. If
+     *     the rspMsg is not provided, then CellInfo will still be requested from the modem and
+     *     cached locally for future lookup.
      */
-    public List<CellInfo> getAllCellInfo(WorkSource workSource) {
-        CellInfoResult result = new CellInfoResult();
-        if (VDBG) log("SST.getAllCellInfo(): E");
-        int ver = mCi.getRilVersion();
-        if (ver >= 8) {
-            if (isCallerOnDifferentThread()) {
-                if ((SystemClock.elapsedRealtime() - mLastCellInfoListTime)
-                        > LAST_CELL_INFO_LIST_MAX_AGE_MS) {
-                    Message msg = obtainMessage(EVENT_GET_CELL_INFO_LIST, result);
-                    synchronized(result.lockObj) {
-                        result.list = null;
-                        mCi.getCellInfoList(msg, workSource);
-                        try {
-                            result.lockObj.wait(5000);
-                        } catch (InterruptedException e) {
-                            e.printStackTrace();
-                        }
-                    }
-                } else {
-                    if (DBG) log("SST.getAllCellInfo(): return last, back to back calls");
-                    result.list = mLastCellInfoList;
-                }
-            } else {
-                if (DBG) log("SST.getAllCellInfo(): return last, same thread can't block");
-                result.list = mLastCellInfoList;
-            }
-        } else {
-            if (DBG) log("SST.getAllCellInfo(): not implemented");
-            result.list = null;
+    public void requestAllCellInfo(WorkSource workSource, Message rspMsg) {
+        if (VDBG) log("SST.requestAllCellInfo(): E");
+        if (mCi.getRilVersion() < 8) {
+            if (DBG) log("SST.requestAllCellInfo(): not implemented");
+            return;
         }
-        synchronized(result.lockObj) {
-            if (result.list != null) {
-                if (VDBG) log("SST.getAllCellInfo(): X size=" + result.list.size()
-                        + " list=" + result.list);
-                return result.list;
-            } else {
-                if (DBG) log("SST.getAllCellInfo(): X size=0 list=null");
-                return null;
+        synchronized (mPendingCellInfoRequests) {
+            // If there are pending requests, then we already have a request active, so add this
+            // request to the response queue without initiating a new request.
+            if (mIsPendingCellInfoRequest) {
+                if (rspMsg != null) mPendingCellInfoRequests.add(rspMsg);
+                return;
             }
+            // Check to see whether the elapsed time is sufficient for a new request; if not, then
+            // return the result of the last request (if expected).
+            final long curTime = SystemClock.elapsedRealtime();
+            if ((curTime - mLastCellInfoReqTime) < mCellInfoMinIntervalMs) {
+                if (rspMsg != null) {
+                    if (DBG) log("SST.requestAllCellInfo(): return last, back to back calls");
+                    AsyncResult.forMessage(rspMsg, mLastCellInfoList, null);
+                    rspMsg.sendToTarget();
+                }
+                return;
+            }
+            // If this request needs an explicit response (it's a synchronous request), then queue
+            // the response message.
+            if (rspMsg != null) mPendingCellInfoRequests.add(rspMsg);
+            // Update the timeout window so that we don't delay based on slow responses
+            mLastCellInfoReqTime = curTime;
+            // Set a flag to remember that we have a pending cell info request
+            mIsPendingCellInfoRequest = true;
+            // Send a cell info request and also chase it with a timeout message
+            Message msg = obtainMessage(EVENT_GET_CELL_INFO_LIST);
+            mCi.getCellInfoList(msg, workSource);
+            // This message will arrive TIMEOUT ms later and ensure that we don't wait forever for
+            // a CELL_INFO response.
+            sendMessageDelayed(
+                    obtainMessage(EVENT_GET_CELL_INFO_LIST), CELL_INFO_LIST_QUERY_TIMEOUT);
         }
     }
 
@@ -4393,9 +4520,9 @@
         pw.println(" mRestrictedState=" + mRestrictedState);
         pw.println(" mPendingRadioPowerOffAfterDataOff=" + mPendingRadioPowerOffAfterDataOff);
         pw.println(" mPendingRadioPowerOffAfterDataOffTag=" + mPendingRadioPowerOffAfterDataOffTag);
-        pw.println(" mCellLoc=" + Rlog.pii(VDBG, mCellLoc));
-        pw.println(" mNewCellLoc=" + Rlog.pii(VDBG, mNewCellLoc));
-        pw.println(" mLastCellInfoListTime=" + mLastCellInfoListTime);
+        pw.println(" mCellIdentity=" + Rlog.pii(VDBG, mCellIdentity));
+        pw.println(" mNewCellIdentity=" + Rlog.pii(VDBG, mNewCellIdentity));
+        pw.println(" mLastCellInfoReqTime=" + mLastCellInfoReqTime);
         dumpCellInfoList(pw);
         pw.flush();
         pw.println(" mPreferredNetworkType=" + mPreferredNetworkType);
@@ -4443,6 +4570,7 @@
         pw.println(" mDeviceShuttingDown=" + mDeviceShuttingDown);
         pw.println(" mSpnUpdatePending=" + mSpnUpdatePending);
         pw.println(" mLteRsrpBoost=" + mLteRsrpBoost);
+        pw.println(" mCellInfoMinIntervalMs=" + mCellInfoMinIntervalMs);
         dumpEarfcnPairList(pw);
 
         mLocaleTracker.dump(fd, pw, args);
@@ -4471,6 +4599,11 @@
         ipw.println(" Radio power Log:");
         ipw.increaseIndent();
         mRadioPowerLog.dump(fd, ipw, args);
+        ipw.decreaseIndent();
+
+        ipw.println(" mMdn Log:");
+        ipw.increaseIndent();
+        mMdnLog.dump(fd, ipw, args);
 
         mNitzState.dumpLogs(fd, ipw, args);
     }
@@ -4499,15 +4632,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
      */
@@ -4524,8 +4648,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;
@@ -4659,7 +4783,7 @@
      * causes state to go to OUT_OF_SERVICE state instead of STATE_OFF
      */
     protected void resetServiceStateInIwlanMode() {
-        if (mCi.getRadioState() == CommandsInterface.RadioState.RADIO_OFF) {
+        if (mCi.getRadioState() == TelephonyManager.RADIO_POWER_OFF) {
             boolean resetIwlanRatVal = false;
             log("set service state as POWER_OFF");
             if (ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN
diff --git a/src/java/com/android/internal/telephony/SmsBroadcastUndelivered.java b/src/java/com/android/internal/telephony/SmsBroadcastUndelivered.java
index 4fb02f0..516ca3f 100644
--- a/src/java/com/android/internal/telephony/SmsBroadcastUndelivered.java
+++ b/src/java/com/android/internal/telephony/SmsBroadcastUndelivered.java
@@ -37,7 +37,7 @@
 
 /**
  * Called when the credential-encrypted storage is unlocked, collecting all acknowledged messages
- * and deleting any partial message segments older than 30 days. Called from a worker thread to
+ * and deleting any partial message segments older than 7 days. Called from a worker thread to
  * avoid delaying phone app startup. The last step is to broadcast the first pending message from
  * the main thread, then the remaining pending messages will be broadcast after the previous
  * ordered broadcast completes.
@@ -46,8 +46,8 @@
     private static final String TAG = "SmsBroadcastUndelivered";
     private static final boolean DBG = InboundSmsHandler.DBG;
 
-    /** Delete any partial message segments older than 30 days. */
-    static final long DEFAULT_PARTIAL_SEGMENT_EXPIRE_AGE = (long) (60 * 60 * 1000) * 24 * 30;
+    /** Delete any partial message segments older than 7 days. */
+    static final long DEFAULT_PARTIAL_SEGMENT_EXPIRE_AGE = (long) (60 * 60 * 1000) * 24 * 7;
 
     /**
      * Query projection for dispatching pending messages at boot time.
@@ -99,7 +99,8 @@
 
         @Override
         public void run() {
-            scanRawTable(context);
+            scanRawTable(context, mCdmaInboundSmsHandler, mGsmInboundSmsHandler,
+                    System.currentTimeMillis() - getUndeliveredSmsExpirationTime(context));
             InboundSmsHandler.cancelNewMessageNotification(context);
         }
     }
@@ -142,18 +143,19 @@
     /**
      * Scan the raw table for complete SMS messages to broadcast, and old PDUs to delete.
      */
-    private void scanRawTable(Context context) {
+    static void scanRawTable(Context context, CdmaInboundSmsHandler cdmaInboundSmsHandler,
+            GsmInboundSmsHandler gsmInboundSmsHandler, long oldMessageTimestamp) {
         if (DBG) Rlog.d(TAG, "scanning raw table for undelivered messages");
         long startTime = System.nanoTime();
+        ContentResolver contentResolver = context.getContentResolver();
         HashMap<SmsReferenceKey, Integer> multiPartReceivedCount =
                 new HashMap<SmsReferenceKey, Integer>(4);
         HashSet<SmsReferenceKey> oldMultiPartMessages = new HashSet<SmsReferenceKey>(4);
         Cursor cursor = null;
         try {
             // query only non-deleted ones
-            cursor = mResolver.query(InboundSmsHandler.sRawUri, PDU_PENDING_MESSAGE_PROJECTION,
-                    "deleted = 0", null,
-                    null);
+            cursor = contentResolver.query(InboundSmsHandler.sRawUri,
+                    PDU_PENDING_MESSAGE_PROJECTION, "deleted = 0", null, null);
             if (cursor == null) {
                 Rlog.e(TAG, "error getting pending message cursor");
                 return;
@@ -163,7 +165,8 @@
             while (cursor.moveToNext()) {
                 InboundSmsTracker tracker;
                 try {
-                    tracker = TelephonyComponentFactory.getInstance().makeInboundSmsTracker(cursor,
+                    tracker = TelephonyComponentFactory.getInstance()
+                            .inject(InboundSmsTracker.class.getName()).makeInboundSmsTracker(cursor,
                             isCurrentFormat3gpp2);
                 } catch (IllegalArgumentException e) {
                     Rlog.e(TAG, "error loading SmsTracker: " + e);
@@ -172,16 +175,15 @@
 
                 if (tracker.getMessageCount() == 1) {
                     // deliver single-part message
-                    broadcastSms(tracker);
+                    broadcastSms(tracker, cdmaInboundSmsHandler, gsmInboundSmsHandler);
                 } else {
                     SmsReferenceKey reference = new SmsReferenceKey(tracker);
                     Integer receivedCount = multiPartReceivedCount.get(reference);
                     if (receivedCount == null) {
                         multiPartReceivedCount.put(reference, 1);    // first segment seen
-                        long expirationTime = getUndeliveredSmsExpirationTime(context);
-                        if (tracker.getTimestamp() <
-                                (System.currentTimeMillis() - expirationTime)) {
-                            // older than 30 days; delete if we don't find all the segments
+                        if (tracker.getTimestamp() < oldMessageTimestamp) {
+                            // older than oldMessageTimestamp; delete if we don't find all the
+                            // segments
                             oldMultiPartMessages.add(reference);
                         }
                     } else {
@@ -190,7 +192,7 @@
                             // looks like we've got all the pieces; send a single tracker
                             // to state machine which will find the other pieces to broadcast
                             if (DBG) Rlog.d(TAG, "found complete multi-part message");
-                            broadcastSms(tracker);
+                            broadcastSms(tracker, cdmaInboundSmsHandler, gsmInboundSmsHandler);
                             // don't delete this old message until after we broadcast it
                             oldMultiPartMessages.remove(reference);
                         } else {
@@ -202,7 +204,7 @@
             // Delete old incomplete message segments
             for (SmsReferenceKey message : oldMultiPartMessages) {
                 // delete permanently
-                int rows = mResolver.delete(InboundSmsHandler.sRawUriPermanentDelete,
+                int rows = contentResolver.delete(InboundSmsHandler.sRawUriPermanentDelete,
                         message.getDeleteWhere(), message.getDeleteWhereArgs());
                 if (rows == 0) {
                     Rlog.e(TAG, "No rows were deleted from raw table!");
@@ -225,12 +227,14 @@
     /**
      * Send tracker to appropriate (3GPP or 3GPP2) inbound SMS handler for broadcast.
      */
-    private void broadcastSms(InboundSmsTracker tracker) {
+    private static void broadcastSms(InboundSmsTracker tracker,
+            CdmaInboundSmsHandler cdmaInboundSmsHandler,
+            GsmInboundSmsHandler gsmInboundSmsHandler) {
         InboundSmsHandler handler;
         if (tracker.is3gpp2()) {
-            handler = mCdmaInboundSmsHandler;
+            handler = cdmaInboundSmsHandler;
         } else {
-            handler = mGsmInboundSmsHandler;
+            handler = gsmInboundSmsHandler;
         }
         if (handler != null) {
             handler.sendMessage(InboundSmsHandler.EVENT_BROADCAST_SMS, tracker);
diff --git a/src/java/com/android/internal/telephony/SmsDispatchersController.java b/src/java/com/android/internal/telephony/SmsDispatchersController.java
index f3cd1c7..d858cbe 100644
--- a/src/java/com/android/internal/telephony/SmsDispatchersController.java
+++ b/src/java/com/android/internal/telephony/SmsDispatchersController.java
@@ -22,15 +22,19 @@
 import android.app.Activity;
 import android.app.PendingIntent;
 import android.app.PendingIntent.CanceledException;
+import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.net.Uri;
 import android.os.AsyncResult;
 import android.os.Handler;
 import android.os.Message;
+import android.os.UserManager;
 import android.provider.Telephony.Sms;
 import android.provider.Telephony.Sms.Intents;
 import android.telephony.Rlog;
+import android.telephony.ServiceState;
 import android.telephony.SmsManager;
 import android.telephony.SmsMessage;
 import android.util.Pair;
@@ -41,6 +45,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;
 
@@ -49,6 +55,7 @@
  */
 public class SmsDispatchersController extends Handler {
     private static final String TAG = "SmsDispatchersController";
+    private static final boolean VDBG = false; // STOPSHIP if true
 
     /** Radio is ON */
     private static final int EVENT_RADIO_ON = 11;
@@ -59,6 +66,29 @@
     /** Callback from RIL_REQUEST_IMS_REGISTRATION_STATE */
     private static final int EVENT_IMS_STATE_DONE = 13;
 
+    /** Service state changed */
+    private static final int EVENT_SERVICE_STATE_CHANGED = 14;
+
+    /** Purge old message segments */
+    private static final int EVENT_PARTIAL_SEGMENT_TIMER_EXPIRY = 15;
+
+    /** User unlocked the device */
+    private static final int EVENT_USER_UNLOCKED = 16;
+
+    /** InboundSmsHandler exited WaitingState */
+    protected static final int EVENT_SMS_HANDLER_EXITING_WAITING_STATE = 17;
+
+    /** Delete any partial message segments after being IN_SERVICE for 1 day. */
+    private static final long PARTIAL_SEGMENT_WAIT_DURATION = (long) (60 * 60 * 1000) * 24;
+    /** Constant for invalid time */
+    private static final long INVALID_TIME = -1;
+    /** Time at which last IN_SERVICE event was received */
+    private long mLastInServiceTime = INVALID_TIME;
+    /** Current IN_SERVICE duration */
+    private long mCurrentWaitElapsedDuration = 0;
+    /** Time at which the current PARTIAL_SEGMENT_WAIT_DURATION timer was started */
+    private long mCurrentWaitStartTime = INVALID_TIME;
+
     private SMSDispatcher mCdmaDispatcher;
     private SMSDispatcher mGsmDispatcher;
     private ImsSmsDispatcher mImsSmsDispatcher;
@@ -100,20 +130,39 @@
 
         mCi.registerForOn(this, EVENT_RADIO_ON, null);
         mCi.registerForImsNetworkStateChanged(this, EVENT_IMS_STATE_CHANGED, null);
+
+        UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+        if (userManager.isUserUnlocked()) {
+            if (VDBG) {
+                logd("SmsDispatchersController: user unlocked; registering for service"
+                        + "state changed");
+            }
+            mPhone.registerForServiceStateChanged(this, EVENT_SERVICE_STATE_CHANGED, null);
+            resetPartialSegmentWaitTimer();
+        } else {
+            if (VDBG) {
+                logd("SmsDispatchersController: user locked; waiting for USER_UNLOCKED");
+            }
+            IntentFilter userFilter = new IntentFilter();
+            userFilter.addAction(Intent.ACTION_USER_UNLOCKED);
+            mContext.registerReceiver(mBroadcastReceiver, userFilter);
+        }
     }
 
-    /* Updates the phone object when there is a change */
-    protected void updatePhoneObject(Phone phone) {
-        Rlog.d(TAG, "In IMS updatePhoneObject ");
-        mCdmaDispatcher.updatePhoneObject(phone);
-        mGsmDispatcher.updatePhoneObject(phone);
-        mGsmInboundSmsHandler.updatePhoneObject(phone);
-        mCdmaInboundSmsHandler.updatePhoneObject(phone);
-    }
+    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(final Context context, Intent intent) {
+            Rlog.d(TAG, "Received broadcast " + intent.getAction());
+            if (Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) {
+                sendMessage(obtainMessage(EVENT_USER_UNLOCKED));
+            }
+        }
+    };
 
     public void dispose() {
         mCi.unregisterForOn(this);
         mCi.unregisterForImsNetworkStateChanged(this);
+        mPhone.unregisterForServiceStateChanged(this);
         mGsmDispatcher.dispose();
         mCdmaDispatcher.dispose();
         mGsmInboundSmsHandler.dispose();
@@ -146,6 +195,23 @@
                 }
                 break;
 
+            case EVENT_SERVICE_STATE_CHANGED:
+            case EVENT_SMS_HANDLER_EXITING_WAITING_STATE:
+                reevaluateTimerStatus();
+                break;
+
+            case EVENT_PARTIAL_SEGMENT_TIMER_EXPIRY:
+                handlePartialSegmentTimerExpiry((Long) msg.obj);
+                break;
+
+            case EVENT_USER_UNLOCKED:
+                if (VDBG) {
+                    logd("handleMessage: EVENT_USER_UNLOCKED");
+                }
+                mPhone.registerForServiceStateChanged(this, EVENT_SERVICE_STATE_CHANGED, null);
+                resetPartialSegmentWaitTimer();
+                break;
+
             default:
                 if (isCdmaMo()) {
                     mCdmaDispatcher.handleMessage(msg);
@@ -155,6 +221,118 @@
         }
     }
 
+    private void reevaluateTimerStatus() {
+        long currentTime = System.currentTimeMillis();
+
+        // Remove unhandled timer expiry message. A new message will be posted if needed.
+        removeMessages(EVENT_PARTIAL_SEGMENT_TIMER_EXPIRY);
+        // Update timer duration elapsed time (add time since last IN_SERVICE to now).
+        // This is needed for IN_SERVICE as well as OUT_OF_SERVICE because same events can be
+        // received back to back
+        if (mLastInServiceTime != INVALID_TIME) {
+            mCurrentWaitElapsedDuration += (currentTime - mLastInServiceTime);
+        }
+
+        if (VDBG) {
+            logd("reevaluateTimerStatus: currentTime: " + currentTime
+                    + " mCurrentWaitElapsedDuration: " + mCurrentWaitElapsedDuration);
+        }
+
+        if (mCurrentWaitElapsedDuration > PARTIAL_SEGMENT_WAIT_DURATION) {
+            // handle this event as timer expiry
+            handlePartialSegmentTimerExpiry(mCurrentWaitStartTime);
+        } else {
+            if (isInService()) {
+                handleInService(currentTime);
+            } else {
+                handleOutOfService(currentTime);
+            }
+        }
+    }
+
+    private void handleInService(long currentTime) {
+        if (VDBG) {
+            logd("handleInService: timer expiry in "
+                    + (PARTIAL_SEGMENT_WAIT_DURATION - mCurrentWaitElapsedDuration) + "ms");
+        }
+
+        // initialize mCurrentWaitStartTime if needed
+        if (mCurrentWaitStartTime == INVALID_TIME) mCurrentWaitStartTime = currentTime;
+
+        // Post a message for timer expiry time. mCurrentWaitElapsedDuration is the duration already
+        // elapsed from the timer.
+        sendMessageDelayed(
+                obtainMessage(EVENT_PARTIAL_SEGMENT_TIMER_EXPIRY, mCurrentWaitStartTime),
+                PARTIAL_SEGMENT_WAIT_DURATION - mCurrentWaitElapsedDuration);
+
+        // update mLastInServiceTime as the current time
+        mLastInServiceTime = currentTime;
+    }
+
+    private void handleOutOfService(long currentTime) {
+        if (VDBG) {
+            logd("handleOutOfService: currentTime: " + currentTime
+                    + " mCurrentWaitElapsedDuration: " + mCurrentWaitElapsedDuration);
+        }
+
+        // mLastInServiceTime is not relevant now since state is OUT_OF_SERVICE; set it to INVALID
+        mLastInServiceTime = INVALID_TIME;
+    }
+
+    private void handlePartialSegmentTimerExpiry(long waitTimerStart) {
+        if (mGsmInboundSmsHandler.getCurrentState().getName().equals("WaitingState")
+                || mCdmaInboundSmsHandler.getCurrentState().getName().equals("WaitingState")) {
+            logd("handlePartialSegmentTimerExpiry: ignoring timer expiry as InboundSmsHandler is"
+                    + " in WaitingState");
+            return;
+        }
+
+        if (VDBG) {
+            logd("handlePartialSegmentTimerExpiry: calling scanRawTable()");
+        }
+        // Timer expired. This indicates that device has been in service for
+        // PARTIAL_SEGMENT_WAIT_DURATION since waitTimerStart. Delete orphaned message segments
+        // older than waitTimerStart.
+        SmsBroadcastUndelivered.scanRawTable(mContext, mCdmaInboundSmsHandler,
+                mGsmInboundSmsHandler, waitTimerStart);
+        if (VDBG) {
+            logd("handlePartialSegmentTimerExpiry: scanRawTable() done");
+        }
+
+        resetPartialSegmentWaitTimer();
+    }
+
+    private void resetPartialSegmentWaitTimer() {
+        long currentTime = System.currentTimeMillis();
+
+        removeMessages(EVENT_PARTIAL_SEGMENT_TIMER_EXPIRY);
+        if (isInService()) {
+            if (VDBG) {
+                logd("resetPartialSegmentWaitTimer: currentTime: " + currentTime
+                        + " IN_SERVICE");
+            }
+            mCurrentWaitStartTime = currentTime;
+            mLastInServiceTime = currentTime;
+            sendMessageDelayed(
+                    obtainMessage(EVENT_PARTIAL_SEGMENT_TIMER_EXPIRY, mCurrentWaitStartTime),
+                    PARTIAL_SEGMENT_WAIT_DURATION);
+        } else {
+            if (VDBG) {
+                logd("resetPartialSegmentWaitTimer: currentTime: " + currentTime
+                        + " not IN_SERVICE");
+            }
+            mCurrentWaitStartTime = INVALID_TIME;
+            mLastInServiceTime = INVALID_TIME;
+        }
+
+        mCurrentWaitElapsedDuration = 0;
+    }
+
+    private boolean isInService() {
+        ServiceState serviceState = mPhone.getServiceState();
+        return serviceState != null && serviceState.getState() == ServiceState.STATE_IN_SERVICE;
+    }
+
     private void setImsSmsFormat(int format) {
         switch (format) {
             case PhoneConstants.PHONE_TYPE_GSM:
@@ -449,7 +627,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 +805,13 @@
     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);
+    }
+
+    private void logd(String msg) {
+        Rlog.d(TAG, msg);
+    }
 }
diff --git a/src/java/com/android/internal/telephony/SmsNumberUtils.java b/src/java/com/android/internal/telephony/SmsNumberUtils.java
index bf548d6..af7d871 100644
--- a/src/java/com/android/internal/telephony/SmsNumberUtils.java
+++ b/src/java/com/android/internal/telephony/SmsNumberUtils.java
@@ -609,7 +609,7 @@
             CarrierConfigManager configManager = (CarrierConfigManager)
                     phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
             if (configManager != null) {
-                PersistableBundle bundle = configManager.getConfig();
+                PersistableBundle bundle = configManager.getConfigForSubId(phone.getSubId());
                 if (bundle != null) {
                     return bundle.getBoolean(CarrierConfigManager
                             .KEY_SMS_REQUIRES_DESTINATION_NUMBER_CONVERSION_BOOL);
diff --git a/src/java/com/android/internal/telephony/SubscriptionController.java b/src/java/com/android/internal/telephony/SubscriptionController.java
index fd12273..09fcc93 100644
--- a/src/java/com/android/internal/telephony/SubscriptionController.java
+++ b/src/java/com/android/internal/telephony/SubscriptionController.java
@@ -16,9 +16,12 @@
 
 package com.android.internal.telephony;
 
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
 import android.Manifest;
 import android.annotation.Nullable;
 import android.app.AppOpsManager;
+import android.app.PendingIntent;
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.Context;
@@ -63,6 +66,7 @@
 import java.util.Map.Entry;
 import java.util.Objects;
 import java.util.Set;
+import java.util.UUID;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.stream.Collectors;
 
@@ -92,9 +96,15 @@
     private static final int DEPRECATED_SETTING = -1;
     private ScLocalLog mLocalLog = new ScLocalLog(MAX_LOCAL_LOG_LINES);
 
+    // Lock that both mCacheActiveSubInfoList and mCacheOpportunisticSubInfoList use.
+    private Object mSubInfoListLock = new Object();
+
     /* The Cache of Active SubInfoRecord(s) list of currently in use SubInfoRecord(s) */
     private final List<SubscriptionInfo> mCacheActiveSubInfoList = new ArrayList<>();
 
+    /* Similar to mCacheActiveSubInfoList but only caching opportunistic subscriptions. */
+    private List<SubscriptionInfo> mCacheOpportunisticSubInfoList = new ArrayList<>();
+
     /**
      * Copied from android.util.LocalLog with flush() adding flush and line number
      * TODO: Update LocalLog
@@ -151,7 +161,6 @@
     protected static Phone[] sPhones;
     protected Context mContext;
     protected TelephonyManager mTelephonyManager;
-    protected CallManager mCM;
 
     private AppOpsManager mAppOps;
 
@@ -163,6 +172,7 @@
 
     private int[] colorArr;
     private long mLastISubServiceRegTime;
+    private int mPreferredDataSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
 
     public static SubscriptionController init(Phone phone) {
         synchronized (SubscriptionController.class) {
@@ -202,7 +212,6 @@
 
     protected void init(Context c) {
         mContext = c;
-        mCM = CallManager.getInstance();
         mTelephonyManager = TelephonyManager.from(mContext);
 
         mAppOps = (AppOpsManager)mContext.getSystemService(Context.APP_OPS_SERVICE);
@@ -221,7 +230,6 @@
 
     private SubscriptionController(Phone phone) {
         mContext = phone.getContext();
-        mCM = CallManager.getInstance();
         mAppOps = mContext.getSystemService(AppOpsManager.class);
 
         if(ServiceManager.getService("isub") == null) {
@@ -290,14 +298,14 @@
         // 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
-        String countryIso = getSubscriptionCountryIso(id);
+        String countryIso = cursor.getString(cursor.getColumnIndexOrThrow(
+                SubscriptionManager.ISO_COUNTRY_CODE));
         boolean isEmbedded = cursor.getInt(cursor.getColumnIndexOrThrow(
                 SubscriptionManager.IS_EMBEDDED)) == 1;
         UiccAccessRule[] accessRules;
@@ -307,6 +315,12 @@
         } else {
             accessRules = null;
         }
+        boolean isOpportunistic = cursor.getInt(cursor.getColumnIndexOrThrow(
+                SubscriptionManager.IS_OPPORTUNISTIC)) == 1;
+        String groupUUID = cursor.getString(cursor.getColumnIndexOrThrow(
+                SubscriptionManager.GROUP_UUID));
+        boolean isMetered = cursor.getInt(cursor.getColumnIndexOrThrow(
+                SubscriptionManager.IS_METERED)) == 1;
 
         if (VDBG) {
             String iccIdToPrint = SubscriptionInfo.givePrintableIccid(iccId);
@@ -316,7 +330,8 @@
                     + " iconTint:" + iconTint + " dataRoaming:" + dataRoaming
                     + " mcc:" + mcc + " mnc:" + mnc + " countIso:" + countryIso + " isEmbedded:"
                     + isEmbedded + " accessRules:" + Arrays.toString(accessRules)
-                    + " cardId:" + cardIdToPrint);
+                    + " cardId:" + cardIdToPrint + " isOpportunistic:" + isOpportunistic
+                    + " groupUUID:" + groupUUID + " isMetered:" + isMetered);
         }
 
         // If line1number has been set to a different number, use it instead.
@@ -326,21 +341,7 @@
         }
         return new SubscriptionInfo(id, iccId, simSlotIndex, displayName, carrierName,
                 nameSource, iconTint, number, dataRoaming, iconBitmap, mcc, mnc, countryIso,
-                isEmbedded, accessRules, cardId);
-    }
-
-    /**
-     * Get ISO country code for the subscription's provider
-     *
-     * @param subId The subscription ID
-     * @return The ISO country code for the subscription's provider
-     */
-    private String getSubscriptionCountryIso(int subId) {
-        final int phoneId = getPhoneId(subId);
-        if (phoneId < 0) {
-            return "";
-        }
-        return mTelephonyManager.getSimCountryIsoForPhone(phoneId);
+                isEmbedded, accessRules, cardId, isOpportunistic, groupUUID, isMetered);
     }
 
     /**
@@ -599,39 +600,7 @@
      */
     @Override
     public List<SubscriptionInfo> getActiveSubscriptionInfoList(String callingPackage) {
-        if (!isSubInfoReady()) {
-            if (DBG) logdl("[getActiveSubInfoList] Sub Controller not ready");
-            return null;
-        }
-
-        boolean canReadAllPhoneState;
-        try {
-            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);
-            }
-
-            // 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());
-        }
+        return getSubscriptionInfoListFromCacheHelper(callingPackage, mCacheActiveSubInfoList);
     }
 
     /**
@@ -647,13 +616,20 @@
             return;
         }
 
-        synchronized (mCacheActiveSubInfoList) {
+        boolean opptSubListChanged;
+
+        synchronized (mSubInfoListLock) {
             mCacheActiveSubInfoList.clear();
             List<SubscriptionInfo> activeSubscriptionInfoList = getSubInfo(
                     SubscriptionManager.SIM_SLOT_INDEX + ">=0", null);
             if (activeSubscriptionInfoList != null) {
+                activeSubscriptionInfoList.sort(SUBSCRIPTION_INFO_COMPARATOR);
                 mCacheActiveSubInfoList.addAll(activeSubscriptionInfoList);
             }
+
+            // Refresh cached opportunistic sub list and detect whether it's changed.
+            opptSubListChanged = refreshCachedOpportunisticSubscriptionInfoList();
+
             if (DBG_CACHE) {
                 if (!mCacheActiveSubInfoList.isEmpty()) {
                     for (SubscriptionInfo si : mCacheActiveSubInfoList) {
@@ -665,6 +641,11 @@
                 }
             }
         }
+
+        // Send notification outside synchronization.
+        if (opptSubListChanged) {
+            notifyOpportunisticSubscriptionInfoChanged();
+        }
     }
 
     /**
@@ -952,9 +933,8 @@
                     }
 
                     if (value.size() > 0) {
-                        resolver.update(SubscriptionManager.CONTENT_URI, value,
-                                SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID +
-                                        "=" + Long.toString(subId), null);
+                        resolver.update(SubscriptionManager.getUriForSubscriptionId(subId),
+                                value, null, null);
 
                         // Refresh the Cache of Active Subscription Info List
                         refreshCachedActiveSubscriptionInfoList();
@@ -1047,9 +1027,8 @@
 
                 ContentValues value = new ContentValues();
                 value.put(SubscriptionManager.DISPLAY_NAME, nameToSet);
-                resolver.update(SubscriptionManager.CONTENT_URI, value,
-                        SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID +
-                                "=" + Long.toString(subId), null);
+                resolver.update(SubscriptionManager.getUriForSubscriptionId(subId), value,
+                        null, null);
 
                 // Refresh the Cache of Active Subscription Info List
                 refreshCachedActiveSubscriptionInfoList();
@@ -1166,9 +1145,8 @@
             ContentValues value = new ContentValues(1);
             value.put(SubscriptionManager.CARRIER_NAME, text);
 
-            int result = mContext.getContentResolver().update(SubscriptionManager.CONTENT_URI,
-                    value, SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" +
-                    Long.toString(subId), null);
+            int result = mContext.getContentResolver().update(
+                    SubscriptionManager.getUriForSubscriptionId(subId), value, null, null);
 
             // Refresh the Cache of Active Subscription Info List
             refreshCachedActiveSubscriptionInfoList();
@@ -1201,9 +1179,8 @@
             value.put(SubscriptionManager.COLOR, tint);
             if (DBG) logd("[setIconTint]- tint:" + tint + " set");
 
-            int result = mContext.getContentResolver().update(SubscriptionManager.CONTENT_URI,
-                    value, SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" +
-                            Long.toString(subId), null);
+            int result = mContext.getContentResolver().update(
+                    SubscriptionManager.getUriForSubscriptionId(subId), value, null, null);
 
             // Refresh the Cache of Active Subscription Info List
             refreshCachedActiveSubscriptionInfoList();
@@ -1265,9 +1242,8 @@
             // to the eSIM itself. Currently it will be blown away the next time the subscription
             // list is updated.
 
-            int result = mContext.getContentResolver().update(SubscriptionManager.CONTENT_URI,
-                    value, SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" +
-                    Long.toString(subId), null);
+            int result = mContext.getContentResolver().update(
+                    SubscriptionManager.getUriForSubscriptionId(subId), value, null, null);
 
             // Refresh the Cache of Active Subscription Info List
             refreshCachedActiveSubscriptionInfoList();
@@ -1311,9 +1287,8 @@
             // that was removed as there doesn't seem to be a reason for that. If it is added
             // back, watch out for deadlocks.
 
-            result = mContext.getContentResolver().update(SubscriptionManager.CONTENT_URI, value,
-                    SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID
-                            + "=" + Long.toString(subId), null);
+            result = mContext.getContentResolver().update(
+                    SubscriptionManager.getUriForSubscriptionId(subId), value, null, null);
 
             // Refresh the Cache of Active Subscription Info List
             refreshCachedActiveSubscriptionInfoList();
@@ -1351,9 +1326,8 @@
             value.put(SubscriptionManager.DATA_ROAMING, roaming);
             if (DBG) logd("[setDataRoaming]- roaming:" + roaming + " set");
 
-            int result = mContext.getContentResolver().update(SubscriptionManager.CONTENT_URI,
-                    value, SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" +
-                    Long.toString(subId), null);
+            int result = mContext.getContentResolver().update(
+                    SubscriptionManager.getUriForSubscriptionId(subId), value, null, null);
 
             // Refresh the Cache of Active Subscription Info List
             refreshCachedActiveSubscriptionInfoList();
@@ -1373,21 +1347,25 @@
      * @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);
+        int result = mContext.getContentResolver().update(
+                SubscriptionManager.getUriForSubscriptionId(subId), value, null, null);
 
         // Refresh the Cache of Active Subscription Info List
         refreshCachedActiveSubscriptionInfoList();
@@ -1397,6 +1375,27 @@
         return result;
     }
 
+    /**
+     * Set ISO country code by subscription ID
+     * @param iso iso country code associated with the subscription
+     * @param subId the unique SubInfoRecord index in database
+     * @return the number of records updated
+     */
+    public int setCountryIso(String iso, int subId) {
+        if (DBG) logd("[setCountryIso]+ iso:" + iso + " subId:" + subId);
+        ContentValues value = new ContentValues();
+        value.put(SubscriptionManager.ISO_COUNTRY_CODE, iso);
+
+        int result = mContext.getContentResolver().update(
+                SubscriptionManager.getUriForSubscriptionId(subId), value, null, null);
+
+        // Refresh the Cache of Active Subscription Info List
+        refreshCachedActiveSubscriptionInfoList();
+
+        notifySubscriptionInfoChanged();
+        return result;
+    }
+
     @Override
     public int getSlotIndex(int subId) {
         if (VDBG) printStackTrace("[getSlotIndex] subId=" + subId);
@@ -1461,10 +1460,10 @@
         int size = sSlotIndexToSubId.size();
         if (size == 0) {
             if (VDBG) {
-                logd("[getSubId]- sSlotIndexToSubId.size == 0, return DummySubIds slotIndex="
+                logd("[getSubId]- sSlotIndexToSubId.size == 0, return null slotIndex="
                         + slotIndex);
             }
-            return getDummySubIds(slotIndex);
+            return null;
         }
 
         // Create an array of subIds that are in this slot?
@@ -1487,8 +1486,8 @@
             if (VDBG) logd("[getSubId]- subIdArr=" + subIdArr);
             return subIdArr;
         } else {
-            if (DBG) logd("[getSubId]- numSubIds == 0, return DummySubIds slotIndex=" + slotIndex);
-            return getDummySubIds(slotIndex);
+            if (DBG) logd("[getSubId]- numSubIds == 0, return null slotIndex=" + slotIndex);
+            return null;
         }
     }
 
@@ -1536,27 +1535,6 @@
 
     }
 
-    private int[] getDummySubIds(int slotIndex) {
-        // FIXME: Remove notion of Dummy SUBSCRIPTION_ID.
-        // I tested this returning null as no one appears to care,
-        // but no connection came up on sprout with two sims.
-        // We need to figure out why and hopefully remove DummySubsIds!!!
-        int numSubs = getActiveSubInfoCountMax();
-        if (numSubs > 0) {
-            int[] dummyValues = new int[numSubs];
-            for (int i = 0; i < numSubs; i++) {
-                dummyValues[i] = SubscriptionManager.DUMMY_SUBSCRIPTION_ID_BASE - slotIndex;
-            }
-            if (VDBG) {
-                logd("getDummySubIds: slotIndex=" + slotIndex
-                    + " return " + numSubs + " DummySubIds with each subId=" + dummyValues[0]);
-            }
-            return dummyValues;
-        } else {
-            return null;
-        }
-    }
-
     /**
      * @return the number of records cleared
      */
@@ -1691,6 +1669,7 @@
         intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
                 | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
         intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId);
+        intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId);
         mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
     }
 
@@ -1775,6 +1754,7 @@
         intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
                 | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
         intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId);
+        intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId);
         mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
     }
 
@@ -1795,7 +1775,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);
@@ -1956,6 +1936,20 @@
     }
 
     @Override
+    public boolean isActiveSubId(int subId, String callingPackage) {
+        if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(mContext, subId, callingPackage,
+              "isActiveSubId")) {
+            throw new SecurityException("Requires READ_PHONE_STATE permission.");
+        }
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            return isActiveSubId(subId);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    @Deprecated // This should be moved into isActiveSubId(int, String)
     public boolean isActiveSubId(int subId) {
         boolean retVal = SubscriptionManager.isValidSubscriptionId(subId)
                 && sSlotIndexToSubId.containsValue(subId);
@@ -2003,23 +1997,30 @@
      * @param subId Subscription Id of Subscription
      * @param propKey Column name in database associated with SubscriptionInfo
      * @param propValue Value to store in DB for particular subId & column name
+     *
+     * @return number of rows updated.
      * @hide
      */
     @Override
-    public void setSubscriptionProperty(int subId, String propKey, String propValue) {
+    public int setSubscriptionProperty(int subId, String propKey, String propValue) {
         enforceModifyPhoneState("setSubscriptionProperty");
         final long token = Binder.clearCallingIdentity();
-        ContentResolver resolver = mContext.getContentResolver();
 
-        setSubscriptionPropertyIntoContentResolver(subId, propKey, propValue, resolver);
+        try {
+            validateSubId(subId);
+            ContentResolver resolver = mContext.getContentResolver();
+            int result = setSubscriptionPropertyIntoContentResolver(
+                    subId, propKey, propValue, resolver);
+            // Refresh the Cache of Active Subscription Info List
+            refreshCachedActiveSubscriptionInfoList();
 
-        // Refresh the Cache of Active Subscription Info List
-        refreshCachedActiveSubscriptionInfoList();
-
-        Binder.restoreCallingIdentity(token);
+            return result;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
     }
 
-    private static void setSubscriptionPropertyIntoContentResolver(
+    private static int setSubscriptionPropertyIntoContentResolver(
             int subId, String propKey, String propValue, ContentResolver resolver) {
         ContentValues value = new ContentValues();
         switch (propKey) {
@@ -2036,6 +2037,8 @@
             case SubscriptionManager.CB_CMAS_TEST_ALERT:
             case SubscriptionManager.CB_OPT_OUT_DIALOG:
             case SubscriptionManager.ENHANCED_4G_MODE_ENABLED:
+            case SubscriptionManager.IS_OPPORTUNISTIC:
+            case SubscriptionManager.IS_METERED:
             case SubscriptionManager.VT_IMS_ENABLED:
             case SubscriptionManager.WFC_IMS_ENABLED:
             case SubscriptionManager.WFC_IMS_MODE:
@@ -2048,9 +2051,8 @@
                 break;
         }
 
-        resolver.update(SubscriptionManager.CONTENT_URI, value,
-                SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID +
-                        "=" + Integer.toString(subId), null);
+        return resolver.update(SubscriptionManager.getUriForSubscriptionId(subId),
+                value, null, null);
     }
 
     /**
@@ -2095,6 +2097,9 @@
                         case SubscriptionManager.WFC_IMS_MODE:
                         case SubscriptionManager.WFC_IMS_ROAMING_MODE:
                         case SubscriptionManager.WFC_IMS_ROAMING_ENABLED:
+                        case SubscriptionManager.IS_OPPORTUNISTIC:
+                        case SubscriptionManager.GROUP_UUID:
+                        case SubscriptionManager.IS_METERED:
                             resultValue = cursor.getInt(0) + "";
                             break;
                         default:
@@ -2232,4 +2237,279 @@
         } catch (Settings.SettingNotFoundException e) {
         }
     }
+
+    /**
+     * Switch to a certain subscription
+     *
+     * @param opportunistic whether it’s opportunistic subscription.
+     * @param subId the unique SubscriptionInfo index in database
+     * @return the number of records updated
+     */
+    @Override
+    public int setOpportunistic(boolean opportunistic, int subId) {
+        return setSubscriptionProperty(subId, SubscriptionManager.IS_OPPORTUNISTIC,
+                String.valueOf(opportunistic ? 1 : 0));
+    }
+
+    /**
+     * Set whether a subscription is metered
+     *
+     * @param isMetered whether it’s a metered subscription.
+     * @param subId the unique SubscriptionInfo index in database
+     * @return the number of records updated
+     */
+    @Override
+    public int setMetered(boolean isMetered, int subId) {
+        return setSubscriptionProperty(subId, SubscriptionManager.IS_METERED,
+                String.valueOf(isMetered ? 1 : 0));
+    }
+
+    @Override
+    public int setPreferredData(int subId) {
+        enforceModifyPhoneState("setPreferredData");
+        final long token = Binder.clearCallingIdentity();
+
+        try {
+            if (mPreferredDataSubId != subId) {
+                mPreferredDataSubId = subId;
+                PhoneSwitcher.getInstance().setPreferredData(subId);
+                notifyPreferredDataSubIdChanged();
+            }
+
+            return 0;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    private void notifyPreferredDataSubIdChanged() {
+        ITelephonyRegistry tr = ITelephonyRegistry.Stub.asInterface(ServiceManager.getService(
+                "telephony.registry"));
+        try {
+            if (DBG) logd("notifyPreferredDataSubIdChanged:");
+            tr.notifyPreferredDataSubIdChanged(mPreferredDataSubId);
+        } catch (RemoteException ex) {
+            // Should never happen because its always available.
+        }
+    }
+
+    @Override
+    public List<SubscriptionInfo> getOpportunisticSubscriptions(String callingPackage) {
+        return getSubscriptionInfoListFromCacheHelper(
+                callingPackage, mCacheOpportunisticSubInfoList);
+    }
+
+    /**
+     * Inform SubscriptionManager that subscriptions in the list are bundled
+     * as a group. Typically it's a primary subscription and an opportunistic
+     * subscription. It should only affect multi-SIM scenarios where primary
+     * and opportunistic subscriptions can be activated together.
+     * Being in the same group means they might be activated or deactivated
+     * together, some of them may be invisible to the users, etc.
+     *
+     * Caller will either have {@link android.Manifest.permission.MODIFY_PHONE_STATE}
+     * permission or can manage all subscriptions in the list, according to their
+     * access rules.
+     *
+     * @return groupUUID a UUID assigned to the subscription group. It returns
+     * null if fails.
+     *
+     */
+    @Override
+    public String setSubscriptionGroup(int[] subIdList, String callingPackage) {
+        boolean hasModifyPermission = mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.MODIFY_PHONE_STATE) == PERMISSION_GRANTED;
+
+        // If caller doesn't have modify permission or carrier privilege permission on certain
+        // subscriptions, maybe because the they are not active. So we keep them in a hashset and
+        // later check access rules in our database to know whether they can manage them.
+        Set<Integer> subIdCheckList = new HashSet<>();
+        for (int subId : subIdList) {
+            if (!mTelephonyManager.hasCarrierPrivileges(subId)) {
+                subIdCheckList.add(subId);
+            }
+        }
+
+        long identity = Binder.clearCallingIdentity();
+
+        try {
+            if (!isSubInfoReady()) {
+                if (DBG) logdl("[getSubscriptionInfoList] Sub Controller not ready");
+                return null;
+            }
+
+            SubscriptionManager subscriptionManager = (SubscriptionManager)
+                    mContext.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
+            List<SubscriptionInfo> subList = getSubInfo(null, null);
+
+            for (SubscriptionInfo subInfo : subList) {
+                if (subIdCheckList.contains(subInfo.getSubscriptionId())) {
+                    // If caller doesn't have modify permission or privilege access to
+                    // the subscription, operation is invalid and returns null.
+                    if (hasModifyPermission || (subInfo.isEmbedded()
+                            && subscriptionManager.canManageSubscription(
+                                    subInfo, callingPackage))) {
+                        subIdCheckList.remove(subInfo.getSubscriptionId());
+                    } else {
+                        if (DBG) {
+                            logdl("setSubscriptionGroup doesn't have permission on"
+                                    + " subInfo " + subInfo);
+                        }
+                        return null;
+                    }
+                }
+            }
+
+            if (!subIdCheckList.isEmpty()) {
+                // Some SubId not found.
+                StringBuilder subIdNotFound = new StringBuilder();
+                for (int subId : subIdCheckList) {
+                    subIdNotFound.append(subId + " ");
+                }
+                if (DBG) {
+                    logdl("setSubscriptionGroup subId not existed: "
+                            + subIdNotFound.toString());
+                }
+
+                return null;
+            }
+
+            // Generate a UUID.
+            String groupUUID = UUID.randomUUID().toString();
+
+            // Selection should be: "in (subId1, subId2, ...)".
+            StringBuilder selection = new StringBuilder();
+            selection.append(SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID);
+            selection.append(" IN (");
+            for (int i = 0; i < subIdList.length - 1; i++) {
+                selection.append(subIdList[i] + ", ");
+            }
+            selection.append(subIdList[subIdList.length - 1]);
+            selection.append(")");
+            ContentValues value = new ContentValues();
+            value.put(SubscriptionManager.GROUP_UUID, groupUUID);
+            int result = mContext.getContentResolver().update(
+                    SubscriptionManager.CONTENT_URI, value, selection.toString(), null);
+
+            if (DBG) logdl("setSubscriptionGroup update DB result: " + result);
+
+            refreshCachedActiveSubscriptionInfoList();
+
+            return groupUUID;
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    // Helper function of getOpportunisticSubscriptions and getActiveSubscriptionInfoList.
+    // They are doing similar things except operating on different cache.
+    private List<SubscriptionInfo> getSubscriptionInfoListFromCacheHelper(
+            String callingPackage, List<SubscriptionInfo> cacheSubList) {
+        if (!isSubInfoReady()) {
+            if (DBG) logdl("[getSubscriptionInfoList] Sub Controller not ready");
+            return null;
+        }
+
+        boolean canReadAllPhoneState;
+        try {
+            canReadAllPhoneState = TelephonyPermissions.checkReadPhoneState(mContext,
+                    SubscriptionManager.INVALID_SUBSCRIPTION_ID, Binder.getCallingPid(),
+                    Binder.getCallingUid(), callingPackage, "getSubscriptionInfoList");
+        } catch (SecurityException e) {
+            canReadAllPhoneState = false;
+        }
+
+        synchronized (mSubInfoListLock) {
+            // If the caller can read all phone state, just return the full list.
+            if (canReadAllPhoneState) {
+                return new ArrayList<>(cacheSubList);
+            }
+
+            // Filter the list to only include subscriptions which the caller can manage.
+            return cacheSubList.stream()
+                    .filter(subscriptionInfo -> {
+                        try {
+                            return TelephonyPermissions.checkCallingOrSelfReadPhoneState(mContext,
+                                    subscriptionInfo.getSubscriptionId(), callingPackage,
+                                    "getSubscriptionInfoList");
+                        } catch (SecurityException e) {
+                            return false;
+                        }
+                    })
+                    .collect(Collectors.toList());
+        }
+    }
+
+    private void notifyOpportunisticSubscriptionInfoChanged() {
+        ITelephonyRegistry tr = ITelephonyRegistry.Stub.asInterface(ServiceManager.getService(
+                "telephony.registry"));
+        try {
+            if (DBG) logd("notifyOpptSubscriptionInfoChanged:");
+            tr.notifyOpportunisticSubscriptionInfoChanged();
+        } catch (RemoteException ex) {
+            // Should never happen because its always available.
+        }
+    }
+
+    private boolean refreshCachedOpportunisticSubscriptionInfoList() {
+        synchronized (mSubInfoListLock) {
+            List<SubscriptionInfo> oldOpptCachedList = mCacheOpportunisticSubInfoList;
+
+            List<SubscriptionInfo> subList = getSubInfo(
+                    SubscriptionManager.IS_OPPORTUNISTIC + "=1 AND ("
+                            + SubscriptionManager.SIM_SLOT_INDEX + ">=0 OR "
+                            + SubscriptionManager.IS_EMBEDDED + "=1)", null);
+
+            if (subList != null) {
+                subList.sort(SUBSCRIPTION_INFO_COMPARATOR);
+            } else {
+                subList = new ArrayList<>();
+            }
+
+            mCacheOpportunisticSubInfoList = subList;
+
+            for (SubscriptionInfo info : mCacheOpportunisticSubInfoList) {
+                if (shouldDisableSubGroup(info.getGroupUuid())) {
+                    info.setGroupDisabled(true);
+                    if (isActiveSubId(info.getSubscriptionId())) {
+                        deactivateSubscription(info);
+                    }
+                }
+            }
+
+            if (DBG_CACHE) {
+                if (!mCacheOpportunisticSubInfoList.isEmpty()) {
+                    for (SubscriptionInfo si : mCacheOpportunisticSubInfoList) {
+                        logd("[refreshCachedOpptSubscriptionInfoList] Setting Cached info="
+                                + si);
+                    }
+                } else {
+                    logdl("[refreshCachedOpptSubscriptionInfoList]- no info return");
+                }
+            }
+
+            return !oldOpptCachedList.equals(mCacheOpportunisticSubInfoList);
+        }
+    }
+
+    private boolean shouldDisableSubGroup(String groupUuid) {
+        if (groupUuid == null) return false;
+
+        for (SubscriptionInfo activeInfo : mCacheActiveSubInfoList) {
+            if (!activeInfo.isOpportunistic() && groupUuid.equals(activeInfo.getGroupUuid())) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    private void deactivateSubscription(SubscriptionInfo info) {
+        // TODO: b/120439488 deactivate pSIM.
+        if (info.isEmbedded()) {
+            EuiccManager euiccManager = new EuiccManager(mContext);
+            euiccManager.switchToSubscription(SubscriptionManager.INVALID_SUBSCRIPTION_ID,
+                    PendingIntent.getService(mContext, 0, new Intent(), 0));
+        }
+    }
 }
diff --git a/src/java/com/android/internal/telephony/SubscriptionInfoUpdater.java b/src/java/com/android/internal/telephony/SubscriptionInfoUpdater.java
index 1a4ed5b..e3c8cad 100644
--- a/src/java/com/android/internal/telephony/SubscriptionInfoUpdater.java
+++ b/src/java/com/android/internal/telephony/SubscriptionInfoUpdater.java
@@ -243,10 +243,11 @@
                 break;
 
             case EVENT_SIM_UNKNOWN:
-                updateCarrierServices(msg.arg1, IccCardConstants.INTENT_VALUE_ICC_UNKNOWN);
                 broadcastSimStateChanged(msg.arg1, IccCardConstants.INTENT_VALUE_ICC_UNKNOWN, null);
                 broadcastSimCardStateChanged(msg.arg1, TelephonyManager.SIM_STATE_UNKNOWN);
                 broadcastSimApplicationStateChanged(msg.arg1, TelephonyManager.SIM_STATE_UNKNOWN);
+                updateSubscriptionCarrierId(msg.arg1, IccCardConstants.INTENT_VALUE_ICC_UNKNOWN);
+                updateCarrierServices(msg.arg1, IccCardConstants.INTENT_VALUE_ICC_UNKNOWN);
                 break;
 
             case EVENT_SIM_IO_ERROR:
@@ -254,12 +255,14 @@
                 break;
 
             case EVENT_SIM_RESTRICTED:
-                updateCarrierServices(msg.arg1, IccCardConstants.INTENT_VALUE_ICC_CARD_RESTRICTED);
                 broadcastSimStateChanged(msg.arg1,
                         IccCardConstants.INTENT_VALUE_ICC_CARD_RESTRICTED,
                         IccCardConstants.INTENT_VALUE_ICC_CARD_RESTRICTED);
                 broadcastSimCardStateChanged(msg.arg1, TelephonyManager.SIM_STATE_CARD_RESTRICTED);
                 broadcastSimApplicationStateChanged(msg.arg1, TelephonyManager.SIM_STATE_NOT_READY);
+                updateSubscriptionCarrierId(msg.arg1,
+                        IccCardConstants.INTENT_VALUE_ICC_CARD_RESTRICTED);
+                updateCarrierServices(msg.arg1, IccCardConstants.INTENT_VALUE_ICC_CARD_RESTRICTED);
                 break;
 
             case EVENT_SIM_READY:
@@ -273,16 +276,8 @@
                 break;
 
             case EVENT_SIM_NOT_READY:
-                broadcastSimStateChanged(msg.arg1, IccCardConstants.INTENT_VALUE_ICC_NOT_READY,
-                        null);
-                broadcastSimCardStateChanged(msg.arg1, TelephonyManager.SIM_STATE_PRESENT);
-                broadcastSimApplicationStateChanged(msg.arg1, TelephonyManager.SIM_STATE_NOT_READY);
+                handleSimNotReady(msg.arg1);
                 // intentional fall through
-                // ICC_NOT_READY is a terminal state for an eSIM on the boot profile. At this
-                // phase, the subscription list is accessible.
-                // TODO(b/64216093): Clean up this special case, likely by treating NOT_READY
-                // as equivalent to ABSENT, once the rest of the system can handle it. Currently
-                // this breaks SystemUI which shows a "No SIM" icon.
 
             case EVENT_REFRESH_EMBEDDED_SUBSCRIPTIONS:
                 if (updateEmbeddedSubscriptions()) {
@@ -333,10 +328,11 @@
             updateSubscriptionInfoByIccId();
         }
 
-        updateCarrierServices(slotId, IccCardConstants.INTENT_VALUE_ICC_LOCKED);
         broadcastSimStateChanged(slotId, IccCardConstants.INTENT_VALUE_ICC_LOCKED, reason);
         broadcastSimCardStateChanged(slotId, TelephonyManager.SIM_STATE_PRESENT);
         broadcastSimApplicationStateChanged(slotId, getSimStateFromLockedReason(reason));
+        updateSubscriptionCarrierId(slotId, IccCardConstants.INTENT_VALUE_ICC_LOCKED);
+        updateCarrierServices(slotId, IccCardConstants.INTENT_VALUE_ICC_LOCKED);
     }
 
     private static int getSimStateFromLockedReason(String lockedReason) {
@@ -355,6 +351,26 @@
         }
     }
 
+    private void handleSimNotReady(int slotId) {
+        logd("handleSimNotReady: slotId: " + slotId);
+
+        IccCard iccCard = mPhone[slotId].getIccCard();
+        if (iccCard.isEmptyProfile()) {
+            // ICC_NOT_READY is a terminal state for an eSIM on the boot profile. At this
+            // phase, the subscription list is accessible. Treating NOT_READY
+            // as equivalent to ABSENT, once the rest of the system can handle it.
+            mIccId[slotId] = ICCID_STRING_FOR_NO_SIM;
+            if (isAllIccIdQueryDone()) {
+                updateSubscriptionInfoByIccId();
+            }
+        }
+
+        broadcastSimStateChanged(slotId, IccCardConstants.INTENT_VALUE_ICC_NOT_READY,
+                null);
+        broadcastSimCardStateChanged(slotId, TelephonyManager.SIM_STATE_PRESENT);
+        broadcastSimApplicationStateChanged(slotId, TelephonyManager.SIM_STATE_NOT_READY);
+    }
+
     private void handleSimLoaded(int slotId) {
         logd("handleSimLoaded: slotId: " + slotId);
 
@@ -389,13 +405,21 @@
 
                 if (!TextUtils.isEmpty(operator)) {
                     if (subId == SubscriptionController.getInstance().getDefaultSubId()) {
-                        MccTable.updateMccMncConfiguration(mContext, operator, false);
+                        MccTable.updateMccMncConfiguration(mContext, operator);
                     }
                     SubscriptionController.getInstance().setMccMnc(operator, subId);
                 } else {
                     logd("EVENT_RECORDS_LOADED Operator name is null");
                 }
 
+                String iso = tm.getSimCountryIsoForPhone(slotId);
+
+                if (!TextUtils.isEmpty(iso)) {
+                    SubscriptionController.getInstance().setCountryIso(iso, subId);
+                } else {
+                    logd("EVENT_RECORDS_LOADED sim country iso is null");
+                }
+
                 String msisdn = tm.getLine1Number(subId);
                 ContentResolver contentResolver = mContext.getContentResolver();
 
@@ -467,9 +491,18 @@
                 mPackageManager, TelephonyManager.getDefault(),
                 mContext.getContentResolver(), mCurrentlyActiveUserId);
 
+        /**
+         * The sim loading sequence will be
+         *  1. ACTION_SUBINFO_CONTENT_CHANGE happens through updateSubscriptionInfoByIccId() above.
+         *  2. ACTION_SIM_STATE_CHANGED/ACTION_SIM_CARD_STATE_CHANGED
+         *  /ACTION_SIM_APPLICATION_STATE_CHANGED
+         *  3. ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED
+         *  4. ACTION_CARRIER_CONFIG_CHANGED
+         */
         broadcastSimStateChanged(loadedSlotId, IccCardConstants.INTENT_VALUE_ICC_LOADED, null);
         broadcastSimCardStateChanged(loadedSlotId, TelephonyManager.SIM_STATE_PRESENT);
         broadcastSimApplicationStateChanged(loadedSlotId, TelephonyManager.SIM_STATE_LOADED);
+        updateSubscriptionCarrierId(loadedSlotId, IccCardConstants.INTENT_VALUE_ICC_LOADED);
         updateCarrierServices(loadedSlotId, IccCardConstants.INTENT_VALUE_ICC_LOADED);
     }
 
@@ -480,6 +513,12 @@
         mCarrierServiceBindHelper.updateForPhoneId(slotId, simState);
     }
 
+    private void updateSubscriptionCarrierId(int slotId, String simState) {
+        if (mPhone != null && mPhone[slotId] != null) {
+            mPhone[slotId].resolveSubscriptionCarrierId(simState);
+        }
+    }
+
     private void handleSimAbsent(int slotId) {
         if (mIccId[slotId] != null && !mIccId[slotId].equals(ICCID_STRING_FOR_NO_SIM)) {
             logd("SIM" + (slotId + 1) + " hot plug out");
@@ -488,10 +527,11 @@
         if (isAllIccIdQueryDone()) {
             updateSubscriptionInfoByIccId();
         }
-        updateCarrierServices(slotId, IccCardConstants.INTENT_VALUE_ICC_ABSENT);
         broadcastSimStateChanged(slotId, IccCardConstants.INTENT_VALUE_ICC_ABSENT, null);
         broadcastSimCardStateChanged(slotId, TelephonyManager.SIM_STATE_ABSENT);
         broadcastSimApplicationStateChanged(slotId, TelephonyManager.SIM_STATE_NOT_READY);
+        updateSubscriptionCarrierId(slotId, IccCardConstants.INTENT_VALUE_ICC_ABSENT);
+        updateCarrierServices(slotId, IccCardConstants.INTENT_VALUE_ICC_ABSENT);
     }
 
     private void handleSimError(int slotId) {
@@ -502,11 +542,12 @@
         if (isAllIccIdQueryDone()) {
             updateSubscriptionInfoByIccId();
         }
-        updateCarrierServices(slotId, IccCardConstants.INTENT_VALUE_ICC_CARD_IO_ERROR);
         broadcastSimStateChanged(slotId, IccCardConstants.INTENT_VALUE_ICC_CARD_IO_ERROR,
                 IccCardConstants.INTENT_VALUE_ICC_CARD_IO_ERROR);
         broadcastSimCardStateChanged(slotId, TelephonyManager.SIM_STATE_CARD_IO_ERROR);
         broadcastSimApplicationStateChanged(slotId, TelephonyManager.SIM_STATE_NOT_READY);
+        updateSubscriptionCarrierId(slotId, IccCardConstants.INTENT_VALUE_ICC_CARD_IO_ERROR);
+        updateCarrierServices(slotId, IccCardConstants.INTENT_VALUE_ICC_CARD_IO_ERROR);
     }
 
     /**
@@ -571,9 +612,8 @@
                     ContentValues value = new ContentValues(1);
                     value.put(SubscriptionManager.SIM_SLOT_INDEX,
                             SubscriptionManager.INVALID_SIM_SLOT_INDEX);
-                    contentResolver.update(SubscriptionManager.CONTENT_URI, value,
-                            SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "="
-                            + Integer.toString(oldSubInfo.get(0).getSubscriptionId()), null);
+                    contentResolver.update(SubscriptionManager.getUriForSubscriptionId(
+                            oldSubInfo.get(0).getSubscriptionId()), value, null, null);
 
                     // refresh Cached Active Subscription Info List
                     SubscriptionController.getInstance().refreshCachedActiveSubscriptionInfoList();
@@ -594,8 +634,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");
@@ -612,22 +650,6 @@
                     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;
                 }
             }
@@ -653,9 +675,8 @@
             if (msisdn != null) {
                 ContentValues value = new ContentValues(1);
                 value.put(SubscriptionManager.NUMBER, msisdn);
-                contentResolver.update(SubscriptionManager.CONTENT_URI, value,
-                        SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "="
-                        + Integer.toString(temp.getSubscriptionId()), null);
+                contentResolver.update(SubscriptionManager.getUriForSubscriptionId(
+                        temp.getSubscriptionId()), value, null, null);
 
                 // refresh Cached Active Subscription Info List
                 SubscriptionController.getInstance().refreshCachedActiveSubscriptionInfoList();
diff --git a/src/java/com/android/internal/telephony/TelephonyComponentFactory.java b/src/java/com/android/internal/telephony/TelephonyComponentFactory.java
index 8ad5720..a8fa4b8 100644
--- a/src/java/com/android/internal/telephony/TelephonyComponentFactory.java
+++ b/src/java/com/android/internal/telephony/TelephonyComponentFactory.java
@@ -16,17 +16,21 @@
 
 package com.android.internal.telephony;
 
+import android.annotation.NonNull;
 import android.content.Context;
+import android.content.res.XmlResourceParser;
 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;
+import android.telephony.Rlog;
 
 import com.android.internal.telephony.cdma.CdmaSubscriptionSourceManager;
 import com.android.internal.telephony.cdma.EriManager;
 import com.android.internal.telephony.dataconnection.DcTracker;
+import com.android.internal.telephony.dataconnection.TransportManager;
+import com.android.internal.telephony.emergency.EmergencyNumberTracker;
 import com.android.internal.telephony.imsphone.ImsExternalCallTracker;
 import com.android.internal.telephony.imsphone.ImsPhone;
 import com.android.internal.telephony.imsphone.ImsPhoneCallTracker;
@@ -34,14 +38,149 @@
 import com.android.internal.telephony.uicc.UiccCard;
 import com.android.internal.telephony.uicc.UiccProfile;
 
+import dalvik.system.PathClassLoader;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.function.Consumer;
+
 /**
  * This class has one-line methods to instantiate objects only. The purpose is to make code
  * unit-test friendly and use this class as a way to do dependency injection. Instantiating objects
  * this way makes it easier to mock them in tests.
  */
 public class TelephonyComponentFactory {
+
+    private static final String TAG = TelephonyComponentFactory.class.getSimpleName();
+
     private static TelephonyComponentFactory sInstance;
 
+    private InjectedComponents mInjectedComponents;
+
+    private static class InjectedComponents {
+        private static final String ATTRIBUTE_JAR = "jar";
+        private static final String ATTRIBUTE_PACKAGE = "package";
+        private static final String TAG_INJECTION = "injection";
+        private static final String TAG_COMPONENTS = "components";
+        private static final String TAG_COMPONENT = "component";
+
+        private final Set<String> mComponentNames = new HashSet<>();
+        private TelephonyComponentFactory mInjectedInstance;
+        private String mPackageName;
+        private String mJarPath;
+
+        private boolean isInjected() {
+            return mPackageName != null && mJarPath != null;
+        }
+
+        private void makeInjectedInstance() {
+            if (isInjected()) {
+                PathClassLoader classLoader = new PathClassLoader(mJarPath,
+                        ClassLoader.getSystemClassLoader());
+                try {
+                    Class<?> cls = classLoader.loadClass(mPackageName);
+                    mInjectedInstance = (TelephonyComponentFactory) cls.newInstance();
+                } catch (ClassNotFoundException e) {
+                    Rlog.e(TAG, "failed: " + e.getMessage());
+                } catch (IllegalAccessException | InstantiationException e) {
+                    Rlog.e(TAG, "injection failed: " + e.getMessage());
+                }
+            }
+        }
+
+        private boolean isComponentInjected(String componentName) {
+            if (mInjectedInstance == null) {
+                return false;
+            }
+            return mComponentNames.contains(componentName);
+        }
+
+        /**
+         * Find the injection tag, set attributes, and then parse the injection.
+         */
+        private void parseXml(@NonNull XmlPullParser parser) {
+            parseXmlByTag(parser, false, p -> {
+                setAttributes(p);
+                parseInjection(p);
+            }, TAG_INJECTION);
+        }
+
+        /**
+         * Only parse the first injection tag. Find the components tag, then try parse it next.
+         */
+        private void parseInjection(@NonNull XmlPullParser parser) {
+            parseXmlByTag(parser, false, p -> parseComponents(p), TAG_COMPONENTS);
+        }
+
+        /**
+         * Only parse the first components tag. Find the component tags, then try parse them next.
+         */
+        private void parseComponents(@NonNull XmlPullParser parser) {
+            parseXmlByTag(parser, true, p -> parseComponent(p), TAG_COMPONENT);
+        }
+
+        /**
+         * Extract text values from component tags.
+         */
+        private void parseComponent(@NonNull XmlPullParser parser) {
+            try {
+                int outerDepth = parser.getDepth();
+                int type;
+                while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                        && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+                    if (type == XmlPullParser.TEXT) {
+                        mComponentNames.add(parser.getText());
+                    }
+                }
+            } catch (XmlPullParserException | IOException e) {
+                Rlog.e(TAG, "Failed to parse the component." , e);
+            }
+        }
+
+        /**
+         * Iterates the tags, finds the corresponding tag and then applies the consumer.
+         */
+        private void parseXmlByTag(@NonNull XmlPullParser parser, boolean allowDuplicate,
+                @NonNull Consumer<XmlPullParser> consumer, @NonNull final String tag) {
+            try {
+                int outerDepth = parser.getDepth();
+                int type;
+                while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                        && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+                    if (type == XmlPullParser.START_TAG && tag.equals(parser.getName())) {
+                        consumer.accept(parser);
+                        if (!allowDuplicate) {
+                            return;
+                        }
+                    }
+                }
+            } catch (XmlPullParserException | IOException e) {
+                Rlog.e(TAG, "Failed to parse or find tag: " + tag, e);
+            }
+        }
+
+        /**
+         * Sets the mPackageName and mJarPath by <injection/> tag.
+         * @param parser
+         * @return
+         */
+        private void setAttributes(@NonNull XmlPullParser parser) {
+            for (int i = 0; i < parser.getAttributeCount(); i++) {
+                String name = parser.getAttributeName(i);
+                String value = parser.getAttributeValue(i);
+                if (InjectedComponents.ATTRIBUTE_PACKAGE.equals(name)) {
+                    mPackageName = value;
+                } else if (InjectedComponents.ATTRIBUTE_JAR.equals(name)) {
+                    mJarPath = value;
+                }
+            }
+        }
+    }
+
     public static TelephonyComponentFactory getInstance() {
         if (sInstance == null) {
             sInstance = new TelephonyComponentFactory();
@@ -49,6 +188,46 @@
         return sInstance;
     }
 
+    /**
+     * Inject TelephonyComponentFactory using a xml config file.
+     * @param parser a nullable {@link XmlResourceParser} created with the injection config file.
+     * The config xml should has below formats:
+     * <injection package="package.InjectedTelephonyComponentFactory" jar="path to jar file">
+     *     <components>
+     *         <component>example.package.ComponentAbc</component>
+     *         <component>example.package.ComponentXyz</component>
+     *         <!-- e.g. com.android.internal.telephony.GsmCdmaPhone -->
+     *     </components>
+     * </injection>
+     */
+    public void injectTheComponentFactory(XmlResourceParser parser) {
+        if (mInjectedComponents != null) {
+            Rlog.i(TAG, "Already injected.");
+            return;
+        }
+
+        if (parser != null) {
+            mInjectedComponents = new InjectedComponents();
+            mInjectedComponents.parseXml(parser);
+            mInjectedComponents.makeInjectedInstance();
+            Rlog.i(TAG, "Total components injected: "
+                    + mInjectedComponents.mComponentNames.size());
+        }
+    }
+
+    /**
+     * Use the injected TelephonyComponentFactory if configured. Otherwise, use the default.
+     * @param componentName Name of the component class uses the injected component factory,
+     * e.g. GsmCdmaPhone.class.getName() for {@link GsmCdmaPhone}
+     * @return injected component factory. If not configured or injected, return the default one.
+     */
+    public TelephonyComponentFactory inject(String componentName) {
+        if (mInjectedComponents != null && mInjectedComponents.isComponentInjected(componentName)) {
+            return mInjectedComponents.mInjectedInstance;
+        }
+        return sInstance;
+    }
+
     public GsmCdmaCallTracker makeGsmCdmaCallTracker(GsmCdmaPhone phone) {
         return new GsmCdmaCallTracker(phone);
     }
@@ -66,18 +245,33 @@
     }
 
     /**
+     * Create a new EmergencyNumberTracker.
+     */
+    public EmergencyNumberTracker makeEmergencyNumberTracker(Phone phone, CommandsInterface ci) {
+        return new EmergencyNumberTracker(phone, ci);
+    }
+
+    /**
+     * 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) {
         return new SimActivationTracker(phone);
     }
 
-    public DcTracker makeDcTracker(Phone phone) {
-        return new DcTracker(phone, TransportType.WWAN);
+    public DcTracker makeDcTracker(Phone phone, int transportType) {
+        return new DcTracker(phone, transportType);
     }
 
     public CarrierSignalAgent makeCarrierSignalAgent(Phone phone) {
@@ -88,8 +282,8 @@
         return new CarrierActionAgent(phone);
     }
 
-    public CarrierIdentifier makeCarrierIdentifier(Phone phone) {
-        return new CarrierIdentifier(phone);
+    public CarrierResolver makeCarrierResolver(Phone phone) {
+        return new CarrierResolver(phone);
     }
 
     public IccPhoneBookInterfaceManager makeIccPhoneBookInterfaceManager(Phone phone) {
@@ -163,6 +357,10 @@
         return new DeviceStateMonitor(phone);
     }
 
+    public TransportManager makeTransportManager(Phone phone) {
+        return new TransportManager(phone);
+    }
+
     public CdmaSubscriptionSourceManager
     getCdmaSubscriptionSourceManagerInstance(Context context, CommandsInterface ci, Handler h,
                                              int what, Object obj) {
@@ -173,8 +371,8 @@
         return IDeviceIdleController.Stub.asInterface(
                 ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
     }
-
-    public LocaleTracker makeLocaleTracker(Phone phone, Looper looper) {
-        return new LocaleTracker(phone, looper);
+    public LocaleTracker makeLocaleTracker(Phone phone, NitzStateMachine nitzStateMachine,
+                                           Looper looper) {
+        return new LocaleTracker(phone, nitzStateMachine, looper);
     }
 }
diff --git a/src/java/com/android/internal/telephony/TelephonyTester.java b/src/java/com/android/internal/telephony/TelephonyTester.java
index d4e0c6b..0ab6ec8 100644
--- a/src/java/com/android/internal/telephony/TelephonyTester.java
+++ b/src/java/com/android/internal/telephony/TelephonyTester.java
@@ -23,7 +23,7 @@
 import android.net.Uri;
 import android.os.BadParcelableException;
 import android.os.Build;
-import android.telephony.NetworkRegistrationState;
+import android.os.Bundle;
 import android.telephony.Rlog;
 import android.telephony.ServiceState;
 import android.telephony.ims.ImsCallProfile;
@@ -105,6 +105,19 @@
     private static final String EXTRA_CODE = "code";
     private static final String EXTRA_TYPE = "type";
 
+    /**
+     * Test-only intent used to trigger signalling that an IMS call is an emergency call.
+     */
+    private static final String ACTION_TEST_IMS_E_CALL =
+            "com.android.internal.telephony.TestImsECall";
+
+    /**
+     * Test-only intent used to trigger a change to the current call's phone number.
+     * Use the {@link #EXTRA_NUMBER} extra to specify the new phone number.
+     */
+    private static final String ACTION_TEST_CHANGE_NUMBER =
+            "com.android.internal.telephony.TestChangeNumber";
+
     private static final String ACTION_TEST_SERVICE_STATE =
             "com.android.internal.telephony.TestServiceState";
 
@@ -115,6 +128,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";
 
@@ -161,6 +175,12 @@
                     mServiceStateTestIntent = intent;
                     mPhone.getServiceStateTracker().sendEmptyMessage(
                             ServiceStateTracker.EVENT_NETWORK_STATE_CHANGED);
+                } else if (action.equals(ACTION_TEST_IMS_E_CALL)) {
+                    log("handle test IMS ecall intent");
+                    testImsECall();
+                } else if (action.equals(ACTION_TEST_CHANGE_NUMBER)) {
+                    log("handle test change number intent");
+                    testChangeNumber(intent);
                 } else {
                     if (DBG) log("onReceive: unknown action=" + action);
                 }
@@ -189,12 +209,13 @@
                 filter.addAction(ACTION_TEST_SUPP_SRVC_FAIL);
                 filter.addAction(ACTION_TEST_HANDOVER_FAIL);
                 filter.addAction(ACTION_TEST_SUPP_SRVC_NOTIFICATION);
+                filter.addAction(ACTION_TEST_IMS_E_CALL);
                 mImsExternalCallStates = new ArrayList<ImsExternalCallState>();
             } else {
                 filter.addAction(ACTION_TEST_SERVICE_STATE);
                 log("register for intent action=" + ACTION_TEST_SERVICE_STATE);
             }
-
+            filter.addAction(ACTION_TEST_CHANGE_NUMBER);
             phone.getContext().registerReceiver(mIntentReceiver, filter, null, mPhone.getHandler());
         }
     }
@@ -341,13 +362,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 +390,63 @@
                     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);
+        }
+    }
+
+    void testImsECall() {
+        // Attempt to get the active IMS call before parsing the test XML file.
+        ImsPhone imsPhone = (ImsPhone) mPhone;
+        if (imsPhone == null) {
+            return;
+        }
+
+        ImsPhoneCall imsPhoneCall = imsPhone.getForegroundCall();
+        if (imsPhoneCall == null) {
+            return;
+        }
+
+        ImsCall imsCall = imsPhoneCall.getImsCall();
+        if (imsCall == null) {
+            return;
+        }
+
+        ImsCallProfile callProfile = imsCall.getCallProfile();
+        Bundle extras = callProfile.getCallExtras();
+        if (extras == null) {
+            extras = new Bundle();
+        }
+        extras.putBoolean(ImsCallProfile.EXTRA_EMERGENCY_CALL, true);
+        callProfile.mCallExtras = extras;
+        imsCall.getImsCallSessionListenerProxy().callSessionUpdated(imsCall.getSession(),
+                callProfile);
+    }
+
+    void testChangeNumber(Intent intent) {
+        if (!intent.hasExtra(EXTRA_NUMBER)) {
+            return;
+        }
+
+        String newNumber = intent.getStringExtra(EXTRA_NUMBER);
+
+        // Update all the calls.
+        mPhone.getForegroundCall().getConnections()
+                .stream()
+                .forEach(c -> {
+                    c.setAddress(newNumber, PhoneConstants.PRESENTATION_ALLOWED);
+                    c.setDialString(newNumber);
+                });
+
+        // <sigh>
+        if (mPhone instanceof GsmCdmaPhone) {
+            ((GsmCdmaPhone) mPhone).notifyPhoneStateChanged();
+            ((GsmCdmaPhone) mPhone).notifyPreciseCallStateChanged();
+        } else if (mPhone instanceof ImsPhone) {
+            ((ImsPhone) mPhone).notifyPhoneStateChanged();
+            ((ImsPhone) mPhone).notifyPreciseCallStateChanged();
+        }
     }
 }
\ No newline at end of file
diff --git a/src/java/com/android/internal/telephony/TimeZoneLookupHelper.java b/src/java/com/android/internal/telephony/TimeZoneLookupHelper.java
index 101fddd..a0f7447 100644
--- a/src/java/com/android/internal/telephony/TimeZoneLookupHelper.java
+++ b/src/java/com/android/internal/telephony/TimeZoneLookupHelper.java
@@ -18,8 +18,8 @@
 
 import android.text.TextUtils;
 
-import libcore.util.CountryTimeZones;
-import libcore.util.TimeZoneFinder;
+import libcore.timezone.CountryTimeZones;
+import libcore.timezone.TimeZoneFinder;
 
 import java.util.Date;
 import java.util.TimeZone;
diff --git a/src/java/com/android/internal/telephony/TransportManager.java b/src/java/com/android/internal/telephony/TransportManager.java
index d81130a..a093b00 100644
--- a/src/java/com/android/internal/telephony/TransportManager.java
+++ b/src/java/com/android/internal/telephony/TransportManager.java
@@ -16,28 +16,170 @@
 
 package com.android.internal.telephony.dataconnection;
 
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
 import android.telephony.AccessNetworkConstants.TransportType;
+import android.telephony.CarrierConfigManager;
 import android.telephony.Rlog;
+import android.telephony.data.ApnSetting.ApnType;
+import android.text.TextUtils;
 
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.dataconnection.AccessNetworksManager.QualifiedNetworks;
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.List;
 
 /**
- * This class represents the transport manager which manages available transports and
- * route requests to correct transport.
+ * This class represents the transport manager which manages available transports (i.e. WWAN or
+ * WLAN)and determine the correct transport for {@link TelephonyNetworkFactory} to handle the data
+ * requests.
  */
-public class TransportManager {
+public class TransportManager extends Handler {
     private static final String TAG = TransportManager.class.getSimpleName();
 
-    private List<Integer> mAvailableTransports = new ArrayList<>();
+    private static final boolean DBG = true;
 
-    public TransportManager() {
-        // TODO: get transpot list from AccessNetworkManager.
-        mAvailableTransports.add(TransportType.WWAN);
+    private static final int EVENT_QUALIFIED_NETWORKS_CHANGED = 1;
+
+    public static final String SYSTEM_PROPERTIES_IWLAN_OPERATION_MODE =
+            "ro.telephony.iwlan_operation_mode";
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"IWLAN_OPERATION_MODE_"},
+            value = {
+                    IWLAN_OPERATION_MODE_DEFAULT,
+                    IWLAN_OPERATION_MODE_LEGACY,
+                    IWLAN_OPERATION_MODE_AP_ASSISTED})
+    public @interface IwlanOperationMode {}
+
+    /**
+     * IWLAN default mode. On device that has IRadio 1.3 or above, it means
+     * {@link #IWLAN_OPERATION_MODE_AP_ASSISTED}. On device that has IRadio 1.2 or below, it means
+     * {@link #IWLAN_OPERATION_MODE_LEGACY}.
+     */
+    public static final int IWLAN_OPERATION_MODE_DEFAULT = 0;
+
+    /**
+     * IWLAN legacy mode. IWLAN is completely handled by the modem, and when the device is on
+     * IWLAN, modem reports IWLAN as a RAT.
+     */
+    public static final int IWLAN_OPERATION_MODE_LEGACY = 1;
+
+    /**
+     * IWLAN application processor assisted mode. IWLAN is handled by the bound IWLAN data service
+     * and network service separately.
+     */
+    public static final int IWLAN_OPERATION_MODE_AP_ASSISTED = 2;
+
+    private final Phone mPhone;
+
+    /** The available transports. Must be one or more of AccessNetworkConstants.TransportType.XXX */
+    private final int[] mAvailableTransports;
+
+    private final AccessNetworksManager mAccessNetworksManager;
+
+    public TransportManager(Phone phone) {
+        mPhone = phone;
+        mAccessNetworksManager = new AccessNetworksManager(phone);
+
+        mAccessNetworksManager.registerForQualifiedNetworksChanged(this,
+                EVENT_QUALIFIED_NETWORKS_CHANGED);
+
+        if (isInLegacyMode()) {
+            // For legacy mode, WWAN is the only transport to handle all data connections, even
+            // the IWLAN ones.
+            mAvailableTransports = new int[]{TransportType.WWAN};
+        } else {
+            mAvailableTransports = new int[]{TransportType.WWAN, TransportType.WLAN};
+        }
     }
 
-    public List<Integer> getAvailableTransports() {
-        return new ArrayList<>(mAvailableTransports);
+    @Override
+    public void handleMessage(Message msg) {
+        switch (msg.what) {
+            case EVENT_QUALIFIED_NETWORKS_CHANGED:
+                AsyncResult ar = (AsyncResult) msg.obj;
+                List<QualifiedNetworks> networks = (List<QualifiedNetworks>) ar.result;
+                updateAvailableNetworks(networks);
+                break;
+            default:
+                loge("Unexpected event " + msg.what);
+                break;
+        }
+    }
+
+    private synchronized void updateAvailableNetworks(List<QualifiedNetworks> networks) {
+        log("updateAvailableNetworks: " + networks);
+        //TODO: Update available networks and transports.
+    }
+
+    /**
+     * @return The available transports. Note that on legacy devices, the only available transport
+     * would be WWAN only. If the device is configured as AP-assisted mode, the available transport
+     * will always be WWAN and WLAN (even if the device is not camped on IWLAN).
+     * See {@link #isInLegacyMode()} for mode details.
+     */
+    public synchronized @NonNull int[] getAvailableTransports() {
+        return mAvailableTransports;
+    }
+
+    /**
+     * @return True if in IWLAN legacy mode. Operating in legacy mode means telephony will send
+     * all data requests to the default data service, which is the cellular data service.
+     * AP-assisted mode requires properly configuring the resource overlay
+     * 'config_wwan_data_service_package' (or the carrier config
+     * {@link CarrierConfigManager#KEY_CARRIER_DATA_SERVICE_WLAN_PACKAGE_OVERRIDE_STRING }) to
+     * the IWLAN data service package, 'config_wwan_network_service_package' (or the carrier config
+     * {@link CarrierConfigManager#KEY_CARRIER_NETWORK_SERVICE_WLAN_PACKAGE_OVERRIDE_STRING })
+     * to the IWLAN network service package, and 'config_qualified_networks_service_package' (or the
+     * carrier config
+     * {@link CarrierConfigManager#KEY_CARRIER_QUALIFIED_NETWORKS_SERVICE_PACKAGE_OVERRIDE_STRING})
+     * to the qualified networks service package.
+     */
+    public boolean isInLegacyMode() {
+        return (mPhone.mCi.getIwlanOperationMode() == IWLAN_OPERATION_MODE_LEGACY);
+    }
+
+    /**
+     * Get the corresponding transport based on the APN type
+     *
+     * @param apnType APN type
+     * @return The transport type
+     */
+    public int getCurrentTransport(@ApnType int apnType) {
+        // TODO: Look up the transport from the transport type map
+        return TransportType.WWAN;
+    }
+
+    /**
+     * Dump the state of transport manager
+     *
+     * @param fd File descriptor
+     * @param printwriter Print writer
+     * @param args Arguments
+     */
+    public void dump(FileDescriptor fd, PrintWriter printwriter, String[] args) {
+        IndentingPrintWriter pw = new IndentingPrintWriter(printwriter, "  ");
+        pw.println("TransportManager:");
+        pw.increaseIndent();
+        pw.print("mAvailableTransports=");
+        List<String> transportsStrings = new ArrayList<>();
+        for (int i = 0; i < mAvailableTransports.length; i++) {
+            transportsStrings.add(TransportType.toString(mAvailableTransports[i]));
+        }
+        pw.println("[" + TextUtils.join(",", transportsStrings) + "]");
+        mAccessNetworksManager.dump(fd, pw, args);
+        pw.decreaseIndent();
+        pw.flush();
     }
 
     private void log(String s) {
diff --git a/src/java/com/android/internal/telephony/UiccSmsController.java b/src/java/com/android/internal/telephony/UiccSmsController.java
index 59449b8..48df5c4 100644
--- a/src/java/com/android/internal/telephony/UiccSmsController.java
+++ b/src/java/com/android/internal/telephony/UiccSmsController.java
@@ -18,13 +18,14 @@
 
 package com.android.internal.telephony;
 
+import static com.android.internal.util.DumpUtils.checkDumpPermission;
+
 import android.annotation.Nullable;
 import android.app.ActivityThread;
 import android.app.PendingIntent;
 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,16 +34,22 @@
 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 {
-    static final String LOG_TAG = "RIL_UiccSmsController";
+public class UiccSmsController extends ISmsImplBase {
+    static final String LOG_TAG = "UiccSmsController";
 
-    protected UiccSmsController() {
+    private final Context mContext;
+
+    protected UiccSmsController(Context context) {
+        mContext = context;
         if (ServiceManager.getService("isms") == null) {
             ServiceManager.addService("isms", this);
         }
@@ -57,9 +64,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 +78,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 +90,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 +117,7 @@
         }
     }
 
+    @Override
     public void sendDataForSubscriberWithSelfPermissions(int subId, String callingPackage,
             String destAddr, String scAddr, int destPort, byte[] data, PendingIntent sentIntent,
             PendingIntent deliveryIntent) {
@@ -122,15 +128,10 @@
         } else {
             Rlog.e(LOG_TAG,"sendText iccSmsIntMgr is null for" +
                           " Subscription: " + subId);
+            sendErrorInPendingIntent(sentIntent, SmsManager.RESULT_ERROR_GENERIC_FAILURE);
         }
     }
 
-    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 +147,7 @@
         }
     }
 
+    @Override
     public void sendTextForSubscriberWithSelfPermissions(int subId, String callingPackage,
             String destAddr, String scAddr, String text, PendingIntent sentIntent,
             PendingIntent deliveryIntent, boolean persistMessage) {
@@ -156,37 +158,30 @@
         } else {
             Rlog.e(LOG_TAG,"sendText iccSmsIntMgr is null for" +
                           " Subscription: " + subId);
+            sendErrorInPendingIntent(sentIntent, SmsManager.RESULT_ERROR_GENERIC_FAILURE);
         }
     }
 
     @Override
     public void sendTextForSubscriberWithOptions(int subId, String callingPackage,
-            String destAddr, String scAddr, String parts, PendingIntent sentIntents,
-            PendingIntent deliveryIntents, boolean persistMessage, int priority,
+            String destAddr, String scAddr, String parts, PendingIntent sentIntent,
+            PendingIntent deliveryIntent, boolean persistMessage, int priority,
             boolean expectMore, int validityPeriod) {
         IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId);
         if (iccSmsIntMgr != null ) {
-            iccSmsIntMgr.sendTextWithOptions(callingPackage, destAddr, scAddr, parts, sentIntents,
-                    deliveryIntents, persistMessage,  priority, expectMore, validityPeriod);
+            iccSmsIntMgr.sendTextWithOptions(callingPackage, destAddr, scAddr, parts, sentIntent,
+                    deliveryIntent, persistMessage,  priority, expectMore, validityPeriod);
         } else {
             Rlog.e(LOG_TAG,"sendTextWithOptions iccSmsIntMgr is null for" +
                           " Subscription: " + subId);
+            sendErrorInPendingIntent(sentIntent, SmsManager.RESULT_ERROR_GENERIC_FAILURE);
         }
     }
 
-    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,
@@ -211,19 +206,19 @@
         } else {
             Rlog.e(LOG_TAG,"sendMultipartTextWithOptions iccSmsIntMgr is null for" +
                           " Subscription: " + subId);
+            sendErrorInPendingIntents(sentIntents, SmsManager.RESULT_ERROR_GENERIC_FAILURE);
         }
     }
 
     @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 +230,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 +350,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 +369,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 +382,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 +400,40 @@
         return getPhone(subId).getAppSmsManager().createAppSpecificSmsToken(callingPkg, intent);
     }
 
+    @Override
+    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        if (!checkDumpPermission(mContext, LOG_TAG, pw)) {
+            return;
+        }
+
+        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 {
@@ -423,8 +444,20 @@
     }
 
     private void sendErrorInPendingIntents(List<PendingIntent> intents, int errorCode) {
+        if (intents == null) {
+            return;
+        }
+
         for (PendingIntent intent : intents) {
             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/VisualVoicemailSmsFilter.java b/src/java/com/android/internal/telephony/VisualVoicemailSmsFilter.java
index a87cbf1..657d6bc 100644
--- a/src/java/com/android/internal/telephony/VisualVoicemailSmsFilter.java
+++ b/src/java/com/android/internal/telephony/VisualVoicemailSmsFilter.java
@@ -124,6 +124,15 @@
         settings = telephonyManager.getActiveVisualVoicemailSmsFilterSettings(subId);
 
         if (settings == null) {
+            FullMessage fullMessage = getFullMessage(pdus, format);
+            if (fullMessage != null) {
+                // This is special case that voice mail SMS received before the filter has been
+                // set. To drop the SMS unconditionally.
+                if (messageBodyMatchesVvmPattern(context, subId, fullMessage.fullMessageBody)) {
+                    Log.e(TAG, "SMS matching VVM format received but the filter not been set yet");
+                    return true;
+                }
+            }
             return false;
         }
 
@@ -182,8 +191,19 @@
             return true;
         }
 
+        if (messageBodyMatchesVvmPattern(context, subId, messageBody)) {
+            Log.w(TAG,
+                    "SMS matches pattern but has illegal format, still dropping as VVM SMS");
+            sendVvmSmsBroadcast(context, settings, phoneAccountHandle, null, messageBody);
+            return true;
+        }
+        return false;
+    }
+
+    private static boolean messageBodyMatchesVvmPattern(Context context, int subId,
+            String messageBody) {
         buildPatternsMap(context);
-        String mccMnc = telephonyManager.getSimOperator(subId);
+        String mccMnc = context.getSystemService(TelephonyManager.class).getSimOperator(subId);
 
         List<Pattern> patterns = sPatterns.get(mccMnc);
         if (patterns == null || patterns.isEmpty()) {
@@ -192,9 +212,7 @@
 
         for (Pattern pattern : patterns) {
             if (pattern.matcher(messageBody).matches()) {
-                Log.w(TAG, "Incoming SMS matches pattern " + pattern + " but has illegal format, "
-                        + "still dropping as VVM SMS");
-                sendVvmSmsBroadcast(context, settings, phoneAccountHandle, null, messageBody);
+                Log.w(TAG, "Incoming SMS matches pattern " + pattern);
                 return true;
             }
         }
diff --git a/src/java/com/android/internal/telephony/WakeLockStateMachine.java b/src/java/com/android/internal/telephony/WakeLockStateMachine.java
index e2061b9..aca016f 100644
--- a/src/java/com/android/internal/telephony/WakeLockStateMachine.java
+++ b/src/java/com/android/internal/telephony/WakeLockStateMachine.java
@@ -48,8 +48,6 @@
     /** Release wakelock after a short timeout when returning to idle state. */
     static final int EVENT_RELEASE_WAKE_LOCK = 3;
 
-    static final int EVENT_UPDATE_PHONE_OBJECT = 4;
-
     protected Phone mPhone;
 
     protected Context mContext;
@@ -77,10 +75,6 @@
         setInitialState(mIdleState);
     }
 
-    public void updatePhoneObject(Phone phone) {
-        sendMessage(EVENT_UPDATE_PHONE_OBJECT, phone);
-    }
-
     /**
      * Tell the state machine to quit after processing all messages.
      */
@@ -112,11 +106,6 @@
         @Override
         public boolean processMessage(Message msg) {
             switch (msg.what) {
-                case EVENT_UPDATE_PHONE_OBJECT: {
-                    mPhone = (Phone) msg.obj;
-                    log("updatePhoneObject: phone=" + mPhone.getClass().getSimpleName());
-                    break;
-                }
                 default: {
                     String errorText = "processMessage: unhandled message type " + msg.what;
                     if (Build.IS_DEBUGGABLE) {
diff --git a/src/java/com/android/internal/telephony/WapPushOverSms.java b/src/java/com/android/internal/telephony/WapPushOverSms.java
index f4c0103..2e66a6d 100755
--- a/src/java/com/android/internal/telephony/WapPushOverSms.java
+++ b/src/java/com/android/internal/telephony/WapPushOverSms.java
@@ -19,6 +19,7 @@
 import static com.google.android.mms.pdu.PduHeaders.MESSAGE_TYPE_DELIVERY_IND;
 import static com.google.android.mms.pdu.PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND;
 import static com.google.android.mms.pdu.PduHeaders.MESSAGE_TYPE_READ_ORIG_IND;
+
 import android.app.Activity;
 import android.app.AppOpsManager;
 import android.app.BroadcastOptions;
@@ -38,7 +39,6 @@
 import android.os.IBinder;
 import android.os.IDeviceIdleController;
 import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Telephony;
@@ -46,12 +46,11 @@
 import android.telephony.Rlog;
 import android.telephony.SmsManager;
 import android.telephony.SubscriptionManager;
+import android.text.TextUtils;
 import android.util.Log;
 
 import com.android.internal.telephony.uicc.IccUtils;
 
-import java.util.HashMap;
-
 import com.google.android.mms.MmsException;
 import com.google.android.mms.pdu.DeliveryInd;
 import com.google.android.mms.pdu.GenericPdu;
@@ -61,6 +60,8 @@
 import com.google.android.mms.pdu.PduPersister;
 import com.google.android.mms.pdu.ReadOrigInd;
 
+import java.util.HashMap;
+
 /**
  * WAP push handler class.
  *
@@ -132,7 +133,8 @@
 
     public WapPushOverSms(Context context) {
         mContext = context;
-        mDeviceIdleController = TelephonyComponentFactory.getInstance().getIDeviceIdleController();
+        mDeviceIdleController = TelephonyComponentFactory.getInstance()
+                .inject(IDeviceIdleController.class.getName()).getIDeviceIdleController();
 
         UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
 
@@ -199,9 +201,9 @@
                     return result;
                 }
             }
-
             WspTypeDecoder pduDecoder =
-                    TelephonyComponentFactory.getInstance().makeWspTypeDecoder(pdu);
+                    TelephonyComponentFactory.getInstance().inject(WspTypeDecoder.class.getName())
+                            .makeWspTypeDecoder(pdu);
 
             /**
              * Parse HeaderLen(unsigned integer).
@@ -325,6 +327,21 @@
      *         to applications
      */
     public int dispatchWapPdu(byte[] pdu, BroadcastReceiver receiver, InboundSmsHandler handler) {
+        return dispatchWapPdu(pdu, receiver, handler, null);
+    }
+
+    /**
+     * Dispatches inbound messages that are in the WAP PDU format. See
+     * wap-230-wsp-20010705-a section 8 for details on the WAP PDU format.
+     *
+     * @param pdu The WAP PDU, made up of one or more SMS PDUs
+     * @param address The originating address
+     * @return a result code from {@link android.provider.Telephony.Sms.Intents}, or
+     *         {@link Activity#RESULT_OK} if the message has been broadcast
+     *         to applications
+     */
+    public int dispatchWapPdu(byte[] pdu, BroadcastReceiver receiver, InboundSmsHandler handler,
+            String address) {
         DecodedResult result = decodeWapPdu(pdu, handler);
         if (result.statusCode != Activity.RESULT_OK) {
             return result.statusCode;
@@ -360,6 +377,9 @@
                     intent.putExtra("data", result.intentData);
                     intent.putExtra("contentTypeParameters", result.contentTypeParameters);
                     SubscriptionManager.putPhoneIdAndSubIdExtra(intent, result.phoneId);
+                    if (!TextUtils.isEmpty(address)) {
+                        intent.putExtra("address", address);
+                    }
 
                     int procRet = wapPushMan.processMessage(
                         result.wapAppId, result.contentType, intent);
@@ -391,6 +411,9 @@
         intent.putExtra("data", result.intentData);
         intent.putExtra("contentTypeParameters", result.contentTypeParameters);
         SubscriptionManager.putPhoneIdAndSubIdExtra(intent, result.phoneId);
+        if (!TextUtils.isEmpty(address)) {
+            intent.putExtra("address", address);
+        }
 
         // Direct the intent to only the default MMS app. If we can't find a default MMS app
         // then sent it to all broadcast receivers.
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 e5f4b90..1084454 100644
--- a/src/java/com/android/internal/telephony/cat/CatService.java
+++ b/src/java/com/android/internal/telephony/cat/CatService.java
@@ -395,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;
@@ -440,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:
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 e2c178a..fcea2d9 100644
--- a/src/java/com/android/internal/telephony/cdma/CdmaInboundSmsHandler.java
+++ b/src/java/com/android/internal/telephony/cdma/CdmaInboundSmsHandler.java
@@ -193,19 +193,6 @@
     }
 
     /**
-     * Called when the phone changes the default method updates mPhone
-     * mStorageMonitor and mCellBroadcastHandler.updatePhoneObject.
-     * Override if different or other behavior is desired.
-     *
-     * @param phone
-     */
-    @Override
-    protected void onUpdatePhoneObject(Phone phone) {
-        super.onUpdatePhoneObject(phone);
-        mCellBroadcastHandler.updatePhoneObject(phone);
-    }
-
-    /**
      * Convert Android result code to CDMA SMS failure cause.
      * @param rc the Android SMS intent result value
      * @return 0 for success, or a CDMA SMS failure cause value
@@ -299,8 +286,8 @@
         // pass the user data portion of the PDU to the shared handler in SMSDispatcher
         byte[] userData = new byte[pdu.length - index];
         System.arraycopy(pdu, index, userData, 0, pdu.length - index);
-
-        InboundSmsTracker tracker = TelephonyComponentFactory.getInstance().makeInboundSmsTracker(
+        InboundSmsTracker tracker = TelephonyComponentFactory.getInstance()
+                .inject(InboundSmsTracker.class.getName()).makeInboundSmsTracker(
                 userData, timestamp, destinationPort, true, address, dispAddr, referenceNumber,
                 segment, totalSegments, true, HexDump.toHexString(userData));
 
diff --git a/src/java/com/android/internal/telephony/dataconnection/AccessNetworksManager.java b/src/java/com/android/internal/telephony/dataconnection/AccessNetworksManager.java
new file mode 100644
index 0000000..a38cc8b
--- /dev/null
+++ b/src/java/com/android/internal/telephony/dataconnection/AccessNetworksManager.java
@@ -0,0 +1,339 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony.dataconnection;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.PersistableBundle;
+import android.os.RegistrantList;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.telephony.AccessNetworkConstants.AccessNetworkType;
+import android.telephony.CarrierConfigManager;
+import android.telephony.Rlog;
+import android.telephony.data.ApnSetting;
+import android.telephony.data.ApnSetting.ApnType;
+import android.telephony.data.IQualifiedNetworksService;
+import android.telephony.data.IQualifiedNetworksServiceCallback;
+import android.telephony.data.QualifiedNetworksService;
+import android.text.TextUtils;
+import android.util.SparseArray;
+
+import com.android.internal.telephony.Phone;
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.io.FileDescriptor;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Access network manager manages the qualified/available networks for mobile data connection.
+ * It binds to the vendor's qualified networks service and actively monitors the qualified
+ * networks changes.
+ */
+public class AccessNetworksManager {
+    private static final String TAG = AccessNetworksManager.class.getSimpleName();
+    private static final boolean DBG = false;
+
+    private static final int[] SUPPORTED_APN_TYPES = {
+            ApnSetting.TYPE_DEFAULT,
+            ApnSetting.TYPE_MMS,
+            ApnSetting.TYPE_FOTA,
+            ApnSetting.TYPE_IMS,
+            ApnSetting.TYPE_CBS,
+            ApnSetting.TYPE_SUPL,
+            ApnSetting.TYPE_EMERGENCY
+    };
+
+    private final Phone mPhone;
+
+    private final CarrierConfigManager mCarrierConfigManager;
+
+    private IQualifiedNetworksService mIQualifiedNetworksService;
+
+    private AccessNetworksManagerDeathRecipient mDeathRecipient;
+
+    // The bound qualified networks service component name
+    private ComponentName mBoundQualifiedNetworksServiceComponent;
+
+    private QualifiedNetworksServiceConnection mServiceConnection;
+
+    private final SparseArray<int[]> mAvailableNetworks = new SparseArray<>();
+
+    private final RegistrantList mQualifiedNetworksChangedRegistrants = new RegistrantList();
+
+    private final BroadcastReceiver mConfigChangedReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final String action = intent.getAction();
+            if (CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED.equals(action)
+                    && mPhone.getPhoneId() == intent.getIntExtra(
+                    CarrierConfigManager.EXTRA_SLOT_INDEX, 0)) {
+                // When carrier config changes, we need to evaluate and see if we should unbind
+                // the existing service and bind to a new one.
+                if (DBG) log("Carrier config changed.");
+                bindQualifiedNetworksService();
+            }
+        }
+    };
+
+    /**
+     * Represents qualified network types list on a specific APN type.
+     */
+    public static class QualifiedNetworks {
+        public final @ApnType int apnType;
+        public final int[] qualifiedNetworks;
+        public QualifiedNetworks(@ApnType int apnType, int[] qualifiedNetworks) {
+            this.apnType = apnType;
+            this.qualifiedNetworks = qualifiedNetworks;
+        }
+
+        @Override
+        public String toString() {
+            List<String> accessNetworkStrings = new ArrayList<>();
+            for (int network : qualifiedNetworks) {
+                accessNetworkStrings.add(AccessNetworkType.toString(network));
+            }
+            return "[QualifiedNetworks: apnType="
+                    + ApnSetting.getApnTypeString(apnType)
+                    + ", networks="
+                    + TextUtils.join(", ", accessNetworkStrings)
+                    + "]";
+        }
+    }
+
+    private class AccessNetworksManagerDeathRecipient implements IBinder.DeathRecipient {
+        @Override
+        public void binderDied() {
+            // TODO: try to rebind the service.
+            loge("QualifiedNetworksService(" + mBoundQualifiedNetworksServiceComponent + ") died.");
+        }
+    }
+
+    private final class QualifiedNetworksServiceConnection implements ServiceConnection {
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            if (DBG) log("onServiceConnected");
+            mBoundQualifiedNetworksServiceComponent = name;
+            mIQualifiedNetworksService = IQualifiedNetworksService.Stub.asInterface(service);
+            mDeathRecipient = new AccessNetworksManagerDeathRecipient();
+
+            try {
+                service.linkToDeath(mDeathRecipient, 0 /* flags */);
+                mIQualifiedNetworksService.createNetworkAvailabilityUpdater(mPhone.getPhoneId(),
+                        new QualifiedNetworksServiceCallback());
+            } catch (RemoteException e) {
+                mDeathRecipient.binderDied();
+                loge("Remote exception. " + e);
+            }
+        }
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            if (DBG) log("onServiceDisconnected");
+            mIQualifiedNetworksService.asBinder().unlinkToDeath(mDeathRecipient, 0);
+        }
+    }
+
+    private final class QualifiedNetworksServiceCallback extends
+            IQualifiedNetworksServiceCallback.Stub {
+        @Override
+        public void onQualifiedNetworkTypesChanged(int apnTypes, int[] qualifiedNetworkTypes) {
+            log("onQualifiedNetworkTypesChanged. apnTypes = "
+                    + ApnSetting.getApnTypesStringFromBitmask(apnTypes)
+                    + ", networks = " + Arrays.toString(qualifiedNetworkTypes));
+            List<QualifiedNetworks> qualifiedNetworksList = new ArrayList<>();
+            for (int supportedApnType : SUPPORTED_APN_TYPES) {
+                if ((apnTypes & supportedApnType) == supportedApnType) {
+                    // TODO: Verify the preference from data settings manager to make sure the order
+                    // of the networks do not violate users/carrier's preference.
+                    if (mAvailableNetworks.get(supportedApnType) != null) {
+                        if (Arrays.equals(mAvailableNetworks.get(supportedApnType),
+                                qualifiedNetworkTypes)) {
+                            log("Available networks for "
+                                    + ApnSetting.getApnTypesStringFromBitmask(supportedApnType)
+                                    + " not changed.");
+                            continue;
+                        }
+                    }
+                    mAvailableNetworks.put(supportedApnType, qualifiedNetworkTypes);
+                    qualifiedNetworksList.add(new QualifiedNetworks(supportedApnType,
+                            qualifiedNetworkTypes));
+                }
+            }
+
+            if (!qualifiedNetworksList.isEmpty()) {
+                mQualifiedNetworksChangedRegistrants.notifyRegistrants(
+                        new AsyncResult(null, qualifiedNetworksList, null));
+            }
+        }
+    }
+
+    /**
+     * Constructor
+     *
+     * @param phone The phone object
+     */
+    public AccessNetworksManager(Phone phone) {
+        mPhone = phone;
+        mCarrierConfigManager = (CarrierConfigManager) phone.getContext().getSystemService(
+                Context.CARRIER_CONFIG_SERVICE);
+
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
+        phone.getContext().registerReceiverAsUser(mConfigChangedReceiver, UserHandle.ALL,
+                intentFilter, null, null);
+
+        bindQualifiedNetworksService();
+    }
+
+    /**
+     * Find the qualified network service from configuration and binds to it. It reads the
+     * configuration from carrier config if it exists. If not, read it from resources.
+     */
+    private void bindQualifiedNetworksService() {
+        String packageName = getQualifiedNetworksServicePackageName();
+
+        if (DBG) log("Qualified network service package = " + packageName);
+        if (TextUtils.isEmpty(packageName)) {
+            loge("Can't find the binding package");
+            return;
+        }
+
+        if (mIQualifiedNetworksService != null
+                && mIQualifiedNetworksService.asBinder().isBinderAlive()) {
+            if (mBoundQualifiedNetworksServiceComponent.getPackageName().equals(packageName)) {
+                if (DBG) log("Service " + packageName + " already bound.");
+                return;
+            }
+
+            // Remove the network availability updater and then unbind the service.
+            try {
+                mIQualifiedNetworksService.removeNetworkAvailabilityUpdater(mPhone.getPhoneId());
+            } catch (RemoteException e) {
+                loge("Cannot remove network availability updater. " + e);
+            }
+
+            mPhone.getContext().unbindService(mServiceConnection);
+        }
+
+        try {
+            mServiceConnection = new QualifiedNetworksServiceConnection();
+            log("bind to " + packageName);
+            if (!mPhone.getContext().bindService(
+                    new Intent(QualifiedNetworksService.QUALIFIED_NETWORKS_SERVICE_INTERFACE)
+                            .setPackage(packageName),
+                    mServiceConnection,
+                    Context.BIND_AUTO_CREATE)) {
+                loge("Cannot bind to the qualified networks service.");
+            }
+        } catch (Exception e) {
+            loge("Cannot bind to the qualified networks service. Exception: " + e);
+        }
+    }
+
+    /**
+     * Get the qualified network service package.
+     *
+     * @return package name of the qualified networks service package. Return empty string when in
+     * legacy mode (i.e. Dedicated IWLAN data/network service is not supported).
+     */
+    private String getQualifiedNetworksServicePackageName() {
+        // Read package name from the resource
+        String packageName = mPhone.getContext().getResources().getString(
+                com.android.internal.R.string.config_qualified_networks_service_package);
+
+        PersistableBundle b = mCarrierConfigManager.getConfigForSubId(mPhone.getSubId());
+
+        if (b != null) {
+            // If carrier config overrides it, use the one from carrier config
+            String carrierConfigPackageName =  b.getString(CarrierConfigManager
+                    .KEY_CARRIER_QUALIFIED_NETWORKS_SERVICE_PACKAGE_OVERRIDE_STRING);
+            if (!TextUtils.isEmpty(carrierConfigPackageName)) {
+                if (DBG) log("Found carrier config override " + carrierConfigPackageName);
+                packageName = carrierConfigPackageName;
+            }
+        }
+
+        return packageName;
+    }
+
+    /**
+     * Register for qualified networks changed event.
+     *
+     * @param h The target to post the event message to.
+     * @param what The event.
+     */
+    public void registerForQualifiedNetworksChanged(Handler h, int what) {
+        if (h != null) {
+            mQualifiedNetworksChangedRegistrants.addUnique(h, what, null);
+        }
+    }
+
+    /**
+     * Unregister for qualified networks changed event.
+     *
+     * @param h The handler
+     */
+    public void unregisterForQualifiedNetworksChanged(Handler h) {
+        if (h != null) {
+            mQualifiedNetworksChangedRegistrants.remove(h);
+        }
+    }
+
+    /**
+     * Dump the state of transport manager
+     *
+     * @param fd File descriptor
+     * @param pw Print writer
+     * @param args Arguments
+     */
+    public void dump(FileDescriptor fd, IndentingPrintWriter pw, String[] args) {
+        pw.println("AccessNetworksManager:");
+        pw.increaseIndent();
+        pw.println("Available networks:");
+        pw.increaseIndent();
+
+        for (int i = 0; i < mAvailableNetworks.size(); i++) {
+            pw.print("APN type "
+                    + ApnSetting.getApnTypeString(mAvailableNetworks.keyAt(i)) + ": ");
+            List<String> networksStrings = new ArrayList<>();
+            for (int network : mAvailableNetworks.valueAt(i)) {
+                networksStrings.add(AccessNetworkType.toString(network));
+            }
+            pw.println("[" + TextUtils.join(",", networksStrings) + "]");
+        }
+        pw.decreaseIndent();
+        pw.decreaseIndent();
+    }
+
+    private void log(String s) {
+        Rlog.d(TAG, s);
+    }
+
+    private void loge(String s) {
+        Rlog.e(TAG, s);
+    }
+
+}
diff --git a/src/java/com/android/internal/telephony/dataconnection/ApnContext.java b/src/java/com/android/internal/telephony/dataconnection/ApnContext.java
index 1b42d4a..fa13dac 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;
 
@@ -60,7 +61,7 @@
 
     private ApnSetting mApnSetting;
 
-    DcAsyncChannel mDcAc;
+    private DataConnection mDataConnection;
 
     String mReason;
 
@@ -129,23 +130,32 @@
     }
 
     /**
-     * Get the data call async channel.
-     * @return The data call async channel
+     * Gets the APN type bitmask.
+     * @return The APN type bitmask
      */
-    public synchronized DcAsyncChannel getDcAc() {
-        return mDcAc;
+    public int getApnTypeBitmask() {
+        return ApnSetting.getApnTypesBitmaskFromString(mApnType);
     }
 
     /**
-     * Set the data call async channel.
-     * @param dcac The data call async channel
+     * Get the associated data connection
+     * @return The data connection
      */
-    public synchronized void setDataConnectionAc(DcAsyncChannel dcac) {
+    public synchronized DataConnection getDataConnection() {
+        return mDataConnection;
+    }
+
+
+    /**
+     * Set the associated data connection.
+     * @param dc data connection
+     */
+    public synchronized void setDataConnection(DataConnection dc) {
         if (DBG) {
-            log("setDataConnectionAc: old dcac=" + mDcAc + " new dcac=" + dcac
+            log("setDataConnectionAc: old dc=" + mDataConnection + ",new dc=" + dc
                     + " this=" + this);
         }
-        mDcAc = dcac;
+        mDataConnection = dc;
     }
 
     /**
@@ -153,9 +163,9 @@
      * @param reason The reason of releasing data connection
      */
     public synchronized void releaseDataConnection(String reason) {
-        if (mDcAc != null) {
-            mDcAc.tearDown(this, reason, null);
-            mDcAc = null;
+        if (mDataConnection != null) {
+            mDataConnection.tearDown(this, reason, null);
+            mDataConnection = null;
         }
         setState(DctConstants.State.IDLE);
     }
@@ -392,8 +402,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 +428,7 @@
             } else {
                 mLocalLogs.add(log);
                 mNetworkRequests.add(networkRequest);
-                mDcTracker.setEnabled(apnIdForApnName(mApnType), true);
+                mDcTracker.setEnabled(ApnSetting.getApnTypesBitmaskFromString(mApnType), true);
             }
         }
     }
@@ -438,7 +448,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);
                 }
             }
         }
@@ -450,7 +460,11 @@
         }
     }
 
-    public boolean hasNoRestrictedRequests(boolean excludeDun) {
+    /**
+     * @param excludeDun True if excluding requests that have DUN capability
+     * @return True if the attached network requests contain restricted capability.
+     */
+    public boolean hasRestrictedRequests(boolean excludeDun) {
         synchronized (mRefCountLock) {
             for (NetworkRequest nr : mNetworkRequests) {
                 if (excludeDun &&
@@ -458,13 +472,13 @@
                         NetworkCapabilities.NET_CAPABILITY_DUN)) {
                     continue;
                 }
-                if (nr.networkCapabilities.hasCapability(
-                        NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED) == false) {
-                    return false;
+                if (!nr.networkCapabilities.hasCapability(
+                        NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)) {
+                    return true;
                 }
             }
         }
-        return true;
+        return false;
     }
 
     private final SparseIntArray mRetriesLeftPerErrorCode = new SparseIntArray();
@@ -540,88 +554,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 +634,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 6f1c8a6..0000000
--- a/src/java/com/android/internal/telephony/dataconnection/ApnSetting.java
+++ /dev/null
@@ -1,832 +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.provider.Telephony.Carriers;
-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 V5_FORMAT_REGEX = "^\\[ApnSettingV5\\]\\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;
-
-    /**
-     * The APN set id.
-     *
-     * APNs that are part of the same set should be preferred together, e.g. if the
-     * user selects a default APN with apnSetId=1, then we will prefer all APNs with apnSetId=1.
-     *
-     * If the apnSetId=Carriers.NO_SET_SET (=0) then the APN is not part of a set.
-     */
-    public final int apnSetId;
-
-    /**
-     * 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.apnSetId = Carriers.NO_SET_SET;
-        this.networkTypeBitmask = ServiceState.convertBearerBitmaskToNetworkTypeBitmask(
-                this.bearerBitmask);
-    }
-
-    // Constructor with default apn set id
-    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, numeric, carrier, apn, proxy, port, mmsc, mmsProxy, mmsPort, user, password,
-                authType, types, protocol, roamingProtocol, carrierEnabled, networkTypeBitmask,
-                profileId, modemCognitive, maxConns, waitTime, maxConnsTime, mtu, mvnoType,
-                mvnoMatchData, Carriers.NO_SET_SET);
-    }
-
-    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, int apnSetId) {
-        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;
-        this.apnSetId = apnSetId;
-    }
-
-    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, apn.apnSetId);
-    }
-
-    /**
-     * 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>
-     *
-     * v5 format:
-     *   [ApnSettingV5] <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>, <apnSetId>
-     *
-     * 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(V5_FORMAT_REGEX + ".*")) {
-            version = 5;
-            data = data.replaceFirst(V5_FORMAT_REGEX, "");
-        } else 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 = "";
-        int apnSetId = Carriers.NO_SET_SET;
-        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 (a.length > 27) {
-                apnSetId = Integer.parseInt(a[27]);
-            }
-        }
-
-        // 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, apnSetId);
-    }
-
-    /**
-     * 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("[ApnSettingV5] ")
-        .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);
-        sb.append(", ").append(apnSetId);
-        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
-                && apnSetId == other.apnSetId;
-    }
-
-    /**
-     * 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)
-                && apnSetId == other.apnSetId;
-    }
-
-    /**
-     * 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
-                && this.apnSetId == other.apnSetId;
-    }
-
-    // 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..9b24084
--- /dev/null
+++ b/src/java/com/android/internal/telephony/dataconnection/ApnSettingUtils.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony.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.text.TextUtils;
+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 || TextUtils.isEmpty(type)) {
+            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 apn APN setting
+     * @param phone The phone object
+     * @return True if this APN setting is metered, otherwise false.
+     */
+    public static boolean isMetered(ApnSetting apn, Phone phone) {
+        if (phone == null || apn == null) {
+            return true;
+        }
+
+        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 0696aa9..e683c47 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DataConnection.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DataConnection.java
@@ -39,10 +39,11 @@
 import android.os.Message;
 import android.os.SystemClock;
 import android.os.SystemProperties;
-import android.telephony.AccessNetworkConstants;
+import android.telephony.AccessNetworkConstants.TransportType;
 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;
@@ -77,9 +78,12 @@
 import java.io.StringWriter;
 import java.net.InetAddress;
 import java.net.UnknownHostException;
+import java.util.ArrayList;
 import java.util.Collection;
-import java.util.HashMap;
+import java.util.List;
 import java.util.Locale;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicInteger;
 
 /**
@@ -112,7 +116,7 @@
     // The DCT that's talking to us, we only support one!
     private DcTracker mDct = null;
 
-    protected String[] mPcscfAddr;
+    private String[] mPcscfAddr;
 
     /**
      * Used internally for saving connecting parameters.
@@ -122,17 +126,14 @@
         ApnContext mApnContext;
         int mProfileId;
         int mRilRat;
-        final boolean mUnmeteredUseOnly;
         Message mOnCompletedMsg;
         final int mConnectionGeneration;
 
         ConnectionParams(ApnContext apnContext, int profileId, int rilRadioTechnology,
-                         boolean unmeteredUseOnly,  Message onCompletedMsg,
-                         int connectionGeneration) {
+                         Message onCompletedMsg, int connectionGeneration) {
             mApnContext = apnContext;
             mProfileId = profileId;
             mRilRat = rilRadioTechnology;
-            mUnmeteredUseOnly = unmeteredUseOnly;
             mOnCompletedMsg = onCompletedMsg;
             mConnectionGeneration = connectionGeneration;
         }
@@ -142,7 +143,6 @@
             return "{mTag=" + mTag + " mApnContext=" + mApnContext
                     + " mProfileId=" + mProfileId
                     + " mRat=" + mRilRat
-                    + " mUnmeteredUseOnly=" + mUnmeteredUseOnly
                     + " mOnCompletedMsg=" + msgToString(mOnCompletedMsg) + "}";
         }
     }
@@ -192,7 +192,7 @@
 
     int mTag;
     public int mCid;
-    public HashMap<ApnContext, ConnectionParams> mApnContexts = null;
+    private final Map<ApnContext, ConnectionParams> mApnContexts = new ConcurrentHashMap<>();
     PendingIntent mReconnectIntent = null;
 
 
@@ -220,9 +220,10 @@
     static final int EVENT_KEEPALIVE_START_REQUEST = BASE + 21;
     static final int EVENT_KEEPALIVE_STOP_REQUEST = BASE + 22;
     static final int EVENT_LINK_CAPACITY_CHANGED = BASE + 23;
+    static final int EVENT_RESET = BASE + 24;
+    static final int EVENT_REEVALUATE_RESTRICTED_STATE = BASE + 25;
 
-    private static final int CMD_TO_STRING_COUNT =
-            EVENT_LINK_CAPACITY_CHANGED - BASE + 1;
+    private static final int CMD_TO_STRING_COUNT = EVENT_REEVALUATE_RESTRICTED_STATE - BASE + 1;
 
     private static String[] sCmdToString = new String[CMD_TO_STRING_COUNT];
     static {
@@ -253,15 +254,16 @@
         sCmdToString[EVENT_KEEPALIVE_START_REQUEST - BASE] = "EVENT_KEEPALIVE_START_REQUEST";
         sCmdToString[EVENT_KEEPALIVE_STOP_REQUEST - BASE] = "EVENT_KEEPALIVE_STOP_REQUEST";
         sCmdToString[EVENT_LINK_CAPACITY_CHANGED - BASE] = "EVENT_LINK_CAPACITY_CHANGED";
+        sCmdToString[EVENT_RESET - BASE] = "EVENT_RESET";
+        sCmdToString[EVENT_REEVALUATE_RESTRICTED_STATE - BASE] =
+                "EVENT_REEVALUATE_RESTRICTED_STATE";
     }
     // Convert cmd to string or null if unknown
     static String cmdToString(int cmd) {
-        String value;
+        String value = null;
         cmd -= BASE;
         if ((cmd >= 0) && (cmd < sCmdToString.length)) {
             value = sCmdToString[cmd];
-        } else {
-            value = DcAsyncChannel.cmdToString(cmd + BASE);
         }
         if (value == null) {
             value = "0x" + Integer.toHexString(cmd + BASE);
@@ -295,7 +297,7 @@
 
     /* Getter functions */
 
-    LinkProperties getCopyLinkProperties() {
+    LinkProperties getLinkProperties() {
         return new LinkProperties(mLinkProperties);
     }
 
@@ -444,9 +446,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;
         }
 
@@ -461,8 +463,7 @@
     //***** Constructor (NOTE: uses dcc.getHandler() as its Handler)
     private DataConnection(Phone phone, String name, int id,
                            DcTracker dct, DataServiceManager dataServiceManager,
-                           DcTesterFailBringUpAll failBringUpAll,
-                DcController dcc) {
+                           DcTesterFailBringUpAll failBringUpAll, DcController dcc) {
         super(name, dcc.getHandler());
         setLogRecSize(300);
         setLogOnlyTransitions(true);
@@ -491,8 +492,6 @@
             addState(mDisconnectingState, mDefaultState);
             addState(mDisconnectingErrorCreatingConnection, mDefaultState);
         setInitialState(mInactiveState);
-
-        mApnContexts = new HashMap<>();
     }
 
     /**
@@ -503,9 +502,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.
@@ -533,7 +535,8 @@
         Message msg = obtainMessage(EVENT_SETUP_DATA_CONNECTION_DONE, cp);
         msg.obj = cp;
 
-        DataProfile dp = DcTracker.createDataProfile(mApnSetting, cp.mProfileId);
+        DataProfile dp = DcTracker.createDataProfile(mApnSetting, cp.mProfileId,
+                mApnSetting.equals(mDct.getPreferredApn()));
 
         // We need to use the actual modem roaming state instead of the framework roaming state
         // here. This flag is only passed down to ril_service for picking the correct protocol (for
@@ -732,6 +735,8 @@
         mLinkProperties = new LinkProperties();
         mApnContexts.clear();
         mApnSetting = null;
+        mUnmeteredUseOnly = false;
+        mRestrictedNetworkOverride = false;
         mDcFailCause = null;
     }
 
@@ -784,12 +789,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;
             }
         }
@@ -883,13 +888,18 @@
     }
 
     /**
+     * Indicates if this data connection was established for unmetered use only. Note that this
+     * flag should be populated when data becomes active. And if it is set to true, it can be set to
+     * false later when we are reevaluating the data connection. But if it is set to false, it
+     * can never become true later because setting it to true will cause this data connection
+     * losing some immutable network capabilities, which can cause issues in connectivity service.
+     */
+    private boolean mUnmeteredUseOnly = false;
+
+    /**
      * Indicates if when this connection was established we had a restricted/privileged
      * NetworkRequest and needed it to overcome data-enabled limitations.
      *
-     * This gets set once per connection setup and is based on conditions at that time.
-     * We could theoretically have dynamic capabilities but now is not a good time to
-     * experiment with that.
-     *
      * This flag overrides the APN-based restriction capability, restricting the network
      * based on both having a NetworkRequest with restricted AND needing a restricted
      * bit to overcome user-disabled status.  This allows us to handle the common case
@@ -897,38 +907,78 @@
      * if conditions require a restricted network to overcome user-disabled then it must
      * be restricted, otherwise it is unrestricted (or restricted based on APN type).
      *
-     * Because we're not supporting dynamic capabilities, if conditions change and we go from
-     * data-enabled to not or vice-versa we will need to tear down networks to deal with it
-     * at connection setup time with the new state.
-     *
      * This supports a privileged app bringing up a network without general apps having access
      * to it when the network is otherwise unavailable (hipri).  The first use case is
      * pre-paid SIM reprovisioning over internet, where the carrier insists on no traffic
      * other than from the privileged carrier-app.
+     *
+     * Note that the data connection cannot go from unrestricted to restricted because the
+     * connectivity service does not support dynamically closing TCP connections at this point.
      */
     private boolean mRestrictedNetworkOverride = false;
 
-    // Should be called once when the call goes active to examine the state of things and
-    // declare the restriction override for the life of the connection
-    private void setNetworkRestriction() {
-        mRestrictedNetworkOverride = false;
-        // first, if we have no restricted requests, this override can stay FALSE:
-        boolean noRestrictedRequests = true;
+    /**
+     * Check if this data connection should be restricted. We should call this when data connection
+     * becomes active, or when we want to re-evaluate the conditions to decide if we need to
+     * unstrict the data connection.
+     *
+     * @return True if this data connection needs to be restricted.
+     */
+
+    private boolean shouldRestrictNetwork() {
+        // first, check if there is any network request that containing restricted capability
+        // (i.e. Do not have NET_CAPABILITY_NOT_RESTRICTED in the request)
+        boolean isAnyRestrictedRequest = false;
         for (ApnContext apnContext : mApnContexts.keySet()) {
-            noRestrictedRequests &= apnContext.hasNoRestrictedRequests(true /* exclude DUN */);
-        }
-        if (noRestrictedRequests) {
-            return;
+            if (apnContext.hasRestrictedRequests(true /* exclude DUN */)) {
+                isAnyRestrictedRequest = true;
+                break;
+            }
         }
 
-        // 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)) {
-            return;
+        // If all of the network requests are non-restricted, then we don't need to restrict
+        // the network.
+        if (!isAnyRestrictedRequest) {
+            return false;
         }
 
-        // Is data disabled?
-        mRestrictedNetworkOverride = !mDct.isDataEnabled();
+        // If the network is unmetered, then we don't need to restrict the network because users
+        // won't be charged anyway.
+        if (!ApnSettingUtils.isMetered(mApnSetting, mPhone)) {
+            return false;
+        }
+
+        // If the data is disabled, then we need to restrict the network so only privileged apps can
+        // use the restricted network while data is disabled.
+        if (!mDct.isDataEnabled()) {
+            return true;
+        }
+
+        // If the device is roaming, and the user does not turn on data roaming, then we need to
+        // restrict the network so only privileged apps can use it.
+        if (!mDct.getDataRoamingEnabled() && mPhone.getServiceState().getDataRoaming()) {
+            return true;
+        }
+
+        // Otherwise we should not restrict the network so anyone who requests can use it.
+        return false;
+    }
+
+    /**
+     * @return True if this data connection should only be used for unmetered purposes.
+     */
+    private boolean isUnmeteredUseOnly() {
+        // The data connection can only be unmetered used only if all requests' reasons are
+        // unmetered.
+        for (ApnContext apnContext : mApnContexts.keySet()) {
+            DataConnectionReasons dataConnectionReasons = new DataConnectionReasons();
+            boolean isDataAllowed = mDct.isDataAllowed(apnContext, dataConnectionReasons);
+            if (!isDataAllowed || !dataConnectionReasons.contains(
+                    DataConnectionReasons.DataAllowedReasonType.UNMETERED_APN)) {
+                return false;
+            }
+        }
+        return true;
     }
 
     NetworkCapabilities getNetworkCapabilities() {
@@ -936,10 +986,11 @@
         result.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
 
         if (mApnSetting != null) {
-            for (String type : mApnSetting.types) {
-                if (!mRestrictedNetworkOverride
-                        && (mConnectionParams != null && mConnectionParams.mUnmeteredUseOnly)
-                        && ApnSetting.isMeteredApnType(type, mPhone)) {
+            final String[] types = ApnSetting.getApnTypesStringFromBitmask(
+                mApnSetting.getApnTypeBitmask()).split(",");
+            for (String type : types) {
+                if (!mRestrictedNetworkOverride && mUnmeteredUseOnly
+                        && ApnSettingUtils.isMeteredApnType(type, mPhone)) {
                     log("Dropped the metered " + type + " for the unmetered data call.");
                     continue;
                 }
@@ -998,9 +1049,8 @@
             // Mark NOT_METERED in the following cases,
             // 1. All APNs in APN settings are unmetered.
             // 2. The non-restricted data and is intended for unmetered use only.
-            if (((mConnectionParams != null && mConnectionParams.mUnmeteredUseOnly)
-                    && !mRestrictedNetworkOverride)
-                    || !mApnSetting.isMetered(mPhone)) {
+            if ((mUnmeteredUseOnly && !mRestrictedNetworkOverride)
+                    || !ApnSettingUtils.isMetered(mApnSetting, mPhone)) {
                 result.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
             } else {
                 result.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
@@ -1008,6 +1058,7 @@
 
             result.maybeMarkCapabilitiesRestricted();
         }
+
         if (mRestrictedNetworkOverride) {
             result.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
             // don't use dun on restriction-overriden networks.
@@ -1171,7 +1222,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);
@@ -1232,7 +1283,7 @@
                 mAc.disconnected();
                 mAc = null;
             }
-            mApnContexts = null;
+            mApnContexts.clear();
             mReconnectIntent = null;
             mDct = null;
             mApnSetting = null;
@@ -1254,70 +1305,7 @@
                         + " RefCount=" + mApnContexts.size());
             }
             switch (msg.what) {
-                case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: {
-                    if (mAc != null) {
-                        if (VDBG) log("Disconnecting to previous connection mAc=" + mAc);
-                        mAc.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED,
-                                AsyncChannel.STATUS_FULL_CONNECTION_REFUSED_ALREADY_CONNECTED);
-                    } else {
-                        mAc = new AsyncChannel();
-                        mAc.connected(null, getHandler(), msg.replyTo);
-                        if (VDBG) log("DcDefaultState: FULL_CONNECTION reply connected");
-                        mAc.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED,
-                                AsyncChannel.STATUS_SUCCESSFUL, mId, "hi");
-                    }
-                    break;
-                }
-                case AsyncChannel.CMD_CHANNEL_DISCONNECTED: {
-                    if (DBG) {
-                        log("DcDefault: CMD_CHANNEL_DISCONNECTED before quiting call dump");
-                        dumpToLog();
-                    }
-
-                    quit();
-                    break;
-                }
-                case DcAsyncChannel.REQ_IS_INACTIVE: {
-                    boolean val = isInactive();
-                    if (VDBG) log("REQ_IS_INACTIVE  isInactive=" + val);
-                    mAc.replyToMessage(msg, DcAsyncChannel.RSP_IS_INACTIVE, val ? 1 : 0);
-                    break;
-                }
-                case DcAsyncChannel.REQ_GET_CID: {
-                    int cid = getCid();
-                    if (VDBG) log("REQ_GET_CID  cid=" + cid);
-                    mAc.replyToMessage(msg, DcAsyncChannel.RSP_GET_CID, cid);
-                    break;
-                }
-                case DcAsyncChannel.REQ_GET_APNSETTING: {
-                    ApnSetting apnSetting = getApnSetting();
-                    if (VDBG) log("REQ_GET_APNSETTING  mApnSetting=" + apnSetting);
-                    mAc.replyToMessage(msg, DcAsyncChannel.RSP_GET_APNSETTING, apnSetting);
-                    break;
-                }
-                case DcAsyncChannel.REQ_GET_LINK_PROPERTIES: {
-                    LinkProperties lp = getCopyLinkProperties();
-                    if (VDBG) log("REQ_GET_LINK_PROPERTIES linkProperties" + lp);
-                    mAc.replyToMessage(msg, DcAsyncChannel.RSP_GET_LINK_PROPERTIES, lp);
-                    break;
-                }
-                case DcAsyncChannel.REQ_SET_LINK_PROPERTIES_HTTP_PROXY: {
-                    ProxyInfo proxy = (ProxyInfo) msg.obj;
-                    if (VDBG) log("REQ_SET_LINK_PROPERTIES_HTTP_PROXY proxy=" + proxy);
-                    setLinkPropertiesHttpProxy(proxy);
-                    mAc.replyToMessage(msg, DcAsyncChannel.RSP_SET_LINK_PROPERTIES_HTTP_PROXY);
-                    if (mNetworkAgent != null) {
-                        mNetworkAgent.sendLinkProperties(mLinkProperties);
-                    }
-                    break;
-                }
-                case DcAsyncChannel.REQ_GET_NETWORK_CAPABILITIES: {
-                    NetworkCapabilities nc = getNetworkCapabilities();
-                    if (VDBG) log("REQ_GET_NETWORK_CAPABILITIES networkCapabilities" + nc);
-                    mAc.replyToMessage(msg, DcAsyncChannel.RSP_GET_NETWORK_CAPABILITIES, nc);
-                    break;
-                }
-                case DcAsyncChannel.REQ_RESET:
+                case EVENT_RESET:
                     if (VDBG) log("DcDefaultState: msg.what=REQ_RESET");
                     transitionTo(mInactiveState);
                     break;
@@ -1328,17 +1316,11 @@
                     break;
 
                 case EVENT_DISCONNECT:
-                    if (DBG) {
-                        log("DcDefaultState deferring msg.what=EVENT_DISCONNECT RefCount="
-                                + mApnContexts.size());
-                    }
-                    deferMessage(msg);
-                    break;
-
                 case EVENT_DISCONNECT_ALL:
+                case EVENT_REEVALUATE_RESTRICTED_STATE:
                     if (DBG) {
-                        log("DcDefaultState deferring msg.what=EVENT_DISCONNECT_ALL RefCount="
-                                + mApnContexts.size());
+                        log("DcDefaultState deferring msg.what=" + getWhatToString(msg.what)
+                                + " RefCount=" + mApnContexts.size());
                     }
                     deferMessage(msg);
                     break;
@@ -1473,9 +1455,9 @@
             StatsLog.write(StatsLog.MOBILE_CONNECTION_STATE_CHANGED,
                     StatsLog.MOBILE_CONNECTION_STATE_CHANGED__STATE__INACTIVE,
                     mPhone.getPhoneId(), mId,
-                    mApnSetting != null ? (long) mApnSetting.typesBitmap : 0L,
+                    mApnSetting != null ? (long) mApnSetting.getApnTypeBitmask() : 0L,
                     mApnSetting != null
-                        ? mApnSetting.canHandleType(PhoneConstants.APN_TYPE_DEFAULT) : false);
+                        ? mApnSetting.canHandleType(ApnSetting.TYPE_DEFAULT) : false);
 
             if (mConnectionParams != null) {
                 if (DBG) {
@@ -1514,9 +1496,11 @@
             boolean retVal;
 
             switch (msg.what) {
-                case DcAsyncChannel.REQ_RESET:
+                case EVENT_RESET:
+                case EVENT_REEVALUATE_RESTRICTED_STATE:
                     if (DBG) {
-                        log("DcInactiveState: msg.what=RSP_RESET, ignore we're already reset");
+                        log("DcInactiveState: msg.what=" + getWhatToString(msg.what)
+                                + ", ignore we're already done");
                     }
                     retVal = HANDLED;
                     break;
@@ -1570,9 +1554,9 @@
             StatsLog.write(StatsLog.MOBILE_CONNECTION_STATE_CHANGED,
                     StatsLog.MOBILE_CONNECTION_STATE_CHANGED__STATE__ACTIVATING,
                     mPhone.getPhoneId(), mId,
-                    mApnSetting != null ? (long) mApnSetting.typesBitmap : 0L,
+                    mApnSetting != null ? (long) mApnSetting.getApnTypeBitmask() : 0L,
                     mApnSetting != null
-                        ? mApnSetting.canHandleType(PhoneConstants.APN_TYPE_DEFAULT) : false);
+                        ? mApnSetting.canHandleType(ApnSetting.TYPE_DEFAULT) : false);
         }
         @Override
         public boolean processMessage(Message msg) {
@@ -1639,8 +1623,8 @@
                             String str = "DcActivatingState: ERROR_DATA_SERVICE_SPECIFIC_ERROR "
                                     + " delay=" + delay
                                     + " result=" + result
-                                    + " result.isRestartRadioFail=" +
-                                    result.mFailCause.isRestartRadioFail(mPhone.getContext(),
+                                    + " result.isRadioRestartFailure="
+                                    + result.mFailCause.isRadioRestartFailure(mPhone.getContext(),
                                             mPhone.getSubId())
                                     + " isPermanentFailure=" +
                                     mDct.isPermanentFailure(result.mFailCause);
@@ -1685,9 +1669,9 @@
             StatsLog.write(StatsLog.MOBILE_CONNECTION_STATE_CHANGED,
                     StatsLog.MOBILE_CONNECTION_STATE_CHANGED__STATE__ACTIVE,
                     mPhone.getPhoneId(), mId,
-                    mApnSetting != null ? (long) mApnSetting.typesBitmap : 0L,
+                    mApnSetting != null ? (long) mApnSetting.getApnTypeBitmask() : 0L,
                     mApnSetting != null
-                        ? mApnSetting.canHandleType(PhoneConstants.APN_TYPE_DEFAULT) : false);
+                        ? mApnSetting.canHandleType(ApnSetting.TYPE_DEFAULT) : false);
 
             updateNetworkInfo();
 
@@ -1705,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();
@@ -1717,15 +1701,22 @@
             }
             misc.subscriberId = mPhone.getSubscriberId();
 
-            setNetworkRestriction();
-            if (DBG) log("mRestrictedNetworkOverride = " + mRestrictedNetworkOverride);
+            mRestrictedNetworkOverride = shouldRestrictNetwork();
+            mUnmeteredUseOnly = isUnmeteredUseOnly();
+
+            if (DBG) {
+                log("mRestrictedNetworkOverride = " + mRestrictedNetworkOverride
+                        + ", mUnmeteredUseOnly = " + mUnmeteredUseOnly);
+            }
             mNetworkAgent = new DcNetworkAgent(getHandler().getLooper(), mPhone.getContext(),
                     "DcNetworkAgent", mNetworkInfo, getNetworkCapabilities(), mLinkProperties,
                     50, misc);
-            mPhone.mCi.registerForNattKeepaliveStatus(
-                    getHandler(), DataConnection.EVENT_KEEPALIVE_STATUS, null);
-            mPhone.mCi.registerForLceInfo(
-                    getHandler(), DataConnection.EVENT_LINK_CAPACITY_CHANGED, null);
+            if (mDataServiceManager.getTransportType() == TransportType.WWAN) {
+                mPhone.mCi.registerForNattKeepaliveStatus(
+                        getHandler(), DataConnection.EVENT_KEEPALIVE_STATUS, null);
+                mPhone.mCi.registerForLceInfo(
+                        getHandler(), DataConnection.EVENT_LINK_CAPACITY_CHANGED, null);
+            }
         }
 
         @Override
@@ -1744,8 +1735,11 @@
 
             mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.DISCONNECTED,
                     reason, mNetworkInfo.getExtraInfo());
-            mPhone.mCi.unregisterForNattKeepaliveStatus(getHandler());
-            mPhone.mCi.unregisterForLceInfo(getHandler());
+
+            if (mDataServiceManager.getTransportType() == TransportType.WWAN) {
+                mPhone.mCi.unregisterForNattKeepaliveStatus(getHandler());
+                mPhone.mCi.unregisterForLceInfo(getHandler());
+            }
             if (mNetworkAgent != null) {
                 mNetworkAgent.sendNetworkInfo(mNetworkInfo);
                 mNetworkAgent = null;
@@ -1867,8 +1861,7 @@
                     KeepalivePacketData pkt = (KeepalivePacketData) msg.obj;
                     int slotId = msg.arg1;
                     int intervalMillis = msg.arg2 * 1000;
-                    if (mDataServiceManager.getTransportType()
-                            == AccessNetworkConstants.TransportType.WWAN) {
+                    if (mDataServiceManager.getTransportType() == TransportType.WWAN) {
                         mPhone.mCi.startNattKeepalive(
                                 DataConnection.this.mCid, pkt, intervalMillis,
                                 DataConnection.this.obtainMessage(
@@ -1978,6 +1971,37 @@
                     retVal = HANDLED;
                     break;
                 }
+                case EVENT_REEVALUATE_RESTRICTED_STATE: {
+                    // If the network was restricted, and now it does not need to be restricted
+                    // anymore, we should add the NET_CAPABILITY_NOT_RESTRICTED capability.
+                    if (mRestrictedNetworkOverride && !shouldRestrictNetwork()) {
+                        if (DBG) {
+                            log("Data connection becomes not-restricted. dc=" + this);
+                        }
+                        // Note we only do this when network becomes non-restricted. When a
+                        // non-restricted becomes restricted (e.g. users disable data, or turn off
+                        // data roaming), DCT will explicitly tear down the networks (because
+                        // connectivity service does not support force-close TCP connections today).
+                        // Also note that NET_CAPABILITY_NOT_RESTRICTED is an immutable capability
+                        // (see {@link NetworkCapabilities}) once we add it to the network, we can't
+                        // remove it through the entire life cycle of the connection.
+                        mRestrictedNetworkOverride = false;
+                        mNetworkAgent.sendNetworkCapabilities(getNetworkCapabilities());
+                    }
+
+                    // If the data does need to be unmetered use only (e.g. users turn on data, or
+                    // device is not roaming anymore assuming data roaming is off), then we can
+                    // dynamically add those metered APN type capabilities back. (But not the
+                    // other way around because most of the APN-type capabilities are immutable
+                    // capabilities.)
+                    if (mUnmeteredUseOnly && !isUnmeteredUseOnly()) {
+                        mUnmeteredUseOnly = false;
+                        mNetworkAgent.sendNetworkCapabilities(getNetworkCapabilities());
+                    }
+
+                    retVal = HANDLED;
+                    break;
+                }
                 default:
                     if (VDBG) {
                         log("DcActiveState not handled msg.what=" + getWhatToString(msg.what));
@@ -1999,9 +2023,9 @@
             StatsLog.write(StatsLog.MOBILE_CONNECTION_STATE_CHANGED,
                     StatsLog.MOBILE_CONNECTION_STATE_CHANGED__STATE__DISCONNECTING,
                     mPhone.getPhoneId(), mId,
-                    mApnSetting != null ? (long) mApnSetting.typesBitmap : 0L,
+                    mApnSetting != null ? (long) mApnSetting.getApnTypeBitmask() : 0L,
                     mApnSetting != null
-                        ? mApnSetting.canHandleType(PhoneConstants.APN_TYPE_DEFAULT) : false);
+                        ? mApnSetting.canHandleType(ApnSetting.TYPE_DEFAULT) : false);
         }
         @Override
         public boolean processMessage(Message msg) {
@@ -2057,9 +2081,9 @@
             StatsLog.write(StatsLog.MOBILE_CONNECTION_STATE_CHANGED,
                     StatsLog.MOBILE_CONNECTION_STATE_CHANGED__STATE__DISCONNECTION_ERROR_CREATING_CONNECTION,
                     mPhone.getPhoneId(), mId,
-                    mApnSetting != null ? (long) mApnSetting.typesBitmap : 0L,
+                    mApnSetting != null ? (long) mApnSetting.getApnTypeBitmask() : 0L,
                     mApnSetting != null
-                        ? mApnSetting.canHandleType(PhoneConstants.APN_TYPE_DEFAULT) : false);
+                        ? mApnSetting.canHandleType(ApnSetting.TYPE_DEFAULT) : false);
         }
         @Override
         public boolean processMessage(Message msg) {
@@ -2139,8 +2163,10 @@
 
         @Override
         protected void pollLceData() {
-            if(mPhone.getLceStatus() == RILConstants.LCE_ACTIVE) {  // active LCE service
-                mPhone.mCi.pullLceData(DataConnection.this.obtainMessage(EVENT_BW_REFRESH_RESPONSE));
+            if (mPhone.getLceStatus() == RILConstants.LCE_ACTIVE     // active LCE service
+                    && mDataServiceManager.getTransportType() == TransportType.WWAN) {
+                mPhone.mCi.pullLceData(
+                        DataConnection.this.obtainMessage(EVENT_BW_REFRESH_RESPONSE));
             }
         }
 
@@ -2303,17 +2329,100 @@
         };
     }
 
+    /**
+     * Bring up a connection to the apn and return an AsyncResult in onCompletedMsg.
+     * Used for cellular networks that use Access Point Names (APN) such
+     * as GSM networks.
+     *
+     * @param apnContext is the Access Point Name to bring up a connection to
+     * @param profileId for the connection
+     * @param rilRadioTechnology Radio technology for the data connection
+     * @param onCompletedMsg is sent with its msg.obj as an AsyncResult object.
+     *                       With AsyncResult.userObj set to the original msg.obj,
+     *                       AsyncResult.result = FailCause and AsyncResult.exception = Exception().
+     * @param connectionGeneration used to track a single connection request so disconnects can get
+     *                             ignored if obsolete.
+     */
+    public void bringUp(ApnContext apnContext, int profileId, int rilRadioTechnology,
+                        Message onCompletedMsg, int connectionGeneration) {
+        if (DBG) {
+            log("bringUp: apnContext=" + apnContext + " onCompletedMsg=" + onCompletedMsg);
+        }
+        sendMessage(DataConnection.EVENT_CONNECT,
+                new ConnectionParams(apnContext, profileId, rilRadioTechnology, onCompletedMsg,
+                        connectionGeneration));
+    }
+
+    /**
+     * Tear down the connection through the apn on the network.
+     *
+     * @param onCompletedMsg is sent with its msg.obj as an AsyncResult object.
+     *        With AsyncResult.userObj set to the original msg.obj.
+     */
+    public void tearDown(ApnContext apnContext, String reason, Message onCompletedMsg) {
+        if (DBG) {
+            log("tearDown: apnContext=" + apnContext
+                    + " reason=" + reason + " onCompletedMsg=" + onCompletedMsg);
+        }
+        sendMessage(DataConnection.EVENT_DISCONNECT,
+                new DisconnectParams(apnContext, reason, onCompletedMsg));
+    }
+
     // ******* "public" interface
 
     /**
      * Used for testing purposes.
      */
-    /* package */ void tearDownNow() {
+    void tearDownNow() {
         if (DBG) log("tearDownNow()");
         sendMessage(obtainMessage(EVENT_TEAR_DOWN_NOW));
     }
 
     /**
+     * Tear down the connection through the apn on the network.  Ignores reference count and
+     * and always tears down.
+     *
+     * @param onCompletedMsg is sent with its msg.obj as an AsyncResult object.
+     *        With AsyncResult.userObj set to the original msg.obj.
+     */
+    public void tearDownAll(String reason, Message onCompletedMsg) {
+        if (DBG) log("tearDownAll: reason=" + reason + " onCompletedMsg=" + onCompletedMsg);
+        sendMessage(DataConnection.EVENT_DISCONNECT_ALL,
+                new DisconnectParams(null, reason, onCompletedMsg));
+    }
+
+    /**
+     * Reset the data connection to inactive state.
+     */
+    public void reset() {
+        sendMessage(EVENT_RESET);
+        if (DBG) log("reset");
+    }
+
+    /**
+     * Re-evaluate the restricted state. If the restricted data connection does not need to be
+     * restricted anymore, we need to dynamically change the network's capability.
+     */
+    void reevaluateRestrictedState() {
+        sendMessage(EVENT_REEVALUATE_RESTRICTED_STATE);
+        if (DBG) log("reevaluate restricted state");
+    }
+
+    /**
+     * @return The parameters used for initiating a data connection.
+     */
+    public ConnectionParams getConnectionParams() {
+        return mConnectionParams;
+    }
+
+    /**
+     * @return The list of PCSCF addresses
+     */
+    public String[] getPcscfAddresses() {
+        return mPcscfAddr;
+    }
+
+    /**
      * Using the result of the SETUP_DATA_CALL determine the retry delay.
      *
      * @param response The response from setup data call
@@ -2343,6 +2452,10 @@
         return (long) response.getSuggestedRetryTime();
     }
 
+    public List<ApnContext> getApnContexts() {
+        return new ArrayList<>(mApnContexts.keySet());
+    }
+
     /**
      * @return the string for msg.what as our info.
      */
@@ -2532,6 +2645,8 @@
         pw.println("mLastFailCause=" + mLastFailCause);
         pw.println("mUserData=" + mUserData);
         pw.println("mSubscriptionOverride=" + Integer.toHexString(mSubscriptionOverride));
+        pw.println("mRestrictedNetworkOverride=" + mRestrictedNetworkOverride);
+        pw.println("mUnmeteredUseOnly=" + mUnmeteredUseOnly);
         pw.println("mInstanceNumber=" + mInstanceNumber);
         pw.println("mAc=" + mAc);
         pw.println("Network capabilities changed history:");
diff --git a/src/java/com/android/internal/telephony/dataconnection/DataEnabledSettings.java b/src/java/com/android/internal/telephony/dataconnection/DataEnabledSettings.java
index 3b856a7..87521b1 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DataEnabledSettings.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DataEnabledSettings.java
@@ -52,6 +52,10 @@
 
     public static final int REASON_DATA_ENABLED_BY_CARRIER = 4;
 
+    public static final int REASON_PROVISIONED_CHANGED = 5;
+
+    public static final int REASON_PROVISIONING_DATA_ENABLED_CHANGED = 6;
+
     /**
      * responds to the setInternalDataEnabled call - used internally to turn off data.
      * For example during emergency calls
@@ -69,6 +73,8 @@
      */
     private boolean mCarrierDataEnabled = true;
 
+    private boolean mIsDataEnabled = false;
+
     private Phone mPhone = null;
     private ContentResolver mResolver = null;
 
@@ -88,15 +94,14 @@
     public DataEnabledSettings(Phone phone) {
         mPhone = phone;
         mResolver = mPhone.getContext().getContentResolver();
+        updateDataEnabled();
     }
 
     public synchronized void setInternalDataEnabled(boolean enabled) {
         localLog("InternalDataEnabled", enabled);
-        boolean prevDataEnabled = isDataEnabled();
         mInternalDataEnabled = enabled;
-        if (prevDataEnabled != isDataEnabled()) {
-            notifyDataEnabledChanged(!prevDataEnabled, REASON_INTERNAL_DATA_ENABLED);
-        }
+
+        updateDataEnabledAndNotify(REASON_INTERNAL_DATA_ENABLED);
     }
     public synchronized boolean isInternalDataEnabled() {
         return mInternalDataEnabled;
@@ -104,14 +109,11 @@
 
     public synchronized void setUserDataEnabled(boolean enabled) {
         localLog("UserDataEnabled", enabled);
-        boolean prevDataEnabled = isDataEnabled();
-
         Settings.Global.putInt(mResolver, getMobileDataSettingName(), enabled ? 1 : 0);
 
-        if (prevDataEnabled != isDataEnabled()) {
-            notifyDataEnabledChanged(!prevDataEnabled, REASON_USER_DATA_ENABLED);
-        }
+        updateDataEnabledAndNotify(REASON_USER_DATA_ENABLED);
     }
+
     public synchronized boolean isUserDataEnabled() {
         boolean defaultVal = "true".equalsIgnoreCase(SystemProperties.get(
                 "ro.com.android.mobiledata", "true"));
@@ -134,33 +136,53 @@
 
     public synchronized void setPolicyDataEnabled(boolean enabled) {
         localLog("PolicyDataEnabled", enabled);
-        boolean prevDataEnabled = isDataEnabled();
         mPolicyDataEnabled = enabled;
-        if (prevDataEnabled != isDataEnabled()) {
-            notifyDataEnabledChanged(!prevDataEnabled, REASON_POLICY_DATA_ENABLED);
-        }
+
+        updateDataEnabledAndNotify(REASON_POLICY_DATA_ENABLED);
     }
+
     public synchronized boolean isPolicyDataEnabled() {
         return mPolicyDataEnabled;
     }
 
     public synchronized void setCarrierDataEnabled(boolean enabled) {
         localLog("CarrierDataEnabled", enabled);
-        boolean prevDataEnabled = isDataEnabled();
         mCarrierDataEnabled = enabled;
-        if (prevDataEnabled != isDataEnabled()) {
-            notifyDataEnabledChanged(!prevDataEnabled, REASON_DATA_ENABLED_BY_CARRIER);
-        }
+
+        updateDataEnabledAndNotify(REASON_DATA_ENABLED_BY_CARRIER);
     }
+
     public synchronized boolean isCarrierDataEnabled() {
         return mCarrierDataEnabled;
     }
 
+    public synchronized void updateProvisionedChanged() {
+        updateDataEnabledAndNotify(REASON_PROVISIONED_CHANGED);
+    }
+
+    public synchronized void updateProvisioningDataEnabled() {
+        updateDataEnabledAndNotify(REASON_PROVISIONING_DATA_ENABLED_CHANGED);
+    }
+
     public synchronized boolean isDataEnabled() {
+        return mIsDataEnabled;
+    }
+
+    private synchronized void updateDataEnabledAndNotify(int reason) {
+        boolean prevDataEnabled = mIsDataEnabled;
+
+        updateDataEnabled();
+
+        if (prevDataEnabled != mIsDataEnabled) {
+            notifyDataEnabledChanged(!prevDataEnabled, reason);
+        }
+    }
+
+    private synchronized void updateDataEnabled() {
         if (isProvisioning()) {
-            return isProvisioningDataEnabled();
+            mIsDataEnabled = isProvisioningDataEnabled();
         } else {
-            return mInternalDataEnabled && isUserDataEnabled()
+            mIsDataEnabled = mInternalDataEnabled && isUserDataEnabled()
                     && mPolicyDataEnabled && mCarrierDataEnabled;
         }
     }
diff --git a/src/java/com/android/internal/telephony/dataconnection/DcAsyncChannel.java b/src/java/com/android/internal/telephony/dataconnection/DcAsyncChannel.java
deleted file mode 100644
index ad0b4ed..0000000
--- a/src/java/com/android/internal/telephony/dataconnection/DcAsyncChannel.java
+++ /dev/null
@@ -1,442 +0,0 @@
-/*
- * Copyright (C) 2011 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.net.LinkProperties;
-import android.net.NetworkCapabilities;
-import android.net.ProxyInfo;
-import android.os.Message;
-
-import com.android.internal.telephony.dataconnection.DataConnection.ConnectionParams;
-import com.android.internal.telephony.dataconnection.DataConnection.DisconnectParams;
-import com.android.internal.util.AsyncChannel;
-import com.android.internal.util.Protocol;
-
-/**
- * AsyncChannel to a DataConnection
- */
-public class DcAsyncChannel extends AsyncChannel {
-    private static final boolean DBG = false;
-    private String mLogTag;
-
-    private DataConnection mDc;
-    private long mDcThreadId;
-
-    public static final int BASE = Protocol.BASE_DATA_CONNECTION_AC;
-
-    public static final int REQ_IS_INACTIVE = BASE + 0;
-    public static final int RSP_IS_INACTIVE = BASE + 1;
-
-    public static final int REQ_GET_CID = BASE + 2;
-    public static final int RSP_GET_CID = BASE + 3;
-
-    public static final int REQ_GET_APNSETTING = BASE + 4;
-    public static final int RSP_GET_APNSETTING = BASE + 5;
-
-    public static final int REQ_GET_LINK_PROPERTIES = BASE + 6;
-    public static final int RSP_GET_LINK_PROPERTIES = BASE + 7;
-
-    public static final int REQ_SET_LINK_PROPERTIES_HTTP_PROXY = BASE + 8;
-    public static final int RSP_SET_LINK_PROPERTIES_HTTP_PROXY = BASE + 9;
-
-    public static final int REQ_GET_NETWORK_CAPABILITIES = BASE + 10;
-    public static final int RSP_GET_NETWORK_CAPABILITIES = BASE + 11;
-
-    public static final int REQ_RESET = BASE + 12;
-    public static final int RSP_RESET = BASE + 13;
-
-    private static final int CMD_TO_STRING_COUNT = RSP_RESET - BASE + 1;
-    private static String[] sCmdToString = new String[CMD_TO_STRING_COUNT];
-    static {
-        sCmdToString[REQ_IS_INACTIVE - BASE] = "REQ_IS_INACTIVE";
-        sCmdToString[RSP_IS_INACTIVE - BASE] = "RSP_IS_INACTIVE";
-        sCmdToString[REQ_GET_CID - BASE] = "REQ_GET_CID";
-        sCmdToString[RSP_GET_CID - BASE] = "RSP_GET_CID";
-        sCmdToString[REQ_GET_APNSETTING - BASE] = "REQ_GET_APNSETTING";
-        sCmdToString[RSP_GET_APNSETTING - BASE] = "RSP_GET_APNSETTING";
-        sCmdToString[REQ_GET_LINK_PROPERTIES - BASE] = "REQ_GET_LINK_PROPERTIES";
-        sCmdToString[RSP_GET_LINK_PROPERTIES - BASE] = "RSP_GET_LINK_PROPERTIES";
-        sCmdToString[REQ_SET_LINK_PROPERTIES_HTTP_PROXY - BASE] =
-                "REQ_SET_LINK_PROPERTIES_HTTP_PROXY";
-        sCmdToString[RSP_SET_LINK_PROPERTIES_HTTP_PROXY - BASE] =
-                "RSP_SET_LINK_PROPERTIES_HTTP_PROXY";
-        sCmdToString[REQ_GET_NETWORK_CAPABILITIES - BASE] = "REQ_GET_NETWORK_CAPABILITIES";
-        sCmdToString[RSP_GET_NETWORK_CAPABILITIES - BASE] = "RSP_GET_NETWORK_CAPABILITIES";
-        sCmdToString[REQ_RESET - BASE] = "REQ_RESET";
-        sCmdToString[RSP_RESET - BASE] = "RSP_RESET";
-    }
-
-    ConnectionParams mLastConnectionParams;
-
-    // Convert cmd to string or null if unknown
-    protected static String cmdToString(int cmd) {
-        cmd -= BASE;
-        if ((cmd >= 0) && (cmd < sCmdToString.length)) {
-            return sCmdToString[cmd];
-        } else {
-            return AsyncChannel.cmdToString(cmd + BASE);
-        }
-    }
-
-    /**
-     * enum used to notify action taken or necessary to be
-     * taken after the link property is changed.
-     */
-    public enum LinkPropertyChangeAction {
-        NONE, CHANGED, RESET;
-
-        public static LinkPropertyChangeAction fromInt(int value) {
-            if (value == NONE.ordinal()) {
-                return NONE;
-            } else if (value == CHANGED.ordinal()) {
-                return CHANGED;
-            } else if (value == RESET.ordinal()) {
-                return RESET;
-            } else {
-                throw new RuntimeException("LinkPropertyChangeAction.fromInt: bad value=" + value);
-            }
-        }
-    }
-
-    public DcAsyncChannel(DataConnection dc, String logTag) {
-        mDc = dc;
-        mDcThreadId = mDc.getHandler().getLooper().getThread().getId();
-        mLogTag = logTag;
-    }
-
-    /**
-     * Request if the state machine is in the inactive state.
-     * Response {@link #rspIsInactive}
-     */
-    public void reqIsInactive() {
-        sendMessage(REQ_IS_INACTIVE);
-        if (DBG) log("reqIsInactive");
-    }
-
-    /**
-     * Evaluate RSP_IS_INACTIVE.
-     *
-     * @return true if the state machine is in the inactive state.
-     */
-    public boolean rspIsInactive(Message response) {
-        boolean retVal = response.arg1 == 1;
-        if (DBG) log("rspIsInactive=" + retVal);
-        return retVal;
-    }
-
-    /**
-     * @return true if the state machine is in the inactive state
-     * and can be used for a new connection.
-     */
-    public boolean isInactiveSync() {
-        boolean value;
-        if (isCallerOnDifferentThread()) {
-            Message response = sendMessageSynchronously(REQ_IS_INACTIVE);
-            if ((response != null) && (response.what == RSP_IS_INACTIVE)) {
-                value = rspIsInactive(response);
-            } else {
-                log("rspIsInactive error response=" + response);
-                value = false;
-            }
-        } else {
-            value = mDc.isInactive();
-        }
-        return value;
-    }
-
-    /**
-     * Request the Connection ID.
-     * Response {@link #rspCid}
-     */
-    public void reqCid() {
-        sendMessage(REQ_GET_CID);
-        if (DBG) log("reqCid");
-    }
-
-    /**
-     * Evaluate a RSP_GET_CID message and return the cid.
-     *
-     * @param response Message
-     * @return connection id or -1 if an error
-     */
-    public int rspCid(Message response) {
-        int retVal = response.arg1;
-        if (DBG) log("rspCid=" + retVal);
-        return retVal;
-    }
-
-    /**
-     * @return connection id or -1 if an error
-     */
-    public int getCidSync() {
-        int value;
-        if (isCallerOnDifferentThread()) {
-            Message response = sendMessageSynchronously(REQ_GET_CID);
-            if ((response != null) && (response.what == RSP_GET_CID)) {
-                value = rspCid(response);
-            } else {
-                log("rspCid error response=" + response);
-                value = -1;
-            }
-        } else {
-            value = mDc.getCid();
-        }
-        return value;
-    }
-
-    /**
-     * Request the connections ApnSetting.
-     * Response {@link #rspApnSetting}
-     */
-    public void reqApnSetting() {
-        sendMessage(REQ_GET_APNSETTING);
-        if (DBG) log("reqApnSetting");
-    }
-
-    /**
-     * Evaluate a RSP_APN_SETTING message and return the ApnSetting.
-     *
-     * @param response Message
-     * @return ApnSetting, maybe null
-     */
-    public ApnSetting rspApnSetting(Message response) {
-        ApnSetting retVal = (ApnSetting) response.obj;
-        if (DBG) log("rspApnSetting=" + retVal);
-        return retVal;
-    }
-
-    /**
-     * Get the connections ApnSetting.
-     *
-     * @return ApnSetting or null if an error
-     */
-    public ApnSetting getApnSettingSync() {
-        ApnSetting value;
-        if (isCallerOnDifferentThread()) {
-            Message response = sendMessageSynchronously(REQ_GET_APNSETTING);
-            if ((response != null) && (response.what == RSP_GET_APNSETTING)) {
-                value = rspApnSetting(response);
-            } else {
-                log("getApnSetting error response=" + response);
-                value = null;
-            }
-        } else {
-            value = mDc.getApnSetting();
-        }
-        return value;
-    }
-
-    /**
-     * Request the connections LinkProperties.
-     * Response {@link #rspLinkProperties}
-     */
-    public void reqLinkProperties() {
-        sendMessage(REQ_GET_LINK_PROPERTIES);
-        if (DBG) log("reqLinkProperties");
-    }
-
-    /**
-     * Evaluate RSP_GET_LINK_PROPERTIES
-     *
-     * @param response
-     * @return LinkProperties, maybe null.
-     */
-    public LinkProperties rspLinkProperties(Message response) {
-        LinkProperties retVal = (LinkProperties) response.obj;
-        if (DBG) log("rspLinkProperties=" + retVal);
-        return retVal;
-    }
-
-    /**
-     * Get the connections LinkProperties.
-     *
-     * @return LinkProperties or null if an error
-     */
-    public LinkProperties getLinkPropertiesSync() {
-        LinkProperties value;
-        if (isCallerOnDifferentThread()) {
-            Message response = sendMessageSynchronously(REQ_GET_LINK_PROPERTIES);
-            if ((response != null) && (response.what == RSP_GET_LINK_PROPERTIES)) {
-                value = rspLinkProperties(response);
-            } else {
-                log("getLinkProperties error response=" + response);
-                value = null;
-            }
-        } else {
-            value = mDc.getCopyLinkProperties();
-        }
-        return value;
-    }
-
-    /**
-     * Request setting the connections LinkProperties.HttpProxy.
-     * Response RSP_SET_LINK_PROPERTIES when complete.
-     */
-    public void reqSetLinkPropertiesHttpProxy(ProxyInfo proxy) {
-        sendMessage(REQ_SET_LINK_PROPERTIES_HTTP_PROXY, proxy);
-        if (DBG) log("reqSetLinkPropertiesHttpProxy proxy=" + proxy);
-    }
-
-    /**
-     * Set the connections LinkProperties.HttpProxy
-     */
-    public void setLinkPropertiesHttpProxySync(ProxyInfo proxy) {
-        if (isCallerOnDifferentThread()) {
-            Message response =
-                sendMessageSynchronously(REQ_SET_LINK_PROPERTIES_HTTP_PROXY, proxy);
-            if ((response != null) && (response.what == RSP_SET_LINK_PROPERTIES_HTTP_PROXY)) {
-                if (DBG) log("setLinkPropertiesHttpPoxy ok");
-            } else {
-                log("setLinkPropertiesHttpPoxy error response=" + response);
-            }
-        } else {
-            mDc.setLinkPropertiesHttpProxy(proxy);
-        }
-    }
-
-    /**
-     * Request the connections NetworkCapabilities.
-     * Response {@link #rspNetworkCapabilities}
-     */
-    public void reqNetworkCapabilities() {
-        sendMessage(REQ_GET_NETWORK_CAPABILITIES);
-        if (DBG) log("reqNetworkCapabilities");
-    }
-
-    /**
-     * Evaluate RSP_GET_NETWORK_CAPABILITIES
-     *
-     * @param response
-     * @return NetworkCapabilities, maybe null.
-     */
-    public NetworkCapabilities rspNetworkCapabilities(Message response) {
-        NetworkCapabilities retVal = (NetworkCapabilities) response.obj;
-        if (DBG) log("rspNetworkCapabilities=" + retVal);
-        return retVal;
-    }
-
-    /**
-     * Get the connections NetworkCapabilities.
-     *
-     * @return NetworkCapabilities or null if an error
-     */
-    public NetworkCapabilities getNetworkCapabilitiesSync() {
-        NetworkCapabilities value;
-        if (isCallerOnDifferentThread()) {
-            Message response = sendMessageSynchronously(REQ_GET_NETWORK_CAPABILITIES);
-            if ((response != null) && (response.what == RSP_GET_NETWORK_CAPABILITIES)) {
-                value = rspNetworkCapabilities(response);
-            } else {
-                value = null;
-            }
-        } else {
-            value = mDc.getNetworkCapabilities();
-        }
-        return value;
-    }
-
-    /**
-     * Response RSP_RESET when complete
-     */
-    public void reqReset() {
-        sendMessage(REQ_RESET);
-        if (DBG) log("reqReset");
-    }
-
-    /**
-     * Bring up a connection to the apn and return an AsyncResult in onCompletedMsg.
-     * Used for cellular networks that use Access Point Names (APN) such
-     * as GSM networks.
-     *
-     * @param apnContext is the Access Point Name to bring up a connection to
-     * @param profileId for the connection
-     * @param rilRadioTechnology Radio technology for the data connection
-     * @param unmeteredUseOnly Indicates the data connection can only used for unmetered purposes
-     * @param onCompletedMsg is sent with its msg.obj as an AsyncResult object.
-     *                       With AsyncResult.userObj set to the original msg.obj,
-     *                       AsyncResult.result = FailCause and AsyncResult.exception = Exception().
-     * @param connectionGeneration used to track a single connection request so disconnects can get
-     *                             ignored if obsolete.
-     */
-    public void bringUp(ApnContext apnContext, int profileId, int rilRadioTechnology,
-                        boolean unmeteredUseOnly, Message onCompletedMsg,
-                        int connectionGeneration) {
-        if (DBG) {
-            log("bringUp: apnContext=" + apnContext + "unmeteredUseOnly=" + unmeteredUseOnly
-                    + " onCompletedMsg=" + onCompletedMsg);
-        }
-        mLastConnectionParams = new ConnectionParams(apnContext, profileId, rilRadioTechnology,
-                unmeteredUseOnly, onCompletedMsg, connectionGeneration);
-        sendMessage(DataConnection.EVENT_CONNECT, mLastConnectionParams);
-    }
-
-    /**
-     * Tear down the connection through the apn on the network.
-     *
-     * @param onCompletedMsg is sent with its msg.obj as an AsyncResult object.
-     *        With AsyncResult.userObj set to the original msg.obj.
-     */
-    public void tearDown(ApnContext apnContext, String reason, Message onCompletedMsg) {
-        if (DBG) {
-            log("tearDown: apnContext=" + apnContext
-                    + " reason=" + reason + " onCompletedMsg=" + onCompletedMsg);
-        }
-        sendMessage(DataConnection.EVENT_DISCONNECT,
-                        new DisconnectParams(apnContext, reason, onCompletedMsg));
-    }
-
-    /**
-     * Tear down the connection through the apn on the network.  Ignores refcount and
-     * and always tears down.
-     *
-     * @param onCompletedMsg is sent with its msg.obj as an AsyncResult object.
-     *        With AsyncResult.userObj set to the original msg.obj.
-     */
-    public void tearDownAll(String reason, Message onCompletedMsg) {
-        if (DBG) log("tearDownAll: reason=" + reason + " onCompletedMsg=" + onCompletedMsg);
-        sendMessage(DataConnection.EVENT_DISCONNECT_ALL,
-                new DisconnectParams(null, reason, onCompletedMsg));
-    }
-
-    /**
-     * @return connection id
-     */
-    public int getDataConnectionIdSync() {
-        // Safe because this is owned by the caller.
-        return mDc.getDataConnectionId();
-    }
-
-    @Override
-    public String toString() {
-        return mDc.getName();
-    }
-
-    private boolean isCallerOnDifferentThread() {
-        long curThreadId = Thread.currentThread().getId();
-        boolean value = mDcThreadId != curThreadId;
-        if (DBG) log("isCallerOnDifferentThread: " + value);
-        return value;
-    }
-
-    private void log(String s) {
-        android.telephony.Rlog.d(mLogTag, "DataConnectionAc " + s);
-    }
-
-    public String[] getPcscfAddr() {
-        return mDc.mPcscfAddr;
-    }
-}
diff --git a/src/java/com/android/internal/telephony/dataconnection/DcController.java b/src/java/com/android/internal/telephony/dataconnection/DcController.java
index 8d05be7..2f74ff0 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DcController.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DcController.java
@@ -43,6 +43,7 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 
 /**
  * Data Connection Controller which is a package visible class and controls
@@ -314,7 +315,8 @@
                     continue;
                 }
 
-                if (dc.mApnContexts.size() == 0) {
+                List<ApnContext> apnContexts = dc.getApnContexts();
+                if (apnContexts.size() == 0) {
                     if (DBG) loge("onDataStateChanged: no connected apns, ignore");
                 } else {
                     // Determine if the connection/apnContext should be cleaned up
@@ -325,11 +327,11 @@
                     }
                     if (newState.getActive() == DATA_CONNECTION_ACTIVE_PH_LINK_INACTIVE) {
                         if (mDct.isCleanupRequired.get()) {
-                            apnsToCleanup.addAll(dc.mApnContexts.keySet());
+                            apnsToCleanup.addAll(apnContexts);
                             mDct.isCleanupRequired.set(false);
                         } else {
                             DcFailCause failCause = DcFailCause.fromInt(newState.getStatus());
-                            if (failCause.isRestartRadioFail(mPhone.getContext(),
+                            if (failCause.isRadioRestartFailure(mPhone.getContext(),
                                         mPhone.getSubId())) {
                                 if (DBG) {
                                     log("onDataStateChanged: X restart radio, failCause="
@@ -341,7 +343,7 @@
                                     log("onDataStateChanged: inactive, add to cleanup list. "
                                             + "failCause=" + failCause);
                                 }
-                                apnsToCleanup.addAll(dc.mApnContexts.keySet());
+                                apnsToCleanup.addAll(apnContexts);
                             } else {
                                 if (DBG) {
                                     log("onDataStateChanged: inactive, add to retry list. "
@@ -382,16 +384,16 @@
                                     }
                                     if (needToClean) {
                                         if (DBG) {
-                                            log("onDataStateChanged: addr change," +
-                                                    " cleanup apns=" + dc.mApnContexts +
-                                                    " oldLp=" + result.oldLp +
-                                                    " newLp=" + result.newLp);
+                                            log("onDataStateChanged: addr change,"
+                                                    + " cleanup apns=" + apnContexts
+                                                    + " oldLp=" + result.oldLp
+                                                    + " newLp=" + result.newLp);
                                         }
-                                        apnsToCleanup.addAll(dc.mApnContexts.keySet());
+                                        apnsToCleanup.addAll(apnContexts);
                                     } else {
                                         if (DBG) log("onDataStateChanged: simple change");
 
-                                        for (ApnContext apnContext : dc.mApnContexts.keySet()) {
+                                        for (ApnContext apnContext : apnContexts) {
                                              mPhone.notifyDataConnection(
                                                  PhoneConstants.REASON_LINK_PROPERTIES_CHANGED,
                                                  apnContext.getApnType());
@@ -403,10 +405,10 @@
                                     }
                                 }
                             } else {
-                                apnsToCleanup.addAll(dc.mApnContexts.keySet());
+                                apnsToCleanup.addAll(apnContexts);
                                 if (DBG) {
                                     log("onDataStateChanged: interface change, cleanup apns="
-                                            + dc.mApnContexts);
+                                            + apnContexts);
                                 }
                             }
                         }
@@ -449,7 +451,7 @@
 
             // Cleanup connections that have changed
             for (ApnContext apnContext : apnsToCleanup) {
-               mDct.sendCleanUpConnection(true, apnContext);
+                mDct.cleanUpConnection(apnContext);
             }
 
             // Retry connections that have disappeared
@@ -487,9 +489,6 @@
     protected String getWhatToString(int what) {
         String info = null;
         info = DataConnection.cmdToString(what);
-        if (info == null) {
-            info = DcAsyncChannel.cmdToString(what);
-        }
         return info;
     }
 
diff --git a/src/java/com/android/internal/telephony/dataconnection/DcFailCause.java b/src/java/com/android/internal/telephony/dataconnection/DcFailCause.java
index b3762e5..79c79de 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DcFailCause.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DcFailCause.java
@@ -16,10 +16,10 @@
 package com.android.internal.telephony.dataconnection;
 
 import android.content.Context;
-import android.content.res.Resources;
 import android.os.PersistableBundle;
 import android.telephony.CarrierConfigManager;
 
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
 
@@ -146,25 +146,35 @@
     }
 
     /**
-     * Returns whether or not the radio has failed and also needs to be restarted.
-     * By default, we do not restart radio on REGULAR_DEACTIVATION.
+     * Returns whether or not the fail cause is a failure that requires a modem restart
      *
      * @param context device context
-     * @param subId subscription id
-     * @return true if the radio has failed and the carrier requres restart, otherwise false
+     * @param subId subscription index
+     * @return true if the fail cause code needs platform to trigger a modem restart.
      */
-    public boolean isRestartRadioFail(Context context, int subId) {
-        if (this == REGULAR_DEACTIVATION) {
-            CarrierConfigManager configManager = (CarrierConfigManager)
-                    context.getSystemService(Context.CARRIER_CONFIG_SERVICE);
-            if (configManager != null) {
-                PersistableBundle b = configManager.getConfigForSubId(subId);
-                if (b != null) {
-                    return b.getBoolean(CarrierConfigManager.
-                            KEY_RESTART_RADIO_ON_PDP_FAIL_REGULAR_DEACTIVATION_BOOL);
+    public boolean isRadioRestartFailure(Context context, int subId) {
+        CarrierConfigManager configManager = (CarrierConfigManager)
+                context.getSystemService(Context.CARRIER_CONFIG_SERVICE);
+        if (configManager != null) {
+            PersistableBundle b = configManager.getConfigForSubId(subId);
+
+            if (b != null) {
+                if (this == REGULAR_DEACTIVATION
+                        && b.getBoolean(CarrierConfigManager
+                        .KEY_RESTART_RADIO_ON_PDP_FAIL_REGULAR_DEACTIVATION_BOOL)) {
+                    // This is for backward compatibility support. We need to continue support this
+                    // old configuration until it gets removed in the future.
+                    return true;
+                }
+                // Check the current configurations.
+                int[] causeCodes = b.getIntArray(CarrierConfigManager
+                        .KEY_RADIO_RESTART_FAILURE_CAUSES_INT_ARRAY);
+                if (causeCodes != null) {
+                    return Arrays.stream(causeCodes).anyMatch(i -> i == getErrorCode());
                 }
             }
         }
+
         return false;
     }
 
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
old mode 100644
new mode 100755
index 1f4ada6..26284c1
--- a/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
@@ -18,6 +18,9 @@
 
 import static android.Manifest.permission.READ_PHONE_STATE;
 
+import static com.android.internal.telephony.RILConstants.DATA_PROFILE_DEFAULT;
+import static com.android.internal.telephony.RILConstants.DATA_PROFILE_INVALID;
+
 import android.annotation.NonNull;
 import android.app.AlarmManager;
 import android.app.PendingIntent;
@@ -38,7 +41,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;
@@ -67,6 +69,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;
@@ -76,7 +79,6 @@
 import android.util.SparseArray;
 import android.view.WindowManager;
 
-import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.CarrierActionAgent;
 import com.android.internal.telephony.DctConstants;
@@ -100,9 +102,9 @@
 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.List;
 import java.util.Map.Entry;
 import java.util.PriorityQueue;
 import java.util.Set;
@@ -114,18 +116,21 @@
  * {@hide}
  */
 public class DcTracker extends Handler {
-    private static final String LOG_TAG = "DCT";
     private static final boolean DBG = true;
     private static final boolean VDBG = false; // STOPSHIP if true
     private static final boolean VDBG_STALL = false; // STOPSHIP if true
     private static final boolean RADIO_TESTS = false;
 
+    private final String mLogTag;
+
     public AtomicBoolean isCleanupRequired = new AtomicBoolean(false);
 
+    private final TelephonyManager mTelephonyManager;
+
     private final AlarmManager mAlarmManager;
 
     /* Currently requested APN type (TODO: This should probably be a parameter not a member) */
-    private String mRequestedApnType = PhoneConstants.APN_TYPE_DEFAULT;
+    private int mRequestedApnType = ApnSetting.TYPE_DEFAULT;
 
     // All data enabling/disabling related settings
     private final DataEnabledSettings mDataEnabledSettings;
@@ -146,8 +151,6 @@
     private static final int DATA_STALL_ALARM_NON_AGGRESSIVE_DELAY_IN_MS_DEFAULT = 1000 * 60 * 6;
     // Default for the data stall alarm for aggressive stall detection
     private static final int DATA_STALL_ALARM_AGGRESSIVE_DELAY_IN_MS_DEFAULT = 1000 * 60;
-    // Tag for tracking stale alarms
-    private static final String DATA_STALL_ALARM_TAG_EXTRA = "data.stall.alram.tag";
 
     private static final boolean DATA_STALL_SUSPECTED = true;
     private static final boolean DATA_STALL_NOT_SUSPECTED = false;
@@ -157,9 +160,15 @@
     private static final String INTENT_RECONNECT_ALARM_EXTRA_TYPE = "reconnect_alarm_extra_type";
     private static final String INTENT_RECONNECT_ALARM_EXTRA_REASON =
             "reconnect_alarm_extra_reason";
+    private static final String INTENT_RECONNECT_ALARM_EXTRA_TRANSPORT_TYPE =
+            "reconnect_alarm_extra_transport_type";
 
     private static final String INTENT_DATA_STALL_ALARM =
             "com.android.internal.telephony.data-stall";
+    // Tag for tracking stale alarms
+    private static final String INTENT_DATA_STALL_ALARM_EXTRA_TAG = "data_stall_alarm_extra_tag";
+    private static final String INTENT_DATA_STALL_ALARM_EXTRA_TRANSPORT_TYPE =
+            "data_stall_alarm_extra_transport_type";
 
     private DcTesterFailBringUpAll mDcTesterFailBringUpAll;
     private DcController mDcc;
@@ -174,8 +183,8 @@
                 }
             } );
 
-    /** allApns holds all apns */
-    private ArrayList<ApnSetting> mAllApnSettings = null;
+    /** all APN settings applicable to the current carrier */
+    private ArrayList<ApnSetting> mAllApnSettings = new ArrayList<>();
 
     /** preferred apn */
     private ApnSetting mPreferredApn = null;
@@ -219,6 +228,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
@@ -239,10 +249,8 @@
                 startNetStatPoll();
                 restartDataStallAlarm();
             } else if (action.startsWith(INTENT_RECONNECT_ALARM)) {
-                if (DBG) log("Reconnect alarm. Previous state was " + mState);
                 onActionIntentReconnectAlarm(intent);
             } else if (action.equals(INTENT_DATA_STALL_ALARM)) {
-                if (DBG) log("Data stall alarm");
                 onActionIntentDataStallAlarm(intent);
             } else if (action.equals(INTENT_PROVISIONING_APN_ALARM)) {
                 if (DBG) log("Provisioning apn alarm");
@@ -278,29 +286,31 @@
     };
 
     private SubscriptionManager mSubscriptionManager;
-    private final OnSubscriptionsChangedListener mOnSubscriptionsChangedListener =
-            new OnSubscriptionsChangedListener() {
-                public final AtomicInteger mPreviousSubId =
-                        new AtomicInteger(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+    private final DctOnSubscriptionsChangedListener
+            mOnSubscriptionsChangedListener = new DctOnSubscriptionsChangedListener();
 
-                /**
-                 * Callback invoked when there is any change to any SubscriptionInfo. Typically
-                 * this method invokes {@link SubscriptionManager#getActiveSubscriptionInfoList}
-                 */
-                @Override
-                public void onSubscriptionsChanged() {
-                    if (DBG) log("SubscriptionListener.onSubscriptionInfoChanged");
-                    // Set the network type, in case the radio does not restore it.
-                    int subId = mPhone.getSubId();
-                    if (SubscriptionManager.isValidSubscriptionId(subId)) {
-                        registerSettingsObserver();
-                    }
-                    if (mPreviousSubId.getAndSet(subId) != subId &&
-                            SubscriptionManager.isValidSubscriptionId(subId)) {
-                        onRecordsLoadedOrSubIdChanged();
-                    }
-                }
-            };
+    private class DctOnSubscriptionsChangedListener extends OnSubscriptionsChangedListener {
+        public final AtomicInteger mPreviousSubId =
+                new AtomicInteger(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+
+        /**
+         * Callback invoked when there is any change to any SubscriptionInfo. Typically
+         * this method invokes {@link SubscriptionManager#getActiveSubscriptionInfoList}
+         */
+        @Override
+        public void onSubscriptionsChanged() {
+            if (DBG) log("SubscriptionListener.onSubscriptionInfoChanged");
+            // Set the network type, in case the radio does not restore it.
+            int subId = mPhone.getSubId();
+            if (SubscriptionManager.isValidSubscriptionId(subId)) {
+                registerSettingsObserver();
+            }
+            if (SubscriptionManager.isValidSubscriptionId(subId) &&
+                    mPreviousSubId.getAndSet(subId) != subId) {
+                onRecordsLoadedOrSubIdChanged();
+            }
+        }
+    };
 
     private final SettingsObserver mSettingsObserver;
 
@@ -319,7 +329,7 @@
                 DctConstants.EVENT_DEVICE_PROVISIONED_CHANGE);
         mSettingsObserver.observe(
                 Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONING_MOBILE_DATA_ENABLED),
-                DctConstants.EVENT_DEVICE_PROVISIONED_CHANGE);
+                DctConstants.EVENT_DEVICE_PROVISIONING_DATA_SETTING_CHANGE);
     }
 
     /**
@@ -386,12 +396,15 @@
         int phoneSubId = mPhone.getSubId();
         int currSubId = bundle.getInt(PhoneConstants.SUBSCRIPTION_KEY,
                 SubscriptionManager.INVALID_SUBSCRIPTION_ID);
-        log("onDataReconnect: currSubId = " + currSubId + " phoneSubId=" + phoneSubId);
 
         // Stop reconnect if not current subId is not correct.
         // FIXME STOPSHIP - phoneSubId is coming up as -1 way after boot and failing this?
         if (!SubscriptionManager.isValidSubscriptionId(currSubId) || (currSubId != phoneSubId)) {
-            log("receive ReconnectAlarm but subId incorrect, ignore");
+            return;
+        }
+
+        int transportType = bundle.getInt(INTENT_RECONNECT_ALARM_EXTRA_TRANSPORT_TYPE, 0);
+        if (transportType != mTransportType) {
             return;
         }
 
@@ -399,8 +412,7 @@
 
         if (DBG) {
             log("onDataReconnect: mState=" + mState + " reason=" + reason + " apnType=" + apnType
-                    + " apnContext=" + apnContext + " mDataConnectionAsyncChannels="
-                    + mDataConnectionAcHashMap);
+                    + " apnContext=" + apnContext);
         }
 
         if ((apnContext != null) && (apnContext.isEnabled())) {
@@ -414,14 +426,14 @@
                 if (DBG) {
                     log("onDataReconnect: state is FAILED|IDLE, disassociate");
                 }
-                DcAsyncChannel dcac = apnContext.getDcAc();
-                if (dcac != null) {
+                DataConnection dataConnection = apnContext.getDataConnection();
+                if (dataConnection != null) {
                     if (DBG) {
                         log("onDataReconnect: tearDown apnContext=" + apnContext);
                     }
-                    dcac.tearDown(apnContext, "", null);
+                    dataConnection.tearDown(apnContext, "", null);
                 }
-                apnContext.setDataConnectionAc(null);
+                apnContext.setDataConnection(null);
                 apnContext.setState(DctConstants.State.IDLE);
             } else {
                 if (DBG) log("onDataReconnect: keep associated");
@@ -435,20 +447,24 @@
 
     private void onActionIntentDataStallAlarm(Intent intent) {
         if (VDBG_STALL) log("onActionIntentDataStallAlarm: action=" + intent.getAction());
+
+        int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
+                SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+        if (!SubscriptionManager.isValidSubscriptionId(subId) || (subId != mPhone.getSubId())) {
+            return;
+        }
+
+        int transportType = intent.getIntExtra(INTENT_DATA_STALL_ALARM_EXTRA_TRANSPORT_TYPE, 0);
+        if (transportType != mTransportType) {
+            return;
+        }
+
         Message msg = obtainMessage(DctConstants.EVENT_DATA_STALL_ALARM,
                 intent.getAction());
-        msg.arg1 = intent.getIntExtra(DATA_STALL_ALARM_TAG_EXTRA, 0);
+        msg.arg1 = intent.getIntExtra(INTENT_DATA_STALL_ALARM_EXTRA_TAG, 0);
         sendMessage(msg);
     }
 
-    private final ConnectivityManager mCm;
-
-    /**
-     * List of messages that are waiting to be posted, when data call disconnect
-     * is complete
-     */
-    private ArrayList<Message> mDisconnectAllCompleteMsgList = new ArrayList<Message>();
-
     private RegistrantList mAllDataDisconnectedRegistrants = new RegistrantList();
 
     // member variables
@@ -495,10 +511,6 @@
     //        really a lower power mode")
     private boolean mIsScreenOn = true;
 
-    // Indicates if we found mvno-specific APNs in the full APN list.
-    // used to determine if we can accept mno-specific APN for tethering.
-    private boolean mMvnoMatched = false;
-
     /** Allows the generation of unique Id's for DataConnection objects */
     private AtomicInteger mUniqueIdGenerator = new AtomicInteger(0);
 
@@ -506,10 +518,6 @@
     private HashMap<Integer, DataConnection> mDataConnections =
             new HashMap<Integer, DataConnection>();
 
-    /** The data connection async channels */
-    private HashMap<Integer, DcAsyncChannel> mDataConnectionAcHashMap =
-            new HashMap<Integer, DcAsyncChannel>();
-
     /** Convert an ApnType string to Id (TODO: Use "enumeration" instead of String for ApnType) */
     private HashMap<String, Integer> mApnToDataConnectionId = new HashMap<String, Integer>();
 
@@ -517,23 +525,11 @@
     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;
 
-    /** Indicate if metered APNs are disabled.
-     *  set to block all the metered APNs from continuously sending requests, which causes
-     *  undesired network load */
-    private boolean mMeteredApnDisabled = false;
-
-    /**
-     * int to remember whether has setDataProfiles and with roaming or not.
-     * 0: default, has never set data profile
-     * 1: has set data profile with home protocol
-     * 2: has set data profile with roaming protocol
-     * This is not needed once RIL command is updated to support both home and roaming protocol.
-     */
-    private int mSetDataProfileStatus = 0;
+    private ArrayList<DataProfile> mLastDataProfileList = new ArrayList<>();
 
     /**
      * Handles changes to the APN db.
@@ -587,6 +583,16 @@
         super();
         mPhone = phone;
         if (DBG) log("DCT.constructor");
+        mTelephonyManager = TelephonyManager.from(phone.getContext())
+                .createForSubscriptionId(phone.getSubId());
+        // The 'C' in tag indicates cellular, and 'I' indicates IWLAN. This is to distinguish
+        // between two DcTrackers, one for each.
+        String tag = "DCT-" + ((transportType == TransportType.WWAN) ? "C" : "I");
+        if (mTelephonyManager.getPhoneCount() > 1) {
+            tag += "-" + mPhone.getPhoneId();
+        }
+        mLogTag = tag;
+
         mTransportType = transportType;
         mDataServiceManager = new DataServiceManager(phone, transportType);
 
@@ -595,9 +601,6 @@
         mUiccController.registerForIccChanged(this, DctConstants.EVENT_ICC_CHANGED, null);
         mAlarmManager =
                 (AlarmManager) mPhone.getContext().getSystemService(Context.ALARM_SERVICE);
-        mCm = (ConnectivityManager) mPhone.getContext().getSystemService(
-                Context.CONNECTIVITY_SERVICE);
-
 
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_SCREEN_ON);
@@ -638,9 +641,7 @@
             mPhone.getContext().registerReceiver(mIntentReceiver, filter, null, mPhone);
         }
 
-        // Add Emergency APN to APN setting list by default to support EPDN in sim absent cases
         initEmergencyApnSetting();
-        addEmergencyApnSetting();
 
         mProvisionActionName = "com.android.internal.telephony.PROVISION" + phone.getPhoneId();
 
@@ -650,8 +651,9 @@
 
     @VisibleForTesting
     public DcTracker() {
+        mLogTag = "DCT";
+        mTelephonyManager = null;
         mAlarmManager = null;
-        mCm = null;
         mPhone = null;
         mUiccController = null;
         mDataConnectionTracker = null;
@@ -707,7 +709,6 @@
         mPhone.getCallTracker().registerForVoiceCallStarted(this,
                 DctConstants.EVENT_VOICE_CALL_STARTED, null);
         registerServiceStateTrackerEvents();
-        mPhone.mCi.registerForPcoData(this, DctConstants.EVENT_PCO_DATA_RECEIVED, null);
         mPhone.getCarrierActionAgent().registerForCarrierAction(
                 CarrierActionAgent.CARRIER_ACTION_SET_METERED_APNS_ENABLED, this,
                 DctConstants.EVENT_SET_CARRIER_DATA_ENABLED, null, false);
@@ -727,12 +728,8 @@
             mProvisioningSpinner = null;
         }
 
-        cleanUpAllConnections(true, null);
+        cleanUpAllConnectionsInternal(true, null);
 
-        for (DcAsyncChannel dcac : mDataConnectionAcHashMap.values()) {
-            dcac.disconnect();
-        }
-        mDataConnectionAcHashMap.clear();
         mIsDisposed = true;
         mPhone.getContext().unregisterReceiver(mIntentReceiver);
         mUiccController.unregisterForIccChanged(this);
@@ -745,7 +742,7 @@
 
         mPhone.getContext().getContentResolver().unregisterContentObserver(mApnObserver);
         mApnContexts.clear();
-        mApnContextsById.clear();
+        mApnContextsByType.clear();
         mPrioritySortedApnContexts.clear();
         unregisterForAllEvents();
 
@@ -768,7 +765,6 @@
         mPhone.getCallTracker().unregisterForVoiceCallEnded(this);
         mPhone.getCallTracker().unregisterForVoiceCallStarted(this);
         unregisterServiceStateTrackerEvents();
-        mPhone.mCi.unregisterForPcoData(this);
         mPhone.getCarrierActionAgent().unregisterForCarrierAction(this,
                 CarrierActionAgent.CARRIER_ACTION_SET_METERED_APNS_ENABLED);
         mDataServiceManager.unregisterForServiceBindingChanged(this);
@@ -803,7 +799,7 @@
                 reevaluateDataConnections();
                 onTrySetupData(Phone.REASON_DATA_ENABLED);
             } else {
-                onCleanUpAllConnections(Phone.REASON_DATA_SPECIFIC_DISABLED);
+                cleanUpAllConnectionsInternal(true, Phone.REASON_DATA_SPECIFIC_DISABLED);
             }
         }
     }
@@ -813,59 +809,42 @@
      *
      * For example, handle reverting restricted networks back to unrestricted. If we're changing
      * user data to enabled and this makes data truly enabled (not disabled by other factors) we
-     * need to tear down any metered apn type that was enabled anyway by a privileged request.
-     * This allows us to reconnect to it in an unrestricted way.
+     * need to reevaluate and possibly add NET_CAPABILITY_NOT_RESTRICTED capability to the data
+     * connection. This allows non-privilege apps to use the network.
      *
      * Or when we brought up a unmetered data connection while data is off, we only limit this
      * data connection for unmetered use only. When data is turned back on, we need to tear that
      * down so a full capable data connection can be re-established.
      */
     private void reevaluateDataConnections() {
-        if (mDataEnabledSettings.isDataEnabled()) {
-            for (ApnContext apnContext : mApnContexts.values()) {
-                if (apnContext.isConnectedOrConnecting()) {
-                    final DcAsyncChannel dcac = apnContext.getDcAc();
-                    if (dcac != null) {
-                        final NetworkCapabilities netCaps = dcac.getNetworkCapabilitiesSync();
-                        if (netCaps != null && !netCaps.hasCapability(NetworkCapabilities
-                                .NET_CAPABILITY_NOT_RESTRICTED) && !netCaps.hasCapability(
-                                NetworkCapabilities.NET_CAPABILITY_NOT_METERED)) {
-                            if (DBG) {
-                                log("Tearing down restricted metered net:" + apnContext);
-                            }
-                            // Tearing down the restricted metered data call when
-                            // conditions change. This will allow reestablishing a new unrestricted
-                            // data connection.
-                            apnContext.setReason(Phone.REASON_DATA_ENABLED);
-                            cleanUpConnection(true, apnContext);
-                        } else if (apnContext.getApnSetting().isMetered(mPhone)
-                                && (netCaps != null && netCaps.hasCapability(
-                                        NetworkCapabilities.NET_CAPABILITY_NOT_METERED))) {
-                            if (DBG) {
-                                log("Tearing down unmetered net:" + apnContext);
-                            }
-                            // The APN settings is metered, but the data was still marked as
-                            // unmetered data, must be the unmetered data connection brought up when
-                            // data is off. We need to tear that down when data is enabled again.
-                            // This will allow reestablishing a new full capability data connection.
-                            apnContext.setReason(Phone.REASON_DATA_ENABLED);
-                            cleanUpConnection(true, apnContext);
-                        }
-                    }
-                }
-            }
+        for (DataConnection dataConnection : mDataConnections.values()) {
+            dataConnection.reevaluateRestrictedState();
         }
     }
 
     private void onDeviceProvisionedChange() {
+        mDataEnabledSettings.updateProvisionedChanged();
+        // TODO: We should register for DataEnabledSetting's data enabled/disabled event and
+        // handle the rest from there.
         if (isDataEnabled()) {
             reevaluateDataConnections();
             onTrySetupData(Phone.REASON_DATA_ENABLED);
         } else {
-            onCleanUpAllConnections(Phone.REASON_DATA_SPECIFIC_DISABLED);
+            cleanUpAllConnectionsInternal(true, Phone.REASON_DATA_SPECIFIC_DISABLED);
         }
     }
 
+    private void onDeviceProvisioningDataChange() {
+        mDataEnabledSettings.updateProvisioningDataEnabled();
+        // TODO: We should register for DataEnabledSetting's data enabled/disabled event and
+        // handle the rest from there.
+        if (isDataEnabled()) {
+            reevaluateDataConnections();
+            onTrySetupData(Phone.REASON_DATA_ENABLED);
+        } else {
+            cleanUpAllConnectionsInternal(true, Phone.REASON_DATA_SPECIFIC_DISABLED);
+        }
+    }
 
     public long getSubId() {
         return mPhone.getSubId();
@@ -882,40 +861,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"));
@@ -984,9 +942,9 @@
     }
 
     private ApnContext addApnContext(String type, NetworkConfig networkConfig) {
-        ApnContext apnContext = new ApnContext(mPhone, type, LOG_TAG, networkConfig, this);
+        ApnContext apnContext = new ApnContext(mPhone, type, mLogTag, networkConfig, this);
         mApnContexts.put(type, apnContext);
-        mApnContextsById.put(ApnContext.apnIdForApnName(type), apnContext);
+        mApnContextsByType.put(ApnSetting.getApnTypesBitmaskFromString(type), apnContext);
         mPrioritySortedApnContexts.add(apnContext);
         return apnContext;
     }
@@ -1044,10 +1002,10 @@
     public LinkProperties getLinkProperties(String apnType) {
         ApnContext apnContext = mApnContexts.get(apnType);
         if (apnContext != null) {
-            DcAsyncChannel dcac = apnContext.getDcAc();
-            if (dcac != null) {
-                if (DBG) log("return link properites for " + apnType);
-                return dcac.getLinkPropertiesSync();
+            DataConnection dataConnection = apnContext.getDataConnection();
+            if (dataConnection != null) {
+                if (DBG) log("return link properties for " + apnType);
+                return dataConnection.getLinkProperties();
             }
         }
         if (DBG) log("return new LinkProperties");
@@ -1057,12 +1015,12 @@
     public NetworkCapabilities getNetworkCapabilities(String apnType) {
         ApnContext apnContext = mApnContexts.get(apnType);
         if (apnContext!=null) {
-            DcAsyncChannel dataConnectionAc = apnContext.getDcAc();
-            if (dataConnectionAc != null) {
+            DataConnection dataConnection = apnContext.getDataConnection();
+            if (dataConnection != null) {
                 if (DBG) {
                     log("get active pdp is not null, return NetworkCapabilities for " + apnType);
                 }
-                return dataConnectionAc.getNetworkCapabilitiesSync();
+                return dataConnection.getNetworkCapabilities();
             }
         }
         if (DBG) log("return new NetworkCapabilities");
@@ -1090,7 +1048,7 @@
         if (apnContext != null) {
             ApnSetting apnSetting = apnContext.getApnSetting();
             if (apnSetting != null) {
-                return apnSetting.apn;
+                return apnSetting.getApnName();
             }
         }
         return null;
@@ -1104,9 +1062,10 @@
      * Assumes there is less than one {@link ApnSetting} can support the given apn type.
      */
     public DctConstants.State getState(String apnType) {
+        final int apnTypeBitmask = ApnSetting.getApnTypesBitmaskFromString(apnType);
         for (DataConnection dc : mDataConnections.values()) {
             ApnSetting apnSetting = dc.getApnSetting();
-            if (apnSetting != null && apnSetting.canHandleType(apnType)) {
+            if (apnSetting != null && apnSetting.canHandleType(apnTypeBitmask)) {
                 if (dc.isActive()) {
                     return DctConstants.State.CONNECTED;
                 } else if (dc.isActivating()) {
@@ -1245,7 +1204,8 @@
      *                              provided.
      * @return True if data connection is allowed, otherwise false.
      */
-    boolean isDataAllowed(ApnContext apnContext, DataConnectionReasons dataConnectionReasons) {
+    public boolean isDataAllowed(ApnContext apnContext,
+                                 DataConnectionReasons dataConnectionReasons) {
         // Step 1: Get all environment conditions.
         // Step 2: Special handling for emergency APN.
         // Step 3. Build disallowed reasons.
@@ -1271,7 +1231,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.
@@ -1368,11 +1328,11 @@
             reasons.add(DataAllowedReasonType.UNMETERED_APN);
         }
 
-        // If the request is restricted and there are only disallowed reasons due to data
-        // disabled, we should allow the data.
+        // 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 (apnContext != null
-                && !apnContext.hasNoRestrictedRequests(true)
-                && reasons.contains(DataDisallowedReasonType.DATA_DISABLED)) {
+                && apnContext.hasRestrictedRequests(true)
+                && !reasons.allowed()) {
             reasons.add(DataAllowedReasonType.RESTRICTED_REQUEST);
         }
 
@@ -1492,8 +1452,7 @@
                 }
             }
 
-            boolean retValue = setupData(apnContext, radioTech, dataConnectionReasons.contains(
-                    DataAllowedReasonType.UNMETERED_APN));
+            boolean retValue = setupData(apnContext, radioTech);
             notifyOffApnsOfAvailability(apnContext.getReason());
 
             if (DBG) log("trySetupData: X retValue=" + retValue);
@@ -1547,17 +1506,33 @@
     }
 
     /**
-     * If tearDown is true, this only tears down a CONNECTED session. Presently,
-     * there is no mechanism for abandoning an CONNECTING session,
-     * but would likely involve cancelling pending async requests or
-     * setting a flag or new state to ignore them when they came in
-     * @param tearDown true if the underlying DataConnection should be
-     * disconnected.
+     * Clean up all data connections. Note this is just detach the APN context from the data
+     * connection. After all APN contexts are detached from the data connection, the data
+     * connection will be torn down.
+     *
+     * @param reason Reason for the clean up.
+     */
+    public void cleanUpAllConnections(String reason) {
+        log("cleanUpAllConnections");
+        Message msg = obtainMessage(DctConstants.EVENT_CLEAN_UP_ALL_CONNECTIONS);
+        msg.obj = reason;
+        sendMessage(msg);
+    }
+
+    /**
+     * Clean up all data connections by detaching the APN contexts from the data connections, which
+     * eventually tearing down all data connections after all APN contexts are detached from the
+     * data connections.
+     *
+     * @param tearDown True if the underlying data connection should be disconnected when no
+     * APN context attached to the data connection. False if we only want to reset the data
+     * connection's state machine without requesting modem to tearing down the data connections.
+     *
      * @param reason reason for the clean up.
      * @return boolean - true if we did cleanup any connections, false if they
      *                   were already all disconnected.
      */
-    private boolean cleanUpAllConnections(boolean tearDown, String reason) {
+    private boolean cleanUpAllConnectionsInternal(boolean tearDown, String reason) {
         if (DBG) log("cleanUpAllConnections: tearDown=" + tearDown + " reason=" + reason);
         boolean didDisconnect = false;
         boolean disableMeteredOnly = false;
@@ -1575,11 +1550,11 @@
                 // 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);
-                    cleanUpConnection(tearDown, apnContext);
+                    cleanUpConnectionInternal(tearDown, apnContext);
                 }
             } else {
                 // Exclude the IMS APN from single DataConenction case.
@@ -1590,7 +1565,7 @@
                 // TODO - only do cleanup if not disconnected
                 if (apnContext.isDisconnected() == false) didDisconnect = true;
                 apnContext.setReason(reason);
-                cleanUpConnection(tearDown, apnContext);
+                cleanUpConnectionInternal(tearDown, apnContext);
             }
         }
 
@@ -1598,11 +1573,10 @@
         stopDataStallAlarm();
 
         // TODO: Do we need mRequestedApnType?
-        mRequestedApnType = PhoneConstants.APN_TYPE_DEFAULT;
+        mRequestedApnType = ApnSetting.TYPE_DEFAULT;
 
-        log("cleanUpConnection: mDisconnectPendingCount = " + mDisconnectPendingCount);
+        log("cleanUpConnectionInternal: mDisconnectPendingCount = " + mDisconnectPendingCount);
         if (tearDown && mDisconnectPendingCount == 0) {
-            notifyDataDisconnectComplete();
             notifyAllDataDisconnected();
         }
 
@@ -1610,55 +1584,55 @@
     }
 
     /**
-     * Cleanup all connections.
+     * Detach the APN context from the associated data connection. This data connection might be
+     * torn down if no other APN context is attached to it.
      *
-     * TODO: Cleanup only a specified connection passed as a parameter.
-     *       Also, make sure when you clean up a conn, if it is last apply
-     *       logic as though it is cleanupAllConnections
-     *
-     * @param cause for the clean up.
+     * @param apnContext The APN context to be detached
      */
-    private void onCleanUpAllConnections(String cause) {
-        cleanUpAllConnections(true, cause);
-    }
-
-    void sendCleanUpConnection(boolean tearDown, ApnContext apnContext) {
-        if (DBG) log("sendCleanUpConnection: tearDown=" + tearDown + " apnContext=" + apnContext);
+    void cleanUpConnection(ApnContext apnContext) {
+        if (DBG) log("cleanUpConnection: apnContext=" + apnContext);
         Message msg = obtainMessage(DctConstants.EVENT_CLEAN_UP_CONNECTION);
-        msg.arg1 = tearDown ? 1 : 0;
         msg.arg2 = 0;
         msg.obj = apnContext;
         sendMessage(msg);
     }
 
-    private void cleanUpConnection(boolean tearDown, ApnContext apnContext) {
+    /**
+     * Detach the APN context from the associated data connection. This data connection might be
+     * torn down if no other APN context is attached to it.
+     *
+     * @param tearDown True if tearing down data connection when no other APN context attached.
+     * False to only reset the data connection's state machine.
+     * @param apnContext The APN context to be detached
+     */
+    private void cleanUpConnectionInternal(boolean tearDown, ApnContext apnContext) {
         if (apnContext == null) {
-            if (DBG) log("cleanUpConnection: apn context is null");
+            if (DBG) log("cleanUpConnectionInternal: apn context is null");
             return;
         }
 
-        DcAsyncChannel dcac = apnContext.getDcAc();
-        String str = "cleanUpConnection: tearDown=" + tearDown + " reason=" +
-                apnContext.getReason();
+        DataConnection dataConnection = apnContext.getDataConnection();
+        String str = "cleanUpConnectionInternal: tearDown=" + tearDown + " reason="
+                + apnContext.getReason();
         if (VDBG) log(str + " apnContext=" + apnContext);
         apnContext.requestLog(str);
         if (tearDown) {
             if (apnContext.isDisconnected()) {
                 // The request is tearDown and but ApnContext is not connected.
-                // If apnContext is not enabled anymore, break the linkage to the DCAC/DC.
+                // If apnContext is not enabled anymore, break the linkage to the data connection.
                 apnContext.setState(DctConstants.State.IDLE);
                 if (!apnContext.isReady()) {
-                    if (dcac != null) {
-                        str = "cleanUpConnection: teardown, disconnected, !ready";
+                    if (dataConnection != null) {
+                        str = "cleanUpConnectionInternal: teardown, disconnected, !ready";
                         if (DBG) log(str + " apnContext=" + apnContext);
                         apnContext.requestLog(str);
-                        dcac.tearDown(apnContext, "", null);
+                        dataConnection.tearDown(apnContext, "", null);
                     }
-                    apnContext.setDataConnectionAc(null);
+                    apnContext.setDataConnection(null);
                 }
             } else {
                 // Connection is still there. Try to clean up.
-                if (dcac != null) {
+                if (dataConnection != null) {
                     if (apnContext.getState() != DctConstants.State.DISCONNECTING) {
                         boolean disconnectAll = false;
                         if (PhoneConstants.APN_TYPE_DUN.equals(apnContext.getApnType())) {
@@ -1666,7 +1640,7 @@
                             // if (PhoneConstants.APN_TYPE_DUN.equals(PhoneConstants.APN_TYPE_DEFAULT)) {
                             if (teardownForDun()) {
                                 if (DBG) {
-                                    log("cleanUpConnection: disconnectAll DUN connection");
+                                    log("cleanUpConnectionInternal: disconnectAll DUN connection");
                                 }
                                 // we need to tear it down - we brought it up just for dun and
                                 // other people are camped on it and now dun is done.  We need
@@ -1676,46 +1650,47 @@
                             }
                         }
                         final int generation = apnContext.getConnectionGeneration();
-                        str = "cleanUpConnection: tearing down" + (disconnectAll ? " all" : "") +
-                                " using gen#" + generation;
+                        str = "cleanUpConnectionInternal: tearing down"
+                                + (disconnectAll ? " all" : "") + " using gen#" + generation;
                         if (DBG) log(str + "apnContext=" + apnContext);
                         apnContext.requestLog(str);
-                        Pair<ApnContext, Integer> pair =
-                                new Pair<ApnContext, Integer>(apnContext, generation);
+                        Pair<ApnContext, Integer> pair = new Pair<>(apnContext, generation);
                         Message msg = obtainMessage(DctConstants.EVENT_DISCONNECT_DONE, pair);
+
                         if (disconnectAll) {
-                            apnContext.getDcAc().tearDownAll(apnContext.getReason(), msg);
+                            dataConnection.tearDownAll(apnContext.getReason(), msg);
                         } else {
-                            apnContext.getDcAc()
-                                .tearDown(apnContext, apnContext.getReason(), msg);
+                            dataConnection.tearDown(apnContext, apnContext.getReason(), msg);
                         }
+
                         apnContext.setState(DctConstants.State.DISCONNECTING);
                         mDisconnectPendingCount++;
                     }
                 } else {
-                    // apn is connected but no reference to dcac.
+                    // apn is connected but no reference to the data connection.
                     // Should not be happen, but reset the state in case.
                     apnContext.setState(DctConstants.State.IDLE);
-                    apnContext.requestLog("cleanUpConnection: connected, bug no DCAC");
+                    apnContext.requestLog("cleanUpConnectionInternal: connected, bug no dc");
                     mPhone.notifyDataConnection(apnContext.getReason(),
                                                 apnContext.getApnType());
                 }
             }
         } else {
             // force clean up the data connection.
-            if (dcac != null) dcac.reqReset();
+            if (dataConnection != null) dataConnection.reset();
             apnContext.setState(DctConstants.State.IDLE);
             mPhone.notifyDataConnection(apnContext.getReason(), apnContext.getApnType());
-            apnContext.setDataConnectionAc(null);
+            apnContext.setDataConnection(null);
         }
 
         // Make sure reconnection alarm is cleaned up if there is no ApnContext
         // associated to the connection.
-        if (dcac != null) {
+        if (dataConnection != null) {
             cancelReconnectAlarm(apnContext);
         }
-        str = "cleanUpConnection: X tearDown=" + tearDown + " reason=" + apnContext.getReason();
-        if (DBG) log(str + " apnContext=" + apnContext + " dcac=" + apnContext.getDcAc());
+        str = "cleanUpConnectionInternal: X tearDown=" + tearDown + " reason="
+                + apnContext.getReason();
+        if (DBG) log(str + " apnContext=" + apnContext + " dc=" + apnContext.getDataConnection());
         apnContext.requestLog(str);
     }
 
@@ -1730,39 +1705,21 @@
             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>();
         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).
+        // APN database
         String apnData = Settings.Global.getString(mResolver, Settings.Global.TETHER_DUN_APN);
         if (!TextUtils.isEmpty(apnData)) {
             dunCandidates.addAll(ApnSetting.arrayFromString(apnData));
             if (VDBG) log("fetchDunApns: dunCandidates from Setting: " + dunCandidates);
         }
 
-        // todo: remove this and config_tether_apndata after APNs are moved from overlay to apns xml
-        // If TETHER_DUN_APN isn't set or APN database doesn't have dun APN,
-        // try the resource as last resort.
-        if (dunCandidates.isEmpty()) {
-            String[] apnArrayData = mPhone.getContext().getResources()
-                .getStringArray(R.array.config_tether_apndata);
-            if (!ArrayUtils.isEmpty(apnArrayData)) {
-                for (String apnString : apnArrayData) {
-                    ApnSetting apn = ApnSetting.fromString(apnString);
-                    // apn may be null if apnString isn't valid or has error parsing
-                    if (apn != null) dunCandidates.add(apn);
-                }
-                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);
                     }
                 }
@@ -1771,20 +1728,11 @@
         }
 
         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.hasMvnoParams()) {
-                    if (r != null && ApnSetting.mvnoMatches(r, dunSetting.mvnoType,
-                            dunSetting.mvnoMatchData)) {
-                        retDunSettings.add(dunSetting);
-                    }
-                } else if (mMvnoMatched == false) {
-                    retDunSettings.add(dunSetting);
-                }
-            }
+            retDunSettings.add(dunSetting);
         }
 
         if (VDBG) log("fetchDunApns: dunSettings=" + retDunSettings);
@@ -1796,13 +1744,24 @@
                 .query(Uri.withAppendedPath(Telephony.Carriers.CONTENT_URI,
                     "preferapnset/subId/" + mPhone.getSubId()),
                         new String[] {Telephony.Carriers.APN_SET_ID}, null, null, null);
+        if (c == null) {
+            loge("getPreferredApnSetId: cursor is null");
+            return Telephony.Carriers.NO_APN_SET_ID;
+        }
+
+        int setId;
         if (c.getCount() < 1) {
             loge("getPreferredApnSetId: no APNs found");
-            return Telephony.Carriers.NO_SET_SET;
+            setId = Telephony.Carriers.NO_APN_SET_ID;
         } else {
             c.moveToFirst();
-            return c.getInt(0 /* index of Telephony.Carriers.APN_SET_ID */);
+            setId = c.getInt(0 /* index of Telephony.Carriers.APN_SET_ID */);
         }
+
+        if (!c.isClosed()) {
+            c.close();
+        }
+        return setId;
     }
 
     public boolean hasMatchedTetherApnSetting() {
@@ -1841,132 +1800,25 @@
         }
     }
 
-    /**
-     * @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)),
-                cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.APN_SET_ID)));
-        return apn;
-    }
-
-    private ArrayList<ApnSetting> createApnList(Cursor cursor) {
-        ArrayList<ApnSetting> mnoApns = new ArrayList<ApnSetting>();
-        ArrayList<ApnSetting> mvnoApns = new ArrayList<ApnSetting>();
-        IccRecords r = mIccRecords.get();
-
-        if (cursor.moveToFirst()) {
-            do {
-                ApnSetting apn = makeApnSetting(cursor);
-                if (apn == null) {
-                    continue;
+    private DataConnection findFreeDataConnection() {
+        for (DataConnection dataConnection : mDataConnections.values()) {
+            boolean inUse = false;
+            for (ApnContext apnContext : mApnContexts.values()) {
+                if (apnContext.getDataConnection() == dataConnection) {
+                    inUse = true;
+                    break;
                 }
-
-                if (apn.hasMvnoParams()) {
-                    if (r != null && ApnSetting.mvnoMatches(r, apn.mvnoType, apn.mvnoMatchData)) {
-                        mvnoApns.add(apn);
-                    }
-                } else {
-                    mnoApns.add(apn);
-                }
-            } while (cursor.moveToNext());
-        }
-
-        ArrayList<ApnSetting> result;
-        if (mvnoApns.isEmpty()) {
-            result = mnoApns;
-            mMvnoMatched = false;
-        } else {
-            result = mvnoApns;
-            mMvnoMatched = true;
-        }
-        if (DBG) log("createApnList: X result=" + result);
-        return result;
-    }
-
-    private boolean dataConnectionNotInUse(DcAsyncChannel dcac) {
-        if (DBG) log("dataConnectionNotInUse: check if dcac is inuse dcac=" + dcac);
-        for (ApnContext apnContext : mApnContexts.values()) {
-            if (apnContext.getDcAc() == dcac) {
-                if (DBG) log("dataConnectionNotInUse: in use by apnContext=" + apnContext);
-                return false;
             }
-        }
-        // TODO: Fix retry handling so free DataConnections have empty apnlists.
-        // Probably move retry handling into DataConnections and reduce complexity
-        // of DCT.
-        if (DBG) log("dataConnectionNotInUse: tearDownAll");
-        dcac.tearDownAll("No connection", null);
-        if (DBG) log("dataConnectionNotInUse: not in use return true");
-        return true;
-    }
-
-    private DcAsyncChannel findFreeDataConnection() {
-        for (DcAsyncChannel dcac : mDataConnectionAcHashMap.values()) {
-            if (dcac.isInactiveSync() && dataConnectionNotInUse(dcac)) {
+            if (!inUse) {
                 if (DBG) {
-                    log("findFreeDataConnection: found free DataConnection=" +
-                        " dcac=" + dcac);
+                    log("findFreeDataConnection: found free DataConnection=" + dataConnection);
                 }
-                return dcac;
+                return dataConnection;
             }
         }
         log("findFreeDataConnection: NO free DataConnection");
@@ -1978,15 +1830,13 @@
      *
      * @param apnContext APN context
      * @param radioTech RAT of the data connection
-     * @param unmeteredUseOnly True if this data connection should be only used for unmetered
-     *                         purposes only.
      * @return True if successful, otherwise false.
      */
-    private boolean setupData(ApnContext apnContext, int radioTech, boolean unmeteredUseOnly) {
+    private boolean setupData(ApnContext apnContext, int radioTech) {
         if (DBG) log("setupData: apnContext=" + apnContext);
         apnContext.requestLog("setupData");
         ApnSetting apnSetting;
-        DcAsyncChannel dcac = null;
+        DataConnection dataConnection = null;
 
         apnSetting = apnContext.getNextApnSetting();
 
@@ -1995,9 +1845,13 @@
             return false;
         }
 
-        int profileId = apnSetting.profileId;
-        if (profileId == 0) {
-            profileId = getApnProfileID(apnContext.getApnType());
+        // profile id is only meaningful when the profile is persistent on the modem.
+        int profileId = DATA_PROFILE_INVALID;
+        if (apnSetting.isPersistent()) {
+            profileId = apnSetting.getProfileId();
+            if (profileId == DATA_PROFILE_DEFAULT) {
+                profileId = getApnProfileID(apnContext.getApnType());
+            }
         }
 
         // On CDMA, if we're explicitly asking for DUN, we need have
@@ -2006,17 +1860,17 @@
         // this type.
         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.
-                ApnSetting dcacApnSetting = dcac.getApnSettingSync();
-                if (dcacApnSetting != null) {
+            dataConnection = checkForCompatibleConnectedApnContext(apnContext);
+            if (dataConnection != null) {
+                // Get the apn setting used by the data connection
+                ApnSetting dataConnectionApnSetting = dataConnection.getApnSetting();
+                if (dataConnectionApnSetting != null) {
                     // Setting is good, so use it.
-                    apnSetting = dcacApnSetting;
+                    apnSetting = dataConnectionApnSetting;
                 }
             }
         }
-        if (dcac == null) {
+        if (dataConnection == null) {
             if (isOnlySingleDcAllowed(radioTech)) {
                 if (isHigherPriorityApnContextActive(apnContext)) {
                     if (DBG) {
@@ -2029,7 +1883,7 @@
                     // Only lower priority calls left.  Disconnect them all in this single PDP case
                     // so that we can bring up the requested higher priority call (once we receive
                     // response for deactivate request for the calls we are about to disconnect
-                    if (cleanUpAllConnections(true, Phone.REASON_SINGLE_PDN_ARBITRATION)) {
+                    if (cleanUpAllConnectionsInternal(true, Phone.REASON_SINGLE_PDN_ARBITRATION)) {
                         // If any call actually requested to be disconnected, means we can't
                         // bring up this connection yet as we need to wait for those data calls
                         // to be disconnected.
@@ -2043,23 +1897,24 @@
                 if (DBG) log("setupData: Single pdp. Continue setting up data call.");
             }
 
-            dcac = findFreeDataConnection();
+            dataConnection = findFreeDataConnection();
 
-            if (dcac == null) {
-                dcac = createDataConnection();
+            if (dataConnection == null) {
+                dataConnection = createDataConnection();
             }
 
-            if (dcac == null) {
+            if (dataConnection == null) {
                 if (DBG) log("setupData: No free DataConnection and couldn't create one, WEIRD");
                 return false;
             }
         }
         final int generation = apnContext.incAndGetConnectionGeneration();
         if (DBG) {
-            log("setupData: dcac=" + dcac + " apnSetting=" + apnSetting + " gen#=" + generation);
+            log("setupData: dc=" + dataConnection + " apnSetting=" + apnSetting + " gen#="
+                    + generation);
         }
 
-        apnContext.setDataConnectionAc(dcac);
+        apnContext.setDataConnection(dataConnection);
         apnContext.setApnSetting(apnSetting);
         apnContext.setState(DctConstants.State.CONNECTING);
         mPhone.notifyDataConnection(apnContext.getReason(), apnContext.getApnType());
@@ -2067,7 +1922,7 @@
         Message msg = obtainMessage();
         msg.what = DctConstants.EVENT_DATA_SETUP_COMPLETE;
         msg.obj = new Pair<ApnContext, Integer>(apnContext, generation);
-        dcac.bringUp(apnContext, profileId, radioTech, unmeteredUseOnly, msg, generation);
+        dataConnection.bringUp(apnContext, profileId, radioTech, msg, generation);
 
         if (DBG) log("setupData: initing!");
         return true;
@@ -2080,21 +1935,21 @@
 
         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()) {
+        } else if (!mAllApnSettings.isEmpty()) {
             firstApnSetting = mAllApnSettings.get(0);
             log("setInitialApn: firstApnSetting=" + firstApnSetting);
 
             // 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;
@@ -2128,7 +1983,8 @@
         } else {
             if (DBG) log("setInitialAttachApn: X selected Apn=" + initialAttachApnSetting);
 
-            mDataServiceManager.setInitialAttachApn(createDataProfile(initialAttachApnSetting),
+            mDataServiceManager.setInitialAttachApn(createDataProfile(initialAttachApnSetting,
+                            initialAttachApnSetting.equals(getPreferredApn())),
                     mPhone.getServiceState().getDataRoamingFromRegistration(), null);
         }
     }
@@ -2150,6 +2006,7 @@
         // match the current operator.
         if (DBG) log("onApnChanged: createAllApnList and cleanUpAllConnections");
         createAllApnList();
+        setDataProfilesAsNeeded();
         setInitialAttachApn();
         cleanUpConnectionsOnUpdatedApns(!isDisconnected, Phone.REASON_APN_CHANGED);
 
@@ -2160,19 +2017,6 @@
     }
 
     /**
-     * @param cid Connection id provided from RIL.
-     * @return DataConnectionAc associated with specified cid.
-     */
-    private DcAsyncChannel findDataConnectionAcByCid(int cid) {
-        for (DcAsyncChannel dcac : mDataConnectionAcHashMap.values()) {
-            if (dcac.getCidSync() == cid) {
-                return dcac;
-            }
-        }
-        return null;
-    }
-
-    /**
      * "Active" here means ApnContext isEnabled() and not in FAILED state
      * @param apnContext to compare with
      * @return true if higher priority active apn found
@@ -2209,7 +2053,7 @@
         CarrierConfigManager configManager = (CarrierConfigManager)
                 mPhone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
         if (configManager != null) {
-            PersistableBundle bundle = configManager.getConfig();
+            PersistableBundle bundle = configManager.getConfigForSubId(mPhone.getSubId());
             if (bundle != null) {
                 singleDcRats = bundle.getIntArray(
                         CarrierConfigManager.KEY_ONLY_SINGLE_DC_ALLOWED_INT_ARRAY);
@@ -2238,8 +2082,8 @@
 
     private void restartRadio() {
         if (DBG) log("restartRadio: ************TURN OFF RADIO**************");
-        cleanUpAllConnections(true, Phone.REASON_RADIO_TURNED_OFF);
-        mPhone.getServiceStateTracker().powerOffRadioSafely(this);
+        cleanUpAllConnectionsInternal(true, Phone.REASON_RADIO_TURNED_OFF);
+        mPhone.getServiceStateTracker().powerOffRadioSafely();
         /* Note: no need to call setRadioPower(true).  Assuming the desired
          * radio power state is still ON (as tracked by ServiceStateTracker),
          * ServiceStateTracker will call setRadioPower when it receives the
@@ -2277,12 +2121,10 @@
         Intent intent = new Intent(INTENT_RECONNECT_ALARM + "." + apnType);
         intent.putExtra(INTENT_RECONNECT_ALARM_EXTRA_REASON, apnContext.getReason());
         intent.putExtra(INTENT_RECONNECT_ALARM_EXTRA_TYPE, apnType);
+        intent.putExtra(INTENT_RECONNECT_ALARM_EXTRA_TRANSPORT_TYPE, mTransportType);
+        SubscriptionManager.putPhoneIdAndSubIdExtra(intent, mPhone.getPhoneId());
         intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
 
-        // Get current sub id.
-        int subId = mPhone.getSubId();
-        intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId);
-
         if (DBG) {
             log("startAlarmForReconnect: delay=" + delay + " action=" + intent.getAction()
                     + " apn=" + apnContext);
@@ -2319,8 +2161,9 @@
                 .getBoolean(com.android.internal.R.bool.config_auto_attach_data_on_creation);
 
         createAllApnList();
+        setDataProfilesAsNeeded();
         setInitialAttachApn();
-        if (mPhone.mCi.getRadioState().isOn()) {
+        if (mPhone.mCi.getRadioState() == TelephonyManager.RADIO_POWER_ON) {
             if (DBG) log("onRecordsLoadedOrSubIdChanged: notifying data availability");
             notifyOffApnsOfAvailability(Phone.REASON_SIM_LOADED);
         }
@@ -2332,7 +2175,7 @@
      */
     private void onSetCarrierDataEnabled(AsyncResult ar) {
         if (ar.exception != null) {
-            Rlog.e(LOG_TAG, "CarrierDataEnable exception: " + ar.exception);
+            loge("CarrierDataEnable exception: " + ar.exception);
             return;
         }
         boolean enabled = (boolean) ar.result;
@@ -2348,7 +2191,8 @@
                 // Send otasp_sim_unprovisioned so that SuW is able to proceed and notify users
                 mPhone.notifyOtaspChanged(TelephonyManager.OTASP_SIM_UNPROVISIONED);
                 // Tear down all metered apns
-                cleanUpAllConnections(true, Phone.REASON_CARRIER_ACTION_DISABLE_METERED_APN);
+                cleanUpAllConnectionsInternal(true,
+                        Phone.REASON_CARRIER_ACTION_DISABLE_METERED_APN);
             } else {
                 // Re-evaluate Otasp state
                 int otaspState = mPhone.getServiceStateTracker().getOtasp();
@@ -2363,29 +2207,16 @@
     private void onSimNotReady() {
         if (DBG) log("onSimNotReady");
 
-        cleanUpAllConnections(true, Phone.REASON_SIM_NOT_READY);
-        mAllApnSettings = null;
+        cleanUpAllConnectionsInternal(true, Phone.REASON_SIM_NOT_READY);
+        mAllApnSettings.clear();
         mAutoAttachOnCreationConfig = false;
         // Clear auto attach as modem is expected to do a new attach once SIM is ready
         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);
-        }
+        mOnSubscriptionsChangedListener.mPreviousSubId.set(
+                SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+        // In no-sim case, we should still send the emergency APN to the modem, if there is any.
+        createAllApnList();
+        setDataProfilesAsNeeded();
     }
 
     public void setPolicyDataEnabled(boolean enabled) {
@@ -2406,7 +2237,7 @@
                     reevaluateDataConnections();
                     onTrySetupData(Phone.REASON_DATA_ENABLED);
                 } else {
-                    onCleanUpAllConnections(Phone.REASON_DATA_SPECIFIC_DISABLED);
+                    cleanUpAllConnectionsInternal(true, Phone.REASON_DATA_SPECIFIC_DISABLED);
                 }
             }
         }
@@ -2484,29 +2315,29 @@
         }
         apnContext.setEnabled(enabled);
         apnContext.setDependencyMet(met);
-        if (cleanup) cleanUpConnection(true, apnContext);
+        if (cleanup) cleanUpConnectionInternal(true, apnContext);
         if (trySetup) {
             apnContext.resetErrorCodeRetries();
             trySetupData(apnContext);
         }
     }
 
-    private DcAsyncChannel checkForCompatibleConnectedApnContext(ApnContext apnContext) {
-        String apnType = apnContext.getApnType();
+    private DataConnection checkForCompatibleConnectedApnContext(ApnContext apnContext) {
+        int apnType = apnContext.getApnTypeBitmask();
         ArrayList<ApnSetting> dunSettings = null;
 
-        if (PhoneConstants.APN_TYPE_DUN.equals(apnType)) {
+        if (ApnSetting.TYPE_DUN == apnType) {
             dunSettings = sortApnListByPreferred(fetchDunApns());
         }
         if (DBG) {
             log("checkForCompatibleConnectedApnContext: apnContext=" + apnContext );
         }
 
-        DcAsyncChannel potentialDcac = null;
+        DataConnection potentialDc = null;
         ApnContext potentialApnCtx = null;
         for (ApnContext curApnCtx : mApnContexts.values()) {
-            DcAsyncChannel curDcac = curApnCtx.getDcAc();
-            if (curDcac != null) {
+            DataConnection curDc = curApnCtx.getDataConnection();
+            if (curDc != null) {
                 ApnSetting apnSetting = curApnCtx.getApnSetting();
                 log("apnSetting: " + apnSetting);
                 if (dunSettings != null && dunSettings.size() > 0) {
@@ -2516,13 +2347,13 @@
                                 case CONNECTED:
                                     if (DBG) {
                                         log("checkForCompatibleConnectedApnContext:"
-                                                + " found dun conn=" + curDcac
+                                                + " found dun conn=" + curDc
                                                 + " curApnCtx=" + curApnCtx);
                                     }
-                                    return curDcac;
+                                    return curDc;
                                 case RETRYING:
                                 case CONNECTING:
-                                    potentialDcac = curDcac;
+                                    potentialDc = curDc;
                                     potentialApnCtx = curApnCtx;
                                     break;
                                 default:
@@ -2536,13 +2367,13 @@
                         case CONNECTED:
                             if (DBG) {
                                 log("checkForCompatibleConnectedApnContext:"
-                                        + " found canHandle conn=" + curDcac
+                                        + " found canHandle conn=" + curDc
                                         + " curApnCtx=" + curApnCtx);
                             }
-                            return curDcac;
+                            return curDc;
                         case RETRYING:
                         case CONNECTING:
-                            potentialDcac = curDcac;
+                            potentialDc = curDc;
                             potentialApnCtx = curApnCtx;
                             break;
                         default:
@@ -2556,29 +2387,29 @@
                 }
             }
         }
-        if (potentialDcac != null) {
+        if (potentialDc != null) {
             if (DBG) {
-                log("checkForCompatibleConnectedApnContext: found potential conn=" + potentialDcac
+                log("checkForCompatibleConnectedApnContext: found potential conn=" + potentialDc
                         + " curApnCtx=" + potentialApnCtx);
             }
-            return potentialDcac;
+            return potentialDc;
         }
 
         if (DBG) log("checkForCompatibleConnectedApnContext: NO conn apnContext=" + apnContext);
         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
@@ -2630,7 +2461,7 @@
             int roaming = enabled ? 1 : 0;
 
             // For single SIM phones, this is a per phone property.
-            if (TelephonyManager.getDefault().getSimCount() == 1) {
+            if (mTelephonyManager.getSimCount() == 1) {
                 Settings.Global.putInt(mResolver, Settings.Global.DATA_ROAMING, roaming);
                 setDataRoamingFromUserAction(true);
             } else {
@@ -2660,7 +2491,7 @@
         final int phoneSubId = mPhone.getSubId();
 
         // For single SIM phones, this is a per phone property.
-        if (TelephonyManager.getDefault().getSimCount() == 1) {
+        if (mTelephonyManager.getSimCount() == 1) {
             isDataRoamingEnabled = Settings.Global.getInt(mResolver,
                     Settings.Global.DATA_ROAMING,
                     getDefaultDataRoamingEnabled() ? 1 : 0) != 0;
@@ -2702,7 +2533,7 @@
         // For single SIM phones, this is a per phone property.
         String setting = Settings.Global.DATA_ROAMING;
         boolean useCarrierSpecificDefault = false;
-        if (TelephonyManager.getDefault().getSimCount() != 1) {
+        if (mTelephonyManager.getSimCount() != 1) {
             setting = setting + mPhone.getSubId();
             try {
                 Settings.Global.getInt(mResolver, setting);
@@ -2745,13 +2576,15 @@
     private void onDataRoamingOff() {
         if (DBG) log("onDataRoamingOff");
 
+        reevaluateDataConnections();
+
         if (!getDataRoamingEnabled()) {
             // TODO: Remove this once all old vendor RILs are gone. We don't need to set initial apn
             // attach and send the data profile again as the modem should have both roaming and
             // non-roaming protocol in place. Modem should choose the right protocol based on the
             // roaming condition.
-            setInitialAttachApn();
             setDataProfilesAsNeeded();
+            setInitialAttachApn();
 
             // If the user did not enable data roaming, now when we transit from roaming to
             // non-roaming, we should try to reestablish the data connection.
@@ -2780,6 +2613,13 @@
         checkDataRoamingStatus(settingChanged);
 
         if (getDataRoamingEnabled()) {
+            // If the restricted data was brought up when data roaming is disabled, and now users
+            // enable data roaming, we need to re-evaluate the conditions and possibly change the
+            // network's capability.
+            if (settingChanged) {
+                reevaluateDataConnections();
+            }
+
             if (DBG) log("onDataRoamingOnOrSettingsChanged: setup data on roaming");
 
             setupDataOnConnectableApns(Phone.REASON_ROAMING_ON);
@@ -2789,7 +2629,7 @@
             // roaming, we need to tear down the data connection otherwise the user might be
             // charged for data roaming usage.
             if (DBG) log("onDataRoamingOnOrSettingsChanged: Tear down data connection on roaming.");
-            cleanUpAllConnections(true, Phone.REASON_ROAMING_ON);
+            cleanUpAllConnectionsInternal(true, Phone.REASON_ROAMING_ON);
             notifyOffApnsOfAvailability(Phone.REASON_ROAMING_ON);
         }
     }
@@ -2803,8 +2643,8 @@
             for (ApnContext apnContext : mApnContexts.values()) {
                 if (apnContext.getState() == DctConstants.State.CONNECTED) {
                     mDataRoamingLeakageLog.log("PossibleRoamingLeakage "
-                            + " connection params: " + (apnContext.getDcAc() != null
-                            ? apnContext.getDcAc().mLastConnectionParams : ""));
+                            + " connection params: " + (apnContext.getDataConnection() != null
+                            ? apnContext.getDataConnection().getConnectionParams() : ""));
                 }
             }
         }
@@ -2827,7 +2667,7 @@
         }
 
         if (getOverallState() != DctConstants.State.IDLE) {
-            cleanUpConnection(true, null);
+            cleanUpConnectionInternal(true, null);
         }
     }
 
@@ -2846,7 +2686,7 @@
             log("We're on the simulator; assuming radio off is meaningless");
         } else {
             if (DBG) log("onRadioOffOrNotAvailable: is off and clean up all connections");
-            cleanUpAllConnections(false, Phone.REASON_RADIO_TURNED_OFF);
+            cleanUpAllConnectionsInternal(false, Phone.REASON_RADIO_TURNED_OFF);
         }
         notifyOffApnsOfAvailability(null);
     }
@@ -2896,7 +2736,7 @@
         if (apnContext == null) return;
 
         if (ar.exception == null) {
-            DcAsyncChannel dcac = apnContext.getDcAc();
+            DataConnection dataConnection = apnContext.getDataConnection();
 
             if (RADIO_TESTS) {
                 // Note: To change radio.test.onDSC.null.dcac from command line you need to
@@ -2909,32 +2749,34 @@
                 if (Settings.System.getInt(cr, radioTestProperty, 0) == 1) {
                     log("onDataSetupComplete: " + radioTestProperty +
                             " is true, set dcac to null and reset property to false");
-                    dcac = null;
+                    dataConnection = null;
                     Settings.System.putInt(cr, radioTestProperty, 0);
                     log("onDataSetupComplete: " + radioTestProperty + "=" +
                             Settings.System.getInt(mPhone.getContext().getContentResolver(),
                                     radioTestProperty, -1));
                 }
             }
-            if (dcac == null) {
+            if (dataConnection == null) {
                 log("onDataSetupComplete: no connection to DC, handle as error");
                 cause = DcFailCause.CONNECTION_TO_DATACONNECTIONAC_BROKEN;
                 handleError = true;
             } 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);
-                        dcac.setLinkPropertiesHttpProxySync(proxy);
+                        int port = apn.getProxyPort();
+                        if (port == -1) {
+                            port = 8080;
+                        }
+                        ProxyInfo proxy = new ProxyInfo(apn.getProxyAddressAsString(), port, null);
+                        dataConnection.setLinkPropertiesHttpProxy(proxy);
                     } catch (NumberFormatException e) {
-                        loge("onDataSetupComplete: NumberFormatException making ProxyProperties (" +
-                                apn.port + "): " + e);
+                        loge("onDataSetupComplete: NumberFormatException making ProxyProperties ("
+                                + apn.getProxyPort() + "): " + e);
                     }
                 }
 
@@ -2949,7 +2791,7 @@
                         if (DBG) log("onDataSetupComplete: PREFERRED APN is null");
                         mPreferredApn = apn;
                         if (mPreferredApn != null) {
-                            setPreferredApn(mPreferredApn.id);
+                            setPreferredApn(mPreferredApn.getId());
                         }
                     }
                 } else {
@@ -2995,7 +2837,7 @@
                     // disappears when radio is off.
                     mProvisionBroadcastReceiver = new ProvisionNotificationBroadcastReceiver(
                             cm.getMobileProvisioningUrl(),
-                            TelephonyManager.getDefault().getNetworkOperatorName());
+                            mTelephonyManager.getNetworkOperatorName());
                     mPhone.getContext().registerReceiver(mProvisionBroadcastReceiver,
                             new IntentFilter(mProvisionActionName));
                     // Put up user notification that sign-in is required.
@@ -3032,17 +2874,18 @@
             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.
                 int cid = getCellLocationId();
                 EventLog.writeEvent(EventLogTags.PDP_SETUP_FAIL,
-                        cause.ordinal(), cid, TelephonyManager.getDefault().getNetworkType());
+                        cause.ordinal(), cid, mTelephonyManager.getNetworkType());
             }
             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
@@ -3051,8 +2894,8 @@
             intent.putExtra(TelephonyIntents.EXTRA_APN_TYPE_KEY, apnContext.getApnType());
             mPhone.getCarrierSignalAgent().notifyCarrierSignalReceivers(intent);
 
-            if (cause.isRestartRadioFail(mPhone.getContext(), mPhone.getSubId()) ||
-                    apnContext.restartOnError(cause.getErrorCode())) {
+            if (cause.isRadioRestartFailure(mPhone.getContext(), mPhone.getSubId())
+                    || apnContext.restartOnError(cause.getErrorCode())) {
                 if (DBG) log("Modem restarted.");
                 sendRestartRadio();
             }
@@ -3075,7 +2918,7 @@
          * to clean data connections.
          */
         if (!mDataEnabledSettings.isInternalDataEnabled()) {
-            cleanUpAllConnections(Phone.REASON_DATA_DISABLED);
+            cleanUpAllConnectionsInternal(true, Phone.REASON_DATA_DISABLED);
         }
 
     }
@@ -3130,7 +2973,7 @@
             // This would be the final state of a data connection.
             apnContext.setState(DctConstants.State.FAILED);
             mPhone.notifyDataConnection(Phone.REASON_APN_FAILED, apnContext.getApnType());
-            apnContext.setDataConnectionAc(null);
+            apnContext.setDataConnection(null);
             log("onDataSetupCompleteError: Stop retrying APNs.");
         }
     }
@@ -3166,7 +3009,7 @@
                 if (DBG) log("onDisconnectDone: radio will be turned off, no retries");
                 // Radio will be turned off. No need to retry data setup
                 apnContext.setApnSetting(null);
-                apnContext.setDataConnectionAc(null);
+                apnContext.setDataConnection(null);
 
                 // Need to notify disconnect as well, in the case of switching Airplane mode.
                 // Otherwise, it would cause 30s delayed to turn on Airplane mode.
@@ -3175,7 +3018,6 @@
                 }
 
                 if (mDisconnectPendingCount == 0) {
-                    notifyDataDisconnectComplete();
                     notifyAllDataDisconnected();
                 }
                 return;
@@ -3207,7 +3049,7 @@
                 restartRadio();
             }
             apnContext.setApnSetting(null);
-            apnContext.setDataConnectionAc(null);
+            apnContext.setDataConnection(null);
             if (isOnlySingleDcAllowed(mPhone.getServiceState().getRilDataRadioTechnology())) {
                 if(DBG) log("onDisconnectDone: isOnlySigneDcAllowed true so setup single apn");
                 setupDataOnConnectableApns(Phone.REASON_SINGLE_PDN_ARBITRATION);
@@ -3222,7 +3064,6 @@
         if (mDisconnectPendingCount == 0) {
             apnContext.setConcurrentVoiceAndDataAllowed(
                     mPhone.getServiceStateTracker().isConcurrentVoiceAndDataAllowed());
-            notifyDataDisconnectComplete();
             notifyAllDataDisconnected();
         }
 
@@ -3270,12 +3111,12 @@
         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);
+            cleanUpConnectionInternal(tearDown, apnContext);
         }
     }
 
@@ -3315,20 +3156,24 @@
 
     private void setDataProfilesAsNeeded() {
         if (DBG) log("setDataProfilesAsNeeded");
-        if (mAllApnSettings != null && !mAllApnSettings.isEmpty()) {
-            ArrayList<DataProfile> dps = new ArrayList<DataProfile>();
-            for (ApnSetting apn : mAllApnSettings) {
-                if (apn.modemCognitive) {
-                    DataProfile dp = createDataProfile(apn);
-                    if (!dps.contains(dp)) {
-                        dps.add(dp);
-                    }
-                }
+
+        ArrayList<DataProfile> dataProfileList = new ArrayList<>();
+
+        for (ApnSetting apn : mAllApnSettings) {
+            DataProfile dp = createDataProfile(apn, apn.equals(getPreferredApn()));
+            if (!dataProfileList.contains(dp)) {
+                dataProfileList.add(dp);
             }
-            if (dps.size() > 0) {
-                mDataServiceManager.setDataProfile(dps,
-                        mPhone.getServiceState().getDataRoamingFromRegistration(), null);
-            }
+        }
+
+        // Check if the data profiles we are sending are same as we did last time. We don't want to
+        // send the redundant profiles to the modem. Also if there the list is empty, we don't
+        // send it to the modem.
+        if (!dataProfileList.isEmpty()
+                && (dataProfileList.size() != mLastDataProfileList.size()
+                || !mLastDataProfileList.containsAll(dataProfileList))) {
+            mDataServiceManager.setDataProfile(dataProfileList,
+                    mPhone.getServiceState().getDataRoamingFromRegistration(), null);
         }
     }
 
@@ -3337,28 +3182,28 @@
      * Data Connections and setup the preferredApn.
      */
     private void createAllApnList() {
-        mMvnoMatched = false;
-        mAllApnSettings = new ArrayList<>();
+        mAllApnSettings.clear();
         IccRecords r = mIccRecords.get();
         String operator = (r != null) ? r.getOperatorNumeric() : "";
-        if (operator != null) {
-            String selection = Telephony.Carriers.NUMERIC + " = '" + operator + "'";
-            // query only enabled apn.
-            // carrier_enabled : 1 means enabled apn, 0 disabled apn.
-            // selection += " and carrier_enabled = 1";
-            if (DBG) log("createAllApnList: selection=" + selection);
 
-            // ORDER BY Telephony.Carriers._ID ("_id")
-            Cursor cursor = mPhone.getContext().getContentResolver().query(
-                    Uri.withAppendedPath(Telephony.Carriers.CONTENT_URI, "filtered"),
-                    null, selection, null, Telephony.Carriers._ID);
+        // ORDER BY Telephony.Carriers._ID ("_id")
+        Cursor cursor = mPhone.getContext().getContentResolver().query(
+                Uri.withAppendedPath(Telephony.Carriers.CONTENT_URI, "filtered/subId/"
+                        + mPhone.getSubId()), null, null, null, Telephony.Carriers._ID);
 
-            if (cursor != null) {
-                if (cursor.getCount() > 0) {
-                    mAllApnSettings = createApnList(cursor);
+        if (cursor != null) {
+            while (cursor.moveToNext()) {
+                ApnSetting apn = ApnSetting.makeApnSetting(cursor);
+                if (apn == null) {
+                    continue;
                 }
-                cursor.close();
+                mAllApnSettings.add(apn);
             }
+            cursor.close();
+        } else {
+            if (DBG) log("createAllApnList: cursor is null");
+            mApnSettingsInitializationLog.log("cursor is null for carrier, operator: "
+                    + operator);
         }
 
         addEmergencyApnSetting();
@@ -3366,21 +3211,21 @@
         dedupeApnSettings();
 
         if (mAllApnSettings.isEmpty()) {
-            if (DBG) log("createAllApnList: No APN found for carrier: " + operator);
+            log("createAllApnList: No APN found for carrier, operator: " + operator);
+            mApnSettingsInitializationLog.log("no APN found for carrier, operator: "
+                    + 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);
             }
             if (DBG) log("createAllApnList: mPreferredApn=" + mPreferredApn);
         }
         if (DBG) log("createAllApnList: X mAllApnSettings=" + mAllApnSettings);
-
-        setDataProfilesAsNeeded();
     }
 
     private void dedupeApnSettings() {
@@ -3409,56 +3254,44 @@
     }
 
     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, dest.apnSetId);
+        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.isPersistent() || src.isPersistent()), dest.getMaxConns(),
+            dest.getWaitTime(), dest.getMaxConnsTime(), dest.getMtu(), dest.getMvnoType(),
+            dest.getMvnoMatchData(), dest.getApnSetId(), dest.getCarrierId());
     }
 
-    /** Return the DC AsyncChannel for the new data connection */
-    private DcAsyncChannel createDataConnection() {
+    private DataConnection createDataConnection() {
         if (DBG) log("createDataConnection E");
 
         int id = mUniqueIdGenerator.getAndIncrement();
-        DataConnection conn = DataConnection.makeDataConnection(mPhone, id, this,
+        DataConnection dataConnection = DataConnection.makeDataConnection(mPhone, id, this,
                 mDataServiceManager, mDcTesterFailBringUpAll, mDcc);
-        mDataConnections.put(id, conn);
-        DcAsyncChannel dcac = new DcAsyncChannel(conn, LOG_TAG);
-        int status = dcac.fullyConnectSync(mPhone.getContext(), this, conn.getHandler());
-        if (status == AsyncChannel.STATUS_SUCCESSFUL) {
-            mDataConnectionAcHashMap.put(dcac.getDataConnectionIdSync(), dcac);
-        } else {
-            loge("createDataConnection: Could not connect to dcac=" + dcac + " status=" + status);
-        }
-
-        if (DBG) log("createDataConnection() X id=" + id + " dc=" + conn);
-        return dcac;
+        mDataConnections.put(id, dataConnection);
+        if (DBG) log("createDataConnection() X id=" + id + " dc=" + dataConnection);
+        return dataConnection;
     }
 
     private void destroyDataConnections() {
@@ -3481,7 +3314,8 @@
         if (DBG) log("buildWaitingApns: E requestedApnType=" + requestedApnType);
         ArrayList<ApnSetting> apnList = new ArrayList<ApnSetting>();
 
-        if (requestedApnType.equals(PhoneConstants.APN_TYPE_DUN)) {
+        int requestedApnTypeBitmask = ApnSetting.getApnTypesBitmaskFromString(requestedApnType);
+        if (requestedApnTypeBitmask == ApnSetting.TYPE_DUN) {
             ArrayList<ApnSetting> dunApns = fetchDunApns();
             if (dunApns.size() > 0) {
                 for (ApnSetting dun : dunApns) {
@@ -3520,13 +3354,13 @@
         }
 
         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);
@@ -3543,28 +3377,25 @@
                 mPreferredApn = null;
             }
         }
-        if (mAllApnSettings != null) {
-            if (DBG) log("buildWaitingApns: mAllApnSettings=" + mAllApnSettings);
-            for (ApnSetting apn : mAllApnSettings) {
-                if (apn.canHandleType(requestedApnType)) {
-                    if (ServiceState.bitmaskHasTech(apn.networkTypeBitmask,
-                            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
-                                    + "do not include radioTech:" + radioTech);
-                        }
+
+        if (DBG) log("buildWaitingApns: mAllApnSettings=" + mAllApnSettings);
+        for (ApnSetting apn : mAllApnSettings) {
+            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: networkTypeBitmask:"
+                                + apn.getNetworkTypeBitmask()
+                                + "do not include radioTech:" + radioTech);
                     }
-                } else if (DBG) {
-                    log("buildWaitingApns: couldn't handle requested ApnType="
-                            + requestedApnType);
                 }
+            } else if (DBG) {
+                log("buildWaitingApns: couldn't handle requested ApnType="
+                        + requestedApnType);
             }
-        } else {
-            loge("mAllApnSettings is null!");
         }
 
         apnList = sortApnListByPreferred(apnList);
@@ -3588,12 +3419,16 @@
     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) {
+        if (preferredApnSetId != Telephony.Carriers.NO_APN_SET_ID) {
             list.sort(new Comparator<ApnSetting>() {
                 @Override
                 public int compare(ApnSetting apn1, ApnSetting apn2) {
-                    if (apn1.apnSetId == preferredApnSetId) return -1;
-                    if (apn2.apnSetId == preferredApnSetId) return 1;
+                    if (apn1.getApnSetId() == preferredApnSetId) {
+                        return -1;
+                    }
+                    if (apn2.getApnSetId() == preferredApnSetId) {
+                        return 1;
+                    }
                     return 0;
                 }
             });
@@ -3631,9 +3466,9 @@
         }
     }
 
-    private ApnSetting getPreferredApn() {
+    ApnSetting getPreferredApn() {
         if (mAllApnSettings == null || mAllApnSettings.isEmpty()) {
-            log("getPreferredApn: mAllApnSettings is " + ((mAllApnSettings == null)?"null":"empty"));
+            log("getPreferredApn: mAllApnSettings is empty");
             return null;
         }
 
@@ -3657,7 +3492,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;
@@ -3731,10 +3566,10 @@
                 } else {
                     // TODO: Should all PDN states be checked to fail?
                     if (mState == DctConstants.State.FAILED) {
-                        cleanUpAllConnections(false, Phone.REASON_PS_RESTRICT_ENABLED);
+                        cleanUpAllConnectionsInternal(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);
@@ -3758,24 +3593,19 @@
                 break;
 
             case DctConstants.EVENT_CLEAN_UP_CONNECTION:
-                boolean tearDown = (msg.arg1 == 0) ? false : true;
-                if (DBG) log("EVENT_CLEAN_UP_CONNECTION tearDown=" + tearDown);
-                if (msg.obj instanceof ApnContext) {
-                    cleanUpConnection(tearDown, (ApnContext)msg.obj);
-                } else {
-                    onCleanUpConnection(tearDown, msg.arg2, (String) msg.obj);
-                }
+                if (DBG) log("EVENT_CLEAN_UP_CONNECTION");
+                cleanUpConnectionInternal(true, (ApnContext) msg.obj);
                 break;
             case DctConstants.EVENT_SET_INTERNAL_DATA_ENABLE: {
                 final boolean enabled = (msg.arg1 == DctConstants.ENABLED) ? true : false;
-                onSetInternalDataEnabled(enabled, (Message) msg.obj);
+                onSetInternalDataEnabled(enabled);
                 break;
             }
             case DctConstants.EVENT_CLEAN_UP_ALL_CONNECTIONS:
                 if ((msg.obj != null) && (msg.obj instanceof String == false)) {
                     msg.obj = null;
                 }
-                onCleanUpAllConnections((String) msg.obj);
+                cleanUpAllConnectionsInternal(true, (String) msg.obj);
                 break;
 
             case DctConstants.EVENT_DATA_RAT_CHANGED:
@@ -3800,13 +3630,6 @@
                     mProvisioningSpinner = null;
                 }
                 break;
-            case AsyncChannel.CMD_CHANNEL_DISCONNECTED: {
-                log("DISCONNECTED_CONNECTED: msg=" + msg);
-                DcAsyncChannel dcac = (DcAsyncChannel) msg.obj;
-                mDataConnectionAcHashMap.remove(dcac.getDataConnectionIdSync());
-                dcac.disconnected();
-                break;
-            }
             case DctConstants.EVENT_ENABLE_NEW_APN:
                 onEnableApn(msg.arg1, msg.arg2);
                 break;
@@ -3828,10 +3651,15 @@
                 onDeviceProvisionedChange();
                 break;
 
+            case DctConstants.EVENT_DEVICE_PROVISIONING_DATA_SETTING_CHANGE:
+                onDeviceProvisioningDataChange();
+                break;
+
             case DctConstants.EVENT_REDIRECTION_DETECTED:
                 String url = (String) msg.obj;
                 log("dataConnectionTracker.handleMessage: EVENT_REDIRECTION_DETECTED=" + url);
                 onDataConnectionRedirected(url);
+                break;
 
             case DctConstants.EVENT_RADIO_AVAILABLE:
                 onRadioAvailable();
@@ -3872,19 +3700,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);
@@ -3950,14 +3765,14 @@
             }
             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");
                         mIsProvisioning = false;
                         mProvisioningUrl = null;
                         stopProvisioningApnAlarm();
-                        sendCleanUpConnection(true, apnCtx);
+                        cleanUpConnectionInternal(true, apnCtx);
                     } else {
                         if (DBG) {
                             log("EVENT_PROVISIONING_APN_ALARM: ignore stale tag,"
@@ -4022,6 +3837,7 @@
                 break;
             case DctConstants.EVENT_DATA_SERVICE_BINDING_CHANGED:
                 onDataServiceBindingChanged((Boolean) ((AsyncResult) msg.obj).result);
+                break;
             default:
                 Rlog.e("DcTracker", "Unhandled event=" + msg);
                 break;
@@ -4091,6 +3907,11 @@
         }
     }
 
+    /**
+     * Update DcTracker.
+     *
+     * TODO: This should be cleaned up. DcTracker should listen to those events.
+     */
     public void update() {
         log("update sub = " + mPhone.getSubId());
         log("update(): Active DDS, register for all events now!");
@@ -4098,45 +3919,17 @@
 
         mAutoAttachOnCreation.set(false);
 
-        ((GsmCdmaPhone)mPhone).updateCurrentCarrierInProvider();
+        mPhone.updateCurrentCarrierInProvider();
     }
 
-    public void cleanUpAllConnections(String cause) {
-        cleanUpAllConnections(cause, null);
-    }
-
-    public void updateRecords() {
-        onUpdateIcc();
-    }
-
-    public void cleanUpAllConnections(String cause, Message disconnectAllCompleteMsg) {
-        log("cleanUpAllConnections");
-        if (disconnectAllCompleteMsg != null) {
-            mDisconnectAllCompleteMsgList.add(disconnectAllCompleteMsg);
-        }
-
-        Message msg = obtainMessage(DctConstants.EVENT_CLEAN_UP_ALL_CONNECTIONS);
-        msg.obj = cause;
-        sendMessage(msg);
-    }
-
-    private void notifyDataDisconnectComplete() {
-        log("notifyDataDisconnectComplete");
-        for (Message m: mDisconnectAllCompleteMsgList) {
-            m.sendToTarget();
-        }
-        mDisconnectAllCompleteMsgList.clear();
-    }
-
-
     private void notifyAllDataDisconnected() {
         sEnableFailFastRefCounter = 0;
         mFailFast = false;
         mAllDataDisconnectedRegistrants.notifyRegistrants();
     }
 
-    public void registerForAllDataDisconnected(Handler h, int what, Object obj) {
-        mAllDataDisconnectedRegistrants.addUnique(h, what, obj);
+    public void registerForAllDataDisconnected(Handler h, int what) {
+        mAllDataDisconnectedRegistrants.addUnique(h, what, null);
 
         if (isDisconnected()) {
             log("notify All Data Disconnected");
@@ -4156,46 +3949,33 @@
         mDataEnabledSettings.unregisterForDataEnabledChanged(h);
     }
 
-    private void onSetInternalDataEnabled(boolean enabled, Message onCompleteMsg) {
+    private void onSetInternalDataEnabled(boolean enabled) {
         if (DBG) log("onSetInternalDataEnabled: enabled=" + enabled);
-        boolean sendOnComplete = true;
-
         mDataEnabledSettings.setInternalDataEnabled(enabled);
         if (enabled) {
             log("onSetInternalDataEnabled: changed to enabled, try to setup data call");
             onTrySetupData(Phone.REASON_DATA_ENABLED);
         } else {
-            sendOnComplete = false;
             log("onSetInternalDataEnabled: changed to disabled, cleanUpAllConnections");
-            cleanUpAllConnections(Phone.REASON_DATA_DISABLED, onCompleteMsg);
-        }
-
-        if (sendOnComplete) {
-            if (onCompleteMsg != null) {
-                onCompleteMsg.sendToTarget();
-            }
+            cleanUpAllConnectionsInternal(true, Phone.REASON_DATA_DISABLED);
         }
     }
 
     public boolean setInternalDataEnabled(boolean enable) {
-        return setInternalDataEnabled(enable, null);
-    }
-
-    public boolean setInternalDataEnabled(boolean enable, Message onCompleteMsg) {
         if (DBG) log("setInternalDataEnabled(" + enable + ")");
 
-        Message msg = obtainMessage(DctConstants.EVENT_SET_INTERNAL_DATA_ENABLE, onCompleteMsg);
+        Message msg = obtainMessage(DctConstants.EVENT_SET_INTERNAL_DATA_ENABLE);
         msg.arg1 = (enable ? DctConstants.ENABLED : DctConstants.DISABLED);
         sendMessage(msg);
         return true;
     }
 
     private void log(String s) {
-        Rlog.d(LOG_TAG, "[" + mPhone.getPhoneId() + "]" + s);
+        Rlog.d(mLogTag, s);
     }
 
     private void loge(String s) {
-        Rlog.e(LOG_TAG, "[" + mPhone.getPhoneId() + "]" + s);
+        Rlog.e(mLogTag, s);
     }
 
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
@@ -4224,6 +4004,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;
@@ -4270,16 +4052,13 @@
             pw.println(" mApnContexts=null");
         }
         pw.flush();
-        ArrayList<ApnSetting> apnSettings = mAllApnSettings;
-        if (apnSettings != null) {
-            pw.println(" mAllApnSettings size=" + apnSettings.size());
-            for (int i=0; i < apnSettings.size(); i++) {
-                pw.printf(" mAllApnSettings[%d]: %s\n", i, apnSettings.get(i));
-            }
-            pw.flush();
-        } else {
-            pw.println(" mAllApnSettings=null");
+
+        pw.println(" mAllApnSettings size=" + mAllApnSettings.size());
+        for (int i = 0; i < mAllApnSettings.size(); i++) {
+            pw.printf(" mAllApnSettings[%d]: %s\n", i, mAllApnSettings.get(i));
         }
+        pw.flush();
+
         pw.println(" mPreferredApn=" + mPreferredApn);
         pw.println(" mIsPsRestricted=" + mIsPsRestricted);
         pw.println(" mIsDisposed=" + mIsDisposed);
@@ -4288,7 +4067,6 @@
         pw.println(" canSetPreferApn=" + mCanSetPreferApn);
         pw.println(" mApnObserver=" + mApnObserver);
         pw.println(" getOverallState=" + getOverallState());
-        pw.println(" mDataConnectionAsyncChannels=%s\n" + mDataConnectionAcHashMap);
         pw.println(" mAttached=" + mAttached.get());
         mDataEnabledSettings.dump(fd, pw, args);
         pw.flush();
@@ -4304,9 +4082,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;
@@ -4317,11 +4095,11 @@
             return null;
         }
 
-        DcAsyncChannel dcac = apnContext.getDcAc();
+        DataConnection dataConnection = apnContext.getDataConnection();
         String[] result = null;
 
-        if (dcac != null) {
-            result = dcac.getPcscfAddr();
+        if (dataConnection != null) {
+            result = dataConnection.getPcscfAddresses();
 
             if (result != null) {
                 for (int i = 0; i < result.length; i++) {
@@ -4351,7 +4129,7 @@
         if (cursor != null) {
             if (cursor.getCount() > 0) {
                 if (cursor.moveToFirst()) {
-                    mEmergencyApn = makeApnSetting(cursor);
+                    mEmergencyApn = ApnSetting.makeApnSetting(cursor);
                 }
             }
             cursor.close();
@@ -4363,23 +4141,16 @@
      */
     private void addEmergencyApnSetting() {
         if(mEmergencyApn != null) {
-            if(mAllApnSettings == null) {
-                mAllApnSettings = new ArrayList<ApnSetting>();
-            } else {
-                boolean hasEmergencyApn = false;
-                for (ApnSetting apn : mAllApnSettings) {
-                    if (ArrayUtils.contains(apn.types, PhoneConstants.APN_TYPE_EMERGENCY)) {
-                        hasEmergencyApn = true;
-                        break;
-                    }
-                }
-
-                if(hasEmergencyApn == false) {
-                    mAllApnSettings.add(mEmergencyApn);
-                } else {
+            for (ApnSetting apn : mAllApnSettings) {
+                if (apn.canHandleType(ApnSetting.TYPE_EMERGENCY)) {
                     log("addEmergencyApnSetting - E-APN setting is already present");
+                    return;
                 }
             }
+
+            // If all of the APN settings cannot handle emergency, we add the emergency APN to the
+            // list explicitly.
+            mAllApnSettings.add(mEmergencyApn);
         }
     }
 
@@ -4402,8 +4173,8 @@
 
     private void cleanUpConnectionsOnUpdatedApns(boolean tearDown, String reason) {
         if (DBG) log("cleanUpConnectionsOnUpdatedApns: tearDown=" + tearDown);
-        if (mAllApnSettings != null && mAllApnSettings.isEmpty()) {
-            cleanUpAllConnections(tearDown, Phone.REASON_APN_CHANGED);
+        if (mAllApnSettings.isEmpty()) {
+            cleanUpAllConnectionsInternal(tearDown, Phone.REASON_APN_CHANGED);
         } else {
             int radioTech = mPhone.getServiceState().getRilDataRadioTechnology();
             if (radioTech == ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN) {
@@ -4429,7 +4200,7 @@
                     if (!apnContext.isDisconnected()) {
                         if (VDBG) log("cleanUpConnectionsOnUpdatedApns for " + apnContext);
                         apnContext.setReason(reason);
-                        cleanUpConnection(true, apnContext);
+                        cleanUpConnectionInternal(true, apnContext);
                     }
                 }
             }
@@ -4440,11 +4211,10 @@
             stopDataStallAlarm();
         }
 
-        mRequestedApnType = PhoneConstants.APN_TYPE_DEFAULT;
+        mRequestedApnType = ApnSetting.TYPE_DEFAULT;
 
         if (DBG) log("mDisconnectPendingCount = " + mDisconnectPendingCount);
         if (tearDown && mDisconnectPendingCount == 0) {
-            notifyDataDisconnectComplete();
             notifyAllDataDisconnected();
         }
     }
@@ -4555,7 +4325,7 @@
 
     private void handlePcoData(AsyncResult ar) {
         if (ar.exception != null) {
-            Rlog.e(LOG_TAG, "PCO_DATA exception: " + ar.exception);
+            loge("PCO_DATA exception: " + ar.exception);
             return;
         }
         PcoData pcoData = (PcoData)(ar.result);
@@ -4565,20 +4335,20 @@
             dcList.add(temp);
         }
         if (dcList.size() == 0) {
-            Rlog.e(LOG_TAG, "PCO_DATA for unknown cid: " + pcoData.cid + ", inferring");
+            loge("PCO_DATA for unknown cid: " + pcoData.cid + ", inferring");
             for (DataConnection dc : mDataConnections.values()) {
                 final int cid = dc.getCid();
                 if (cid == pcoData.cid) {
-                    if (VDBG) Rlog.d(LOG_TAG, "  found " + dc);
+                    if (VDBG) log("  found " + dc);
                     dcList.clear();
                     dcList.add(dc);
                     break;
                 }
                 // check if this dc is still connecting
                 if (cid == -1) {
-                    for (ApnContext apnContext : dc.mApnContexts.keySet()) {
+                    for (ApnContext apnContext : dc.getApnContexts()) {
                         if (apnContext.getState() == DctConstants.State.CONNECTING) {
-                            if (VDBG) Rlog.d(LOG_TAG, "  found potential " + dc);
+                            if (VDBG) log("  found potential " + dc);
                             dcList.add(dc);
                             break;
                         }
@@ -4587,15 +4357,16 @@
             }
         }
         if (dcList.size() == 0) {
-            Rlog.e(LOG_TAG, "PCO_DATA - couldn't infer cid");
+            loge("PCO_DATA - couldn't infer cid");
             return;
         }
         for (DataConnection dc : dcList) {
-            if (dc.mApnContexts.size() == 0) {
+            List<ApnContext> apnContextList = dc.getApnContexts();
+            if (apnContextList.size() == 0) {
                 break;
             }
             // send one out for each apn type in play
-            for (ApnContext apnContext : dc.mApnContexts.keySet()) {
+            for (ApnContext apnContext : apnContextList) {
                 String apnType = apnContext.getApnType();
 
                 final Intent intent = new Intent(TelephonyIntents.ACTION_CARRIER_SIGNAL_PCO_VALUE);
@@ -4663,7 +4434,7 @@
                     EventLog.writeEvent(EventLogTags.DATA_STALL_RECOVERY_CLEANUP,
                             mSentSinceLastRecv);
                     if (DBG) log("doRecovery() cleanup all connections");
-                    cleanUpAllConnections(Phone.REASON_PDP_RESET);
+                    cleanUpAllConnectionsInternal(true, Phone.REASON_PDP_RESET);
                     putRecoveryAction(RecoveryAction.REREGISTER);
                     break;
                 case RecoveryAction.REREGISTER:
@@ -4732,7 +4503,7 @@
     }
 
     private boolean isPhoneStateIdle() {
-        for (int i = 0; i < TelephonyManager.getDefault().getPhoneCount(); i++) {
+        for (int i = 0; i < mTelephonyManager.getPhoneCount(); i++) {
             Phone phone = PhoneFactory.getPhone(i);
             if (phone != null && phone.getState() != PhoneConstants.State.IDLE) {
                 log("isPhoneStateIdle false: Voice call active on phone " + i);
@@ -4749,6 +4520,8 @@
             }
             return;
         }
+
+        if (DBG) log("Data stall alarm");
         updateDataStallInfo();
 
         int hangWatchdogTrigger = Settings.Global.getInt(mResolver,
@@ -4794,7 +4567,9 @@
                         " delay=" + (delayInMs / 1000) + "s");
             }
             Intent intent = new Intent(INTENT_DATA_STALL_ALARM);
-            intent.putExtra(DATA_STALL_ALARM_TAG_EXTRA, mDataStallAlarmTag);
+            intent.putExtra(INTENT_DATA_STALL_ALARM_EXTRA_TAG, mDataStallAlarmTag);
+            intent.putExtra(INTENT_DATA_STALL_ALARM_EXTRA_TRANSPORT_TYPE, mTransportType);
+            SubscriptionManager.putPhoneIdAndSubIdExtra(intent, mPhone.getPhoneId());
             mDataStallAlarmIntent = PendingIntent.getBroadcast(mPhone.getContext(), 0, intent,
                     PendingIntent.FLAG_UPDATE_CURRENT);
             mAlarmManager.set(AlarmManager.ELAPSED_REALTIME,
@@ -4883,17 +4658,17 @@
         }
     }
 
-    private static DataProfile createDataProfile(ApnSetting apn) {
-        return createDataProfile(apn, apn.profileId);
+    private static DataProfile createDataProfile(ApnSetting apn, boolean isPreferred) {
+        return createDataProfile(apn, apn.getProfileId(), isPreferred);
     }
 
     @VisibleForTesting
-    public static DataProfile createDataProfile(ApnSetting apn, int profileId) {
+    public static DataProfile createDataProfile(ApnSetting apn, int profileId,
+                                                boolean isPreferred) {
         int profileType;
 
-        int bearerBitmap = 0;
-        bearerBitmap = ServiceState.convertNetworkTypeBitmaskToBearerBitmask(
-                apn.networkTypeBitmask);
+        int bearerBitmap = ServiceState.convertNetworkTypeBitmaskToBearerBitmask(
+                apn.getNetworkTypeBitmask());
 
         if (bearerBitmap == 0) {
             profileType = DataProfile.TYPE_COMMON;
@@ -4903,11 +4678,12 @@
             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(), apn.isPersistent(), isPreferred);
     }
 
     private void onDataServiceBindingChanged(boolean bound) {
diff --git a/src/java/com/android/internal/telephony/dataconnection/TelephonyNetworkFactory.java b/src/java/com/android/internal/telephony/dataconnection/TelephonyNetworkFactory.java
index 114a4b4..5c44f37 100644
--- a/src/java/com/android/internal/telephony/dataconnection/TelephonyNetworkFactory.java
+++ b/src/java/com/android/internal/telephony/dataconnection/TelephonyNetworkFactory.java
@@ -18,7 +18,6 @@
 
 import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
 
-import android.content.Context;
 import android.net.NetworkCapabilities;
 import android.net.NetworkFactory;
 import android.net.NetworkRequest;
@@ -26,9 +25,11 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.telephony.AccessNetworkConstants.TransportType;
 import android.telephony.Rlog;
 import android.util.LocalLog;
 
+import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneSwitcher;
 import com.android.internal.telephony.SubscriptionController;
 import com.android.internal.telephony.SubscriptionMonitor;
@@ -52,9 +53,12 @@
     private final HashMap<NetworkRequest, LocalLog> mSpecificRequests =
             new HashMap<NetworkRequest, LocalLog>();
 
-    private int mPhoneId;
+    private final Phone mPhone;
+    // Only when this network factory is active, it will apply any network requests.
     private boolean mIsActive;
-    private boolean mIsDefault;
+    // Whether this network factory is active and should handle default network requests.
+    // Default network requests are those that don't specify subscription ID.
+    private boolean mIsActiveForDefault;
     private int mSubscriptionId;
 
     private final static int TELEPHONY_NETWORK_SCORE = 50;
@@ -62,37 +66,37 @@
     private final Handler mInternalHandler;
     private static final int EVENT_ACTIVE_PHONE_SWITCH          = 1;
     private static final int EVENT_SUBSCRIPTION_CHANGED         = 2;
-    private static final int EVENT_DEFAULT_SUBSCRIPTION_CHANGED = 3;
-    private static final int EVENT_NETWORK_REQUEST              = 4;
-    private static final int EVENT_NETWORK_RELEASE              = 5;
+    private static final int EVENT_NETWORK_REQUEST              = 3;
+    private static final int EVENT_NETWORK_RELEASE              = 4;
 
-    public TelephonyNetworkFactory(PhoneSwitcher phoneSwitcher,
-            SubscriptionController subscriptionController, SubscriptionMonitor subscriptionMonitor,
-            Looper looper, Context context, int phoneId, DcTracker dcTracker) {
-        super(looper, context, "TelephonyNetworkFactory[" + phoneId + "]", null);
+    public TelephonyNetworkFactory(SubscriptionMonitor subscriptionMonitor, Looper looper,
+                                   Phone phone) {
+        super(looper, phone.getContext(), "TelephonyNetworkFactory[" + phone.getPhoneId()
+                + "]", null);
+        mPhone = phone;
         mInternalHandler = new InternalHandler(looper);
 
-        setCapabilityFilter(makeNetworkFilter(subscriptionController, phoneId));
+        mSubscriptionController = SubscriptionController.getInstance();
+
+        setCapabilityFilter(makeNetworkFilter(mSubscriptionController, mPhone.getPhoneId()));
         setScoreFilter(TELEPHONY_NETWORK_SCORE);
 
-        mPhoneSwitcher = phoneSwitcher;
-        mSubscriptionController = subscriptionController;
+        mPhoneSwitcher = PhoneSwitcher.getInstance();
         mSubscriptionMonitor = subscriptionMonitor;
-        mPhoneId = phoneId;
-        LOG_TAG = "TelephonyNetworkFactory[" + phoneId + "]";
-        mDcTracker = dcTracker;
+        LOG_TAG = "TelephonyNetworkFactory[" + mPhone.getPhoneId() + "]";
+        // TODO: Will need to dynamically route network requests to the corresponding DcTracker in
+        // the future. For now we route everything to WWAN.
+        mDcTracker = mPhone.getDcTracker(TransportType.WWAN);
 
         mIsActive = false;
-        mPhoneSwitcher.registerForActivePhoneSwitch(mPhoneId, mInternalHandler,
-                EVENT_ACTIVE_PHONE_SWITCH, null);
+        mPhoneSwitcher.registerForActivePhoneSwitch(mInternalHandler, EVENT_ACTIVE_PHONE_SWITCH,
+                null);
 
         mSubscriptionId = INVALID_SUBSCRIPTION_ID;
-        mSubscriptionMonitor.registerForSubscriptionChanged(mPhoneId, mInternalHandler,
+        mSubscriptionMonitor.registerForSubscriptionChanged(mPhone.getPhoneId(), mInternalHandler,
                 EVENT_SUBSCRIPTION_CHANGED, null);
 
-        mIsDefault = false;
-        mSubscriptionMonitor.registerForDefaultDataSubscriptionChanged(mPhoneId, mInternalHandler,
-                EVENT_DEFAULT_SUBSCRIPTION_CHANGED, null);
+        mIsActiveForDefault = false;
 
         register();
     }
@@ -138,10 +142,6 @@
                     onSubIdChange();
                     break;
                 }
-                case EVENT_DEFAULT_SUBSCRIPTION_CHANGED: {
-                    onDefaultChange();
-                    break;
-                }
                 case EVENT_NETWORK_REQUEST: {
                     onNeedNetworkFor(msg);
                     break;
@@ -155,40 +155,60 @@
     }
 
     private static final int REQUEST_LOG_SIZE = 40;
-    private static final boolean REQUEST = true;
-    private static final boolean RELEASE = false;
 
-    private void applyRequests(HashMap<NetworkRequest, LocalLog> requestMap, boolean action,
-            String logStr) {
+    private static final int ACTION_NO_OP   = 0;
+    private static final int ACTION_REQUEST = 1;
+    private static final int ACTION_RELEASE = 2;
+
+    private void applyRequests(HashMap<NetworkRequest, LocalLog> requestMap,
+            int action, String logStr) {
+        if (action == ACTION_NO_OP) return;
+
         for (NetworkRequest networkRequest : requestMap.keySet()) {
             LocalLog localLog = requestMap.get(networkRequest);
             localLog.log(logStr);
-            if (action == REQUEST) {
+            if (action == ACTION_REQUEST) {
                 mDcTracker.requestNetwork(networkRequest, localLog);
-            } else {
+            } else if (action == ACTION_RELEASE) {
                 mDcTracker.releaseNetwork(networkRequest, localLog);
             }
         }
     }
 
+    private static int getAction(boolean wasActive, boolean isActive) {
+        if (!wasActive && isActive) {
+            return ACTION_REQUEST;
+        } else if (wasActive && !isActive) {
+            return ACTION_RELEASE;
+        } else {
+            return ACTION_NO_OP;
+        }
+    }
+
     // apply or revoke requests if our active-ness changes
     private void onActivePhoneSwitch() {
-        final boolean newIsActive = mPhoneSwitcher.isPhoneActive(mPhoneId);
-        if (mIsActive != newIsActive) {
-            mIsActive = newIsActive;
-            String logString = "onActivePhoneSwitch(" + mIsActive + ", " + mIsDefault + ")";
-            if (DBG) log(logString);
-            if (mIsDefault) {
-                applyRequests(mDefaultRequests, (mIsActive ? REQUEST : RELEASE), logString);
-            }
-            applyRequests(mSpecificRequests, (mIsActive ? REQUEST : RELEASE), logString);
-        }
+        final boolean newIsActive = mPhoneSwitcher.shouldApplySpecifiedRequests(
+                mPhone.getPhoneId());
+        final boolean newIsActiveForDefault =
+                mPhoneSwitcher.shouldApplyUnspecifiedRequests(mPhone.getPhoneId());
+
+        String logString = "onActivePhoneSwitch(newIsActive " + newIsActive + ", "
+                + "newIsActiveForDefault " + newIsActiveForDefault + ")";
+        if (DBG) log(logString);
+
+        applyRequests(mSpecificRequests, getAction(mIsActive, newIsActive), logString);
+        applyRequests(mDefaultRequests, getAction(mIsActiveForDefault, newIsActiveForDefault),
+                logString);
+
+        mIsActive = newIsActive;
+        mIsActiveForDefault = newIsActiveForDefault;
     }
 
     // watch for phone->subId changes, reapply new filter and let
     // that flow through to apply/revoke of requests
     private void onSubIdChange() {
-        final int newSubscriptionId = mSubscriptionController.getSubIdUsingPhoneId(mPhoneId);
+        final int newSubscriptionId = mSubscriptionController.getSubIdUsingPhoneId(
+                mPhone.getPhoneId());
         if (mSubscriptionId != newSubscriptionId) {
             if (DBG) log("onSubIdChange " + mSubscriptionId + "->" + newSubscriptionId);
             mSubscriptionId = newSubscriptionId;
@@ -196,21 +216,6 @@
         }
     }
 
-    // watch for default-data changes (could be side effect of
-    // phoneId->subId map change or direct change of default subId)
-    // and apply/revoke default-only requests.
-    private void onDefaultChange() {
-        final int newDefaultSubscriptionId = mSubscriptionController.getDefaultDataSubId();
-        final boolean newIsDefault = (newDefaultSubscriptionId == mSubscriptionId);
-        if (newIsDefault != mIsDefault) {
-            mIsDefault = newIsDefault;
-            String logString = "onDefaultChange(" + mIsActive + "," + mIsDefault + ")";
-            if (DBG) log(logString);
-            if (mIsActive == false) return;
-            applyRequests(mDefaultRequests, (mIsDefault ? REQUEST : RELEASE), logString);
-        }
-    }
-
     @Override
     public void needNetworkFor(NetworkRequest networkRequest, int score) {
         Message msg = mInternalHandler.obtainMessage(EVENT_NETWORK_REQUEST);
@@ -229,23 +234,23 @@
                 localLog = new LocalLog(REQUEST_LOG_SIZE);
                 localLog.log("created for " + networkRequest);
                 mDefaultRequests.put(networkRequest, localLog);
-                isApplicable = mIsDefault;
+                isApplicable = mIsActiveForDefault;
             }
         } else {
             localLog = mSpecificRequests.get(networkRequest);
             if (localLog == null) {
                 localLog = new LocalLog(REQUEST_LOG_SIZE);
                 mSpecificRequests.put(networkRequest, localLog);
-                isApplicable = true;
+                isApplicable = mIsActive;
             }
         }
-        if (mIsActive && isApplicable) {
+        if (isApplicable) {
             String s = "onNeedNetworkFor";
             localLog.log(s);
             log(s + " " + networkRequest);
             mDcTracker.requestNetwork(networkRequest, localLog);
         } else {
-            String s = "not acting - isApp=" + isApplicable + ", isAct=" + mIsActive;
+            String s = "not acting - isApplicable=" + isApplicable + ", mIsActive=" + mIsActive;
             localLog.log(s);
             log(s + " " + networkRequest);
         }
@@ -264,19 +269,19 @@
         boolean isApplicable = false;
         if (networkRequest.networkCapabilities.getNetworkSpecifier() == null) {
             // request only for the default network
+            isApplicable = mDefaultRequests.containsKey(networkRequest) && mIsActiveForDefault;
             localLog = mDefaultRequests.remove(networkRequest);
-            isApplicable = (localLog != null) && mIsDefault;
         } else {
+            isApplicable = mSpecificRequests.containsKey(networkRequest) && mIsActive;
             localLog = mSpecificRequests.remove(networkRequest);
-            isApplicable = (localLog != null);
         }
-        if (mIsActive && isApplicable) {
+        if (isApplicable) {
             String s = "onReleaseNetworkFor";
             localLog.log(s);
             log(s + " " + networkRequest);
             mDcTracker.releaseNetwork(networkRequest, localLog);
         } else {
-            String s = "not releasing - isApp=" + isApplicable + ", isAct=" + mIsActive;
+            String s = "not releasing - isApplicable=" + isApplicable + ", mIsActive=" + mIsActive;
             localLog.log(s);
             log(s + " " + networkRequest);
         }
@@ -289,7 +294,7 @@
     public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
         final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
         pw.println(LOG_TAG + " mSubId=" + mSubscriptionId + " mIsActive=" +
-                mIsActive + " mIsDefault=" + mIsDefault);
+                mIsActive + " mIsActiveForDefault=" + mIsActiveForDefault);
         pw.println("Default Requests:");
         pw.increaseIndent();
         for (NetworkRequest nr : mDefaultRequests.keySet()) {
diff --git a/src/java/com/android/internal/telephony/emergency/EmergencyNumberTracker.java b/src/java/com/android/internal/telephony/emergency/EmergencyNumberTracker.java
new file mode 100644
index 0000000..5425f2b
--- /dev/null
+++ b/src/java/com/android/internal/telephony/emergency/EmergencyNumberTracker.java
@@ -0,0 +1,180 @@
+/*
+ * 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.emergency;
+
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
+import android.telephony.Rlog;
+import android.telephony.emergency.EmergencyNumber;
+import android.util.LocalLog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.Phone;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.phone.ecc.nano.ProtobufEccData;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Emergency Number Tracker that handles update of emergency number list from RIL and emergency
+ * number database. This is multi-sim based and each Phone has a EmergencyNumberTracker.
+ */
+public class EmergencyNumberTracker extends Handler {
+    private static final String TAG = EmergencyNumberTracker.class.getSimpleName();
+
+    /** @hide */
+    public static boolean DBG = false;
+
+    private final CommandsInterface mCi;
+    private final Phone mPhone;
+    private List<EmergencyNumber> mEmergencyNumberListFromDatabase = new ArrayList<>();
+    private List<EmergencyNumber> mEmergencyNumberListFromRadio = new ArrayList<>();
+    private List<EmergencyNumber> mEmergencyNumberList = new ArrayList<>();
+
+    private final LocalLog mEmergencyNumberListDatabaseLocalLog = new LocalLog(20);
+    private final LocalLog mEmergencyNumberListRadioLocalLog = new LocalLog(20);
+    private final LocalLog mEmergencyNumberListLocalLog = new LocalLog(20);
+
+    /** Event indicating the update for the emergency number list from the radio. */
+    private static final int EVENT_UNSOL_EMERGENCY_NUMBER_LIST = 1;
+
+    // TODO EVENT_UPDATE_NETWORK_COUNTRY_ISO
+
+    public EmergencyNumberTracker(Phone phone, CommandsInterface ci) {
+        mPhone = phone;
+        mCi = ci;
+        // TODO cache Emergency Number List Database per country ISO;
+        // TODO register for Locale Tracker Country ISO Change
+        mCi.registerForEmergencyNumberList(this, EVENT_UNSOL_EMERGENCY_NUMBER_LIST, null);
+    }
+
+    /**
+     * Message handler for updating emergency number list from RIL, updating emergency number list
+     * from database if the country ISO is changed, and notifying the change of emergency number
+     * list.
+     *
+     * @param msg The message
+     */
+    @Override
+    public void handleMessage(Message msg) {
+        switch (msg.what) {
+            case EVENT_UNSOL_EMERGENCY_NUMBER_LIST:
+                AsyncResult ar = (AsyncResult) msg.obj;
+                if (ar.result == null) {
+                    loge("EVENT_UNSOL_EMERGENCY_NUMBER_LIST: Result from RIL is null.");
+                } else if ((ar.result != null) && (ar.exception == null)) {
+                    updateAndNotifyEmergencyNumberList((List<EmergencyNumber>) ar.result);
+                } else {
+                    loge("EVENT_UNSOL_EMERGENCY_NUMBER_LIST: Exception from RIL : "
+                            + ar.exception);
+                }
+                break;
+        }
+    }
+
+    private void updateAndNotifyEmergencyNumberList(
+            List<EmergencyNumber> emergencyNumberListRadio) {
+        Collections.sort(emergencyNumberListRadio);
+        logd("updateAndNotifyEmergencyNumberList(): receiving " + emergencyNumberListRadio);
+
+        if (!emergencyNumberListRadio.equals(mEmergencyNumberListFromRadio)) {
+            try {
+                mEmergencyNumberListFromRadio = emergencyNumberListRadio;
+                if (!DBG) {
+                    mEmergencyNumberListRadioLocalLog.log("updateRadioEmergencyNumberList:"
+                            + emergencyNumberListRadio);
+                }
+                List<EmergencyNumber> emergencyNumberListMergedWithDatabase =
+                        constructEmergencyNumberListWithDatabase();
+                mEmergencyNumberList = emergencyNumberListMergedWithDatabase;
+                if (!DBG) {
+                    mEmergencyNumberListLocalLog.log("updateEmergencyNumberList:"
+                            + emergencyNumberListMergedWithDatabase);
+                }
+                notifyEmergencyNumberList();
+            } catch (NullPointerException ex) {
+                loge("updateAndNotifyEmergencyNumberList() Phone already destroyed: " + ex
+                        + "EmergencyNumberList not notified");
+            }
+        }
+    }
+
+    private void notifyEmergencyNumberList() {
+        List<EmergencyNumber> emergencyNumberListToNotify = getEmergencyNumberList();
+        mPhone.notifyEmergencyNumberList(emergencyNumberListToNotify);
+        logd("notifyEmergencyNumberList():" + emergencyNumberListToNotify);
+    }
+
+    private List<EmergencyNumber> constructEmergencyNumberListWithDatabase() {
+        List<EmergencyNumber> emergencyNumberListRadioAndDatabase = mEmergencyNumberListFromRadio;
+        // TODO integrate with emergency number database
+        // TODO sorting
+        return emergencyNumberListRadioAndDatabase;
+    }
+
+    public List<EmergencyNumber> getEmergencyNumberList() {
+        return new ArrayList<>(mEmergencyNumberList);
+    }
+
+    @VisibleForTesting
+    public List<EmergencyNumber> getRadioEmergencyNumberList() {
+        return new ArrayList<>(mEmergencyNumberListFromRadio);
+    }
+
+    private static void logd(String str) {
+        Rlog.d(TAG, str);
+    }
+
+    private static void loge(String str) {
+        Rlog.e(TAG, str);
+    }
+
+    /**
+     * Dump Emergency Number List info in the tracking
+     *
+     * @param fd FileDescriptor
+     * @param pw PrintWriter
+     * @param args args
+     */
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
+        ipw.println("mEmergencyNumberListDatabaseLocalLog:");
+        ipw.increaseIndent();
+        mEmergencyNumberListDatabaseLocalLog.dump(fd, pw, args);
+        ipw.decreaseIndent();
+        ipw.println("   -   -   -   -   -   -   -   -");
+
+        ipw.println("mEmergencyNumberListRadioLocalLog:");
+        ipw.increaseIndent();
+        mEmergencyNumberListRadioLocalLog.dump(fd, pw, args);
+        ipw.decreaseIndent();
+        ipw.println("   -   -   -   -   -   -   -   -");
+
+        ipw.println("mEmergencyNumberListLocalLog:");
+        ipw.increaseIndent();
+        mEmergencyNumberListLocalLog.dump(fd, pw, args);
+        ipw.decreaseIndent();
+
+        ipw.flush();
+    }
+}
diff --git a/src/java/com/android/internal/telephony/euicc/EuiccCardController.java b/src/java/com/android/internal/telephony/euicc/EuiccCardController.java
index 5c5d244..6de3984 100644
--- a/src/java/com/android/internal/telephony/euicc/EuiccCardController.java
+++ b/src/java/com/android/internal/telephony/euicc/EuiccCardController.java
@@ -194,7 +194,16 @@
     @Override
     public void getAllProfiles(String callingPackage, String cardId,
             IGetAllProfilesCallback callback) {
-        checkCallingPackage(callingPackage);
+        try {
+            checkCallingPackage(callingPackage);
+        } catch (SecurityException se) {
+            try {
+                callback.onComplete(EuiccCardManager.RESULT_CALLER_NOT_ALLOWED, null);
+            } catch (RemoteException re) {
+                loge("callback onComplete failure after checkCallingPackage.", re);
+            }
+            return;
+        }
 
         EuiccCard card = getEuiccCard(cardId);
         if (card == null) {
@@ -234,7 +243,16 @@
     @Override
     public void getProfile(String callingPackage, String cardId, String iccid,
             IGetProfileCallback callback) {
-        checkCallingPackage(callingPackage);
+        try {
+            checkCallingPackage(callingPackage);
+        } catch (SecurityException se) {
+            try {
+                callback.onComplete(EuiccCardManager.RESULT_CALLER_NOT_ALLOWED, null);
+            } catch (RemoteException re) {
+                loge("callback onComplete failure after checkCallingPackage.", re);
+            }
+            return;
+        }
 
         EuiccCard card = getEuiccCard(cardId);
         if (card == null) {
@@ -273,7 +291,16 @@
     @Override
     public void disableProfile(String callingPackage, String cardId, String iccid, boolean refresh,
             IDisableProfileCallback callback) {
-        checkCallingPackage(callingPackage);
+        try {
+            checkCallingPackage(callingPackage);
+        } catch (SecurityException se) {
+            try {
+                callback.onComplete(EuiccCardManager.RESULT_CALLER_NOT_ALLOWED);
+            } catch (RemoteException re) {
+                loge("callback onComplete failure after checkCallingPackage.", re);
+            }
+            return;
+        }
 
         EuiccCard card = getEuiccCard(cardId);
         if (card == null) {
@@ -312,7 +339,16 @@
     @Override
     public void switchToProfile(String callingPackage, String cardId, String iccid, boolean refresh,
             ISwitchToProfileCallback callback) {
-        checkCallingPackage(callingPackage);
+        try {
+            checkCallingPackage(callingPackage);
+        } catch (SecurityException se) {
+            try {
+                callback.onComplete(EuiccCardManager.RESULT_CALLER_NOT_ALLOWED, null);
+            } catch (RemoteException re) {
+                loge("callback onComplete failure after checkCallingPackage.", re);
+            }
+            return;
+        }
 
         EuiccCard card = getEuiccCard(cardId);
         if (card == null) {
@@ -369,7 +405,16 @@
     @Override
     public void setNickname(String callingPackage, String cardId, String iccid, String nickname,
             ISetNicknameCallback callback) {
-        checkCallingPackage(callingPackage);
+        try {
+            checkCallingPackage(callingPackage);
+        } catch (SecurityException se) {
+            try {
+                callback.onComplete(EuiccCardManager.RESULT_CALLER_NOT_ALLOWED);
+            } catch (RemoteException re) {
+                loge("callback onComplete failure after checkCallingPackage.", re);
+            }
+            return;
+        }
 
         EuiccCard card = getEuiccCard(cardId);
         if (card == null) {
@@ -408,7 +453,16 @@
     @Override
     public void deleteProfile(String callingPackage, String cardId, String iccid,
             IDeleteProfileCallback callback) {
-        checkCallingPackage(callingPackage);
+        try {
+            checkCallingPackage(callingPackage);
+        } catch (SecurityException se) {
+            try {
+                callback.onComplete(EuiccCardManager.RESULT_CALLER_NOT_ALLOWED);
+            } catch (RemoteException re) {
+                loge("callback onComplete failure after checkCallingPackage.", re);
+            }
+            return;
+        }
 
         EuiccCard card = getEuiccCard(cardId);
         if (card == null) {
@@ -449,7 +503,16 @@
     @Override
     public void resetMemory(String callingPackage, String cardId,
             @EuiccCardManager.ResetOption int options, IResetMemoryCallback callback) {
-        checkCallingPackage(callingPackage);
+        try {
+            checkCallingPackage(callingPackage);
+        } catch (SecurityException se) {
+            try {
+                callback.onComplete(EuiccCardManager.RESULT_CALLER_NOT_ALLOWED);
+            } catch (RemoteException re) {
+                loge("callback onComplete failure after checkCallingPackage.", re);
+            }
+            return;
+        }
 
         EuiccCard card = getEuiccCard(cardId);
         if (card == null) {
@@ -490,7 +553,16 @@
     @Override
     public void getDefaultSmdpAddress(String callingPackage, String cardId,
             IGetDefaultSmdpAddressCallback callback) {
-        checkCallingPackage(callingPackage);
+        try {
+            checkCallingPackage(callingPackage);
+        } catch (SecurityException se) {
+            try {
+                callback.onComplete(EuiccCardManager.RESULT_CALLER_NOT_ALLOWED, null);
+            } catch (RemoteException re) {
+                loge("callback onComplete failure after checkCallingPackage.", re);
+            }
+            return;
+        }
 
         EuiccCard card = getEuiccCard(cardId);
         if (card == null) {
@@ -529,7 +601,16 @@
     @Override
     public void getSmdsAddress(String callingPackage, String cardId,
             IGetSmdsAddressCallback callback) {
-        checkCallingPackage(callingPackage);
+        try {
+            checkCallingPackage(callingPackage);
+        } catch (SecurityException se) {
+            try {
+                callback.onComplete(EuiccCardManager.RESULT_CALLER_NOT_ALLOWED, null);
+            } catch (RemoteException re) {
+                loge("callback onComplete failure after checkCallingPackage.", re);
+            }
+            return;
+        }
 
         EuiccCard card = getEuiccCard(cardId);
         if (card == null) {
@@ -568,7 +649,16 @@
     @Override
     public void setDefaultSmdpAddress(String callingPackage, String cardId, String address,
             ISetDefaultSmdpAddressCallback callback) {
-        checkCallingPackage(callingPackage);
+        try {
+            checkCallingPackage(callingPackage);
+        } catch (SecurityException se) {
+            try {
+                callback.onComplete(EuiccCardManager.RESULT_CALLER_NOT_ALLOWED);
+            } catch (RemoteException re) {
+                loge("callback onComplete failure after checkCallingPackage.", re);
+            }
+            return;
+        }
 
         EuiccCard card = getEuiccCard(cardId);
         if (card == null) {
@@ -607,7 +697,16 @@
     @Override
     public void getRulesAuthTable(String callingPackage, String cardId,
             IGetRulesAuthTableCallback callback) {
-        checkCallingPackage(callingPackage);
+        try {
+            checkCallingPackage(callingPackage);
+        } catch (SecurityException se) {
+            try {
+                callback.onComplete(EuiccCardManager.RESULT_CALLER_NOT_ALLOWED, null);
+            } catch (RemoteException re) {
+                loge("callback onComplete failure after checkCallingPackage.", re);
+            }
+            return;
+        }
 
         EuiccCard card = getEuiccCard(cardId);
         if (card == null) {
@@ -647,7 +746,16 @@
     @Override
     public void getEuiccChallenge(String callingPackage, String cardId,
             IGetEuiccChallengeCallback callback) {
-        checkCallingPackage(callingPackage);
+        try {
+            checkCallingPackage(callingPackage);
+        } catch (SecurityException se) {
+            try {
+                callback.onComplete(EuiccCardManager.RESULT_CALLER_NOT_ALLOWED, null);
+            } catch (RemoteException re) {
+                loge("callback onComplete failure after checkCallingPackage.", re);
+            }
+            return;
+        }
 
         EuiccCard card = getEuiccCard(cardId);
         if (card == null) {
@@ -686,7 +794,16 @@
     @Override
     public void getEuiccInfo1(String callingPackage, String cardId,
             IGetEuiccInfo1Callback callback) {
-        checkCallingPackage(callingPackage);
+        try {
+            checkCallingPackage(callingPackage);
+        } catch (SecurityException se) {
+            try {
+                callback.onComplete(EuiccCardManager.RESULT_CALLER_NOT_ALLOWED, null);
+            } catch (RemoteException re) {
+                loge("callback onComplete failure after checkCallingPackage.", re);
+            }
+            return;
+        }
 
         EuiccCard card = getEuiccCard(cardId);
         if (card == null) {
@@ -725,7 +842,16 @@
     @Override
     public void getEuiccInfo2(String callingPackage, String cardId,
             IGetEuiccInfo2Callback callback) {
-        checkCallingPackage(callingPackage);
+        try {
+            checkCallingPackage(callingPackage);
+        } catch (SecurityException se) {
+            try {
+                callback.onComplete(EuiccCardManager.RESULT_CALLER_NOT_ALLOWED, null);
+            } catch (RemoteException re) {
+                loge("callback onComplete failure after checkCallingPackage.", re);
+            }
+            return;
+        }
 
         EuiccCard card = getEuiccCard(cardId);
         if (card == null) {
@@ -765,7 +891,16 @@
     public void authenticateServer(String callingPackage, String cardId, String matchingId,
             byte[] serverSigned1, byte[] serverSignature1, byte[] euiccCiPkIdToBeUsed,
             byte[] serverCertificate, IAuthenticateServerCallback callback) {
-        checkCallingPackage(callingPackage);
+        try {
+            checkCallingPackage(callingPackage);
+        } catch (SecurityException se) {
+            try {
+                callback.onComplete(EuiccCardManager.RESULT_CALLER_NOT_ALLOWED, null);
+            } catch (RemoteException re) {
+                loge("callback onComplete failure after checkCallingPackage.", re);
+            }
+            return;
+        }
 
         EuiccCard card = getEuiccCard(cardId);
         if (card == null) {
@@ -806,7 +941,16 @@
     public void prepareDownload(String callingPackage, String cardId, @Nullable byte[] hashCc,
             byte[] smdpSigned2, byte[] smdpSignature2, byte[] smdpCertificate,
             IPrepareDownloadCallback callback) {
-        checkCallingPackage(callingPackage);
+        try {
+            checkCallingPackage(callingPackage);
+        } catch (SecurityException se) {
+            try {
+                callback.onComplete(EuiccCardManager.RESULT_CALLER_NOT_ALLOWED, null);
+            } catch (RemoteException re) {
+                loge("callback onComplete failure after checkCallingPackage.", re);
+            }
+            return;
+        }
 
         EuiccCard card = getEuiccCard(cardId);
         if (card == null) {
@@ -846,7 +990,16 @@
     @Override
     public void loadBoundProfilePackage(String callingPackage, String cardId,
             byte[] boundProfilePackage, ILoadBoundProfilePackageCallback callback) {
-        checkCallingPackage(callingPackage);
+        try {
+            checkCallingPackage(callingPackage);
+        } catch (SecurityException se) {
+            try {
+                callback.onComplete(EuiccCardManager.RESULT_CALLER_NOT_ALLOWED, null);
+            } catch (RemoteException re) {
+                loge("callback onComplete failure after checkCallingPackage.", re);
+            }
+            return;
+        }
 
         EuiccCard card = getEuiccCard(cardId);
         if (card == null) {
@@ -887,7 +1040,16 @@
     @Override
     public void cancelSession(String callingPackage, String cardId, byte[] transactionId,
             @EuiccCardManager.CancelReason int reason, ICancelSessionCallback callback) {
-        checkCallingPackage(callingPackage);
+        try {
+            checkCallingPackage(callingPackage);
+        } catch (SecurityException se) {
+            try {
+                callback.onComplete(EuiccCardManager.RESULT_CALLER_NOT_ALLOWED, null);
+            } catch (RemoteException re) {
+                loge("callback onComplete failure after checkCallingPackage.", re);
+            }
+            return;
+        }
 
         EuiccCard card = getEuiccCard(cardId);
         if (card == null) {
@@ -926,7 +1088,16 @@
     @Override
     public void listNotifications(String callingPackage, String cardId,
             @EuiccNotification.Event int events, IListNotificationsCallback callback) {
-        checkCallingPackage(callingPackage);
+        try {
+            checkCallingPackage(callingPackage);
+        } catch (SecurityException se) {
+            try {
+                callback.onComplete(EuiccCardManager.RESULT_CALLER_NOT_ALLOWED, null);
+            } catch (RemoteException re) {
+                loge("callback onComplete failure after checkCallingPackage.", re);
+            }
+            return;
+        }
 
         EuiccCard card = getEuiccCard(cardId);
         if (card == null) {
@@ -966,7 +1137,16 @@
     @Override
     public void retrieveNotificationList(String callingPackage, String cardId,
             @EuiccNotification.Event int events, IRetrieveNotificationListCallback callback) {
-        checkCallingPackage(callingPackage);
+        try {
+            checkCallingPackage(callingPackage);
+        } catch (SecurityException se) {
+            try {
+                callback.onComplete(EuiccCardManager.RESULT_CALLER_NOT_ALLOWED, null);
+            } catch (RemoteException re) {
+                loge("callback onComplete failure after checkCallingPackage.", re);
+            }
+            return;
+        }
 
         EuiccCard card = getEuiccCard(cardId);
         if (card == null) {
@@ -1006,7 +1186,16 @@
     @Override
     public void retrieveNotification(String callingPackage, String cardId, int seqNumber,
             IRetrieveNotificationCallback callback) {
-        checkCallingPackage(callingPackage);
+        try {
+            checkCallingPackage(callingPackage);
+        } catch (SecurityException se) {
+            try {
+                callback.onComplete(EuiccCardManager.RESULT_CALLER_NOT_ALLOWED, null);
+            } catch (RemoteException re) {
+                loge("callback onComplete failure after checkCallingPackage.", re);
+            }
+            return;
+        }
 
         EuiccCard card = getEuiccCard(cardId);
         if (card == null) {
@@ -1046,7 +1235,16 @@
     @Override
     public void removeNotificationFromList(String callingPackage, String cardId, int seqNumber,
             IRemoveNotificationFromListCallback callback) {
-        checkCallingPackage(callingPackage);
+        try {
+            checkCallingPackage(callingPackage);
+        } catch (SecurityException se) {
+            try {
+                callback.onComplete(EuiccCardManager.RESULT_CALLER_NOT_ALLOWED);
+            } catch (RemoteException re) {
+                loge("callback onComplete failure after checkCallingPackage.", re);
+            }
+            return;
+        }
 
         EuiccCard card = getEuiccCard(cardId);
         if (card == null) {
diff --git a/src/java/com/android/internal/telephony/gsm/GsmInboundSmsHandler.java b/src/java/com/android/internal/telephony/gsm/GsmInboundSmsHandler.java
index 3fe2a33..c3ff090 100644
--- a/src/java/com/android/internal/telephony/gsm/GsmInboundSmsHandler.java
+++ b/src/java/com/android/internal/telephony/gsm/GsmInboundSmsHandler.java
@@ -29,8 +29,6 @@
 import com.android.internal.telephony.SmsMessageBase;
 import com.android.internal.telephony.SmsStorageMonitor;
 import com.android.internal.telephony.VisualVoicemailSmsFilter;
-import com.android.internal.telephony.uicc.IccRecords;
-import com.android.internal.telephony.uicc.UiccController;
 import com.android.internal.telephony.uicc.UsimServiceTable;
 
 /**
@@ -154,15 +152,6 @@
         }
         // update voice mail count in Phone
         mPhone.setVoiceMessageCount(voicemailCount);
-        // store voice mail count in SIM & shared preferences
-        IccRecords records = UiccController.getInstance().getIccRecords(
-                mPhone.getPhoneId(), UiccController.APP_FAM_3GPP);
-        if (records != null) {
-            log("updateMessageWaitingIndicator: updating SIM Records");
-            records.setVoiceMessageWaiting(1, voicemailCount);
-        } else {
-            log("updateMessageWaitingIndicator: SIM Records not found");
-        }
     }
 
     /**
@@ -177,22 +166,6 @@
     }
 
     /**
-     * Called when the phone changes the default method updates mPhone
-     * mStorageMonitor and mCellBroadcastHandler.updatePhoneObject.
-     * Override if different or other behavior is desired.
-     *
-     * @param phone
-     */
-    @Override
-    protected void onUpdatePhoneObject(Phone phone) {
-        super.onUpdatePhoneObject(phone);
-        log("onUpdatePhoneObject: dispose of old CellBroadcastHandler and make a new one");
-        mCellBroadcastHandler.dispose();
-        mCellBroadcastHandler = GsmCellBroadcastHandler
-                .makeGsmCellBroadcastHandler(mContext, phone);
-    }
-
-    /**
      * Convert Android result code to 3GPP SMS failure cause.
      * @param rc the Android SMS intent result value
      * @return 0 for success, or a 3GPP SMS failure cause value
diff --git a/src/java/com/android/internal/telephony/ims/ImsServiceController.java b/src/java/com/android/internal/telephony/ims/ImsServiceController.java
index e2a0f43..60f11c6 100644
--- a/src/java/com/android/internal/telephony/ims/ImsServiceController.java
+++ b/src/java/com/android/internal/telephony/ims/ImsServiceController.java
@@ -78,7 +78,7 @@
                 mIsBinding = false;
                 mIsBound = false;
             }
-            notifyAllFeaturesRemoved();
+            cleanupAllFeatures();
             cleanUpService();
             startDelayedRebindToService();
         }
@@ -144,7 +144,7 @@
             if (isServiceControllerAvailable()) {
                 mImsServiceControllerBinder.unlinkToDeath(mImsDeathRecipient, 0);
             }
-            notifyAllFeaturesRemoved();
+            cleanupAllFeatures();
             cleanUpService();
         }
     }
@@ -373,6 +373,7 @@
                     boolean bindSucceeded = startBindToService(imsServiceIntent,
                             mImsServiceConnection, serviceFlags);
                     if (!bindSucceeded) {
+                        mIsBinding = false;
                         mBackoff.notifyFailed();
                     }
                     return bindSucceeded;
@@ -700,8 +701,7 @@
     }
 
     // This method should only be called when synchronized on mLock
-    private void removeImsServiceFeature(ImsFeatureConfiguration.FeatureSlotPair featurePair)
-            throws RemoteException {
+    private void removeImsServiceFeature(ImsFeatureConfiguration.FeatureSlotPair featurePair) {
         if (!isServiceControllerAvailable() || mCallbacks == null) {
             Log.w(LOG_TAG, "removeImsServiceFeature called with null values.");
             return;
@@ -714,11 +714,18 @@
             if (callbackToRemove != null) {
                 mFeatureStatusCallbacks.remove(callbackToRemove);
             }
-            removeImsFeature(featurePair.slotId, featurePair.featureType,
-                    (callbackToRemove != null ? callbackToRemove.getCallback() : null));
             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);
@@ -773,19 +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 (ImsFeatureConfiguration.FeatureSlotPair feature : mImsFeatures) {
-                if (feature.featureType != ImsFeature.FEATURE_EMERGENCY_MMTEL) {
-                    // don't update ImsServiceController for emergency MMTEL.
-                    mCallbacks.imsServiceFeatureRemoved(feature.slotId, feature.featureType, this);
-                }
-                sendImsFeatureRemovedCallback(feature.slotId, feature.featureType);
+            // 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();
         }
     }
 
diff --git a/src/java/com/android/internal/telephony/ims/ImsServiceControllerCompat.java b/src/java/com/android/internal/telephony/ims/ImsServiceControllerCompat.java
index e8e242d..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);
@@ -104,7 +104,7 @@
      * @return the IImsRegistration that corresponds to the slot id specified.
      */
     @Override
-    public IImsRegistration getRegistration(int slotId) throws RemoteException {
+    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);
@@ -117,7 +117,7 @@
      * @return the IImsConfig that corresponds to the slot id specified.
      */
     @Override
-    public IImsConfig getConfig(int slotId) throws RemoteException {
+    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);
@@ -127,13 +127,14 @@
     }
 
     @Override
-    protected void notifyImsServiceReady() throws RemoteException {
+    protected final void notifyImsServiceReady() {
         Log.d(TAG, "notifyImsServiceReady");
         // don't do anything for compat impl.
     }
 
     @Override
-    protected IInterface createImsFeature(int slotId, int featureType, IImsFeatureStatusCallback c)
+    protected final IInterface createImsFeature(int slotId, int featureType,
+            IImsFeatureStatusCallback c)
             throws RemoteException {
         switch (featureType) {
             case ImsFeature.MMTEL: {
@@ -148,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/imsphone/ImsPhone.java b/src/java/com/android/internal/telephony/imsphone/ImsPhone.java
index 3333697..ab39212 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhone.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhone.java
@@ -256,8 +256,11 @@
         // setting the multiendpoint listener on the external call tracker.  So we need to ensure
         // the external call tracker is available first to avoid potential timing issues.
         mExternalCallTracker =
-                TelephonyComponentFactory.getInstance().makeImsExternalCallTracker(this);
-        mCT = TelephonyComponentFactory.getInstance().makeImsPhoneCallTracker(this);
+                TelephonyComponentFactory.getInstance()
+                        .inject(ImsExternalCallTracker.class.getName())
+                        .makeImsExternalCallTracker(this);
+        mCT = TelephonyComponentFactory.getInstance().inject(ImsPhoneCallTracker.class.getName())
+                .makeImsPhoneCallTracker(this);
         mCT.registerPhoneStateListener(mExternalCallTracker);
         mExternalCallTracker.setCallPuller(mCT);
 
@@ -356,7 +359,7 @@
     @Override
     public void
     switchHoldingAndActive() throws CallStateException {
-        mCT.switchWaitingOrHoldingAndActive();
+        throw new UnsupportedOperationException("Use hold() and unhold() instead.");
     }
 
     @Override
@@ -365,7 +368,12 @@
     }
 
     public boolean canDial() {
-        return mCT.canDial();
+        try {
+            mCT.checkForDialIssues();
+        } catch (CallStateException cse) {
+            return false;
+        }
+        return true;
     }
 
     @Override
@@ -411,6 +419,23 @@
         return mCT.isImsServiceReady();
     }
 
+    /**
+     * Hold the currently active call, possibly unholding a currently held call.
+     * @throws CallStateException
+     */
+    public void holdActiveCall() throws CallStateException {
+        mCT.holdActiveCall();
+    }
+
+    /**
+     * Unhold the currently active call, possibly holding a currently active call.
+     * If the call tracker is already in the middle of a hold operation, this is a noop.
+     * @throws CallStateException
+     */
+    public void unholdHeldCall() throws CallStateException {
+        mCT.unholdHeldCall();
+    }
+
     private boolean handleCallDeflectionIncallSupplementaryService(
             String dialString) {
         if (dialString.length() > 1) {
@@ -494,8 +519,8 @@
                     if (DBG) logd("MmiCode 1: hangup foreground");
                     mCT.hangup(call);
                 } else {
-                    if (DBG) logd("MmiCode 1: switchWaitingOrHoldingAndActive");
-                    mCT.switchWaitingOrHoldingAndActive();
+                    if (DBG) logd("MmiCode 1: holdActiveCallForWaitingCall");
+                    mCT.holdActiveCallForWaitingCall();
                 }
             }
         } catch (CallStateException e) {
@@ -522,8 +547,8 @@
                     if (DBG) logd("MmiCode 2: accept ringing call");
                     mCT.acceptCall(ImsCallProfile.CALL_TYPE_VOICE);
                 } else {
-                    if (DBG) logd("MmiCode 2: switchWaitingOrHoldingAndActive");
-                    mCT.switchWaitingOrHoldingAndActive();
+                    if (DBG) logd("MmiCode 2: holdActiveCall");
+                    mCT.holdActiveCall();
                 }
             } catch (CallStateException e) {
                 if (DBG) Rlog.d(LOG_TAG, "switch failed", e);
@@ -1438,8 +1463,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:
@@ -1448,7 +1479,7 @@
                 // only update if roaming status changed
                 boolean newRoaming = getCurrentRoaming();
                 if (mRoaming != newRoaming) {
-                    updateRoamingState(newRoaming);
+                    updateRoamingState(newRoaming, true);
                 }
                 break;
 
@@ -1592,6 +1623,11 @@
     }
 
     @Override
+    public boolean isImsCapabilityAvailable(int capability, int regTech) {
+        return mCT.isImsCapabilityAvailable(capability, regTech);
+    }
+
+    @Override
     public boolean isVolteEnabled() {
         return mCT.isVolteEnabled();
     }
@@ -1810,12 +1846,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,
@@ -1826,7 +1866,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/ImsPhoneBase.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneBase.java
index 7361e70..c08ce6c 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneBase.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneBase.java
@@ -23,9 +23,6 @@
 import android.os.Message;
 import android.os.RegistrantList;
 import android.os.SystemProperties;
-import android.os.WorkSource;
-import android.telephony.CellInfo;
-import android.telephony.CellLocation;
 import android.telephony.NetworkScanRequest;
 import android.telephony.Rlog;
 import android.telephony.ServiceState;
@@ -145,19 +142,6 @@
         return s;
     }
 
-    /**
-     * @return all available cell information or null if none.
-     */
-    @Override
-    public List<CellInfo> getAllCellInfo(WorkSource workSource) {
-        return getServiceStateTracker().getAllCellInfo(workSource);
-    }
-
-    @Override
-    public CellLocation getCellLocation(WorkSource workSource) {
-        return null;
-    }
-
     @Override
     public PhoneConstants.State getState() {
         return mState;
@@ -495,7 +479,7 @@
     }
 
     @Override
-    public boolean isDataAllowed() {
+    public boolean isDataAllowed(int apnType) {
         return false;
     }
 
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
index 7a5275c..8fea1aa 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
@@ -46,6 +46,7 @@
 import android.telecom.ConferenceParticipant;
 import android.telecom.TelecomManager;
 import android.telecom.VideoProfile;
+import android.telephony.AccessNetworkConstants.TransportType;
 import android.telephony.CarrierConfigManager;
 import android.telephony.DisconnectCause;
 import android.telephony.PhoneNumberUtils;
@@ -55,12 +56,13 @@
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.telephony.ims.ImsCallProfile;
+import android.telephony.ims.ImsMmTelManager;
 import android.telephony.ims.ImsReasonInfo;
 import android.telephony.ims.ImsStreamMediaProfile;
 import android.telephony.ims.ImsSuppServiceNotification;
+import android.telephony.ims.ProvisioningManager;
 import android.telephony.ims.feature.ImsFeature;
 import android.telephony.ims.feature.MmTelFeature;
-import android.telephony.ims.stub.ImsConfigImplBase;
 import android.telephony.ims.stub.ImsRegistrationImplBase;
 import android.text.TextUtils;
 import android.util.ArrayMap;
@@ -270,7 +272,6 @@
     static final int MAX_CONNECTIONS_PER_CALL = 5;
 
     private static final int EVENT_HANGUP_PENDINGMO = 18;
-    private static final int EVENT_RESUME_BACKGROUND = 19;
     private static final int EVENT_DIAL_PENDINGMO = 20;
     private static final int EVENT_EXIT_ECBM_BEFORE_PENDINGMO = 21;
     private static final int EVENT_VT_DATA_USAGE_UPDATE = 22;
@@ -280,6 +281,8 @@
     private static final int EVENT_SUPP_SERVICE_INDICATION = 27;
     private static final int EVENT_REDIAL_WIFI_E911_CALL = 28;
     private static final int EVENT_REDIAL_WIFI_E911_TIMEOUT = 29;
+    private static final int EVENT_ANSWER_WAITING_CALL = 30;
+    private static final int EVENT_RESUME_NOW_FOREGROUND_CALL = 31;
 
     private static final int TIMEOUT_HANGUP_PENDINGMO = 500;
 
@@ -287,6 +290,24 @@
 
     private static final int TIMEOUT_REDIAL_WIFI_E911_MS = 10000;
 
+    // Following values are for mHoldSwitchingState
+    private enum HoldSwapState {
+        // Not in the middle of a hold/swap operation
+        INACTIVE,
+        // Pending a single call getting held
+        PENDING_SINGLE_CALL_HOLD,
+        // Pending a single call getting unheld
+        PENDING_SINGLE_CALL_UNHOLD,
+        // Pending swapping a active and a held call
+        SWAPPING_ACTIVE_AND_HELD,
+        // Pending holding a call to answer a call-waiting call
+        HOLDING_TO_ANSWER_INCOMING,
+        // Pending resuming the foreground call after some kind of failure
+        PENDING_RESUME_FOREGROUND_AFTER_FAILURE,
+        // Pending holding a call to dial another outgoing call
+        HOLDING_TO_DIAL_OUTGOING,
+    }
+
     //***** Instance Variables
     private ArrayList<ImsPhoneConnection> mConnections = new ArrayList<ImsPhoneConnection>();
     private RegistrantList mVoiceCallEndedRegistrants = new RegistrantList();
@@ -342,6 +363,9 @@
     private boolean mIsViLteDataMetered = false;
     private boolean mAlwaysPlayRemoteHoldTone = false;
     private boolean mAutoRetryFailedWifiEmergencyCall = false;
+    // Tracks the state of our background/foreground calls while a call hold/swap operation is
+    // in progress. Values listed above.
+    private HoldSwapState mHoldSwitchingState = HoldSwapState.INACTIVE;
 
     private String mLastDialString = null;
     private PhoneInternalInterface.DialArgs mLastDialArgs = null;
@@ -464,6 +488,8 @@
                 PreciseDisconnectCause.SIP_NOT_REACHABLE);
         PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_SIP_CLIENT_ERROR,
                 PreciseDisconnectCause.SIP_CLIENT_ERROR);
+        PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_SIP_TRANSACTION_DOES_NOT_EXIST,
+                PreciseDisconnectCause.SIP_TRANSACTION_DOES_NOT_EXIST);
         PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_SIP_SERVER_INTERNAL_ERROR,
                 PreciseDisconnectCause.SIP_SERVER_INTERNAL_ERROR);
         PRECISE_CAUSE_MAP.append(ImsReasonInfo.CODE_SIP_SERVICE_UNAVAILABLE,
@@ -627,12 +653,12 @@
     private boolean mNotifyHandoverVideoFromLTEToWifi = false;
 
     /**
-     * When {@code} false, indicates that no handover from LTE to WIFI has occurred during the start
-     * of the call.
+     * When {@code} false, indicates that no handover from LTE to WIFI has been attempted during the
+     * start of the call.
      * When {@code true}, indicates that the start of call handover from LTE to WIFI has been
-     * attempted (it may have suceeded or failed).
+     * attempted (it may have succeeded or failed).
      */
-    private boolean mHasPerformedStartOfCallHandover = false;
+    private boolean mHasAttemptedStartOfCallHandover = false;
 
     /**
      * Carrier configuration option which determines whether the carrier supports the
@@ -812,7 +838,7 @@
         // Only close on valid session.
         if (mImsManager != null) {
             try {
-                mImsManager.getConfigInterface().removeConfigCallback(mConfigCallback);
+                mImsManager.getConfigInterface().removeConfigCallback(mConfigCallback.getBinder());
             } catch (ImsException e) {
                 Log.w(LOG_TAG, "stopListeningForCalls: unable to remove config callback.");
             }
@@ -914,9 +940,9 @@
             throw new CallStateException("service not available");
         }
 
-        if (!canDial()) {
-            throw new CallStateException("cannot dial in current state");
-        }
+        // See if there are any issues which preclude placing a call; throw a CallStateException
+        // if there is.
+        checkForDialIssues();
 
         if (isPhoneInEcmMode && isEmergencyNumber) {
             handleEcmTimer(ImsPhone.CANCEL_ECM_TIMER);
@@ -937,15 +963,16 @@
         // there on hold
         if (mForegroundCall.getState() == ImsPhoneCall.State.ACTIVE) {
             if (mBackgroundCall.getState() != ImsPhoneCall.State.IDLE) {
-                //we should have failed in !canDial() above before we get here
-                throw new CallStateException("cannot dial in current state");
+                //we should have failed in checkForDialIssues above before we get here
+                throw new CallStateException(CallStateException.ERROR_TOO_MANY_CALLS,
+                        "Already too many ongoing calls.");
             }
             // foreground call is empty for the newly dialed connection
             holdBeforeDial = true;
             // Cache the video state for pending MO call.
             mPendingCallVideoState = videoState;
             mPendingIntentExtras = dialArgs.intentExtras;
-            switchWaitingOrHoldingAndActive();
+            holdActiveCallForPendingMo();
         }
 
         ImsPhoneCall.State fgState = ImsPhoneCall.State.IDLE;
@@ -1126,6 +1153,9 @@
                         fromCode = Integer.parseInt(values[0]);
                     }
                     String message = values[1];
+                    if (message == null) {
+                        message = "";
+                    }
                     int toCode = Integer.parseInt(values[2]);
 
                     addReasonCodeRemapping(fromCode, message, toCode);
@@ -1237,7 +1267,7 @@
      * @param videoState The video State
      * @throws CallStateException
      */
-    public void acceptCall (int videoState) throws CallStateException {
+    public void acceptCall(int videoState) throws CallStateException {
         if (DBG) log("acceptCall");
 
         if (mForegroundCall.getState().isAlive()
@@ -1269,7 +1299,7 @@
                     throw new CallStateException("cannot accept call");
                 }
             } else {
-                switchWaitingOrHoldingAndActive();
+                holdActiveCallForWaitingCall();
             }
         } else if (mRingingCall.getState().isRinging()) {
             if (DBG) log("acceptCall: incoming...");
@@ -1313,51 +1343,123 @@
         }
     }
 
-    public void switchWaitingOrHoldingAndActive() throws CallStateException {
-        if (DBG) log("switchWaitingOrHoldingAndActive");
-
-        if (mRingingCall.getState() == ImsPhoneCall.State.INCOMING) {
-            throw new CallStateException("cannot be in the incoming state");
+    private void holdActiveCallForPendingMo() throws CallStateException {
+        if (mHoldSwitchingState == HoldSwapState.PENDING_SINGLE_CALL_HOLD
+                || mHoldSwitchingState == HoldSwapState.SWAPPING_ACTIVE_AND_HELD) {
+            logi("Ignoring hold request while already holding or swapping");
+            return;
         }
+        ImsCall callToHold = mForegroundCall.getImsCall();
 
-        if (mForegroundCall.getState() == ImsPhoneCall.State.ACTIVE) {
-            ImsCall imsCall = mForegroundCall.getImsCall();
-            if (imsCall == null) {
-                throw new CallStateException("no ims call");
-            }
+        mHoldSwitchingState = HoldSwapState.HOLDING_TO_DIAL_OUTGOING;
+        logHoldSwapState("holdActiveCallForPendingMo");
 
-            // Swap the ImsCalls pointed to by the foreground and background ImsPhoneCalls.
-            // If hold or resume later fails, we will swap them back.
-            boolean switchingWithWaitingCall = !mBackgroundCall.getState().isAlive() &&
-                    mRingingCall != null &&
-                    mRingingCall.getState() == ImsPhoneCall.State.WAITING;
-
-            mSwitchingFgAndBgCalls = true;
-            if (switchingWithWaitingCall) {
-                mCallExpectedToResume = mRingingCall.getImsCall();
-            } else {
-                mCallExpectedToResume = mBackgroundCall.getImsCall();
-            }
+        mForegroundCall.switchWith(mBackgroundCall);
+        try {
+            callToHold.hold();
+            mMetrics.writeOnImsCommand(mPhone.getPhoneId(), callToHold.getSession(),
+                    ImsCommand.IMS_CMD_HOLD);
+        } catch (ImsException e) {
             mForegroundCall.switchWith(mBackgroundCall);
+            throw new CallStateException(e.getMessage());
+        }
+    }
 
-            // Hold the foreground call; once the foreground call is held, the background call will
-            // be resumed.
+    /**
+     * Holds the active call, possibly resuming the already-held background call if it exists.
+     */
+    public void holdActiveCall() throws CallStateException {
+        if (mForegroundCall.getState() == ImsPhoneCall.State.ACTIVE) {
+            if (mHoldSwitchingState == HoldSwapState.PENDING_SINGLE_CALL_HOLD
+                    || mHoldSwitchingState == HoldSwapState.SWAPPING_ACTIVE_AND_HELD) {
+                logi("Ignoring hold request while already holding or swapping");
+                return;
+            }
+            ImsCall callToHold = mForegroundCall.getImsCall();
+            if (mBackgroundCall.getState().isAlive()) {
+                mCallExpectedToResume = mBackgroundCall.getImsCall();
+                mHoldSwitchingState = HoldSwapState.SWAPPING_ACTIVE_AND_HELD;
+            } else {
+                mHoldSwitchingState = HoldSwapState.PENDING_SINGLE_CALL_HOLD;
+            }
+            logHoldSwapState("holdActiveCall");
+            mForegroundCall.switchWith(mBackgroundCall);
             try {
-                imsCall.hold();
-                mMetrics.writeOnImsCommand(mPhone.getPhoneId(), imsCall.getSession(),
+                callToHold.hold();
+                mMetrics.writeOnImsCommand(mPhone.getPhoneId(), callToHold.getSession(),
                         ImsCommand.IMS_CMD_HOLD);
-
-                // If there is no background call to resume, then don't expect there to be a switch.
-                if (mCallExpectedToResume == null) {
-                    log("mCallExpectedToResume is null");
-                    mSwitchingFgAndBgCalls = false;
-                }
             } catch (ImsException e) {
                 mForegroundCall.switchWith(mBackgroundCall);
                 throw new CallStateException(e.getMessage());
             }
-        } else if (mBackgroundCall.getState() == ImsPhoneCall.State.HOLDING) {
-            resumeWaitingOrHolding();
+        }
+    }
+
+    /**
+     * Hold the currently active call in order to answer the waiting call.
+     */
+    public void holdActiveCallForWaitingCall() throws CallStateException {
+        boolean switchingWithWaitingCall = !mBackgroundCall.getState().isAlive()
+                && mRingingCall.getState() == ImsPhoneCall.State.WAITING;
+        if (switchingWithWaitingCall) {
+            ImsCall callToHold = mForegroundCall.getImsCall();
+            mHoldSwitchingState = HoldSwapState.HOLDING_TO_ANSWER_INCOMING;
+            mForegroundCall.switchWith(mBackgroundCall);
+            logHoldSwapState("holdActiveCallForWaitingCall");
+            try {
+                callToHold.hold();
+                mMetrics.writeOnImsCommand(mPhone.getPhoneId(), callToHold.getSession(),
+                        ImsCommand.IMS_CMD_HOLD);
+            } catch (ImsException e) {
+                mForegroundCall.switchWith(mBackgroundCall);
+                throw new CallStateException(e.getMessage());
+            }
+        }
+    }
+
+    /**
+     * Unhold the currently held call, possibly putting the already-active call on hold if present.
+     */
+    void unholdHeldCall() throws CallStateException {
+        try {
+            ImsCall imsCall = mBackgroundCall.getImsCall();
+            if (mHoldSwitchingState == HoldSwapState.PENDING_SINGLE_CALL_UNHOLD
+                    || mHoldSwitchingState == HoldSwapState.SWAPPING_ACTIVE_AND_HELD) {
+                logi("Ignoring unhold request while already unholding or swapping");
+                return;
+            }
+            if (imsCall != null) {
+                mCallExpectedToResume = imsCall;
+                mHoldSwitchingState = HoldSwapState.PENDING_SINGLE_CALL_UNHOLD;
+                logHoldSwapState("unholdCurrentCall");
+                imsCall.resume();
+                mMetrics.writeOnImsCommand(mPhone.getPhoneId(), imsCall.getSession(),
+                        ImsCommand.IMS_CMD_RESUME);
+            }
+        } catch (ImsException e) {
+            throw new CallStateException(e.getMessage());
+        }
+    }
+
+    private void resumeForegroundCall() throws ImsException {
+        //resume foreground call after holding background call
+        //they were switched before holding
+        ImsCall imsCall = mForegroundCall.getImsCall();
+        if (imsCall != null) {
+            imsCall.resume();
+            mMetrics.writeOnImsCommand(mPhone.getPhoneId(), imsCall.getSession(),
+                    ImsCommand.IMS_CMD_RESUME);
+        }
+    }
+
+    private void answerWaitingCall() throws ImsException {
+        //accept waiting call after holding background call
+        ImsCall imsCall = mRingingCall.getImsCall();
+        if (imsCall != null) {
+            imsCall.accept(
+                    ImsCallProfile.getCallTypeFromVideoState(mPendingCallVideoState));
+            mMetrics.writeOnImsCommand(mPhone.getPhoneId(), imsCall.getSession(),
+                    ImsCommand.IMS_CMD_ACCEPT);
         }
     }
 
@@ -1447,18 +1549,30 @@
             && !mForegroundCall.isFull();
     }
 
-    public boolean canDial() {
-        boolean ret;
+    /**
+     * Determines if there are issues which would preclude dialing an outgoing call.  Throws a
+     * {@link CallStateException} if there is an issue.
+     * @throws CallStateException
+     */
+    public void checkForDialIssues() throws CallStateException {
         String disableCall = SystemProperties.get(
                 TelephonyProperties.PROPERTY_DISABLE_CALL, "false");
-
-        ret = mPendingMO == null
-                && !mRingingCall.isRinging()
-                && !disableCall.equals("true")
-                && (!mForegroundCall.getState().isAlive()
-                        || !mBackgroundCall.getState().isAlive());
-
-        return ret;
+        if (disableCall.equals("true")) {
+            throw new CallStateException(CallStateException.ERROR_CALLING_DISABLED,
+                    "ro.telephony.disable-call has been used to disable calling.");
+        }
+        if (mPendingMO != null) {
+            throw new CallStateException(CallStateException.ERROR_ALREADY_DIALING,
+                    "Another outgoing call is already being dialed.");
+        }
+        if (mRingingCall.isRinging()) {
+            throw new CallStateException(CallStateException.ERROR_CALL_RINGING,
+                    "Can't place a call while another is ringing.");
+        }
+        if (mForegroundCall.getState().isAlive() & mBackgroundCall.getState().isAlive()) {
+            throw new CallStateException(CallStateException.ERROR_TOO_MANY_CALLS,
+                    "Already an active foreground and background call.");
+        }
     }
 
     public boolean
@@ -1728,45 +1842,6 @@
         }
     }
 
-    /* package */
-    void resumeWaitingOrHolding() throws CallStateException {
-        if (DBG) log("resumeWaitingOrHolding");
-
-        try {
-            if (mForegroundCall.getState().isAlive()) {
-                //resume foreground call after holding background call
-                //they were switched before holding
-                ImsCall imsCall = mForegroundCall.getImsCall();
-                if (imsCall != null) {
-                    imsCall.resume();
-                    mMetrics.writeOnImsCommand(mPhone.getPhoneId(), imsCall.getSession(),
-                            ImsCommand.IMS_CMD_RESUME);
-                }
-            } else if (mRingingCall.getState() == ImsPhoneCall.State.WAITING) {
-                //accept waiting call after holding background call
-                ImsCall imsCall = mRingingCall.getImsCall();
-                if (imsCall != null) {
-                    imsCall.accept(
-                        ImsCallProfile.getCallTypeFromVideoState(mPendingCallVideoState));
-                    mMetrics.writeOnImsCommand(mPhone.getPhoneId(), imsCall.getSession(),
-                            ImsCommand.IMS_CMD_ACCEPT);
-                }
-            } else {
-                //Just resume background call.
-                //To distinguish resuming call with swapping calls
-                //we do not switch calls.here
-                //ImsPhoneConnection.update will chnage the parent when completed
-                ImsCall imsCall = mBackgroundCall.getImsCall();
-                if (imsCall != null) {
-                    imsCall.resume();
-                    mMetrics.writeOnImsCommand(mPhone.getPhoneId(), imsCall.getSession(),
-                            ImsCommand.IMS_CMD_RESUME);
-                }
-            }
-        } catch (ImsException e) {
-            throw new CallStateException(e.getMessage());
-        }
-    }
 
     public void sendUSSD (String ussdString, Message response) {
         if (DBG) log("sendUSSD");
@@ -1791,6 +1866,7 @@
                     ImsCallProfile.DIALSTRING_USSD);
 
             mUssdSession = mImsManager.makeCall(profile, callees, mImsUssdListener);
+            mPendingUssd = response;
         } catch (ImsException e) {
             loge("sendUSSD : " + e);
             mPhone.sendErrorResponse(response, e);
@@ -1801,11 +1877,7 @@
     public void cancelUSSD() {
         if (mUssdSession == null) return;
 
-        try {
-            mUssdSession.terminate(ImsReasonInfo.CODE_USER_TERMINATED);
-        } catch (ImsException e) {
-        }
-
+        mUssdSession.terminate(ImsReasonInfo.CODE_USER_TERMINATED);
     }
 
     private synchronized ImsPhoneConnection findConnection(final ImsCall imsCall) {
@@ -1937,22 +2009,29 @@
     @VisibleForTesting
     public int maybeRemapReasonCode(ImsReasonInfo reasonInfo) {
         int code = reasonInfo.getCode();
-
-        Pair<Integer, String> toCheck = new Pair<>(code, reasonInfo.getExtraMessage());
-        Pair<Integer, String> wildcardToCheck = new Pair<>(null, reasonInfo.getExtraMessage());
+        String reason = reasonInfo.getExtraMessage();
+        if (reason == null) {
+            reason = "";
+        }
+        log("maybeRemapReasonCode : fromCode = " + reasonInfo.getCode() + " ; message = "
+                + reason);
+        Pair<Integer, String> toCheck = new Pair<>(code, reason);
+        Pair<Integer, String> wildcardToCheck = new Pair<>(null, reason);
         if (mImsReasonCodeMap.containsKey(toCheck)) {
             int toCode = mImsReasonCodeMap.get(toCheck);
 
             log("maybeRemapReasonCode : fromCode = " + reasonInfo.getCode() + " ; message = "
-                    + reasonInfo.getExtraMessage() + " ; toCode = " + toCode);
+                    + reason + " ; toCode = " + toCode);
             return toCode;
-        } else if (mImsReasonCodeMap.containsKey(wildcardToCheck)) {
+        } else if (!reason.isEmpty() && mImsReasonCodeMap.containsKey(wildcardToCheck)) {
             // Handle the case where a wildcard is specified for the fromCode; in this case we will
             // match without caring about the fromCode.
+            // If the reason is empty, we won't do wildcard remapping; otherwise we'd basically be
+            // able to remap all ImsReasonInfo codes to a single code, which is not desirable.
             int toCode = mImsReasonCodeMap.get(wildcardToCheck);
 
             log("maybeRemapReasonCode : fromCode(wildcard) = " + reasonInfo.getCode() +
-                    " ; message = " + reasonInfo.getExtraMessage() + " ; toCode = " + toCode);
+                    " ; message = " + reason + " ; toCode = " + toCode);
             return toCode;
         }
         return code;
@@ -2006,10 +2085,12 @@
                 return DisconnectCause.SERVER_ERROR;
 
             case ImsReasonInfo.CODE_SIP_SERVICE_UNAVAILABLE:
-            case ImsReasonInfo.CODE_SIP_NOT_FOUND:
             case ImsReasonInfo.CODE_SIP_SERVER_ERROR:
                 return DisconnectCause.SERVER_UNREACHABLE;
 
+            case ImsReasonInfo.CODE_SIP_NOT_FOUND:
+                return DisconnectCause.INVALID_NUMBER;
+
             case ImsReasonInfo.CODE_LOCAL_NETWORK_ROAMING:
             case ImsReasonInfo.CODE_LOCAL_NETWORK_IP_CHANGED:
             case ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN:
@@ -2101,6 +2182,19 @@
             case ImsReasonInfo.CODE_UNOBTAINABLE_NUMBER:
                 return DisconnectCause.UNOBTAINABLE_NUMBER;
 
+            case ImsReasonInfo.CODE_UNSPECIFIED:
+                if (mPhone.getDefaultPhone().getServiceStateTracker().mRestrictedState
+                        .isCsRestricted()) {
+                    return DisconnectCause.CS_RESTRICTED;
+                } else if (mPhone.getDefaultPhone().getServiceStateTracker().mRestrictedState
+                        .isCsEmergencyRestricted()) {
+                    return DisconnectCause.CS_RESTRICTED_EMERGENCY;
+                } else if (mPhone.getDefaultPhone().getServiceStateTracker().mRestrictedState
+                        .isCsNormalRestricted()) {
+                    return DisconnectCause.CS_RESTRICTED_NORMAL;
+                }
+                break;
+
             default:
         }
 
@@ -2151,13 +2245,14 @@
         public void onCallStarted(ImsCall imsCall) {
             if (DBG) log("onCallStarted");
 
-            if (mSwitchingFgAndBgCalls) {
+            if (mHoldSwitchingState == HoldSwapState.HOLDING_TO_ANSWER_INCOMING) {
                 // If we put a call on hold to answer an incoming call, we should reset the
                 // variables that keep track of the switch here.
                 if (mCallExpectedToResume != null && mCallExpectedToResume == imsCall) {
                     if (DBG) log("onCallStarted: starting a call as a result of a switch.");
-                    mSwitchingFgAndBgCalls = false;
+                    mHoldSwitchingState = HoldSwapState.INACTIVE;
                     mCallExpectedToResume = null;
+                    logHoldSwapState("onCallStarted");
                 }
             }
 
@@ -2170,13 +2265,15 @@
                     // Schedule check to see if handover succeeded.
                     sendMessageDelayed(obtainMessage(EVENT_CHECK_FOR_WIFI_HANDOVER, imsCall),
                             HANDOVER_TO_WIFI_TIMEOUT_MS);
+                    mHasAttemptedStartOfCallHandover = false;
                 } else {
                     // No wifi connectivity, so keep track of network availability for potential
                     // handover.
                     registerForConnectivityChanges();
+                    // No WIFI, so assume we've already attempted a handover.
+                    mHasAttemptedStartOfCallHandover = true;
                 }
             }
-            mHasPerformedStartOfCallHandover = false;
             mMetrics.writeOnImsCallStarted(mPhone.getPhoneId(), imsCall.getCallSession());
         }
 
@@ -2205,13 +2302,14 @@
         public void onCallStartFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) {
             if (DBG) log("onCallStartFailed reasonCode=" + reasonInfo.getCode());
 
-            if (mSwitchingFgAndBgCalls) {
+            if (mHoldSwitchingState == HoldSwapState.HOLDING_TO_ANSWER_INCOMING) {
                 // If we put a call on hold to answer an incoming call, we should reset the
                 // variables that keep track of the switch here.
                 if (mCallExpectedToResume != null && mCallExpectedToResume == imsCall) {
                     if (DBG) log("onCallStarted: starting a call as a result of a switch.");
-                    mSwitchingFgAndBgCalls = false;
+                    mHoldSwitchingState = HoldSwapState.INACTIVE;
                     mCallExpectedToResume = null;
+                    logHoldSwapState("onCallStartFailed");
                 }
             }
 
@@ -2329,12 +2427,10 @@
                 if (mRingingCall.getState().isRinging()) {
                     // Drop pending MO. We should address incoming call first
                     mPendingMO = null;
-                } else if (mPendingMO != null) {
-                    sendEmptyMessage(EVENT_DIAL_PENDINGMO);
                 }
             }
 
-            if (mSwitchingFgAndBgCalls) {
+            if (mHoldSwitchingState == HoldSwapState.SWAPPING_ACTIVE_AND_HELD) {
                 if (DBG) {
                     log("onCallTerminated: Call terminated in the midst of Switching " +
                             "Fg and Bg calls.");
@@ -2355,11 +2451,38 @@
                         " and ringing call in state " + (mRingingCall == null ? "null" :
                         mRingingCall.getState().toString()));
 
-                if (mForegroundCall.getState() == ImsPhoneCall.State.HOLDING ||
-                        mRingingCall.getState() == ImsPhoneCall.State.WAITING) {
-                    sendEmptyMessage(EVENT_RESUME_BACKGROUND);
-                    mSwitchingFgAndBgCalls = false;
+                sendEmptyMessage(EVENT_RESUME_NOW_FOREGROUND_CALL);
+                mHoldSwitchingState = HoldSwapState.INACTIVE;
+                mCallExpectedToResume = null;
+                logHoldSwapState("onCallTerminated swap active and hold case");
+            } else if (mHoldSwitchingState == HoldSwapState.PENDING_SINGLE_CALL_UNHOLD
+                    || mHoldSwitchingState == HoldSwapState.PENDING_SINGLE_CALL_HOLD) {
+                mCallExpectedToResume = null;
+                mHoldSwitchingState = HoldSwapState.INACTIVE;
+                logHoldSwapState("onCallTerminated single call case");
+            } else if (mHoldSwitchingState == HoldSwapState.HOLDING_TO_ANSWER_INCOMING) {
+                // Check to see which call got terminated. If it's the one that was gonna get held,
+                // ignore it. If it's the one that was gonna get answered, restore the one that
+                // possibly got held.
+                if (imsCall == mCallExpectedToResume) {
+                    mForegroundCall.switchWith(mBackgroundCall);
                     mCallExpectedToResume = null;
+                    mHoldSwitchingState = HoldSwapState.INACTIVE;
+                    logHoldSwapState("onCallTerminated hold to answer case");
+                    sendEmptyMessage(EVENT_RESUME_NOW_FOREGROUND_CALL);
+                }
+            } else if (mHoldSwitchingState == HoldSwapState.HOLDING_TO_DIAL_OUTGOING) {
+                // The call that we were gonna hold might've gotten terminated. If that's the case,
+                // dial mPendingMo if present.
+                if (mPendingMO == null
+                        || mPendingMO.getDisconnectCause() != DisconnectCause.NOT_DISCONNECTED) {
+                    mHoldSwitchingState = HoldSwapState.INACTIVE;
+                    logHoldSwapState("onCallTerminated hold to dial but no pendingMo");
+                }
+                if (imsCall != mPendingMO.getImsCall()) {
+                    sendEmptyMessage(EVENT_DIAL_PENDINGMO);
+                    mHoldSwitchingState = HoldSwapState.INACTIVE;
+                    logHoldSwapState("onCallTerminated hold to dial, dial pendingMo");
                 }
             }
 
@@ -2395,31 +2518,36 @@
                 if (oldState == ImsPhoneCall.State.ACTIVE) {
                     // Note: This case comes up when we have just held a call in response to a
                     // switchWaitingOrHoldingAndActive.  We now need to resume the background call.
-                    // The EVENT_RESUME_BACKGROUND causes resumeWaitingOrHolding to be called.
-                    if ((mForegroundCall.getState() == ImsPhoneCall.State.HOLDING)
-                            || (mRingingCall.getState() == ImsPhoneCall.State.WAITING)) {
-                            sendEmptyMessage(EVENT_RESUME_BACKGROUND);
+                    if (mForegroundCall.getState() == ImsPhoneCall.State.HOLDING
+                            && mHoldSwitchingState == HoldSwapState.SWAPPING_ACTIVE_AND_HELD) {
+                        sendEmptyMessage(EVENT_RESUME_NOW_FOREGROUND_CALL);
+                    } else if (mRingingCall.getState() == ImsPhoneCall.State.WAITING
+                            && mHoldSwitchingState == HoldSwapState.HOLDING_TO_ANSWER_INCOMING) {
+                        sendEmptyMessage(EVENT_ANSWER_WAITING_CALL);
+                    } else if (mPendingMO != null
+                            && mHoldSwitchingState == HoldSwapState.HOLDING_TO_DIAL_OUTGOING) {
+                        dialPendingMO();
+                        mHoldSwitchingState = HoldSwapState.INACTIVE;
+                        logHoldSwapState("onCallHeld hold to dial");
                     } else {
-                        //when multiple connections belong to background call,
-                        //only the first callback reaches here
-                        //otherwise the oldState is already HOLDING
-                        if (mPendingMO != null) {
-                            dialPendingMO();
-                        }
-
                         // In this case there will be no call resumed, so we can assume that we
                         // are done switching fg and bg calls now.
                         // This may happen if there is no BG call and we are holding a call so that
                         // we can dial another one.
-                        mSwitchingFgAndBgCalls = false;
+                        mHoldSwitchingState = HoldSwapState.INACTIVE;
+                        logHoldSwapState("onCallHeld normal case");
                     }
-                } else if (oldState == ImsPhoneCall.State.IDLE && mSwitchingFgAndBgCalls) {
+                } else if (oldState == ImsPhoneCall.State.IDLE
+                        && (mHoldSwitchingState == HoldSwapState.SWAPPING_ACTIVE_AND_HELD
+                                || mHoldSwitchingState
+                                == HoldSwapState.HOLDING_TO_ANSWER_INCOMING)) {
                     // The other call terminated in the midst of a switch before this call was held,
                     // so resume the foreground call back to ACTIVE state since the switch failed.
                     if (mForegroundCall.getState() == ImsPhoneCall.State.HOLDING) {
-                        sendEmptyMessage(EVENT_RESUME_BACKGROUND);
-                        mSwitchingFgAndBgCalls = false;
+                        sendEmptyMessage(EVENT_RESUME_NOW_FOREGROUND_CALL);
+                        mHoldSwitchingState = HoldSwapState.INACTIVE;
                         mCallExpectedToResume = null;
+                        logHoldSwapState("onCallHeld premature termination of other call");
                     }
                 }
             }
@@ -2437,6 +2565,16 @@
                     if (mPendingMO != null) {
                         dialPendingMO();
                     }
+                    mHoldSwitchingState = HoldSwapState.INACTIVE;
+                } else if (mPendingMO.isEmergency()) {
+                    // If mPendingMO is an emergency call, disconnect the call that we tried to
+                    // hold.
+                    mBackgroundCall.getImsCall().terminate(ImsReasonInfo.CODE_UNSPECIFIED);
+                    if (imsCall != mCallExpectedToResume) {
+                        mCallExpectedToResume = null;
+                    }
+                    // Leave mHoldSwitchingState as is for now -- we'll reset it
+                    // in onCallTerminated, which will also dial the outgoing emergency call.
                 } else if (bgState == ImsPhoneCall.State.ACTIVE) {
                     mForegroundCall.switchWith(mBackgroundCall);
 
@@ -2444,6 +2582,10 @@
                         mPendingMO.setDisconnectCause(DisconnectCause.ERROR_UNSPECIFIED);
                         sendEmptyMessageDelayed(EVENT_HANGUP_PENDINGMO, TIMEOUT_HANGUP_PENDINGMO);
                     }
+                    if (imsCall != mCallExpectedToResume) {
+                        mCallExpectedToResume = null;
+                    }
+                    mHoldSwitchingState = HoldSwapState.INACTIVE;
                 }
                 mPhone.notifySuppServiceFailed(Phone.SuppService.HOLD);
             }
@@ -2458,7 +2600,9 @@
             // If we are the in midst of swapping FG and BG calls and the call we end up resuming
             // is not the one we expected, we likely had a resume failure and we need to swap the
             // FG and BG calls back.
-            if (mSwitchingFgAndBgCalls) {
+            if (mHoldSwitchingState == HoldSwapState.SWAPPING_ACTIVE_AND_HELD
+                    || mHoldSwitchingState == HoldSwapState.PENDING_RESUME_FOREGROUND_AFTER_FAILURE
+                    || mHoldSwitchingState == HoldSwapState.PENDING_SINGLE_CALL_UNHOLD) {
                 if (imsCall != mCallExpectedToResume) {
                     // If the call which resumed isn't as expected, we need to swap back to the
                     // previous configuration; the swap has failed.
@@ -2474,8 +2618,9 @@
                         log("onCallResumed : expected call resumed.");
                     }
                 }
-                mSwitchingFgAndBgCalls = false;
+                mHoldSwitchingState = HoldSwapState.INACTIVE;
                 mCallExpectedToResume = null;
+                logHoldSwapState("onCallResumed");
             }
             processCallStateChange(imsCall, ImsPhoneCall.State.ACTIVE,
                     DisconnectCause.NOT_DISCONNECTED);
@@ -2484,7 +2629,9 @@
 
         @Override
         public void onCallResumeFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) {
-            if (mSwitchingFgAndBgCalls) {
+            if (mHoldSwitchingState == HoldSwapState.SWAPPING_ACTIVE_AND_HELD
+                    || mHoldSwitchingState == HoldSwapState.PENDING_RESUME_FOREGROUND_AFTER_FAILURE
+                    || mHoldSwitchingState == HoldSwapState.PENDING_SINGLE_CALL_UNHOLD) {
                 // If we are in the midst of swapping the FG and BG calls and
                 // we got a resume fail, we need to swap back the FG and BG calls.
                 // Since the FG call was held, will also try to resume the same.
@@ -2495,13 +2642,14 @@
                     }
                     mForegroundCall.switchWith(mBackgroundCall);
                     if (mForegroundCall.getState() == ImsPhoneCall.State.HOLDING) {
-                            sendEmptyMessage(EVENT_RESUME_BACKGROUND);
+                        sendEmptyMessage(EVENT_RESUME_NOW_FOREGROUND_CALL);
                     }
                 }
 
                 //Call swap is done, reset the relevant variables
                 mCallExpectedToResume = null;
-                mSwitchingFgAndBgCalls = false;
+                mHoldSwitchingState = HoldSwapState.INACTIVE;
+                logHoldSwapState("onCallResumeFailed");
             }
             mPhone.notifySuppServiceFailed(Phone.SuppService.RESUME);
             mMetrics.writeOnImsCallResumeFailed(mPhone.getPhoneId(), imsCall.getCallSession(),
@@ -2616,6 +2764,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);
@@ -2650,9 +2800,25 @@
         @Override
         public void onCallHandover(ImsCall imsCall, int srcAccessTech, int targetAccessTech,
             ImsReasonInfo reasonInfo) {
+            // Check with the DCTracker to see if data is enabled; there may be a case when
+            // ImsPhoneCallTracker isn't being informed of the right data enabled state via its
+            // registration, so we'll refresh now.
+            boolean isDataEnabled = false;
+            if (mPhone.getDefaultPhone().getDcTracker(TransportType.WWAN) != null) {
+                isDataEnabled = mPhone.getDefaultPhone().getDcTracker(TransportType.WWAN)
+                        .isDataEnabled();
+            }
+
             if (DBG) {
-                log("onCallHandover ::  srcAccessTech=" + srcAccessTech + ", targetAccessTech=" +
-                        targetAccessTech + ", reasonInfo=" + reasonInfo);
+                log("onCallHandover ::  srcAccessTech=" + srcAccessTech + ", targetAccessTech="
+                        + targetAccessTech + ", reasonInfo=" + reasonInfo + ", dataEnabled="
+                        + mIsDataEnabled + "/" + isDataEnabled + ", dataMetered="
+                        + mIsViLteDataMetered);
+            }
+            if (mIsDataEnabled != isDataEnabled) {
+                loge("onCallHandover: data enabled state doesn't match! (was=" + mIsDataEnabled
+                        + ", actually=" + isDataEnabled);
+                mIsDataEnabled = isDataEnabled;
             }
 
             // Only consider it a valid handover to WIFI if the source radio tech is known.
@@ -2671,7 +2837,7 @@
                     if (isHandoverToWifi) {
                         removeMessages(EVENT_CHECK_FOR_WIFI_HANDOVER);
 
-                        if (mNotifyHandoverVideoFromLTEToWifi && mHasPerformedStartOfCallHandover) {
+                        if (mNotifyHandoverVideoFromLTEToWifi && mHasAttemptedStartOfCallHandover) {
                             // This is a handover which happened mid-call (ie not the start of call
                             // handover from LTE to WIFI), so we'll notify the InCall UI.
                             conn.onConnectionEvent(
@@ -2688,8 +2854,16 @@
                     }
                 }
 
+                if (isHandoverToWifi && mIsViLteDataMetered) {
+                    conn.setLocalVideoCapable(true);
+                }
+
                 if (isHandoverFromWifi && imsCall.isVideoCall()) {
-                    if (mNotifyHandoverVideoFromWifiToLTE &&    mIsDataEnabled) {
+                    if (mIsViLteDataMetered) {
+                        conn.setLocalVideoCapable(mIsDataEnabled);
+                    }
+
+                    if (mNotifyHandoverVideoFromWifiToLTE && mIsDataEnabled) {
                         if (conn.getDisconnectCause() == DisconnectCause.NOT_DISCONNECTED) {
                             log("onCallHandover :: notifying of WIFI to LTE handover.");
                             conn.onConnectionEvent(
@@ -2706,15 +2880,16 @@
                     if (!mIsDataEnabled && mIsViLteDataMetered) {
                         // Call was downgraded from WIFI to LTE and data is metered; downgrade the
                         // call now.
+                        log("onCallHandover :: data is not enabled; attempt to downgrade.");
                         downgradeVideoCall(ImsReasonInfo.CODE_WIFI_LOST, conn);
                     }
                 }
             } else {
                 loge("onCallHandover :: connection null.");
             }
-
-            if (!mHasPerformedStartOfCallHandover) {
-                mHasPerformedStartOfCallHandover = true;
+            // If there's a handover, then we're not in the "start of call" handover phase.
+            if (!mHasAttemptedStartOfCallHandover) {
+                mHasAttemptedStartOfCallHandover = true;
             }
             mMetrics.writeOnImsCallHandoverEvent(mPhone.getPhoneId(),
                     TelephonyCallSession.Event.Type.IMS_CALL_HANDOVER, imsCall.getCallSession(),
@@ -2752,8 +2927,8 @@
                     conn.onHandoverToWifiFailed();
                 }
             }
-            if (!mHasPerformedStartOfCallHandover) {
-                mHasPerformedStartOfCallHandover = true;
+            if (!mHasAttemptedStartOfCallHandover) {
+                mHasAttemptedStartOfCallHandover = true;
             }
         }
 
@@ -2828,7 +3003,7 @@
         public void onCallTerminated(ImsCall imsCall, ImsReasonInfo reasonInfo) {
             if (DBG) log("mImsUssdListener onCallTerminated reasonCode=" + reasonInfo.getCode());
             removeMessages(EVENT_CHECK_FOR_WIFI_HANDOVER);
-            mHasPerformedStartOfCallHandover = false;
+            mHasAttemptedStartOfCallHandover = false;
             unregisterForConnectivityChanges();
 
             if (imsCall == mUssdSession) {
@@ -2865,8 +3040,8 @@
         }
     };
 
-    private final ImsRegistrationImplBase.Callback mImsRegistrationCallback =
-            new ImsRegistrationImplBase.Callback() {
+    private final ImsMmTelManager.RegistrationCallback mImsRegistrationCallback =
+            new ImsMmTelManager.RegistrationCallback() {
 
                 @Override
                 public void onRegistered(
@@ -2905,13 +3080,14 @@
                 }
             };
 
-    private final ImsFeature.CapabilityCallback mImsCapabilityCallback =
-            new ImsFeature.CapabilityCallback() {
+    private final ImsMmTelManager.CapabilityCallback mImsCapabilityCallback =
+            new ImsMmTelManager.CapabilityCallback() {
                 @Override
-                public void onCapabilitiesStatusChanged(ImsFeature.Capabilities config) {
-                    if (DBG) log("onCapabilitiesStatusChanged: " + config);
+                public void onCapabilitiesStatusChanged(
+                        MmTelFeature.MmTelCapabilities capabilities) {
+                    if (DBG) log("onCapabilitiesStatusChanged: " + capabilities);
                     SomeArgs args = SomeArgs.obtain();
-                    args.arg1 = config;
+                    args.arg1 = capabilities;
                     // Remove any pending updates; they're already stale, so no need to process
                     // them.
                     removeMessages(EVENT_ON_FEATURE_CAPABILITY_CHANGED);
@@ -2936,14 +3112,15 @@
 
     };
 
-    private final ImsConfigImplBase.Callback mConfigCallback = new ImsConfigImplBase.Callback() {
+    private final ProvisioningManager.Callback mConfigCallback =
+            new ProvisioningManager.Callback() {
         @Override
-        public void onConfigChanged(int item, int value) {
+        public void onProvisioningIntChanged(int item, int value) {
             sendConfigChangedIntent(item, Integer.toString(value));
         }
 
         @Override
-        public void onConfigChanged(int item, String value) {
+        public void onProvisioningStringChanged(int item, String value) {
             sendConfigChangedIntent(item, value);
         }
 
@@ -3053,12 +3230,21 @@
                 updatePhoneState();
                 mPhone.notifyPreciseCallStateChanged();
                 break;
-            case EVENT_RESUME_BACKGROUND:
+            case EVENT_RESUME_NOW_FOREGROUND_CALL:
                 try {
-                    resumeWaitingOrHolding();
-                } catch (CallStateException e) {
+                    resumeForegroundCall();
+                } catch (ImsException e) {
                     if (Phone.DEBUG_PHONE) {
-                        loge("handleMessage EVENT_RESUME_BACKGROUND exception=" + e);
+                        loge("handleMessage EVENT_RESUME_NOW_FOREGROUND_CALL exception=" + e);
+                    }
+                }
+                break;
+            case EVENT_ANSWER_WAITING_CALL:
+                try {
+                    answerWaitingCall();
+                } catch (ImsException e) {
+                    if (Phone.DEBUG_PHONE) {
+                        loge("handleMessage EVENT_ANSWER_WAITING_CALL exception=" + e);
                     }
                 }
                 break;
@@ -3118,6 +3304,9 @@
                         // Handover check and its not the foreground call any more.
                         return;
                     }
+                    if (!mHasAttemptedStartOfCallHandover) {
+                        mHasAttemptedStartOfCallHandover = true;
+                    }
                     if (!imsCall.isWifiCall()) {
                         // Call did not handover to wifi, notify of handover failure.
                         ImsPhoneConnection conn = findConnection(imsCall);
@@ -3251,6 +3440,38 @@
         Rlog.e(LOG_TAG, "[" + mPhone.getPhoneId() + "] " + msg);
     }
 
+    void logi(String msg) {
+        Rlog.i(LOG_TAG, "[" + mPhone.getPhoneId() + "] " + msg);
+    }
+
+    void logHoldSwapState(String loc) {
+        String holdSwapState = "???";
+        switch (mHoldSwitchingState) {
+            case INACTIVE:
+                holdSwapState = "INACTIVE";
+                break;
+            case PENDING_SINGLE_CALL_HOLD:
+                holdSwapState = "PENDING_SINGLE_CALL_HOLD";
+                break;
+            case PENDING_SINGLE_CALL_UNHOLD:
+                holdSwapState = "PENDING_SINGLE_CALL_UNHOLD";
+                break;
+            case SWAPPING_ACTIVE_AND_HELD:
+                holdSwapState = "SWAPPING_ACTIVE_AND_HELD";
+                break;
+            case HOLDING_TO_ANSWER_INCOMING:
+                holdSwapState = "HOLDING_TO_ANSWER_INCOMING";
+                break;
+            case PENDING_RESUME_FOREGROUND_AFTER_FAILURE:
+                holdSwapState = "PENDING_RESUME_FOREGROUND_AFTER_FAILURE";
+                break;
+            case HOLDING_TO_DIAL_OUTGOING:
+                holdSwapState = "HOLDING_TO_DIAL_OUTGOING";
+                break;
+        }
+        logi("holdSwapState set to " + holdSwapState + " at " + loc);
+    }
+
     /**
      * Logs the current state of the ImsPhoneCallTracker.  Useful for debugging issues with
      * call tracking.
@@ -3353,6 +3574,14 @@
         return mIsInEmergencyCall;
     }
 
+    /**
+     * @return true if the IMS capability for the specified registration technology is currently
+     * available.
+     */
+    public boolean isImsCapabilityAvailable(int capability, int regTech) {
+        return (getImsRegistrationTech() == regTech) && mMmTelCapabilities.isCapable(capability);
+    }
+
     public boolean isVolteEnabled() {
         boolean isRadioTechLte = getImsRegistrationTech()
                 == ImsRegistrationImplBase.REGISTRATION_TECH_LTE;
@@ -3611,6 +3840,10 @@
         }
     }
 
+    public boolean isViLteDataMetered() {
+        return mIsViLteDataMetered;
+    }
+
     /**
      * Handler of data enabled changed event
      * @param enabled True if data is enabled, otherwise disabled.
@@ -3632,7 +3865,9 @@
         // Inform connections that data has been disabled to ensure we turn off video capability
         // if this is an LTE call.
         for (ImsPhoneConnection conn : mConnections) {
-            conn.handleDataEnabledChange(enabled);
+            ImsCall imsCall = conn.getImsCall();
+            boolean isLocalVideoCapable = enabled || (imsCall != null && imsCall.isWifiCall());
+            conn.setLocalVideoCapable(isLocalVideoCapable);
         }
 
         int reasonCode;
@@ -3741,30 +3976,38 @@
         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) {
+                log("downgradeVideoCall :: callId=" + conn.getTelecomCallId()
+                        + " Downgrade to audio");
                 // If the carrier supports downgrading to voice, then we can simply issue a
                 // downgrade to voice instead of terminating the call.
                 modifyVideoCall(imsCall, VideoProfile.STATE_AUDIO_ONLY);
             } else if (mSupportPauseVideo && reasonCode != ImsReasonInfo.CODE_WIFI_LOST) {
                 // The carrier supports video pause signalling, so pause the video if we didn't just
                 // lose wifi; in that case just disconnect.
+                log("downgradeVideoCall :: callId=" + conn.getTelecomCallId()
+                        + " Pause audio");
                 mShouldUpdateImsConfigOnDisconnect = true;
                 conn.pauseVideo(VideoPauseTracker.SOURCE_DATA_ENABLED);
             } else {
+                log("downgradeVideoCall :: callId=" + conn.getTelecomCallId()
+                        + " Disconnect call.");
                 // At this point the only choice we have is to terminate the call.
-                try {
-                    imsCall.terminate(ImsReasonInfo.CODE_USER_TERMINATED, reasonCode);
-                } catch (ImsException ie) {
-                    loge("Couldn't terminate call " + imsCall);
-                }
+                imsCall.terminate(ImsReasonInfo.CODE_USER_TERMINATED, reasonCode);
             }
         }
     }
 
     private void resetImsCapabilities() {
         log("Resetting Capabilities...");
+        boolean tmpIsVideoCallEnabled = isVideoCallEnabled();
         mMmTelCapabilities = new MmTelFeature.MmTelCapabilities();
+
+        boolean isVideoEnabled = isVideoCallEnabled();
+        if (tmpIsVideoCallEnabled != isVideoEnabled) {
+            mPhone.notifyForVideoCapabilityChanged(isVideoEnabled);
+        }
     }
 
     /**
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCommandInterface.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCommandInterface.java
index 93a6d3e..2d024d1 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCommandInterface.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCommandInterface.java
@@ -21,7 +21,6 @@
 import android.net.LinkProperties;
 import android.os.Handler;
 import android.os.Message;
-import android.service.carrier.CarrierIdentifier;
 import android.telephony.ImsiEncryptionInfo;
 import android.telephony.NetworkScanRequest;
 import android.telephony.data.DataProfile;
@@ -33,8 +32,6 @@
 import com.android.internal.telephony.cdma.CdmaSmsBroadcastConfigInfo;
 import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
 
-import java.util.List;
-
 /**
  * Volte doesn't need CommandsInterface. The class does nothing but made to work
  * with Phone's constructor.
@@ -589,12 +586,6 @@
                                             int p3, String data, Message response) {}
 
     @Override
-    public void nvReadItem(int itemID, Message response) {}
-
-    @Override
-    public void nvWriteItem(int itemID, String itemValue, Message response) {}
-
-    @Override
     public void nvWriteCdmaPrl(byte[] preferredRoamingList, Message response) {}
 
     @Override
@@ -628,23 +619,11 @@
     }
 
     @Override
-    public void getModemActivityInfo(Message result) {
-    }
-
-    @Override
     public void setCarrierInfoForImsiEncryption(ImsiEncryptionInfo imsiEncryptionInfo,
                                                 Message result) {
     }
 
     @Override
-    public void setAllowedCarriers(List<CarrierIdentifier> carriers, Message result) {
-    }
-
-    @Override
-    public void getAllowedCarriers(Message result) {
-    }
-
-    @Override
     public void sendDeviceState(int stateType, boolean state, Message result) {
     }
 
@@ -664,10 +643,6 @@
     }
 
     @Override
-    public void setSimCardPower(int state, Message result) {
-    }
-
-    @Override
     public void startNattKeepalive(
             int contextId, KeepalivePacketData packetData, int intervalMillis, 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 47b3624..9cb2054 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java
@@ -46,6 +46,7 @@
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.UUSInfo;
+import com.android.internal.telephony.metrics.TelephonyMetrics;
 
 import java.util.Objects;
 
@@ -64,6 +65,7 @@
     private ImsPhoneCall mParent;
     private ImsCall mImsCall;
     private Bundle mExtras = new Bundle();
+    private TelephonyMetrics mMetrics = TelephonyMetrics.getInstance();
 
     private boolean mDisconnected;
 
@@ -121,7 +123,10 @@
      * currently available, but mobile data is off and the carrier is metering data for video
      * calls.
      */
-    private boolean mIsVideoEnabled = true;
+    private boolean mIsLocalVideoCapable = true;
+
+    // Store the current audio codec
+    private int mAudioCodec = ImsStreamMediaProfile.AUDIO_QUALITY_NONE;
 
     //***** Event Constants
     private static final int EVENT_DTMF_DONE = 1;
@@ -264,7 +269,7 @@
         capabilities = removeCapability(capabilities,
                 Connection.Capability.SUPPORTS_VT_LOCAL_BIDIRECTIONAL);
 
-        if (!mIsVideoEnabled) {
+        if (!mIsLocalVideoCapable) {
             Rlog.i(LOG_TAG, "applyLocalCallCapabilities - disabling video (overidden)");
             return capabilities;
         }
@@ -955,6 +960,22 @@
                 changed = true;
             }
 
+            if (!mOwner.isViLteDataMetered()) {
+                Rlog.v(LOG_TAG, "data is not metered");
+            } else {
+                if (mImsVideoCallProviderWrapper != null) {
+                    mImsVideoCallProviderWrapper.setIsVideoEnabled(
+                            hasCapabilities(Connection.Capability.SUPPORTS_VT_LOCAL_BIDIRECTIONAL));
+                }
+            }
+
+            // Metrics for audio codec
+            if (localCallProfile != null
+                    && localCallProfile.mMediaProfile.mAudioQuality != mAudioCodec) {
+                mAudioCodec = localCallProfile.mMediaProfile.mAudioQuality;
+                mMetrics.writeAudioCodecIms(mOwner.mPhone.getPhoneId(), imsCall.getCallSession());
+            }
+
             int newAudioQuality =
                     getAudioQualityFromCallProfile(localCallProfile, remoteCallProfile);
             if (getAudioQuality() != newAudioQuality) {
@@ -975,15 +996,27 @@
         setVideoState(newVideoState);
     }
 
-    public void sendRttModifyRequest(android.telecom.Connection.RttTextStream textStream) {
+
+    /**
+     * Send a RTT upgrade request to the remote party.
+     * @param textStream RTT text stream to use
+     */
+    public void startRtt(android.telecom.Connection.RttTextStream textStream) {
         ImsCall imsCall = getImsCall();
         if (imsCall != null) {
-            getImsCall().sendRttModifyRequest();
+            getImsCall().sendRttModifyRequest(true);
             setCurrentRttTextStream(textStream);
         }
     }
 
     /**
+     * Terminate the current RTT session.
+     */
+    public void stopRtt() {
+        getImsCall().sendRttModifyRequest(false);
+    }
+
+    /**
      * Sends the user's response to a remotely-issued RTT upgrade request
      *
      * @param textStream A valid {@link android.telecom.Connection.RttTextStream} if the user
@@ -1064,26 +1097,28 @@
     }
 
     /**
-     * Updates the wifi state based on the {@link ImsCallProfile#EXTRA_CALL_RAT_TYPE}.
-     * The call is considered to be a WIFI call if the extra value is
-     * {@link ServiceState#RIL_RADIO_TECHNOLOGY_IWLAN}.
+     * Updates the IMS call rat based on the {@link ImsCallProfile#EXTRA_CALL_RAT_TYPE}.
      *
      * @param extras The ImsCallProfile extras.
      */
-    private void updateWifiStateFromExtras(Bundle extras) {
+    private void updateImsCallRatFromExtras(Bundle extras) {
         if (extras.containsKey(ImsCallProfile.EXTRA_CALL_RAT_TYPE) ||
                 extras.containsKey(ImsCallProfile.EXTRA_CALL_RAT_TYPE_ALT)) {
 
             ImsCall call = getImsCall();
-            boolean isWifi = false;
+            int callTech = ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN;
             if (call != null) {
-                isWifi = call.isWifiCall();
+                callTech = call.getRadioTechnology();
             }
 
-            // Report any changes
-            if (isWifi() != isWifi) {
-                setWifi(isWifi);
-            }
+            // Report any changes for call tech change
+            setCallRadioTech(callTech);
+        }
+    }
+
+    private void updateEmergencyCallFromExtras(Bundle extras) {
+        if (extras.getBoolean(ImsCallProfile.EXTRA_EMERGENCY_CALL)) {
+            setIsNetworkIdentifiedEmergencyCall(true);
         }
     }
 
@@ -1107,8 +1142,8 @@
 
         final boolean changed = !areBundlesEqual(extras, mExtras);
         if (changed) {
-            updateWifiStateFromExtras(extras);
-
+            updateImsCallRatFromExtras(extras);
+            updateEmergencyCallFromExtras(extras);
             mExtras.clear();
             mExtras.putAll(extras);
             setConnectionExtras(mExtras);
@@ -1166,7 +1201,7 @@
                 || localCallProfile.mMediaProfile.mAudioQuality
                         == ImsStreamMediaProfile.AUDIO_QUALITY_EVRC_WB
                 || isEvsCodecHighDef)
-                && remoteCallProfile.mRestrictCause == ImsCallProfile.CALL_RESTRICT_CAUSE_NONE;
+                && remoteCallProfile.getRestrictCause() == ImsCallProfile.CALL_RESTRICT_CAUSE_NONE;
         return isHighDef ? AUDIO_QUALITY_HIGH_DEFINITION : AUDIO_QUALITY_STANDARD;
     }
 
@@ -1332,14 +1367,10 @@
         mShouldIgnoreVideoStateChanges = false;
     }
 
-    public void handleDataEnabledChange(boolean isDataEnabled) {
-        mIsVideoEnabled = isDataEnabled;
-        Rlog.i(LOG_TAG, "handleDataEnabledChange: isDataEnabled=" + isDataEnabled
+    public void setLocalVideoCapable(boolean isVideoEnabled) {
+        mIsLocalVideoCapable = isVideoEnabled;
+        Rlog.i(LOG_TAG, "setLocalVideoCapable: mIsLocalVideoCapable = " + mIsLocalVideoCapable
                 + "; updating local video availability.");
         updateMediaCapabilities(getImsCall());
-        if (mImsVideoCallProviderWrapper != null) {
-            mImsVideoCallProviderWrapper.setIsVideoEnabled(
-                    hasCapabilities(Connection.Capability.SUPPORTS_VT_LOCAL_BIDIRECTIONAL));
-        }
     }
 }
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneMmiCode.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneMmiCode.java
index 87e51ec..72f3a38 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneMmiCode.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneMmiCode.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.telephony.imsphone;
 
+import static android.telephony.ServiceState.STATE_IN_SERVICE;
+
 import static com.android.internal.telephony.CommandsInterface.SERVICE_CLASS_DATA;
 import static com.android.internal.telephony.CommandsInterface.SERVICE_CLASS_DATA_ASYNC;
 import static com.android.internal.telephony.CommandsInterface.SERVICE_CLASS_DATA_SYNC;
@@ -154,17 +156,6 @@
     public static final String UT_BUNDLE_KEY_CLIR = "queryClir";
     public static final String UT_BUNDLE_KEY_SSINFO = "imsSsInfo";
 
-    //***** Calling Line Identity Restriction Constants
-    // The 'm' parameter from TS 27.007 7.7
-    private static final int CLIR_NOT_PROVISIONED                    = 0;
-    private static final int CLIR_PROVISIONED_PERMANENT              = 1;
-    private static final int CLIR_PRESENTATION_RESTRICTED_TEMPORARY  = 3;
-    private static final int CLIR_PRESENTATION_ALLOWED_TEMPORARY     = 4;
-    // The 'n' parameter from TS 27.007 7.7
-    private static final int CLIR_DEFAULT     = 0;
-    private static final int CLIR_INVOCATION  = 1;
-    private static final int CLIR_SUPPRESSION = 2;
-
     //***** Instance Variables
 
     private ImsPhone mPhone;
@@ -1001,9 +992,19 @@
                     throw new RuntimeException ("Invalid or Unsupported MMI Code");
                 }
             } else if (mPoundString != null) {
-                Rlog.d(LOG_TAG, "processCode: Sending pound string '"
-                       + mDialingNumber + "' over CS pipe.");
-                throw new CallStateException(Phone.CS_FALLBACK);
+                // We'll normally send USSD over the CS pipe, but if it happens that the CS phone
+                // is out of service, we'll just try over IMS instead.
+                if (mPhone.getDefaultPhone().getServiceStateTracker().mSS.getState()
+                        == STATE_IN_SERVICE) {
+                    Rlog.i(LOG_TAG, "processCode: Sending ussd string '"
+                            + Rlog.pii(LOG_TAG, mPoundString) + "' over CS pipe.");
+                    throw new CallStateException(Phone.CS_FALLBACK);
+                } else {
+                    Rlog.i(LOG_TAG, "processCode: CS is out of service, sending ussd string '"
+                            + Rlog.pii(LOG_TAG, mPoundString) + "' over IMS pipe.");
+                    sendUssd(mPoundString);
+                }
+
             } else {
                 Rlog.d(LOG_TAG, "processCode: invalid or unsupported MMI");
                 throw new RuntimeException ("Invalid or Unsupported MMI Code");
@@ -1480,7 +1481,11 @@
                 if (ssInfo != null) {
                     Rlog.d(LOG_TAG,
                             "onSuppSvcQueryComplete: ImsSsInfo mStatus = " + ssInfo.getStatus());
-                    if (ssInfo.getStatus() == ImsSsInfo.DISABLED) {
+                    if (ssInfo.getProvisionStatus() == ImsSsInfo.SERVICE_NOT_PROVISIONED) {
+                        sb.append(mContext.getText(
+                                com.android.internal.R.string.serviceNotProvisioned));
+                        mState = State.COMPLETE;
+                    } else if (ssInfo.getStatus() == ImsSsInfo.DISABLED) {
                         sb.append(mContext.getText(com.android.internal.R.string.serviceDisabled));
                         mState = State.COMPLETE;
                     } else if (ssInfo.getStatus() == ImsSsInfo.ENABLED) {
@@ -1531,9 +1536,9 @@
                 sb.append(mContext.getText(com.android.internal.R.string.serviceDisabled));
             } else {
                 for (int i = 0, s = infos.length; i < s ; i++) {
-                    if (infos[i].getIcbNum() != null) {
-                        sb.append("Num: " + infos[i].getIcbNum() + " status: "
-                                + infos[i].getStatus() + "\n");
+                    if (infos[i].getIncomingCommunicationBarringNumber() != null) {
+                        sb.append("Num: " + infos[i].getIncomingCommunicationBarringNumber()
+                                + " status: " + infos[i].getStatus() + "\n");
                     } else if (infos[i].getStatus() == 1) {
                         sb.append(mContext.getText(com.android.internal
                                 .R.string.serviceEnabled));
@@ -1568,30 +1573,30 @@
 
             // 'm' parameter.
             switch (clirInfo[1]) {
-                case CLIR_NOT_PROVISIONED:
+                case ImsSsInfo.CLIR_STATUS_NOT_PROVISIONED:
                     sb.append(mContext.getText(
                             com.android.internal.R.string.serviceNotProvisioned));
                     mState = State.COMPLETE;
                     break;
-                case CLIR_PROVISIONED_PERMANENT:
+                case ImsSsInfo.CLIR_STATUS_PROVISIONED_PERMANENT:
                     sb.append(mContext.getText(
                             com.android.internal.R.string.CLIRPermanent));
                     mState = State.COMPLETE;
                     break;
-                case CLIR_PRESENTATION_RESTRICTED_TEMPORARY:
+                case ImsSsInfo.CLIR_STATUS_TEMPORARILY_RESTRICTED:
                     // 'n' parameter.
                     switch (clirInfo[0]) {
-                        case CLIR_DEFAULT:
+                        case ImsSsInfo.CLIR_OUTGOING_DEFAULT:
                             sb.append(mContext.getText(
                                     com.android.internal.R.string.CLIRDefaultOnNextCallOn));
                             mState = State.COMPLETE;
                             break;
-                        case CLIR_INVOCATION:
+                        case ImsSsInfo.CLIR_OUTGOING_INVOCATION:
                             sb.append(mContext.getText(
                                     com.android.internal.R.string.CLIRDefaultOnNextCallOn));
                             mState = State.COMPLETE;
                             break;
-                        case CLIR_SUPPRESSION:
+                        case ImsSsInfo.CLIR_OUTGOING_SUPPRESSION:
                             sb.append(mContext.getText(
                                     com.android.internal.R.string.CLIRDefaultOnNextCallOff));
                             mState = State.COMPLETE;
@@ -1602,20 +1607,20 @@
                             mState = State.FAILED;
                     }
                     break;
-                case CLIR_PRESENTATION_ALLOWED_TEMPORARY:
+                case ImsSsInfo.CLIR_STATUS_TEMPORARILY_ALLOWED:
                     // 'n' parameter.
                     switch (clirInfo[0]) {
-                        case CLIR_DEFAULT:
+                        case ImsSsInfo.CLIR_OUTGOING_DEFAULT:
                             sb.append(mContext.getText(
                                     com.android.internal.R.string.CLIRDefaultOffNextCallOff));
                             mState = State.COMPLETE;
                             break;
-                        case CLIR_INVOCATION:
+                        case ImsSsInfo.CLIR_OUTGOING_INVOCATION:
                             sb.append(mContext.getText(
                                     com.android.internal.R.string.CLIRDefaultOffNextCallOn));
                             mState = State.COMPLETE;
                             break;
-                        case CLIR_SUPPRESSION:
+                        case ImsSsInfo.CLIR_OUTGOING_SUPPRESSION:
                             sb.append(mContext.getText(
                                     com.android.internal.R.string.CLIRDefaultOffNextCallOff));
                             mState = State.COMPLETE;
@@ -1725,27 +1730,27 @@
     }
 
     void parseSsData(ImsSsData ssData) {
-        ImsException ex = (ssData.result != ImsSsData.RESULT_SUCCESS)
-                ? new ImsException(null, ssData.result) : null;
-        mSc = getScStringFromScType(ssData.serviceType);
-        mAction = getActionStringFromReqType(ssData.requestType);
+        ImsException ex = (ssData.getResult() != ImsSsData.RESULT_SUCCESS)
+                ? new ImsException(null, ssData.getResult()) : null;
+        mSc = getScStringFromScType(ssData.getServiceType());
+        mAction = getActionStringFromReqType(ssData.getRequestType());
         Rlog.d(LOG_TAG, "parseSsData msc = " + mSc + ", action = " + mAction + ", ex = " + ex);
 
-        switch (ssData.requestType) {
+        switch (ssData.getRequestType()) {
             case ImsSsData.SS_ACTIVATION:
             case ImsSsData.SS_DEACTIVATION:
             case ImsSsData.SS_REGISTRATION:
             case ImsSsData.SS_ERASURE:
-                if ((ssData.result == ImsSsData.RESULT_SUCCESS)
+                if ((ssData.getResult() == ImsSsData.RESULT_SUCCESS)
                         && ssData.isTypeUnConditional()) {
                     /*
                      * When ssData.serviceType is unconditional (SS_CFU or SS_CF_ALL) and
                      * ssData.requestType is activate/register and
                      * ServiceClass is Voice/Video/None, turn on voice call forwarding.
                      */
-                    boolean cffEnabled = ((ssData.requestType == ImsSsData.SS_ACTIVATION
-                            || ssData.requestType == ImsSsData.SS_REGISTRATION)
-                            && isServiceClassVoiceVideoOrNone(ssData.serviceClass));
+                    boolean cffEnabled = ((ssData.getRequestType() == ImsSsData.SS_ACTIVATION
+                            || ssData.getRequestType() == ImsSsData.SS_REGISTRATION)
+                            && isServiceClassVoiceVideoOrNone(ssData.getServiceClass()));
 
                     Rlog.d(LOG_TAG, "setCallForwardingFlag cffEnabled: " + cffEnabled);
                     if (mIccRecords != null) {
@@ -1763,29 +1768,29 @@
                 if (ssData.isTypeClir()) {
                     Rlog.d(LOG_TAG, "CLIR INTERROGATION");
                     Bundle clirInfo = new Bundle();
-                    clirInfo.putIntArray(UT_BUNDLE_KEY_CLIR, ssData.getSuppServiceInfo());
+                    clirInfo.putIntArray(UT_BUNDLE_KEY_CLIR, ssData.getSuppServiceInfoCompat());
                     onQueryClirComplete(new AsyncResult(null, clirInfo, ex));
                 } else if (ssData.isTypeCF()) {
                     Rlog.d(LOG_TAG, "CALL FORWARD INTERROGATION");
                     onQueryCfComplete(new AsyncResult(null, mPhone
                             .handleCfQueryResult(ssData.getCallForwardInfo()), ex));
                 } else if (ssData.isTypeBarring()) {
-                    onSuppSvcQueryComplete(new AsyncResult(null, ssData.getSuppServiceInfo(), ex));
+                    onSuppSvcQueryComplete(new AsyncResult(null, ssData.getSuppServiceInfoCompat(),
+                            ex));
                 } else if (ssData.isTypeColr() || ssData.isTypeClip() || ssData.isTypeColp()) {
-                    int[] suppServiceInfo = ssData.getSuppServiceInfo();
-                    ImsSsInfo ssInfo = new ImsSsInfo(suppServiceInfo[0], null);
+                    int[] suppServiceInfo = ssData.getSuppServiceInfoCompat();
+                    ImsSsInfo ssInfo = new ImsSsInfo.Builder(suppServiceInfo[0]).build();
                     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.getImsSpecificSuppServiceInfo(),
-                            ex));
+                    onIcbQueryComplete(new AsyncResult(null, ssData.getSuppServiceInfo(), ex));
                 } else {
-                    onQueryComplete(new AsyncResult(null, ssData.getSuppServiceInfo(), ex));
+                    onQueryComplete(new AsyncResult(null, ssData.getSuppServiceInfoCompat(), ex));
                 }
                 break;
             default:
-                Rlog.e(LOG_TAG, "Invaid requestType in SSData : " + ssData.requestType);
+                Rlog.e(LOG_TAG, "Invaid requestType in SSData : " + ssData.getRequestType());
                 break;
         }
     }
diff --git a/src/java/com/android/internal/telephony/metrics/CallSessionEventBuilder.java b/src/java/com/android/internal/telephony/metrics/CallSessionEventBuilder.java
index a8221b4..b4a5e6e 100644
--- a/src/java/com/android/internal/telephony/metrics/CallSessionEventBuilder.java
+++ b/src/java/com/android/internal/telephony/metrics/CallSessionEventBuilder.java
@@ -130,4 +130,10 @@
         mEvent.calls = rilCalls;
         return this;
     }
+
+    /** Set the audio codec. */
+    public CallSessionEventBuilder setAudioCodec(int audioCodec) {
+        mEvent.audioCodec = audioCodec;
+        return this;
+    }
 }
diff --git a/src/java/com/android/internal/telephony/metrics/ModemPowerMetrics.java b/src/java/com/android/internal/telephony/metrics/ModemPowerMetrics.java
index 8001eb6..f8d519c 100644
--- a/src/java/com/android/internal/telephony/metrics/ModemPowerMetrics.java
+++ b/src/java/com/android/internal/telephony/metrics/ModemPowerMetrics.java
@@ -60,9 +60,16 @@
             m.rxTimeMs = stats.getRxTimeMs();
             long[] t = stats.getTxTimeMs();
             m.txTimeMs = new long[t.length];
-            for (int i = 0; i < t.length; i++) {
-                m.txTimeMs[i] = t[i];
-            }
+            System.arraycopy(t, 0, m.txTimeMs, 0, t.length);
+            m.numBytesTx = stats.getNumBytesTx();
+            m.numPacketsRx = stats.getNumPacketsRx();
+            m.numBytesRx = stats.getNumBytesRx();
+            long[] tr = stats.getTimeInRatMs();
+            m.timeInRatMs = new long[tr.length];
+            System.arraycopy(tr, 0, m.timeInRatMs, 0, tr.length);
+            long[] trx = stats.getTimeInRxSignalStrengthLevelMs();
+            m.timeInRxSignalStrengthLevelMs = new long[trx.length];
+            System.arraycopy(trx, 0, m.timeInRxSignalStrengthLevelMs, 0, trx.length);
         }
         return m;
     }
diff --git a/src/java/com/android/internal/telephony/metrics/SmsSessionEventBuilder.java b/src/java/com/android/internal/telephony/metrics/SmsSessionEventBuilder.java
index 5004ce7..45d8061 100644
--- a/src/java/com/android/internal/telephony/metrics/SmsSessionEventBuilder.java
+++ b/src/java/com/android/internal/telephony/metrics/SmsSessionEventBuilder.java
@@ -54,6 +54,11 @@
         return this;
     }
 
+    public SmsSessionEventBuilder setImsServiceErrno(int errno) {
+        mEvent.imsError = errno;
+        return this;
+    }
+
     public SmsSessionEventBuilder setSettings(TelephonySettings settings) {
         mEvent.settings = settings;
         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 75ea68f..2fb2211 100644
--- a/src/java/com/android/internal/telephony/metrics/TelephonyMetrics.java
+++ b/src/java/com/android/internal/telephony/metrics/TelephonyMetrics.java
@@ -41,18 +41,24 @@
 import android.os.SystemProperties;
 import android.telephony.Rlog;
 import android.telephony.ServiceState;
+import android.telephony.SmsManager;
+import android.telephony.SmsMessage;
 import android.telephony.TelephonyHistogram;
 import android.telephony.TelephonyManager;
 import android.telephony.data.DataCallResponse;
 import android.telephony.data.DataService;
+import android.telephony.ims.ImsCallProfile;
 import android.telephony.ims.ImsCallSession;
 import android.telephony.ims.ImsReasonInfo;
+import android.telephony.ims.ImsStreamMediaProfile;
 import android.telephony.ims.feature.MmTelFeature;
 import android.telephony.ims.stub.ImsRegistrationImplBase;
+import android.telephony.ims.stub.ImsSmsImplBase;
 import android.text.TextUtils;
 import android.util.Base64;
 import android.util.SparseArray;
 
+import com.android.internal.telephony.DriverCall;
 import com.android.internal.telephony.GsmCdmaConnection;
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.RIL;
@@ -297,6 +303,8 @@
                 return "PHONE_STATE_CHANGED";
             case TelephonyCallSession.Event.Type.NITZ_TIME:
                 return "NITZ_TIME";
+            case TelephonyCallSession.Event.Type.AUDIO_CODEC:
+                return "AUDIO_CODEC";
             default:
                 return Integer.toString(event);
         }
@@ -386,6 +394,9 @@
                                 + " isMultiparty = " + call.isMultiparty);
                     }
                     pw.decreaseIndent();
+                } else if (event.type == TelephonyCallSession.Event.Type.AUDIO_CODEC) {
+                    pw.println(callSessionEventToString(event.type)
+                            + "(" + event.audioCodec + ")");
                 } else {
                     pw.println(callSessionEventToString(event.type));
                 }
@@ -423,14 +434,22 @@
         pw.println("Power log duration (battery time) (ms): " + s.loggingDurationMs);
         pw.println("Energy consumed by modem (mAh): " + s.energyConsumedMah);
         pw.println("Number of packets sent (tx): " + s.numPacketsTx);
-        pw.println("Amount of time kernel is active because of cellular data (ms): " +
-            s.cellularKernelActiveTimeMs);
-        pw.println("Amount of time spent in very poor rx signal level (ms): " +
-            s.timeInVeryPoorRxSignalLevelMs);
+        pw.println("Number of bytes sent (tx): " + s.numBytesTx);
+        pw.println("Number of packets received (rx): " + s.numPacketsRx);
+        pw.println("Number of bytes received (rx): " + s.numBytesRx);
+        pw.println("Amount of time kernel is active because of cellular data (ms): "
+                + s.cellularKernelActiveTimeMs);
+        pw.println("Amount of time spent in very poor rx signal level (ms): "
+                + s.timeInVeryPoorRxSignalLevelMs);
         pw.println("Amount of time modem is in sleep (ms): " + s.sleepTimeMs);
         pw.println("Amount of time modem is in idle (ms): " + s.idleTimeMs);
         pw.println("Amount of time modem is in rx (ms): " + s.rxTimeMs);
         pw.println("Amount of time modem is in tx (ms): " + Arrays.toString(s.txTimeMs));
+        pw.println("Amount of time phone spent in various Radio Access Technologies (ms): "
+                + Arrays.toString(s.timeInRatMs));
+        pw.println("Amount of time phone spent in various cellular "
+                + "rx signal strength levels (ms): "
+                + Arrays.toString(s.timeInRxSignalStrengthLevelMs));
         pw.decreaseIndent();
         pw.println("Hardware Version: " + SystemProperties.get("ro.boot.revision", ""));
     }
@@ -1456,6 +1475,31 @@
     }
 
     /**
+     * Write SMS related solicited response event
+     *
+     * @param phoneId Phone id
+     * @param errorReason Defined in {@link SmsManager} RESULT_XXX.
+     */
+    public synchronized void writeOnImsServiceSmsSolicitedResponse(int phoneId,
+            @ImsSmsImplBase.SendStatusResult int resultCode, int errorReason) {
+
+        InProgressSmsSession smsSession = mInProgressSmsSessions.get(phoneId);
+        if (smsSession == null) {
+            Rlog.e(TAG, "SMS session is missing");
+        } else {
+
+            smsSession.addEvent(new SmsSessionEventBuilder(
+                    SmsSession.Event.Type.SMS_SEND_RESULT_IMS_SERVICE)
+                    .setImsServiceErrno(resultCode)
+                    .setErrorCode(errorReason)
+            );
+
+            smsSession.decreaseExpectedResponse();
+            finishSmsSessionIfNeeded(smsSession);
+        }
+    }
+
+    /**
      * Write deactivate data call response event
      *
      * @param phoneId Phone id
@@ -1740,6 +1784,35 @@
     }
 
     /**
+     * Write Send SMS event using ImsService. Expecting response from
+     * {@link #writeOnSmsSolicitedResponse}.
+     *
+     * @param phoneId Phone id
+     * @param format SMS format. Either {@link SmsMessage#FORMAT_3GPP} or
+     * {@link SmsMessage#FORMAT_3GPP2}.
+     */
+    public synchronized void writeImsServiceSendSms(int phoneId, String format) {
+        InProgressSmsSession smsSession = startNewSmsSessionIfNeeded(phoneId);
+        int formatCode = SmsSession.Event.Format.SMS_FORMAT_UNKNOWN;
+        switch (format) {
+            case SmsMessage.FORMAT_3GPP : {
+                formatCode = SmsSession.Event.Format.SMS_FORMAT_3GPP;
+                break;
+            }
+            case SmsMessage.FORMAT_3GPP2: {
+                formatCode = SmsSession.Event.Format.SMS_FORMAT_3GPP2;
+                break;
+            }
+        }
+        smsSession.addEvent(new SmsSessionEventBuilder(SmsSession.Event.Type.SMS_SEND_IMS_SERVICE)
+                .setTech(SmsSession.Event.Tech.SMS_IMS)
+                .setFormat(formatCode)
+        );
+
+        smsSession.increaseExpectedResponse();
+    }
+
+    /**
      * Write incoming SMS event
      *
      * @param phoneId Phone id
@@ -1758,6 +1831,35 @@
     }
 
     /**
+     * Write incoming SMS event
+     *
+     * @param phoneId Phone id
+     * @param format SMS format. Either {@link SmsMessage#FORMAT_3GPP} or
+     * {@link SmsMessage#FORMAT_3GPP2}.
+     */
+    public synchronized void writeImsServiceNewSms(int phoneId, String format) {
+        InProgressSmsSession smsSession = startNewSmsSessionIfNeeded(phoneId);
+        int formatCode = SmsSession.Event.Format.SMS_FORMAT_UNKNOWN;
+        switch (format) {
+            case SmsMessage.FORMAT_3GPP : {
+                formatCode = SmsSession.Event.Format.SMS_FORMAT_3GPP;
+                break;
+            }
+            case SmsMessage.FORMAT_3GPP2: {
+                formatCode = SmsSession.Event.Format.SMS_FORMAT_3GPP2;
+                break;
+            }
+        }
+        smsSession.addEvent(new SmsSessionEventBuilder(
+                SmsSession.Event.Type.SMS_RECEIVED_IMS_SERVICE)
+                .setTech(SmsSession.Event.Tech.SMS_IMS)
+                .setFormat(formatCode)
+        );
+
+        finishSmsSessionIfNeeded(smsSession);
+    }
+
+    /**
      * Write incoming Broadcast SMS event
      *
      * @param phoneId Phone id
@@ -1867,6 +1969,136 @@
         addTelephonyEvent(event);
     }
 
+    /**
+     * Convert IMS audio codec into proto defined value
+     *
+     * @param c IMS codec value
+     * @return Codec value defined in call session proto
+     */
+    private int convertImsCodec(int c) {
+        switch (c) {
+            case ImsStreamMediaProfile.AUDIO_QUALITY_AMR:
+                return TelephonyCallSession.Event.AudioCodec.AUDIO_CODEC_AMR;
+            case ImsStreamMediaProfile.AUDIO_QUALITY_AMR_WB:
+                return TelephonyCallSession.Event.AudioCodec.AUDIO_CODEC_AMR_WB;
+            case ImsStreamMediaProfile.AUDIO_QUALITY_QCELP13K:
+                return TelephonyCallSession.Event.AudioCodec.AUDIO_CODEC_QCELP13K;
+            case ImsStreamMediaProfile.AUDIO_QUALITY_EVRC:
+                return TelephonyCallSession.Event.AudioCodec.AUDIO_CODEC_EVRC;
+            case ImsStreamMediaProfile.AUDIO_QUALITY_EVRC_B:
+                return TelephonyCallSession.Event.AudioCodec.AUDIO_CODEC_EVRC_B;
+            case ImsStreamMediaProfile.AUDIO_QUALITY_EVRC_WB:
+                return TelephonyCallSession.Event.AudioCodec.AUDIO_CODEC_EVRC_WB;
+            case ImsStreamMediaProfile.AUDIO_QUALITY_EVRC_NW:
+                return TelephonyCallSession.Event.AudioCodec.AUDIO_CODEC_EVRC_NW;
+            case ImsStreamMediaProfile.AUDIO_QUALITY_GSM_EFR:
+                return TelephonyCallSession.Event.AudioCodec.AUDIO_CODEC_GSM_EFR;
+            case ImsStreamMediaProfile.AUDIO_QUALITY_GSM_FR:
+                return TelephonyCallSession.Event.AudioCodec.AUDIO_CODEC_GSM_FR;
+            case ImsStreamMediaProfile.AUDIO_QUALITY_GSM_HR:
+                return TelephonyCallSession.Event.AudioCodec.AUDIO_CODEC_GSM_HR;
+            case ImsStreamMediaProfile.AUDIO_QUALITY_G711U:
+                return TelephonyCallSession.Event.AudioCodec.AUDIO_CODEC_G711U;
+            case ImsStreamMediaProfile.AUDIO_QUALITY_G723:
+                return TelephonyCallSession.Event.AudioCodec.AUDIO_CODEC_G723;
+            case ImsStreamMediaProfile.AUDIO_QUALITY_G711A:
+                return TelephonyCallSession.Event.AudioCodec.AUDIO_CODEC_G711A;
+            case ImsStreamMediaProfile.AUDIO_QUALITY_G722:
+                return TelephonyCallSession.Event.AudioCodec.AUDIO_CODEC_G722;
+            case ImsStreamMediaProfile.AUDIO_QUALITY_G711AB:
+                return TelephonyCallSession.Event.AudioCodec.AUDIO_CODEC_G711AB;
+            case ImsStreamMediaProfile.AUDIO_QUALITY_G729:
+                return TelephonyCallSession.Event.AudioCodec.AUDIO_CODEC_G729;
+            case ImsStreamMediaProfile.AUDIO_QUALITY_EVS_NB:
+                return TelephonyCallSession.Event.AudioCodec.AUDIO_CODEC_EVS_NB;
+            case ImsStreamMediaProfile.AUDIO_QUALITY_EVS_WB:
+                return TelephonyCallSession.Event.AudioCodec.AUDIO_CODEC_EVS_WB;
+            case ImsStreamMediaProfile.AUDIO_QUALITY_EVS_SWB:
+                return TelephonyCallSession.Event.AudioCodec.AUDIO_CODEC_EVS_SWB;
+            case ImsStreamMediaProfile.AUDIO_QUALITY_EVS_FB:
+                return TelephonyCallSession.Event.AudioCodec.AUDIO_CODEC_EVS_FB;
+            default:
+                return TelephonyCallSession.Event.AudioCodec.AUDIO_CODEC_UNKNOWN;
+        }
+    }
+
+    /**
+     * Convert GSM/CDMA audio codec into proto defined value
+     *
+     * @param c GSM/CDMA codec value
+     * @return Codec value defined in call session proto
+     */
+    private int convertGsmCdmaCodec(int c) {
+        switch (c) {
+            case DriverCall.AUDIO_QUALITY_AMR:
+                return TelephonyCallSession.Event.AudioCodec.AUDIO_CODEC_AMR;
+            case DriverCall.AUDIO_QUALITY_AMR_WB:
+                return TelephonyCallSession.Event.AudioCodec.AUDIO_CODEC_AMR_WB;
+            case DriverCall.AUDIO_QUALITY_GSM_EFR:
+                return TelephonyCallSession.Event.AudioCodec.AUDIO_CODEC_GSM_EFR;
+            case DriverCall.AUDIO_QUALITY_GSM_FR:
+                return TelephonyCallSession.Event.AudioCodec.AUDIO_CODEC_GSM_FR;
+            case DriverCall.AUDIO_QUALITY_GSM_HR:
+                return TelephonyCallSession.Event.AudioCodec.AUDIO_CODEC_GSM_HR;
+            case DriverCall.AUDIO_QUALITY_EVRC:
+                return TelephonyCallSession.Event.AudioCodec.AUDIO_CODEC_EVRC;
+            case DriverCall.AUDIO_QUALITY_EVRC_B:
+                return TelephonyCallSession.Event.AudioCodec.AUDIO_CODEC_EVRC_B;
+            case DriverCall.AUDIO_QUALITY_EVRC_WB:
+                return TelephonyCallSession.Event.AudioCodec.AUDIO_CODEC_EVRC_WB;
+            case DriverCall.AUDIO_QUALITY_EVRC_NW:
+                return TelephonyCallSession.Event.AudioCodec.AUDIO_CODEC_EVRC_NW;
+            default:
+                return TelephonyCallSession.Event.AudioCodec.AUDIO_CODEC_UNKNOWN;
+        }
+    }
+
+    /**
+     * Write audio codec event
+     *
+     * @param phoneId Phone id
+     * @param session IMS call session
+     */
+    public void writeAudioCodecIms(int phoneId, ImsCallSession session) {
+        InProgressCallSession callSession = mInProgressCallSessions.get(phoneId);
+        if (callSession == null) {
+            Rlog.e(TAG, "Call session is missing");
+            return;
+        }
+
+        ImsCallProfile localCallProfile = session.getLocalCallProfile();
+        if (localCallProfile != null) {
+            int codec = convertImsCodec(localCallProfile.mMediaProfile.mAudioQuality);
+            callSession.addEvent(new CallSessionEventBuilder(
+                    TelephonyCallSession.Event.Type.AUDIO_CODEC)
+                    .setCallIndex(getCallId(session))
+                    .setAudioCodec(codec));
+
+            if (VDBG) Rlog.v(TAG, "Logged Audio Codec event. Value: " + codec);
+        }
+    }
+
+    /**
+     * Write audio codec event
+     *
+     * @param phoneId Phone id
+     * @param audioQuality Audio quality value
+     */
+    public void writeAudioCodecGsmCdma(int phoneId, int audioQuality) {
+        InProgressCallSession callSession = mInProgressCallSessions.get(phoneId);
+        if (callSession == null) {
+            Rlog.e(TAG, "Call session is missing");
+            return;
+        }
+
+        int codec = convertGsmCdmaCodec(audioQuality);
+        callSession.addEvent(new CallSessionEventBuilder(
+                TelephonyCallSession.Event.Type.AUDIO_CODEC)
+                .setAudioCodec(codec));
+
+        if (VDBG) Rlog.v(TAG, "Logged Audio Codec event. Value: " + codec);
+    }
+
     //TODO: Expand the proto in the future
     public void writeOnImsCallProgressing(int phoneId, ImsCallSession session) {}
     public void writeOnImsCallStarted(int phoneId, ImsCallSession session) {}
diff --git a/src/java/com/android/internal/telephony/sip/SipCommandInterface.java b/src/java/com/android/internal/telephony/sip/SipCommandInterface.java
index 396fd4b..83a5ff9 100644
--- a/src/java/com/android/internal/telephony/sip/SipCommandInterface.java
+++ b/src/java/com/android/internal/telephony/sip/SipCommandInterface.java
@@ -21,7 +21,6 @@
 import android.net.LinkProperties;
 import android.os.Handler;
 import android.os.Message;
-import android.service.carrier.CarrierIdentifier;
 import android.telephony.ImsiEncryptionInfo;
 import android.telephony.NetworkScanRequest;
 import android.telephony.data.DataProfile;
@@ -32,8 +31,6 @@
 import com.android.internal.telephony.cdma.CdmaSmsBroadcastConfigInfo;
 import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
 
-import java.util.List;
-
 /**
  * SIP doesn't need CommandsInterface. The class does nothing but made to work
  * with Phone's constructor.
@@ -594,14 +591,6 @@
     }
 
     @Override
-    public void nvReadItem(int itemID, Message response) {
-    }
-
-    @Override
-    public void nvWriteItem(int itemID, String itemValue, Message response) {
-    }
-
-    @Override
     public void nvWriteCdmaPrl(byte[] preferredRoamingList, Message response) {
     }
 
@@ -630,23 +619,11 @@
     }
 
     @Override
-    public void getModemActivityInfo(Message result) {
-    }
-
-    @Override
     public void setCarrierInfoForImsiEncryption(ImsiEncryptionInfo imsiEncryptionInfo,
                                                 Message result) {
     }
 
     @Override
-    public void setAllowedCarriers(List<CarrierIdentifier> carriers, Message result) {
-    }
-
-    @Override
-    public void getAllowedCarriers(Message result) {
-    }
-
-    @Override
     public void sendDeviceState(int stateType, boolean state, Message result) {
     }
 
@@ -666,10 +643,6 @@
     }
 
     @Override
-    public void setSimCardPower(int state, Message result) {
-    }
-
-    @Override
     public void startNattKeepalive(
             int contextId, KeepalivePacketData packetData, int intervalMillis, Message result) {
     }
diff --git a/src/java/com/android/internal/telephony/sip/SipPhoneBase.java b/src/java/com/android/internal/telephony/sip/SipPhoneBase.java
index c72ea86..272187e 100755
--- a/src/java/com/android/internal/telephony/sip/SipPhoneBase.java
+++ b/src/java/com/android/internal/telephony/sip/SipPhoneBase.java
@@ -19,21 +19,17 @@
 import android.content.Context;
 import android.net.LinkProperties;
 import android.os.AsyncResult;
-import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
 import android.os.RegistrantList;
 import android.os.ResultReceiver;
 import android.os.SystemProperties;
-import android.os.WorkSource;
-import android.telephony.CellLocation;
 import android.telephony.NetworkScanRequest;
 import android.telephony.Rlog;
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
 
 import com.android.internal.telephony.Call;
-import com.android.internal.telephony.CallStateException;
 import com.android.internal.telephony.Connection;
 import com.android.internal.telephony.IccCard;
 import com.android.internal.telephony.IccPhoneBookInterfaceManager;
@@ -43,7 +39,6 @@
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.PhoneNotifier;
 import com.android.internal.telephony.TelephonyProperties;
-import com.android.internal.telephony.UUSInfo;
 import com.android.internal.telephony.uicc.IccFileHandler;
 
 import java.util.ArrayList;
@@ -105,11 +100,6 @@
     }
 
     @Override
-    public CellLocation getCellLocation(WorkSource workSource) {
-        return null;
-    }
-
-    @Override
     public PhoneConstants.State getState() {
         return mState;
     }
@@ -451,7 +441,7 @@
     }
 
     @Override
-    public boolean isDataAllowed() {
+    public boolean isDataAllowed(int apnType) {
         return false;
     }
 
diff --git a/src/java/com/android/internal/telephony/test/SimulatedCommands.java b/src/java/com/android/internal/telephony/test/SimulatedCommands.java
index 1fc9a26..9a87d01 100644
--- a/src/java/com/android/internal/telephony/test/SimulatedCommands.java
+++ b/src/java/com/android/internal/telephony/test/SimulatedCommands.java
@@ -39,6 +39,7 @@
 import android.telephony.Rlog;
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
+import android.telephony.TelephonyManager;
 import android.telephony.data.DataCallResponse;
 import android.telephony.data.DataProfile;
 
@@ -131,7 +132,8 @@
     public int mMaxDataCalls;
 
     private SignalStrength mSignalStrength;
-    private List<CellInfo> mCellInfoList;
+    private List<CellInfo> mCellInfoList = null;
+    private boolean mShouldReturnCellInfo = true;
     private int[] mImsRegState;
     private IccCardStatus mIccCardStatus;
     private IccSlotStatus mIccSlotStatus;
@@ -157,7 +159,7 @@
 
         simulatedCallState = new SimulatedGsmCallState(looper);
 
-        setRadioState(RadioState.RADIO_ON);
+        setRadioState(TelephonyManager.RADIO_POWER_ON, false /* forceNotifyRegistrants */);
         mSimLockedState = INITIAL_LOCK_STATE;
         mSimLockEnabled = (mSimLockedState != SimLockState.NONE);
         mPinCode = DEFAULT_SIM_PIN_CODE;
@@ -504,7 +506,7 @@
     @Override
     public void getCurrentCalls (Message result) {
         SimulatedCommandsVerifier.getInstance().getCurrentCalls(result);
-        if ((mState == RadioState.RADIO_ON) && !isSimLocked()) {
+        if ((mState == TelephonyManager.RADIO_POWER_ON) && !isSimLocked()) {
             //Rlog.i("GSM", "[SimCmds] getCurrentCalls");
             resultSuccess(result, simulatedCallState.getDriverCalls());
         } else {
@@ -1194,17 +1196,6 @@
     }
 
     @Override
-    public void getNeighboringCids(Message result, WorkSource workSource) {
-        int ret[] = new int[7];
-
-        ret[0] = 6;
-        for (int i = 1; i<7; i++) {
-            ret[i] = i;
-        }
-        resultSuccess(result, ret);
-    }
-
-    @Override
     public void setLocationUpdates(boolean enable, Message response) {
         SimulatedCommandsVerifier.getInstance().setLocationUpdates(enable, response);
         resultSuccess(response, null);
@@ -1251,9 +1242,9 @@
         }
 
         if(on) {
-            setRadioState(RadioState.RADIO_ON);
+            setRadioState(TelephonyManager.RADIO_POWER_ON, false /* forceNotifyRegistrants */);
         } else {
-            setRadioState(RadioState.RADIO_OFF);
+            setRadioState(TelephonyManager.RADIO_POWER_OFF, false /* forceNotifyRegistrants */);
         }
         resultSuccess(result, null);
     }
@@ -1609,7 +1600,7 @@
     @Override
     public void
     shutdown() {
-        setRadioState(RadioState.RADIO_UNAVAILABLE);
+        setRadioState(TelephonyManager.RADIO_POWER_UNAVAILABLE, false /* forceNotifyRegistrants */);
         Looper looper = mHandlerThread.getLooper();
         if (looper != null) {
             looper.quit();
@@ -1886,26 +1877,45 @@
         mCellInfoList = list;
     }
 
+    private CellInfoGsm getCellInfoGsm() {
+        Parcel p = Parcel.obtain();
+        // CellInfo
+        p.writeInt(1);
+        p.writeInt(1);
+        p.writeInt(2);
+        p.writeLong(1453510289108L);
+        p.writeInt(0);
+        // CellIdentity
+        p.writeInt(1);
+        p.writeString("310");
+        p.writeString("260");
+        p.writeString("long");
+        p.writeString("short");
+        // CellIdentityGsm
+        p.writeInt(123);
+        p.writeInt(456);
+        p.writeInt(950);
+        p.writeInt(27);
+        // CellSignalStrength
+        p.writeInt(99);
+        p.writeInt(0);
+        p.writeInt(3);
+        p.setDataPosition(0);
+
+        return CellInfoGsm.CREATOR.createFromParcel(p);
+    }
+
+    public synchronized void setCellInfoListBehavior(boolean shouldReturn) {
+        mShouldReturnCellInfo = shouldReturn;
+    }
+
     @Override
-    public void getCellInfoList(Message response, WorkSource WorkSource) {
+    public synchronized void getCellInfoList(Message response, WorkSource workSource) {
+        if (!mShouldReturnCellInfo) return;
+
         if (mCellInfoList == null) {
-            Parcel p = Parcel.obtain();
-            p.writeInt(1);
-            p.writeInt(1);
-            p.writeInt(2);
-            p.writeLong(1453510289108L);
-            p.writeInt(310);
-            p.writeInt(260);
-            p.writeInt(123);
-            p.writeInt(456);
-            p.writeInt(99);
-            p.writeInt(3);
-            p.setDataPosition(0);
-
-            CellInfoGsm cellInfo = CellInfoGsm.CREATOR.createFromParcel(p);
-
             ArrayList<CellInfo> mCellInfoList = new ArrayList();
-            mCellInfoList.add(cellInfo);
+            mCellInfoList.add(getCellInfoGsm());
         }
 
         resultSuccess(response, mCellInfoList);
@@ -1989,12 +1999,12 @@
     }
 
     @Override
-    public void nvReadItem(int itemID, Message response) {
+    public void nvReadItem(int itemID, Message response, WorkSource workSource) {
         unimplemented(response);
     }
 
     @Override
-    public void nvWriteItem(int itemID, String itemValue, Message response) {
+    public void nvWriteItem(int itemID, String itemValue, Message response, WorkSource workSource) {
         unimplemented(response);
     }
 
@@ -2015,7 +2025,7 @@
 
     @Override
     public void requestShutdown(Message result) {
-        setRadioState(RadioState.RADIO_UNAVAILABLE);
+        setRadioState(TelephonyManager.RADIO_POWER_UNAVAILABLE, false /* forceNotifyRegistrants */);
     }
 
     @Override
@@ -2045,17 +2055,18 @@
     }
 
     @Override
-    public void getModemActivityInfo(Message result) {
+    public void getModemActivityInfo(Message result, WorkSource workSource) {
         unimplemented(result);
     }
 
     @Override
-    public void setAllowedCarriers(List<CarrierIdentifier> carriers, Message result) {
+    public void setAllowedCarriers(List<CarrierIdentifier> carriers, Message result,
+            WorkSource workSource) {
         unimplemented(result);
     }
 
     @Override
-    public void getAllowedCarriers(Message result) {
+    public void getAllowedCarriers(Message result, WorkSource workSource) {
         unimplemented(result);
     }
 
@@ -2230,7 +2241,7 @@
     }
 
     @Override
-    public void setSimCardPower(int state, Message result) {
+    public void setSimCardPower(int state, Message result, WorkSource workSource) {
     }
 
     @VisibleForTesting
@@ -2284,4 +2295,8 @@
     public void stopNattKeepalive(int sessionHandle, Message result) {
         SimulatedCommandsVerifier.getInstance().stopNattKeepalive(sessionHandle, result);
     }
+
+    public Handler getHandler() {
+        return mHandlerThread.getThreadHandler();
+    }
 }
diff --git a/src/java/com/android/internal/telephony/test/SimulatedCommandsVerifier.java b/src/java/com/android/internal/telephony/test/SimulatedCommandsVerifier.java
index f43cee0..5cb130b 100644
--- a/src/java/com/android/internal/telephony/test/SimulatedCommandsVerifier.java
+++ b/src/java/com/android/internal/telephony/test/SimulatedCommandsVerifier.java
@@ -20,9 +20,9 @@
 import android.net.LinkProperties;
 import android.os.Handler;
 import android.os.Message;
-import android.service.carrier.CarrierIdentifier;
 import android.telephony.ImsiEncryptionInfo;
 import android.telephony.NetworkScanRequest;
+import android.telephony.TelephonyManager;
 import android.telephony.data.DataProfile;
 
 import com.android.internal.telephony.CommandsInterface;
@@ -31,8 +31,6 @@
 import com.android.internal.telephony.cdma.CdmaSmsBroadcastConfigInfo;
 import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
 
-import java.util.List;
-
 public class SimulatedCommandsVerifier implements CommandsInterface {
     private static SimulatedCommandsVerifier sInstance;
 
@@ -48,8 +46,8 @@
     }
 
     @Override
-    public RadioState getRadioState() {
-        return null;
+    public int getRadioState() {
+        return TelephonyManager.RADIO_POWER_UNAVAILABLE;
     }
 
     @Override
@@ -1278,16 +1276,6 @@
     }
 
     @Override
-    public void nvReadItem(int itemID, Message response) {
-
-    }
-
-    @Override
-    public void nvWriteItem(int itemID, String itemValue, Message response) {
-
-    }
-
-    @Override
     public void nvWriteCdmaPrl(byte[] preferredRoamingList, Message response) {
 
     }
@@ -1369,27 +1357,12 @@
     }
 
     @Override
-    public void getModemActivityInfo(Message result) {
-
-    }
-
-    @Override
     public void setCarrierInfoForImsiEncryption(ImsiEncryptionInfo imsiEncryptionInfo,
                                                 Message result) {
 
     }
 
     @Override
-    public void setAllowedCarriers(List<CarrierIdentifier> carriers, Message result) {
-
-    }
-
-    @Override
-    public void getAllowedCarriers(Message result) {
-
-    }
-
-    @Override
     public void registerForPcoData(Handler h, int what, Object obj) {
     }
 
@@ -1425,10 +1398,6 @@
     }
 
     @Override
-    public void setSimCardPower(int state, Message result) {
-    }
-
-    @Override
     public void registerForCarrierInfoForImsiEncryption(Handler h, int what, Object obj) {
     }
 
@@ -1453,6 +1422,14 @@
     }
 
     @Override
+    public void registerForEmergencyNumberList(Handler h, int what, Object obj) {
+    }
+
+    @Override
+    public void unregisterForEmergencyNumberList(Handler h) {
+    }
+
+    @Override
     public void startNattKeepalive(
             int contextId, KeepalivePacketData packetData, int intervalMillis, Message result) {
     }
diff --git a/src/java/com/android/internal/telephony/uicc/AnswerToReset.java b/src/java/com/android/internal/telephony/uicc/AnswerToReset.java
index b94df6d..6fd7c68 100644
--- a/src/java/com/android/internal/telephony/uicc/AnswerToReset.java
+++ b/src/java/com/android/internal/telephony/uicc/AnswerToReset.java
@@ -38,10 +38,10 @@
     private static final boolean VDBG = false; // STOPSHIP if true
     private static final int TAG_CARD_CAPABILITIES = 0x07;
     private static final int EXTENDED_APDU_INDEX = 2;
+    private static final int B8_MASK = 0x80;
     private static final int B7_MASK = 0x40;
     private static final int B2_MASK = 0x02;
 
-    public static final byte EUICC_SUPPORTED = (byte) 0x82;
     public static final byte DIRECT_CONVENTION = (byte) 0x3B;
     public static final byte INVERSE_CONVENTION = (byte) 0x3F;
     public static final int INTERFACE_BYTES_MASK = 0xF0;
@@ -148,12 +148,13 @@
     }
 
     private void checkIsEuiccSupported() {
-        // eUICC is supported only if the value of the first tB after T=15 is 82.
+        // eUICC is supported only if the b8 and b2 of the first tB after T=15 are set to 1.
         for (int i = 0; i < mInterfaceBytes.size() - 1; i++) {
             if (mInterfaceBytes.get(i).getTD() != null
                     && (mInterfaceBytes.get(i).getTD() & T_MASK) == T_VALUE_FOR_GLOBAL_INTERFACE
                     && mInterfaceBytes.get(i + 1).getTB() != null
-                    && mInterfaceBytes.get(i + 1).getTB() == EUICC_SUPPORTED) {
+                    && (mInterfaceBytes.get(i + 1).getTB() & B8_MASK) != 0
+                    && (mInterfaceBytes.get(i + 1).getTB() & B2_MASK) != 0) {
                 mIsEuiccSupported = true;
                 return;
             }
diff --git a/src/java/com/android/internal/telephony/uicc/IccCardStatus.java b/src/java/com/android/internal/telephony/uicc/IccCardStatus.java
index 3998eb2..5e7abfd 100644
--- a/src/java/com/android/internal/telephony/uicc/IccCardStatus.java
+++ b/src/java/com/android/internal/telephony/uicc/IccCardStatus.java
@@ -67,6 +67,7 @@
     public int        physicalSlotIndex = UiccController.INVALID_SLOT_ID;
     public String     atr;
     public String     iccid;
+    public String     eid;
 
     public IccCardApplicationStatus[] mApplications;
 
@@ -120,8 +121,12 @@
 
         StringBuilder sb = new StringBuilder();
         sb.append("IccCardState {").append(mCardState).append(",")
-        .append(mUniversalPinState)
-        .append(",num_apps=").append(mApplications.length);
+        .append(mUniversalPinState);
+        if (mApplications != null) {
+            sb.append(",num_apps=").append(mApplications.length);
+        } else {
+            sb.append(",mApplications=null");
+        }
 
         sb.append(",gsm_id=").append(mGsmUmtsSubscriptionAppIndex);
         if (mApplications != null
@@ -149,6 +154,7 @@
 
         sb.append(",physical_slot_id=").append(physicalSlotIndex).append(",atr=").append(atr);
         sb.append(",iccid=").append(SubscriptionInfo.givePrintableIccid(iccid));
+        sb.append(",eid=").append(eid);
 
         sb.append("}");
         return sb.toString();
diff --git a/src/java/com/android/internal/telephony/uicc/IccIoResult.java b/src/java/com/android/internal/telephony/uicc/IccIoResult.java
index b1b6e75..01d270c 100644
--- a/src/java/com/android/internal/telephony/uicc/IccIoResult.java
+++ b/src/java/com/android/internal/telephony/uicc/IccIoResult.java
@@ -187,9 +187,14 @@
 
     @Override
     public String toString() {
-        return "IccIoResult sw1:0x" + Integer.toHexString(sw1) + " sw2:0x"
-                + Integer.toHexString(sw2) + " Payload: "
-                + ((Build.IS_DEBUGGABLE && Build.IS_ENG) ? payload : "*******")
+        return "IccIoResult sw1:0x"
+                + Integer.toHexString(sw1)
+                + " sw2:0x"
+                + Integer.toHexString(sw2)
+                + " Payload: "
+                + ((Build.IS_DEBUGGABLE && Build.IS_ENG)
+                        ? IccUtils.bytesToHexString(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 97d497c..7bc2acc 100644
--- a/src/java/com/android/internal/telephony/uicc/IccRecords.java
+++ b/src/java/com/android/internal/telephony/uicc/IccRecords.java
@@ -29,12 +29,14 @@
 import android.text.TextUtils;
 
 import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.MccTable;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.io.UnsupportedEncodingException;
 import java.util.Arrays;
 import java.util.HashMap;
+import java.util.Objects;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -45,6 +47,30 @@
     protected static final boolean DBG = true;
     protected static final boolean VDBG = false; // STOPSHIP if true
 
+    // Lookup table for carriers known to produce SIMs which incorrectly indicate MNC length.
+    private static final String[] MCCMNC_CODES_HAVING_3DIGITS_MNC = {
+        "302370", "302720", "310260",
+        "405025", "405026", "405027", "405028", "405029", "405030", "405031", "405032",
+        "405033", "405034", "405035", "405036", "405037", "405038", "405039", "405040",
+        "405041", "405042", "405043", "405044", "405045", "405046", "405047", "405750",
+        "405751", "405752", "405753", "405754", "405755", "405756", "405799", "405800",
+        "405801", "405802", "405803", "405804", "405805", "405806", "405807", "405808",
+        "405809", "405810", "405811", "405812", "405813", "405814", "405815", "405816",
+        "405817", "405818", "405819", "405820", "405821", "405822", "405823", "405824",
+        "405825", "405826", "405827", "405828", "405829", "405830", "405831", "405832",
+        "405833", "405834", "405835", "405836", "405837", "405838", "405839", "405840",
+        "405841", "405842", "405843", "405844", "405845", "405846", "405847", "405848",
+        "405849", "405850", "405851", "405852", "405853", "405854", "405855", "405856",
+        "405857", "405858", "405859", "405860", "405861", "405862", "405863", "405864",
+        "405865", "405866", "405867", "405868", "405869", "405870", "405871", "405872",
+        "405873", "405874", "405875", "405876", "405877", "405878", "405879", "405880",
+        "405881", "405882", "405883", "405884", "405885", "405886", "405908", "405909",
+        "405910", "405911", "405912", "405913", "405914", "405915", "405916", "405917",
+        "405918", "405919", "405920", "405921", "405922", "405923", "405924", "405925",
+        "405926", "405927", "405928", "405929", "405930", "405931", "405932", "502142",
+        "502143", "502145", "502146", "502147", "502148"
+    };
+
     // ***** Instance Variables
     protected AtomicBoolean mDestroyed = new AtomicBoolean(false);
     protected AtomicBoolean mLoaded = new AtomicBoolean(false);
@@ -92,7 +118,7 @@
     protected String mNewVoiceMailNum = null;
     protected String mNewVoiceMailTag = null;
     protected boolean mIsVoiceMailFixed = false;
-    protected String mImsi;
+    protected String mImsi; // IMSI must be only valid numeric characters 0-9 without padding 'f's
     private IccIoResult auth_rsp;
 
     protected int mMncLength = UNINITIALIZED;
@@ -455,14 +481,81 @@
     }
 
     /**
-     * Imsi could be set by ServiceStateTrackers in case of cdma
-     * @param imsi
+     * Update IMSI record and try to extract the PLMN information and notify registrants.
+     * @param inImsi the IMSI value
      */
-    public void setImsi(String imsi) {
-        mImsi = imsi;
+    public void setImsi(String inImsi) {
+        // Remove trailing F's if present in IMSI.
+        mImsi = IccUtils.stripTrailingFs(inImsi);
+        if (!Objects.equals(mImsi, inImsi)) {
+            loge("Invalid IMSI padding digits received.");
+        }
+
+        if (TextUtils.isEmpty(mImsi)) mImsi = null;
+
+        if (mImsi != null && !mImsi.matches("[0-9]+")) {
+            loge("Invalid non-numeric IMSI digits received.");
+            mImsi = null;
+        }
+
+        // IMSI (MCC+MNC+MSIN) is at least 6 digits, but not more
+        // than 15 (and usually 15).
+        // This will also handle un-set IMSI records (all Fs)
+        if (mImsi != null && (mImsi.length() < 6 || mImsi.length() > 15)) {
+            loge("invalid IMSI " + mImsi);
+            mImsi = null;
+        }
+
+        log("IMSI: mMncLength=" + mMncLength);
+
+        if (mImsi != null && mImsi.length() >= 6) {
+            log("IMSI: " + mImsi.substring(0, 6) + Rlog.pii(VDBG, mImsi.substring(6)));
+        }
+
+        // IMSI has changed so the PLMN might have changed as well
+        updateOperatorPlmn();
+
         mImsiReadyRegistrants.notifyRegistrants();
     }
 
+    protected void updateOperatorPlmn() {
+        // In case of a test override, use the test IMSI
+        String imsi = getIMSI();
+
+        if (imsi != null) {
+            // First try to guess the length based on a table of known 3-digit MNCs.
+            if (((mMncLength == UNKNOWN) || (mMncLength == 2)) && imsi.length() >= 6) {
+                String mccmncCode = imsi.substring(0, 6);
+                for (String mccmnc : MCCMNC_CODES_HAVING_3DIGITS_MNC) {
+                    if (mccmnc.equals(mccmncCode)) {
+                        mMncLength = 3;
+                        log("IMSI: setting1 mMncLength=" + mMncLength);
+                        break;
+                    }
+                }
+            }
+
+            // If still unknown, guess using the MCC.
+            if (mMncLength == UNKNOWN) {
+                try {
+                    int mcc = Integer.parseInt(imsi.substring(0, 3));
+                    mMncLength = MccTable.smallestDigitsMccForMnc(mcc);
+                    log("setting2 mMncLength=" + mMncLength);
+                } catch (NumberFormatException e) {
+                    loge("Corrupt IMSI! setting3 mMncLength=" + mMncLength);
+                }
+            }
+
+            if (mMncLength != UNKNOWN && mMncLength != UNINITIALIZED
+                    && imsi.length() >= 3 + mMncLength) {
+                log("update mccmnc=" + imsi.substring(0, 3 + mMncLength));
+                // finally have both the imsi and the mncLength and
+                // can parse the imsi properly
+                MccTable.updateMccMncConfiguration(mContext, imsi.substring(0, 3 + mMncLength));
+            }
+        }
+    }
+
     /**
      * Get the Network Access ID (NAI) on a CSIM for CDMA like networks. Default is null if IMSI is
      * not supported or unavailable.
diff --git a/src/java/com/android/internal/telephony/uicc/IccSlotStatus.java b/src/java/com/android/internal/telephony/uicc/IccSlotStatus.java
index 5fdd322..43f525e 100644
--- a/src/java/com/android/internal/telephony/uicc/IccSlotStatus.java
+++ b/src/java/com/android/internal/telephony/uicc/IccSlotStatus.java
@@ -34,6 +34,7 @@
     public int        logicalSlotIndex;
     public String     atr;
     public String     iccid;
+    public String     eid;
 
     /**
      * Set the cardState according to the input state.
@@ -80,7 +81,8 @@
                 .append(slotState).append(",")
                 .append("logicalSlotIndex=").append(logicalSlotIndex).append(",")
                 .append("atr=").append(atr).append(",iccid=")
-                .append(SubscriptionInfo.givePrintableIccid(iccid));
+                .append(SubscriptionInfo.givePrintableIccid(iccid)).append(",")
+                .append("eid=").append(eid);
 
         sb.append("}");
         return sb.toString();
@@ -100,7 +102,8 @@
                 && (slotState == that.slotState)
                 && (logicalSlotIndex == that.logicalSlotIndex)
                 && (TextUtils.equals(atr, that.atr))
-                && (TextUtils.equals(iccid, that.iccid));
+                && (TextUtils.equals(iccid, that.iccid))
+                && (TextUtils.equals(eid, that.eid));
     }
 
 }
diff --git a/src/java/com/android/internal/telephony/uicc/PlmnActRecord.java b/src/java/com/android/internal/telephony/uicc/PlmnActRecord.java
index ea87c8d..4d0b484 100755
--- a/src/java/com/android/internal/telephony/uicc/PlmnActRecord.java
+++ b/src/java/com/android/internal/telephony/uicc/PlmnActRecord.java
@@ -16,11 +16,15 @@
 
 package com.android.internal.telephony.uicc;
 
+import android.annotation.IntDef;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.telephony.Rlog;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.Arrays;
+import java.util.Objects;
 
 /**
  * {@hide}
@@ -28,6 +32,13 @@
 public class PlmnActRecord implements Parcelable {
     private static final String LOG_TAG = "PlmnActRecord";
 
+    private static final boolean VDBG = false;
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, value = {ACCESS_TECH_UTRAN, ACCESS_TECH_EUTRAN, ACCESS_TECH_GSM,
+            ACCESS_TECH_GSM_COMPACT, ACCESS_TECH_CDMA2000_HRPD, ACCESS_TECH_CDMA2000_1XRTT,
+            ACCESS_TECH_RESERVED})
+    public @interface AccessTech {}
     // Values specified in 3GPP 31.102 sec. 4.2.5
     public static final int ACCESS_TECH_UTRAN = 0x8000;
     public static final int ACCESS_TECH_EUTRAN = 0x4000;
@@ -42,20 +53,13 @@
     public final String plmn;
     public final int accessTechs;
 
-    private static final boolean VDBG = false;
-
-    public static final Parcelable.Creator<PlmnActRecord> CREATOR =
-            new Parcelable.Creator<PlmnActRecord>() {
-        @Override
-        public PlmnActRecord createFromParcel(Parcel source) {
-            return new PlmnActRecord(source.readString(), source.readInt());
-        }
-
-        @Override
-        public PlmnActRecord[] newArray(int size) {
-            return new PlmnActRecord[size];
-        }
-    };
+    /**
+     * Instantiate a PLMN w/ ACT Record
+     */
+    public PlmnActRecord(String plmn, int accessTechs) {
+        this.plmn = plmn;
+        this.accessTechs = accessTechs;
+    }
 
     /* From 3gpp 31.102 section 4.2.5
      * Bytes 0-2 bcd-encoded PLMN-ID
@@ -68,9 +72,15 @@
                         | Byte.toUnsignedInt(bytes[offset + 4]);
     }
 
-    private PlmnActRecord(String plmn, int accessTechs) {
-        this.plmn = plmn;
-        this.accessTechs = accessTechs;
+    /**
+     * Get a record encoded as per 31.102 section 4.2.5
+     */
+    public byte[] getBytes() {
+        byte[] ret = new byte[ENCODED_LENGTH];
+        IccUtils.stringToBcdPlmn(this.plmn, ret, 0);
+        ret[3] = (byte) (this.accessTechs >> 8);
+        ret[4] = (byte) this.accessTechs;
+        return ret;
     }
 
     private String accessTechString() {
@@ -142,4 +152,29 @@
         dest.writeInt(accessTechs);
     }
 
+    public static final Parcelable.Creator<PlmnActRecord> CREATOR =
+            new Parcelable.Creator<PlmnActRecord>() {
+        @Override
+        public PlmnActRecord createFromParcel(Parcel source) {
+            return new PlmnActRecord(source.readString(), source.readInt());
+        }
+
+        @Override
+        public PlmnActRecord[] newArray(int size) {
+            return new PlmnActRecord[size];
+        }
+    };
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(plmn, accessTechs);
+    }
+
+    @Override
+    public boolean equals(Object rhs) {
+        if (!(rhs instanceof PlmnActRecord)) return false;
+
+        PlmnActRecord r = (PlmnActRecord) rhs;
+        return plmn.equals(r.plmn) && accessTechs == r.accessTechs;
+    }
 }
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 1075146..ba10eff 100755
--- a/src/java/com/android/internal/telephony/uicc/SIMRecords.java
+++ b/src/java/com/android/internal/telephony/uicc/SIMRecords.java
@@ -59,7 +59,6 @@
 
     private int mCallForwardingStatus;
 
-
     /**
      * States only used by getSpnFsm FSM
      */
@@ -172,31 +171,6 @@
     private static final int EVENT_APP_NETWORK_LOCKED = 3 + SYSTEM_EVENT_BASE;
 
 
-    // Lookup table for carriers known to produce SIMs which incorrectly indicate MNC length.
-
-    private static final String[] MCCMNC_CODES_HAVING_3DIGITS_MNC = {
-        "302370", "302720", "310260",
-        "405025", "405026", "405027", "405028", "405029", "405030", "405031", "405032",
-        "405033", "405034", "405035", "405036", "405037", "405038", "405039", "405040",
-        "405041", "405042", "405043", "405044", "405045", "405046", "405047", "405750",
-        "405751", "405752", "405753", "405754", "405755", "405756", "405799", "405800",
-        "405801", "405802", "405803", "405804", "405805", "405806", "405807", "405808",
-        "405809", "405810", "405811", "405812", "405813", "405814", "405815", "405816",
-        "405817", "405818", "405819", "405820", "405821", "405822", "405823", "405824",
-        "405825", "405826", "405827", "405828", "405829", "405830", "405831", "405832",
-        "405833", "405834", "405835", "405836", "405837", "405838", "405839", "405840",
-        "405841", "405842", "405843", "405844", "405845", "405846", "405847", "405848",
-        "405849", "405850", "405851", "405852", "405853", "405854", "405855", "405856",
-        "405857", "405858", "405859", "405860", "405861", "405862", "405863", "405864",
-        "405865", "405866", "405867", "405868", "405869", "405870", "405871", "405872",
-        "405873", "405874", "405875", "405876", "405877", "405878", "405879", "405880",
-        "405881", "405882", "405883", "405884", "405885", "405886", "405908", "405909",
-        "405910", "405911", "405912", "405913", "405914", "405915", "405916", "405917",
-        "405918", "405919", "405920", "405921", "405922", "405923", "405924", "405925",
-        "405926", "405927", "405928", "405929", "405930", "405931", "405932", "502142",
-        "502143", "502145", "502146", "502147", "502148"
-    };
-
     // ***** Constructor
 
     public SIMRecords(UiccCardApplication app, Context c, CommandsInterface ci) {
@@ -656,7 +630,6 @@
                 /* IO events */
                 case EVENT_GET_IMSI_DONE:
                     isRecordLoadResponse = true;
-
                     ar = (AsyncResult) msg.obj;
 
                     if (ar.exception != null) {
@@ -664,58 +637,7 @@
                         break;
                     }
 
-                    mImsi = (String) ar.result;
-
-                    // IMSI (MCC+MNC+MSIN) is at least 6 digits, but not more
-                    // than 15 (and usually 15).
-                    if (mImsi != null && (mImsi.length() < 6 || mImsi.length() > 15)) {
-                        loge("invalid IMSI " + mImsi);
-                        mImsi = null;
-                    }
-
-                    log("IMSI: mMncLength=" + mMncLength);
-
-                    if (mImsi != null && mImsi.length() >= 6) {
-                        log("IMSI: " + mImsi.substring(0, 6)
-                                + Rlog.pii(LOG_TAG, mImsi.substring(6)));
-                    }
-
-                    String imsi = getIMSI();
-
-                    if (((mMncLength == UNKNOWN) || (mMncLength == 2))
-                            && ((imsi != null) && (imsi.length() >= 6))) {
-                        String mccmncCode = imsi.substring(0, 6);
-                        for (String mccmnc : MCCMNC_CODES_HAVING_3DIGITS_MNC) {
-                            if (mccmnc.equals(mccmncCode)) {
-                                mMncLength = 3;
-                                log("IMSI: setting1 mMncLength=" + mMncLength);
-                                break;
-                            }
-                        }
-                    }
-
-                    if (mMncLength == UNKNOWN) {
-                        // the SIM has told us all it knows, but it didn't know the mnc length.
-                        // guess using the mcc
-                        try {
-                            int mcc = Integer.parseInt(imsi.substring(0, 3));
-                            mMncLength = MccTable.smallestDigitsMccForMnc(mcc);
-                            log("setting2 mMncLength=" + mMncLength);
-                        } catch (NumberFormatException e) {
-                            mMncLength = UNKNOWN;
-                            loge("Corrupt IMSI! setting3 mMncLength=" + mMncLength);
-                        }
-                    }
-
-                    if (mMncLength != UNKNOWN && mMncLength != UNINITIALIZED
-                            && imsi.length() >= 3 + mMncLength) {
-                        log("update mccmnc=" + imsi.substring(0, 3 + mMncLength));
-                        // finally have both the imsi and the mncLength and
-                        // can parse the imsi properly
-                        MccTable.updateMccMncConfiguration(mContext,
-                                imsi.substring(0, 3 + mMncLength), false);
-                    }
-                    mImsiReadyRegistrants.notifyRegistrants();
+                    setImsi((String) ar.result);
                     break;
 
                 case EVENT_GET_MBI_DONE:
@@ -903,20 +825,10 @@
                     break;
 
                 case EVENT_GET_AD_DONE:
+                    isRecordLoadResponse = true;
+                    mMncLength = UNKNOWN;
                     try {
-                        isRecordLoadResponse = true;
-
-                        if (mCarrierTestOverride.isInTestMode() && getIMSI() != null) {
-                            imsi = getIMSI();
-                            try {
-                                int mcc = Integer.parseInt(imsi.substring(0, 3));
-                                mMncLength = MccTable.smallestDigitsMccForMnc(mcc);
-                                log("[TestMode] mMncLength=" + mMncLength);
-                            } catch (NumberFormatException e) {
-                                mMncLength = UNKNOWN;
-                                loge("[TestMode] Corrupt IMSI! mMncLength=" + mMncLength);
-                            }
-                        } else {
+                        if (!mCarrierTestOverride.isInTestMode()) {
                             ar = (AsyncResult) msg.obj;
                             data = (byte[]) ar.result;
 
@@ -936,62 +848,15 @@
                                 break;
                             }
 
-                            mMncLength = data[3] & 0xf;
-                            log("setting4 mMncLength=" + mMncLength);
-                        }
-
-                        if (mMncLength == 0xf) {
-                            mMncLength = UNKNOWN;
-                            log("setting5 mMncLength=" + mMncLength);
-                        } else if (mMncLength != 2 && mMncLength != 3) {
-                            mMncLength = UNINITIALIZED;
-                            log("setting5 mMncLength=" + mMncLength);
+                            int len = data[3] & 0xf;
+                            if (len == 2 || len == 3) {
+                                mMncLength = len;
+                            } else {
+                                log("Received invalid or unset MNC Length=" + len);
+                            }
                         }
                     } finally {
-
-                        // IMSI could be a value reading from Sim or a fake IMSI if in the test mode
-                        imsi = getIMSI();
-
-                        if (((mMncLength == UNINITIALIZED) || (mMncLength == UNKNOWN)
-                                    || (mMncLength == 2)) && ((imsi != null)
-                                    && (imsi.length() >= 6))) {
-                            String mccmncCode = imsi.substring(0, 6);
-                            log("mccmncCode=" + mccmncCode);
-                            for (String mccmnc : MCCMNC_CODES_HAVING_3DIGITS_MNC) {
-                                if (mccmnc.equals(mccmncCode)) {
-                                    mMncLength = 3;
-                                    log("setting6 mMncLength=" + mMncLength);
-                                    break;
-                                }
-                            }
-                        }
-
-                        if (mMncLength == UNKNOWN || mMncLength == UNINITIALIZED) {
-                            if (imsi != null) {
-                                try {
-                                    int mcc = Integer.parseInt(imsi.substring(0, 3));
-
-                                    mMncLength = MccTable.smallestDigitsMccForMnc(mcc);
-                                    log("setting7 mMncLength=" + mMncLength);
-                                } catch (NumberFormatException e) {
-                                    mMncLength = UNKNOWN;
-                                    loge("Corrupt IMSI! setting8 mMncLength=" + mMncLength);
-                                }
-                            } else {
-                                // Indicate we got this info, but it didn't contain the length.
-                                mMncLength = UNKNOWN;
-                                log("MNC length not present in EF_AD setting9 "
-                                        + "mMncLength=" + mMncLength);
-                            }
-                        }
-                        if (imsi != null && mMncLength != UNKNOWN
-                                && imsi.length() >= 3 + mMncLength) {
-                            // finally have both imsi and the length of the mnc and can parse
-                            // the imsi properly
-                            log("update mccmnc=" + imsi.substring(0, 3 + mMncLength));
-                            MccTable.updateMccMncConfiguration(mContext,
-                                    imsi.substring(0, 3 + mMncLength), false);
-                        }
+                        updateOperatorPlmn();
                     }
                     break;
 
@@ -1163,16 +1028,23 @@
                                         onCphsCompleted));
                     } else {
                         if (ar.userObj != null) {
-                            CarrierConfigManager configLoader = (CarrierConfigManager)
+                            CarrierConfigManager configManager = (CarrierConfigManager)
                                     mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
-                            if (ar.exception != null && configLoader != null
-                                    && configLoader.getConfig().getBoolean(
-                                    CarrierConfigManager.KEY_EDITABLE_VOICEMAIL_NUMBER_BOOL)) {
-                                // GsmCdmaPhone will store vm number on device
-                                // when IccVmNotSupportedException occurred
-                                AsyncResult.forMessage(((Message) ar.userObj)).exception =
-                                        new IccVmNotSupportedException(
-                                            "Update SIM voice mailbox error");
+                            if (ar.exception != null && configManager != null) {
+                                PersistableBundle b = configManager.getConfigForSubId(
+                                        SubscriptionController.getInstance().getSubIdUsingPhoneId(
+                                                mParentApp.getPhoneId()));
+                                if (b != null && b.getBoolean(
+                                        CarrierConfigManager.KEY_EDITABLE_VOICEMAIL_NUMBER_BOOL)) {
+                                    // GsmCdmaPhone will store vm number on device
+                                    // when IccVmNotSupportedException occurred
+                                    AsyncResult.forMessage(((Message) ar.userObj)).exception =
+                                            new IccVmNotSupportedException(
+                                                    "Update SIM voice mailbox error");
+                                } else {
+                                    AsyncResult.forMessage(((Message) ar.userObj))
+                                            .exception = ar.exception;
+                                }
                             } else {
                                 AsyncResult.forMessage(((Message) ar.userObj))
                                     .exception = ar.exception;
@@ -1536,6 +1408,7 @@
 
     private void onLockedAllRecordsLoaded() {
         setSimLanguageFromEF();
+        setVoiceCallForwardingFlagFromSimRecords();
         if (mLockedRecordsReqReason == LOCKED_RECORDS_REQ_REASON_LOCKED) {
             mLockedRecordsLoadedRegistrants.notifyRegistrants(new AsyncResult(null, null, null));
         } else if (mLockedRecordsReqReason == LOCKED_RECORDS_REQ_REASON_NETWORK_LOCKED) {
@@ -1571,8 +1444,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");
         }
diff --git a/src/java/com/android/internal/telephony/uicc/UiccCard.java b/src/java/com/android/internal/telephony/uicc/UiccCard.java
index 68463c5..2cf19f9 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccCard.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccCard.java
@@ -47,8 +47,8 @@
             "com.android.internal.telephony.uicc.ICC_CARD_ADDED";
 
     // 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;
+    // between UiccSlot, UiccCard, EuiccCard, and UiccProfile for now.
+    protected final Object mLock;
     private CardState mCardState;
     private String mIccid;
     protected String mCardId;
@@ -85,7 +85,8 @@
 
             if (mCardState != CardState.CARDSTATE_ABSENT) {
                 if (mUiccProfile == null) {
-                    mUiccProfile = TelephonyComponentFactory.getInstance().makeUiccProfile(
+                    mUiccProfile = TelephonyComponentFactory.getInstance()
+                            .inject(UiccProfile.class.getName()).makeUiccProfile(
                             mContext, mCi, ics, mPhoneId, this, mLock);
                 } else {
                     mUiccProfile.update(mContext, mCi, ics);
@@ -233,10 +234,10 @@
      * @deprecated Please use {@link UiccProfile#resetAppWithAid(String, boolean)} instead.
      */
     @Deprecated
-    public boolean resetAppWithAid(String aid, boolean disposeCatService) {
+    public boolean resetAppWithAid(String aid, boolean reset) {
         synchronized (mLock) {
             if (mUiccProfile != null) {
-                return mUiccProfile.resetAppWithAid(aid, disposeCatService);
+                return mUiccProfile.resetAppWithAid(aid, reset);
             } else {
                 return false;
             }
diff --git a/src/java/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRules.java b/src/java/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRules.java
index a7bbb95..76a0227 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRules.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRules.java
@@ -30,6 +30,7 @@
 import android.telephony.TelephonyManager;
 import android.telephony.UiccAccessRule;
 import android.text.TextUtils;
+import android.util.LocalLog;
 
 import com.android.internal.telephony.CommandException;
 
@@ -185,7 +186,8 @@
     private List<UiccAccessRule> mAccessRules;
     private String mRules;
     private Message mLoadedCallback;
-    private String mStatusMessage;  // Only used for debugging.
+    // LocalLog buffer to hold important status messages for debugging.
+    private LocalLog mStatusMessage = new LocalLog(100);
     private int mChannelId; // Channel Id for communicating with UICC.
     private int mRetryCount;  // Number of retries for open logical channel.
     private boolean mCheckedRules = false;  // Flag that used to mark whether get rules from ARA-D.
@@ -209,7 +211,7 @@
         log("Creating UiccCarrierPrivilegeRules");
         mUiccProfile = uiccProfile;
         mState = new AtomicInteger(STATE_LOADING);
-        mStatusMessage = "Not loaded.";
+        mStatusMessage.log("Not loaded.");
         mLoadedCallback = loadedCallback;
         mRules = "";
         mAccessRules = new ArrayList<>();
@@ -447,7 +449,7 @@
                         if (mAIDInUse == ARAD) {
                             // Open logical channel with ARA_M.
                             mRules = "";
-                            openChannel(1);
+                            openChannel(ARAM);
                         }
                         if (mAIDInUse == ARAM) {
                             if (mCheckedRules) {
@@ -456,6 +458,13 @@
                                 // if rules cannot be read from both ARA_D and ARA_M applet,
                                 // fallback to PKCS15-based ARF.
                                 log("No ARA, try ARF next.");
+                                if (ar.exception instanceof CommandException
+                                        && ((CommandException) (ar.exception)).getCommandError()
+                                        != CommandException.Error.NO_SUCH_ELEMENT) {
+                                    updateStatusMessage("No ARA due to "
+                                            +
+                                            ((CommandException) (ar.exception)).getCommandError());
+                                }
                                 mUiccPkcs15 = new UiccPkcs15(mUiccProfile,
                                         obtainMessage(EVENT_PKCS15_READ_DONE));
                             }
@@ -501,8 +510,19 @@
                         }
                     }
                 } else {
-                    if (mAIDInUse == ARAM) {
-                        updateState(STATE_ERROR, "Error reading value from SIM.");
+                    String errorMsg =  "Error reading value from SIM via "
+                            + ((mAIDInUse == ARAD) ? "ARAD" : "ARAM") + " due to ";
+                    if (ar.exception instanceof CommandException) {
+                        CommandException.Error errorCode =
+                                ((CommandException) (ar.exception)).getCommandError();
+                        errorMsg += "error code : " + errorCode;
+                    } else {
+                        errorMsg += "unknown exception : " + ar.exception.getMessage();
+                    }
+                    if (mAIDInUse == ARAD) {
+                        updateStatusMessage(errorMsg);
+                    } else {
+                        updateState(STATE_ERROR, errorMsg);
                     }
                 }
 
@@ -516,7 +536,7 @@
                 if (mAIDInUse == ARAD) {
                     // Close logical channel with ARA_D and then open logical channel with ARA_M.
                     mRules = "";
-                    openChannel(1);
+                    openChannel(ARAM);
                 }
                 break;
 
@@ -667,7 +687,11 @@
             mLoadedCallback.sendToTarget();
         }
 
-        mStatusMessage = statusMessage;
+        updateStatusMessage(statusMessage);
+    }
+
+    private void updateStatusMessage(String statusMessage) {
+        mStatusMessage.log(statusMessage);
     }
 
     private static void log(String msg) {
@@ -680,7 +704,8 @@
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("UiccCarrierPrivilegeRules: " + this);
         pw.println(" mState=" + getStateString(mState.get()));
-        pw.println(" mStatusMessage='" + mStatusMessage + "'");
+        pw.println(" mStatusMessage=");
+        mStatusMessage.dump(fd, pw, args);
         if (mAccessRules != null) {
             pw.println(" mAccessRules: ");
             for (UiccAccessRule ar : mAccessRules) {
diff --git a/src/java/com/android/internal/telephony/uicc/UiccController.java b/src/java/com/android/internal/telephony/uicc/UiccController.java
index 670b85a..cb860d8 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccController.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccController.java
@@ -18,17 +18,22 @@
 
 import android.content.Context;
 import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
 import android.os.AsyncResult;
 import android.os.Handler;
 import android.os.Message;
 import android.os.Registrant;
 import android.os.RegistrantList;
 import android.os.storage.StorageManager;
+import android.preference.PreferenceManager;
 import android.telephony.CarrierConfigManager;
 import android.telephony.Rlog;
 import android.telephony.TelephonyManager;
+import android.text.TextUtils;
 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;
@@ -36,6 +41,7 @@
 import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.RadioConfig;
 import com.android.internal.telephony.SubscriptionInfoUpdater;
+import com.android.internal.telephony.uicc.IccCardStatus.CardState;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -111,15 +117,26 @@
 
     // this needs to be here, because on bootup we dont know which index maps to which UiccSlot
     private CommandsInterface[] mCis;
-    private UiccSlot[] mUiccSlots;
+    @VisibleForTesting
+    public UiccSlot[] mUiccSlots;
     private int[] mPhoneIdToSlotId;
     private boolean mIsSlotStatusSupported = true;
 
+    // This maps the externally exposed card ID (int) to the internal card ID string (ICCID/EID).
+    // The array index is the card ID (int).
+    // This mapping exists to expose card-based functionality without exposing the EID, which is
+    // considered sensetive information.
+    private ArrayList<String> mCardStrings;
+
+    // SharedPreference key for saving the known card strings (ICCIDs and EIDs) ordered by card ID
+    private static final String CARD_STRINGS = "card_strings";
+
     private static final Object mLock = new Object();
     private static UiccController mInstance;
     private static ArrayList<IccSlotStatus> sLastSlotStatus;
 
-    private Context mContext;
+    @VisibleForTesting
+    public Context mContext;
 
     protected RegistrantList mIccChangedRegistrants = new RegistrantList();
 
@@ -181,6 +198,7 @@
         }
 
         mLauncher = new UiccStateChangedLauncher(c, this);
+        mCardStrings = loadCardStrings();
     }
 
     private int getSlotIdFromPhoneId(int phoneId) {
@@ -463,10 +481,32 @@
         }
     }
 
-    static void updateInternalIccState(String value, String reason, int phoneId) {
+    static String getIccStateIntentString(IccCardConstants.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;
+        }
+    }
+
+    static void updateInternalIccState(Context context, IccCardConstants.State state, String reason,
+            int phoneId) {
+        TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(
+                Context.TELEPHONY_SERVICE);
+        telephonyManager.setSimStateForPhone(phoneId, state.toString());
+
         SubscriptionInfoUpdater subInfoUpdator = PhoneFactory.getSubscriptionInfoUpdater();
         if (subInfoUpdator != null) {
-            subInfoUpdator.updateInternalIccState(value, reason, phoneId);
+            subInfoUpdator.updateInternalIccState(getIccStateIntentString(state),
+                    reason, phoneId);
         } else {
             Rlog.e(LOG_TAG, "subInfoUpdate is null.");
         }
@@ -507,10 +547,52 @@
 
         mUiccSlots[slotId].update(mCis[index], status, index);
 
+        UiccCard card = mUiccSlots[slotId].getUiccCard();
+        if (card != null && (card.getCardState() == CardState.CARDSTATE_PRESENT)) {
+            // Card.getCardId returns the cardString, not the public card ID int
+            String cardString = card.getCardId();
+            addCardId(cardString);
+        }
+
         if (DBG) log("Notifying IccChangedRegistrants");
         mIccChangedRegistrants.notifyRegistrants(new AsyncResult(null, index, null));
     }
 
+    private void addCardId(String cardString) {
+        if (TextUtils.isEmpty(cardString)) {
+            return;
+        }
+        if (!mCardStrings.contains(cardString)) {
+            mCardStrings.add(cardString);
+            saveCardStrings();
+        }
+    }
+
+    /**
+     * Converts the card string (the ICCID/EID, formerly named card ID) to the public int cardId.
+     * Returns -1 if the card string does not map to a cardId.
+     */
+    public int convertToPublicCardId(String cardString) {
+        return mCardStrings.indexOf(cardString);
+    }
+
+    private ArrayList<String> loadCardStrings() {
+        String cardStrings =
+                PreferenceManager.getDefaultSharedPreferences(mContext).getString(CARD_STRINGS, "");
+        if (TextUtils.isEmpty(cardStrings)) {
+            // just return an empty list, since String.split would return the list { "" }
+            return new ArrayList<String>();
+        }
+        return new ArrayList<String>(Arrays.asList(cardStrings.split(",")));
+    }
+
+    private void saveCardStrings() {
+        SharedPreferences.Editor editor =
+                PreferenceManager.getDefaultSharedPreferences(mContext).edit();
+        editor.putString(CARD_STRINGS, TextUtils.join(",", mCardStrings));
+        editor.commit();
+    }
+
     private synchronized void onGetSlotStatusDone(AsyncResult ar) {
         if (!mIsSlotStatusSupported) {
             if (VDBG) log("onGetSlotStatusDone: ignoring since mIsSlotStatusSupported is false");
@@ -569,6 +651,11 @@
             }
 
             mUiccSlots[i].update(isActive ? mCis[iss.logicalSlotIndex] : null, iss);
+
+            if (mUiccSlots[i].isEuicc()) {
+                String eid = iss.eid;
+                addCardId(eid);
+            }
         }
 
         if (VDBG) logPhoneIdToSlotIdMapping();
@@ -645,11 +732,11 @@
             // 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 /* disposeCatService */);
+                changed = uiccCard.resetAppWithAid(resp.aid, true /* reset */);
                 break;
             case IccRefreshResponse.REFRESH_RESULT_INIT:
                 // don't dispose CatService on SIM REFRESH of type INIT
-                changed = uiccCard.resetAppWithAid(resp.aid, false /* disposeCatService */);
+                changed = uiccCard.resetAppWithAid(resp.aid, false /* initialize */);
                 break;
             default:
                 return;
@@ -673,6 +760,18 @@
         mCis[index].getIccCardStatus(obtainMessage(EVENT_GET_ICC_STATUS_DONE, index));
     }
 
+    /**
+     * static method to return whether CDMA is supported on the device
+     * @param context object representative of the application that is calling this method
+     * @return true if CDMA is supported by the device
+     */
+    public static boolean isCdmaSupported(Context context) {
+        PackageManager packageManager = context.getPackageManager();
+        boolean isCdmaSupported =
+                packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_CDMA);
+        return isCdmaSupported;
+    }
+
     private boolean isValidPhoneIndex(int index) {
         return (index >= 0 && index < TelephonyManager.getDefault().getPhoneCount());
     }
@@ -700,6 +799,7 @@
         }
         pw.println();
         pw.flush();
+        pw.println(" mIsCdmaSupported=" + isCdmaSupported(mContext));
         pw.println(" mUiccSlots: size=" + mUiccSlots.length);
         for (int i = 0; i < mUiccSlots.length; i++) {
             if (mUiccSlots[i] == null) {
diff --git a/src/java/com/android/internal/telephony/uicc/UiccPkcs15.java b/src/java/com/android/internal/telephony/uicc/UiccPkcs15.java
index b3a3482..cc99ae9 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccPkcs15.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccPkcs15.java
@@ -170,7 +170,7 @@
     private UiccProfile mUiccProfile;  // Parent
     private Message mLoadedCallback;
     private int mChannelId = -1; // Channel Id for communicating with UICC.
-    private List<String> mRules = new ArrayList<String>();
+    private List<String> mRules = null;
     private Pkcs15Selector mPkcs15Selector;
     private FileHandler mFh;
 
@@ -195,45 +195,46 @@
         AsyncResult ar = (AsyncResult) msg.obj;
 
         switch (msg.what) {
-          case EVENT_SELECT_PKCS15_DONE:
-              if (ar.exception == null) {
-                  // ar.result is null if using logical channel,
-                  // or string for pkcs15 path if using file access.
-                  mFh = new FileHandler((String)ar.result);
-                  if (!mFh.loadFile(ID_ACRF, obtainMessage(EVENT_LOAD_ACRF_DONE))) {
-                      cleanUp();
-                  }
-              } else {
-                  log("select pkcs15 failed: " + ar.exception);
-                  // select PKCS15 failed, notify uiccCarrierPrivilegeRules
-                  mLoadedCallback.sendToTarget();
-              }
-              break;
+            case EVENT_SELECT_PKCS15_DONE:
+                if (ar.exception == null) {
+                    // ar.result is null if using logical channel,
+                    // or string for pkcs15 path if using file access.
+                    mFh = new FileHandler((String) ar.result);
+                    if (!mFh.loadFile(ID_ACRF, obtainMessage(EVENT_LOAD_ACRF_DONE))) {
+                        cleanUp();
+                    }
+                } else {
+                    log("select pkcs15 failed: " + ar.exception);
+                    // select PKCS15 failed, notify uiccCarrierPrivilegeRules
+                    mLoadedCallback.sendToTarget();
+                }
+                break;
 
-          case EVENT_LOAD_ACRF_DONE:
-              if (ar.exception == null && ar.result != null) {
-                  String idAccf = parseAcrf((String)ar.result);
-                  if (!mFh.loadFile(idAccf, obtainMessage(EVENT_LOAD_ACCF_DONE))) {
-                      cleanUp();
-                  }
-              } else {
-                  cleanUp();
-              }
-              break;
+            case EVENT_LOAD_ACRF_DONE:
+                if (ar.exception == null && ar.result != null) {
+                    mRules = new ArrayList<String>();
+                    String idAccf = parseAcrf((String) ar.result);
+                    if (!mFh.loadFile(idAccf, obtainMessage(EVENT_LOAD_ACCF_DONE))) {
+                        cleanUp();
+                    }
+                } else {
+                    cleanUp();
+                }
+                break;
 
-          case EVENT_LOAD_ACCF_DONE:
-              if (ar.exception == null && ar.result != null) {
-                  parseAccf((String)ar.result);
-              }
-              // We are done here, no more file to read
-              cleanUp();
-              break;
+            case EVENT_LOAD_ACCF_DONE:
+                if (ar.exception == null && ar.result != null) {
+                    parseAccf((String) ar.result);
+                }
+                // We are done here, no more file to read
+                cleanUp();
+                break;
 
-          case EVENT_CLOSE_LOGICAL_CHANNEL_DONE:
-              break;
+            case EVENT_CLOSE_LOGICAL_CHANNEL_DONE:
+                break;
 
-          default:
-              Rlog.e(LOG_TAG, "Unknown event " + msg.what);
+            default:
+                Rlog.e(LOG_TAG, "Unknown event " + msg.what);
         }
     }
 
diff --git a/src/java/com/android/internal/telephony/uicc/UiccProfile.java b/src/java/com/android/internal/telephony/uicc/UiccProfile.java
index 2910ff7..7215f51 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccProfile.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccProfile.java
@@ -64,6 +64,7 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
@@ -204,6 +205,7 @@
 
                 case EVENT_CARRIER_CONFIG_CHANGED:
                     handleCarrierNameOverride();
+                    handleSimCountryIsoOverride();
                     break;
 
                 case EVENT_OPEN_LOGICAL_CHANNEL_DONE:
@@ -241,6 +243,7 @@
         }
 
         if (mUiccCard instanceof EuiccCard) {
+            // for RadioConfig<1.1 eid is not known when the EuiccCard is constructed
             ((EuiccCard) mUiccCard).registerForEidReady(mHandler, EVENT_EID_READY, null);
         }
 
@@ -304,9 +307,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;
@@ -353,6 +354,41 @@
         updateCarrierNameForSubscription(subCon, subId);
     }
 
+    /**
+     * Override sim country iso based on carrier config.
+     * Telephony country iso is based on MCC table which is coarse and doesn't work with dual IMSI
+     * SIM. e.g, a US carrier might have a roaming agreement with carriers from Europe. Devices
+     * will switch to different IMSI (differnt mccmnc) when enter roaming state. As a result, sim
+     * country iso (locale) will change to non-US.
+     *
+     * Each sim carrier should have a single country code. We should improve the accuracy of
+     * SIM country code look-up by using carrierid-to-countrycode table as an override on top of
+     * MCC table
+     */
+    private void handleSimCountryIsoOverride() {
+        SubscriptionController subCon = SubscriptionController.getInstance();
+        final int subId = subCon.getSubIdUsingPhoneId(mPhoneId);
+        if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+            loge("subId not valid for Phone " + mPhoneId);
+            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);
+        String iso = config.getString(CarrierConfigManager.KEY_SIM_COUNTRY_ISO_OVERRIDE_STRING);
+        if (!TextUtils.isEmpty(iso) &&
+                !iso.equals(mTelephonyManager.getSimCountryIsoForPhone(mPhoneId))) {
+            mTelephonyManager.setSimCountryIsoForPhone(mPhoneId, iso);
+            SubscriptionController.getInstance().setCountryIso(iso, subId);
+        }
+    }
+
     private void updateCarrierNameForSubscription(SubscriptionController subCon, int subId) {
         /* update display name with carrier override */
         SubscriptionInfo subInfo = subCon.getActiveSubscriptionInfo(
@@ -424,6 +460,7 @@
         }
 
         if (mUiccCard instanceof EuiccCard && ((EuiccCard) mUiccCard).getEid() == null) {
+            // for RadioConfig<1.1 the EID is not known when the EuiccCard is constructed
             if (DBG) log("EID is not ready yet.");
             return;
         }
@@ -595,7 +632,7 @@
                         String countryCode = operator.substring(0, 3);
                         if (countryCode != null) {
                             mTelephonyManager.setSimCountryIsoForPhone(mPhoneId,
-                                    MccTable.countryCodeForMcc(Integer.parseInt(countryCode)));
+                                    MccTable.countryCodeForMcc(countryCode));
                         } else {
                             loge("setExternalState: state LOADED; Country code is null");
                         }
@@ -605,9 +642,8 @@
                 }
             }
             log("setExternalState: set mPhoneId=" + mPhoneId + " mExternalState=" + mExternalState);
-            mTelephonyManager.setSimStateForPhone(mPhoneId, getState().toString());
 
-            UiccController.updateInternalIccState(getIccStateIntentString(mExternalState),
+            UiccController.updateInternalIccState(mContext, mExternalState,
                     getIccStateReason(mExternalState), mPhoneId);
         }
     }
@@ -629,22 +665,6 @@
         }
     }
 
-    private String getIccStateIntentString(IccCardConstants.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
@@ -785,6 +805,13 @@
     }
 
     @Override
+    public boolean getIccFdnAvailable() {
+        synchronized (mLock) {
+            return mUiccApplication != null && mUiccApplication.getIccFdnAvailable();
+        }
+    }
+
+    @Override
     public boolean getIccPin2Blocked() {
         /* defaults to disabled */
         return mUiccApplication != null && mUiccApplication.getIccPin2Blocked();
@@ -797,6 +824,16 @@
     }
 
     @Override
+    public boolean isEmptyProfile() {
+        // If there's no UiccCardApplication, it's an empty profile.
+        // Empty profile is a valid case of eSIM (default boot profile).
+        for (UiccCardApplication app : mUiccApplications) {
+            if (app != null) return false;
+        }
+        return true;
+    }
+
+    @Override
     public void setIccLockEnabled(boolean enabled, String password, Message onComplete) {
         synchronized (mLock) {
             if (mUiccApplication != null) {
@@ -966,15 +1003,18 @@
      * 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.isCdmaSupported(mContext)
+                && (app.getType() == AppType.APPTYPE_CSIM
+                || app.getType() == AppType.APPTYPE_RUIM))) {
+            return true;
         }
-        return true;
+        return false;
     }
 
     private void checkAndUpdateIfAnyAppToBeIgnored() {
@@ -1118,6 +1158,7 @@
 
     private void promptInstallCarrierApp(String pkgName) {
         Intent showDialogIntent = InstallCarrierAppTrampolineActivity.get(mContext, pkgName);
+        showDialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         mContext.startActivity(showDialogIntent);
     }
 
@@ -1299,11 +1340,10 @@
      * 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 disposeCatService flag indicating if CatService should be disposed as part of
-     *                          this reset
+     * @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, boolean disposeCatService) {
+    public boolean resetAppWithAid(String aid, boolean reset) {
         synchronized (mLock) {
             boolean changed = false;
             for (int i = 0; i < mUiccApplications.length; i++) {
@@ -1315,12 +1355,13 @@
                     changed = true;
                 }
             }
-            if (TextUtils.isEmpty(aid)) {
+            if (reset && TextUtils.isEmpty(aid)) {
                 if (mCarrierPrivilegeRules != null) {
                     mCarrierPrivilegeRules = null;
                     changed = true;
                 }
-                if (disposeCatService && mCatService != null) {
+                // CatService shall be disposed only when a card level reset happens.
+                if (mCatService != null) {
                     mCatService.dispose();
                     mCatService = null;
                     changed = true;
@@ -1472,6 +1513,24 @@
     }
 
     /**
+     * Return a list of certs in hex string from loaded carrier privileges access rules.
+     *
+     * @return a list of certificate in hex string. return {@code null} if there is no certs
+     * or privilege rules are not loaded yet.
+     */
+    public List<String> getCertsFromCarrierPrivilegeAccessRules() {
+        final List<String> certs = new ArrayList<>();
+        final UiccCarrierPrivilegeRules carrierPrivilegeRules = getCarrierPrivilegeRules();
+        if (carrierPrivilegeRules != null) {
+            List<UiccAccessRule> accessRules = carrierPrivilegeRules.getAccessRules();
+            for (UiccAccessRule accessRule : accessRules) {
+                certs.add(accessRule.getCertificateHexString());
+            }
+        }
+        return certs.isEmpty() ? null : certs;
+    }
+
+    /**
      * Exposes {@link UiccCarrierPrivilegeRules#getCarrierPackageNamesForIntent}.
      */
     public List<String> getCarrierPackageNamesForIntent(
diff --git a/src/java/com/android/internal/telephony/uicc/UiccSlot.java b/src/java/com/android/internal/telephony/uicc/UiccSlot.java
index 25ef637..fcc60a1 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccSlot.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccSlot.java
@@ -27,11 +27,12 @@
 import android.os.Message;
 import android.os.PowerManager;
 import android.telephony.Rlog;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
 import android.view.WindowManager;
 
 import com.android.internal.R;
 import com.android.internal.telephony.CommandsInterface;
-import com.android.internal.telephony.CommandsInterface.RadioState;
 import com.android.internal.telephony.IccCardConstants;
 import com.android.internal.telephony.uicc.IccCardStatus.CardState;
 import com.android.internal.telephony.uicc.euicc.EuiccCard;
@@ -57,7 +58,7 @@
     private Context mContext;
     private CommandsInterface mCi;
     private UiccCard mUiccCard;
-    private RadioState mLastRadioState = RadioState.RADIO_UNAVAILABLE;
+    private int mLastRadioState = TelephonyManager.RADIO_POWER_UNAVAILABLE;
     private boolean mIsEuicc;
     private String mIccId;
     private AnswerToReset mAtr;
@@ -86,7 +87,7 @@
             parseAtr(ics.atr);
             mCi = ci;
 
-            RadioState radioState = mCi.getRadioState();
+            int radioState = mCi.getRadioState();
             if (DBG) {
                 log("update: radioState=" + radioState + " mLastRadioState=" + mLastRadioState);
             }
@@ -100,7 +101,8 @@
             } 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 (radioState == TelephonyManager.RADIO_POWER_ON
+                        && mLastRadioState == TelephonyManager.RADIO_POWER_ON) {
                     if (DBG) log("update: notify card added");
                     sendMessage(obtainMessage(EVENT_CARD_ADDED, null));
                 }
@@ -114,6 +116,11 @@
                 if (!mIsEuicc) {
                     mUiccCard = new UiccCard(mContext, mCi, ics, mPhoneId, mLock);
                 } else {
+                    // The EID should be reported with the card status, but in case it's not we want
+                    // to catch that here
+                    if (TextUtils.isEmpty(ics.eid)) {
+                        loge("update: eid is missing. ics.eid=" + ics.eid);
+                    }
                     mUiccCard = new EuiccCard(mContext, mCi, ics, phoneId, mLock);
                 }
             } else {
@@ -141,7 +148,7 @@
                 // even if it's inactive.
                 if (mActive) {
                     mActive = false;
-                    mLastRadioState = RadioState.RADIO_UNAVAILABLE;
+                    mLastRadioState = TelephonyManager.RADIO_POWER_UNAVAILABLE;
                     mPhoneId = INVALID_PHONE_ID;
                     if (mUiccCard != null) mUiccCard.dispose();
                     nullifyUiccCard(true /* sim state is unknown */);
@@ -165,16 +172,17 @@
     }
 
     private void updateCardStateAbsent() {
-        RadioState radioState =
-                (mCi == null) ? RadioState.RADIO_UNAVAILABLE : mCi.getRadioState();
+        int radioState =
+                (mCi == null) ? TelephonyManager.RADIO_POWER_UNAVAILABLE : mCi.getRadioState();
         // No notifications while radio is off or we just powering up
-        if (radioState == RadioState.RADIO_ON && mLastRadioState == RadioState.RADIO_ON) {
+        if (radioState == TelephonyManager.RADIO_POWER_ON
+                && mLastRadioState == TelephonyManager.RADIO_POWER_ON) {
             if (DBG) log("update: notify card removed");
             sendMessage(obtainMessage(EVENT_CARD_REMOVED, null));
         }
 
         UiccController.updateInternalIccState(
-                IccCardConstants.INTENT_VALUE_ICC_ABSENT, null, mPhoneId);
+                mContext, IccCardConstants.State.ABSENT, null, mPhoneId);
 
         // no card present in the slot now; dispose card and make mUiccCard null
         if (mUiccCard != null) {
@@ -206,9 +214,6 @@
 
     private void parseAtr(String atr) {
         mAtr = AnswerToReset.parseAtr(atr);
-        if (mAtr == null) {
-            return;
-        }
         checkIsEuiccSupported();
     }
 
@@ -365,11 +370,11 @@
 
         if (mPhoneId != INVALID_PHONE_ID) {
             UiccController.updateInternalIccState(
-                    IccCardConstants.INTENT_VALUE_ICC_UNKNOWN, null, mPhoneId);
+                    mContext, IccCardConstants.State.UNKNOWN, null, mPhoneId);
         }
 
         mCardState = CardState.CARDSTATE_ABSENT;
-        mLastRadioState = RadioState.RADIO_UNAVAILABLE;
+        mLastRadioState = TelephonyManager.RADIO_POWER_UNAVAILABLE;
     }
 
     private void log(String msg) {
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 b8b2818..094a6f3 100644
--- a/src/java/com/android/internal/telephony/uicc/euicc/EuiccCard.java
+++ b/src/java/com/android/internal/telephony/uicc/euicc/EuiccCard.java
@@ -83,7 +83,8 @@
     // Error code for no result available when retrieving notifications.
     private static final int CODE_NO_RESULT_AVAILABLE = 1;
 
-    private static final EuiccSpecVersion SGP_2_0 = new EuiccSpecVersion(2, 0, 0);
+    private static final EuiccSpecVersion SGP22_V_2_0 = new EuiccSpecVersion(2, 0, 0);
+    private static final EuiccSpecVersion SGP22_V_2_1 = new EuiccSpecVersion(2, 1, 0);
 
     // Device capabilities.
     private static final String DEV_CAP_GSM = "gsm";
@@ -111,7 +112,6 @@
     }
 
     private final ApduSender mApduSender;
-    private final Object mLock = new Object();
     private RegistrantList mEidReadyRegistrants;
     private EuiccSpecVersion mSpecVersion;
     private volatile String mEid;
@@ -121,7 +121,13 @@
         // TODO: Set supportExtendedApdu based on ATR.
         mApduSender = new ApduSender(ci, ISD_R_AID, false /* supportExtendedApdu */);
 
-        loadEidAndNotifyRegistrants();
+        if (TextUtils.isEmpty(ics.eid)) {
+            loge("no eid given in constructor for phone " + phoneId);
+            loadEidAndNotifyRegistrants();
+        } else {
+            mEid = ics.eid;
+            mCardId = ics.eid;
+        }
     }
 
     /**
@@ -149,6 +155,8 @@
         }
     }
 
+    // For RadioConfig<1.1 we don't know the EID when constructing the EuiccCard, so callers may
+    // need to register to be notified when we have the EID
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     protected void loadEidAndNotifyRegistrants() {
         Handler euiccMainThreadHandler = new Handler();
@@ -189,6 +197,16 @@
     }
 
     @Override
+    public void update(Context c, CommandsInterface ci, IccCardStatus ics) {
+        synchronized (mLock) {
+            if (!TextUtils.isEmpty(ics.eid)) {
+                mEid = ics.eid;
+            }
+            super.update(c, ci, ics);
+        }
+    }
+
+    @Override
     protected void updateCardId() {
         if (TextUtils.isEmpty(mEid)) {
             super.updateCardId();
@@ -660,9 +678,15 @@
                             .addChild(ctxParams1Builder)
                             .build().toHex());
                 }),
-                (byte[] response) ->
-                        parseResponseAndCheckSimpleError(response,
-                                EuiccCardErrorException.OPERATION_AUTHENTICATE_SERVER).toBytes(),
+                (byte[] response) -> {
+                    Asn1Node root = parseResponse(response);
+                    if (root.hasChild(Tags.TAG_CTX_COMP_1, Tags.TAG_UNI_2)) {
+                        throw new EuiccCardErrorException(
+                                EuiccCardErrorException.OPERATION_AUTHENTICATE_SERVER,
+                                root.getChild(Tags.TAG_CTX_COMP_1, Tags.TAG_UNI_2).asInteger());
+                    }
+                    return root.toBytes();
+                },
                 callback, handler);
     }
 
@@ -976,10 +1000,69 @@
 
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     protected byte[] getDeviceId() {
-        byte[] imeiBytes = new byte[8];
         Phone phone = PhoneFactory.getPhone(getPhoneId());
-        if (phone != null) {
-            IccUtils.bcdToBytes(phone.getDeviceId(), imeiBytes);
+        if (phone == null) {
+            return new byte[8];
+        }
+        return getDeviceId(phone.getDeviceId(), mSpecVersion);
+    }
+
+    /**
+     * Different versions of SGP.22 specify different encodings of the device's IMEI, so we handle
+     * those differences here.
+     *
+     * @param imei The IMEI of the device. Assumed to be 15 decimal digits.
+     * @param specVersion The SGP.22 version which we're encoding the IMEI for.
+     * @return A byte string representing the given IMEI according to the specified SGP.22 version.
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    public static byte[] getDeviceId(String imei, EuiccSpecVersion specVersion) {
+        byte[] imeiBytes = new byte[8];
+        // The IMEI's encoding is version-dependent.
+        if (specVersion.compareTo(SGP22_V_2_1) >= 0) {
+            /*
+             * In SGP.22 v2.1, a clarification was added to clause 4.2 that requires the nibbles of
+             * the last byte to be swapped from normal TBCD encoding (so put back in normal order):
+             *
+             * The IMEI (including the check digit) SHALL be represented as a string of 8 octets
+             * that is coded as a Telephony Binary Coded Decimal String as defined in 3GPP TS 29.002
+             * [63], except that the last octet contains the check digit (in high nibble) and an 'F'
+             * filler (in low nibble). It SHOULD be present if the Device contains a non-removable
+             * eUICC.
+             *
+             * 3GPP TS 29.002 clause 17.7.8 in turn says this:
+             *
+             * TBCD-STRING ::= OCTET STRING
+             * This type (Telephony Binary Coded Decimal String) is used to represent several digits
+             * from 0 through 9, *, #, a, b, c, two digits per octet, each digit encoded 0000 to
+             * 1001 (0 to 9), 1010 (*), 1011 (#), 1100 (a), 1101 (b) or 1110 (c); 1111 used as
+             * filler when there is an odd number of digits.
+             * Bits 8765 of octet n encoding digit 2n
+             * Bits 4321 of octet n encoding digit 2(n-1) + 1
+             */
+            // Since the IMEI is always just decimal digits, we can still use BCD encoding (which
+            // correctly swaps digit ordering within bytes), but we have to manually pad a 0xF value
+            // instead of 0.
+            imei += 'F';
+            IccUtils.bcdToBytes(imei, imeiBytes);
+            // And now the funky last byte flip (this is not normal TBCD, the GSMA added it on top
+            // just for the IMEI for some reason). Bitwise operations promote to int first, so we
+            // have to do some extra masking.
+            byte last = imeiBytes[7];
+            imeiBytes[7] = (byte) ((last & 0xFF) << 4 | ((last & 0xFF) >>> 4));
+        } else {
+            /*
+             * Prior to SGP.22 v2.1, clause 4.2 reads as follows:
+             *
+             * The IMEI (including the check digit) SHALL be represented as a string of 8 octets
+             * that is BCD coded as defined in 3GPP TS 23.003 [35]. It SHOULD be present if the
+             * Device contains a non-removable eUICC.
+             *
+             * It appears that 3GPP TS 23.003 doesn't define anything about BCD encoding, it just
+             * defines what IMEI and a few other telephony identifiers are. We default to normal BCD
+             * encoding since the spec is unclear here.
+             */
+            IccUtils.bcdToBytes(imei, imeiBytes);
         }
         return imeiBytes;
     }
@@ -996,7 +1079,7 @@
                 throw new EuiccCardException("Cannot get eUICC spec version.");
             }
             try {
-                if (ver.compareTo(SGP_2_0) < 0) {
+                if (ver.compareTo(SGP22_V_2_0) < 0) {
                     throw new EuiccCardException("eUICC spec version is unsupported: " + ver);
                 }
                 builder.build(requestBuilder);
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/Android.mk b/tests/Android.mk
deleted file mode 100644
index 2027fef..0000000
--- a/tests/Android.mk
+++ /dev/null
@@ -1,21 +0,0 @@
-# Copyright (C) 2015 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.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-# Include subdirectory makefiles
-# ============================================================
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/telephonytests/Android.bp b/tests/telephonytests/Android.bp
new file mode 100644
index 0000000..3a8a1e6
--- /dev/null
+++ b/tests/telephonytests/Android.bp
@@ -0,0 +1,26 @@
+android_test {
+    name: "FrameworksTelephonyTests",
+
+    srcs: ["**/*.java"],
+
+    libs: [
+        "android.test.base",
+        "android.test.mock",
+        "android.test.runner",
+        "ims-common",
+    ],
+
+    static_libs: [
+        "android-support-test",
+        "frameworks-base-testutils",
+        "guava",
+        "mockito-target-minus-junit4",
+        "platform-test-annotations",
+        "services.core",
+        "telephony-common",
+        "truth-prebuilt",
+    ],
+
+    platform_apis: true,
+    test_suites: ["device-tests"],
+}
diff --git a/tests/telephonytests/Android.mk b/tests/telephonytests/Android.mk
deleted file mode 100644
index 3bcbcc3..0000000
--- a/tests/telephonytests/Android.mk
+++ /dev/null
@@ -1,30 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-
-LOCAL_JAVA_LIBRARIES := \
-    android.test.runner \
-    ims-common \
-    bouncycastle \
-    android.test.base \
-    android.test.mock
-
-LOCAL_STATIC_JAVA_LIBRARIES := guava \
-                               frameworks-base-testutils \
-                               services.core \
-                               telephony-common \
-                               mockito-target-minus-junit4 \
-                               android-support-test \
-                               platform-test-annotations
-
-LOCAL_PACKAGE_NAME := FrameworksTelephonyTests
-LOCAL_PRIVATE_PLATFORM_APIS := true
-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/ImsFeatureTest.java b/tests/telephonytests/src/android/telephony/ims/ImsFeatureTest.java
index 07d3223..b7d2998 100644
--- a/tests/telephonytests/src/android/telephony/ims/ImsFeatureTest.java
+++ b/tests/telephonytests/src/android/telephony/ims/ImsFeatureTest.java
@@ -23,7 +23,9 @@
 import static org.mockito.Mockito.verify;
 
 import android.os.Parcel;
+import android.os.RemoteException;
 import android.support.test.runner.AndroidJUnit4;
+import android.telephony.ims.aidl.IImsCapabilityCallback;
 import android.telephony.ims.feature.CapabilityChangeRequest;
 import android.telephony.ims.feature.ImsFeature;
 import android.telephony.ims.feature.MmTelFeature;
@@ -42,9 +44,28 @@
 
 @RunWith(AndroidJUnit4.class)
 public class ImsFeatureTest {
+    // Public for Mockito testing
+    public class CapabilityCallback extends IImsCapabilityCallback.Stub {
+
+        @Override
+        public void onQueryCapabilityConfiguration(int capability, int radioTech, boolean enabled)
+                throws RemoteException {
+
+        }
+
+        @Override
+        public void onChangeCapabilityConfigurationError(int capability, int radioTech, int reason)
+                throws RemoteException {
+
+        }
+
+        @Override
+        public void onCapabilitiesStatusChanged(int config) throws RemoteException {
+
+        }
+    }
 
     private TestImsFeature mTestImsFeature;
-    private ImsFeature.CapabilityCallback mCapabilityCallback;
 
     @Mock
     private IImsFeatureStatusCallback mTestStatusCallback;
@@ -55,14 +76,11 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mTestImsFeature = new TestImsFeature();
-        mCapabilityCallback = Mockito.spy(new ImsFeature.CapabilityCallback());
-        mTestImsFeature.addCapabilityCallback(mCapabilityCallback);
     }
 
     @After
     public void tearDown() {
         mTestImsFeature = null;
-        mCapabilityCallback = null;
     }
 
     @Test
@@ -145,14 +163,17 @@
     @SmallTest
     @Test
     public void testSetCapabilityConfigError() throws Exception {
+        CapabilityCallback capabilityCallback = Mockito.spy(new CapabilityCallback());
+        mTestImsFeature.addCapabilityCallback(capabilityCallback);
+
         CapabilityChangeRequest request = new CapabilityChangeRequest();
         request.addCapabilitiesToEnableForTech(TestImsFeature.CAPABILITY_TEST_1,
                 ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN);
 
         mTestImsFeature.setCapabilitiesResult = ImsFeature.CAPABILITY_ERROR_GENERIC;
-        mTestImsFeature.requestChangeEnabledCapabilities(request, mCapabilityCallback);
+        mTestImsFeature.requestChangeEnabledCapabilities(request, capabilityCallback);
 
-        verify(mCapabilityCallback).onChangeCapabilityConfigurationError(
+        verify(capabilityCallback).onChangeCapabilityConfigurationError(
                 eq(TestImsFeature.CAPABILITY_TEST_1),
                 eq(ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN),
                 eq(ImsFeature.CAPABILITY_ERROR_GENERIC));
@@ -175,6 +196,9 @@
     @SmallTest
     @Test
     public void testNotifyCapabilityStatusChangedCallback() throws Exception {
+        CapabilityCallback capabilityCallback = Mockito.spy(new CapabilityCallback());
+        mTestImsFeature.addCapabilityCallback(capabilityCallback);
+
         ImsFeature.Capabilities status =
                 new ImsFeature.Capabilities();
         status.addCapabilities(TestImsFeature.CAPABILITY_TEST_1);
@@ -183,7 +207,8 @@
         mTestImsFeature.capabilitiesStatusChanged(status);
 
         assertEquals(status.getMask(), mTestImsFeature.queryCapabilityStatus().getMask());
-        verify(mCapabilityCallback).onCapabilitiesStatusChanged(eq(status));
+        verify(capabilityCallback).onCapabilitiesStatusChanged(
+                eq(TestImsFeature.CAPABILITY_TEST_1 | TestImsFeature.CAPABILITY_TEST_2));
     }
 
     @SmallTest
@@ -231,4 +256,23 @@
 
         assertEquals(request, result);
     }
+
+    @SmallTest
+    @Test
+    public void testCapabilityCallbackWhenRegistering() throws Exception {
+        CapabilityCallback capabilityCallback = Mockito.spy(new CapabilityCallback());
+
+        // Signal the status has changed
+        ImsFeature.Capabilities status =
+                new ImsFeature.Capabilities();
+        status.addCapabilities(TestImsFeature.CAPABILITY_TEST_1);
+        status.addCapabilities(TestImsFeature.CAPABILITY_TEST_2);
+        mTestImsFeature.capabilitiesStatusChanged(status);
+
+        // addCapabilityCallback should cause capabilityCallback to call back with status.
+        mTestImsFeature.addCapabilityCallback(capabilityCallback);
+        assertEquals(status.getMask(), mTestImsFeature.queryCapabilityStatus().getMask());
+        verify(capabilityCallback).onCapabilitiesStatusChanged(
+                eq(TestImsFeature.CAPABILITY_TEST_1 | TestImsFeature.CAPABILITY_TEST_2));
+    }
 }
diff --git a/tests/telephonytests/src/android/telephony/ims/ImsMmTelManagerTests.java b/tests/telephonytests/src/android/telephony/ims/ImsMmTelManagerTests.java
new file mode 100644
index 0000000..404e584
--- /dev/null
+++ b/tests/telephonytests/src/android/telephony/ims/ImsMmTelManagerTests.java
@@ -0,0 +1,97 @@
+/*
+ * 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.ims;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.verify;
+
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.ims.aidl.IImsRegistrationCallback;
+import android.telephony.ims.stub.ImsRegistrationImplBase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.internal.telephony.ITelephony;
+import com.android.internal.telephony.TelephonyTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+
+public class ImsMmTelManagerTests extends TelephonyTest {
+
+    @Mock
+    IBinder mMockBinder;
+    @Mock
+    ITelephony mMockTelephonyInterface;
+
+    public class LocalCallback extends ImsMmTelManager.RegistrationCallback {
+        int mRegResult = -1;
+
+        @Override
+        public void onRegistered(int imsRadioTech) {
+            mRegResult = imsRadioTech;
+        }
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp("ImsMmTelManagerTests");
+        doReturn(mMockTelephonyInterface).when(mMockBinder).queryLocalInterface(anyString());
+        mServiceManagerMockedServices.put("phone", mMockBinder);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    /**
+     * Ensure that LTE-> WWAN and IWLAN-> WLAN map correctly as well as ensure that wacky values
+     * result in a -1 result.
+     */
+    @SmallTest
+    @Test
+    public void testCallbackValues() throws RemoteException {
+        LocalCallback cb = new LocalCallback();
+        ImsMmTelManager managerUT = new ImsMmTelManager(0);
+        managerUT.registerImsRegistrationCallback(Runnable::run, cb);
+        // Capture the RegistrationCallback that was registered.
+        ArgumentCaptor<IImsRegistrationCallback> callbackCaptor =
+                ArgumentCaptor.forClass(IImsRegistrationCallback.class);
+        verify(mMockTelephonyInterface).registerImsRegistrationCallback(anyInt(),
+                callbackCaptor.capture());
+
+        IImsRegistrationCallback cbBinder = callbackCaptor.getValue();
+        // Ensure the transport types are correct
+        cbBinder.onRegistered(ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
+        assertEquals(AccessNetworkConstants.TransportType.WWAN, cb.mRegResult);
+        cbBinder.onRegistered(ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN);
+        assertEquals(AccessNetworkConstants.TransportType.WLAN, cb.mRegResult);
+        cbBinder.onRegistered(ImsRegistrationImplBase.REGISTRATION_TECH_NONE);
+        assertEquals(-1, cb.mRegResult);
+        // Wacky value
+        cbBinder.onRegistered(0xDEADBEEF);
+        assertEquals(-1, cb.mRegResult);
+    }
+}
diff --git a/tests/telephonytests/src/android/telephony/ims/MmTelFeatureTests.java b/tests/telephonytests/src/android/telephony/ims/MmTelFeatureTests.java
index 6be2fb0..79b9d60 100644
--- a/tests/telephonytests/src/android/telephony/ims/MmTelFeatureTests.java
+++ b/tests/telephonytests/src/android/telephony/ims/MmTelFeatureTests.java
@@ -30,8 +30,8 @@
 import android.os.Messenger;
 import android.support.test.runner.AndroidJUnit4;
 import android.telecom.TelecomManager;
+import android.telephony.ims.aidl.IImsCapabilityCallback;
 import android.telephony.ims.aidl.IImsMmTelFeature;
-import android.telephony.ims.feature.ImsFeature;
 import android.telephony.ims.feature.MmTelFeature;
 import android.telephony.ims.stub.ImsCallSessionImplBase;
 import android.test.suitebuilder.annotation.SmallTest;
@@ -49,6 +49,26 @@
 @RunWith(AndroidJUnit4.class)
 public class MmTelFeatureTests extends ImsTestBase {
 
+    // Public for Mockito testing
+    public class CapabilityCallback extends IImsCapabilityCallback.Stub {
+
+        @Override
+        public void onQueryCapabilityConfiguration(int capability, int radioTech, boolean enabled) {
+
+        }
+
+        @Override
+        public void onChangeCapabilityConfigurationError(int capability, int radioTech,
+                int reason) {
+
+        }
+
+        @Override
+        public void onCapabilitiesStatusChanged(int config) {
+
+        }
+    }
+
     private static final int TEST_CAPABILITY = 1;
     private static final int TEST_RADIO_TECH = 0;
 
@@ -60,7 +80,7 @@
 
     private android.telephony.ims.TestMmTelFeature mFeature;
     private IImsMmTelFeature mFeatureBinder;
-    private ImsFeature.CapabilityCallback mCapabilityCallback;
+    private CapabilityCallback mCapabilityCallback;
     private MmTelFeature.Listener mListener;
 
     // set to true when the handler receives a message back from the Feature.
@@ -92,7 +112,7 @@
         super.setUp();
         mFeature = new TestMmTelFeature();
         mFeatureBinder = mFeature.getBinder();
-        mCapabilityCallback = spy(new ImsFeature.CapabilityCallback());
+        mCapabilityCallback = spy(new CapabilityCallback());
         mListener = spy(new MmTelFeature.Listener());
         mFeatureBinder.setListener(mListener);
         mHandlerResults = new boolean[TEST_RESULT_MAX];
diff --git a/tests/telephonytests/src/android/telephony/ims/TestMmTelFeature.java b/tests/telephonytests/src/android/telephony/ims/TestMmTelFeature.java
index 6414403..fc1d670 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.Bundle;
 import android.os.Message;
 import android.os.RemoteException;
 import android.telephony.ims.feature.CapabilityChangeRequest;
@@ -50,8 +51,8 @@
         }
     }
 
-    public void incomingCall(ImsCallSessionImplBase c) throws RemoteException {
-        notifyIncomingCall(c, null);
+    public void incomingCall(ImsCallSessionImplBase c) {
+        notifyIncomingCall(c, new Bundle());
     }
 
     @Override
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CallManagerTest.java b/tests/telephonytests/src/com/android/internal/telephony/CallManagerTest.java
index bda6d95..c05d4be 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CallManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CallManagerTest.java
@@ -15,6 +15,22 @@
  */
 package com.android.internal.telephony;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.isA;
+import static org.mockito.Matchers.isNull;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyChar;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Message;
@@ -27,26 +43,9 @@
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
-import org.mockito.Mockito;
 
 import java.lang.reflect.Field;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.anyBoolean;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Matchers.isA;
-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 static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyChar;
-import static org.mockito.Mockito.anyString;
-
 public class CallManagerTest extends TelephonyTest {
 
     @Mock
@@ -153,12 +152,6 @@
     }
 
     @SmallTest @Test
-    public void testBasicAcceptCall() throws Exception {
-        CallManager.getInstance().acceptCall(mRingingCall);
-        verify(mPhone, times(1)).acceptCall(anyInt());
-    }
-
-    @SmallTest @Test
     public void testBasicRejectCall() throws Exception {
         //verify can dial and dial function of the phone is being triggered
         CallManager.getInstance().rejectCall(mRingingCall);
@@ -230,51 +223,6 @@
     }
 
     @SmallTest @Test
-    public void testSwitchHoldingAndActive() throws Exception {
-        /* case 1: only active call */
-        doReturn(false).when(mFgCall).isIdle();
-        CallManager.getInstance().switchHoldingAndActive(null);
-        verify(mPhone, times(1)).switchHoldingAndActive();
-        /* case 2: no active call but only held call, aka, unhold */
-        doReturn(true).when(mFgCall).isIdle();
-        CallManager.getInstance().switchHoldingAndActive(mBgCall);
-        verify(mPhone, times(2)).switchHoldingAndActive();
-        /* case 3: both active and held calls from same phone, aka, swap */
-        doReturn(false).when(mFgCall).isIdle();
-        CallManager.getInstance().switchHoldingAndActive(mBgCall);
-        verify(mPhone, times(3)).switchHoldingAndActive();
-        GsmCdmaPhone mPhoneHold = Mockito.mock(GsmCdmaPhone.class);
-        /* case 4: active and held calls from different phones, aka, phone swap */
-        doReturn(mPhoneHold).when(mBgCall).getPhone();
-        CallManager.getInstance().switchHoldingAndActive(mBgCall);
-        verify(mPhone, times(4)).switchHoldingAndActive();
-        verify(mPhoneHold, times(1)).switchHoldingAndActive();
-    }
-
-    @SmallTest @Test
-    public void testHangupForegroundResumeBackground() throws Exception {
-        CallManager.getInstance().hangupForegroundResumeBackground(mBgCall);
-        /* no active fgCall */
-        verify(mPhone, times(0)).switchHoldingAndActive();
-        verify(mFgCall, times(0)).hangup();
-
-        /* have active foreground call, get hanged up */
-        doReturn(false).when(mFgCall).isIdle();
-        CallManager.getInstance().hangupForegroundResumeBackground(mBgCall);
-        verify(mFgCall, times(1)).hangup();
-        verify(mPhone, times(0)).switchHoldingAndActive();
-
-        /* mock bgcall and fgcall from different phone */
-        GsmCdmaPhone mPhoneHold = Mockito.mock(GsmCdmaPhone.class);
-        doReturn(mPhoneHold).when(mBgCall).getPhone();
-        CallManager.getInstance().hangupForegroundResumeBackground(mBgCall);
-        verify(mFgCall, times(2)).hangup();
-        /* always hangup fgcall and both phone trigger swap */
-        verify(mPhoneHold, times(1)).switchHoldingAndActive();
-        verify(mPhone, times(1)).switchHoldingAndActive();
-    }
-
-    @SmallTest @Test
     public void testFgCallActiveDial() throws Exception {
         /* set Fg/Bg Call state to active, verify CallManager Logical */
         doReturn(false).when(mFgCall).isIdle();
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CarrierIdentifierTest.java b/tests/telephonytests/src/com/android/internal/telephony/CarrierIdentifierTest.java
deleted file mode 100644
index 6ce6d20..0000000
--- a/tests/telephonytests/src/com/android/internal/telephony/CarrierIdentifierTest.java
+++ /dev/null
@@ -1,289 +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 static com.android.internal.telephony.TelephonyTestUtils.waitForMs;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.doReturn;
-
-import android.database.Cursor;
-import android.database.MatrixCursor;
-import android.net.Uri;
-import android.os.HandlerThread;
-import android.provider.Telephony.CarrierId;
-import android.provider.Telephony.Carriers;
-import android.test.mock.MockContentProvider;
-import android.test.mock.MockContentResolver;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-import java.util.Arrays;
-
-public class CarrierIdentifierTest extends TelephonyTest {
-    private static final String MCCMNC = "311480";
-    private static final String NAME = "VZW";
-    private static final int CID_VZW = 1;
-
-    private static final String SPN_FI = "PROJECT FI";
-    private static final String NAME_FI = "FI";
-    private static final int CID_FI = 2;
-
-    private static final String NAME_DOCOMO = "DOCOMO";
-    private static final String APN_DOCOMO = "mopera.net";
-    private static final int CID_DOCOMO = 3;
-
-    private static final String NAME_TMO = "TMO";
-    private static final String GID1 = "ae";
-    private static final int CID_TMO = 4;
-
-    private static final int CID_UNKNOWN = -1;
-
-    // events to trigger carrier identification
-    private static final int SIM_LOAD_EVENT       = 1;
-    private static final int SIM_ABSENT_EVENT     = 2;
-    private static final int SPN_OVERRIDE_EVENT   = 3;
-    private static final int PREFER_APN_SET_EVENT = 5;
-
-    private CarrierIdentifier mCarrierIdentifier;
-    private CarrierIdentifierHandler mCarrierIdentifierHandler;
-
-    private class CarrierIdentifierHandler extends HandlerThread {
-        private CarrierIdentifierHandler(String name) {
-            super(name);
-        }
-
-        @Override
-        public void onLooperPrepared() {
-            mCarrierIdentifier = new CarrierIdentifier(mPhone);
-            setReady(true);
-        }
-    }
-
-    @Before
-    public void setUp() throws Exception {
-        logd("CarrierIdentifierTest +Setup!");
-        super.setUp(getClass().getSimpleName());
-        ((MockContentResolver) mContext.getContentResolver()).addProvider(
-                CarrierId.AUTHORITY, new CarrierIdContentProvider());
-        // start handler thread
-        mCarrierIdentifierHandler = new CarrierIdentifierHandler(getClass().getSimpleName());
-        mCarrierIdentifierHandler.start();
-        waitUntilReady();
-        logd("CarrierIdentifierTest -Setup!");
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        logd("CarrierIdentifier -tearDown");
-        mCarrierIdentifier.removeCallbacksAndMessages(null);
-        mCarrierIdentifier = null;
-        mCarrierIdentifierHandler.quit();
-        super.tearDown();
-    }
-
-    @Test
-    @SmallTest
-    public void testCarrierMatch() {
-        int phoneId = mPhone.getPhoneId();
-        doReturn(MCCMNC).when(mTelephonyManager).getSimOperatorNumericForPhone(eq(phoneId));
-        // trigger sim loading event
-        mCarrierIdentifier.sendEmptyMessage(SIM_LOAD_EVENT);
-        waitForMs(200);
-        assertEquals(CID_VZW, mCarrierIdentifier.getCarrierId());
-        assertEquals(NAME, mCarrierIdentifier.getCarrierName());
-
-        doReturn(SPN_FI).when(mTelephonyManager).getSimOperatorNameForPhone(eq(phoneId));
-        mCarrierIdentifier.sendEmptyMessage(SIM_LOAD_EVENT);
-        waitForMs(200);
-        assertEquals(CID_FI, mCarrierIdentifier.getCarrierId());
-        assertEquals(NAME_FI, mCarrierIdentifier.getCarrierName());
-
-        doReturn(GID1).when(mPhone).getGroupIdLevel1();
-        mCarrierIdentifier.sendEmptyMessage(SIM_LOAD_EVENT);
-        waitForMs(200);
-        assertEquals(CID_TMO, mCarrierIdentifier.getCarrierId());
-        assertEquals(NAME_TMO, mCarrierIdentifier.getCarrierName());
-    }
-
-    @Test
-    @SmallTest
-    public void testCarrierMatchSpnOverride() {
-        int phoneId = mPhone.getPhoneId();
-        doReturn(MCCMNC).when(mTelephonyManager).getSimOperatorNumericForPhone(eq(phoneId));
-        // trigger sim loading event
-        mCarrierIdentifier.sendEmptyMessage(SIM_LOAD_EVENT);
-        waitForMs(200);
-        assertEquals(CID_VZW, mCarrierIdentifier.getCarrierId());
-        assertEquals(NAME, mCarrierIdentifier.getCarrierName());
-        // spn override
-        doReturn(SPN_FI).when(mTelephonyManager).getSimOperatorNameForPhone(eq(phoneId));
-        mCarrierIdentifier.sendEmptyMessage(SPN_OVERRIDE_EVENT);
-        waitForMs(200);
-        assertEquals(CID_FI, mCarrierIdentifier.getCarrierId());
-        assertEquals(NAME_FI, mCarrierIdentifier.getCarrierName());
-    }
-
-    @Test
-    @SmallTest
-    public void testCarrierMatchSimAbsent() {
-        int phoneId = mPhone.getPhoneId();
-        doReturn(MCCMNC).when(mTelephonyManager).getSimOperatorNumericForPhone(eq(phoneId));
-        // trigger sim loading event
-        mCarrierIdentifier.sendEmptyMessage(SIM_LOAD_EVENT);
-        waitForMs(200);
-        assertEquals(CID_VZW, mCarrierIdentifier.getCarrierId());
-        assertEquals(NAME, mCarrierIdentifier.getCarrierName());
-        // trigger sim absent event
-        mCarrierIdentifier.sendEmptyMessage(SIM_ABSENT_EVENT);
-        waitForMs(200);
-        assertEquals(CID_UNKNOWN, mCarrierIdentifier.getCarrierId());
-        assertNull(mCarrierIdentifier.getCarrierName());
-    }
-
-    @Test
-    @SmallTest
-    public void testCarrierNoMatch() {
-        // un-configured MCCMNC
-        int phoneId = mPhone.getPhoneId();
-        doReturn("12345").when(mTelephonyManager).getSimOperatorNumericForPhone(eq(phoneId));
-        // trigger sim loading event
-        mCarrierIdentifier.sendEmptyMessage(SIM_LOAD_EVENT);
-        waitForMs(200);
-        assertEquals(CID_UNKNOWN, mCarrierIdentifier.getCarrierId());
-        assertNull(mCarrierIdentifier.getCarrierName());
-    }
-
-    @Test
-    @SmallTest
-    public void testCarrierMatchPreferApnChange() {
-        int phoneId = mPhone.getPhoneId();
-        doReturn(MCCMNC).when(mTelephonyManager).getSimOperatorNumericForPhone(eq(phoneId));
-        // trigger sim loading event
-        mCarrierIdentifier.sendEmptyMessage(SIM_LOAD_EVENT);
-        waitForMs(200);
-        assertEquals(CID_VZW, mCarrierIdentifier.getCarrierId());
-        assertEquals(NAME, mCarrierIdentifier.getCarrierName());
-        // mock apn
-        ((MockContentResolver) mContext.getContentResolver()).addProvider(
-                Carriers.CONTENT_URI.getAuthority(), new CarrierIdContentProvider());
-        mCarrierIdentifier.sendEmptyMessage(PREFER_APN_SET_EVENT);
-        waitForMs(200);
-        assertEquals(CID_DOCOMO, mCarrierIdentifier.getCarrierId());
-        assertEquals(NAME_DOCOMO, mCarrierIdentifier.getCarrierName());
-    }
-
-    private class CarrierIdContentProvider extends MockContentProvider {
-        @Override
-        public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
-                String sortOrder) {
-            logd("CarrierIdContentProvider: query");
-            logd("   uri = " + uri);
-            logd("   projection = " + Arrays.toString(projection));
-            logd("   selection = " + selection);
-            logd("   selectionArgs = " + Arrays.toString(selectionArgs));
-            logd("   sortOrder = " + sortOrder);
-
-            if (CarrierId.All.CONTENT_URI.getAuthority().equals(
-                    uri.getAuthority())) {
-                MatrixCursor mc = new MatrixCursor(
-                        new String[]{CarrierId._ID,
-                                CarrierId.All.MCCMNC,
-                                CarrierId.All.GID1,
-                                CarrierId.All.GID2,
-                                CarrierId.All.PLMN,
-                                CarrierId.All.IMSI_PREFIX_XPATTERN,
-                                CarrierId.All.ICCID_PREFIX,
-                                CarrierId.All.SPN,
-                                CarrierId.All.APN,
-                                CarrierId.CARRIER_NAME,
-                                CarrierId.CARRIER_ID});
-
-                mc.addRow(new Object[] {
-                        1,                      // id
-                        MCCMNC,                 // mccmnc
-                        null,                   // gid1
-                        null,                   // gid2
-                        null,                   // plmn
-                        null,                   // imsi_prefix
-                        null,                   // iccid_prefix
-                        null,                   // spn
-                        null,                   // apn
-                        NAME,                   // carrier name
-                        CID_VZW,                // cid
-                });
-                mc.addRow(new Object[] {
-                        2,                      // id
-                        MCCMNC,                 // mccmnc
-                        GID1,                   // gid1
-                        null,                   // gid2
-                        null,                   // plmn
-                        null,                   // imsi_prefix
-                        null,                   // iccid_prefix
-                        null,                   // spn
-                        null,                   // apn
-                        NAME_TMO,               // carrier name
-                        CID_TMO,                // cid
-                });
-                mc.addRow(new Object[] {
-                        3,                      // id
-                        MCCMNC,                 // mccmnc
-                        null,                   // gid1
-                        null,                   // gid2
-                        null,                   // plmn
-                        null,                   // imsi_prefix
-                        null,                   // iccid_prefix
-                        SPN_FI,                 // spn
-                        null,                   // apn
-                        NAME_FI,                // carrier name
-                        CID_FI,                 // cid
-                });
-                mc.addRow(new Object[] {
-                        4,                      // id
-                        MCCMNC,                 // mccmnc
-                        null,                   // gid1
-                        null,                   // gid2
-                        null,                   // plmn
-                        null,                   // imsi_prefix
-                        null,                   // iccid_prefix
-                        null,                   // spn
-                        APN_DOCOMO,             // apn
-                        NAME_DOCOMO,            // carrier name
-                        CID_DOCOMO,             // cid
-                });
-                return mc;
-            } else if (Carriers.CONTENT_URI.getAuthority().equals(uri.getAuthority())) {
-                MatrixCursor mc = new MatrixCursor(new String[]{Carriers._ID, Carriers.APN});
-                mc.addRow(new Object[] {
-                        1,                      // id
-                        APN_DOCOMO              // apn
-                });
-                return mc;
-            }
-            return null;
-        }
-        @Override
-        public int update(android.net.Uri uri, android.content.ContentValues values,
-                java.lang.String selection, java.lang.String[] selectionArgs) {
-            return 0;
-        }
-    }
-}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CarrierResolverTest.java b/tests/telephonytests/src/com/android/internal/telephony/CarrierResolverTest.java
new file mode 100644
index 0000000..6a57469
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/CarrierResolverTest.java
@@ -0,0 +1,522 @@
+/*
+ * 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 static com.android.internal.telephony.TelephonyTestUtils.waitForMs;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doReturn;
+
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.net.Uri;
+import android.os.HandlerThread;
+import android.provider.Telephony.CarrierId;
+import android.provider.Telephony.Carriers;
+import android.service.carrier.CarrierIdentifier;
+import android.telephony.TelephonyManager;
+import android.test.mock.MockContentProvider;
+import android.test.mock.MockContentResolver;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Arrays;
+
+public class CarrierResolverTest extends TelephonyTest {
+    private static final String MCCMNC = "311480";
+    private static final String NAME = "VZW";
+    private static final int CID_VZW = 1;
+
+    private static final String MCCMNC_VODAFONE = "20205";
+    private static final String NAME_VODAFONE = "VODAFONE";
+    private static final String SPN_VODAFONE = "vodafone GR";
+    private static final int CID_VODAFONE = 5;
+
+    private static final String SPN_FI = "PROJECT FI";
+    private static final String NAME_FI = "FI";
+    private static final int CID_FI = 2;
+
+    private static final String NAME_DOCOMO = "DOCOMO";
+    private static final String APN_DOCOMO = "mopera.net";
+    private static final int CID_DOCOMO = 3;
+
+    private static final String NAME_TMO = "TMO";
+    private static final String MCCMNC_TMO = "310260";
+    private static final int CID_TMO = 4;
+
+    private static final String MCCMNC_ATT = "310410";
+    private static final int CID_ATT = 5;
+
+    private static final int CID_TRACFONE = 6;
+    private static final int CID_TRACFONE_ATT = 7;
+    private static final int CID_TRACFONE_TMO = 8;
+    private static final String MCCMNC_TRACFONE_ATT = "310410";
+    private static final String MCCMNC_TRACFONE_TMO = "310260";
+    private static final String GID_TRACFONE = "DDFF";
+
+    private static final int CID_O2 = 9;
+    private static final int CID_O2_PREPAID = 10;
+    private static final String MCCMNC_O2 = "23410";
+    private static final String GID_O2_PREPAID = "61";
+
+
+    private static final int CID_UNKNOWN = -1;
+
+    // events to trigger carrier identification
+    private static final int SIM_LOAD_EVENT       = 1;
+    private static final int ICC_CHANGED_EVENT    = 2;
+    private static final int PREFER_APN_SET_EVENT = 3;
+
+    private CarrierResolver mCarrierResolver;
+    private CarrierResolverHandler mCarrierCarrierResolverHandler;
+
+    private class CarrierResolverHandler extends HandlerThread {
+        private CarrierResolverHandler(String name) {
+            super(name);
+        }
+
+        @Override
+        public void onLooperPrepared() {
+            mCarrierResolver = new CarrierResolver(mPhone);
+            setReady(true);
+        }
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        logd("CarrierResolverTest +Setup!");
+        super.setUp(getClass().getSimpleName());
+        ((MockContentResolver) mContext.getContentResolver()).addProvider(
+                CarrierId.AUTHORITY, new CarrierIdContentProvider());
+        // start handler thread
+        mCarrierCarrierResolverHandler = new CarrierResolverHandler(getClass().getSimpleName());
+        mCarrierCarrierResolverHandler.start();
+        waitUntilReady();
+        mCarrierResolver.sendEmptyMessage(ICC_CHANGED_EVENT);
+        logd("CarrierResolverTest -Setup!");
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        logd("CarrierResolver -tearDown");
+        mCarrierResolver.removeCallbacksAndMessages(null);
+        mCarrierResolver = null;
+        mCarrierCarrierResolverHandler.quit();
+        mCarrierCarrierResolverHandler.join();
+        super.tearDown();
+    }
+
+    @Test
+    @SmallTest
+    public void testCarrierMatch() {
+        int phoneId = mPhone.getPhoneId();
+        doReturn(MCCMNC).when(mTelephonyManager).getSimOperatorNumericForPhone(eq(phoneId));
+        // trigger sim loading event
+        mCarrierResolver.sendEmptyMessage(SIM_LOAD_EVENT);
+        waitForMs(200);
+        assertEquals(CID_VZW, mCarrierResolver.getCarrierId());
+        assertEquals(NAME, mCarrierResolver.getCarrierName());
+
+        doReturn(SPN_FI).when(mSimRecords).getServiceProviderName();
+        mCarrierResolver.sendEmptyMessage(SIM_LOAD_EVENT);
+        waitForMs(200);
+        assertEquals(CID_FI, mCarrierResolver.getCarrierId());
+        assertEquals(NAME_FI, mCarrierResolver.getCarrierName());
+    }
+
+    @Test
+    @SmallTest
+    public void testMnoCarrierId() {
+        int phoneId = mPhone.getPhoneId();
+        doReturn(MCCMNC).when(mTelephonyManager).getSimOperatorNumericForPhone(eq(phoneId));
+        doReturn(SPN_FI).when(mSimRecords).getServiceProviderName();
+
+        mCarrierResolver.sendEmptyMessage(SIM_LOAD_EVENT);
+        waitForMs(200);
+
+        assertEquals(CID_FI, mCarrierResolver.getCarrierId());
+        assertEquals(NAME_FI, mCarrierResolver.getCarrierName());
+        assertEquals(CID_VZW, mCarrierResolver.getMnoCarrierId());
+
+        doReturn(MCCMNC_VODAFONE).when(mTelephonyManager)
+                .getSimOperatorNumericForPhone(eq(phoneId));
+        doReturn(SPN_VODAFONE).when(mSimRecords).getServiceProviderName();
+        mCarrierResolver.sendEmptyMessage(SIM_LOAD_EVENT);
+        waitForMs(200);
+        assertEquals(CID_VODAFONE, mCarrierResolver.getCarrierId());
+        assertEquals(NAME_VODAFONE, mCarrierResolver.getCarrierName());
+        assertEquals(CID_VODAFONE, mCarrierResolver.getMnoCarrierId());
+    }
+
+    @Test
+    @SmallTest
+    public void testPreciseCarrierId() {
+        int phoneId = mPhone.getPhoneId();
+        doReturn(MCCMNC_TRACFONE_ATT).when(mTelephonyManager)
+                .getSimOperatorNumericForPhone(eq(phoneId));
+
+        mCarrierResolver.sendEmptyMessage(SIM_LOAD_EVENT);
+        waitForMs(200);
+        assertEquals(CID_ATT, mCarrierResolver.getCarrierId());
+        assertEquals(CID_ATT, mCarrierResolver.getPreciseCarrierId());
+
+        doReturn(GID_TRACFONE).when(mPhone).getGroupIdLevel1();
+        mCarrierResolver.sendEmptyMessage(SIM_LOAD_EVENT);
+        waitForMs(200);
+        assertEquals(CID_TRACFONE, mCarrierResolver.getCarrierId());
+        assertEquals(CID_TRACFONE_ATT, mCarrierResolver.getPreciseCarrierId());
+
+        doReturn(MCCMNC_TRACFONE_TMO).when(mTelephonyManager)
+                .getSimOperatorNumericForPhone(eq(phoneId));
+        mCarrierResolver.sendEmptyMessage(SIM_LOAD_EVENT);
+        waitForMs(200);
+        assertEquals(CID_TRACFONE, mCarrierResolver.getCarrierId());
+        assertEquals(CID_TRACFONE_TMO, mCarrierResolver.getPreciseCarrierId());
+
+        doReturn(MCCMNC_O2).when(mTelephonyManager)
+                .getSimOperatorNumericForPhone(eq(phoneId));
+        mCarrierResolver.sendEmptyMessage(SIM_LOAD_EVENT);
+        waitForMs(200);
+        assertEquals(CID_O2, mCarrierResolver.getCarrierId());
+        assertEquals(CID_O2, mCarrierResolver.getPreciseCarrierId());
+
+        doReturn(MCCMNC_O2).when(mTelephonyManager)
+                .getSimOperatorNumericForPhone(eq(phoneId));
+        doReturn(GID_O2_PREPAID).when(mPhone).getGroupIdLevel1();
+        mCarrierResolver.sendEmptyMessage(SIM_LOAD_EVENT);
+        waitForMs(200);
+        assertEquals(CID_O2, mCarrierResolver.getCarrierId());
+        assertEquals(CID_O2_PREPAID, mCarrierResolver.getPreciseCarrierId());
+    }
+
+    @Test
+    @SmallTest
+    public void testCarrierMatchSimAbsent() {
+        int phoneId = mPhone.getPhoneId();
+        doReturn(MCCMNC).when(mTelephonyManager).getSimOperatorNumericForPhone(eq(phoneId));
+        // trigger sim loading event
+        mCarrierResolver.sendEmptyMessage(SIM_LOAD_EVENT);
+        waitForMs(200);
+        assertEquals(CID_VZW, mCarrierResolver.getCarrierId());
+        assertEquals(NAME, mCarrierResolver.getCarrierName());
+        // trigger sim absent event
+        mCarrierResolver.resolveSubscriptionCarrierId(IccCardConstants.INTENT_VALUE_ICC_ABSENT);
+        waitForMs(200);
+        assertEquals(CID_UNKNOWN, mCarrierResolver.getCarrierId());
+        assertNull(mCarrierResolver.getCarrierName());
+    }
+
+    @Test
+    @SmallTest
+    public void testCarrierNoMatch() {
+        // un-configured MCCMNC
+        int phoneId = mPhone.getPhoneId();
+        doReturn("12345").when(mTelephonyManager).getSimOperatorNumericForPhone(eq(phoneId));
+        // trigger sim loading event
+        mCarrierResolver.sendEmptyMessage(SIM_LOAD_EVENT);
+        waitForMs(200);
+        assertEquals(CID_UNKNOWN, mCarrierResolver.getCarrierId());
+        assertNull(mCarrierResolver.getCarrierName());
+    }
+
+    @Test
+    @SmallTest
+    public void testGetCarrierIdFromIdentifier() {
+        // trigger sim loading event
+        mCarrierResolver.sendEmptyMessage(SIM_LOAD_EVENT);
+        waitForMs(200);
+
+        CarrierIdentifier identifier = new CarrierIdentifier(null, null, null, null, null, null);
+        int carrierid = mCarrierResolver.getCarrierIdFromIdentifier(mContext, identifier);
+        assertEquals(CID_UNKNOWN, carrierid);
+
+        identifier = new CarrierIdentifier(MCCMNC.substring(0, 3), MCCMNC.substring(3), null, null,
+                null, null);
+        carrierid = mCarrierResolver.getCarrierIdFromIdentifier(mContext, identifier);
+        assertEquals(CID_VZW, carrierid);
+
+        identifier = new CarrierIdentifier(MCCMNC.substring(0, 3), MCCMNC.substring(3),  SPN_FI, null,
+                null, null);
+        carrierid = mCarrierResolver.getCarrierIdFromIdentifier(mContext, identifier);
+        assertEquals(CID_FI, carrierid);
+    }
+
+    @Test
+    @SmallTest
+    public void testCarrierMatchPreferApnChange() {
+        int phoneId = mPhone.getPhoneId();
+        doReturn(MCCMNC).when(mTelephonyManager).getSimOperatorNumericForPhone(eq(phoneId));
+        // trigger sim loading event
+        mCarrierResolver.sendEmptyMessage(SIM_LOAD_EVENT);
+        waitForMs(200);
+        assertEquals(CID_VZW, mCarrierResolver.getCarrierId());
+        assertEquals(NAME, mCarrierResolver.getCarrierName());
+        // mock apn
+        ((MockContentResolver) mContext.getContentResolver()).addProvider(
+                Carriers.CONTENT_URI.getAuthority(), new CarrierIdContentProvider());
+        mCarrierResolver.sendEmptyMessage(PREFER_APN_SET_EVENT);
+        waitForMs(200);
+        assertEquals(CID_DOCOMO, mCarrierResolver.getCarrierId());
+        assertEquals(NAME_DOCOMO, mCarrierResolver.getCarrierName());
+    }
+
+    private class CarrierIdContentProvider extends MockContentProvider {
+        @Override
+        public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+                String sortOrder) {
+            logd("CarrierIdContentProvider: query");
+            logd("   uri = " + uri);
+            logd("   projection = " + Arrays.toString(projection));
+            logd("   selection = " + selection);
+            logd("   selectionArgs = " + Arrays.toString(selectionArgs));
+            logd("   sortOrder = " + sortOrder);
+
+            if (CarrierId.All.CONTENT_URI.getAuthority().equals(
+                    uri.getAuthority())) {
+                MatrixCursor mc = new MatrixCursor(
+                        new String[]{CarrierId._ID,
+                                CarrierId.All.MCCMNC,
+                                CarrierId.All.GID1,
+                                CarrierId.All.GID2,
+                                CarrierId.All.PLMN,
+                                CarrierId.All.IMSI_PREFIX_XPATTERN,
+                                CarrierId.All.ICCID_PREFIX,
+                                CarrierId.All.PRIVILEGE_ACCESS_RULE,
+                                CarrierId.All.SPN,
+                                CarrierId.All.APN,
+                                CarrierId.CARRIER_NAME,
+                                CarrierId.CARRIER_ID,
+                                CarrierId.PARENT_CARRIER_ID});
+
+                mc.addRow(new Object[] {
+                        1,                      // id
+                        MCCMNC,                 // mccmnc
+                        null,                   // gid1
+                        null,                   // gid2
+                        null,                   // plmn
+                        null,                   // imsi_prefix
+                        null,                   // iccid_prefix
+                        null,                   // access rule
+                        null,                   // spn
+                        null,                   // apn
+                        NAME,                   // carrier name
+                        CID_VZW,                // cid
+                        TelephonyManager.UNKNOWN_CARRIER_ID, // parent cid
+                });
+                mc.addRow(new Object[] {
+                        2,                      // id
+                        MCCMNC_TMO,             // mccmnc
+                        null,                   // gid1
+                        null,                   // gid2
+                        null,                   // plmn
+                        null,                   // imsi_prefix
+                        null,                   // iccid_prefix
+                        null,                   // access_rule
+                        null,                   // spn
+                        null,                   // apn
+                        NAME_TMO,               // carrier name
+                        CID_TMO,                // cid
+                        TelephonyManager.UNKNOWN_CARRIER_ID, // parent cid
+                });
+                mc.addRow(new Object[] {
+                        3,                      // id
+                        MCCMNC,                 // mccmnc
+                        null,                   // gid1
+                        null,                   // gid2
+                        null,                   // plmn
+                        null,                   // imsi_prefix
+                        null,                   // iccid_prefix
+                        null,                   // access_rule
+                        SPN_FI,                 // spn
+                        null,                   // apn
+                        NAME_FI,                // carrier name
+                        CID_FI,                 // cid
+                        TelephonyManager.UNKNOWN_CARRIER_ID, // parent cid
+                });
+                mc.addRow(new Object[] {
+                        4,                      // id
+                        MCCMNC,                 // mccmnc
+                        null,                   // gid1
+                        null,                   // gid2
+                        null,                   // plmn
+                        null,                   // imsi_prefix
+                        null,                   // iccid_prefix
+                        null,                   // access_rule
+                        SPN_FI,                 // spn
+                        null,                   // apn
+                        null,                   // carrier name
+                        CID_FI,                 // cid
+                        TelephonyManager.UNKNOWN_CARRIER_ID, // parent cid
+                });
+                mc.addRow(new Object[] {
+                        5,                      // id
+                        MCCMNC,                 // mccmnc
+                        null,                   // gid1
+                        null,                   // gid2
+                        null,                   // plmn
+                        null,                   // imsi_prefix
+                        null,                   // iccid_prefix
+                        null,                   // access_rule
+                        null,                   // spn
+                        APN_DOCOMO,             // apn
+                        NAME_DOCOMO,            // carrier name
+                        CID_DOCOMO,             // cid
+                        TelephonyManager.UNKNOWN_CARRIER_ID, // parent cid
+                });
+                mc.addRow(new Object[] {
+                        6,                      // id
+                        MCCMNC_VODAFONE,        // mccmnc
+                        null,                   // gid1
+                        null,                   // gid2
+                        null,                   // plmn
+                        null,                   // imsi_prefix
+                        null,                   // iccid_prefix
+                        null,                   // access_rule
+                        SPN_VODAFONE,           // spn
+                        null,                   // apn
+                        NAME_VODAFONE,          // carrier name
+                        CID_VODAFONE,           // cid
+                        TelephonyManager.UNKNOWN_CARRIER_ID, // parent cid
+                });
+                mc.addRow(new Object[] {
+                        7,                      // id
+                        MCCMNC_ATT,             // mccmnc
+                        null,                   // gid1
+                        null,                   // gid2
+                        null,                   // plmn
+                        null,                   // imsi_prefix
+                        null,                   // iccid_prefix
+                        null,                   // access_rule
+                        null,                   // spn
+                        null,                   // apn
+                        null,                   // carrier name
+                        CID_ATT,                // cid
+                        TelephonyManager.UNKNOWN_CARRIER_ID, // parent cid
+                });
+                mc.addRow(new Object[] {
+                        8,                      // id
+                        MCCMNC_TRACFONE_ATT,    // mccmnc
+                        GID_TRACFONE,           // gid1
+                        null,                   // gid2
+                        null,                   // plmn
+                        null,                   // imsi_prefix
+                        null,                   // iccid_prefix
+                        null,                   // access_rule
+                        null,                   // spn
+                        null,                   // apn
+                        null,                   // carrier name
+                        CID_TRACFONE,           // cid
+                        TelephonyManager.UNKNOWN_CARRIER_ID, // parent cid
+                });
+                mc.addRow(new Object[] {
+                        9,                      // id
+                        MCCMNC_TRACFONE_TMO,    // mccmnc
+                        GID_TRACFONE,           // gid1
+                        null,                   // gid2
+                        null,                   // plmn
+                        null,                   // imsi_prefix
+                        null,                   // iccid_prefix
+                        null,                   // access_rule
+                        null,                   // spn
+                        null,                   // apn
+                        null,                   // carrier name
+                        CID_TRACFONE,           // cid
+                        TelephonyManager.UNKNOWN_CARRIER_ID, // parent cid
+                });
+                mc.addRow(new Object[] {
+                        10,                     // id
+                        MCCMNC_TRACFONE_ATT,    // mccmnc
+                        GID_TRACFONE,           // gid1
+                        null,                   // gid2
+                        null,                   // plmn
+                        null,                   // imsi_prefix
+                        null,                   // iccid_prefix
+                        null,                   // access_rule
+                        null,                   // spn
+                        null,                   // apn
+                        null,                   // carrier name
+                        CID_TRACFONE_ATT,       // cid
+                        CID_TRACFONE,           // parent cid
+                });
+                mc.addRow(new Object[] {
+                        11,                     // id
+                        MCCMNC_TRACFONE_TMO,    // mccmnc
+                        GID_TRACFONE,           // gid1
+                        null,                   // gid2
+                        null,                   // plmn
+                        null,                   // imsi_prefix
+                        null,                   // iccid_prefix
+                        null,                   // access_rule
+                        null,                   // spn
+                        null,                   // apn
+                        null,                   // carrier name
+                        CID_TRACFONE_TMO,       // cid
+                        CID_TRACFONE,           // parent cid
+                });
+                mc.addRow(new Object[] {
+                        12,                     // id
+                        MCCMNC_O2,              // mccmnc
+                        null,                   // gid1
+                        null,                   // gid2
+                        null,                   // plmn
+                        null,                   // imsi_prefix
+                        null,                   // iccid_prefix
+                        null,                   // access_rule
+                        null,                   // spn
+                        null,                   // apn
+                        null,                   // carrier name
+                        CID_O2,                 // cid
+                        TelephonyManager.UNKNOWN_CARRIER_ID,  // parent cid
+                });
+                mc.addRow(new Object[] {
+                        13,                     // id
+                        MCCMNC_O2,              // mccmnc
+                        GID_O2_PREPAID,         // gid1
+                        null,                   // gid2
+                        null,                   // plmn
+                        null,                   // imsi_prefix
+                        null,                   // iccid_prefix
+                        null,                   // access_rule
+                        null,                   // spn
+                        null,                   // apn
+                        null,                   // carrier name
+                        CID_O2_PREPAID,         // cid
+                        CID_O2,                 // parent cid
+                });
+                return mc;
+            } else if (Carriers.CONTENT_URI.getAuthority().equals(uri.getAuthority())) {
+                MatrixCursor mc = new MatrixCursor(new String[]{Carriers._ID, Carriers.APN});
+                mc.addRow(new Object[] {
+                        1,                      // id
+                        APN_DOCOMO              // apn
+                });
+                return mc;
+            }
+            return null;
+        }
+        @Override
+        public int update(android.net.Uri uri, android.content.ContentValues values,
+                java.lang.String selection, java.lang.String[] selectionArgs) {
+            return 0;
+        }
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CarrierServiceStateTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/CarrierServiceStateTrackerTest.java
index 529a798..2dc5c66 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CarrierServiceStateTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CarrierServiceStateTrackerTest.java
@@ -31,16 +31,12 @@
 import android.app.NotificationManager;
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-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;
@@ -60,22 +56,10 @@
     private CarrierServiceStateTracker mSpyCarrierSST;
     private CarrierServiceStateTracker mCarrierSST;
     private CarrierServiceStateTrackerTestHandler mCarrierServiceStateTrackerTestHandler;
-    private FakeContentResolver mFakeContentResolver;
 
     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);
@@ -99,11 +83,6 @@
         mCarrierServiceStateTrackerTestHandler =
                 new CarrierServiceStateTrackerTestHandler(getClass().getSimpleName());
         mCarrierServiceStateTrackerTestHandler.start();
-        mFakeContentResolver = new CarrierServiceStateTrackerTest.FakeContentResolver();
-
-        when(mPhone.getContext().getContentResolver()).thenReturn(mFakeContentResolver);
-
-        doReturn(new ApplicationInfo()).when(mContext).getApplicationInfo();
 
         mNotificationManager = (NotificationManager) mContext.getSystemService(
                 Context.NOTIFICATION_SERVICE);
@@ -184,20 +163,57 @@
         doReturn(mNotificationBuilder).when(spyPrefNetworkNotification).getNotificationBuilder();
 
         String prefNetworkMode = Settings.Global.PREFERRED_NETWORK_MODE + mPhone.getSubId();
-        Settings.Global.putInt(mFakeContentResolver, prefNetworkMode,
+        Settings.Global.putInt(mContext.getContentResolver(), prefNetworkMode,
                 RILConstants.NETWORK_MODE_LTE_CDMA_EVDO);
-        mFakeContentResolver.notifyChange(
-                Settings.Global.getUriFor(prefNetworkMode), mSpyCarrierSST.getContentObserver());
+        mSpyCarrierSST.getContentObserver().dispatchChange(false,
+                Settings.Global.getUriFor(prefNetworkMode));
         waitForMs(500);
-        verify(mNotificationManager).notify(
+        verify(mNotificationManager, atLeast(1)).notify(
                 eq(CarrierServiceStateTracker.NOTIFICATION_PREF_NETWORK), isA(Notification.class));
 
-        Settings.Global.putInt(mFakeContentResolver, prefNetworkMode,
+        Settings.Global.putInt(mContext.getContentResolver(), prefNetworkMode,
                 RILConstants.NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA);
-        mFakeContentResolver.notifyChange(
-                Settings.Global.getUriFor(prefNetworkMode), mSpyCarrierSST.getContentObserver());
+        mSpyCarrierSST.getContentObserver().dispatchChange(false,
+                Settings.Global.getUriFor(prefNetworkMode));
         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/CarrierServicesSmsFilterTest.java b/tests/telephonytests/src/com/android/internal/telephony/CarrierServicesSmsFilterTest.java
index 6635c08..0a14b65 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CarrierServicesSmsFilterTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CarrierServicesSmsFilterTest.java
@@ -24,6 +24,7 @@
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -31,6 +32,7 @@
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ServiceInfo;
+import android.os.Looper;
 import android.os.RemoteException;
 import android.service.carrier.CarrierMessagingService;
 import android.service.carrier.ICarrierMessagingCallback;
@@ -71,9 +73,14 @@
     @Before
     public void setUp() throws Exception {
         super.setUp(getClass().getSimpleName());
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
+            Looper.loop();
+        }
         mCarrierServicesSmsFilterUT = new CarrierServicesSmsFilter(
                 mContext, mPhone, new byte[][]{SMS_PDU},
-                0, "3gpp", mFilterCallback, getClass().getSimpleName());
+                0, "3gpp", mFilterCallback, getClass().getSimpleName()
+        );
     }
 
     @After
@@ -92,7 +99,8 @@
     public void testFilter_carrierAppPresent_handled() throws Exception {
         mockCarrierApp();
         mockCarrierAppStubResults(
-                CarrierMessagingService.RECEIVE_OPTIONS_DROP, mICarrierAppMessagingService);
+                CarrierMessagingService.RECEIVE_OPTIONS_DROP, mICarrierAppMessagingService,
+                true);
         assertTrue(mCarrierServicesSmsFilterUT.filter());
 
         verify(mFilterCallback, timeout(100))
@@ -104,7 +112,8 @@
     public void testFilter_systemAppPresent_handled() throws Exception {
         mockSystemApp();
         mockCarrierAppStubResults(
-                CarrierMessagingService.RECEIVE_OPTIONS_DROP, mISystemCarrierMessagingService);
+                CarrierMessagingService.RECEIVE_OPTIONS_DROP, mISystemCarrierMessagingService,
+                true);
 
         assertTrue(mCarrierServicesSmsFilterUT.filter());
 
@@ -118,9 +127,11 @@
         mockCarrierApp();
         mockSystemApp();
         mockCarrierAppStubResults(
-                CarrierMessagingService.RECEIVE_OPTIONS_DEFAULT, mICarrierAppMessagingService);
+                CarrierMessagingService.RECEIVE_OPTIONS_DEFAULT, mICarrierAppMessagingService,
+                true);
         mockCarrierAppStubResults(
-                CarrierMessagingService.RECEIVE_OPTIONS_DROP, mISystemCarrierMessagingService);
+                CarrierMessagingService.RECEIVE_OPTIONS_DROP, mISystemCarrierMessagingService,
+                true);
 
         assertTrue(mCarrierServicesSmsFilterUT.filter());
 
@@ -128,6 +139,34 @@
                 .onFilterComplete(eq(CarrierMessagingService.RECEIVE_OPTIONS_DEFAULT));
     }
 
+    @Test
+    public void testFilterSmsShouldNotTimeout_whenOnFilterCompleteCalled() throws Exception {
+        //This will make sure mCarrierServicesSmsFilterUT.filter() will return true, and therefore
+        // filterSms() will return true
+        mockCarrierApp();
+        mockCarrierAppStubResults(
+                CarrierMessagingService.RECEIVE_OPTIONS_DEFAULT, mICarrierAppMessagingService,
+                true);
+        assertTrue(mCarrierServicesSmsFilterUT.filter());
+
+        verify(mFilterCallback, times(1))
+                .onFilterComplete(eq(CarrierMessagingService.RECEIVE_OPTIONS_DEFAULT));
+    }
+
+    @Test
+    public void testFilterSmsShouldTimeout_whenOnFilterCompleteNotCalled() throws Exception {
+        //This will make sure mCarrierServicesSmsFilterUT.filter() will return true, and therefore
+        // filterSms() will return true
+        mockCarrierApp();
+        mockCarrierAppStubResults(
+                CarrierMessagingService.RECEIVE_OPTIONS_DEFAULT, mICarrierAppMessagingService,
+                false);
+        assertTrue(mCarrierServicesSmsFilterUT.filter());
+
+        verify(mFilterCallback, times(0))
+                .onFilterComplete(anyInt());
+    }
+
     private void mockCarrierApp()
             throws RemoteException {
         mContextFixture.addService(
@@ -158,7 +197,8 @@
                 serviceInfo);
     }
 
-    private void mockCarrierAppStubResults(final int result, ICarrierMessagingService.Stub stub)
+    private void mockCarrierAppStubResults(final int result, ICarrierMessagingService.Stub stub,
+            boolean callOnFilterComplete)
             throws RemoteException {
         when(stub.queryLocalInterface(anyString())).thenReturn(stub);
         when(stub.asBinder()).thenReturn(stub);
@@ -167,7 +207,9 @@
             public Void answer(InvocationOnMock invocation) throws Throwable {
                 Object[] args = invocation.getArguments();
                 ICarrierMessagingCallback callback = (ICarrierMessagingCallback) args[4];
-                callback.onFilterComplete(result);
+                if (callOnFilterComplete) {
+                    callback.onFilterComplete(result);
+                }
                 return null;
             }
         }).when(stub).filterSms(
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CellIdentityGsmTest.java b/tests/telephonytests/src/com/android/internal/telephony/CellIdentityGsmTest.java
index cb003b7..cd4d17d 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CellIdentityGsmTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CellIdentityGsmTest.java
@@ -17,8 +17,8 @@
 package com.android.internal.telephony;
 
 import android.os.Parcel;
-import android.telephony.CellIdentity;
 import android.telephony.CellIdentityGsm;
+import android.telephony.CellInfo;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 
@@ -185,7 +185,7 @@
                 new CellIdentityGsm(LAC, CID, ARFCN, BSIC, null, null, ALPHA_LONG, ALPHA_SHORT);
 
         Parcel p = Parcel.obtain();
-        p.writeInt(CellIdentityGsm.TYPE_GSM);
+        p.writeInt(CellInfo.TYPE_GSM);
         p.writeString(String.valueOf(Integer.MAX_VALUE));
         p.writeString(String.valueOf(Integer.MAX_VALUE));
         p.writeString(ALPHA_LONG);
@@ -208,7 +208,7 @@
                 new CellIdentityGsm(LAC, CID, ARFCN, BSIC, null, null, ALPHA_LONG, ALPHA_SHORT);
 
         Parcel p = Parcel.obtain();
-        p.writeInt(CellIdentity.TYPE_GSM);
+        p.writeInt(CellInfo.TYPE_GSM);
         p.writeString(invalidMcc);
         p.writeString(invalidMnc);
         p.writeString(ALPHA_LONG);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CellIdentityLteTest.java b/tests/telephonytests/src/com/android/internal/telephony/CellIdentityLteTest.java
index bbd9078..310b50c6 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CellIdentityLteTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CellIdentityLteTest.java
@@ -17,8 +17,8 @@
 package com.android.internal.telephony;
 
 import android.os.Parcel;
-import android.telephony.CellIdentity;
 import android.telephony.CellIdentityLte;
+import android.telephony.CellInfo;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 
@@ -193,7 +193,7 @@
                 CI, PCI, TAC, EARFCN, BANDWIDTH, null, null, ALPHA_LONG, ALPHA_SHORT);
 
         Parcel p = Parcel.obtain();
-        p.writeInt(CellIdentity.TYPE_LTE);
+        p.writeInt(CellInfo.TYPE_LTE);
         p.writeString(String.valueOf(Integer.MAX_VALUE));
         p.writeString(String.valueOf(Integer.MAX_VALUE));
         p.writeString(ALPHA_LONG);
@@ -217,7 +217,7 @@
                 CI, PCI, TAC, EARFCN, BANDWIDTH, null, null, ALPHA_LONG, ALPHA_SHORT);
 
         Parcel p = Parcel.obtain();
-        p.writeInt(CellIdentity.TYPE_LTE);
+        p.writeInt(CellInfo.TYPE_LTE);
         p.writeString(invalidMcc);
         p.writeString(invalidMnc);
         p.writeString(ALPHA_LONG);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CellIdentityNrTest.java b/tests/telephonytests/src/com/android/internal/telephony/CellIdentityNrTest.java
new file mode 100644
index 0000000..dc2c320
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/CellIdentityNrTest.java
@@ -0,0 +1,103 @@
+/*
+ * 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 com.google.common.truth.Truth.assertThat;
+
+import android.os.Parcel;
+import android.telephony.CellIdentityNr;
+import android.telephony.CellInfo;
+import android.test.AndroidTestCase;
+
+import org.junit.Test;
+
+public class CellIdentityNrTest extends AndroidTestCase {
+    private static final String MCC = "310";
+    private static final String MNC = "260";
+    private static final String ANOTHER_MCC = "134";
+    private static final String ANOTHER_MNC = "256";
+    private static final String ALPHAL = "long operator name";
+    private static final String ALPHAS = "lon";
+    private static final int NRARFCN = 13456;
+    private static final int PCI = 123;
+    private static final int TAC = 32767;
+
+    @Test
+    public void testGetMethod() {
+        // GIVEN an instance of CellIdentityNr
+        CellIdentityNr cellIdentityNr =
+                new CellIdentityNr(PCI, TAC, NRARFCN, MCC, MNC, ALPHAL, ALPHAS);
+
+        // THEN the get method should return correct value
+        assertThat(cellIdentityNr.getType()).isEqualTo(CellInfo.TYPE_NR);
+        assertThat(cellIdentityNr.getChannelNumber()).isEqualTo(NRARFCN);
+        assertThat(cellIdentityNr.getPci()).isEqualTo(PCI);
+        assertThat(cellIdentityNr.getTac()).isEqualTo(TAC);
+        assertThat(cellIdentityNr.getOperatorAlphaLong()).isEqualTo(ALPHAL);
+        assertThat(cellIdentityNr.getOperatorAlphaShort()).isEqualTo(ALPHAS);
+        assertThat(cellIdentityNr.getMccString()).isEqualTo(MCC);
+        assertThat(cellIdentityNr.getMncString()).isEqualTo(MNC);
+    }
+
+    @Test
+    public void testEquals_sameParameters() {
+        // GIVEN an instance of CellIdentityNr, and create another object with the same parameters
+        CellIdentityNr cellIdentityNr =
+                new CellIdentityNr(PCI, TAC, NRARFCN, MCC, MNC, ALPHAL, ALPHAS);
+        CellIdentityNr anotherCellIdentityNr =
+                new CellIdentityNr(PCI, TAC, NRARFCN, MCC, MNC, ALPHAL, ALPHAS);
+
+        // THEN this two objects are equivalent
+        assertThat(cellIdentityNr).isEqualTo(anotherCellIdentityNr);
+    }
+
+    @Test
+    public void testEquals_differentParameters() {
+        // GIVEN an instance of CellIdentityNr, and create another object with different parameters
+        CellIdentityNr cellIdentityNr =
+                new CellIdentityNr(PCI, TAC, NRARFCN, MCC, MNC, ALPHAL, ALPHAS);
+        CellIdentityNr anotherCellIdentityNr =
+                new CellIdentityNr(PCI, TAC, NRARFCN, ANOTHER_MCC, ANOTHER_MNC, ALPHAL, ALPHAS);
+
+        // THEN this two objects are different
+        assertThat(cellIdentityNr).isNotEqualTo(anotherCellIdentityNr);
+    }
+
+    @Test
+    public void testParcel() {
+        // GIVEN an instance of CellIdentityNr
+        CellIdentityNr cellIdentityNr =
+                new CellIdentityNr(PCI, TAC, NRARFCN, MCC, MNC, ALPHAL, ALPHAS);
+
+        // WHEN write the object to parcel and create another object with that parcel
+        Parcel parcel = Parcel.obtain();
+        cellIdentityNr.writeToParcel(parcel, 0 /* type */);
+        parcel.setDataPosition(0);
+        CellIdentityNr anotherCellIdentityNr = CellIdentityNr.CREATOR.createFromParcel(parcel);
+
+        // THEN the new object is equal to the old one
+        assertThat(anotherCellIdentityNr).isEqualTo(anotherCellIdentityNr);
+        assertThat(anotherCellIdentityNr.getType()).isEqualTo(CellInfo.TYPE_NR);
+        assertThat(anotherCellIdentityNr.getChannelNumber()).isEqualTo(NRARFCN);
+        assertThat(anotherCellIdentityNr.getPci()).isEqualTo(PCI);
+        assertThat(anotherCellIdentityNr.getTac()).isEqualTo(TAC);
+        assertThat(anotherCellIdentityNr.getOperatorAlphaLong()).isEqualTo(ALPHAL);
+        assertThat(anotherCellIdentityNr.getOperatorAlphaShort()).isEqualTo(ALPHAS);
+        assertThat(anotherCellIdentityNr.getMccString()).isEqualTo(MCC);
+        assertThat(anotherCellIdentityNr.getMncString()).isEqualTo(MNC);
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CellIdentityTdscdmaTest.java b/tests/telephonytests/src/com/android/internal/telephony/CellIdentityTdscdmaTest.java
index 1cdf18ca..b19ca5e 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CellIdentityTdscdmaTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CellIdentityTdscdmaTest.java
@@ -17,8 +17,8 @@
 package com.android.internal.telephony;
 
 import android.os.Parcel;
-import android.telephony.CellIdentity;
 import android.telephony.CellIdentityTdscdma;
+import android.telephony.CellInfo;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 
@@ -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);
@@ -99,10 +105,11 @@
     @SmallTest
     public void testParcelWithUnknowMccMnc() {
         CellIdentityTdscdma ci =
-                new CellIdentityTdscdma(null, null, LAC, CID, CPID, ALPHA_LONG, ALPHA_SHORT);
+                new CellIdentityTdscdma(
+                        null, null, LAC, CID, CPID, UARFCN, ALPHA_LONG, ALPHA_SHORT);
 
         Parcel p = Parcel.obtain();
-        p.writeInt(CellIdentity.TYPE_TDSCDMA);
+        p.writeInt(CellInfo.TYPE_TDSCDMA);
         p.writeString(String.valueOf(Integer.MAX_VALUE));
         p.writeString(String.valueOf(Integer.MAX_VALUE));
         p.writeString(ALPHA_LONG);
@@ -110,6 +117,7 @@
         p.writeInt(LAC);
         p.writeInt(CID);
         p.writeInt(CPID);
+        p.writeInt(UARFCN);
         p.setDataPosition(0);
 
         CellIdentityTdscdma newCi = CellIdentityTdscdma.CREATOR.createFromParcel(p);
@@ -121,10 +129,11 @@
         final String invalidMcc = "randomStuff";
         final String invalidMnc = "randomStuff";
         CellIdentityTdscdma ci =
-                new CellIdentityTdscdma(null, null, LAC, CID, CPID, ALPHA_LONG, ALPHA_SHORT);
+                new CellIdentityTdscdma(
+                        null, null, LAC, CID, CPID, UARFCN, ALPHA_LONG, ALPHA_SHORT);
 
         Parcel p = Parcel.obtain();
-        p.writeInt(CellIdentity.TYPE_TDSCDMA);
+        p.writeInt(CellInfo.TYPE_TDSCDMA);
         p.writeString(invalidMcc);
         p.writeString(invalidMnc);
         p.writeString(ALPHA_LONG);
@@ -132,6 +141,7 @@
         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 d0e30b4..9fc0af4 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CellIdentityWcdmaTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CellIdentityWcdmaTest.java
@@ -17,8 +17,8 @@
 package com.android.internal.telephony;
 
 import android.os.Parcel;
-import android.telephony.CellIdentity;
 import android.telephony.CellIdentityWcdma;
+import android.telephony.CellInfo;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 
@@ -183,7 +183,7 @@
                 new CellIdentityWcdma(LAC, CID, PSC, UARFCN, null, null, ALPHA_LONG, ALPHA_SHORT);
 
         Parcel p = Parcel.obtain();
-        p.writeInt(CellIdentity.TYPE_WCDMA);
+        p.writeInt(CellInfo.TYPE_WCDMA);
         p.writeString(String.valueOf(Integer.MAX_VALUE));
         p.writeString(String.valueOf(Integer.MAX_VALUE));
         p.writeString(ALPHA_LONG);
@@ -206,7 +206,7 @@
                 new CellIdentityWcdma(LAC, CID, PSC, UARFCN, null, null, ALPHA_LONG, ALPHA_SHORT);
 
         Parcel p = Parcel.obtain();
-        p.writeInt(CellIdentity.TYPE_WCDMA);
+        p.writeInt(CellInfo.TYPE_WCDMA);
         p.writeString(invalidMcc);
         p.writeString(invalidMnc);
         p.writeString(ALPHA_LONG);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CellSignalStrengthNrTest.java b/tests/telephonytests/src/com/android/internal/telephony/CellSignalStrengthNrTest.java
new file mode 100644
index 0000000..2fdf0b1
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/CellSignalStrengthNrTest.java
@@ -0,0 +1,149 @@
+/*
+ * 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 com.google.common.truth.Truth.assertThat;
+
+import android.os.Parcel;
+import android.telephony.CellSignalStrength;
+import android.telephony.CellSignalStrengthNr;
+import android.test.AndroidTestCase;
+
+import com.google.common.collect.BoundType;
+import com.google.common.collect.Range;
+
+import org.junit.Test;
+
+public class CellSignalStrengthNrTest extends AndroidTestCase {
+    private static final int CSIRSRP = -123;
+    private static final int CSIRSRQ = -111;
+    private static final int ANOTHER_CSIRSRP = -111;
+    private static final int ANOTHER_CSIRSRQ = -120;
+    private static final int INVALID_CSIRSRP = Integer.MAX_VALUE;
+    private static final int CSISINR = 64;
+    private static final int SSRSRP = -112;
+    private static final int SSRSRQ = -94;
+    private static final int SSSINR = 32;
+
+    @Test
+    public void testGetMethod() {
+        // GIVEN an instance of CellSignalStrengthNr
+        CellSignalStrengthNr css = new CellSignalStrengthNr(
+                CSIRSRP, CSIRSRQ, CSISINR, SSRSRP, SSRSRQ, SSSINR);
+
+        // THEN the get method should return correct value
+        assertThat(css.getCsiRsrp()).isEqualTo(CSIRSRP);
+        assertThat(css.getCsiRsrq()).isEqualTo(CSIRSRQ);
+        assertThat(css.getCsiSinr()).isEqualTo(CSISINR);
+        assertThat(css.getSsRsrp()).isEqualTo(SSRSRP);
+        assertThat(css.getSsRsrq()).isEqualTo(SSRSRQ);
+        assertThat(css.getSsSinr()).isEqualTo(SSSINR);
+    }
+
+    @Test
+    public void testEquals_sameParameters() {
+        // GIVEN an instance of CellSignalStrengthNr and another object with the same parameters
+        CellSignalStrengthNr css = new CellSignalStrengthNr(
+                CSIRSRP, CSIRSRQ, CSISINR, SSRSRP, SSRSRQ, SSSINR);
+        CellSignalStrengthNr anotherCss = new CellSignalStrengthNr(
+                CSIRSRP, CSIRSRQ, CSISINR, SSRSRP, SSRSRQ, SSSINR);
+
+        // THEN this two objects are equivalent
+        assertThat(css).isEqualTo(anotherCss);
+    }
+
+    @Test
+    public void testEquals_differentParameters() {
+        // GIVEN an instance of CellSignalStrengthNr and another object with some different
+        // parameters
+        CellSignalStrengthNr css = new CellSignalStrengthNr(
+                CSIRSRP, CSIRSRQ, CSISINR, SSRSRP, SSRSRQ, SSSINR);
+        CellSignalStrengthNr anotherCss = new CellSignalStrengthNr(
+                ANOTHER_CSIRSRP, ANOTHER_CSIRSRQ, CSISINR, SSRSRP, SSRSRQ, SSSINR);
+
+        // THEN this two objects are different
+        assertThat(css).isNotEqualTo(anotherCss);
+    }
+
+    @Test
+    public void testAusLevel_validValue() {
+        // GIVEN an instance of CellSignalStrengthNr with valid csirsrp
+        CellSignalStrengthNr css = new CellSignalStrengthNr(
+                CSIRSRP, CSIRSRQ, CSISINR, SSRSRP, SSRSRQ, SSSINR);
+
+        // THEN the asu level is in range [0, 97]
+        assertThat(css.getAsuLevel()).isIn(Range.range(0, BoundType.CLOSED, 97, BoundType.CLOSED));
+    }
+
+    @Test
+    public void testAsuLevel_invalidValue() {
+        // GIVEN an instance of CellSignalStrengthNr with invalid csirsrp
+        CellSignalStrengthNr css = new CellSignalStrengthNr(
+                INVALID_CSIRSRP, CSIRSRQ, CSISINR, SSRSRP, SSRSRQ, SSSINR);
+
+        // THEN the asu level is unknown
+        assertThat(css.getAsuLevel()).isEqualTo(CellSignalStrengthNr.UNKNOWN_ASU_LEVEL);
+    }
+
+    @Test
+    public void testSignalLevel_validValue() {
+        for (int csiRsrp = -140; csiRsrp <= -44; csiRsrp++) {
+            // GIVEN an instance of CellSignalStrengthNr with valid csirsrp
+            CellSignalStrengthNr css = new CellSignalStrengthNr(
+                    csiRsrp, CSIRSRQ, CSISINR, SSRSRP, SSRSRQ, SSSINR);
+
+            // THEN the signal level is valid
+            assertThat(css.getLevel()).isAnyOf(
+                    CellSignalStrength.SIGNAL_STRENGTH_GREAT,
+                    CellSignalStrength.SIGNAL_STRENGTH_GOOD,
+                    CellSignalStrength.SIGNAL_STRENGTH_MODERATE,
+                    CellSignalStrength.SIGNAL_STRENGTH_POOR);
+        }
+    }
+
+    @Test
+    public void testSignalLevel_invalidValue() {
+        // GIVEN an instance of CellSignalStrengthNr with invalid csirsrp
+        CellSignalStrengthNr css = new CellSignalStrengthNr(
+                INVALID_CSIRSRP, CSIRSRQ, CSISINR, SSRSRP, SSRSRQ, SSSINR);
+
+        // THEN the signal level is unknown
+        assertThat(css.getLevel()).isEqualTo(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+    }
+
+    @Test
+    public void testParcel() {
+        // GIVEN an instance of CellSignalStrengthNr
+        CellSignalStrengthNr css = new CellSignalStrengthNr(
+                CSIRSRP, CSIRSRQ, CSISINR, SSRSRP, SSRSRQ, SSSINR);
+
+        // WHEN write the object to parcel and create another object with that parcel
+        Parcel parcel = Parcel.obtain();
+        css.writeToParcel(parcel, 0 /* type */);
+        parcel.setDataPosition(0);
+        CellSignalStrengthNr anotherCss = CellSignalStrengthNr.CREATOR.createFromParcel(parcel);
+
+        // THEN the new object is equal to the old one
+        assertThat(anotherCss).isEqualTo(css);
+        assertThat(anotherCss.getCsiRsrp()).isEqualTo(CSIRSRP);
+        assertThat(anotherCss.getCsiRsrq()).isEqualTo(CSIRSRQ);
+        assertThat(anotherCss.getCsiSinr()).isEqualTo(CSISINR);
+        assertThat(anotherCss.getSsRsrp()).isEqualTo(SSRSRP);
+        assertThat(anotherCss.getSsRsrq()).isEqualTo(SSRSRQ);
+        assertThat(anotherCss.getSsSinr()).isEqualTo(SSSINR);
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CellularNetworkServiceTest.java b/tests/telephonytests/src/com/android/internal/telephony/CellularNetworkServiceTest.java
index 16bc535..c604cf0 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,9 +155,9 @@
         waitForMs(1000);
 
         expectedState = new NetworkRegistrationState(
-                AccessNetworkConstants.TransportType.WWAN, domain, voiceRegState,
+                domain, AccessNetworkConstants.TransportType.WWAN, voiceRegState,
                 ServiceState.rilRadioTechnologyToNetworkType(voiceRadioTech), reasonForDenial,
-                false, availableServices, null, maxDataCalls);
+                false, availableServices, null, maxDataCalls, false, false, false);
 
         try {
             verify(mCallback, times(1)).onGetNetworkRegistrationStateComplete(
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ClientWakelockTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/ClientWakelockTrackerTest.java
index c18581a..28ac9eb 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ClientWakelockTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ClientWakelockTrackerTest.java
@@ -17,6 +17,9 @@
 package com.android.internal.telephony;
 
 import static com.android.internal.telephony.TelephonyTestUtils.waitForMs;
+
+import android.support.test.filters.FlakyTest;
+
 import junit.framework.TestCase;
 
 public class ClientWakelockTrackerTest extends TestCase {
@@ -35,6 +38,7 @@
     client "PQR" sends a message at t+20 and gets response at t+120. Verify that
     "ABC" is attributed 30ms and "PQR" 90ms of the total wakelock time of 120ms
      */
+    @FlakyTest /* flakes 0.37% of the time */
     public void testTwoClients() throws Exception {
         myTracker.startTracking("ABC", 101, 1, 1);
         waitForMs(20);
@@ -69,6 +73,7 @@
     and sends another message at t+20 and gets response at t+120. Verify that
     "ABC" is attributed 120ms
      */
+    @FlakyTest /* flakes 0.37% of the time */
     public void testOneClient() throws Exception {
         myTracker.startTracking("ABC", 101, 1, 1);
         waitForMs(20);
@@ -95,6 +100,7 @@
     /* This test has client "ABC" send 1 message at time t and another at time t+20
     and gets response for all at t+40. Verify that "ABC" is attributed 40ms
      */
+    @FlakyTest /* flakes 0.37% of the time */
     public void testStopTrackingAllOneClient() throws Exception {
         myTracker.startTracking("ABC", 101, 1, 1);
         waitForMs(20);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java b/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java
index 1d08c42..6313633 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java
@@ -40,6 +40,7 @@
 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;
@@ -258,6 +259,8 @@
         public String getSystemServiceName(Class<?> serviceClass) {
             if (serviceClass == SubscriptionManager.class) {
                 return Context.TELEPHONY_SUBSCRIPTION_SERVICE;
+            } else if (serviceClass == AppOpsManager.class) {
+                return Context.APP_OPS_SERVICE;
             }
             return super.getSystemServiceName(serviceClass);
         }
@@ -278,6 +281,11 @@
         }
 
         @Override
+        public ApplicationInfo getApplicationInfo() {
+            return mApplicationInfo;
+        }
+
+        @Override
         public String getOpPackageName() {
             return "com.android.internal.telephony";
         }
@@ -493,6 +501,11 @@
         public String getPackageName() {
             return "com.android.internal.telephony";
         }
+
+        @Override
+        public Context getApplicationContext() {
+            return null;
+        }
     }
 
     private final Multimap<String, ComponentName> mComponentNamesByAction =
@@ -526,6 +539,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);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/DefaultPhoneNotifierTest.java b/tests/telephonytests/src/com/android/internal/telephony/DefaultPhoneNotifierTest.java
index 5299b3f..1785c8f 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/DefaultPhoneNotifierTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/DefaultPhoneNotifierTest.java
@@ -15,35 +15,33 @@
  */
 package com.android.internal.telephony;
 
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mock;
-
-import java.util.List;
-import java.util.ArrayList;
-
+import static org.junit.Assert.assertEquals;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.doReturn;
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.times;
-import org.mockito.ArgumentCaptor;
+import static org.mockito.Mockito.verify;
+
+import android.os.Bundle;
 import android.telephony.CellInfo;
 import android.telephony.DisconnectCause;
 import android.telephony.PreciseCallState;
 import android.telephony.PreciseDisconnectCause;
 import android.telephony.SignalStrength;
 import android.telephony.TelephonyManager;
-import android.telephony.VoLteServiceState;
 import android.telephony.gsm.GsmCellLocation;
-import android.os.Bundle;
-import android.os.Process;
-import android.os.WorkSource;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+
+import java.util.ArrayList;
+import java.util.List;
+
 public class DefaultPhoneNotifierTest extends TelephonyTest {
 
     private DefaultPhoneNotifier mDefaultPhoneNotifierUT;
@@ -247,7 +245,7 @@
         ArgumentCaptor<Bundle> cellLocationCapture =
                 ArgumentCaptor.forClass(Bundle.class);
 
-        mDefaultPhoneNotifierUT.notifyCellLocation(mPhone);
+        mDefaultPhoneNotifierUT.notifyCellLocation(mPhone, mGsmCellLocation);
         verify(mTelephonyRegisteryMock).notifyCellLocationForSubscriber(eq(0),
                 cellLocationCapture.capture());
         assertEquals(2, cellLocationCapture.getValue().getInt("lac"));
@@ -256,7 +254,7 @@
 
         doReturn(1).when(mPhone).getSubId();
         mGsmCellLocation.setPsc(5);
-        mDefaultPhoneNotifierUT.notifyCellLocation(mPhone);
+        mDefaultPhoneNotifierUT.notifyCellLocation(mPhone, mGsmCellLocation);
         verify(mTelephonyRegisteryMock).notifyCellLocationForSubscriber(eq(1),
                 cellLocationCapture.capture());
         assertEquals(2, cellLocationCapture.getValue().getInt("lac"));
@@ -272,15 +270,4 @@
         mDefaultPhoneNotifierUT.notifyOtaspChanged(mPhone, TelephonyManager.OTASP_UNKNOWN);
         verify(mTelephonyRegisteryMock).notifyOtaspChanged(TelephonyManager.OTASP_UNKNOWN);
     }
-
-    @Test @SmallTest
-    public void testNotifyVoLteServiceStateChanged() throws Exception {
-        VoLteServiceState state = new VoLteServiceState(VoLteServiceState.NOT_SUPPORTED);
-        mDefaultPhoneNotifierUT.notifyVoLteServiceStateChanged(mPhone, state);
-        verify(mTelephonyRegisteryMock).notifyVoLteServiceStateChanged(state);
-
-        state = new VoLteServiceState(VoLteServiceState.HANDOVER_COMPLETED);
-        mDefaultPhoneNotifierUT.notifyVoLteServiceStateChanged(mPhone, state);
-        verify(mTelephonyRegisteryMock).notifyVoLteServiceStateChanged(state);
-    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/DeviceStateMonitorTest.java b/tests/telephonytests/src/com/android/internal/telephony/DeviceStateMonitorTest.java
index 1f883b8..1f2124b 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/DeviceStateMonitorTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/DeviceStateMonitorTest.java
@@ -20,6 +20,7 @@
 
 import static com.android.internal.telephony.TelephonyTestUtils.waitForMs;
 
+import static org.junit.Assert.assertEquals;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Matchers.nullable;
 import static org.mockito.Mockito.times;
@@ -33,14 +34,14 @@
 import android.os.HandlerThread;
 import android.os.Message;
 import android.support.test.filters.FlakyTest;
+import android.test.suitebuilder.annotation.MediumTest;
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Ignore;
+import org.junit.Test;
 
 import java.util.ArrayList;
 
-@Ignore
 public class DeviceStateMonitorTest extends TelephonyTest {
 
     private DeviceStateMonitor mDSM;
@@ -132,4 +133,41 @@
         verify(mSimulatedCommandsVerifier, times(1)).setUnsolResponseFilter(eq(-1),
                 nullable(Message.class));
     }
+
+    private void sendStates(int screenState, int chargingState, int wifiState) {
+        setReady(false);
+        mDSM.obtainMessage(
+                DeviceStateMonitor.EVENT_SCREEN_STATE_CHANGED, screenState, 0).sendToTarget();
+        mDSM.obtainMessage(
+                DeviceStateMonitor.EVENT_CHARGING_STATE_CHANGED, chargingState, 0).sendToTarget();
+        mDSM.obtainMessage(
+                DeviceStateMonitor.EVENT_WIFI_CONNECTION_CHANGED, wifiState, 0).sendToTarget();
+        mDSM.post(() -> setReady(true));
+        waitUntilReady();
+    }
+
+    @Test
+    @MediumTest
+    public void testWifi() throws Exception  {
+        // screen off
+        sendStates(0, 0, 0);
+        assertEquals(
+                DeviceStateMonitor.CELL_INFO_INTERVAL_LONG_MS, mDSM.computeCellInfoMinInterval());
+        // screen off, but charging
+        sendStates(0, 1, 0);
+        assertEquals(
+                DeviceStateMonitor.CELL_INFO_INTERVAL_LONG_MS, mDSM.computeCellInfoMinInterval());
+        // screen on, no wifi
+        sendStates(1, 0, 0);
+        assertEquals(
+                DeviceStateMonitor.CELL_INFO_INTERVAL_SHORT_MS, mDSM.computeCellInfoMinInterval());
+        // screen on, but on wifi
+        sendStates(1, 0, 1);
+        assertEquals(
+                DeviceStateMonitor.CELL_INFO_INTERVAL_LONG_MS, mDSM.computeCellInfoMinInterval());
+        // screen on, charging
+        sendStates(1, 1, 0);
+        assertEquals(
+                DeviceStateMonitor.CELL_INFO_INTERVAL_SHORT_MS, mDSM.computeCellInfoMinInterval());
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/FakeTelephonyProvider.java b/tests/telephonytests/src/com/android/internal/telephony/FakeTelephonyProvider.java
new file mode 100644
index 0000000..2271a5a
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/FakeTelephonyProvider.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.telephony;
+
+
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.UriMatcher;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.BaseColumns;
+import android.support.test.InstrumentationRegistry;
+import android.telephony.SubscriptionManager;
+import android.test.mock.MockContentProvider;
+import android.util.Log;
+
+public class FakeTelephonyProvider extends MockContentProvider {
+    static final String TAG = "FakeTelephonyProvider";
+
+    private InMemoryTelephonyProviderDbHelper mDbHelper =
+            new InMemoryTelephonyProviderDbHelper();
+
+    /**
+     * An in memory DB.
+     */
+    private class InMemoryTelephonyProviderDbHelper extends SQLiteOpenHelper {
+        InMemoryTelephonyProviderDbHelper() {
+            super(InstrumentationRegistry.getTargetContext(),
+                    null,    // db file name is null for in-memory db
+                    null,    // CursorFactory is null by default
+                    1);      // db version is no-op for tests
+            Log.d(TAG, "InMemoryTelephonyProviderDbHelper creating in-memory database");
+        }
+
+        // This should always be consistent with TelephonyProvider#getStringForSimInfoTableCreation.
+        private String getStringForSimInfoTableCreation(String tableName) {
+            return "CREATE TABLE " + tableName + "("
+                    + SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID
+                    + " INTEGER PRIMARY KEY AUTOINCREMENT,"
+                    + SubscriptionManager.ICC_ID + " TEXT NOT NULL,"
+                    + SubscriptionManager.SIM_SLOT_INDEX
+                    + " INTEGER DEFAULT " + SubscriptionManager.SIM_NOT_INSERTED + ","
+                    + SubscriptionManager.DISPLAY_NAME + " TEXT,"
+                    + SubscriptionManager.CARRIER_NAME + " TEXT,"
+                    + SubscriptionManager.NAME_SOURCE
+                    + " INTEGER DEFAULT " + SubscriptionManager.NAME_SOURCE_DEFAULT_SOURCE + ","
+                    + SubscriptionManager.COLOR + " INTEGER DEFAULT "
+                    + SubscriptionManager.COLOR_DEFAULT + ","
+                    + SubscriptionManager.NUMBER + " TEXT,"
+                    + SubscriptionManager.DISPLAY_NUMBER_FORMAT
+                    + " INTEGER NOT NULL DEFAULT "
+                    + SubscriptionManager.DISPLAY_NUMBER_DEFAULT + ","
+                    + SubscriptionManager.DATA_ROAMING
+                    + " INTEGER DEFAULT " + SubscriptionManager.DATA_ROAMING_DEFAULT + ","
+                    + SubscriptionManager.MCC + " INTEGER DEFAULT 0,"
+                    + SubscriptionManager.MNC + " INTEGER DEFAULT 0,"
+                    + SubscriptionManager.MCC_STRING + " TEXT,"
+                    + SubscriptionManager.MNC_STRING + " TEXT,"
+                    + SubscriptionManager.SIM_PROVISIONING_STATUS
+                    + " INTEGER DEFAULT " + SubscriptionManager.SIM_PROVISIONED + ","
+                    + SubscriptionManager.IS_EMBEDDED + " INTEGER DEFAULT 0,"
+                    + SubscriptionManager.CARD_ID + " TEXT NOT NULL,"
+                    + SubscriptionManager.ACCESS_RULES + " BLOB,"
+                    + SubscriptionManager.IS_REMOVABLE + " INTEGER DEFAULT 0,"
+                    + SubscriptionManager.CB_EXTREME_THREAT_ALERT + " INTEGER DEFAULT 1,"
+                    + SubscriptionManager.CB_SEVERE_THREAT_ALERT + " INTEGER DEFAULT 1,"
+                    + SubscriptionManager.CB_AMBER_ALERT + " INTEGER DEFAULT 1,"
+                    + SubscriptionManager.CB_EMERGENCY_ALERT + " INTEGER DEFAULT 1,"
+                    + SubscriptionManager.CB_ALERT_SOUND_DURATION + " INTEGER DEFAULT 4,"
+                    + SubscriptionManager.CB_ALERT_REMINDER_INTERVAL + " INTEGER DEFAULT 0,"
+                    + SubscriptionManager.CB_ALERT_VIBRATE + " INTEGER DEFAULT 1,"
+                    + SubscriptionManager.CB_ALERT_SPEECH + " INTEGER DEFAULT 1,"
+                    + SubscriptionManager.CB_ETWS_TEST_ALERT + " INTEGER DEFAULT 0,"
+                    + SubscriptionManager.CB_CHANNEL_50_ALERT + " INTEGER DEFAULT 1,"
+                    + SubscriptionManager.CB_CMAS_TEST_ALERT + " INTEGER DEFAULT 0,"
+                    + SubscriptionManager.CB_OPT_OUT_DIALOG + " INTEGER DEFAULT 1,"
+                    + SubscriptionManager.ENHANCED_4G_MODE_ENABLED + " INTEGER DEFAULT -1,"
+                    + SubscriptionManager.VT_IMS_ENABLED + " INTEGER DEFAULT -1,"
+                    + SubscriptionManager.WFC_IMS_ENABLED + " INTEGER DEFAULT -1,"
+                    + SubscriptionManager.WFC_IMS_MODE + " INTEGER DEFAULT -1,"
+                    + SubscriptionManager.WFC_IMS_ROAMING_MODE + " INTEGER DEFAULT -1,"
+                    + SubscriptionManager.WFC_IMS_ROAMING_ENABLED + " INTEGER DEFAULT -1,"
+                    + SubscriptionManager.IS_OPPORTUNISTIC + " INTEGER DEFAULT 0,"
+                    + SubscriptionManager.GROUP_UUID + " TEXT,"
+                    + SubscriptionManager.IS_METERED + " INTEGER DEFAULT 1,"
+                    + SubscriptionManager.ISO_COUNTRY_CODE + " TEXT"
+                    + ");";
+        }
+
+        @Override
+        public void onCreate(SQLiteDatabase db) {
+            // TODO: set up other tables when needed.
+            // set up the siminfo table
+            Log.d(TAG, "InMemoryTelephonyProviderDbHelper onCreate creating the siminfo table");
+            db.execSQL(getStringForSimInfoTableCreation("siminfo"));
+        }
+
+        @Override
+        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+            Log.d(TAG, "InMemoryTelephonyProviderDbHelper onUpgrade doing nothing");
+            return;
+        }
+    }
+
+    @Override
+    public Uri insert(Uri uri, ContentValues values) {
+        SQLiteDatabase db = mDbHelper.getWritableDatabase();
+        long id = db.insert("siminfo", null, values);
+        return ContentUris.withAppendedId(SubscriptionManager.CONTENT_URI, id);
+    }
+
+    @Override
+    public synchronized int delete(Uri url, String where, String[] whereArgs) {
+        return mDbHelper.getWritableDatabase().delete("siminfo", where, whereArgs);
+    }
+
+    @Override
+    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+            String sortOrder) {
+        return mDbHelper.getReadableDatabase().query("siminfo", projection, selection,
+                selectionArgs, null, null, sortOrder);
+    }
+
+    @Override
+    public Bundle call(String method, String request, Bundle args) {
+        return null;
+    }
+
+    @Override
+    public final int update(Uri uri, ContentValues values, String where, String[] selectionArgs) {
+        // handle URI with appended subId
+        final int urlSimInfoSubId = 0;
+        UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
+        matcher.addURI("telephony", "siminfo/#", urlSimInfoSubId);
+        if (matcher.match(uri) == urlSimInfoSubId) {
+            where = BaseColumns._ID + "=" + uri.getLastPathSegment();
+        }
+
+        int count = mDbHelper.getWritableDatabase().update("siminfo", values, where,
+                selectionArgs);
+        return count;
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaCallTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaCallTrackerTest.java
index f3066f4..d12b6cc 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaCallTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaCallTrackerTest.java
@@ -19,6 +19,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.doReturn;
@@ -271,6 +272,7 @@
         assertEquals(0, mCTUT.mRingingCall.getConnections().size());
     }
 
+    @FlakyTest /* flakes 2.57% of the time */
     @Test
     @SmallTest
     public void testMTCallReject() {
@@ -434,5 +436,40 @@
         // verify that the active call is disconnected
         verify(mConnection).onDisconnect(DisconnectCause.ERROR_UNSPECIFIED);
     }
+
+    @Test
+    @SmallTest
+    public void testDispatchCsCallRadioTech() {
+        // fake connection
+        mCTUT.mConnections[0] = mConnection;
+
+        // dispatch umts
+        mCTUT.dispatchCsCallRadioTech(ServiceState.RIL_RADIO_TECHNOLOGY_UMTS);
+        // verify that call radio tech is set
+        verify(mConnection).setCallRadioTech(ServiceState.RIL_RADIO_TECHNOLOGY_UMTS);
+
+        // dispatch unknown
+        mCTUT.dispatchCsCallRadioTech(ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN);
+        // verify that call radio tech is set
+        verify(mConnection).setCallRadioTech(ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN);
+    }
+
+    @Test
+    @SmallTest
+    public void testCantCallOtaspInProgress() {
+        mDialString = "*22899";
+        testMOCallDial();
+        waitForHandlerAction(mSimulatedCommands.getHandler(), 5000);
+        mSimulatedCommands.progressConnectingToActive();
+        waitForHandlerAction(mSimulatedCommands.getHandler(), 5000);
+        // Try to place another call.
+        try {
+            mCTUT.dial("650-555-1212");
+        } catch (CallStateException cse) {
+            assertEquals(CallStateException.ERROR_OTASP_PROVISIONING_IN_PROCESS, cse.getError());
+            return;
+        }
+        fail("Expected otasp call state exception");
+    }
 }
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaConnectionTest.java b/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaConnectionTest.java
index 1a4d619..6fc9156 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaConnectionTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaConnectionTest.java
@@ -71,6 +71,19 @@
     }
 
     @Test @SmallTest
+    public void testOriginalDialString(){
+        doReturn(PhoneConstants.PHONE_TYPE_CDMA).when(mPhone).getPhoneType();
+        connection = new GsmCdmaConnection(mPhone, "+8610000", mCT, null,
+                false /*isEmergencyCall*/);
+        assertEquals("+8610000", connection.getOrigDialString());
+
+        doReturn(PhoneConstants.PHONE_TYPE_GSM).when(mPhone).getPhoneType();
+        connection = new GsmCdmaConnection(mPhone, "+8610000", mCT, null,
+                false /*isEmergencyCall*/);
+        assertEquals("+8610000", connection.getOrigDialString());
+    }
+
+    @Test @SmallTest
     public void testSanityGSM() {
         connection = new GsmCdmaConnection(mPhone, String.format(
                 "+1 (700).555-41NN%c1234", PhoneNumberUtils.PAUSE), mCT, null,
@@ -102,8 +115,7 @@
         assertEquals(PhoneConstants.PRESENTATION_ALLOWED, connection.getNumberPresentation());
         assertFalse(connection.isMultiparty());
         assertNotNull(connection.getRemainingPostDialString());
-        /* CDMA phone type dont have origDialString */
-        assertNull(connection.getOrigDialString());
+        assertEquals("+1 (700).555-41NN,1234", connection.getOrigDialString());
     }
 
     @Test @SmallTest
diff --git a/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java b/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java
index 60bc61f..cb5309b 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java
@@ -143,6 +143,7 @@
         mPhoneUT.removeCallbacksAndMessages(null);
         mPhoneUT = null;
         mGsmCdmaPhoneTestHandler.quit();
+        mGsmCdmaPhoneTestHandler.join();
         super.tearDown();
     }
 
@@ -238,40 +239,19 @@
         CellLocation cellLocation = new GsmCellLocation();
         WorkSource workSource = new WorkSource(Process.myUid(),
             mContext.getPackageName());
-        doReturn(cellLocation).when(mSST).getCellLocation(workSource);
-        assertEquals(cellLocation, mPhoneUT.getCellLocation(workSource));
+        doReturn(cellLocation).when(mSST).getCellLocation();
+        assertEquals(cellLocation, mPhoneUT.getCellLocation());
 
         // Switch to CDMA
         switchToCdma();
 
         CdmaCellLocation cdmaCellLocation = new CdmaCellLocation();
-        cdmaCellLocation.setCellLocationData(0, 0, 0, 0, 0);
-        mSST.mCellLoc = cdmaCellLocation;
-
-        /*
-        LOCATION_MODE is a special case in SettingsProvider. Adding the special handling in mock
-        content provider is probably not worth the effort; it will also tightly couple tests with
-        SettingsProvider implementation.
-        // LOCATION_MODE_ON
-        Settings.Secure.putInt(mContext.getContentResolver(),
-                Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_HIGH_ACCURACY);
-        waitForMs(50);
-        CdmaCellLocation actualCellLocation = (CdmaCellLocation) mPhoneUT.getCellLocation();
-        assertEquals(0, actualCellLocation.getBaseStationLatitude());
-        assertEquals(0, actualCellLocation.getBaseStationLongitude());
-
-        // LOCATION_MODE_OFF
-        Settings.Secure.putInt(TestApplication.getAppContext().getContentResolver(),
-                Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_OFF);
-        waitForMs(50);
-        */
+        doReturn(cdmaCellLocation).when(mSST).getCellLocation();
 
         CdmaCellLocation actualCellLocation =
-                (CdmaCellLocation) mPhoneUT.getCellLocation(workSource);
-        assertEquals(CdmaCellLocation.INVALID_LAT_LONG,
-                actualCellLocation.getBaseStationLatitude());
-        assertEquals(CdmaCellLocation.INVALID_LAT_LONG,
-                actualCellLocation.getBaseStationLongitude());
+                (CdmaCellLocation) mPhoneUT.getCellLocation();
+
+        assertEquals(actualCellLocation, cdmaCellLocation);
     }
 
     @Test
@@ -957,4 +937,31 @@
         assertEquals(EVENT_SET_ICC_LOCK_ENABLED, message.what);
         assertTrue(ret.exception != null);
     }
+
+    @Test
+    @SmallTest
+    public void testGetCsCallRadioTech() {
+        ServiceState ss = new ServiceState();
+        mSST.mSS = ss;
+
+        // vrs in-service, vrat umts, expected umts
+        ss.setVoiceRegState(ServiceState.STATE_IN_SERVICE);
+        ss.setRilVoiceRadioTechnology(ServiceState.RIL_RADIO_TECHNOLOGY_UMTS);
+        assertEquals(mPhoneUT.getCsCallRadioTech(), ServiceState.RIL_RADIO_TECHNOLOGY_UMTS);
+
+        // vrs oos, vrat umts, expected unknown
+        ss.setVoiceRegState(ServiceState.STATE_OUT_OF_SERVICE);
+        ss.setRilVoiceRadioTechnology(ServiceState.RIL_RADIO_TECHNOLOGY_UMTS);
+        assertEquals(mPhoneUT.getCsCallRadioTech(), ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN);
+
+        // vrs in-service, vrat lte, expected unknown
+        ss.setVoiceRegState(ServiceState.STATE_IN_SERVICE);
+        ss.setRilVoiceRadioTechnology(ServiceState.RIL_RADIO_TECHNOLOGY_LTE);
+        assertEquals(mPhoneUT.getCsCallRadioTech(), ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN);
+
+        // vrs in-service, vrat iwlan, expected unknown
+        ss.setVoiceRegState(ServiceState.STATE_IN_SERVICE);
+        ss.setRilVoiceRadioTechnology(ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN);
+        assertEquals(mPhoneUT.getCsCallRadioTech(), ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN);
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ImsSmsDispatcherTest.java b/tests/telephonytests/src/com/android/internal/telephony/ImsSmsDispatcherTest.java
index f052a9a..76ead95 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ImsSmsDispatcherTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ImsSmsDispatcherTest.java
@@ -17,77 +17,129 @@
 package com.android.internal.telephony;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doReturn;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
-import android.os.HandlerThread;
+import android.os.Looper;
+import android.telephony.SmsMessage;
+import android.telephony.ims.stub.ImsSmsImplBase;
 import android.test.suitebuilder.annotation.SmallTest;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 
+import java.util.HashMap;
+
 public class ImsSmsDispatcherTest extends TelephonyTest {
     @Mock private SmsDispatchersController mSmsDispatchersController;
     @Mock private SMSDispatcher.SmsTracker mSmsTracker;
+    private HashMap<String, Object> mTrackerData;
     private ImsSmsDispatcher mImsSmsDispatcher;
-    private ImsSmsDispatcherTestHandler mImsSmsDispatcherTestHandler;
-
-
-    private class ImsSmsDispatcherTestHandler extends HandlerThread {
-
-        private ImsSmsDispatcherTestHandler(String name) {
-            super(name);
-        }
-
-        @Override
-        public void onLooperPrepared() {
-            mImsSmsDispatcher = spy(new ImsSmsDispatcher(mPhone, mSmsDispatchersController));
-            setReady(true);
-        }
-    }
 
     @Before
     public void setUp() throws Exception {
         super.setUp(getClass().getSimpleName());
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
+        }
 
-        mImsSmsDispatcherTestHandler = new ImsSmsDispatcherTestHandler(TAG);
-        mImsSmsDispatcherTestHandler.start();
-        waitUntilReady();
+        mImsSmsDispatcher = spy(new ImsSmsDispatcher(mPhone, mSmsDispatchersController));
+        when(mSmsDispatchersController.isIms()).thenReturn(true);
+
+        mTrackerData = new HashMap<>(1);
+        when(mSmsTracker.getData()).thenReturn(mTrackerData);
     }
 
-    @Test @SmallTest
-    public void testSendSms() {
+    /**
+     * Send an SMS and verify that the token and PDU is correct.
+     */
+    @Test
+    @SmallTest
+    public void testSendSms() throws Exception {
         int token = mImsSmsDispatcher.mNextToken.get();
         int trackersSize = mImsSmsDispatcher.mTrackers.size();
 
+        byte[] pdu = com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(null,
+                "+15555551212", "Test", false).encodedMessage;
+        mTrackerData.put("pdu", pdu);
+        when(mImsManager.getSmsFormat()).thenReturn(SmsMessage.FORMAT_3GPP);
+        when(mPhone.getPhoneType()).thenReturn(PhoneConstants.PHONE_TYPE_GSM);
+
+        //Send an SMS
         mImsSmsDispatcher.sendSms(mSmsTracker);
 
         assertEquals(token + 1, mImsSmsDispatcher.mNextToken.get());
         assertEquals(trackersSize + 1, mImsSmsDispatcher.mTrackers.size());
+        verify(mImsManager).sendSms(eq(token + 1), anyInt(), eq(SmsMessage.FORMAT_3GPP),
+                nullable(String.class), eq(false), eq(pdu));
     }
 
-    @Test @SmallTest
-    public void testFallbackToPstn() {
-        int token = 0;
+    /**
+     * Ensure that when sending a GSM text fails with SEND_STATUS_ERROR_FALLBACK, retry with
+     * a non-zero retry count (set TP-RD).
+     */
+    @Test
+    @SmallTest
+    public void testFallbackGsmRetry() throws Exception {
+        int token = mImsSmsDispatcher.mNextToken.get();
+        mTrackerData.put("pdu", com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(null,
+                "+15555551212", "Test", false).encodedMessage);
         mImsSmsDispatcher.mTrackers.put(token, mSmsTracker);
-        doNothing().when(mSmsDispatchersController).sendRetrySms(mSmsTracker);
+        when(mPhone.getPhoneType()).thenReturn(PhoneConstants.PHONE_TYPE_GSM);
 
-        mImsSmsDispatcher.fallbackToPstn(token, mSmsTracker);
+        // Fallback over GSM
+        mImsSmsDispatcher.getSmsListener().onSendSmsResult(token, 0,
+                ImsSmsImplBase.SEND_STATUS_ERROR_FALLBACK, 0);
+        ArgumentCaptor<SMSDispatcher.SmsTracker> captor =
+                ArgumentCaptor.forClass(SMSDispatcher.SmsTracker.class);
+        // Ensure GsmSmsDispatcher calls sendSms
+        verify(mSmsDispatchersController).sendRetrySms(captor.capture());
 
-        verify(mSmsDispatchersController).sendRetrySms(mSmsTracker);
-        assertNull(mImsSmsDispatcher.mTrackers.get(token));
+        assertNotNull(captor.getValue());
+        assertTrue(captor.getValue().mRetryCount > 0);
+
+    }
+
+    /**
+     * Ensure that when an outgoing SMS has failed over IMS with SEND_STATUS_ERROR_RETRY, it is
+     * sent over the IMS channel again with the TP-RD bit set.
+     */
+    @Test
+    @SmallTest
+    public void testErrorImsRetry() throws Exception {
+        int token = mImsSmsDispatcher.mNextToken.get();
+        mTrackerData.put("pdu", com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(null,
+                "+15555551212", "Test", false).encodedMessage);
+        when(mImsManager.getSmsFormat()).thenReturn(SmsMessage.FORMAT_3GPP);
+        mImsSmsDispatcher.mTrackers.put(token, mSmsTracker);
+        when(mPhone.getPhoneType()).thenReturn(PhoneConstants.PHONE_TYPE_GSM);
+
+        // Fallback over GSM
+        mImsSmsDispatcher.getSmsListener().onSendSmsResult(token, 0,
+                ImsSmsImplBase.SEND_STATUS_ERROR_RETRY, 0);
+
+        // Make sure retry bit set
+        ArgumentCaptor<byte[]> byteCaptor = ArgumentCaptor.forClass(byte[].class);
+        verify(mImsManager).sendSms(eq(token + 1), anyInt(), nullable(String.class),
+                nullable(String.class), eq(true), byteCaptor.capture());
+        byte[] pdu = byteCaptor.getValue();
+        // Make sure that TP-RD is set for this message
+        assertNotNull(pdu);
+        assertEquals(0x04, (pdu[0] & 0x04));
     }
 
     @After
     public void tearDown() throws Exception {
         mImsSmsDispatcher = null;
-        mImsSmsDispatcherTestHandler.quit();
         super.tearDown();
     }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/LocaleTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/LocaleTrackerTest.java
index 02f78ab..ce2d62f 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/LocaleTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/LocaleTrackerTest.java
@@ -16,10 +16,11 @@
 
 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.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -28,6 +29,7 @@
 import android.net.wifi.WifiManager;
 import android.os.AsyncResult;
 import android.os.HandlerThread;
+import android.os.Message;
 import android.telephony.CellIdentityGsm;
 import android.telephony.CellInfoGsm;
 import android.telephony.ServiceState;
@@ -47,7 +49,6 @@
     private static final String COUNTRY_CODE_UNAVAILABLE = "";
 
     private LocaleTracker mLocaleTracker;
-    private LocaleTrackerTestHandler mLocaleTrackerTestHandler;
 
     private CellInfoGsm mCellInfo;
     private WifiManager mWifiManager;
@@ -60,16 +61,23 @@
 
         @Override
         public void onLooperPrepared() {
-            mLocaleTracker = new LocaleTracker(mPhone, this.getLooper());
+            mLocaleTracker = new LocaleTracker(mPhone, mNitzStateMachine, this.getLooper());
             setReady(true);
         }
     }
 
+    private LocaleTrackerTestHandler mHandlerThread;
+
     @Before
     public void setUp() throws Exception {
         logd("LocaleTrackerTest +Setup!");
         super.setUp(getClass().getSimpleName());
 
+        mHandlerThread = new LocaleTrackerTestHandler("LocaleTrackerTestHandler");
+        mHandlerThread.start();
+        waitUntilReady();
+
+
         // 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);
@@ -77,35 +85,44 @@
         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();
+        doAnswer(invocation -> {
+            Message m = invocation.getArgument(1);
+            AsyncResult.forMessage(m, Arrays.asList(mCellInfo), null);
+            m.sendToTarget();
+            return null; }).when(mPhone).requestCellInfoUpdate(any(), any());
 
-        mLocaleTrackerTestHandler = new LocaleTrackerTestHandler(getClass().getSimpleName());
-        mLocaleTrackerTestHandler.start();
-        waitUntilReady();
         logd("LocaleTrackerTest -Setup!");
     }
 
     @After
     public void tearDown() throws Exception {
-        mLocaleTracker.removeCallbacksAndMessages(null);
-        mLocaleTrackerTestHandler.quit();
+        mHandlerThread.quit();
+        mHandlerThread.join();
         super.tearDown();
     }
 
+    private void sendServiceState(int state) {
+        ServiceState ss = new ServiceState();
+        ss.setState(state);
+        AsyncResult ar = new AsyncResult(null, ss, null);
+        mLocaleTracker.sendMessage(
+                mLocaleTracker.obtainMessage(2 /*SERVICE_STATE_CHANGED*/, ar));
+        waitForHandlerAction(mLocaleTracker, 100);
+    }
+
+    private void sendGsmCellInfo() {
+        // send an unsol cell info
+        mLocaleTracker
+                .obtainMessage(4 /*UNSOL_CELL_INFO*/,
+                        new AsyncResult(null, Arrays.asList(mCellInfo), null))
+                .sendToTarget();
+        waitForHandlerAction(mLocaleTracker, 100);
+    }
+
     @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);
-    }
-
-    @Test
-    @SmallTest
-    public void testUpdateOperatorNumericAsync() throws Exception {
-        mLocaleTracker.updateOperatorNumericAsync(US_MCC + FAKE_MNC);
-        waitForMs(100);
+        mLocaleTracker.updateOperatorNumeric(US_MCC + FAKE_MNC);
         assertEquals(US_COUNTRY_CODE, mLocaleTracker.getCurrentCountry());
         verify(mWifiManager).setCountryCode(US_COUNTRY_CODE);
     }
@@ -113,72 +130,74 @@
     @Test
     @SmallTest
     public void testNoSim() throws Exception {
-        mLocaleTracker.updateOperatorNumericAsync("");
-        waitForHandlerAction(mLocaleTracker, 100);
+        mLocaleTracker.updateOperatorNumeric("");
+        sendGsmCellInfo();
+        sendServiceState(ServiceState.STATE_EMERGENCY_ONLY);
         assertEquals(US_COUNTRY_CODE, mLocaleTracker.getCurrentCountry());
         verify(mWifiManager).setCountryCode(US_COUNTRY_CODE);
+        assertTrue(mLocaleTracker.isTracking());
     }
 
     @Test
     @SmallTest
     public void testBootupInAirplaneModeOn() throws Exception {
-        doReturn(false).when(mSST).getDesiredPowerState();
-        mLocaleTracker.updateOperatorNumericAsync("");
-        waitForHandlerAction(mLocaleTracker, 100);
+        mLocaleTracker.updateOperatorNumeric("");
+        sendServiceState(ServiceState.STATE_POWER_OFF);
         assertEquals(COUNTRY_CODE_UNAVAILABLE, mLocaleTracker.getCurrentCountry());
         verify(mWifiManager).setCountryCode(COUNTRY_CODE_UNAVAILABLE);
+        assertFalse(mLocaleTracker.isTracking());
     }
 
     @Test
     @SmallTest
-    public void testTogglingAirplaneMode() throws Exception {
-        mLocaleTracker.updateOperatorNumericSync(US_MCC + FAKE_MNC);
+    public void testToggleAirplaneModeOn() throws Exception {
+        sendServiceState(ServiceState.STATE_IN_SERVICE);
+        mLocaleTracker.updateOperatorNumeric(US_MCC + FAKE_MNC);
         assertEquals(US_COUNTRY_CODE, mLocaleTracker.getCurrentCountry());
         verify(mWifiManager).setCountryCode(US_COUNTRY_CODE);
+        assertFalse(mLocaleTracker.isTracking());
 
-        doReturn(false).when(mSST).getDesiredPowerState();
-        mLocaleTracker.updateOperatorNumericAsync("");
+        mLocaleTracker.updateOperatorNumeric("");
         waitForHandlerAction(mLocaleTracker, 100);
         assertEquals(COUNTRY_CODE_UNAVAILABLE, mLocaleTracker.getCurrentCountry());
-        verify(mWifiManager).setCountryCode(COUNTRY_CODE_UNAVAILABLE);
-
-        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);
+        verify(mWifiManager, times(2)).setCountryCode(COUNTRY_CODE_UNAVAILABLE);
+        sendServiceState(ServiceState.STATE_POWER_OFF);
+        assertFalse(mLocaleTracker.isTracking());
     }
 
     @Test
     @SmallTest
-    public void testCellInfoUnavailableRetry() throws Exception {
-        doReturn(null).when(mPhone).getAllCellInfo(isNull());
-        mLocaleTracker.updateOperatorNumericAsync("");
+    public void testToggleAirplaneModeOff() throws Exception {
+        sendServiceState(ServiceState.STATE_POWER_OFF);
+        mLocaleTracker.updateOperatorNumeric("");
         waitForHandlerAction(mLocaleTracker, 100);
         assertEquals(COUNTRY_CODE_UNAVAILABLE, mLocaleTracker.getCurrentCountry());
         verify(mWifiManager).setCountryCode(COUNTRY_CODE_UNAVAILABLE);
+        assertFalse(mLocaleTracker.isTracking());
 
-        doReturn(Arrays.asList(mCellInfo)).when(mPhone).getAllCellInfo(isNull());
-        waitForHandlerActionDelayed(mLocaleTracker, 100, 2500);
+        sendServiceState(ServiceState.STATE_OUT_OF_SERVICE);
+        waitForHandlerAction(mLocaleTracker, 100);
+        assertTrue(mLocaleTracker.isTracking());
+        waitForHandlerAction(mLocaleTracker, 100);
         assertEquals(US_COUNTRY_CODE, mLocaleTracker.getCurrentCountry());
-        verify(mWifiManager).setCountryCode(US_COUNTRY_CODE);
     }
 
     @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);
+    public void testGetCellInfoDelayTime() throws Exception {
+        assertEquals(2000, LocaleTracker.getCellInfoDelayTime(0));
+        assertEquals(2000, LocaleTracker.getCellInfoDelayTime(1));
+        assertEquals(4000, LocaleTracker.getCellInfoDelayTime(2));
+        assertEquals(8000, LocaleTracker.getCellInfoDelayTime(3));
+        assertEquals(16000, LocaleTracker.getCellInfoDelayTime(4));
+        assertEquals(32000, LocaleTracker.getCellInfoDelayTime(5));
+        assertEquals(64000, LocaleTracker.getCellInfoDelayTime(6));
+        assertEquals(128000, LocaleTracker.getCellInfoDelayTime(7));
+        assertEquals(256000, LocaleTracker.getCellInfoDelayTime(8));
+        assertEquals(512000, LocaleTracker.getCellInfoDelayTime(9));
 
-        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);
+        for (int i = 10; i <= 2000; i++) {
+            assertEquals(600000, LocaleTracker.getCellInfoDelayTime(i));
+        }
     }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/MccTableTest.java b/tests/telephonytests/src/com/android/internal/telephony/MccTableTest.java
index de98c67..ca398bc 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/MccTableTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/MccTableTest.java
@@ -19,12 +19,17 @@
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import org.junit.Ignore;
+
 import java.util.Locale;
 
+// TODO try using InstrumentationRegistry.getContext() instead of the default
+// AndroidTestCase context
 public class MccTableTest extends AndroidTestCase {
     private final static String LOG_TAG = "GSM";
 
     @SmallTest
+    @Ignore
     public void testTimeZone() throws Exception {
         assertEquals("Europe/Paris", MccTable.defaultTimeZoneForMcc(208));
         assertEquals("Europe/Vienna", MccTable.defaultTimeZoneForMcc(232));
@@ -41,6 +46,7 @@
     }
 
     @SmallTest
+    @Ignore
     public void testCountryCode() throws Exception {
         assertEquals("lu", MccTable.countryCodeForMcc(270));
         assertEquals("gr", MccTable.countryCodeForMcc(202));
@@ -53,6 +59,7 @@
     }
 
     @SmallTest
+    @Ignore
     public void testLang() throws Exception {
         assertEquals("en", MccTable.defaultLanguageForMcc(311));
         assertEquals("de", MccTable.defaultLanguageForMcc(232));
@@ -64,6 +71,7 @@
     }
 
     @SmallTest
+    @Ignore
     public void testLang_India() throws Exception {
         assertEquals("en", MccTable.defaultLanguageForMcc(404));
         assertEquals("en", MccTable.defaultLanguageForMcc(405));
@@ -71,6 +79,7 @@
     }
 
     @SmallTest
+    @Ignore
     public void testLocale() throws Exception {
         assertEquals(Locale.forLanguageTag("en-CA"),
                 MccTable.getLocaleFromMcc(getContext(), 302, null));
@@ -87,6 +96,7 @@
     }
 
     @SmallTest
+    @Ignore
     public void testSmDigits() throws Exception {
         assertEquals(3, MccTable.smallestDigitsMccForMnc(312));
         assertEquals(2, MccTable.smallestDigitsMccForMnc(430));
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ModemInfoTest.java b/tests/telephonytests/src/com/android/internal/telephony/ModemInfoTest.java
new file mode 100644
index 0000000..6e646d5
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/ModemInfoTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import android.os.Parcel;
+import android.telephony.ModemInfo;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Test;
+
+public class ModemInfoTest {
+    @Test
+    @SmallTest
+    public void basicTests() throws Exception {
+        int modemId = 1;
+        int rat = 2;
+        boolean isVoiceSupported = true;
+        boolean isDataSupported = false;
+        ModemInfo modemInfo = new ModemInfo(modemId, rat, isVoiceSupported, isDataSupported);
+
+        assertEquals(modemId, modemInfo.modemId);
+        assertEquals(rat, modemInfo.rat);
+        assertEquals(isVoiceSupported, modemInfo.isVoiceSupported);
+        assertEquals(isDataSupported, modemInfo.isDataSupported);
+        assertNotEquals(modemInfo, new ModemInfo(
+                modemId + 1, rat, isVoiceSupported, isDataSupported));
+        assertNotEquals(modemInfo, new ModemInfo(
+                modemId, rat + 1, isVoiceSupported, isDataSupported));
+        assertNotEquals(modemInfo, new ModemInfo(modemId, rat, !isVoiceSupported, isDataSupported));
+        assertNotEquals(modemInfo, new ModemInfo(modemId, rat, isVoiceSupported, !isDataSupported));
+    }
+
+    @Test
+    @SmallTest
+    public void parcelReadWrite() throws Exception {
+        int modemId = 1;
+        int rat = 2;
+        boolean isVoiceSupported = true;
+        boolean isDataSupported = false;
+        ModemInfo modemInfo = new ModemInfo(modemId, rat, isVoiceSupported, isDataSupported);
+
+        Parcel parcel = Parcel.obtain();
+        modemInfo.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        ModemInfo toCompare = ModemInfo.CREATOR.createFromParcel(parcel);
+
+        assertEquals(modemId, toCompare.modemId);
+        assertEquals(rat, toCompare.rat);
+        assertEquals(isVoiceSupported, toCompare.isVoiceSupported);
+        assertEquals(isDataSupported, toCompare.isDataSupported);
+        assertEquals(modemInfo, toCompare);
+    }
+}
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/NetworkScanResultTest.java b/tests/telephonytests/src/com/android/internal/telephony/NetworkScanResultTest.java
index 47a9979..041c5f5 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/NetworkScanResultTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/NetworkScanResultTest.java
@@ -45,7 +45,6 @@
         CellSignalStrengthGsm cssg = new CellSignalStrengthGsm(5, 6, 7);
         CellInfoGsm gsm = new CellInfoGsm();
         gsm.setRegistered(true);
-        gsm.setTimeStampType(8);
         gsm.setTimeStamp(9);
         gsm.setCellIdentity(cig);
         gsm.setCellSignalStrength(cssg);
@@ -55,7 +54,6 @@
         CellSignalStrengthLte cssl = new CellSignalStrengthLte(15, 16, 17, 18, 19, 20);
         CellInfoLte lte = new CellInfoLte();
         lte.setRegistered(false);
-        lte.setTimeStampType(21);
         lte.setTimeStamp(22);
         lte.setCellIdentity(cil);
         lte.setCellSignalStrength(cssl);
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/NitzDataTest.java b/tests/telephonytests/src/com/android/internal/telephony/NitzDataTest.java
index e9d0b44..5f683c0 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/NitzDataTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/NitzDataTest.java
@@ -53,19 +53,17 @@
             assertNull(nitz.getEmulatorHostTimeZone());
         }
         {
-            NitzData nitz = NitzData.parse("15/06/20,01:02:03+8,4");
+            NitzData nitz = NitzData.parse("15/06/20,01:02:03+8,1");
             assertEquals(createUtcTime(2015, 6, 20, 1, 2, 3), nitz.getCurrentTimeInMillis());
             assertEquals(TimeUnit.MINUTES.toMillis(8 * 15), nitz.getLocalOffsetMillis());
-            assertEquals(TimeUnit.MINUTES.toMillis(4 * 15),
-                    nitz.getDstAdjustmentMillis().longValue());
+            assertEquals(TimeUnit.HOURS.toMillis(1), nitz.getDstAdjustmentMillis().longValue());
             assertNull(nitz.getEmulatorHostTimeZone());
         }
         {
-            NitzData nitz = NitzData.parse("15/06/20,01:02:03-8,4");
+            NitzData nitz = NitzData.parse("15/06/20,01:02:03-8,1");
             assertEquals(createUtcTime(2015, 6, 20, 1, 2, 3), nitz.getCurrentTimeInMillis());
             assertEquals(TimeUnit.MINUTES.toMillis(-8 * 15), nitz.getLocalOffsetMillis());
-            assertEquals(TimeUnit.MINUTES.toMillis(4 * 15),
-                    nitz.getDstAdjustmentMillis().longValue());
+            assertEquals(TimeUnit.HOURS.toMillis(1), nitz.getDstAdjustmentMillis().longValue());
             assertNull(nitz.getEmulatorHostTimeZone());
         }
     }
@@ -90,11 +88,10 @@
 
     @Test
     public void testParse_androidEmulatorTimeZoneExtension() {
-        NitzData nitz = NitzData.parse("15/06/20,01:02:03-32,4,America!Los_Angeles");
+        NitzData nitz = NitzData.parse("15/06/20,01:02:03-32,1,America!Los_Angeles");
         assertEquals(createUtcTime(2015, 6, 20, 1, 2, 3), nitz.getCurrentTimeInMillis());
         assertEquals(TimeUnit.MINUTES.toMillis(-32 * 15), nitz.getLocalOffsetMillis());
-        assertEquals(TimeUnit.MINUTES.toMillis(4 * 15),
-                nitz.getDstAdjustmentMillis().longValue());
+        assertEquals(TimeUnit.HOURS.toMillis(1), nitz.getDstAdjustmentMillis().longValue());
         assertEquals("America/Los_Angeles", nitz.getEmulatorHostTimeZone().getID());
     }
 
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/PhoneCapabilityTest.java b/tests/telephonytests/src/com/android/internal/telephony/PhoneCapabilityTest.java
new file mode 100644
index 0000000..84fbae1
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/PhoneCapabilityTest.java
@@ -0,0 +1,83 @@
+/*
+ * 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.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import android.os.Parcel;
+import android.telephony.ModemInfo;
+import android.telephony.PhoneCapability;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class PhoneCapabilityTest {
+    @Test
+    @SmallTest
+    public void basicTests() throws Exception {
+        int maxActiveVoiceCalls = 1;
+        int maxActiveData = 2;
+        int max5G = 3;
+        ModemInfo modemInfo = new ModemInfo(1, 2, true, false);
+        List<ModemInfo> logicalModemList = new ArrayList<>();
+        logicalModemList.add(modemInfo);
+
+        PhoneCapability capability = new PhoneCapability(maxActiveVoiceCalls, maxActiveData, max5G,
+                logicalModemList);
+
+        assertEquals(maxActiveVoiceCalls, capability.maxActiveVoiceCalls);
+        assertEquals(maxActiveData, capability.maxActiveData);
+        assertEquals(max5G, capability.max5G);
+        assertEquals(1, capability.logicalModemList.size());
+        assertEquals(modemInfo, capability.logicalModemList.get(0));
+        PhoneCapability toCompare = new PhoneCapability(
+                maxActiveVoiceCalls + 1, maxActiveData - 1, max5G, logicalModemList);
+        assertEquals(capability, new PhoneCapability(
+                maxActiveVoiceCalls, maxActiveData, max5G, logicalModemList));
+        assertNotEquals(capability, toCompare);
+    }
+
+    @Test
+    @SmallTest
+    public void parcelReadWrite() throws Exception {
+        int maxActiveVoiceCalls = 1;
+        int maxActiveData = 2;
+        int max5G = 3;
+        ModemInfo modemInfo = new ModemInfo(1, 2, true, false);
+        List<ModemInfo> logicalModemList = new ArrayList<>();
+        logicalModemList.add(modemInfo);
+
+        PhoneCapability capability = new PhoneCapability(maxActiveVoiceCalls, maxActiveData, max5G,
+                logicalModemList);
+
+        Parcel parcel = Parcel.obtain();
+        capability.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        PhoneCapability toCompare = PhoneCapability.CREATOR.createFromParcel(parcel);
+
+        assertEquals(maxActiveVoiceCalls, toCompare.maxActiveVoiceCalls);
+        assertEquals(maxActiveData, toCompare.maxActiveData);
+        assertEquals(max5G, toCompare.max5G);
+        assertEquals(1, toCompare.logicalModemList.size());
+        assertEquals(modemInfo, toCompare.logicalModemList.get(0));
+        assertEquals(capability, toCompare);
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/PhoneStateListenerExecutorTest.java b/tests/telephonytests/src/com/android/internal/telephony/PhoneStateListenerExecutorTest.java
new file mode 100644
index 0000000..40940bd
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/PhoneStateListenerExecutorTest.java
@@ -0,0 +1,129 @@
+/*
+ * 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.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.verify;
+
+import android.telephony.PhoneStateListener;
+import android.telephony.PhysicalChannelConfig;
+import android.telephony.ServiceState;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.lang.reflect.Field;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+public class PhoneStateListenerExecutorTest extends TelephonyTest {
+
+    private Executor mSimpleExecutor = new Executor() {
+        @Override
+        public void execute(Runnable r) {
+            r.run();
+        }
+    };
+
+    private PhoneStateListener mPhoneStateListenerUT;
+
+    private boolean mUserMobileDataState = false;
+    private List<PhysicalChannelConfig> mPhysicalChannelConfigs;
+
+    @Before
+    public void setUp() throws Exception {
+        this.setUp(this.getClass().getSimpleName());
+
+        mPhoneStateListenerUT = new PhoneStateListener(mSimpleExecutor) {
+            @Override
+            public void onServiceStateChanged(ServiceState serviceState) {
+                logd("Service State Changed");
+                mServiceState.setVoiceRegState(serviceState.getVoiceRegState());
+                mServiceState.setDataRegState(serviceState.getDataRegState());
+            }
+
+            @Override
+            public void onUserMobileDataStateChanged(boolean state) {
+                logd("User Mobile Data State Changed");
+                mUserMobileDataState = true;
+            }
+
+            @Override
+            public void onPhysicalChannelConfigurationChanged(
+                    List<PhysicalChannelConfig> configs) {
+                logd("PhysicalChannelConfig Changed");
+                mPhysicalChannelConfigs = configs;
+            }
+        };
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    @Test @SmallTest
+    public void testTriggerServiceStateChanged() throws Exception {
+        Field field = PhoneStateListener.class.getDeclaredField("callback");
+        field.setAccessible(true);
+
+        ServiceState ss = new ServiceState();
+        ss.setDataRegState(ServiceState.STATE_IN_SERVICE);
+        ss.setVoiceRegState(ServiceState.STATE_EMERGENCY_ONLY);
+
+        ((IPhoneStateListener) field.get(mPhoneStateListenerUT)).onServiceStateChanged(ss);
+
+        verify(mServiceState).setDataRegState(ServiceState.STATE_IN_SERVICE);
+        verify(mServiceState).setVoiceRegState(ServiceState.STATE_EMERGENCY_ONLY);
+    }
+
+    @Test @SmallTest
+    public void testTriggerUserMobileDataStateChanged() throws Exception {
+        Field field = PhoneStateListener.class.getDeclaredField("callback");
+        field.setAccessible(true);
+
+        assertFalse(mUserMobileDataState);
+
+        ((IPhoneStateListener) field.get(mPhoneStateListenerUT)).onUserMobileDataStateChanged(true);
+
+        assertTrue(mUserMobileDataState);
+    }
+
+    @Test @SmallTest
+    public void testTriggerPhysicalChannelConfigurationChanged() throws Exception {
+        Field field = PhoneStateListener.class.getDeclaredField("callback");
+        field.setAccessible(true);
+
+        assertNull(mPhysicalChannelConfigs);
+
+        PhysicalChannelConfig config = new PhysicalChannelConfig.Builder()
+                .setCellConnectionStatus(PhysicalChannelConfig.CONNECTION_PRIMARY_SERVING)
+                .setCellBandwidthDownlinkKhz(2000 /* bandwidth */)
+                .build();
+
+        List<PhysicalChannelConfig> configs = Collections.singletonList(config);
+
+        ((IPhoneStateListener) field.get(mPhoneStateListenerUT))
+            .onPhysicalChannelConfigurationChanged(configs);
+
+        assertTrue(mPhysicalChannelConfigs.equals(configs));
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/PhoneStateListenerTest.java b/tests/telephonytests/src/com/android/internal/telephony/PhoneStateListenerTest.java
index 1d4b173..729260e 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/PhoneStateListenerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/PhoneStateListenerTest.java
@@ -128,8 +128,10 @@
 
         assertNull(mPhysicalChannelConfigs);
 
-        PhysicalChannelConfig config = new PhysicalChannelConfig(
-                PhysicalChannelConfig.CONNECTION_PRIMARY_SERVING, 20000 /* bandwidth */);
+        PhysicalChannelConfig config = new PhysicalChannelConfig.Builder()
+                .setCellConnectionStatus(PhysicalChannelConfig.CONNECTION_PRIMARY_SERVING)
+                .setCellBandwidthDownlinkKhz(20000 /* bandwidth */)
+                .build();
 
         List<PhysicalChannelConfig> configs = Collections.singletonList(config);
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/PhoneSwitcherTest.java b/tests/telephonytests/src/com/android/internal/telephony/PhoneSwitcherTest.java
index b5ec320..56bf746 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/PhoneSwitcherTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/PhoneSwitcherTest.java
@@ -16,257 +16,123 @@
 
 package com.android.internal.telephony;
 
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
 import android.content.Context;
+import android.content.Intent;
 import android.net.ConnectivityManager;
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
 import android.net.StringNetworkSpecifier;
-import android.os.AsyncResult;
-import android.os.Binder;
 import android.os.Handler;
 import android.os.HandlerThread;
-import android.os.Looper;
 import android.os.Message;
-import android.telephony.Rlog;
-import android.test.AndroidTestCase;
+import android.os.Messenger;
+import android.telephony.SubscriptionManager;
 import android.test.suitebuilder.annotation.SmallTest;
 
-import com.android.internal.telephony.mocks.ConnectivityServiceMock;
-import com.android.internal.telephony.mocks.SubscriptionControllerMock;
-import com.android.internal.telephony.mocks.TelephonyRegistryMock;
-import com.android.internal.telephony.test.SimulatedCommands;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
 
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicReference;
+public class PhoneSwitcherTest extends TelephonyTest {
+    private static final String[] sNetworkAttributes = new String[] {
+            "mobile,0,0,0,-1,true", "mobile_mms,2,0,2,60000,true",
+            "mobile_supl,3,0,2,60000,true", "mobile_dun,4,0,2,60000,true",
+            "mobile_hipri,5,0,3,60000,true", "mobile_fota,10,0,2,60000,true",
+            "mobile_ims,11,0,2,60000,true", "mobile_cbs,12,0,2,60000,true",
+            "mobile_ia,14,0,2,-1,true", "mobile_emergency,15,0,2,-1,true"};
 
-public class PhoneSwitcherTest extends AndroidTestCase {
-    private final static String LOG_TAG = "PhoneSwitcherTest";
+    private static final int ACTIVE_PHONE_SWITCH = 1;
 
-    static void failAndStack(String str) {
-        fail(str + "\n" + SubscriptionMonitorTest.stack());
+    @Mock
+    private ITelephonyRegistry.Stub mTelRegistryMock;
+    @Mock
+    private CommandsInterface mCommandsInterface0;
+    @Mock
+    private CommandsInterface mCommandsInterface1;
+    @Mock
+    private Phone mPhone2; // mPhone as phone 1 is already defined in TelephonyTest.
+    @Mock
+    private Handler mActivePhoneSwitchHandler;
+
+    // The thread that mPhoneSwitcher will handle events in.
+    private HandlerThread mHandlerThread;
+    private PhoneSwitcher mPhoneSwitcher;
+    private IOnSubscriptionsChangedListener mSubChangedListener;
+    private ConnectivityManager mConnectivityManager;
+    // The messenger of PhoneSwitcher used to receive network requests.
+    private Messenger mNetworkFactoryMessenger = null;
+    private int mDefaultDataSub = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+    private CommandsInterface[] mCommandsInterfaces;
+    private int[][] mSlotIndexToSubId;
+    private boolean[] mDataAllowed;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp(getClass().getSimpleName());
     }
 
-    static String stack() {
-        StringBuilder sb = new StringBuilder();
-        for(StackTraceElement e : Thread.currentThread().getStackTrace()) {
-            sb.append(e.toString()).append("\n");
-        }
-        return sb.toString();
-    }
-
-    private static class TestHandler extends Handler {
-        public final static int ACTIVE_PHONE_SWITCH = 1;
-        public final static int IN_IDLE = 2;
-
-        HandlerThread handlerThread;
-
-        public TestHandler(Looper looper) {
-            super(looper);
-        }
-
-        public void die() {
-            if(handlerThread != null) {
-                handlerThread.quit();
-                handlerThread = null;
-            }
-        }
-
-        public void blockTilIdle() {
-            Object lock = new Object();
-            synchronized (lock) {
-                Message msg = this.obtainMessage(IN_IDLE, lock);
-                msg.sendToTarget();
-                try {
-                    lock.wait();
-                } catch (InterruptedException e) {}
-            }
-        }
-
-        public static TestHandler makeHandler() {
-            final HandlerThread handlerThread = new HandlerThread("TestHandler");
-            handlerThread.start();
-            final TestHandler result = new TestHandler(handlerThread.getLooper());
-            result.handlerThread = handlerThread;
-            return result;
-        }
-
-        private boolean objectEquals(Object o1, Object o2) {
-            if (o1 == null) return (o2 == null);
-            return o1.equals(o2);
-        }
-
-        private void failAndStack(String str) {
-            SubscriptionMonitorTest.failAndStack(str);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case ACTIVE_PHONE_SWITCH: {
-                    AsyncResult ar = (AsyncResult)(msg.obj);
-                    if (objectEquals(ar.userObj, mActivePhoneSwitchObject.get()) == false) {
-                        failAndStack("Active Phone Switch object is incorrect!");
-                    }
-                    int count = mActivePhoneSwitchCount.incrementAndGet();
-                    Rlog.d(LOG_TAG, "ACTIVE_PHONE_SWITCH, inc to " + count);
-                    break;
-                }
-                case IN_IDLE: {
-                    Object lock = msg.obj;
-                    synchronized (lock) {
-                        lock.notify();
-                    }
-                    break;
-                }
-            }
-        }
-
-        private final AtomicInteger mActivePhoneSwitchCount = new AtomicInteger(0);
-        private final AtomicReference<Object> mActivePhoneSwitchObject =
-                new AtomicReference<Object>();
-
-        public void reset() {
-            mActivePhoneSwitchCount.set(0);
-            mActivePhoneSwitchObject.set(null);
-        }
-
-        public void setActivePhoneSwitchObject(Object o) {
-            mActivePhoneSwitchObject.set(o);
-        }
-
-        public int getActivePhoneSwitchCount() {
-            return mActivePhoneSwitchCount.get();
-        }
-    }
-
-    private void waitABit() {
-        try {
-            Thread.sleep(250);
-        } catch (Exception e) {}
-    }
-
-    private String mTestName = "";
-
-    private void log(String str) {
-        Rlog.d(LOG_TAG + " " + mTestName, str);
-    }
-
-    private NetworkRequest makeSubSpecificDefaultRequest(ConnectivityServiceMock cs, int subId) {
-        NetworkCapabilities netCap = (new NetworkCapabilities()).
-                addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET).
-                addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED).
-                addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
-        netCap.setNetworkSpecifier(new StringNetworkSpecifier(Integer.toString(subId)));
-        return cs.requestNetwork(netCap, null, 0, new Binder(), -1);
-    }
-
-    private NetworkRequest makeSubSpecificMmsRequest(ConnectivityServiceMock cs, int subId) {
-        NetworkCapabilities netCap = (new NetworkCapabilities()).
-                addCapability(NetworkCapabilities.NET_CAPABILITY_MMS).
-                addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED).
-                addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
-        netCap.setNetworkSpecifier(new StringNetworkSpecifier(Integer.toString(subId)));
-        return cs.requestNetwork(netCap, null, 0, new Binder(), -1);
-    }
-
-    private Context makeContext() {
-        final ContextFixture contextFixture = new ContextFixture();
-        String[] networkConfigString = getContext().getResources().getStringArray(
-                com.android.internal.R.array.networkAttributes);
-        contextFixture.putStringArrayResource(com.android.internal.R.array.networkAttributes,
-                networkConfigString);
-        return contextFixture.getTestDouble();
+    @After
+    public void tearDown() throws Exception {
+        super.tearDown();
     }
 
     /**
      * Test that a single phone case results in our phone being active and the RIL called
      */
+    @Test
     @SmallTest
     public void testRegister() throws Exception {
-        mTestName = "testRegister";
         final int numPhones = 2;
         final int maxActivePhones = 1;
-        final HandlerThread handlerThread = new HandlerThread("PhoneSwitcherTestThread");
-        handlerThread.start();
-        final ContextFixture contextFixture = new ContextFixture();
-        String[] networkConfigString = getContext().getResources().getStringArray(
-                com.android.internal.R.array.networkAttributes);
-        contextFixture.putStringArrayResource(com.android.internal.R.array.networkAttributes,
-                networkConfigString);
-        final Context contextMock = contextFixture.getTestDouble();
-        final ConnectivityServiceMock connectivityServiceMock =
-                new ConnectivityServiceMock(contextMock);
-        final ConnectivityManager cm =
-                new ConnectivityManager(contextMock, connectivityServiceMock);
-        contextFixture.setSystemService(Context.CONNECTIVITY_SERVICE, cm);
-        final ITelephonyRegistry.Stub telRegistryMock = new TelephonyRegistryMock();
-        final SubscriptionControllerMock subControllerMock =
-                new SubscriptionControllerMock(contextMock, telRegistryMock, numPhones);
-        final SimulatedCommands[] commandsInterfaces = new SimulatedCommands[numPhones];
-        final PhoneMock[] phones = new PhoneMock[numPhones];
-        for (int i = 0; i < numPhones; i++) {
-            commandsInterfaces[i] = new SimulatedCommands();
-        //    phones[i] = new PhoneMock(contextMock, commandsInterfaces[i]);
-        }
-
-        PhoneSwitcher phoneSwitcher = new PhoneSwitcher(maxActivePhones, numPhones,
-                contextMock, subControllerMock, handlerThread.getLooper(), telRegistryMock,
-                commandsInterfaces, phones);
+        initialize(numPhones, maxActivePhones);
 
         // verify nothing has been done while there are no inputs
-        if (commandsInterfaces[0].isDataAllowed()) fail("data allowed initially");
-        if (phoneSwitcher.isPhoneActive(0))        fail("phone active initially");
+        assertFalse("data allowed initially", mDataAllowed[0]);
+        assertFalse("data allowed initially", mDataAllowed[0]);
+        assertFalse("phone active initially", mPhoneSwitcher.shouldApplySpecifiedRequests(0));
 
-        connectivityServiceMock.addDefaultRequest();
+        NetworkRequest internetNetworkRequest = addInternetNetworkRequest(null, 50);
         waitABit();
 
-        if (commandsInterfaces[0].isDataAllowed()) fail("data allowed after request");
-        if (phoneSwitcher.isPhoneActive(0))        fail("phone active after request");
-
-        TestHandler testHandler = TestHandler.makeHandler();
-        Object activePhoneSwitchObject = new Object();
-        testHandler.setActivePhoneSwitchObject(activePhoneSwitchObject);
-
-        testHandler.blockTilIdle();
+        assertFalse("data allowed after request", mDataAllowed[0]);
+        assertFalse("phone active after request", mPhoneSwitcher.shouldApplySpecifiedRequests(0));
 
         // not registered yet - shouldn't inc
-        if (testHandler.getActivePhoneSwitchCount() != 0) {
-            fail("pretest of ActivePhoneSwitchCount");
-        }
-        boolean threw = false;
-        try {
-            // should throw
-            phoneSwitcher.registerForActivePhoneSwitch(2, testHandler,
-                    TestHandler.ACTIVE_PHONE_SWITCH, activePhoneSwitchObject);
-        } catch (IllegalArgumentException e) {
-            threw = true;
-        }
-        if (threw == false) fail("register with bad phoneId didn't throw");
+        verify(mActivePhoneSwitchHandler, never()).sendMessageAtTime(any(), anyLong());
 
-        phoneSwitcher.registerForActivePhoneSwitch(0, testHandler,
-                TestHandler.ACTIVE_PHONE_SWITCH,
-                activePhoneSwitchObject);
-        testHandler.blockTilIdle();
+        mPhoneSwitcher.registerForActivePhoneSwitch(mActivePhoneSwitchHandler,
+                ACTIVE_PHONE_SWITCH, null);
 
-        if (testHandler.getActivePhoneSwitchCount() != 1) {
-            fail("post register of ActivePhoneSwitchCount not 1!");
-        }
+        verify(mActivePhoneSwitchHandler, times(1)).sendMessageAtTime(any(), anyLong());
+        clearInvocations(mActivePhoneSwitchHandler);
 
-        subControllerMock.setDefaultDataSubId(0);
-        testHandler.blockTilIdle();
+        setDefaultDataSubId(0);
 
-        if (testHandler.getActivePhoneSwitchCount() != 1) {
-            fail("after set of default to 0, ActivePhoneSwitchCount not 1!");
-        }
-        if (commandsInterfaces[0].isDataAllowed()) fail("data allowed");
+        verify(mActivePhoneSwitchHandler, never()).sendMessageAtTime(any(), anyLong());
+        assertFalse("data allowed", mDataAllowed[0]);
 
-        subControllerMock.setSlotSubId(0, 0);
+        setSlotIndexToSubId(0, 0);
+        mSubChangedListener.onSubscriptionsChanged();
         waitABit();
 
-        if (testHandler.getActivePhoneSwitchCount() != 2) {
-            fail("after mapping of 0 to 0, ActivePhoneSwitchCount not 2!");
-        }
-        if (commandsInterfaces[0].isDataAllowed() == false) fail("data not allowed");
+        verify(mActivePhoneSwitchHandler, times(1)).sendMessageAtTime(any(), anyLong());
+        clearInvocations(mActivePhoneSwitchHandler);
+        assertTrue("data not allowed", mDataAllowed[0]);
 
         // now try various things that should cause the active phone to switch:
         // 1 lose default via default sub change
@@ -281,93 +147,97 @@
         // 10 don't switch phones when in emergency mode
 
         // 1 lose default via default sub change
-        subControllerMock.setDefaultDataSubId(1);
+        setDefaultDataSubId(1);
         waitABit();
-        if (testHandler.getActivePhoneSwitchCount() != 3) {
-            fail("after set of default to 1, ActivePhoneSwitchCount not 3!");
-        }
-        if (commandsInterfaces[0].isDataAllowed()) fail("data allowed");
 
-        subControllerMock.setSlotSubId(1, 1);
+        verify(mActivePhoneSwitchHandler, times(1)).sendMessageAtTime(any(), anyLong());
+        clearInvocations(mActivePhoneSwitchHandler);
+        assertFalse("data allowed", mDataAllowed[0]);
+
+        setSlotIndexToSubId(1, 1);
+        mSubChangedListener.onSubscriptionsChanged();
         waitABit();
-        if (testHandler.getActivePhoneSwitchCount() != 3) {
-            fail("after mapping of 1 to 1, ActivePhoneSwitchCount not 3!");
-        }
-        if (commandsInterfaces[0].isDataAllowed()) fail("data allowed");
-        if (commandsInterfaces[1].isDataAllowed() == false) fail("data not allowed");
+
+        verify(mActivePhoneSwitchHandler, times(1)).sendMessageAtTime(any(), anyLong());
+        clearInvocations(mActivePhoneSwitchHandler);
+        assertFalse("data allowed", mDataAllowed[0]);
+        assertTrue("data not allowed", mDataAllowed[1]);
 
         // 2 gain default via default sub change
-        subControllerMock.setDefaultDataSubId(0);
+        setDefaultDataSubId(0);
         waitABit();
-        if (testHandler.getActivePhoneSwitchCount() != 4) {
-            fail("after set of default to 0, ActivePhoneSwitchCount not 4!");
-        }
-        if (commandsInterfaces[1].isDataAllowed()) fail("data allowed");
-        if (commandsInterfaces[0].isDataAllowed() == false) fail("data not allowed");
+
+        verify(mActivePhoneSwitchHandler, times(1)).sendMessageAtTime(any(), anyLong());
+        clearInvocations(mActivePhoneSwitchHandler);
+        assertFalse("data allowed", mDataAllowed[1]);
+        assertTrue("data not allowed", mDataAllowed[0]);
 
         // 3 lose default via sub->phone change
-        subControllerMock.setSlotSubId(0, 2);
+        setSlotIndexToSubId(0, 2);
+        mSubChangedListener.onSubscriptionsChanged();
         waitABit();
 
-        if (testHandler.getActivePhoneSwitchCount() != 5) {
-            fail("after mapping of 0 to 2, ActivePhoneSwitchCount not 5!");
-        }
-        if (commandsInterfaces[0].isDataAllowed()) fail("data allowed");
-        if (commandsInterfaces[1].isDataAllowed()) fail("data allowed");
+        verify(mActivePhoneSwitchHandler, times(1)).sendMessageAtTime(any(), anyLong());
+        clearInvocations(mActivePhoneSwitchHandler);
+        assertFalse("data allowed", mDataAllowed[0]);
+        assertFalse("data allowed", mDataAllowed[1]);
 
         // 4 gain default via sub->phone change
-        subControllerMock.setSlotSubId(0, 0);
+        setSlotIndexToSubId(0, 0);
+        mSubChangedListener.onSubscriptionsChanged();
         waitABit();
-        if (testHandler.getActivePhoneSwitchCount() != 6) {
-            fail("after mapping of 0 to 0, ActivePhoneSwitchCount not 6!");
-        }
-        if (commandsInterfaces[0].isDataAllowed() == false) fail("data not allowed");
-        if (commandsInterfaces[1].isDataAllowed()) fail("data allowed");
+
+        verify(mActivePhoneSwitchHandler, times(1)).sendMessageAtTime(any(), anyLong());
+        clearInvocations(mActivePhoneSwitchHandler);
+        assertTrue("data not allowed", mDataAllowed[0]);
+        assertFalse("data allowed", mDataAllowed[1]);
 
         // 5 lose default network request
-        connectivityServiceMock.removeDefaultRequest();
+        releaseNetworkRequest(internetNetworkRequest);
         waitABit();
-        if (testHandler.getActivePhoneSwitchCount() != 7) {
-            fail("after loss of network request, ActivePhoneSwitchCount not 7!");
-        }
-        if (commandsInterfaces[0].isDataAllowed()) fail("data allowed");
-        if (commandsInterfaces[1].isDataAllowed()) fail("data allowed");
+
+        verify(mActivePhoneSwitchHandler, times(1)).sendMessageAtTime(any(), anyLong());
+        clearInvocations(mActivePhoneSwitchHandler);
+        assertFalse("data allowed", mDataAllowed[0]);
+        assertFalse("data allowed", mDataAllowed[1]);
 
         // 6 gain subscription-specific request
-        NetworkRequest request = makeSubSpecificDefaultRequest(connectivityServiceMock, 0);
+        NetworkRequest specificInternetRequest = addInternetNetworkRequest(0, 50);
         waitABit();
-        if (testHandler.getActivePhoneSwitchCount() != 8) {
-            fail("after gain of network request, ActivePhoneSwitchCount not 8!");
-        }
-        if (commandsInterfaces[0].isDataAllowed() == false) fail("data not allowed");
-        if (commandsInterfaces[1].isDataAllowed()) fail("data allowed");
+
+        verify(mActivePhoneSwitchHandler, times(1)).sendMessageAtTime(any(), anyLong());
+        clearInvocations(mActivePhoneSwitchHandler);
+        assertTrue("data not allowed", mDataAllowed[0]);
+        assertFalse("data allowed", mDataAllowed[1]);
 
         // 7 lose via sub->phone change
-        subControllerMock.setSlotSubId(0, 1);
+        setSlotIndexToSubId(0, 1);
+        mSubChangedListener.onSubscriptionsChanged();
         waitABit();
-        if (testHandler.getActivePhoneSwitchCount() != 9) {
-            fail("after loss of request due to subId map change, ActivePhoneSwitchCount not 9!");
-        }
-        if (commandsInterfaces[0].isDataAllowed()) fail("data allowed");
-        if (commandsInterfaces[1].isDataAllowed()) fail("data allowed");
+
+        verify(mActivePhoneSwitchHandler, times(1)).sendMessageAtTime(any(), anyLong());
+        clearInvocations(mActivePhoneSwitchHandler);
+        assertFalse("data allowed", mDataAllowed[0]);
+        assertFalse("data allowed", mDataAllowed[1]);
 
         // 8 gain via sub->phone change
-        subControllerMock.setSlotSubId(0, 0);
+        setSlotIndexToSubId(0, 0);
+        mSubChangedListener.onSubscriptionsChanged();
         waitABit();
-        if (testHandler.getActivePhoneSwitchCount() != 10) {
-            fail("after gain of request due to subId map change, ActivePhoneSwitchCount not 10!");
-        }
-        if (commandsInterfaces[0].isDataAllowed() == false) fail("data not allowed");
-        if (commandsInterfaces[1].isDataAllowed()) fail("data allowed");
+
+        verify(mActivePhoneSwitchHandler, times(1)).sendMessageAtTime(any(), anyLong());
+        clearInvocations(mActivePhoneSwitchHandler);
+        assertTrue("data not allowed", mDataAllowed[0]);
+        assertFalse("data allowed", mDataAllowed[1]);
 
         // 9 lose subscription-specific request
-        connectivityServiceMock.releaseNetworkRequest(request);
+        releaseNetworkRequest(specificInternetRequest);
         waitABit();
-        if (testHandler.getActivePhoneSwitchCount() != 11) {
-            fail("after release of request, ActivePhoneSwitchCount not 11!");
-        }
-        if (commandsInterfaces[0].isDataAllowed()) fail("data allowed");
-        if (commandsInterfaces[1].isDataAllowed()) fail("data allowed");
+
+        verify(mActivePhoneSwitchHandler, times(1)).sendMessageAtTime(any(), anyLong());
+        clearInvocations(mActivePhoneSwitchHandler);
+        assertFalse("data allowed", mDataAllowed[0]);
+        assertFalse("data allowed", mDataAllowed[1]);
 
         // 10 don't switch phones when in emergency mode
         // not ready yet - Phone turns out to be hard to stub out
@@ -389,13 +259,7 @@
 //        if (commandsInterfaces[0].isDataAllowed()) fail("data allowed");
 //        if (commandsInterfaces[1].isDataAllowed()) fail("data allowed");
 
-        for (int i = 0; i < numPhones; i++) {
-            commandsInterfaces[i].dispose();
-        }
-
-        connectivityServiceMock.die();
-        testHandler.die();
-        handlerThread.quit();
+        mHandlerThread.quit();
     }
 
     /**
@@ -414,156 +278,396 @@
      * - bring up low priority phone when sub change causes join
      * - don't switch phones when in emergency mode
      */
+    @Test
     @SmallTest
     public void testPrioritization() throws Exception {
-        mTestName = "testPrioritization";
         final int numPhones = 2;
         final int maxActivePhones = 1;
-        final HandlerThread handlerThread = new HandlerThread("PhoneSwitcherTestThread");
-        handlerThread.start();
-        final ContextFixture contextFixture = new ContextFixture();
-        String[] networkConfigString = getContext().getResources().getStringArray(
-                com.android.internal.R.array.networkAttributes);
-        contextFixture.putStringArrayResource(com.android.internal.R.array.networkAttributes,
-                networkConfigString);
-        final Context contextMock = contextFixture.getTestDouble();
-        final ConnectivityServiceMock connectivityServiceMock =
-            new ConnectivityServiceMock(contextMock);
-        final ConnectivityManager cm =
-                new ConnectivityManager(contextMock, connectivityServiceMock);
-        contextFixture.setSystemService(Context.CONNECTIVITY_SERVICE, cm);
-        final ITelephonyRegistry.Stub telRegistryMock = new TelephonyRegistryMock();
-        final SubscriptionControllerMock subControllerMock =
-                new SubscriptionControllerMock(contextMock, telRegistryMock, numPhones);
-        final SimulatedCommands[] commandsInterfaces = new SimulatedCommands[numPhones];
-        final PhoneMock[] phones = new PhoneMock[numPhones];
-        for (int i = 0; i < numPhones; i++) {
-            commandsInterfaces[i] = new SimulatedCommands();
-        }
+        initialize(numPhones, maxActivePhones);
 
-        PhoneSwitcher phoneSwitcher = new PhoneSwitcher(maxActivePhones, numPhones,
-                contextMock, subControllerMock, handlerThread.getLooper(), telRegistryMock,
-                commandsInterfaces, phones);
-
-        TestHandler testHandler = TestHandler.makeHandler();
-        Object activePhoneSwitchObject = new Object();
-        testHandler.setActivePhoneSwitchObject(activePhoneSwitchObject);
-
-        connectivityServiceMock.addDefaultRequest();
-        subControllerMock.setSlotSubId(0, 0);
-        subControllerMock.setSlotSubId(1, 1);
-        subControllerMock.setDefaultDataSubId(0);
+        addInternetNetworkRequest(null, 50);
+        setSlotIndexToSubId(0, 0);
+        setSlotIndexToSubId(1, 1);
+        setDefaultDataSubId(0);
         waitABit();
-        phoneSwitcher.registerForActivePhoneSwitch(0, testHandler, TestHandler.ACTIVE_PHONE_SWITCH,
-                activePhoneSwitchObject);
+        mPhoneSwitcher.registerForActivePhoneSwitch(mActivePhoneSwitchHandler,
+                ACTIVE_PHONE_SWITCH, null);
         waitABit();
         // verify initial conditions
-        if (testHandler.getActivePhoneSwitchCount() != 1) {
-            fail("Initial conditions not met: ActivePhoneSwitchCount not 1! " +
-                    testHandler.getActivePhoneSwitchCount());
-        }
-        if (commandsInterfaces[0].isDataAllowed() == false) fail("data not allowed");
-        if (commandsInterfaces[1].isDataAllowed()) fail("data allowed");
+        verify(mActivePhoneSwitchHandler, times(1)).sendMessageAtTime(any(), anyLong());
+
+        assertTrue("data not allowed", mDataAllowed[0]);
+        assertFalse("data allowed", mDataAllowed[1]);
 
         // now start a higher priority conneciton on the other sub
-        NetworkRequest request = makeSubSpecificMmsRequest(connectivityServiceMock, 1);
+        addMmsNetworkRequest(1);
         waitABit();
-        if (testHandler.getActivePhoneSwitchCount() != 2) {
-            fail("after gain of network request, ActivePhoneSwitchCount not 2!");
-        }
-        if (commandsInterfaces[0].isDataAllowed()) fail("data allowed");
-        if (commandsInterfaces[1].isDataAllowed() == false) fail("data not allowed");
 
-        for (int i = 0; i < numPhones; i++) {
-            commandsInterfaces[i].dispose();
-        }
+        // After gain of network request, mActivePhoneSwitchHandler should be notified 2 times.
+        verify(mActivePhoneSwitchHandler, times(2)).sendMessageAtTime(any(), anyLong());
+        assertFalse("data allowed", mDataAllowed[0]);
+        assertTrue("data not allowed", mDataAllowed[1]);
 
-        connectivityServiceMock.die();
-        testHandler.die();
-        handlerThread.quit();
+        mHandlerThread.quit();
     }
 
     /**
      * Verify we don't send spurious DATA_ALLOWED calls when another NetworkFactory
      * wins (ie, switch to wifi).
      */
+    @Test
     @SmallTest
     public void testHigherPriorityDefault() throws Exception {
-        mTestName = "testPrioritization";
         final int numPhones = 2;
         final int maxActivePhones = 1;
-        final HandlerThread handlerThread = new HandlerThread("PhoneSwitcherTestThread");
-        handlerThread.start();
-        final ContextFixture contextFixture = new ContextFixture();
-        String[] networkConfigString = getContext().getResources().getStringArray(
-                com.android.internal.R.array.networkAttributes);
-        contextFixture.putStringArrayResource(com.android.internal.R.array.networkAttributes,
-                networkConfigString);
-        final Context contextMock = contextFixture.getTestDouble();
-        final ConnectivityServiceMock connectivityServiceMock =
-                new ConnectivityServiceMock(contextMock);
-        final ConnectivityManager cm =
-                new ConnectivityManager(contextMock, connectivityServiceMock);
-        contextFixture.setSystemService(Context.CONNECTIVITY_SERVICE, cm);
-        final ITelephonyRegistry.Stub telRegistryMock = new TelephonyRegistryMock();
-        final SubscriptionControllerMock subControllerMock =
-                new SubscriptionControllerMock(contextMock, telRegistryMock, numPhones);
-        final SimulatedCommands[] commandsInterfaces = new SimulatedCommands[numPhones];
-        final PhoneMock[] phones = new PhoneMock[numPhones];
-        for (int i = 0; i < numPhones; i++) {
-            commandsInterfaces[i] = new SimulatedCommands();
-        }
+        initialize(numPhones, maxActivePhones);
 
-        PhoneSwitcher phoneSwitcher = new PhoneSwitcher(maxActivePhones, numPhones,
-                contextMock, subControllerMock, handlerThread.getLooper(), telRegistryMock,
-                commandsInterfaces, phones);
+        addInternetNetworkRequest(null, 50);
+        waitABit();
 
-        TestHandler testHandler = TestHandler.makeHandler();
-        Object activePhoneSwitchObject = new Object();
-        testHandler.setActivePhoneSwitchObject(activePhoneSwitchObject);
-
-        connectivityServiceMock.addDefaultRequest();
-        subControllerMock.setSlotSubId(0, 0);
-        subControllerMock.setSlotSubId(1, 1);
-        subControllerMock.setDefaultDataSubId(0);
+        setSlotIndexToSubId(0, 0);
+        setSlotIndexToSubId(1, 1);
+        setDefaultDataSubId(0);
         waitABit();
 
         // Phone 0 should be active
-        if (commandsInterfaces[0].isDataAllowed() == false) fail("data not allowed");
-        if (commandsInterfaces[1].isDataAllowed()) fail("data allowed");
+        assertTrue("data not allowed", mDataAllowed[0]);
+        assertFalse("data allowed", mDataAllowed[1]);
 
-        connectivityServiceMock.setCurrentScoreForRequest(connectivityServiceMock.defaultRequest,
-                100);
+        addInternetNetworkRequest(null, 100);
         waitABit();
 
         // should be no change
-        if (commandsInterfaces[0].isDataAllowed() == false) fail("data not allowed");
-        if (commandsInterfaces[1].isDataAllowed()) fail("data allowed");
+        assertTrue("data not allowed", mDataAllowed[0]);
+        assertFalse("data allowed", mDataAllowed[1]);
 
-        connectivityServiceMock.setCurrentScoreForRequest(connectivityServiceMock.defaultRequest,
-                0);
+        addInternetNetworkRequest(null, 0);
         waitABit();
 
         // should be no change
-        if (commandsInterfaces[0].isDataAllowed() == false) fail("data not allowed");
-        if (commandsInterfaces[1].isDataAllowed()) fail("data allowed");
+        assertTrue("data not allowed", mDataAllowed[0]);
+        assertFalse("data allowed", mDataAllowed[1]);
 
-        for (int i = 0; i < numPhones; i++) {
-            commandsInterfaces[i].dispose();
-        }
-
-        connectivityServiceMock.die();
-        testHandler.die();
-        handlerThread.quit();
+        mHandlerThread.quit();
     }
 
     /**
-     * Test MSMA testing prioritiziation
-     * - leave multiple on (up to the limit)
-     * - tear down lowest priority phone when new request comes in
-     * - tear down low priority phone when sub change causes split
-     * - bring up low priority phone when sub change causes join
-     * - don't switch phones when in emergency mode
+     * Verify testSetPreferredData.
+     * When preferredData is set, it overwrites defaultData sub to be active sub in single
+     * active phone mode. If it's unset (to DEFAULT_SUBSCRIPTION_ID), defaultData sub becomes
+     * active one.
      */
+    @Test
+    @SmallTest
+    public void testSetPreferredData() throws Exception {
+        final int numPhones = 2;
+        final int maxActivePhones = 1;
+        initialize(numPhones, maxActivePhones);
 
+        // Phone 0 has sub 1, phone 1 has sub 2.
+        // Sub 1 is default data sub.
+        // Both are active subscriptions are active sub, as they are in both active slots.
+        setSlotIndexToSubId(0, 1);
+        setSlotIndexToSubId(1, 2);
+        setDefaultDataSubId(1);
+
+        // Notify phoneSwitcher about default data sub and default network request.
+        addInternetNetworkRequest(null, 50);
+        waitABit();
+        // Phone 0 (sub 1) should be activated as it has default data sub.
+        assertTrue(mDataAllowed[0]);
+
+        // Set sub 2 as preferred sub should make phone 1 activated and phone 0 deactivated.
+        mPhoneSwitcher.setPreferredData(2);
+        waitABit();
+        assertFalse(mDataAllowed[0]);
+        assertTrue(mDataAllowed[1]);
+
+        // Unset preferred sub should make default data sub (phone 0 / sub 1) activated again.
+        mPhoneSwitcher.setPreferredData(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
+        waitABit();
+        assertTrue(mDataAllowed[0]);
+        assertFalse(mDataAllowed[1]);
+
+        mHandlerThread.quit();
+    }
+
+    @Test
+    @SmallTest
+    public void testSetPreferredDataModemCommand() throws Exception {
+        final int numPhones = 2;
+        final int maxActivePhones = 1;
+        doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
+        initialize(numPhones, maxActivePhones);
+        mPhoneSwitcher.registerForActivePhoneSwitch(mActivePhoneSwitchHandler,
+                ACTIVE_PHONE_SWITCH, null);
+        mPhoneSwitcher.registerForActivePhoneSwitch(mActivePhoneSwitchHandler,
+                ACTIVE_PHONE_SWITCH, null);
+        verify(mActivePhoneSwitchHandler, times(2)).sendMessageAtTime(any(), anyLong());
+        clearInvocations(mActivePhoneSwitchHandler);
+
+        // Phone 0 has sub 1, phone 1 has sub 2.
+        // Sub 1 is default data sub.
+        // Both are active subscriptions are active sub, as they are in both active slots.
+        setSlotIndexToSubId(0, 1);
+        setSlotIndexToSubId(1, 2);
+        setDefaultDataSubId(1);
+        waitABit();
+        // Phone 0 (sub 1) should preferredDataModem it has default data sub.
+        verify(mMockRadioConfig).setPreferredDataModem(eq(0), any());
+        verify(mActivePhoneSwitchHandler, times(2)).sendMessageAtTime(any(), anyLong());
+        assertTrue(mPhoneSwitcher.shouldApplySpecifiedRequests(0));
+        assertTrue(mPhoneSwitcher.shouldApplySpecifiedRequests(1));
+        assertTrue(mPhoneSwitcher.shouldApplyUnspecifiedRequests(0));
+        assertFalse(mPhoneSwitcher.shouldApplyUnspecifiedRequests(1));
+
+        clearInvocations(mMockRadioConfig);
+        clearInvocations(mActivePhoneSwitchHandler);
+
+        // Notify phoneSwitcher about default data sub and default network request.
+        // It shouldn't change anything.
+        addInternetNetworkRequest(null, 50);
+        addMmsNetworkRequest(2);
+        waitABit();
+        verify(mMockRadioConfig, never()).setPreferredDataModem(anyInt(), any());
+        verify(mActivePhoneSwitchHandler, never()).sendMessageAtTime(any(), anyLong());
+        assertTrue(mPhoneSwitcher.shouldApplySpecifiedRequests(0));
+        assertTrue(mPhoneSwitcher.shouldApplySpecifiedRequests(1));
+        assertTrue(mPhoneSwitcher.shouldApplyUnspecifiedRequests(0));
+        assertFalse(mPhoneSwitcher.shouldApplyUnspecifiedRequests(1));
+
+        // Set sub 2 as preferred sub should make phone 1 preferredDataModem
+        mPhoneSwitcher.setPreferredData(2);
+        waitABit();
+        verify(mMockRadioConfig).setPreferredDataModem(eq(1), any());
+        verify(mActivePhoneSwitchHandler, times(2)).sendMessageAtTime(any(), anyLong());
+        assertTrue(mPhoneSwitcher.shouldApplySpecifiedRequests(0));
+        assertTrue(mPhoneSwitcher.shouldApplySpecifiedRequests(1));
+        assertFalse(mPhoneSwitcher.shouldApplyUnspecifiedRequests(0));
+        assertTrue(mPhoneSwitcher.shouldApplyUnspecifiedRequests(1));
+
+        clearInvocations(mMockRadioConfig);
+        clearInvocations(mActivePhoneSwitchHandler);
+
+        // Unset preferred sub should make phone0 preferredDataModem again.
+        mPhoneSwitcher.setPreferredData(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
+        waitABit();
+        verify(mMockRadioConfig).setPreferredDataModem(eq(0), any());
+        verify(mActivePhoneSwitchHandler, times(2)).sendMessageAtTime(any(), anyLong());
+        assertTrue(mPhoneSwitcher.shouldApplySpecifiedRequests(0));
+        assertTrue(mPhoneSwitcher.shouldApplySpecifiedRequests(1));
+        assertTrue(mPhoneSwitcher.shouldApplyUnspecifiedRequests(0));
+        assertFalse(mPhoneSwitcher.shouldApplyUnspecifiedRequests(1));
+
+        // SetDataAllowed should never be triggered.
+        verify(mCommandsInterface0, never()).setDataAllowed(anyBoolean(), any());
+        verify(mCommandsInterface1, never()).setDataAllowed(anyBoolean(), any());
+
+        mHandlerThread.quit();
+
+    }
+
+    /* Private utility methods start here */
+
+    private void sendDefaultDataSubChanged() {
+        final Intent intent = new Intent(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED);
+        mContext.sendBroadcast(intent);
+    }
+
+    private void initialize(int numPhones, int maxActivePhones) throws Exception {
+        mContextFixture.putStringArrayResource(com.android.internal.R.array.networkAttributes,
+                sNetworkAttributes);
+
+        setNumPhones(numPhones);
+
+        initializeSubControllerMock();
+        initializeCommandInterfacesMock(numPhones);
+        initializeTelRegistryMock();
+        initializeConnManagerMock();
+
+        mHandlerThread = new HandlerThread("PhoneSwitcherTestThread") {
+            @Override
+            public void onLooperPrepared() {
+                mPhoneSwitcher = new PhoneSwitcher(maxActivePhones, numPhones,
+                        mContext, mSubscriptionController, this.getLooper(),
+                        mTelRegistryMock, mCommandsInterfaces, mPhones);
+            }
+        };
+
+        mHandlerThread.start();
+        waitABit();
+
+        verify(mTelRegistryMock).addOnSubscriptionsChangedListener(
+                eq(mContext.getOpPackageName()), any());
+    }
+
+    /**
+     * Certain variables needs initialized depending on number of phones.
+     */
+    private void setNumPhones(int numPhones) {
+        mDataAllowed = new boolean[numPhones];
+        mSlotIndexToSubId = new int[numPhones][];
+        for (int i = 0; i < numPhones; i++) {
+            mSlotIndexToSubId[i] = new int[1];
+            mSlotIndexToSubId[i][0] = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+        }
+
+        if (numPhones == 1) {
+            mCommandsInterfaces = new CommandsInterface[] {mCommandsInterface0};
+            mPhones = new Phone[] {mPhone};
+        } else if (numPhones == 2) {
+            mCommandsInterfaces =
+                    new CommandsInterface[] {mCommandsInterface0, mCommandsInterface1};
+            mPhones = new Phone[] {mPhone, mPhone2};
+        }
+    }
+
+    private void initializeCommandInterfacesMock(int numPhones) {
+        // Tell PhoneSwitcher that radio is on.
+        doAnswer(invocation -> {
+            Handler handler = (Handler) invocation.getArguments()[0];
+            int message = (int) invocation.getArguments()[1];
+            Object obj = invocation.getArguments()[2];
+            handler.obtainMessage(message, obj).sendToTarget();
+            return null;
+        }).when(mCommandsInterface0).registerForAvailable(any(), anyInt(), any());
+
+        // Store values of dataAllowed in mDataAllowed[] for easier checking.
+        doAnswer(invocation -> {
+            mDataAllowed[0] = (boolean) invocation.getArguments()[0];
+            return null;
+        }).when(mCommandsInterface0).setDataAllowed(anyBoolean(), any());
+
+        if (numPhones == 2) {
+            doAnswer(invocation -> {
+                mDataAllowed[1] = (boolean) invocation.getArguments()[0];
+                return null;
+            }).when(mCommandsInterface1).setDataAllowed(anyBoolean(), any());
+        }
+    }
+
+    /**
+     * Store subChangedListener of PhoneSwitcher so that testing can notify
+     * PhoneSwitcher of sub change.
+     */
+    private void initializeTelRegistryMock() throws Exception {
+        doAnswer(invocation -> {
+            IOnSubscriptionsChangedListener subChangedListener =
+                    (IOnSubscriptionsChangedListener) invocation.getArguments()[1];
+            mSubChangedListener = subChangedListener;
+            mSubChangedListener.onSubscriptionsChanged();
+            return null;
+        }).when(mTelRegistryMock).addOnSubscriptionsChangedListener(any(), any());
+    }
+
+    /**
+     * Capture mNetworkFactoryMessenger so that testing can request or release
+     * network requests on PhoneSwitcher.
+     */
+    private void initializeConnManagerMock() {
+        mConnectivityManager = (ConnectivityManager)
+                mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+
+        doAnswer(invocation -> {
+            mNetworkFactoryMessenger = invocation.getArgument(0);
+            return null;
+        }).when(mConnectivityManager).registerNetworkFactory(any(), any());
+    }
+
+    /**
+     * Capture mNetworkFactoryMessenger so that testing can request or release
+     * network requests on PhoneSwitcher.
+     */
+    private void initializeSubControllerMock() {
+        doReturn(mDefaultDataSub).when(mSubscriptionController).getDefaultDataSubId();
+        doAnswer(invocation -> {
+            int phoneId = (int) invocation.getArguments()[0];
+            return mSlotIndexToSubId[phoneId][0];
+        }).when(mSubscriptionController).getSubIdUsingPhoneId(anyInt());
+
+        doAnswer(invocation -> {
+            int subId = (int) invocation.getArguments()[0];
+
+            if (!SubscriptionManager.isUsableSubIdValue(subId)) return false;
+
+            for (int i = 0; i < mSlotIndexToSubId.length; i++) {
+                if (mSlotIndexToSubId[i][0] == subId) return true;
+            }
+            return false;
+        }).when(mSubscriptionController).isActiveSubId(anyInt());
+    }
+
+    private void setDefaultDataSubId(int defaultDataSub) {
+        mDefaultDataSub = defaultDataSub;
+        doReturn(mDefaultDataSub).when(mSubscriptionController).getDefaultDataSubId();
+        sendDefaultDataSubChanged();
+    }
+
+    private void setSlotIndexToSubId(int slotId, int subId) {
+        mSlotIndexToSubId[slotId][0] = subId;
+    }
+
+    /**
+     * Create an internet PDN network request and send it to PhoneSwitcher.
+     */
+    private NetworkRequest addInternetNetworkRequest(Integer subId, int score) throws Exception {
+        NetworkCapabilities netCap = (new NetworkCapabilities())
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+                .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
+        if (subId != null) {
+            netCap.setNetworkSpecifier(new StringNetworkSpecifier(Integer.toString(subId)));
+        }
+        NetworkRequest networkRequest = new NetworkRequest(netCap, ConnectivityManager.TYPE_NONE,
+                0, NetworkRequest.Type.REQUEST);
+
+        Message message = Message.obtain();
+        message.what = android.net.NetworkFactory.CMD_REQUEST_NETWORK;
+        message.arg1 = score;
+        message.obj = networkRequest;
+        mNetworkFactoryMessenger.send(message);
+
+        return networkRequest;
+    }
+
+    /**
+     * Create a mms PDN network request and send it to PhoneSwitcher.
+     */
+    private NetworkRequest addMmsNetworkRequest(Integer subId) throws Exception {
+        NetworkCapabilities netCap = (new NetworkCapabilities())
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_MMS)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+                .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
+        netCap.setNetworkSpecifier(new StringNetworkSpecifier(Integer.toString(subId)));
+        if (subId != null) {
+            netCap.setNetworkSpecifier(new StringNetworkSpecifier(Integer.toString(subId)));
+        }
+        NetworkRequest networkRequest = new NetworkRequest(netCap, ConnectivityManager.TYPE_NONE,
+                1, NetworkRequest.Type.REQUEST);
+
+        Message message = Message.obtain();
+        message.what = android.net.NetworkFactory.CMD_REQUEST_NETWORK;
+        message.arg1 = 50; // Score
+        message.obj = networkRequest;
+        mNetworkFactoryMessenger.send(message);
+
+        return networkRequest;
+    }
+
+    /**
+     * Tell PhoneSwitcher to release a network request.
+     */
+    private void releaseNetworkRequest(NetworkRequest networkRequest) throws Exception {
+        Message message = Message.obtain();
+        message.what = android.net.NetworkFactory.CMD_CANCEL_REQUEST;
+        message.obj = networkRequest;
+        mNetworkFactoryMessenger.send(message);
+    }
+
+    private void waitABit() {
+        try {
+            Thread.sleep(250);
+        } catch (Exception e) {
+        }
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/PhysicalChannelConfigTest.java b/tests/telephonytests/src/com/android/internal/telephony/PhysicalChannelConfigTest.java
new file mode 100644
index 0000000..535f7b2
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/PhysicalChannelConfigTest.java
@@ -0,0 +1,79 @@
+/*
+ * 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 com.google.common.truth.Truth.assertThat;
+
+import android.os.Parcel;
+import android.telephony.PhysicalChannelConfig;
+import android.telephony.PhysicalChannelConfig.Builder;
+import android.telephony.ServiceState;
+
+import org.junit.Test;
+
+/** Unit test for {@link android.telephony.PhysicalChannelConfig}. */
+public class PhysicalChannelConfigTest {
+
+    private static final int RAT = ServiceState.RIL_RADIO_TECHNOLOGY_LTE;
+    private static final int CONNECTION_STATUS = PhysicalChannelConfig.CONNECTION_PRIMARY_SERVING;
+    private static final int CELL_BANDWIDTH = 12345;
+    private static final int FREQUENCY_RANGE = 1;
+    private static final int CHANNEL_NUMBER = 1234;
+    private static final int[] CONTEXT_IDS = new int[] {123, 555, 1, 0};
+    private static final int PHYSICAL_CELL_ID = 502;
+
+    @Test
+    public void testBuilder() {
+        PhysicalChannelConfig config = new Builder()
+                .setRat(RAT)
+                .setCellConnectionStatus(CONNECTION_STATUS)
+                .setCellBandwidthDownlinkKhz(CELL_BANDWIDTH)
+                .setFrequencyRange(FREQUENCY_RANGE)
+                .setChannelNumber(CHANNEL_NUMBER)
+                .setContextIds(CONTEXT_IDS)
+                .setPhysicalCellId(PHYSICAL_CELL_ID)
+                .build();
+
+        assertThat(config.getRat()).isEqualTo(RAT);
+        assertThat(config.getConnectionStatus()).isEqualTo(CONNECTION_STATUS);
+        assertThat(config.getCellBandwidthDownlink()).isEqualTo(CELL_BANDWIDTH);
+        assertThat(config.getFrequencyRange()).isEqualTo(FREQUENCY_RANGE);
+        assertThat(config.getChannelNumber()).isEqualTo(CHANNEL_NUMBER);
+        assertThat(config.getContextIds()).isEqualTo(CONTEXT_IDS);
+        assertThat(config.getPhysicalCellId()).isEqualTo(PHYSICAL_CELL_ID);
+    }
+
+    @Test
+    public void testParcel() {
+        PhysicalChannelConfig config = new Builder()
+                .setRat(RAT)
+                .setCellConnectionStatus(CONNECTION_STATUS)
+                .setCellBandwidthDownlinkKhz(CELL_BANDWIDTH)
+                .setFrequencyRange(FREQUENCY_RANGE)
+                .setChannelNumber(CHANNEL_NUMBER)
+                .setContextIds(CONTEXT_IDS)
+                .setPhysicalCellId(PHYSICAL_CELL_ID)
+                .build();
+
+        Parcel parcel = Parcel.obtain();
+        config.writeToParcel(parcel, 0 /* flags */);
+        parcel.setDataPosition(0);
+
+        PhysicalChannelConfig fromParcel = PhysicalChannelConfig.CREATOR.createFromParcel(parcel);
+
+        assertThat(fromParcel).isEqualTo(config);
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/PlmnActRecordTest.java b/tests/telephonytests/src/com/android/internal/telephony/PlmnActRecordTest.java
new file mode 100644
index 0000000..6769105
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/PlmnActRecordTest.java
@@ -0,0 +1,84 @@
+/*
+ * 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.uicc;
+
+import static org.junit.Assert.assertEquals;
+
+import android.os.Parcel;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import java.util.Arrays;
+
+/** Unit tests for PlmnActRecord */
+
+public class PlmnActRecordTest extends AndroidTestCase {
+
+    private static final String TEST_PLMN_5DIGIT = "12345";
+    private static final byte[] TEST_RECORD_5DIGIT = new byte[] {
+            (byte) 0x21, (byte) 0xF3, (byte) 0x54, (byte) 0xC0, (byte) 0x80};
+    private static final String TEST_PLMN_6DIGIT = "123456";
+    private static final byte[] TEST_RECORD_6DIGIT = new byte[] {
+            (byte) 0x21, (byte) 0x63, (byte) 0x54, (byte) 0xC0, (byte) 0x80};
+
+    private static final int ACCESS_TECHS_3GPP = PlmnActRecord.ACCESS_TECH_EUTRAN
+            | PlmnActRecord.ACCESS_TECH_UTRAN | PlmnActRecord.ACCESS_TECH_GSM;
+
+    @SmallTest
+    public void testConstructors() {
+        PlmnActRecord rec = new PlmnActRecord(TEST_PLMN_5DIGIT, ACCESS_TECHS_3GPP);
+        assertEquals(TEST_PLMN_5DIGIT, rec.plmn);
+        assertEquals(ACCESS_TECHS_3GPP, rec.accessTechs);
+
+        PlmnActRecord rec2 = new PlmnActRecord(TEST_RECORD_5DIGIT, 0);
+        assertEquals(TEST_PLMN_5DIGIT, rec.plmn);
+        assertEquals(ACCESS_TECHS_3GPP, rec.accessTechs);
+
+        assertEquals(rec, rec2);
+
+        rec = new PlmnActRecord(TEST_PLMN_6DIGIT, ACCESS_TECHS_3GPP);
+        assertEquals(TEST_PLMN_6DIGIT, rec.plmn);
+        assertEquals(ACCESS_TECHS_3GPP, rec.accessTechs);
+
+        rec2 = new PlmnActRecord(TEST_RECORD_6DIGIT, 0);
+        assertEquals(TEST_PLMN_6DIGIT, rec.plmn);
+        assertEquals(ACCESS_TECHS_3GPP, rec.accessTechs);
+
+        assertEquals(rec, rec2);
+    }
+
+    @SmallTest
+    public void testParcel() {
+        PlmnActRecord par = new PlmnActRecord(TEST_PLMN_5DIGIT, ACCESS_TECHS_3GPP);
+
+        Parcel p = Parcel.obtain();
+        par.writeToParcel(p, 0);
+        p.setDataPosition(0);
+
+        PlmnActRecord par2 = PlmnActRecord.CREATOR.createFromParcel(p);
+        assertEquals(par, par2);
+    }
+
+    @SmallTest
+    public void testEncoding() {
+        PlmnActRecord rec = new PlmnActRecord(TEST_RECORD_5DIGIT, 0);
+        assertTrue(Arrays.equals(rec.getBytes(), TEST_RECORD_5DIGIT));
+
+        rec = new PlmnActRecord(TEST_RECORD_6DIGIT, 0);
+        assertTrue(Arrays.equals(rec.getBytes(), TEST_RECORD_6DIGIT));
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/RILTest.java b/tests/telephonytests/src/com/android/internal/telephony/RILTest.java
index f2afc96..f528ac4 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/RILTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/RILTest.java
@@ -115,22 +115,25 @@
 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;
@@ -194,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;
@@ -205,6 +210,7 @@
     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";
@@ -223,7 +229,7 @@
     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 static final boolean PERSISTENT = true;
 
     private class RILTestHandler extends HandlerThread {
 
@@ -279,6 +285,7 @@
     @After
     public void tearDown() throws Exception {
         mTestHandler.quit();
+        mTestHandler.join();
         super.tearDown();
     }
 
@@ -692,11 +699,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(), false);
         boolean isRoaming = false;
 
         mRILUnderTest.setInitialAttachApn(dataProfile, isRoaming, obtainMessage());
@@ -707,7 +715,7 @@
                         "convertToHalDataProfile",
                         new Class<?>[] {DataProfile.class},
                         new Object[] {dataProfile})),
-                eq(dataProfile.isModemCognitive()),
+                eq(dataProfile.isPersistent()),
                 eq(isRoaming));
         verifyRILResponse(
                 mRILUnderTest, mSerialNumberCaptor.getValue(), RIL_REQUEST_SET_INITIAL_ATTACH_APN);
@@ -823,7 +831,7 @@
     public void testNvWriteItem() throws Exception {
         int itemId = 1;
         String itemValue = "value";
-        mRILUnderTest.nvWriteItem(itemId, itemValue, obtainMessage());
+        mRILUnderTest.nvWriteItem(itemId, itemValue, obtainMessage(), new WorkSource());
         NvWriteItem item = new NvWriteItem();
         item.itemId = itemId;
         item.value = itemValue;
@@ -836,7 +844,7 @@
     @Test
     public void testNvReadItem() throws Exception {
         int itemId = 1;
-        mRILUnderTest.nvReadItem(itemId, obtainMessage());
+        mRILUnderTest.nvReadItem(itemId, obtainMessage(), new WorkSource());
         verify(mRadioProxy).nvReadItem(mSerialNumberCaptor.capture(), eq(itemId));
         verifyRILResponse(
                 mRILUnderTest, mSerialNumberCaptor.getValue(), RIL_REQUEST_NV_READ_ITEM);
@@ -936,7 +944,7 @@
     @FlakyTest
     @Test
     public void testGetModemActivityInfo() throws Exception {
-        mRILUnderTest.getModemActivityInfo(obtainMessage());
+        mRILUnderTest.getModemActivityInfo(obtainMessage(), new WorkSource());
         verify(mRadioProxy).getModemActivityInfo(mSerialNumberCaptor.capture());
         verifyRILResponse(
                 mRILUnderTest, mSerialNumberCaptor.getValue(), RIL_REQUEST_GET_ACTIVITY_INFO);
@@ -945,7 +953,7 @@
     @FlakyTest
     @Test
     public void testGetModemActivityInfoTimeout() {
-        mRILUnderTest.getModemActivityInfo(obtainMessage());
+        mRILUnderTest.getModemActivityInfo(obtainMessage(), new WorkSource());
         assertEquals(1, mRILUnderTest.getRilRequestList().size());
         waitForHandlerActionDelayed(mRilHandler, 10, DEFAULT_BLOCKING_MESSAGE_RESPONSE_TIMEOUT_MS);
         assertEquals(0, mRILUnderTest.getRilRequestList().size());
@@ -978,7 +986,8 @@
     @FlakyTest
     @Test
     public void testSetSimCardPowerForPowerDownState() throws Exception {
-        mRILUnderTest.setSimCardPower(TelephonyManager.CARD_POWER_DOWN, obtainMessage());
+        mRILUnderTest.setSimCardPower(TelephonyManager.CARD_POWER_DOWN, obtainMessage(),
+                new WorkSource());
         verify(mRadioProxy).setSimCardPower(mSerialNumberCaptor.capture(), eq(false));
         verifyRILResponse(
                 mRILUnderTest, mSerialNumberCaptor.getValue(), RIL_REQUEST_SET_SIM_CARD_POWER);
@@ -987,7 +996,8 @@
     @FlakyTest
     @Test
     public void testSetSimCardPowerForPowerUpState() throws Exception {
-        mRILUnderTest.setSimCardPower(TelephonyManager.CARD_POWER_UP, obtainMessage());
+        mRILUnderTest.setSimCardPower(TelephonyManager.CARD_POWER_UP, obtainMessage(),
+                new WorkSource());
         verify(mRadioProxy).setSimCardPower(mSerialNumberCaptor.capture(), eq(true));
         verifyRILResponse(
                 mRILUnderTest, mSerialNumberCaptor.getValue(), RIL_REQUEST_SET_SIM_CARD_POWER);
@@ -1100,7 +1110,6 @@
         CellInfoLte expected = new CellInfoLte();
         expected.setRegistered(false);
         expected.setTimeStamp(TIMESTAMP);
-        expected.setTimeStampType(RIL_TIMESTAMP_TYPE_OEM_RIL);
         CellIdentityLte cil = new CellIdentityLte(CI, PCI, TAC, EARFCN, Integer.MAX_VALUE, MCC_STR,
                 MNC_STR, EMPTY_ALPHA_LONG, EMPTY_ALPHA_SHORT);
         CellSignalStrengthLte css = new CellSignalStrengthLte(
@@ -1108,6 +1117,7 @@
         expected.setCellIdentity(cil);
         expected.setCellSignalStrength(css);
         expected.setCellConnectionStatus(CellInfo.CONNECTION_UNKNOWN);
+        cellInfoLte.setTimeStamp(TIMESTAMP); // override the timestamp
         assertEquals(expected, cellInfoLte);
     }
 
@@ -1141,7 +1151,6 @@
         CellInfoGsm expected = new CellInfoGsm();
         expected.setRegistered(false);
         expected.setTimeStamp(TIMESTAMP);
-        expected.setTimeStampType(RIL_TIMESTAMP_TYPE_OEM_RIL);
         CellIdentityGsm ci = new CellIdentityGsm(
                 LAC, CID, ARFCN, BSIC, MCC_STR, MNC_STR, EMPTY_ALPHA_LONG, EMPTY_ALPHA_SHORT);
         CellSignalStrengthGsm cs = new CellSignalStrengthGsm(
@@ -1149,6 +1158,7 @@
         expected.setCellIdentity(ci);
         expected.setCellSignalStrength(cs);
         expected.setCellConnectionStatus(CellInfo.CONNECTION_UNKNOWN);
+        cellInfoGsm.setTimeStamp(TIMESTAMP); // override the timestamp
         assertEquals(expected, cellInfoGsm);
     }
 
@@ -1181,17 +1191,59 @@
         CellInfoWcdma expected = new CellInfoWcdma();
         expected.setRegistered(false);
         expected.setTimeStamp(TIMESTAMP);
-        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);
+        cellInfoWcdma.setTimeStamp(TIMESTAMP); // override the timestamp
         assertEquals(expected, cellInfoWcdma);
     }
 
     @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.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);
+        cellInfoTdscdma.setTimeStamp(TIMESTAMP); // override the timestamp
+        assertEquals(expected, cellInfoTdscdma);
+    }
+
+    @Test
     public void testConvertHalCellInfoListForCdma() throws Exception {
         android.hardware.radio.V1_0.CellInfoCdma cellinfo =
                 new android.hardware.radio.V1_0.CellInfoCdma();
@@ -1222,7 +1274,6 @@
         CellInfoCdma expected = new CellInfoCdma();
         expected.setRegistered(false);
         expected.setTimeStamp(TIMESTAMP);
-        expected.setTimeStampType(RIL_TIMESTAMP_TYPE_OEM_RIL);
         CellIdentityCdma ci = new CellIdentityCdma(
                 NETWORK_ID, SYSTEM_ID, BASESTATION_ID, LONGITUDE, LATITUDE,
                 EMPTY_ALPHA_LONG, EMPTY_ALPHA_SHORT);
@@ -1231,6 +1282,7 @@
         expected.setCellIdentity(ci);
         expected.setCellSignalStrength(cs);
         expected.setCellConnectionStatus(CellInfo.CONNECTION_UNKNOWN);
+        cellInfoCdma.setTimeStamp(TIMESTAMP); // override the timestamp
         assertEquals(expected, cellInfoCdma);
     }
 
@@ -1243,7 +1295,6 @@
         CellInfoLte expected = new CellInfoLte();
         expected.setRegistered(false);
         expected.setTimeStamp(TIMESTAMP);
-        expected.setTimeStampType(RIL_TIMESTAMP_TYPE_OEM_RIL);
         CellIdentityLte cil = new CellIdentityLte(
                 CI, PCI, TAC, EARFCN, BANDWIDTH, MCC_STR, MNC_STR, ALPHA_LONG, ALPHA_SHORT);
         CellSignalStrengthLte css = new CellSignalStrengthLte(
@@ -1251,6 +1302,7 @@
         expected.setCellIdentity(cil);
         expected.setCellSignalStrength(css);
         expected.setCellConnectionStatus(CellInfo.CONNECTION_NONE);
+        cellInfoLte.setTimeStamp(TIMESTAMP); // override the timestamp
         assertEquals(expected, cellInfoLte);
     }
 
@@ -1264,7 +1316,6 @@
         CellInfoLte expected = new CellInfoLte();
         expected.setRegistered(false);
         expected.setTimeStamp(TIMESTAMP);
-        expected.setTimeStampType(RIL_TIMESTAMP_TYPE_OEM_RIL);
         CellIdentityLte cil = new CellIdentityLte(CI, PCI, TAC, EARFCN, BANDWIDTH, MCC_STR, MNC_STR,
                 EMPTY_ALPHA_LONG, EMPTY_ALPHA_SHORT);
         CellSignalStrengthLte css = new CellSignalStrengthLte(
@@ -1272,6 +1323,7 @@
         expected.setCellIdentity(cil);
         expected.setCellSignalStrength(css);
         expected.setCellConnectionStatus(CellInfo.CONNECTION_NONE);
+        cellInfoLte.setTimeStamp(TIMESTAMP); // override the timestamp
         assertEquals(expected, cellInfoLte);
     }
 
@@ -1287,7 +1339,6 @@
         CellInfoLte expected = new CellInfoLte();
         expected.setRegistered(false);
         expected.setTimeStamp(TIMESTAMP);
-        expected.setTimeStampType(RIL_TIMESTAMP_TYPE_OEM_RIL);
         CellIdentityLte cil = new CellIdentityLte(
                 CI, PCI, TAC, EARFCN, BANDWIDTH, null, null, ALPHA_LONG, ALPHA_SHORT);
         CellSignalStrengthLte css = new CellSignalStrengthLte(
@@ -1295,6 +1346,7 @@
         expected.setCellIdentity(cil);
         expected.setCellSignalStrength(css);
         expected.setCellConnectionStatus(CellInfo.CONNECTION_NONE);
+        cellInfoLte.setTimeStamp(TIMESTAMP); // override the timestamp
         assertEquals(expected, cellInfoLte);
     }
 
@@ -1307,7 +1359,6 @@
         CellInfoGsm expected = new CellInfoGsm();
         expected.setRegistered(false);
         expected.setTimeStamp(TIMESTAMP);
-        expected.setTimeStampType(RIL_TIMESTAMP_TYPE_OEM_RIL);
         CellIdentityGsm ci = new CellIdentityGsm(
                 LAC, CID, ARFCN, BSIC, MCC_STR, MNC_STR, ALPHA_LONG, ALPHA_SHORT);
         CellSignalStrengthGsm cs = new CellSignalStrengthGsm(
@@ -1315,6 +1366,7 @@
         expected.setCellIdentity(ci);
         expected.setCellSignalStrength(cs);
         expected.setCellConnectionStatus(CellInfo.CONNECTION_NONE);
+        cellInfoGsm.setTimeStamp(TIMESTAMP); // override the timestamp
         assertEquals(expected, cellInfoGsm);
     }
 
@@ -1328,7 +1380,6 @@
         CellInfoGsm expected = new CellInfoGsm();
         expected.setRegistered(false);
         expected.setTimeStamp(TIMESTAMP);
-        expected.setTimeStampType(RIL_TIMESTAMP_TYPE_OEM_RIL);
         CellIdentityGsm ci = new CellIdentityGsm(
                 LAC, CID, ARFCN, BSIC, MCC_STR, MNC_STR, EMPTY_ALPHA_LONG, EMPTY_ALPHA_SHORT);
         CellSignalStrengthGsm cs = new CellSignalStrengthGsm(
@@ -1336,6 +1387,7 @@
         expected.setCellIdentity(ci);
         expected.setCellSignalStrength(cs);
         expected.setCellConnectionStatus(CellInfo.CONNECTION_NONE);
+        cellInfoGsm.setTimeStamp(TIMESTAMP); // override the timestamp
         assertEquals(expected, cellInfoGsm);
     }
 
@@ -1351,7 +1403,6 @@
         CellInfoGsm expected = new CellInfoGsm();
         expected.setRegistered(false);
         expected.setTimeStamp(TIMESTAMP);
-        expected.setTimeStampType(RIL_TIMESTAMP_TYPE_OEM_RIL);
         CellIdentityGsm ci = new CellIdentityGsm(
                 LAC, CID, ARFCN, BSIC, null, null, ALPHA_LONG, ALPHA_SHORT);
         CellSignalStrengthGsm cs = new CellSignalStrengthGsm(
@@ -1359,6 +1410,7 @@
         expected.setCellIdentity(ci);
         expected.setCellConnectionStatus(CellInfo.CONNECTION_NONE);
         expected.setCellSignalStrength(cs);
+        cellInfoGsm.setTimeStamp(TIMESTAMP); // override the timestamp
         assertEquals(expected, cellInfoGsm);
     }
 
@@ -1372,13 +1424,14 @@
         CellInfoWcdma expected = new CellInfoWcdma();
         expected.setRegistered(false);
         expected.setTimeStamp(TIMESTAMP);
-        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);
+        cellInfoWcdma.setTimeStamp(TIMESTAMP); // override the timestamp
         assertEquals(expected, cellInfoWcdma);
     }
 
@@ -1392,13 +1445,14 @@
         CellInfoWcdma expected = new CellInfoWcdma();
         expected.setRegistered(false);
         expected.setTimeStamp(TIMESTAMP);
-        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);
+        cellInfoWcdma.setTimeStamp(TIMESTAMP); // override the timestamp
         assertEquals(expected, cellInfoWcdma);
     }
 
@@ -1414,13 +1468,14 @@
         CellInfoWcdma expected = new CellInfoWcdma();
         expected.setRegistered(false);
         expected.setTimeStamp(TIMESTAMP);
-        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);
+        cellInfoWcdma.setTimeStamp(TIMESTAMP); // override the timestamp
         assertEquals(expected, cellInfoWcdma);
     }
 
@@ -1433,7 +1488,6 @@
         CellInfoCdma expected = new CellInfoCdma();
         expected.setRegistered(false);
         expected.setTimeStamp(TIMESTAMP);
-        expected.setTimeStampType(RIL_TIMESTAMP_TYPE_OEM_RIL);
         CellIdentityCdma ci = new CellIdentityCdma(
                 NETWORK_ID, SYSTEM_ID, BASESTATION_ID, LONGITUDE, LATITUDE,
                 ALPHA_LONG, ALPHA_SHORT);
@@ -1442,6 +1496,7 @@
         expected.setCellIdentity(ci);
         expected.setCellSignalStrength(cs);
         expected.setCellConnectionStatus(CellInfo.CONNECTION_NONE);
+        cellInfoCdma.setTimeStamp(TIMESTAMP); // override the timestamp
         assertEquals(expected, cellInfoCdma);
     }
 
@@ -1454,7 +1509,6 @@
         CellInfoCdma expected = new CellInfoCdma();
         expected.setRegistered(false);
         expected.setTimeStamp(TIMESTAMP);
-        expected.setTimeStampType(RIL_TIMESTAMP_TYPE_OEM_RIL);
         CellIdentityCdma ci = new CellIdentityCdma(
                 NETWORK_ID, SYSTEM_ID, BASESTATION_ID, LONGITUDE, LATITUDE,
                 EMPTY_ALPHA_LONG, EMPTY_ALPHA_SHORT);
@@ -1463,6 +1517,7 @@
         expected.setCellIdentity(ci);
         expected.setCellSignalStrength(cs);
         expected.setCellConnectionStatus(CellInfo.CONNECTION_NONE);
+        cellInfoCdma.setTimeStamp(TIMESTAMP); // override the timestamp
         assertEquals(expected, cellInfoCdma);
     }
 
@@ -1564,8 +1619,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;
@@ -1667,11 +1722,12 @@
     }
 
     @Test
+    @FlakyTest
     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);
+                ROAMING_PROTOCOL, BEARER_BITMAP, MTU, PERSISTENT, false);
         mRILUnderTest.setupDataCall(AccessNetworkConstants.AccessNetworkType.EUTRAN, dp, false,
                 false, 0, null, obtainMessage());
         ArgumentCaptor<DataProfileInfo> dpiCaptor = ArgumentCaptor.forClass(DataProfileInfo.class);
@@ -1696,7 +1752,5 @@
         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 0fa88bf..c8524a9 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTest.java
@@ -35,6 +35,13 @@
     @SmallTest
     public void testRoaming() {
         ServiceState ss = new ServiceState();
+        // add data registration state
+        ss.addNetworkRegistrationState(new NetworkRegistrationState(
+                NetworkRegistrationState.DOMAIN_PS, AccessNetworkConstants.TransportType.WWAN,
+                NetworkRegistrationState.REG_STATE_ROAMING,
+                TelephonyManager.NETWORK_TYPE_UNKNOWN, 0,
+                false, null, null));
+        assertTrue(ss.getDataRoamingFromRegistration());
 
         ss.setCdmaDefaultRoamingIndicator(1);
         assertEquals(1, ss.getCdmaDefaultRoamingIndicator());
@@ -52,9 +59,6 @@
         assertTrue(ss.getDataRoaming());
         assertEquals(ServiceState.ROAMING_TYPE_DOMESTIC, ss.getDataRoamingType());
 
-        ss.setDataRoamingFromRegistration(true);
-        assertTrue(ss.getDataRoamingFromRegistration());
-
         ss.setVoiceRoamingType(ServiceState.ROAMING_TYPE_DOMESTIC);
         assertTrue(ss.getVoiceRoaming());
         assertEquals(ServiceState.ROAMING_TYPE_DOMESTIC, ss.getVoiceRoamingType());
@@ -238,7 +242,6 @@
         ss.setCdmaEriIconIndex(6);
         ss.setCdmaEriIconMode(7);
         ss.setEmergencyOnly(true);
-        ss.setDataRoamingFromRegistration(true);
         ss.setChannelNumber(2100);
         ss.setCellBandwidths(new int[]{1400, 5000, 10000});
 
@@ -268,7 +271,6 @@
         ss.setCdmaEriIconIndex(6);
         ss.setCdmaEriIconMode(7);
         ss.setEmergencyOnly(true);
-        ss.setDataRoamingFromRegistration(true);
         ss.setChannelNumber(2100);
         ss.setCellBandwidths(new int[]{3, 4, 10});
 
@@ -282,18 +284,17 @@
     @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,
-                0, 0, 0, false,
-                null, null, 0);
+                NetworkRegistrationState.DOMAIN_PS, AccessNetworkConstants.TransportType.WWAN,
+                0, 0, 0, false, null, null, 0, false, false, false);
 
         NetworkRegistrationState wlanRegState = new NetworkRegistrationState(
-                AccessNetworkConstants.TransportType.WLAN, NetworkRegistrationState.DOMAIN_PS,
+                NetworkRegistrationState.DOMAIN_PS, AccessNetworkConstants.TransportType.WLAN,
                 0, 0, 0, false,
                 null, null);
 
@@ -303,20 +304,19 @@
         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,
-                0, 0, 0, true,
-                null, null, 0);
+                NetworkRegistrationState.DOMAIN_PS, AccessNetworkConstants.TransportType.WWAN,
+                0, 0, 0, true, null, null, 0, false, false, false);
         ss.addNetworkRegistrationState(wwanDataRegState);
-        assertEquals(ss.getNetworkRegistrationStates(AccessNetworkConstants.TransportType.WWAN,
-                NetworkRegistrationState.DOMAIN_PS), wwanDataRegState);
+        assertEquals(ss.getNetworkRegistrationStates(NetworkRegistrationState.DOMAIN_PS,
+                AccessNetworkConstants.TransportType.WWAN), wwanDataRegState);
     }
 
     @SmallTest
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java
index cb9f2ad..2fa4d3f 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java
@@ -38,6 +38,7 @@
 import android.app.IAlarmManager;
 import android.app.Notification;
 import android.app.NotificationManager;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -49,6 +50,7 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.os.Looper;
 import android.os.Message;
 import android.os.Parcel;
 import android.os.PersistableBundle;
@@ -59,10 +61,13 @@
 import android.support.test.filters.FlakyTest;
 import android.telephony.AccessNetworkConstants.AccessNetworkType;
 import android.telephony.CarrierConfigManager;
+import android.telephony.CellIdentity;
+import android.telephony.CellIdentityCdma;
 import android.telephony.CellIdentityGsm;
 import android.telephony.CellIdentityLte;
 import android.telephony.CellInfo;
 import android.telephony.CellInfoGsm;
+import android.telephony.INetworkService;
 import android.telephony.NetworkRegistrationState;
 import android.telephony.NetworkService;
 import android.telephony.PhysicalChannelConfig;
@@ -70,17 +75,17 @@
 import android.telephony.SignalStrength;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
+import android.telephony.cdma.CdmaCellLocation;
 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;
@@ -96,9 +101,6 @@
 import java.util.List;
 
 public class ServiceStateTrackerTest extends TelephonyTest {
-
-    @Mock
-    private DcTracker mDct;
     @Mock
     private ProxyController mProxyController;
     @Mock
@@ -106,7 +108,12 @@
     @Mock
     protected IAlarmManager mAlarmManager;
 
-    CellularNetworkService mCellularNetworkService;
+    private CellularNetworkService mCellularNetworkService;
+
+    @Mock
+    private NetworkService mIwlanNetworkService;
+    @Mock
+    private INetworkService.Stub mIwlanNetworkServiceStub;
 
     private ServiceStateTracker sst;
     private ServiceStateTrackerTestHandler mSSTTestHandler;
@@ -123,6 +130,7 @@
     private static final int EVENT_PS_RESTRICT_DISABLED = 9;
     private static final int EVENT_VOICE_ROAMING_ON = 10;
     private static final int EVENT_VOICE_ROAMING_OFF = 11;
+    private static final int EVENT_VOICE_RAT_CHANGED = 12;
 
     private class ServiceStateTrackerTestHandler extends HandlerThread {
 
@@ -139,17 +147,33 @@
 
     private void addNetworkService() {
         mCellularNetworkService = new CellularNetworkService();
-        ServiceInfo serviceInfo =  new ServiceInfo();
-        serviceInfo.packageName = "com.android.phone";
-        serviceInfo.permission = "android.permission.BIND_NETWORK_SERVICE";
-        IntentFilter filter = new IntentFilter();
+        ServiceInfo CellularServiceInfo = new ServiceInfo();
+        CellularServiceInfo.packageName = "com.android.phone";
+        CellularServiceInfo.name = "CellularNetworkService";
+        CellularServiceInfo.permission = "android.permission.BIND_TELEPHONY_NETWORK_SERVICE";
+        IntentFilter cellularIntentfilter = new IntentFilter();
         mContextFixture.addService(
                 NetworkService.NETWORK_SERVICE_INTERFACE,
-                null,
+                new ComponentName("com.android.phone",
+                        "com.android.internal.telephony.CellularNetworkService"),
                 "com.android.phone",
                 mCellularNetworkService.mBinder,
-                serviceInfo,
-                filter);
+                CellularServiceInfo,
+                cellularIntentfilter);
+
+        ServiceInfo iwlanServiceInfo = new ServiceInfo();
+        iwlanServiceInfo.packageName = "com.xyz.iwlan.networkservice";
+        iwlanServiceInfo.name = "IwlanNetworkService";
+        iwlanServiceInfo.permission = "android.permission.BIND_TELEPHONY_NETWORK_SERVICE";
+        IntentFilter iwlanIntentFilter = new IntentFilter();
+        mContextFixture.addService(
+                NetworkService.NETWORK_SERVICE_INTERFACE,
+                new ComponentName("com.xyz.iwlan.networkservice",
+                        "com.xyz.iwlan.IwlanNetworkService"),
+                "com.xyz.iwlan.networkservice",
+                mIwlanNetworkServiceStub,
+                iwlanServiceInfo,
+                iwlanIntentFilter);
     }
 
     @Before
@@ -160,10 +184,12 @@
 
         mContextFixture.putResource(R.string.config_wwan_network_service_package,
                 "com.android.phone");
+        mContextFixture.putResource(R.string.config_wlan_network_service_package,
+                "com.xyz.iwlan.networkservice");
+        doReturn(mIwlanNetworkServiceStub).when(mIwlanNetworkServiceStub).asBinder();
         addNetworkService();
 
-        doReturn(true).when(mDct).isDisconnected();
-        mPhone.mDcTracker = mDct;
+        doReturn(true).when(mDcTracker).isDisconnected();
 
         replaceInstance(ProxyController.class, "sProxyController", null, mProxyController);
         mBundle = mContextFixture.getCarrierConfigBundle();
@@ -184,6 +210,7 @@
 
         int dds = SubscriptionManager.getDefaultDataSubscriptionId();
         doReturn(dds).when(mPhone).getSubId();
+        doReturn(true).when(mPhone).areAllDataDisconnected();
 
         mSSTTestHandler = new ServiceStateTrackerTestHandler(getClass().getSimpleName());
         mSSTTestHandler.start();
@@ -196,16 +223,30 @@
     public void tearDown() throws Exception {
         sst = null;
         mSSTTestHandler.quit();
+        mSSTTestHandler.join();
         super.tearDown();
     }
 
     @Test
     @MediumTest
     public void testSetRadioPower() {
-        boolean oldState = mSimulatedCommands.getRadioState().isOn();
+        boolean oldState = (mSimulatedCommands.getRadioState() == TelephonyManager.RADIO_POWER_ON);
         sst.setRadioPower(!oldState);
         waitForMs(100);
-        assertTrue(oldState != mSimulatedCommands.getRadioState().isOn());
+        assertTrue(oldState
+                != (mSimulatedCommands.getRadioState() == TelephonyManager.RADIO_POWER_ON));
+    }
+
+    @Test
+    @MediumTest
+    public void testSetRadioPowerOffUnderDataConnected() {
+        sst.setRadioPower(true);
+        waitForMs(100);
+        doReturn(false).when(mPhone).areAllDataDisconnected();
+        sst.setRadioPower(false);
+        waitForMs(200);
+        verify(this.mProxyController, times(1)).registerForAllDataDisconnected(anyInt(),
+                 eq(sst), anyInt());
     }
 
     @Test
@@ -214,21 +255,23 @@
         // Carrier disable radio power
         sst.setRadioPowerFromCarrier(false);
         waitForMs(100);
-        assertFalse(mSimulatedCommands.getRadioState().isOn());
+        assertFalse(mSimulatedCommands.getRadioState()
+                == TelephonyManager.RADIO_POWER_ON);
         assertTrue(sst.getDesiredPowerState());
         assertFalse(sst.getPowerStateFromCarrier());
 
         // User toggle radio power will not overrides carrier settings
         sst.setRadioPower(true);
         waitForMs(100);
-        assertFalse(mSimulatedCommands.getRadioState().isOn());
+        assertFalse(mSimulatedCommands.getRadioState()
+                == TelephonyManager.RADIO_POWER_ON);
         assertTrue(sst.getDesiredPowerState());
         assertFalse(sst.getPowerStateFromCarrier());
 
         // Carrier re-enable radio power
         sst.setRadioPowerFromCarrier(true);
         waitForMs(100);
-        assertTrue(mSimulatedCommands.getRadioState().isOn());
+        assertTrue(mSimulatedCommands.getRadioState() == TelephonyManager.RADIO_POWER_ON);
         assertTrue(sst.getDesiredPowerState());
         assertTrue(sst.getPowerStateFromCarrier());
 
@@ -236,7 +279,8 @@
         sst.setRadioPower(false);
         sst.setRadioPowerFromCarrier(true);
         waitForMs(100);
-        assertFalse(mSimulatedCommands.getRadioState().isOn());
+        assertFalse(mSimulatedCommands.getRadioState()
+                == TelephonyManager.RADIO_POWER_ON);
         assertFalse(sst.getDesiredPowerState());
         assertTrue(sst.getPowerStateFromCarrier());
     }
@@ -329,31 +373,106 @@
                 intArgumentCaptor.getValue().intValue());
     }
 
-    @Test
-    @MediumTest
-    public void testCellInfoList() {
+    private CellInfoGsm getCellInfoGsm() {
         Parcel p = Parcel.obtain();
+        // CellInfo
         p.writeInt(1);
         p.writeInt(1);
         p.writeInt(2);
         p.writeLong(1453510289108L);
-        p.writeInt(310);
-        p.writeInt(260);
+        p.writeInt(0);
+        // CellIdentity
+        p.writeInt(1);
+        p.writeString("310");
+        p.writeString("260");
+        p.writeString("long");
+        p.writeString("short");
+        // CellIdentityGsm
         p.writeInt(123);
         p.writeInt(456);
+        p.writeInt(950);
+        p.writeInt(27);
+        // CellSignalStrength
         p.writeInt(99);
+        p.writeInt(0);
         p.writeInt(3);
         p.setDataPosition(0);
 
-        CellInfoGsm cellInfo = CellInfoGsm.CREATOR.createFromParcel(p);
+        return CellInfoGsm.CREATOR.createFromParcel(p);
+    }
 
+    @Test
+    @MediumTest
+    public void testCachedCellInfoList() {
         ArrayList<CellInfo> list = new ArrayList();
-        list.add(cellInfo);
+        list.add(getCellInfoGsm());
         mSimulatedCommands.setCellInfoList(list);
 
         WorkSource workSource = new WorkSource(Process.myUid(),
                 mContext.getPackageName());
-        assertEquals(sst.getAllCellInfo(workSource), list);
+
+        // null worksource and no response message will update the writethrough cache
+        sst.requestAllCellInfo(null, null);
+        waitForMs(200);
+        assertEquals(sst.getAllCellInfo(), list);
+    }
+
+    private static class CellInfoHandler extends Handler {
+        // Need to define this here so that it's accessible
+        public List<CellInfo> cellInfoResult;
+
+        CellInfoHandler(Looper l) {
+            super(l);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            synchronized (msg) {
+                assertTrue("handler received null message", msg.obj != null);
+                AsyncResult ar = (AsyncResult) msg.obj;
+                cellInfoResult = (List<CellInfo>) ar.result;
+                msg.notifyAll();
+            }
+        }
+    }
+
+    @Test
+    @MediumTest
+    public void testGetCellInfoResponse() throws InterruptedException {
+        mSimulatedCommands.setCellInfoListBehavior(true);
+        ArrayList<CellInfo> list = new ArrayList();
+        list.add(getCellInfoGsm());
+        mSimulatedCommands.setCellInfoList(list);
+        CellInfoHandler cih = new CellInfoHandler(mSSTTestHandler.getLooper());
+
+        Message rsp = cih.obtainMessage(0x7357);
+
+        sst.requestAllCellInfo(null, rsp);
+
+        synchronized (rsp) {
+            if (cih.cellInfoResult == null) rsp.wait(5000);
+        }
+
+        AsyncResult ar = (AsyncResult) rsp.obj;
+        assertTrue("CellInfo Response Not Received", cih.cellInfoResult != null);
+        assertEquals(getCellInfoGsm(), cih.cellInfoResult.get(0));
+    }
+
+    @Test
+    @MediumTest
+    public void testGetCellInfoResponseTimeout() throws InterruptedException {
+        mSimulatedCommands.setCellInfoListBehavior(false);
+        CellInfoHandler cih = new CellInfoHandler(mSSTTestHandler.getLooper());
+
+        Message rsp = cih.obtainMessage(0x7357);
+
+        sst.requestAllCellInfo(null, rsp);
+
+        synchronized (rsp) {
+            if (cih.cellInfoResult == null) rsp.wait(5000);
+        }
+
+        assertTrue("Spurious CellInfo Response Received", cih.cellInfoResult == null);
     }
 
     @Test
@@ -625,6 +744,7 @@
 
     @Test
     @MediumTest
+    // TODO(nharold): we probably should remove support for this procedure (GET_LOC)
     public void testGsmCellLocation() {
         CellIdentityGsm cellIdentityGsm = new CellIdentityGsm(0, 0, 2, 3);
         NetworkRegistrationState result = new NetworkRegistrationState(
@@ -635,11 +755,30 @@
 
         waitForMs(200);
         WorkSource workSource = new WorkSource(Process.myUid(), mContext.getPackageName());
-        GsmCellLocation cl = (GsmCellLocation) sst.getCellLocation(workSource);
+        GsmCellLocation cl = (GsmCellLocation) sst.getCellLocation();
         assertEquals(2, cl.getLac());
         assertEquals(3, cl.getCid());
     }
 
+    @FlakyTest /* flakes 0.86% of the time */
+    @Test
+    @MediumTest
+    // TODO(nharold): we probably should remove support for this procedure (GET_LOC)
+    public void testCdmaCellLocation() {
+        CellIdentityCdma cellIdentityCdma = new CellIdentityCdma(1, 2, 3, 4, 5);
+        NetworkRegistrationState result = new NetworkRegistrationState(
+                0, 0, 0, 0, 0, false, null, cellIdentityCdma);
+
+        sst.sendMessage(sst.obtainMessage(ServiceStateTracker.EVENT_GET_LOC_DONE,
+                new AsyncResult(null, result, null)));
+
+        waitForMs(200);
+        WorkSource workSource = new WorkSource(Process.myUid(), mContext.getPackageName());
+        CdmaCellLocation cl = (CdmaCellLocation) sst.getCellLocation();
+        assertEquals(5, cl.getBaseStationLatitude());
+        assertEquals(4, cl.getBaseStationLongitude());
+    }
+
     @Test
     @MediumTest
     public void testUpdatePhoneType() {
@@ -994,6 +1133,25 @@
 
     @Test
     @MediumTest
+    public void testRegisterForVoiceRegStateOrRatChange() {
+        int vrs = NetworkRegistrationState.REG_STATE_HOME;
+        int vrat = sst.mSS.RIL_RADIO_TECHNOLOGY_LTE;
+        sst.mSS.setRilVoiceRadioTechnology(vrat);
+        sst.mSS.setVoiceRegState(vrs);
+        sst.registerForVoiceRegStateOrRatChanged(mTestHandler, EVENT_VOICE_RAT_CHANGED, null);
+
+        waitForMs(100);
+
+        // Verify if message was posted to handler and value of result
+        ArgumentCaptor<Message> messageArgumentCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(mTestHandler).sendMessageAtTime(messageArgumentCaptor.capture(), anyLong());
+        assertEquals(EVENT_VOICE_RAT_CHANGED, messageArgumentCaptor.getValue().what);
+        assertEquals(new Pair<Integer, Integer>(vrs, vrat),
+                ((AsyncResult)messageArgumentCaptor.getValue().obj).result);
+    }
+
+    @Test
+    @MediumTest
     public void testRegisterForDataRegStateOrRatChange() {
         int drs = NetworkRegistrationState.REG_STATE_HOME;
         int rat = sst.mSS.RIL_RADIO_TECHNOLOGY_LTE;
@@ -1011,6 +1169,7 @@
                 ((AsyncResult)messageArgumentCaptor.getValue().obj).result);
     }
 
+    @FlakyTest /* flakes 0.43% of the time */
     @Test
     @MediumTest
     public void testRegAndUnregForNetworkAttached() throws Exception {
@@ -1459,7 +1618,8 @@
 
         sst.requestShutdown();
         waitForMs(100);
-        assertFalse(mSimulatedCommands.getRadioState().isAvailable());
+        assertFalse(mSimulatedCommands.getRadioState()
+                != TelephonyManager.RADIO_POWER_UNAVAILABLE);
     }
 
     @Test
@@ -1472,10 +1632,11 @@
         mSimulatedCommands.setRadioPowerFailResponse(true);
         sst.setRadioPower(false);
         waitForMs(100);
-        assertTrue(mSimulatedCommands.getRadioState().isOn());
+        assertTrue(mSimulatedCommands.getRadioState() == TelephonyManager.RADIO_POWER_ON);
         sst.requestShutdown();
         waitForMs(100);
-        assertFalse(mSimulatedCommands.getRadioState().isAvailable());
+        assertFalse(mSimulatedCommands.getRadioState()
+                != TelephonyManager.RADIO_POWER_UNAVAILABLE);
     }
 
     @Test
@@ -1494,53 +1655,52 @@
             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());
         }
     }
 
+    private void changeRegState(int state, CellIdentity cid, int rat) {
+        changeRegState(state, cid, rat, rat);
+    }
+
+    private void changeRegState(int state, CellIdentity cid, int voiceRat, int dataRat) {
+        NetworkRegistrationState dataResult = new NetworkRegistrationState(
+                0, 0, state, dataRat, 0, false, null, cid, 1, false, false, false);
+        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);
+        NetworkRegistrationState voiceResult = new NetworkRegistrationState(
+                0, 0, state, voiceRat, 0, false, null, cid, false, 0, 0, 0);
+        sst.sendMessage(sst.obtainMessage(ServiceStateTracker.EVENT_POLL_STATE_REGISTRATION,
+                new AsyncResult(sst.mPollingContext, voiceResult, null)));
+        waitForMs(200);
+    }
+
     // Edge and GPRS are grouped under the same family and Edge has higher rate than GPRS.
     // Expect no rat update when move from E to G.
     @Test
     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());
+        // start on GPRS
+        changeRegState(1, cellIdentity, 16, 1);
+        assertEquals(ServiceState.STATE_IN_SERVICE, sst.getCurrentDataConnectionState());
+        assertEquals(ServiceState.RIL_RADIO_TECHNOLOGY_GPRS, sst.mSS.getRilDataRadioTechnology());
+        // upgrade to EDGE
+        changeRegState(1, cellIdentity, 16, 2);
+        assertEquals(ServiceState.RIL_RADIO_TECHNOLOGY_EDGE, sst.mSS.getRilDataRadioTechnology());
+        // drop back to GPRS and expect a ratchet
+        changeRegState(1, cellIdentity, 16, 1);
+        assertEquals(ServiceState.RIL_RADIO_TECHNOLOGY_EDGE, sst.mSS.getRilDataRadioTechnology());
     }
 
     // Edge and GPRS are grouped under the same family and Edge has higher rate than GPRS.
@@ -1548,95 +1708,48 @@
     @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());
-
+        changeRegState(1, cellIdentity, 16, 2);
+        assertEquals(ServiceState.STATE_IN_SERVICE, sst.getCurrentDataConnectionState());
+        assertEquals(ServiceState.RIL_RADIO_TECHNOLOGY_GSM, sst.mSS.getRilVoiceRadioTechnology());
+        assertEquals(ServiceState.RIL_RADIO_TECHNOLOGY_EDGE, sst.mSS.getRilDataRadioTechnology());
         // 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());
+        changeRegState(1, cellIdentity, 16, 1);
+        assertEquals(ServiceState.RIL_RADIO_TECHNOLOGY_GPRS, sst.mSS.getRilDataRadioTechnology());
 
-        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  .
+    // TODO(nharold): This actually seems like broken behavior; rather than preserve it, we should
+    // probably remove it.
+    // GSM, Edge, GPRS are grouped under the same family where Edge > GPRS > GSM.
     // 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.
+    // Expect ratratchet (from G to E) 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());
+        changeRegState(1, cellIdentity, 16, 2);
+        assertEquals(ServiceState.STATE_IN_SERVICE, sst.getCurrentDataConnectionState());
+        assertEquals(ServiceState.RIL_RADIO_TECHNOLOGY_EDGE, sst.mSS.getRilDataRadioTechnology());
 
         // 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());
+        changeRegState(1, cellIdentity, 16, 1);
+        assertEquals(ServiceState.RIL_RADIO_TECHNOLOGY_GPRS, sst.mSS.getRilDataRadioTechnology());
 
-        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());
+        // RAT: GPRS -> EDGE should ratchet.
+        changeRegState(1, cellIdentity, 16, 2);
+        assertEquals(ServiceState.RIL_RADIO_TECHNOLOGY_EDGE, sst.mSS.getRilDataRadioTechnology());
     }
 
     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));
+            pc.add(new PhysicalChannelConfig.Builder()
+                    .setCellConnectionStatus(ssType)
+                    .setCellBandwidthDownlinkKhz(bw)
+                    .build());
 
             // All cells after the first are secondary serving cells.
             ssType = PhysicalChannelConfig.CONNECTION_SECONDARY_SERVING;
@@ -1648,7 +1761,8 @@
 
     private void sendRegStateUpdateForLteCellId(CellIdentityLte cellId) {
         NetworkRegistrationState dataResult = new NetworkRegistrationState(
-                1, 2, 1, TelephonyManager.NETWORK_TYPE_LTE, 0, false, null, cellId, 1);
+                2, 1, 1, TelephonyManager.NETWORK_TYPE_LTE, 0, false, null, cellId, 1,
+                false, false, false);
         NetworkRegistrationState voiceResult = new NetworkRegistrationState(
                 1, 1, 1, TelephonyManager.NETWORK_TYPE_LTE, 0, false, null, cellId,
                 false, 0, 0, 0);
@@ -1711,7 +1825,8 @@
     public void testPhyChanBandwidthResetsOnOos() throws Exception {
         testPhyChanBandwidthRatchetedOnPhyChanBandwidth();
         NetworkRegistrationState dataResult = new NetworkRegistrationState(
-                1, 2, 0, TelephonyManager.NETWORK_TYPE_UNKNOWN, 0, false, null, null, 1);
+                2, 1, 0, TelephonyManager.NETWORK_TYPE_UNKNOWN, 0, false, null, null, 1, false,
+                false, false);
         NetworkRegistrationState voiceResult = new NetworkRegistrationState(
                 1, 1, 0, TelephonyManager.NETWORK_TYPE_UNKNOWN, 0, false, null, null,
                 false, 0, 0, 0);
@@ -1724,4 +1839,49 @@
         waitForMs(200);
         assertTrue(Arrays.equals(new int[0], sst.mSS.getCellBandwidths()));
     }
+
+    @Test
+    @SmallTest
+    public void testGetMdn() throws Exception {
+        doReturn(false).when(mPhone).isPhoneTypeGsm();
+        doReturn(false).when(mPhone).isPhoneTypeCdma();
+        doReturn(true).when(mPhone).isPhoneTypeCdmaLte();
+        doReturn(CdmaSubscriptionSourceManager.SUBSCRIPTION_FROM_RUIM).when(mCdmaSSM)
+                .getCdmaSubscriptionSource();
+
+        logd("Calling updatePhoneType");
+        // switch to CDMA
+        sst.updatePhoneType();
+
+        // trigger RUIM_RECORDS_LOADED
+        ArgumentCaptor<Integer> integerArgumentCaptor = ArgumentCaptor.forClass(Integer.class);
+        verify(mRuimRecords).registerForRecordsLoaded(eq(sst), integerArgumentCaptor.capture(),
+                nullable(Object.class));
+
+        // response for mRuimRecords.registerForRecordsLoaded()
+        Message msg = Message.obtain();
+        msg.what = integerArgumentCaptor.getValue();
+        msg.obj = new AsyncResult(null, null, null);
+        sst.sendMessage(msg);
+
+        // wait for RUIM_RECORDS_LOADED to be handled
+        waitForHandlerAction(sst, 5000);
+
+        // mdn should be null as nothing populated it
+        assertEquals(null, sst.getMdnNumber());
+
+        // if ruim is provisioned, mdn should still be null
+        doReturn(true).when(mRuimRecords).isProvisioned();
+        assertEquals(null, sst.getMdnNumber());
+
+        // if ruim is not provisioned, and mdn is non null, sst should still return null
+        doReturn(false).when(mRuimRecords).isProvisioned();
+        String mockMdn = "mockMdn";
+        doReturn(mockMdn).when(mRuimRecords).getMdn();
+        assertEquals(null, sst.getMdnNumber());
+
+        // if ruim is provisioned, and mdn is non null, sst should also return the correct value
+        doReturn(true).when(mRuimRecords).isProvisioned();
+        assertEquals(mockMdn, sst.getMdnNumber());
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SignalStrengthTest.java b/tests/telephonytests/src/com/android/internal/telephony/SignalStrengthTest.java
index 3cd5239..0b33c80 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SignalStrengthTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SignalStrengthTest.java
@@ -35,9 +35,9 @@
     @Test
     public void testDefaults() throws Exception {
         SignalStrength s = new SignalStrength();
-        assertEquals(-1, s.getCdmaDbm());
+        assertEquals(SignalStrength.INVALID, s.getCdmaDbm());
         assertEquals(-1, s.getCdmaEcio());
-        assertEquals(-1, s.getEvdoDbm());
+        assertEquals(SignalStrength.INVALID, s.getEvdoDbm());
         assertEquals(-1, s.getEvdoEcio());
         assertEquals(-1, s.getEvdoSnr());
         assertEquals(-1, s.getGsmBitErrorRate());
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SmsDispatchersControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/SmsDispatchersControllerTest.java
index 41c033b..7f7e1ec 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SmsDispatchersControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SmsDispatchersControllerTest.java
@@ -21,7 +21,6 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.isNull;
@@ -32,11 +31,6 @@
 import static org.mockito.Mockito.verify;
 
 import android.app.ActivityManager;
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
 import android.os.HandlerThread;
 import android.os.Message;
 import android.provider.Telephony.Sms.Intents;
@@ -126,7 +120,7 @@
                 anyInt(), anyInt(), any(Message.class));
     }
 
-    @Test @SmallTest
+    @Test @SmallTest @FlakyTest /* flakes 0.73% of the time on gce, 0.57% on marlin */
     public void testSendImsCdmaTest() throws Exception {
         switchImsSmsFormat(PhoneConstants.PHONE_TYPE_CDMA);
         mSmsDispatchersController.sendText("111"/* desAddr*/, "222" /*scAddr*/, TAG,
@@ -135,7 +129,7 @@
                 any(Message.class));
     }
 
-    @Test @SmallTest
+    @Test @SmallTest @FlakyTest /* flakes 0.71% of the time on marlin, 0.61% on gce */
     public void testSendRetrySmsCdmaTest() throws Exception {
         // newFormat will be based on voice technology
         ArgumentCaptor<byte[]> captor = ArgumentCaptor.forClass(byte[].class);
@@ -150,7 +144,7 @@
         assertNull(captor.getAllValues().get(0));
     }
 
-    @Test @SmallTest
+    @Test @SmallTest @FlakyTest /* flakes 0.85% of the time on gce, 0.43% on marlin */
     public void testSendRetrySmsGsmTest() throws Exception {
         // newFormat will be based on voice technology will be GSM if phone type is not CDMA
         switchImsSmsFormat(PhoneConstants.PHONE_TYPE_GSM);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SubscriptionControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/SubscriptionControllerTest.java
index ef9f7d4..61b0077 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SubscriptionControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SubscriptionControllerTest.java
@@ -15,24 +15,29 @@
  */
 package com.android.internal.telephony;
 
-import static org.junit.Assert.*;
-import static org.mockito.Mockito.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 
-import android.app.AppOpsManager;
+import android.Manifest;
 import android.content.ContentValues;
 import android.content.Intent;
-import android.database.Cursor;
-import android.database.MatrixCursor;
-import android.net.Uri;
 import android.os.Bundle;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
-import android.test.mock.MockContentProvider;
 import android.test.mock.MockContentResolver;
 import android.test.suitebuilder.annotation.SmallTest;
-import android.util.Log;
 
 import org.junit.After;
 import org.junit.Before;
@@ -40,113 +45,16 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 
-import java.util.ArrayList;
 import java.util.List;
 
 public class SubscriptionControllerTest extends TelephonyTest {
-
     private static final int SINGLE_SIM = 1;
     private String mCallingPackage;
     private SubscriptionController mSubscriptionControllerUT;
     private MockContentResolver mMockContentResolver;
-
-    @Mock private List<SubscriptionInfo> mSubList;
-    @Mock private AppOpsManager mAppOps;
-
-    public class FakeSubscriptionContentProvider extends MockContentProvider {
-
-        private ArrayList<ContentValues> mSubscriptionArray =
-                new ArrayList<ContentValues>();
-
-        private String[] mKeyMappingSet = new String[]{
-                SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID,
-                SubscriptionManager.ICC_ID, SubscriptionManager.SIM_SLOT_INDEX,
-                SubscriptionManager.DISPLAY_NAME, SubscriptionManager.CARRIER_NAME,
-                SubscriptionManager.NAME_SOURCE, SubscriptionManager.COLOR,
-                SubscriptionManager.NUMBER, SubscriptionManager.DISPLAY_NUMBER_FORMAT,
-                SubscriptionManager.DATA_ROAMING, SubscriptionManager.MCC,
-                SubscriptionManager.MNC, SubscriptionManager.CB_EXTREME_THREAT_ALERT,
-                SubscriptionManager.CB_SEVERE_THREAT_ALERT, SubscriptionManager.CB_AMBER_ALERT,
-                SubscriptionManager.CB_ALERT_SOUND_DURATION,
-                SubscriptionManager.CB_ALERT_REMINDER_INTERVAL,
-                SubscriptionManager.CB_ALERT_VIBRATE, SubscriptionManager.CB_ALERT_SPEECH,
-                SubscriptionManager.CB_ETWS_TEST_ALERT, SubscriptionManager.CB_CHANNEL_50_ALERT,
-                SubscriptionManager.CB_CMAS_TEST_ALERT, SubscriptionManager.CB_OPT_OUT_DIALOG,
-                SubscriptionManager.SIM_PROVISIONING_STATUS, SubscriptionManager.IS_EMBEDDED,
-                SubscriptionManager.ACCESS_RULES, SubscriptionManager.ENHANCED_4G_MODE_ENABLED,
-                SubscriptionManager.VT_IMS_ENABLED, SubscriptionManager.WFC_IMS_ENABLED,
-                SubscriptionManager.WFC_IMS_MODE, SubscriptionManager.WFC_IMS_ROAMING_MODE,
-                SubscriptionManager.WFC_IMS_ROAMING_ENABLED,
-                SubscriptionManager.CARD_ID};
-
-        /* internal util function */
-        private MatrixCursor convertFromContentToCursor(ContentValues initialValues,
-                String[] projection) {
-            MatrixCursor cursor = null;
-            ArrayList<Object> values = new ArrayList<Object>();
-            if (projection == null) {
-                projection = mKeyMappingSet;
-            }
-            if (initialValues != null && projection.length != 0) {
-                cursor = new MatrixCursor(projection);
-                /* push value from contentValues to matrixCursors */
-                for (String key : projection) {
-                    if (initialValues.containsKey(key)) {
-                        values.add(initialValues.get(key));
-                    } else {
-                        values.add(null);
-                    }
-                }
-            }
-            cursor.addRow(values.toArray());
-            return cursor;
-        }
-
-        @Override
-        public int delete(Uri uri, String selection, String[] selectionArgs) {
-            if (mSubscriptionArray.size() > 0) {
-                mSubscriptionArray.remove(0);
-                return 1;
-            }
-            return -1;
-        }
-
-        @Override
-        public Uri insert(Uri uri, ContentValues values) {
-            values.put(SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID, 0);
-            mSubscriptionArray.add(values);
-            return uri;
-        }
-
-        @Override
-        public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
-                            String sortOrder) {
-            if (mSubscriptionArray.size() > 0) {
-                return convertFromContentToCursor(mSubscriptionArray.get(0), projection);
-            }
-            return null;
-        }
-
-        @Override
-        public Bundle call(String method, String request, Bundle args) {
-            return null;
-        }
-
-        @Override
-        public int update(android.net.Uri uri, android.content.ContentValues values,
-                          java.lang.String selection, java.lang.String[] selectionArgs) {
-            if (mSubscriptionArray.size() > 0) {
-                ContentValues val = mSubscriptionArray.get(0);
-                for (String key : values.keySet()) {
-                    val.put(key, values.getAsString(key));
-                    Log.d(TAG, "update the values..." + key + "..." + values.getAsString(key));
-                }
-                mSubscriptionArray.set(0, val);
-                return 1;
-            }
-            return -1;
-        }
-    }
+    private FakeTelephonyProvider mFakeTelephonyProvider;
+    @Mock
+    private ITelephonyRegistry.Stub mTelephonyRegisteryMock;
 
     @Before
     public void setUp() throws Exception {
@@ -166,15 +74,22 @@
 
         mSubscriptionControllerUT.getInstance().updatePhonesAvailability(new Phone[]{mPhone});
         mMockContentResolver = (MockContentResolver) mContext.getContentResolver();
+        mFakeTelephonyProvider = new FakeTelephonyProvider();
         mMockContentResolver.addProvider(SubscriptionManager.CONTENT_URI.getAuthority(),
-                new FakeSubscriptionContentProvider());
+                mFakeTelephonyProvider);
+
     }
 
     @After
     public void tearDown() throws Exception {
+        mContextFixture.addCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);
         /* should clear fake content provider and resolver here */
         mContext.getContentResolver().delete(SubscriptionManager.CONTENT_URI, null, null);
 
+        /*clear sub info in mSubscriptionControllerUT since they will otherwise be persistent
+         * between each test case. */
+        mSubscriptionControllerUT.clearSubInfo();
+
         /* clear settings for default voice/data/sms sub ID */
         Settings.Global.putInt(mContext.getContentResolver(),
                 Settings.Global.MULTI_SIM_VOICE_CALL_SUBSCRIPTION,
@@ -192,11 +107,10 @@
 
     @Test @SmallTest
     public void testInsertSim() {
-        int slotID = mSubscriptionControllerUT.getAllSubInfoCount(mCallingPackage);
-
         //verify there is no sim inserted in the SubscriptionManager
-        assertEquals(0, slotID);
+        assertEquals(0, mSubscriptionControllerUT.getAllSubInfoCount(mCallingPackage));
 
+        int slotID = 0;
         //insert one Subscription Info
         mSubscriptionControllerUT.addSubInfoRecord("test", slotID);
 
@@ -220,6 +134,8 @@
         int iconTint = 1;
         String disName = "TESTING";
         String disNum = "12345";
+        boolean isOpportunistic = true;
+        boolean isMetered = false;
 
         testInsertSim();
         /* Get SUB ID */
@@ -227,20 +143,30 @@
         assertTrue(subIds != null && subIds.length != 0);
         int subID = subIds[0];
 
+        /* Getting, there is no direct getter function for each fields of property */
+        SubscriptionInfo subInfo = mSubscriptionControllerUT
+                .getActiveSubscriptionInfo(subID, mCallingPackage);
+        //isMetered should initialize as true
+        assertTrue(subInfo.isMetered());
+
         /* Setting */
         mSubscriptionControllerUT.setDisplayName(disName, subID);
         mSubscriptionControllerUT.setDataRoaming(dataRoaming, subID);
         mSubscriptionControllerUT.setDisplayNumber(disNum, subID);
         mSubscriptionControllerUT.setIconTint(iconTint, subID);
+        mSubscriptionControllerUT.setOpportunistic(isOpportunistic, subID);
+        mSubscriptionControllerUT.setMetered(isMetered, subID);
 
-        /* Getting, there is no direct getter function for each fields of property */
-        SubscriptionInfo subInfo = mSubscriptionControllerUT
-                .getActiveSubscriptionInfo(subID, mCallingPackage);
+        subInfo = mSubscriptionControllerUT
+            .getActiveSubscriptionInfo(subID, mCallingPackage);
+
         assertNotNull(subInfo);
         assertEquals(dataRoaming, subInfo.getDataRoaming());
         assertEquals(disName, subInfo.getDisplayName());
         assertEquals(iconTint, subInfo.getIconTint());
         assertEquals(disNum, subInfo.getNumber());
+        assertEquals(isOpportunistic, subInfo.isOpportunistic());
+        assertEquals(isMetered, subInfo.isMetered());
 
         /* verify broadcast intent */
         ArgumentCaptor<Intent> captorIntent = ArgumentCaptor.forClass(Intent.class);
@@ -279,11 +205,11 @@
     @Test @SmallTest
     public void testCleanUpSIM() {
         testInsertSim();
-        assertFalse(mSubscriptionControllerUT.isActiveSubId(1));
+        assertFalse(mSubscriptionControllerUT.isActiveSubId(2));
         mSubscriptionControllerUT.clearSubInfo();
-        assertFalse(mSubscriptionControllerUT.isActiveSubId(0));
+        assertFalse(mSubscriptionControllerUT.isActiveSubId(1));
         assertEquals(SubscriptionManager.SIM_NOT_INSERTED,
-                mSubscriptionControllerUT.getSlotIndex(0));
+                mSubscriptionControllerUT.getSlotIndex(1));
     }
 
     @Test @SmallTest
@@ -309,10 +235,10 @@
     public void testSetGetMCCMNC() {
         testInsertSim();
         String mCcMncVERIZON = "310004";
-        mSubscriptionControllerUT.setMccMnc(mCcMncVERIZON, 0);
+        mSubscriptionControllerUT.setMccMnc(mCcMncVERIZON, 1);
 
         SubscriptionInfo subInfo = mSubscriptionControllerUT
-                .getActiveSubscriptionInfo(0, mCallingPackage);
+                .getActiveSubscriptionInfo(1, mCallingPackage);
         assertNotNull(subInfo);
         assertEquals(Integer.parseInt(mCcMncVERIZON.substring(0, 3)), subInfo.getMcc());
         assertEquals(Integer.parseInt(mCcMncVERIZON.substring(3)), subInfo.getMnc());
@@ -422,4 +348,178 @@
                 SubscriptionManager.WFC_IMS_ROAMING_MODE,
                 mCallingPackage));
     }
+
+
+    @Test
+    @SmallTest
+    public void testOpptSubInfoListChanged() throws Exception {
+        registerMockTelephonyRegistry();
+        verify(mTelephonyRegisteryMock, times(0))
+                .notifyOpportunisticSubscriptionInfoChanged();
+
+        testInsertSim();
+        mSubscriptionControllerUT.addSubInfoRecord("test2", 0);
+
+        // Neither sub1 or sub2 are opportunistic. So getOpportunisticSubscriptions
+        // should return empty list and no callback triggered.
+        List<SubscriptionInfo> opptSubList = mSubscriptionControllerUT
+                .getOpportunisticSubscriptions(mCallingPackage);
+
+        assertTrue(opptSubList.isEmpty());
+        verify(mTelephonyRegisteryMock, times(0))
+                .notifyOpportunisticSubscriptionInfoChanged();
+
+        // Setting sub2 as opportunistic should trigger callback.
+        mSubscriptionControllerUT.setOpportunistic(true, 2);
+
+        verify(mTelephonyRegisteryMock, times(1))
+                .notifyOpportunisticSubscriptionInfoChanged();
+        opptSubList = mSubscriptionControllerUT
+                .getOpportunisticSubscriptions(mCallingPackage);
+        assertEquals(1, opptSubList.size());
+        assertEquals("test2", opptSubList.get(0).getIccId());
+
+        // Changing non-opportunistic sub1 shouldn't trigger callback.
+        mSubscriptionControllerUT.setDisplayName("DisplayName", 1);
+        verify(mTelephonyRegisteryMock, times(1))
+                .notifyOpportunisticSubscriptionInfoChanged();
+
+        mSubscriptionControllerUT.setDisplayName("DisplayName", 2);
+        verify(mTelephonyRegisteryMock, times(2))
+                .notifyOpportunisticSubscriptionInfoChanged();
+    }
+
+    @Test
+    @SmallTest
+    public void testSetSubscriptionGroupWithModifyPermission() throws Exception {
+        testInsertSim();
+        mSubscriptionControllerUT.addSubInfoRecord("test2", 0);
+        mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);
+        mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE);
+
+        int[] subIdList = new int[] {1, 2};
+        // It should fail since it has no permission.
+        String groupId = mSubscriptionControllerUT.setSubscriptionGroup(
+                subIdList, mContext.getOpPackageName());
+        assertEquals(null, groupId);
+
+        // With modify permission it should succeed.
+        mContextFixture.addCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE);
+        groupId = mSubscriptionControllerUT.setSubscriptionGroup(
+                subIdList, mContext.getOpPackageName());
+        assertNotEquals(null, groupId);
+
+        // Calling it again should generate a new group ID.
+        String newGroupId = mSubscriptionControllerUT.setSubscriptionGroup(
+                subIdList, mContext.getOpPackageName());
+        assertNotEquals(null, newGroupId);
+        assertNotEquals(groupId, newGroupId);
+
+        // SubId 6 doesn't exist. Should fail.
+        subIdList = new int[] {1, 6};
+        mContextFixture.addCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE);
+        groupId = mSubscriptionControllerUT.setSubscriptionGroup(
+                subIdList, mContext.getOpPackageName());
+        assertEquals(null, groupId);
+    }
+
+    @Test
+    @SmallTest
+    public void testSetSubscriptionGroupWithCarrierPrivilegePermission() throws Exception {
+        testInsertSim();
+        // Adding a second profile and mark as embedded.
+        mSubscriptionControllerUT.addSubInfoRecord("test2", 0);
+        ContentValues values = new ContentValues();
+        values.put(SubscriptionManager.IS_EMBEDDED, 1);
+        mFakeTelephonyProvider.update(SubscriptionManager.CONTENT_URI, values,
+                SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" + 2, null);
+        mSubscriptionControllerUT.refreshCachedActiveSubscriptionInfoList();
+
+        mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);
+        mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE);
+
+        int[] subIdList = new int[] {1, 2};
+        // It should fail since it has no permission.
+        String groupId = mSubscriptionControllerUT.setSubscriptionGroup(
+                subIdList, mContext.getOpPackageName());
+        assertEquals(null, groupId);
+
+        // With modify permission it should succeed.
+        doReturn(true).when(mTelephonyManager).hasCarrierPrivileges(1);
+        groupId = mSubscriptionControllerUT.setSubscriptionGroup(
+                subIdList, mContext.getOpPackageName());
+        assertEquals(null, groupId);
+
+        doReturn(true).when(mTelephonyManager).hasCarrierPrivileges(2);
+        groupId = mSubscriptionControllerUT.setSubscriptionGroup(
+                subIdList, mContext.getOpPackageName());
+        assertNotEquals(null, groupId);
+
+        List<SubscriptionInfo> subInfoList = mSubscriptionControllerUT
+                .getActiveSubscriptionInfoList(mContext.getOpPackageName());
+
+        // Revoke carrier privilege of sub 2 but make it manageable by caller.
+        doReturn(false).when(mTelephonyManager).hasCarrierPrivileges(2);
+        doReturn(true).when(mSubscriptionManager).canManageSubscription(
+                eq(subInfoList.get(1)), anyString());
+
+        String newGroupId = mSubscriptionControllerUT.setSubscriptionGroup(
+                subIdList, mContext.getOpPackageName());
+        assertNotEquals(null, newGroupId);
+        assertNotEquals(groupId, newGroupId);
+    }
+
+    @Test
+    @SmallTest
+    public void testDisabledSubscriptionGroup() throws Exception {
+        registerMockTelephonyRegistry();
+
+        testInsertSim();
+        // Adding a second profile and mark as embedded.
+        mSubscriptionControllerUT.addSubInfoRecord("test2", 0);
+
+        ContentValues values = new ContentValues();
+        values.put(SubscriptionManager.IS_EMBEDDED, 1);
+        values.put(SubscriptionManager.IS_OPPORTUNISTIC, 1);
+        mFakeTelephonyProvider.update(SubscriptionManager.CONTENT_URI, values,
+                SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" + 2, null);
+        mSubscriptionControllerUT.refreshCachedActiveSubscriptionInfoList();
+
+        verify(mTelephonyRegisteryMock, times(1))
+                .notifyOpportunisticSubscriptionInfoChanged();
+
+        // Set sub 1 and 2 into same group.
+        int[] subIdList = new int[] {1, 2};
+        String groupId = mSubscriptionControllerUT.setSubscriptionGroup(
+                subIdList, mContext.getOpPackageName());
+        assertNotEquals(null, groupId);
+
+        verify(mTelephonyRegisteryMock, times(2))
+                .notifyOpportunisticSubscriptionInfoChanged();
+        List<SubscriptionInfo> opptSubList = mSubscriptionControllerUT
+                .getOpportunisticSubscriptions(mCallingPackage);
+        assertEquals(1, opptSubList.size());
+        assertEquals(2, opptSubList.get(0).getSubscriptionId());
+        assertEquals(false, opptSubList.get(0).isGroupDisabled());
+
+        // Unplug SIM 1. This should trigger subscription controller disabling sub 2.
+        values = new ContentValues();
+        values.put(SubscriptionManager.SIM_SLOT_INDEX, -1);
+        mFakeTelephonyProvider.update(SubscriptionManager.CONTENT_URI, values,
+                SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" + 1, null);
+        mSubscriptionControllerUT.refreshCachedActiveSubscriptionInfoList();
+
+        verify(mTelephonyRegisteryMock, times(3))
+                .notifyOpportunisticSubscriptionInfoChanged();
+        opptSubList = mSubscriptionControllerUT.getOpportunisticSubscriptions(mCallingPackage);
+        assertEquals(1, opptSubList.size());
+        assertEquals(2, opptSubList.get(0).getSubscriptionId());
+        assertEquals(true, opptSubList.get(0).isGroupDisabled());
+    }
+
+    private void registerMockTelephonyRegistry() {
+        mServiceManagerMockedServices.put("telephony.registry", mTelephonyRegisteryMock);
+        doReturn(mTelephonyRegisteryMock).when(mTelephonyRegisteryMock)
+                .queryLocalInterface(anyString());
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoTest.java b/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoTest.java
index ac97937..86df4f5 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoTest.java
@@ -15,15 +15,16 @@
  */
 package com.android.internal.telephony;
 
-import android.telephony.SubscriptionManager;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+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 +36,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", false, null, null);
     }
 
     @Test
@@ -78,4 +79,22 @@
         mSubscriptionInfoUT.setIconTint(0);
         assertEquals(0, mSubscriptionInfoUT.getIconTint());
     }
+
+    @Test
+    @SmallTest
+    public void testEquals() {
+        SubscriptionInfo copiedInfo = new SubscriptionInfo(1, "890126042XXXXXXXXXXX", 0,
+                "T-mobile", "T-mobile", 0, 255, "12345", 0, null,
+                "310", "260", "156", false, null, null);
+        SubscriptionInfo differentDisplayName = new SubscriptionInfo(1, "890126042XXXXXXXXXXX", 0,
+                "AT&T", "T-mobile", 0, 255, "12345", 0, null,
+                "310", "260", "156", false, null, null);
+        SubscriptionInfo differentSubId = new SubscriptionInfo(2, "890126042XXXXXXXXXXX", 0,
+                "AT&T", "T-mobile", 0, 255, "12345", 0, null,
+                "310", "260", "156", false, null, null);
+
+        assertEquals(mSubscriptionInfoUT, copiedInfo);
+        assertNotEquals(mSubscriptionInfoUT, differentDisplayName);
+        assertNotEquals(mSubscriptionInfoUT, differentSubId);
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoUpdaterTest.java b/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoUpdaterTest.java
index a8bb8fb..60bbff9 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoUpdaterTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoUpdaterTest.java
@@ -455,12 +455,12 @@
         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 */,
-                        null /* accessRules */));
+                        0, "1", 0, "", "", 0, 0, "", 0, null, "0", "0", "", false /* isEmbedded */,
+                        null /* accessRules */, null));
         // 2: embedded but no longer present.
         subInfoList.add(new SubscriptionInfo(
-                0, "2", 0, "", "", 0, 0, "", 0, null, 0, 0, "", true /* isEmbedded */,
-                null /* accessRules */));
+                0, "2", 0, "", "", 0, 0, "", 0, null, "0", "0", "", true /* isEmbedded */,
+                null /* accessRules */, null));
 
         when(mSubscriptionController.getSubscriptionInfoListForEmbeddedSubscriptionUpdate(
                 new String[] { "1", "3"}, false /* removable */)).thenReturn(subInfoList);
@@ -506,12 +506,12 @@
         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 */,
-                null /* accessRules */));
+                0, "1", 0, "", "", 0, 0, "", 0, null, "0", "0", "", false /* isEmbedded */,
+                null /* accessRules */, null));
         // 2: embedded.
         subInfoList.add(new SubscriptionInfo(
-                0, "2", 0, "", "", 0, 0, "", 0, null, 0, 0, "", true /* isEmbedded */,
-                null /* accessRules */));
+                0, "2", 0, "", "", 0, 0, "", 0, null, "0", "0", "", true /* isEmbedded */,
+                null /* accessRules */, null));
 
         when(mSubscriptionController.getSubscriptionInfoListForEmbeddedSubscriptionUpdate(
                 new String[0], false /* removable */)).thenReturn(subInfoList);
@@ -548,8 +548,8 @@
         List<SubscriptionInfo> subInfoList = new ArrayList<>();
         // 1: not embedded.
         subInfoList.add(new SubscriptionInfo(
-                0, "1", 0, "", "", 0, 0, "", 0, null, 0, 0, "", false /* isEmbedded */,
-                null /* accessRules */));
+                0, "1", 0, "", "", 0, 0, "", 0, null, "0", "0", "", false /* isEmbedded */,
+                null /* accessRules */, null));
 
         when(mSubscriptionController.getSubscriptionInfoListForEmbeddedSubscriptionUpdate(
                 new String[0], false /* removable */)).thenReturn(subInfoList);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/TelephonyRegistryTest.java b/tests/telephonytests/src/com/android/internal/telephony/TelephonyRegistryTest.java
new file mode 100644
index 0000000..fc2b632
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/TelephonyRegistryTest.java
@@ -0,0 +1,191 @@
+/*
+ * 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 android.telephony.PhoneStateListener.LISTEN_PHONE_CAPABILITY_CHANGE;
+import static android.telephony.PhoneStateListener.LISTEN_PREFERRED_DATA_SUBID_CHANGE;
+import static android.telephony.PhoneStateListener.LISTEN_SRVCC_STATE_CHANGED;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doReturn;
+
+import android.os.HandlerThread;
+import android.os.ServiceManager;
+import android.telephony.PhoneCapability;
+import android.telephony.PhoneStateListener;
+import android.telephony.TelephonyManager;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.TelephonyRegistry;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+public class TelephonyRegistryTest extends TelephonyTest {
+    @Mock
+    private ISub.Stub mISubStub;
+    private PhoneStateListener mPhoneStateListener;
+    private TelephonyRegistry mTelephonyRegistry;
+    private PhoneCapability mPhoneCapability;
+    private int mPreferredSubId;
+    private int mSrvccState = -1;
+
+    public class PhoneStateListenerWrapper extends PhoneStateListener {
+        @Override
+        public void onSrvccStateChanged(int srvccState) {
+            mSrvccState = srvccState;
+            setReady(true);
+        }
+
+        @Override
+        public void onPhoneCapabilityChanged(PhoneCapability capability) {
+            mPhoneCapability = capability;
+            setReady(true);
+        }
+        @Override
+        public void onPreferredDataSubIdChanged(int preferredSubId) {
+            mPreferredSubId = preferredSubId;
+            setReady(true);
+        }
+    }
+
+    private void addTelephonyRegistryService() {
+        mServiceManagerMockedServices.put("telephony.registry", mTelephonyRegistry.asBinder());
+    }
+
+    private HandlerThread mHandlerThread = new HandlerThread("ListenerThread") {
+        @Override
+        public void onLooperPrepared() {
+            mTelephonyRegistry = new TelephonyRegistry(mContext);
+            addTelephonyRegistryService();
+            mPhoneStateListener = new PhoneStateListenerWrapper();
+            setReady(true);
+        }
+    };
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp("TelephonyRegistryTest");
+        // ServiceManager.getService("isub") will return this stub for any call to
+        // SubscriptionManager.
+        mServiceManagerMockedServices.put("isub", mISubStub);
+        mHandlerThread.start();
+        waitUntilReady();
+        assertEquals(mTelephonyRegistry.asBinder(),
+                ServiceManager.getService("telephony.registry"));
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mTelephonyRegistry = null;
+        mHandlerThread.quit();
+        super.tearDown();
+    }
+
+    @Test @SmallTest
+    public void testPhoneCapabilityChanged() {
+        // mTelephonyRegistry.listen with notifyNow = true should trigger callback immediately.
+        setReady(false);
+        PhoneCapability phoneCapability = new PhoneCapability(1, 2, 3, null);
+        mTelephonyRegistry.notifyPhoneCapabilityChanged(phoneCapability);
+        mTelephonyRegistry.listen(mContext.getOpPackageName(),
+                mPhoneStateListener.callback,
+                LISTEN_PHONE_CAPABILITY_CHANGE, true);
+        waitUntilReady();
+        assertEquals(phoneCapability, mPhoneCapability);
+
+        // notifyPhoneCapabilityChanged with a new capability. Callback should be triggered.
+        setReady(false);
+        phoneCapability = new PhoneCapability(3, 2, 2, null);
+        mTelephonyRegistry.notifyPhoneCapabilityChanged(phoneCapability);
+        waitUntilReady();
+        assertEquals(phoneCapability, mPhoneCapability);
+    }
+
+
+    @Test @SmallTest
+    public void testPreferredDataSubChanged() {
+        // mTelephonyRegistry.listen with notifyNow = true should trigger callback immediately.
+        setReady(false);
+        int preferredSubId = 0;
+        mTelephonyRegistry.notifyPreferredDataSubIdChanged(preferredSubId);
+        mTelephonyRegistry.listen(mContext.getOpPackageName(),
+                mPhoneStateListener.callback,
+                LISTEN_PREFERRED_DATA_SUBID_CHANGE, true);
+        waitUntilReady();
+        assertEquals(preferredSubId, mPreferredSubId);
+
+        // notifyPhoneCapabilityChanged with a new capability. Callback should be triggered.
+        setReady(false);
+        mPreferredSubId = 1;
+        mTelephonyRegistry.notifyPreferredDataSubIdChanged(preferredSubId);
+        waitUntilReady();
+        assertEquals(preferredSubId, mPreferredSubId);
+    }
+
+    /**
+     * Test that we first receive a callback when listen(...) is called that contains the latest
+     * notify(...) response and then that the callback is called correctly when notify(...) is
+     * called.
+     */
+    @Test
+    @SmallTest
+    public void testSrvccStateChanged() throws Exception {
+        // Return a phone ID of 0 for all sub ids given.
+        doReturn(0/*phoneId*/).when(mISubStub).getPhoneId(anyInt());
+        setReady(false);
+        int srvccState = TelephonyManager.SRVCC_STATE_HANDOVER_STARTED;
+        mTelephonyRegistry.notifySrvccStateChanged(0 /*subId*/, srvccState);
+        // Should receive callback when listen is called that contains the latest notify result.
+        mTelephonyRegistry.listenForSubscriber(0 /*subId*/, mContext.getOpPackageName(),
+                mPhoneStateListener.callback,
+                LISTEN_SRVCC_STATE_CHANGED, true);
+        waitUntilReady();
+        assertEquals(srvccState, mSrvccState);
+
+        // trigger callback
+        setReady(false);
+        srvccState = TelephonyManager.SRVCC_STATE_HANDOVER_COMPLETED;
+        mTelephonyRegistry.notifySrvccStateChanged(0 /*subId*/, srvccState);
+        waitUntilReady();
+        assertEquals(srvccState, mSrvccState);
+    }
+
+    /**
+     * Test that a SecurityException is thrown when we try to listen to a SRVCC state change without
+     * READ_PRIVILEGED_PHONE_STATE.
+     */
+    @Test
+    @SmallTest
+    public void testSrvccStateChangedNoPermission() {
+        // Clear all permission grants for test.
+        mContextFixture.addCallingOrSelfPermission("");
+        int srvccState = TelephonyManager.SRVCC_STATE_HANDOVER_STARTED;
+        mTelephonyRegistry.notifySrvccStateChanged(0 /*subId*/, srvccState);
+        try {
+            mTelephonyRegistry.listenForSubscriber(0 /*subId*/, mContext.getOpPackageName(),
+                    mPhoneStateListener.callback,
+                    LISTEN_SRVCC_STATE_CHANGED, true);
+            fail();
+        } catch (SecurityException e) {
+            // pass test!
+        }
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java b/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
index 89d8143..2517d22 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
@@ -16,9 +16,10 @@
 
 package com.android.internal.telephony;
 
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Matchers.anyBoolean;
-import static org.mockito.Matchers.anyLong;
 import static org.mockito.Matchers.nullable;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.doAnswer;
@@ -34,7 +35,6 @@
 import android.content.SharedPreferences;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
-import android.database.Cursor;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
@@ -44,6 +44,7 @@
 import android.os.ServiceManager;
 import android.provider.BlockedNumberContract;
 import android.provider.Settings;
+import android.telephony.AccessNetworkConstants.TransportType;
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
@@ -60,6 +61,8 @@
 import com.android.internal.telephony.cdma.CdmaSubscriptionSourceManager;
 import com.android.internal.telephony.cdma.EriManager;
 import com.android.internal.telephony.dataconnection.DcTracker;
+import com.android.internal.telephony.dataconnection.TransportManager;
+import com.android.internal.telephony.emergency.EmergencyNumberTracker;
 import com.android.internal.telephony.imsphone.ImsExternalCallTracker;
 import com.android.internal.telephony.imsphone.ImsPhone;
 import com.android.internal.telephony.imsphone.ImsPhoneCallTracker;
@@ -103,6 +106,8 @@
     @Mock
     protected ServiceStateTracker mSST;
     @Mock
+    protected EmergencyNumberTracker mEmergencyNumberTracker;
+    @Mock
     protected GsmCdmaCallTracker mCT;
     @Mock
     protected ImsPhoneCallTracker mImsCT;
@@ -163,8 +168,6 @@
     @Mock
     protected IActivityManager mIActivityManager;
     @Mock
-    protected InboundSmsTracker mInboundSmsTracker;
-    @Mock
     protected IIntentSender mIIntentSender;
     @Mock
     protected IBinder mIBinder;
@@ -187,8 +190,14 @@
     @Mock
     protected AppSmsManager mAppSmsManager;
     @Mock
+    protected IccSmsInterfaceManager mIccSmsInterfaceManager;
+    @Mock
+    protected SmsDispatchersController mSmsDispatchersController;
+    @Mock
     protected DeviceStateMonitor mDeviceStateMonitor;
     @Mock
+    protected TransportManager mTransportManager;
+    @Mock
     protected IntentBroadcaster mIntentBroadcaster;
     @Mock
     protected NitzStateMachine mNitzStateMachine;
@@ -198,6 +207,8 @@
     protected SubscriptionInfoUpdater mSubInfoRecordUpdater;
     @Mock
     protected LocaleTracker mLocaleTracker;
+    @Mock
+    protected RestrictedState mRestrictedState;
 
     protected ImsCallProfile mImsCallProfile;
     protected TelephonyManager mTelephonyManager;
@@ -211,7 +222,7 @@
     private Object mLock = new Object();
     private boolean mReady;
     protected HashMap<String, IBinder> mServiceManagerMockedServices = new HashMap<>();
-    private Phone[] mPhones;
+    protected Phone[] mPhones;
 
 
     protected HashMap<Integer, ImsManager> mImsManagerInstances = new HashMap<>();
@@ -248,13 +259,15 @@
 
     protected void waitUntilReady() {
         synchronized (mLock) {
-            try {
-                mLock.wait(MAX_INIT_WAIT_MS);
-            } catch (InterruptedException ie) {
-            }
-
             if (!mReady) {
-                fail("Telephony tests failed to initialize");
+                try {
+                    mLock.wait(MAX_INIT_WAIT_MS);
+                } catch (InterruptedException ie) {
+                }
+
+                if (!mReady) {
+                    fail("Telephony tests failed to initialize");
+                }
             }
         }
     }
@@ -327,9 +340,13 @@
         mPackageManager = mContext.getPackageManager();
 
         //mTelephonyComponentFactory
+        doReturn(mTelephonyComponentFactory).when(mTelephonyComponentFactory).inject(anyString());
         doReturn(mSST).when(mTelephonyComponentFactory)
                 .makeServiceStateTracker(nullable(GsmCdmaPhone.class),
                         nullable(CommandsInterface.class));
+        doReturn(mEmergencyNumberTracker).when(mTelephonyComponentFactory)
+                .makeEmergencyNumberTracker(nullable(Phone.class),
+                        nullable(CommandsInterface.class));
         doReturn(mUiccProfile).when(mTelephonyComponentFactory)
                 .makeUiccProfile(nullable(Context.class), nullable(CommandsInterface.class),
                         nullable(IccCardStatus.class), anyInt(), nullable(UiccCard.class),
@@ -339,19 +356,9 @@
         doReturn(mIccPhoneBookIntManager).when(mTelephonyComponentFactory)
                 .makeIccPhoneBookInterfaceManager(nullable(Phone.class));
         doReturn(mDcTracker).when(mTelephonyComponentFactory)
-                .makeDcTracker(nullable(Phone.class));
+                .makeDcTracker(nullable(Phone.class), anyInt());
         doReturn(mWspTypeDecoder).when(mTelephonyComponentFactory)
                 .makeWspTypeDecoder(nullable(byte[].class));
-        doReturn(mInboundSmsTracker).when(mTelephonyComponentFactory)
-                .makeInboundSmsTracker(nullable(byte[].class), anyLong(), anyInt(), anyBoolean(),
-                        anyBoolean(), nullable(String.class), nullable(String.class),
-                        nullable(String.class));
-        doReturn(mInboundSmsTracker).when(mTelephonyComponentFactory)
-                .makeInboundSmsTracker(nullable(byte[].class), anyLong(), anyInt(), anyBoolean(),
-                        nullable(String.class), nullable(String.class), anyInt(), anyInt(),
-                        anyInt(), anyBoolean(), nullable(String.class));
-        doReturn(mInboundSmsTracker).when(mTelephonyComponentFactory)
-                .makeInboundSmsTracker(nullable(Cursor.class), anyBoolean());
         doReturn(mImsCT).when(mTelephonyComponentFactory)
                 .makeImsPhoneCallTracker(nullable(ImsPhone.class));
         doReturn(mCdmaSSM).when(mTelephonyComponentFactory)
@@ -370,10 +377,13 @@
                 .makeCarrierActionAgent(nullable(Phone.class));
         doReturn(mDeviceStateMonitor).when(mTelephonyComponentFactory)
                 .makeDeviceStateMonitor(nullable(Phone.class));
+        doReturn(mTransportManager).when(mTelephonyComponentFactory)
+                .makeTransportManager(nullable(Phone.class));
         doReturn(mNitzStateMachine).when(mTelephonyComponentFactory)
                 .makeNitzStateMachine(nullable(GsmCdmaPhone.class));
         doReturn(mLocaleTracker).when(mTelephonyComponentFactory)
-                .makeLocaleTracker(nullable(Phone.class), nullable(Looper.class));
+                .makeLocaleTracker(nullable(Phone.class), nullable(NitzStateMachine.class),
+                        nullable(Looper.class));
 
         //mPhone
         doReturn(mContext).when(mPhone).getContext();
@@ -383,13 +393,19 @@
         doReturn(mServiceState).when(mPhone).getServiceState();
         doReturn(mServiceState).when(mImsPhone).getServiceState();
         doReturn(mPhone).when(mImsPhone).getDefaultPhone();
+        doReturn(true).when(mDcTracker).isDataEnabled();
         doReturn(true).when(mPhone).isPhoneTypeGsm();
         doReturn(PhoneConstants.PHONE_TYPE_GSM).when(mPhone).getPhoneType();
         doReturn(mCT).when(mPhone).getCallTracker();
         doReturn(mSST).when(mPhone).getServiceStateTracker();
+        doReturn(mEmergencyNumberTracker).when(mPhone).getEmergencyNumberTracker();
         doReturn(mCarrierSignalAgent).when(mPhone).getCarrierSignalAgent();
         doReturn(mCarrierActionAgent).when(mPhone).getCarrierActionAgent();
         doReturn(mAppSmsManager).when(mPhone).getAppSmsManager();
+        doReturn(mIccSmsInterfaceManager).when(mPhone).getIccSmsInterfaceManager();
+        doReturn(mTransportManager).when(mPhone).getTransportManager();
+        doReturn(mDcTracker).when(mPhone).getDcTracker(anyInt());
+        mIccSmsInterfaceManager.mDispatchersController = mSmsDispatchersController;
         mPhone.mEriManager = mEriManager;
 
         //mUiccController
@@ -457,8 +473,13 @@
                 nullable(String.class), nullable(IBinder.class), nullable(String.class), anyInt(),
                 nullable(Intent[].class), nullable(String[].class), anyInt(),
                 nullable(Bundle.class), anyInt());
+        doReturn(mTelephonyManager).when(mTelephonyManager).createForSubscriptionId(anyInt());
         mSST.mSS = mServiceState;
+        mSST.mRestrictedState = mRestrictedState;
         mServiceManagerMockedServices.put("connectivity_metrics_logger", mConnMetLoggerBinder);
+        doReturn(new int[]{TransportType.WWAN, TransportType.WLAN})
+                .when(mTransportManager).getAvailableTransports();
+        doReturn(TransportType.WWAN).when(mTransportManager).getCurrentTransport(anyInt());
 
         //SIM
         doReturn(1).when(mTelephonyManager).getSimCount();
@@ -499,6 +520,8 @@
         replaceInstance(PhoneFactory.class, "sSubInfoRecordUpdater", null, mSubInfoRecordUpdater);
         replaceInstance(RadioConfig.class, "sRadioConfig", null, mMockRadioConfig);
 
+        assertNotNull("Failed to set up SubscriptionController singleton",
+                SubscriptionController.getInstance());
         setReady(false);
     }
 
@@ -525,8 +548,10 @@
             switch (method) {
                 case BlockedNumberContract.SystemContract.METHOD_SHOULD_SYSTEM_BLOCK_NUMBER:
                     Bundle bundle = new Bundle();
-                    bundle.putBoolean(BlockedNumberContract.RES_NUMBER_IS_BLOCKED,
-                            mBlockedNumbers.contains(arg));
+                    int blockStatus = mBlockedNumbers.contains(arg)
+                            ? BlockedNumberContract.STATUS_BLOCKED_IN_LIST
+                            : BlockedNumberContract.STATUS_NOT_BLOCKED;
+                    bundle.putInt(BlockedNumberContract.RES_BLOCK_STATUS, blockStatus);
                     return bundle;
                 case BlockedNumberContract.SystemContract.METHOD_NOTIFY_EMERGENCY_CONTACT:
                     mNumEmergencyContactNotifications++;
diff --git a/tests/telephonytests/src/com/android/internal/telephony/TimeZoneLookupHelperTest.java b/tests/telephonytests/src/com/android/internal/telephony/TimeZoneLookupHelperTest.java
index d0eeb43..e2cf307 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/TimeZoneLookupHelperTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/TimeZoneLookupHelperTest.java
@@ -24,7 +24,7 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
-import libcore.util.TimeZoneFinder;
+import libcore.timezone.TimeZoneFinder;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -161,7 +161,7 @@
 
         // Summer, known DST state (DST == true).
         {
-            String summerTimeNitzStringWithDst = summerTimeNitzString + ",4";
+            String summerTimeNitzStringWithDst = summerTimeNitzString + ",1";
             NitzData nitzData = NitzData.parse(summerTimeNitzStringWithDst);
             int expectedUtcOffset = (int) TimeUnit.HOURS.toMillis(2);
             Integer expectedDstOffset = (int) TimeUnit.HOURS.toMillis(1);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/VisualVoicemailSmsFilterTest.java b/tests/telephonytests/src/com/android/internal/telephony/VisualVoicemailSmsFilterTest.java
index fab16be..a2be8c7 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/VisualVoicemailSmsFilterTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/VisualVoicemailSmsFilterTest.java
@@ -21,6 +21,7 @@
 
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.res.Resources;
 import android.telecom.PhoneAccountHandle;
 import android.telephony.TelephonyManager;
 import android.telephony.VisualVoicemailSmsFilterSettings;
@@ -61,6 +62,22 @@
             (byte) 0xE9, (byte) 0x62, (byte) 0x37, (byte) 0x50, (byte) 0x0B, (byte) 0x86,
             (byte) 0x83, (byte) 0xC1, (byte) 0x76, (byte) 0xEC, (byte) 0x1E, (byte) 0x0D}};
 
+    /**
+     * PDU for the following message:
+     * <p>originating number: 129
+     * <p>message: //VZWVVM
+     */
+    private static final byte[][] VZWVVM_PDU = {{
+            (byte) 0x07, (byte) 0x91, (byte) 0x41, (byte) 0x50, (byte) 0x74, (byte) 0x02,
+            (byte) 0x50, (byte) 0xF5, (byte) 0x04, (byte) 0x03, (byte) 0xC9, (byte) 0x21,
+            (byte) 0xF9, (byte) 0x00, (byte) 0x00, (byte) 0x71, (byte) 0x30, (byte) 0x70,
+            (byte) 0x81, (byte) 0x71, (byte) 0x81, (byte) 0x2B, (byte) 0x08, (byte) 0xAF,
+            (byte) 0x97, (byte) 0x55, (byte) 0x7B, (byte) 0xB5, (byte) 0x5A, (byte) 0x9B}};
+
+    private static final String SIM_MCC_MNC = "001002";
+
+    private static final String[] VVM_PATTERN_REGEXP = {SIM_MCC_MNC + ";^//VZWVVM.*"};
+
     private Context mContext;
     private TelephonyManager mTelephonyManager;
 
@@ -109,6 +126,21 @@
                 VisualVoicemailSmsFilter.filter(mContext, pdus, SmsConstants.FORMAT_3GPP, 0, 0));
     }
 
+    public void testFilterNotSet_matchesVvmPattern_filtered() {
+        setSettings(null);
+        Resources resources = Mockito.mock(Resources.class);
+        when(mTelephonyManager.getSimOperator(anyInt()))
+                .thenReturn(SIM_MCC_MNC);
+        when(mContext.getResources())
+                .thenReturn(resources);
+        when(resources.getStringArray(com.android.internal.R.array.config_vvmSmsFilterRegexes))
+                .thenReturn(VVM_PATTERN_REGEXP);
+
+        assertTrue(
+                VisualVoicemailSmsFilter.filter(mContext, VZWVVM_PDU, SmsConstants.FORMAT_3GPP, 0,
+                        0));
+    }
+
     public void testOriginatingNumber_unspecified_filtered() {
         setSettings(new VisualVoicemailSmsFilterSettings.Builder().build());
         assertTrue(VisualVoicemailSmsFilter
diff --git a/tests/telephonytests/src/com/android/internal/telephony/WapPushOverSmsTest.java b/tests/telephonytests/src/com/android/internal/telephony/WapPushOverSmsTest.java
index e1ac5f3..3643d47 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/WapPushOverSmsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/WapPushOverSmsTest.java
@@ -84,7 +84,7 @@
                 (byte) 0xFF
         };
 
-        mWapPushOverSmsUT.dispatchWapPdu(pdu, null, mInboundSmsHandler);
+        mWapPushOverSmsUT.dispatchWapPdu(pdu, null, mInboundSmsHandler, "123456");
 
         ArgumentCaptor<Intent> intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class);
         verify(mInboundSmsHandler).dispatchIntent(intentArgumentCaptor.capture(),
@@ -97,6 +97,7 @@
         assertEquals(Telephony.Sms.Intents.WAP_PUSH_DELIVER_ACTION, intent.getAction());
         assertEquals(0xFF, intent.getIntExtra("transactionId", 0));
         assertEquals(0x06, intent.getIntExtra("pduType", 0));
+        assertEquals("123456", intent.getStringExtra("address"));
 
         byte[] header = intent.getByteArrayExtra("header");
         assertEquals(2, header.length);
@@ -146,4 +147,4 @@
                 any(BroadcastReceiver.class),
                 any(UserHandle.class));
     }
-}
\ No newline at end of file
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/cdma/CdmaInboundSmsHandlerTest.java b/tests/telephonytests/src/com/android/internal/telephony/cdma/CdmaInboundSmsHandlerTest.java
index 5786a70..08e54ac 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/cdma/CdmaInboundSmsHandlerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/cdma/CdmaInboundSmsHandlerTest.java
@@ -21,6 +21,8 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Matchers.any;
 import static org.mockito.Mockito.anyBoolean;
 import static org.mockito.Mockito.anyInt;
@@ -29,9 +31,9 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
-import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
+import android.database.Cursor;
 import android.os.AsyncResult;
 import android.os.HandlerThread;
 import android.os.RemoteException;
@@ -44,10 +46,10 @@
 
 import com.android.internal.telephony.FakeSmsContentProvider;
 import com.android.internal.telephony.InboundSmsHandler;
+import com.android.internal.telephony.InboundSmsTracker;
 import com.android.internal.telephony.SmsStorageMonitor;
 import com.android.internal.telephony.TelephonyTest;
 import com.android.internal.telephony.cdma.sms.SmsEnvelope;
-import com.android.internal.util.HexDump;
 import com.android.internal.util.IState;
 import com.android.internal.util.StateMachine;
 
@@ -72,8 +74,9 @@
     private CdmaInboundSmsHandler mCdmaInboundSmsHandler;
     private CdmaInboundSmsHandlerTestHandler mCdmaInboundSmsHandlerTestHandler;
     private SmsEnvelope mSmsEnvelope = new SmsEnvelope();
-    private ContentValues mInboundSmsTrackerCV = new ContentValues();
     private FakeSmsContentProvider mContentProvider;
+    private InboundSmsTracker mInboundSmsTracker;
+    private byte[] mSmsPdu = new byte[]{(byte) 0xFF, (byte) 0xFF, (byte) 0xFF};
 
     private class CdmaInboundSmsHandlerTestHandler extends HandlerThread {
 
@@ -118,28 +121,32 @@
             fail("Unexpected RemoteException: " + re.getStackTrace());
         }
 
-        byte[] smsPdu = new byte[]{(byte)0xFF, (byte)0xFF, (byte)0xFF};
         mSmsMessage.mWrappedSmsMessage = mCdmaSmsMessage;
-        doReturn(smsPdu).when(mCdmaSmsMessage).getPdu();
-
-        mInboundSmsTrackerCV.put("destination_port", 1 << 16);
-        mInboundSmsTrackerCV.put("pdu", HexDump.toHexString(smsPdu));
-        mInboundSmsTrackerCV.put("address", "1234567890");
-        mInboundSmsTrackerCV.put("reference_number", 1);
-        mInboundSmsTrackerCV.put("sequence", 1);
-        mInboundSmsTrackerCV.put("count", 1);
-        mInboundSmsTrackerCV.put("date", System.currentTimeMillis());
-        mInboundSmsTrackerCV.put("message_body", "This is the message body of a single-part " +
-                "message");
+        doReturn(mSmsPdu).when(mCdmaSmsMessage).getPdu();
 
         doReturn(true).when(mTelephonyManager).getSmsReceiveCapableForPhone(anyInt(), anyBoolean());
         doReturn(true).when(mSmsStorageMonitor).isStorageAvailable();
-        doReturn(1).when(mInboundSmsTracker).getMessageCount();
-        doReturn(-1).when(mInboundSmsTracker).getDestPort();
-        doReturn(mInboundSmsTrackerCV).when(mInboundSmsTracker).getContentValues();
-        doReturn(smsPdu).when(mInboundSmsTracker).getPdu();
-        doReturn("This is the message body").when(mInboundSmsTracker).getMessageBody();
-        doReturn("1234567890").when(mInboundSmsTracker).getAddress();
+
+        mInboundSmsTracker = new InboundSmsTracker(
+            mSmsPdu, /* pdu */
+            System.currentTimeMillis(), /* timestamp */
+            -1, /* destPort */
+            true, /* is3gpp2 */
+            false, /* is3gpp2WapPdu */
+            "1234567890", /* address */
+            "1234567890", /* displayAddress */
+            "This is the message body of a single-part message" /* messageBody */);
+
+        doReturn(mInboundSmsTracker).when(mTelephonyComponentFactory)
+                .makeInboundSmsTracker(nullable(byte[].class), anyLong(), anyInt(), anyBoolean(),
+                anyBoolean(), nullable(String.class), nullable(String.class),
+                nullable(String.class));
+        doReturn(mInboundSmsTracker).when(mTelephonyComponentFactory)
+                .makeInboundSmsTracker(nullable(byte[].class), anyLong(), anyInt(), anyBoolean(),
+                nullable(String.class), nullable(String.class), anyInt(), anyInt(),
+                anyInt(), anyBoolean(), nullable(String.class));
+        doReturn(mInboundSmsTracker).when(mTelephonyComponentFactory)
+                .makeInboundSmsTracker(nullable(Cursor.class), anyBoolean());
 
         mContentProvider = new FakeSmsContentProvider();
         ((MockContentResolver)mContext.getContentResolver()).addProvider(
@@ -209,11 +216,24 @@
         assertEquals("IdleState", getCurrentState().getName());
     }
 
+    @FlakyTest /* flakes 0.43% of the time */
     @Test
     @MediumTest
     public void testNewSmsFromBlockedNumber_noBroadcastsSent() {
         String blockedNumber = "123456789";
-        doReturn(blockedNumber).when(mInboundSmsTracker).getDisplayAddress();
+        mInboundSmsTracker = new InboundSmsTracker(
+            mSmsPdu, /* pdu */
+            System.currentTimeMillis(), /* timestamp */
+            -1, /* destPort */
+            true, /* is3gpp2 */
+            false, /* is3gpp2WapPdu */
+            "1234567890", /* address */
+            blockedNumber, /* displayAddress */
+            "This is the message body of a single-part message" /* messageBody */);
+        doReturn(mInboundSmsTracker).when(mTelephonyComponentFactory)
+                .makeInboundSmsTracker(nullable(byte[].class), anyLong(), anyInt(), anyBoolean(),
+                anyBoolean(), nullable(String.class), nullable(String.class),
+                nullable(String.class));
         mFakeBlockedNumberContentProvider.mBlockedNumbers.add(blockedNumber);
 
         transitionFromStartupToIdle();
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..32cad59 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,17 @@
 
 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.assertTrue;
+
+import static org.mockito.Matchers.eq;
+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 +40,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
@@ -89,19 +87,6 @@
 
     @Test
     @SmallTest
-    public void testDataCallAsyncChannel() throws Exception {
-
-        DcAsyncChannel dcAc = mock(DcAsyncChannel.class);
-
-        mApnContext.setDataConnectionAc(dcAc);
-        assertEquals(dcAc, mApnContext.getDcAc());
-        mApnContext.releaseDataConnection("");
-        assertNull(mApnContext.getDcAc());
-        assertEquals(DctConstants.State.IDLE, mApnContext.getState());
-    }
-
-    @Test
-    @SmallTest
     public void testDependencyMet() throws Exception {
         assertTrue(mApnContext.getDependencyMet());
         mApnContext.setDependencyMet(false);
@@ -143,14 +128,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 +161,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 4c49e5b..b7b8d83 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,91 +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.apnSetId, a2.apnSetId);
+        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.isPersistent(), a2.isPersistent());
+        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 =
@@ -200,51 +188,58 @@
                         + "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 = new ApnSetting(
-                -1, "12345", "Name", "apn", "", "", "", "", "", "", "", 0, mmsTypes, "IPV6",
-                "IP", true, 0, 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,
+                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 = new ApnSetting(
-                -1, "12345", "Name", "apn", "", "", "", "", "", "", "", 0, mmsTypes, "IPV6",
-                "IP", true, 0, 0, false, 0, 0, 0, 0, "spn", "testspn", 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, -1);
+        assertApnSettingEqual(expectedApn, ApnSetting.fromString(testString));
+
+        // A v6 string with carrierId=100
+        testString =
+            "[ApnSettingV5] Name,apn,,,,,,,,,123,45,,mms|*,IPV6,IP,true,0,,,,,,,spn,testspn,0,3,"
+                + "100";
+        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, 100);
         assertApnSettingEqual(expectedApn, ApnSetting.fromString(testString));
 
         // Return no apn if insufficient fields given.
@@ -258,6 +253,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";
@@ -268,44 +264,48 @@
         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(new ApnSetting(
-                -1, "12346", "Name1", "apn2", "", "", "", "", "", "", "", 0, new String[]{"mms"}, "IPV6",
-                "IP", true, 0, 0, false, 0, 0, 0, 0, "", "", 3));
+        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, -1));
         assertApnSettingsEqual(expectedApns, ApnSetting.arrayFromString(testString));
     }
 
     @Test
     @SmallTest
     public void testToString() throws Exception {
-        String[] types = {"default", "*"};
-        // use default apn_set_id constructor
-        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 = "[ApnSettingV5] Name, 99, 12345, apn, proxy, "
-                + "mmsc, mmsproxy, mmsport, port, 0, default | *, "
-                + "IPV6, IP, true, 14, 8192, 0, false, 0, 0, 0, 0, , , false, 4096, 0";
+        // 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());
 
-        int networkTypeBitmask = 1 << (14 - 1);
-        int bearerBitmask =
-                ServiceState.convertNetworkTypeBitmaskToBearerBitmask(networkTypeBitmask);
-        apn = new ApnSetting(99, "12345", "Name", "apn", "proxy", "port",
-                "mmsc", "mmsproxy", "mmsport", "user", "password", 0,
-                types, "IPV6", "IP", true, networkTypeBitmask, 0, false, 0, 0, 0, 0, "", "", 3);
-        expected = "[ApnSettingV5] Name, 99, 12345, apn, proxy, "
-                + "mmsc, mmsproxy, mmsport, port, 0, default | *, IPV6, IP, true, 0, "
-                + bearerBitmask + ", 0, false, 0, 0, 0, 0, , , false, 8192, 3";
+        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, -1);
+        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());
     }
 
@@ -317,50 +317,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
@@ -371,42 +364,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
@@ -419,42 +404,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
@@ -465,33 +442,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
@@ -501,42 +471,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
@@ -548,42 +511,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
@@ -595,19 +551,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
@@ -618,20 +571,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
@@ -644,19 +593,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
@@ -668,22 +614,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
@@ -695,20 +636,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
@@ -722,20 +660,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
@@ -743,59 +678,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
@@ -804,8 +732,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();
@@ -815,17 +745,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());
             }
@@ -838,60 +774,58 @@
     @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));
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 b2fea9b..dab06d8 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DataConnectionTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DataConnectionTest.java
@@ -55,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;
@@ -101,60 +102,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 = new ApnSetting(
+    private ApnSetting mApn2 = ApnSetting.makeApnSetting(
             2164,                   // 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", "dun"},     // types
-            "IP",                   // protocol
-            "IP",                   // roaming_protocol
+            ApnSetting.TYPE_DEFAULT | ApnSetting.TYPE_DUN,     // 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 class DataConnectionTestHandler extends HandlerThread {
@@ -179,7 +178,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,
@@ -504,6 +503,7 @@
         assertTrue(getNetworkCapabilities().hasCapability(NET_CAPABILITY_NOT_CONGESTED));
     }
 
+    @Test
     @SmallTest
     public void testIsIpAddress() throws Exception {
         // IPv4
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..d657cf8 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,128 @@
 
 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(), false);
+        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.getWaitTime(), dp.getWaitTime());
+        assertEquals(mApn1.isEnabled(), dp.isEnabled());
+        assertFalse(dp.isPersistent());
+        assertFalse(dp.isPreferred());
     }
 
     @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(), false);
+        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.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(), false);
+        DataProfile dp2 = DcTracker.createDataProfile(mApn1, mApn1.getProfileId(), false);
         assertEquals(dp1, dp2);
 
-        dp2 = DcTracker.createDataProfile(mApn2, mApn2.profileId);
+        dp2 = DcTracker.createDataProfile(mApn2, mApn2.getProfileId(), false);
         assertFalse(dp1.equals(dp2));
     }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DcControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DcControllerTest.java
index c86c911..400a9a6 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DcControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DcControllerTest.java
@@ -42,7 +42,6 @@
 
 import com.android.internal.telephony.DctConstants;
 import com.android.internal.telephony.TelephonyTest;
-import com.android.internal.telephony.dataconnection.DataConnection.ConnectionParams;
 import com.android.internal.telephony.dataconnection.DataConnection.UpdateLinkPropertyResult;
 import com.android.internal.util.IState;
 import com.android.internal.util.StateMachine;
@@ -55,7 +54,7 @@
 import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.HashMap;
+import java.util.List;
 
 public class DcControllerTest extends TelephonyTest {
 
@@ -63,11 +62,11 @@
     private static final int EVENT_DATA_STATE_CHANGED = 0x00040007;
 
     @Mock
-    DataConnection mDc;
+    private DataConnection mDc;
     @Mock
-    HashMap<ApnContext, ConnectionParams> mApnContexts;
+    private List<ApnContext> mApnContexts;
     @Mock
-    DataServiceManager mDataServiceManager;
+    private DataServiceManager mDataServiceManager;
 
     UpdateLinkPropertyResult mResult;
 
@@ -107,8 +106,8 @@
         super.setUp(getClass().getSimpleName());
 
         doReturn("fake.action_detached").when(mPhone).getActionDetached();
-        mDc.mApnContexts = mApnContexts;
         doReturn(1).when(mApnContexts).size();
+        doReturn(mApnContexts).when(mDc).getApnContexts();
 
         LinkProperties lp = new LinkProperties();
         mResult = new UpdateLinkPropertyResult(lp);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DcFailCauseTest.java b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DcFailCauseTest.java
index 6ef1e39..48fc139 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DcFailCauseTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DcFailCauseTest.java
@@ -16,6 +16,13 @@
 
 package com.android.internal.telephony.dataconnection;
 
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.Mockito.doReturn;
+
+import android.content.Context;
 import android.os.PersistableBundle;
 import android.telephony.CarrierConfigManager;
 import android.test.suitebuilder.annotation.SmallTest;
@@ -27,14 +34,12 @@
 import org.junit.Test;
 
 import java.util.ArrayList;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
-import static org.mockito.Mockito.doReturn;
+import java.util.Random;
 
 public class DcFailCauseTest extends TelephonyTest {
 
+    private PersistableBundle mPersistableBundle;
+
     private class DcFailCauseData {
         public final int mCause;
         public final boolean mPermanentFailure;
@@ -135,10 +140,20 @@
         mFailCauseDataList.add(new DcFailCauseData(0x10003, false, false));
         mFailCauseDataList.add(new DcFailCauseData(0x10004, false, false));
         mFailCauseDataList.add(new DcFailCauseData(0x10005, false, false));
+
+        CarrierConfigManager configManager = (CarrierConfigManager)
+                mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
+        mPersistableBundle = configManager.getConfigForSubId(mPhone.getSubId());
+        mPersistableBundle.putBoolean(CarrierConfigManager
+                .KEY_RESTART_RADIO_ON_PDP_FAIL_REGULAR_DEACTIVATION_BOOL, false);
+        mPersistableBundle.putIntArray(CarrierConfigManager
+                .KEY_RADIO_RESTART_FAILURE_CAUSES_INT_ARRAY, new int[0]);
+        logd(getClass().getSimpleName() + " -Setup!");
     }
 
     @After
     public void tearDown() throws Exception {
+        mPersistableBundle = null;
         super.tearDown();
     }
 
@@ -197,4 +212,29 @@
         assertEquals(DcFailCause.UNKNOWN.getErrorCode(),
                 DcFailCause.fromInt(123456).getErrorCode());
     }
+
+    @Test
+    public void testIsRadioRestartFailureRegularDeactivation() {
+        assertFalse(DcFailCause.fromInt(DcFailCause.REGULAR_DEACTIVATION.getErrorCode())
+                .isRadioRestartFailure(mContext, mPhone.getSubId()));
+        mPersistableBundle.putBoolean(CarrierConfigManager
+                .KEY_RESTART_RADIO_ON_PDP_FAIL_REGULAR_DEACTIVATION_BOOL, true);
+        assertTrue(DcFailCause.fromInt(DcFailCause.REGULAR_DEACTIVATION.getErrorCode())
+                .isRadioRestartFailure(mContext, mPhone.getSubId()));
+    }
+
+    @Test
+    public void testIsRadioRestartFailureNotRegularDeactivation() {
+        DcFailCause randomCause = DcFailCause.fromInt(mFailCauseDataList
+                .get(new Random().nextInt(mFailCauseDataList.size())).mCause);
+        while (randomCause == DcFailCause.REGULAR_DEACTIVATION) {
+            randomCause = DcFailCause.fromInt(mFailCauseDataList
+                    .get(new Random().nextInt(mFailCauseDataList.size())).mCause);
+        }
+        assertFalse(randomCause.isRadioRestartFailure(mContext, mPhone.getSubId()));
+        int [] matchingErrorCodes = {randomCause.getErrorCode()};
+        mPersistableBundle.putIntArray(CarrierConfigManager
+                .KEY_RADIO_RESTART_FAILURE_CAUSES_INT_ARRAY, matchingErrorCodes);
+        assertTrue(randomCause.isRadioRestartFailure(mContext, mPhone.getSubId()));
+    }
 }
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 89d670c..04e9305 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DcTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DcTrackerTest.java
@@ -29,6 +29,7 @@
 import static org.mockito.Matchers.anyLong;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
@@ -51,6 +52,7 @@
 import android.net.NetworkRequest;
 import android.net.Uri;
 import android.os.AsyncResult;
+import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.Message;
@@ -64,13 +66,16 @@
 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 android.util.Pair;
 
 import com.android.internal.R;
 import com.android.internal.telephony.DctConstants;
@@ -93,8 +98,6 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 
 public class DcTrackerTest extends TelephonyTest {
 
@@ -126,7 +129,8 @@
             1 << (TelephonyManager.NETWORK_TYPE_EHRPD - 1);
     private static final Uri PREFERAPN_URI = Uri.parse(
             Telephony.Carriers.CONTENT_URI + "/preferapn");
-
+    private static final int DATA_ENABLED_CHANGED = 0;
+    private static final String FAKE_PLMN = "44010";
 
     @Mock
     ISub mIsub;
@@ -141,9 +145,11 @@
     @Mock
     ApnSetting mApnSetting;
     @Mock
-    DcAsyncChannel mDcac;
+    DataConnection mDataConnection;
     @Mock
     PackageManagerService mMockPackageManagerInternal;
+    @Mock
+    Handler mHandler;
 
     private DcTracker mDct;
     private DcTrackerTestHandler mDcTrackerTestHandler;
@@ -157,11 +163,13 @@
     private final ApnSettingContentProvider mApnSettingContentProvider =
             new ApnSettingContentProvider();
 
+    private Message mMessage;
+
     private void addDataService() {
         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,
@@ -199,20 +207,11 @@
             logd("   sortOrder = " + sortOrder);
 
             if (uri.compareTo(Telephony.Carriers.CONTENT_URI) == 0
-                    || uri.compareTo(Uri.withAppendedPath(
-                            Telephony.Carriers.CONTENT_URI, "filtered")) == 0) {
-                if (projection == null && selectionArgs == null && selection != null) {
+                    || uri.toString().startsWith(Uri.withAppendedPath(
+                            Telephony.Carriers.CONTENT_URI, "filtered").toString())) {
+                if (projection == null) {
 
-                    Pattern pattern = Pattern.compile("^numeric = '([0-9]*)'");
-                    Matcher matcher = pattern.matcher(selection);
-                    if (!matcher.find()) {
-                        logd("Cannot find MCC/MNC from " + selection);
-                        return null;
-                    }
-
-                    String plmn = matcher.group(1);
-
-                    logd("Query '" + plmn + "' APN settings");
+                    logd("Query '" + FAKE_PLMN + "' APN settings");
                     MatrixCursor mc = new MatrixCursor(
                             new String[]{Telephony.Carriers._ID, Telephony.Carriers.NUMERIC,
                                     Telephony.Carriers.NAME, Telephony.Carriers.APN,
@@ -226,17 +225,20 @@
                                     Telephony.Carriers.CARRIER_ENABLED, Telephony.Carriers.BEARER,
                                     Telephony.Carriers.BEARER_BITMASK,
                                     Telephony.Carriers.PROFILE_ID,
-                                    Telephony.Carriers.MODEM_COGNITIVE,
-                                    Telephony.Carriers.MAX_CONNS, Telephony.Carriers.WAIT_TIME,
-                                    Telephony.Carriers.MAX_CONNS_TIME, Telephony.Carriers.MTU,
+                                    Telephony.Carriers.MODEM_PERSIST,
+                                    Telephony.Carriers.MAX_CONNECTIONS,
+                                    Telephony.Carriers.WAIT_TIME_RETRY,
+                                    Telephony.Carriers.TIME_LIMIT_FOR_MAX_CONNECTIONS,
+                                    Telephony.Carriers.MTU,
                                     Telephony.Carriers.MVNO_TYPE,
                                     Telephony.Carriers.MVNO_MATCH_DATA,
                                     Telephony.Carriers.NETWORK_TYPE_BITMASK,
-                                    Telephony.Carriers.APN_SET_ID});
+                                    Telephony.Carriers.APN_SET_ID,
+                                    Telephony.Carriers.CARRIER_ID});
 
                     mc.addRow(new Object[]{
                             2163,                   // id
-                            plmn,                   // numeric
+                            FAKE_PLMN,              // numeric
                             "sp-mode",              // name
                             FAKE_APN1,              // apn
                             "",                     // proxy
@@ -254,7 +256,7 @@
                             ServiceState.RIL_RADIO_TECHNOLOGY_LTE, // bearer
                             0,                      // bearer_bitmask
                             0,                      // profile_id
-                            0,                      // modem_cognitive
+                            1,                      // modem_cognitive
                             0,                      // max_conns
                             0,                      // wait_time
                             0,                      // max_conns_time
@@ -262,12 +264,13 @@
                             "",                     // mvno_type
                             "",                     // mnvo_match_data
                             NETWORK_TYPE_LTE_BITMASK, // network_type_bitmask
-                            0                       // apn_set_id
+                            0,                      // apn_set_id
+                            -1                      // carrier_id
                     });
 
                     mc.addRow(new Object[]{
                             2164,                   // id
-                            plmn,                   // numeric
+                            FAKE_PLMN,              // numeric
                             "mopera U",             // name
                             FAKE_APN2,              // apn
                             "",                     // proxy
@@ -285,7 +288,7 @@
                             ServiceState.RIL_RADIO_TECHNOLOGY_LTE, // bearer,
                             0,                      // bearer_bitmask
                             0,                      // profile_id
-                            0,                      // modem_cognitive
+                            1,                      // modem_cognitive
                             0,                      // max_conns
                             0,                      // wait_time
                             0,                      // max_conns_time
@@ -293,12 +296,13 @@
                             "",                     // mvno_type
                             "",                     // mnvo_match_data
                             NETWORK_TYPE_LTE_BITMASK, // network_type_bitmask
-                            0                       // apn_set_id
+                            0,                      // apn_set_id
+                            -1                      // carrier_id
                     });
 
                     mc.addRow(new Object[]{
                             2165,                   // id
-                            plmn,                   // numeric
+                            FAKE_PLMN,              // numeric
                             "b-mobile for Nexus",   // name
                             FAKE_APN3,              // apn
                             "",                     // proxy
@@ -316,7 +320,7 @@
                             0,                      // bearer
                             0,                      // bearer_bitmask
                             0,                      // profile_id
-                            0,                      // modem_cognitive
+                            1,                      // modem_cognitive
                             0,                      // max_conns
                             0,                      // wait_time
                             0,                      // max_conns_time
@@ -324,12 +328,13 @@
                             "",                     // mvno_type
                             "",                     // mnvo_match_data
                             0,                      // network_type_bitmask
-                            0                       // apn_set_id
+                            0,                      // apn_set_id
+                            -1                      // carrier_id
                     });
 
                     mc.addRow(new Object[]{
                             2166,                   // id
-                            plmn,                   // numeric
+                            FAKE_PLMN,              // numeric
                             "sp-mode ehrpd",        // name
                             FAKE_APN4,              // apn
                             "",                     // proxy
@@ -347,7 +352,7 @@
                             ServiceState.RIL_RADIO_TECHNOLOGY_EHRPD, // bearer
                             0,                      // bearer_bitmask
                             0,                      // profile_id
-                            0,                      // modem_cognitive
+                            1,                      // modem_cognitive
                             0,                      // max_conns
                             0,                      // wait_time
                             0,                      // max_conns_time
@@ -355,12 +360,13 @@
                             "",                     // mvno_type
                             "",                     // mnvo_match_data
                             NETWORK_TYPE_EHRPD_BITMASK, // network_type_bitmask
-                            0                       // apn_set_id
+                            0,                      // apn_set_id
+                            -1                      // carrier_id
                     });
 
                     mc.addRow(new Object[]{
                             2166,                   // id
-                            plmn,                   // numeric
+                            FAKE_PLMN,              // numeric
                             "b-mobile for Nexus",   // name
                             FAKE_APN5,              // apn
                             "",                     // proxy
@@ -378,7 +384,7 @@
                             0,                      // bearer
                             0,                      // bearer_bitmask
                             0,                      // profile_id
-                            0,                      // modem_cognitive
+                            1,                      // modem_cognitive
                             0,                      // max_conns
                             0,                      // wait_time
                             0,                      // max_conns_time
@@ -386,7 +392,8 @@
                             "",                     // mvno_type
                             "",                     // mnvo_match_data
                             0,                      // network_type_bitmask
-                            0                       // apn_set_id
+                            0,                      // apn_set_id
+                            -1                      // carrier_id
                     });
 
                     return mc;
@@ -420,7 +427,6 @@
         doReturn("fake.action_attached").when(mPhone).getActionAttached();
         doReturn(ServiceState.RIL_RADIO_TECHNOLOGY_LTE).when(mServiceState)
                 .getRilDataRadioTechnology();
-        doReturn("44010").when(mSimRecords).getOperatorNumeric();
 
         mContextFixture.putStringArrayResource(com.android.internal.R.array.networkAttributes,
                 sNetworkAttributes);
@@ -517,17 +523,14 @@
         assertEquals("", dp.getUserName());
         assertEquals("", dp.getPassword());
         assertEquals(type, dp.getType());
-        assertEquals(0, dp.getMaxConnsTime());
-        assertEquals(0, dp.getMaxConns());
         assertEquals(0, dp.getWaitTime());
         assertTrue(dp.isEnabled());
         assertEquals(supportedApnTypesBitmap, dp.getSupportedApnTypesBitmap());
         assertEquals("IP", dp.getRoamingProtocol());
         assertEquals(bearerBitmask, dp.getBearerBitmap());
         assertEquals(0, dp.getMtu());
-        assertEquals("", dp.getMvnoType());
-        assertEquals("", dp.getMvnoMatchData());
-        assertFalse(dp.isModemCognitive());
+        assertTrue(dp.isPersistent());
+        assertFalse(dp.isPreferred());
     }
 
     private void verifyDataConnected(final String apnSetting) {
@@ -608,7 +611,7 @@
 
         logd("Sending EVENT_ENABLE_NEW_APN");
         // APN id 0 is APN_TYPE_DEFAULT
-        mDct.setEnabled(DctConstants.APN_DEFAULT_ID, true);
+        mDct.setEnabled(ApnSetting.TYPE_DEFAULT, true);
         waitForMs(200);
 
         dataConnectionReasons = new DataConnectionReasons();
@@ -682,7 +685,7 @@
 
         logd("Sending EVENT_ENABLE_NEW_APN");
         // APN id 0 is APN_TYPE_DEFAULT
-        mDct.setEnabled(DctConstants.APN_DEFAULT_ID, true);
+        mDct.setEnabled(ApnSetting.TYPE_DEFAULT, true);
         waitForMs(200);
 
 
@@ -713,6 +716,7 @@
         // Simulate the timer expires.
         Intent intent = new Intent("com.android.internal.telephony.data-reconnect.default");
         intent.putExtra("reconnect_alarm_extra_type", PhoneConstants.APN_TYPE_DEFAULT);
+        intent.putExtra("reconnect_alarm_extra_transport_type", TransportType.WWAN);
         intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, 0);
         intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
         mContext.sendBroadcast(intent);
@@ -741,8 +745,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(DctConstants.APN_IMS_ID, true);
-        mDct.setEnabled(DctConstants.APN_DEFAULT_ID, 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));
@@ -796,8 +800,8 @@
         mBundle.putStringArray(CarrierConfigManager.KEY_CARRIER_METERED_ROAMING_APN_TYPES_STRINGS,
                 new String[]{PhoneConstants.APN_TYPE_DEFAULT, PhoneConstants.APN_TYPE_MMS});
 
-        mDct.setEnabled(DctConstants.APN_IMS_ID, true);
-        mDct.setEnabled(DctConstants.APN_DEFAULT_ID, 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));
@@ -854,8 +858,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(DctConstants.APN_IMS_ID, true);
-        mDct.setEnabled(DctConstants.APN_DEFAULT_ID, true);
+        mDct.setEnabled(ApnSetting.TYPE_IMS, true);
+        mDct.setEnabled(ApnSetting.TYPE_DEFAULT, true);
 
         logd("Sending DATA_ENABLED_CMD");
         mDct.setUserDataEnabled(true);
@@ -891,6 +895,7 @@
     }
 
     // Test the default data switch scenario.
+    @FlakyTest /* flakes 1.57% of the time */
     @Test
     @MediumTest
     public void testDDSResetAutoAttach() throws Exception {
@@ -969,8 +974,8 @@
         boolean dataEnabled = mDct.isUserDataEnabled();
         mDct.setUserDataEnabled(true);
 
-        mDct.setEnabled(DctConstants.APN_IMS_ID, true);
-        mDct.setEnabled(DctConstants.APN_DEFAULT_ID, 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));
@@ -1010,14 +1015,15 @@
     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();
+        doReturn(mDataConnection).when(mApnContext).getDataConnection();
         doReturn(true).when(mApnContext).isEnabled();
         doReturn(true).when(mApnContext).getDependencyMet();
         doReturn(true).when(mApnContext).isReady();
-        doReturn(true).when(mApnContext).hasNoRestrictedRequests(eq(true));
+        doReturn(false).when(mApnContext).hasRestrictedRequests(eq(true));
     }
 
     // Test the emergency APN setup.
@@ -1121,7 +1127,7 @@
     @SmallTest
     public void testTrySetupRestrictedDataDisabled() throws Exception {
         initApns(PhoneConstants.APN_TYPE_DEFAULT, new String[]{PhoneConstants.APN_TYPE_ALL});
-        doReturn(false).when(mApnContext).hasNoRestrictedRequests(eq(true));
+        doReturn(true).when(mApnContext).hasRestrictedRequests(eq(true));
 
         mDct.setUserDataEnabled(false);
 
@@ -1149,7 +1155,7 @@
     @SmallTest
     public void testTrySetupRestrictedRoamingDisabled() throws Exception {
         initApns(PhoneConstants.APN_TYPE_DEFAULT, new String[]{PhoneConstants.APN_TYPE_ALL});
-        doReturn(false).when(mApnContext).hasNoRestrictedRequests(eq(true));
+        doReturn(true).when(mApnContext).hasRestrictedRequests(eq(true));
 
         mDct.setUserDataEnabled(true);
         mDct.setDataRoamingEnabledByUser(false);
@@ -1169,8 +1175,7 @@
         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),
+        verify(mSimulatedCommandsVerifier, times(1)).setupDataCall(anyInt(), any(DataProfile.class),
                 eq(false), eq(false), eq(DataService.REQUEST_REASON_NORMAL), any(),
                 any(Message.class));
     }
@@ -1258,6 +1263,7 @@
     }
 
     // Test update waiting apn list when on data rat change
+    @FlakyTest /* flakes 0.86% of the time */
     @Test
     @SmallTest
     public void testUpdateWaitingApnListOnDataRatChange() throws Exception {
@@ -1265,7 +1271,7 @@
                 .getRilDataRadioTechnology();
         mBundle.putStringArray(CarrierConfigManager.KEY_CARRIER_METERED_APN_TYPES_STRINGS,
                 new String[]{PhoneConstants.APN_TYPE_DEFAULT});
-        mDct.setEnabled(DctConstants.APN_DEFAULT_ID, true);
+        mDct.setEnabled(ApnSetting.TYPE_DEFAULT, true);
         mDct.setUserDataEnabled(true);
         initApns(PhoneConstants.APN_TYPE_DEFAULT, new String[]{PhoneConstants.APN_TYPE_ALL});
 
@@ -1306,6 +1312,7 @@
         Intent intent = new Intent("com.android.internal.telephony.data-reconnect.default");
         intent.putExtra("reconnect_alarm_extra_type", PhoneConstants.APN_TYPE_DEFAULT);
         intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, 0);
+        intent.putExtra("reconnect_alarm_extra_transport_type", TransportType.WWAN);
         intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
         mContext.sendBroadcast(intent);
         waitForMs(200);
@@ -1342,7 +1349,7 @@
                 Settings.Global.TETHER_DUN_APN, null);
         // should return APN from db
         dunApn = mDct.fetchDunApns().get(0);
-        assertEquals(FAKE_APN5, dunApn.apn);
+        assertEquals(FAKE_APN5, dunApn.getApnName());
     }
 
     // Test for fetchDunApns() with apn set id
@@ -1385,7 +1392,7 @@
                 .getRilDataRadioTechnology();
         mBundle.putStringArray(CarrierConfigManager.KEY_CARRIER_METERED_APN_TYPES_STRINGS,
                 new String[]{PhoneConstants.APN_TYPE_DEFAULT});
-        mDct.setEnabled(DctConstants.APN_DEFAULT_ID, true);
+        mDct.setEnabled(ApnSetting.TYPE_DEFAULT, true);
         mDct.setUserDataEnabled(true);
         initApns(PhoneConstants.APN_TYPE_DEFAULT, new String[]{PhoneConstants.APN_TYPE_ALL});
 
@@ -1440,7 +1447,6 @@
         assertTrue(mDct.isDataEnabled());
         assertTrue(mDct.isUserDataEnabled());
 
-
         mDct.setUserDataEnabled(false);
         waitForMs(200);
 
@@ -1450,6 +1456,8 @@
 
         // Changing provisioned to 0.
         Settings.Global.putInt(resolver, Settings.Global.DEVICE_PROVISIONED, 0);
+        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_DEVICE_PROVISIONED_CHANGE, null));
+        waitForMs(200);
 
         assertTrue(mDct.isDataEnabled());
         assertTrue(mDct.isUserDataEnabled());
@@ -1458,10 +1466,57 @@
         // Settings.Global.MOBILE_DATA and keep data enabled when provisioned.
         mDct.setUserDataEnabled(true);
         Settings.Global.putInt(resolver, Settings.Global.DEVICE_PROVISIONED, 1);
+        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_DEVICE_PROVISIONED_CHANGE, null));
         waitForMs(200);
 
         assertTrue(mDct.isDataEnabled());
         assertTrue(mDct.isUserDataEnabled());
         assertEquals(1, Settings.Global.getInt(resolver, Settings.Global.MOBILE_DATA));
     }
+
+    @Test
+    @SmallTest
+    public void testNotifyDataEnabledChanged() throws Exception {
+        doAnswer(invocation -> {
+            mMessage = (Message) invocation.getArguments()[0];
+            return true;
+        }).when(mHandler).sendMessageDelayed(any(), anyLong());
+
+        // Test registration.
+        mDct.registerForDataEnabledChanged(mHandler, DATA_ENABLED_CHANGED, null);
+        verifyDataEnabledChangedMessage(true, DataEnabledSettings.REASON_REGISTERED);
+
+        // Disable user data. Should receive data enabled change to false.
+        mDct.setUserDataEnabled(false);
+        waitForMs(200);
+        verifyDataEnabledChangedMessage(false, DataEnabledSettings.REASON_USER_DATA_ENABLED);
+
+        // Changing provisioned to 0. Shouldn't receive any message, as data enabled remains false.
+        ContentResolver resolver = mContext.getContentResolver();
+        Settings.Global.putInt(resolver, Settings.Global.DEVICE_PROVISIONED, 0);
+        Settings.Global.putInt(resolver, Settings.Global.DEVICE_PROVISIONING_MOBILE_DATA_ENABLED,
+                0);
+        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_DEVICE_PROVISIONED_CHANGE, null));
+        waitForMs(200);
+        assertFalse(mDct.isDataEnabled());
+        verify(mHandler, never()).sendMessageDelayed(any(), anyLong());
+
+        // Changing provisioningDataEnabled to 1. It should trigger data enabled change to true.
+        Settings.Global.putInt(resolver,
+                Settings.Global.DEVICE_PROVISIONING_MOBILE_DATA_ENABLED, 1);
+        mDct.sendMessage(mDct.obtainMessage(
+                DctConstants.EVENT_DEVICE_PROVISIONING_DATA_SETTING_CHANGE, null));
+        waitForMs(200);
+        verifyDataEnabledChangedMessage(
+                true, DataEnabledSettings.REASON_PROVISIONING_DATA_ENABLED_CHANGED);
+    }
+
+    private void verifyDataEnabledChangedMessage(boolean enabled, int reason) {
+        verify(mHandler, times(1)).sendMessageDelayed(any(), anyLong());
+        Pair<Boolean, Integer> result = (Pair) ((AsyncResult) mMessage.obj).result;
+        assertEquals(DATA_ENABLED_CHANGED, mMessage.what);
+        assertEquals(enabled, result.first);
+        assertEquals(reason, (int) result.second);
+        clearInvocations(mHandler);
+    }
 }
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/dataconnection/TelephonyNetworkFactoryTest.java b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/TelephonyNetworkFactoryTest.java
index 6d1cf8b..0a11a5b 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/TelephonyNetworkFactoryTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/TelephonyNetworkFactoryTest.java
@@ -16,6 +16,12 @@
 
 package com.android.internal.telephony.dataconnection;
 
+import static com.android.internal.telephony.TelephonyTestUtils.waitForMs;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+
 import android.content.Context;
 import android.net.ConnectivityManager;
 import android.net.NetworkCapabilities;
@@ -24,124 +30,133 @@
 import android.os.Binder;
 import android.os.HandlerThread;
 import android.os.Looper;
-import android.os.Message;
 import android.support.test.filters.FlakyTest;
 import android.telephony.Rlog;
-import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 
-import com.android.internal.telephony.ContextFixture;
+import com.android.internal.telephony.PhoneSwitcher;
+import com.android.internal.telephony.RadioConfig;
+import com.android.internal.telephony.SubscriptionController;
+import com.android.internal.telephony.TelephonyTest;
 import com.android.internal.telephony.mocks.ConnectivityServiceMock;
-import com.android.internal.telephony.mocks.DcTrackerMock;
 import com.android.internal.telephony.mocks.PhoneSwitcherMock;
 import com.android.internal.telephony.mocks.SubscriptionControllerMock;
 import com.android.internal.telephony.mocks.SubscriptionMonitorMock;
 import com.android.internal.telephony.mocks.TelephonyRegistryMock;
 
-import org.junit.Ignore;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
 
-public class TelephonyNetworkFactoryTest extends AndroidTestCase {
+import java.util.ArrayList;
+
+public class TelephonyNetworkFactoryTest extends TelephonyTest {
     private final static String LOG_TAG = "TelephonyNetworkFactoryTest";
 
-    private void waitABit() {
-        try {
-            Thread.sleep(250);
-        } catch (Exception e) {}
-    }
+    @Mock
+    private RadioConfig mMockRadioConfig;
 
     private String mTestName = "";
 
+    private TelephonyRegistryMock mTelephonyRegistryMock;
+    private PhoneSwitcherMock mPhoneSwitcherMock;
+    private SubscriptionControllerMock mSubscriptionControllerMock;
+    private SubscriptionMonitorMock mSubscriptionMonitorMock;
+    private HandlerThread mHandlerThread;
+    private ConnectivityServiceMock mConnectivityServiceMock;
+    private Looper mLooper;
+    private final ArrayList<NetworkRequest> mNetworkRequestList = new ArrayList<>();
+
+    private TelephonyNetworkFactory mTelephonyNetworkFactoryUT;
+
     private void log(String str) {
         Rlog.d(LOG_TAG + " " + mTestName, str);
     }
 
-    private class TestSetup {
-        final TelephonyRegistryMock telephonyRegistryMock;
-        final PhoneSwitcherMock phoneSwitcherMock;
-        final SubscriptionControllerMock subscriptionControllerMock;
-        final SubscriptionMonitorMock subscriptionMonitorMock;
-        final HandlerThread handlerThread;
-        final ConnectivityServiceMock connectivityServiceMock;
-        final Looper looper;
-        DcTrackerMock dcTrackerMock;
-        final Context contextMock;
-        private Object mLock = new Object();
-        private static final int MAX_INIT_WAIT_MS = 30000; // 30 seconds
-
-        TestSetup(int numPhones) {
-            handlerThread = new HandlerThread("TelephonyNetworkFactoryTest") {
-                @Override
-                public void onLooperPrepared() {
-                    synchronized (mLock) {
-                        if (dcTrackerMock == null) dcTrackerMock = new DcTrackerMock();
-                        mLock.notifyAll();
-                    }
-                }
-            };
-            handlerThread.start();
-            // wait until dct created
-            synchronized (mLock) {
-                try {
-                    mLock.wait(MAX_INIT_WAIT_MS);
-                } catch (InterruptedException ie) {
-                }
-                if (dcTrackerMock == null) fail("failed to initialize dct");
-            }
-            looper = handlerThread.getLooper();
-
-            final ContextFixture contextFixture = new ContextFixture();
-            String[] networkConfigString = getContext().getResources().getStringArray(
-                    com.android.internal.R.array.networkAttributes);
-            contextFixture.putStringArrayResource(com.android.internal.R.array.networkAttributes,
-                    networkConfigString);
-            contextMock = contextFixture.getTestDouble();
-
-            connectivityServiceMock = new ConnectivityServiceMock(contextMock);
-            ConnectivityManager cm =
-                    new ConnectivityManager(contextMock, connectivityServiceMock);
-            contextFixture.setSystemService(Context.CONNECTIVITY_SERVICE, cm);
-
-            telephonyRegistryMock = new TelephonyRegistryMock();
-            phoneSwitcherMock = new PhoneSwitcherMock(numPhones, looper);
-            subscriptionControllerMock =
-                    new SubscriptionControllerMock(contextMock, telephonyRegistryMock, numPhones);
-            subscriptionMonitorMock = new SubscriptionMonitorMock(numPhones);
-        }
-
-        void die() {
-            connectivityServiceMock.die();
-            looper.quit();
-            handlerThread.quit();
-        }
-    }
-
-    private TelephonyNetworkFactory makeTnf(int phoneId, TestSetup ts) {
-        return new TelephonyNetworkFactory(ts.phoneSwitcherMock, ts.subscriptionControllerMock,
-                ts.subscriptionMonitorMock, ts.looper, ts.contextMock, phoneId, ts.dcTrackerMock);
-    }
-
-    private NetworkRequest makeSubSpecificDefaultRequest(TestSetup ts, int subId) {
+    private NetworkRequest makeSubSpecificDefaultRequest(int subId) {
         NetworkCapabilities netCap = (new NetworkCapabilities()).
                 addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET).
                 addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED).
                 addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
         netCap.setNetworkSpecifier(new StringNetworkSpecifier(Integer.toString(subId)));
-        return ts.connectivityServiceMock.requestNetwork(netCap, null, 0, new Binder(), -1);
+        return mConnectivityServiceMock.requestNetwork(netCap, null, 0, new Binder(), -1);
     }
-    private NetworkRequest makeSubSpecificMmsRequest(TestSetup ts, int subId) {
+    private NetworkRequest makeSubSpecificMmsRequest(int subId) {
         NetworkCapabilities netCap = (new NetworkCapabilities()).
                 addCapability(NetworkCapabilities.NET_CAPABILITY_MMS).
                 addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED).
                 addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
         netCap.setNetworkSpecifier(new StringNetworkSpecifier(Integer.toString(subId)));
-        return ts.connectivityServiceMock.requestNetwork(netCap, null, 0, new Binder(), -1);
+        return mConnectivityServiceMock.requestNetwork(netCap, null, 0, new Binder(), -1);
     }
 
+    @Before
+    public void setUp() throws Exception {
+        super.setUp(getClass().getSimpleName());
+        replaceInstance(RadioConfig.class, "sRadioConfig", null, mMockRadioConfig);
+
+        mHandlerThread = new HandlerThread("TelephonyNetworkFactoryTest");
+        mHandlerThread.start();
+        mLooper = mHandlerThread.getLooper();
+
+        mContextFixture.putStringArrayResource(com.android.internal.R.array.networkAttributes,
+                new String[]{"wifi,1,1,1,-1,true", "mobile,0,0,0,-1,true",
+                        "mobile_mms,2,0,2,60000,true", "mobile_supl,3,0,2,60000,true",
+                        "mobile_dun,4,0,2,60000,true", "mobile_hipri,5,0,3,60000,true",
+                        "mobile_fota,10,0,2,60000,true", "mobile_ims,11,0,2,60000,true",
+                        "mobile_cbs,12,0,2,60000,true", "wifi_p2p,13,1,0,-1,true",
+                        "mobile_ia,14,0,2,-1,true", "mobile_emergency,15,0,2,-1,true"});
+
+        doAnswer(invocation -> {
+            mNetworkRequestList.add((NetworkRequest) invocation.getArguments()[0]);
+            return null;
+        }).when(mDcTracker).requestNetwork(any(), any());
+
+        doAnswer(invocation -> {
+            mNetworkRequestList.remove((NetworkRequest) invocation.getArguments()[0]);
+            return null;
+        }).when(mDcTracker).releaseNetwork(any(), any());
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mConnectivityServiceMock.die();
+        mLooper.quit();
+        mHandlerThread.quit();
+        super.tearDown();
+    }
+
+    private void createMockedTelephonyComponents(int numberOfPhones) throws Exception {
+        mConnectivityServiceMock = new ConnectivityServiceMock(mContext);
+        mContextFixture.setSystemService(Context.CONNECTIVITY_SERVICE,
+                new ConnectivityManager(mContext, mConnectivityServiceMock));
+        mTelephonyRegistryMock = new TelephonyRegistryMock();
+        mPhoneSwitcherMock = new PhoneSwitcherMock(numberOfPhones, mLooper);
+        mSubscriptionControllerMock = new SubscriptionControllerMock(mContext,
+                mTelephonyRegistryMock, numberOfPhones);
+        mSubscriptionMonitorMock = new SubscriptionMonitorMock(numberOfPhones);
+        mPhoneSwitcherMock = new PhoneSwitcherMock(numberOfPhones, mLooper);
+        mSubscriptionControllerMock = new SubscriptionControllerMock(mContext,
+                mTelephonyRegistryMock, numberOfPhones);
+        mSubscriptionMonitorMock = new SubscriptionMonitorMock(numberOfPhones);
+
+        replaceInstance(SubscriptionController.class, "sInstance", null,
+                mSubscriptionControllerMock);
+        replaceInstance(PhoneSwitcher.class, "sPhoneSwitcher", null, mPhoneSwitcherMock);
+
+        mTelephonyNetworkFactoryUT = new TelephonyNetworkFactory(mSubscriptionMonitorMock, mLooper,
+                mPhone);
+
+        replaceInstance(TelephonyNetworkFactory.class, "mDcTracker",
+                mTelephonyNetworkFactoryUT, mDcTracker);
+    }
 
     /**
      * Test that phone active changes cause the DcTracker to get poked.
      */
     @FlakyTest
+    @Test
     @SmallTest
     public void testActive() throws Exception {
         mTestName = "testActive";
@@ -149,91 +164,68 @@
         final int phoneId = 0;
         final int subId = 0;
 
-        TestSetup ts = new TestSetup(numberOfPhones);
+        createMockedTelephonyComponents(numberOfPhones);
 
-        makeTnf(phoneId, ts);
-
-        ts.subscriptionControllerMock.setDefaultDataSubId(subId);
-        ts.subscriptionControllerMock.setSlotSubId(phoneId, subId);
-        ts.subscriptionMonitorMock.notifySubscriptionChanged(phoneId);
-        ts.subscriptionMonitorMock.notifyDefaultSubscriptionChanged(phoneId);
+        mPhoneSwitcherMock.setPreferredDataPhoneId(phoneId);
+        mSubscriptionControllerMock.setDefaultDataSubId(subId);
+        mSubscriptionControllerMock.setSlotSubId(phoneId, subId);
+        mSubscriptionMonitorMock.notifySubscriptionChanged(phoneId);
 
         log("addDefaultRequest");
-        ts.connectivityServiceMock.addDefaultRequest();
-        waitABit();
-        if (ts.dcTrackerMock.getNumberOfLiveRequests() != 0) {
-            fail("pretest of LiveRequests != 0");
-        }
+        mConnectivityServiceMock.addDefaultRequest();
+        waitForMs(250);
+        assertEquals(0, mNetworkRequestList.size());
 
         log("setPhoneActive true: phoneId = " + phoneId);
-        ts.phoneSwitcherMock.setPhoneActive(phoneId, true);
-        waitABit();
-        if (ts.dcTrackerMock.getNumberOfLiveRequests() != 1) {
-            fail("post-active test of LiveRequests != 1");
-        }
+        mPhoneSwitcherMock.setPhoneActive(phoneId, true);
+        waitForMs(250);
+        assertEquals(1, mNetworkRequestList.size());
 
         log("makeSubSpecificDefaultRequest: subId = " + subId);
-        NetworkRequest subSpecificDefault = makeSubSpecificDefaultRequest(ts, subId);
-        waitABit();
-        if (ts.dcTrackerMock.getNumberOfLiveRequests() != 2) {
-            fail("post-second-request test of LiveRequests != 2");
-        }
+        NetworkRequest subSpecificDefault = makeSubSpecificDefaultRequest(subId);
+        waitForMs(250);
+        assertEquals(2, mNetworkRequestList.size());
 
         log("setPhoneActive false: phoneId = " + phoneId);
-        ts.phoneSwitcherMock.setPhoneActive(phoneId, false);
-        waitABit();
-        if (ts.dcTrackerMock.getNumberOfLiveRequests() != 0) {
-            fail("post-inactive test of LiveRequests != 0");
-        }
+        mPhoneSwitcherMock.setPhoneActive(phoneId, false);
+        waitForMs(250);
+        assertEquals(0, mNetworkRequestList.size());
 
         log("makeSubSpecificDefaultRequest: subId = " + subId);
-        NetworkRequest subSpecificMms = makeSubSpecificMmsRequest(ts, subId);
-        waitABit();
-        if (ts.dcTrackerMock.getNumberOfLiveRequests() != 0) {
-            fail("post-mms-add test of LiveRequests != 0");
-        }
+        NetworkRequest subSpecificMms = makeSubSpecificMmsRequest(subId);
+        waitForMs(250);
+        assertEquals(0, mNetworkRequestList.size());
 
         log("setPhoneActive true: phoneId = " + phoneId);
-        ts.phoneSwitcherMock.setPhoneActive(phoneId, true);
-        waitABit();
-        if (ts.dcTrackerMock.getNumberOfLiveRequests() != 3) {
-            fail("post-active-mms-add test of LiveRequests != 3");
-        }
+        mPhoneSwitcherMock.setPhoneActive(phoneId, true);
+        waitForMs(250);
+        assertEquals(3, mNetworkRequestList.size());
 
         log("releaseNetworkRequest: subSpecificDefault = " + subSpecificDefault);
-        ts.connectivityServiceMock.releaseNetworkRequest(subSpecificDefault);
-        waitABit();
-        if (ts.dcTrackerMock.getNumberOfLiveRequests() != 2) {
-            fail("post-remove-default test of LiveRequests != 2");
-        }
+        mConnectivityServiceMock.releaseNetworkRequest(subSpecificDefault);
+        waitForMs(250);
+        assertEquals(2, mNetworkRequestList.size());
 
         log("setPhoneActive false: phoneId = " + phoneId);
-        ts.phoneSwitcherMock.setPhoneActive(phoneId, false);
-        waitABit();
-        if (ts.dcTrackerMock.getNumberOfLiveRequests() != 0) {
-            fail("test 8, LiveRequests != 0");
-        }
+        mPhoneSwitcherMock.setPhoneActive(phoneId, false);
+        waitForMs(250);
+        assertEquals(0, mNetworkRequestList.size());
 
         log("releaseNetworkRequest: subSpecificMms = " + subSpecificMms);
-        ts.connectivityServiceMock.releaseNetworkRequest(subSpecificMms);
-        waitABit();
-        if (ts.dcTrackerMock.getNumberOfLiveRequests() != 0) {
-            fail("test 9, LiveRequests != 0");
-        }
+        mConnectivityServiceMock.releaseNetworkRequest(subSpecificMms);
+        waitForMs(250);
+        assertEquals(0, mNetworkRequestList.size());
 
         log("setPhoneActive true: phoneId = " + phoneId);
-        ts.phoneSwitcherMock.setPhoneActive(phoneId, true);
-        waitABit();
-        if (ts.dcTrackerMock.getNumberOfLiveRequests() != 1) {
-            fail("test 10, LiveRequests != 1," + ts.dcTrackerMock.getNumberOfLiveRequests());
-        }
-
-        ts.die();
+        mPhoneSwitcherMock.setPhoneActive(phoneId, true);
+        waitForMs(250);
+        assertEquals(1, mNetworkRequestList.size());
     }
 
     /**
      * Test that network request changes cause the DcTracker to get poked.
      */
+    @Test
     @SmallTest
     public void testRequests() throws Exception {
         mTestName = "testActive";
@@ -244,80 +236,57 @@
         final int altSubId = 1;
         final int unusedSubId = 2;
 
-        TestSetup ts = new TestSetup(numberOfPhones);
+        createMockedTelephonyComponents(numberOfPhones);
 
-        makeTnf(phoneId, ts);
+        mPhoneSwitcherMock.setPreferredDataPhoneId(phoneId);
+        mSubscriptionControllerMock.setDefaultDataSubId(subId);
+        mSubscriptionControllerMock.setSlotSubId(phoneId, subId);
+        mSubscriptionMonitorMock.notifySubscriptionChanged(phoneId);
+        waitForMs(250);
 
-        ts.subscriptionControllerMock.setDefaultDataSubId(subId);
-        ts.subscriptionControllerMock.setSlotSubId(phoneId, subId);
-        ts.subscriptionMonitorMock.notifySubscriptionChanged(phoneId);
-        ts.subscriptionMonitorMock.notifyDefaultSubscriptionChanged(phoneId);
-        waitABit();
+        assertEquals(0, mNetworkRequestList.size());
 
-        if (ts.dcTrackerMock.getNumberOfLiveRequests() != 0) {
-            fail("test 1, LiveRequests != 0");
-        }
+        mPhoneSwitcherMock.setPhoneActive(phoneId, true);
+        waitForMs(250);
+        assertEquals(0, mNetworkRequestList.size());
 
-        ts.phoneSwitcherMock.setPhoneActive(phoneId, true);
-        waitABit();
-        if (ts.dcTrackerMock.getNumberOfLiveRequests() != 0) {
-            fail("test 2, LiveRequests != 0");
-        }
+        mConnectivityServiceMock.addDefaultRequest();
+        waitForMs(250);
+        assertEquals(1, mNetworkRequestList.size());
 
-        ts.connectivityServiceMock.addDefaultRequest();
-        waitABit();
-        if (ts.dcTrackerMock.getNumberOfLiveRequests() != 1) {
-            fail("test 3, LiveRequests != 1");
-        }
+        mSubscriptionControllerMock.setSlotSubId(altPhoneId, altSubId);
+        waitForMs(250);
+        assertEquals(1, mNetworkRequestList.size());
 
-        ts.subscriptionControllerMock.setSlotSubId(altPhoneId, altSubId);
-        waitABit();
-        if (ts.dcTrackerMock.getNumberOfLiveRequests() != 1) {
-            fail("test 4, LiveRequests != 1");
-        }
+        mPhoneSwitcherMock.setPreferredDataPhoneId(altPhoneId);
+        mSubscriptionControllerMock.setDefaultDataSubId(altSubId);
+        mPhoneSwitcherMock.notifyActivePhoneChange(phoneId);
 
-        ts.subscriptionControllerMock.setDefaultDataSubId(altSubId);
-        ts.subscriptionMonitorMock.notifyDefaultSubscriptionChanged(phoneId);
-        ts.subscriptionMonitorMock.notifyDefaultSubscriptionChanged(altPhoneId);
-        waitABit();
-        if (ts.dcTrackerMock.getNumberOfLiveRequests() != 0) {
-            fail("test 5, LiveRequests != 0");
-        }
+        waitForMs(250);
+        assertEquals(0, mNetworkRequestList.size());
 
-        makeSubSpecificMmsRequest(ts, subId);
-        waitABit();
-        if (ts.dcTrackerMock.getNumberOfLiveRequests() != 1) {
-            fail("test 6,  LiveRequests != 1");
-        }
+        makeSubSpecificMmsRequest(subId);
+        waitForMs(250);
+        assertEquals(1, mNetworkRequestList.size());
 
-        ts.subscriptionControllerMock.setSlotSubId(phoneId, unusedSubId);
-        ts.subscriptionMonitorMock.notifySubscriptionChanged(phoneId);
-        waitABit();
-        if (ts.dcTrackerMock.getNumberOfLiveRequests() != 0) {
-            fail("test 7,  LiveRequests != 0");
-        }
+        mSubscriptionControllerMock.setSlotSubId(phoneId, unusedSubId);
+        mSubscriptionMonitorMock.notifySubscriptionChanged(phoneId);
+        waitForMs(250);
+        assertEquals(0, mNetworkRequestList.size());
 
-        makeSubSpecificDefaultRequest(ts, subId);
-        waitABit();
-        if (ts.dcTrackerMock.getNumberOfLiveRequests() != 0) {
-            fail("test 8, LiveRequests != 0");
-        }
+        makeSubSpecificDefaultRequest(subId);
+        waitForMs(250);
+        assertEquals(0, mNetworkRequestList.size());
 
-        ts.subscriptionControllerMock.setSlotSubId(phoneId, subId);
-        ts.subscriptionMonitorMock.notifySubscriptionChanged(phoneId);
-        waitABit();
-        if (ts.dcTrackerMock.getNumberOfLiveRequests() != 2) {
-            fail("test 9,  LiveRequests != 2");
-        }
+        mSubscriptionControllerMock.setSlotSubId(phoneId, subId);
+        mSubscriptionMonitorMock.notifySubscriptionChanged(phoneId);
+        waitForMs(250);
+        assertEquals(2, mNetworkRequestList.size());
 
-        ts.subscriptionControllerMock.setDefaultDataSubId(subId);
-        ts.subscriptionMonitorMock.notifyDefaultSubscriptionChanged(phoneId);
-        ts.subscriptionMonitorMock.notifyDefaultSubscriptionChanged(altPhoneId);
-        ts.subscriptionMonitorMock.notifyDefaultSubscriptionChanged(phoneId);
-        waitABit();
-        if (ts.dcTrackerMock.getNumberOfLiveRequests() != 3) {
-            fail("test 10, LiveRequests != 3");
-        }
-        ts.die();
+        mSubscriptionControllerMock.setDefaultDataSubId(subId);
+        mPhoneSwitcherMock.setPreferredDataPhoneId(phoneId);
+        mPhoneSwitcherMock.notifyActivePhoneChange(phoneId);
+        waitForMs(250);
+        assertEquals(3, mNetworkRequestList.size());
     }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyNumberTest.java b/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyNumberTest.java
new file mode 100644
index 0000000..5e4cc44
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyNumberTest.java
@@ -0,0 +1,214 @@
+/*
+ * 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.emergency;
+
+import android.telephony.emergency.EmergencyNumber;
+
+import junit.framework.TestCase;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class EmergencyNumberTest extends TestCase {
+    public void testEmergencyNumberUnspecified() throws Exception {
+        EmergencyNumber number = new EmergencyNumber(
+                "911",
+                "us",
+                EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED,
+                EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING);
+        assertEquals(number.getNumber(), "911");
+        assertEquals(number.getCountryIso(), "us");
+        assertTrue(number.isInEmergencyServiceCategories(
+                EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED));
+        assertTrue(number.isInEmergencyServiceCategories(
+                EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_POLICE));
+        assertTrue(number.isInEmergencyServiceCategories(
+                EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_AMBULANCE));
+        assertTrue(number.isInEmergencyServiceCategories(
+                EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_FIRE_BRIGADE));
+        assertTrue(number.isInEmergencyServiceCategories(
+                EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_MARINE_GUARD));
+        assertTrue(number.isInEmergencyServiceCategories(
+                EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_MOUNTAIN_RESCUE));
+        assertTrue(number.isInEmergencyServiceCategories(
+                EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_MIEC));
+        assertTrue(number.isInEmergencyServiceCategories(
+                EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_AIEC));
+        assertEquals(0, number.getEmergencyServiceCategoryBitmask());
+
+        List<Integer> categories = number.getEmergencyServiceCategories();
+        assertEquals(1, categories.size());
+        assertEquals(EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED,
+                (int) categories.get(0));
+
+        assertTrue(number.isFromSources(EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING));
+        assertFalse(number.isFromSources(EmergencyNumber.EMERGENCY_NUMBER_SOURCE_SIM));
+        assertFalse(number.isFromSources(EmergencyNumber.EMERGENCY_NUMBER_SOURCE_MODEM_CONFIG));
+        assertFalse(number.isFromSources(EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DEFAULT));
+        assertEquals(EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING,
+                number.getEmergencyNumberSourceBitmask());
+
+        List<Integer> sources = number.getEmergencyNumberSources();
+        assertEquals(1, sources.size());
+        assertEquals(EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING,
+                (int) sources.get(0));
+    }
+
+    public void testEmergencyNumberSpecificService() throws Exception {
+        EmergencyNumber number = new EmergencyNumber(
+                "911",
+                "us",
+                EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_MARINE_GUARD,
+                EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING
+                        | EmergencyNumber.EMERGENCY_NUMBER_SOURCE_MODEM_CONFIG);
+        assertEquals(number.getNumber(), "911");
+        assertEquals(number.getCountryIso(), "us");
+        assertFalse(number.isInEmergencyServiceCategories(
+                EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED));
+        assertFalse(number.isInEmergencyServiceCategories(
+                EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_POLICE));
+        assertFalse(number.isInEmergencyServiceCategories(
+                EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_AMBULANCE));
+        assertFalse(number.isInEmergencyServiceCategories(
+                EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_FIRE_BRIGADE));
+        assertTrue(number.isInEmergencyServiceCategories(
+                EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_MARINE_GUARD));
+        assertFalse(number.isInEmergencyServiceCategories(
+                EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_MOUNTAIN_RESCUE));
+        assertFalse(number.isInEmergencyServiceCategories(
+                EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_MIEC));
+        assertFalse(number.isInEmergencyServiceCategories(
+                EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_AIEC));
+        assertEquals(8, number.getEmergencyServiceCategoryBitmask());
+
+        List<Integer> categories = number.getEmergencyServiceCategories();
+        assertEquals(1, categories.size());
+        assertEquals(EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_MARINE_GUARD,
+                (int) categories.get(0));
+
+        assertTrue(number.isFromSources(EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING));
+        assertFalse(number.isFromSources(EmergencyNumber.EMERGENCY_NUMBER_SOURCE_SIM));
+        assertTrue(number.isFromSources(EmergencyNumber.EMERGENCY_NUMBER_SOURCE_MODEM_CONFIG));
+        assertFalse(number.isFromSources(EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DEFAULT));
+        assertEquals(EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING
+                | EmergencyNumber.EMERGENCY_NUMBER_SOURCE_MODEM_CONFIG,
+                number.getEmergencyNumberSourceBitmask());
+
+        List<Integer> sources = number.getEmergencyNumberSources();
+        assertEquals(2, sources.size());
+        Collections.sort(sources);
+        List<Integer> sourcesToVerify = new ArrayList<Integer>();
+        sourcesToVerify.add(EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING);
+        sourcesToVerify.add(EmergencyNumber.EMERGENCY_NUMBER_SOURCE_MODEM_CONFIG);
+        Collections.sort(sourcesToVerify);
+        assertTrue(sourcesToVerify.equals(sources));
+    }
+
+    public void testEmergencyNumberMultipleServices() throws Exception {
+        EmergencyNumber number = new EmergencyNumber(
+                "110",
+                "jp",
+                EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_POLICE
+                        | EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_AMBULANCE
+                        | EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_MIEC,
+                EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING
+                        | EmergencyNumber.EMERGENCY_NUMBER_SOURCE_SIM
+                        | EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DEFAULT);
+        assertEquals(number.getNumber(), "110");
+        assertEquals(number.getCountryIso(), "jp");
+        assertFalse(number.isInEmergencyServiceCategories(
+                EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED));
+        assertTrue(number.isInEmergencyServiceCategories(
+                EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_POLICE));
+        assertTrue(number.isInEmergencyServiceCategories(
+                EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_AMBULANCE));
+        assertFalse(number.isInEmergencyServiceCategories(
+                EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_FIRE_BRIGADE));
+        assertFalse(number.isInEmergencyServiceCategories(
+                EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_MARINE_GUARD));
+        assertFalse(number.isInEmergencyServiceCategories(
+                EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_MOUNTAIN_RESCUE));
+        assertTrue(number.isInEmergencyServiceCategories(
+                EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_MIEC));
+        assertFalse(number.isInEmergencyServiceCategories(
+                EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_AIEC));
+        assertEquals(EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_POLICE
+                | EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_AMBULANCE
+                | EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_MIEC,
+                number.getEmergencyServiceCategoryBitmask());
+
+        List<Integer> categories = number.getEmergencyServiceCategories();
+        assertEquals(3, categories.size());
+        Collections.sort(categories);
+        List<Integer> categoriesToVerify = new ArrayList<Integer>();
+        categoriesToVerify.add(EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_POLICE);
+        categoriesToVerify.add(EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_AMBULANCE);
+        categoriesToVerify.add(EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_MIEC);
+        Collections.sort(categoriesToVerify);
+        assertTrue(categoriesToVerify.equals(categories));
+
+        assertTrue(number.isFromSources(EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING));
+        assertTrue(number.isFromSources(EmergencyNumber.EMERGENCY_NUMBER_SOURCE_SIM));
+        assertFalse(number.isFromSources(EmergencyNumber.EMERGENCY_NUMBER_SOURCE_MODEM_CONFIG));
+        assertTrue(number.isFromSources(EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DEFAULT));
+        assertEquals(EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING
+                | EmergencyNumber.EMERGENCY_NUMBER_SOURCE_SIM
+                | EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DEFAULT,
+                number.getEmergencyNumberSourceBitmask());
+
+        List<Integer> sources = number.getEmergencyNumberSources();
+        assertEquals(3, sources.size());
+        Collections.sort(sources);
+        List<Integer> sourcesToVerify = new ArrayList<Integer>();
+        sourcesToVerify.add(EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING);
+        sourcesToVerify.add(EmergencyNumber.EMERGENCY_NUMBER_SOURCE_SIM);
+        sourcesToVerify.add(EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DEFAULT);
+        Collections.sort(sourcesToVerify);
+        assertTrue(sourcesToVerify.equals(sources));
+    }
+
+    public void testEmergencyNumberDisplayPriority() throws Exception {
+        EmergencyNumber numberHighestDisplayPriority = new EmergencyNumber(
+                "911",
+                "us",
+                EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED,
+                EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING);
+
+        EmergencyNumber numberHigherDisplayPriority = new EmergencyNumber(
+                "922",
+                "us",
+                EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_POLICE
+                        | EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_AMBULANCE
+                        | EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_MIEC,
+                EmergencyNumber.EMERGENCY_NUMBER_SOURCE_SIM);
+
+        EmergencyNumber numberLowestDisplayPriority = new EmergencyNumber(
+                "110",
+                "us",
+                EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_POLICE
+                        | EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_AMBULANCE
+                        | EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_MIEC,
+                EmergencyNumber.EMERGENCY_NUMBER_SOURCE_MODEM_CONFIG
+                        | EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DEFAULT);
+
+        assertTrue(numberHighestDisplayPriority.compareTo(
+                numberHigherDisplayPriority) < 0);
+        assertTrue(numberHigherDisplayPriority.compareTo(
+                numberLowestDisplayPriority) < 0);
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyNumberTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyNumberTrackerTest.java
new file mode 100644
index 0000000..11936f5
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyNumberTrackerTest.java
@@ -0,0 +1,96 @@
+/*
+ * 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.emergency;
+
+import static org.junit.Assert.assertEquals;
+
+import android.os.AsyncResult;
+import android.os.HandlerThread;
+import android.telephony.emergency.EmergencyNumber;
+
+import com.android.internal.telephony.TelephonyTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tests for EmergencyNumberTracker.java
+ */
+public class EmergencyNumberTrackerTest extends TelephonyTest {
+
+    private EmergencyNumberTracker mEmergencyNumberTrackerMock;
+    private List<EmergencyNumber> mEmergencyNumberListTestSample = new ArrayList<>();
+    private static final long TIMEOUT_MS = 500;
+
+    private class EmergencyNumberTrackerTestHandler extends HandlerThread {
+        private EmergencyNumberTrackerTestHandler(String name) {
+            super(name);
+        }
+        @Override
+        public void onLooperPrepared() {
+            mEmergencyNumberTrackerMock = new EmergencyNumberTracker(mPhone, mSimulatedCommands);
+            mEmergencyNumberTrackerMock.DBG = true;
+            setReady(true);
+        }
+    }
+
+    private EmergencyNumberTrackerTestHandler mHandlerThread;
+
+    @Before
+    public void setUp() throws Exception {
+        logd("EmergencyNumberTrackerTest +Setup!");
+        super.setUp("EmergencyNumberTrackerTest");
+        initializeEmergencyNumberListTestSamples();
+        mHandlerThread = new EmergencyNumberTrackerTestHandler("EmergencyNumberTrackerTestHandler");
+        mHandlerThread.start();
+        waitUntilReady();
+        logd("EmergencyNumberTrackerTest -Setup!");
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mHandlerThread.quit();
+        mHandlerThread.join();
+        super.tearDown();
+    }
+
+    private void initializeEmergencyNumberListTestSamples() {
+        EmergencyNumber emergencyNumberForTest = new EmergencyNumber("119", "jp",
+                EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_FIRE_BRIGADE,
+                EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING);
+        mEmergencyNumberListTestSample.add(emergencyNumberForTest);
+    }
+
+    private void sendEmergencyNumberListFromRadio() {
+        mEmergencyNumberTrackerMock.sendMessage(
+                mEmergencyNumberTrackerMock.obtainMessage(
+                        1 /* EVENT_UNSOL_EMERGENCY_NUMBER_LIST */,
+                        new AsyncResult(null, mEmergencyNumberListTestSample, null)));
+        waitForHandlerAction(mEmergencyNumberTrackerMock, TIMEOUT_MS);
+    }
+
+    @Test
+    public void testEmergencyNumberListFromRadio() throws Exception {
+        sendEmergencyNumberListFromRadio();
+        assertEquals(mEmergencyNumberListTestSample,
+                mEmergencyNumberTrackerMock.getRadioEmergencyNumberList());
+    }
+}
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..aafe865 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccControllerTest.java
@@ -837,8 +837,8 @@
     private void setHasCarrierPrivilegesOnActiveSubscription(boolean hasPrivileges)
             throws Exception {
         SubscriptionInfo subInfo = new SubscriptionInfo(
-                0, "", 0, "", "", 0, 0, "", 0, null, 0, 0, "", true /* isEmbedded */,
-                hasPrivileges ? new UiccAccessRule[] { ACCESS_RULE } : null);
+                0, "", 0, "", "", 0, 0, "", 0, null, "0", "0", "", true /* isEmbedded */,
+                hasPrivileges ? new UiccAccessRule[] { ACCESS_RULE } : null, null);
         when(mSubscriptionManager.canManageSubscription(subInfo, PACKAGE_NAME)).thenReturn(
                 hasPrivileges);
         when(mSubscriptionManager.getActiveSubscriptionInfoList()).thenReturn(
@@ -847,8 +847,9 @@
 
     private void prepareOperationSubscription(boolean hasPrivileges) throws Exception {
         SubscriptionInfo subInfo = new SubscriptionInfo(
-                SUBSCRIPTION_ID, ICC_ID, 0, "", "", 0, 0, "", 0, null, 0, 0, "",
-                true /* isEmbedded */, hasPrivileges ? new UiccAccessRule[] { ACCESS_RULE } : null);
+                SUBSCRIPTION_ID, ICC_ID, 0, "", "", 0, 0, "", 0, null, "0", "0", "",
+                true /* isEmbedded */, hasPrivileges ? new UiccAccessRule[] { ACCESS_RULE } : null,
+                null);
         when(mSubscriptionManager.canManageSubscription(subInfo, PACKAGE_NAME)).thenReturn(
                 hasPrivileges);
         when(mSubscriptionManager.getAvailableSubscriptionInfoList()).thenReturn(
diff --git a/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmInboundSmsHandlerTest.java b/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmInboundSmsHandlerTest.java
index 12453ed..1e29908 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmInboundSmsHandlerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmInboundSmsHandlerTest.java
@@ -64,7 +64,6 @@
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
@@ -80,11 +79,13 @@
     private SmsMessage mGsmSmsMessage;
     @Mock
     private SmsHeader mSmsHeader;
-    @Mock
+    private InboundSmsTracker mInboundSmsTracker;
     private InboundSmsTracker mInboundSmsTrackerPart1;
-    @Mock
     private InboundSmsTracker mInboundSmsTrackerPart2;
     @Mock
+    private InboundSmsTracker mMockInboundSmsTracker;
+    private ContentValues mInboundSmsTrackerCV;
+    @Mock
     private CdmaInboundSmsHandler mCdmaInboundSmsHandler;
 
     private GsmInboundSmsHandler mGsmInboundSmsHandler;
@@ -94,11 +95,8 @@
     private static final String RAW_TABLE_NAME = "raw";
     private static final Uri sRawUri = Uri.withAppendedPath(Telephony.Sms.CONTENT_URI,
             RAW_TABLE_NAME);
+    private static final int TEST_TIMEOUT = 5000;
 
-    private ContentValues mInboundSmsTrackerCV = new ContentValues();
-    // For multi-part SMS
-    private ContentValues mInboundSmsTrackerCVPart1;
-    private ContentValues mInboundSmsTrackerCVPart2;
     private String mMessageBody = "This is the message body of a single-part message";
     private String mMessageBodyPart1 = "This is the first part of a multi-part message";
     private String mMessageBodyPart2 = "This is the second part of a multi-part message";
@@ -130,6 +128,38 @@
         }
     }
 
+    /**
+     * This is used only for InboundSmsTracker constructed through Cursor. For other cases
+     * real objects should be used. This should be used only for tests related to
+     * SmsBroadcastUndelivered.
+     */
+    private void createMockInboundSmsTracker() {
+        mInboundSmsTrackerCV = new ContentValues();
+        mInboundSmsTrackerCV.put("destination_port", InboundSmsTracker.DEST_PORT_FLAG_NO_PORT);
+        mInboundSmsTrackerCV.put("pdu", HexDump.toHexString(mSmsPdu));
+        mInboundSmsTrackerCV.put("address", "1234567890");
+        mInboundSmsTrackerCV.put("reference_number", 1);
+        mInboundSmsTrackerCV.put("sequence", 1);
+        mInboundSmsTrackerCV.put("count", 1);
+        mInboundSmsTrackerCV.put("date", System.currentTimeMillis());
+        mInboundSmsTrackerCV.put("message_body", mMessageBody);
+        mInboundSmsTrackerCV.put("display_originating_addr", "1234567890");
+
+        doReturn(1).when(mMockInboundSmsTracker).getMessageCount();
+        doReturn(1).when(mMockInboundSmsTracker).getReferenceNumber();
+        doReturn("1234567890").when(mMockInboundSmsTracker).getAddress();
+        doReturn(1).when(mMockInboundSmsTracker).getSequenceNumber();
+        doReturn(1).when(mMockInboundSmsTracker).getIndexOffset();
+        doReturn(-1).when(mMockInboundSmsTracker).getDestPort();
+        doReturn(mMessageBody).when(mMockInboundSmsTracker).getMessageBody();
+        doReturn(mSmsPdu).when(mMockInboundSmsTracker).getPdu();
+        doReturn(mInboundSmsTrackerCV.get("date")).when(mMockInboundSmsTracker).getTimestamp();
+        doReturn(mInboundSmsTrackerCV).when(mMockInboundSmsTracker).getContentValues();
+
+        doReturn(mMockInboundSmsTracker).when(mTelephonyComponentFactory)
+            .makeInboundSmsTracker(nullable(Cursor.class), anyBoolean());
+    }
+
     @Before
     public void setUp() throws Exception {
         super.setUp("GsmInboundSmsHandlerTest");
@@ -147,26 +177,22 @@
         }
 
         mSmsMessage.mWrappedSmsMessage = mGsmSmsMessage;
-        mInboundSmsTrackerCV.put("destination_port", InboundSmsTracker.DEST_PORT_FLAG_NO_PORT);
-        mInboundSmsTrackerCV.put("pdu", HexDump.toHexString(mSmsPdu));
-        mInboundSmsTrackerCV.put("address", "1234567890");
-        mInboundSmsTrackerCV.put("reference_number", 1);
-        mInboundSmsTrackerCV.put("sequence", 1);
-        mInboundSmsTrackerCV.put("count", 1);
-        mInboundSmsTrackerCV.put("date", System.currentTimeMillis());
-        mInboundSmsTrackerCV.put("message_body", mMessageBody);
-        mInboundSmsTrackerCV.put("display_originating_addr", "1234567890");
 
-        doReturn(1).when(mInboundSmsTracker).getMessageCount();
-        doReturn(1).when(mInboundSmsTracker).getReferenceNumber();
-        doReturn("1234567890").when(mInboundSmsTracker).getAddress();
-        doReturn(1).when(mInboundSmsTracker).getSequenceNumber();
-        doReturn(1).when(mInboundSmsTracker).getIndexOffset();
-        doReturn(-1).when(mInboundSmsTracker).getDestPort();
-        doReturn(mMessageBody).when(mInboundSmsTracker).getMessageBody();
-        doReturn(mSmsPdu).when(mInboundSmsTracker).getPdu();
-        doReturn(mInboundSmsTrackerCV.get("date")).when(mInboundSmsTracker).getTimestamp();
-        doReturn(mInboundSmsTrackerCV).when(mInboundSmsTracker).getContentValues();
+        mInboundSmsTracker = new InboundSmsTracker(
+            mSmsPdu, /* pdu */
+            System.currentTimeMillis(), /* timestamp */
+            -1, /* destPort */
+            false, /* is3gpp2 */
+            false, /* is3gpp2WapPdu */
+            "1234567890", /* address */
+            "1234567890", /* displayAddress */
+            mMessageBody /* messageBody */);
+        doReturn(mInboundSmsTracker).when(mTelephonyComponentFactory)
+                .makeInboundSmsTracker(nullable(byte[].class), anyLong(), anyInt(), anyBoolean(),
+                anyBoolean(), nullable(String.class), nullable(String.class),
+                nullable(String.class));
+
+        createMockInboundSmsTracker();
 
         mContentProvider = new FakeSmsContentProvider();
         ((MockContentResolver)mContext.getContentResolver()).addProvider(
@@ -189,6 +215,7 @@
         mGsmInboundSmsHandler = null;
         mContentProvider.shutdown();
         mGsmInboundSmsHandlerTestHandler.quit();
+        mGsmInboundSmsHandlerTestHandler.join();
         super.tearDown();
     }
 
@@ -198,7 +225,7 @@
 
         // trigger transition to IdleState
         mGsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_START_ACCEPTING_SMS);
-        waitForMs(50);
+        waitForHandlerAction(mGsmInboundSmsHandler.getHandler(), TEST_TIMEOUT);
 
         assertEquals("IdleState", getCurrentState().getName());
     }
@@ -221,29 +248,37 @@
         assertEquals("WaitingState", getCurrentState().getName());
 
         mContextFixture.sendBroadcastToOrderedBroadcastReceivers();
-        waitForMs(50);
-
+        // handle broadcast complete msg
+        waitForHandlerAction(mGsmInboundSmsHandler.getHandler(), TEST_TIMEOUT);
+        // transition from waiting state to delivering state
+        waitForHandlerAction(mGsmInboundSmsHandler.getHandler(), TEST_TIMEOUT);
+        // transition from delivering state to idle state
+        waitForHandlerAction(mGsmInboundSmsHandler.getHandler(), TEST_TIMEOUT);
         assertEquals("IdleState", getCurrentState().getName());
     }
 
+    private void sendNewSms() {
+        mGsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_NEW_SMS,
+                new AsyncResult(null, mSmsMessage, null));
+        // handle EVENT_NEW_SMS
+        waitForHandlerAction(mGsmInboundSmsHandler.getHandler(), TEST_TIMEOUT);
+        // handle EVENT_BROADCAST_SMS
+        waitForHandlerAction(mGsmInboundSmsHandler.getHandler(), TEST_TIMEOUT);
+    }
+
     @FlakyTest
-    @Ignore
     @Test
     @MediumTest
     public void testNewSms() {
         transitionFromStartupToIdle();
 
         // send new SMS to state machine and verify that triggers SMS_DELIVER_ACTION
-        mGsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_NEW_SMS,
-                new AsyncResult(null, mSmsMessage, null));
-        waitForMs(100);
+        sendNewSms();
 
         verifySmsIntentBroadcasts(0);
 
         // send same SMS again, verify no broadcasts are sent
-        mGsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_NEW_SMS,
-                new AsyncResult(null, mSmsMessage, null));
-        waitForMs(100);
+        sendNewSms();
 
         verify(mContext, times(2)).sendBroadcast(any(Intent.class));
         assertEquals("IdleState", getCurrentState().getName());
@@ -253,14 +288,11 @@
     @MediumTest
     public void testNewSmsFromBlockedNumber_noBroadcastsSent() {
         String blockedNumber = "1234567890";
-        doReturn(blockedNumber).when(mInboundSmsTracker).getDisplayAddress();
         mFakeBlockedNumberContentProvider.mBlockedNumbers.add(blockedNumber);
 
         transitionFromStartupToIdle();
 
-        mGsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_NEW_SMS,
-                new AsyncResult(null, mSmsMessage, null));
-        waitForMs(100);
+        sendNewSms();
 
         verify(mContext, never()).sendBroadcast(any(Intent.class));
         assertEquals("IdleState", getCurrentState().getName());
@@ -275,7 +307,12 @@
         assertEquals("WaitingState", getCurrentState().getName());
 
         mContextFixture.sendBroadcastToOrderedBroadcastReceivers();
-        waitForMs(50);
+        // handle broadcast complete msg
+        waitForHandlerAction(mGsmInboundSmsHandler.getHandler(), TEST_TIMEOUT);
+        // transition from waiting state to delivering state
+        waitForHandlerAction(mGsmInboundSmsHandler.getHandler(), TEST_TIMEOUT);
+        // transition from delivering state to idle state
+        waitForHandlerAction(mGsmInboundSmsHandler.getHandler(), TEST_TIMEOUT);
 
         assertEquals("IdleState", getCurrentState().getName());
     }
@@ -285,23 +322,36 @@
     public void testBroadcastSms() {
         transitionFromStartupToIdle();
 
-        doReturn(0).when(mInboundSmsTracker).getDestPort();
+        mInboundSmsTracker = new InboundSmsTracker(
+                mSmsPdu, /* pdu */
+                System.currentTimeMillis(), /* timestamp */
+                0, /* destPort */
+                false, /* is3gpp2 */
+                false, /* is3gpp2WapPdu */
+                "1234567890", /* address */
+                "1234567890", /* displayAddress */
+                mMessageBody /* messageBody */);
+        doReturn(mInboundSmsTracker).when(mTelephonyComponentFactory)
+                .makeInboundSmsTracker(nullable(byte[].class), anyLong(), anyInt(), anyBoolean(),
+                anyBoolean(), nullable(String.class), nullable(String.class),
+                nullable(String.class));
         mGsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_BROADCAST_SMS,
                 mInboundSmsTracker);
-        waitForMs(100);
+        waitForHandlerAction(mGsmInboundSmsHandler.getHandler(), TEST_TIMEOUT);
+        waitForHandlerAction(mGsmInboundSmsHandler.getHandler(), TEST_TIMEOUT);
 
         verifyDataSmsIntentBroadcasts(0);
 
         // send same data sms again, and since it's not text sms it should be broadcast again
         mGsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_BROADCAST_SMS,
                 mInboundSmsTracker);
-        waitForMs(100);
+        waitForHandlerAction(mGsmInboundSmsHandler.getHandler(), TEST_TIMEOUT);
+        waitForHandlerAction(mGsmInboundSmsHandler.getHandler(), TEST_TIMEOUT);
 
         verifyDataSmsIntentBroadcasts(1);
     }
 
     @FlakyTest
-    @Ignore
     @Test
     @MediumTest
     public void testInjectSms() {
@@ -309,121 +359,49 @@
 
         mGsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_INJECT_SMS, new AsyncResult(null,
                 mSmsMessage, null));
-        waitForMs(200);
+        waitForHandlerAction(mGsmInboundSmsHandler.getHandler(), TEST_TIMEOUT);
+        waitForHandlerAction(mGsmInboundSmsHandler.getHandler(), TEST_TIMEOUT);
 
         verifySmsIntentBroadcasts(0);
 
         // inject same SMS again, verify no broadcasts are sent
         mGsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_INJECT_SMS, new AsyncResult(null,
                 mSmsMessage, null));
-        waitForMs(100);
+        waitForHandlerAction(mGsmInboundSmsHandler.getHandler(), TEST_TIMEOUT);
+        waitForHandlerAction(mGsmInboundSmsHandler.getHandler(), TEST_TIMEOUT);
 
         verify(mContext, times(2)).sendBroadcast(any(Intent.class));
         assertEquals("IdleState", getCurrentState().getName());
     }
 
-    private void prepareMultiPartSms(boolean isWapPush) {
+    private void prepareMultiPartSms(boolean is3gpp2WapPush) {
         // Part 1
-        mInboundSmsTrackerCVPart1 = new ContentValues();
-        mInboundSmsTrackerCVPart1.put("pdu", HexDump.toHexString(mSmsPdu));
-        mInboundSmsTrackerCVPart1.put("address", "1234567890");
-        mInboundSmsTrackerCVPart1.put("reference_number", 1);
-        mInboundSmsTrackerCVPart1.put("sequence", 1);
-        mInboundSmsTrackerCVPart1.put("count", 2);
-        mInboundSmsTrackerCVPart1.put("date", System.currentTimeMillis());
-        mInboundSmsTrackerCVPart1.put("message_body", mMessageBodyPart1);
-        mInboundSmsTrackerCVPart1.put("display_originating_addr", "1234567890");
-
-        doReturn(2).when(mInboundSmsTrackerPart1).getMessageCount();
-        doReturn(1).when(mInboundSmsTrackerPart1).getReferenceNumber();
-        doReturn("1234567890").when(mInboundSmsTrackerPart1).getAddress();
-        doReturn(1).when(mInboundSmsTrackerPart1).getSequenceNumber();
-        doReturn(1).when(mInboundSmsTrackerPart1).getIndexOffset();
-        doReturn(-1).when(mInboundSmsTrackerPart1).getDestPort();
-        doReturn(mMessageBodyPart1).when(mInboundSmsTrackerPart1).getMessageBody();
-        doReturn(mSmsPdu).when(mInboundSmsTrackerPart1).getPdu();
-        doReturn(new String[]{mInboundSmsTrackerPart1.getAddress(),
-                Integer.toString(mInboundSmsTrackerPart1.getReferenceNumber()),
-                Integer.toString(mInboundSmsTrackerPart1.getMessageCount())})
-                .when(mInboundSmsTrackerPart1).getDeleteWhereArgs();
-        doReturn(mInboundSmsTrackerCVPart1.get("date")).when(mInboundSmsTrackerPart1).
-                getTimestamp();
-        doReturn(mInboundSmsTrackerCVPart1).when(mInboundSmsTrackerPart1).getContentValues();
-        if (isWapPush) {
-            mInboundSmsTrackerCVPart1.put("destination_port",
-                    (InboundSmsTracker.DEST_PORT_FLAG_3GPP2 |
-                            InboundSmsTracker.DEST_PORT_FLAG_3GPP2_WAP_PDU |
-                            SmsHeader.PORT_WAP_PUSH));
-            doReturn(InboundSmsTracker.SELECT_BY_REFERENCE_3GPP2WAP).when(mInboundSmsTrackerPart1)
-                    .getQueryForSegments();
-            doReturn(InboundSmsTracker.SELECT_BY_DUPLICATE_REFERENCE_3GPP2WAP)
-                    .when(mInboundSmsTrackerPart1).getQueryForMultiPartDuplicates();
-            doReturn(InboundSmsTracker.SELECT_BY_REFERENCE_3GPP2WAP).when(mInboundSmsTrackerPart1)
-                    .getDeleteWhere();
-            doReturn(SmsHeader.PORT_WAP_PUSH).when(mInboundSmsTrackerPart1).getDestPort();
-            doReturn(true).when(mInboundSmsTrackerPart1).is3gpp2();
-
-        } else {
-            mInboundSmsTrackerCVPart1.put("destination_port",
-                    InboundSmsTracker.DEST_PORT_FLAG_NO_PORT);
-            doReturn(InboundSmsTracker.SELECT_BY_REFERENCE).when(mInboundSmsTrackerPart1)
-                    .getQueryForSegments();
-            doReturn(InboundSmsTracker.SELECT_BY_DUPLICATE_REFERENCE)
-                    .when(mInboundSmsTrackerPart1).getQueryForMultiPartDuplicates();
-            doReturn(InboundSmsTracker.SELECT_BY_REFERENCE).when(mInboundSmsTrackerPart1)
-                    .getDeleteWhere();
-        }
+        mInboundSmsTrackerPart1 = new InboundSmsTracker(
+            mSmsPdu, /* pdu */
+            System.currentTimeMillis(), /* timestamp */
+            -1, /* destPort */
+            is3gpp2WapPush, /* is3gpp2 */
+            "1234567890", /* address */
+            "1234567890", /* displayAddress */
+            1, /* referenceNumber */
+            1, /* sequenceNumber */
+            2, /* messageCount */
+            is3gpp2WapPush, /* is3gpp2WapPdu */
+            mMessageBodyPart1 /* messageBody */);
 
         // Part 2
-        mInboundSmsTrackerCVPart2 = new ContentValues();
-        mInboundSmsTrackerCVPart2.put("pdu", HexDump.toHexString(mSmsPdu));
-        mInboundSmsTrackerCVPart2.put("address", "1234567890");
-        mInboundSmsTrackerCVPart2.put("reference_number", 1);
-        mInboundSmsTrackerCVPart2.put("sequence", 2);
-        mInboundSmsTrackerCVPart2.put("count", 2);
-        mInboundSmsTrackerCVPart2.put("date", System.currentTimeMillis());
-        mInboundSmsTrackerCVPart2.put("message_body", mMessageBodyPart2);
-        mInboundSmsTrackerCVPart2.put("display_originating_addr", "1234567890");
-
-        doReturn(2).when(mInboundSmsTrackerPart2).getMessageCount();
-        doReturn(1).when(mInboundSmsTrackerPart2).getReferenceNumber();
-        doReturn("1234567890").when(mInboundSmsTrackerPart2).getAddress();
-        doReturn(2).when(mInboundSmsTrackerPart2).getSequenceNumber();
-        doReturn(1).when(mInboundSmsTrackerPart2).getIndexOffset();
-        doReturn(-1).when(mInboundSmsTrackerPart2).getDestPort();
-        doReturn(mMessageBodyPart2).when(mInboundSmsTrackerPart2).getMessageBody();
-        doReturn(mSmsPdu).when(mInboundSmsTrackerPart2).getPdu();
-        doReturn(new String[]{mInboundSmsTrackerPart2.getAddress(),
-                Integer.toString(mInboundSmsTrackerPart2.getReferenceNumber()),
-                Integer.toString(mInboundSmsTrackerPart2.getMessageCount())})
-                .when(mInboundSmsTrackerPart2).getDeleteWhereArgs();
-        doReturn(mInboundSmsTrackerCVPart2.get("date")).when(mInboundSmsTrackerPart2).
-                getTimestamp();
-        doReturn(mInboundSmsTrackerCVPart2).when(mInboundSmsTrackerPart2).getContentValues();
-        if (isWapPush) {
-            mInboundSmsTrackerCVPart2.put("destination_port",
-                    (InboundSmsTracker.DEST_PORT_FLAG_3GPP2 |
-                            InboundSmsTracker.DEST_PORT_FLAG_3GPP2_WAP_PDU |
-                            SmsHeader.PORT_WAP_PUSH));
-            doReturn(InboundSmsTracker.SELECT_BY_REFERENCE_3GPP2WAP).when(mInboundSmsTrackerPart2)
-                    .getQueryForSegments();
-            doReturn(InboundSmsTracker.SELECT_BY_DUPLICATE_REFERENCE_3GPP2WAP)
-                    .when(mInboundSmsTrackerPart2).getQueryForMultiPartDuplicates();
-            doReturn(InboundSmsTracker.SELECT_BY_REFERENCE_3GPP2WAP).when(mInboundSmsTrackerPart2)
-                    .getDeleteWhere();
-            doReturn(SmsHeader.PORT_WAP_PUSH).when(mInboundSmsTrackerPart2).getDestPort();
-            doReturn(true).when(mInboundSmsTrackerPart2).is3gpp2();
-
-        } else {
-            mInboundSmsTrackerCVPart2.put("destination_port",
-                    InboundSmsTracker.DEST_PORT_FLAG_NO_PORT);
-            doReturn(InboundSmsTracker.SELECT_BY_REFERENCE).when(mInboundSmsTrackerPart2)
-                    .getQueryForSegments();
-            doReturn(InboundSmsTracker.SELECT_BY_DUPLICATE_REFERENCE)
-                    .when(mInboundSmsTrackerPart2).getQueryForMultiPartDuplicates();
-            doReturn(InboundSmsTracker.SELECT_BY_REFERENCE).when(mInboundSmsTrackerPart2)
-                    .getDeleteWhere();
-        }
+        mInboundSmsTrackerPart2 = new InboundSmsTracker(
+            mSmsPdu, /* pdu */
+            System.currentTimeMillis(), /* timestamp */
+            -1, /* destPort */
+            is3gpp2WapPush, /* is3gpp2 */
+            "1234567890", /* address */
+            "1234567890", /* displayAddress */
+            1, /* referenceNumber */
+            2, /* sequenceNumber */
+            2, /* messageCount */
+            is3gpp2WapPush, /* is3gpp2WapPdu */
+            mMessageBodyPart2 /* messageBody */);
     }
 
     @Test
@@ -447,9 +425,7 @@
                 .makeInboundSmsTracker(nullable(byte[].class), anyLong(), anyInt(), anyBoolean(),
                         nullable(String.class), nullable(String.class), anyInt(), anyInt(),
                         anyInt(), anyBoolean(), nullable(String.class));
-        mGsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_NEW_SMS, new AsyncResult(null,
-                mSmsMessage, null));
-        waitForMs(200);
+        sendNewSms();
 
         // State machine should go back to idle and wait for second part
         assertEquals("IdleState", getCurrentState().getName());
@@ -460,9 +436,8 @@
                 .makeInboundSmsTracker(nullable(byte[].class), anyLong(), anyInt(), anyBoolean(),
                         nullable(String.class), nullable(String.class), anyInt(), anyInt(),
                         anyInt(), anyBoolean(), nullable(String.class));
-        mGsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_NEW_SMS, new AsyncResult(null,
-                mSmsMessage, null));
-        waitForMs(200);
+        sendNewSms();
+
         // State machine should go back to idle and wait for second part
         assertEquals("IdleState", getCurrentState().getName());
 
@@ -475,9 +450,7 @@
                 .makeInboundSmsTracker(nullable(byte[].class), anyLong(), anyInt(), anyBoolean(),
                         nullable(String.class), nullable(String.class), anyInt(), anyInt(),
                         anyInt(), anyBoolean(), nullable(String.class));
-        mGsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_NEW_SMS, new AsyncResult(null,
-                mSmsMessage, null));
-        waitForMs(200);
+        sendNewSms();
 
         // verify broadcast intents
         verifySmsIntentBroadcasts(0);
@@ -488,7 +461,6 @@
     }
 
     @FlakyTest
-    @Ignore
     @Test
     @MediumTest
     public void testMultiPartSms() {
@@ -504,9 +476,7 @@
                 .makeInboundSmsTracker(nullable(byte[].class), anyLong(), anyInt(), anyBoolean(),
                         nullable(String.class), nullable(String.class), anyInt(), anyInt(),
                         anyInt(), anyBoolean(), nullable(String.class));
-        mGsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_NEW_SMS, new AsyncResult(null,
-                mSmsMessage, null));
-        waitForMs(100);
+        sendNewSms();
 
         // State machine should go back to idle and wait for second part
         assertEquals("IdleState", getCurrentState().getName());
@@ -515,9 +485,7 @@
                 .makeInboundSmsTracker(nullable(byte[].class), anyLong(), anyInt(), anyBoolean(),
                         nullable(String.class), nullable(String.class), anyInt(), anyInt(),
                         anyInt(), anyBoolean(), nullable(String.class));
-        mGsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_NEW_SMS, new AsyncResult(null,
-                mSmsMessage, null));
-        waitForMs(100);
+        sendNewSms();
 
         // verify broadcast intents
         verifySmsIntentBroadcasts(0);
@@ -531,9 +499,7 @@
                 .makeInboundSmsTracker(nullable(byte[].class), anyLong(), anyInt(), anyBoolean(),
                         nullable(String.class), nullable(String.class), anyInt(), anyInt(),
                         anyInt(), anyBoolean(), nullable(String.class));
-        mGsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_NEW_SMS, new AsyncResult(null,
-                mSmsMessage, null));
-        waitForMs(100);
+        sendNewSms();
 
         // verify no additional broadcasts sent
         verify(mContext, times(2)).sendBroadcast(any(Intent.class));
@@ -549,9 +515,7 @@
                 .makeInboundSmsTracker(nullable(byte[].class), anyLong(), anyInt(), anyBoolean(),
                         nullable(String.class), nullable(String.class), anyInt(), anyInt(),
                         anyInt(), anyBoolean(), nullable(String.class));
-        mGsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_NEW_SMS, new AsyncResult(null,
-                mSmsMessage, null));
-        waitForMs(100);
+        sendNewSms();
 
         // verify no additional broadcasts sent
         verify(mContext, times(2)).sendBroadcast(any(Intent.class));
@@ -571,8 +535,18 @@
         // prepare SMS part 1 and part 2
         prepareMultiPartSms(false);
         // change seqNumber in part 2 to 1
-        mInboundSmsTrackerCVPart2.put("sequence", 1);
-        doReturn(1).when(mInboundSmsTrackerPart2).getSequenceNumber();
+        mInboundSmsTrackerPart2 = new InboundSmsTracker(
+            mSmsPdu, /* pdu */
+            System.currentTimeMillis(), /* timestamp */
+            -1, /* destPort */
+            false, /* is3gpp2 */
+            "1234567890", /* address */
+            "1234567890", /* displayAddress */
+            1, /* referenceNumber */
+            1, /* sequenceNumber */
+            2, /* messageCount */
+            false, /* is3gpp2WapPdu */
+            mMessageBodyPart2 /* messageBody */);
 
         mSmsHeader.concatRef = new SmsHeader.ConcatRef();
         doReturn(mSmsHeader).when(mGsmSmsMessage).getUserDataHeader();
@@ -581,9 +555,7 @@
                 .makeInboundSmsTracker(nullable(byte[].class), anyLong(), anyInt(), anyBoolean(),
                         nullable(String.class), nullable(String.class), anyInt(), anyInt(),
                         anyInt(), anyBoolean(), nullable(String.class));
-        mGsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_NEW_SMS, new AsyncResult(null,
-                mSmsMessage, null));
-        waitForMs(100);
+        sendNewSms();
 
         // State machine should go back to idle and wait for second part
         assertEquals("IdleState", getCurrentState().getName());
@@ -592,14 +564,16 @@
                 .makeInboundSmsTracker(nullable(byte[].class), anyLong(), anyInt(), anyBoolean(),
                         nullable(String.class), nullable(String.class), anyInt(), anyInt(),
                         anyInt(), anyBoolean(), nullable(String.class));
-        mGsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_NEW_SMS, new AsyncResult(null,
-                mSmsMessage, null));
-        waitForMs(100);
+        sendNewSms();
 
         // verify no broadcasts sent
         verify(mContext, never()).sendBroadcast(any(Intent.class));
         // verify there's only 1 of the segments in the db (other should be discarded as dup)
         assertEquals(1, mContentProvider.getNumRows());
+        // verify the first one is discarded, and second message is present in the db
+        Cursor c = mContentProvider.query(sRawUri, null, null, null, null);
+        c.moveToFirst();
+        assertEquals(mMessageBodyPart2, c.getString(c.getColumnIndex("message_body")));
         // State machine should go back to idle
         assertEquals("IdleState", getCurrentState().getName());
     }
@@ -619,9 +593,7 @@
                 .makeInboundSmsTracker(nullable(byte[].class), anyLong(), anyInt(), anyBoolean(),
                         nullable(String.class), nullable(String.class), anyInt(), anyInt(),
                         anyInt(), anyBoolean(), nullable(String.class));
-        mGsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_NEW_SMS, new AsyncResult(null,
-                mSmsMessage, null));
-        waitForMs(100);
+        sendNewSms();
 
         // verify the message is stored in the raw table
         assertEquals(1, mContentProvider.getNumRows());
@@ -631,16 +603,24 @@
 
         // change seqNumber in part 2 to an invalid value
         int invalidSeqNumber = -1;
-        mInboundSmsTrackerCVPart2.put("sequence", invalidSeqNumber);
-        doReturn(invalidSeqNumber).when(mInboundSmsTrackerPart2).getSequenceNumber();
+        mInboundSmsTrackerPart2 = new InboundSmsTracker(
+            mSmsPdu, /* pdu */
+            System.currentTimeMillis(), /* timestamp */
+            -1, /* destPort */
+            false, /* is3gpp2 */
+            "1234567890", /* address */
+            "1234567890", /* displayAddress */
+            1, /* referenceNumber */
+            invalidSeqNumber, /* sequenceNumber */
+            2, /* messageCount */
+            false, /* is3gpp2WapPdu */
+            mMessageBodyPart2 /* messageBody */);
 
         doReturn(mInboundSmsTrackerPart2).when(mTelephonyComponentFactory)
                 .makeInboundSmsTracker(nullable(byte[].class), anyLong(), anyInt(), anyBoolean(),
                         nullable(String.class), nullable(String.class), anyInt(), anyInt(),
                         anyInt(), anyBoolean(), nullable(String.class));
-        mGsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_NEW_SMS, new AsyncResult(null,
-                mSmsMessage, null));
-        waitForMs(100);
+        sendNewSms();
 
         // verify no broadcasts sent
         verify(mContext, never()).sendBroadcast(any(Intent.class));
@@ -665,9 +645,7 @@
                         nullable(String.class), nullable(String.class), anyInt(), anyInt(),
                         anyInt(), anyBoolean(), nullable(String.class));
 
-        mGsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_NEW_SMS, new AsyncResult(null,
-                mSmsMessage, null));
-        waitForMs(100);
+        sendNewSms();
 
         // State machine should go back to idle and wait for second part
         assertEquals("IdleState", getCurrentState().getName());
@@ -676,9 +654,7 @@
                 .makeInboundSmsTracker(nullable(byte[].class), anyLong(), anyInt(), anyBoolean(),
                         nullable(String.class), nullable(String.class), anyInt(), anyInt(),
                         anyInt(), anyBoolean(), nullable(String.class));
-        mGsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_NEW_SMS, new AsyncResult(null,
-                mSmsMessage, null));
-        waitForMs(100);
+        sendNewSms();
 
         verify(mContext, never()).sendBroadcast(any(Intent.class));
         assertEquals("IdleState", getCurrentState().getName());
@@ -694,7 +670,18 @@
         // prepare SMS part 1 and part 2
         prepareMultiPartSms(false);
         // only the first SMS is configured with the display originating email address
-        mInboundSmsTrackerCVPart1.put("display_originating_addr", "1234567890@test.com");
+        mInboundSmsTrackerPart1 = new InboundSmsTracker(
+            mSmsPdu, /* pdu */
+            System.currentTimeMillis(), /* timestamp */
+            -1, /* destPort */
+            false, /* is3gpp2 */
+            "1234567890", /* address */
+            "1234567890@test.com", /* displayAddress */
+            1, /* referenceNumber */
+            1, /* sequenceNumber */
+            2, /* messageCount */
+            false, /* is3gpp2WapPdu */
+            mMessageBodyPart1 /* messageBody */);
 
         mSmsHeader.concatRef = new SmsHeader.ConcatRef();
         doReturn(mSmsHeader).when(mGsmSmsMessage).getUserDataHeader();
@@ -703,9 +690,7 @@
                         nullable(String.class), nullable(String.class), anyInt(), anyInt(),
                         anyInt(), anyBoolean(), nullable(String.class));
 
-        mGsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_NEW_SMS, new AsyncResult(null,
-                mSmsMessage, null));
-        waitForMs(100);
+        sendNewSms();
 
         // State machine should go back to idle and wait for second part
         assertEquals("IdleState", getCurrentState().getName());
@@ -714,9 +699,7 @@
                 .makeInboundSmsTracker(nullable(byte[].class), anyLong(), anyInt(), anyBoolean(),
                         nullable(String.class), nullable(String.class), anyInt(), anyInt(),
                         anyInt(), anyBoolean(), nullable(String.class));
-        mGsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_NEW_SMS, new AsyncResult(null,
-                mSmsMessage, null));
-        waitForMs(100);
+        sendNewSms();
 
         verify(mContext, never()).sendBroadcast(any(Intent.class));
         assertEquals("IdleState", getCurrentState().getName());
@@ -726,13 +709,10 @@
     @MediumTest
     public void testBroadcastUndeliveredUserLocked() throws Exception {
         replaceInstance(SmsBroadcastUndelivered.class, "instance", null, null);
-        doReturn(0).when(mInboundSmsTracker).getDestPort();
+        doReturn(0).when(mMockInboundSmsTracker).getDestPort();
 
         // add a fake entry to db
-        mContentProvider.insert(sRawUri, mInboundSmsTrackerCV);
-
-        // make it a single-part message
-        doReturn(1).when(mInboundSmsTracker).getMessageCount();
+        mContentProvider.insert(sRawUri, mMockInboundSmsTracker.getContentValues());
 
         // user locked
         UserManager userManager = (UserManager)mContext.getSystemService(Context.USER_SERVICE);
@@ -745,6 +725,7 @@
         verify(mContext).registerReceiverAsUser(any(BroadcastReceiver.class), eq((UserHandle)null),
                 any(IntentFilter.class), eq((String)null), eq((Handler)null));
 
+        // wait for ScanRawTableThread
         waitForMs(100);
 
         // verify no broadcasts sent because due to !isUserUnlocked
@@ -753,6 +734,7 @@
         // when user unlocks the device, the message in db should be broadcast
         doReturn(true).when(userManager).isUserUnlocked();
         mContext.sendBroadcast(new Intent(Intent.ACTION_USER_UNLOCKED));
+        // wait for ScanRawTableThread
         waitForMs(100);
 
         verifyDataSmsIntentBroadcasts(1);
@@ -762,15 +744,14 @@
     @MediumTest
     public void testBroadcastUndeliveredUserUnlocked() throws Exception {
         replaceInstance(SmsBroadcastUndelivered.class, "instance", null, null);
-        doReturn(0).when(mInboundSmsTracker).getDestPort();
+        doReturn(0).when(mMockInboundSmsTracker).getDestPort();
 
         // add a fake entry to db
-        mContentProvider.insert(sRawUri, mInboundSmsTrackerCV);
-
-        // make it a single-part message
-        doReturn(1).when(mInboundSmsTracker).getMessageCount();
+        mContentProvider.insert(sRawUri, mMockInboundSmsTracker.getContentValues());
 
         SmsBroadcastUndelivered.initialize(mContext, mGsmInboundSmsHandler, mCdmaInboundSmsHandler);
+
+        // wait for ScanRawTableThread
         waitForMs(100);
 
         // user is unlocked; intent should be broadcast right away
@@ -782,18 +763,28 @@
     public void testBroadcastUndeliveredDeleted() throws Exception {
         replaceInstance(SmsBroadcastUndelivered.class, "instance", null, null);
         SmsBroadcastUndelivered.initialize(mContext, mGsmInboundSmsHandler, mCdmaInboundSmsHandler);
-        doReturn(0).when(mInboundSmsTracker).getDestPort();
+        mInboundSmsTracker = new InboundSmsTracker(
+            mSmsPdu, /* pdu */
+            System.currentTimeMillis(), /* timestamp */
+            0, /* destPort */
+            false, /* is3gpp2 */
+            false, /* is3gpp2WapPdu */
+            "1234567890", /* address */
+            "1234567890", /* displayAddress */
+            mMessageBody /* messageBody */);
+        doReturn(mInboundSmsTracker).when(mTelephonyComponentFactory)
+                .makeInboundSmsTracker(nullable(byte[].class), anyLong(), anyInt(), anyBoolean(),
+                anyBoolean(), nullable(String.class), nullable(String.class),
+                nullable(String.class));
 
         //add a fake entry to db
         ContentValues rawSms = new ContentValues();
         rawSms.put("deleted", 1);
         mContentProvider.insert(sRawUri, rawSms);
 
-        //make it a single-part message
-        doReturn(1).when(mInboundSmsTracker).getMessageCount();
-
         //when user unlocks the device, broadcast should not be sent for new message
         mContext.sendBroadcast(new Intent(Intent.ACTION_USER_UNLOCKED));
+        // wait for ScanRawTableThread
         waitForMs(100);
 
         verify(mContext, times(1)).sendBroadcast(any(Intent.class));
@@ -802,7 +793,6 @@
     }
 
     @FlakyTest
-    @Ignore
     @Test
     @MediumTest
     public void testBroadcastUndeliveredMultiPart() throws Exception {
@@ -812,8 +802,8 @@
         prepareMultiPartSms(false);
 
         //add the 2 SMS parts to db
-        mContentProvider.insert(sRawUri, mInboundSmsTrackerCVPart1);
-        mContentProvider.insert(sRawUri, mInboundSmsTrackerCVPart2);
+        mContentProvider.insert(sRawUri, mInboundSmsTrackerPart1.getContentValues());
+        mContentProvider.insert(sRawUri, mInboundSmsTrackerPart2.getContentValues());
 
         //return InboundSmsTracker objects corresponding to the 2 parts
         doReturn(mInboundSmsTrackerPart1).doReturn(mInboundSmsTrackerPart2).
@@ -821,7 +811,8 @@
                 anyBoolean());
 
         SmsBroadcastUndelivered.initialize(mContext, mGsmInboundSmsHandler, mCdmaInboundSmsHandler);
-        waitForMs(100);
+        // wait for ScanRawTableThread
+        waitForMs(200);
 
         verifySmsIntentBroadcasts(0);
     }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmSmsDispatcherTest.java b/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmSmsDispatcherTest.java
index 57f8df9..a24bdfc 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmSmsDispatcherTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmSmsDispatcherTest.java
@@ -65,6 +65,7 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 
+import java.util.ArrayList;
 import java.util.HashMap;
 
 public class GsmSmsDispatcherTest extends TelephonyTest {
@@ -86,7 +87,7 @@
     @Mock
     private ISub.Stub mISubStub;
     private Object mLock = new Object();
-    private boolean mReceivedTestIntent = false;
+    private boolean mReceivedTestIntent;
     private static final String TEST_INTENT = "com.android.internal.telephony.TEST_INTENT";
     private BroadcastReceiver mTestReceiver = new BroadcastReceiver() {
         @Override
@@ -215,6 +216,7 @@
         PendingIntent pendingIntent = PendingIntent.getBroadcast(realContext, 0,
                 new Intent(TEST_INTENT), 0);
         // send invalid dest address: +
+        mReceivedTestIntent = false;
         mGsmSmsDispatcher.sendText("+", "222" /*scAddr*/, TAG,
                 pendingIntent, null, null, null, false, -1, false, -1);
         waitForMs(500);
@@ -257,4 +259,39 @@
         verify(mSmsTracker, times(1)).onFailed(any(), argumentCaptor.capture(), anyInt());
         assertEquals(RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED, (int) argumentCaptor.getValue());
     }
+
+    @Test @SmallTest
+    public void testSendMultipartTextWithInvalidText() throws Exception {
+        // unmock ActivityManager to be able to register receiver, create real PendingIntent and
+        // receive TEST_INTENT
+        restoreInstance(Singleton.class, "mInstance", mIActivityManagerSingleton);
+        restoreInstance(ActivityManager.class, "IActivityManagerSingleton", null);
+
+        Context realContext = TestApplication.getAppContext();
+        realContext.registerReceiver(mTestReceiver, new IntentFilter(TEST_INTENT));
+
+        // initiate parameters for an invalid text MO SMS (the 2nd segmeant has 161 characters)
+        ArrayList<String> parts = new ArrayList<>();
+        parts.add("valid segment1");
+        parts.add("too long segment2 12345678912345678912345678912345678912345678912345678912345678"
+                + "91234567891234567891234567891234567891234567891234567891234567891234567891234567"
+                + "8");
+
+        ArrayList<PendingIntent> sentIntents = new ArrayList<>();
+        PendingIntent sentIntent = PendingIntent.getBroadcast(realContext, 0,
+                new Intent(TEST_INTENT), 0);
+        sentIntents.add(sentIntent);
+        sentIntents.add(sentIntent);
+
+        // send SMS and check sentIntent
+        mReceivedTestIntent = false;
+        mGsmSmsDispatcher.sendMultipartText("+123" /*destAddr*/, "222" /*scAddr*/, parts,
+                sentIntents, null, null, null, false, -1, false, -1);
+
+        waitForMs(500);
+        synchronized (mLock) {
+            assertEquals(true, mReceivedTestIntent);
+            assertEquals(SmsManager.RESULT_ERROR_GENERIC_FAILURE, mTestReceiver.getResultCode());
+        }
+    }
 }
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 3db8adc..e923dfa 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
@@ -496,10 +496,6 @@
     }
 
     @Override
-    public void getNeighboringCids(Message response) {
-    }
-
-    @Override
     public void setLocationUpdates(boolean enable, 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 2bf0094..fbb579e 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ims/ImsManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsManagerTest.java
@@ -73,7 +73,7 @@
 
     @Before
     public void setUp() throws Exception {
-        super.setUp("SubscriptionControllerTest");
+        super.setUp("ImsManagerTest");
         mPhoneId = mPhone.getPhoneId();
         mBundle = mContextFixture.getCarrierConfigBundle();
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ims/MmTelFeatureConnectionTest.java b/tests/telephonytests/src/com/android/internal/telephony/ims/MmTelFeatureConnectionTest.java
new file mode 100644
index 0000000..4bb92c9
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/ims/MmTelFeatureConnectionTest.java
@@ -0,0 +1,295 @@
+/*
+ * 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 static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.IInterface;
+import android.os.Looper;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.ims.MmTelFeatureConnection;
+import com.android.internal.telephony.TelephonyTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class MmTelFeatureConnectionTest extends TelephonyTest {
+
+    private class TestCallback extends Binder implements IInterface {
+
+        @Override
+        public IBinder asBinder() {
+            return this;
+        }
+    }
+
+    private class CallbackManagerTest extends
+            MmTelFeatureConnection.CallbackAdapterManager<TestCallback> {
+
+        List<TestCallback> mCallbacks = new ArrayList<>();
+
+        CallbackManagerTest(Context context, Object lock) {
+            super(context, lock);
+        }
+
+        // A callback has been registered. Register that callback with the MmTelFeature.
+        @Override
+        public boolean registerCallback(TestCallback localCallback) {
+            return mCallbacks.add(localCallback);
+        }
+
+        // A callback has been removed, unregister that callback with the MmTelFeature.
+        @Override
+        public void unregisterCallback(TestCallback localCallback) {
+            mCallbacks.remove(localCallback);
+        }
+
+        public boolean doesCallbackExist(TestCallback callback) {
+            return mCallbacks.contains(callback);
+        }
+    }
+    private CallbackManagerTest mCallbackManagerUT;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp("MmTelFeatureConnectionTest");
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
+        }
+        mCallbackManagerUT = new CallbackManagerTest(mContext, this);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mCallbackManagerUT = null;
+        super.tearDown();
+    }
+
+    /**
+     * Basic test of deprecated functionality, ensure that adding the callback directly triggers the
+     * appropriate registerCallback and unregisterCallback calls.
+     */
+    @Test
+    @SmallTest
+    public void testCallbackAdapter_addAndRemoveCallback() throws Exception {
+        TestCallback testCallback = new TestCallback();
+        mCallbackManagerUT.addCallback(testCallback);
+        assertTrue(mCallbackManagerUT.doesCallbackExist(testCallback));
+        // The subscriptions changed listener should only be added for callbacks that are being
+        // linked to a subscription.
+        verify(mSubscriptionManager, never()).addOnSubscriptionsChangedListener(
+                any(SubscriptionManager.OnSubscriptionsChangedListener.class));
+
+        mCallbackManagerUT.removeCallback(testCallback);
+        assertFalse(mCallbackManagerUT.doesCallbackExist(testCallback));
+        // The subscriptions changed listener should only be removed for callbacks that are
+        // linked to a subscription.
+        verify(mSubscriptionManager, never()).removeOnSubscriptionsChangedListener(
+                any(SubscriptionManager.OnSubscriptionsChangedListener.class));
+    }
+
+    /**
+     * Ensure that adding the callback and linking subId triggers the appropriate registerCallback
+     * and unregisterCallback calls as well as the subscriptionChanged listener.
+     */
+    @Test
+    @SmallTest
+    public void testCallbackAdapter_addAndRemoveCallbackForSub() throws Exception {
+        TestCallback testCallback = new TestCallback();
+        int testSub = 1;
+        mCallbackManagerUT.addCallbackForSubscription(testCallback, testSub);
+        assertTrue(mCallbackManagerUT.doesCallbackExist(testCallback));
+        verify(mSubscriptionManager, times(1)).addOnSubscriptionsChangedListener(
+                any(SubscriptionManager.OnSubscriptionsChangedListener.class));
+
+        mCallbackManagerUT.removeCallbackForSubscription(testCallback, testSub);
+        assertFalse(mCallbackManagerUT.doesCallbackExist(testCallback));
+        verify(mSubscriptionManager, times(1)).removeOnSubscriptionsChangedListener(
+                any(SubscriptionManager.OnSubscriptionsChangedListener.class));
+    }
+
+    /**
+     * Ensure that adding the callback and linking multiple subIds trigger the appropriate
+     * registerCallback and unregisterCallback calls as well as the subscriptionChanged listener.
+     * When removing the callbacks, the subscriptionChanged listener shoud only be removed when all
+     * callbacks have been removed.
+     */
+    @Test
+    @SmallTest
+    public void testCallbackAdapter_addAndRemoveCallbackForMultipleSubs() throws Exception {
+        TestCallback testCallback1 = new TestCallback();
+        TestCallback testCallback2 = new TestCallback();
+        int testSub1 = 1;
+        int testSub2 = 2;
+        mCallbackManagerUT.addCallbackForSubscription(testCallback1, testSub1);
+        assertTrue(mCallbackManagerUT.doesCallbackExist(testCallback1));
+        mCallbackManagerUT.addCallbackForSubscription(testCallback2, testSub2);
+        assertTrue(mCallbackManagerUT.doesCallbackExist(testCallback2));
+        // This should only happen once.
+        verify(mSubscriptionManager, times(1)).addOnSubscriptionsChangedListener(
+                any(SubscriptionManager.OnSubscriptionsChangedListener.class));
+
+        mCallbackManagerUT.removeCallbackForSubscription(testCallback1, testSub1);
+        assertFalse(mCallbackManagerUT.doesCallbackExist(testCallback1));
+        // removing the listener should not happen until the second callback is removed.
+        verify(mSubscriptionManager, never()).removeOnSubscriptionsChangedListener(
+                any(SubscriptionManager.OnSubscriptionsChangedListener.class));
+
+        mCallbackManagerUT.removeCallbackForSubscription(testCallback2, testSub2);
+        assertFalse(mCallbackManagerUT.doesCallbackExist(testCallback2));
+        verify(mSubscriptionManager, times(1)).removeOnSubscriptionsChangedListener(
+                any(SubscriptionManager.OnSubscriptionsChangedListener.class));
+    }
+
+    /**
+     * The subscriptions have changed, ensure that the callbacks registered to the original
+     * subscription testSub1 are removed, while keeping the callbacks for testSub2, since it was not
+     * removed.
+     */
+    @Test
+    @SmallTest
+    public void testCallbackAdapter_onSubscriptionsChangedMultipleSubs() throws Exception {
+        TestCallback testCallback1 = new TestCallback();
+        TestCallback testCallback2 = new TestCallback();
+        int testSub1 = 1;
+        int testSub2 = 2;
+        int testSub3 = 3;
+        mCallbackManagerUT.addCallbackForSubscription(testCallback1, testSub1);
+        assertTrue(mCallbackManagerUT.doesCallbackExist(testCallback1));
+        mCallbackManagerUT.addCallbackForSubscription(testCallback2, testSub2);
+        assertTrue(mCallbackManagerUT.doesCallbackExist(testCallback2));
+        verify(mSubscriptionManager, times(1)).addOnSubscriptionsChangedListener(
+                any(SubscriptionManager.OnSubscriptionsChangedListener.class));
+
+        // Simulate subscriptions changed, where testSub1 is no longer active
+        doReturn(createSubscriptionInfoList(new int[] {testSub2, testSub3}))
+                .when(mSubscriptionManager).getActiveSubscriptionInfoList();
+        mCallbackManagerUT.mSubChangedListener.onSubscriptionsChanged();
+        assertFalse(mCallbackManagerUT.doesCallbackExist(testCallback1));
+        // verify that the subscription changed listener is not removed, since we still have a
+        // callback on testSub2
+        verify(mSubscriptionManager, never()).removeOnSubscriptionsChangedListener(
+                any(SubscriptionManager.OnSubscriptionsChangedListener.class));
+    }
+
+    /**
+     * The active subscription has changed, ensure that the callback registered to the original
+     * subscription testSub1 are removed as well as the subscription changed listener, since
+     * there are mo more active callbacks.
+     */
+    @Test
+    @SmallTest
+    public void testCallbackAdapter_onSubscriptionsChangedOneSub() throws Exception {
+        TestCallback testCallback1 = new TestCallback();
+        int testSub1 = 1;
+        int testSub2 = 2;
+        mCallbackManagerUT.addCallbackForSubscription(testCallback1, testSub1);
+        assertTrue(mCallbackManagerUT.doesCallbackExist(testCallback1));
+        verify(mSubscriptionManager, times(1)).addOnSubscriptionsChangedListener(
+                any(SubscriptionManager.OnSubscriptionsChangedListener.class));
+
+        // Simulate subscriptions changed, where testSub1 is no longer active
+        doReturn(createSubscriptionInfoList(new int[] {testSub2}))
+                .when(mSubscriptionManager).getActiveSubscriptionInfoList();
+        mCallbackManagerUT.mSubChangedListener.onSubscriptionsChanged();
+        assertFalse(mCallbackManagerUT.doesCallbackExist(testCallback1));
+        // verify that the subscription listener is removed, since the only active callback has been
+        // removed.
+        verify(mSubscriptionManager, times(1)).removeOnSubscriptionsChangedListener(
+                any(SubscriptionManager.OnSubscriptionsChangedListener.class));
+    }
+
+    /**
+     * The close() method has been called, so al callbacks should be cleaned up and notified
+     * that they have been removed. The subscriptions changed listener should also be removed.
+     */
+    @Test
+    @SmallTest
+    public void testCallbackAdapter_closeMultipleSubs() throws Exception {
+        TestCallback testCallback1 = new TestCallback();
+        TestCallback testCallback2 = new TestCallback();
+        int testSub1 = 1;
+        int testSub2 = 2;
+        mCallbackManagerUT.addCallbackForSubscription(testCallback1, testSub1);
+        assertTrue(mCallbackManagerUT.doesCallbackExist(testCallback1));
+        mCallbackManagerUT.addCallbackForSubscription(testCallback2, testSub2);
+        assertTrue(mCallbackManagerUT.doesCallbackExist(testCallback2));
+        verify(mSubscriptionManager, times(1)).addOnSubscriptionsChangedListener(
+                any(SubscriptionManager.OnSubscriptionsChangedListener.class));
+
+        // Close the manager, ensure all subscription callbacks are removed
+        mCallbackManagerUT.close();
+        assertFalse(mCallbackManagerUT.doesCallbackExist(testCallback1));
+        assertFalse(mCallbackManagerUT.doesCallbackExist(testCallback2));
+        // verify that the subscription changed listener is removed.
+        verify(mSubscriptionManager, times(1)).removeOnSubscriptionsChangedListener(
+                any(SubscriptionManager.OnSubscriptionsChangedListener.class));
+    }
+
+    /**
+     * The close() method has been called, so all callbacks should be cleaned up. Since they are
+     * not associated with any subscriptions, no subscription based logic should be called.
+     */
+    @Test
+    @SmallTest
+    public void testCallbackAdapter_closeSlotBasedCallbacks() throws Exception {
+        TestCallback testCallback1 = new TestCallback();
+        TestCallback testCallback2 = new TestCallback();
+        mCallbackManagerUT.addCallback(testCallback1);
+        assertTrue(mCallbackManagerUT.doesCallbackExist(testCallback1));
+        mCallbackManagerUT.addCallback(testCallback2);
+        assertTrue(mCallbackManagerUT.doesCallbackExist(testCallback2));
+        // verify that the subscription changed listener is never called for these callbacks
+        // because they are not associated with any subscriptions.
+        verify(mSubscriptionManager, never()).addOnSubscriptionsChangedListener(
+                any(SubscriptionManager.OnSubscriptionsChangedListener.class));
+
+        // Close the manager, ensure all subscription callbacks are removed
+        mCallbackManagerUT.close();
+        assertFalse(mCallbackManagerUT.doesCallbackExist(testCallback1));
+        assertFalse(mCallbackManagerUT.doesCallbackExist(testCallback2));
+        // verify that the subscription changed removed method is never called
+        verify(mSubscriptionManager, never()).removeOnSubscriptionsChangedListener(
+                any(SubscriptionManager.OnSubscriptionsChangedListener.class));
+    }
+
+    private List<SubscriptionInfo> createSubscriptionInfoList(int[] subIds) {
+        List<SubscriptionInfo> infos = new ArrayList<>();
+        for (int i = 0; i < subIds.length; i++) {
+            SubscriptionInfo info = new SubscriptionInfo(subIds[i], null, -1, null, null, -1, -1,
+                    null, -1, null, null, null, null, false, null, null);
+            infos.add(info);
+        }
+        return infos;
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsCallTest.java b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsCallTest.java
index 2a2e38f..d64c69d 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsCallTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsCallTest.java
@@ -29,7 +29,9 @@
 import org.junit.Before;
 import org.junit.Test;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.doReturn;
 
@@ -55,9 +57,11 @@
     public void testSetWifi() {
         ImsCall mTestImsCall = new ImsCall(mContext, mTestCallProfile);
         assertFalse(mTestImsCall.isWifiCall());
+        assertNotEquals(mTestImsCall.getRadioTechnology(), ServiceState.RIL_RADIO_TECHNOLOGY_LTE);
         mBundle.putString(ImsCallProfile.EXTRA_CALL_RAT_TYPE,
                 ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN + "");
         assertTrue(mTestImsCall.isWifiCall());
+        assertNotEquals(mTestImsCall.getRadioTechnology(), ServiceState.RIL_RADIO_TECHNOLOGY_LTE);
     }
 
     @Test
@@ -65,9 +69,11 @@
     public void testSetWifiAlt() {
         ImsCall mTestImsCall = new ImsCall(mContext, mTestCallProfile);
         assertFalse(mTestImsCall.isWifiCall());
+        assertNotEquals(mTestImsCall.getRadioTechnology(), ServiceState.RIL_RADIO_TECHNOLOGY_LTE);
         mBundle.putString(ImsCallProfile.EXTRA_CALL_RAT_TYPE_ALT,
                 ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN + "");
         assertTrue(mTestImsCall.isWifiCall());
+        assertNotEquals(mTestImsCall.getRadioTechnology(), ServiceState.RIL_RADIO_TECHNOLOGY_LTE);
     }
 
     @Test
@@ -75,9 +81,11 @@
     public void testSetLteNoWifi() {
         ImsCall mTestImsCall = new ImsCall(mContext, mTestCallProfile);
         assertFalse(mTestImsCall.isWifiCall());
+        assertNotEquals(mTestImsCall.getRadioTechnology(), ServiceState.RIL_RADIO_TECHNOLOGY_LTE);
         mBundle.putString(ImsCallProfile.EXTRA_CALL_RAT_TYPE,
                 ServiceState.RIL_RADIO_TECHNOLOGY_LTE + "");
         assertFalse(mTestImsCall.isWifiCall());
+        assertEquals(mTestImsCall.getRadioTechnology(), ServiceState.RIL_RADIO_TECHNOLOGY_LTE);
     }
 
     @Test
@@ -85,8 +93,10 @@
     public void testSetLteNoWifiAlt() {
         ImsCall mTestImsCall = new ImsCall(mContext, mTestCallProfile);
         assertFalse(mTestImsCall.isWifiCall());
+        assertNotEquals(mTestImsCall.getRadioTechnology(), ServiceState.RIL_RADIO_TECHNOLOGY_LTE);
         mBundle.putString(ImsCallProfile.EXTRA_CALL_RAT_TYPE_ALT,
                 ServiceState.RIL_RADIO_TECHNOLOGY_LTE + "");
         assertFalse(mTestImsCall.isWifiCall());
+        assertEquals(mTestImsCall.getRadioTechnology(), ServiceState.RIL_RADIO_TECHNOLOGY_LTE);
     }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneCallTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneCallTrackerTest.java
index eac6159..5728648 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneCallTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneCallTrackerTest.java
@@ -52,6 +52,7 @@
 import android.telephony.TelephonyManager;
 import android.telephony.ims.ImsCallProfile;
 import android.telephony.ims.ImsCallSession;
+import android.telephony.ims.ImsMmTelManager;
 import android.telephony.ims.ImsReasonInfo;
 import android.telephony.ims.ImsStreamMediaProfile;
 import android.telephony.ims.feature.ImsFeature;
@@ -84,8 +85,8 @@
     private ImsPhoneCallTracker mCTUT;
     private ImsCTHandlerThread mImsCTHandlerThread;
     private MmTelFeature.Listener mMmTelListener;
-    private ImsRegistrationImplBase.Callback mRegistrationCallback;
-    private ImsFeature.CapabilityCallback mCapabilityCallback;
+    private ImsMmTelManager.RegistrationCallback mRegistrationCallback;
+    private ImsMmTelManager.CapabilityCallback mCapabilityCallback;
     private ImsCall.Listener mImsCallListener;
     private ImsCall mImsCall;
     private ImsCall mSecondImsCall;
@@ -114,24 +115,26 @@
                     ImsReasonInfo.CODE_ANSWERED_ELSEWHERE);
             mCTUT.addReasonCodeRemapping(510, "Call answered elsewhere.",
                     ImsReasonInfo.CODE_ANSWERED_ELSEWHERE);
+            mCTUT.addReasonCodeRemapping(ImsReasonInfo.CODE_USER_TERMINATED_BY_REMOTE, "",
+                    ImsReasonInfo.CODE_SIP_FORBIDDEN);
             mCTUT.setDataEnabled(true);
             mCTHander = new Handler(mCTUT.getLooper());
             setReady(true);
         }
     }
 
-    private void imsCallMocking(final ImsCall mImsCall) throws Exception {
+    private void imsCallMocking(final ImsCall imsCall) throws Exception {
 
         doAnswer(new Answer<Void>() {
             @Override
             public Void answer(InvocationOnMock invocation) throws Throwable {
                 // trigger the listener on accept call
                 if (mImsCallListener != null) {
-                    mImsCallListener.onCallStarted(mImsCall);
+                    mImsCallListener.onCallStarted(imsCall);
                 }
                 return null;
             }
-        }).when(mImsCall).accept(anyInt());
+        }).when(imsCall).accept(anyInt());
 
         doAnswer(new Answer<Void>() {
             @Override
@@ -139,12 +142,12 @@
                 // trigger the listener on reject call
                 int reasonCode = (int) invocation.getArguments()[0];
                 if (mImsCallListener != null) {
-                    mImsCallListener.onCallStartFailed(mImsCall, new ImsReasonInfo(reasonCode, -1));
-                    mImsCallListener.onCallTerminated(mImsCall, new ImsReasonInfo(reasonCode, -1));
+                    mImsCallListener.onCallStartFailed(imsCall, new ImsReasonInfo(reasonCode, -1));
+                    mImsCallListener.onCallTerminated(imsCall, new ImsReasonInfo(reasonCode, -1));
                 }
                 return null;
             }
-        }).when(mImsCall).reject(anyInt());
+        }).when(imsCall).reject(anyInt());
 
         doAnswer(new Answer<Void>() {
             @Override
@@ -152,23 +155,23 @@
                 // trigger the listener on reject call
                 int reasonCode = (int) invocation.getArguments()[0];
                 if (mImsCallListener != null) {
-                    mImsCallListener.onCallTerminated(mImsCall, new ImsReasonInfo(reasonCode, -1));
+                    mImsCallListener.onCallTerminated(imsCall, new ImsReasonInfo(reasonCode, -1));
                 }
                 return null;
             }
-        }).when(mImsCall).terminate(anyInt());
+        }).when(imsCall).terminate(anyInt());
 
         doAnswer(new Answer<Void>() {
             @Override
             public Void answer(InvocationOnMock invocation) throws Throwable {
                 if (mImsCallListener != null) {
-                    mImsCallListener.onCallHeld(mImsCall);
+                    mImsCallListener.onCallHeld(imsCall);
                 }
                 return null;
             }
-        }).when(mImsCall).hold();
+        }).when(imsCall).hold();
 
-        mImsCall.attachSession(mImsCallSession);
+        imsCall.attachSession(mImsCallSession);
     }
 
     @Before
@@ -215,13 +218,14 @@
         doAnswer(invocation -> {
             mRegistrationCallback = invocation.getArgument(0);
             return mRegistrationCallback;
-        }).when(mImsManager).addRegistrationCallback(any(ImsRegistrationImplBase.Callback.class));
+        }).when(mImsManager).addRegistrationCallback(
+                any(android.telephony.ims.ImsMmTelManager.RegistrationCallback.class));
 
         doAnswer(invocation -> {
-            mCapabilityCallback = (ImsFeature.CapabilityCallback) invocation.getArguments()[0];
+            mCapabilityCallback = (ImsMmTelManager.CapabilityCallback) invocation.getArguments()[0];
             return mCapabilityCallback;
 
-        }).when(mImsManager).addCapabilitiesCallback(any(ImsFeature.CapabilityCallback.class));
+        }).when(mImsManager).addCapabilitiesCallback(any(ImsMmTelManager.CapabilityCallback.class));
 
         doReturn(mImsConfig).when(mImsManager).getConfigInterface();
 
@@ -282,7 +286,7 @@
         assertFalse(mCTUT.isVowifiEnabled());
 
         // enable Voice over LTE
-        ImsFeature.Capabilities caps = new ImsFeature.Capabilities();
+        MmTelFeature.MmTelCapabilities caps = new MmTelFeature.MmTelCapabilities();
         caps.addCapabilities(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE);
         mCapabilityCallback.onCapabilitiesStatusChanged(caps);
         waitForHandlerAction(mCTHander, 1000);
@@ -300,7 +304,7 @@
         assertFalse(mCTUT.isVowifiEnabled());
 
         // enable Voice over IWLAN
-        ImsFeature.Capabilities caps = new ImsFeature.Capabilities();
+        MmTelFeature.MmTelCapabilities caps = new MmTelFeature.MmTelCapabilities();
         caps.addCapabilities(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE);
         mCapabilityCallback.onCapabilitiesStatusChanged(caps);
         waitForHandlerAction(mCTHander, 1000);
@@ -318,7 +322,7 @@
         assertFalse(mCTUT.isVideoCallEnabled());
 
         // enable only Voice
-        ImsFeature.Capabilities caps = new ImsFeature.Capabilities();
+        MmTelFeature.MmTelCapabilities caps = new MmTelFeature.MmTelCapabilities();
         caps.addCapabilities(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE);
         mCapabilityCallback.onCapabilitiesStatusChanged(caps);
         waitForHandlerAction(mCTHander, 1000);
@@ -330,7 +334,7 @@
         verify(mImsPhone, times(1)).onFeatureCapabilityChanged();
 
         // enable video call
-        ImsFeature.Capabilities capsVideo = new ImsFeature.Capabilities();
+        MmTelFeature.MmTelCapabilities capsVideo = new MmTelFeature.MmTelCapabilities();
         capsVideo.addCapabilities(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE);
         capsVideo.addCapabilities(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VIDEO);
         mCapabilityCallback.onCapabilitiesStatusChanged(capsVideo);
@@ -515,8 +519,16 @@
     @Test
     @SmallTest
     public void testImsMOCallDial() {
+        startOutgoingCall();
+        //call established
+        mImsCallListener.onCallProgressing(mSecondImsCall);
+        assertEquals(Call.State.ALERTING, mCTUT.mForegroundCall.getState());
+    }
+
+    private void startOutgoingCall() {
         assertEquals(Call.State.IDLE, mCTUT.mForegroundCall.getState());
         assertEquals(PhoneConstants.State.IDLE, mCTUT.getState());
+
         try {
             mCTUT.dial("+17005554141", ImsCallProfile.CALL_TYPE_VOICE, null);
             verify(mImsManager, times(1)).makeCall(eq(mImsCallProfile),
@@ -527,9 +539,6 @@
         }
         assertEquals(PhoneConstants.State.OFFHOOK, mCTUT.getState());
         assertEquals(Call.State.DIALING, mCTUT.mForegroundCall.getState());
-        //call established
-        mImsCallListener.onCallProgressing(mSecondImsCall);
-        assertEquals(Call.State.ALERTING, mCTUT.mForegroundCall.getState());
     }
 
     @FlakyTest
@@ -812,4 +821,131 @@
         }
         verify(mImsPhone, times(1)).startOnHoldTone(nullable(Connection.class));
     }
+
+    @Test
+    @SmallTest
+    public void testCallRestrictedDisconnect() {
+        doReturn(true).when(mSST.mRestrictedState).isCsRestricted();
+        assertEquals(DisconnectCause.CS_RESTRICTED, mCTUT.getDisconnectCauseFromReasonInfo(
+                new ImsReasonInfo(ImsReasonInfo.CODE_UNSPECIFIED, 0), Call.State.ACTIVE));
+    }
+
+    @Test
+    @SmallTest
+    public void testCallRestrictedEmergencyDisconnect() {
+        doReturn(true).when(mSST.mRestrictedState).isCsEmergencyRestricted();
+        assertEquals(DisconnectCause.CS_RESTRICTED_EMERGENCY,
+                mCTUT.getDisconnectCauseFromReasonInfo(
+                        new ImsReasonInfo(ImsReasonInfo.CODE_UNSPECIFIED, 0), Call.State.ACTIVE));
+    }
+
+    @Test
+    @SmallTest
+    public void testCallRestrictedNormal() {
+        doReturn(true).when(mSST.mRestrictedState).isCsNormalRestricted();
+        assertEquals(DisconnectCause.CS_RESTRICTED_NORMAL,
+                mCTUT.getDisconnectCauseFromReasonInfo(
+                        new ImsReasonInfo(ImsReasonInfo.CODE_UNSPECIFIED, 0), Call.State.ACTIVE));
+    }
+
+    @Test
+    @SmallTest
+    public void testSipNotFoundRemap() {
+        assertEquals(DisconnectCause.INVALID_NUMBER,
+                mCTUT.getDisconnectCauseFromReasonInfo(
+                        new ImsReasonInfo(ImsReasonInfo.CODE_SIP_NOT_FOUND, 0), Call.State.ACTIVE));
+    }
+
+    @Test
+    @SmallTest
+    public void testCantMakeCallWhileRinging() {
+        testImsMTCall();
+        try {
+            mCTUT.dial("6505551212", VideoProfile.STATE_AUDIO_ONLY, new Bundle());
+        } catch (CallStateException e) {
+            // We expect a call state exception!
+            assertEquals(CallStateException.ERROR_CALL_RINGING, e.getError());
+            return;
+        }
+        Assert.fail("Expected CallStateException");
+    }
+
+    @Test
+    @SmallTest
+    public void testCantMakeCallWhileDialing() {
+        startOutgoingCall();
+        try {
+            mCTUT.dial("6505551212", VideoProfile.STATE_AUDIO_ONLY, new Bundle());
+        } catch (CallStateException e) {
+            // We expect a call state exception!
+            assertEquals(CallStateException.ERROR_ALREADY_DIALING, e.getError());
+            return;
+        }
+        Assert.fail("Expected CallStateException");
+    }
+
+    @Test
+    @SmallTest
+    public void testCantMakeCallTooMany() {
+        // Place a call.
+        placeCallAndMakeActive();
+
+        // Place another call
+        placeCallAndMakeActive();
+
+        // Finally, dial a third.
+        try {
+            mCTUT.dial("6505551212", VideoProfile.STATE_AUDIO_ONLY, new Bundle());
+        } catch (CallStateException e) {
+            // We expect a call state exception!
+            assertEquals(CallStateException.ERROR_TOO_MANY_CALLS, e.getError());
+            return;
+        }
+        Assert.fail("Expected CallStateException");
+    }
+
+    @Test
+    @SmallTest
+    public void testNumericOnlyRemap() {
+        assertEquals(ImsReasonInfo.CODE_SIP_FORBIDDEN, mCTUT.maybeRemapReasonCode(
+                new ImsReasonInfo(ImsReasonInfo.CODE_USER_TERMINATED_BY_REMOTE, 0)));
+        assertEquals(ImsReasonInfo.CODE_SIP_FORBIDDEN, mCTUT.maybeRemapReasonCode(
+                new ImsReasonInfo(ImsReasonInfo.CODE_USER_TERMINATED_BY_REMOTE, 0, "")));
+    }
+
+    private void placeCallAndMakeActive() {
+        try {
+            doAnswer(new Answer<ImsCall>() {
+                @Override
+                public ImsCall answer(InvocationOnMock invocation) throws Throwable {
+                    mImsCallListener =
+                            (ImsCall.Listener) invocation.getArguments()[2];
+                    ImsCall imsCall = spy(new ImsCall(mContext, mImsCallProfile));
+                    imsCall.setListener(mImsCallListener);
+                    imsCallMocking(imsCall);
+                    return imsCall;
+                }
+            }).when(mImsManager).makeCall(eq(mImsCallProfile), (String[]) any(),
+                    (ImsCall.Listener) any());
+        } catch (ImsException ie) {
+        }
+
+        ImsPhoneConnection connection = null;
+        try {
+            connection = (ImsPhoneConnection) mCTUT.dial("+16505551212",
+                    ImsCallProfile.CALL_TYPE_VOICE, null);
+        } catch (Exception ex) {
+            ex.printStackTrace();
+            Assert.fail("unexpected exception thrown" + ex.getMessage());
+        }
+        if (connection == null) {
+            Assert.fail("connection is null");
+        }
+        ImsCall imsCall = connection.getImsCall();
+        imsCall.getImsCallSessionListenerProxy().callSessionProgressing(imsCall.getSession(),
+                new ImsStreamMediaProfile());
+        imsCall.getImsCallSessionListenerProxy().callSessionStarted(imsCall.getSession(),
+                new ImsCallProfile());
+    }
 }
+
diff --git a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneConnectionTest.java b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneConnectionTest.java
index 87d4467..b7a4ff0 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneConnectionTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneConnectionTest.java
@@ -46,6 +46,7 @@
 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.assertNotEquals;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.any;
@@ -242,8 +243,8 @@
     public void testSetWifi() {
         mConnectionUT = new ImsPhoneConnection(mImsPhone, mImsCall, mImsCT, mForeGroundCall, false);
         assertFalse(mConnectionUT.isWifi());
-        // ImsCall.isWifiCall is tested elsewhere
-        doReturn(true).when(mImsCall).isWifiCall();
+        // ImsCall.getRadioTechnology is tested elsewhere
+        doReturn(ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN).when(mImsCall).getRadioTechnology();
         mBundle.putString(ImsCallProfile.EXTRA_CALL_RAT_TYPE,
                 ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN + "");
         assertTrue(mConnectionUT.update(mImsCall, Call.State.ACTIVE));
@@ -255,8 +256,8 @@
     public void testSetWifi2() {
         mConnectionUT = new ImsPhoneConnection(mImsPhone, mImsCall, mImsCT, mForeGroundCall, false);
         assertFalse(mConnectionUT.isWifi());
-        // ImsCall.isWifiCall is tested elsewhere
-        doReturn(true).when(mImsCall).isWifiCall();
+        // ImsCall.getRadioTechnology is tested elsewhere
+        doReturn(ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN).when(mImsCall).getRadioTechnology();
         // Tests to make sure that the EXTRA_CALL_RAT_TYPE_ALT string is set correctly for newer
         // devices.
         mBundle.putString(ImsCallProfile.EXTRA_CALL_RAT_TYPE_ALT,
@@ -265,6 +266,34 @@
         assertTrue(mConnectionUT.isWifi());
     }
 
+    @Test
+    @SmallTest
+    public void testSetLTE() {
+        mConnectionUT = new ImsPhoneConnection(mImsPhone, mImsCall, mImsCT, mForeGroundCall, false);
+        assertNotEquals(mConnectionUT.getCallRadioTech(), ServiceState.RIL_RADIO_TECHNOLOGY_LTE);
+        // ImsCall.getRadioTechnology is tested elsewhere
+        doReturn(ServiceState.RIL_RADIO_TECHNOLOGY_LTE).when(mImsCall).getRadioTechnology();
+        mBundle.putString(ImsCallProfile.EXTRA_CALL_RAT_TYPE,
+                ServiceState.RIL_RADIO_TECHNOLOGY_LTE + "");
+        assertTrue(mConnectionUT.update(mImsCall, Call.State.ACTIVE));
+        assertEquals(mConnectionUT.getCallRadioTech(), ServiceState.RIL_RADIO_TECHNOLOGY_LTE);
+    }
+
+    @Test
+    @SmallTest
+    public void testSetLTE2() {
+        mConnectionUT = new ImsPhoneConnection(mImsPhone, mImsCall, mImsCT, mForeGroundCall, false);
+        assertNotEquals(mConnectionUT.getCallRadioTech(), ServiceState.RIL_RADIO_TECHNOLOGY_LTE);
+        // ImsCall.getRadioTechnology is tested elsewhere
+        doReturn(ServiceState.RIL_RADIO_TECHNOLOGY_LTE).when(mImsCall).getRadioTechnology();
+        // Tests to make sure that the EXTRA_CALL_RAT_TYPE_ALT string is set correctly for newer
+        // devices.
+        mBundle.putString(ImsCallProfile.EXTRA_CALL_RAT_TYPE_ALT,
+                ServiceState.RIL_RADIO_TECHNOLOGY_LTE + "");
+        assertTrue(mConnectionUT.update(mImsCall, Call.State.ACTIVE));
+        assertEquals(mConnectionUT.getCallRadioTech(), ServiceState.RIL_RADIO_TECHNOLOGY_LTE);
+    }
+
     /**
      * Test updates to address for incoming calls.
      */
diff --git a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneTest.java b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneTest.java
index aae876d..97e6691 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneTest.java
@@ -28,7 +28,9 @@
 import static org.mockito.Matchers.anyLong;
 import static org.mockito.Matchers.nullable;
 import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
@@ -50,14 +52,15 @@
 import android.support.test.filters.FlakyTest;
 import android.telephony.CarrierConfigManager;
 import android.telephony.ServiceState;
+import android.telephony.ims.ImsCallProfile;
+import android.telephony.ims.ImsReasonInfo;
 import android.test.suitebuilder.annotation.SmallTest;
 
-import android.telephony.ims.ImsCallProfile;
 import com.android.ims.ImsEcbmStateListener;
 import com.android.ims.ImsManager;
-import android.telephony.ims.ImsReasonInfo;
 import com.android.ims.ImsUtInterface;
 import com.android.internal.telephony.Call;
+import com.android.internal.telephony.CallStateException;
 import com.android.internal.telephony.CommandsInterface;
 import com.android.internal.telephony.Connection;
 import com.android.internal.telephony.Phone;
@@ -192,7 +195,7 @@
         doReturn(Call.State.IDLE).when(mForegroundCall).getState();
         doReturn(Call.State.INCOMING).when(mRingingCall).getState();
         assertEquals(true, mImsPhoneUT.handleInCallMmiCommands("1"));
-        verify(mImsCT).switchWaitingOrHoldingAndActive();
+        verify(mImsCT).holdActiveCallForWaitingCall();
     }
 
     @Test
@@ -213,7 +216,7 @@
 
         // ringing call is idle
         assertEquals(true, mImsPhoneUT.handleInCallMmiCommands("2"));
-        verify(mImsCT).switchWaitingOrHoldingAndActive();
+        verify(mImsCT).holdActiveCall();
 
         // ringing call is not idle
         doReturn(Call.State.INCOMING).when(mRingingCall).getState();
@@ -298,18 +301,16 @@
         mImsPhoneUT.rejectCall();
         verify(mImsCT).rejectCall();
 
-        mImsPhoneUT.switchHoldingAndActive();
-        verify(mImsCT).switchWaitingOrHoldingAndActive();
-
         assertEquals(false, mImsPhoneUT.canConference());
         doReturn(true).when(mImsCT).canConference();
         assertEquals(true, mImsPhoneUT.canConference());
         verify(mImsCT, times(2)).canConference();
 
-        assertEquals(false, mImsPhoneUT.canDial());
-        doReturn(true).when(mImsCT).canDial();
+        doNothing().when(mImsCT).checkForDialIssues();
         assertEquals(true, mImsPhoneUT.canDial());
-        verify(mImsCT, times(2)).canDial();
+        doThrow(CallStateException.class).when(mImsCT).checkForDialIssues();
+        assertEquals(false, mImsPhoneUT.canDial());
+        verify(mImsCT, times(2)).checkForDialIssues();
 
         mImsPhoneUT.conference();
         verify(mImsCT).conference();
diff --git a/tests/telephonytests/src/com/android/internal/telephony/mocks/ConnectivityServiceMock.java b/tests/telephonytests/src/com/android/internal/telephony/mocks/ConnectivityServiceMock.java
index 8b89269..ccb3acc 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/mocks/ConnectivityServiceMock.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/mocks/ConnectivityServiceMock.java
@@ -23,6 +23,7 @@
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
+import android.net.ConnectionInfo;
 import android.net.ConnectivityManager;
 import android.net.IConnectivityManager;
 import android.net.LinkProperties;
@@ -985,5 +986,10 @@
         defaultRequest = null;
     }
 
+    @Override
+    public int getConnectionOwnerUid(ConnectionInfo connectionInfo) {
+        throw new RuntimeException("not implemented");
+    }
+
 
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/mocks/DcTrackerMock.java b/tests/telephonytests/src/com/android/internal/telephony/mocks/DcTrackerMock.java
deleted file mode 100644
index a09c4eb..0000000
--- a/tests/telephonytests/src/com/android/internal/telephony/mocks/DcTrackerMock.java
+++ /dev/null
@@ -1,178 +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.mocks;
-
-import android.net.LinkProperties;
-import android.net.NetworkCapabilities;
-import android.net.NetworkRequest;
-import android.os.Handler;
-import android.os.Message;
-import android.util.LocalLog;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.telephony.DctConstants;
-import com.android.internal.telephony.dataconnection.DcTracker;
-
-import java.util.ArrayList;
-
-public class DcTrackerMock extends DcTracker {
-    public DcTrackerMock() {
-    }
-    @Override
-    public void registerServiceStateTrackerEvents() {
-        throw new RuntimeException("registerServiceStateTrackerEvents not implemented");
-    }
-    @Override
-    public void unregisterServiceStateTrackerEvents() {
-        throw new RuntimeException("Not Implemented");
-    }
-    @Override
-    public void setUserDataEnabled(boolean enable) {
-        throw new RuntimeException("Not Implemented");
-    }
-    @Override
-    public long getSubId() {
-        throw new RuntimeException("Not Implemented");
-    }
-    @Override
-    public DctConstants.Activity getActivity() {
-        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");
-    }
-    @Override
-    public NetworkCapabilities getNetworkCapabilities(String apnType) {
-        throw new RuntimeException("Not Implemented");
-    }
-    @Override
-    public String[] getActiveApnTypes() {
-        throw new RuntimeException("Not Implemented");
-    }
-    @Override
-    public String getActiveApnString(String apnType) {
-        throw new RuntimeException("Not Implemented");
-    }
-    @Override
-    public DctConstants.State getState(String apnType) {
-        throw new RuntimeException("Not Implemented");
-    }
-    @Override
-    public DctConstants.State getOverallState() {
-        throw new RuntimeException("Not Implemented");
-    }
-    @Override
-    public boolean hasMatchedTetherApnSetting() {
-        throw new RuntimeException("Not Implemented");
-    }
-    @Override
-    public boolean getAutoAttachOnCreation() {
-        throw new RuntimeException("Not Implemented");
-    }
-    @Override
-    public boolean isUserDataEnabled() {
-        throw new RuntimeException("Not Implemented");
-    }
-    @Override
-    public boolean isDataEnabled() {
-        throw new RuntimeException("Not Implemented");
-    }
-    @Override
-    public void setDataRoamingEnabledByUser(boolean enabled) {
-        throw new RuntimeException("Not Implemented");
-    }
-    @Override
-    public boolean getDataRoamingEnabled() {
-        throw new RuntimeException("Not Implemented");
-    }
-    @Override
-    public boolean isDisconnected() {
-        throw new RuntimeException("Not Implemented");
-    }
-    @Override
-    public void update() {
-        throw new RuntimeException("Not Implemented");
-    }
-    @Override
-    public void cleanUpAllConnections(String cause) {
-        throw new RuntimeException("Not Implemented");
-    }
-    @Override
-    public void updateRecords() {
-        throw new RuntimeException("Not Implemented");
-    }
-    @Override
-    public void cleanUpAllConnections(String cause, Message disconnectAllCompleteMsg) {
-        throw new RuntimeException("Not Implemented");
-    }
-    @Override
-    public void registerForAllDataDisconnected(Handler h, int what, Object obj) {
-        throw new RuntimeException("Not Implemented");
-    }
-    @Override
-    public void unregisterForAllDataDisconnected(Handler h) {
-        throw new RuntimeException("Not Implemented");
-    }
-    @Override
-    public boolean setInternalDataEnabled(boolean enable) {
-        throw new RuntimeException("Not Implemented");
-    }
-    @Override
-    public String[] getPcscfAddress(String apnType) {
-        throw new RuntimeException("Not Implemented");
-    }
-    @Override
-    public void sendStartNetStatPoll(DctConstants.Activity activity) {
-        throw new RuntimeException("Not Implemented");
-    }
-    @Override
-    public void sendStopNetStatPoll(DctConstants.Activity activity) {
-        throw new RuntimeException("Not Implemented");
-    }
-
-    private final ArrayList<NetworkRequest> mRequestedNetworks = new ArrayList<NetworkRequest>();
-
-    @Override
-    public void requestNetwork(NetworkRequest networkRequest, LocalLog log) {
-        synchronized (mRequestedNetworks) {
-            mRequestedNetworks.add(networkRequest);
-        }
-    }
-
-    @Override
-    public void releaseNetwork(NetworkRequest networkRequest, LocalLog log) {
-        synchronized (mRequestedNetworks) {
-            mRequestedNetworks.remove(networkRequest);
-        }
-    }
-
-    @VisibleForTesting
-    public int getNumberOfLiveRequests() {
-        synchronized (mRequestedNetworks) {
-            return mRequestedNetworks.size();
-        }
-    }
-}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/mocks/OWNERS b/tests/telephonytests/src/com/android/internal/telephony/mocks/OWNERS
index 909c9bb..ae3c9a9 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/mocks/OWNERS
+++ b/tests/telephonytests/src/com/android/internal/telephony/mocks/OWNERS
@@ -1,13 +1,6 @@
 # Re-list all owners appearing in the root OWNERS file for this project
 # This allows adding new owners for specific files using the per-file rule.
-per-file *=amitmahajan@google.com
-per-file *=breadley@google.com
-per-file *=fionaxu@google.com
-per-file *=jackyu@google.com
-per-file *=jsh@google.com
-per-file *=rgreenwalt@google.com
-per-file *=tgunn@google.com
+per-file * = amitmahajan@google.com,breadley@google.com,fionaxu@google.com
+per-file * = jackyu@google.com,rgreenwalt@google.com,tgunn@google.com
 
-per-file ConnectivityServiceMock.java=ek@google.com
-per-file ConnectivityServiceMock.java=hugobenichi@google.com
-per-file ConnectivityServiceMock.java=lorenzo@google.com
+per-file ConnectivityServiceMock.java=ek@google.com,jchalard@google.com,lorenzo@google.com,reminv@google.com
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 1b20833..d6c86c6 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/mocks/PhoneMock.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/mocks/PhoneMock.java
@@ -20,7 +20,6 @@
 import android.net.LinkProperties;
 import android.net.NetworkCapabilities;
 import android.os.AsyncResult;
-import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
 import android.os.Registrant;
@@ -1025,7 +1024,7 @@
         throw new RuntimeException("not implemented");
     }
 
-    public void registerForAllDataDisconnected(Handler h, int what, Object obj) {
+    public void registerForAllDataDisconnected(Handler h, int what) {
         throw new RuntimeException("not implemented");
     }
 
@@ -1248,10 +1247,6 @@
         throw new RuntimeException("not implemented");
     }
 
-    public void getNeighboringCids(Message response) {
-        throw new RuntimeException("not implemented");
-    }
-
     public void setOnPostDialCharacter(Handler h, int what, Object obj) {
         throw new RuntimeException("not implemented");
     }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/mocks/PhoneSwitcherMock.java b/tests/telephonytests/src/com/android/internal/telephony/mocks/PhoneSwitcherMock.java
index ca7e680..d3ab208 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/mocks/PhoneSwitcherMock.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/mocks/PhoneSwitcherMock.java
@@ -28,43 +28,45 @@
 
 public class PhoneSwitcherMock extends PhoneSwitcher {
     private final int mNumPhones;
-    private final RegistrantList mActivePhoneRegistrants[];
+    private final RegistrantList mActivePhoneRegistrants;
     private final AtomicBoolean mIsActive[];
 
     public PhoneSwitcherMock(int numPhones, Looper looper) {
         super(looper);
 
         mNumPhones = numPhones;
-        mActivePhoneRegistrants = new RegistrantList[numPhones];
+        mActivePhoneRegistrants = new RegistrantList();
         mIsActive = new AtomicBoolean[numPhones];
         for(int i = 0; i < numPhones; i++) {
-            mActivePhoneRegistrants[i] = new RegistrantList();
             mIsActive[i] = new AtomicBoolean(false);
         }
     }
 
     @Override
-    public void resendDataAllowed(int phoneId) {
+    public void onRadioCapChanged(int phoneId) {
         throw new RuntimeException("resendPhone not implemented");
     }
 
     @Override
-    public boolean isPhoneActive(int phoneId) {
+    public boolean shouldApplySpecifiedRequests(int phoneId) {
         return mIsActive[phoneId].get();
     }
 
     @Override
-    public void registerForActivePhoneSwitch(int phoneId, Handler h, int what, Object o) {
-        validatePhoneId(phoneId);
+    public boolean shouldApplyUnspecifiedRequests(int phoneId) {
+        return mIsActive[phoneId].get() && phoneId == mPreferredDataPhoneId;
+    }
+
+    @Override
+    public void registerForActivePhoneSwitch(Handler h, int what, Object o) {
         Registrant r = new Registrant(h, what, o);
-        mActivePhoneRegistrants[phoneId].add(r);
+        mActivePhoneRegistrants.add(r);
         r.notifyRegistrant();
     }
 
     @Override
-    public void unregisterForActivePhoneSwitch(int phoneId, Handler h) {
-        validatePhoneId(phoneId);
-        mActivePhoneRegistrants[phoneId].remove(h);
+    public void unregisterForActivePhoneSwitch(Handler h) {
+        mActivePhoneRegistrants.remove(h);
     }
 
     private void validatePhoneId(int phoneId) {
@@ -77,7 +79,15 @@
     public void setPhoneActive(int phoneId, boolean active) {
         validatePhoneId(phoneId);
         if (mIsActive[phoneId].getAndSet(active) != active) {
-            mActivePhoneRegistrants[phoneId].notifyRegistrants();
+            notifyActivePhoneChange(phoneId);
         }
     }
+
+    public void setPreferredDataPhoneId(int preferredDataPhoneId) {
+        mPreferredDataPhoneId = preferredDataPhoneId;
+    }
+
+    public void notifyActivePhoneChange(int phoneId) {
+        mActivePhoneRegistrants.notifyRegistrants();
+    }
 }
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 b14fc10..beeba62 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/mocks/SubscriptionControllerMock.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/mocks/SubscriptionControllerMock.java
@@ -202,10 +202,6 @@
     }
     @Override
     public int getPhoneId(int subId) {
-        if (subId == DEFAULT_SUBSCRIPTION_ID) {
-            subId = getDefaultSubId();
-        }
-
         if (subId <= INVALID_SUBSCRIPTION_ID) return INVALID_PHONE_INDEX;
 
         for (int i = 0; i < mSlotIndexToSubId.length; i++) {
@@ -259,14 +255,14 @@
     }
     @Override
     public boolean isActiveSubId(int subId) {
-        throw new RuntimeException("not implemented");
+        return getPhoneId(subId) != INVALID_PHONE_INDEX;
     }
     @Override
     public int getSimStateForSlotIndex(int slotIndex) {
         throw new RuntimeException("not implemented");
     }
     @Override
-    public void setSubscriptionProperty(int subId, String propKey, String propValue) {
+    public int setSubscriptionProperty(int subId, String propKey, String propValue) {
         throw new RuntimeException("not implemented");
     }
     @Override
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 2c73efa..19906bd 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/mocks/TelephonyRegistryMock.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/mocks/TelephonyRegistryMock.java
@@ -23,11 +23,12 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.telephony.CellInfo;
+import android.telephony.PhoneCapability;
 import android.telephony.PhysicalChannelConfig;
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
 import android.telephony.SubscriptionManager;
-import android.telephony.VoLteServiceState;
+import android.telephony.emergency.EmergencyNumber;
 
 import com.android.internal.telephony.IOnSubscriptionsChangedListener;
 import com.android.internal.telephony.IPhoneStateListener;
@@ -45,6 +46,7 @@
 
         IPhoneStateListener callback;
         IOnSubscriptionsChangedListener onSubscriptionsChangedListenerCallback;
+        IOnSubscriptionsChangedListener onOpportunisticSubscriptionsChangedListenerCallback;
 
         int callerUserId;
 
@@ -64,12 +66,19 @@
             return (onSubscriptionsChangedListenerCallback != null);
         }
 
+        boolean matchOnOpportunisticSubscriptionsChangedListener() {
+            return (onOpportunisticSubscriptionsChangedListenerCallback != null);
+        }
+
+
         @Override
         public String toString() {
             return "{callingPackage=" + callingPackage + " binder=" + binder
                     + " callback=" + callback
-                    + " onSubscriptionsChangedListenererCallback="
-                                            + onSubscriptionsChangedListenerCallback
+                    + " onSubscriptionsChangedListenerCallback="
+                    + onSubscriptionsChangedListenerCallback
+                    + " onOpportunisticSubscriptionsChangedListenerCallback="
+                    + onOpportunisticSubscriptionsChangedListenerCallback
                     + " callerUserId=" + callerUserId + " subId=" + subId + " phoneId=" + phoneId
                     + " events=" + Integer.toHexString(events)
                     + " canReadPhoneState=" + canReadPhoneState + "}";
@@ -78,7 +87,8 @@
 
     private final ArrayList<IBinder> mRemoveList = new ArrayList<IBinder>();
     private final ArrayList<Record> mRecords = new ArrayList<Record>();
-    private boolean hasNotifySubscriptionInfoChangedOccurred = false;
+    private boolean mHasNotifySubscriptionInfoChangedOccurred = false;
+    private boolean mHasNotifyOpportunisticSubscriptionInfoChangedOccurred = false;
 
     public TelephonyRegistryMock() {
     }
@@ -133,14 +143,54 @@
             r.events = 0;
             r.canReadPhoneState = true; // permission has been enforced above
             // Always notify when registration occurs if there has been a notification.
-            if (hasNotifySubscriptionInfoChangedOccurred) {
+            if (mHasNotifySubscriptionInfoChangedOccurred) {
                 try {
                     r.onSubscriptionsChangedListenerCallback.onSubscriptionsChanged();
                 } catch (RemoteException e) {
                     remove(r.binder);
                 }
             } else {
-                //log("listen oscl: hasNotifySubscriptionInfoChangedOccurred==false no callback");
+                //log("listen oscl: mHasNotifySubscriptionInfoChangedOccurred==false no callback");
+            }
+        }
+
+    }
+
+    @Override
+    public void addOnOpportunisticSubscriptionsChangedListener(String callingPackage,
+            IOnSubscriptionsChangedListener callback) {
+        Record r;
+
+        synchronized (mRecords) {
+            // register
+            find_and_add: {
+                IBinder b = callback.asBinder();
+                final int n = mRecords.size();
+                for (int i = 0; i < n; i++) {
+                    r = mRecords.get(i);
+                    if (b == r.binder) {
+                        break find_and_add;
+                    }
+                }
+                r = new Record();
+                r.binder = b;
+                mRecords.add(r);
+            }
+
+            r.onOpportunisticSubscriptionsChangedListenerCallback = callback;
+            r.callingPackage = callingPackage;
+            r.callerUserId = UserHandle.getCallingUserId();
+            r.events = 0;
+            r.canReadPhoneState = true; // permission has been enforced above
+            // Always notify when registration occurs if there has been a notification.
+            if (mHasNotifyOpportunisticSubscriptionInfoChangedOccurred) {
+                try {
+                    r.onOpportunisticSubscriptionsChangedListenerCallback.onSubscriptionsChanged();
+                } catch (RemoteException e) {
+                    remove(r.binder);
+                }
+            } else {
+                //log("listen oscl: mHasNotifySubscriptionInfoChangedOccurred==false no callback");
             }
         }
 
@@ -155,11 +205,11 @@
     @Override
     public void notifySubscriptionInfoChanged() {
         synchronized (mRecords) {
-            if (!hasNotifySubscriptionInfoChangedOccurred) {
+            if (!mHasNotifySubscriptionInfoChangedOccurred) {
                 //log("notifySubscriptionInfoChanged: first invocation mRecords.size="
                 //        + mRecords.size());
             }
-            hasNotifySubscriptionInfoChangedOccurred = true;
+            mHasNotifySubscriptionInfoChangedOccurred = true;
             mRemoveList.clear();
             for (Record r : mRecords) {
                 if (r.matchOnSubscriptionsChangedListener()) {
@@ -175,6 +225,29 @@
     }
 
     @Override
+    public void notifyOpportunisticSubscriptionInfoChanged() {
+        synchronized (mRecords) {
+            if (!mHasNotifyOpportunisticSubscriptionInfoChangedOccurred) {
+                //log("notifySubscriptionInfoChanged: first invocation mRecords.size="
+                //        + mRecords.size());
+            }
+            mHasNotifyOpportunisticSubscriptionInfoChangedOccurred = true;
+            mRemoveList.clear();
+            for (Record r : mRecords) {
+                if (r.matchOnOpportunisticSubscriptionsChangedListener()) {
+                    try {
+                        r.onOpportunisticSubscriptionsChangedListenerCallback
+                                .onSubscriptionsChanged();
+                    } catch (RemoteException ex) {
+                        mRemoveList.add(r.binder);
+                    }
+                }
+            }
+            handleRemoveListLocked();
+        }
+    }
+
+    @Override
     public void listen(String pkg, IPhoneStateListener callback, int events, boolean notifyNow) {
         throw new RuntimeException("Not implemented");
     }
@@ -289,6 +362,11 @@
     }
 
     @Override
+    public void notifyEmergencyNumberList(List<EmergencyNumber> emergencyNumberList) {
+        throw new RuntimeException("Not implemented");
+    }
+
+    @Override
     public void notifyPreciseCallState(int ringingCallState, int foregroundCallState,
             int backgroundCallState) {
         throw new RuntimeException("Not implemented");
@@ -311,7 +389,7 @@
     }
 
     @Override
-    public void notifyVoLteServiceStateChanged(VoLteServiceState lteState) {
+    public void notifySrvccStateChanged(int subId, int state) {
         throw new RuntimeException("Not implemented");
     }
 
@@ -333,6 +411,20 @@
 
     @Override
     public void notifyUserMobileDataStateChangedForPhoneId(int phoneId, int subId, boolean state) {
+    }
+
+    @Override
+    public void notifyPhoneCapabilityChanged(PhoneCapability capability) {
+        throw new RuntimeException("Not implemented");
+    }
+
+    @Override
+    public void notifyRadioPowerStateChanged(int state) {
+        throw new RuntimeException("Not implemented");
+    }
+
+    @Override
+    public void notifyPreferredDataSubIdChanged(int subId) {
         throw new RuntimeException("Not implemented");
     }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/rcs/RcsControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/rcs/RcsControllerTest.java
new file mode 100644
index 0000000..d80b94b
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/rcs/RcsControllerTest.java
@@ -0,0 +1,43 @@
+/*
+ * 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.rcs;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.internal.telephony.RcsController;
+import com.android.internal.telephony.TelephonyTest;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class RcsControllerTest extends TelephonyTest {
+
+    private RcsController mRcsController;
+
+    @Before
+    public void setUp() {
+        mRcsController = new RcsController(mContext, null);
+    }
+
+    /**
+     * TODO(sahinc): fix the test once there is an implementation in place
+     */
+    @Test
+    public void testGetMessageCount() {
+        assertEquals(1018, mRcsController.getMessageCount(0));
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/IccRecordsTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/IccRecordsTest.java
index ac68f6c..b38b5e8 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/IccRecordsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/IccRecordsTest.java
@@ -29,16 +29,16 @@
 
 package com.android.internal.telephony.uicc;
 
-import org.mockito.Mock;
+import static org.junit.Assert.assertEquals;
 import static org.mockito.Mockito.*;
-import org.mockito.MockitoAnnotations;
+
+import android.os.HandlerThread;
+
+import com.android.internal.telephony.TelephonyTest;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
-import com.android.internal.telephony.TelephonyTest;
-
-import android.content.Context;
-import android.os.HandlerThread;
 
 public class IccRecordsTest extends TelephonyTest {
 
@@ -79,5 +79,17 @@
 
     }
 
-
+    @Test
+    public void testSetImsiInvalid() {
+        mIccRecords.setImsi("0123456789FFFFFF");
+        assertEquals(mIccRecords.getIMSI(), "0123456789");
+        mIccRecords.setImsi("0123456789ffffff");
+        assertEquals(mIccRecords.getIMSI(), "0123456789");
+        mIccRecords.setImsi("ffffff");
+        assertEquals(mIccRecords.getIMSI(), null);
+        mIccRecords.setImsi("12F34F567890");
+        assertEquals(mIccRecords.getIMSI(), null);
+        mIccRecords.setImsi("123456ABCDEF");
+        assertEquals(mIccRecords.getIMSI(), null);
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccControllerTest.java
index 756015d..4012e76 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccControllerTest.java
@@ -26,10 +26,13 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
+import android.os.AsyncResult;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Message;
+import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
+import android.telephony.TelephonyManager;
 
 import com.android.internal.telephony.CommandsInterface;
 import com.android.internal.telephony.TelephonyTest;
@@ -40,15 +43,23 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 
+import java.util.ArrayList;
+
 public class UiccControllerTest extends TelephonyTest {
     private UiccController mUiccControllerUT;
     private UiccControllerHandlerThread mUiccControllerHandlerThread;
     private static final int PHONE_COUNT = 1;
-    private static int ICC_CHANGED_EVENT = 0;
+    private static final int ICC_CHANGED_EVENT = 0;
+    private static final int EVENT_GET_ICC_STATUS_DONE = 3;
+    private static final int EVENT_GET_SLOT_STATUS_DONE = 4;
     @Mock
     private Handler mMockedHandler;
     @Mock
     private IccCardStatus mIccCardStatus;
+    @Mock
+    private UiccSlot mMockSlot;
+    @Mock
+    private UiccCard mMockCard;
 
     private class UiccControllerHandlerThread extends HandlerThread {
 
@@ -143,16 +154,15 @@
         mSimulatedCommands.requestShutdown(null);
         waitForMs(50);
         assertNull(mUiccControllerUT.getUiccCard(0));
-        assertEquals(CommandsInterface.RadioState.RADIO_UNAVAILABLE,
-                mSimulatedCommands.getRadioState());
+        assertEquals(TelephonyManager.RADIO_POWER_UNAVAILABLE, mSimulatedCommands.getRadioState());
     }
 
-    @Test@SmallTest
+    @Test @SmallTest
     public void testPowerOn() {
         mSimulatedCommands.setRadioPower(true, null);
         waitForMs(500);
         assertNotNull(mUiccControllerUT.getUiccCard(0));
-        assertEquals(CommandsInterface.RadioState.RADIO_ON, mSimulatedCommands.getRadioState());
+        assertEquals(TelephonyManager.RADIO_POWER_ON, mSimulatedCommands.getRadioState());
     }
 
     @Test @SmallTest
@@ -200,4 +210,53 @@
                 mCaptorLong.capture());
         assertEquals(ICC_CHANGED_EVENT, mCaptorMessage.getValue().what);
     }
+
+    @Test
+    public void testCardIdFromIccStatus() {
+        // Give UiccController a real context so it can use shared preferences
+        mUiccControllerUT.mContext = InstrumentationRegistry.getContext();
+
+        // Mock out UiccSlots
+        mUiccControllerUT.mUiccSlots[0] = mMockSlot;
+        doReturn(mMockCard).when(mMockSlot).getUiccCard();
+        doReturn("A1B2C3D4").when(mMockCard).getCardId();
+        doReturn(IccCardStatus.CardState.CARDSTATE_PRESENT).when(mMockCard).getCardState();
+
+        // simulate card status loaded
+        IccCardStatus ics = new IccCardStatus();
+        ics.setCardState(1 /* present */);
+        ics.setUniversalPinState(3 /* disabled */);
+        ics.atr = "abcdef0123456789abcdef";
+        ics.iccid = "123451234567890";
+        ics.eid = "A1B2C3D4";
+        AsyncResult ar = new AsyncResult(null, ics, null);
+        Message msg = Message.obtain(mUiccControllerUT, EVENT_GET_ICC_STATUS_DONE, ar);
+        mUiccControllerUT.handleMessage(msg);
+
+        // assert that the card ID was created
+        assertEquals(0, mUiccControllerUT.convertToPublicCardId(ics.eid));
+    }
+
+    @Test
+    public void testCardIdFromSlotStatus() {
+        // Give UiccController a real context so it can use shared preferences
+        mUiccControllerUT.mContext = InstrumentationRegistry.getContext();
+
+        // Mock out UiccSlots
+        mUiccControllerUT.mUiccSlots[0] = mMockSlot;
+        doReturn(true).when(mMockSlot).isEuicc();
+
+        // simulate slot status loaded
+        IccSlotStatus iss = new IccSlotStatus();
+        iss.setSlotState(1 /* active */);
+        iss.eid = "ABADACB";
+        ArrayList<IccSlotStatus> status = new ArrayList<IccSlotStatus>();
+        status.add(iss);
+        AsyncResult ar = new AsyncResult(null, status, null);
+        Message msg = Message.obtain(mUiccControllerUT, EVENT_GET_SLOT_STATUS_DONE, ar);
+        mUiccControllerUT.handleMessage(msg);
+
+        // assert that the card ID was created
+        assertEquals(0, mUiccControllerUT.convertToPublicCardId(iss.eid));
+    }
 }
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 37f45a6..e99a4e5 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccProfileTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccProfileTest.java
@@ -30,6 +30,7 @@
 import static org.mockito.Mockito.verify;
 
 import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Message;
@@ -516,6 +517,65 @@
         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(mPackageManager).hasSystemFeature(PackageManager.FEATURE_TELEPHONY_CDMA);
+
+        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 not supported
+        doReturn(false)
+            .when(mPackageManager).hasSystemFeature(PackageManager.FEATURE_TELEPHONY_CDMA);
+
+        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() {
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 a3d7245..e782f1e 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
@@ -166,6 +166,28 @@
     }
 
     @Test
+    public void testPassEidInContructor() throws InterruptedException {
+        mMockIccCardStatus.eid = "1A2B3C4D";
+        mEuiccCard = new EuiccCard(mContextFixture.getTestDouble(), mMockCi,
+                mMockIccCardStatus, 0 /* phoneId */, new Object());
+
+        final int eventEidReady = 0;
+        final CountDownLatch latch = new CountDownLatch(1);
+        Handler handler = new Handler(mTestHandlerThread.getLooper()) {
+            @Override
+            public void handleMessage(Message msg) {
+                if (msg.what == eventEidReady) {
+                    assertEquals("1A2B3C4D", mEuiccCard.getEid());
+                    latch.countDown();
+                }
+            }
+        };
+        // This will instantly return, since EID is already set
+        mEuiccCard.registerForEidReady(handler, eventEidReady, null /* obj */);
+        assertTrue(latch.await(WAIT_TIMEOUT_MLLIS, TimeUnit.MILLISECONDS));
+    }
+
+    @Test
     public void testLoadEidAndNotifyRegistrants() throws InterruptedException {
         int channel = mockLogicalChannelResponses("BF3E065A041A2B3C4D9000");
 
@@ -564,7 +586,7 @@
                 com.android.internal.R.array.config_telephonyEuiccDeviceCapabilities))
                 .thenReturn(new String[] {});
 
-        int channel = mockLogicalChannelResponses("BF38038101039000");
+        int channel = mockLogicalChannelResponses("BF3805A1030201039000");
 
         ResultCaptor<byte[]> resultCaptor = new ResultCaptor<>();
         mEuiccCard.authenticateServer("A1B2C3-X4Y5Z6", // Matching id
@@ -984,6 +1006,22 @@
         assertFalse(node.hasChild(Tags.TAG_CTX_0));
     }
 
+    @Test
+    public void testGetDeviceId() {
+        // Unclear v2.0 definition
+        assertArrayEquals(
+                new byte[] {0x21, 0x43, 0x65, (byte) 0x87, 0x09, 0x21, 0x43, 0x05},
+                EuiccCard.getDeviceId("123456789012345", new EuiccSpecVersion(2, 0, 0)));
+        // Clarified v2.1+ definition
+        assertArrayEquals(
+                new byte[] {0x21, 0x43, 0x65, (byte) 0x87, 0x09, 0x21, 0x43, 0x5F},
+                EuiccCard.getDeviceId("123456789012345", new EuiccSpecVersion(2, 1, 0)));
+        // Same definition on v2.2
+        assertArrayEquals(
+                new byte[] {0x21, 0x43, 0x65, (byte) 0x87, 0x09, 0x21, 0x43, 0x5F},
+                EuiccCard.getDeviceId("123456789012345", new EuiccSpecVersion(2, 2, 0)));
+    }
+
     private void verifyStoreData(int channel, String command) {
         verify(mMockCi, times(1))
                 .iccTransmitApduLogicalChannel(eq(channel), eq(0x80 | channel), eq(0xE2), eq(0x91),