Merge "refactor servicestate data/voice roaming states"
diff --git a/Android.bp b/Android.bp
index 790cb53..0fba2d1 100644
--- a/Android.bp
+++ b/Android.bp
@@ -44,13 +44,13 @@
         "voip-common",
         "ims-common",
         "services",
-        "bouncycastle",
     ],
     static_libs: [
         "telephony-protos",
         "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.config-V1.0-java",
         "android.hardware.radio.deprecated-V1.0-java",
         "android.hidl.base-V1.0-java",
diff --git a/src/java/com/android/internal/telephony/CallStateException.java b/src/java/com/android/internal/telephony/CallStateException.java
index 8429146..064ecf4 100644
--- a/src/java/com/android/internal/telephony/CallStateException.java
+++ b/src/java/com/android/internal/telephony/CallStateException.java
@@ -28,6 +28,10 @@
 
     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
     CallStateException()
diff --git a/src/java/com/android/internal/telephony/GsmCdmaCallTracker.java b/src/java/com/android/internal/telephony/GsmCdmaCallTracker.java
index 90527a1..025e31c 100755
--- a/src/java/com/android/internal/telephony/GsmCdmaCallTracker.java
+++ b/src/java/com/android/internal/telephony/GsmCdmaCallTracker.java
@@ -274,9 +274,8 @@
         // 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.
+        checkForDialIssues();
 
         String origNumber = dialString;
         dialString = convertNumberIfNecessary(mPhone, dialString);
@@ -385,9 +384,8 @@
         // 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.
+        checkForDialIssues();
 
         TelephonyManager tm =
                 (TelephonyManager) mPhone.getContext().getSystemService(Context.TELEPHONY_SERVICE);
@@ -613,41 +611,46 @@
                 && !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() 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().isOn()) {
+            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.");
+        }
     }
 
     public boolean canTransfer() {
diff --git a/src/java/com/android/internal/telephony/GsmCdmaPhone.java b/src/java/com/android/internal/telephony/GsmCdmaPhone.java
index 66c05cf..58eb4e7 100644
--- a/src/java/com/android/internal/telephony/GsmCdmaPhone.java
+++ b/src/java/com/android/internal/telephony/GsmCdmaPhone.java
@@ -1118,7 +1118,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;
                 }
@@ -1747,9 +1747,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)
@@ -1770,7 +1776,7 @@
                         CommandsInterface.SERVICE_CLASS_VOICE, null, resp);
             }
         } else {
-            loge("getCallForwardingOption: not possible in CDMA");
+            loge("getCallForwardingOption: not possible in CDMA without IMS");
         }
     }
 
@@ -1780,7 +1786,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)
@@ -1809,7 +1815,7 @@
                         resp);
             }
         } else {
-            loge("setCallForwardingOption: not possible in CDMA");
+            loge("setCallForwardingOption: not possible in CDMA without IMS");
         }
     }
 
@@ -1902,7 +1908,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)
@@ -1921,7 +1927,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)
@@ -1932,7 +1938,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!");
         }
     }
 
@@ -2669,6 +2675,7 @@
                                 simOperatorNumeric);
                     }
                 }
+                updateDataConnectionTracker();
             }
         }
     }
diff --git a/src/java/com/android/internal/telephony/IccSmsInterfaceManager.java b/src/java/com/android/internal/telephony/IccSmsInterfaceManager.java
index 9b01a0b..280ab9a 100644
--- a/src/java/com/android/internal/telephony/IccSmsInterfaceManager.java
+++ b/src/java/com/android/internal/telephony/IccSmsInterfaceManager.java
@@ -86,8 +86,8 @@
     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);
 
@@ -147,7 +147,6 @@
         mPhone = phone;
         mContext = context;
         mAppOps = appOps;
-        mUserManager = userManager;
         mDispatchersController = dispatchersController;
     }
 
diff --git a/src/java/com/android/internal/telephony/InboundSmsHandler.java b/src/java/com/android/internal/telephony/InboundSmsHandler.java
index b779299..a115dba 100644
--- a/src/java/com/android/internal/telephony/InboundSmsHandler.java
+++ b/src/java/com/android/internal/telephony/InboundSmsHandler.java
@@ -61,6 +61,7 @@
 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;
@@ -99,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",
@@ -133,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=?";
 
@@ -508,6 +518,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
@@ -1183,55 +1195,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) {
@@ -1239,9 +1243,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
@@ -1255,7 +1299,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) {
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/PhoneFactory.java b/src/java/com/android/internal/telephony/PhoneFactory.java
index 8004f02..b5eb9a7 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;
 
@@ -246,7 +245,7 @@
                 int maxActivePhones = sPhoneConfigurationManager
                         .getNumberOfModemsWithSimultaneousDataConnections();
 
-                sPhoneSwitcher = new PhoneSwitcher(maxActivePhones, numPhones,
+                sPhoneSwitcher = PhoneSwitcher.make(maxActivePhones, numPhones,
                         sContext, sc, Looper.myLooper(), tr, sCommandsInterfaces,
                         sPhones);
 
@@ -458,17 +457,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/PhoneSwitcher.java b/src/java/com/android/internal/telephony/PhoneSwitcher.java
index 6d0e536..bc8fdb5 100644
--- a/src/java/com/android/internal/telephony/PhoneSwitcher.java
+++ b/src/java/com/android/internal/telephony/PhoneSwitcher.java
@@ -39,6 +39,7 @@
 import android.telephony.PhoneCapability;
 import android.telephony.PhoneStateListener;
 import android.telephony.Rlog;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.util.LocalLog;
 
@@ -77,17 +78,53 @@
     private final PhoneStateListener mPhoneStateListener;
 
     private int mMaxActivePhones;
-    private int mDefaultDataSubscription;
+    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 requets, which
+    //    are requests without specifying a subId.
+    private int mPreferredDataSubId = SubscriptionManager.INVALID_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_RESEND_DATA_ALLOWED            = 106;
+    private static final int EVENT_PREFERRED_SUBSCRIPTION_CHANGED = 107;
 
     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);
@@ -108,6 +145,7 @@
         };
     }
 
+    @VisibleForTesting
     public PhoneSwitcher(int maxActivePhones, int numPhones, Context context,
             SubscriptionController subscriptionController, Looper looper, ITelephonyRegistry tr,
             CommandsInterface[] cis, Phone[] phones) {
@@ -221,6 +259,10 @@
                 onResendDataAllowed(msg);
                 break;
             }
+            case EVENT_PREFERRED_SUBSCRIPTION_CHANGED: {
+                onEvaluate(REQUESTS_UNCHANGED, "preferredDataSubIdChanged");
+                break;
+            }
         }
     }
 
@@ -292,14 +334,17 @@
             return;
         }
 
+        // Check if preferred slotId is changed.
         boolean diffDetected = requestsChanged;
+
+        // Check if user setting of default data sub is changed.
         final int dataSub = mSubscriptionController.getDefaultDataSubId();
-        if (dataSub != mDefaultDataSubscription) {
-            sb.append(" default ").append(mDefaultDataSubscription).append("->").append(dataSub);
-            mDefaultDataSubscription = dataSub;
-            diffDetected = true;
+        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]) {
@@ -310,6 +355,15 @@
             }
         }
 
+        // 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());
 
@@ -324,7 +378,8 @@
             }
 
             if (VDBG) {
-                log("default subId = " + mDefaultDataSubscription);
+                log("default subId = " + mDefaultDataSubId);
+                log("preferred subId = " + mPreferredDataSubId);
                 for (int i = 0; i < mNumPhones; i++) {
                     log(" phone[" + i + "] using sub[" + mPhoneSubscriptions[i] + "]");
                 }
@@ -399,17 +454,19 @@
         if (mMaxActivePhones != newMaxActivePhones) {
             mMaxActivePhones = newMaxActivePhones;
             log("Max active phones changed to " + mMaxActivePhones);
-            onEvaluate(true, "phoneCfgChanged");
+            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) {
@@ -433,6 +490,39 @@
         return phoneId;
     }
 
+    private int getSubIdForDefaultNetworkRequests() {
+        if (mSubscriptionController.isActiveSubId(mPreferredDataSubId)) {
+            return mPreferredDataSubId;
+        } else {
+            return mDefaultDataSubId;
+        }
+    }
+
+    // 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 default network requests.
+     */
+    public boolean isActiveForDefaultRequests(int phoneId) {
+        return isPhoneActive(phoneId) && phoneId == mPreferredDataPhoneId;
+    }
+
     public boolean isPhoneActive(int phoneId) {
         validatePhoneId(phoneId);
         return mPhoneStates[phoneId].active;
@@ -456,6 +546,19 @@
         }
     }
 
+    /**
+     * 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 log(String l) {
         Rlog.d(LOG_TAG, l);
         mLocalLog.log(l);
diff --git a/src/java/com/android/internal/telephony/RadioResponse.java b/src/java/com/android/internal/telephony/RadioResponse.java
index 0fc5279..dda8b60 100644
--- a/src/java/com/android/internal/telephony/RadioResponse.java
+++ b/src/java/com/android/internal/telephony/RadioResponse.java
@@ -1313,37 +1313,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);
-                }
-                // 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);
-                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);
         }
-        // If responseInfo.error != NONE, the processResponseDone sends the response message.
-        mRil.processResponseDone(rr, responseInfo, ret);
     }
 
     /**
@@ -1351,16 +1350,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
         }
     }
 
diff --git a/src/java/com/android/internal/telephony/ServiceStateTracker.java b/src/java/com/android/internal/telephony/ServiceStateTracker.java
index 433766c..b2f5fc4 100644
--- a/src/java/com/android/internal/telephony/ServiceStateTracker.java
+++ b/src/java/com/android/internal/telephony/ServiceStateTracker.java
@@ -4039,16 +4039,16 @@
                         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
+                    if (!ProxyController.getInstance().isDataDisconnected(mPhone.getSubId())) {
+                        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(dds, this,
-                                EVENT_ALL_DATA_DISCONNECTED, null);
+                        ProxyController.getInstance().registerForAllDataDisconnected(
+                                mPhone.getSubId(), this, EVENT_ALL_DATA_DISCONNECTED, null);
                         mPendingRadioPowerOffAfterDataOff = true;
                     }
+                    dcTracker.cleanUpAllConnections(Phone.REASON_RADIO_TURNED_OFF);
+
                     Message msg = Message.obtain(this);
                     msg.what = EVENT_SET_RADIO_POWER_OFF;
                     msg.arg1 = ++mPendingRadioPowerOffAfterDataOffTag;
diff --git a/src/java/com/android/internal/telephony/SmsBroadcastUndelivered.java b/src/java/com/android/internal/telephony/SmsBroadcastUndelivered.java
index 4fb02f0..32edb9c 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;
@@ -172,16 +174,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 +191,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 +203,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 +226,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 ff01d5b..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;
@@ -51,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;
@@ -61,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;
@@ -102,11 +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);
+        }
     }
 
+    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();
@@ -139,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);
@@ -148,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:
@@ -625,4 +810,8 @@
         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/SubscriptionController.java b/src/java/com/android/internal/telephony/SubscriptionController.java
index 9766cf0..8c08420 100644
--- a/src/java/com/android/internal/telephony/SubscriptionController.java
+++ b/src/java/com/android/internal/telephony/SubscriptionController.java
@@ -168,6 +168,7 @@
 
     private int[] colorArr;
     private long mLastISubServiceRegTime;
+    private int mPreferredDataSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
 
     public static SubscriptionController init(Phone phone) {
         synchronized (SubscriptionController.class) {
@@ -940,9 +941,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();
@@ -1035,9 +1035,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();
@@ -1154,9 +1153,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();
@@ -1189,9 +1187,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();
@@ -1253,9 +1250,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();
@@ -1299,9 +1295,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();
@@ -1339,9 +1334,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();
@@ -1378,8 +1372,8 @@
         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();
@@ -2065,9 +2059,8 @@
                 break;
         }
 
-        return resolver.update(SubscriptionManager.CONTENT_URI, value,
-                SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID +
-                        "=" + Integer.toString(subId), null);
+        return resolver.update(SubscriptionManager.getUriForSubscriptionId(subId),
+                value, null, null);
     }
 
     /**
@@ -2301,8 +2294,23 @@
 
     @Override
     public int setPreferredData(int slotId) {
-        // TODO: send to phone switcher.
-        return 0;
+        enforceModifyPhoneState("setPreferredData");
+        final long token = Binder.clearCallingIdentity();
+
+        try {
+            // TODO: make this API takes in subId directly.
+            int subId = getSubIdUsingPhoneId(slotId);
+
+            if (mPreferredDataSubId != subId) {
+                mPreferredDataSubId = subId;
+                PhoneSwitcher.getInstance().setPreferredData(subId);
+                //TODO: notifyPreferredDataSubIdChanged();
+            }
+
+            return 0;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
     }
 
     @Override
diff --git a/src/java/com/android/internal/telephony/SubscriptionInfoUpdater.java b/src/java/com/android/internal/telephony/SubscriptionInfoUpdater.java
index 4a08445..1ee96a8 100644
--- a/src/java/com/android/internal/telephony/SubscriptionInfoUpdater.java
+++ b/src/java/com/android/internal/telephony/SubscriptionInfoUpdater.java
@@ -564,9 +564,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();
@@ -628,9 +627,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/TelephonyTester.java b/src/java/com/android/internal/telephony/TelephonyTester.java
index 9b4aa2f..bb7ac00 100644
--- a/src/java/com/android/internal/telephony/TelephonyTester.java
+++ b/src/java/com/android/internal/telephony/TelephonyTester.java
@@ -409,7 +409,7 @@
         if (extras == null) {
             extras = new Bundle();
         }
-        extras.putBoolean(ImsCallProfile.EXTRA_E_CALL, true);
+        extras.putBoolean(ImsCallProfile.EXTRA_EMERGENCY_CALL, true);
         callProfile.mCallExtras = extras;
         imsCall.getImsCallSessionListenerProxy().callSessionUpdated(imsCall.getSession(),
                 callProfile);
diff --git a/src/java/com/android/internal/telephony/dataconnection/DataConnection.java b/src/java/com/android/internal/telephony/dataconnection/DataConnection.java
index a36a8c5..b304e5b 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DataConnection.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DataConnection.java
@@ -39,7 +39,7 @@
 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;
@@ -461,8 +461,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);
@@ -1664,10 +1663,12 @@
             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
@@ -1686,8 +1687,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;
@@ -1809,8 +1813,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(
@@ -2081,8 +2084,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));
             }
         }
 
diff --git a/src/java/com/android/internal/telephony/dataconnection/DcTracker.java b/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
index 394d20f..5faf169 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
@@ -536,7 +536,19 @@
 
     private int mDisconnectPendingCount = 0;
 
-    private ArrayList<DataProfile> mLastDataProfileList = null;
+    /** 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;
 
     /**
      * Handles changes to the APN db.
@@ -716,7 +728,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);
@@ -773,7 +784,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);
@@ -3208,25 +3218,18 @@
     private void setDataProfilesAsNeeded() {
         if (DBG) log("setDataProfilesAsNeeded");
 
-        ArrayList<DataProfile> dataProfileList = new ArrayList<>();
-
+        ArrayList<DataProfile> dps = new ArrayList<DataProfile>();
         for (ApnSetting apn : mAllApnSettings) {
-            DataProfile dp = createDataProfile(apn);
-            if (!dataProfileList.contains(dp)) {
-                dataProfileList.add(dp);
+            if (apn.getModemCognitive()) {
+                DataProfile dp = createDataProfile(apn);
+                if (!dps.contains(dp)) {
+                    dps.add(dp);
+                }
             }
         }
-
-        // 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 note that when no data profiles are
-        // available, for example when SIM is not present, we send the empty list to the modem.
-        // Also we always send the data profiles once after boot up.
-        if (mLastDataProfileList == null
-                || dataProfileList.size() != mLastDataProfileList.size()
-                || !mLastDataProfileList.containsAll(dataProfileList)) {
-            mDataServiceManager.setDataProfile(dataProfileList,
+        if (dps.size() > 0) {
+            mDataServiceManager.setDataProfile(dps,
                     mPhone.getServiceState().getDataRoamingFromRegistration(), null);
-            mLastDataProfileList = dataProfileList;
         }
     }
 
diff --git a/src/java/com/android/internal/telephony/dataconnection/TelephonyNetworkFactory.java b/src/java/com/android/internal/telephony/dataconnection/TelephonyNetworkFactory.java
index 114a4b4..339fa9a 100644
--- a/src/java/com/android/internal/telephony/dataconnection/TelephonyNetworkFactory.java
+++ b/src/java/com/android/internal/telephony/dataconnection/TelephonyNetworkFactory.java
@@ -52,9 +52,12 @@
     private final HashMap<NetworkRequest, LocalLog> mSpecificRequests =
             new HashMap<NetworkRequest, LocalLog>();
 
-    private int mPhoneId;
+    private final int mPhoneId;
+    // 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,9 +65,8 @@
     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,
@@ -90,9 +92,7 @@
         mSubscriptionMonitor.registerForSubscriptionChanged(mPhoneId, mInternalHandler,
                 EVENT_SUBSCRIPTION_CHANGED, null);
 
-        mIsDefault = false;
-        mSubscriptionMonitor.registerForDefaultDataSubscriptionChanged(mPhoneId, mInternalHandler,
-                EVENT_DEFAULT_SUBSCRIPTION_CHANGED, null);
+        mIsActiveForDefault = false;
 
         register();
     }
@@ -138,10 +138,6 @@
                     onSubIdChange();
                     break;
                 }
-                case EVENT_DEFAULT_SUBSCRIPTION_CHANGED: {
-                    onDefaultChange();
-                    break;
-                }
                 case EVENT_NETWORK_REQUEST: {
                     onNeedNetworkFor(msg);
                     break;
@@ -155,34 +151,51 @@
     }
 
     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 newIsActiveForDefault = mPhoneSwitcher.isActiveForDefaultRequests(mPhoneId);
+
+        String logString = "onActivePhoneSwitch(newIsActive " + newIsActive + ", "
+                + "newIsActive " + 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
@@ -196,21 +209,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 +227,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 +262,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 +287,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/imsphone/ImsPhone.java b/src/java/com/android/internal/telephony/imsphone/ImsPhone.java
index a75b4ce..dc8c126 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhone.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhone.java
@@ -365,7 +365,12 @@
     }
 
     public boolean canDial() {
-        return mCT.canDial();
+        try {
+            mCT.checkForDialIssues();
+        } catch (CallStateException cse) {
+            return false;
+        }
+        return true;
     }
 
     @Override
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
index c61af38..82f4702 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
@@ -934,9 +934,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);
@@ -957,8 +957,9 @@
         // 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;
@@ -1517,18 +1518,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
@@ -2769,7 +2782,15 @@
                     }
                 }
 
+                if (isHandoverToWifi && mIsViLteDataMetered) {
+                    conn.setLocalVideoCapable(true);
+                }
+
                 if (isHandoverFromWifi && imsCall.isVideoCall()) {
+                    if (mIsViLteDataMetered) {
+                        conn.setLocalVideoCapable(mIsDataEnabled);
+                    }
+
                     if (mNotifyHandoverVideoFromWifiToLTE &&    mIsDataEnabled) {
                         if (conn.getDisconnectCause() == DisconnectCause.NOT_DISCONNECTED) {
                             log("onCallHandover :: notifying of WIFI to LTE handover.");
@@ -3755,7 +3776,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;
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java
index d24b256..b707761 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java
@@ -121,7 +121,7 @@
      * currently available, but mobile data is off and the carrier is metering data for video
      * calls.
      */
-    private boolean mIsVideoEnabled = true;
+    private boolean mIsLocalVideoCapable = true;
 
     //***** Event Constants
     private static final int EVENT_DTMF_DONE = 1;
@@ -264,7 +264,7 @@
         capabilities = removeCapability(capabilities,
                 Connection.Capability.SUPPORTS_VT_LOCAL_BIDIRECTIONAL);
 
-        if (!mIsVideoEnabled) {
+        if (!mIsLocalVideoCapable) {
             Rlog.i(LOG_TAG, "applyLocalCallCapabilities - disabling video (overidden)");
             return capabilities;
         }
@@ -981,9 +981,24 @@
         setVideoState(newVideoState);
     }
 
-    public void sendRttModifyRequest(android.telecom.Connection.RttTextStream textStream) {
-        getImsCall().sendRttModifyRequest();
-        setCurrentRttTextStream(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(true);
+            setCurrentRttTextStream(textStream);
+        }
+    }
+
+    /**
+     * Terminate the current RTT session.
+     */
+    public void stopRtt() {
+        getImsCall().sendRttModifyRequest(false);
     }
 
     /**
@@ -996,11 +1011,13 @@
         boolean accept = textStream != null;
         ImsCall imsCall = getImsCall();
 
-        imsCall.sendRttModifyResponse(accept);
-        if (accept) {
-            setCurrentRttTextStream(textStream);
-        } else {
-            Rlog.e(LOG_TAG, "sendRttModifyResponse: foreground call has no connections");
+        if (imsCall != null) {
+            imsCall.sendRttModifyResponse(accept);
+            if (accept) {
+                setCurrentRttTextStream(textStream);
+            } else {
+                Rlog.e(LOG_TAG, "sendRttModifyResponse: foreground call has no connections");
+            }
         }
     }
 
@@ -1055,7 +1072,12 @@
     // Make sure to synchronize on ImsPhoneConnection.this before calling.
     private void createRttTextHandler() {
         mRttTextHandler = new ImsRttTextHandler(Looper.getMainLooper(),
-                (message) -> getImsCall().sendRttMessage(message));
+                (message) -> {
+                    ImsCall imsCall = getImsCall();
+                    if (imsCall != null) {
+                        imsCall.sendRttMessage(message);
+                    }
+                });
         mRttTextHandler.initialize(mRttTextStream);
     }
 
@@ -1080,7 +1102,7 @@
     }
 
     private void updateEmergencyCallFromExtras(Bundle extras) {
-        if (extras.getBoolean(ImsCallProfile.EXTRA_E_CALL)) {
+        if (extras.getBoolean(ImsCallProfile.EXTRA_EMERGENCY_CALL)) {
             setIsNetworkIdentifiedEmergencyCall(true);
         }
     }
@@ -1330,9 +1352,9 @@
         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());
     }
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/tests/telephonytests/Android.bp b/tests/telephonytests/Android.bp
index 960c892..f772c49 100644
--- a/tests/telephonytests/Android.bp
+++ b/tests/telephonytests/Android.bp
@@ -6,7 +6,6 @@
     libs: [
         "android.test.runner",
         "ims-common",
-        "bouncycastle",
         "android.test.base",
         "android.test.mock",
     ],
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java b/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java
index c671f4f..6313633 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java
@@ -259,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);
         }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/FakeTelephonyProvider.java b/tests/telephonytests/src/com/android/internal/telephony/FakeTelephonyProvider.java
index 86c41c9..b794b4d 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/FakeTelephonyProvider.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/FakeTelephonyProvider.java
@@ -18,11 +18,13 @@
 
 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;
@@ -139,8 +141,17 @@
     }
 
     @Override
-    public final int update(Uri uri, ContentValues values, String where,
-            String[] selectionArgs) {
-        return mDbHelper.getWritableDatabase().update("siminfo", values, where, selectionArgs);
+    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/PhoneSwitcherTest.java b/tests/telephonytests/src/com/android/internal/telephony/PhoneSwitcherTest.java
index fae1a25..4f450d0 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/PhoneSwitcherTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/PhoneSwitcherTest.java
@@ -16,37 +16,38 @@
 
 package com.android.internal.telephony;
 
-import static org.junit.Assert.fail;
+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.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.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 java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicReference;
+import org.mockito.Mock;
 
 public class PhoneSwitcherTest extends TelephonyTest {
-    private static final String LOG_TAG = "PhoneSwitcherTest";
-
     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",
@@ -54,132 +55,30 @@
             "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"};
 
-    static void failAndStack(String str) {
-        fail(str + "\n" + SubscriptionMonitorTest.stack());
-    }
+    private static final int ACTIVE_PHONE_SWITCH = 1;
 
-    static String stack() {
-        StringBuilder sb = new StringBuilder();
-        for(StackTraceElement e : Thread.currentThread().getStackTrace()) {
-            sb.append(e.toString()).append("\n");
-        }
-        return sb.toString();
-    }
+    @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;
 
-    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);
-    }
+    // 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 {
@@ -197,87 +96,51 @@
     @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();
-        mContextFixture.putStringArrayResource(com.android.internal.R.array.networkAttributes,
-                sNetworkAttributes);
-        final Context contextMock = mContextFixture.getTestDouble();
-        final ConnectivityServiceMock connectivityServiceMock =
-                new ConnectivityServiceMock(contextMock);
-        final ConnectivityManager cm =
-                new ConnectivityManager(contextMock, connectivityServiceMock);
-        mContextFixture.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.isPhoneActive(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.isPhoneActive(0));
 
         // not registered yet - shouldn't inc
-        if (testHandler.getActivePhoneSwitchCount() != 0) {
-            fail("pretest of ActivePhoneSwitchCount");
-        }
+        verify(mActivePhoneSwitchHandler, never()).sendMessageAtTime(any(), anyLong());
+
         boolean threw = false;
         try {
             // should throw
-            phoneSwitcher.registerForActivePhoneSwitch(2, testHandler,
-                    TestHandler.ACTIVE_PHONE_SWITCH, activePhoneSwitchObject);
+            mPhoneSwitcher.registerForActivePhoneSwitch(2, mActivePhoneSwitchHandler,
+                    ACTIVE_PHONE_SWITCH, null);
         } catch (IllegalArgumentException e) {
             threw = true;
         }
-        if (threw == false) fail("register with bad phoneId didn't throw");
+        assertTrue("register with bad phoneId didn't throw", threw);
 
-        phoneSwitcher.registerForActivePhoneSwitch(0, testHandler,
-                TestHandler.ACTIVE_PHONE_SWITCH,
-                activePhoneSwitchObject);
-        testHandler.blockTilIdle();
+        mPhoneSwitcher.registerForActivePhoneSwitch(0, mActivePhoneSwitchHandler,
+                ACTIVE_PHONE_SWITCH, null);
 
-        if (testHandler.getActivePhoneSwitchCount() != 1) {
-            fail("post register of ActivePhoneSwitchCount not 1!");
-        }
+        verify(mActivePhoneSwitchHandler, times(1)).sendMessageAtTime(any(), anyLong());
 
-        subControllerMock.setDefaultDataSubId(0);
-        testHandler.blockTilIdle();
 
-        if (testHandler.getActivePhoneSwitchCount() != 1) {
-            fail("after set of default to 0, ActivePhoneSwitchCount not 1!");
-        }
-        if (commandsInterfaces[0].isDataAllowed()) fail("data allowed");
+        setDefaultDataSubId(0);
 
-        subControllerMock.setSlotSubId(0, 0);
+        verify(mActivePhoneSwitchHandler, times(1)).sendMessageAtTime(any(), anyLong());
+        assertFalse("data allowed", mDataAllowed[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(2)).sendMessageAtTime(any(), anyLong());
+        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
@@ -292,93 +155,87 @@
         // 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(3)).sendMessageAtTime(any(), anyLong());
+        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(3)).sendMessageAtTime(any(), anyLong());
+        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(4)).sendMessageAtTime(any(), anyLong());
+        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(5)).sendMessageAtTime(any(), anyLong());
+        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(6)).sendMessageAtTime(any(), anyLong());
+        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(7)).sendMessageAtTime(any(), anyLong());
+        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(8)).sendMessageAtTime(any(), anyLong());
+        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(9)).sendMessageAtTime(any(), anyLong());
+        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(10)).sendMessageAtTime(any(), anyLong());
+        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(11)).sendMessageAtTime(any(), anyLong());
+        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
@@ -400,13 +257,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();
     }
 
     /**
@@ -428,68 +279,34 @@
     @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();
-        mContextFixture.putStringArrayResource(com.android.internal.R.array.networkAttributes,
-                sNetworkAttributes);
-        final Context contextMock = mContextFixture.getTestDouble();
-        final ConnectivityServiceMock connectivityServiceMock =
-            new ConnectivityServiceMock(contextMock);
-        final ConnectivityManager cm =
-                new ConnectivityManager(contextMock, connectivityServiceMock);
-        mContextFixture.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(0, 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();
     }
 
     /**
@@ -499,78 +316,274 @@
     @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();
-        mContextFixture.putStringArrayResource(com.android.internal.R.array.networkAttributes,
-                sNetworkAttributes);
-        final Context contextMock = mContextFixture.getTestDouble();
-        final ConnectivityServiceMock connectivityServiceMock =
-                new ConnectivityServiceMock(contextMock);
-        final ConnectivityManager cm =
-                new ConnectivityManager(contextMock, connectivityServiceMock);
-        mContextFixture.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();
 
         // 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();
+    }
+
+    /* 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 {
+        mHandlerThread = new HandlerThread("PhoneSwitcherTestThread");
+        mHandlerThread.start();
+
+        mContextFixture.putStringArrayResource(com.android.internal.R.array.networkAttributes,
+                sNetworkAttributes);
+
+        setNumPhones(numPhones);
+
+        initializeSubControllerMock();
+        initializeCommandInterfacesMock(numPhones);
+        initializeTelRegistryMock();
+        initializeConnManagerMock();
+
+        mPhoneSwitcher = new PhoneSwitcher(maxActivePhones, numPhones,
+                mContext, mSubscriptionController, mHandlerThread.getLooper(),
+                mTelRegistryMock, mCommandsInterfaces, mPhones);
+
+        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/ServiceStateTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java
index 188b8b7..e631269 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java
@@ -25,6 +25,7 @@
 import static org.mockito.Matchers.anyLong;
 import static org.mockito.Matchers.nullable;
 import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyObject;
 import static org.mockito.Mockito.anyString;
 import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.doReturn;
@@ -216,6 +217,18 @@
 
     @Test
     @MediumTest
+    public void testSetRadioPowerOffUnderDataConnected() {
+        sst.setRadioPower(true);
+        waitForMs(100);
+        doReturn(false).when(mDct).isDisconnected();
+        sst.setRadioPower(false);
+        waitForMs(200);
+        verify(this.mProxyController, times(1)).registerForAllDataDisconnected(anyInt(),
+                 eq(sst), anyInt(), anyObject());
+    }
+
+    @Test
+    @MediumTest
     public void testSetRadioPowerFromCarrier() {
         // Carrier disable radio power
         sst.setRadioPowerFromCarrier(false);
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..42a8ad1
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/TelephonyRegistryTest.java
@@ -0,0 +1,100 @@
+/*
+ * 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 org.junit.Assert.assertEquals;
+
+import android.os.HandlerThread;
+import android.os.ServiceManager;
+import android.telephony.PhoneCapability;
+import android.telephony.PhoneStateListener;
+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;
+
+    public class PhoneStateListenerWrapper extends PhoneStateListener {
+        @Override
+        public void onPhoneCapabilityChanged(PhoneCapability capability) {
+            mPhoneCapability = capability;
+            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");
+        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);
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java b/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
index 6e4257e..2cf880b 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
@@ -185,6 +185,10 @@
     @Mock
     protected AppSmsManager mAppSmsManager;
     @Mock
+    protected IccSmsInterfaceManager mIccSmsInterfaceManager;
+    @Mock
+    protected SmsDispatchersController mSmsDispatchersController;
+    @Mock
     protected DeviceStateMonitor mDeviceStateMonitor;
     @Mock
     protected AccessNetworksManager mAccessNetworksManager;
@@ -213,7 +217,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<>();
@@ -386,6 +390,8 @@
         doReturn(mCarrierSignalAgent).when(mPhone).getCarrierSignalAgent();
         doReturn(mCarrierActionAgent).when(mPhone).getCarrierActionAgent();
         doReturn(mAppSmsManager).when(mPhone).getAppSmsManager();
+        doReturn(mIccSmsInterfaceManager).when(mPhone).getIccSmsInterfaceManager();
+        mIccSmsInterfaceManager.mDispatchersController = mSmsDispatchersController;
         mPhone.mEriManager = mEriManager;
 
         //mUiccController
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..d99bbc1 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/TelephonyNetworkFactoryTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/TelephonyNetworkFactoryTest.java
@@ -24,7 +24,6 @@
 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;
@@ -38,8 +37,6 @@
 import com.android.internal.telephony.mocks.SubscriptionMonitorMock;
 import com.android.internal.telephony.mocks.TelephonyRegistryMock;
 
-import org.junit.Ignore;
-
 public class TelephonyNetworkFactoryTest extends AndroidTestCase {
     private final static String LOG_TAG = "TelephonyNetworkFactoryTest";
 
@@ -153,10 +150,10 @@
 
         makeTnf(phoneId, ts);
 
+        ts.phoneSwitcherMock.setPreferredDataPhoneId(phoneId);
         ts.subscriptionControllerMock.setDefaultDataSubId(subId);
         ts.subscriptionControllerMock.setSlotSubId(phoneId, subId);
         ts.subscriptionMonitorMock.notifySubscriptionChanged(phoneId);
-        ts.subscriptionMonitorMock.notifyDefaultSubscriptionChanged(phoneId);
 
         log("addDefaultRequest");
         ts.connectivityServiceMock.addDefaultRequest();
@@ -248,10 +245,10 @@
 
         makeTnf(phoneId, ts);
 
+        ts.phoneSwitcherMock.setPreferredDataPhoneId(phoneId);
         ts.subscriptionControllerMock.setDefaultDataSubId(subId);
         ts.subscriptionControllerMock.setSlotSubId(phoneId, subId);
         ts.subscriptionMonitorMock.notifySubscriptionChanged(phoneId);
-        ts.subscriptionMonitorMock.notifyDefaultSubscriptionChanged(phoneId);
         waitABit();
 
         if (ts.dcTrackerMock.getNumberOfLiveRequests() != 0) {
@@ -276,9 +273,10 @@
             fail("test 4, LiveRequests != 1");
         }
 
+        ts.phoneSwitcherMock.setPreferredDataPhoneId(altPhoneId);
         ts.subscriptionControllerMock.setDefaultDataSubId(altSubId);
-        ts.subscriptionMonitorMock.notifyDefaultSubscriptionChanged(phoneId);
-        ts.subscriptionMonitorMock.notifyDefaultSubscriptionChanged(altPhoneId);
+        ts.phoneSwitcherMock.notifyActivePhoneChange(phoneId);
+
         waitABit();
         if (ts.dcTrackerMock.getNumberOfLiveRequests() != 0) {
             fail("test 5, LiveRequests != 0");
@@ -311,9 +309,8 @@
         }
 
         ts.subscriptionControllerMock.setDefaultDataSubId(subId);
-        ts.subscriptionMonitorMock.notifyDefaultSubscriptionChanged(phoneId);
-        ts.subscriptionMonitorMock.notifyDefaultSubscriptionChanged(altPhoneId);
-        ts.subscriptionMonitorMock.notifyDefaultSubscriptionChanged(phoneId);
+        ts.phoneSwitcherMock.setPreferredDataPhoneId(phoneId);
+        ts.phoneSwitcherMock.notifyActivePhoneChange(phoneId);
         waitABit();
         if (ts.dcTrackerMock.getNumberOfLiveRequests() != 3) {
             fail("test 10, LiveRequests != 3");
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..694d610
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyNumberTest.java
@@ -0,0 +1,166 @@
+/*
+ * 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",
+                // EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED
+                0,
+                // EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALLING
+                1);
+        assertEquals(number.getNumber(), "911");
+        assertEquals(number.getCountryIso(), "us");
+        assertTrue(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));
+        assertFalse(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(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(1, 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",
+                // EMERGENCY_SERVICE_CATEGORY_MARINE_GUARD
+                8,
+                // EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALLING
+                // EMERGENCY_NUMBER_SOURCE_MODEM
+                5);
+        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(5, 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",
+                // EMERGENCY_SERVICE_CATEGORY_POLICE
+                // EMERGENCY_SERVICE_CATEGORY_AMBULANCE
+                // EMERGENCY_SERVICE_CATEGORY_MIEC
+                35,
+                // EMERGENCY_NUMBER_SOURCE_NETWORK_SINGALING
+                // EMERGENCY_NUMBER_SOURCE_SIM
+                // EMERGENCY_NUMBER_SOURCE_DEFAULT
+                11);
+        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(35, 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(11, 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));
+    }
+}
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 851a217..1e29908 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmInboundSmsHandlerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmInboundSmsHandlerTest.java
@@ -570,6 +570,10 @@
         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());
     }
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 bad56e7..759309c 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneCallTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneCallTrackerTest.java
@@ -515,8 +515,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 +535,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
@@ -846,5 +851,88 @@
                 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");
+    }
+
+    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/ImsPhoneTest.java b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneTest.java
index e5bad6d..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;
@@ -58,6 +60,7 @@
 import com.android.ims.ImsManager;
 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;
@@ -303,10 +306,11 @@
         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/PhoneSwitcherMock.java b/tests/telephonytests/src/com/android/internal/telephony/mocks/PhoneSwitcherMock.java
index ca7e680..7f995f9 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/mocks/PhoneSwitcherMock.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/mocks/PhoneSwitcherMock.java
@@ -77,7 +77,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[phoneId].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 1697abd..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,7 +255,7 @@
     }
     @Override
     public boolean isActiveSubId(int subId) {
-        throw new RuntimeException("not implemented");
+        return getPhoneId(subId) != INVALID_PHONE_INDEX;
     }
     @Override
     public int getSimStateForSlotIndex(int slotIndex) {