Merge kwd to master

Change-Id: Idb607c0aa32f80fe4fe1539aedea7a221e9e7f04
diff --git a/Android.mk b/Android.mk
index b0ab1d0..449faa0 100644
--- a/Android.mk
+++ b/Android.mk
@@ -24,7 +24,7 @@
 	$(call all-Iaidl-files-under, src/java) \
 	$(call all-logtags-files-under, src/java)
 
-LOCAL_JAVA_LIBRARIES := voip-common
+LOCAL_JAVA_LIBRARIES := voip-common ims-common
 LOCAL_MODULE_TAGS := optional
 LOCAL_MODULE := telephony-common
 
diff --git a/CleanSpec.mk b/CleanSpec.mk
index c5b95a1..4f5f260 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -44,6 +44,7 @@
 $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/telephony-common_intermediates)
 $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/telephony-common_intermediates)
 $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/telephony-common_intermediates)
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/telephony-common_intermediates)
 
 # ************************************************
 # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
diff --git a/src/java/android/provider/Telephony.java b/src/java/android/provider/Telephony.java
index 82ad215..ae15b3a 100644
--- a/src/java/android/provider/Telephony.java
+++ b/src/java/android/provider/Telephony.java
@@ -27,10 +27,12 @@
 import android.database.sqlite.SqliteWrapper;
 import android.net.Uri;
 import android.telephony.SmsMessage;
+import android.telephony.SubscriptionManager;
 import android.text.TextUtils;
 import android.telephony.Rlog;
 import android.util.Patterns;
 
+import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.SmsApplication;
 
 
@@ -238,7 +240,14 @@
         public static final String LOCKED = "locked";
 
         /**
-         * Error code associated with sending or receiving this message.
+         * The sub_id to which the message belongs to
+         * <p>Type: INTEGER (long) </p>
+         * @hide
+         */
+        public static final String SUB_ID = "sub_id";
+
+        /**
+         * Error code associated with sending or receiving this message
          * <P>Type: INTEGER</P>
          */
         public static final String ERROR_CODE = "error_code";
@@ -314,7 +323,29 @@
         public static Uri addMessageToUri(ContentResolver resolver,
                 Uri uri, String address, String body, String subject,
                 Long date, boolean read, boolean deliveryReport) {
-            return addMessageToUri(resolver, uri, address, body, subject,
+            return addMessageToUri(SubscriptionManager.getPreferredSmsSubId(),
+                    resolver, uri, address, body, subject, date, read, deliveryReport, -1L);
+        }
+
+        /**
+         * Add an SMS to the given URI.
+         *
+         * @param resolver the content resolver to use
+         * @param uri the URI to add the message to
+         * @param address the address of the sender
+         * @param body the body of the message
+         * @param subject the psuedo-subject of the message
+         * @param date the timestamp for the message
+         * @param read true if the message has been read, false if not
+         * @param deliveryReport true if a delivery report was requested, false if not
+         * @param subId the sub_id which the message belongs to
+         * @return the URI for the new message
+         * @hide
+         */
+        public static Uri addMessageToUri(long subId, ContentResolver resolver,
+                Uri uri, String address, String body, String subject,
+                Long date, boolean read, boolean deliveryReport) {
+            return addMessageToUri(subId, resolver, uri, address, body, subject,
                     date, read, deliveryReport, -1L);
         }
 
@@ -336,8 +367,34 @@
         public static Uri addMessageToUri(ContentResolver resolver,
                 Uri uri, String address, String body, String subject,
                 Long date, boolean read, boolean deliveryReport, long threadId) {
-            ContentValues values = new ContentValues(7);
+            return addMessageToUri(SubscriptionManager.getPreferredSmsSubId(),
+                    resolver, uri, address, body, subject,
+                    date, read, deliveryReport, threadId);
+        }
 
+        /**
+         * Add an SMS to the given URI with thread_id specified.
+         *
+         * @param resolver the content resolver to use
+         * @param uri the URI to add the message to
+         * @param address the address of the sender
+         * @param body the body of the message
+         * @param subject the psuedo-subject of the message
+         * @param date the timestamp for the message
+         * @param read true if the message has been read, false if not
+         * @param deliveryReport true if a delivery report was requested, false if not
+         * @param threadId the thread_id of the message
+         * @param subId the sub_id which the message belongs to
+         * @return the URI for the new message
+         * @hide
+         */
+        public static Uri addMessageToUri(long subId, ContentResolver resolver,
+                Uri uri, String address, String body, String subject,
+                Long date, boolean read, boolean deliveryReport, long threadId) {
+            ContentValues values = new ContentValues(8);
+            Rlog.v(TAG,"Telephony addMessageToUri sub id: " + subId);
+
+            values.put(SUB_ID, subId);
             values.put(ADDRESS, address);
             if (date != null) {
                 values.put(DATE, date);
@@ -450,7 +507,26 @@
             public static Uri addMessage(ContentResolver resolver,
                     String address, String body, String subject, Long date,
                     boolean read) {
-                return addMessageToUri(resolver, CONTENT_URI, address, body,
+                return addMessageToUri(SubscriptionManager.getPreferredSmsSubId(),
+                        resolver, CONTENT_URI, address, body, subject, date, read, false);
+            }
+
+            /**
+             * Add an SMS to the Draft box.
+             *
+             * @param resolver the content resolver to use
+             * @param address the address of the sender
+             * @param body the body of the message
+             * @param subject the psuedo-subject of the message
+             * @param date the timestamp for the message
+             * @param read true if the message has been read, false if not
+             * @param subId the sub_id which the message belongs to
+             * @return the URI for the new message
+             * @hide
+             */
+            public static Uri addMessage(long subId, ContentResolver resolver,
+                    String address, String body, String subject, Long date, boolean read) {
+                return addMessageToUri(subId, resolver, CONTENT_URI, address, body,
                         subject, date, read, false);
             }
         }
@@ -490,7 +566,25 @@
              */
             public static Uri addMessage(ContentResolver resolver,
                     String address, String body, String subject, Long date) {
-                return addMessageToUri(resolver, CONTENT_URI, address, body,
+                return addMessageToUri(SubscriptionManager.getPreferredSmsSubId(),
+                        resolver, CONTENT_URI, address, body, subject, date, true, false);
+            }
+
+            /**
+             * Add an SMS to the Draft box.
+             *
+             * @param resolver the content resolver to use
+             * @param address the address of the sender
+             * @param body the body of the message
+             * @param subject the psuedo-subject of the message
+             * @param date the timestamp for the message
+             * @param subId the sub_id which the message belongs to
+             * @return the URI for the new message
+             * @hide
+             */
+            public static Uri addMessage(long subId, ContentResolver resolver,
+                    String address, String body, String subject, Long date) {
+                return addMessageToUri(subId, resolver, CONTENT_URI, address, body,
                         subject, date, true, false);
             }
         }
@@ -512,6 +606,33 @@
              */
             public static final Uri CONTENT_URI = Uri.parse("content://sms/draft");
 
+           /**
+            * @hide
+            */
+            public static Uri addMessage(ContentResolver resolver,
+                    String address, String body, String subject, Long date) {
+                return addMessageToUri(SubscriptionManager.getPreferredSmsSubId(),
+                        resolver, CONTENT_URI, address, body, subject, date, true, false);
+            }
+
+            /**
+             * Add an SMS to the Draft box.
+             *
+             * @param resolver the content resolver to use
+             * @param address the address of the sender
+             * @param body the body of the message
+             * @param subject the psuedo-subject of the message
+             * @param date the timestamp for the message
+             * @param subId the sub_id which the message belongs to
+             * @return the URI for the new message
+             * @hide
+             */
+            public static Uri addMessage(long subId, ContentResolver resolver,
+                    String address, String body, String subject, Long date) {
+                return addMessageToUri(subId, resolver, CONTENT_URI, address, body,
+                        subject, date, true, false);
+            }
+
             /**
              * The default sort order for this table.
              */
@@ -555,7 +676,28 @@
             public static Uri addMessage(ContentResolver resolver,
                     String address, String body, String subject, Long date,
                     boolean deliveryReport, long threadId) {
-                return addMessageToUri(resolver, CONTENT_URI, address, body,
+                return addMessageToUri(SubscriptionManager.getPreferredSmsSubId(),
+                        resolver, CONTENT_URI, address, body, subject, date,
+                        true, deliveryReport, threadId);
+            }
+
+            /**
+             * Add an SMS to the Out box.
+             *
+             * @param resolver the content resolver to use
+             * @param address the address of the sender
+             * @param body the body of the message
+             * @param subject the psuedo-subject of the message
+             * @param date the timestamp for the message
+             * @param deliveryReport whether a delivery report was requested for the message
+             * @param subId the sub_id which the message belongs to
+             * @return the URI for the new message
+             * @hide
+             */
+            public static Uri addMessage(long subId, ContentResolver resolver,
+                    String address, String body, String subject, Long date,
+                    boolean deliveryReport, long threadId) {
+                return addMessageToUri(subId, resolver, CONTENT_URI, address, body,
                         subject, date, true, deliveryReport, threadId);
             }
         }
@@ -888,6 +1030,9 @@
             public static SmsMessage[] getMessagesFromIntent(Intent intent) {
                 Object[] messages = (Object[]) intent.getSerializableExtra("pdus");
                 String format = intent.getStringExtra("format");
+                long subId = intent.getLongExtra(PhoneConstants.SUBSCRIPTION_KEY, 0);
+
+                Rlog.v(TAG, " getMessagesFromIntent sub_id : " + subId);
 
                 int pduCount = messages.length;
                 SmsMessage[] msgs = new SmsMessage[pduCount];
@@ -895,6 +1040,7 @@
                 for (int i = 0; i < pduCount; i++) {
                     byte[] pdu = (byte[]) messages[i];
                     msgs[i] = SmsMessage.createFromPdu(pdu, format);
+                    msgs[i].setSubId(subId);
                 }
                 return msgs;
             }
@@ -1452,6 +1598,14 @@
          * <P>Type: INTEGER (boolean)</P>
          */
         public static final String LOCKED = "locked";
+
+        /**
+         * The sub id to which message belongs to
+         * <p>Type: INTEGER</p>
+         * @hide
+         */
+        public static final String SUB_ID = "sub_id";
+
     }
 
     /**
@@ -2181,6 +2335,13 @@
              * <P>Type: INTEGER (long)</P>
              */
             public static final String LAST_TRY = "last_try";
+
+            /**
+             * The sub_id to which the pending message belongs to
+             * <p>Type: INTEGER (long) </p>
+             * @hide
+             */
+            public static final String SUB_ID = "pending_sub_id";
         }
 
         /**
@@ -2387,6 +2548,14 @@
          * <P>Type: TEXT</P>
          */
         public static final String MVNO_MATCH_DATA = "mvno_match_data";
+
+        /**
+         * The sub_id to which the APN belongs to
+         * <p>Type: INTEGER (long) </p>
+         * @hide
+         */
+        public static final String SUB_ID = "sub_id";
+
     }
 
     /**
diff --git a/src/java/android/telephony/CellBroadcastMessage.java b/src/java/android/telephony/CellBroadcastMessage.java
index af11bc4..1d30608 100644
--- a/src/java/android/telephony/CellBroadcastMessage.java
+++ b/src/java/android/telephony/CellBroadcastMessage.java
@@ -49,6 +49,31 @@
     private final long mDeliveryTime;
     private boolean mIsRead;
 
+    /**
+     * Indicates the subId
+     *
+     * @hide
+     */
+    private long mSubId = 0;
+
+    /**
+     * set Subscription information
+     *
+     * @hide
+     */
+    public void setSubId(long subId) {
+        mSubId = subId;
+    }
+
+    /**
+     * get Subscription information
+     *
+     * @hide
+     */
+    public long getSubId() {
+        return mSubId;
+    }
+
     public CellBroadcastMessage(SmsCbMessage message) {
         mSmsCbMessage = message;
         mDeliveryTime = System.currentTimeMillis();
diff --git a/src/java/android/telephony/SmsManager.java b/src/java/android/telephony/SmsManager.java
index 3af756e..8fd48f4 100644
--- a/src/java/android/telephony/SmsManager.java
+++ b/src/java/android/telephony/SmsManager.java
@@ -47,6 +47,7 @@
 public final class SmsManager {
     /** Singleton object constructed during class initialization. */
     private static final SmsManager sInstance = new SmsManager();
+    private static final int DEFAULT_SUB = 0;
 
     /**
      * Send a text based SMS.
@@ -87,6 +88,42 @@
     public void sendTextMessage(
             String destinationAddress, String scAddress, String text,
             PendingIntent sentIntent, PendingIntent deliveryIntent) {
+        sendTextMessage(getPreferredSmsSubscription(), destinationAddress, scAddress, text,
+           sentIntent, deliveryIntent);
+    }
+
+    /**
+     * Send a text based SMS.
+     *
+     * @param destinationAddress the address to send the message to
+     * @param scAddress is the service center address or null to use
+     *  the current default SMSC
+     * @param text the body of the message to send
+     * @param sentIntent if not NULL this <code>PendingIntent</code> is
+     *  broadcast when the message is successfully sent, or failed.
+     *  The result code will be <code>Activity.RESULT_OK</code> for success,
+     *  or one of these errors:<br>
+     *  <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
+     *  <code>RESULT_ERROR_RADIO_OFF</code><br>
+     *  <code>RESULT_ERROR_NULL_PDU</code><br>
+     *  For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
+     *  the extra "errorCode" containing a radio technology specific value,
+     *  generally only useful for troubleshooting.<br>
+     *  The per-application based SMS control checks sentIntent. If sentIntent
+     *  is NULL the caller will be checked against all unknown applications,
+     *  which cause smaller number of SMS to be sent in checking period.
+     * @param deliveryIntent if not NULL this <code>PendingIntent</code> is
+     *  broadcast when the message is delivered to the recipient.  The
+     *  raw pdu of the status report is in the extended data ("pdu").
+     * @param subId on which the SMS has to be sent.
+     *
+     * @throws IllegalArgumentException if destinationAddress or text are empty
+     *
+     */
+    /** @hide */
+    public void sendTextMessage(
+            long subId, String destinationAddress, String scAddress, String text,
+            PendingIntent sentIntent, PendingIntent deliveryIntent) {
         if (TextUtils.isEmpty(destinationAddress)) {
             throw new IllegalArgumentException("Invalid destinationAddress");
         }
@@ -165,6 +202,47 @@
     public void sendMultipartTextMessage(
             String destinationAddress, String scAddress, ArrayList<String> parts,
             ArrayList<PendingIntent> sentIntents, ArrayList<PendingIntent> deliveryIntents) {
+        sendMultipartTextMessage(getPreferredSmsSubscription(), destinationAddress, scAddress, parts, sentIntents,
+            deliveryIntents);
+    }
+
+    /**
+     * Send a multi-part text based SMS.  The callee should have already
+     * divided the message into correctly sized parts by calling
+     * <code>divideMessage</code>.
+     *
+     * @param destinationAddress the address to send the message to
+     * @param scAddress is the service center address or null to use
+     *   the current default SMSC
+     * @param parts an <code>ArrayList</code> of strings that, in order,
+     *   comprise the original message
+     * @param sentIntents if not null, an <code>ArrayList</code> of
+     *   <code>PendingIntent</code>s (one for each message part) that is
+     *   broadcast when the corresponding message part has been sent.
+     *   The result code will be <code>Activity.RESULT_OK</code> for success,
+     *   or one of these errors:<br>
+     *   <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
+     *   <code>RESULT_ERROR_RADIO_OFF</code><br>
+     *   <code>RESULT_ERROR_NULL_PDU</code><br>
+     *   For <code>RESULT_ERROR_GENERIC_FAILURE</code> each sentIntent may include
+     *   the extra "errorCode" containing a radio technology specific value,
+     *   generally only useful for troubleshooting.<br>
+     *   The per-application based SMS control checks sentIntent. If sentIntent
+     *   is NULL the caller will be checked against all unknown applications,
+     *   which cause smaller number of SMS to be sent in checking period.
+     * @param deliveryIntents if not null, an <code>ArrayList</code> of
+     *   <code>PendingIntent</code>s (one for each message part) that is
+     *   broadcast when the corresponding message part has been delivered
+     *   to the recipient.  The raw pdu of the status report is in the
+     *   extended data ("pdu").
+     *   @param subId on which the SMS has to be sent.
+     *
+     * @throws IllegalArgumentException if destinationAddress or data are empty
+     */
+    /** @hide */
+    public void sendMultipartTextMessage(long subId, String destinationAddress, String scAddress,
+            ArrayList<String> parts, ArrayList<PendingIntent> sentIntents,
+            ArrayList<PendingIntent> deliveryIntents) {
         if (TextUtils.isEmpty(destinationAddress)) {
             throw new IllegalArgumentException("Invalid destinationAddress");
         }
@@ -228,6 +306,43 @@
     public void sendDataMessage(
             String destinationAddress, String scAddress, short destinationPort,
             byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) {
+        sendDataMessage(getPreferredSmsSubscription(),
+            destinationAddress, scAddress, destinationPort,
+            data, sentIntent, deliveryIntent);
+    }
+
+    /**
+     * Send a data based SMS to a specific application port.
+     *
+     * @param destinationAddress the address to send the message to
+     * @param scAddress is the service center address or null to use
+     *  the current default SMSC
+     * @param destinationPort the port to deliver the message to
+     * @param data the body of the message to send
+     * @param sentIntent if not NULL this <code>PendingIntent</code> is
+     *  broadcast when the message is successfully sent, or failed.
+     *  The result code will be <code>Activity.RESULT_OK</code> for success,
+     *  or one of these errors:<br>
+     *  <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
+     *  <code>RESULT_ERROR_RADIO_OFF</code><br>
+     *  <code>RESULT_ERROR_NULL_PDU</code><br>
+     *  For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
+     *  the extra "errorCode" containing a radio technology specific value,
+     *  generally only useful for troubleshooting.<br>
+     *  The per-application based SMS control checks sentIntent. If sentIntent
+     *  is NULL the caller will be checked against all unknown applications,
+     *  which cause smaller number of SMS to be sent in checking period.
+     * @param deliveryIntent if not NULL this <code>PendingIntent</code> is
+     *  broadcast when the message is delivered to the recipient.  The
+     *  raw pdu of the status report is in the extended data ("pdu").
+     *  @param subId on which the SMS has to be sent.
+     *
+     * @throws IllegalArgumentException if destinationAddress or data are empty
+     */
+    /** @hide */
+    public void sendDataMessage(long subId,
+            String destinationAddress, String scAddress, short destinationPort,
+            byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) {
         if (TextUtils.isEmpty(destinationAddress)) {
             throw new IllegalArgumentException("Invalid destinationAddress");
         }
@@ -289,7 +404,28 @@
      * @throws IllegalArgumentException if pdu is NULL
      * {@hide}
      */
-    public boolean copyMessageToIcc(byte[] smsc, byte[] pdu, int status) {
+    public boolean copyMessageToIcc(byte[] smsc, byte[] pdu,int status) {
+        return copyMessageToIcc(getPreferredSmsSubscription(), smsc, pdu, status);
+    }
+
+    /**
+     * Copy a raw SMS PDU to the ICC on  subId.
+     * ICC (Integrated Circuit Card) is the card of the device.
+     * For example, this can be the SIM or USIM for GSM.
+     *
+     * @param smsc the SMSC for this message, or NULL for the default SMSC
+     * @param pdu the raw PDU to store
+     * @param status message status (STATUS_ON_ICC_READ, STATUS_ON_ICC_UNREAD,
+     *               STATUS_ON_ICC_SENT, STATUS_ON_ICC_UNSENT)
+     * @param subId from which SMS has to be copied.
+     * @return true for success
+     *
+     * @throws IllegalArgumentException if pdu is NULL
+     * {@hide}
+     */
+
+    /** @hide */
+    public boolean copyMessageToIcc(long subId, byte[] smsc, byte[] pdu, int status) {
         boolean success = false;
 
         if (null == pdu) {
@@ -320,6 +456,22 @@
      */
     public boolean
     deleteMessageFromIcc(int messageIndex) {
+        return deleteMessageFromIcc(getPreferredSmsSubscription(), messageIndex);
+    }
+
+    /**
+     * Delete the specified message from the ICC on  subId.
+     * ICC (Integrated Circuit Card) is the card of the device.
+     * For example, this can be the SIM or USIM for GSM.
+     *
+     * @param messageIndex is the record index of the message on ICC
+     * @param subId from which SMS has to be deleted.
+     * @return true for success
+     *
+     */
+    /** @hide */
+    public boolean
+    deleteMessageFromIcc(long subId, int messageIndex) {
         boolean success = false;
         byte[] pdu = new byte[IccConstants.SMS_RECORD_LENGTH-1];
         Arrays.fill(pdu, (byte)0xff);
@@ -352,6 +504,26 @@
      * {@hide}
      */
     public boolean updateMessageOnIcc(int messageIndex, int newStatus, byte[] pdu) {
+        return updateMessageOnIcc(getPreferredSmsSubscription(), messageIndex, newStatus, pdu);
+    }
+
+    /**
+     * Update the specified message on the ICC on  subId.
+     * ICC (Integrated Circuit Card) is the card of the device.
+     * For example, this can be the SIM or USIM for GSM.
+     *
+     * @param messageIndex record index of message to update
+     * @param newStatus new message status (STATUS_ON_ICC_READ,
+     *                  STATUS_ON_ICC_UNREAD, STATUS_ON_ICC_SENT,
+     *                  STATUS_ON_ICC_UNSENT, STATUS_ON_ICC_FREE)
+     * @param pdu the raw PDU to store
+     * @param subId on which the SMS had to be updated.
+     * @return true for success
+     *
+     */
+    /** @hide */
+    public boolean updateMessageOnIcc(long subId, int messageIndex, int newStatus,
+                           byte[] pdu)   {
         boolean success = false;
 
         try {
@@ -377,6 +549,21 @@
      * {@hide}
      */
     public static ArrayList<SmsMessage> getAllMessagesFromIcc() {
+        return getAllMessagesFromIcc(getPreferredSmsSubscription());
+    }
+
+    /**
+     * Retrieves all messages currently stored on ICC on the default
+     * subId.
+     * ICC (Integrated Circuit Card) is the card of the device.
+     * For example, this can be the SIM or USIM for GSM.
+     *
+     * @param subId from which the messages had to be retrieved.
+     * @return <code>ArrayList</code> of <code>SmsMessage</code> objects
+     *
+     */
+    /** @hide */
+    public static ArrayList<SmsMessage> getAllMessagesFromIcc(long subId) {
         List<SmsRawData> records = null;
 
         try {
@@ -408,6 +595,28 @@
      * {@hide}
      */
     public boolean enableCellBroadcast(int messageIdentifier) {
+        return enableCellBroadcast(getPreferredSmsSubscription(), messageIdentifier);
+    }
+
+    /**
+     * Enable reception of cell broadcast (SMS-CB) messages with the given
+     * message identifier on a particular subId.
+     * Note that if two different clients enable the same
+     * message identifier, they must both disable it for the device to stop
+     * receiving those messages. All received messages will be broadcast in an
+     * intent with the action "android.provider.telephony.SMS_CB_RECEIVED".
+     * Note: This call is blocking, callers may want to avoid calling it from
+     * the main thread of an application.
+     *
+     * @param messageIdentifier Message identifier as specified in TS 23.041 (3GPP)
+     * or C.R1001-G (3GPP2)
+     * @param subId for which the broadcast has to be enabled
+     * @return true if successful, false otherwise
+     * @see #disableCellBroadcast(int)
+     *
+     */
+     /** @hide */
+    public boolean enableCellBroadcast(long subId, int messageIdentifier) {
         boolean success = false;
 
         try {
@@ -439,6 +648,27 @@
      * {@hide}
      */
     public boolean disableCellBroadcast(int messageIdentifier) {
+        return disableCellBroadcast(getPreferredSmsSubscription(), messageIdentifier);
+    }
+
+    /**
+     * Disable reception of cell broadcast (SMS-CB) messages with the given
+     * message identifier on a particular subId.
+     * Note that if two different clients enable the same
+     * message identifier, they must both disable it for the device to stop
+     * receiving those messages.
+     * Note: This call is blocking, callers may want to avoid calling it from
+     * the main thread of an application.
+     *
+     * @param messageIdentifier Message identifier as specified in TS 23.041
+     * @param subId for which the broadcast has to be disabled
+     * @return true if successful, false otherwise
+     *
+     * @see #enableCellBroadcast(int)
+     *
+     */
+    /** @hide */
+    public boolean disableCellBroadcast(long subId, int messageIdentifier) {
         boolean success = false;
 
         try {
@@ -473,6 +703,30 @@
      * {@hide}
      */
     public boolean enableCellBroadcastRange(int startMessageId, int endMessageId) {
+        return enableCellBroadcastRange(getPreferredSmsSubscription(), startMessageId,
+                endMessageId);
+    }
+
+    /**
+     * Enable reception of cell broadcast (SMS-CB) messages with the given
+     * message identifier range on a particular subId.
+     * Note that if two different clients enable the same
+     * message identifier, they must both disable it for the device to stop
+     * receiving those messages. All received messages will be broadcast in an
+     * intent with the action "android.provider.Telephony.SMS_CB_RECEIVED".
+     * Note: This call is blocking, callers may want to avoid calling it from
+     * the main thread of an application.
+     *
+     * @param startMessageId first message identifier as specified in TS 23.041
+     * @param endMessageId last message identifier as specified in TS 23.041
+     * @return true if successful, false otherwise
+     * @see #disableCellBroadcastRange(int, int)
+     * @throws IllegalArgumentException if endMessageId < startMessageId
+     *
+     */
+    /** @hide */
+    public boolean enableCellBroadcastRange(long subId, int startMessageId,
+            int endMessageId) {
         boolean success = false;
 
         if (endMessageId < startMessageId) {
@@ -510,6 +764,30 @@
      * {@hide}
      */
     public boolean disableCellBroadcastRange(int startMessageId, int endMessageId) {
+        return disableCellBroadcastRange(getPreferredSmsSubscription(), startMessageId,
+                endMessageId);
+    }
+
+    /**
+     * Disable reception of cdma broadcast messages with the given
+     * message identifier range on a particular subId.
+     * Note that if two different clients enable the same
+     * message identifier range, they must both disable it for the device to stop
+     * receiving those messages.
+     * Note: This call is blocking, callers may want to avoid calling it from
+     * the main thread of an application.
+     *
+     * @param startMessageId first message identifier as specified in TS 23.041
+     * @param endMessageId last message identifier as specified in TS 23.041
+     * @return true if successful, false otherwise
+     *
+     * @see #enableCellBroadcastRange(int, int)
+     * @throws IllegalArgumentException if endMessageId < startMessageId
+     *
+     */
+    /** @hide */
+    public boolean disableCellBroadcastRange(long subId, int startMessageId,
+            int endMessageId) {
         boolean success = false;
 
         if (endMessageId < startMessageId) {
@@ -564,6 +842,11 @@
      * @hide
      */
     boolean isImsSmsSupported() {
+        return isImsSmsSupported(getPreferredSmsSubscription());
+    }
+
+    /** @hide */
+    boolean isImsSmsSupported(long subId) {
         boolean boSupported = false;
         try {
             ISms iccISms = getISmsService();
@@ -589,6 +872,11 @@
      * @hide
      */
     String getImsSmsFormat() {
+        return getImsSmsFormat(getPreferredSmsSubscription());
+    }
+
+    /** @hide */
+    String getImsSmsFormat(long subId) {
         String format = com.android.internal.telephony.SmsConstants.FORMAT_UNKNOWN;
         try {
             ISms iccISms = getISmsService();
@@ -601,6 +889,42 @@
         return format;
     }
 
+    /**
+     * Get the preferred sms subId
+     *
+     * @return the preferred subId
+     * @hide
+     */
+    public static long getPreferredSmsSubscription() {
+        ISms iccISms = null;
+        try {
+            iccISms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
+            return (long) iccISms.getPreferredSmsSubscription();
+        } catch (RemoteException ex) {
+            return DEFAULT_SUB;
+        } catch (NullPointerException ex) {
+            return DEFAULT_SUB;
+        }
+    }
+
+    /**
+     * Get SMS prompt property,  enabled or not
+     *
+     * @return true if enabled, false otherwise
+     * @hide
+     */
+    public boolean isSMSPromptEnabled() {
+        ISms iccISms = null;
+        try {
+            iccISms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
+            return iccISms.isSMSPromptEnabled();
+        } catch (RemoteException ex) {
+            return false;
+        } catch (NullPointerException ex) {
+            return false;
+        }
+    }
+
     // see SmsMessage.getStatusOnIcc
 
     /** Free space (TS 51.011 10.5.3 / 3GPP2 C.S0023 3.4.27). */
diff --git a/src/java/android/telephony/SmsMessage.java b/src/java/android/telephony/SmsMessage.java
index 12d6949..64368aa 100644
--- a/src/java/android/telephony/SmsMessage.java
+++ b/src/java/android/telephony/SmsMessage.java
@@ -96,6 +96,28 @@
      */
     public SmsMessageBase mWrappedSmsMessage;
 
+    /** Indicates the subId
+     *
+     * @hide
+     */
+    private long mSubId = 0;
+
+    /** set Subscription information
+     *
+     * @hide
+     */
+    public void setSubId(long subId) {
+        mSubId = subId;
+    }
+
+    /** get Subscription information
+     *
+     * @hide
+     */
+    public long getSubId() {
+        return mSubId;
+    }
+
     public static class SubmitPdu {
 
         public byte[] encodedScAddress; // Null if not applicable.
diff --git a/src/java/com/android/internal/telephony/BaseCommands.java b/src/java/com/android/internal/telephony/BaseCommands.java
index 19ff4ed..47dcc99 100644
--- a/src/java/com/android/internal/telephony/BaseCommands.java
+++ b/src/java/com/android/internal/telephony/BaseCommands.java
@@ -18,6 +18,7 @@
 
 
 import android.content.Context;
+import android.os.Message;
 import android.os.RegistrantList;
 import android.os.Registrant;
 import android.os.Handler;
@@ -64,6 +65,9 @@
     protected RegistrantList mRilConnectedRegistrants = new RegistrantList();
     protected RegistrantList mIccRefreshRegistrants = new RegistrantList();
     protected RegistrantList mRilCellInfoListRegistrants = new RegistrantList();
+    protected RegistrantList mSubscriptionStatusRegistrants = new RegistrantList();
+    protected RegistrantList mSrvccStateRegistrants = new RegistrantList();
+    protected RegistrantList mHardwareConfigChangeRegistrants = new RegistrantList();
 
     protected Registrant mGsmSmsRegistrant;
     protected Registrant mCdmaSmsRegistrant;
@@ -636,6 +640,17 @@
         mExitEmergencyCallbackModeRegistrants.remove(h);
     }
 
+    @Override
+    public void registerForHardwareConfigChanged(Handler h, int what, Object obj) {
+        Registrant r = new Registrant (h, what, obj);
+        mHardwareConfigChangeRegistrants.add(r);
+    }
+
+    @Override
+    public void unregisterForHardwareConfigChanged(Handler h) {
+        mHardwareConfigChangeRegistrants.remove(h);
+    }
+
     /**
      * {@inheritDoc}
      */
@@ -653,6 +668,15 @@
         mRilConnectedRegistrants.remove(h);
     }
 
+     public void registerForSubscriptionStatusChanged(Handler h, int what, Object obj) {
+         Registrant r = new Registrant (h, what, obj);
+         mSubscriptionStatusRegistrants.add(r);
+     }
+
+     public void unregisterForSubscriptionStatusChanged(Handler h) {
+         mSubscriptionStatusRegistrants.remove(h);
+     }
+
     //***** Protected Methods
     /**
      * Store new RadioState and send notification based on the changes
@@ -724,10 +748,29 @@
     }
 
     @Override
+    public void registerForSrvccStateChanged(Handler h, int what, Object obj) {
+        Registrant r = new Registrant (h, what, obj);
+
+        mSrvccStateRegistrants.add(r);
+    }
+
+    @Override
+    public void unregisterForSrvccStateChanged(Handler h) {
+        mSrvccStateRegistrants.remove(h);
+    }
+
+    @Override
     public void testingEmergencyCall() {}
 
     @Override
     public int getRilVersion() {
         return mRilVersion;
     }
+
+    public void setUiccSubscription(int slotId, int appIndex, int subId, int subStatus,
+            Message response) {
+    }
+
+    public void setDataAllowed(boolean allowed, Message response) {
+    }
 }
diff --git a/src/java/com/android/internal/telephony/Call.java b/src/java/com/android/internal/telephony/Call.java
index b83d33e..46df399 100644
--- a/src/java/com/android/internal/telephony/Call.java
+++ b/src/java/com/android/internal/telephony/Call.java
@@ -45,6 +45,9 @@
         }
     }
 
+    public enum SrvccState {
+        NONE, STARTED, COMPLETED, FAILED, CANCELED;
+    }
 
     /* Instance Variables */
 
diff --git a/src/java/com/android/internal/telephony/CallManager.java b/src/java/com/android/internal/telephony/CallManager.java
index aa4cca8..637b6eb 100644
--- a/src/java/com/android/internal/telephony/CallManager.java
+++ b/src/java/com/android/internal/telephony/CallManager.java
@@ -26,6 +26,8 @@
 import android.os.RegistrantList;
 import android.os.Registrant;
 import android.telephony.PhoneNumberUtils;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
 import android.telephony.PhoneStateListener;
 import android.telephony.ServiceState;
 import android.telephony.Rlog;
@@ -79,6 +81,7 @@
     private static final int EVENT_SUPP_SERVICE_FAILED = 117;
     private static final int EVENT_SERVICE_STATE_CHANGED = 118;
     private static final int EVENT_POST_DIAL_CHARACTER = 119;
+    private static final int EVENT_ONHOLD_TONE = 120;
 
     // Singleton instance
     private static final CallManager INSTANCE = new CallManager();
@@ -125,6 +128,9 @@
     protected final RegistrantList mRingbackToneRegistrants
     = new RegistrantList();
 
+    protected final RegistrantList mOnHoldToneRegistrants
+    = new RegistrantList();
+
     protected final RegistrantList mInCallVoicePrivacyOnRegistrants
     = new RegistrantList();
 
@@ -232,6 +238,21 @@
     }
 
     /**
+     * get Phone object corresponds to subId
+     * @return Phone
+     */
+    private Phone getPhone(long subId) {
+        Phone p = null;
+        for (Phone phone : mPhones) {
+            if (phone.getSubId() == subId && !(phone instanceof VoicePhone)) {
+                p = phone;
+                break;
+            }
+        }
+        return p;
+    }
+
+    /**
      * Get current coarse-grained voice call state.
      * If the Call Manager has an active call and call waiting occurs,
      * then the phone state is RINGING not OFFHOOK
@@ -251,6 +272,27 @@
     }
 
     /**
+     * Get current coarse-grained voice call state on a subId.
+     * If the Call Manager has an active call and call waiting occurs,
+     * then the phone state is RINGING not OFFHOOK
+     *
+     */
+    public PhoneConstants.State getState(long subId) {
+        PhoneConstants.State s = PhoneConstants.State.IDLE;
+
+        for (Phone phone : mPhones) {
+            if (phone.getSubId() == subId) {
+                if (phone.getState() == PhoneConstants.State.RINGING) {
+                    s = PhoneConstants.State.RINGING;
+                } else if (phone.getState() == PhoneConstants.State.OFFHOOK) {
+                    if (s == PhoneConstants.State.IDLE) s = PhoneConstants.State.OFFHOOK;
+                }
+            }
+        }
+        return s;
+    }
+
+    /**
      * @return the service state of CallManager, which represents the
      * highest priority state of all the service states of phones
      *
@@ -286,6 +328,65 @@
     }
 
     /**
+     * @return the Phone service state corresponds to subId
+     */
+    public int getServiceState(long subId) {
+        int resultState = ServiceState.STATE_OUT_OF_SERVICE;
+
+        for (Phone phone : mPhones) {
+            if (phone.getSubId() == subId) {
+                int serviceState = phone.getServiceState().getState();
+                if (serviceState == ServiceState.STATE_IN_SERVICE) {
+                    // IN_SERVICE has the highest priority
+                    resultState = serviceState;
+                    break;
+                } else if (serviceState == ServiceState.STATE_OUT_OF_SERVICE) {
+                    // OUT_OF_SERVICE replaces EMERGENCY_ONLY and POWER_OFF
+                    // Note: EMERGENCY_ONLY is not in use at this moment
+                    if ( resultState == ServiceState.STATE_EMERGENCY_ONLY ||
+                            resultState == ServiceState.STATE_POWER_OFF) {
+                        resultState = serviceState;
+                    }
+                } else if (serviceState == ServiceState.STATE_EMERGENCY_ONLY) {
+                    if (resultState == ServiceState.STATE_POWER_OFF) {
+                        resultState = serviceState;
+                    }
+                }
+            }
+        }
+        return resultState;
+    }
+
+    /**
+     * @return the phone associated with any call
+     */
+    public Phone getPhoneInCall() {
+        Phone phone = null;
+        if (!getFirstActiveRingingCall().isIdle()) {
+            phone = getFirstActiveRingingCall().getPhone();
+        } else if (!getActiveFgCall().isIdle()) {
+            phone = getActiveFgCall().getPhone();
+        } else {
+            // If BG call is idle, we return default phone
+            phone = getFirstActiveBgCall().getPhone();
+        }
+        return phone;
+    }
+
+    public Phone getPhoneInCall(long subId) {
+        Phone phone = null;
+        if (!getFirstActiveRingingCall(subId).isIdle()) {
+            phone = getFirstActiveRingingCall(subId).getPhone();
+        } else if (!getActiveFgCall(subId).isIdle()) {
+            phone = getActiveFgCall(subId).getPhone();
+        } else {
+            // If BG call is idle, we return default phone
+            phone = getFirstActiveBgCall(subId).getPhone();
+        }
+        return phone;
+    }
+
+    /**
      * Register phone to CallManager
      * @param phone to be registered
      * @return true if register successfully
@@ -327,6 +428,11 @@
                         phone.getPhoneName() + " " + phone + ")");
             }
 
+            Phone vPhone = basePhone.getVoicePhone();
+            if (vPhone != null) {
+               unregisterPhone(vPhone);
+            }
+
             mPhones.remove(basePhone);
             mRingingCalls.remove(basePhone.getRingingCall());
             mBackgroundCalls.remove(basePhone.getBackgroundCall());
@@ -357,6 +463,14 @@
     }
 
     /**
+     * @return the phone associated with the foreground call
+     * of a particular subId
+     */
+    public Phone getFgPhone(long subId) {
+        return getActiveFgCall(subId).getPhone();
+    }
+
+    /**
      * @return the phone associated with the background call
      */
     public Phone getBgPhone() {
@@ -364,12 +478,28 @@
     }
 
     /**
+     * @return the phone associated with the background call
+     * of a particular subId
+     */
+    public Phone getBgPhone(long subId) {
+        return getFirstActiveBgCall(subId).getPhone();
+    }
+
+    /**
      * @return the phone associated with the ringing call
      */
     public Phone getRingingPhone() {
         return getFirstActiveRingingCall().getPhone();
     }
 
+    /**
+     * @return the phone associated with the ringing call
+     * of a particular subId
+     */
+    public Phone getRingingPhone(long subId) {
+        return getFirstActiveRingingCall(subId).getPhone();
+    }
+
     public void setAudioMode() {
         Context context = getContext();
         if (context == null) return;
@@ -407,14 +537,18 @@
 
                 int newAudioMode = AudioManager.MODE_IN_CALL;
                 if (offhookPhone instanceof SipPhone) {
+                    Rlog.d(LOG_TAG, "setAudioMode Set audio mode for SIP call!");
                     // enable IN_COMMUNICATION audio mode instead for sipPhone
                     newAudioMode = AudioManager.MODE_IN_COMMUNICATION;
                 }
-                if (audioManager.getMode() != newAudioMode || mSpeedUpAudioForMtCall) {
+                int currMode = audioManager.getMode();
+                if (currMode != newAudioMode || mSpeedUpAudioForMtCall) {
                     // request audio focus before setting the new mode
                     if (VDBG) Rlog.d(LOG_TAG, "requestAudioFocus on STREAM_VOICE_CALL");
                     audioManager.requestAudioFocusForCall(AudioManager.STREAM_VOICE_CALL,
                             AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
+                    Rlog.d(LOG_TAG, "setAudioMode Setting audio mode from "
+                            + currMode + " to " + newAudioMode);
                     audioManager.setMode(newAudioMode);
                 }
                 mSpeedUpAudioForMtCall = false;
@@ -429,6 +563,7 @@
                 mSpeedUpAudioForMtCall = false;
                 break;
         }
+        Rlog.d(LOG_TAG, "setAudioMode state = " + getState());
     }
 
     private Context getContext() {
@@ -454,9 +589,10 @@
         phone.registerForSuppServiceFailed(mHandler, EVENT_SUPP_SERVICE_FAILED, null);
         phone.registerForServiceStateChanged(mHandler, EVENT_SERVICE_STATE_CHANGED, null);
 
-        // for events supported only by GSM and CDMA phone
+        // for events supported only by GSM, CDMA and IMS phone
         if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_GSM ||
-                phone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
+                phone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA ||
+                phone.getPhoneType() == PhoneConstants.PHONE_TYPE_IMS) {
             phone.setOnPostDialCharacter(mHandler, EVENT_POST_DIAL_CHARACTER, null);
         }
 
@@ -467,6 +603,11 @@
             phone.registerForCallWaiting(mHandler, EVENT_CALL_WAITING, null);
             phone.registerForEcmTimerReset(mHandler, EVENT_ECM_TIMER_RESET, null);
         }
+
+        // for events supported only by IMS phone
+        if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_IMS) {
+            phone.registerForOnHoldTone(mHandler, EVENT_ONHOLD_TONE, null);
+        }
     }
 
     private void unregisterForPhoneStates(Phone phone) {
@@ -487,9 +628,10 @@
         phone.unregisterForSuppServiceFailed(mHandler);
         phone.unregisterForServiceStateChanged(mHandler);
 
-        // for events supported only by GSM and CDMA phone
+        // for events supported only by GSM, CDMA and IMS phone
         if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_GSM ||
-                phone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
+                phone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA ||
+                phone.getPhoneType() == PhoneConstants.PHONE_TYPE_IMS) {
             phone.setOnPostDialCharacter(null, EVENT_POST_DIAL_CHARACTER, null);
         }
 
@@ -500,6 +642,11 @@
             phone.unregisterForCallWaiting(mHandler);
             phone.unregisterForEcmTimerReset(mHandler);
         }
+
+        // for events supported only by IMS phone
+        if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_IMS) {
+            phone.unregisterForOnHoldTone(mHandler);
+        }
     }
 
     /**
@@ -550,7 +697,8 @@
             AudioManager audioManager = (AudioManager)
                     context.getSystemService(Context.AUDIO_SERVICE);
             int currMode = audioManager.getMode();
-            if ((currMode != AudioManager.MODE_IN_CALL) && !(ringingPhone instanceof SipPhone)) {
+            if ((currMode != AudioManager.MODE_IN_CALL)
+                    && !(ringingPhone instanceof SipPhone)) {
                 Rlog.d(LOG_TAG, "setAudioMode Setting audio mode from " +
                                 currMode + " to " + AudioManager.MODE_IN_CALL);
                 audioManager.setMode(AudioManager.MODE_IN_CALL);
@@ -698,6 +846,28 @@
     }
 
     /**
+     * Whether or not the phone can conference in the current phone
+     * state--that is, one call holding and one call active.
+     * This method consider the phone object which is specific
+     * to the provided subId.
+     * @return true if the phone can conference; false otherwise.
+     */
+    public boolean canConference(Call heldCall, long subId) {
+        Phone activePhone = null;
+        Phone heldPhone = null;
+
+        if (hasActiveFgCall(subId)) {
+            activePhone = getActiveFgCall(subId).getPhone();
+        }
+
+        if (heldCall != null) {
+            heldPhone = heldCall.getPhone();
+        }
+
+        return heldPhone.getClass().equals(activePhone.getClass());
+    }
+
+    /**
      * Conferences holding and active. Conference occurs asynchronously
      * and may fail. Final notification occurs via
      * {@link #registerForPreciseCallStateChanged(android.os.Handler, int,
@@ -707,20 +877,24 @@
      * In these cases, this operation may not be performed.
      */
     public void conference(Call heldCall) throws CallStateException {
+        long subId  = heldCall.getPhone().getSubId();
 
         if (VDBG) {
             Rlog.d(LOG_TAG, "conference(" +heldCall + ")");
             Rlog.d(LOG_TAG, toString());
         }
 
-
-        Phone fgPhone = getFgPhone();
-        if (fgPhone instanceof SipPhone) {
-            ((SipPhone) fgPhone).conference(heldCall);
-        } else if (canConference(heldCall)) {
-            fgPhone.conference();
+        Phone fgPhone = getFgPhone(subId);
+        if (fgPhone != null) {
+            if (fgPhone instanceof SipPhone) {
+                ((SipPhone) fgPhone).conference(heldCall);
+            } else if (canConference(heldCall)) {
+                fgPhone.conference();
+            } else {
+                throw(new CallStateException("Can't conference foreground and selected background call"));
+            }
         } else {
-            throw(new CallStateException("Can't conference foreground and selected background call"));
+            Rlog.d(LOG_TAG, "conference: fgPhone=null");
         }
 
         if (VDBG) {
@@ -742,10 +916,12 @@
      */
     public Connection dial(Phone phone, String dialString) throws CallStateException {
         Phone basePhone = getPhoneBase(phone);
+        long subId = phone.getSubId();
         Connection result;
 
         if (VDBG) {
-            Rlog.d(LOG_TAG, " dial(" + basePhone + ", "+ dialString + ")");
+            Rlog.d(LOG_TAG, " dial(" + basePhone + ", "+ dialString + ")" +
+                    " subId = " + subId);
             Rlog.d(LOG_TAG, toString());
         }
 
@@ -763,18 +939,22 @@
             }
         }
 
-        if ( hasActiveFgCall() ) {
-            Phone activePhone = getActiveFgCall().getPhone();
+        if ( hasActiveFgCall(subId) ) {
+            Phone activePhone = getActiveFgCall(subId).getPhone();
             boolean hasBgCall = !(activePhone.getBackgroundCall().isIdle());
 
             if (DBG) {
                 Rlog.d(LOG_TAG, "hasBgCall: "+ hasBgCall + " sameChannel:" + (activePhone == basePhone));
             }
 
-            if (activePhone != basePhone) {
+            // Manipulation between IMS phone and its owner
+            // will be treated in GSM/CDMA phone.
+            Phone vPhone = basePhone.getVoicePhone();
+            if (activePhone != basePhone
+                    && (vPhone == null || vPhone != activePhone)) {
                 if (hasBgCall) {
                     Rlog.d(LOG_TAG, "Hangup");
-                    getActiveFgCall().hangup();
+                    getActiveFgCall(subId).hangup();
                 } else {
                     Rlog.d(LOG_TAG, "Switch");
                     activePhone.switchHoldingAndActive();
@@ -816,6 +996,18 @@
     }
 
     /**
+     * clear disconnect connection for a phone specific
+     * to the provided subId
+     */
+    public void clearDisconnected(long subId) {
+        for(Phone phone : mPhones) {
+            if (phone.getSubId() == subId) {
+                phone.clearDisconnected();
+            }
+        }
+    }
+
+    /**
      * Phone can make a call only if ALL of the following are true:
      *        - Phone is not powered off
      *        - There's no incoming or waiting call
@@ -826,14 +1018,19 @@
      */
     private boolean canDial(Phone phone) {
         int serviceState = phone.getServiceState().getState();
+        long subId = phone.getSubId();
         boolean hasRingingCall = hasActiveRingingCall();
-        Call.State fgCallState = getActiveFgCallState();
+        Call.State fgCallState = getActiveFgCallState(subId);
 
         boolean result = (serviceState != ServiceState.STATE_POWER_OFF
                 && !hasRingingCall
                 && ((fgCallState == Call.State.ACTIVE)
                     || (fgCallState == Call.State.IDLE)
-                    || (fgCallState == Call.State.DISCONNECTED)));
+                    || (fgCallState == Call.State.DISCONNECTED)
+                    /*As per 3GPP TS 51.010-1 section 31.13.1.4
+                    call should be alowed when the foreground
+                    call is in ALERTING state*/
+                    || (fgCallState == Call.State.ALERTING)));
 
         if (result == false) {
             Rlog.d(LOG_TAG, "canDial serviceState=" + serviceState
@@ -864,6 +1061,26 @@
     }
 
     /**
+     * Whether or not the phone specific to subId can do explicit call transfer
+     * in the current phone state--that is, one call holding and one call active.
+     * @return true if the phone can do explicit call transfer; false otherwise.
+     */
+    public boolean canTransfer(Call heldCall, long subId) {
+        Phone activePhone = null;
+        Phone heldPhone = null;
+
+        if (hasActiveFgCall(subId)) {
+            activePhone = getActiveFgCall(subId).getPhone();
+        }
+
+        if (heldCall != null) {
+            heldPhone = heldCall.getPhone();
+        }
+
+        return (heldPhone == activePhone && activePhone.canTransfer());
+    }
+
+    /**
      * Connects the held call and active call
      * Disconnects the subscriber from both calls
      *
@@ -1206,6 +1423,27 @@
     }
 
     /**
+     * Notifies when out-band on-hold tone is needed.<p>
+     *
+     *  Messages received from this:
+     *  Message.obj will be an AsyncResult
+     *  AsyncResult.userObj = obj
+     *  AsyncResult.result = boolean, true to start play on-hold tone
+     *                       and false to stop. <p>
+     */
+    public void registerForOnHoldTone(Handler h, int what, Object obj){
+        mOnHoldToneRegistrants.addUnique(h, what, obj);
+    }
+
+    /**
+     * Unregisters for on-hold tone notification.
+     */
+
+    public void unregisterForOnHoldTone(Handler h){
+        mOnHoldToneRegistrants.remove(h);
+    }
+
+    /**
      * Registers the handler to reset the uplink mute state to get
      * uplink audio.
      */
@@ -1447,7 +1685,7 @@
     }
 
     /**
-     * Registration point for subscription info ready
+     * Registration point for subcription info ready
      * @param h handler to notify
      * @param what what code of message when delivered
      * @param obj placed in Message.obj
@@ -1542,6 +1780,14 @@
     }
 
     /**
+     * Return true if there is at least one active foreground call
+     * on a particular subId or an active sip call
+     */
+    public boolean hasActiveFgCall(long subId) {
+        return (getFirstActiveCall(mForegroundCalls, subId) != null);
+    }
+
+    /**
      * Return true if there is at least one active background call
      */
     public boolean hasActiveBgCall() {
@@ -1551,6 +1797,16 @@
     }
 
     /**
+     * Return true if there is at least one active background call
+     * on a particular subId or an active sip call
+     */
+    public boolean hasActiveBgCall(long subId) {
+        // TODO since hasActiveBgCall may get called often
+        // better to cache it to improve performance
+        return (getFirstActiveCall(mBackgroundCalls, subId) != null);
+    }
+
+    /**
      * Return true if there is at least one active ringing call
      *
      */
@@ -1559,6 +1815,13 @@
     }
 
     /**
+     * Return true if there is at least one active ringing call
+     */
+    public boolean hasActiveRingingCall(long subId) {
+        return (getFirstActiveCall(mRingingCalls, subId) != null);
+    }
+
+    /**
      * return the active foreground call from foreground calls
      *
      * Active call means the call is NOT in Call.State.IDLE
@@ -1579,6 +1842,17 @@
         return call;
     }
 
+    public Call getActiveFgCall(long subId) {
+        Call call = getFirstNonIdleCall(mForegroundCalls, subId);
+        if (call == null) {
+            Phone phone = getPhone(subId);
+            call = (phone == null)
+                    ? null
+                    : phone.getForegroundCall();
+        }
+        return call;
+    }
+
     // Returns the first call that is not in IDLE state. If both active calls
     // and disconnecting/disconnected calls exist, return the first active call.
     private Call getFirstNonIdleCall(List<Call> calls) {
@@ -1593,6 +1867,23 @@
         return result;
     }
 
+    // Returns the first call that is not in IDLE state. If both active calls
+    // and disconnecting/disconnected calls exist, return the first active call.
+    private Call getFirstNonIdleCall(List<Call> calls, long subId) {
+        Call result = null;
+        for (Call call : calls) {
+            if ((call.getPhone().getSubId() == subId) ||
+                    (call.getPhone() instanceof SipPhone)) {
+                if (!call.isIdle()) {
+                    return call;
+                } else if (call.getState() != Call.State.IDLE) {
+                    if (result == null) result = call;
+                }
+            }
+        }
+        return result;
+    }
+
     /**
      * return one active background call from background calls
      *
@@ -1617,6 +1908,35 @@
     }
 
     /**
+     * return one active background call from background calls of the
+     * requested subId.
+     *
+     * Active call means the call is NOT idle defined by Call.isIdle()
+     *
+     * 1. If there is only one active background call on given sub or
+     *    on SIP Phone, return it
+     * 2. If there is more than one active background call, return the background call
+     *    associated with the active sub.
+     * 3. If there is no background call at all, return null.
+     *
+     * Complete background calls list can be get by getBackgroundCalls()
+     */
+    public Call getFirstActiveBgCall(long subId) {
+        Phone phone = getPhone(subId);
+        if (hasMoreThanOneHoldingCall(subId)) {
+            return phone.getBackgroundCall();
+        } else {
+            Call call = getFirstNonIdleCall(mBackgroundCalls, subId);
+            if (call == null) {
+                call = (phone == null)
+                        ? null
+                        : phone.getBackgroundCall();
+            }
+            return call;
+        }
+    }
+
+    /**
      * return one active ringing call from ringing calls
      *
      * Active call means the call is NOT idle defined by Call.isIdle()
@@ -1639,6 +1959,17 @@
         return call;
     }
 
+    public Call getFirstActiveRingingCall(long subId) {
+        Phone phone = getPhone(subId);
+        Call call = getFirstNonIdleCall(mRingingCalls, subId);
+        if (call == null) {
+            call = (phone == null)
+                    ? null
+                    : phone.getRingingCall();
+        }
+        return call;
+    }
+
     /**
      * @return the state of active foreground call
      * return IDLE if there is no active foreground call
@@ -1653,6 +1984,16 @@
         return Call.State.IDLE;
     }
 
+    public Call.State getActiveFgCallState(long subId) {
+        Call fgCall = getActiveFgCall(subId);
+
+        if (fgCall != null) {
+            return fgCall.getState();
+        }
+
+        return Call.State.IDLE;
+    }
+
     /**
      * @return the connections of active foreground call
      * return empty list if there is no active foreground call
@@ -1666,6 +2007,18 @@
     }
 
     /**
+     * @return the connections of active foreground call
+     * return empty list if there is no active foreground call
+     */
+    public List<Connection> getFgCallConnections(long subId) {
+        Call fgCall = getActiveFgCall(subId);
+        if ( fgCall != null) {
+            return fgCall.getConnections();
+        }
+        return mEmptyConnections;
+    }
+
+    /**
      * @return the connections of active background call
      * return empty list if there is no active background call
      */
@@ -1678,6 +2031,18 @@
     }
 
     /**
+     * @return the connections of active background call
+     * return empty list if there is no active background call
+     */
+    public List<Connection> getBgCallConnections(long subId) {
+        Call bgCall = getFirstActiveBgCall(subId);
+        if ( bgCall != null) {
+            return bgCall.getConnections();
+        }
+        return mEmptyConnections;
+    }
+
+    /**
      * @return the latest connection of active foreground call
      * return null if there is no active foreground call
      */
@@ -1690,6 +2055,18 @@
     }
 
     /**
+     * @return the latest connection of active foreground call
+     * return null if there is no active foreground call
+     */
+    public Connection getFgCallLatestConnection(long subId) {
+        Call fgCall = getActiveFgCall(subId);
+        if ( fgCall != null) {
+            return fgCall.getLatestConnection();
+        }
+        return null;
+    }
+
+    /**
      * @return true if there is at least one Foreground call in disconnected state
      */
     public boolean hasDisconnectedFgCall() {
@@ -1697,6 +2074,14 @@
     }
 
     /**
+     * @return true if there is at least one Foreground call in disconnected state
+     */
+    public boolean hasDisconnectedFgCall(long subId) {
+        return (getFirstCallOfState(mForegroundCalls, Call.State.DISCONNECTED,
+                subId) != null);
+    }
+
+    /**
      * @return true if there is at least one background call in disconnected state
      */
     public boolean hasDisconnectedBgCall() {
@@ -1704,6 +2089,15 @@
     }
 
     /**
+     * @return true if there is at least one background call in disconnected state
+     */
+    public boolean hasDisconnectedBgCall(long subId) {
+        return (getFirstCallOfState(mBackgroundCalls, Call.State.DISCONNECTED,
+                subId) != null);
+    }
+
+
+    /**
      * @return the first active call from a call list
      */
     private  Call getFirstActiveCall(ArrayList<Call> calls) {
@@ -1716,6 +2110,19 @@
     }
 
     /**
+     * @return the first active call from a call list
+     */
+    private  Call getFirstActiveCall(ArrayList<Call> calls, long subId) {
+        for (Call call : calls) {
+            if ((!call.isIdle()) && ((call.getPhone().getSubId() == subId) ||
+                    (call.getPhone() instanceof SipPhone))) {
+                return call;
+            }
+        }
+        return null;
+    }
+
+    /**
      * @return the first call in a the Call.state from a call list
      */
     private Call getFirstCallOfState(ArrayList<Call> calls, Call.State state) {
@@ -1727,6 +2134,20 @@
         return null;
     }
 
+    /**
+     * @return the first call in a the Call.state from a call list
+     */
+    private Call getFirstCallOfState(ArrayList<Call> calls, Call.State state,
+            long subId) {
+        for (Call call : calls) {
+            if ((call.getState() == state) ||
+                ((call.getPhone().getSubId() == subId) ||
+                (call.getPhone() instanceof SipPhone))) {
+                return call;
+            }
+        }
+        return null;
+    }
 
     private boolean hasMoreThanOneRingingCall() {
         int count = 0;
@@ -1738,6 +2159,44 @@
         return false;
     }
 
+    /**
+     * @return true if more than one active ringing call exists on
+     * the active subId.
+     * This checks for the active calls on provided
+     * subId and also active calls on SIP Phone.
+     *
+     */
+    private boolean hasMoreThanOneRingingCall(long subId) {
+        int count = 0;
+        for (Call call : mRingingCalls) {
+            if ((call.getState().isRinging()) &&
+                ((call.getPhone().getSubId() == subId) ||
+                (call.getPhone() instanceof SipPhone))) {
+                if (++count > 1) return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * @return true if more than one active background call exists on
+     * the provided subId.
+     * This checks for the background calls on provided
+     * subId and also background calls on SIP Phone.
+     *
+     */
+    private boolean hasMoreThanOneHoldingCall(long subId) {
+        int count = 0;
+        for (Call call : mBackgroundCalls) {
+            if ((call.getState() == Call.State.HOLDING) &&
+                ((call.getPhone().getSubId() == subId) ||
+                (call.getPhone() instanceof SipPhone))) {
+                if (++count > 1) return true;
+            }
+        }
+        return false;
+    }
+
     private Handler mHandler = new Handler() {
 
         @Override
@@ -1754,8 +2213,9 @@
                     break;
                 case EVENT_NEW_RINGING_CONNECTION:
                     if (VDBG) Rlog.d(LOG_TAG, " handleMessage (EVENT_NEW_RINGING_CONNECTION)");
-                    if (getActiveFgCallState().isDialing() || hasMoreThanOneRingingCall()) {
-                        Connection c = (Connection) ((AsyncResult) msg.obj).result;
+                    Connection c = (Connection) ((AsyncResult) msg.obj).result;
+                    long subId = c.getCall().getPhone().getSubId();
+                    if (getActiveFgCallState(subId).isDialing() || hasMoreThanOneRingingCall()) {
                         try {
                             Rlog.d(LOG_TAG, "silently drop incoming call: " + c.getCall());
                             c.getCall().hangup();
@@ -1845,6 +2305,10 @@
                         notifyMsg.sendToTarget();
                     }
                     break;
+                case EVENT_ONHOLD_TONE:
+                    if (VDBG) Rlog.d(LOG_TAG, " handleMessage (EVENT_ONHOLD_TONE)");
+                    mOnHoldToneRegistrants.notifyRegistrants((AsyncResult) msg.obj);
+                    break;
             }
         }
     };
@@ -1853,20 +2317,21 @@
     public String toString() {
         Call call;
         StringBuilder b = new StringBuilder();
-
-        b.append("CallManager {");
-        b.append("\nstate = " + getState());
-        call = getActiveFgCall();
-        b.append("\n- Foreground: " + getActiveFgCallState());
-        b.append(" from " + call.getPhone());
-        b.append("\n  Conn: ").append(getFgCallConnections());
-        call = getFirstActiveBgCall();
-        b.append("\n- Background: " + call.getState());
-        b.append(" from " + call.getPhone());
-        b.append("\n  Conn: ").append(getBgCallConnections());
-        call = getFirstActiveRingingCall();
-        b.append("\n- Ringing: " +call.getState());
-        b.append(" from " + call.getPhone());
+        for (int i = 0; i < TelephonyManager.getDefault().getPhoneCount(); i++) {
+            b.append("CallManager {");
+            b.append("\nstate = " + getState(i));
+            call = getActiveFgCall(i);
+            b.append("\n- Foreground: " + getActiveFgCallState(i));
+            b.append(" from " + call.getPhone());
+            b.append("\n  Conn: ").append(getFgCallConnections(i));
+            call = getFirstActiveBgCall(i);
+            b.append("\n- Background: " + call.getState());
+            b.append(" from " + call.getPhone());
+            b.append("\n  Conn: ").append(getBgCallConnections(i));
+            call = getFirstActiveRingingCall(i);
+            b.append("\n- Ringing: " +call.getState());
+            b.append(" from " + call.getPhone());
+        }
 
         for (Phone phone : getAllPhones()) {
             if (phone != null) {
diff --git a/src/java/com/android/internal/telephony/CellBroadcastHandler.java b/src/java/com/android/internal/telephony/CellBroadcastHandler.java
index f7e0e3a..36f9521 100644
--- a/src/java/com/android/internal/telephony/CellBroadcastHandler.java
+++ b/src/java/com/android/internal/telephony/CellBroadcastHandler.java
@@ -23,6 +23,7 @@
 import android.content.Intent;
 import android.os.Message;
 import android.provider.Telephony;
+import android.telephony.SubscriptionManager;
 import android.telephony.SmsCbMessage;
 
 /**
@@ -88,6 +89,7 @@
             appOp = AppOpsManager.OP_RECEIVE_SMS;
         }
         intent.putExtra("message", message);
+        SubscriptionManager.putPhoneIdAndSubIdExtra(intent, mPhone.getPhoneId());
         mContext.sendOrderedBroadcast(intent, receiverPermission, appOp, mReceiver,
                 getHandler(), Activity.RESULT_OK, null, null);
     }
diff --git a/src/java/com/android/internal/telephony/CommandException.java b/src/java/com/android/internal/telephony/CommandException.java
index 80338ae..bf24689 100644
--- a/src/java/com/android/internal/telephony/CommandException.java
+++ b/src/java/com/android/internal/telephony/CommandException.java
@@ -44,6 +44,7 @@
         ILLEGAL_SIM_OR_ME,
         MISSING_RESOURCE,
         NO_SUCH_ELEMENT,
+        SUBSCRIPTION_NOT_SUPPORTED,
     }
 
     public CommandException(Error e) {
@@ -89,6 +90,8 @@
                 return new CommandException(Error.MISSING_RESOURCE);
             case RILConstants.NO_SUCH_ELEMENT:
                 return new CommandException(Error.NO_SUCH_ELEMENT);
+            case RILConstants.SUBSCRIPTION_NOT_SUPPORTED:
+                return new CommandException(Error.SUBSCRIPTION_NOT_SUPPORTED);
             default:
                 Rlog.e("GSM", "Unrecognized RIL errno " + ril_errno);
                 return new CommandException(Error.INVALID_RESPONSE);
diff --git a/src/java/com/android/internal/telephony/CommandsInterface.java b/src/java/com/android/internal/telephony/CommandsInterface.java
index d292753..e75de0b 100644
--- a/src/java/com/android/internal/telephony/CommandsInterface.java
+++ b/src/java/com/android/internal/telephony/CommandsInterface.java
@@ -191,6 +191,26 @@
     void registerForInCallVoicePrivacyOff(Handler h, int what, Object obj);
     void unregisterForInCallVoicePrivacyOff(Handler h);
 
+    /** Single Radio Voice Call State progress notifications */
+    void registerForSrvccStateChanged(Handler h, int what, Object obj);
+    void unregisterForSrvccStateChanged(Handler h);
+
+    /**
+     * Handlers for subscription status change indications.
+     *
+     * @param h Handler for subscription status change messages.
+     * @param what User-defined message code.
+     * @param obj User object.
+     */
+    void registerForSubscriptionStatusChanged(Handler h, int what, Object obj);
+    void unregisterForSubscriptionStatusChanged(Handler h);
+
+    /**
+     * fires on any change in hardware configuration.
+     */
+    void registerForHardwareConfigChanged(Handler h, int what, Object obj);
+    void unregisterForHardwareConfigChanged(Handler h);
+
     /**
      * unlike the register* methods, there's only one new 3GPP format SMS handler.
      * if you need to unregister, you should also tell the radio to stop
@@ -1624,10 +1644,23 @@
      *
      * @param nonce the nonce string to pass with the ISIM authentication request
      * @param response a callback message with the String response in the obj field
+     * @deprecated
+     * @see requestIccSimAuthentication
      */
     public void requestIsimAuthentication(String nonce, Message response);
 
     /**
+     * Request the SIM application on the UICC to perform authentication
+     * challenge/response algorithm. The data string and challenge response are
+     * Base64 encoded Strings.
+     * Can support EAP-SIM, EAP-AKA with results encoded per 3GPP TS 31.102.
+     *
+     * @param data authentication challenge data
+     * @param response a callback message with the String response in the obj field
+     */
+    public void requestIccSimAuthentication(String data, Message response);
+
+    /**
      * Get the current Voice Radio Technology.
      *
      * AsyncResult.result is an int array with the first value
@@ -1650,11 +1683,11 @@
      * Sets the minimum time in milli-seconds between when RIL_UNSOL_CELL_INFO_LIST
      * should be invoked.
      *
-     * The default, 0, means invoke RIL_UNSOL_CELL_INFO_LIST when any of the reported 
+     * The default, 0, means invoke RIL_UNSOL_CELL_INFO_LIST when any of the reported
      * information changes. Setting the value to INT_MAX(0x7fffffff) means never issue
      * A RIL_UNSOL_CELL_INFO_LIST.
      *
-     * 
+     *
 
      * @param rateInMillis is sent back to handler and result.obj is a AsyncResult
      * @param response.obj is AsyncResult ar when sent to associated handler
@@ -1793,7 +1826,45 @@
     void nvResetConfig(int resetType, Message response);
 
     /**
+     *  returned message
+     *  retMsg.obj = AsyncResult ar
+     *  ar.exception carries exception on failure
+     *  ar.userObject contains the orignal value of result.obj
+     *  ar.result contains a List of HardwareConfig
+     */
+    void getHardwareConfig (Message result);
+
+    /**
      * @return version of the ril.
      */
     int getRilVersion();
+
+   /**
+     * Sets user selected subscription at Modem.
+     *
+     * @param slotId
+     *          Slot.
+     * @param appIndex
+     *          Application index in the card.
+     * @param subId
+     *          Indicates subscription 0 or subscription 1.
+     * @param subStatus
+     *          Activation status, 1 = activate and 0 = deactivate.
+     * @param result
+     *          Callback message contains the information of SUCCESS/FAILURE.
+     */
+    // FIXME Update the doc and consider modifying the request to make more generic.
+    public void setUiccSubscription(int slotId, int appIndex, int subId, int subStatus,
+            Message result);
+
+    /**
+     * Tells the modem if data is allowed or not.
+     *
+     * @param allowed
+     *          true = allowed, false = not alowed
+     * @param result
+     *          Callback message contains the information of SUCCESS/FAILURE.
+     */
+    // FIXME We may need to pass AID and slotid also
+    public void setDataAllowed(boolean allowed, Message result);
 }
diff --git a/src/java/com/android/internal/telephony/Connection.java b/src/java/com/android/internal/telephony/Connection.java
index 59a5586..e1ef562 100644
--- a/src/java/com/android/internal/telephony/Connection.java
+++ b/src/java/com/android/internal/telephony/Connection.java
@@ -99,6 +99,14 @@
     public abstract long getConnectTime();
 
     /**
+     * Connection connect time in elapsedRealtime() format.
+     * For outgoing calls: Begins at (DIALING|ALERTING) -> ACTIVE transition.
+     * For incoming calls: Begins at (INCOMING|WAITING) -> ACTIVE transition.
+     * Returns 0 before then.
+     */
+    public abstract long getConnectTimeReal();
+
+    /**
      * Disconnect time in currentTimeMillis() format.
      * The time when this Connection makes a transition into ENDED or FAIL.
      * Returns 0 before then.
@@ -114,6 +122,13 @@
     public abstract long getDurationMillis();
 
     /**
+     * The time when this Connection last transitioned into HOLDING
+     * in elapsedRealtime() format.
+     * Returns 0, if it has never made a transition into HOLDING.
+     */
+    public abstract long getHoldingStartTime();
+
+    /**
      * If this connection is HOLDING, return the number of milliseconds
      * that it has been on hold for (approximately).
      * If this connection is in any other state, return 0.
@@ -282,6 +297,19 @@
     public abstract int getPreciseDisconnectCause();
 
     /**
+     * Returns the original Connection instance associated with
+     * this Connection
+     */
+    public abstract Connection getOrigConnection();
+
+    /**
+     * Returns whether the original ImsPhoneConnection was a member
+     * of a conference call
+     * @return valid only when getOrigConnection() is not null
+     */
+    public abstract boolean isMultiparty();
+
+    /**
      * Build a human representation of a connection instance, suitable for debugging.
      * Don't log personal stuff unless in debug mode.
      * @return a string representing the internal state of this connection.
diff --git a/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java b/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java
index c001b79..d1cc618 100644
--- a/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java
+++ b/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java
@@ -23,12 +23,19 @@
 import android.os.ServiceManager;
 import android.telephony.CellInfo;
 import android.telephony.DataConnectionRealTimeInfo;
+import android.telephony.Rlog;
+import android.telephony.VoLteServiceState;
 import android.telephony.ServiceState;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.telephony.PreciseCallState;
 import android.telephony.DisconnectCause;
 
+import com.android.internal.telephony.Call;
+import com.android.internal.telephony.CallManager;
+import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.ITelephonyRegistry;
+import com.android.internal.telephony.PhoneConstants;
 
 import java.util.List;
 
@@ -36,11 +43,12 @@
  * broadcast intents
  */
 public class DefaultPhoneNotifier implements PhoneNotifier {
+    static final String LOG_TAG = "DefaultPhoneNotifier";
 
-    private ITelephonyRegistry mRegistry;
+    protected ITelephonyRegistry mRegistry;
 
     /*package*/
-    DefaultPhoneNotifier() {
+    protected DefaultPhoneNotifier() {
         mRegistry = ITelephonyRegistry.Stub.asInterface(ServiceManager.getService(
                     "telephony.registry"));
     }
@@ -48,13 +56,56 @@
     @Override
     public void notifyPhoneState(Phone sender) {
         Call ringingCall = sender.getRingingCall();
+        long subId = sender.getSubId();
         String incomingNumber = "";
         if (ringingCall != null && ringingCall.getEarliestConnection() != null){
             incomingNumber = ringingCall.getEarliestConnection().getAddress();
         }
         try {
             if (mRegistry != null) {
-                mRegistry.notifyCallState(convertCallState(sender.getState()), incomingNumber);
+                  mRegistry.notifyCallStateUsingSubId(subId,
+                        convertCallState(sender.getState()), incomingNumber);
+            }
+        } catch (RemoteException ex) {
+            // system process is dead
+        }
+        notifyCallStateToTelephonyRegistry(sender);
+    }
+
+    /*
+     *  Suppose, some third party app e.g. FM app registers for a call state changed indication
+     *  through TelephonyManager/PhoneStateListener and an incoming call is received on sub1 or
+     *  sub2. Then ir-respective of sub1/sub2 FM app should be informed of call state
+     *  changed(onCallStateChanged()) indication so that FM app can be paused.
+     *  Hence send consolidated call state information to apps. (i.e. sub1 or sub2 active
+     *  call state,  in priority order RINGING > OFFHOOK > IDLE)
+     */
+    public void notifyCallStateToTelephonyRegistry(Phone sender) {
+        Call ringingCall = null;
+        CallManager cm = CallManager.getInstance();
+        PhoneConstants.State state = sender.getState();
+        String incomingNumber = "";
+        for (Phone phone : cm.getAllPhones()) {
+            if (phone.getState() == PhoneConstants.State.RINGING) {
+                ringingCall = phone.getRingingCall();
+                if (ringingCall != null && ringingCall.getEarliestConnection() != null) {
+                    incomingNumber = ringingCall.getEarliestConnection().getAddress();
+                }
+                sender = phone;
+                state = PhoneConstants.State.RINGING;
+                break;
+            } else if (phone.getState() == PhoneConstants.State.OFFHOOK) {
+                if (state == PhoneConstants.State.IDLE) {
+                    state = PhoneConstants.State.OFFHOOK;
+                    sender = phone;
+                }
+            }
+        }
+        Rlog.d(LOG_TAG, "notifyCallStateToTelephonyRegistry, subId = " + sender.getSubId()
+                + " state = " + state);
+        try {
+            if (mRegistry != null) {
+                mRegistry.notifyCallState(convertCallState(state), incomingNumber);
             }
         } catch (RemoteException ex) {
             // system process is dead
@@ -64,13 +115,16 @@
     @Override
     public void notifyServiceState(Phone sender) {
         ServiceState ss = sender.getServiceState();
+        long subId = sender.getSubId();
+        Rlog.d(LOG_TAG, "nofityServiceState: mRegistry=" + mRegistry + " ss=" + ss
+                + " sender=" + sender);
         if (ss == null) {
             ss = new ServiceState();
             ss.setStateOutOfService();
         }
         try {
             if (mRegistry != null) {
-                mRegistry.notifyServiceState(ss);
+                mRegistry.notifyServiceStateUsingSubId(subId, ss);
             }
         } catch (RemoteException ex) {
             // system process is dead
@@ -79,9 +133,12 @@
 
     @Override
     public void notifySignalStrength(Phone sender) {
+        long subId = sender.getSubId();
+        Rlog.d(LOG_TAG, "notifySignalStrength: mRegistry=" + mRegistry
+                + " ss=" + sender.getSignalStrength() + " sender=" + sender);
         try {
             if (mRegistry != null) {
-                mRegistry.notifySignalStrength(sender.getSignalStrength());
+                mRegistry.notifySignalStrengthUsingSubId(subId, sender.getSignalStrength());
             }
         } catch (RemoteException ex) {
             // system process is dead
@@ -90,9 +147,11 @@
 
     @Override
     public void notifyMessageWaitingChanged(Phone sender) {
+        long subId = sender.getSubId();
         try {
             if (mRegistry != null) {
-                mRegistry.notifyMessageWaitingChanged(sender.getMessageWaitingIndicator());
+                mRegistry.notifyMessageWaitingChangedUsingSubId(subId,
+                        sender.getMessageWaitingIndicator());
             }
         } catch (RemoteException ex) {
             // system process is dead
@@ -101,9 +160,11 @@
 
     @Override
     public void notifyCallForwardingChanged(Phone sender) {
+        long subId = sender.getSubId();
         try {
             if (mRegistry != null) {
-                mRegistry.notifyCallForwardingChanged(sender.getCallForwardingIndicator());
+                mRegistry.notifyCallForwardingChangedUsingSubId(subId,
+                        sender.getCallForwardingIndicator());
             }
         } catch (RemoteException ex) {
             // system process is dead
@@ -112,9 +173,11 @@
 
     @Override
     public void notifyDataActivity(Phone sender) {
+        long subId = sender.getSubId();
         try {
             if (mRegistry != null) {
-                mRegistry.notifyDataActivity(convertDataActivityState(sender.getDataActivityState()));
+                mRegistry.notifyDataActivityUsingSubId(subId,
+                        convertDataActivityState(sender.getDataActivityState()));
             }
         } catch (RemoteException ex) {
             // system process is dead
@@ -129,6 +192,10 @@
 
     private void doNotifyDataConnection(Phone sender, String reason, String apnType,
             PhoneConstants.DataState state) {
+        long subId = sender.getSubId();
+        long dds = SubscriptionManager.getDefaultDataSubId();
+        Rlog.d(LOG_TAG, "subId = " + subId + ", DDS = " + dds);
+
         // TODO
         // use apnType as the key to which connection we're talking about.
         // pass apnType back up to fetch particular for this one.
@@ -146,16 +213,16 @@
 
         try {
             if (mRegistry != null) {
-                mRegistry.notifyDataConnection(
-                        convertDataState(state),
-                        sender.isDataConnectivityPossible(apnType), reason,
-                        sender.getActiveApnHost(apnType),
-                        apnType,
-                        linkProperties,
-                        networkCapabilities,
-                        ((telephony!=null) ? telephony.getNetworkType() :
-                        TelephonyManager.NETWORK_TYPE_UNKNOWN),
-                        roaming);
+                mRegistry.notifyDataConnectionUsingSubId(subId,
+                    convertDataState(state),
+                    sender.isDataConnectivityPossible(apnType), reason,
+                    sender.getActiveApnHost(apnType),
+                    apnType,
+                    linkProperties,
+                    networkCapabilities,
+                    ((telephony!=null) ? telephony.getNetworkType() :
+                    TelephonyManager.NETWORK_TYPE_UNKNOWN),
+                    roaming);
             }
         } catch (RemoteException ex) {
             // system process is dead
@@ -164,9 +231,10 @@
 
     @Override
     public void notifyDataConnectionFailed(Phone sender, String reason, String apnType) {
+        long subId = sender.getSubId();
         try {
             if (mRegistry != null) {
-                mRegistry.notifyDataConnectionFailed(reason, apnType);
+                mRegistry.notifyDataConnectionFailedUsingSubId(subId, reason, apnType);
             }
         } catch (RemoteException ex) {
             // system process is dead
@@ -175,11 +243,12 @@
 
     @Override
     public void notifyCellLocation(Phone sender) {
+        long subId = sender.getSubId();
         Bundle data = new Bundle();
         sender.getCellLocation().fillInNotifierBundle(data);
         try {
             if (mRegistry != null) {
-                mRegistry.notifyCellLocation(data);
+                mRegistry.notifyCellLocationUsingSubId(subId, data);
             }
         } catch (RemoteException ex) {
             // system process is dead
@@ -188,9 +257,10 @@
 
     @Override
     public void notifyCellInfo(Phone sender, List<CellInfo> cellInfo) {
+        long subId = sender.getSubId();
         try {
             if (mRegistry != null) {
-                mRegistry.notifyCellInfo(cellInfo);
+                mRegistry.notifyCellInfoUsingSubId(subId, cellInfo);
             }
         } catch (RemoteException ex) {
 
@@ -209,6 +279,7 @@
 
     @Override
     public void notifyOtaspChanged(Phone sender, int otaspMode) {
+        // FIXME: subId?
         try {
             if (mRegistry != null) {
                 mRegistry.notifyOtaspChanged(otaspMode);
@@ -219,6 +290,7 @@
     }
 
     public void notifyPreciseCallState(Phone sender) {
+        // FIXME: subId?
         Call ringingCall = sender.getRingingCall();
         Call foregroundCall = sender.getForegroundCall();
         Call backgroundCall = sender.getBackgroundCall();
@@ -235,6 +307,7 @@
     }
 
     public void notifyDisconnectCause(int cause, int preciseCause) {
+        // FIXME: subId?
         try {
             mRegistry.notifyDisconnectCause(cause, preciseCause);
         } catch (RemoteException ex) {
@@ -244,6 +317,7 @@
 
     public void notifyPreciseDataConnectionFailed(Phone sender, String reason, String apnType,
             String apn, String failCause) {
+        // FIXME: subId?
         try {
             mRegistry.notifyPreciseDataConnectionFailed(reason, apnType, apn, failCause);
         } catch (RemoteException ex) {
@@ -251,6 +325,16 @@
         }
     }
 
+    @Override
+    public void notifyVoLteServiceStateChanged(Phone sender, VoLteServiceState lteState) {
+        // FIXME: subID
+        try {
+            mRegistry.notifyVoLteServiceStateChanged(lteState);
+        } catch (RemoteException ex) {
+            // system process is dead
+        }
+    }
+
     /**
      * Convert the {@link PhoneConstants.State} enum into the TelephonyManager.CALL_STATE_*
      * constants for the public API.
@@ -406,4 +490,9 @@
                 return Call.State.IDLE;
         }
     }
+
+    public interface IDataStateChangedCallback {
+        void onDataStateChanged(long subId, String state, String reason, String apnName,
+            String apnType, boolean unavailable);
+    }
 }
diff --git a/src/java/com/android/internal/telephony/HardwareConfig.java b/src/java/com/android/internal/telephony/HardwareConfig.java
new file mode 100644
index 0000000..0f855de
--- /dev/null
+++ b/src/java/com/android/internal/telephony/HardwareConfig.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony;
+
+import android.telephony.Rlog;
+import java.util.BitSet;
+import android.telephony.ServiceState;
+
+/**
+ * {@hide}
+ *
+ * hardware configuration information reported by the ril layer and for
+ * use by the telephone framework.
+ *
+ * the hardware configuration is managed by the TelephonyDevController
+ * (aka: the 'TDC').
+ *
+ * the hardware resources are:
+ *    - modem: physical entity providing acces technology.
+ *    - sim: physicaly entity providing a slot interface.
+ */
+public class HardwareConfig {
+    static final String LOG_TAG = "HardwareConfig";
+
+    /**
+     * hardware configuration kind.
+     */
+    public static final int DEV_HARDWARE_TYPE_MODEM = 0;
+    public static final int DEV_HARDWARE_TYPE_SIM   = 1;
+    /**
+     * ril attachment model.  if single, there is a one-to-one
+     * relationship between a modem hardware and a ril daemon.
+     * if multiple, there is a one-to-many relatioship between a
+     * modem hardware and several ril simultaneous ril daemons.
+     */
+    public static final int DEV_MODEM_RIL_MODEL_SINGLE   = 0;
+    public static final int DEV_MODEM_RIL_MODEL_MULTIPLE = 1;
+    /**
+     * hardware state of the resource.
+     *
+     *   enabled: the resource can be used by the msim-framework,
+     *            call activity can be handled on it.
+     *   standby: the resource can be used by the msim-framework but
+     *            only for non call related activity.  as example:
+     *            reading the address book from a sim device. attempt
+     *            to use this resource for call activity leads to
+     *            undetermined results.
+     *   disabled: the resource  cannot be used and attempt to use
+     *             it leads to undetermined results.
+     *
+     * by default, all resources are 'enabled', what causes a resource
+     * to be marked otherwise is a property of the underlying hardware
+     * knowledge and implementation and it is out of scope of the TDC.
+     */
+    public static final int DEV_HARDWARE_STATE_ENABLED  = 0;
+    public static final int DEV_HARDWARE_STATE_STANDBY  = 1;
+    public static final int DEV_HARDWARE_STATE_DISABLED = 2;
+
+    /**
+     * common hardware configuration.
+     *
+     * type - see DEV_HARDWARE_TYPE_
+     * uuid - unique identifier for this hardware.
+     * state - see DEV_HARDWARE_STATE_
+     */
+    public int type;
+    public String uuid;
+    public int state;
+    /**
+     * following is some specific hardware configuration based on the hardware type.
+     */
+    /**
+     * DEV_HARDWARE_TYPE_MODEM.
+     *
+     * rilModel - see DEV_MODEM_RIL_MODEL_
+     * rat - BitSet value, based on android.telephony.ServiceState
+     * maxActiveVoiceCall - maximum number of concurent active voice calls.
+     * maxActiveDataCall - maximum number of concurent active data calls.
+     * maxStandby - maximum number of concurent standby connections.
+     *
+     * note: the maxStandby is not necessarily an equal sum of the maxActiveVoiceCall
+     * and maxActiveDataCall (nor a derivative of it) since it really depends on the
+     * modem capability, hence it is left for the hardware to define.
+     */
+    public int rilModel;
+    public BitSet rat;
+    public int maxActiveVoiceCall;
+    public int maxActiveDataCall;
+    public int maxStandby;
+    /**
+     * DEV_HARDWARE_TYPE_SIM.
+     *
+     * modemUuid - unique association to a modem for a sim.
+     */
+    public String modemUuid;
+
+    /**
+     * default constructor.
+     */
+    public HardwareConfig(int type) {
+        type = type;
+    }
+
+    /**
+     * create from a resource string format.
+     */
+    public HardwareConfig(String res) {
+        String split[] = res.split(",");
+
+        type = Integer.parseInt(split[0]);
+
+        switch (type) {
+            case DEV_HARDWARE_TYPE_MODEM: {
+                assignModem(
+                    split[1].trim(),            /* uuid */
+                    Integer.parseInt(split[2]), /* state */
+                    Integer.parseInt(split[3]), /* ril-model */
+                    Integer.parseInt(split[4]), /* rat */
+                    Integer.parseInt(split[5]), /* max-voice */
+                    Integer.parseInt(split[6]), /* max-data */
+                    Integer.parseInt(split[7])  /* max-standby */
+                );
+                break;
+            }
+            case DEV_HARDWARE_TYPE_SIM: {
+                assignSim(
+                    split[1].trim(),            /* uuid */
+                    Integer.parseInt(split[2]), /* state */
+                    split[3].trim()             /* modem-uuid */
+                );
+                break;
+            }
+        }
+    }
+
+    public void assignModem(String id, int state, int model, int ratBits,
+        int maxV, int maxD, int maxS) {
+        if (type == DEV_HARDWARE_TYPE_MODEM) {
+            char[] bits = Integer.toBinaryString(ratBits).toCharArray();
+            uuid = id;
+            state = state;
+            rilModel = model;
+            rat = new BitSet(bits.length);
+            for (int i = 0 ; i < bits.length ; i++) {
+                rat.set(i, (bits[i] == '1' ? true : false));
+            }
+            maxActiveVoiceCall = maxV;
+            maxActiveDataCall = maxD;
+            maxStandby = maxS;
+        }
+    }
+
+    public void assignSim(String id, int state, String link) {
+        if (type == DEV_HARDWARE_TYPE_SIM) {
+            uuid = id;
+            modemUuid = link;
+            state = state;
+        }
+    }
+
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        if (type == DEV_HARDWARE_TYPE_MODEM) {
+            builder.append("Modem ");
+            builder.append("{ uuid=" + uuid);
+            builder.append(", state=" + state);
+            builder.append(", rilModel=" + rilModel);
+            builder.append(", rat=" + rat.toString());
+            builder.append(", maxActiveVoiceCall=" + maxActiveVoiceCall);
+            builder.append(", maxActiveDataCall=" + maxActiveDataCall);
+            builder.append(", maxStandby=" + maxStandby);
+            builder.append(" }");
+        } else if (type == DEV_HARDWARE_TYPE_SIM) {
+            builder.append("Sim ");
+            builder.append("{ uuid=" + uuid);
+            builder.append(", modemUuid=" + modemUuid);
+            builder.append(", state=" + state);
+            builder.append(" }");
+        } else {
+            builder.append("Invalid Configration");
+        }
+        return builder.toString();
+    }
+
+    public int compareTo(HardwareConfig hw) {
+        String one = this.toString();
+        String two = hw.toString();
+
+        return (one.compareTo(two));
+    }
+}
diff --git a/src/java/com/android/internal/telephony/IIccPhoneBook.aidl b/src/java/com/android/internal/telephony/IIccPhoneBook.aidl
index 95b3efb..0f802cc 100644
--- a/src/java/com/android/internal/telephony/IIccPhoneBook.aidl
+++ b/src/java/com/android/internal/telephony/IIccPhoneBook.aidl
@@ -46,6 +46,16 @@
     List<AdnRecord> getAdnRecordsInEf(int efid);
 
     /**
+     * Loads the AdnRecords in efid and returns them as a
+     * List of AdnRecords
+     *
+     * @param efid the EF id of a ADN-like SIM
+     * @param subId user preferred subId
+     * @return List of AdnRecord
+     */
+    List<AdnRecord> getAdnRecordsInEfUsingSubId(long subId, int efid);
+
+    /**
      * Replace oldAdn with newAdn in ADN-like record in EF
      *
      * getAdnRecordsInEf must be called at least once before this function,
@@ -68,6 +78,31 @@
             String newTag, String newPhoneNumber,
             String pin2);
 
+
+
+    /**
+     * Replace oldAdn with newAdn in ADN-like record in EF
+     *
+     * getAdnRecordsInEf must be called at least once before this function,
+     * otherwise an error will be returned
+     *
+     * @param efid must be one among EF_ADN, EF_FDN, and EF_SDN
+     * @param oldTag adn tag to be replaced
+     * @param oldPhoneNumber adn number to be replaced
+     *        Set both oldTag and oldPhoneNubmer to "" means to replace an
+     *        empty record, aka, insert new record
+     * @param newTag adn tag to be stored
+     * @param newPhoneNumber adn number ot be stored
+     *        Set both newTag and newPhoneNubmer to "" means to replace the old
+     *        record with empty one, aka, delete old record
+     * @param pin2 required to update EF_FDN, otherwise must be null
+     * @param subId user preferred subId
+     * @return true for success
+     */
+    boolean updateAdnRecordsInEfBySearchUsingSubId(long subId, int efid,
+            String oldTag, String oldPhoneNumber,
+            String newTag, String newPhoneNumber,
+            String pin2);
     /**
      * Update an ADN-like EF record by record index
      *
@@ -88,6 +123,26 @@
             String pin2);
 
     /**
+     * Update an ADN-like EF record by record index
+     *
+     * This is useful for iteration the whole ADN file, such as write the whole
+     * phone book or erase/format the whole phonebook
+     *
+     * @param efid must be one among EF_ADN, EF_FDN, and EF_SDN
+     * @param newTag adn tag to be stored
+     * @param newPhoneNumber adn number to be stored
+     *        Set both newTag and newPhoneNubmer to "" means to replace the old
+     *        record with empty one, aka, delete old record
+     * @param index is 1-based adn record index to be updated
+     * @param pin2 required to update EF_FDN, otherwise must be null
+     * @param subId user preferred subId
+     * @return true for success
+     */
+    boolean updateAdnRecordsInEfByIndexUsingSubId(long subId, int efid, String newTag,
+            String newPhoneNumber, int index,
+            String pin2);
+
+    /**
      * Get the max munber of records in efid
      *
      * @param efid the EF id of a ADN-like SIM
@@ -98,4 +153,16 @@
      */
     int[] getAdnRecordsSize(int efid);
 
+    /**
+     * Get the max munber of records in efid
+     *
+     * @param efid the EF id of a ADN-like SIM
+     * @param subId user preferred subId
+     * @return  int[3] array
+     *            recordSizes[0]  is the single record length
+     *            recordSizes[1]  is the total length of the EF file
+     *            recordSizes[2]  is the number of records in the EF file
+     */
+    int[] getAdnRecordsSizeUsingSubId(long subId, int efid);
+
 }
diff --git a/src/java/com/android/internal/telephony/IccPhoneBookInterfaceManager.java b/src/java/com/android/internal/telephony/IccPhoneBookInterfaceManager.java
index d4ae7fb..8699148 100644
--- a/src/java/com/android/internal/telephony/IccPhoneBookInterfaceManager.java
+++ b/src/java/com/android/internal/telephony/IccPhoneBookInterfaceManager.java
@@ -28,6 +28,8 @@
 import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppType;
 import com.android.internal.telephony.uicc.IccConstants;
 import com.android.internal.telephony.uicc.IccRecords;
+import com.android.internal.telephony.uicc.UiccCard;
+import com.android.internal.telephony.uicc.UiccCardApplication;
 
 import java.util.List;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -36,16 +38,19 @@
  * SimPhoneBookInterfaceManager to provide an inter-process communication to
  * access ADN-like SIM records.
  */
-public abstract class IccPhoneBookInterfaceManager extends IIccPhoneBook.Stub {
+public abstract class IccPhoneBookInterfaceManager {
     protected static final boolean DBG = true;
 
     protected PhoneBase mPhone;
+    private   UiccCardApplication mCurrentApp = null;
     protected AdnRecordCache mAdnCache;
     protected final Object mLock = new Object();
     protected int mRecordSize[];
     protected boolean mSuccess;
+    private   boolean mIs3gCard = false;  // flag to determine if card is 3G or 2G
     protected List<AdnRecord> mRecords;
 
+
     protected static final boolean ALLOW_SIM_OP_IN_UI_THREAD = false;
 
     protected static final int EVENT_GET_SIZE_DONE = 1;
@@ -126,11 +131,6 @@
         }
     }
 
-    protected void publish() {
-        //NOTE service "simphonebook" added by IccSmsInterfaceManagerProxy
-        ServiceManager.addService("simphonebook", this);
-    }
-
     protected abstract void logd(String msg);
 
     protected abstract void loge(String msg);
@@ -155,7 +155,6 @@
      * @param pin2 required to update EF_FDN, otherwise must be null
      * @return true for success
      */
-    @Override
     public boolean
     updateAdnRecordsInEfBySearch (int efid,
             String oldTag, String oldPhoneNumber,
@@ -210,7 +209,6 @@
      * @param pin2 required to update EF_FDN, otherwise must be null
      * @return true for success
      */
-    @Override
     public boolean
     updateAdnRecordsInEfByIndex(int efid, String newTag,
             String newPhoneNumber, int index, String pin2) {
@@ -250,7 +248,6 @@
      *            recordSizes[1]  is the total length of the EF file
      *            recordSizes[2]  is the number of records in the EF file
      */
-    @Override
     public abstract int[] getAdnRecordsSize(int efid);
 
     /**
@@ -262,7 +259,6 @@
      * @param efid the EF id of a ADN-like ICC
      * @return List of AdnRecord
      */
-    @Override
     public List<AdnRecord> getAdnRecordsInEf(int efid) {
 
         if (mPhone.getContext().checkCallingOrSelfPermission(
diff --git a/src/java/com/android/internal/telephony/IccPhoneBookInterfaceManagerProxy.java b/src/java/com/android/internal/telephony/IccPhoneBookInterfaceManagerProxy.java
index 3a2fbc5..b2ccbfe 100644
--- a/src/java/com/android/internal/telephony/IccPhoneBookInterfaceManagerProxy.java
+++ b/src/java/com/android/internal/telephony/IccPhoneBookInterfaceManagerProxy.java
@@ -27,15 +27,12 @@
  * SimPhoneBookInterfaceManager to provide an inter-process communication to
  * access ADN-like SIM records.
  */
-public class IccPhoneBookInterfaceManagerProxy extends IIccPhoneBook.Stub {
+public class IccPhoneBookInterfaceManagerProxy {
     private IccPhoneBookInterfaceManager mIccPhoneBookInterfaceManager;
 
     public IccPhoneBookInterfaceManagerProxy(IccPhoneBookInterfaceManager
             iccPhoneBookInterfaceManager) {
         mIccPhoneBookInterfaceManager = iccPhoneBookInterfaceManager;
-        if(ServiceManager.getService("simphonebook") == null) {
-            ServiceManager.addService("simphonebook", this);
-        }
     }
 
     public void setmIccPhoneBookInterfaceManager(
@@ -43,7 +40,6 @@
         mIccPhoneBookInterfaceManager = iccPhoneBookInterfaceManager;
     }
 
-    @Override
     public boolean
     updateAdnRecordsInEfBySearch (int efid,
             String oldTag, String oldPhoneNumber,
@@ -53,7 +49,6 @@
                 efid, oldTag, oldPhoneNumber, newTag, newPhoneNumber, pin2);
     }
 
-    @Override
     public boolean
     updateAdnRecordsInEfByIndex(int efid, String newTag,
             String newPhoneNumber, int index, String pin2) {
@@ -61,12 +56,10 @@
                 newTag, newPhoneNumber, index, pin2);
     }
 
-    @Override
     public int[] getAdnRecordsSize(int efid) {
         return mIccPhoneBookInterfaceManager.getAdnRecordsSize(efid);
     }
 
-    @Override
     public List<AdnRecord> getAdnRecordsInEf(int efid) {
         return mIccPhoneBookInterfaceManager.getAdnRecordsInEf(efid);
     }
diff --git a/src/java/com/android/internal/telephony/IccProvider.java b/src/java/com/android/internal/telephony/IccProvider.java
old mode 100644
new mode 100755
index 6f2c4ed..79d2eba
--- a/src/java/com/android/internal/telephony/IccProvider.java
+++ b/src/java/com/android/internal/telephony/IccProvider.java
@@ -20,10 +20,14 @@
 import android.content.UriMatcher;
 import android.content.ContentValues;
 import android.database.Cursor;
+import android.database.MergeCursor;
 import android.database.MatrixCursor;
 import android.net.Uri;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.telephony.SubInfoRecord;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.telephony.Rlog;
 
@@ -39,7 +43,7 @@
  */
 public class IccProvider extends ContentProvider {
     private static final String TAG = "IccProvider";
-    private static final boolean DBG = false;
+    private static final boolean DBG = true;
 
 
     private static final String[] ADDRESS_BOOK_COLUMN_NAMES = new String[] {
@@ -49,25 +53,31 @@
         "_id"
     };
 
-    private static final int ADN = 1;
-    private static final int FDN = 2;
-    private static final int SDN = 3;
+    protected static final int ADN = 1;
+    protected static final int ADN_SUB = 2;
+    protected static final int FDN = 3;
+    protected static final int FDN_SUB = 4;
+    protected static final int SDN = 5;
+    protected static final int SDN_SUB = 6;
+    protected static final int ADN_ALL = 7;
 
-    private static final String STR_TAG = "tag";
-    private static final String STR_NUMBER = "number";
-    private static final String STR_EMAILS = "emails";
-    private static final String STR_PIN2 = "pin2";
+    protected static final String STR_TAG = "tag";
+    protected static final String STR_NUMBER = "number";
+    protected static final String STR_EMAILS = "emails";
+    protected static final String STR_PIN2 = "pin2";
 
     private static final UriMatcher URL_MATCHER =
                             new UriMatcher(UriMatcher.NO_MATCH);
 
     static {
         URL_MATCHER.addURI("icc", "adn", ADN);
+        URL_MATCHER.addURI("icc", "adn/subId/#", ADN_SUB);
         URL_MATCHER.addURI("icc", "fdn", FDN);
+        URL_MATCHER.addURI("icc", "fdn/subId/#", FDN_SUB);
         URL_MATCHER.addURI("icc", "sdn", SDN);
+        URL_MATCHER.addURI("icc", "sdn/subId/#", SDN_SUB);
     }
 
-
     @Override
     public boolean onCreate() {
         return true;
@@ -76,27 +86,66 @@
     @Override
     public Cursor query(Uri url, String[] projection, String selection,
             String[] selectionArgs, String sort) {
+        if (DBG) log("query");
+
         switch (URL_MATCHER.match(url)) {
             case ADN:
-                return loadFromEf(IccConstants.EF_ADN);
+                return loadFromEf(IccConstants.EF_ADN, SubscriptionManager.getDefaultSubId());
+
+            case ADN_SUB:
+                return loadFromEf(IccConstants.EF_ADN, getRequestSubId(url));
 
             case FDN:
-                return loadFromEf(IccConstants.EF_FDN);
+                return loadFromEf(IccConstants.EF_FDN, SubscriptionManager.getDefaultSubId());
+
+            case FDN_SUB:
+                return loadFromEf(IccConstants.EF_FDN, getRequestSubId(url));
 
             case SDN:
-                return loadFromEf(IccConstants.EF_SDN);
+                return loadFromEf(IccConstants.EF_SDN, SubscriptionManager.getDefaultSubId());
+
+            case SDN_SUB:
+                return loadFromEf(IccConstants.EF_SDN, getRequestSubId(url));
+
+            case ADN_ALL:
+                return loadAllSimContacts(IccConstants.EF_ADN);
 
             default:
                 throw new IllegalArgumentException("Unknown URL " + url);
         }
     }
 
+    private Cursor loadAllSimContacts(int efType) {
+        Cursor [] result;
+        List<SubInfoRecord> subInfoList = SubscriptionManager.getActivatedSubInfoList(null);
+
+        if ((subInfoList == null) || (subInfoList.size() == 0)) {
+            result = new Cursor[0];
+        } else {
+            int subIdCount = subInfoList.size();
+            result = new Cursor[subIdCount];
+            long subId;
+
+            for (int i = 0; i < subIdCount; i++) {
+                subId = subInfoList.get(i).mSubId;
+                result[i] = loadFromEf(efType, subId);
+                Rlog.i(TAG,"ADN Records loaded for Subscription ::" + subId);
+            }
+        }
+
+        return new MergeCursor(result);
+    }
+
     @Override
     public String getType(Uri url) {
         switch (URL_MATCHER.match(url)) {
             case ADN:
+            case ADN_SUB:
             case FDN:
+            case FDN_SUB:
             case SDN:
+            case SDN_SUB:
+            case ADN_ALL:
                 return "vnd.android.cursor.dir/sim-contact";
 
             default:
@@ -109,6 +158,7 @@
         Uri resultUri;
         int efType;
         String pin2 = null;
+        long subId;
 
         if (DBG) log("insert");
 
@@ -116,10 +166,23 @@
         switch (match) {
             case ADN:
                 efType = IccConstants.EF_ADN;
+                subId = SubscriptionManager.getDefaultSubId();
+                break;
+
+            case ADN_SUB:
+                efType = IccConstants.EF_ADN;
+                subId = getRequestSubId(url);
                 break;
 
             case FDN:
                 efType = IccConstants.EF_FDN;
+                subId = SubscriptionManager.getDefaultSubId();
+                pin2 = initialValues.getAsString("pin2");
+                break;
+
+            case FDN_SUB:
+                efType = IccConstants.EF_FDN;
+                subId = getRequestSubId(url);
                 pin2 = initialValues.getAsString("pin2");
                 break;
 
@@ -131,7 +194,7 @@
         String tag = initialValues.getAsString("tag");
         String number = initialValues.getAsString("number");
         // TODO(): Read email instead of sending null.
-        boolean success = addIccRecordToEf(efType, tag, number, null, pin2);
+        boolean success = addIccRecordToEf(efType, tag, number, null, pin2, subId);
 
         if (!success) {
             return null;
@@ -143,9 +206,17 @@
                 buf.append("adn/");
                 break;
 
+            case ADN_SUB:
+                buf.append("adn/subId/");
+                break;
+
             case FDN:
                 buf.append("fdn/");
                 break;
+
+            case FDN_SUB:
+                buf.append("fdn/subId/");
+                break;
         }
 
         // TODO: we need to find out the rowId for the newly added record
@@ -153,6 +224,7 @@
 
         resultUri = Uri.parse(buf.toString());
 
+        getContext().getContentResolver().notifyChange(url, null);
         /*
         // notify interested parties that an insertion happened
         getContext().getContentResolver().notifyInsert(
@@ -164,6 +236,11 @@
 
     private String normalizeValue(String inVal) {
         int len = inVal.length();
+        // If name is empty in contact return null to avoid crash.
+        if (len == 0) {
+            if (DBG) log("len of input String is 0");
+            return inVal;
+        }
         String retVal = inVal;
 
         if (inVal.charAt(0) == '\'' && inVal.charAt(len-1) == '\'') {
@@ -176,17 +253,28 @@
     @Override
     public int delete(Uri url, String where, String[] whereArgs) {
         int efType;
-
-        if (DBG) log("delete");
+        long subId;
 
         int match = URL_MATCHER.match(url);
         switch (match) {
             case ADN:
                 efType = IccConstants.EF_ADN;
+                subId = SubscriptionManager.getDefaultSubId();
+                break;
+
+            case ADN_SUB:
+                efType = IccConstants.EF_ADN;
+                subId = getRequestSubId(url);
                 break;
 
             case FDN:
                 efType = IccConstants.EF_FDN;
+                subId = SubscriptionManager.getDefaultSubId();
+                break;
+
+            case FDN_SUB:
+                efType = IccConstants.EF_FDN;
+                subId = getRequestSubId(url);
                 break;
 
             default:
@@ -194,6 +282,8 @@
                         "Cannot insert into URL: " + url);
         }
 
+        if (DBG) log("delete");
+
         // parse where clause
         String tag = null;
         String number = null;
@@ -207,8 +297,12 @@
             String param = tokens[n];
             if (DBG) log("parsing '" + param + "'");
 
-            String[] pair = param.split("=", 2);
+            String[] pair = param.split("=");
 
+            if (pair.length != 2) {
+                Rlog.e(TAG, "resolve: bad whereClause parameter: " + param);
+                continue;
+            }
             String key = pair[0].trim();
             String val = pair[1].trim();
 
@@ -224,26 +318,24 @@
             }
         }
 
-        if (TextUtils.isEmpty(number)) {
+        if (efType == FDN && TextUtils.isEmpty(pin2)) {
             return 0;
         }
 
-        if (efType == IccConstants.EF_FDN && TextUtils.isEmpty(pin2)) {
-            return 0;
-        }
-
-        boolean success = deleteIccRecordFromEf(efType, tag, number, emails, pin2);
+        boolean success = deleteIccRecordFromEf(efType, tag, number, emails, pin2, subId);
         if (!success) {
             return 0;
         }
 
+        getContext().getContentResolver().notifyChange(url, null);
         return 1;
     }
 
     @Override
     public int update(Uri url, ContentValues values, String where, String[] whereArgs) {
-        int efType;
         String pin2 = null;
+        int efType;
+        long subId;
 
         if (DBG) log("update");
 
@@ -251,10 +343,23 @@
         switch (match) {
             case ADN:
                 efType = IccConstants.EF_ADN;
+                subId = SubscriptionManager.getDefaultSubId();
+                break;
+
+            case ADN_SUB:
+                efType = IccConstants.EF_ADN;
+                subId = getRequestSubId(url);
                 break;
 
             case FDN:
                 efType = IccConstants.EF_FDN;
+                subId = SubscriptionManager.getDefaultSubId();
+                pin2 = values.getAsString("pin2");
+                break;
+
+            case FDN_SUB:
+                efType = IccConstants.EF_FDN;
+                subId = getRequestSubId(url);
                 pin2 = values.getAsString("pin2");
                 break;
 
@@ -271,24 +376,25 @@
         String[] newEmails = null;
         // TODO(): Update for email.
         boolean success = updateIccRecordInEf(efType, tag, number,
-                newTag, newNumber, pin2);
+                newTag, newNumber, pin2, subId);
 
         if (!success) {
             return 0;
         }
 
+        getContext().getContentResolver().notifyChange(url, null);
         return 1;
     }
 
-    private MatrixCursor loadFromEf(int efType) {
-        if (DBG) log("loadFromEf: efType=" + efType);
+    private MatrixCursor loadFromEf(int efType, long subId) {
+        if (DBG) log("loadFromEf: efType=" + efType + ", subscription=" + subId);
 
         List<AdnRecord> adnRecords = null;
         try {
             IIccPhoneBook iccIpb = IIccPhoneBook.Stub.asInterface(
                     ServiceManager.getService("simphonebook"));
             if (iccIpb != null) {
-                adnRecords = iccIpb.getAdnRecordsInEf(efType);
+                adnRecords = iccIpb.getAdnRecordsInEfUsingSubId(subId, efType);
             }
         } catch (RemoteException ex) {
             // ignore it
@@ -313,9 +419,10 @@
     }
 
     private boolean
-    addIccRecordToEf(int efType, String name, String number, String[] emails, String pin2) {
+    addIccRecordToEf(int efType, String name, String number, String[] emails,
+            String pin2, long subId) {
         if (DBG) log("addIccRecordToEf: efType=" + efType + ", name=" + name +
-                ", number=" + number + ", emails=" + emails);
+                ", number=" + number + ", emails=" + emails + ", subscription=" + subId);
 
         boolean success = false;
 
@@ -328,8 +435,8 @@
             IIccPhoneBook iccIpb = IIccPhoneBook.Stub.asInterface(
                     ServiceManager.getService("simphonebook"));
             if (iccIpb != null) {
-                success = iccIpb.updateAdnRecordsInEfBySearch(efType, "", "",
-                        name, number, pin2);
+                success = iccIpb.updateAdnRecordsInEfBySearchUsingSubId(subId, efType,
+                        "", "", name, number, pin2);
             }
         } catch (RemoteException ex) {
             // ignore it
@@ -342,18 +449,20 @@
 
     private boolean
     updateIccRecordInEf(int efType, String oldName, String oldNumber,
-            String newName, String newNumber, String pin2) {
+            String newName, String newNumber, String pin2, long subId) {
         if (DBG) log("updateIccRecordInEf: efType=" + efType +
                 ", oldname=" + oldName + ", oldnumber=" + oldNumber +
-                ", newname=" + newName + ", newnumber=" + newNumber);
+                ", newname=" + newName + ", newnumber=" + newNumber +
+                ", subscription=" + subId);
+
         boolean success = false;
 
         try {
             IIccPhoneBook iccIpb = IIccPhoneBook.Stub.asInterface(
                     ServiceManager.getService("simphonebook"));
             if (iccIpb != null) {
-                success = iccIpb.updateAdnRecordsInEfBySearch(efType,
-                        oldName, oldNumber, newName, newNumber, pin2);
+                success = iccIpb.updateAdnRecordsInEfBySearchUsingSubId(subId, efType, oldName,
+                        oldNumber, newName, newNumber, pin2);
             }
         } catch (RemoteException ex) {
             // ignore it
@@ -366,9 +475,10 @@
 
 
     private boolean deleteIccRecordFromEf(int efType, String name, String number, String[] emails,
-            String pin2) {
+            String pin2, long subId) {
         if (DBG) log("deleteIccRecordFromEf: efType=" + efType +
-                ", name=" + name + ", number=" + number + ", emails=" + emails + ", pin2=" + pin2);
+                ", name=" + name + ", number=" + number + ", emails=" + emails +
+                ", pin2=" + pin2 + ", subscription=" + subId);
 
         boolean success = false;
 
@@ -376,8 +486,8 @@
             IIccPhoneBook iccIpb = IIccPhoneBook.Stub.asInterface(
                     ServiceManager.getService("simphonebook"));
             if (iccIpb != null) {
-                success = iccIpb.updateAdnRecordsInEfBySearch(efType,
-                        name, number, "", "", pin2);
+                success = iccIpb.updateAdnRecordsInEfBySearchUsingSubId(subId, efType,
+                          name, number, "", "", pin2);
             }
         } catch (RemoteException ex) {
             // ignore it
@@ -423,4 +533,13 @@
         Rlog.d(TAG, "[IccProvider] " + msg);
     }
 
+    private long getRequestSubId(Uri url) {
+        if (DBG) log("getRequestSubId url: " + url);
+
+        try {
+            return Long.parseLong(url.getLastPathSegment());
+        } catch (NumberFormatException ex) {
+            throw new IllegalArgumentException("Unknown URL " + url);
+        }
+    }
 }
diff --git a/src/java/com/android/internal/telephony/IccSmsInterfaceManager.java b/src/java/com/android/internal/telephony/IccSmsInterfaceManager.java
index 938fd38..acef542 100644
--- a/src/java/com/android/internal/telephony/IccSmsInterfaceManager.java
+++ b/src/java/com/android/internal/telephony/IccSmsInterfaceManager.java
@@ -47,7 +47,7 @@
  * IccSmsInterfaceManager to provide an inter-process communication to
  * access Sms in Icc.
  */
-public class IccSmsInterfaceManager extends ISms.Stub {
+public class IccSmsInterfaceManager {
     static final String LOG_TAG = "IccSmsInterfaceManager";
     static final boolean DBG = true;
 
@@ -120,9 +120,6 @@
         mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
         mDispatcher = new ImsSMSDispatcher(phone,
                 phone.mSmsStorageMonitor, phone.mSmsUsageMonitor);
-        if (ServiceManager.getService("isms") == null) {
-            ServiceManager.addService("isms", this);
-        }
     }
 
     protected void markMessagesAsRead(ArrayList<byte[]> messages) {
@@ -181,7 +178,7 @@
      * @return success or not
      *
      */
-    @Override
+
     public boolean
     updateMessageOnIccEf(String callingPackage, int index, int status, byte[] pdu) {
         if (DBG) log("updateMessageOnIccEf: index=" + index +
@@ -236,7 +233,6 @@
      * @return success or not
      *
      */
-    @Override
     public boolean copyMessageToIccEf(String callingPackage, int status, byte[] pdu, byte[] smsc) {
         //NOTE smsc not used in RUIM
         if (DBG) log("copyMessageToIccEf: status=" + status + " ==> " +
@@ -274,7 +270,7 @@
      *
      * @return list of SmsRawData of all sms on Icc
      */
-    @Override
+
     public List<SmsRawData> getAllMessagesFromIccEf(String callingPackage) {
         if (DBG) log("getAllMessagesFromEF");
 
@@ -333,7 +329,7 @@
      *  broadcast when the message is delivered to the recipient.  The
      *  raw pdu of the status report is in the extended data ("pdu").
      */
-    @Override
+
     public void sendData(String callingPackage, String destAddr, String scAddr, int destPort,
             byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) {
         mPhone.getContext().enforceCallingPermission(
@@ -375,7 +371,7 @@
      *  broadcast when the message is delivered to the recipient.  The
      *  raw pdu of the status report is in the extended data ("pdu").
      */
-    @Override
+
     public void sendText(String callingPackage, String destAddr, String scAddr,
             String text, PendingIntent sentIntent, PendingIntent deliveryIntent) {
         mPhone.getContext().enforceCallingPermission(
@@ -418,7 +414,7 @@
      *   to the recipient.  The raw pdu of the status report is in the
      *   extended data ("pdu").
      */
-    @Override
+
     public void sendMultipartText(String callingPackage, String destAddr, String scAddr,
             List<String> parts, List<PendingIntent> sentIntents,
             List<PendingIntent> deliveryIntents) {
@@ -440,12 +436,12 @@
                 (ArrayList<PendingIntent>) sentIntents, (ArrayList<PendingIntent>) deliveryIntents);
     }
 
-    @Override
+
     public int getPremiumSmsPermission(String packageName) {
         return mDispatcher.getPremiumSmsPermission(packageName);
     }
 
-    @Override
+
     public void setPremiumSmsPermission(String packageName, int permission) {
         mDispatcher.setPremiumSmsPermission(packageName, permission);
     }
diff --git a/src/java/com/android/internal/telephony/InboundSmsHandler.java b/src/java/com/android/internal/telephony/InboundSmsHandler.java
index 95a650f..709bfc2 100644
--- a/src/java/com/android/internal/telephony/InboundSmsHandler.java
+++ b/src/java/com/android/internal/telephony/InboundSmsHandler.java
@@ -37,8 +37,10 @@
 import android.provider.Telephony.Sms.Intents;
 import android.telephony.Rlog;
 import android.telephony.SmsMessage;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 
+import com.android.internal.telephony.PhoneBase;
 import com.android.internal.util.HexDump;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
@@ -219,6 +221,11 @@
         }
     }
 
+    // CAF_MSIM Is this used anywhere ? if not remove it
+    public PhoneBase getPhone() {
+        return mPhone;
+    }
+
     /**
      * This parent state throws an exception (for debug builds) or prints an error for unhandled
      * message types.
@@ -699,9 +706,10 @@
      * @param permission receivers are required to have this permission
      * @param appOp app op that is being performed when dispatching to a receiver
      */
-    void dispatchIntent(Intent intent, String permission, int appOp,
+    protected void dispatchIntent(Intent intent, String permission, int appOp,
             BroadcastReceiver resultReceiver) {
         intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT);
+        SubscriptionManager.putPhoneIdAndSubIdExtra(intent, mPhone.getPhoneId());
         mContext.sendOrderedBroadcast(intent, permission, appOp, resultReceiver,
                 getHandler(), Activity.RESULT_OK, null, null);
     }
diff --git a/src/java/com/android/internal/telephony/MccTable.java b/src/java/com/android/internal/telephony/MccTable.java
index a0c8349..c303219 100644
--- a/src/java/com/android/internal/telephony/MccTable.java
+++ b/src/java/com/android/internal/telephony/MccTable.java
@@ -24,6 +24,7 @@
 import android.os.Build;
 import android.os.RemoteException;
 import android.os.SystemProperties;
+import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.Slog;
 
@@ -175,9 +176,18 @@
      */
     public static void updateMccMncConfiguration(Context context, String mccmnc,
             boolean fromServiceState) {
+        Slog.d(LOG_TAG, "updateMccMncConfiguration mccmnc='" + mccmnc + "' fromServiceState=" + fromServiceState);
         if (!TextUtils.isEmpty(mccmnc)) {
             int mcc, mnc;
 
+            String defaultMccMnc = TelephonyManager.getDefault().getSimOperator();
+            Slog.d(LOG_TAG, "updateMccMncConfiguration defaultMccMnc=" + defaultMccMnc);
+            //Update mccmnc only for default subscription in case of MultiSim.
+//            if (!defaultMccMnc.equals(mccmnc)) {
+//                Slog.d(LOG_TAG, "Not a Default subscription, ignoring mccmnc config update.");
+//                return;
+//            }
+
             try {
                 mcc = Integer.parseInt(mccmnc.substring(0,3));
                 mnc = Integer.parseInt(mccmnc.substring(3));
@@ -237,7 +247,8 @@
      * @return Locale or null if no appropriate value
      *  {@hide}
      */
-    public static Locale getLocaleForLanguageCountry(Context context, String language, String country) {
+    public static Locale getLocaleForLanguageCountry(Context context, String language,
+            String country) {
         String l = SystemProperties.get("persist.sys.language");
         String c = SystemProperties.get("persist.sys.country");
 
diff --git a/src/java/com/android/internal/telephony/MmiCode.java b/src/java/com/android/internal/telephony/MmiCode.java
index c71ff77..feab370 100644
--- a/src/java/com/android/internal/telephony/MmiCode.java
+++ b/src/java/com/android/internal/telephony/MmiCode.java
@@ -45,6 +45,11 @@
     public CharSequence getMessage();
 
     /**
+     * @return Phone associated with the MMI/USSD message
+     */
+    public Phone getPhone();
+
+    /**
      * Cancels pending MMI request.
      * State becomes CANCELLED unless already COMPLETE or FAILED
      */
diff --git a/src/java/com/android/internal/telephony/Phone.java b/src/java/com/android/internal/telephony/Phone.java
index 5b7b005..706332c 100644
--- a/src/java/com/android/internal/telephony/Phone.java
+++ b/src/java/com/android/internal/telephony/Phone.java
@@ -29,6 +29,7 @@
 
 import com.android.internal.telephony.test.SimulatedRadioControl;
 import com.android.internal.telephony.uicc.IsimRecords;
+import com.android.internal.telephony.uicc.UiccCard;
 import com.android.internal.telephony.uicc.UsimServiceTable;
 
 import com.android.internal.telephony.PhoneConstants.*; // ???? 
@@ -104,6 +105,7 @@
     static final String REASON_LOST_DATA_CONNECTION = "lostDataConnection";
     static final String REASON_CONNECTED = "connected";
     static final String REASON_SINGLE_PDN_ARBITRATION = "SinglePdnArbitration";
+    static final String REASON_DATA_SPECIFIC_DISABLED = "specificDisabled";
 
     // Used for band mode selection methods
     static final int BM_UNSPECIFIED = 0; // selected by baseband automatically
@@ -384,6 +386,23 @@
     void unregisterForRingbackTone(Handler h);
 
     /**
+     * Notifies when out-band on-hold tone is needed.<p>
+     *
+     *  Messages received from this:
+     *  Message.obj will be an AsyncResult
+     *  AsyncResult.userObj = obj
+     *  AsyncResult.result = boolean, true to start play on-hold tone
+     *                       and false to stop. <p>
+     */
+    void registerForOnHoldTone(Handler h, int what, Object obj);
+
+    /**
+     * Unregisters for on-hold tone notification.
+     */
+
+    void unregisterForOnHoldTone(Handler h);
+
+    /**
      * Registers the handler to reset the uplink mute state to get
      * uplink audio.
      */
@@ -592,6 +611,20 @@
     public void unregisterForSubscriptionInfoReady(Handler h);
 
     /**
+     * Registration point for Sim records loaded
+     * @param h handler to notify
+     * @param what what code of message when delivered
+     * @param obj placed in Message.obj
+     */
+    public void registerForSimRecordsLoaded(Handler h, int what, Object obj);
+
+    /**
+     * Unregister for notifications for Sim records loaded
+     * @param h Handler to be removed from the registrant list.
+     */
+    public void unregisterForSimRecordsLoaded(Handler h);
+
+    /**
      * Returns SIM record load state. Use
      * <code>getSimCard().registerForReady()</code> for change notification.
      *
@@ -1693,16 +1726,6 @@
     IsimRecords getIsimRecords();
 
     /**
-     * Request the ISIM application on the UICC to perform the AKA
-     * challenge/response algorithm for IMS authentication. The nonce string
-     * and challenge response are Base64 encoded Strings.
-     *
-     * @param nonce the nonce string to pass with the ISIM authentication request
-     * @param response a callback message with the String response in the obj field
-     */
-    void requestIsimAuthentication(String nonce, Message response);
-
-    /**
      * Sets the SIM voice message waiting indicator records.
      * @param line GSM Subscriber Profile Number, one-based. Only '1' is supported
      * @param countWaiting The number of messages waiting, if known. Use
@@ -1718,6 +1741,12 @@
     UsimServiceTable getUsimServiceTable();
 
     /**
+     * Gets the Uicc card corresponding to this phone.
+     * @return the UiccCard object corresponding to the phone ID.
+     */
+    UiccCard getUiccCard();
+
+    /**
      * Unregister from all events it registered for and dispose objects
      * created by this object.
      */
@@ -1772,4 +1801,36 @@
      * @param response Callback message.
      */
     void nvResetConfig(int resetType, Message response);
+
+    /*
+     * Returns the subscription id.
+     */
+    public long getSubId();
+
+    /*
+     * Returns the phone id.
+     */
+    public int getPhoneId();
+
+    /**
+     * Get P-CSCF address from PCO after data connection is established or modified.
+     */
+    public String[] getPcscfAddress();
+
+    /**
+     * Set IMS registration state
+     */
+    public void setImsRegistrationState(boolean registered);
+
+    /**
+     * Return an instance of a ImsPhone phone
+     * @return an interface of a ImsPhone phone
+     */
+    Phone getVoicePhone();
+
+    /**
+     * Return the service state of mVoicePhone if it is STATE_IN_SERVICE
+     * otherwise return the current voice service state
+     */
+    int getVoiceServiceState();
 }
diff --git a/src/java/com/android/internal/telephony/PhoneBase.java b/src/java/com/android/internal/telephony/PhoneBase.java
index 2955880..a46934f 100644
--- a/src/java/com/android/internal/telephony/PhoneBase.java
+++ b/src/java/com/android/internal/telephony/PhoneBase.java
@@ -16,7 +16,10 @@
 
 package com.android.internal.telephony;
 
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.SharedPreferences;
 import android.net.LinkProperties;
 import android.net.NetworkCapabilities;
@@ -34,11 +37,13 @@
 import android.telephony.CellInfo;
 import android.telephony.CellInfoCdma;
 import android.telephony.DataConnectionRealTimeInfo;
+import android.telephony.VoLteServiceState;
 import android.telephony.Rlog;
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
 import android.text.TextUtils;
 
+import com.android.ims.ImsManager;
 import com.android.internal.R;
 import com.android.internal.telephony.dataconnection.DcTrackerBase;
 import com.android.internal.telephony.test.SimulatedRadioControl;
@@ -46,6 +51,7 @@
 import com.android.internal.telephony.uicc.IccFileHandler;
 import com.android.internal.telephony.uicc.IccRecords;
 import com.android.internal.telephony.uicc.IsimRecords;
+import com.android.internal.telephony.uicc.UiccCard;
 import com.android.internal.telephony.uicc.UiccCardApplication;
 import com.android.internal.telephony.uicc.UiccController;
 import com.android.internal.telephony.uicc.UsimServiceTable;
@@ -56,6 +62,7 @@
 import java.util.List;
 import java.util.concurrent.atomic.AtomicReference;
 
+import static com.android.internal.telephony.PhoneConstants.DEFAULT_SUBSCRIPTION;
 
 /**
  * (<em>Not for SDK use</em>)
@@ -72,6 +79,19 @@
 public abstract class PhoneBase extends Handler implements Phone {
     private static final String LOG_TAG = "PhoneBase";
 
+    private BroadcastReceiver mImsIntentReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (intent.getAction().equals(ImsManager.ACTION_IMS_SERVICE_UP)) {
+                mImsServiceReady = true;
+                updateVoicePhone();
+            } else if (intent.getAction().equals(ImsManager.ACTION_IMS_SERVICE_DOWN)) {
+                mImsServiceReady = false;
+                updateVoicePhone();
+            }
+        }
+    };
+
     // Key used to read and write the saved network selection numeric value
     public static final String NETWORK_SELECTION_KEY = "network_selection_key";
     // Key used to read and write the saved network selection operator name
@@ -118,6 +138,10 @@
     protected static final int EVENT_SET_NETWORK_AUTOMATIC          = 28;
     protected static final int EVENT_ICC_RECORD_EVENTS              = 29;
     protected static final int EVENT_ICC_CHANGED                    = 30;
+    // Single Radio Voice Call Continuity
+    protected static final int EVENT_SRVCC_STATE_CHANGED            = 31;
+    protected static final int EVENT_INITIATE_SILENT_REDIAL         = 32;
+    protected static final int EVENT_LAST                           = EVENT_INITIATE_SILENT_REDIAL;
 
     // Key used to read/write current CLIR setting
     public static final String CLIR_KEY = "clir_key";
@@ -158,6 +182,13 @@
     private final String mActionDetached;
     private final String mActionAttached;
 
+    // Holds the subscription information
+    protected Subscription mSubscriptionData = null;
+    protected int mPhoneId;
+
+    protected boolean mImsServiceReady = false;
+    protected Phone mVoicePhone = null;
+
     @Override
     public String getPhoneName() {
         return mName;
@@ -188,6 +219,7 @@
     /**
      * Set a system property, unless we're in unit test mode
      */
+    // CAF_MSIM TODO this need to be replated with TelephonyManager API ?
     public void setSystemProperty(String property, String value) {
         if(getUnitTestMode()) {
             return;
@@ -223,6 +255,9 @@
     protected final RegistrantList mSuppServiceFailedRegistrants
             = new RegistrantList();
 
+    protected final RegistrantList mSimRecordsLoadedRegistrants
+            = new RegistrantList();
+
     protected Looper mLooper; /* to insure registrants are in correct thread*/
 
     protected final Context mContext;
@@ -262,6 +297,23 @@
      */
     protected PhoneBase(String name, PhoneNotifier notifier, Context context, CommandsInterface ci,
             boolean unitTestMode) {
+        this(name, notifier, context, ci, unitTestMode, DEFAULT_SUBSCRIPTION);
+    }
+
+    /**
+     * Constructs a PhoneBase in normal (non-unit test) mode.
+     *
+     * @param notifier An instance of DefaultPhoneNotifier,
+     * @param context Context object from hosting application
+     * unless unit testing.
+     * @param ci is CommandsInterface
+     * @param unitTestMode when true, prevents notifications
+     * of state change events
+     * @param subscription is current phone subscription
+     */
+    protected PhoneBase(String name, PhoneNotifier notifier, Context context, CommandsInterface ci,
+            boolean unitTestMode, int phoneId) {
+        mPhoneId = phoneId;
         mName = name;
         mNotifier = notifier;
         mContext = context;
@@ -274,8 +326,6 @@
             mTelephonyTester = new TelephonyTester(this);
         }
 
-        setPropertiesByCarrier();
-
         setUnitTestMode(unitTestMode);
 
         SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
@@ -309,16 +359,27 @@
                 TelephonyProperties.PROPERTY_CALL_RING_DELAY, 3000);
         Rlog.d(LOG_TAG, "mCallRingDelay=" + mCallRingDelay);
 
+        if (getPhoneType() == PhoneConstants.PHONE_TYPE_IMS) return;
+
+        setPropertiesByCarrier();
+
         // Initialize device storage and outgoing SMS usage monitors for SMSDispatchers.
         mSmsStorageMonitor = new SmsStorageMonitor(this);
         mSmsUsageMonitor = new SmsUsageMonitor(context);
         mUiccController = UiccController.getInstance();
         mUiccController.registerForIccChanged(this, EVENT_ICC_CHANGED, null);
+
+        // Monitor IMS service
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(ImsManager.ACTION_IMS_SERVICE_UP);
+        filter.addAction(ImsManager.ACTION_IMS_SERVICE_DOWN);
+        mContext.registerReceiver(mImsIntentReceiver, filter);
     }
 
     @Override
     public void dispose() {
         synchronized(PhoneProxy.lockForRadioTechnologyChange) {
+            mContext.unregisterReceiver(mImsIntentReceiver);
             mCi.unSetOnCallRing(this);
             // Must cleanup all connectionS and needs to use sendMessage!
             mDcTracker.cleanUpAllConnections(null);
@@ -331,6 +392,11 @@
             if (mTelephonyTester != null) {
                 mTelephonyTester.dispose();
             }
+
+            if (mVoicePhone != null) {
+                ((VoicePhone)mVoicePhone).unregisterForSilentRedial(this);
+                mVoicePhone.dispose();
+            }
         }
     }
 
@@ -342,6 +408,11 @@
         mUiccApplication.set(null);
         mDcTracker = null;
         mUiccController = null;
+
+        if (mVoicePhone != null) {
+            mVoicePhone.removeReferences();
+            mVoicePhone = null;
+        }
     }
 
     /**
@@ -394,6 +465,20 @@
                 handleSetSelectNetwork((AsyncResult) msg.obj);
                 break;
 
+            case EVENT_INITIATE_SILENT_REDIAL:
+                Rlog.d(LOG_TAG, "Event EVENT_INITIATE_SILENT_REDIAL Received");
+                ar = (AsyncResult) msg.obj;
+                if ((ar.exception == null) && (ar.result != null)) {
+                    String dialString = (String) ar.result;
+                    if (TextUtils.isEmpty(dialString)) return;
+                    try {
+                        dialInternal(dialString, null);
+                    } catch (CallStateException e) {
+                        Rlog.e(LOG_TAG, "silent redial failed: " + e);
+                    }
+                }
+                break;
+
             default:
                 throw new RuntimeException("unexpected event not handled");
         }
@@ -581,6 +666,14 @@
         mMmiCompleteRegistrants.remove(h);
     }
 
+    public void registerForSimRecordsLoaded(Handler h, int what, Object obj) {
+        logUnexpectedCdmaMethodCall("registerForSimRecordsLoaded");
+    }
+
+    public void unregisterForSimRecordsLoaded(Handler h) {
+        logUnexpectedCdmaMethodCall("unregisterForSimRecordsLoaded");
+    }
+
     @Override
     public void setNetworkSelectionModeAutomatic(Message response) {
         // wrap the response message in our own message along with
@@ -719,6 +812,16 @@
 
     // Inherited documentation suffices.
     @Override
+    public void registerForOnHoldTone(Handler h, int what, Object obj) {
+    }
+
+    // Inherited documentation suffices.
+    @Override
+    public void unregisterForOnHoldTone(Handler h) {
+    }
+
+    // Inherited documentation suffices.
+    @Override
     public void registerForResendIncallMute(Handler h, int what, Object obj) {
         mCi.registerForResendIncallMute(h, what, obj);
     }
@@ -1105,6 +1208,10 @@
         mNotifier.notifyDataConnectionRealTimeInfo(this, dcRtInfo);
     }
 
+    public void notifyVoLteServiceStateChanged(VoLteServiceState lteState) {
+        mNotifier.notifyVoLteServiceStateChanged(this, lteState);
+    }
+
     /**
      * @return true if a mobile originating emergency call is active
      */
@@ -1437,11 +1544,6 @@
     }
 
     @Override
-    public void requestIsimAuthentication(String nonce, Message result) {
-        Rlog.e(LOG_TAG, "requestIsimAuthentication() is only supported on LTE devices");
-    }
-
-    @Override
     public String getMsisdn() {
         logUnexpectedGsmMethodCall("getMsisdn");
         return null;
@@ -1517,6 +1619,64 @@
         return (r != null) ? r.getUsimServiceTable() : null;
     }
 
+    /**
+     * Gets the Uicc card corresponding to this phone.
+     * @return the UiccCard object corresponding to the phone ID.
+     */
+    @Override
+    public UiccCard getUiccCard() {
+        return mUiccController.getUiccCard(mPhoneId);
+    }
+
+    /**
+     * Get P-CSCF address from PCO after data connection is established or modified.
+     */
+    @Override
+    public String[] getPcscfAddress() {
+        return mDcTracker.getPcscfAddress();
+    }
+
+    /**
+     * Set IMS registration state
+     */
+    @Override
+    public void setImsRegistrationState(boolean registered) {
+        mDcTracker.setImsRegistrationState(registered);
+    }
+
+    /**
+     * Return an instance of a IMS phone
+     */
+    @Override
+    public Phone getVoicePhone() {
+        return mVoicePhone;
+    }
+
+    protected void updateVoicePhone() {
+        Rlog.d(LOG_TAG, "updateVoicePhone"
+                + " mImsServiceReady=" + mImsServiceReady);
+
+        if (mImsServiceReady && (mVoicePhone == null)) {
+            mVoicePhone = PhoneFactory.makeImsPhone(mNotifier, this);
+            CallManager.getInstance().registerPhone(mVoicePhone);
+            ((VoicePhone)mVoicePhone).registerForSilentRedial(
+                    this, EVENT_INITIATE_SILENT_REDIAL, null);
+        } else if (!mImsServiceReady && (mVoicePhone != null)) {
+            CallManager.getInstance().unregisterPhone(mVoicePhone);
+            ((VoicePhone)mVoicePhone).unregisterForSilentRedial(this);
+
+            mVoicePhone.dispose();
+            mVoicePhone.removeReferences();
+            mVoicePhone = null;
+        }
+    }
+
+    protected Connection dialInternal(String dialString, UUSInfo uusInfo)
+            throws CallStateException {
+        // dialInternal shall be overriden by GSMPhone and CDMAPhone
+        return null;
+    }
+
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("PhoneBase:");
         pw.println(" mCi=" + mCi);
@@ -1554,4 +1714,37 @@
         pw.println(" isDataConnectivityPossible()=" + isDataConnectivityPossible());
         pw.println(" needsOtaServiceProvisioning=" + needsOtaServiceProvisioning());
     }
+
+    /**
+     * Returns the subscription id.
+     */
+    public long getSubId() {
+        long [] subId = SubscriptionController.getInstance().getSubId(mPhoneId);
+        return subId[0];
+    }
+
+    /**
+     * Returns the phone id.
+     */
+    public int getPhoneId() {
+        return mPhoneId;
+    }
+
+    //Gets Subscription information in the Phone Object
+    public Subscription getSubscriptionInfo() {
+        return mSubscriptionData;
+    }
+
+    /**
+     * Return the service state of mVoicePhone if it is STATE_IN_SERVICE
+     * otherwise return the current voice service state
+     */
+    @Override
+    public int getVoiceServiceState() {
+        if (mVoicePhone != null
+                && mVoicePhone.getServiceState().getState() == ServiceState.STATE_IN_SERVICE) {
+            return ServiceState.STATE_IN_SERVICE;
+        }
+        return getServiceState().getState();
+    }
 }
diff --git a/src/java/com/android/internal/telephony/PhoneFactory.java b/src/java/com/android/internal/telephony/PhoneFactory.java
index 33b21d4..5d20052 100644
--- a/src/java/com/android/internal/telephony/PhoneFactory.java
+++ b/src/java/com/android/internal/telephony/PhoneFactory.java
@@ -18,10 +18,15 @@
 
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
 import android.net.LocalServerSocket;
 import android.os.Looper;
+import android.os.SystemProperties;
+import android.os.UserHandle;
 import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
 import android.telephony.Rlog;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 
 import com.android.internal.telephony.cdma.CDMALTEPhone;
@@ -31,6 +36,9 @@
 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.imsphone.ImsPhoneFactory;
+
+import static com.android.internal.telephony.TelephonyProperties.PROPERTY_DEFAULT_SUBSCRIPTION;
 
 /**
  * {@hide}
@@ -42,8 +50,15 @@
 
     //***** Class Variables
 
+    static private Phone[] sProxyPhones = null;
+    static private CommandsInterface[] sCommandsInterfaces = null;
+
+    static private ProxyController mProxyController;
+    static private UiccController mUiccController;
+
     static private Phone sProxyPhone = null;
     static private CommandsInterface sCommandsInterface = null;
+    static private SubInfoRecordUpdater sSubInfoRecordUpdater = null;
 
     static private boolean sMadeDefaults = false;
     static private PhoneNotifier sPhoneNotifier;
@@ -71,6 +86,9 @@
                         "PhoneFactory.makeDefaultPhone must be called from Looper thread");
                 }
 
+                // create the telephony device controller.
+                TelephonyDevController.create();
+
                 int retryCount = 0;
                 for(;;) {
                     boolean hasException = false;
@@ -98,39 +116,71 @@
 
                 sPhoneNotifier = new DefaultPhoneNotifier();
 
-                // Get preferred network type.
-                int networkType = calculatePreferredNetworkType(context);
-                Rlog.i(LOG_TAG, "Network Type set to " + Integer.toString(networkType));
+                // Get preferred network mode
+                int preferredNetworkMode = RILConstants.PREFERRED_NETWORK_MODE;
+                if (TelephonyManager.getLteOnCdmaModeStatic() == PhoneConstants.LTE_ON_CDMA_TRUE) {
+                    preferredNetworkMode = Phone.NT_MODE_GLOBAL;
+                }
 
                 int cdmaSubscription = CdmaSubscriptionSourceManager.getDefault(context);
                 Rlog.i(LOG_TAG, "Cdma Subscription set to " + cdmaSubscription);
 
-                //reads the system properties and makes commandsinterface
-                sCommandsInterface = new RIL(context, networkType, cdmaSubscription);
+                /* In case of multi SIM mode two instances of PhoneProxy, RIL are created,
+                   where as in single SIM mode only instance. isMultiSimEnabled() function checks
+                   whether it is single SIM or multi SIM mode */
+                int numPhones = TelephonyManager.getDefault().getPhoneCount();
+                int[] networkModes = new int[numPhones];
+                sProxyPhones = new PhoneProxy[numPhones];
+                sCommandsInterfaces = new RIL[numPhones];
 
-                // Instantiate UiccController so that all other classes can just call getInstance()
-                UiccController.make(context, sCommandsInterface);
+                for (int i = 0; i < numPhones; i++) {
+                    //reads the system properties and makes commandsinterface
+                    try {
+//                        // Get preferred network type.
+//                        TODO: Sishir added this code to but we need a new technique for MSim
+//                        int networkType = calculatePreferredNetworkType(context);
+//                        Rlog.i(LOG_TAG, "Network Type set to " + Integer.toString(networkType));
 
-                int phoneType = TelephonyManager.getPhoneType(networkType);
-                if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
-                    Rlog.i(LOG_TAG, "Creating GSMPhone");
-                    sProxyPhone = new PhoneProxy(new GSMPhone(context,
-                            sCommandsInterface, sPhoneNotifier));
-                } else if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
-                    switch (TelephonyManager.getLteOnCdmaModeStatic()) {
-                        case PhoneConstants.LTE_ON_CDMA_TRUE:
-                            Rlog.i(LOG_TAG, "Creating CDMALTEPhone");
-                            sProxyPhone = new PhoneProxy(new CDMALTEPhone(context,
-                                sCommandsInterface, sPhoneNotifier));
-                            break;
-                        case PhoneConstants.LTE_ON_CDMA_FALSE:
-                        default:
-                            Rlog.i(LOG_TAG, "Creating CDMAPhone");
-                            sProxyPhone = new PhoneProxy(new CDMAPhone(context,
-                                    sCommandsInterface, sPhoneNotifier));
-                            break;
+                        networkModes[i]  = TelephonyManager.getIntAtIndex(
+                                context.getContentResolver(),
+                                Settings.Global.PREFERRED_NETWORK_MODE, i);
+                    } catch (SettingNotFoundException snfe) {
+                        Rlog.e(LOG_TAG, "Settings Exception Reading Value At Index for"+
+                                " Settings.Global.PREFERRED_NETWORK_MODE");
+                        networkModes[i] = preferredNetworkMode;
                     }
+
+                    Rlog.i(LOG_TAG, "Network Mode set to " + Integer.toString(networkModes[i]));
+                    sCommandsInterfaces[i] = new RIL(context, networkModes[i],
+                            cdmaSubscription, i);
                 }
+                Rlog.i(LOG_TAG, "Creating SubscriptionController");
+                SubscriptionController.init(context, sCommandsInterfaces);
+
+                // Instantiate UiccController so that all other classes can just
+                // call getInstance()
+                mUiccController = UiccController.make(context, sCommandsInterfaces);
+
+                for (int i = 0; i < numPhones; i++) {
+                    PhoneBase phone = null;
+                    int phoneType = TelephonyManager.getPhoneType(networkModes[i]);
+                    if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
+                        phone = new GSMPhone(context,
+                                sCommandsInterfaces[i], sPhoneNotifier, i);
+                    } else if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
+                        phone = new CDMALTEPhone(context,
+                                sCommandsInterfaces[i], sPhoneNotifier, i);
+                    }
+                    Rlog.i(LOG_TAG, "Creating Phone with type = " + phoneType + " sub = " + i);
+
+                    sProxyPhones[i] = new PhoneProxy(phone);
+                }
+                mProxyController = ProxyController.getInstance(context, sProxyPhones,
+                        mUiccController, sCommandsInterfaces);
+
+                // Set the default phone in base class
+                sProxyPhone = sProxyPhones[PhoneConstants.DEFAULT_SUBSCRIPTION];
+                sCommandsInterface = sCommandsInterfaces[PhoneConstants.DEFAULT_SUBSCRIPTION];
 
                 // Ensure that we have a default SMS app. Requesting the app with
                 // updateIfNeeded set to true is enough to configure a default SMS app.
@@ -146,10 +196,31 @@
                 SmsApplication.initSmsPackageMonitor(context);
 
                 sMadeDefaults = true;
+
+                Rlog.i(LOG_TAG, "Creating SubInfoRecordUpdater ");
+                sSubInfoRecordUpdater = new SubInfoRecordUpdater(context,
+                        sProxyPhones, sCommandsInterfaces);
             }
         }
     }
 
+    public static Phone getCdmaPhone(int phoneId) {
+        Phone phone;
+        synchronized(PhoneProxy.lockForRadioTechnologyChange) {
+            phone = new CDMALTEPhone(sContext, sCommandsInterfaces[phoneId],
+                    sPhoneNotifier, phoneId);
+        }
+        return phone;
+    }
+
+    public static Phone getGsmPhone(int phoneId) {
+        synchronized(PhoneProxy.lockForRadioTechnologyChange) {
+            Phone phone = new GSMPhone(sContext, sCommandsInterfaces[phoneId],
+                    sPhoneNotifier, phoneId);
+            return phone;
+        }
+    }
+
     public static Phone getDefaultPhone() {
         if (sLooper != Looper.myLooper()) {
             throw new RuntimeException(
@@ -162,6 +233,31 @@
        return sProxyPhone;
     }
 
+    public static Phone getPhone(int phoneId) {
+        if (sLooper != Looper.myLooper()) {
+            throw new RuntimeException(
+                "PhoneFactory.getPhone must be called from Looper thread");
+        }
+        if (!sMadeDefaults) {
+            throw new IllegalStateException("Default phones haven't been made yet!");
+        // CAF_MSIM FIXME need to introduce default phone id ?
+        } else if (phoneId == PhoneConstants.DEFAULT_SUBSCRIPTION) {
+            return sProxyPhone;
+        }
+        return sProxyPhones[phoneId];
+    }
+
+    public static Phone [] getPhones() {
+        if (sLooper != Looper.myLooper()) {
+            throw new RuntimeException(
+                "PhoneFactory.getPhone must be called from Looper thread");
+        }
+        if (!sMadeDefaults) {
+            throw new IllegalStateException("Default phones haven't been made yet!");
+        }
+        return sProxyPhones;
+    }
+
     public static Phone getCdmaPhone() {
         Phone phone;
         synchronized(PhoneProxy.lockForRadioTechnologyChange) {
@@ -182,10 +278,11 @@
     }
 
     public static Phone getGsmPhone() {
-        synchronized(PhoneProxy.lockForRadioTechnologyChange) {
-            Phone phone = new GSMPhone(sContext, sCommandsInterface, sPhoneNotifier);
-            return phone;
+        int phoneId = SubscriptionController.getInstance().getPhoneId(getDefaultSubscription());
+        if (phoneId < 0 || phoneId >= TelephonyManager.getDefault().getPhoneCount()) {
+            phoneId = 0;
         }
+        return getGsmPhone(phoneId);
     }
 
     /**
@@ -197,12 +294,42 @@
         return SipPhoneFactory.makePhone(sipUri, sContext, sPhoneNotifier);
     }
 
+    /* Sets the default subscription. If only one phone instance is active that
+     * subscription is set as default subscription. If both phone instances
+     * are active the first instance "0" is set as default subscription
+     */
+    public static void setDefaultSubscription(int subId) {
+        SystemProperties.set(PROPERTY_DEFAULT_SUBSCRIPTION, Integer.toString(subId));
+        int phoneId = SubscriptionController.getInstance().getPhoneId(subId);
+
+        // Set the default phone in base class
+        if (phoneId >= 0 && phoneId < sProxyPhones.length) {
+            sProxyPhone = sProxyPhones[phoneId];
+            sCommandsInterface = sCommandsInterfaces[phoneId];
+            sMadeDefaults = true;
+        }
+
+        // Update MCC MNC device configuration information
+        String defaultMccMnc = TelephonyManager.getDefault().getSimOperator(phoneId);
+        Rlog.d(LOG_TAG, "update mccmnc=" + defaultMccMnc);
+        MccTable.updateMccMncConfiguration(sContext, defaultMccMnc, false);
+
+        // Broadcast an Intent for default sub change
+        Intent intent = new Intent(TelephonyIntents.ACTION_DEFAULT_SUBSCRIPTION_CHANGED);
+        intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+        SubscriptionManager.putPhoneIdAndSubIdExtra(intent, phoneId);
+        Rlog.d(LOG_TAG, "setDefaultSubscription : " + subId
+                + " Broadcasting Default Subscription Changed...");
+        sContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+    }
+
     /**
      * Returns the preferred network type that should be set in the modem.
      *
      * @param context The current {@link Context}.
      * @return the preferred network mode that should be set.
      */
+    // TODO: Fix when we "properly" have TelephonyDevController/SubscriptionController ..
     public static int calculatePreferredNetworkType(Context context) {
         int preferredNetworkType = RILConstants.PREFERRED_NETWORK_MODE;
         if (TelephonyManager.getLteOnCdmaModeStatic() == PhoneConstants.LTE_ON_CDMA_TRUE) {
@@ -212,4 +339,171 @@
                 Settings.Global.PREFERRED_NETWORK_MODE, preferredNetworkType);
         return networkType;
     }
+
+    /* Gets the default subscription */
+    public static long getDefaultSubscription() {
+        return SubscriptionController.getInstance().getDefaultSubId();
+    }
+
+    /* Gets User preferred Voice subscription setting*/
+    public static int getVoiceSubscription() {
+        int subId = 0;
+
+        try {
+            subId = Settings.Global.getInt(sContext.getContentResolver(),
+                    Settings.Global.MULTI_SIM_VOICE_CALL_SUBSCRIPTION);
+        } catch (SettingNotFoundException snfe) {
+            Rlog.e(LOG_TAG, "Settings Exception Reading Dual Sim Voice Call Values");
+        }
+
+        int phoneId = SubscriptionController.getInstance().getPhoneId(subId);
+        // Set subscription to 0 if current subscription is invalid.
+        // Ex: multisim.config property is TSTS and subscription is 2.
+        // If user is trying to set multisim.config to DSDS and reboots
+        // in this case index 2 is invalid so need to set to 0.
+        if (phoneId < 0 || phoneId >= TelephonyManager.getDefault().getPhoneCount()) {
+            Rlog.i(LOG_TAG, "Subscription is invalid..." + subId + " Set to 0");
+            subId = 0;
+            setVoiceSubscription(subId);
+        }
+
+        return subId;
+    }
+
+    /* Returns User Prompt property,  enabed or not */
+    public static boolean isPromptEnabled() {
+        boolean prompt = false;
+        int value = 0;
+        try {
+            value = Settings.Global.getInt(sContext.getContentResolver(),
+                    Settings.Global.MULTI_SIM_VOICE_PROMPT);
+        } catch (SettingNotFoundException snfe) {
+            Rlog.e(LOG_TAG, "Settings Exception Reading Dual Sim Voice Prompt Values");
+        }
+        prompt = (value == 0) ? false : true ;
+        Rlog.d(LOG_TAG, "Prompt option:" + prompt);
+
+       return prompt;
+    }
+
+    /*Sets User Prompt property,  enabed or not */
+    public static void setPromptEnabled(boolean enabled) {
+        int value = (enabled == false) ? 0 : 1;
+        Settings.Global.putInt(sContext.getContentResolver(),
+                Settings.Global.MULTI_SIM_VOICE_PROMPT, value);
+        Rlog.d(LOG_TAG, "setVoicePromptOption to " + enabled);
+    }
+
+    /* Returns User SMS Prompt property,  enabled or not */
+    public static boolean isSMSPromptEnabled() {
+        boolean prompt = false;
+        int value = 0;
+        try {
+            value = Settings.Global.getInt(sContext.getContentResolver(),
+                    Settings.Global.MULTI_SIM_SMS_PROMPT);
+        } catch (SettingNotFoundException snfe) {
+            Rlog.e(LOG_TAG, "Settings Exception Reading Dual Sim SMS Prompt Values");
+        }
+        prompt = (value == 0) ? false : true ;
+        Rlog.d(LOG_TAG, "SMS Prompt option:" + prompt);
+
+       return prompt;
+    }
+
+    /*Sets User SMS Prompt property,  enable or not */
+    public static void setSMSPromptEnabled(boolean enabled) {
+        int value = (enabled == false) ? 0 : 1;
+        Settings.Global.putInt(sContext.getContentResolver(),
+                Settings.Global.MULTI_SIM_SMS_PROMPT, value);
+        Rlog.d(LOG_TAG, "setSMSPromptOption to " + enabled);
+    }
+
+    /* Gets User preferred Data subscription setting*/
+    public static long getDataSubscription() {
+        long subId = 1;
+
+        try {
+            subId = Settings.Global.getLong(sContext.getContentResolver(),
+                    Settings.Global.MULTI_SIM_DATA_CALL_SUBSCRIPTION);
+        } catch (SettingNotFoundException snfe) {
+            Rlog.e(LOG_TAG, "Settings Exception Reading Dual Sim Data Call Values");
+        }
+
+        int phoneId = SubscriptionController.getInstance().getPhoneId(subId);
+        if (phoneId < 0 || phoneId >= TelephonyManager.getDefault().getPhoneCount()) {
+            subId = 1;
+            Rlog.i(LOG_TAG, "Subscription is invalid..." + subId + " Set to 0");
+            setDataSubscription(subId);
+        }
+
+        return subId;
+    }
+
+    /* Gets User preferred SMS subscription setting*/
+    public static int getSMSSubscription() {
+        int subId = 0;
+        try {
+            subId = Settings.Global.getInt(sContext.getContentResolver(),
+                    Settings.Global.MULTI_SIM_SMS_SUBSCRIPTION);
+        } catch (SettingNotFoundException snfe) {
+            Rlog.e(LOG_TAG, "Settings Exception Reading Dual Sim SMS Values");
+        }
+
+        int phoneId = SubscriptionController.getInstance().getPhoneId(subId);
+        if (phoneId < 0 || phoneId >= TelephonyManager.getDefault().getPhoneCount()) {
+            Rlog.i(LOG_TAG, "Subscription is invalid..." + subId + " Set to 0");
+            subId = 0;
+            setSMSSubscription(subId);
+        }
+
+        return subId;
+    }
+
+    static public void setVoiceSubscription(int subId) {
+        Settings.Global.putInt(sContext.getContentResolver(),
+                Settings.Global.MULTI_SIM_VOICE_CALL_SUBSCRIPTION, subId);
+        Rlog.d(LOG_TAG, "setVoiceSubscription : " + subId);
+    }
+
+    static public void setDataSubscription(long subId) {
+        boolean enabled;
+
+        Settings.Global.putLong(sContext.getContentResolver(),
+                Settings.Global.MULTI_SIM_DATA_CALL_SUBSCRIPTION, subId);
+        Rlog.d(LOG_TAG, "setDataSubscription: " + subId);
+
+        // Update the current mobile data flag
+        enabled = Settings.Global.getInt(sContext.getContentResolver(),
+                Settings.Global.MOBILE_DATA + subId, 0) != 0;
+        Settings.Global.putInt(sContext.getContentResolver(),
+                Settings.Global.MOBILE_DATA, enabled ? 1 : 0);
+        Rlog.d(LOG_TAG, "set mobile_data: " + enabled);
+
+        // Update the current data roaming flag
+        enabled = Settings.Global.getInt(sContext.getContentResolver(),
+                Settings.Global.DATA_ROAMING + subId, 0) != 0;
+        Settings.Global.putInt(sContext.getContentResolver(),
+                Settings.Global.DATA_ROAMING, enabled ? 1 : 0);
+        Rlog.d(LOG_TAG, "set data_roaming: " + enabled);
+    }
+
+    static public void setSMSSubscription(int subId) {
+        Settings.Global.putInt(sContext.getContentResolver(),
+                Settings.Global.MULTI_SIM_SMS_SUBSCRIPTION, subId);
+
+        Intent intent = new Intent("com.android.mms.transaction.SEND_MESSAGE");
+        sContext.sendBroadcast(intent);
+
+        // Change occured in SMS preferred sub, update the default
+        // SMS interface Manager object with the new SMS preferred subscription.
+        Rlog.d(LOG_TAG, "setSMSSubscription : " + subId);
+    }
+
+    /**
+     * Makes a {@link ImsPhone} object.
+     * @return the {@code ImsPhone} object or null if the exception occured
+     */
+    public static Phone makeImsPhone(PhoneNotifier phoneNotifier, Phone defaultPhone) {
+        return ImsPhoneFactory.makePhone(sContext, phoneNotifier, defaultPhone);
+    }
 }
diff --git a/src/java/com/android/internal/telephony/PhoneNotifier.java b/src/java/com/android/internal/telephony/PhoneNotifier.java
index aebf694..da3e305 100644
--- a/src/java/com/android/internal/telephony/PhoneNotifier.java
+++ b/src/java/com/android/internal/telephony/PhoneNotifier.java
@@ -18,6 +18,7 @@
 
 import android.telephony.CellInfo;
 import android.telephony.DataConnectionRealTimeInfo;
+import android.telephony.VoLteServiceState;
 
 import java.util.List;
 
@@ -58,4 +59,6 @@
             String apn, String failCause);
 
     public void notifyDataConnectionRealTimeInfo(Phone sender, DataConnectionRealTimeInfo dcRtInfo);
+
+    public void notifyVoLteServiceStateChanged(Phone sender, VoLteServiceState lteState);
 }
diff --git a/src/java/com/android/internal/telephony/PhoneProxy.java b/src/java/com/android/internal/telephony/PhoneProxy.java
index 7c232a1..3b5a562 100644
--- a/src/java/com/android/internal/telephony/PhoneProxy.java
+++ b/src/java/com/android/internal/telephony/PhoneProxy.java
@@ -32,11 +32,19 @@
 import android.telephony.CellLocation;
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
+import android.telephony.SubscriptionManager;
 import android.telephony.Rlog;
 
+import com.android.internal.telephony.cdma.CDMAPhone;
+import com.android.internal.telephony.gsm.GSMPhone;
+import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.test.SimulatedRadioControl;
+import com.android.internal.telephony.cdma.CDMALTEPhone;
+import com.android.internal.telephony.gsm.GSMPhone;
 import com.android.internal.telephony.uicc.IccCardProxy;
+import com.android.internal.telephony.uicc.IccFileHandler;
 import com.android.internal.telephony.uicc.IsimRecords;
+import com.android.internal.telephony.uicc.UiccCard;
 import com.android.internal.telephony.uicc.UsimServiceTable;
 import com.android.internal.telephony.CallManager;
 
@@ -62,6 +70,8 @@
     private static final int EVENT_RIL_CONNECTED = 4;
     private static final int EVENT_UPDATE_PHONE_OBJECT = 5;
 
+    private int mPhoneId = 0;
+
     private static final String LOG_TAG = "PhoneProxy";
 
     //***** Class Methods
@@ -69,8 +79,6 @@
         mActivePhone = phone;
         mResetModemOnRadioTechnologyChange = SystemProperties.getBoolean(
                 TelephonyProperties.PROPERTY_RESET_ON_RADIO_TECH_CHANGE, false);
-        mIccSmsInterfaceManager =
-                new IccSmsInterfaceManager((PhoneBase) this.mActivePhone);
         mIccPhoneBookInterfaceManagerProxy = new IccPhoneBookInterfaceManagerProxy(
                 phone.getIccPhoneBookInterfaceManager());
         mPhoneSubInfoProxy = new PhoneSubInfoProxy(phone.getPhoneSubInfo());
@@ -80,7 +88,11 @@
         mCommandsInterface.registerForOn(this, EVENT_RADIO_ON, null);
         mCommandsInterface.registerForVoiceRadioTechChanged(
                              this, EVENT_VOICE_RADIO_TECH_CHANGED, null);
-        mIccCardProxy = new IccCardProxy(phone.getContext(), mCommandsInterface);
+        mPhoneId = phone.getPhoneId();
+        mIccSmsInterfaceManager =
+                new IccSmsInterfaceManager((PhoneBase)this.mActivePhone);
+        mIccCardProxy = new IccCardProxy(mActivePhone.getContext(), mCommandsInterface, mActivePhone.getPhoneId());
+
         if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_GSM) {
             // For the purpose of IccCardProxy we only care about the technology family
             mIccCardProxy.setVoiceRadioTech(ServiceState.RIL_RADIO_TECHNOLOGY_UMTS);
@@ -226,6 +238,7 @@
         Intent intent = new Intent(TelephonyIntents.ACTION_RADIO_TECHNOLOGY_CHANGED);
         intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
         intent.putExtra(PhoneConstants.PHONE_NAME_KEY, mActivePhone.getPhoneName());
+        SubscriptionManager.putPhoneIdAndSubIdExtra(intent, mPhoneId);
         ActivityManagerNative.broadcastStickyIntent(intent, null, UserHandle.USER_ALL);
 
     }
@@ -256,9 +269,9 @@
         // System.gc();
 
         if (ServiceState.isCdma(newVoiceRadioTech)) {
-            mActivePhone = PhoneFactory.getCdmaPhone();
+            mActivePhone = PhoneFactory.getCdmaPhone(mPhoneId);
         } else if (ServiceState.isGsm(newVoiceRadioTech)) {
-            mActivePhone = PhoneFactory.getGsmPhone();
+            mActivePhone = PhoneFactory.getGsmPhone(mPhoneId);
         }
 
         if (oldPhone != null) {
@@ -272,6 +285,22 @@
         oldPhone = null;
     }
 
+    public IccSmsInterfaceManager getIccSmsInterfaceManager(){
+        return mIccSmsInterfaceManager;
+    }
+
+    public PhoneSubInfoProxy getPhoneSubInfoProxy(){
+        return mPhoneSubInfoProxy;
+    }
+
+    public IccPhoneBookInterfaceManagerProxy getIccPhoneBookInterfaceManagerProxy() {
+        return mIccPhoneBookInterfaceManagerProxy;
+    }
+
+    public IccFileHandler getIccFileHandler() {
+        return ((PhoneBase)mActivePhone).getIccFileHandler();
+    }
+
     @Override
     public void updatePhoneObject(int voiceRadioTech) {
         logd("updatePhoneObject: radioTechnology=" + voiceRadioTech);
@@ -545,6 +574,16 @@
     }
 
     @Override
+    public void registerForOnHoldTone(Handler h, int what, Object obj) {
+        mActivePhone.registerForOnHoldTone(h,what,obj);
+    }
+
+    @Override
+    public void unregisterForOnHoldTone(Handler h) {
+        mActivePhone.unregisterForOnHoldTone(h);
+    }
+
+    @Override
     public void registerForResendIncallMute(Handler h, int what, Object obj) {
         mActivePhone.registerForResendIncallMute(h,what,obj);
     }
@@ -555,6 +594,15 @@
     }
 
     @Override
+    public void registerForSimRecordsLoaded(Handler h, int what, Object obj) {
+        mActivePhone.registerForSimRecordsLoaded(h,what,obj);
+    }
+
+    public void unregisterForSimRecordsLoaded(Handler h) {
+        mActivePhone.unregisterForSimRecordsLoaded(h);
+    }
+
+    @Override
     public boolean getIccRecordsLoaded() {
         return mIccCardProxy.getIccRecordsLoaded();
     }
@@ -1162,11 +1210,9 @@
         return mActivePhone.getIsimRecords();
     }
 
-    @Override
-    public void requestIsimAuthentication(String nonce, Message response) {
-        mActivePhone.requestIsimAuthentication(nonce, response);
-    }
-
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public int getLteOnCdmaMode() {
         return mActivePhone.getLteOnCdmaMode();
@@ -1183,6 +1229,11 @@
     }
 
     @Override
+    public UiccCard getUiccCard() {
+        return mActivePhone.getUiccCard();
+    }
+
+    @Override
     public void nvReadItem(int itemID, Message response) {
         mActivePhone.nvReadItem(itemID, response);
     }
@@ -1214,4 +1265,111 @@
         mActivePhone = null;
         mCommandsInterface = null;
     }
+
+    public boolean updateCurrentCarrierInProvider() {
+        if (mActivePhone instanceof CDMALTEPhone) {
+            return ((CDMALTEPhone)mActivePhone).updateCurrentCarrierInProvider();
+        } else if (mActivePhone instanceof GSMPhone) {
+            return ((GSMPhone)mActivePhone).updateCurrentCarrierInProvider();
+        } else {
+           loge("Phone object is not MultiSim. This should not hit!!!!");
+           return false;
+        }
+    }
+
+    public void updateDataConnectionTracker() {
+        logd("Updating Data Connection Tracker");
+        if (mActivePhone instanceof CDMALTEPhone) {
+            ((CDMALTEPhone)mActivePhone).updateDataConnectionTracker();
+        } else if (mActivePhone instanceof GSMPhone) {
+            ((GSMPhone)mActivePhone).updateDataConnectionTracker();
+        } else {
+           loge("Phone object is not MultiSim. This should not hit!!!!");
+        }
+    }
+
+    public void setInternalDataEnabled(boolean enable) {
+        setInternalDataEnabled(enable, null);
+    }
+
+    public boolean setInternalDataEnabledFlag(boolean enable) {
+        boolean flag = false;
+        if (mActivePhone instanceof CDMALTEPhone) {
+            flag = ((CDMALTEPhone)mActivePhone).setInternalDataEnabledFlag(enable);
+        } else if (mActivePhone instanceof GSMPhone) {
+            flag = ((GSMPhone)mActivePhone).setInternalDataEnabledFlag(enable);
+        } else {
+           loge("Phone object is not MultiSim. This should not hit!!!!");
+        }
+        return flag;
+    }
+
+    public void setInternalDataEnabled(boolean enable, Message onCompleteMsg) {
+        if (mActivePhone instanceof CDMALTEPhone) {
+            ((CDMALTEPhone)mActivePhone).setInternalDataEnabled(enable, onCompleteMsg);
+        } else if (mActivePhone instanceof GSMPhone) {
+            ((GSMPhone)mActivePhone).setInternalDataEnabled(enable, onCompleteMsg);
+        } else {
+           loge("Phone object is not MultiSim. This should not hit!!!!");
+        }
+    }
+
+    public void registerForAllDataDisconnected(Handler h, int what, Object obj) {
+        if (mActivePhone instanceof CDMALTEPhone) {
+            ((CDMALTEPhone)mActivePhone).registerForAllDataDisconnected(h, what, obj);
+        } else if (mActivePhone instanceof GSMPhone) {
+            ((GSMPhone)mActivePhone).registerForAllDataDisconnected(h, what, obj);
+        } else {
+           loge("Phone object is not MultiSim. This should not hit!!!!");
+        }
+    }
+
+    public void unregisterForAllDataDisconnected(Handler h) {
+        if (mActivePhone instanceof CDMALTEPhone) {
+            ((CDMALTEPhone)mActivePhone).unregisterForAllDataDisconnected(h);
+        } else if (mActivePhone instanceof GSMPhone) {
+            ((GSMPhone)mActivePhone).unregisterForAllDataDisconnected(h);
+        } else {
+           loge("Phone object is not MultiSim. This should not hit!!!!");
+        }
+    }
+
+
+    public long getSubId() {
+        return mActivePhone.getSubId();
+    }
+
+    public int getPhoneId() {
+        return mActivePhone.getPhoneId();
+    }
+
+    @Override
+    public String[] getPcscfAddress() {
+        return mActivePhone.getPcscfAddress();
+    }
+
+    @Override
+    public void setImsRegistrationState(boolean registered){
+        logd("setImsRegistrationState - registered: " + registered);
+
+        mActivePhone.setImsRegistrationState(registered);
+
+        if ((mActivePhone.getPhoneName()).equals("GSM")) {
+            GSMPhone GP = (GSMPhone)mActivePhone;
+            GP.getServiceStateTracker().setImsRegistrationState(registered);
+        } else if ((mActivePhone.getPhoneName()).equals("CDMA")) {
+            CDMAPhone CP = (CDMAPhone)mActivePhone;
+            CP.getServiceStateTracker().setImsRegistrationState(registered);
+        }
+    }
+
+    @Override
+    public Phone getVoicePhone() {
+        return null;
+    }
+
+    @Override
+    public int getVoiceServiceState() {
+        return mActivePhone.getVoiceServiceState();
+    }
 }
diff --git a/src/java/com/android/internal/telephony/PhoneSubInfo.java b/src/java/com/android/internal/telephony/PhoneSubInfo.java
index 185814d..4da8e9e 100755
--- a/src/java/com/android/internal/telephony/PhoneSubInfo.java
+++ b/src/java/com/android/internal/telephony/PhoneSubInfo.java
@@ -21,12 +21,15 @@
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.os.Binder;
+import android.os.RemoteException;
 import android.telephony.PhoneNumberUtils;
 import android.telephony.Rlog;
 
 import com.android.internal.telephony.uicc.IsimRecords;
+import com.android.internal.telephony.uicc.UiccCard;
+import com.android.internal.telephony.uicc.UiccCardApplication;
 
-public class PhoneSubInfo extends IPhoneSubInfo.Stub {
+public class PhoneSubInfo {
     static final String LOG_TAG = "PhoneSubInfo";
     private static final boolean DBG = true;
     private static final boolean VDBG = false; // STOPSHIP if true
@@ -62,7 +65,6 @@
     /**
      * Retrieves the unique device ID, e.g., IMEI for GSM phones and MEID for CDMA phones.
      */
-    @Override
     public String getDeviceId() {
         mContext.enforceCallingOrSelfPermission(READ_PHONE_STATE, "Requires READ_PHONE_STATE");
         return mPhone.getDeviceId();
@@ -72,7 +74,6 @@
      * Retrieves the software version number for the device, e.g., IMEI/SV
      * for GSM phones.
      */
-    @Override
     public String getDeviceSvn() {
         mContext.enforceCallingOrSelfPermission(READ_PHONE_STATE, "Requires READ_PHONE_STATE");
         return mPhone.getDeviceSvn();
@@ -81,7 +82,6 @@
     /**
      * Retrieves the unique subscriber ID, e.g., IMSI for GSM phones.
      */
-    @Override
     public String getSubscriberId() {
         mContext.enforceCallingOrSelfPermission(READ_PHONE_STATE, "Requires READ_PHONE_STATE");
         return mPhone.getSubscriberId();
@@ -98,7 +98,6 @@
     /**
      * Retrieves the serial number of the ICC, if applicable.
      */
-    @Override
     public String getIccSerialNumber() {
         mContext.enforceCallingOrSelfPermission(READ_PHONE_STATE, "Requires READ_PHONE_STATE");
         return mPhone.getIccSerialNumber();
@@ -107,7 +106,6 @@
     /**
      * Retrieves the phone number string for line 1.
      */
-    @Override
     public String getLine1Number() {
         mContext.enforceCallingOrSelfPermission(READ_PHONE_STATE, "Requires READ_PHONE_STATE");
         return mPhone.getLine1Number();
@@ -116,7 +114,6 @@
     /**
      * Retrieves the alpha identifier for line 1.
      */
-    @Override
     public String getLine1AlphaTag() {
         mContext.enforceCallingOrSelfPermission(READ_PHONE_STATE, "Requires READ_PHONE_STATE");
         return mPhone.getLine1AlphaTag();
@@ -125,7 +122,6 @@
     /**
      * Retrieves the MSISDN string.
      */
-    @Override
     public String getMsisdn() {
         mContext.enforceCallingOrSelfPermission(READ_PHONE_STATE, "Requires READ_PHONE_STATE");
         return mPhone.getMsisdn();
@@ -134,7 +130,6 @@
     /**
      * Retrieves the voice mail number.
      */
-    @Override
     public String getVoiceMailNumber() {
         mContext.enforceCallingOrSelfPermission(READ_PHONE_STATE, "Requires READ_PHONE_STATE");
         String number = PhoneNumberUtils.extractNetworkPortion(mPhone.getVoiceMailNumber());
@@ -147,7 +142,6 @@
      *
      * @hide
      */
-    @Override
     public String getCompleteVoiceMailNumber() {
         mContext.enforceCallingOrSelfPermission(CALL_PRIVILEGED,
                 "Requires CALL_PRIVILEGED");
@@ -159,7 +153,6 @@
     /**
      * Retrieves the alpha identifier associated with the voice mail number.
      */
-    @Override
     public String getVoiceMailAlphaTag() {
         mContext.enforceCallingOrSelfPermission(READ_PHONE_STATE, "Requires READ_PHONE_STATE");
         return mPhone.getVoiceMailAlphaTag();
@@ -169,7 +162,6 @@
      * Returns the IMS private user identity (IMPI) that was loaded from the ISIM.
      * @return the IMPI, or null if not present or not loaded
      */
-    @Override
     public String getIsimImpi() {
         mContext.enforceCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE,
                 "Requires READ_PRIVILEGED_PHONE_STATE");
@@ -185,7 +177,6 @@
      * Returns the IMS home network domain name that was loaded from the ISIM.
      * @return the IMS domain name, or null if not present or not loaded
      */
-    @Override
     public String getIsimDomain() {
         mContext.enforceCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE,
                 "Requires READ_PRIVILEGED_PHONE_STATE");
@@ -202,7 +193,6 @@
      * @return an array of IMPU strings, with one IMPU per string, or null if
      *      not present or not loaded
      */
-    @Override
     public String[] getIsimImpu() {
         mContext.enforceCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE,
                 "Requires READ_PRIVILEGED_PHONE_STATE");
@@ -214,6 +204,84 @@
         }
     }
 
+    /**
+     * Returns the IMS Service Table (IST) that was loaded from the ISIM.
+     * @return IMS Service Table or null if not present or not loaded
+     */
+    public String getIsimIst(){
+        mContext.enforceCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE,
+                "Requires READ_PRIVILEGED_PHONE_STATE");
+        IsimRecords isim = mPhone.getIsimRecords();
+        if (isim != null) {
+            return isim.getIsimIst();
+        } else {
+            return null;
+        }
+     }
+
+    /**
+     * Returns the IMS Proxy Call Session Control Function(PCSCF) that were loaded from the ISIM.
+     * @return an array of  PCSCF strings with one PCSCF per string, or null if
+     *      not present or not loaded
+     */
+    public String[] getIsimPcscf() {
+        mContext.enforceCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE,
+                "Requires READ_PRIVILEGED_PHONE_STATE");
+        IsimRecords isim = mPhone.getIsimRecords();
+        if (isim != null) {
+            return isim.getIsimPcscf();
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Returns the response of ISIM Authetification through RIL.
+     * Returns null if the Authentification hasn't been successed or isn't present iphonesubinfo.
+     * @return the response of ISIM Authetification, or null if not available
+     */
+    public String getIsimChallengeResponse(String nonce){
+        mContext.enforceCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE,
+                "Requires READ_PRIVILEGED_PHONE_STATE");
+        IsimRecords isim = mPhone.getIsimRecords();
+        if (isim != null) {
+            return isim.getIsimChallengeResponse(nonce);
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Returns the response of the SIM application on the UICC to authentication
+     * challenge/response algorithm. The data string and challenge response are
+     * Base64 encoded Strings.
+     * Can support EAP-SIM, EAP-AKA with results encoded per 3GPP TS 31.102.
+     *
+     * @param appType ICC application family (@see com.android.internal.telephony.PhoneConstants#APPTYPE_xxx)
+     * @param data authentication challenge data
+     * @return challenge response
+     */
+    public String getIccSimChallengeResponse(long subId, int appType, String data) {
+        // FIXME: use subId!!
+        mContext.enforceCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE,
+                "Requires READ_PRIVILEGED_PHONE_STATE");
+
+        UiccCard uiccCard = mPhone.getUiccCard();
+        if (uiccCard == null) {
+            Rlog.e(LOG_TAG, "getIccSimChallengeResponse() UiccCard is null");
+            return null;
+        }
+
+        UiccCardApplication uiccApp = uiccCard.getApplicationByType(appType);
+        if (uiccApp == null) {
+            Rlog.e(LOG_TAG, "getIccSimChallengeResponse() no app with specified type -- " +
+                    appType);
+            return null;
+        }
+
+        return uiccApp.getIccRecords().getIccSimChallengeResponse(data);
+    }
+
     private void log(String s) {
         Rlog.d(LOG_TAG, s);
     }
@@ -222,7 +290,6 @@
         Rlog.e(LOG_TAG, s, e);
     }
 
-    @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
                 != PackageManager.PERMISSION_GRANTED) {
diff --git a/src/java/com/android/internal/telephony/PhoneSubInfoController.java b/src/java/com/android/internal/telephony/PhoneSubInfoController.java
new file mode 100644
index 0000000..5e5606b
--- /dev/null
+++ b/src/java/com/android/internal/telephony/PhoneSubInfoController.java
@@ -0,0 +1,272 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ * Copyright (c) 2011-2013, The Linux Foundation. All rights reserved.
+ * Not a Contribution.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony;
+
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.telephony.SubscriptionManager;
+import android.telephony.Rlog;
+import android.telephony.TelephonyManager;
+
+import java.lang.NullPointerException;
+import java.lang.ArrayIndexOutOfBoundsException;
+
+import com.android.internal.telephony.IPhoneSubInfo;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneSubInfoProxy;
+
+public class PhoneSubInfoController extends IPhoneSubInfo.Stub {
+    private static final String TAG = "PhoneSubInfoController";
+    private Phone[] mPhone;
+
+    public PhoneSubInfoController(Phone[] phone) {
+        mPhone = phone;
+        if (ServiceManager.getService("iphonesubinfo") == null) {
+            ServiceManager.addService("iphonesubinfo", this);
+        }
+    }
+
+
+    public String getDeviceId() {
+        return getDeviceIdUsingSubId(getDefaultSubscription());
+    }
+
+    public String getDeviceIdUsingSubId(long subId) {
+        PhoneSubInfoProxy phoneSubInfoProxy = getPhoneSubInfoProxy(subId);
+        if (phoneSubInfoProxy != null) {
+            return phoneSubInfoProxy.getDeviceId();
+        } else {
+            Rlog.e(TAG,"getDeviceId phoneSubInfoProxy is null" +
+                      " for Subscription:" + subId);
+            return null;
+        }
+    }
+
+    public String getDeviceSvn() {
+        PhoneSubInfoProxy phoneSubInfoProxy = getPhoneSubInfoProxy(getDefaultSubscription());
+        if (phoneSubInfoProxy != null) {
+            return phoneSubInfoProxy.getDeviceSvn();
+        } else {
+            Rlog.e(TAG,"getDeviceSvn phoneSubInfoProxy is null");
+            return null;
+        }
+    }
+
+    public String getSubscriberId() {
+        return getSubscriberIdUsingSubId(getDefaultSubscription());
+    }
+
+    public String getSubscriberIdUsingSubId(long subId) {
+        PhoneSubInfoProxy phoneSubInfoProxy = getPhoneSubInfoProxy(subId);
+        if (phoneSubInfoProxy != null) {
+            return phoneSubInfoProxy.getSubscriberId();
+        } else {
+            Rlog.e(TAG,"getSubscriberId phoneSubInfoProxy is" +
+                      " null for Subscription:" + subId);
+            return null;
+        }
+    }
+
+    /**
+     * Retrieves the serial number of the ICC, if applicable.
+     */
+    public String getIccSerialNumber() {
+        return getIccSerialNumberUsingSubId(getDefaultSubscription());
+    }
+
+    public String getIccSerialNumberUsingSubId(long subId) {
+        PhoneSubInfoProxy phoneSubInfoProxy = getPhoneSubInfoProxy(subId);
+        if (phoneSubInfoProxy != null) {
+            return phoneSubInfoProxy.getIccSerialNumber();
+        } else {
+            Rlog.e(TAG,"getIccSerialNumber phoneSubInfoProxy is" +
+                      " null for Subscription:" + subId);
+            return null;
+        }
+    }
+
+    public String getLine1Number() {
+        return getLine1NumberUsingSubId(getDefaultSubscription());
+    }
+
+    public String getLine1NumberUsingSubId(long subId) {
+        PhoneSubInfoProxy phoneSubInfoProxy = getPhoneSubInfoProxy(subId);
+        if (phoneSubInfoProxy != null) {
+            return phoneSubInfoProxy.getLine1Number();
+        } else {
+            Rlog.e(TAG,"getLine1Number phoneSubInfoProxy is" +
+                      " null for Subscription:" + subId);
+            return null;
+        }
+    }
+
+    public String getLine1AlphaTag() {
+        return getLine1AlphaTagUsingSubId(getDefaultSubscription());
+    }
+
+    public String getLine1AlphaTagUsingSubId(long subId) {
+        PhoneSubInfoProxy phoneSubInfoProxy = getPhoneSubInfoProxy(subId);
+        if (phoneSubInfoProxy != null) {
+            return phoneSubInfoProxy.getLine1AlphaTag();
+        } else {
+            Rlog.e(TAG,"getLine1AlphaTag phoneSubInfoProxy is" +
+                      " null for Subscription:" + subId);
+            return null;
+        }
+    }
+
+    public String getMsisdn() {
+        return getMsisdnUsingSubId(getDefaultSubscription());
+    }
+
+    public String getMsisdnUsingSubId(long subId) {
+        PhoneSubInfoProxy phoneSubInfoProxy = getPhoneSubInfoProxy(subId);
+        if (phoneSubInfoProxy != null) {
+            return phoneSubInfoProxy.getMsisdn();
+        } else {
+            Rlog.e(TAG,"getMsisdn phoneSubInfoProxy is" +
+                      " null for Subscription:" + subId);
+            return null;
+        }
+    }
+
+    public String getVoiceMailNumber() {
+        return getVoiceMailNumberUsingSubId(getDefaultSubscription());
+    }
+
+    public String getVoiceMailNumberUsingSubId(long subId) {
+        PhoneSubInfoProxy phoneSubInfoProxy = getPhoneSubInfoProxy(subId);
+        if (phoneSubInfoProxy != null) {
+            return phoneSubInfoProxy.getVoiceMailNumber();
+        } else {
+            Rlog.e(TAG,"getVoiceMailNumber phoneSubInfoProxy is" +
+                      " null for Subscription:" + subId);
+            return null;
+        }
+    }
+
+    public String getCompleteVoiceMailNumber() {
+        return getCompleteVoiceMailNumberUsingSubId(getDefaultSubscription());
+    }
+
+    public String getCompleteVoiceMailNumberUsingSubId(long subId) {
+        PhoneSubInfoProxy phoneSubInfoProxy = getPhoneSubInfoProxy(subId);
+        if (phoneSubInfoProxy != null) {
+            return phoneSubInfoProxy.getCompleteVoiceMailNumber();
+        } else {
+            Rlog.e(TAG,"getCompleteVoiceMailNumber phoneSubInfoProxy" +
+                      " is null for Subscription:" + subId);
+            return null;
+        }
+    }
+
+    public String getVoiceMailAlphaTag() {
+        return getVoiceMailAlphaTagUsingSubId(getDefaultSubscription());
+    }
+
+    public String getVoiceMailAlphaTagUsingSubId(long subId) {
+        PhoneSubInfoProxy phoneSubInfoProxy = getPhoneSubInfoProxy(subId);
+        if (phoneSubInfoProxy != null) {
+            return phoneSubInfoProxy.getVoiceMailAlphaTag();
+        } else {
+            Rlog.e(TAG,"getVoiceMailAlphaTag phoneSubInfoProxy is" +
+                      " null for Subscription:" + subId);
+            return null;
+        }
+    }
+
+    /**
+     * get Phone sub info proxy object based on subId.
+     **/
+    private PhoneSubInfoProxy getPhoneSubInfoProxy(long subId) {
+
+        long phoneId = SubscriptionManager.getPhoneId(subId);
+        if (phoneId < 0 || phoneId >= TelephonyManager.getDefault().getPhoneCount()) {
+            phoneId = 0;
+        }
+
+        try {
+            return ((PhoneProxy)mPhone[(int)phoneId]).getPhoneSubInfoProxy();
+        } catch (NullPointerException e) {
+            Rlog.e(TAG, "Exception is :" + e.toString() + " For subId :" + subId);
+            e.printStackTrace();
+            return null;
+        } catch (ArrayIndexOutOfBoundsException e) {
+            Rlog.e(TAG, "Exception is :" + e.toString() + " For subId :" + subId);
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    private long getDefaultSubscription() {
+        return  PhoneFactory.getDefaultSubscription();
+    }
+
+
+    public String getIsimImpi() {
+        PhoneSubInfoProxy phoneSubInfoProxy = getPhoneSubInfoProxy(getDefaultSubscription());
+        return phoneSubInfoProxy.getIsimImpi();
+    }
+
+    public String getIsimDomain() {
+        PhoneSubInfoProxy phoneSubInfoProxy = getPhoneSubInfoProxy(getDefaultSubscription());
+        return phoneSubInfoProxy.getIsimDomain();
+    }
+
+    public String[] getIsimImpu() {
+        PhoneSubInfoProxy phoneSubInfoProxy = getPhoneSubInfoProxy(getDefaultSubscription());
+        return phoneSubInfoProxy.getIsimImpu();
+    }
+
+    public String getIsimIst() throws RemoteException {
+        PhoneSubInfoProxy phoneSubInfoProxy = getPhoneSubInfoProxy(getDefaultSubscription());
+        return phoneSubInfoProxy.getIsimIst();
+    }
+
+    public String[] getIsimPcscf() throws RemoteException {
+        PhoneSubInfoProxy phoneSubInfoProxy = getPhoneSubInfoProxy(getDefaultSubscription());
+        return phoneSubInfoProxy.getIsimPcscf();
+    }
+
+    public String getIsimChallengeResponse(String nonce) throws RemoteException {
+        PhoneSubInfoProxy phoneSubInfoProxy = getPhoneSubInfoProxy(getDefaultSubscription());
+        return phoneSubInfoProxy.getIsimChallengeResponse(nonce);
+    }
+
+    public String getIccSimChallengeResponse(long subId, int appType, String data)
+            throws RemoteException {
+        PhoneSubInfoProxy phoneSubInfoProxy = getPhoneSubInfoProxy(subId);
+        return phoneSubInfoProxy.getIccSimChallengeResponse(subId, appType, data);
+    }
+
+     public String getGroupIdLevel1() {
+         return getGroupIdLevel1UsingSubId(getDefaultSubscription());
+     }
+
+     public String getGroupIdLevel1UsingSubId(long subId) {
+         PhoneSubInfoProxy phoneSubInfoProxy = getPhoneSubInfoProxy(subId);
+         if (phoneSubInfoProxy != null) {
+             return phoneSubInfoProxy.getGroupIdLevel1();
+         } else {
+             Rlog.e(TAG,"getGroupIdLevel1 phoneSubInfoProxy is" +
+                       " null for Subscription:" + subId);
+             return null;
+         }
+     }
+}
diff --git a/src/java/com/android/internal/telephony/PhoneSubInfoProxy.java b/src/java/com/android/internal/telephony/PhoneSubInfoProxy.java
index 1974ff8..4a8e7d6 100755
--- a/src/java/com/android/internal/telephony/PhoneSubInfoProxy.java
+++ b/src/java/com/android/internal/telephony/PhoneSubInfoProxy.java
@@ -19,6 +19,7 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 
+import android.os.RemoteException;
 import android.os.ServiceManager;
 
 
@@ -146,6 +147,110 @@
     }
 
     @Override
+    public String getDeviceIdUsingSubId(long subId) throws RemoteException {
+        // FIXME: getDeviceIdUsingSubId
+        return null;
+    }
+
+    @Override
+    public String getSubscriberIdUsingSubId(long subId) throws RemoteException {
+        // FIXME: getSubscriberIdUsingSubId
+        return null;
+    }
+
+    @Override
+    public String getGroupIdLevel1UsingSubId(long subId) throws RemoteException {
+        // FIXME: getGroupIdLevel1UsingSubId
+        return null;
+    }
+
+    @Override
+    public String getIccSerialNumberUsingSubId(long subId) throws RemoteException {
+        // FIXME: getIccSerialNumberUsingSubId
+        return null;
+    }
+
+    @Override
+    public String getLine1NumberUsingSubId(long subId) throws RemoteException {
+        // FIXME: getLine1NumberUsingSubId
+        return null;
+    }
+
+    @Override
+    public String getLine1AlphaTagUsingSubId(long subId) throws RemoteException {
+        // FIXME: getLine1AlphaTagUsingSubId
+        return null;
+    }
+
+    @Override
+    public String getMsisdnUsingSubId(long subId) throws RemoteException {
+        // FIXME: getMsisdnUsingSubId
+        return null;
+    }
+
+    @Override
+    public String getVoiceMailNumberUsingSubId(long subId) throws RemoteException {
+        // FIXME: getVoiceMailNumberUsingSubId
+        return null;
+    }
+
+    @Override
+    public String getCompleteVoiceMailNumberUsingSubId(long subId) throws RemoteException {
+        // FIXME: getCompleteVoiceMailNumberUsingSubId
+        return null;
+    }
+
+    @Override
+    public String getVoiceMailAlphaTagUsingSubId(long subId) throws RemoteException {
+        // FIXME: getVoiceMailAlphaTagUsingSubId
+        return null;
+    }
+
+    /**
+     * Returns the IMS Service Table (IST) that was loaded from the ISIM.
+     * @return IMS Service Table or null if not present or not loaded
+     */
+    @Override
+    public String getIsimIst() {
+        return mPhoneSubInfo.getIsimIst();
+    }
+
+    /**
+     * Returns the IMS Proxy Call Session Control Function(PCSCF) that were loaded from the ISIM.
+     * @return an array of  PCSCF strings with one PCSCF per string, or null if
+     *      not present or not loaded
+     */
+    @Override
+    public String[] getIsimPcscf() {
+        return mPhoneSubInfo.getIsimPcscf();
+    }
+
+    /**
+     * Returns the response of ISIM Authetification through RIL.
+     * Returns null if the Authentification hasn't been successed or isn't present iphonesubinfo.
+     * @return the response of ISIM Authetification, or null if not available
+     * @deprecated
+     * @see getIccSimChallengeResponse
+     */
+    public String getIsimChallengeResponse(String nonce) {
+        return mPhoneSubInfo.getIsimChallengeResponse(nonce);
+    }
+
+    /**
+     * Returns the response of the SIM application on the UICC to authentication
+     * challenge/response algorithm. The data string and challenge response are
+     * Base64 encoded Strings.
+     * Can support EAP-SIM, EAP-AKA with results encoded per 3GPP TS 31.102.
+     *
+     * @param appType ICC application type (@see com.android.internal.telephony.PhoneConstants#APPTYPE_xxx)
+     * @param data authentication challenge data
+     * @return challenge response
+     */
+    public String getIccSimChallengeResponse(long subId, int appType, String data) {
+        return mPhoneSubInfo.getIccSimChallengeResponse(subId, appType, data);
+    }
+
+    @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         mPhoneSubInfo.dump(fd, pw, args);
     }
diff --git a/src/java/com/android/internal/telephony/ProxyController.java b/src/java/com/android/internal/telephony/ProxyController.java
new file mode 100644
index 0000000..80612b7
--- /dev/null
+++ b/src/java/com/android/internal/telephony/ProxyController.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (c) 2011-2013, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ *       copyright notice, this list of conditions and the following
+ *       disclaimer in the documentation and/or other materials provided
+ *       with the distribution.
+ *     * Neither the name of The Linux Foundation nor the names of its
+ *       contributors may be used to endorse or promote products derived
+ *       from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.android.internal.telephony;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Message;
+
+import android.telephony.Rlog;
+import android.telephony.ServiceState;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+
+import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneBase;
+import com.android.internal.telephony.PhoneProxy;
+import com.android.internal.telephony.dataconnection.DctController;
+import com.android.internal.telephony.uicc.UiccController;
+
+public class ProxyController {
+    static final String LOG_TAG = "ProxyController";
+
+    //***** Class Variables
+    private static ProxyController sProxyController;
+
+    private Phone[] mProxyPhones;
+
+    private UiccController mUiccController;
+
+    private CommandsInterface[] mCi;
+
+    private Context mContext;
+
+    private static DctController mDctController;
+
+    //UiccPhoneBookController to use proper IccPhoneBookInterfaceManagerProxy object
+    private UiccPhoneBookController mUiccPhoneBookController;
+
+    //PhoneSubInfoController to use proper PhoneSubInfoProxy object
+    private PhoneSubInfoController mPhoneSubInfoController;
+
+    //UiccSmsController to use proper IccSmsInterfaceManager object
+    private UiccSmsController mUiccSmsController;
+
+  //  private SubscriptionManager mSubscriptionManager;
+
+    //***** Class Methods
+    public static ProxyController getInstance(Context context, Phone[] phoneProxy,
+            UiccController uiccController, CommandsInterface[] ci) {
+        if (sProxyController == null) {
+            sProxyController = new ProxyController(context, phoneProxy, uiccController, ci);
+        }
+        return sProxyController;
+    }
+
+    static public ProxyController getInstance() {
+        return sProxyController;
+    }
+
+    private ProxyController(Context context, Phone[] phoneProxy, UiccController uiccController,
+            CommandsInterface[] ci) {
+        logd("Constructor - Enter");
+
+        mContext = context;
+        mProxyPhones = phoneProxy;
+        mUiccController = uiccController;
+        mCi = ci;
+
+        mDctController = DctController.makeDctController((PhoneProxy[])phoneProxy);
+        mUiccPhoneBookController = new UiccPhoneBookController(mProxyPhones);
+        mPhoneSubInfoController = new PhoneSubInfoController(mProxyPhones);
+        mUiccSmsController = new UiccSmsController(mProxyPhones);
+       // mSubscriptionManager = SubscriptionManager.getInstance(context, uiccController, ci);
+
+        logd("Constructor - Exit");
+    }
+
+    public void updateDataConnectionTracker(int sub) {
+        ((PhoneProxy) mProxyPhones[sub]).updateDataConnectionTracker();
+    }
+
+    public void enableDataConnectivity(int sub) {
+        ((PhoneProxy) mProxyPhones[sub]).setInternalDataEnabled(true);
+    }
+
+    public void disableDataConnectivity(int sub,
+            Message dataCleanedUpMsg) {
+        ((PhoneProxy) mProxyPhones[sub]).setInternalDataEnabled(false, dataCleanedUpMsg);
+    }
+
+    public boolean enableDataConnectivityFlag(int sub) {
+        return ((PhoneProxy) mProxyPhones[sub]).setInternalDataEnabledFlag(true);
+    }
+
+    public boolean disableDataConnectivityFlag(int sub) {
+        return ((PhoneProxy) mProxyPhones[sub]).setInternalDataEnabledFlag(false);
+    }
+
+    public void updateCurrentCarrierInProvider(int sub) {
+        ((PhoneProxy) mProxyPhones[sub]).updateCurrentCarrierInProvider();
+    }
+
+    public void checkAndUpdatePhoneObject(Subscription userSub) {
+        int subId = userSub.subId;
+        if ((userSub.appType.equals("SIM")
+                || userSub.appType.equals("USIM"))
+                && (!mProxyPhones[subId].getPhoneName().equals("GSM"))) {
+            logd("gets New GSM phone" );
+            ((PhoneProxy) mProxyPhones[subId])
+                .updatePhoneObject(ServiceState.RIL_RADIO_TECHNOLOGY_GSM);
+        } else if ((userSub.appType.equals("RUIM")
+                || userSub.appType.equals("CSIM")
+                || userSub.appType.equals("GLOBAL"))
+                && (!mProxyPhones[subId].getPhoneName().equals("CDMA"))) {
+            logd("gets New CDMA phone" );
+            ((PhoneProxy) mProxyPhones[subId])
+                .updatePhoneObject(ServiceState.RIL_RADIO_TECHNOLOGY_1xRTT);
+        }
+    }
+
+    public void registerForAllDataDisconnected(long subId, Handler h, int what, Object obj) {
+        int phoneId = SubscriptionController.getInstance().getPhoneId(subId);
+
+        if (phoneId >= 0 && phoneId < TelephonyManager.getDefault().getPhoneCount()) {
+            ((PhoneProxy) mProxyPhones[phoneId]).registerForAllDataDisconnected(h, what, obj);
+        }
+    }
+
+    public void unregisterForAllDataDisconnected(long subId, Handler h) {
+        int phoneId = SubscriptionController.getInstance().getPhoneId(subId);
+
+        if (phoneId >= 0 && phoneId < TelephonyManager.getDefault().getPhoneCount()) {
+            ((PhoneProxy) mProxyPhones[phoneId]).unregisterForAllDataDisconnected(h);
+        }
+    }
+
+    public boolean isDataDisconnected(long subId) {
+        int phoneId = SubscriptionController.getInstance().getPhoneId(subId);
+
+        if (phoneId >= 0 && phoneId < TelephonyManager.getDefault().getPhoneCount()) {
+            Phone activePhone = ((PhoneProxy) mProxyPhones[phoneId]).getActivePhone();
+            return ((PhoneBase) activePhone).mDcTracker.isDisconnected();
+        } else {
+            return false;
+        }
+    }
+
+    private void logd(String string) {
+        Rlog.d(LOG_TAG, string);
+    }
+}
diff --git a/src/java/com/android/internal/telephony/RIL.java b/src/java/com/android/internal/telephony/RIL.java
index cc71b86..be9b1c9 100644
--- a/src/java/com/android/internal/telephony/RIL.java
+++ b/src/java/com/android/internal/telephony/RIL.java
@@ -41,6 +41,7 @@
 import android.os.PowerManager;
 import android.os.SystemProperties;
 import android.os.PowerManager.WakeLock;
+import android.provider.Settings.SettingNotFoundException;
 import android.telephony.CellInfo;
 import android.telephony.NeighboringCellInfo;
 import android.telephony.PhoneNumberUtils;
@@ -63,6 +64,8 @@
 import com.android.internal.telephony.cdma.CdmaSmsBroadcastConfigInfo;
 import com.android.internal.telephony.dataconnection.DcFailCause;
 import com.android.internal.telephony.dataconnection.DataCallResponse;
+import com.android.internal.telephony.TelephonyDevController;
+import com.android.internal.telephony.HardwareConfig;
 
 import java.io.ByteArrayInputStream;
 import java.io.DataInputStream;
@@ -89,6 +92,7 @@
     private static RILRequest sPool = null;
     private static int sPoolSize = 0;
     private static final int MAX_POOL_SIZE = 4;
+    private Context mContext;
 
     //***** Instance Variables
     int mSerial;
@@ -243,6 +247,8 @@
     // When we are testing emergency calls
     AtomicBoolean mTestingEmergencyCall = new AtomicBoolean(false);
 
+    private Integer mInstanceId;
+
     //***** Events
 
     static final int EVENT_SEND                 = 1;
@@ -255,7 +261,7 @@
     static final int RESPONSE_SOLICITED = 0;
     static final int RESPONSE_UNSOLICITED = 1;
 
-    static final String SOCKET_NAME_RIL = "rild";
+    static final String[] SOCKET_NAME_RIL = {"rild", "rild2", "rild3"};
 
     static final int SOCKET_OPEN_RETRY_MILLIS = 4 * 1000;
 
@@ -464,14 +470,21 @@
         public void
         run() {
             int retryCount = 0;
+            String rilSocket = "rild";
 
             try {for (;;) {
                 LocalSocket s = null;
                 LocalSocketAddress l;
 
+                if (mInstanceId == null || mInstanceId == 0 ) {
+                    rilSocket = SOCKET_NAME_RIL[0];
+                } else {
+                    rilSocket = SOCKET_NAME_RIL[mInstanceId];
+                }
+
                 try {
                     s = new LocalSocket();
-                    l = new LocalSocketAddress(SOCKET_NAME_RIL,
+                    l = new LocalSocketAddress(rilSocket,
                             LocalSocketAddress.Namespace.RESERVED);
                     s.connect(l);
                 } catch (IOException ex){
@@ -488,12 +501,12 @@
 
                     if (retryCount == 8) {
                         Rlog.e (RILJ_LOG_TAG,
-                            "Couldn't find '" + SOCKET_NAME_RIL
+                            "Couldn't find '" + rilSocket
                             + "' socket after " + retryCount
                             + " times, continuing to retry silently");
                     } else if (retryCount > 0 && retryCount < 8) {
                         Rlog.i (RILJ_LOG_TAG,
-                            "Couldn't find '" + SOCKET_NAME_RIL
+                            "Couldn't find '" + rilSocket
                             + "' socket; retrying after timeout");
                     }
 
@@ -509,7 +522,7 @@
                 retryCount = 0;
 
                 mSocket = s;
-                Rlog.i(RILJ_LOG_TAG, "Connected to '" + SOCKET_NAME_RIL + "' socket");
+                Rlog.i(RILJ_LOG_TAG, "Connected to '" + rilSocket + "' socket");
 
                 int length = 0;
                 try {
@@ -535,14 +548,14 @@
                         p.recycle();
                     }
                 } catch (java.io.IOException ex) {
-                    Rlog.i(RILJ_LOG_TAG, "'" + SOCKET_NAME_RIL + "' socket closed",
+                    Rlog.i(RILJ_LOG_TAG, "'" + rilSocket + "' socket closed",
                           ex);
                 } catch (Throwable tr) {
                     Rlog.e(RILJ_LOG_TAG, "Uncaught exception read length=" + length +
                         "Exception:" + tr.toString());
                 }
 
-                Rlog.i(RILJ_LOG_TAG, "Disconnected from '" + SOCKET_NAME_RIL
+                Rlog.i(RILJ_LOG_TAG, "Disconnected from '" + rilSocket
                       + "' socket");
 
                 setRadioState (RadioState.RADIO_UNAVAILABLE);
@@ -571,15 +584,22 @@
     //***** Constructors
 
     public RIL(Context context, int preferredNetworkType, int cdmaSubscription) {
+        this(context, preferredNetworkType, cdmaSubscription, null);
+    }
+
+    public RIL(Context context, int preferredNetworkType,
+            int cdmaSubscription, Integer instanceId) {
         super(context);
         if (RILJ_LOGD) {
             riljLog("RIL(context, preferredNetworkType=" + preferredNetworkType +
                     " cdmaSubscription=" + cdmaSubscription + ")");
         }
 
+        mContext = context;
         mCdmaSubscription  = cdmaSubscription;
         mPreferredNetworkType = preferredNetworkType;
         mPhoneType = RILConstants.NO_PHONE;
+        mInstanceId = instanceId;
 
         PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
         mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, RILJ_LOG_TAG);
@@ -609,6 +629,9 @@
             filter.addAction(Intent.ACTION_SCREEN_OFF);
             context.registerReceiver(mIntentReceiver, filter);
         }
+
+        TelephonyDevController tdc = TelephonyDevController.getInstance();
+        tdc.registerRIL(this);
     }
 
     //***** CommandsInterface implementation
@@ -657,6 +680,33 @@
         send(rr);
     }
 
+    public void setUiccSubscription(int slotId, int appIndex, int subId,
+            int subStatus, Message result) {
+        //Note: This RIL request is also valid for SIM and RUIM (ICC card)
+        RILRequest rr = RILRequest.obtain(RIL_REQUEST_SET_UICC_SUBSCRIPTION, result);
+
+        if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+                + " slot: " + slotId + " appIndex: " + appIndex
+                + " subId: " + subId + " subStatus: " + subStatus);
+
+        rr.mParcel.writeInt(slotId);
+        rr.mParcel.writeInt(appIndex);
+        rr.mParcel.writeInt(subId);
+        rr.mParcel.writeInt(subStatus);
+
+        send(rr);
+    }
+
+    // FIXME This API should take an AID and slot ID
+    public void setDataAllowed(boolean allowed, Message result) {
+        RILRequest rr = RILRequest.obtain(RIL_REQUEST_ALLOW_DATA, result);
+        if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+        rr.mParcel.writeInt(1);
+        rr.mParcel.writeInt(allowed ? 1 : 0);
+        send(rr);
+    }
+
     @Override public void
     supplyIccPin(String pin, Message result) {
         supplyIccPinForApp(pin, null, result);
@@ -1141,6 +1191,16 @@
 
     @Override
     public void
+    getHardwareConfig (Message result) {
+        RILRequest rr = RILRequest.obtain(RIL_REQUEST_GET_HARDWARE_CONFIG, result);
+
+        if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+        send(rr);
+    }
+
+    @Override
+    public void
     sendDtmf(char c, Message result) {
         RILRequest rr
                 = RILRequest.obtain(RIL_REQUEST_DTMF, result);
@@ -2433,6 +2493,10 @@
             case RIL_REQUEST_NV_WRITE_ITEM: ret = responseVoid(p); break;
             case RIL_REQUEST_NV_WRITE_CDMA_PRL: ret = responseVoid(p); break;
             case RIL_REQUEST_NV_RESET_CONFIG: ret = responseVoid(p); break;
+            case RIL_REQUEST_SET_UICC_SUBSCRIPTION: ret = responseVoid(p); break;
+            case RIL_REQUEST_ALLOW_DATA: ret = responseVoid(p); break;
+            case RIL_REQUEST_GET_HARDWARE_CONFIG: ret = responseHardwareConfig(p); break;
+            case RIL_REQUEST_ICC_SIM_AUTHENTICATION: ret =  responseString(p); break;
             default:
                 throw new RuntimeException("Unrecognized solicited response: " + rr.mRequest);
             //break;
@@ -2556,6 +2620,13 @@
                 sb.append(cell).append(" ");
             }
             s = sb.toString();
+        } else if (req == RIL_REQUEST_GET_HARDWARE_CONFIG) {
+            ArrayList<HardwareConfig> hwcfgs = (ArrayList<HardwareConfig>) ret;
+            sb = new StringBuilder(" ");
+            for (HardwareConfig hwcfg : hwcfgs) {
+                sb.append("[").append(hwcfg).append("] ");
+            }
+            s = sb.toString();
         } else {
             s = ret.toString();
         }
@@ -2613,6 +2684,9 @@
             case RIL_UNSOL_VOICE_RADIO_TECH_CHANGED: ret =  responseInts(p); break;
             case RIL_UNSOL_CELL_INFO_LIST: ret = responseCellInfoList(p); break;
             case RIL_UNSOL_RESPONSE_IMS_NETWORK_STATE_CHANGED: ret =  responseVoid(p); break;
+            case RIL_UNSOL_UICC_SUBSCRIPTION_STATUS_CHANGED: ret =  responseInts(p); break;
+            case RIL_UNSOL_SRVCC_STATE_NOTIFY: ret = responseInts(p); break;
+            case RIL_UNSOL_HARDWARE_CONFIG_CHANGED: ret = responseHardwareConfig(p); break;
 
             default:
                 throw new RuntimeException("Unrecognized unsol response: " + response);
@@ -2986,6 +3060,32 @@
                 }
                 break;
             }
+            case RIL_UNSOL_UICC_SUBSCRIPTION_STATUS_CHANGED: {
+                if (RILJ_LOGD) unsljLogRet(response, ret);
+
+                if (mSubscriptionStatusRegistrants != null) {
+                    mSubscriptionStatusRegistrants.notifyRegistrants(
+                                        new AsyncResult (null, ret, null));
+                }
+                break;
+            }
+            case RIL_UNSOL_SRVCC_STATE_NOTIFY: {
+                if (RILJ_LOGD) unsljLogRet(response, ret);
+
+                if (mSrvccStateRegistrants != null) {
+                    mSrvccStateRegistrants
+                            .notifyRegistrants(new AsyncResult(null, ret, null));
+                }
+                break;
+            }
+            case RIL_UNSOL_HARDWARE_CONFIG_CHANGED:
+                if (RILJ_LOGD) unsljLogRet(response, ret);
+
+                if (mHardwareConfigChangeRegistrants != null) {
+                    mHardwareConfigChangeRegistrants.notifyRegistrants(
+                                             new AsyncResult (null, ret, null));
+                }
+                break;
         }
     }
 
@@ -3284,6 +3384,10 @@
             if (!TextUtils.isEmpty(gateways)) {
                 dataCall.gateways = gateways.split(" ");
             }
+            String pcscf = p.readString();
+            if (!TextUtils.isEmpty(pcscf)) {
+                dataCall.pcscf = pcscf.split(" ");
+            }
         }
         return dataCall;
     }
@@ -3339,6 +3443,13 @@
                     dataCall.gateways = gateways.split(" ");
                 }
             }
+            if (num >= 6) {
+                String pcscf = p.readString();
+                if (RILJ_LOGD) riljLog("responseSetupDataCall got pcscf=" + pcscf);
+                if (!TextUtils.isEmpty(pcscf)) {
+                    dataCall.pcscf = pcscf.split(" ");
+                }
+            }
         } else {
             if (num != 1) {
                 throw new RuntimeException(
@@ -3617,6 +3728,44 @@
         return response;
     }
 
+   private Object
+   responseHardwareConfig(Parcel p) {
+      int num;
+      ArrayList<HardwareConfig> response;
+      HardwareConfig hw;
+
+      num = p.readInt();
+      response = new ArrayList<HardwareConfig>(num);
+
+      if (RILJ_LOGV) {
+         riljLog("responseHardwareConfig: num=" + num);
+      }
+      for (int i = 0 ; i < num ; i++) {
+         int type = p.readInt();
+         switch(type) {
+            case HardwareConfig.DEV_HARDWARE_TYPE_MODEM: {
+               hw = new HardwareConfig(type);
+               hw.assignModem(p.readString(), p.readInt(), p.readInt(),
+                  p.readInt(), p.readInt(), p.readInt(), p.readInt());
+               break;
+            }
+            case HardwareConfig.DEV_HARDWARE_TYPE_SIM: {
+               hw = new HardwareConfig(type);
+               hw.assignSim(p.readString(), p.readInt(), p.readString());
+               break;
+            }
+            default: {
+               throw new RuntimeException(
+                  "RIL_REQUEST_GET_HARDWARE_CONFIG invalid hardward type:" + type);
+            }
+         }
+
+         response.add(hw);
+      }
+
+      return response;
+   }
+
     static String
     requestToString(int request) {
 /*
@@ -3746,6 +3895,10 @@
             case RIL_REQUEST_NV_WRITE_ITEM: return "RIL_REQUEST_NV_WRITE_ITEM";
             case RIL_REQUEST_NV_WRITE_CDMA_PRL: return "RIL_REQUEST_NV_WRITE_CDMA_PRL";
             case RIL_REQUEST_NV_RESET_CONFIG: return "RIL_REQUEST_NV_RESET_CONFIG";
+            case RIL_REQUEST_SET_UICC_SUBSCRIPTION: return "RIL_REQUEST_SET_UICC_SUBSCRIPTION";
+            case RIL_REQUEST_ALLOW_DATA: return "RIL_REQUEST_ALLOW_DATA";
+            case RIL_REQUEST_GET_HARDWARE_CONFIG: return "GET_HARDWARE_CONFIG";
+            case RIL_REQUEST_ICC_SIM_AUTHENTICATION: return "RIL_REQUEST_SIM_AUTHENTICATION";
             default: return "<unknown request>";
         }
     }
@@ -3798,16 +3951,23 @@
             case RIL_UNSOL_CELL_INFO_LIST: return "UNSOL_CELL_INFO_LIST";
             case RIL_UNSOL_RESPONSE_IMS_NETWORK_STATE_CHANGED:
                 return "UNSOL_RESPONSE_IMS_NETWORK_STATE_CHANGED";
+            case RIL_UNSOL_UICC_SUBSCRIPTION_STATUS_CHANGED:
+                    return "RIL_UNSOL_UICC_SUBSCRIPTION_STATUS_CHANGED";
+            case RIL_UNSOL_SRVCC_STATE_NOTIFY:
+                    return "UNSOL_SRVCC_STATE_NOTIFY";
+            case RIL_UNSOL_HARDWARE_CONFIG_CHANGED: return "RIL_UNSOL_HARDWARE_CONFIG_CHANGED";
             default: return "<unknown response>";
         }
     }
 
     private void riljLog(String msg) {
-        Rlog.d(RILJ_LOG_TAG, msg);
+        Rlog.d(RILJ_LOG_TAG, msg
+                + (mInstanceId != null ? (" [SUB" + mInstanceId + "]") : ""));
     }
 
     private void riljLogv(String msg) {
-        Rlog.v(RILJ_LOG_TAG, msg);
+        Rlog.v(RILJ_LOG_TAG, msg
+                + (mInstanceId != null ? (" [SUB" + mInstanceId + "]") : ""));
     }
 
     private void unsljLog(int response) {
@@ -4037,6 +4197,17 @@
         send(rr);
     }
 
+    @Override
+    public void requestIccSimAuthentication(String data, Message response) {
+        RILRequest rr = RILRequest.obtain(RIL_REQUEST_ICC_SIM_AUTHENTICATION, response);
+
+        rr.mParcel.writeString(data);
+
+        if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+
+        send(rr);
+    }
+
     /**
      * {@inheritDoc}
      */
diff --git a/src/java/com/android/internal/telephony/ServiceStateTracker.java b/src/java/com/android/internal/telephony/ServiceStateTracker.java
index f737ab8..8462427 100644
--- a/src/java/com/android/internal/telephony/ServiceStateTracker.java
+++ b/src/java/com/android/internal/telephony/ServiceStateTracker.java
@@ -16,7 +16,9 @@
 
 package com.android.internal.telephony;
 
+import android.app.PendingIntent;
 import android.content.Context;
+import android.content.IntentFilter;
 import android.os.AsyncResult;
 import android.os.Handler;
 import android.os.Message;
@@ -26,6 +28,7 @@
 import android.telephony.CellInfo;
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
+import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.Pair;
 import android.util.TimeUtils;
@@ -106,8 +109,8 @@
     protected RegistrantList mPsRestrictDisabledRegistrants = new RegistrantList();
 
     /* Radio power off pending flag and tag counter */
-    private boolean mPendingRadioPowerOffAfterDataOff = false;
-    private int mPendingRadioPowerOffAfterDataOffTag = 0;
+    protected boolean mPendingRadioPowerOffAfterDataOff = false;
+    protected int mPendingRadioPowerOffAfterDataOffTag = 0;
 
     /** Signal strength poll rate. */
     protected static final int POLL_PERIOD_MILLIS = 20 * 1000;
@@ -156,9 +159,10 @@
     protected static final int EVENT_CDMA_SUBSCRIPTION_SOURCE_CHANGED  = 39;
     protected static final int EVENT_CDMA_PRL_VERSION_CHANGED          = 40;
     protected static final int EVENT_RADIO_ON                          = 41;
-    protected static final int EVENT_ICC_CHANGED                       = 42;
+    public static final int EVENT_ICC_CHANGED                          = 42;
     protected static final int EVENT_GET_CELL_INFO_LIST                = 43;
     protected static final int EVENT_UNSOL_CELL_INFO_LIST              = 44;
+    protected static final int EVENT_CHANGE_IMS_STATE                  = 45;
 
     protected static final String TIMEZONE_PROPERTY = "persist.sys.timezone";
 
@@ -200,6 +204,13 @@
     protected static final String REGISTRATION_DENIED_GEN  = "General";
     protected static final String REGISTRATION_DENIED_AUTH = "Authentication Failure";
 
+    protected boolean mImsRegistrationOnOff = false;
+    protected boolean mAlarmSwitch = false;
+    protected IntentFilter mIntentFilter = null;
+    protected PendingIntent mRadioOffIntent = null;
+    protected static final String ACTION_RADIO_OFF = "android.intent.action.ACTION_RADIO_OFF";
+    protected boolean mPowerOffDelayNeed = true;
+
     protected ServiceStateTracker(PhoneBase phoneBase, CommandsInterface ci, CellInfo cellInfo) {
         mPhoneBase = phoneBase;
         mCellInfo = cellInfo;
@@ -455,6 +466,8 @@
     public abstract int getCurrentDataConnectionState();
     public abstract boolean isConcurrentVoiceAndDataAllowed();
 
+    public abstract void setImsRegistrationState(boolean registered);
+
     /**
      * Registration point for transition into DataConnection attached.
      * @param h handler to notify
@@ -707,6 +720,10 @@
         return retVal;
     }
 
+    public String getSystemProperty(String property, String defValue) {
+        return TelephonyManager.getTelephonyProperty(property, mPhoneBase.getSubId(), defValue);
+    }
+
     /**
      * @return all available cell information or null if none.
      */
@@ -798,6 +815,7 @@
         // if we have a change in operator, notify wifi (even to/from none)
         if (((newOp == null) && (TextUtils.isEmpty(oldOp) == false)) ||
                 ((newOp != null) && (newOp.equals(oldOp) == false))) {
+            log("update mccmnc=" + newOp + " fromServiceState=true");
             MccTable.updateMccMncConfiguration(context, newOp, true);
         }
     }
diff --git a/src/java/com/android/internal/telephony/SmsStorageMonitor.java b/src/java/com/android/internal/telephony/SmsStorageMonitor.java
old mode 100644
new mode 100755
index 820c408..ca5d015
--- a/src/java/com/android/internal/telephony/SmsStorageMonitor.java
+++ b/src/java/com/android/internal/telephony/SmsStorageMonitor.java
@@ -26,6 +26,7 @@
 import android.os.PowerManager;
 import android.provider.Telephony.Sms.Intents;
 import android.telephony.Rlog;
+import android.telephony.SubscriptionManager;
 
 /**
  * Monitors the device and ICC storage, and sends the appropriate events.
@@ -54,6 +55,9 @@
 
     private boolean mReportMemoryStatusPending;
 
+    /** it is use to put in to extra value for SIM_FULL_ACTION and SMS_REJECTED_ACTION */
+    PhoneBase mPhone;
+
     final CommandsInterface mCi;                            // accessed from inner class
     boolean mStorageAvailable = true;                       // accessed from inner class
 
@@ -68,6 +72,7 @@
      * @param phone the Phone to use
      */
     public SmsStorageMonitor(PhoneBase phone) {
+        mPhone = phone;
         mContext = phone.getContext();
         mCi = phone.mCi;
 
@@ -139,6 +144,7 @@
         // broadcast SIM_FULL intent
         Intent intent = new Intent(Intents.SIM_FULL_ACTION);
         mWakeLock.acquire(WAKE_LOCK_TIMEOUT);
+        SubscriptionManager.putPhoneIdAndSubIdExtra(intent, mPhone.getPhoneId());
         mContext.sendBroadcast(intent, android.Manifest.permission.RECEIVE_SMS);
     }
 
diff --git a/src/java/com/android/internal/telephony/SubInfoRecordUpdater.java b/src/java/com/android/internal/telephony/SubInfoRecordUpdater.java
new file mode 100644
index 0000000..9e3d417
--- /dev/null
+++ b/src/java/com/android/internal/telephony/SubInfoRecordUpdater.java
@@ -0,0 +1,451 @@
+/*
+* Copyright (C) 2011-2014 MediaTek Inc.
+*
+* 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.Manifest.permission.READ_PHONE_STATE;
+import android.app.ActivityManagerNative;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.telephony.Rlog;
+import android.telephony.SubscriptionManager;
+import android.telephony.SubInfoRecord;
+import android.telephony.TelephonyManager;
+import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.IccCardConstants;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.PhoneProxy;
+import com.android.internal.telephony.TelephonyIntents;
+import com.android.internal.telephony.TelephonyProperties;
+import com.android.internal.telephony.uicc.IccConstants;
+import com.android.internal.telephony.uicc.IccFileHandler;
+import com.android.internal.telephony.uicc.IccUtils;
+
+import java.util.List;
+
+/**
+ *@hide
+ */
+public class SubInfoRecordUpdater extends Handler {
+    private static final String LOG_TAG = "SUB";
+    private static final int PROJECT_SIM_NUM = TelephonyManager.getDefault().getPhoneCount();
+    private static final int EVENT_OFFSET = 8;
+    private static final int EVENT_QUERY_ICCID_DONE = 1;
+    private static final String ICCID_STRING_FOR_NO_SIM = "";
+    private static final int ICCID_WAIT_TIMER = 90;
+
+    /**
+     *  int[] sInsertSimState maintains all slots' SIM inserted status currently,
+     *  it may contain 4 kinds of values:
+     *    SIM_NOT_INSERT : no SIM inserted in slot i now
+     *    SIM_CHANGED    : a valid SIM insert in slot i and is different SIM from last time
+     *                     it will later become SIM_NEW or SIM_REPOSITION during update procedure
+     *    SIM_NOT_CHANGE : a valid SIM insert in slot i and is the same SIM as last time
+     *    SIM_NEW        : a valid SIM insert in slot i and is a new SIM
+     *    SIM_REPOSITION : a valid SIM insert in slot i and is inserted in different slot last time
+     *    positive integer #: index to distinguish SIM cards with the same IccId
+     */
+    public static final int SIM_NOT_CHANGE = 0;
+    public static final int SIM_CHANGED    = -1;
+    public static final int SIM_NEW        = -2;
+    public static final int SIM_REPOSITION = -3;
+    public static final int SIM_NOT_INSERT = -99;
+
+    public static final int STATUS_NO_SIM_INSERTED = 0x00;
+    public static final int STATUS_SIM1_INSERTED = 0x01;
+    public static final int STATUS_SIM2_INSERTED = 0x02;
+    public static final int STATUS_SIM3_INSERTED = 0x04;
+    public static final int STATUS_SIM4_INSERTED = 0x08;
+
+    private static Phone[] sPhone;
+    private static Context sContext = null;
+    private static CommandsInterface[] sCi;
+    private static IccFileHandler[] sFh = new IccFileHandler[PROJECT_SIM_NUM];
+    private static String sIccId[] = new String[PROJECT_SIM_NUM];
+    private static int[] sInsertSimState = new int[PROJECT_SIM_NUM];
+    private static TelephonyManager sTelephonyMgr = null;
+    // To prevent repeatedly update flow every time receiver SIM_STATE_CHANGE
+    private static boolean sNeedUpdate = true;
+
+    public SubInfoRecordUpdater(Context context, Phone[] phoneProxy, CommandsInterface[] ci) {
+        logd("Constructor invoked");
+
+        sContext = context;
+        sPhone = phoneProxy;
+        sCi = ci;
+        IntentFilter intentFilter = new IntentFilter(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
+        sContext.registerReceiver(sReceiver, intentFilter);
+    }
+
+    private static int encodeEventId(int event, int slotId) {
+        return event << (slotId * EVENT_OFFSET);
+    }
+
+    private final BroadcastReceiver sReceiver = new  BroadcastReceiver() {
+        public void onReceive(Context context, Intent intent) {
+            logd("[Receiver]+");
+            String action = intent.getAction();
+            int slotId;
+            logd("Action: " + action);
+            if (action.equals(TelephonyIntents.ACTION_SIM_STATE_CHANGED)) {
+                String simStatus = intent.getStringExtra(IccCardConstants.INTENT_KEY_ICC_STATE);
+                slotId = intent.getIntExtra(PhoneConstants.SLOT_KEY, 0);
+                logd("slotId: " + slotId + " simStatus: " + simStatus);
+                if (IccCardConstants.INTENT_VALUE_ICC_READY.equals(simStatus)
+                        || IccCardConstants.INTENT_VALUE_ICC_LOCKED.equals(simStatus)) {
+                    if (sIccId[slotId] != null && sIccId[slotId].equals(ICCID_STRING_FOR_NO_SIM)) {
+                        logd("SIM" + (slotId + 1) + " hot plug in");
+                        sIccId[slotId] = null;
+                        sNeedUpdate = true;
+                    }
+                    queryIccId(slotId);
+                } else if (IccCardConstants.INTENT_VALUE_ICC_LOADED.equals(simStatus)) {
+                    queryIccId(slotId);
+                    if (sTelephonyMgr == null) {
+                        sTelephonyMgr = TelephonyManager.from(sContext);
+                    }
+                    //setDisplayNameForNewSim(sTelephonyMgr.getSimOperatorName(slotId), slotId, SimInfoManager.SIM_SOURCE);
+                } else if (IccCardConstants.INTENT_VALUE_ICC_ABSENT.equals(simStatus)) {
+                    if (sIccId[slotId] != null && !sIccId[slotId].equals(ICCID_STRING_FOR_NO_SIM)) {
+                        logd("SIM" + (slotId + 1) + " hot plug out");
+                        sNeedUpdate = true;
+                    }
+                    sFh[slotId] = null;
+                    sIccId[slotId] = ICCID_STRING_FOR_NO_SIM;
+                    if (isAllIccIdQueryDone() && sNeedUpdate) {
+                        updateSimInfoByIccId();
+                    }
+                }
+            }
+            logd("[Receiver]-");
+        }
+    };
+
+    private boolean isAllIccIdQueryDone() {
+        for (int i = 0; i < PROJECT_SIM_NUM; i++) {
+            if (sIccId[i] == null) {
+                logd("Wait for SIM" + (i + 1) + " IccId");
+                return false;
+            }
+        }
+        logd("All IccIds query complete");
+
+        return true;
+    }
+
+    public static void setDisplayNameForNewSub(String newSubName, int subId, int newNameSource) {
+        SubInfoRecord subInfo = SubscriptionManager.getSubInfoUsingSubId(sContext, subId);
+        if (subInfo != null) {
+            // overwrite SIM display name if it is not assigned by user
+            int oldNameSource = subInfo.mNameSource;
+            String oldSubName = subInfo.mDisplayName;
+            logd("[setDisplayNameForNewSub] mSubInfoIdx = " + subInfo.mSubId + ", oldSimName = " + oldSubName 
+                    + ", oldNameSource = " + oldNameSource + ", newSubName = " + newSubName + ", newNameSource = " + newNameSource);
+            if (oldSubName == null || 
+                (oldNameSource == SubscriptionManager.DEFAULT_SOURCE && newSubName != null) ||
+                (oldNameSource == SubscriptionManager.SIM_SOURCE && newSubName != null && !newSubName.equals(oldSubName))) {
+                SubscriptionManager.setDisplayName(sContext, newSubName, subInfo.mSubId, newNameSource);
+            }
+        } else {
+            logd("SUB" + (subId + 1) + " SubInfo not created yet");
+        }
+    }
+
+    public void handleMessage(Message msg) {
+        AsyncResult ar = (AsyncResult)msg.obj;
+        int msgNum = msg.what;
+        int slotId;
+        for (slotId = PhoneConstants.SUB1; slotId <= PhoneConstants.SUB3; slotId++) {
+            int pivot = 1 << (slotId * EVENT_OFFSET);
+            if (msgNum >= pivot) {
+                continue;
+            } else {
+                break;
+            }
+        }
+        slotId--;
+        int event = msgNum >> (slotId * EVENT_OFFSET);
+        switch (event) {
+            case EVENT_QUERY_ICCID_DONE:
+                logd("handleMessage : <EVENT_QUERY_ICCID_DONE> SIM" + (slotId + 1));
+                if (ar.exception == null) {
+                    if (ar.result != null) {
+                        byte[] data = (byte[])ar.result;
+                        sIccId[slotId] = IccUtils.bcdToString(data, 0, data.length);
+                    } else {
+                        logd("Null ar");
+                        sIccId[slotId] = ICCID_STRING_FOR_NO_SIM;
+                    }
+                } else {
+                    sIccId[slotId] = ICCID_STRING_FOR_NO_SIM;
+                    logd("Query IccId fail: " + ar.exception);
+                }
+                logd("sIccId[" + slotId + "] = " + sIccId[slotId]);
+                if (isAllIccIdQueryDone() && sNeedUpdate) {
+                    updateSimInfoByIccId();
+                }
+                break;
+            default:
+                logd("Unknown msg:" + msg.what);
+        }
+    }
+
+    private void queryIccId(int slotId) {
+        if (sFh[slotId] == null) {
+            logd("Getting IccFileHandler");
+            sFh[slotId] = ((PhoneProxy)sPhone[slotId]).getIccFileHandler();
+        }
+        if (sFh[slotId] != null) {
+            if (sIccId[slotId] == null) {
+                logd("Querying IccId");
+                sFh[slotId].loadEFTransparent(IccConstants.EF_ICCID, obtainMessage(encodeEventId(EVENT_QUERY_ICCID_DONE, slotId)));
+            }
+        } else {
+            sIccId[slotId] = ICCID_STRING_FOR_NO_SIM;
+            logd("sFh[" + slotId + "] is null, SIM not inserted");
+        }
+    }
+
+    synchronized public void updateSimInfoByIccId() {
+        logd("[updateSimInfoByIccId]+ Start");
+        sNeedUpdate = false;
+
+        SubscriptionManager.clearSubInfo();
+
+        for (int i = 0; i < PROJECT_SIM_NUM; i++) {
+            sInsertSimState[i] = SIM_NOT_CHANGE;
+        }
+
+        int insertedSimCount = PROJECT_SIM_NUM;
+        for (int i = 0; i < PROJECT_SIM_NUM; i++) {
+            if (ICCID_STRING_FOR_NO_SIM.equals(sIccId[i])) {
+                insertedSimCount--;
+                sInsertSimState[i] = SIM_NOT_INSERT;
+            }
+        }
+        logd("insertedSimCount = " + insertedSimCount);
+
+        int index = 0;
+        for (int i = 0; i < PROJECT_SIM_NUM; i++) {
+            if (sInsertSimState[i] == SIM_NOT_INSERT) {
+                continue;
+            }
+            index = 2;
+            for (int j = i + 1; j < PROJECT_SIM_NUM; j++) {
+                if (sInsertSimState[j] == SIM_NOT_CHANGE && sIccId[i].equals(sIccId[j])) {
+                    sInsertSimState[i] = 1;
+                    sInsertSimState[j] = index;
+                    index++;
+                }
+            }
+        }
+
+        ContentResolver contentResolver = sContext.getContentResolver();
+        String[] oldIccId = new String[PROJECT_SIM_NUM];
+        for (int i = 0; i < PROJECT_SIM_NUM; i++) {
+            oldIccId[i] = null;
+            List<SubInfoRecord> oldSubInfo = SubscriptionController.getInstance().getSubInfoUsingSlotIdWithCheck(i, false);
+            if (oldSubInfo != null) {
+                oldIccId[i] = oldSubInfo.get(0).mIccId;
+                logd("oldSubId = " + oldSubInfo.get(0).mSubId);
+                if (sInsertSimState[i] == SIM_NOT_CHANGE && !sIccId[i].equals(oldIccId[i])) {
+                    sInsertSimState[i] = SIM_CHANGED;
+                }
+                if (sInsertSimState[i] != SIM_NOT_CHANGE) {
+                    ContentValues value = new ContentValues(1);
+                    value.put(SubscriptionManager.SIM_ID, SubscriptionManager.SIM_NOT_INSERTED);
+                    contentResolver.update(SubscriptionManager.CONTENT_URI, value,
+                                                SubscriptionManager._ID + "=" + Long.toString(oldSubInfo.get(0).mSubId), null);
+                }
+            } else {
+                if (sInsertSimState[i] == SIM_NOT_CHANGE) {
+                    // no SIM inserted last time, but there is one SIM inserted now
+                    sInsertSimState[i] = SIM_CHANGED;
+                }
+                oldIccId[i] = ICCID_STRING_FOR_NO_SIM;
+                logd("No SIM in slot " + i + " last time");
+            }
+        }
+
+        for (int i = 0; i < PROJECT_SIM_NUM; i++) {
+            logd("oldIccId[" + i + "] = " + oldIccId[i] + ", sIccId[" + i + "] = " + sIccId[i]);
+        }
+
+        //check if the inserted SIM is new SIM
+        int nNewCardCount = 0;
+        int nNewSimStatus = 0;
+        for (int i = 0; i < PROJECT_SIM_NUM; i++) {
+            if (sInsertSimState[i] == SIM_NOT_INSERT) {
+                logd("No SIM inserted in slot " + i + " this time");
+            } else {
+                if (sInsertSimState[i] > 0) {
+                    //some special SIMs may have the same IccIds, add suffix to distinguish them
+                    //FIXME: addSubInfoRecord can return an error.
+                    SubscriptionManager.addSubInfoRecord(sContext, sIccId[i] + Integer.toString(sInsertSimState[i]), i);
+                    logd("SUB" + (i + 1) + " has invalid IccId");
+                } else /*if (sInsertSimState[i] != SIM_NOT_INSERT)*/ {
+                    SubscriptionManager.addSubInfoRecord(sContext, sIccId[i], i);
+                }
+                if (isNewSim(sIccId[i], oldIccId)) {
+                    nNewCardCount++;
+                    switch (i) {
+                        case PhoneConstants.SUB1:
+                            nNewSimStatus |= STATUS_SIM1_INSERTED;
+                            break;
+                        case PhoneConstants.SUB2:
+                            nNewSimStatus |= STATUS_SIM2_INSERTED;
+                            break;
+                        case PhoneConstants.SUB3:
+                            nNewSimStatus |= STATUS_SIM3_INSERTED;
+                            break;
+                        //case PhoneConstants.SUB3:
+                        //    nNewSimStatus |= STATUS_SIM4_INSERTED;
+                        //    break;
+                    }
+
+                    sInsertSimState[i] = SIM_NEW;
+                }
+            }
+        }
+
+        for (int i = 0; i < PROJECT_SIM_NUM; i++) {
+            if (sInsertSimState[i] == SIM_CHANGED) {
+                sInsertSimState[i] = SIM_REPOSITION;
+            }
+            logd("sInsertSimState[" + i + "] = " + sInsertSimState[i]);
+        }
+
+        long[] subIdInSlot = {-3, -3, -3, -3};
+        List<SubInfoRecord> subInfos = SubscriptionManager.getActivatedSubInfoList(sContext);
+        int nSubCount = (subInfos == null) ? 0 : subInfos.size();
+        logd("nSubCount = " + nSubCount);
+        for (int i=0; i<nSubCount; i++) {
+            SubInfoRecord temp = subInfos.get(i);
+            subIdInSlot[temp.mSlotId] = temp.mSubId;
+            logd("subIdInSlot[" + temp.mSlotId + "] = " + temp.mSubId);
+        }
+
+        // true if any slot has no SIM this time, but has SIM last time
+        boolean hasSimRemoved = false;
+        for (int i=0; i < PROJECT_SIM_NUM; i++) {
+            if (sIccId[i] != null && sIccId[i].equals(ICCID_STRING_FOR_NO_SIM) && !oldIccId[i].equals("")) {
+                hasSimRemoved = true;
+                break;
+            }
+        }
+
+        if (nNewCardCount == 0) {
+            int i;
+            if (hasSimRemoved) {
+                // no new SIM, at least one SIM is removed, check if any SIM is repositioned first
+                for (i=0; i < PROJECT_SIM_NUM; i++) {
+                    if (sInsertSimState[i] == SIM_REPOSITION) {
+                        logd("No new SIM detected and SIM repositioned");
+                        setUpdatedData(SubscriptionManager.EXTRA_VALUE_REPOSITION_SIM, nSubCount, nNewSimStatus);
+                        break;
+                    }
+                }
+                if (i == PROJECT_SIM_NUM) {
+                    // no new SIM, no SIM is repositioned => at least one SIM is removed
+                    logd("No new SIM detected and SIM removed");
+                    setUpdatedData(SubscriptionManager.EXTRA_VALUE_REMOVE_SIM, nSubCount, nNewSimStatus);
+                }
+            } else {
+                // no SIM is removed, no new SIM, just check if any SIM is repositioned
+                for (i=0; i< PROJECT_SIM_NUM; i++) {
+                    if (sInsertSimState[i] == SIM_REPOSITION) {
+                        logd("No new SIM detected and SIM repositioned");
+                        setUpdatedData(SubscriptionManager.EXTRA_VALUE_REPOSITION_SIM, nSubCount, nNewSimStatus);
+                        break;
+                    }
+                }
+                if (i == PROJECT_SIM_NUM) {
+                    // all status remain unchanged
+                    logd("[updateSimInfoByIccId] All SIM inserted into the same slot");
+                    setUpdatedData(SubscriptionManager.EXTRA_VALUE_NOCHANGE, nSubCount, nNewSimStatus);
+                }
+            }
+        } else {
+            logd("New SIM detected");
+            setUpdatedData(SubscriptionManager.EXTRA_VALUE_NEW_SIM, nSubCount, nNewSimStatus);
+        }
+
+        SubscriptionController.getInstance().updateDefaultSubId();
+        logd("[updateSimInfoByIccId]- SimInfo update complete");
+    }
+
+    private static void setUpdatedData(int detectedType, int subCount, int newSimStatus) {
+
+        Intent intent = new Intent(TelephonyIntents.ACTION_SUBINFO_RECORD_UPDATED);
+
+        logd("[setUpdatedData]+ ");
+
+        if (detectedType == SubscriptionManager.EXTRA_VALUE_NEW_SIM ) {
+            intent.putExtra(SubscriptionManager.INTENT_KEY_DETECT_STATUS, SubscriptionManager.EXTRA_VALUE_NEW_SIM);
+            intent.putExtra(SubscriptionManager.INTENT_KEY_SIM_COUNT, subCount);
+            intent.putExtra(SubscriptionManager.INTENT_KEY_NEW_SIM_SLOT, newSimStatus);
+        } else if (detectedType == SubscriptionManager.EXTRA_VALUE_REPOSITION_SIM) {
+            intent.putExtra(SubscriptionManager.INTENT_KEY_DETECT_STATUS, SubscriptionManager.EXTRA_VALUE_REPOSITION_SIM);
+            intent.putExtra(SubscriptionManager.INTENT_KEY_SIM_COUNT, subCount);
+        } else if (detectedType == SubscriptionManager.EXTRA_VALUE_REMOVE_SIM) {
+            intent.putExtra(SubscriptionManager.INTENT_KEY_DETECT_STATUS, SubscriptionManager.EXTRA_VALUE_REMOVE_SIM);
+            intent.putExtra(SubscriptionManager.INTENT_KEY_SIM_COUNT, subCount);
+        } else if (detectedType == SubscriptionManager.EXTRA_VALUE_NOCHANGE) {
+            intent.putExtra(SubscriptionManager.INTENT_KEY_DETECT_STATUS, SubscriptionManager.EXTRA_VALUE_NOCHANGE);
+        }
+
+        logd("broadcast intent ACTION_SUBINFO_RECORD_UPDATED : [" + detectedType + ", " + subCount + ", " + newSimStatus+ "]");
+        ActivityManagerNative.broadcastStickyIntent(intent, READ_PHONE_STATE, UserHandle.USER_ALL);
+        logd("[setUpdatedData]- ");
+    }
+
+    private static boolean isNewSim(String iccId, String[] oldIccId) {
+        boolean newSim = true;
+        for(int i = 0; i < PROJECT_SIM_NUM; i++) {
+            if(iccId.equals(oldIccId[i])) {
+                newSim = false;
+                break;
+            }
+        }
+        logd("newSim = " + newSim);
+
+        return newSim;
+    }
+
+    public void dispose() {
+        logd("[dispose]");
+        sContext.unregisterReceiver(sReceiver);
+    }
+
+    private static void logd(String message) {
+        Rlog.d(LOG_TAG, "[SubInfoRecordUpdater]" + message);
+    }
+}
+
diff --git a/src/java/com/android/internal/telephony/Subscription.java b/src/java/com/android/internal/telephony/Subscription.java
new file mode 100644
index 0000000..86bc201
--- /dev/null
+++ b/src/java/com/android/internal/telephony/Subscription.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (c) 2010-2013, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ *       copyright notice, this list of conditions and the following
+ *       disclaimer in the documentation and/or other materials provided
+ *       with the distribution.
+ *     * Neither the name of The Linux Foundation nor the names of its
+ *       contributors may be used to endorse or promote products derived
+ *       from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.android.internal.telephony;
+
+import android.text.TextUtils;
+
+import android.telephony.Rlog;
+
+/**
+ * Class holding all the information of a subscription from UICC Card.
+ */
+public final class Subscription {
+    private static final String LOG_TAG = "Subscription";
+
+    public int slotId;                       // Slot id
+    public int m3gppIndex;                   // Subscription index in the card for GSM
+    public int m3gpp2Index;                  // Subscription index in the card for CDMA
+    public int subId;                        // SUB 0 or SUB 1
+    public SubscriptionStatus subStatus;      // DEACTIVATE = 0, ACTIVATE = 1,
+                                             // ACTIVATED = 2, DEACTIVATED = 3, INVALID = 4;
+    public String appId;
+    public String appLabel;
+    public String appType;
+    public String iccId;
+
+    private boolean DEBUG = false;
+
+    /**
+     * Subscription activation status
+     */
+    public enum SubscriptionStatus {
+        SUB_DEACTIVATE,
+            SUB_ACTIVATE,
+            SUB_ACTIVATED,
+            SUB_DEACTIVATED,
+            SUB_INVALID
+    }
+
+    public static final int SUBSCRIPTION_INDEX_INVALID = -1;
+
+    public Subscription() {
+        clear();
+    }
+
+    public String toString() {
+        return "Subscription = { "
+            + "slotId = " + slotId
+            + ", 3gppIndex = " + m3gppIndex
+            + ", 3gpp2Index = " + m3gpp2Index
+            + ", subId = " + subId
+            + ", subStatus = " + subStatus
+            + ", appId = " + appId
+            + ", appLabel = " + appLabel
+            + ", appType = " + appType
+            + ", iccId = " + iccId + " }";
+    }
+
+    public boolean equals(Subscription sub) {
+        if (sub != null) {
+            if ((slotId == sub.slotId) && (m3gppIndex == sub.m3gppIndex)
+                    && (m3gpp2Index == sub.m3gpp2Index) && (subId == sub.subId)
+                    && (subStatus == sub.subStatus)
+                    && ((TextUtils.isEmpty(appId) && TextUtils.isEmpty(sub.appId))
+                            || TextUtils.equals(appId, sub.appId))
+                    && ((TextUtils.isEmpty(appLabel) && TextUtils.isEmpty(sub.appLabel))
+                            || TextUtils.equals(appLabel, sub.appLabel))
+                    && ((TextUtils.isEmpty(appType) && TextUtils.isEmpty(sub.appType))
+                            || TextUtils.equals(appType, sub.appType))
+                    && ((TextUtils.isEmpty(iccId) && TextUtils.isEmpty(sub.iccId))
+                            || TextUtils.equals(iccId, sub.iccId))) {
+                return true;
+            }
+        } else {
+            Rlog.d(LOG_TAG, "Subscription.equals: sub == null");
+        }
+        return false;
+    }
+
+    /**
+     * Return true if the appIndex, appId, appLabel and iccId are matching.
+     * @param sub
+     * @return
+     */
+    public boolean isSame(Subscription sub) {
+        // Not checking the subId, subStatus and slotId, which are related to the
+        // activated status
+        if (sub != null) {
+            if (DEBUG) {
+                Rlog.d(LOG_TAG, "isSame(): this = " + m3gppIndex
+                        + ":" + m3gpp2Index
+                        + ":" + appId
+                        + ":" + appType
+                        + ":" + iccId);
+                Rlog.d(LOG_TAG, "compare with = " + sub.m3gppIndex
+                        + ":" + sub.m3gpp2Index
+                        + ":" + sub.appId
+                        + ":" + sub.appType
+                        + ":" + sub.iccId);
+            }
+            if ((m3gppIndex == sub.m3gppIndex)
+                    && (m3gpp2Index == sub.m3gpp2Index)
+                    && ((TextUtils.isEmpty(appId) && TextUtils.isEmpty(sub.appId))
+                            || TextUtils.equals(appId, sub.appId))
+                    && ((TextUtils.isEmpty(appType) && TextUtils.isEmpty(sub.appType))
+                            || TextUtils.equals(appType, sub.appType))
+                    && ((TextUtils.isEmpty(iccId) && TextUtils.isEmpty(sub.iccId))
+                            || TextUtils.equals(iccId, sub.iccId))){
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Reset the subscription
+     */
+    public void clear() {
+        slotId = SUBSCRIPTION_INDEX_INVALID;
+        m3gppIndex = SUBSCRIPTION_INDEX_INVALID;
+        m3gpp2Index = SUBSCRIPTION_INDEX_INVALID;
+        subId = SUBSCRIPTION_INDEX_INVALID;
+        subStatus = SubscriptionStatus.SUB_INVALID;
+        appId = null;
+        appLabel = null;
+        appType = null;
+        iccId = null;
+    }
+
+    /**
+     * Copies the subscription parameters
+     * @param from
+     * @return
+     */
+    public Subscription copyFrom(Subscription from) {
+        if (from != null) {
+            slotId = from.slotId;
+            m3gppIndex = from.m3gppIndex;
+            m3gpp2Index = from.m3gpp2Index;
+            subId = from.subId;
+            subStatus = from.subStatus;
+            if (from.appId != null) {
+                appId = new String(from.appId);
+            }
+            if (from.appLabel != null) {
+                appLabel = new String(from.appLabel);
+            }
+            if (from.appType != null) {
+                appType = new String(from.appType);
+            }
+            if (from.iccId != null) {
+                iccId = new String(from.iccId);
+            }
+        }
+
+        return this;
+    }
+
+    /**
+     * Return the valid app index (either 3gpp or 3gpp2 index)
+     * @return
+     */
+    public int getAppIndex() {
+        if (this.m3gppIndex != SUBSCRIPTION_INDEX_INVALID) {
+            return this.m3gppIndex;
+        } else {
+            return this.m3gpp2Index;
+        }
+    }
+}
diff --git a/src/java/com/android/internal/telephony/SubscriptionController.java b/src/java/com/android/internal/telephony/SubscriptionController.java
new file mode 100644
index 0000000..6ea3425
--- /dev/null
+++ b/src/java/com/android/internal/telephony/SubscriptionController.java
@@ -0,0 +1,1111 @@
+/*
+* Copyright (C) 2011-2014 MediaTek Inc.
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*      http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package com.android.internal.telephony;
+
+import android.Manifest;
+import android.app.AppOpsManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.os.AsyncResult;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.Message;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.telephony.Rlog;
+import android.util.Log;
+import android.net.Uri;
+import android.database.Cursor;
+import android.content.Intent;
+import android.provider.BaseColumns;
+import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+
+import com.android.internal.telephony.ISub;
+import com.android.internal.telephony.dataconnection.DctController;
+import com.android.internal.telephony.uicc.IccConstants;
+import com.android.internal.telephony.uicc.IccFileHandler;
+import android.telephony.SubscriptionManager;
+import android.telephony.SubInfoRecord;
+import android.telephony.TelephonyManager;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map.Entry;
+/**
+ * SubscriptionController to provide an inter-process communication to
+ * access Sms in Icc.
+ */
+public class SubscriptionController extends ISub.Stub {
+    static final String LOG_TAG = "SUB";
+    static final boolean DBG = true;
+    static final boolean VDBG = false;
+
+    protected final Object mLock = new Object();
+    protected boolean mSuccess;
+
+    /** The singleton instance. */
+    private static SubscriptionController sInstance = null;
+    protected static Phone mPhone;
+    protected static Context mContext;
+
+    private DataConnectionHandler mDataConnectionHandler;
+
+    public static final Uri CONTENT_URI =
+            Uri.parse("content://telephony/siminfo");
+
+    public static final int DEFAULT_INT_VALUE = -100;
+
+    public static final String DEFAULT_STRING_VALUE = "N/A";
+
+    private static final int EVENT_SET_DEFAULT_DATA_DONE = 1;
+
+    public static final int EXTRA_VALUE_NEW_SIM = 1;
+    public static final int EXTRA_VALUE_REMOVE_SIM = 2;
+    public static final int EXTRA_VALUE_REPOSITION_SIM = 3;
+    public static final int EXTRA_VALUE_NOCHANGE = 4;
+
+    public static final String INTENT_KEY_DETECT_STATUS = "simDetectStatus";
+    public static final String INTENT_KEY_SIM_COUNT = "simCount";
+    public static final String INTENT_KEY_NEW_SIM_SLOT = "newSIMSlot";
+    public static final String INTENT_KEY_NEW_SIM_STATUS = "newSIMStatus";
+
+    /**
+     * The ICC ID of a SIM.
+     * <P>Type: TEXT (String)</P>
+     */
+    public static final String ICC_ID = "icc_id";
+
+    /**
+     * <P>Type: INTEGER (int)</P>
+     */
+    public static final String SIM_ID = "sim_id";
+
+    public static final int SIM_NOT_INSERTED = -1;
+
+    /**
+     * The display name of a SIM.
+     * <P>Type: TEXT (String)</P>
+     */
+    public static final String DISPLAY_NAME = "display_name";
+
+    public static final int DEFAULT_NAME_RES = com.android.internal.R.string.unknownName;
+
+    /**
+     * The display name source of a SIM.
+     * <P>Type: INT (int)</P>
+     */
+    public static final String NAME_SOURCE = "name_source";
+
+    public static final int DEFAULT_SOURCE = 0;
+
+    public static final int SIM_SOURCE = 1;
+
+    public static final int USER_INPUT = 2;
+
+    /**
+     * The color of a SIM.
+     * <P>Type: INTEGER (int)</P>
+     */
+    public static final String COLOR = "color";
+
+    public static final int COLOR_1 = 0;
+
+    public static final int COLOR_2 = 1;
+
+    public static final int COLOR_3 = 2;
+
+    public static final int COLOR_4 = 3;
+
+    public static final int COLOR_DEFAULT = COLOR_1;
+
+    /**
+     * The phone number of a SIM.
+     * <P>Type: TEXT (String)</P>
+     */
+    public static final String NUMBER = "number";
+
+    /**
+     * The number display format of a SIM.
+     * <P>Type: INTEGER (int)</P>
+     */
+    public static final String DISPLAY_NUMBER_FORMAT = "display_number_format";
+
+    public static final int DISPALY_NUMBER_NONE = 0;
+
+    public static final int DISPLAY_NUMBER_FIRST = 1;
+
+    public static final int DISPLAY_NUMBER_LAST = 2;
+
+    public static final int DISLPAY_NUMBER_DEFAULT = DISPLAY_NUMBER_FIRST;
+
+    /**
+     * Permission for data roaming of a SIM.
+     * <P>Type: INTEGER (int)</P>
+     */
+    public static final String DATA_ROAMING = "data_roaming";
+
+    public static final int DATA_ROAMING_ENABLE = 1;
+
+    public static final int DATA_ROAMING_DISABLE = 0;
+
+    public static final int DATA_ROAMING_DEFAULT = DATA_ROAMING_DISABLE;
+
+    private static final int RES_TYPE_BACKGROUND_DARK = 0;
+
+    private static final int RES_TYPE_BACKGROUND_LIGHT = 1;
+
+    private static final int[] sSimBackgroundDarkRes = setSimResource(RES_TYPE_BACKGROUND_DARK);
+
+    private static final int[] sSimBackgroundLightRes = setSimResource(RES_TYPE_BACKGROUND_LIGHT);
+
+    private static HashMap<Integer, Long> mSimInfo = new HashMap<Integer, Long>();
+    // FIXME define an invalid SUB_ID, and use that below instead of "1".
+    private static long mDefaultVoiceSubId = 1;
+    private static int mDefaultPhoneId = 0;
+
+    public static SubscriptionController init(Phone phone) {
+        synchronized (SubscriptionController.class) {
+            if (sInstance == null) {
+                sInstance = new SubscriptionController(phone);
+            } else {
+                Log.wtf(LOG_TAG, "init() called multiple times!  sInstance = " + sInstance);
+            }
+            return sInstance;
+        }
+    }
+
+    public static SubscriptionController init(Context c, CommandsInterface[] ci) {
+        synchronized (SubscriptionController.class) {
+            if (sInstance == null) {
+                sInstance = new SubscriptionController(c);
+            } else {
+                Log.wtf(LOG_TAG, "init() called multiple times!  sInstance = " + sInstance);
+            }
+            return sInstance;
+        }
+    }
+
+    public static SubscriptionController getInstance() {
+        if (sInstance == null)
+        {
+           Log.wtf(LOG_TAG, "getInstance null");
+        }
+
+        return sInstance;
+    }
+
+    private SubscriptionController(Context c) {
+        mContext = c;
+
+        if(ServiceManager.getService("isub") == null) {
+                ServiceManager.addService("isub", this);
+        }
+
+        mDataConnectionHandler = new DataConnectionHandler();
+        logd("SubscriptionController init by Context");
+    }
+
+    private boolean isSubInfoReady() {
+        return (mSimInfo.size() > 0) ? true : false;
+    }
+
+    private SubscriptionController(Phone phone) {
+        mContext = phone.getContext();
+
+        if(ServiceManager.getService("isub") == null) {
+                ServiceManager.addService("isub", this);
+        }
+
+        logd("SubscriptionController init by Phone");
+    }
+
+    /**
+     * Broadcast when subinfo settings has chanded
+     * @SubId The unique SubInfoRecord index in database
+     * @param columnName The column that is updated
+     * @param intContent The updated integer value
+     * @param stringContent The updated string value
+     */
+     private void broadcastSimInfoContentChanged(long subId,
+            String columnName, int intContent, String stringContent) {
+        Intent intent = new Intent(TelephonyIntents.ACTION_SUBINFO_CONTENT_CHANGE);
+        intent.putExtra(BaseColumns._ID, subId);
+        intent.putExtra(TelephonyIntents.EXTRA_COLUMN_NAME, columnName);
+        intent.putExtra(TelephonyIntents.EXTRA_INT_CONTENT, intContent);
+        intent.putExtra(TelephonyIntents.EXTRA_STRING_CONTENT, stringContent);
+        if (intContent != DEFAULT_INT_VALUE) {
+            logd("SubInfoRecord" + subId + " changed, " + columnName + " -> " +  intContent);
+        } else {
+            logd("SubInfoRecord" + subId + " changed, " + columnName + " -> " +  stringContent);
+        }
+        mContext.sendBroadcast(intent);
+    }
+
+
+    /**
+     * New SubInfoRecord instance and fill in detail info
+     * @param cursor
+     * @return the query result of desired SubInfoRecord
+     */
+    private SubInfoRecord getSubInfoRecord(Cursor cursor) {
+            SubInfoRecord info = new SubInfoRecord();
+            info.mSubId = cursor.getLong(cursor.getColumnIndexOrThrow(BaseColumns._ID));
+            info.mIccId = cursor.getString(cursor.getColumnIndexOrThrow(ICC_ID));
+            info.mSlotId = cursor.getInt(cursor.getColumnIndexOrThrow(SIM_ID));
+            info.mDisplayName = cursor.getString(cursor.getColumnIndexOrThrow(DISPLAY_NAME));
+            info.mNameSource = cursor.getInt(cursor.getColumnIndexOrThrow(NAME_SOURCE));
+            info.mColor = cursor.getInt(cursor.getColumnIndexOrThrow(COLOR));
+            info.mNumber = cursor.getString(cursor.getColumnIndexOrThrow(NUMBER));
+            info.mDispalyNumberFormat = cursor.getInt(cursor.getColumnIndexOrThrow(DISPLAY_NUMBER_FORMAT));
+            info.mDataRoaming = cursor.getInt(cursor.getColumnIndexOrThrow(DATA_ROAMING));
+
+            int size = sSimBackgroundDarkRes.length;
+            if (info.mColor >= 0 && info.mColor < size) {
+                info.mSimIconRes[RES_TYPE_BACKGROUND_DARK] = sSimBackgroundDarkRes[info.mColor];
+                info.mSimIconRes[RES_TYPE_BACKGROUND_LIGHT] = sSimBackgroundLightRes[info.mColor];
+            }
+            logd("[getSubInfoRecord] SubId:" + info.mSubId + " iccid:" + info.mIccId + " slotId:" + info.mSlotId
+                    + " displayName:" + info.mDisplayName + " color:" + info.mColor);
+
+            return info;
+    }
+
+    /**
+     * Query SubInfoRecord(s) from subinfo database
+     * @param selection A filter declaring which rows to return
+     * @param queryKey query key content
+     * @return Array list of queried result from database
+     */
+     private List<SubInfoRecord> getSubInfo(String selection, Object queryKey) {
+        logd("selection:" + selection + " " + queryKey);
+        String[] selectionArgs = null;
+        if (queryKey != null) {
+            selectionArgs = new String[] {queryKey.toString()};
+        }
+        ArrayList<SubInfoRecord> subList = null;
+        Cursor cursor = mContext.getContentResolver().query(CONTENT_URI,
+                null, selection, selectionArgs, null);
+        try {
+            if (cursor != null) {
+                while (cursor.moveToNext()) {
+                    SubInfoRecord subInfo = getSubInfoRecord(cursor);
+                    if (subInfo != null)
+                    {
+                        if (subList == null)
+                        {
+                            subList = new ArrayList<SubInfoRecord>();
+                        }
+                        subList.add(subInfo);
+                }
+                }
+            } else {
+                logd("Query fail");
+            }
+        } finally {
+            if (cursor != null) {
+                cursor.close();
+            }
+        }
+
+        return subList;
+    }
+
+
+
+    /**
+     * Get the SubInfoRecord according to an index
+     * @param subId The unique SubInfoRecord index in database
+     * @return SubInfoRecord, maybe null
+     */
+    @Override
+    public SubInfoRecord getSubInfoUsingSubId(long subId) {
+        logd("[getSubInfoUsingSubIdx]+ subId:" + subId);
+        if (subId <= 0 || !isSubInfoReady()) {
+            logd("[getSubInfoUsingSubIdx]- subId <= 0 or not ready");
+            return null;
+        }
+        Cursor cursor = mContext.getContentResolver().query(CONTENT_URI,
+                null, BaseColumns._ID + "=?", new String[] {Long.toString(subId)}, null);
+        try {
+            if (cursor != null) {
+                if (cursor.moveToFirst()) {
+                    logd("[getSubInfoUsingSubIdx]- Info detail:");
+                    return getSubInfoRecord(cursor);
+                }
+            }
+        } finally {
+            if (cursor != null) {
+                cursor.close();
+            }
+        }
+        logd("[getSubInfoUsingSubIdx]- null info return");
+
+        return null;
+    }
+
+    /**
+     * Get the SubInfoRecord according to an IccId
+     * @param iccId the IccId of SIM card
+     * @return SubInfoRecord, maybe null
+     */
+    @Override
+    public List<SubInfoRecord> getSubInfoUsingIccId(String iccId) {
+        logd("[getSubInfoUsingIccId]+ iccId:" + iccId);
+        if (iccId == null || !isSubInfoReady()) {
+            logd("[getSubInfoUsingIccId]- null iccid or not ready");
+            return null;
+        }
+        Cursor cursor = mContext.getContentResolver().query(CONTENT_URI,
+                null, ICC_ID + "=?", new String[] {iccId}, null);
+        ArrayList<SubInfoRecord> subList = null;
+        try {
+            if (cursor != null) {
+                while (cursor.moveToNext()) {
+                    SubInfoRecord subInfo = getSubInfoRecord(cursor);
+                    if (subInfo != null)
+                    {
+                        if (subList == null)
+                        {
+                            subList = new ArrayList<SubInfoRecord>();
+                        }
+                        subList.add(subInfo);
+                }
+                }
+            } else {
+                logd("Query fail");
+            }
+        } finally {
+            if (cursor != null) {
+                cursor.close();
+            }
+        }
+
+        return subList;
+    }
+
+    /**
+     * Get the SubInfoRecord according to slotId
+     * @param slotId the slot which the SIM is inserted
+     * @return SubInfoRecord, maybe null
+     */
+    @Override
+    public List<SubInfoRecord> getSubInfoUsingSlotId(int slotId) {
+        return getSubInfoUsingSlotIdWithCheck(slotId, true);
+    }
+
+    /**
+     * Get all the SubInfoRecord(s) in subinfo database
+     * @return Array list of all SubInfoRecords in database, include thsoe that were inserted before
+     */
+    @Override
+    public List<SubInfoRecord> getAllSubInfoList() {
+        logd("[getAllSubInfoList]+");
+        List<SubInfoRecord> subList = null;
+        subList = getSubInfo(null, null);
+        if (subList != null) {
+            logd("[getAllSubInfoList]- " + subList.size() + " infos return");
+        } else {
+            logd("[getAllSubInfoList]- no info return");
+        }
+
+        return subList;
+    }
+
+    /**
+     * Get the SubInfoRecord(s) of the currently inserted SIM(s)
+     * @return Array list of currently inserted SubInfoRecord(s)
+     */
+    @Override
+    public List<SubInfoRecord> getActivatedSubInfoList() {
+        logd("[getActivatedSubInfoList]+");
+        List<SubInfoRecord> subList = null;
+
+        if (!isSubInfoReady()) {
+            logd("[getActivatedSubInfoList] Sub Controller not ready");
+            return subList;
+        }
+
+        subList = getSubInfo(SIM_ID + "!=" + SIM_NOT_INSERTED, null);
+        if (subList != null) {
+            logd("[getActivatedSubInfoList]- " + subList.size() + " infos return");
+        } else {
+            logd("[getActivatedSubInfoList]- no info return");
+        }
+
+        return subList;
+    }
+
+    /**
+     * Get the SUB count of all SUB(s) in subinfo database
+     * @return all SIM count in database, include what was inserted before
+     */
+    @Override
+    public int getAllSubInfoCount() {
+        logd("[getAllSubInfoCount]+");
+        Cursor cursor = mContext.getContentResolver().query(CONTENT_URI,
+                null, null, null, null);
+        try {
+            if (cursor != null) {
+                int count = cursor.getCount();
+                logd("[getAllSubInfoCount]- " + count + " SUB(s) in DB");
+                return count;
+            }
+        } finally {
+            if (cursor != null) {
+                cursor.close();
+            }
+        }
+        logd("[getAllSubInfoCount]- no SUB in DB");
+
+        return 0;
+    }
+
+    /**
+     * Add a new SubInfoRecord to subinfo database if needed
+     * @param iccId the IccId of the SIM card
+     * @param slotId the slot which the SIM is inserted
+     * @return the URL of the newly created row or the updated row
+     */
+    @Override
+    public int addSubInfoRecord(String iccId, int slotId) {
+        logd("[addSubInfoRecord]+ iccId:" + iccId + " slotId:" + slotId);
+        if (iccId == null) {
+            logd("[addSubInfoRecord]- null iccId");
+        }
+        Uri uri = null;
+        String nameToSet;
+        nameToSet = "SUB 0"+Integer.toString(slotId+1);
+        ContentResolver resolver = mContext.getContentResolver();
+        Cursor cursor = resolver.query(CONTENT_URI, new String[] {BaseColumns._ID, SIM_ID, NAME_SOURCE},
+                ICC_ID + "=?", new String[] {iccId}, null);
+        try {
+            if (cursor == null || !cursor.moveToFirst()) {
+                ContentValues value = new ContentValues();
+                value.put(ICC_ID, iccId);
+                // default SIM color differs between slots
+                value.put(COLOR, slotId);
+                value.put(SIM_ID, slotId);
+                value.put(DISPLAY_NAME, nameToSet);
+                uri = resolver.insert(CONTENT_URI, value);
+                logd("[addSubInfoRecord] New record creating values=" + value);
+                logd("[addSubInfoRecord] New record result uri=" + uri);
+           } else {
+                long subId = cursor.getLong(0);
+                int oldSimInfoId = cursor.getInt(1);
+                int nameSource = cursor.getInt(2);
+                ContentValues value = new ContentValues();
+
+                if (slotId != oldSimInfoId) {
+                    value.put(SIM_ID, slotId);
+                }
+
+                if (nameSource != USER_INPUT) {
+                    value.put(DISPLAY_NAME, nameToSet);
+                }
+
+                if (value.size() > 0) {
+                    String where = BaseColumns._ID + "=" + Long.toString(subId);
+                    logd("[addSubInfoRecord] resolver.update value=" + value + " where=" + where);
+                    resolver.update(CONTENT_URI, value, where, null);
+                }
+
+                logd("[addSubInfoRecord] Record already exist");
+            }
+        } finally {
+            if (cursor != null) {
+                cursor.close();
+            }
+        }
+
+        cursor = resolver.query(CONTENT_URI,
+                null, SIM_ID + "=?", new String[] {String.valueOf(slotId)}, null);
+        if (cursor == null) logd("[addSubInfoRecord] 1 cursor is null");
+
+        try {
+            if (cursor != null && cursor.moveToFirst()) {
+                do {
+                    long subId = cursor.getLong(cursor.getColumnIndexOrThrow(BaseColumns._ID));
+                    logd("[addSubInfoRecord] subId=" + subId + " mSimInfo.size" + mSimInfo.size());
+                    // If mSinInfo is already having a valid subId for a slotId/phoneId,
+                    // do not add another subId for same slotId/phoneId.
+                    long[] sub = getSubId(slotId);
+
+                    if ((mSimInfo.size() == 0) || (sub[0] <= 0)) {
+                        // set the first entry as default sub
+                        // TODO While two subs active, if user deactivats first
+                        // one, need to update the default subId with second
+                        // one.
+                        if (mSimInfo.size() == 0) {
+                            logd("[addSubInfoRecord] call setDefaultSubId subId=" + subId);
+                            setDefaultSubId(subId);
+                        }
+                        mSimInfo.put(slotId, subId);
+                        logd("[addSubInfoRecord] mSimInfo.size=" + mSimInfo.size()
+                                + " slotId = " + slotId + " subId = " + subId);
+                    } else {
+                        logd("[addSubInfoRecord] size != 0 && sub[0] > 0, IGNORE");
+                    }
+                } while (cursor.moveToNext());
+            } else {
+                logd("[addSubInfoRecord] no records for " + BaseColumns._ID);
+            }
+        } finally {
+            if (cursor != null) {
+                cursor.close();
+            }
+        }
+
+        int size = mSimInfo.size();
+        logd("[addSubInfoRecord]- info size=" + size);
+
+        return 1;
+    }
+
+    /**
+     * Set SIM color by simInfo index
+     * @param color the color of the SIM
+     * @param subId the unique SubInfoRecord index in database
+     * @return the number of records updated
+     */
+    @Override
+    public int setColor(int color, long subId) {
+        logd("[setColor]+ color:" + color + " subId:" + subId);
+        int size = sSimBackgroundDarkRes.length;
+        if (subId <= 0 || color < 0 || color >= size) {
+            logd("[setColor]- fail");
+            return -1;
+        }
+        ContentValues value = new ContentValues(1);
+        value.put(COLOR, color);
+        logd("[setColor]- color:" + color + " set");
+
+        int result = mContext.getContentResolver().update(CONTENT_URI, value,
+                                                    BaseColumns._ID + "=" + Long.toString(subId), null);
+        broadcastSimInfoContentChanged(subId, COLOR, color, DEFAULT_STRING_VALUE);
+
+        return result;
+    }
+
+    /**
+     * Set display name by simInfo index
+     * @param displayName the display name of SIM card
+     * @param subId the unique SubInfoRecord index in database
+     * @return the number of records updated
+     */
+    @Override
+    public int setDisplayName(String displayName, long subId) {
+        return setDisplayNameUsingSrc(displayName, subId, -1);
+    }
+
+    /**
+     * Set display name by simInfo index with name source
+     * @param displayName the display name of SIM card
+     * @param subId the unique SubInfoRecord index in database
+     * @param nameSource 0: DEFAULT_SOURCE, 1: SIM_SOURCE, 2: USER_INPUT
+     * @return the number of records updated
+     */
+    @Override
+    public int setDisplayNameUsingSrc(String displayName, long subId, long nameSource) {
+        logd("[setDisplayName]+  displayName:" + displayName + " subId:" + subId + " nameSource:" + nameSource);
+        if (subId <= 0) {
+            logd("[setDisplayName]- fail");
+            return -1;
+        }
+        String nameToSet;
+        if (displayName == null) {
+            nameToSet = mContext.getString(DEFAULT_NAME_RES);
+        } else {
+            nameToSet = displayName;
+        }
+        ContentValues value = new ContentValues(1);
+        value.put(DISPLAY_NAME, nameToSet);
+        if (nameSource >= DEFAULT_SOURCE) {
+            logd("Set nameSource=" + nameSource);
+            value.put(NAME_SOURCE, nameSource);
+        }
+        logd("[setDisplayName]- mDisplayName:" + nameToSet + " set");
+
+        int result = mContext.getContentResolver().update(CONTENT_URI, value,
+                                                  BaseColumns._ID + "=" + Long.toString(subId), null);
+        broadcastSimInfoContentChanged(subId, DISPLAY_NAME, DEFAULT_INT_VALUE, nameToSet);
+
+        return result;
+    }
+
+    /**
+     * Set phone number by subId
+     * @param number the phone number of the SIM
+     * @param subId the unique SubInfoRecord index in database
+     * @return the number of records updated
+     */
+    @Override
+    public int setDispalyNumber(String number, long subId) {
+        logd("[setDispalyNumber]+ number:" + number + " subId:" + subId);
+        if (number == null || subId <= 0) {
+            logd("[setDispalyNumber]- fail");
+            return -1;
+        }
+        ContentValues value = new ContentValues(1);
+        value.put(NUMBER, number);
+        logd("[setDispalyNumber]- number:" + number + " set");
+
+        int result = mContext.getContentResolver().update(CONTENT_URI, value,
+                                                  BaseColumns._ID + "=" + Long.toString(subId), null);
+        broadcastSimInfoContentChanged(subId, NUMBER, DEFAULT_INT_VALUE, number);
+
+        return result;
+    }
+
+    /**
+     * Set number display format. 0: none, 1: the first four digits, 2: the last four digits
+     * @param format the display format of phone number
+     * @param subId the unique SubInfoRecord index in database
+     * @return the number of records updated
+     */
+    @Override
+    public int setDisplayNumberFormat(int format, long subId) {
+        logd("[setDisplayNumberFormat]+ format:" + format + " subId:" + subId);
+        if (format < 0 || subId <= 0) {
+            logd("[setDisplayNumberFormat]- fail, return -1");
+            return -1;
+        }
+        ContentValues value = new ContentValues(1);
+        value.put(DISPLAY_NUMBER_FORMAT, format);
+        logd("[setDisplayNumberFormat]- format:" + format + " set");
+
+        int result = mContext.getContentResolver().update(CONTENT_URI, value,
+                                                  BaseColumns._ID + "=" + Long.toString(subId), null);
+        broadcastSimInfoContentChanged(subId, DISPLAY_NUMBER_FORMAT, format, DEFAULT_STRING_VALUE);
+
+        return result;
+    }
+
+    /**
+     * Set data roaming by simInfo index
+     * @param roaming 0:Don't allow data when roaming, 1:Allow data when roaming
+     * @param subId the unique SubInfoRecord index in database
+     * @return the number of records updated
+     */
+    @Override
+    public int setDataRoaming(int roaming, long subId) {
+        logd("[setDataRoaming]+ roaming:" + roaming + " subId:" + subId);
+        if (roaming < 0 || subId <= 0) {
+            logd("[setDataRoaming]- fail");
+            return -1;
+        }
+        ContentValues value = new ContentValues(1);
+        value.put(DATA_ROAMING, roaming);
+        logd("[setDataRoaming]- roaming:" + roaming + " set");
+
+        int result = mContext.getContentResolver().update(CONTENT_URI, value,
+                                                  BaseColumns._ID + "=" + Long.toString(subId), null);
+        broadcastSimInfoContentChanged(subId, DATA_ROAMING, roaming, DEFAULT_STRING_VALUE);
+
+        return result;
+    }
+
+    @Override
+    public int getSlotId(long subId) {
+        logd("[getSlotId]+ subId:" + subId);
+        if (subId <= 0) {
+            logd("[getSlotId]- subId <= 0");
+            return SIM_NOT_INSERTED;
+        }
+
+        int size = mSimInfo.size();
+        logd("[getSlotId]- info size="+size);
+
+        if (size == 0)
+        {
+            return SIM_NOT_INSERTED;
+        }
+
+        for (Entry<Integer, Long> entry: mSimInfo.entrySet()) {
+            int sim = entry.getKey();
+            long sub = entry.getValue();
+            logd("[getSlotId]- entry, sim ="+sim+", sub="+ sub);
+
+            if (subId == sub)
+            {
+                logd("[getSlotId]- return ="+sim);
+                return sim;
+            }
+        }
+
+        logd("[getSlotId]- return fail");
+        return (int)(subId-1);
+
+    }
+
+    /**
+     * Return the subId for specified sim Id.
+     * @deprecated
+     */
+    @Override
+    @Deprecated
+    public long[] getSubId(int slotId) {
+        if (VDBG) logd("[getSubId]+ slotId:" + slotId);
+
+        // FIXME this should return the subIds associated with the PhoneIds
+        // using {1, 2} for now, to workaround problem with data classes
+        // not being able to find a subscription that has value set to
+        // default data during bootup, since SubscriptionController has not
+        // discovered all the subs when this query is made.
+
+        long[] subId = new long[] {-1-slotId, -1-slotId};
+
+        if (slotId < 0) {
+            logd("[getSubId]- slotId < 0, return dummy instead");
+            return subId;
+        }
+
+        int size = mSimInfo.size();
+        if (VDBG) logd("[getSubId]- info size="+size);
+
+        if (size == 0)
+        {
+            logd("[getSubId]- size == 0, return dummy instead");
+            return subId;
+        }
+
+        int subIdx = 0;
+
+        for (Entry<Integer, Long> entry: mSimInfo.entrySet()) {
+            int sim = entry.getKey();
+            long sub = entry.getValue();
+
+            if (VDBG) logd("[getSubId]- entry, sim ="+sim+", sub="+ sub);
+            if (slotId == sim)
+            {
+                subId[subIdx] = sub;
+                if (VDBG) logd("[getSubId]- subId["+subIdx+"] = "+subId[subIdx]);
+                subIdx++;
+            }
+        }
+
+        if (VDBG) logd("[getSubId]-, subId = "+subId[0]);
+        return subId;
+    }
+
+    @Override
+    public int getPhoneId(long subId) {
+        if (VDBG) logd("[getPhoneId]+ subId:" + subId);
+        if (subId <= 0) {
+            // FIXME Do not auto map subId to phoneId
+            // May be we shoud add dummy/default subId's during
+            // initialization of subscription controller ??
+            if (subId == -1) {
+                logd("[getPhoneId]- subId == -1 return =" + 0);
+                return 0;
+            } else if (subId == -2) {
+                logd("[getPhoneId]- subId == -2 return =" + 1);
+                return 1;
+            }
+        }
+
+        int size = mSimInfo.size();
+
+        if (size == 0) {
+            if (VDBG) logd("[getPhoneId]- returning defaultPhoneId=" + mDefaultPhoneId);
+            return mDefaultPhoneId;
+        }
+
+        for (Entry<Integer, Long> entry: mSimInfo.entrySet()) {
+            int sim = entry.getKey();
+            long sub = entry.getValue();
+            if (VDBG) logd("[getPhoneId]- entry, sim="+sim+", sub="+ sub);
+
+            if (subId == sub)
+            {
+                if (VDBG) logd("[getPhoneId]- return="+sim);
+                return sim;
+            }
+        }
+
+        if (VDBG) logd("[getPhoneId]- return=" + (int)(subId-1));
+        return (int)(subId-1);
+
+    }
+
+    @Override
+    public int clearSubInfo()
+    {
+        if (VDBG) logd("[clearSubInfo]+");
+
+        int size = mSimInfo.size();
+        if (VDBG) logd("[getSubId]- info size="+size);
+
+        if (size == 0)
+        {
+            return 0;
+        }
+
+        mSimInfo.clear();
+        if (VDBG) logd("[clearSubInfo]-");
+        return 0;
+
+    }
+
+    private static int[] setSimResource(int type) {
+        int[] simResource = null;
+
+        switch (type) {
+            case RES_TYPE_BACKGROUND_DARK:
+                simResource = new int[] {
+                    com.android.internal.R.drawable.sim_dark_blue,
+                    com.android.internal.R.drawable.sim_dark_orange,
+                    com.android.internal.R.drawable.sim_dark_green,
+                    com.android.internal.R.drawable.sim_dark_purple
+                };
+                break;
+            case RES_TYPE_BACKGROUND_LIGHT:
+                simResource = new int[] {
+                    com.android.internal.R.drawable.sim_light_blue,
+                    com.android.internal.R.drawable.sim_light_orange,
+                    com.android.internal.R.drawable.sim_light_green,
+                    com.android.internal.R.drawable.sim_light_purple
+                };
+                break;
+        }
+
+        return simResource;
+    }
+
+    private void logd(String msg) {
+        Rlog.d(LOG_TAG, "[SubController]" + msg);
+    }
+
+    private void loge(String msg) {
+        Rlog.e(LOG_TAG, "[SubController]" + msg);
+    }
+
+    @Override
+    public long getDefaultSubId() {
+        if (VDBG) logd("getDefaultSubId: value=" + mDefaultVoiceSubId);
+        return mDefaultVoiceSubId;
+    }
+
+    @Override
+    public void setDefaultVoiceSubId(long subId) {
+        Settings.Global.putLong(mContext.getContentResolver(),
+                Settings.Global.MULTI_SIM_VOICE_CALL_SUBSCRIPTION, subId);
+        broadcastDefaultVoiceSubIdChanged(subId);
+        if (VDBG) logd("setDefaultVoiceSubId, subId=" + subId);
+    }
+
+    private static void broadcastDefaultVoiceSubIdChanged(long subId) {
+        // Broadcast an Intent for default voice sub change
+        Intent intent = new Intent(TelephonyIntents.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED);
+        intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+        intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId);
+        mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+    }
+
+    @Override
+    public long getDefaultVoiceSubId() {
+       long subId = SubscriptionManager.INVALID_SUB_ID;
+
+        try {
+            subId = Settings.Global.getLong(mContext.getContentResolver(),
+                    Settings.Global.MULTI_SIM_VOICE_CALL_SUBSCRIPTION);
+        } catch (SettingNotFoundException snfe) {
+            loge("Settings Exception Reading Dual Sim Voice Call Values");
+        }
+
+        if (VDBG) logd("getDefaultVoiceSubId, value = " + subId);
+        return subId;
+    }
+
+    @Override
+    public long getDefaultDataSubId() {
+       long subId = SubscriptionManager.INVALID_SUB_ID;
+       try {
+           subId = Settings.Global.getLong(mContext.getContentResolver(),
+                   Settings.Global.MULTI_SIM_DATA_CALL_SUBSCRIPTION);
+       } catch (SettingNotFoundException snfe) {
+           loge("Settings Exception Reading Dual Sim Data Call Values");
+       }
+
+       return subId;
+    }
+
+    public void setDefaultDataSubId(long subId) {
+        loge("setDataSubId: subId=" + subId + " FIXME NOP right now");
+        DctController dctController = DctController.getInstance();
+        dctController.setDataSubId(subId);
+        dctController.registerForDataSwitchInfo(mDataConnectionHandler,
+                EVENT_SET_DEFAULT_DATA_DONE, null);
+    }
+
+    private void updateDataSubId(long subId) {
+        if (VDBG) logd(" updateDataSubId,  subId=" + subId);
+        Settings.Global.putLong(mContext.getContentResolver(),
+                Settings.Global.MULTI_SIM_DATA_CALL_SUBSCRIPTION, subId);
+        broadcastDefaultDataSubIdChanged(subId);
+    }
+
+    private static void broadcastDefaultDataSubIdChanged(long subId) {
+        // Broadcast an Intent for default data sub change
+        Intent intent = new Intent(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED);
+        intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+        intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId);
+        mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+    }
+
+    /* Sets the default subscription. If only one sub is active that
+     * sub is set as default subId. If two or more  sub's are active
+     * the first sub is set as default subscription
+     */
+    // FIXME Modify/rename this method name as part of
+    // refactoring other subscription changes
+    public void setDefaultSubId(long subId) {
+        if (subId > 0 && subId != SubscriptionManager.INVALID_SUB_ID
+                && subId != SubscriptionManager.DEFAULT_SUB_ID) {
+            int phoneId = getPhoneId(subId);
+            if (phoneId >= 0 && phoneId < TelephonyManager.getDefault().getPhoneCount()) {
+                mDefaultVoiceSubId = subId; // returned by getFirstActiveSubId()
+                if (VDBG) logd("setDefaultSubId: mDefaultVoiceSubId=subId=" + subId);
+                // Update MCC MNC device configuration information
+                String defaultMccMnc = TelephonyManager.getDefault().getSimOperator(phoneId);
+                if (VDBG) logd("setDefaultSubId: call update mccmnc=" + defaultMccMnc);
+                MccTable.updateMccMncConfiguration(mContext, defaultMccMnc, false);
+
+                // Broadcast an Intent for default sub change
+                Intent intent = new Intent(TelephonyIntents.ACTION_DEFAULT_SUBSCRIPTION_CHANGED);
+                intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+                SubscriptionManager.putPhoneIdAndSubIdExtra(intent, phoneId);
+                if (VDBG) logd("setDefaultSubId: " + subId + " Broadcasting Default SubId Changed");
+                mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+            } else {
+                if (VDBG) logd("setDefaultSubId: not set invalid phoneId=" + phoneId + " subId=" + subId);
+            }
+        } else {
+            if (VDBG) logd("setDefaultSubId: not set invalid subId=" + subId);
+        }
+    }
+
+    // FIXME SubscriptionController should register (perhaps using Registrants)
+    // with SimInfoUpdater for any updates to the subscriptions. When it is
+    // notified of any updates, this API should be called. Rather than
+    // SimInfoUpdater calling into updateDefaultSubId.
+    public void updateDefaultSubId() {
+        if (!isSubActive(getDefaultDataSubId())) {
+            updateDataSubId(getFirstActiveSubId());
+        }
+
+        if (!isSubActive(getDefaultVoiceSubId())) {
+            setDefaultVoiceSubId(getFirstActiveSubId());
+        }
+    }
+
+    // FIXME As part of getDefaultSubId method cleanup, modify
+    // the mDefaultVoiceSubId to mFirstActiveSubId.
+    private long getFirstActiveSubId() {
+        if (VDBG) logd("getFirstActiveSubId, value = " + mDefaultVoiceSubId);
+        return mDefaultVoiceSubId;
+    }
+
+    private boolean isSubActive(long subId) {
+        boolean subActive = false;
+        List<SubInfoRecord> activeSubList = getActivatedSubInfoList();
+
+        if (activeSubList != null) {
+            for (SubInfoRecord subInfoRecord : activeSubList) {
+                if (subInfoRecord.mSubId == subId) {
+                    if (VDBG) logd("isSubActive, found active sub " + subId);
+                    subActive = true;
+                    break;
+                }
+            }
+        }
+        return subActive;
+    }
+
+    private class DataConnectionHandler extends Handler {
+        @Override
+        public void handleMessage(Message msg) {
+            AsyncResult ar = (AsyncResult) msg.obj;
+            switch (msg.what) {
+                case EVENT_SET_DEFAULT_DATA_DONE:
+                    Long subId = (Long)ar.result;
+                    if (VDBG) logd("EVENT_SET_DEFAULT_DATA_DONE subId:" + subId);
+                    updateDataSubId(subId);
+                    break;
+            }
+        }
+    }
+
+    /* This should return long and not long [] since each phone has
+     * exactly 1 sub id for now, it could return the 0th element
+     * returned from getSubId()
+     */
+    // FIXME will design a mechanism to manage the relationship between PhoneId/SlotId/SubId
+    // since phoneId = SlotId is not always true
+    public long getSubIdUsingPhoneId(int phoneId) {
+        long[] subId = getSubId(phoneId);
+        return subId[0];
+    }
+
+    public long[] getSubIdUsingSlotId(int slotId) {
+        return getSubId(slotId);
+    }
+
+    public List<SubInfoRecord> getSubInfoUsingSlotIdWithCheck(int slotId, boolean needCheck) {
+        if (VDBG) logd("[getSubInfoUsingSlotIdWithCheck]+ slotId=" + slotId);
+        if (slotId < 0 ) {
+            if (VDBG) logd("[getSubInfoUsingSlotIdWithCheck]- return null, slotId < 0");
+            return null;
+        }
+
+        if (needCheck && !isSubInfoReady()) {
+            if (VDBG) logd("[getSubInfoUsingSlotIdWithCheck]- not ready return null");
+            return null;
+        }
+
+        Cursor cursor = mContext.getContentResolver().query(CONTENT_URI,
+                null, SIM_ID + "=?", new String[] {String.valueOf(slotId)}, null);
+        ArrayList<SubInfoRecord> subList = null;
+        try {
+            if (cursor != null) {
+                while (cursor.moveToNext()) {
+                    SubInfoRecord subInfo = getSubInfoRecord(cursor);
+                    if (subInfo != null)
+                    {
+                        if (subList == null)
+                        {
+                            subList = new ArrayList<SubInfoRecord>();
+                        }
+                        subList.add(subInfo);
+                    }
+                }
+            }
+        } finally {
+            if (cursor != null) {
+                cursor.close();
+            }
+        }
+        if (VDBG) logd("[getSubInfoUsingSlotId]- subList=" + subList);
+        return subList;
+
+    }
+}
diff --git a/src/java/com/android/internal/telephony/SubscriptionData.java b/src/java/com/android/internal/telephony/SubscriptionData.java
new file mode 100644
index 0000000..2ba8e90
--- /dev/null
+++ b/src/java/com/android/internal/telephony/SubscriptionData.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2010-2013, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ *       copyright notice, this list of conditions and the following
+ *       disclaimer in the documentation and/or other materials provided
+ *       with the distribution.
+ *     * Neither the name of The Linux Foundation nor the names of its
+ *       contributors may be used to endorse or promote products derived
+ *       from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.android.internal.telephony;
+
+import java.util.Arrays;
+
+/**
+ * Class holding a list of subscriptions
+ */
+public class SubscriptionData {
+    public Subscription [] subscription;
+
+    public SubscriptionData(int numSub) {
+        subscription = new Subscription[numSub];
+        for (int i = 0; i < numSub; i++) {
+            subscription[i] = new Subscription();
+        }
+    }
+
+    public int getLength() {
+        if (subscription != null) {
+            return subscription.length;
+        }
+        return 0;
+    }
+
+    public SubscriptionData copyFrom(SubscriptionData from) {
+        if (from != null) {
+            subscription = new Subscription[from.getLength()];
+            for (int i = 0; i < from.getLength(); i++) {
+                subscription[i] = new Subscription();
+                subscription[i].copyFrom(from.subscription[i]);
+            }
+        }
+        return this;
+    }
+
+    public String getIccId() {
+        if (subscription.length > 0 && subscription[0] != null) {
+            return subscription[0].iccId;
+        }
+        return null;
+    }
+
+    public boolean hasSubscription(Subscription sub){
+        for (int i = 0; i < subscription.length; i++) {
+            if (subscription[i].isSame(sub)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public Subscription getSubscription(Subscription sub){
+        for (int i = 0; i < subscription.length; i++) {
+            if (subscription[i].isSame(sub)) {
+                return subscription[i];
+            }
+        }
+        return null;
+    }
+
+    public String toString() {
+        return Arrays.toString(subscription);
+    }
+}
diff --git a/src/java/com/android/internal/telephony/TelephonyCapabilities.java b/src/java/com/android/internal/telephony/TelephonyCapabilities.java
index bbef33e..bc1288a 100644
--- a/src/java/com/android/internal/telephony/TelephonyCapabilities.java
+++ b/src/java/com/android/internal/telephony/TelephonyCapabilities.java
@@ -136,7 +136,8 @@
      */
     public static boolean supportsHoldAndUnhold(Phone phone) {
         return ((phone.getPhoneType() == PhoneConstants.PHONE_TYPE_GSM)
-                || (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_SIP));
+                || (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_SIP)
+                || (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_IMS));
     }
 
     /**
diff --git a/src/java/com/android/internal/telephony/TelephonyDevController.java b/src/java/com/android/internal/telephony/TelephonyDevController.java
new file mode 100644
index 0000000..448e5bd
--- /dev/null
+++ b/src/java/com/android/internal/telephony/TelephonyDevController.java
@@ -0,0 +1,343 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony;
+
+import android.content.res.Resources;
+import com.android.internal.telephony.*;
+import android.telephony.TelephonyManager;
+
+import android.os.AsyncResult;
+import android.telephony.Rlog;
+import java.util.BitSet;
+import java.util.List;
+import java.util.ArrayList;
+import android.text.TextUtils;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Registrant;
+import android.os.RegistrantList;
+import android.telephony.ServiceState;
+
+/**
+ * TelephonyDevController - provides a unified view of the
+ * telephony hardware resources on a device.
+ *
+ * manages the set of HardwareConfig for the framework.
+ */
+public class TelephonyDevController extends Handler {
+    private static final String LOG_TAG = "TDC";
+    private static final boolean DBG = true;
+    private static final Object mLock = new Object();
+
+    private static final int EVENT_HARDWARE_CONFIG_CHANGED = 1;
+
+    private static TelephonyDevController sTelephonyDevController;
+    private static ArrayList<HardwareConfig> mModems = new ArrayList<HardwareConfig>();
+    private static ArrayList<HardwareConfig> mSims = new ArrayList<HardwareConfig>();
+
+    private static Message sRilHardwareConfig;
+
+    private static void logd(String s) {
+        Rlog.d(LOG_TAG, s);
+    }
+
+    private static void loge(String s) {
+        Rlog.e(LOG_TAG, s);
+    }
+
+    public static TelephonyDevController create() {
+        synchronized (mLock) {
+            if (sTelephonyDevController != null) {
+                throw new RuntimeException("TelephonyDevController already created!?!");
+            }
+            sTelephonyDevController = new TelephonyDevController();
+            return sTelephonyDevController;
+        }
+    }
+
+    public static TelephonyDevController getInstance() {
+        synchronized (mLock) {
+            if (sTelephonyDevController == null) {
+                throw new RuntimeException("TelephonyDevController not yet created!?!");
+            }
+            return sTelephonyDevController;
+        }
+    }
+
+    private void initFromResource() {
+        Resources resource = Resources.getSystem();
+        String[] hwStrings = resource.getStringArray(
+            com.android.internal.R.array.config_telephonyHardware);
+        if (hwStrings != null) {
+            for (String hwString : hwStrings) {
+                HardwareConfig hw = new HardwareConfig(hwString);
+                if (hw != null) {
+                    if (hw.type == HardwareConfig.DEV_HARDWARE_TYPE_MODEM) {
+                        updateOrInsert(hw, mModems);
+                    } else if (hw.type == HardwareConfig.DEV_HARDWARE_TYPE_SIM) {
+                        updateOrInsert(hw, mSims);
+                    }
+                }
+            }
+        }
+    }
+
+    private TelephonyDevController() {
+        initFromResource();
+
+        mModems.trimToSize();
+        mSims.trimToSize();
+    }
+
+    /**
+     * each RIL call this interface to register/unregister the unsolicited hardware
+     * configuration callback data it can provide.
+     */
+    public static void registerRIL(CommandsInterface cmdsIf) {
+        /* get the current configuration from this ril... */
+        cmdsIf.getHardwareConfig(sRilHardwareConfig);
+        /* ... process it ... */
+        if (sRilHardwareConfig != null) {
+            AsyncResult ar = (AsyncResult) sRilHardwareConfig.obj;
+            if (ar.exception == null) {
+                handleGetHardwareConfigChanged(ar);
+            }
+        }
+        /* and register for async device configuration change. */
+        cmdsIf.registerForHardwareConfigChanged(sTelephonyDevController, EVENT_HARDWARE_CONFIG_CHANGED, null);
+    }
+
+    public static void unregisterRIL(CommandsInterface cmdsIf) {
+        cmdsIf.unregisterForHardwareConfigChanged(sTelephonyDevController);
+    }
+
+    /**
+     * handle callbacks from RIL.
+     */
+    public void handleMessage(Message msg) {
+        AsyncResult ar;
+        switch (msg.what) {
+            case EVENT_HARDWARE_CONFIG_CHANGED:
+                if (DBG) logd("handleMessage: received EVENT_HARDWARE_CONFIG_CHANGED");
+                ar = (AsyncResult) msg.obj;
+                handleGetHardwareConfigChanged(ar);
+            break;
+            default:
+                loge("handleMessage: Unknown Event " + msg.what);
+        }
+    }
+
+    /**
+     * hardware configuration update or insert.
+     */
+    private static void updateOrInsert(HardwareConfig hw, ArrayList<HardwareConfig> list) {
+        int size;
+        HardwareConfig item;
+        synchronized (mLock) {
+            size = list.size();
+            for (int i = 0 ; i < size ; i++) {
+                item = list.get(i);
+                if (item.uuid.compareTo(hw.uuid) == 0) {
+                    if (DBG) logd("updateOrInsert: removing: " + item);
+                    list.remove(i);
+                }
+            }
+            if (DBG) logd("updateOrInsert: inserting: " + hw);
+            list.add(hw);
+        }
+    }
+
+    /**
+     * hardware configuration changed.
+     */
+    private static void handleGetHardwareConfigChanged(AsyncResult ar) {
+        if ((ar.exception == null) && (ar.result != null)) {
+            List hwcfg = (List)ar.result;
+            for (int i = 0 ; i < hwcfg.size() ; i++) {
+                HardwareConfig hw = null;
+
+                hw = (HardwareConfig) hwcfg.get(i);
+                if (hw != null) {
+                    if (hw.type == HardwareConfig.DEV_HARDWARE_TYPE_MODEM) {
+                        updateOrInsert(hw, mModems);
+                    } else if (hw.type == HardwareConfig.DEV_HARDWARE_TYPE_SIM) {
+                        updateOrInsert(hw, mSims);
+                    }
+                }
+            }
+        } else {
+            /* error detected, ignore.  are we missing some real time configutation
+             * at this point?  what to do...
+             */
+            loge("handleGetHardwareConfigChanged - returned an error.");
+        }
+    }
+
+    /**
+     * get total number of registered modem.
+     */
+    public static int getModemCount() {
+        synchronized (mLock) {
+            int count = mModems.size();
+            if (DBG) logd("getModemCount: " + count);
+            return count;
+        }
+    }
+
+    /**
+     * get modem at index 'index'.
+     */
+    public HardwareConfig getModem(int index) {
+        synchronized (mLock) {
+            if (mModems.isEmpty()) {
+                loge("getModem: no registered modem device?!?");
+                return null;
+            }
+
+            if (index > getModemCount()) {
+                loge("getModem: out-of-bounds access for modem device " + index + " max: " + getModemCount());
+                return null;
+            }
+
+            if (DBG) logd("getModem: " + index);
+            return mModems.get(index);
+        }
+    }
+
+    /**
+     * get total number of registered sims.
+     */
+    public int getSimCount() {
+        synchronized (mLock) {
+            int count = mSims.size();
+            if (DBG) logd("getSimCount: " + count);
+            return count;
+        }
+    }
+
+    /**
+     * get sim at index 'index'.
+     */
+    public HardwareConfig getSim(int index) {
+        synchronized (mLock) {
+            if (mSims.isEmpty()) {
+                loge("getSim: no registered sim device?!?");
+                return null;
+            }
+
+            if (index > getSimCount()) {
+                loge("getSim: out-of-bounds access for sim device " + index + " max: " + getSimCount());
+                return null;
+            }
+
+            if (DBG) logd("getSim: " + index);
+            return mSims.get(index);
+        }
+    }
+
+    /**
+     * get modem associated with sim index 'simIndex'.
+     */
+    public HardwareConfig getModemForSim(int simIndex) {
+        synchronized (mLock) {
+            if (mModems.isEmpty() || mSims.isEmpty()) {
+                loge("getModemForSim: no registered modem/sim device?!?");
+                return null;
+            }
+
+            if (simIndex > getSimCount()) {
+                loge("getModemForSim: out-of-bounds access for sim device " + simIndex + " max: " + getSimCount());
+                return null;
+            }
+
+            if (DBG) logd("getModemForSim " + simIndex);
+
+            HardwareConfig sim = getSim(simIndex);
+            for (HardwareConfig modem: mModems) {
+                if (modem.uuid.equals(sim.modemUuid)) {
+                    return modem;
+                }
+            }
+
+            return null;
+        }
+    }
+
+    /**
+     * get all sim's associated with modem at index 'modemIndex'.
+     */
+    public ArrayList<HardwareConfig> getAllSimsForModem(int modemIndex) {
+        synchronized (mLock) {
+            if (mSims.isEmpty()) {
+                loge("getAllSimsForModem: no registered sim device?!?");
+                return null;
+            }
+
+            if (modemIndex > getModemCount()) {
+                loge("getAllSimsForModem: out-of-bounds access for modem device " + modemIndex + " max: " + getModemCount());
+                return null;
+            }
+
+            if (DBG) logd("getAllSimsForModem " + modemIndex);
+
+            ArrayList<HardwareConfig> result = new ArrayList<HardwareConfig>();
+            HardwareConfig modem = getModem(modemIndex);
+            for (HardwareConfig sim: mSims) {
+                if (sim.modemUuid.equals(modem.uuid)) {
+                    result.add(sim);
+                }
+            }
+            return result;
+        }
+    }
+
+    /**
+     * get all modem's registered.
+     */
+    public ArrayList<HardwareConfig> getAllModems() {
+        synchronized (mLock) {
+            ArrayList<HardwareConfig> modems = new ArrayList<HardwareConfig>();
+            if (mModems.isEmpty()) {
+                if (DBG) logd("getAllModems: empty list.");
+            } else {
+                for (HardwareConfig modem: mModems) {
+                    modems.add(modem);
+                }
+            }
+
+            return modems;
+        }
+    }
+
+    /**
+     * get all sim's registered.
+     */
+    public ArrayList<HardwareConfig> getAllSims() {
+        synchronized (mLock) {
+            ArrayList<HardwareConfig> sims = new ArrayList<HardwareConfig>();
+            if (mSims.isEmpty()) {
+                if (DBG) logd("getAllSims: empty list.");
+            } else {
+                for (HardwareConfig sim: mSims) {
+                    sims.add(sim);
+                }
+            }
+
+            return sims;
+        }
+    }
+}
diff --git a/src/java/com/android/internal/telephony/UiccPhoneBookController.java b/src/java/com/android/internal/telephony/UiccPhoneBookController.java
new file mode 100644
index 0000000..7cc582d
--- /dev/null
+++ b/src/java/com/android/internal/telephony/UiccPhoneBookController.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (c) 2011-2013, The Linux Foundation. All rights reserved.
+ * Not a Contribution.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony;
+
+import android.os.ServiceManager;
+import android.os.RemoteException;
+import android.telephony.Rlog;
+
+import com.android.internal.telephony.IccPhoneBookInterfaceManagerProxy;
+import com.android.internal.telephony.IIccPhoneBook;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.uicc.AdnRecord;
+
+import java.lang.ArrayIndexOutOfBoundsException;
+import java.lang.NullPointerException;
+import java.util.List;
+
+public class UiccPhoneBookController extends IIccPhoneBook.Stub {
+    private static final String TAG = "UiccPhoneBookController";
+    private Phone[] mPhone;
+
+    /* only one UiccPhoneBookController exists */
+    public UiccPhoneBookController(Phone[] phone) {
+        if (ServiceManager.getService("simphonebook") == null) {
+               ServiceManager.addService("simphonebook", this);
+        }
+        mPhone = phone;
+    }
+
+    public boolean
+    updateAdnRecordsInEfBySearch (int efid, String oldTag, String oldPhoneNumber,
+            String newTag, String newPhoneNumber, String pin2) throws android.os.RemoteException {
+        return updateAdnRecordsInEfBySearchUsingSubId(getDefaultSubscription(), efid, oldTag,
+                oldPhoneNumber, newTag, newPhoneNumber, pin2);
+    }
+
+    public boolean
+    updateAdnRecordsInEfBySearchUsingSubId(long subId, int efid, String oldTag,
+            String oldPhoneNumber, String newTag, String newPhoneNumber,
+            String pin2) throws android.os.RemoteException {
+        IccPhoneBookInterfaceManagerProxy iccPbkIntMgrProxy =
+                             getIccPhoneBookInterfaceManagerProxy(subId);
+        if (iccPbkIntMgrProxy != null) {
+            return iccPbkIntMgrProxy.updateAdnRecordsInEfBySearch(efid, oldTag,
+                    oldPhoneNumber, newTag, newPhoneNumber, pin2);
+        } else {
+            Rlog.e(TAG,"updateAdnRecordsInEfBySearch iccPbkIntMgrProxy is" +
+                      " null for Subscription:"+subId);
+            return false;
+        }
+    }
+
+    public boolean
+    updateAdnRecordsInEfByIndex(int efid, String newTag,
+            String newPhoneNumber, int index, String pin2) throws android.os.RemoteException {
+        return updateAdnRecordsInEfByIndexUsingSubId(getDefaultSubscription(), efid, newTag,
+                newPhoneNumber, index, pin2);
+    }
+
+    public boolean
+    updateAdnRecordsInEfByIndexUsingSubId(long subId, int efid, String newTag,
+            String newPhoneNumber, int index, String pin2) throws android.os.RemoteException {
+        IccPhoneBookInterfaceManagerProxy iccPbkIntMgrProxy =
+                             getIccPhoneBookInterfaceManagerProxy(subId);
+        if (iccPbkIntMgrProxy != null) {
+            return iccPbkIntMgrProxy.updateAdnRecordsInEfByIndex(efid, newTag,
+                    newPhoneNumber, index, pin2);
+        } else {
+            Rlog.e(TAG,"updateAdnRecordsInEfByIndex iccPbkIntMgrProxy is" +
+                      " null for Subscription:"+subId);
+            return false;
+        }
+    }
+
+    public int[] getAdnRecordsSize(int efid) throws android.os.RemoteException {
+        return getAdnRecordsSizeUsingSubId(getDefaultSubscription(), efid);
+    }
+
+    public int[]
+    getAdnRecordsSizeUsingSubId(long subId, int efid) throws android.os.RemoteException {
+        IccPhoneBookInterfaceManagerProxy iccPbkIntMgrProxy =
+                             getIccPhoneBookInterfaceManagerProxy(subId);
+        if (iccPbkIntMgrProxy != null) {
+            return iccPbkIntMgrProxy.getAdnRecordsSize(efid);
+        } else {
+            Rlog.e(TAG,"getAdnRecordsSize iccPbkIntMgrProxy is" +
+                      " null for Subscription:"+subId);
+            return null;
+        }
+    }
+
+    public List<AdnRecord> getAdnRecordsInEf(int efid) throws android.os.RemoteException {
+        return getAdnRecordsInEfUsingSubId(getDefaultSubscription(), efid);
+    }
+
+    public List<AdnRecord> getAdnRecordsInEfUsingSubId(long subId, int efid)
+           throws android.os.RemoteException {
+        IccPhoneBookInterfaceManagerProxy iccPbkIntMgrProxy =
+                             getIccPhoneBookInterfaceManagerProxy(subId);
+        if (iccPbkIntMgrProxy != null) {
+            return iccPbkIntMgrProxy.getAdnRecordsInEf(efid);
+        } else {
+            Rlog.e(TAG,"getAdnRecordsInEf iccPbkIntMgrProxy is" +
+                      "null for Subscription:"+subId);
+            return null;
+        }
+    }
+
+    /**
+     * get phone book interface manager proxy object based on subscription.
+     **/
+    private IccPhoneBookInterfaceManagerProxy
+            getIccPhoneBookInterfaceManagerProxy(long subId) {
+
+        long phoneId = SubscriptionController.getInstance().getPhoneId(subId);
+        try {
+            return ((PhoneProxy)mPhone[(int)phoneId]).getIccPhoneBookInterfaceManagerProxy();
+        } catch (NullPointerException e) {
+            Rlog.e(TAG, "Exception is :"+e.toString()+" For subscription :"+subId );
+            e.printStackTrace(); //To print stack trace
+            return null;
+        } catch (ArrayIndexOutOfBoundsException e) {
+            Rlog.e(TAG, "Exception is :"+e.toString()+" For subscription :"+subId );
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    private long getDefaultSubscription() {
+        return PhoneFactory.getDefaultSubscription();
+    }
+}
diff --git a/src/java/com/android/internal/telephony/UiccSmsController.java b/src/java/com/android/internal/telephony/UiccSmsController.java
new file mode 100755
index 0000000..d5b4c88
--- /dev/null
+++ b/src/java/com/android/internal/telephony/UiccSmsController.java
@@ -0,0 +1,308 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (c) 2011-2013, The Linux Foundation. All rights reserved.
+ * Not a Contribution.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony;
+
+import android.app.PendingIntent;
+import android.os.ServiceManager;
+import android.telephony.Rlog;
+
+import com.android.internal.telephony.ISms;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.SmsRawData;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * UiccSmsController to provide an inter-process communication to
+ * access Sms in Icc.
+ */
+public class UiccSmsController extends ISms.Stub {
+    static final String LOG_TAG = "RIL_UiccSmsController";
+
+    protected Phone[] mPhone;
+
+    protected UiccSmsController(Phone[] phone){
+        mPhone = phone;
+
+        if (ServiceManager.getService("isms") == null) {
+            ServiceManager.addService("isms", this);
+        }
+    }
+
+    public boolean
+    updateMessageOnIccEf(String callingPackage, int index, int status, byte[] pdu)
+            throws android.os.RemoteException {
+        return  updateMessageOnIccEfUsingSubId(getPreferredSmsSubscription(), callingPackage,
+                index, status, pdu);
+    }
+
+    public boolean
+    updateMessageOnIccEfUsingSubId(long subId, String callingPackage, int index, int status,
+                byte[] pdu) throws android.os.RemoteException {
+        IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId);
+        if (iccSmsIntMgr != null) {
+            return iccSmsIntMgr.updateMessageOnIccEf(callingPackage, index, status, pdu);
+        } else {
+            Rlog.e(LOG_TAG,"updateMessageOnIccEf iccSmsIntMgr is null" +
+                          " for Subscription: " + subId);
+            return false;
+        }
+    }
+
+    public boolean copyMessageToIccEf(String callingPackage, int status, byte[] pdu, byte[] smsc)
+            throws android.os.RemoteException {
+        return copyMessageToIccEfUsingSubId(getPreferredSmsSubscription(), callingPackage, status,
+                pdu, smsc);
+    }
+
+    public boolean copyMessageToIccEfUsingSubId(long subId, String callingPackage, int status,
+            byte[] pdu, byte[] smsc) throws android.os.RemoteException {
+        IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId);
+        if (iccSmsIntMgr != null) {
+            return iccSmsIntMgr.copyMessageToIccEf(callingPackage, status, pdu, smsc);
+        } else {
+            Rlog.e(LOG_TAG,"copyMessageToIccEf iccSmsIntMgr is null" +
+                          " for Subscription: " + subId);
+            return false;
+        }
+    }
+
+    public List<SmsRawData> getAllMessagesFromIccEf(String callingPackage)
+            throws android.os.RemoteException {
+        return getAllMessagesFromIccEfUsingSubId(getPreferredSmsSubscription(), callingPackage);
+    }
+
+    public List<SmsRawData> getAllMessagesFromIccEfUsingSubId(long subId, String callingPackage)
+                throws android.os.RemoteException {
+        IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId);
+        if (iccSmsIntMgr != null) {
+            return iccSmsIntMgr.getAllMessagesFromIccEf(callingPackage);
+        } else {
+            Rlog.e(LOG_TAG,"getAllMessagesFromIccEf iccSmsIntMgr is" +
+                          " null for Subscription: " + subId);
+            return null;
+        }
+    }
+
+    public void sendData(String callingPackage, String destAddr, String scAddr, int destPort,
+            byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) {
+         sendDataUsingSubId(getPreferredSmsSubscription(), callingPackage, destAddr, scAddr,
+                 destPort, data, sentIntent, deliveryIntent);
+    }
+
+    public void sendDataUsingSubId(long subId, String callingPackage, String destAddr,
+            String scAddr, int destPort, byte[] data, PendingIntent sentIntent,
+            PendingIntent deliveryIntent) {
+        IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId);
+        if (iccSmsIntMgr != null) {
+            iccSmsIntMgr.sendData(callingPackage, destAddr, scAddr, destPort, data,
+                    sentIntent, deliveryIntent);
+        } else {
+            Rlog.e(LOG_TAG,"sendText iccSmsIntMgr is null for" +
+                          " Subscription: " + subId);
+        }
+    }
+
+    public void sendText(String callingPackage, String destAddr, String scAddr,
+            String text, PendingIntent sentIntent, PendingIntent deliveryIntent) {
+        sendTextUsingSubId(getPreferredSmsSubscription(), callingPackage, destAddr, scAddr,
+            text, sentIntent, deliveryIntent);
+    }
+
+    public void sendTextUsingSubId(long subId, String callingPackage, String destAddr,
+            String scAddr, String text, PendingIntent sentIntent, PendingIntent deliveryIntent) {
+        IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId);
+        if (iccSmsIntMgr != null) {
+            iccSmsIntMgr.sendText(callingPackage, destAddr, scAddr, text, sentIntent,
+                    deliveryIntent);
+        } else {
+            Rlog.e(LOG_TAG,"sendText iccSmsIntMgr is null for" +
+                          " Subscription: " + subId);
+        }
+    }
+
+    public void sendMultipartText(String callingPackage, String destAddr, String scAddr,
+            List<String> parts, List<PendingIntent> sentIntents,
+            List<PendingIntent> deliveryIntents) throws android.os.RemoteException {
+         sendMultipartTextUsingSubId(getPreferredSmsSubscription(), callingPackage, destAddr,
+                 scAddr, parts, sentIntents, deliveryIntents);
+    }
+
+    public void sendMultipartTextUsingSubId(long subId, String callingPackage, String destAddr,
+            String scAddr, List<String> parts, List<PendingIntent> sentIntents,
+            List<PendingIntent> deliveryIntents)
+            throws android.os.RemoteException {
+        IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId);
+        if (iccSmsIntMgr != null ) {
+            iccSmsIntMgr.sendMultipartText(callingPackage, destAddr, scAddr, parts, sentIntents,
+                    deliveryIntents);
+        } else {
+            Rlog.e(LOG_TAG,"sendMultipartText iccSmsIntMgr is null for" +
+                          " Subscription: " + subId);
+        }
+    }
+
+    public boolean enableCellBroadcast(int messageIdentifier) throws android.os.RemoteException {
+        return enableCellBroadcastUsingSubId(getPreferredSmsSubscription(), messageIdentifier);
+    }
+
+    public boolean enableCellBroadcastUsingSubId(long subId, int messageIdentifier)
+                throws android.os.RemoteException {
+        return enableCellBroadcastRangeUsingSubId(subId, messageIdentifier, messageIdentifier);
+    }
+
+    public boolean enableCellBroadcastRange(int startMessageId, int endMessageId)
+            throws android.os.RemoteException {
+        return enableCellBroadcastRangeUsingSubId(getPreferredSmsSubscription(), startMessageId,
+                endMessageId);
+    }
+
+    public boolean enableCellBroadcastRangeUsingSubId(long subId, int startMessageId,
+            int endMessageId) throws android.os.RemoteException {
+        IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId);
+        if (iccSmsIntMgr != null ) {
+            return iccSmsIntMgr.enableCellBroadcastRange(startMessageId, endMessageId);
+        } else {
+            Rlog.e(LOG_TAG,"enableCellBroadcast iccSmsIntMgr is null for" +
+                          " Subscription: " + subId);
+        }
+        return false;
+    }
+
+    public boolean disableCellBroadcast(int messageIdentifier) throws android.os.RemoteException {
+        return disableCellBroadcastUsingSubId(getPreferredSmsSubscription(), messageIdentifier);
+    }
+
+    public boolean disableCellBroadcastUsingSubId(long subId, int messageIdentifier)
+                throws android.os.RemoteException {
+        return disableCellBroadcastRangeUsingSubId(subId, messageIdentifier, messageIdentifier);
+    }
+
+    public boolean disableCellBroadcastRange(int startMessageId, int endMessageId)
+            throws android.os.RemoteException {
+        return disableCellBroadcastRangeUsingSubId(getPreferredSmsSubscription(), startMessageId,
+                endMessageId);
+    }
+
+    public boolean disableCellBroadcastRangeUsingSubId(long subId, int startMessageId,
+            int endMessageId) throws android.os.RemoteException {
+        IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId);
+        if (iccSmsIntMgr != null ) {
+            return iccSmsIntMgr.disableCellBroadcastRange(startMessageId, endMessageId);
+        } else {
+            Rlog.e(LOG_TAG,"disableCellBroadcast iccSmsIntMgr is null for" +
+                          " Subscription:"+subId);
+        }
+       return false;
+    }
+
+    public int getPremiumSmsPermission(String packageName) {
+        return getPremiumSmsPermissionUsingSubId(getPreferredSmsSubscription(), packageName);
+    }
+
+    @Override
+    public int getPremiumSmsPermissionUsingSubId(long subId, String packageName) {
+        IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId);
+        if (iccSmsIntMgr != null ) {
+            return iccSmsIntMgr.getPremiumSmsPermission(packageName);
+        } else {
+            Rlog.e(LOG_TAG, "getPremiumSmsPermission iccSmsIntMgr is null");
+        }
+        //TODO Rakesh
+        return 0;
+    }
+
+    public void setPremiumSmsPermission(String packageName, int permission) {
+         setPremiumSmsPermissionUsingSubId(getPreferredSmsSubscription(), packageName, permission);
+    }
+
+    @Override
+    public void setPremiumSmsPermissionUsingSubId(long subId, String packageName, int permission) {
+        IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId);
+        if (iccSmsIntMgr != null ) {
+            iccSmsIntMgr.setPremiumSmsPermission(packageName, permission);
+        } else {
+            Rlog.e(LOG_TAG, "setPremiumSmsPermission iccSmsIntMgr is null");
+        }
+    }
+
+    public boolean isImsSmsSupported() {
+        return isImsSmsSupportedUsingSubId(getPreferredSmsSubscription());
+    }
+
+    @Override
+    public boolean isImsSmsSupportedUsingSubId(long subId) {
+        IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId);
+        if (iccSmsIntMgr != null ) {
+            return iccSmsIntMgr.isImsSmsSupported();
+        } else {
+            Rlog.e(LOG_TAG, "isImsSmsSupported iccSmsIntMgr is null");
+        }
+        return false;
+    }
+
+    public String getImsSmsFormat() {
+        return getImsSmsFormatUsingSubId(getPreferredSmsSubscription());
+    }
+
+    @Override
+    public String getImsSmsFormatUsingSubId(long subId) {
+       IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId);
+        if (iccSmsIntMgr != null ) {
+            return iccSmsIntMgr.getImsSmsFormat();
+        } else {
+            Rlog.e(LOG_TAG, "getImsSmsFormat iccSmsIntMgr is null");
+        }
+        return null;
+    }
+
+    /**
+     * get sms interface manager object based on subscription.
+     **/
+    private IccSmsInterfaceManager getIccSmsInterfaceManager(long subId) {
+        long phoneId = SubscriptionController.getInstance().getPhoneId(subId);
+        try {
+            return (IccSmsInterfaceManager)
+                ((PhoneProxy)mPhone[(int)phoneId]).getIccSmsInterfaceManager();
+        } catch (NullPointerException e) {
+            Rlog.e(LOG_TAG, "Exception is :"+e.toString()+" For subscription :"+subId );
+            e.printStackTrace(); //This will print stact trace
+            return null;
+        } catch (ArrayIndexOutOfBoundsException e) {
+            Rlog.e(LOG_TAG, "Exception is :"+e.toString()+" For subscription :"+subId );
+            e.printStackTrace(); //This will print stack trace
+            return null;
+        }
+    }
+
+    /**
+       Gets User preferred SMS subscription */
+    public long getPreferredSmsSubscription() {
+        return  PhoneFactory.getDefaultSubscription();
+    }
+
+    /**
+     * Get SMS prompt property,  enabled or not
+     **/
+    public boolean isSMSPromptEnabled() {
+        return PhoneFactory.isSMSPromptEnabled();
+    }
+
+}
diff --git a/src/java/com/android/internal/telephony/VoicePhone.java b/src/java/com/android/internal/telephony/VoicePhone.java
new file mode 100644
index 0000000..2589f00
--- /dev/null
+++ b/src/java/com/android/internal/telephony/VoicePhone.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony;
+
+import android.os.Handler;
+import android.os.RegistrantList;
+
+/**
+ * Internal interface used to control the IMS phone from GSM/CDMA Phone
+ *
+ * {@hide}
+ *
+ */
+public interface VoicePhone {
+
+    public static final String CS_FALLBACK = "cs_fallback";
+
+    Connection getHandoverConnection();
+
+    void notifySrvccState(Call.SrvccState state);
+
+    // To redial through GSM or CDMA when dialing IMS call fails
+    void registerForSilentRedial(Handler h, int what, Object obj);
+    void unregisterForSilentRedial(Handler h);
+}
diff --git a/src/java/com/android/internal/telephony/WapPushOverSms.java b/src/java/com/android/internal/telephony/WapPushOverSms.java
index 2f22794..bf43698 100755
--- a/src/java/com/android/internal/telephony/WapPushOverSms.java
+++ b/src/java/com/android/internal/telephony/WapPushOverSms.java
@@ -26,9 +26,11 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.provider.Telephony.Sms.Intents;
+import android.telephony.SubscriptionManager;
 import android.telephony.Rlog;
 
 import com.android.internal.telephony.uicc.IccUtils;
+import com.android.internal.telephony.PhoneConstants;
 
 /**
  * WAP push handler class.
@@ -95,6 +97,9 @@
             int transactionId = pdu[index++] & 0xFF;
             int pduType = pdu[index++] & 0xFF;
 
+            // Should we "abort" if no subId for now just no supplying extra param below
+            int phoneId = handler.getPhone().getPhoneId();
+
             if ((pduType != WspTypeDecoder.PDU_TYPE_PUSH) &&
                     (pduType != WspTypeDecoder.PDU_TYPE_CONFIRMED_PUSH)) {
                 index = mContext.getResources().getInteger(
@@ -201,6 +206,7 @@
                         intent.putExtra("data", intentData);
                         intent.putExtra("contentTypeParameters",
                                 pduDecoder.getContentParameters());
+                        SubscriptionManager.putPhoneIdAndSubIdExtra(intent, phoneId);
 
                         int procRet = wapPushMan.processMessage(wapAppId, contentType, intent);
                         if (DBG) Rlog.v(TAG, "procRet:" + procRet);
@@ -241,6 +247,7 @@
             intent.putExtra("header", header);
             intent.putExtra("data", intentData);
             intent.putExtra("contentTypeParameters", pduDecoder.getContentParameters());
+            SubscriptionManager.putPhoneIdAndSubIdExtra(intent, phoneId);
 
             // Direct the intent to only the default MMS app. If we can't find a default MMS app
             // then sent it to all broadcast receivers.
diff --git a/src/java/com/android/internal/telephony/cat/CatService.java b/src/java/com/android/internal/telephony/cat/CatService.java
index ec61889..16a0853 100644
--- a/src/java/com/android/internal/telephony/cat/CatService.java
+++ b/src/java/com/android/internal/telephony/cat/CatService.java
@@ -28,6 +28,8 @@
 import android.os.SystemProperties;
 
 import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.SubscriptionController;
 import com.android.internal.telephony.uicc.IccFileHandler;
 import com.android.internal.telephony.uicc.IccRecords;
 import com.android.internal.telephony.uicc.IccUtils;
@@ -82,14 +84,17 @@
     private boolean mStkAppInstalled = false;
 
     // Service constants.
-    static final int MSG_ID_SESSION_END              = 1;
-    static final int MSG_ID_PROACTIVE_COMMAND        = 2;
-    static final int MSG_ID_EVENT_NOTIFY             = 3;
-    static final int MSG_ID_CALL_SETUP               = 4;
+    protected static final int MSG_ID_SESSION_END              = 1;
+    protected static final int MSG_ID_PROACTIVE_COMMAND        = 2;
+    protected static final int MSG_ID_EVENT_NOTIFY             = 3;
+    protected static final int MSG_ID_CALL_SETUP               = 4;
     static final int MSG_ID_REFRESH                  = 5;
     static final int MSG_ID_RESPONSE                 = 6;
     static final int MSG_ID_SIM_READY                = 7;
 
+    protected static final int MSG_ID_ICC_CHANGED    = 8;
+    protected static final int MSG_ID_ALPHA_NOTIFY   = 9;
+
     static final int MSG_ID_RIL_MSG_DECODED          = 10;
 
     // Events to signal SIM presence or absent in the device.
@@ -102,9 +107,12 @@
 
     static final String STK_DEFAULT = "Default Message";
 
-    /* Intentionally private for singleton */
+    private HandlerThread mHandlerThread;
+    private int mSlotId;
+
+    /* For multisim catservice should not be singleton */
     private CatService(CommandsInterface ci, UiccCardApplication ca, IccRecords ir,
-            Context context, IccFileHandler fh, UiccCard ic) {
+            Context context, IccFileHandler fh, UiccCard ic, int slotId) {
         if (ci == null || ca == null || ir == null || context == null || fh == null
                 || ic == null) {
             throw new NullPointerException(
@@ -112,9 +120,17 @@
         }
         mCmdIf = ci;
         mContext = context;
+        mSlotId = slotId;
+        mHandlerThread = new HandlerThread("Cat Telephony service" + slotId);
+        mHandlerThread.start();
 
         // Get the RilMessagesDecoder for decoding the messages.
-        mMsgDecoder = RilMessageDecoder.getInstance(this, fh);
+        mMsgDecoder = RilMessageDecoder.getInstance(this, fh, slotId);
+        if (null == mMsgDecoder) {
+            CatLog.d(this, "Null RilMessageDecoder instance");
+            return;
+        }
+        mMsgDecoder.start();
 
         // Register ril events handling.
         mCmdIf.setOnCatSessionEnd(this, MSG_ID_SESSION_END, null);
@@ -127,23 +143,62 @@
         mUiccApplication = ca;
 
         // Register for SIM ready event.
+        CatLog.d(this, "registerForReady slotid: " + mSlotId + "instance : " + this);
         mUiccApplication.registerForReady(this, MSG_ID_SIM_READY, null);
         mIccRecords.registerForRecordsLoaded(this, MSG_ID_ICC_RECORDS_LOADED, null);
 
         // Check if STK application is availalbe
         mStkAppInstalled = isStkAppInstalled();
 
-        CatLog.d(this, "Running CAT service. STK app installed:" + mStkAppInstalled);
+        CatLog.d(this, "Running CAT service on Slotid: " + mSlotId +
+                ". STK app installed:" + mStkAppInstalled);
+    }
+
+    /**
+     * Used for instantiating the Service from the Card.
+     *
+     * @param ci CommandsInterface object
+     * @param context phone app context
+     * @param ic Icc card
+     * @param slotId to know the index of card
+     * @return The only Service object in the system
+     */
+    public static CatService getInstance(CommandsInterface ci,
+            Context context, UiccCard ic, int slotId) {
+        UiccCardApplication ca = null;
+        IccFileHandler fh = null;
+        IccRecords ir = null;
+        if (ic != null) {
+            /* Since Cat is not tied to any application, but rather is Uicc application
+             * in itself - just get first FileHandler and IccRecords object
+             */
+            ca = ic.getApplicationIndex(0);
+            if (ca != null) {
+                fh = ca.getIccFileHandler();
+                ir = ca.getIccRecords();
+            }
+        }
+
+        if (ci == null || ca == null || ir == null || context == null || fh == null
+                || ic == null) {
+            return null;
+        }
+
+        return new CatService(ci, ca, ir, context, fh, ic, slotId);
     }
 
     public void dispose() {
-        mIccRecords.unregisterForRecordsLoaded(this);
-        mCmdIf.unSetOnCatSessionEnd(this);
-        mCmdIf.unSetOnCatProactiveCmd(this);
-        mCmdIf.unSetOnCatEvent(this);
-        mCmdIf.unSetOnCatCallSetUp(this);
+        synchronized (sInstanceLock) {
+            CatLog.d(this, "Disposing CatService object");
+            mIccRecords.unregisterForRecordsLoaded(this);
 
-        removeCallbacksAndMessages(null);
+            mCmdIf.unSetOnCatSessionEnd(this);
+            mCmdIf.unSetOnCatProactiveCmd(this);
+            mCmdIf.unSetOnCatEvent(this);
+            mCmdIf.unSetOnCatCallSetUp(this);
+
+            removeCallbacksAndMessages(null);
+        }
     }
 
     @Override
@@ -346,8 +401,15 @@
                 return;
         }
         mCurrntCmd = cmdMsg;
+        broadcastCatCmdIntent(cmdMsg);
+    }
+
+
+    private void broadcastCatCmdIntent(CatCmdMessage cmdMsg) {
         Intent intent = new Intent(AppInterface.CAT_CMD_ACTION);
         intent.putExtra("STK CMD", cmdMsg);
+        intent.putExtra("SLOT_ID", mSlotId);
+        CatLog.d(this, "Sending CmdMsg: " + cmdMsg+ " on slotid:" + mSlotId);
         mContext.sendBroadcast(intent);
     }
 
@@ -356,13 +418,15 @@
      *
      */
     private void handleSessionEnd() {
-        CatLog.d(this, "SESSION END");
+        CatLog.d(this, "SESSION END on "+ mSlotId);
 
         mCurrntCmd = mMenuCmd;
         Intent intent = new Intent(AppInterface.CAT_SESSION_END_ACTION);
+        intent.putExtra("SLOT_ID", mSlotId);
         mContext.sendBroadcast(intent);
     }
 
+
     private void sendTerminalResponse(CommandDetails cmdDet,
             ResultCode resultCode, boolean includeAdditionalInfo,
             int additionalInfo, ResponseData resp) {
@@ -527,60 +591,18 @@
     }
 
     /**
-     * Used for instantiating/updating the Service from the GsmPhone or CdmaPhone constructor.
+     * Used by application to get an AppInterface object.
      *
-     * @param ci CommandsInterface object
-     * @param context phone app context
-     * @param ic Icc card
      * @return The only Service object in the system
      */
-    public static CatService getInstance(CommandsInterface ci,
-            Context context, UiccCard ic) {
-        UiccCardApplication ca = null;
-        IccFileHandler fh = null;
-        IccRecords ir = null;
-        if (ic != null) {
-            /* Since Cat is not tied to any application, but rather is Uicc application
-             * in itself - just get first FileHandler and IccRecords object
-             */
-            ca = ic.getApplicationIndex(0);
-            if (ca != null) {
-                fh = ca.getIccFileHandler();
-                ir = ca.getIccRecords();
-            }
+    //TODO Need to take care for MSIM
+    public static AppInterface getInstance() {
+        int slotId = PhoneConstants.DEFAULT_CARD_INDEX;
+        SubscriptionController sControl = SubscriptionController.getInstance();
+        if (sControl != null) {
+            slotId = sControl.getSlotId(sControl.getDefaultSubId());
         }
-        synchronized (sInstanceLock) {
-            if (sInstance == null) {
-                if (ci == null || ca == null || ir == null || context == null || fh == null
-                        || ic == null) {
-                    return null;
-                }
-                HandlerThread thread = new HandlerThread("Cat Telephony service");
-                thread.start();
-                sInstance = new CatService(ci, ca, ir, context, fh, ic);
-                CatLog.d(sInstance, "NEW sInstance");
-            } else if ((ir != null) && (mIccRecords != ir)) {
-                if (mIccRecords != null) {
-                    mIccRecords.unregisterForRecordsLoaded(sInstance);
-                }
-
-                if (mUiccApplication != null) {
-                    mUiccApplication.unregisterForReady(sInstance);
-                }
-                CatLog.d(sInstance,
-                        "Reinitialize the Service with SIMRecords and UiccCardApplication");
-                mIccRecords = ir;
-                mUiccApplication = ca;
-
-                // re-Register for SIM ready event.
-                mIccRecords.registerForRecordsLoaded(sInstance, MSG_ID_ICC_RECORDS_LOADED, null);
-                mUiccApplication.registerForReady(sInstance, MSG_ID_SIM_READY, null);
-                CatLog.d(sInstance, "sr changed reinitialize and return current sInstance");
-            } else {
-                CatLog.d(sInstance, "Return current sInstance");
-            }
-            return sInstance;
-        }
+        return getInstance(null, null, null, slotId);
     }
 
     /**
@@ -588,19 +610,20 @@
      *
      * @return The only Service object in the system
      */
-    public static AppInterface getInstance() {
-        return getInstance(null, null, null);
+    public static AppInterface getInstance(int slotId) {
+        return getInstance(null, null, null, slotId);
     }
 
     @Override
     public void handleMessage(Message msg) {
+        CatLog.d(this, "handleMessage[" + msg.what + "]");
 
         switch (msg.what) {
         case MSG_ID_SESSION_END:
         case MSG_ID_PROACTIVE_COMMAND:
         case MSG_ID_EVENT_NOTIFY:
         case MSG_ID_REFRESH:
-            CatLog.d(this, "ril message arrived");
+            CatLog.d(this, "ril message arrived,slotid:" + mSlotId);
             String data = null;
             if (msg.obj != null) {
                 AsyncResult ar = (AsyncResult) msg.obj;
@@ -771,4 +794,41 @@
 
         return (numReceiver > 0);
     }
+
+    public void update(CommandsInterface ci,
+            Context context, UiccCard ic) {
+        UiccCardApplication ca = null;
+        IccRecords ir = null;
+
+        if (ic != null) {
+            /* Since Cat is not tied to any application, but rather is Uicc application
+             * in itself - just get first FileHandler and IccRecords object
+             */
+            ca = ic.getApplicationIndex(0);
+            if (ca != null) {
+                ir = ca.getIccRecords();
+            }
+        }
+
+        synchronized (sInstanceLock) {
+            if ((ir != null) && (mIccRecords != ir)) {
+                if (mIccRecords != null) {
+                    mIccRecords.unregisterForRecordsLoaded(this);
+                }
+
+                if (mUiccApplication != null) {
+                    CatLog.d(this, "unregisterForReady slotid: " + mSlotId + "instance : " + this);
+                    mUiccApplication.unregisterForReady(this);
+                }
+                CatLog.d(this,
+                        "Reinitialize the Service with SIMRecords and UiccCardApplication");
+                mIccRecords = ir;
+                mUiccApplication = ca;
+
+                // re-Register for SIM ready event.
+                mIccRecords.registerForRecordsLoaded(this, MSG_ID_ICC_RECORDS_LOADED, null);
+                CatLog.d(this, "registerForReady slotid: " + mSlotId + "instance : " + this);
+            }
+        }
+    }
 }
diff --git a/src/java/com/android/internal/telephony/cat/RilMessageDecoder.java b/src/java/com/android/internal/telephony/cat/RilMessageDecoder.java
old mode 100644
new mode 100755
index d75c63d..dad8347
--- a/src/java/com/android/internal/telephony/cat/RilMessageDecoder.java
+++ b/src/java/com/android/internal/telephony/cat/RilMessageDecoder.java
@@ -18,11 +18,13 @@
 
 import com.android.internal.telephony.uicc.IccFileHandler;
 import com.android.internal.telephony.uicc.IccUtils;
+import com.android.internal.telephony.PhoneConstants;
 
 import android.os.Handler;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
 import android.os.Message;
+import android.telephony.TelephonyManager;
 
 /**
  * Class used for queuing raw ril messages, decoding them into CommanParams
@@ -35,10 +37,11 @@
     private static final int CMD_PARAMS_READY = 2;
 
     // members
-    private static RilMessageDecoder sInstance = null;
     private CommandParamsFactory mCmdParamsFactory = null;
     private RilMessage mCurrentRilMessage = null;
     private Handler mCaller = null;
+    private static int mSimCount = 0;
+    private static RilMessageDecoder[] mInstance = null;
 
     // States
     private StateStart mStateStart = new StateStart();
@@ -51,12 +54,26 @@
      * @param fh
      * @return RilMesssageDecoder
      */
-    public static synchronized RilMessageDecoder getInstance(Handler caller, IccFileHandler fh) {
-        if (sInstance == null) {
-            sInstance = new RilMessageDecoder(caller, fh);
-            sInstance.start();
+    public static synchronized RilMessageDecoder getInstance(Handler caller, IccFileHandler fh,
+            int slotId) {
+        if (null == mInstance) {
+            mSimCount = TelephonyManager.getDefault().getSimCount();
+            mInstance = new RilMessageDecoder[mSimCount];
+            for (int i = 0; i < mSimCount; i++) {
+                mInstance[i] = null;
+            }
         }
-        return sInstance;
+
+        if (slotId < mSimCount) {
+            if (null == mInstance[slotId]) {
+                mInstance[slotId] = new RilMessageDecoder(caller, fh);
+            }
+        } else {
+            CatLog.d("RilMessageDecoder", "invaild slot id: " + slotId);
+            return null;
+        }
+
+        return mInstance[slotId];
     }
 
     /**
@@ -101,6 +118,10 @@
         mCmdParamsFactory = CommandParamsFactory.getInstance(this, fh);
     }
 
+    private RilMessageDecoder() {
+        super("RilMessageDecoder");
+    }
+
     private class StateStart extends State {
         @Override
         public boolean processMessage(Message msg) {
diff --git a/src/java/com/android/internal/telephony/cdma/CDMALTEPhone.java b/src/java/com/android/internal/telephony/cdma/CDMALTEPhone.java
index 8e60f53..dd07281 100644
--- a/src/java/com/android/internal/telephony/cdma/CDMALTEPhone.java
+++ b/src/java/com/android/internal/telephony/cdma/CDMALTEPhone.java
@@ -16,25 +16,39 @@
 
 package com.android.internal.telephony.cdma;
 
+import android.app.ActivityManagerNative;
 import android.content.ContentValues;
 import android.content.Context;
+import android.content.Intent;
 import android.content.SharedPreferences;
 import android.database.SQLException;
 import android.net.Uri;
 import android.os.AsyncResult;
+import android.os.Handler;
 import android.os.Message;
+import android.os.UserHandle;
 import android.preference.PreferenceManager;
+import android.os.PowerManager;
+import android.os.SystemProperties;
 import android.provider.Telephony;
+import android.text.TextUtils;
+import android.telephony.SubscriptionManager;
 import android.telephony.Rlog;
 
 import com.android.internal.telephony.CommandsInterface;
+import android.telephony.TelephonyManager;
 
+import com.android.internal.telephony.dataconnection.DcTracker;
+import com.android.internal.telephony.MccTable;
 import com.android.internal.telephony.OperatorInfo;
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.PhoneNotifier;
 import com.android.internal.telephony.PhoneProxy;
+import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.PhoneSubInfo;
 import com.android.internal.telephony.SMSDispatcher;
 import com.android.internal.telephony.SmsBroadcastUndelivered;
+import com.android.internal.telephony.Subscription;
 import com.android.internal.telephony.gsm.GsmSMSDispatcher;
 import com.android.internal.telephony.gsm.SmsMessage;
 import com.android.internal.telephony.uicc.IsimRecords;
@@ -42,10 +56,20 @@
 import com.android.internal.telephony.uicc.SIMRecords;
 import com.android.internal.telephony.uicc.UiccCardApplication;
 import com.android.internal.telephony.uicc.UiccController;
+import com.android.internal.telephony.ServiceStateTracker;
+import com.android.internal.telephony.TelephonyIntents;
+import com.android.internal.telephony.TelephonyProperties;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 
+import static com.android.internal.telephony.TelephonyProperties.PROPERTY_ICC_OPERATOR_ALPHA;
+import static com.android.internal.telephony.TelephonyProperties.PROPERTY_ICC_OPERATOR_ISO_COUNTRY;
+import static com.android.internal.telephony.TelephonyProperties.PROPERTY_ICC_OPERATOR_NUMERIC;
+
+import static com.android.internal.telephony.PhoneConstants.EVENT_SUBSCRIPTION_ACTIVATED;
+import static com.android.internal.telephony.PhoneConstants.EVENT_SUBSCRIPTION_DEACTIVATED;
+
 public class CDMALTEPhone extends CDMAPhone {
     static final String LOG_LTE_TAG = "CDMALTEPhone";
     private static final boolean DBG = true;
@@ -57,11 +81,45 @@
     private IsimUiccRecords mIsimUiccRecords;
 
     // Constructors
+    public CDMALTEPhone(Context context, CommandsInterface ci, PhoneNotifier notifier,
+            int phoneId) {
+        this(context, ci, notifier, false, phoneId);
+    }
+
+    public CDMALTEPhone(Context context, CommandsInterface ci, PhoneNotifier notifier,
+            boolean unitTestMode, int phoneId) {
+        super(context, ci, notifier, phoneId);
+
+        Rlog.d(LOG_TAG, "CDMALTEPhone: constructor: sub = " + mPhoneId);
+
+        mDcTracker = new DcTracker(this);
+
+    }
+
+    // Constructors
     public CDMALTEPhone(Context context, CommandsInterface ci, PhoneNotifier notifier) {
         super(context, ci, notifier, false);
     }
 
     @Override
+    public void handleMessage (Message msg) {
+        switch (msg.what) {
+            case EVENT_SUBSCRIPTION_ACTIVATED:
+                log("EVENT_SUBSCRIPTION_ACTIVATED");
+                onSubscriptionActivated();
+                break;
+
+            case EVENT_SUBSCRIPTION_DEACTIVATED:
+                log("EVENT_SUBSCRIPTION_DEACTIVATED");
+                onSubscriptionDeactivated();
+                break;
+
+            default:
+                super.handleMessage(msg);
+        }
+    }
+
+    @Override
     protected void initSstIcc() {
         mSST = new CdmaLteServiceStateTracker(this);
     }
@@ -127,7 +185,7 @@
     @Override
     boolean updateCurrentCarrierInProvider(String operatorNumeric) {
         boolean retVal;
-        if (mUiccController.getUiccCardApplication(UiccController.APP_FAM_3GPP) == null) {
+        if (mUiccController.getUiccCardApplication(mPhoneId, UiccController.APP_FAM_3GPP) == null) {
             if (DBG) log("updateCurrentCarrierInProvider APP_FAM_3GPP == null");
             retVal = super.updateCurrentCarrierInProvider(operatorNumeric);
         } else {
@@ -140,21 +198,22 @@
 
     @Override
     public boolean updateCurrentCarrierInProvider() {
-        if (mSimRecords != null) {
+        long currentDds = SubscriptionManager.getDefaultDataSubId();
+        String operatorNumeric = getOperatorNumeric();
+
+        Rlog.d(LOG_TAG, "updateCurrentCarrierInProvider: mSubscription = " + getSubId()
+                + " currentDds = " + currentDds + " operatorNumeric = " + operatorNumeric);
+
+        if (!TextUtils.isEmpty(operatorNumeric) && (getSubId() == currentDds)) {
             try {
                 Uri uri = Uri.withAppendedPath(Telephony.Carriers.CONTENT_URI, "current");
                 ContentValues map = new ContentValues();
-                String operatorNumeric = mSimRecords.getOperatorNumeric();
                 map.put(Telephony.Carriers.NUMERIC, operatorNumeric);
-                if (DBG) log("updateCurrentCarrierInProvider from UICC: numeric=" +
-                        operatorNumeric);
                 mContext.getContentResolver().insert(uri, map);
                 return true;
             } catch (SQLException e) {
-                loge("Can't store current operator ret false", e);
+                Rlog.e(LOG_TAG, "Can't store current operator", e);
             }
-        } else {
-            if (DBG) log("updateCurrentCarrierInProvider mIccRecords == null ret false");
         }
         return false;
     }
@@ -197,40 +256,28 @@
     }
 
     @Override
-    public void requestIsimAuthentication(String nonce, Message result) {
-        mCi.requestIsimAuthentication(nonce, result);
-    }
-
-    @Override
     protected void onUpdateIccAvailability() {
         if (mUiccController == null ) {
             return;
         }
 
-        // Update IsimRecords
-        UiccCardApplication newUiccApplication =
-                mUiccController.getUiccCardApplication(UiccController.APP_FAM_IMS);
-        IsimUiccRecords newIsimUiccRecords = null;
+        UiccCardApplication newUiccApplication = getUiccCardApplication();
 
-        if (newUiccApplication != null) {
-            newIsimUiccRecords = (IsimUiccRecords)newUiccApplication.getIccRecords();
-        }
-        mIsimUiccRecords = newIsimUiccRecords;
-
-        // Update UsimRecords
-        newUiccApplication = mUiccController.getUiccCardApplication(UiccController.APP_FAM_3GPP);
-        SIMRecords newSimRecords = null;
-        if (newUiccApplication != null) {
-            newSimRecords = (SIMRecords)newUiccApplication.getIccRecords();
-        }
-        if (mSimRecords != newSimRecords) {
-            if (mSimRecords != null) {
-                log("Removing stale SIMRecords object.");
-                mSimRecords = null;
+        UiccCardApplication app = mUiccApplication.get();
+        if (app != newUiccApplication) {
+            if (app != null) {
+                log("Removing stale icc objects.");
+                if (mIccRecords.get() != null) {
+                    unregisterForRuimRecordEvents();
+                }
+                mIccRecords.set(null);
+                mUiccApplication.set(null);
             }
-            if (newSimRecords != null) {
-                log("New SIMRecords found");
-                mSimRecords = newSimRecords;
+            if (newUiccApplication != null) {
+                log("New Uicc application found");
+                mUiccApplication.set(newUiccApplication);
+                mIccRecords.set(newUiccApplication.getIccRecords());
+                registerForRuimRecordEvents();
             }
         }
 
@@ -238,6 +285,159 @@
     }
 
     @Override
+    protected void init(Context context, PhoneNotifier notifier) {
+        mCi.setPhoneType(PhoneConstants.PHONE_TYPE_CDMA);
+        mCT = new CdmaCallTracker(this);
+        mCdmaSSM = CdmaSubscriptionSourceManager.getInstance(context, mCi, this,
+                EVENT_CDMA_SUBSCRIPTION_SOURCE_CHANGED, null);
+        mRuimPhoneBookInterfaceManager = new RuimPhoneBookInterfaceManager(this);
+        mSubInfo = new PhoneSubInfo(this);
+        mEriManager = new EriManager(this, context, EriManager.ERI_FROM_XML);
+
+        mCi.registerForAvailable(this, EVENT_RADIO_AVAILABLE, null);
+        mCi.registerForOffOrNotAvailable(this, EVENT_RADIO_OFF_OR_NOT_AVAILABLE, null);
+        mCi.registerForOn(this, EVENT_RADIO_ON, null);
+        mCi.setOnSuppServiceNotification(this, EVENT_SSN, null);
+        mSST.registerForNetworkAttached(this, EVENT_REGISTERED_TO_NETWORK, null);
+        mCi.setEmergencyCallbackMode(this, EVENT_EMERGENCY_CALLBACK_MODE_ENTER, null);
+        mCi.registerForExitEmergencyCallbackMode(this, EVENT_EXIT_EMERGENCY_CALLBACK_RESPONSE,
+                null);
+
+        PowerManager pm
+            = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,LOG_TAG);
+
+        // This is needed to handle phone process crashes
+        String inEcm = SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE, "false");
+        mIsPhoneInEcmState = inEcm.equals("true");
+        if (mIsPhoneInEcmState) {
+            // Send a message which will invoke handleExitEmergencyCallbackMode
+            mCi.exitEmergencyCallbackMode(obtainMessage(EVENT_EXIT_EMERGENCY_CALLBACK_RESPONSE));
+        }
+
+        // get the string that specifies the carrier OTA Sp number
+        mCarrierOtaSpNumSchema = SystemProperties.get(
+                TelephonyProperties.PROPERTY_OTASP_NUM_SCHEMA,"");
+
+        // Notify voicemails.
+        notifier.notifyMessageWaitingChanged(this);
+        setProperties();
+    }
+
+    private void onSubscriptionActivated() {
+//        mSubscriptionData = SubscriptionManager.getCurrentSubscription(mSubscription);
+
+        log("SUBSCRIPTION ACTIVATED : slotId : " + mSubscriptionData.slotId
+                + " appid : " + mSubscriptionData.m3gpp2Index
+                + " subId : " + mSubscriptionData.subId
+                + " subStatus : " + mSubscriptionData.subStatus);
+
+        // Make sure properties are set for proper subscription.
+        setProperties();
+
+        onUpdateIccAvailability();
+        mSST.sendMessage(mSST.obtainMessage(ServiceStateTracker.EVENT_ICC_CHANGED));
+        ((CdmaLteServiceStateTracker)mSST).updateCdmaSubscription();
+        ((DcTracker)mDcTracker).updateRecords();
+    }
+
+    private void onSubscriptionDeactivated() {
+        log("SUBSCRIPTION DEACTIVATED");
+        // resetSubSpecifics
+        mSubscriptionData = null;
+    }
+
+    // Set the properties per subscription
+    private void setProperties() {
+        //Change the system property
+        setSystemProperty(TelephonyProperties.CURRENT_ACTIVE_PHONE,
+                new Integer(PhoneConstants.PHONE_TYPE_CDMA).toString());
+        // Sets operator alpha property by retrieving from build-time system property
+        String operatorAlpha = SystemProperties.get("ro.cdma.home.operator.alpha");
+        setSystemProperty(PROPERTY_ICC_OPERATOR_ALPHA, operatorAlpha);
+
+        // Sets operator numeric property by retrieving from build-time system property
+        String operatorNumeric = SystemProperties.get(PROPERTY_CDMA_HOME_OPERATOR_NUMERIC);
+        log("update icc_operator_numeric=" + operatorNumeric);
+        setSystemProperty(PROPERTY_ICC_OPERATOR_NUMERIC, operatorNumeric);
+        // Sets iso country property by retrieving from build-time system property
+        setIsoCountryProperty(operatorNumeric);
+        // Updates MCC MNC device configuration information
+        log("update mccmnc=" + operatorNumeric);
+        MccTable.updateMccMncConfiguration(mContext, operatorNumeric, false);
+        // Sets current entry in the telephony carrier table
+        updateCurrentCarrierInProvider();
+    }
+
+    @Override
+    protected UiccCardApplication getUiccCardApplication() {
+            return  mUiccController.getUiccCardApplication(mPhoneId, UiccController.APP_FAM_3GPP2);
+    }
+
+    @Override
+    public void setSystemProperty(String property, String value) {
+        if(getUnitTestMode()) {
+            return;
+        }
+        TelephonyManager.setTelephonyProperty(property, getSubId(), value);
+    }
+
+    public String getSystemProperty(String property, String defValue) {
+        if(getUnitTestMode()) {
+            return null;
+        }
+        return TelephonyManager.getTelephonyProperty(property, getSubId(), defValue);
+    }
+
+    public void updateDataConnectionTracker() {
+        ((DcTracker)mDcTracker).update();
+    }
+
+    public void setInternalDataEnabled(boolean enable, Message onCompleteMsg) {
+        ((DcTracker)mDcTracker)
+                .setInternalDataEnabled(enable, onCompleteMsg);
+    }
+
+    public boolean setInternalDataEnabledFlag(boolean enable) {
+       return ((DcTracker)mDcTracker)
+                .setInternalDataEnabledFlag(enable);
+    }
+
+    /**
+     * @return operator numeric.
+     */
+    public String getOperatorNumeric() {
+        String operatorNumeric = null;
+
+        if (mCdmaSubscriptionSource == CDMA_SUBSCRIPTION_NV) {
+            operatorNumeric = SystemProperties.get("ro.cdma.home.operator.numeric");
+        } else if (mCdmaSubscriptionSource == CDMA_SUBSCRIPTION_RUIM_SIM
+                && mIccRecords != null && mIccRecords.get() != null) {
+            operatorNumeric = mIccRecords.get().getOperatorNumeric();
+        } else {
+            Rlog.e(LOG_TAG, "getOperatorNumeric: Cannot retrieve operatorNumeric:"
+                    + " mCdmaSubscriptionSource = " + mCdmaSubscriptionSource + " mIccRecords = "
+                    + ((mIccRecords != null) && (mIccRecords.get() != null)
+                        ? mIccRecords.get().getRecordsLoaded()
+                        : null));
+        }
+
+        Rlog.d(LOG_TAG, "getOperatorNumeric: mCdmaSubscriptionSource = " + mCdmaSubscriptionSource
+                + " operatorNumeric = " + operatorNumeric);
+
+        return operatorNumeric;
+    }
+    public void registerForAllDataDisconnected(Handler h, int what, Object obj) {
+        ((DcTracker)mDcTracker)
+               .registerForAllDataDisconnected(h, what, obj);
+    }
+
+    public void unregisterForAllDataDisconnected(Handler h) {
+        ((DcTracker)mDcTracker)
+                .unregisterForAllDataDisconnected(h);
+    }
+
+    @Override
     protected void log(String s) {
             Rlog.d(LOG_LTE_TAG, s);
     }
diff --git a/src/java/com/android/internal/telephony/cdma/CDMAPhone.java b/src/java/com/android/internal/telephony/cdma/CDMAPhone.java
index fb177ca..b656e40 100644
--- a/src/java/com/android/internal/telephony/cdma/CDMAPhone.java
+++ b/src/java/com/android/internal/telephony/cdma/CDMAPhone.java
@@ -38,6 +38,7 @@
 import android.telephony.CellLocation;
 import android.telephony.PhoneNumberUtils;
 import android.telephony.ServiceState;
+import android.telephony.SubscriptionManager;
 import android.telephony.cdma.CdmaCellLocation;
 import android.text.TextUtils;
 import android.telephony.Rlog;
@@ -62,10 +63,12 @@
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.internal.telephony.TelephonyProperties;
 import com.android.internal.telephony.UUSInfo;
+import com.android.internal.telephony.VoicePhone;
 import com.android.internal.telephony.dataconnection.DcTracker;
 import com.android.internal.telephony.uicc.IccException;
 import com.android.internal.telephony.uicc.IccRecords;
 import com.android.internal.telephony.uicc.RuimRecords;
+import com.android.internal.telephony.uicc.UiccCard;
 import com.android.internal.telephony.uicc.UiccCardApplication;
 import com.android.internal.telephony.uicc.UiccController;
 import java.io.FileDescriptor;
@@ -75,6 +78,7 @@
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
+import static com.android.internal.telephony.PhoneConstants.SUBSCRIPTION_KEY;
 import static com.android.internal.telephony.TelephonyProperties.PROPERTY_ICC_OPERATOR_ALPHA;
 import static com.android.internal.telephony.TelephonyProperties.PROPERTY_ICC_OPERATOR_ISO_COUNTRY;
 import static com.android.internal.telephony.TelephonyProperties.PROPERTY_ICC_OPERATOR_NUMERIC;
@@ -103,7 +107,8 @@
     CdmaSubscriptionSourceManager mCdmaSSM;
     ArrayList <CdmaMmiCode> mPendingMmis = new ArrayList<CdmaMmiCode>();
     RuimPhoneBookInterfaceManager mRuimPhoneBookInterfaceManager;
-    int mCdmaSubscriptionSource = CdmaSubscriptionSourceManager.SUBSCRIPTION_SOURCE_UNKNOWN;
+    int mCdmaSubscriptionSource =
+            CdmaSubscriptionSourceManager.SUBSCRIPTION_SOURCE_UNKNOWN;
     PhoneSubInfo mSubInfo;
     EriManager mEriManager;
     WakeLock mWakeLock;
@@ -117,14 +122,14 @@
     // mEcmExitRespRegistrant is informed after the phone has been exited
     //the emergency callback mode
     //keep track of if phone is in emergency callback mode
-    private boolean mIsPhoneInEcmState;
+    protected boolean mIsPhoneInEcmState;
     private Registrant mEcmExitRespRegistrant;
     protected String mImei;
     protected String mImeiSv;
     private String mEsn;
     private String mMeid;
     // string to define how the carrier specifies its own ota sp number
-    private String mCarrierOtaSpNumSchema;
+    protected String mCarrierOtaSpNumSchema;
 
     // A runnable which is used to automatically exit from Ecm after a period of time.
     private Runnable mExitEcmRunnable = new Runnable() {
@@ -146,6 +151,13 @@
     }
 
     public CDMAPhone(Context context, CommandsInterface ci, PhoneNotifier notifier,
+            int phoneId) {
+        super("CDMA", notifier, context, ci, false, phoneId);
+        initSstIcc();
+        init(context, notifier);
+    }
+
+    public CDMAPhone(Context context, CommandsInterface ci, PhoneNotifier notifier,
             boolean unitTestMode) {
         super("CDMA", notifier, context, ci, unitTestMode);
         initSstIcc();
@@ -200,7 +212,7 @@
         String operatorNumeric = SystemProperties.get(PROPERTY_CDMA_HOME_OPERATOR_NUMERIC);
         log("init: operatorAlpha='" + operatorAlpha
                 + "' operatorNumeric='" + operatorNumeric + "'");
-        if (mUiccController.getUiccCardApplication(UiccController.APP_FAM_3GPP) == null) {
+        if (mUiccController.getUiccCardApplication(mPhoneId, UiccController.APP_FAM_3GPP) == null) {
             log("init: APP_FAM_3GPP == NULL");
             if (!TextUtils.isEmpty(operatorAlpha)) {
                 log("init: set 'gsm.sim.operator.alpha' to operator='" + operatorAlpha + "'");
@@ -208,6 +220,7 @@
             }
             if (!TextUtils.isEmpty(operatorNumeric)) {
                 log("init: set 'gsm.sim.operator.numeric' to operator='" + operatorNumeric + "'");
+                log("update icc_operator_numeric=" + operatorNumeric);
                 setSystemProperty(PROPERTY_ICC_OPERATOR_NUMERIC, operatorNumeric);
             }
             setIsoCountryProperty(operatorNumeric);
@@ -258,6 +271,7 @@
         mSST = null;
         mEriManager = null;
         mExitEcmRunnable = null;
+
         super.removeReferences();
     }
 
@@ -371,6 +385,26 @@
     @Override
     public Connection
     dial (String dialString) throws CallStateException {
+        if (mVoicePhone != null
+                && mVoicePhone.getServiceState().getState() == ServiceState.STATE_IN_SERVICE
+                && !PhoneNumberUtils.isEmergencyNumber(dialString)) {
+            try {
+                return mVoicePhone.dial(dialString);
+            } catch (CallStateException e) {
+                if (!VoicePhone.CS_FALLBACK.equals(e.getMessage())) {
+                    CallStateException ce = new CallStateException(e.getMessage());
+                    ce.setStackTrace(e.getStackTrace());
+                    throw ce;
+                }
+            }
+        }
+
+        return dialInternal(dialString, null);
+    }
+
+    @Override
+    protected Connection
+    dialInternal (String dialString, UUSInfo uusInfo) throws CallStateException {
         // Need to make sure dialString gets parsed properly
         String newDialString = PhoneNumberUtils.stripSeparators(dialString);
         return mCT.dial(newDialString);
@@ -447,7 +481,7 @@
         IccRecords r = mIccRecords.get();
         if (r == null) {
             // to get ICCID form SIMRecords because it is on MF.
-            r = mUiccController.getIccRecords(UiccController.APP_FAM_3GPP);
+            r = mUiccController.getIccRecords(mPhoneId, UiccController.APP_FAM_3GPP);
         }
         return (r != null) ? r.getIccId() : null;
     }
@@ -828,9 +862,9 @@
         // Read platform settings for dynamic voicemail number
         if (getContext().getResources().getBoolean(com.android.internal
                 .R.bool.config_telephony_use_own_number_for_voicemail)) {
-            number = sp.getString(VM_NUMBER_CDMA, getLine1Number());
+            number = sp.getString(VM_NUMBER_CDMA + getPhoneId(), getLine1Number());
         } else {
-            number = sp.getString(VM_NUMBER_CDMA, "*86");
+            number = sp.getString(VM_NUMBER_CDMA + getPhoneId(), "*86");
         }
         return number;
     }
@@ -960,6 +994,7 @@
         //Send an Intent
         Intent intent = new Intent(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
         intent.putExtra(PhoneConstants.PHONE_IN_ECM_STATE, mIsPhoneInEcmState);
+        SubscriptionManager.putPhoneIdAndSubIdExtra(intent, getPhoneId());
         ActivityManagerNative.broadcastStickyIntent(intent,null,UserHandle.USER_ALL);
         if (DBG) Rlog.d(LOG_TAG, "sendEmergencyCallbackModeChange");
     }
@@ -1125,12 +1160,18 @@
 
             case EVENT_RADIO_OFF_OR_NOT_AVAILABLE:{
                 Rlog.d(LOG_TAG, "Event EVENT_RADIO_OFF_OR_NOT_AVAILABLE Received");
+                if (mVoicePhone != null) {
+                    mVoicePhone.getServiceState().setStateOff();
+                }
             }
             break;
 
             case EVENT_RADIO_ON:{
                 Rlog.d(LOG_TAG, "Event EVENT_RADIO_ON Received");
                 handleCdmaSubscriptionSource(mCdmaSSM.getCdmaSubscriptionSource());
+                if (mVoicePhone != null) {
+                    mVoicePhone.getServiceState().setStateOutOfService();
+                }
             }
             break;
 
@@ -1176,19 +1217,22 @@
         }
     }
 
+    protected UiccCardApplication getUiccCardApplication() {
+        return  mUiccController.getUiccCardApplication(mPhoneId, UiccController.APP_FAM_3GPP2);
+    }
+
     @Override
     protected void onUpdateIccAvailability() {
         if (mUiccController == null ) {
             return;
         }
 
-        UiccCardApplication newUiccApplication =
-                mUiccController.getUiccCardApplication(UiccController.APP_FAM_3GPP2);
+        UiccCardApplication newUiccApplication = getUiccCardApplication();
 
         if (newUiccApplication == null) {
             log("can't find 3GPP2 application; trying APP_FAM_3GPP");
-            newUiccApplication = mUiccController
-                    .getUiccCardApplication(UiccController.APP_FAM_3GPP);
+            newUiccApplication =
+                    mUiccController.getUiccCardApplication(mPhoneId, UiccController.APP_FAM_3GPP);
         }
 
         UiccCardApplication app = mUiccApplication.get();
@@ -1197,7 +1241,6 @@
                 log("Removing stale icc objects.");
                 if (mIccRecords.get() != null) {
                     unregisterForRuimRecordEvents();
-                    mRuimPhoneBookInterfaceManager.updateIccRecords(null);
                 }
                 mIccRecords.set(null);
                 mUiccApplication.set(null);
@@ -1207,7 +1250,6 @@
                 mUiccApplication.set(newUiccApplication);
                 mIccRecords.set(newUiccApplication.getIccRecords());
                 registerForRuimRecordEvents();
-                mRuimPhoneBookInterfaceManager.updateIccRecords(mIccRecords.get());
             }
         }
     }
@@ -1269,7 +1311,7 @@
      * {@inheritDoc}
      */
     @Override
-    public final void setSystemProperty(String property, String value) {
+    public void setSystemProperty(String property, String value) {
         super.setSystemProperty(property, value);
     }
 
@@ -1534,7 +1576,7 @@
         // Update the preference value of voicemail number
         SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
         SharedPreferences.Editor editor = sp.edit();
-        editor.putString(VM_NUMBER_CDMA, number);
+        editor.putString(VM_NUMBER_CDMA + getPhoneId(), number);
         editor.apply();
     }
 
@@ -1542,7 +1584,7 @@
      * Sets PROPERTY_ICC_OPERATOR_ISO_COUNTRY property
      *
      */
-    private void setIsoCountryProperty(String operatorNumeric) {
+    protected void setIsoCountryProperty(String operatorNumeric) {
         if (TextUtils.isEmpty(operatorNumeric)) {
             log("setIsoCountryProperty: clear 'gsm.sim.operator.iso-country'");
             setSystemProperty(PROPERTY_ICC_OPERATOR_ISO_COUNTRY, "");
@@ -1579,6 +1621,7 @@
                 getContext().getContentResolver().insert(uri, map);
 
                 // Updates MCC MNC device configuration information
+                log("update mccmnc=" + operatorNumeric);
                 MccTable.updateMccMncConfiguration(mContext, operatorNumeric, false);
 
                 return true;
@@ -1600,6 +1643,10 @@
     }
 
     public void prepareEri() {
+        if (mEriManager == null) {
+            Rlog.e(LOG_TAG, "PrepareEri: Trying to access stale objects");
+            return;
+        }
         mEriManager.loadEriFile();
         if(mEriManager.isEriFileLoaded()) {
             // when the ERI file is loaded
diff --git a/src/java/com/android/internal/telephony/cdma/CdmaConnection.java b/src/java/com/android/internal/telephony/cdma/CdmaConnection.java
index f1d6bcb..6571f85 100755
--- a/src/java/com/android/internal/telephony/cdma/CdmaConnection.java
+++ b/src/java/com/android/internal/telephony/cdma/CdmaConnection.java
@@ -251,6 +251,11 @@
     }
 
     @Override
+    public long getConnectTimeReal() {
+        return mConnectTimeReal;
+    }
+
+    @Override
     public long getDisconnectTime() {
         return mDisconnectTime;
     }
@@ -267,6 +272,11 @@
     }
 
     @Override
+    public long getHoldingStartTime() {
+        return mHoldingStartTime;
+    }
+
+    @Override
     public long getHoldDurationMillis() {
         if (getState() != CdmaCall.State.HOLDING) {
             // If not holding, return 0
@@ -963,4 +973,13 @@
         return mPreciseCause;
     }
 
+    @Override
+    public Connection getOrigConnection() {
+        return null;
+    }
+
+    @Override
+    public boolean isMultiparty() {
+        return false;
+    }
 }
diff --git a/src/java/com/android/internal/telephony/cdma/CdmaInboundSmsHandler.java b/src/java/com/android/internal/telephony/cdma/CdmaInboundSmsHandler.java
index 9352639..355de31 100644
--- a/src/java/com/android/internal/telephony/cdma/CdmaInboundSmsHandler.java
+++ b/src/java/com/android/internal/telephony/cdma/CdmaInboundSmsHandler.java
@@ -255,7 +255,7 @@
         // Store the voicemail count in preferences.
         SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
         SharedPreferences.Editor editor = sp.edit();
-        editor.putInt(CDMAPhone.VM_COUNT_CDMA, voicemailCount);
+        editor.putInt(CDMAPhone.VM_COUNT_CDMA + mPhone.getPhoneId(), voicemailCount);
         editor.apply();
         mPhone.setVoiceMessageWaiting(1, voicemailCount);
     }
diff --git a/src/java/com/android/internal/telephony/cdma/CdmaLteServiceStateTracker.java b/src/java/com/android/internal/telephony/cdma/CdmaLteServiceStateTracker.java
index 7c46004..0015cd7 100644
--- a/src/java/com/android/internal/telephony/cdma/CdmaLteServiceStateTracker.java
+++ b/src/java/com/android/internal/telephony/cdma/CdmaLteServiceStateTracker.java
@@ -16,11 +16,15 @@
 
 package com.android.internal.telephony.cdma;
 
+import android.content.Intent;
 import com.android.internal.telephony.TelephonyProperties;
 import com.android.internal.telephony.MccTable;
 import com.android.internal.telephony.EventLogTags;
 import com.android.internal.telephony.uicc.RuimRecords;
 import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppState;
+import com.android.internal.telephony.TelephonyIntents;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneFactory;
 
 import android.telephony.CellInfo;
 import android.telephony.CellInfoLte;
@@ -29,15 +33,23 @@
 import android.telephony.SignalStrength;
 import android.telephony.ServiceState;
 import android.telephony.cdma.CdmaCellLocation;
+import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.os.AsyncResult;
 import android.os.Message;
+import android.os.UserHandle;
 import android.os.SystemClock;
 import android.os.SystemProperties;
 
 import android.telephony.Rlog;
 import android.util.EventLog;
 
+import com.android.internal.telephony.dataconnection.DcTrackerBase;
+import com.android.internal.telephony.ProxyController;
+import android.telephony.SubscriptionManager;
+import com.android.internal.telephony.uicc.UiccCardApplication;
+import com.android.internal.telephony.uicc.UiccController;
+
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -46,6 +58,7 @@
 public class CdmaLteServiceStateTracker extends CdmaServiceStateTracker {
     private CDMALTEPhone mCdmaLtePhone;
     private final CellInfoLte mCellInfoLte;
+    private static final int EVENT_ALL_DATA_DISCONNECTED = 1001;
 
     private CellIdentityLte mNewCellIdentityLte = new CellIdentityLte();
     private CellIdentityLte mLasteCellIdentityLte = new CellIdentityLte();
@@ -95,6 +108,19 @@
             // the latest variables.
             pollState();
             break;
+        case EVENT_ALL_DATA_DISCONNECTED:
+            long dds = SubscriptionManager.getDefaultDataSubId();
+            ProxyController.getInstance().unregisterForAllDataDisconnected(dds, this);
+            synchronized(this) {
+                if (mPendingRadioPowerOffAfterDataOff) {
+                    if (DBG) log("EVENT_ALL_DATA_DISCONNECTED, turn radio off now.");
+                    hangupAndPowerOff();
+                    mPendingRadioPowerOffAfterDataOff = false;
+                } else {
+                    log("EVENT_ALL_DATA_DISCONNECTED is stale");
+                }
+            }
+            break;
         default:
             super.handleMessage(msg);
         }
@@ -570,6 +596,63 @@
     }
 
     @Override
+    protected UiccCardApplication getUiccCardApplication() {
+            return  mUiccController.getUiccCardApplication(((CDMALTEPhone)mPhone).
+                    getPhoneId(), UiccController.APP_FAM_3GPP2);
+    }
+
+    protected void updateCdmaSubscription() {
+        mCi.getCDMASubscription(obtainMessage(EVENT_POLL_STATE_CDMA_SUBSCRIPTION));
+    }
+
+    /**
+     * Clean up existing voice and data connection then turn off radio power.
+     *
+     * Hang up the existing voice calls to decrease call drop rate.
+     */
+    @Override
+    public void powerOffRadioSafely(DcTrackerBase dcTracker) {
+        synchronized (this) {
+            if (!mPendingRadioPowerOffAfterDataOff) {
+                long dds = SubscriptionManager.getDefaultDataSubId();
+                // To minimize race conditions we call cleanUpAllConnections on
+                // both if else paths instead of before this isDisconnected test.
+                if (dcTracker.isDisconnected()
+                        && (dds == mPhone.getSubId()
+                            || (dds != mPhone.getSubId()
+                                && ProxyController.getInstance().isDataDisconnected(dds)))) {
+                    // To minimize race conditions we do this after isDisconnected
+                    dcTracker.cleanUpAllConnections(Phone.REASON_RADIO_TURNED_OFF);
+                    if (DBG) log("Data disconnected, turn off radio right away.");
+                    hangupAndPowerOff();
+                } else {
+                    dcTracker.cleanUpAllConnections(Phone.REASON_RADIO_TURNED_OFF);
+                    if (dds != mPhone.getSubId()
+                            && !ProxyController.getInstance().isDataDisconnected(dds)) {
+                        if (DBG) log("Data is active on DDS.  Wait for all data disconnect");
+                        // Data is not disconnected on DDS. Wait for the data disconnect complete
+                        // before sending the RADIO_POWER off.
+                        ProxyController.getInstance().registerForAllDataDisconnected(dds, this,
+                                EVENT_ALL_DATA_DISCONNECTED, null);
+                        mPendingRadioPowerOffAfterDataOff = true;
+                    }
+                    Message msg = Message.obtain(this);
+                    msg.what = EVENT_SET_RADIO_POWER_OFF;
+                    msg.arg1 = ++mPendingRadioPowerOffAfterDataOffTag;
+                    if (sendMessageDelayed(msg, 30000)) {
+                        if (DBG) log("Wait upto 30s for data to disconnect, then turn off radio.");
+                        mPendingRadioPowerOffAfterDataOff = true;
+                    } else {
+                        log("Cannot send delayed Msg, turn off radio right away.");
+                        hangupAndPowerOff();
+                        mPendingRadioPowerOffAfterDataOff = false;
+                    }
+                }
+            }
+        }
+    }
+
+    @Override
     protected void log(String s) {
         Rlog.d(LOG_TAG, "[CdmaLteSST] " + s);
     }
diff --git a/src/java/com/android/internal/telephony/cdma/CdmaMmiCode.java b/src/java/com/android/internal/telephony/cdma/CdmaMmiCode.java
index 3cb8bc7..9ff9200 100644
--- a/src/java/com/android/internal/telephony/cdma/CdmaMmiCode.java
+++ b/src/java/com/android/internal/telephony/cdma/CdmaMmiCode.java
@@ -22,6 +22,7 @@
 import com.android.internal.telephony.uicc.UiccCardApplication;
 import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppState;
 import com.android.internal.telephony.MmiCode;
+import com.android.internal.telephony.Phone;
 
 import android.os.AsyncResult;
 import android.os.Handler;
@@ -162,6 +163,11 @@
         return mMessage;
     }
 
+    public Phone
+    getPhone() {
+        return ((Phone) mPhone);
+    }
+
     // inherited javadoc suffices
     @Override
     public void
diff --git a/src/java/com/android/internal/telephony/cdma/CdmaServiceCategoryProgramHandler.java b/src/java/com/android/internal/telephony/cdma/CdmaServiceCategoryProgramHandler.java
index bb9f9d5..81413c6 100644
--- a/src/java/com/android/internal/telephony/cdma/CdmaServiceCategoryProgramHandler.java
+++ b/src/java/com/android/internal/telephony/cdma/CdmaServiceCategoryProgramHandler.java
@@ -26,10 +26,12 @@
 import android.os.Message;
 import android.provider.Telephony.Sms.Intents;
 import android.telephony.PhoneNumberUtils;
+import android.telephony.SubscriptionManager;
 import android.telephony.cdma.CdmaSmsCbProgramData;
 import android.telephony.cdma.CdmaSmsCbProgramResults;
 
 import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.WakeLockStateMachine;
 import com.android.internal.telephony.cdma.sms.BearerData;
 import com.android.internal.telephony.cdma.sms.CdmaSmsAddress;
@@ -103,7 +105,7 @@
         Intent intent = new Intent(Intents.SMS_SERVICE_CATEGORY_PROGRAM_DATA_RECEIVED_ACTION);
         intent.putExtra("sender", sms.getOriginatingAddress());
         intent.putParcelableArrayListExtra("program_data", programDataList);
-
+        SubscriptionManager.putPhoneIdAndSubIdExtra(intent, mPhone.getPhoneId());
         mContext.sendOrderedBroadcast(intent, Manifest.permission.RECEIVE_SMS,
                 AppOpsManager.OP_RECEIVE_SMS, mScpResultsReceiver,
                 getHandler(), Activity.RESULT_OK, null, null);
diff --git a/src/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java b/src/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java
index 8281b7f..ca38aab 100644
--- a/src/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java
+++ b/src/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java
@@ -38,6 +38,8 @@
 import android.telephony.Rlog;
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
+import android.telephony.TelephonyManager;
+import android.telephony.SubscriptionManager;
 import android.telephony.cdma.CdmaCellLocation;
 import android.text.TextUtils;
 import android.util.EventLog;
@@ -500,6 +502,11 @@
             }
             break;
 
+        case EVENT_CHANGE_IMS_STATE:
+            if (DBG) log("EVENT_CHANGE_IMS_STATE");
+            setPowerStateToDesired();
+            break;
+
         default:
             super.handleMessage(msg);
         break;
@@ -537,6 +544,7 @@
     protected void updateSpnDisplay() {
         // mOperatorAlphaLong contains the ERI text
         String plmn = mSS.getOperatorAlphaLong();
+
         if (!TextUtils.equals(plmn, mCurPlmn)) {
             // Allow A blank plmn, "" to set showPlmn to true. Previously, we
             // would set showPlmn to true only if plmn was not empty, i.e. was not
@@ -553,6 +561,7 @@
             intent.putExtra(TelephonyIntents.EXTRA_SPN, "");
             intent.putExtra(TelephonyIntents.EXTRA_SHOW_PLMN, showPlmn);
             intent.putExtra(TelephonyIntents.EXTRA_PLMN, plmn);
+            SubscriptionManager.putPhoneIdAndSubIdExtra(intent, mPhone.getPhoneId());
             mPhone.getContext().sendStickyBroadcastAsUser(intent, UserHandle.ALL);
         }
 
@@ -1291,7 +1300,7 @@
      */
     private
     boolean isRoamingBetweenOperators(boolean cdmaRoaming, ServiceState s) {
-        String spn = SystemProperties.get(TelephonyProperties.PROPERTY_ICC_OPERATOR_ALPHA, "empty");
+        String spn = getSystemProperty(TelephonyProperties.PROPERTY_ICC_OPERATOR_ALPHA, "empty");
 
         // NOTE: in case of RUIM we should completely ignore the ERI data file and
         // mOperatorAlphaLong is set from RIL_REQUEST_OPERATOR response 0 (alpha ONS)
@@ -1377,7 +1386,7 @@
                 zone = TimeZone.getTimeZone( tzname );
             }
 
-            String iso = SystemProperties.get(TelephonyProperties.PROPERTY_OPERATOR_ISO_COUNTRY);
+            String iso = getSystemProperty(TelephonyProperties.PROPERTY_OPERATOR_ISO_COUNTRY, "");
 
             if (zone == null) {
                 if (mGotCountryCode) {
@@ -1634,7 +1643,7 @@
      */
     String getImsi() {
         // TODO: When RUIM is enabled, IMSI will come from RUIM not build-time props.
-        String operatorNumeric = SystemProperties.get(
+        String operatorNumeric = getSystemProperty(
                 TelephonyProperties.PROPERTY_ICC_OPERATOR_NUMERIC, "");
 
         if (!TextUtils.isEmpty(operatorNumeric) && getCdmaMin() != null) {
@@ -1730,14 +1739,18 @@
         }
     }
 
+    protected UiccCardApplication getUiccCardApplication() {
+            return  mUiccController.getUiccCardApplication(mPhone.getPhoneId(),
+                    UiccController.APP_FAM_3GPP2);
+    }
+
     @Override
     protected void onUpdateIccAvailability() {
         if (mUiccController == null ) {
             return;
         }
 
-        UiccCardApplication newUiccApplication =
-                mUiccController.getUiccCardApplication(UiccController.APP_FAM_3GPP2);
+        UiccCardApplication newUiccApplication = getUiccCardApplication();
 
         if (mUiccApplcation != newUiccApplication) {
             if (mUiccApplcation != null) {
@@ -1810,4 +1823,24 @@
         pw.println(" mRegistrationDeniedReason=" + mRegistrationDeniedReason);
         pw.println(" mCurrentCarrier=" + mCurrentCarrier);
     }
+
+    @Override
+    public void setImsRegistrationState(boolean registered) {
+        log("ImsRegistrationState - registered : " + registered);
+
+        if (mImsRegistrationOnOff && !registered) {
+            if (mAlarmSwitch) {
+                mImsRegistrationOnOff = registered;
+
+                Context context = mPhone.getContext();
+                AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+                am.cancel(mRadioOffIntent);
+                mAlarmSwitch = false;
+
+                sendMessage(obtainMessage(EVENT_CHANGE_IMS_STATE));
+                return;
+            }
+        }
+        mImsRegistrationOnOff = registered;
+    }
 }
diff --git a/src/java/com/android/internal/telephony/cdma/CdmaSubscriptionSourceManager.java b/src/java/com/android/internal/telephony/cdma/CdmaSubscriptionSourceManager.java
index 0388f57..f7b8936 100644
--- a/src/java/com/android/internal/telephony/cdma/CdmaSubscriptionSourceManager.java
+++ b/src/java/com/android/internal/telephony/cdma/CdmaSubscriptionSourceManager.java
@@ -36,6 +36,10 @@
     private static final int EVENT_CDMA_SUBSCRIPTION_SOURCE_CHANGED = 1;
     private static final int EVENT_GET_CDMA_SUBSCRIPTION_SOURCE     = 2;
     private static final int EVENT_RADIO_ON                         = 3;
+    private static final int EVENT_SUBSCRIPTION_STATUS_CHANGED      = 4;
+
+    // To know subscription is activated
+    private static final int SUBSCRIPTION_ACTIVATED                 = 1;
 
     public static final int SUBSCRIPTION_SOURCE_UNKNOWN = -1;
     public static final int SUBSCRIPTION_FROM_RUIM      = 0; /* CDMA subscription from RUIM */
@@ -62,6 +66,7 @@
         mCi.registerForOn(this, EVENT_RADIO_ON, null);
         int subscriptionSource = getDefault(context);
         mCdmaSubscriptionSource.set(subscriptionSource);
+        mCi.registerForSubscriptionStatusChanged(this, EVENT_SUBSCRIPTION_STATUS_CHANGED, null);
     }
 
     /**
@@ -91,6 +96,7 @@
             if (sReferenceCount <= 0) {
                 mCi.unregisterForCdmaSubscriptionChanged(this);
                 mCi.unregisterForOn(this);
+                mCi.unregisterForSubscriptionStatusChanged(this);
                 sInstance = null;
             }
         }
@@ -116,6 +122,24 @@
                 mCi.getCdmaSubscriptionSource(obtainMessage(EVENT_GET_CDMA_SUBSCRIPTION_SOURCE));
             }
             break;
+            case EVENT_SUBSCRIPTION_STATUS_CHANGED: {
+                log("EVENT_SUBSCRIPTION_STATUS_CHANGED");
+                ar = (AsyncResult)msg.obj;
+                if (ar.exception == null) {
+                    int actStatus = ((int[])ar.result)[0];
+                    log("actStatus = " + actStatus);
+                    if (actStatus == SUBSCRIPTION_ACTIVATED) { // Subscription Activated
+                        // In case of multi-SIM, framework should wait for the subscription ready
+                        // to send any request to RIL.  Otherwise it will return failure.
+                        Rlog.v(LOG_TAG,"get Cdma Subscription Source");
+                        mCi.getCdmaSubscriptionSource(
+                                obtainMessage(EVENT_GET_CDMA_SUBSCRIPTION_SOURCE));
+                    }
+                } else {
+                    logw("EVENT_SUBSCRIPTION_STATUS_CHANGED, Exception:" + ar.exception);
+                }
+            }
+            break;
             default:
                 super.handleMessage(msg);
         }
diff --git a/src/java/com/android/internal/telephony/cdma/SmsMessage.java b/src/java/com/android/internal/telephony/cdma/SmsMessage.java
index 62ad42a..5c99a43 100644
--- a/src/java/com/android/internal/telephony/cdma/SmsMessage.java
+++ b/src/java/com/android/internal/telephony/cdma/SmsMessage.java
@@ -1033,8 +1033,9 @@
     /**
      * Returns the list of service category program data, if present.
      * @return a list of CdmaSmsCbProgramData objects, or null if not present
+     * @hide
      */
-    ArrayList<CdmaSmsCbProgramData> getSmsCbProgramData() {
+    public ArrayList<CdmaSmsCbProgramData> getSmsCbProgramData() {
         return mBearerData.serviceCategoryProgramData;
     }
 }
diff --git a/src/java/com/android/internal/telephony/dataconnection/DataCallResponse.java b/src/java/com/android/internal/telephony/dataconnection/DataCallResponse.java
index 75dd327..5d3a556 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DataCallResponse.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DataCallResponse.java
@@ -47,6 +47,7 @@
     public String [] dnses = new String[0];
     public String[] gateways = new String[0];
     public int suggestedRetryTime = -1;
+    public String [] pcscf = new String[0];
 
     /**
      * Class returned by onSetupConnectionCompleted.
@@ -100,6 +101,12 @@
             sb.append(",");
         }
         if (gateways.length > 0) sb.deleteCharAt(sb.length()-1);
+        sb.append("] pcscf=[");
+        for (String addr : pcscf) {
+            sb.append(addr);
+            sb.append(",");
+        }
+        if (pcscf.length > 0) sb.deleteCharAt(sb.length()-1);
         sb.append("]}");
         return sb.toString();
     }
diff --git a/src/java/com/android/internal/telephony/dataconnection/DataConnection.java b/src/java/com/android/internal/telephony/dataconnection/DataConnection.java
index 616fef7..abe0afe 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DataConnection.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DataConnection.java
@@ -58,6 +58,9 @@
 import java.util.List;
 import java.util.concurrent.atomic.AtomicInteger;
 
+import java.net.InetAddress;
+import java.util.Collection;
+
 /**
  * {@hide}
  *
@@ -104,6 +107,8 @@
     // The DCT that's talking to us, we only support one!
     private DcTrackerBase mDct = null;
 
+    protected String[] mPcscfAddr;
+
     /**
      * Used internally for saving connecting parameters.
      */
@@ -291,6 +296,40 @@
         }
     }
 
+    public boolean isIpv4Connected() {
+        boolean ret = false;
+        Collection <InetAddress> addresses = mLinkProperties.getAddresses();
+
+        for (InetAddress addr: addresses) {
+            if (addr instanceof java.net.Inet4Address) {
+                java.net.Inet4Address i4addr = (java.net.Inet4Address) addr;
+                if (!i4addr.isAnyLocalAddress() && !i4addr.isLinkLocalAddress() &&
+                        !i4addr.isLoopbackAddress() && !i4addr.isMulticastAddress()) {
+                    ret = true;
+                    break;
+                }
+            }
+        }
+        return ret;
+    }
+
+    public boolean isIpv6Connected() {
+        boolean ret = false;
+        Collection <InetAddress> addresses = mLinkProperties.getAddresses();
+
+        for (InetAddress addr: addresses) {
+            if (addr instanceof java.net.Inet6Address) {
+                java.net.Inet6Address i6addr = (java.net.Inet6Address) addr;
+                if (!i6addr.isAnyLocalAddress() && !i6addr.isLinkLocalAddress() &&
+                        !i6addr.isLoopbackAddress() && !i6addr.isMulticastAddress()) {
+                    ret = true;
+                    break;
+                }
+            }
+        }
+        return ret;
+    }
+
     UpdateLinkPropertyResult updateLinkProperty(DataCallResponse newState) {
         UpdateLinkPropertyResult result = new UpdateLinkPropertyResult(mLinkProperties);
 
@@ -434,6 +473,7 @@
             response.gateways = new String[0];
             response.suggestedRetryTime =
                     mDcTesterFailBringUpAll.getDcFailBringUp().mSuggestedRetryTime;
+            response.pcscf = new String[0];
 
             Message msg = obtainMessage(EVENT_SETUP_DATA_CONNECTION_DONE, cp);
             AsyncResult.forMessage(msg, response, null);
@@ -645,6 +685,8 @@
         mLastFailCause = DcFailCause.NONE;
         mCid = -1;
 
+        mPcscfAddr = new String[5];
+
         mLinkProperties = new LinkProperties();
         mApnContexts.clear();
         mApnSetting = null;
@@ -690,6 +732,9 @@
         } else {
             if (DBG) log("onSetupConnectionCompleted received DataCallResponse: " + response);
             mCid = response.cid;
+
+            mPcscfAddr = response.pcscf;
+
             result = updateLinkProperty(response).setupResult;
         }
 
diff --git a/src/java/com/android/internal/telephony/dataconnection/DcAsyncChannel.java b/src/java/com/android/internal/telephony/dataconnection/DcAsyncChannel.java
index 178f18d..9e77e1b 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DcAsyncChannel.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DcAsyncChannel.java
@@ -429,4 +429,8 @@
     private void log(String s) {
         android.telephony.Rlog.d(mLogTag, "DataConnectionAc " + s);
     }
+
+    public String[] getPcscfAddr() {
+        return mDc.mPcscfAddr;
+    }
 }
diff --git a/src/java/com/android/internal/telephony/dataconnection/DcController.java b/src/java/com/android/internal/telephony/dataconnection/DcController.java
index dff6777..3d45ced 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DcController.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DcController.java
@@ -236,17 +236,22 @@
                     if (DBG) log("onDataStateChanged: Found ConnId=" + newState.cid
                             + " newState=" + newState.toString());
                     if (newState.active == DATA_CONNECTION_ACTIVE_PH_LINK_INACTIVE) {
-                        DcFailCause failCause = DcFailCause.fromInt(newState.status);
-                        if (DBG) log("onDataStateChanged: inactive failCause=" + failCause);
-                        if (failCause.isRestartRadioFail()) {
-                            if (DBG) log("onDataStateChanged: X restart radio");
-                            mDct.sendRestartRadio();
-                        } else if (failCause.isPermanentFail()) {
-                            if (DBG) log("onDataStateChanged: inactive, add to cleanup list");
+                        if (mDct.mIsCleanupRequired) {
                             apnsToCleanup.addAll(dc.mApnContexts);
+                            mDct.mIsCleanupRequired = false;
                         } else {
-                            if (DBG) log("onDataStateChanged: inactive, add to retry list");
-                            dcsToRetry.add(dc);
+                            DcFailCause failCause = DcFailCause.fromInt(newState.status);
+                            if (DBG) log("onDataStateChanged: inactive failCause=" + failCause);
+                            if (failCause.isRestartRadioFail()) {
+                                if (DBG) log("onDataStateChanged: X restart radio");
+                                mDct.sendRestartRadio();
+                            } else if (failCause.isPermanentFail()) {
+                                if (DBG) log("onDataStateChanged: inactive, add to cleanup list");
+                                apnsToCleanup.addAll(dc.mApnContexts);
+                            } else {
+                                if (DBG) log("onDataStateChanged: inactive, add to retry list");
+                                dcsToRetry.add(dc);
+                            }
                         }
                     } else {
                         // Its active so update the DataConnections link properties
@@ -288,6 +293,7 @@
                                         apnsToCleanup.addAll(dc.mApnContexts);
                                     } else {
                                         if (DBG) log("onDataStateChanged: simple change");
+
                                         for (ApnContext apnContext : dc.mApnContexts) {
                                              mPhone.notifyDataConnection(
                                                  PhoneConstants.REASON_LINK_PROPERTIES_CHANGED,
diff --git a/src/java/com/android/internal/telephony/dataconnection/DcSwitchAsyncChannel.java b/src/java/com/android/internal/telephony/dataconnection/DcSwitchAsyncChannel.java
new file mode 100644
index 0000000..9bd4582
--- /dev/null
+++ b/src/java/com/android/internal/telephony/dataconnection/DcSwitchAsyncChannel.java
@@ -0,0 +1,164 @@
+/*
+* Copyright (C) 2011-2014 MediaTek Inc.
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package com.android.internal.telephony.dataconnection;
+
+import com.android.internal.util.AsyncChannel;
+import com.android.internal.util.Protocol;
+import com.android.internal.telephony.PhoneConstants;
+
+import android.os.Message;
+import android.util.Log;
+
+public class DcSwitchAsyncChannel extends AsyncChannel {
+    private static final boolean DBG = true;
+    private static final boolean VDBG = false;
+    private static final String LOG_TAG = "DcSwitchAsyncChannel";
+
+    private int tagId = 0;
+    private DcSwitchState mDcSwitchState;
+
+    // ***** Event codes for driving the state machine
+    private static final int BASE = Protocol.BASE_DATA_CONNECTION_TRACKER + 0x00002000;
+    static final int REQ_CONNECT = BASE + 0;
+    static final int RSP_CONNECT = BASE + 1;
+    static final int REQ_DISCONNECT = BASE + 2;
+    static final int RSP_DISCONNECT = BASE + 3;
+    static final int REQ_IS_IDLE_STATE = BASE + 4;
+    static final int RSP_IS_IDLE_STATE = BASE + 5;
+    static final int REQ_IS_IDLE_OR_DEACTING_STATE = BASE + 6;
+    static final int RSP_IS_IDLE_OR_DEACTING_STATE = BASE + 7;
+
+    private static final int CMD_TO_STRING_COUNT = RSP_IS_IDLE_OR_DEACTING_STATE - BASE + 1;
+    private static String[] sCmdToString = new String[CMD_TO_STRING_COUNT];
+    static {
+        sCmdToString[REQ_CONNECT - BASE] = "REQ_CONNECT";
+        sCmdToString[RSP_CONNECT - BASE] = "RSP_CONNECT";
+        sCmdToString[REQ_DISCONNECT - BASE] = "REQ_DISCONNECT";
+        sCmdToString[RSP_DISCONNECT - BASE] = "RSP_DISCONNECT";
+        sCmdToString[REQ_IS_IDLE_STATE - BASE] = "REQ_IS_IDLE_STATE";
+        sCmdToString[RSP_IS_IDLE_STATE - BASE] = "RSP_IS_IDLE_STATE";
+        sCmdToString[REQ_IS_IDLE_OR_DEACTING_STATE - BASE] = "REQ_IS_IDLE_OR_DEACTING_STATE";
+        sCmdToString[RSP_IS_IDLE_OR_DEACTING_STATE - BASE] = "RSP_IS_IDLE_OR_DEACTING_STATE";
+    }
+
+    protected static String cmdToString(int cmd) {
+        cmd -= BASE;
+        if ((cmd >= 0) && (cmd < sCmdToString.length)) {
+            return sCmdToString[cmd];
+        } else {
+            return AsyncChannel.cmdToString(cmd + BASE);
+        }
+    }
+
+    public DcSwitchAsyncChannel(DcSwitchState dcSwitchState, int id) {
+        mDcSwitchState = dcSwitchState;
+        tagId = id;
+    }
+
+    public void reqConnect(String type) {
+        sendMessage(REQ_CONNECT, type);
+        if (DBG) log("reqConnect");
+    }
+
+    public int rspConnect(Message response) {
+        int retVal = response.arg1;
+        if (DBG) log("rspConnect=" + retVal);
+        return retVal;
+    }
+
+    public int connectSync(String type) {
+        Message response = sendMessageSynchronously(REQ_CONNECT, type);
+        if ((response != null) && (response.what == RSP_CONNECT)) {
+            return rspConnect(response);
+        } else {
+            log("rspConnect error response=" + response);
+            return PhoneConstants.APN_REQUEST_FAILED;
+        }
+    }
+
+    public void reqDisconnect(String type) {
+        sendMessage(REQ_DISCONNECT, type);
+        if (DBG) log("reqDisconnect");
+    }
+
+    public int rspDisconnect(Message response) {
+        int retVal = response.arg1;
+        if (DBG) log("rspDisconnect=" + retVal);
+        return retVal;
+    }
+
+    public int disconnectSync(String type) {
+        Message response = sendMessageSynchronously(REQ_DISCONNECT, type);
+        if ((response != null) && (response.what == RSP_DISCONNECT)) {
+            return rspDisconnect(response);
+        } else {
+            log("rspDisconnect error response=" + response);
+            return PhoneConstants.APN_REQUEST_FAILED;
+        }
+    }
+
+    public void reqIsIdle() {
+        sendMessage(REQ_IS_IDLE_STATE);
+        if (DBG) log("reqIsIdle");
+    }
+
+    public boolean rspIsIdle(Message response) {
+        boolean retVal = response.arg1 == 1;
+        if (DBG) log("rspIsIdle=" + retVal);
+        return retVal;
+    }
+
+    public boolean isIdleSync() {
+        Message response = sendMessageSynchronously(REQ_IS_IDLE_STATE);
+        if ((response != null) && (response.what == RSP_IS_IDLE_STATE)) {
+            return rspIsIdle(response);
+        } else {
+            log("rspIsIndle error response=" + response);
+            return false;
+        }
+    }
+
+    public void reqIsIdleOrDeacting() {
+        sendMessage(REQ_IS_IDLE_OR_DEACTING_STATE);
+        if (DBG) log("reqIsIdleOrDeacting");
+    }
+
+    public boolean rspIsIdleOrDeacting(Message response) {
+        boolean retVal = response.arg1 == 1;
+        if (DBG) log("rspIsIdleOrDeacting=" + retVal);
+        return retVal;
+    }
+
+    public boolean isIdleOrDeactingSync() {
+        Message response = sendMessageSynchronously(REQ_IS_IDLE_OR_DEACTING_STATE);
+        if ((response != null) && (response.what == RSP_IS_IDLE_OR_DEACTING_STATE)) {
+            return rspIsIdleOrDeacting(response);
+        } else {
+            log("rspIsIndleOrDeacting error response=" + response);
+            return false;
+        }
+    }
+
+    @Override
+    public String toString() {
+        return mDcSwitchState.getName();
+    }
+
+    private void log(String s) {
+        Log.d(LOG_TAG, "[DcSwitchAsyncChannel-" + tagId + "]: " + s);
+    }
+}
diff --git a/src/java/com/android/internal/telephony/dataconnection/DcSwitchState.java b/src/java/com/android/internal/telephony/dataconnection/DcSwitchState.java
new file mode 100644
index 0000000..47740ef
--- /dev/null
+++ b/src/java/com/android/internal/telephony/dataconnection/DcSwitchState.java
@@ -0,0 +1,462 @@
+/*
+* Copyright (C) 2011-2014 MediaTek Inc.
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package com.android.internal.telephony.dataconnection;
+
+import com.android.internal.util.AsyncChannel;
+import com.android.internal.util.Protocol;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.PhoneBase;
+import com.android.internal.telephony.PhoneProxy;
+
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Registrant;
+import android.os.RegistrantList;
+import android.text.TextUtils;
+
+import android.util.Log;
+
+import java.util.HashSet;
+import java.util.Iterator;
+
+public class DcSwitchState extends StateMachine {
+    private static final boolean DBG = true;
+    private static final boolean VDBG = false;
+    private static final String LOG_TAG = "DcSwitchState";
+
+    // ***** Event codes for driving the state machine
+    private static final int BASE = Protocol.BASE_DATA_CONNECTION_TRACKER + 0x00001000;
+    private static final int EVENT_CONNECT = BASE + 0;
+    private static final int EVENT_DISCONNECT = BASE + 1;
+    private static final int EVENT_CLEANUP_ALL = BASE + 2;
+    private static final int EVENT_CONNECTED = BASE + 3;
+    private static final int EVENT_DETACH_DONE = BASE + 4;
+    private static final int EVENT_TO_IDLE_DIRECTLY = BASE + 5;
+    private static final int EVENT_TO_ACTING_DIRECTLY = BASE + 6;
+
+    private int mId;
+    private Phone mPhone;
+    private AsyncChannel mAc;
+    private RegistrantList mIdleRegistrants = new RegistrantList();
+    private HashSet<String> mApnTypes = new HashSet<String>();
+
+    private IdleState     mIdleState = new IdleState();
+    private ActingState   mActingState = new ActingState();
+    private ActedState    mActedState = new ActedState();
+    private DeactingState mDeactingState = new DeactingState();
+    private DefaultState  mDefaultState = new DefaultState();
+
+    protected DcSwitchState(Phone phone, String name, int id) {
+        super(name);
+        if (DBG) log("DcSwitchState constructor E");
+        mPhone = phone;
+        mId = id;
+
+        addState(mDefaultState);
+        addState(mIdleState, mDefaultState);
+        addState(mActingState, mDefaultState);
+        addState(mActedState, mDefaultState);
+        addState(mDeactingState, mDefaultState);
+        setInitialState(mIdleState);
+
+        if (DBG) log("DcSwitchState constructor X");
+    }
+
+    private int setupConnection(String type) {
+        mApnTypes.add(type);
+        log("DcSwitchState:setupConnection type = " + type);
+        return mPhone.enableApnType(type);
+    }
+
+    private int teardownConnection(String type) {
+        mApnTypes.remove(type);
+        if (mApnTypes.isEmpty()) {
+            log("No APN is using, then clean up all");
+            // Since last type is removed from mApnTypes and will not be disabled in requestDataIdle()
+            mPhone.disableApnType(type);
+            requestDataIdle();
+            transitionTo(mDeactingState);
+            return PhoneConstants.APN_REQUEST_STARTED;
+        } else {
+            return mPhone.disableApnType(type);
+        }
+    }
+
+    private void requestDataIdle() {
+        if (DBG) log("requestDataIdle is triggered");
+        Iterator<String> itrType = mApnTypes.iterator();
+        while (itrType.hasNext()) {
+            mPhone.disableApnType(itrType.next());
+        }
+        mApnTypes.clear();
+        PhoneBase pb = (PhoneBase)((PhoneProxy)mPhone).getActivePhone();
+        pb.mCi.setDataAllowed(false, obtainMessage(EVENT_DETACH_DONE));
+    }
+
+    public void notifyDataConnection(int phoneId, String state, String reason,
+            String apnName, String apnType, boolean unavailable) {
+        if (phoneId == mId &&
+                TextUtils.equals(state, PhoneConstants.DataState.CONNECTED.toString())) {
+            sendMessage(obtainMessage(EVENT_CONNECTED));
+        }
+    }
+
+    public void cleanupAllConnection() {
+        sendMessage(obtainMessage(EVENT_CLEANUP_ALL));
+    }
+
+    public void registerForIdle(Handler h, int what, Object obj) {
+        Registrant r = new Registrant(h, what, obj);
+        mIdleRegistrants.add(r);
+    }
+
+    public void unregisterForIdle(Handler h) {
+        mIdleRegistrants.remove(h);
+    }
+
+    public void transitToIdleState() {
+        sendMessage(obtainMessage(EVENT_TO_IDLE_DIRECTLY));
+    }
+
+    public void transitToActingState() {
+        sendMessage(obtainMessage(EVENT_TO_ACTING_DIRECTLY));
+    }
+
+    private class IdleState extends State {
+        @Override
+        public void enter() {
+            mIdleRegistrants.notifyRegistrants();
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            boolean retVal;
+
+            switch (msg.what) {
+                case DcSwitchAsyncChannel.REQ_CONNECT:
+                case EVENT_CONNECT: {
+                    String type = (String)msg.obj;
+                    if (DBG) {
+                        log("IdleState: REQ_CONNECT/EVENT_CONNECT(" +
+                            msg.what + ") type=" + type);
+                    }
+
+                    PhoneBase pb = (PhoneBase)((PhoneProxy)mPhone).getActivePhone();
+                    pb.mCi.setDataAllowed(true, null);
+
+                    int result = setupConnection(type);
+                    if (msg.what == DcSwitchAsyncChannel.REQ_CONNECT) {
+                            mAc.replyToMessage(msg, DcSwitchAsyncChannel.RSP_CONNECT, result);
+                    }
+                    transitionTo(mActingState);
+                    retVal = HANDLED;
+                    break;
+                }
+                case DcSwitchAsyncChannel.REQ_DISCONNECT: {
+                    String type = (String)msg.obj;
+                    if (DBG) {
+                        log("IdleState: DcSwitchAsyncChannel.REQ_DISCONNECT type=" + type);
+                    }
+                    mAc.replyToMessage(msg, DcSwitchAsyncChannel.RSP_DISCONNECT,
+                        PhoneConstants.APN_ALREADY_INACTIVE);
+                    retVal = HANDLED;
+                    break;
+                }
+                case EVENT_CLEANUP_ALL: {
+                    if (DBG) {
+                        log("IdleState: EVENT_CLEANUP_ALL" );
+                    }
+                    requestDataIdle();
+                    retVal = HANDLED;
+                    break;
+                }
+                case EVENT_CONNECTED: {
+                    if (DBG) {
+                        log("IdleState: Receive invalid event EVENT_CONNECTED!");
+                    }
+                    retVal = HANDLED;
+                    break;
+                }
+                default:
+                    if (VDBG) {
+                        log("IdleState: nothandled msg.what=0x" +
+                                Integer.toHexString(msg.what));
+                    }
+                    retVal = NOT_HANDLED;
+                    break;
+            }
+            return retVal;
+        }
+    }
+
+    private class ActingState extends State {
+        @Override
+        public boolean processMessage(Message msg) {
+            boolean retVal;
+
+            switch (msg.what) {
+                case DcSwitchAsyncChannel.REQ_CONNECT:
+                case EVENT_CONNECT: {
+                    String type = (String)msg.obj;
+                    if (DBG) {
+                        log("ActingState: REQ_CONNECT/EVENT_CONNECT(" + msg.what +
+                            ") type=" + type);
+                    }
+                    int result = setupConnection(type);
+                    if (msg.what == DcSwitchAsyncChannel.REQ_CONNECT) {
+                        mAc.replyToMessage(msg, DcSwitchAsyncChannel.RSP_CONNECT, result);
+                    }
+                    retVal = HANDLED;
+                    break;
+                }
+                case DcSwitchAsyncChannel.REQ_DISCONNECT: {
+                    String type = (String)msg.obj;
+                    if (DBG) {
+                        log("ActingState: DcSwitchAsyncChannel.REQ_DISCONNECT type=" + type);
+                    }
+                    int result = teardownConnection(type);
+                    mAc.replyToMessage(msg, DcSwitchAsyncChannel.RSP_DISCONNECT, result);
+                    retVal = HANDLED;
+                    break;
+                }
+                case EVENT_CONNECTED: {
+                    if (DBG) {
+                        log("ActingState: EVENT_CONNECTED");
+                    }
+                    transitionTo(mActedState);
+                    retVal = HANDLED;
+                    break;
+                }
+                case EVENT_CLEANUP_ALL: {
+                    if (DBG) {
+                        log("ActingState: EVENT_CLEANUP_ALL" );
+                    }
+                    requestDataIdle();
+                    transitionTo(mDeactingState);
+                    retVal = HANDLED;
+                    break;
+                }
+                default:
+                    if (VDBG) {
+                        log("ActingState: nothandled msg.what=0x" +
+                                Integer.toHexString(msg.what));
+                    }
+                    retVal = NOT_HANDLED;
+                    break;
+            }
+            return retVal;
+        }
+    }
+
+    private class ActedState extends State {
+        @Override
+        public boolean processMessage(Message msg) {
+            boolean retVal;
+
+            switch (msg.what) {
+                case DcSwitchAsyncChannel.REQ_CONNECT:
+                case EVENT_CONNECT: {
+                    String type = (String)msg.obj;
+                    if (DBG) {
+                        log("ActedState: REQ_CONNECT/EVENT_CONNECT(" + msg.what + ") type=" + type);
+                    }
+                    int result = setupConnection(type);
+                    if (msg.what == DcSwitchAsyncChannel.REQ_CONNECT) {
+                        mAc.replyToMessage(msg, DcSwitchAsyncChannel.RSP_CONNECT, result);
+                    }
+                    retVal = HANDLED;
+                    break;
+                }
+                case DcSwitchAsyncChannel.REQ_DISCONNECT: {
+                    String type = (String)msg.obj;
+                    if (DBG) {
+                        log("ActedState: DcSwitchAsyncChannel.REQ_DISCONNECT type=" + type);
+                    }
+                    int result = teardownConnection(type);
+                    mAc.replyToMessage(msg, DcSwitchAsyncChannel.RSP_DISCONNECT, result);
+                    retVal = HANDLED;
+                    break;
+                }
+                case EVENT_CONNECTED: {
+                    if (DBG) {
+                        log("ActedState: EVENT_CONNECTED");
+                    }
+                    retVal = HANDLED;
+                    break;
+                }
+                case EVENT_CLEANUP_ALL: {
+                    if (DBG) {
+                        log("ActedState: EVENT_CLEANUP_ALL" );
+                    }
+                    requestDataIdle();
+                    transitionTo(mDeactingState);
+                    retVal = HANDLED;
+                    break;
+                }
+                default:
+                    if (VDBG) {
+                        log("ActingState: nothandled msg.what=0x" +
+                                Integer.toHexString(msg.what));
+                    }
+                    retVal = NOT_HANDLED;
+                    break;
+            }
+            return retVal;
+        }
+    }
+
+    private class DeactingState extends State {
+        @Override
+        public boolean processMessage(Message msg) {
+            boolean retVal;
+
+            switch (msg.what) {
+                case DcSwitchAsyncChannel.REQ_CONNECT:
+                case EVENT_CONNECT: {
+                    String type = (String)msg.obj;
+                    if (DBG) {
+                        log("DeactingState: REQ_CONNECT/EVENT_CONNECT(" +
+                            msg.what + ") type=" + type + ", request is defered.");
+                    }
+                    deferMessage(obtainMessage(EVENT_CONNECT, type));
+                    if (msg.what == DcSwitchAsyncChannel.REQ_CONNECT) {
+                        mAc.replyToMessage(msg, DcSwitchAsyncChannel.RSP_CONNECT,
+                                PhoneConstants.APN_REQUEST_STARTED);
+                    }
+                    retVal = HANDLED;
+                    break;
+                }
+                case DcSwitchAsyncChannel.REQ_DISCONNECT: {
+                    String type = (String)msg.obj;
+                    if (DBG) {
+                        log("DeactingState: DcSwitchAsyncChannel.REQ_DISCONNECT type=" + type);
+                    }
+                    mAc.replyToMessage(msg, DcSwitchAsyncChannel.RSP_DISCONNECT,
+                            PhoneConstants.APN_ALREADY_INACTIVE);
+                    retVal = HANDLED;
+                    break;
+                }
+                case EVENT_DETACH_DONE: {
+                    if (DBG) {
+                        log("DeactingState: EVENT_DETACH_DONE");
+                    }
+                    transitionTo(mIdleState);
+                    retVal = HANDLED;
+                    break;
+                }
+                case EVENT_CONNECTED: {
+                    if (DBG) {
+                        log("DeactingState: Receive invalid event EVENT_CONNECTED!");
+                    }
+                    retVal = HANDLED;
+                    break;
+                }
+                case EVENT_CLEANUP_ALL: {
+                    if (DBG) {
+                        log("DeactingState: EVENT_CLEANUP_ALL, already deacting." );
+                    }
+                    retVal = HANDLED;
+                    break;
+                }
+                default:
+                    if (VDBG) {
+                        log("DeactingState: nothandled msg.what=0x" +
+                                Integer.toHexString(msg.what));
+                    }
+                    retVal = NOT_HANDLED;
+                    break;
+            }
+            return retVal;
+        }
+    }
+
+    private class DefaultState extends State {
+        @Override
+        public boolean processMessage(Message msg) {
+            AsyncResult ar;
+
+            switch (msg.what) {
+                case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: {
+                    if (mAc != null) {
+                        if (VDBG) log("Disconnecting to previous connection mAc=" + mAc);
+                        mAc.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED,
+                                AsyncChannel.STATUS_FULL_CONNECTION_REFUSED_ALREADY_CONNECTED);
+                    } else {
+                        mAc = new AsyncChannel();
+                        mAc.connected(null, getHandler(), msg.replyTo);
+                        if (VDBG) log("DcDefaultState: FULL_CONNECTION reply connected");
+                        mAc.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED,
+                                AsyncChannel.STATUS_SUCCESSFUL, mId, "hi");
+                    }
+                    break;
+                }
+                case AsyncChannel.CMD_CHANNEL_DISCONNECT: {
+                    if (VDBG) log("CMD_CHANNEL_DISCONNECT");
+                    mAc.disconnect();
+                    break;
+                }
+                case AsyncChannel.CMD_CHANNEL_DISCONNECTED: {
+                    if (VDBG) log("CMD_CHANNEL_DISCONNECTED");
+                    mAc = null;
+                    break;
+                }
+                case DcSwitchAsyncChannel.REQ_IS_IDLE_STATE: {
+                    boolean val = getCurrentState() == mIdleState;
+                    if (VDBG) log("REQ_IS_IDLE_STATE  isIdle=" + val);
+                    mAc.replyToMessage(msg, DcSwitchAsyncChannel.RSP_IS_IDLE_STATE, val ? 1 : 0);
+                    break;
+                }
+                case DcSwitchAsyncChannel.REQ_IS_IDLE_OR_DEACTING_STATE: {
+                    boolean val = (getCurrentState() == mIdleState || getCurrentState() == mDeactingState);
+                    if (VDBG) log("REQ_IS_IDLE_OR_DEACTING_STATE  isIdleDeacting=" + val);
+                    mAc.replyToMessage(msg, DcSwitchAsyncChannel.RSP_IS_IDLE_OR_DEACTING_STATE, val ? 1 : 0);
+                    break;
+                }
+                case EVENT_TO_ACTING_DIRECTLY: {
+                    log("Just transit to Acting state");
+                    transitionTo(mActingState);
+                    break;
+                }
+                case EVENT_TO_IDLE_DIRECTLY: {
+                    log("Just transit to Idle state");
+                    Iterator<String> itrType = mApnTypes.iterator();
+                    while (itrType.hasNext()) {
+                        mPhone.disableApnType(itrType.next());
+                    }
+                    mApnTypes.clear();
+                    transitionTo(mIdleState);
+                }
+                default:
+                    if (DBG) {
+                        log("DefaultState: shouldn't happen but ignore msg.what=0x" +
+                                Integer.toHexString(msg.what));
+                    }
+                    break;
+            }
+            return HANDLED;
+        }
+    }
+
+    protected void log(String s) {
+        Log.d(LOG_TAG, "[" + getName() + "] " + s);
+    }
+}
diff --git a/src/java/com/android/internal/telephony/dataconnection/DcTracker.java b/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
index 1144737..db299cb 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
@@ -38,9 +38,11 @@
 import android.net.Uri;
 import android.os.AsyncResult;
 import android.os.Build;
+import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
 import android.os.Messenger;
+import android.os.RegistrantList;
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
@@ -49,12 +51,14 @@
 import android.telephony.CellLocation;
 import android.telephony.ServiceState;
 import android.telephony.TelephonyManager;
+import android.telephony.SubscriptionManager;
 import android.telephony.cdma.CdmaCellLocation;
 import android.telephony.gsm.GsmCellLocation;
 import android.text.TextUtils;
 import android.util.EventLog;
 import android.telephony.Rlog;
 
+import com.android.internal.telephony.cdma.CDMALTEPhone;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneBase;
 import com.android.internal.telephony.DctConstants;
@@ -73,6 +77,9 @@
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.HashMap;
 
+import android.provider.Settings;
+
+import com.android.internal.telephony.ServiceStateTracker;
 /**
  * {@hide}
  */
@@ -80,6 +87,16 @@
     protected final String LOG_TAG = "DCT";
 
     /**
+     * List of messages that are waiting to be posted, when data call disconnect
+     * is complete
+     */
+    private ArrayList<Message> mDisconnectAllCompleteMsgList = new ArrayList<Message>();
+
+    private RegistrantList mAllDataDisconnectedRegistrants = new RegistrantList();
+
+    protected int mDisconnectPendingCount = 0;
+
+    /**
      * Handles changes to the APN db.
      */
     private class ApnChangeObserver extends ContentObserver {
@@ -122,34 +139,18 @@
     private NetworkFactory mNetworkFactory;
     private NetworkCapabilities mNetworkFilter;
 
+    public boolean mImsRegistrationState = false;
+    private ApnContext mWaitCleanUpApnContext = null;
+    private boolean mDeregistrationAlarmState = false;
+    private PendingIntent mImsDeregistrationDelayIntent = null;
+
     //***** Constructor
     public DcTracker(PhoneBase p) {
         super(p);
         if (DBG) log("GsmDCT.constructor");
-        p.mCi.registerForAvailable (this, DctConstants.EVENT_RADIO_AVAILABLE, null);
-        p.mCi.registerForOffOrNotAvailable(this, DctConstants.EVENT_RADIO_OFF_OR_NOT_AVAILABLE,
-                null);
-
-        p.getCallTracker().registerForVoiceCallEnded (this, DctConstants.EVENT_VOICE_CALL_ENDED,
-                null);
-        p.getCallTracker().registerForVoiceCallStarted (this, DctConstants.EVENT_VOICE_CALL_STARTED,
-                null);
-        p.getServiceStateTracker().registerForDataConnectionAttached(this,
-                DctConstants.EVENT_DATA_CONNECTION_ATTACHED, null);
-        p.getServiceStateTracker().registerForDataConnectionDetached(this,
-                DctConstants.EVENT_DATA_CONNECTION_DETACHED, null);
-        p.getServiceStateTracker().registerForRoamingOn(this, DctConstants.EVENT_ROAMING_ON, null);
-        p.getServiceStateTracker().registerForRoamingOff(this, DctConstants.EVENT_ROAMING_OFF,
-                null);
-        p.getServiceStateTracker().registerForPsRestrictedEnabled(this,
-                DctConstants.EVENT_PS_RESTRICT_ENABLED, null);
-        p.getServiceStateTracker().registerForPsRestrictedDisabled(this,
-                DctConstants.EVENT_PS_RESTRICT_DISABLED, null);
-        p.getServiceStateTracker().registerForDataRegStateOrRatChanged(this,
-                DctConstants.EVENT_DATA_RAT_CHANGED, null);
 
         mDataConnectionTracker = this;
-
+        update();
         mApnObserver = new ApnChangeObserver();
         p.getContext().getContentResolver().registerContentObserver(
                 Telephony.Carriers.CONTENT_URI, true, mApnObserver);
@@ -179,6 +180,33 @@
         cm.registerNetworkFactory(mNetworkFactoryMessenger, "Telephony");
     }
 
+    protected void registerForAllEvents() {
+        mPhone.mCi.registerForAvailable(this, DctConstants.EVENT_RADIO_AVAILABLE, null);
+        mPhone.mCi.registerForOffOrNotAvailable(this,
+               DctConstants.EVENT_RADIO_OFF_OR_NOT_AVAILABLE, null);
+        mPhone.mCi.registerForDataNetworkStateChanged(this,
+               DctConstants.EVENT_DATA_STATE_CHANGED, null);
+        mPhone.getCallTracker().registerForVoiceCallEnded (this,
+               DctConstants.EVENT_VOICE_CALL_ENDED, null);
+        mPhone.getCallTracker().registerForVoiceCallStarted (this,
+               DctConstants.EVENT_VOICE_CALL_STARTED, null);
+        mPhone.getServiceStateTracker().registerForDataConnectionAttached(this,
+               DctConstants.EVENT_DATA_CONNECTION_ATTACHED, null);
+        mPhone.getServiceStateTracker().registerForDataConnectionDetached(this,
+               DctConstants.EVENT_DATA_CONNECTION_DETACHED, null);
+        mPhone.getServiceStateTracker().registerForRoamingOn(this,
+               DctConstants.EVENT_ROAMING_ON, null);
+        mPhone.getServiceStateTracker().registerForRoamingOff(this,
+               DctConstants.EVENT_ROAMING_OFF, null);
+        mPhone.getServiceStateTracker().registerForPsRestrictedEnabled(this,
+                DctConstants.EVENT_PS_RESTRICT_ENABLED, null);
+        mPhone.getServiceStateTracker().registerForPsRestrictedDisabled(this,
+                DctConstants.EVENT_PS_RESTRICT_DISABLED, null);
+     //   SubscriptionManager.registerForDdsSwitch(this,
+     //          DctConstants.EVENT_CLEAN_UP_ALL_CONNECTIONS, null);
+        mPhone.getServiceStateTracker().registerForDataRegStateOrRatChanged(this,
+                DctConstants.EVENT_DATA_RAT_CHANGED, null);
+    }
     @Override
     public void dispose() {
         if (DBG) log("GsmDCT.dispose");
@@ -192,11 +220,21 @@
 
         super.dispose();
 
-        //Unregister for all events
+        mPhone.getContext().getContentResolver().unregisterContentObserver(mApnObserver);
+        mApnContexts.clear();
+        mPrioritySortedApnContexts.clear();
+
+        destroyDataConnections();
+    }
+    protected void unregisterForAllEvents() {
+         //Unregister for all events
         mPhone.mCi.unregisterForAvailable(this);
         mPhone.mCi.unregisterForOffOrNotAvailable(this);
         IccRecords r = mIccRecords.get();
-        if (r != null) { r.unregisterForRecordsLoaded(this);}
+        if (r != null) {
+            r.unregisterForRecordsLoaded(this);
+            mIccRecords.set(null);
+        }
         mPhone.mCi.unregisterForDataNetworkStateChanged(this);
         mPhone.getCallTracker().unregisterForVoiceCallEnded(this);
         mPhone.getCallTracker().unregisterForVoiceCallStarted(this);
@@ -206,12 +244,7 @@
         mPhone.getServiceStateTracker().unregisterForRoamingOff(this);
         mPhone.getServiceStateTracker().unregisterForPsRestrictedEnabled(this);
         mPhone.getServiceStateTracker().unregisterForPsRestrictedDisabled(this);
-
-        mPhone.getContext().getContentResolver().unregisterContentObserver(mApnObserver);
-        mApnContexts.clear();
-        mPrioritySortedApnContexts.clear();
-
-        destroyDataConnections();
+        //SubscriptionManager.unregisterForDdsSwitch(this);
     }
 
     private class TelephonyNetworkFactory extends NetworkFactory {
@@ -365,6 +398,25 @@
         if(DBG) log("finalize");
     }
 
+    protected void supplyMessenger() {
+       // Supply the data connection tracker messenger only if
+       // this is corresponding to the current DDS.
+       if (!isActiveDataSubscription()) {
+           return;
+       }
+
+        ConnectivityManager cm = (ConnectivityManager)mPhone.getContext().getSystemService(
+                Context.CONNECTIVITY_SERVICE);
+        cm.supplyMessenger(ConnectivityManager.TYPE_MOBILE, new Messenger(this));
+        cm.supplyMessenger(ConnectivityManager.TYPE_MOBILE_MMS, new Messenger(this));
+        cm.supplyMessenger(ConnectivityManager.TYPE_MOBILE_SUPL, new Messenger(this));
+        cm.supplyMessenger(ConnectivityManager.TYPE_MOBILE_DUN, new Messenger(this));
+        cm.supplyMessenger(ConnectivityManager.TYPE_MOBILE_HIPRI, new Messenger(this));
+        cm.supplyMessenger(ConnectivityManager.TYPE_MOBILE_FOTA, new Messenger(this));
+        cm.supplyMessenger(ConnectivityManager.TYPE_MOBILE_IMS, new Messenger(this));
+        cm.supplyMessenger(ConnectivityManager.TYPE_MOBILE_CBS, new Messenger(this));
+    }
+
     private ApnContext addApnContext(String type, NetworkConfig networkConfig) {
         ApnContext apnContext = new ApnContext(mPhone.getContext(), type, LOG_TAG, networkConfig,
                 this);
@@ -525,7 +577,15 @@
      */
     @Override
     public synchronized int enableApnType(String apnType) {
-        ApnContext apnContext = mApnContexts.get(apnType);
+       ApnContext apnContext = mApnContexts.get(apnType);
+       if (!isActiveDataSubscription()) {
+           if(apnType.equals(PhoneConstants.APN_TYPE_DEFAULT)) {
+               log("enableApnType(): NOT active DDS, apnContext setEnabled as true for default");
+               apnContext.setEnabled(true);
+           }
+           return PhoneConstants.APN_REQUEST_FAILED;
+       }
+
         if (apnContext == null || !isApnTypeAvailable(apnType)) {
             if (DBG) log("enableApnType: " + apnType + " is type not available");
             return PhoneConstants.APN_TYPE_NOT_AVAILABLE;
@@ -550,6 +610,13 @@
     public synchronized int disableApnType(String type) {
         if (DBG) log("disableApnType:" + type);
         ApnContext apnContext = mApnContexts.get(type);
+       if (!isActiveDataSubscription()) {
+           if (type.equals(PhoneConstants.APN_TYPE_DEFAULT)) {
+               log("disableApnType(): NOT active DDS, apnContext setEnabled as false for default");
+               apnContext.setEnabled(false);
+           }
+           return PhoneConstants.APN_REQUEST_FAILED;
+       }
 
         if (apnContext != null) {
             setEnabled(apnTypeToId(type), false);
@@ -606,6 +673,23 @@
         }
     }
 
+    public boolean getAnyDataEnabled(boolean checkUserDataEnabled) {
+        synchronized (mDataEnabledLock) {
+            if (!(mInternalDataEnabled && (!checkUserDataEnabled || mUserDataEnabled)
+                        && (!checkUserDataEnabled || sPolicyDataEnabled)))
+                return false;
+
+            for (ApnContext apnContext : mApnContexts.values()) {
+                // Make sure we dont have a context that going down
+                // and is explicitly disabled.
+                if (isDataAllowed(apnContext)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
     private boolean isDataAllowed(ApnContext apnContext) {
         return apnContext.isReady() && isDataAllowed();
     }
@@ -655,6 +739,14 @@
         IccRecords r = mIccRecords.get();
         boolean recordsLoaded = (r != null) ? r.getRecordsLoaded() : false;
 
+        //FIXME always attach
+        boolean psRestricted = mIsPsRestricted;
+        int phoneNum = TelephonyManager.getDefault().getPhoneCount();
+        if (phoneNum > 1) {
+            attachedState = true;
+            psRestricted = false;
+        }
+
         boolean allowed =
                     (attachedState || mAutoAttachOnCreation) &&
                     recordsLoaded &&
@@ -662,7 +754,8 @@
                      mPhone.getServiceStateTracker().isConcurrentVoiceAndDataAllowed()) &&
                     internalDataEnabled &&
                     (!mPhone.getServiceState().getRoaming() || getDataOnRoamingEnabled()) &&
-                    !mIsPsRestricted &&
+                    //!mIsPsRestricted &&
+                    !psRestricted &&
                     desiredPowerState;
         if (!allowed && DBG) {
             String reason = "";
@@ -720,9 +813,12 @@
         }
 
         boolean desiredPowerState = mPhone.getServiceStateTracker().getDesiredPowerState();
+        boolean checkUserDataEnabled =
+                    !(apnContext.getApnType().equals(PhoneConstants.APN_TYPE_IMS));
 
         if (apnContext.isConnectable() &&
-                isDataAllowed(apnContext) && getAnyDataEnabled() && !isEmergency()) {
+                isDataAllowed(apnContext) &&
+                getAnyDataEnabled(checkUserDataEnabled) && !isEmergency()) {
             if (apnContext.getState() == DctConstants.State.FAILED) {
                 if (DBG) log("trySetupData: make a FAILED ApnContext IDLE so its reusable");
                 apnContext.setState(DctConstants.State.IDLE);
@@ -798,12 +894,25 @@
     protected boolean cleanUpAllConnections(boolean tearDown, String reason) {
         if (DBG) log("cleanUpAllConnections: tearDown=" + tearDown + " reason=" + reason);
         boolean didDisconnect = false;
+        boolean specificdisable = false;
+
+        if (!TextUtils.isEmpty(reason)) {
+            specificdisable = reason.equals(Phone.REASON_DATA_SPECIFIC_DISABLED);
+        }
 
         for (ApnContext apnContext : mApnContexts.values()) {
             if (apnContext.isDisconnected() == false) didDisconnect = true;
-            // TODO - only do cleanup if not disconnected
-            apnContext.setReason(reason);
-            cleanUpConnection(tearDown, apnContext);
+            if (specificdisable) {
+                if (!apnContext.getApnType().equals(PhoneConstants.APN_TYPE_IMS)) {
+                    if (DBG) log("ApnConextType: " + apnContext.getApnType());
+                    apnContext.setReason(reason);
+                    cleanUpConnection(tearDown, apnContext);
+                }
+            } else {
+                // TODO - only do cleanup if not disconnected
+                apnContext.setReason(reason);
+                cleanUpConnection(tearDown, apnContext);
+            }
         }
 
         stopNetStatPoll();
@@ -811,6 +920,13 @@
 
         // TODO: Do we need mRequestedApnType?
         mRequestedApnType = PhoneConstants.APN_TYPE_DEFAULT;
+
+        log("cleanUpConnection: mDisconnectPendingCount = " + mDisconnectPendingCount);
+        if (tearDown && mDisconnectPendingCount == 0) {
+            notifyDataDisconnectComplete();
+            notifyAllDataDisconnected();
+        }
+
         return didDisconnect;
     }
 
@@ -829,7 +945,7 @@
         cleanUpAllConnections(true, cause);
     }
 
-    private void cleanUpConnection(boolean tearDown, ApnContext apnContext) {
+    protected void cleanUpConnection(boolean tearDown, ApnContext apnContext) {
 
         if (apnContext == null) {
             if (DBG) log("cleanUpConnection: apn context is null");
@@ -858,6 +974,8 @@
                     if (apnContext.getState() != DctConstants.State.DISCONNECTING) {
                         boolean disconnectAll = false;
                         if (PhoneConstants.APN_TYPE_DUN.equals(apnContext.getApnType())) {
+                            // CAF_MSIM is this below condition required.
+                            // if (PhoneConstants.APN_TYPE_DUN.equals(PhoneConstants.APN_TYPE_DEFAULT)) {
                             ApnSetting dunSetting = fetchDunApn();
                             if (dunSetting != null &&
                                     dunSetting.equals(apnContext.getApnSetting())) {
@@ -880,6 +998,7 @@
                                 .tearDown(apnContext, apnContext.getReason(), msg);
                         }
                         apnContext.setState(DctConstants.State.DISCONNECTING);
+                        mDisconnectPendingCount++;
                     }
                 } else {
                     // apn is connected but no reference to dcac.
@@ -1288,6 +1407,10 @@
         intent.putExtra(INTENT_RECONNECT_ALARM_EXTRA_REASON, apnContext.getReason());
         intent.putExtra(INTENT_RECONNECT_ALARM_EXTRA_TYPE, apnType);
 
+        // Get current sub id.
+        long subId = SubscriptionManager.getDefaultDataSubId();
+        intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId);
+
         if (DBG) {
             log("startAlarmForReconnect: delay=" + delay + " action=" + intent.getAction()
                     + " apn=" + apnContext);
@@ -1425,8 +1548,10 @@
         ApnContext potentialApnCtx = null;
         for (ApnContext curApnCtx : mApnContexts.values()) {
             DcAsyncChannel curDcac = curApnCtx.getDcAc();
+            log("curDcac: " + curDcac);
             if (curDcac != null) {
                 ApnSetting apnSetting = curApnCtx.getApnSetting();
+                log("apnSetting: " + apnSetting);
                 if (dunSetting != null) {
                     if (dunSetting.equals(apnSetting)) {
                         switch (curApnCtx.getState()) {
@@ -1760,6 +1885,14 @@
         if (handleError) {
             onDataSetupCompleteError(ar);
         }
+
+        /* If flag is set to false after SETUP_DATA_CALL is invoked, we need
+         * to clean data connections.
+         */
+        if (!mInternalDataEnabled) {
+            cleanUpAllConnections(null);
+        }
+
     }
 
     /**
@@ -1846,6 +1979,16 @@
                 // Radio will be turned off. No need to retry data setup
                 apnContext.setApnSetting(null);
                 apnContext.setDataConnectionAc(null);
+
+                // Need to notify disconnect as well, in the case of switching Airplane mode.
+                // Otherwise, it would cause 30s delayed to turn on Airplane mode.
+                if (mDisconnectPendingCount > 0)
+                    mDisconnectPendingCount--;
+
+                if (mDisconnectPendingCount == 0) {
+                    notifyDataDisconnectComplete();
+                    notifyAllDataDisconnected();
+                }
                 return;
             }
         }
@@ -1875,6 +2018,15 @@
                 if(DBG) log("onDisconnectDone: not retrying");
             }
         }
+
+        if (mDisconnectPendingCount > 0)
+            mDisconnectPendingCount--;
+
+        if (mDisconnectPendingCount == 0) {
+            notifyDataDisconnectComplete();
+            notifyAllDataDisconnected();
+        }
+
     }
 
     /**
@@ -2218,6 +2370,11 @@
             return;
         }
 
+        if (!isActiveDataSubscription()) {
+            loge("Ignore msgs since phone is not the current DDS");
+            return;
+        }
+
         switch (msg.what) {
             case DctConstants.EVENT_RECORDS_LOADED:
                 onRecordsLoaded();
@@ -2301,6 +2458,18 @@
                     super.handleMessage(msg);
                 }
                 break;
+            case DctConstants.EVENT_SET_INTERNAL_DATA_ENABLE:
+                boolean enabled = (msg.arg1 == DctConstants.ENABLED) ? true : false;
+                onSetInternalDataEnabled(enabled, (Message) msg.obj);
+                break;
+
+            case DctConstants.EVENT_CLEAN_UP_ALL_CONNECTIONS:
+                Message mCause = obtainMessage(DctConstants.EVENT_CLEAN_UP_ALL_CONNECTIONS, null);
+                if ((msg.obj != null) && (msg.obj instanceof String)) {
+                    mCause.obj = msg.obj;
+                }
+                super.handleMessage(mCause);
+                break;
 
             case DctConstants.EVENT_DATA_RAT_CHANGED:
                 //May new Network allow setupData, so try it here
@@ -2344,13 +2513,18 @@
         return cid;
     }
 
+    private IccRecords getUiccRecords(int appFamily) {
+        return mUiccController.getIccRecords(mPhone.getPhoneId(), appFamily);
+    }
+
+
     @Override
     protected void onUpdateIcc() {
         if (mUiccController == null ) {
             return;
         }
 
-        IccRecords newIccRecords = mUiccController.getIccRecords(UiccController.APP_FAM_3GPP);
+        IccRecords newIccRecords = getUiccRecords(UiccController.APP_FAM_3GPP);
 
         IccRecords r = mIccRecords.get();
         if (r != newIccRecords) {
@@ -2368,14 +2542,157 @@
         }
     }
 
+    // setAsCurrentDataConnectionTracker
+    public void update() {
+        log("update sub = " + mPhone.getSubId());
+        if (isActiveDataSubscription()) {
+            log("update(): Active DDS, register for all events now!");
+            registerForAllEvents();
+            onUpdateIcc();
+
+            mUserDataEnabled = Settings.Global.getInt(mPhone.getContext().getContentResolver(),
+                    Settings.Global.MOBILE_DATA, 1) == 1;
+
+            if (mPhone instanceof CDMALTEPhone) {
+                ((CDMALTEPhone)mPhone).updateCurrentCarrierInProvider();
+                supplyMessenger();
+            } else if (mPhone instanceof GSMPhone) {
+                ((GSMPhone)mPhone).updateCurrentCarrierInProvider();
+                supplyMessenger();
+            } else {
+                log("Phone object is not MultiSim. This should not hit!!!!");
+            }
+
+        } else {
+            unregisterForAllEvents();
+            log("update(): NOT the active DDS, unregister for all events!");
+        }
+    }
+
+    @Override
+    public void cleanUpAllConnections(String cause) {
+        cleanUpAllConnections(cause, null);
+    }
+
+    public void updateRecords() {
+        if (isActiveDataSubscription()) {
+            onUpdateIcc();
+        }
+    }
+
+    public void cleanUpAllConnections(String cause, Message disconnectAllCompleteMsg) {
+        log("cleanUpAllConnections");
+        if (disconnectAllCompleteMsg != null) {
+            mDisconnectAllCompleteMsgList.add(disconnectAllCompleteMsg);
+        }
+
+        Message msg = obtainMessage(DctConstants.EVENT_CLEAN_UP_ALL_CONNECTIONS);
+        msg.obj = cause;
+        sendMessage(msg);
+    }
+
+    protected void notifyDataDisconnectComplete() {
+        log("notifyDataDisconnectComplete");
+        for (Message m: mDisconnectAllCompleteMsgList) {
+            m.sendToTarget();
+        }
+        mDisconnectAllCompleteMsgList.clear();
+    }
+
+
+    protected void notifyAllDataDisconnected() {
+        sEnableFailFastRefCounter = 0;
+        mFailFast = false;
+        mAllDataDisconnectedRegistrants.notifyRegistrants();
+    }
+
+    public void registerForAllDataDisconnected(Handler h, int what, Object obj) {
+        mAllDataDisconnectedRegistrants.addUnique(h, what, obj);
+
+        if (isDisconnected()) {
+            log("notify All Data Disconnected");
+            notifyAllDataDisconnected();
+        }
+    }
+
+    public void unregisterForAllDataDisconnected(Handler h) {
+        mAllDataDisconnectedRegistrants.remove(h);
+    }
+
+
+    @Override
+    protected void onSetInternalDataEnabled(boolean enable) {
+        onSetInternalDataEnabled(enable, null);
+    }
+
+    protected void onSetInternalDataEnabled(boolean enabled, Message onCompleteMsg) {
+        boolean sendOnComplete = true;
+
+        synchronized (mDataEnabledLock) {
+            mInternalDataEnabled = enabled;
+            if (enabled) {
+                log("onSetInternalDataEnabled: changed to enabled, try to setup data call");
+                onTrySetupData(Phone.REASON_DATA_ENABLED);
+            } else {
+                sendOnComplete = false;
+                log("onSetInternalDataEnabled: changed to disabled, cleanUpAllConnections");
+                cleanUpAllConnections(null, onCompleteMsg);
+            }
+        }
+
+        if (sendOnComplete) {
+            if (onCompleteMsg != null) {
+                onCompleteMsg.sendToTarget();
+            }
+        }
+    }
+
+    public boolean setInternalDataEnabledFlag(boolean enable) {
+        if (DBG)
+            log("setInternalDataEnabledFlag(" + enable + ")");
+
+        if (mInternalDataEnabled != enable) {
+            mInternalDataEnabled = enable;
+        }
+        return true;
+    }
+
+    @Override
+    public boolean setInternalDataEnabled(boolean enable) {
+        return setInternalDataEnabled(enable, null);
+    }
+
+    public boolean setInternalDataEnabled(boolean enable, Message onCompleteMsg) {
+        if (DBG)
+            log("setInternalDataEnabled(" + enable + ")");
+
+        Message msg = obtainMessage(DctConstants.EVENT_SET_INTERNAL_DATA_ENABLE, onCompleteMsg);
+        msg.arg1 = (enable ? DctConstants.ENABLED : DctConstants.DISABLED);
+        sendMessage(msg);
+        return true;
+    }
+
+    /** Returns true if this is current DDS. */
+    protected boolean isActiveDataSubscription() {
+        // FIXME This should have code like
+        // return (mPhone.getSubId() == SubscriptionManager.getDefaultDataSubId());
+        return true;
+    }
+
+    public void setDataAllowed(boolean enable, Message response) {
+         mIsCleanupRequired = !enable;
+         mPhone.mCi.setDataAllowed(enable, response);
+         mInternalDataEnabled = enable;
+    }
+
     @Override
     protected void log(String s) {
-        Rlog.d(LOG_TAG, s);
+        Rlog.d(LOG_TAG, "[" + mPhone.getPhoneId() + "]" + s);
     }
 
     @Override
     protected void loge(String s) {
-        Rlog.e(LOG_TAG, s);
+        Rlog.e(LOG_TAG, "[" + mPhone.getPhoneId() + "]" + s);
     }
 
     @Override
@@ -2389,4 +2706,42 @@
         pw.println(" mDataConnectionAsyncChannels=%s\n" + mDataConnectionAcHashMap);
         pw.println(" mAttached=" + mAttached.get());
     }
+
+    @Override
+    public String[] getPcscfAddress() {
+        log("getPcscfAddress()");
+
+        ApnContext apnContext = mApnContexts.get(PhoneConstants.APN_TYPE_IMS);
+
+        if (apnContext == null) {
+            log("apnContext is null, return null");
+            return null;
+        }
+
+        DcAsyncChannel dcac = apnContext.getDcAc();
+        String[] result = null;
+
+        if (dcac != null) {
+            result = dcac.getPcscfAddr();
+
+            for (int i = 0; i < result.length; i++) {
+                log("Pcscf[" + i + "]: " + result[i]);
+            }
+            return result;
+        }
+        return null;
+    }
+
+    @Override
+    public void setImsRegistrationState(boolean registered) {
+        log("setImsRegistrationState - mImsRegistrationState(before): "+ mImsRegistrationState
+                + ", registered(current) : " + registered);
+
+        if (mPhone == null) return;
+
+        ServiceStateTracker sst = mPhone.getServiceStateTracker();
+        if (sst == null) return;
+
+        sst.setImsRegistrationState(registered);
+    }
 }
diff --git a/src/java/com/android/internal/telephony/dataconnection/DcTrackerBase.java b/src/java/com/android/internal/telephony/dataconnection/DcTrackerBase.java
index b3f6d3f..a4e7c7c 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DcTrackerBase.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DcTrackerBase.java
@@ -43,6 +43,7 @@
 import android.preference.PreferenceManager;
 import android.provider.Settings;
 import android.provider.Settings.SettingNotFoundException;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.EventLog;
@@ -81,6 +82,7 @@
     protected static final boolean VDBG_STALL = true; // STOPSHIP if true
     protected static final boolean RADIO_TESTS = false;
 
+    static boolean mIsCleanupRequired = false;
     /**
      * Constants for the data connection activity:
      * physical link down/up
@@ -469,6 +471,17 @@
         String reason = intent.getStringExtra(INTENT_RECONNECT_ALARM_EXTRA_REASON);
         String apnType = intent.getStringExtra(INTENT_RECONNECT_ALARM_EXTRA_TYPE);
 
+        long phoneSubId = mPhone.getSubId();
+        long currSubId = intent.getLongExtra(PhoneConstants.SUBSCRIPTION_KEY,
+                SubscriptionManager.INVALID_SUB_ID);
+        log("onActionIntentReconnectAlarm: currSubId = " + currSubId + " phoneSubId=" + phoneSubId);
+
+        // Stop reconnect if not current subId is not correct.
+        if ((currSubId == SubscriptionManager.INVALID_SUB_ID) || (currSubId != phoneSubId)) {
+            log("receive ReconnectAlarm but subId incorrect, ignore");
+            return;
+        }
+
         ApnContext apnContext = mApnContexts.get(apnType);
 
         if (DBG) {
@@ -530,8 +543,8 @@
      */
     protected DcTrackerBase(PhoneBase phone) {
         super();
-        if (DBG) log("DCT.constructor");
         mPhone = phone;
+        if (DBG) log("DCT.constructor");
         mResolver = mPhone.getContext().getContentResolver();
         mUiccController = UiccController.getInstance();
         mUiccController.registerForIccChanged(this, DctConstants.EVENT_ICC_CHANGED, null);
@@ -727,6 +740,9 @@
     public abstract boolean isDataPossible(String apnType);
     protected abstract void onUpdateIcc();
     protected abstract void completeConnection(ApnContext apnContext);
+    public abstract void setDataAllowed(boolean enable, Message response);
+    public abstract String[] getPcscfAddress();
+    public abstract void setImsRegistrationState(boolean registered);
 
     @Override
     public void handleMessage(Message msg) {
@@ -1345,7 +1361,7 @@
                     if (!prevEnabled) {
                         onTrySetupData(Phone.REASON_DATA_ENABLED);
                     } else {
-                        onCleanUpAllConnections(Phone.REASON_DATA_DISABLED);
+                        onCleanUpAllConnections(Phone.REASON_DATA_SPECIFIC_DISABLED);
                     }
                 }
             }
@@ -1364,7 +1380,7 @@
                     if (!prevEnabled) {
                         onTrySetupData(Phone.REASON_DATA_ENABLED);
                     } else {
-                        onCleanUpAllConnections(Phone.REASON_DATA_DISABLED);
+                        onCleanUpAllConnections(Phone.REASON_DATA_SPECIFIC_DISABLED);
                     }
                 }
             }
diff --git a/src/java/com/android/internal/telephony/dataconnection/DctController.java b/src/java/com/android/internal/telephony/dataconnection/DctController.java
new file mode 100644
index 0000000..e5631eb
--- /dev/null
+++ b/src/java/com/android/internal/telephony/dataconnection/DctController.java
@@ -0,0 +1,472 @@
+/*
+ * Copyright (C) 2014 MediaTek Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony.dataconnection;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.os.Message;
+import android.os.AsyncResult;
+import android.os.SystemProperties;
+import android.telephony.ServiceState;
+import android.telephony.TelephonyManager;
+import android.telephony.SubscriptionManager;
+
+import com.android.internal.telephony.IccCardConstants;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.PhoneBase;
+import com.android.internal.telephony.PhoneProxy;
+import com.android.internal.telephony.TelephonyIntents;
+import com.android.internal.util.AsyncChannel;
+import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.TelephonyProperties;
+import com.android.internal.telephony.DefaultPhoneNotifier;
+import com.android.internal.telephony.SubscriptionController;
+
+import android.util.Log;
+import java.util.HashSet;
+import java.util.Iterator;
+import android.os.Registrant;
+import android.os.RegistrantList;
+import android.telephony.Rlog;
+
+public class DctController extends Handler {
+    private static final String LOG_TAG = "DctController";
+    private static final boolean DBG = true;
+
+    private static final int EVENT_PHONE1_DETACH = 1;
+    private static final int EVENT_PHONE2_DETACH = 2;
+    private static final int EVENT_PHONE3_DETACH = 3;
+    private static final int EVENT_PHONE4_DETACH = 4;
+    private static final int EVENT_PHONE1_RADIO_OFF = 5;
+    private static final int EVENT_PHONE2_RADIO_OFF = 6;
+    private static final int EVENT_PHONE3_RADIO_OFF = 7;
+    private static final int EVENT_PHONE4_RADIO_OFF = 8;
+
+    private static final int PHONE_NONE = -1;
+
+    private static DctController sDctController;
+
+    private static final int EVENT_ALL_DATA_DISCONNECTED = 1;
+    private static final int EVENT_SET_DATA_ALLOW_DONE = 2;
+
+    private RegistrantList mNotifyDataSwitchInfo = new RegistrantList();
+    private SubscriptionController mSubController = SubscriptionController.getInstance();
+
+    private Phone mActivePhone;
+    private int mPhoneNum;
+    private boolean[] mServicePowerOffFlag;
+    private PhoneProxy[] mPhones;
+    private DcSwitchState[] mDcSwitchState;
+    private DcSwitchAsyncChannel[] mDcSwitchAsyncChannel;
+    private Handler[] mDcSwitchStateHandler;
+
+    private HashSet<String> mApnTypes = new HashSet<String>();
+
+    private BroadcastReceiver mDataStateReceiver;
+    private Context mContext;
+
+    private int mCurrentDataPhone = PHONE_NONE;
+    private int mRequestedDataPhone = PHONE_NONE;
+
+    private Handler mRspHander = new Handler() {
+        public void handleMessage(Message msg){
+            AsyncResult ar;
+            switch(msg.what) {
+                case EVENT_PHONE1_DETACH:
+                case EVENT_PHONE2_DETACH:
+                case EVENT_PHONE3_DETACH:
+                case EVENT_PHONE4_DETACH:
+                    logd("EVENT_PHONE" + msg.what +
+                            "_DETACH: mRequestedDataPhone=" + mRequestedDataPhone);
+                    mCurrentDataPhone = PHONE_NONE;
+                    if (mRequestedDataPhone != PHONE_NONE) {
+                        mCurrentDataPhone = mRequestedDataPhone;
+                        mRequestedDataPhone = PHONE_NONE;
+
+                        Iterator<String> itrType = mApnTypes.iterator();
+                        while (itrType.hasNext()) {
+                            mDcSwitchAsyncChannel[mCurrentDataPhone].connectSync(itrType.next());
+                        }
+                        mApnTypes.clear();
+                    }
+                break;
+
+                case EVENT_PHONE1_RADIO_OFF:
+                case EVENT_PHONE2_RADIO_OFF:
+                case EVENT_PHONE3_RADIO_OFF:
+                case EVENT_PHONE4_RADIO_OFF:
+                    logd("EVENT_PHONE" + (msg.what - EVENT_PHONE1_RADIO_OFF + 1) + "_RADIO_OFF.");
+                    mServicePowerOffFlag[msg.what - EVENT_PHONE1_RADIO_OFF] = true;
+                break;
+
+                default:
+                break;
+            }
+        }
+    };
+
+    private DefaultPhoneNotifier.IDataStateChangedCallback mDataStateChangedCallback =
+            new DefaultPhoneNotifier.IDataStateChangedCallback() {
+        public void onDataStateChanged(long subId, String state, String reason,
+                String apnName, String apnType, boolean unavailable) {
+            logd("[DataStateChanged]:" + "state=" + state + ",reason=" + reason
+                      + ",apnName=" + apnName + ",apnType=" + apnType + ",from subId=" + subId);
+            int phoneId = SubscriptionManager.getPhoneId(subId);
+            mDcSwitchState[phoneId].notifyDataConnection(phoneId, state, reason,
+                    apnName, apnType, unavailable);
+        }
+    };
+
+    private class DataStateReceiver extends BroadcastReceiver {
+        public void onReceive(Context context, Intent intent) {
+            synchronized(this) {
+                if (intent.getAction().equals(TelephonyIntents.ACTION_SERVICE_STATE_CHANGED)) {
+                    ServiceState ss = ServiceState.newFromBundle(intent.getExtras());
+
+                    long subId = intent.getLongExtra(PhoneConstants.SUBSCRIPTION_KEY, PhoneConstants.SUB1);
+                    int phoneId = SubscriptionManager.getPhoneId(subId);
+                    logd("DataStateReceiver phoneId= " + phoneId);
+
+                    // for the case of network out of service when bootup
+                    if (subId == -1 || subId == -2) {
+                        logd("Network out of service and return");
+                        return;
+                    }
+
+                    boolean prevPowerOff = mServicePowerOffFlag[phoneId];
+                    if (ss != null) {
+                        int state = ss.getState();
+                        switch (state) {
+                            case ServiceState.STATE_POWER_OFF:
+                                mServicePowerOffFlag[phoneId] = true;
+                                logd("Recv STATE_POWER_OFF Intent from phoneId=" + phoneId);
+                                break;
+                            case ServiceState.STATE_IN_SERVICE:
+                                mServicePowerOffFlag[phoneId] = false;
+                                logd("Recv STATE_IN_SERVICE Intent from phoneId=" + phoneId);
+                                break;
+                            case ServiceState.STATE_OUT_OF_SERVICE:
+                                logd("Recv STATE_OUT_OF_SERVICE Intent from phoneId=" + phoneId);
+                                if (mServicePowerOffFlag[phoneId]) {
+                                    mServicePowerOffFlag[phoneId] = false;
+                                }
+                                break;
+                            case ServiceState.STATE_EMERGENCY_ONLY:
+                                logd("Recv STATE_EMERGENCY_ONLY Intent from phoneId=" + phoneId);
+                                break;
+                            default:
+                                logd("Recv SERVICE_STATE_CHANGED invalid state");
+                                break;
+                        }
+
+                        if (prevPowerOff && mServicePowerOffFlag[phoneId] == false &&
+                                mCurrentDataPhone == PHONE_NONE &&
+                                phoneId == getDataConnectionFromSetting()) {
+                            logd("Current Phone is none and default Phone is " +
+                                    phoneId + ", then enableApnType()");
+                            enableApnType(subId, PhoneConstants.APN_TYPE_DEFAULT);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    public DefaultPhoneNotifier.IDataStateChangedCallback getDataStateChangedCallback() {
+        return mDataStateChangedCallback;
+    }
+
+    public static DctController getInstance() {
+       if (sDctController == null) {
+        throw new RuntimeException(
+            "DCTrackerController.getInstance can't be called before makeDCTController()");
+        }
+       return sDctController;
+    }
+
+    public static DctController makeDctController(PhoneProxy[] phones) {
+        if (sDctController == null) {
+            sDctController = new DctController(phones);
+        }
+        return sDctController;
+    }
+
+    private DctController(PhoneProxy[] phones) {
+        mPhoneNum = TelephonyManager.getDefault().getPhoneCount();
+        mServicePowerOffFlag = new boolean[mPhoneNum];
+        mPhones = phones;
+
+        mDcSwitchState = new DcSwitchState[mPhoneNum];
+        mDcSwitchAsyncChannel = new DcSwitchAsyncChannel[mPhoneNum];
+        mDcSwitchStateHandler = new Handler[mPhoneNum];
+
+        mActivePhone = mPhones[0];
+
+        for (int i = 0; i < mPhoneNum; ++i) {
+            int phoneId = i;
+            mServicePowerOffFlag[i] = true;
+            mDcSwitchState[i] = new DcSwitchState(mPhones[i], "DcSwitchState-" + phoneId, phoneId);
+            mDcSwitchState[i].start();
+            mDcSwitchAsyncChannel[i] = new DcSwitchAsyncChannel(mDcSwitchState[i], phoneId);
+            mDcSwitchStateHandler[i] = new Handler();
+
+            int status = mDcSwitchAsyncChannel[i].fullyConnectSync(mPhones[i].getContext(),
+                mDcSwitchStateHandler[i], mDcSwitchState[i].getHandler());
+
+            if (status == AsyncChannel.STATUS_SUCCESSFUL) {
+                logd("Connect success: " + i);
+            } else {
+                loge("Could not connect to " + i);
+            }
+
+            mDcSwitchState[i].registerForIdle(mRspHander, EVENT_PHONE1_DETACH + i, null);
+
+            // Register for radio state change
+            PhoneBase phoneBase = (PhoneBase)((PhoneProxy)mPhones[i]).getActivePhone();
+            phoneBase.mCi.registerForOffOrNotAvailable(mRspHander, EVENT_PHONE1_RADIO_OFF + i, null);
+        }
+
+        mContext = mActivePhone.getContext();
+
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(TelephonyIntents.ACTION_DATA_CONNECTION_FAILED);
+        filter.addAction(TelephonyIntents.ACTION_SERVICE_STATE_CHANGED);
+
+        mDataStateReceiver = new DataStateReceiver();
+        Intent intent = mContext.registerReceiver(mDataStateReceiver, filter);
+    }
+
+    private IccCardConstants.State getIccCardState(int phoneId) {
+        return mPhones[phoneId].getIccCard().getState();
+    }
+
+    /**
+     * Enable PDP interface by apn type and phone id
+     *
+     * @param type enable pdp interface by apn type, such as PhoneConstants.APN_TYPE_MMS, etc.
+     * @param subId Indicate which sub to query
+     * @return PhoneConstants.APN_REQUEST_STARTED: action is already started
+     * PhoneConstants.APN_ALREADY_ACTIVE: interface has already active
+     * PhoneConstants.APN_TYPE_NOT_AVAILABLE: invalid APN type
+     * PhoneConstants.APN_REQUEST_FAILED: request failed
+     * PhoneConstants.APN_REQUEST_FAILED_DUE_TO_RADIO_OFF: readio turn off
+     * @see #disableApnType()
+     */
+    public synchronized int enableApnType(long subId, String type) {
+        int phoneId = SubscriptionManager.getPhoneId(subId);
+
+        if (phoneId == PHONE_NONE || !isValidphoneId(phoneId)) {
+            logw("enableApnType(): with PHONE_NONE or Invalid PHONE ID");
+            return PhoneConstants.APN_REQUEST_FAILED;
+        }
+
+        logd("enableApnType():type=" + type + ",phoneId=" + phoneId +
+                ",powerOff=" + mServicePowerOffFlag[phoneId]);
+
+        if (!PhoneConstants.APN_TYPE_DEFAULT.equals(type)) {
+            for (int peerphoneId =0; peerphoneId < mPhoneNum; peerphoneId++) {
+                // check peer Phone has non default APN activated as receiving non default APN request.
+                if (phoneId == peerphoneId) {
+                    continue;
+                }
+
+                String[] activeApnTypes = mPhones[peerphoneId].getActiveApnTypes();
+                if (activeApnTypes != null && activeApnTypes.length != 0) {
+                    for (int i=0; i<activeApnTypes.length; i++) {
+                        if (!PhoneConstants.APN_TYPE_DEFAULT.equals(activeApnTypes[i]) &&
+                                mPhones[peerphoneId].getDataConnectionState(activeApnTypes[i]) !=
+                                PhoneConstants.DataState.DISCONNECTED) {
+                            logd("enableApnType:Peer Phone still have non-default active APN type:"+
+                                    "activeApnTypes[" + i + "]=" + activeApnTypes[i]);
+                            return PhoneConstants.APN_REQUEST_FAILED;
+                        }
+                    }
+                }
+            }
+        }
+
+        logd("enableApnType(): CurrentDataPhone=" +
+                    mCurrentDataPhone + ", RequestedDataPhone=" + mRequestedDataPhone);
+
+        if (phoneId == mCurrentDataPhone &&
+               !mDcSwitchAsyncChannel[mCurrentDataPhone].isIdleOrDeactingSync()) {
+           mRequestedDataPhone = PHONE_NONE;
+           logd("enableApnType(): mRequestedDataPhone equals request PHONE ID.");
+           return mDcSwitchAsyncChannel[phoneId].connectSync(type);
+        } else {
+            // Only can switch data when mCurrentDataPhone is PHONE_NONE,
+            // it is set to PHONE_NONE only as receiving EVENT_PHONEX_DETACH
+            if (mCurrentDataPhone == PHONE_NONE) {
+                mCurrentDataPhone = phoneId;
+                mRequestedDataPhone = PHONE_NONE;
+                logd("enableApnType(): current PHONE is NONE or IDLE, mCurrentDataPhone=" +
+                        mCurrentDataPhone);
+                return mDcSwitchAsyncChannel[phoneId].connectSync(type);
+            } else {
+                logd("enableApnType(): current PHONE:" + mCurrentDataPhone + " is active.");
+                if (phoneId != mRequestedDataPhone) {
+                    mApnTypes.clear();
+                }
+                mApnTypes.add(type);
+                mRequestedDataPhone = phoneId;
+                mDcSwitchState[mCurrentDataPhone].cleanupAllConnection();
+            }
+        }
+        return PhoneConstants.APN_REQUEST_STARTED;
+    }
+
+    /**
+     * disable PDP interface by apn type and sub id
+     *
+     * @param type enable pdp interface by apn type, such as PhoneConstants.APN_TYPE_MMS, etc.
+     * @param subId Indicate which sub to query
+     * @return PhoneConstants.APN_REQUEST_STARTED: action is already started
+     * PhoneConstants.APN_ALREADY_ACTIVE: interface has already active
+     * PhoneConstants.APN_TYPE_NOT_AVAILABLE: invalid APN type
+     * PhoneConstants.APN_REQUEST_FAILED: request failed
+     * PhoneConstants.APN_REQUEST_FAILED_DUE_TO_RADIO_OFF: readio turn off
+     * @see #enableApnTypeGemini()
+     */
+    public synchronized int disableApnType(long subId, String type) {
+
+        int phoneId = SubscriptionManager.getPhoneId(subId);
+
+        if (phoneId == PHONE_NONE || !isValidphoneId(phoneId)) {
+            logw("disableApnType(): with PHONE_NONE or Invalid PHONE ID");
+            return PhoneConstants.APN_REQUEST_FAILED;
+        }
+        logd("disableApnType():type=" + type + ",phoneId=" + phoneId +
+                ",powerOff=" + mServicePowerOffFlag[phoneId]);
+        return mDcSwitchAsyncChannel[phoneId].disconnectSync(type);
+    }
+
+    public boolean isDataConnectivityPossible(String type, int phoneId) {
+        if (phoneId == PHONE_NONE || !isValidphoneId(phoneId)) {
+            logw("isDataConnectivityPossible(): with PHONE_NONE or Invalid PHONE ID");
+            return false;
+        } else {
+            return mPhones[phoneId].isDataConnectivityPossible(type);
+        }
+    }
+
+    public boolean isIdleOrDeacting(int phoneId) {
+        if (mDcSwitchAsyncChannel[phoneId].isIdleOrDeactingSync()) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    private boolean isValidphoneId(int phoneId) {
+        return phoneId >= 0 && phoneId <= mPhoneNum;
+    }
+
+    private boolean isValidApnType(String apnType) {
+         if (apnType.equals(PhoneConstants.APN_TYPE_DEFAULT)
+             || apnType.equals(PhoneConstants.APN_TYPE_MMS)
+             || apnType.equals(PhoneConstants.APN_TYPE_SUPL)
+             || apnType.equals(PhoneConstants.APN_TYPE_DUN)
+             || apnType.equals(PhoneConstants.APN_TYPE_HIPRI)
+             || apnType.equals(PhoneConstants.APN_TYPE_FOTA)
+             || apnType.equals(PhoneConstants.APN_TYPE_IMS)
+             || apnType.equals(PhoneConstants.APN_TYPE_CBS))
+        {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    private int getDataConnectionFromSetting(){
+        long [] subId = SubscriptionManager.getSubId(PhoneConstants.SIM_ID_1);
+        int phoneId = SubscriptionManager.getPhoneId(subId[0]);
+        return phoneId;
+    }
+
+    private static void logv(String s) {
+        Log.v(LOG_TAG, "[DctController] " + s);
+    }
+
+    private static void logd(String s) {
+        Log.d(LOG_TAG, "[DctController] " + s);
+    }
+
+    private static void logw(String s) {
+        Log.w(LOG_TAG, "[DctController] " + s);
+    }
+
+    private static void loge(String s) {
+        Log.e(LOG_TAG, "[DctController] " + s);
+    }
+
+
+    public void setDataSubId(long subId) {
+        //FIXME This should rework
+        //FIXME Need to have a StateMachine logic to handle this api considering various clients
+        Rlog.d(LOG_TAG, "setDataAllowed subId :" + subId);
+        int phoneId = mSubController.getPhoneId(subId);
+        int prefPhoneId = mSubController.getPhoneId(mSubController.getDefaultDataSubId());
+        Phone phone = mPhones[prefPhoneId].getActivePhone();
+        DcTrackerBase dcTracker =((PhoneBase)phone).mDcTracker;
+        dcTracker.setDataAllowed(false, null);
+        mPhones[prefPhoneId].registerForAllDataDisconnected(
+                this, EVENT_ALL_DATA_DISCONNECTED, new Integer(phoneId));
+
+    }
+
+    public void registerForDataSwitchInfo(Handler h, int what, Object obj) {
+        //FIXME This should rework
+        Registrant r = new Registrant (h, what, obj);
+        synchronized (mNotifyDataSwitchInfo) {
+            mNotifyDataSwitchInfo.add(r);
+        }
+    }
+
+    @Override
+    public void handleMessage (Message msg) {
+        //FIXME This should rework
+            AsyncResult ar = (AsyncResult)msg.obj;
+            Rlog.d(LOG_TAG, "handleMessage msg=" + msg);
+
+            switch (msg.what) {
+                case EVENT_ALL_DATA_DISCONNECTED:
+                    Integer phoneId = (Integer)ar.userObj;
+                    int prefPhoneId = mSubController.getPhoneId(
+                            mSubController.getDefaultDataSubId());
+                    Rlog.d(LOG_TAG, "EVENT_ALL_DATA_DISCONNECTED phoneId :" + phoneId);
+                    mPhones[prefPhoneId].unregisterForAllDataDisconnected(this);
+                    Message alllowedDataDone = Message.obtain(this, EVENT_SET_DATA_ALLOW_DONE,
+                            new Integer(phoneId));
+                    Phone phone = mPhones[phoneId].getActivePhone();
+                    DcTrackerBase dcTracker =((PhoneBase)phone).mDcTracker;
+                    dcTracker.setDataAllowed(true, alllowedDataDone);
+                    break;
+
+                case EVENT_SET_DATA_ALLOW_DONE:
+                    phoneId = (Integer)ar.userObj;
+                    long[] subId = mSubController.getSubId(phoneId);
+                    Rlog.d(LOG_TAG, "EVENT_SET_DATA_ALLOWED_DONE  phoneId :" + subId[0]);
+                    mNotifyDataSwitchInfo.notifyRegistrants(new AsyncResult(null, subId[0], null));
+                    mPhones[phoneId].updateDataConnectionTracker();
+                    break;
+            }
+    }
+}
diff --git a/src/java/com/android/internal/telephony/gsm/GSMPhone.java b/src/java/com/android/internal/telephony/gsm/GSMPhone.java
index b169936..24015b2 100644
--- a/src/java/com/android/internal/telephony/gsm/GSMPhone.java
+++ b/src/java/com/android/internal/telephony/gsm/GSMPhone.java
@@ -32,6 +32,8 @@
 import android.telephony.CellLocation;
 import android.telephony.PhoneNumberUtils;
 import android.telephony.ServiceState;
+import android.telephony.TelephonyManager;
+import android.telephony.VoLteServiceState;
 import com.android.internal.telephony.CallTracker;
 import android.text.TextUtils;
 import android.telephony.Rlog;
@@ -51,6 +53,7 @@
 
 import com.android.internal.telephony.SmsBroadcastUndelivered;
 import com.android.internal.telephony.dataconnection.DcTracker;
+import com.android.internal.telephony.Call;
 import com.android.internal.telephony.CallForwardInfo;
 import com.android.internal.telephony.CallStateException;
 import com.android.internal.telephony.CommandsInterface;
@@ -61,18 +64,25 @@
 import com.android.internal.telephony.OperatorInfo;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneBase;
+import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.PhoneNotifier;
 import com.android.internal.telephony.PhoneProxy;
 import com.android.internal.telephony.PhoneSubInfo;
+import com.android.internal.telephony.Subscription;
+import android.telephony.SubscriptionManager;
 import com.android.internal.telephony.TelephonyProperties;
 import com.android.internal.telephony.UUSInfo;
+import com.android.internal.telephony.VoicePhone;
 import com.android.internal.telephony.test.SimulatedRadioControl;
 import com.android.internal.telephony.uicc.IccRecords;
 import com.android.internal.telephony.uicc.IccVmNotSupportedException;
 import com.android.internal.telephony.uicc.UiccCardApplication;
 import com.android.internal.telephony.uicc.UiccController;
 import com.android.internal.telephony.ServiceStateTracker;
+import com.android.internal.telephony.uicc.IsimRecords;
+import com.android.internal.telephony.uicc.IsimUiccRecords;
+
 
 import java.io.FileDescriptor;
 import java.io.IOException;
@@ -83,6 +93,10 @@
 import java.util.ArrayList;
 import java.util.List;
 
+import static com.android.internal.telephony.PhoneConstants.EVENT_SUBSCRIPTION_ACTIVATED;
+import static com.android.internal.telephony.PhoneConstants.EVENT_SUBSCRIPTION_DEACTIVATED;
+
+
 /**
  * {@hide}
  */
@@ -118,6 +132,8 @@
     private String mImeiSv;
     private String mVmNumber;
 
+    private IsimUiccRecords mIsimUiccRecords;
+
     // Create Cfu (Call forward unconditional) so that dialling number &
     // mOnComplete (Message object passed by client) can be packed &
     // given as a single Cfu object as user data to RIL.
@@ -148,9 +164,45 @@
 
         mCi.setPhoneType(PhoneConstants.PHONE_TYPE_GSM);
         mCT = new GsmCallTracker(this);
-        mSST = new GsmServiceStateTracker (this);
 
+        mSST = new GsmServiceStateTracker(this);
         mDcTracker = new DcTracker(this);
+
+        if (!unitTestMode) {
+            mSimPhoneBookIntManager = new SimPhoneBookInterfaceManager(this);
+            mSubInfo = new PhoneSubInfo(this);
+        }
+
+        mCi.registerForAvailable(this, EVENT_RADIO_AVAILABLE, null);
+        mCi.registerForOffOrNotAvailable(this, EVENT_RADIO_OFF_OR_NOT_AVAILABLE, null);
+        mCi.registerForOn(this, EVENT_RADIO_ON, null);
+        mCi.setOnUSSD(this, EVENT_USSD, null);
+        mCi.setOnSuppServiceNotification(this, EVENT_SSN, null);
+        mCi.registerForSrvccStateChanged(this, EVENT_SRVCC_STATE_CHANGED, null);
+        mSST.registerForNetworkAttached(this, EVENT_REGISTERED_TO_NETWORK, null);
+        setProperties();
+    }
+
+    public
+    GSMPhone (Context context, CommandsInterface ci, PhoneNotifier notifier, int phoneId) {
+        this(context, ci, notifier, false, phoneId);
+    }
+
+    public
+    GSMPhone (Context context, CommandsInterface ci,
+            PhoneNotifier notifier, boolean unitTestMode, int phoneId) {
+        super("GSM", notifier, context, ci, unitTestMode, phoneId);
+
+        if (ci instanceof SimulatedRadioControl) {
+            mSimulatedRadioControl = (SimulatedRadioControl) ci;
+        }
+
+        mCi.setPhoneType(PhoneConstants.PHONE_TYPE_GSM);
+        mCT = new GsmCallTracker(this);
+
+        mSST = new GsmServiceStateTracker(this);
+        mDcTracker = new DcTracker(this);
+
         if (!unitTestMode) {
             mSimPhoneBookIntManager = new SimPhoneBookInterfaceManager(this);
             mSubInfo = new PhoneSubInfo(this);
@@ -162,10 +214,16 @@
         mCi.setOnUSSD(this, EVENT_USSD, null);
         mCi.setOnSuppServiceNotification(this, EVENT_SSN, null);
         mSST.registerForNetworkAttached(this, EVENT_REGISTERED_TO_NETWORK, null);
+        setProperties();
 
-        //Change the system property
-        SystemProperties.set(TelephonyProperties.CURRENT_ACTIVE_PHONE,
-                new Integer(PhoneConstants.PHONE_TYPE_GSM).toString());
+        log("GSMPhone: constructor: sub = " + mPhoneId);
+
+        setProperties();
+    }
+
+    protected void setProperties() {
+        TelephonyManager.setTelephonyProperty(TelephonyProperties.CURRENT_ACTIVE_PHONE,
+                getSubId(), new Integer(PhoneConstants.PHONE_TYPE_GSM).toString());
     }
 
     @Override
@@ -181,6 +239,7 @@
             mSST.unregisterForNetworkAttached(this); //EVENT_REGISTERED_TO_NETWORK
             mCi.unSetOnUSSD(this);
             mCi.unSetOnSuppServiceNotification(this);
+            mCi.unregisterForSrvccStateChanged(this);
 
             mPendingMMIs.clear();
 
@@ -201,6 +260,7 @@
         mSubInfo = null;
         mCT = null;
         mSST = null;
+
         super.removeReferences();
     }
 
@@ -210,6 +270,28 @@
     }
 
 
+    private void onSubscriptionActivated() {
+        //mSubscriptionData = SubscriptionManager.getCurrentSubscription(mSubscription);
+
+        log("SUBSCRIPTION ACTIVATED : slotId : " + mSubscriptionData.slotId
+                + " appid : " + mSubscriptionData.m3gppIndex
+                + " subId : " + mSubscriptionData.subId
+                + " subStatus : " + mSubscriptionData.subStatus);
+
+        // Make sure properties are set for proper subscription.
+        setProperties();
+
+        onUpdateIccAvailability();
+        mSST.sendMessage(mSST.obtainMessage(ServiceStateTracker.EVENT_ICC_CHANGED));
+        ((DcTracker)mDcTracker).updateRecords();
+    }
+
+    private void onSubscriptionDeactivated() {
+        log("SUBSCRIPTION DEACTIVATED");
+        mSubscriptionData = null;
+        resetSubSpecifics();
+    }
+
     @Override
     public ServiceState
     getServiceState() {
@@ -391,9 +473,9 @@
      * {@inheritDoc}
      */
     @Override
-    public final void
+    public void
     setSystemProperty(String property, String value) {
-        super.setSystemProperty(property, value);
+        TelephonyManager.setTelephonyProperty(property, getSubId(), value);
     }
 
     @Override
@@ -410,6 +492,16 @@
     }
 
     @Override
+    public void registerForSimRecordsLoaded(Handler h, int what, Object obj) {
+        mSimRecordsLoadedRegistrants.addUnique(h, what, obj);
+    }
+
+    @Override
+    public void unregisterForSimRecordsLoaded(Handler h) {
+        mSimRecordsLoadedRegistrants.remove(h);
+    }
+
+    @Override
     public void
     acceptCall() throws CallStateException {
         mCT.acceptCall();
@@ -627,7 +719,12 @@
     }
 
     @Override
-    public boolean handleInCallMmiCommands(String dialString) {
+    public boolean handleInCallMmiCommands(String dialString) throws CallStateException {
+        if (mVoicePhone != null
+                && mVoicePhone.getServiceState().getState() == ServiceState.STATE_IN_SERVICE) {
+            return mVoicePhone.handleInCallMmiCommands(dialString);
+        }
+
         if (!isInCall()) {
             return false;
         }
@@ -685,6 +782,26 @@
     @Override
     public Connection
     dial (String dialString, UUSInfo uusInfo) throws CallStateException {
+        if (mVoicePhone != null
+                && mVoicePhone.getServiceState().getState() == ServiceState.STATE_IN_SERVICE
+                && !PhoneNumberUtils.isEmergencyNumber(dialString)) {
+            try {
+                return mVoicePhone.dial(dialString);
+            } catch (CallStateException e) {
+                if (!VoicePhone.CS_FALLBACK.equals(e.getMessage())) {
+                    CallStateException ce = new CallStateException(e.getMessage());
+                    ce.setStackTrace(e.getStackTrace());
+                    throw ce;
+                }
+            }
+        }
+
+        return dialInternal(dialString, null);
+    }
+
+    @Override
+    protected Connection
+    dialInternal (String dialString, UUSInfo uusInfo) throws CallStateException {
         // Need to make sure dialString gets parsed properly
         String newDialString = PhoneNumberUtils.stripSeparators(dialString);
 
@@ -780,7 +897,7 @@
     private void storeVoiceMailNumber(String number) {
         SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
         SharedPreferences.Editor editor = sp.edit();
-        editor.putString(VM_NUMBER, number);
+        editor.putString(VM_NUMBER + getPhoneId(), number);
         editor.apply();
         setVmSimImsi(getSubscriberId());
     }
@@ -792,20 +909,20 @@
         String number = (r != null) ? r.getVoiceMailNumber() : "";
         if (TextUtils.isEmpty(number)) {
             SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
-            number = sp.getString(VM_NUMBER, null);
+            number = sp.getString(VM_NUMBER + getPhoneId(), null);
         }
         return number;
     }
 
     private String getVmSimImsi() {
         SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
-        return sp.getString(VM_SIM_IMSI, null);
+        return sp.getString(VM_SIM_IMSI + getPhoneId(), null);
     }
 
     private void setVmSimImsi(String imsi) {
         SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
         SharedPreferences.Editor editor = sp.edit();
-        editor.putString(VM_SIM_IMSI, imsi);
+        editor.putString(VM_SIM_IMSI + getPhoneId(), imsi);
         editor.apply();
     }
 
@@ -835,6 +952,11 @@
     }
 
     @Override
+    public IsimRecords getIsimRecords() {
+        return mIsimUiccRecords;
+    }
+
+    @Override
     public String getImei() {
         return mImei;
     }
@@ -917,6 +1039,13 @@
         }
     }
 
+    public String getSystemProperty(String property, String defValue) {
+        if(getUnitTestMode()) {
+            return null;
+        }
+        return TelephonyManager.getTelephonyProperty(property, getSubId(), defValue);
+    }
+
     private boolean isValidCommandInterfaceCFAction (int commandInterfaceCFAction) {
         switch (commandInterfaceCFAction) {
         case CF_ACTION_DISABLE:
@@ -929,12 +1058,22 @@
         }
     }
 
+    public void updateDataConnectionTracker() {
+        ((DcTracker)mDcTracker).update();
+    }
+
     protected  boolean isCfEnable(int action) {
         return (action == CF_ACTION_ENABLE) || (action == CF_ACTION_REGISTRATION);
     }
 
     @Override
     public void getCallForwardingOption(int commandInterfaceCFReason, Message onComplete) {
+        if ((mVoicePhone != null)
+                && (mVoicePhone.getServiceState().getState() == ServiceState.STATE_IN_SERVICE)) {
+            mVoicePhone.getCallForwardingOption(commandInterfaceCFReason, onComplete);
+            return;
+        }
+
         if (isValidCommandInterfaceCFReason(commandInterfaceCFReason)) {
             if (LOCAL_DEBUG) Rlog.d(LOG_TAG, "requesting call forwarding query.");
             Message resp;
@@ -953,6 +1092,13 @@
             String dialingNumber,
             int timerSeconds,
             Message onComplete) {
+        if ((mVoicePhone != null)
+                && (mVoicePhone.getServiceState().getState() == ServiceState.STATE_IN_SERVICE)) {
+            mVoicePhone.setCallForwardingOption(commandInterfaceCFAction,
+                    commandInterfaceCFReason, dialingNumber, timerSeconds, onComplete);
+            return;
+        }
+
         if (    (isValidCommandInterfaceCFAction(commandInterfaceCFAction)) &&
                 (isValidCommandInterfaceCFReason(commandInterfaceCFReason))) {
 
@@ -987,6 +1133,12 @@
 
     @Override
     public void getCallWaiting(Message onComplete) {
+        if ((mVoicePhone != null)
+                && (mVoicePhone.getServiceState().getState() == ServiceState.STATE_IN_SERVICE)) {
+            mVoicePhone.getCallWaiting(onComplete);
+            return;
+        }
+
         //As per 3GPP TS 24.083, section 1.6 UE doesn't need to send service
         //class parameter in call waiting interrogation  to network
         mCi.queryCallWaiting(CommandsInterface.SERVICE_CLASS_NONE, onComplete);
@@ -994,6 +1146,12 @@
 
     @Override
     public void setCallWaiting(boolean enable, Message onComplete) {
+        if ((mVoicePhone != null)
+                && (mVoicePhone.getServiceState().getState() == ServiceState.STATE_IN_SERVICE)) {
+            mVoicePhone.setCallWaiting(enable, onComplete);
+            return;
+        }
+
         mCi.setCallWaiting(enable, CommandsInterface.SERVICE_CLASS_VOICE, onComplete);
     }
 
@@ -1143,12 +1301,45 @@
      */
     protected  void syncClirSetting() {
         SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
-        int clirSetting = sp.getInt(CLIR_KEY, -1);
+        int clirSetting = sp.getInt(CLIR_KEY + getPhoneId(), -1);
         if (clirSetting >= 0) {
             mCi.setCLIR(clirSetting, null);
         }
     }
 
+    private void handleSrvccStateChanged(int[] ret) {
+        Rlog.d(LOG_TAG, "handleSrvccStateChanged");
+
+        Connection conn = null;
+        Call.SrvccState srvccState = Call.SrvccState.NONE;
+        if (ret != null && ret.length != 0) {
+            int state = ret[0];
+            switch(state) {
+                case VoLteServiceState.HANDOVER_STARTED:
+                    srvccState = Call.SrvccState.STARTED;
+                    conn = ((VoicePhone)mVoicePhone).getHandoverConnection();
+                    break;
+                case VoLteServiceState.HANDOVER_COMPLETED:
+                    srvccState = Call.SrvccState.COMPLETED;
+                    ((VoicePhone) mVoicePhone).notifySrvccState(srvccState);
+                    break;
+                case VoLteServiceState.HANDOVER_FAILED:
+                case VoLteServiceState.HANDOVER_CANCELED:
+                    srvccState = Call.SrvccState.FAILED;
+                    break;
+
+                default:
+                    //ignore invalid state
+                    return;
+            }
+
+            mCT.notifySrvccState(srvccState, conn);
+
+            VoLteServiceState lteState = new VoLteServiceState(state);
+            notifyVoLteServiceStateChanged(lteState);
+        }
+    }
+
     @Override
     public void handleMessage (Message msg) {
         AsyncResult ar;
@@ -1170,6 +1361,9 @@
             break;
 
             case EVENT_RADIO_ON:
+                if (mVoicePhone != null) {
+                    mVoicePhone.getServiceState().setStateOutOfService();
+                }
             break;
 
             case EVENT_REGISTERED_TO_NETWORK:
@@ -1188,6 +1382,7 @@
                     setVmSimImsi(null);
                 }
 
+                mSimRecordsLoadedRegistrants.notifyRegistrants();
             break;
 
             case EVENT_GET_BASEBAND_VERSION_DONE:
@@ -1246,6 +1441,9 @@
                         mPendingMMIs.get(i).onUssdFinishedError();
                     }
                 }
+                if (mVoicePhone != null) {
+                    mVoicePhone.getServiceState().setStateOff();
+                }
             break;
 
             case EVENT_SSN:
@@ -1322,11 +1520,33 @@
                 }
                 break;
 
+            case EVENT_SUBSCRIPTION_ACTIVATED:
+                log("EVENT_SUBSCRIPTION_ACTIVATED");
+                onSubscriptionActivated();
+                break;
+
+            case EVENT_SUBSCRIPTION_DEACTIVATED:
+                log("EVENT_SUBSCRIPTION_DEACTIVATED");
+                onSubscriptionDeactivated();
+                break;
+
+            case EVENT_SRVCC_STATE_CHANGED:
+                ar = (AsyncResult)msg.obj;
+                if (ar.exception == null) {
+                    handleSrvccStateChanged((int[]) ar.result);
+                }
+                break;
+
              default:
                  super.handleMessage(msg);
         }
     }
 
+    protected UiccCardApplication getUiccCardApplication() {
+            return  ((UiccController) mUiccController).getUiccCardApplication(mPhoneId,
+                    UiccController.APP_FAM_3GPP);
+    }
+
     @Override
     protected void onUpdateIccAvailability() {
         if (mUiccController == null ) {
@@ -1334,7 +1554,16 @@
         }
 
         UiccCardApplication newUiccApplication =
-                mUiccController.getUiccCardApplication(UiccController.APP_FAM_3GPP);
+                mUiccController.getUiccCardApplication(mPhoneId, UiccController.APP_FAM_IMS);
+        IsimUiccRecords newIsimUiccRecords = null;
+
+        if (newUiccApplication != null) {
+            newIsimUiccRecords = (IsimUiccRecords)newUiccApplication.getIccRecords();
+            if (LOCAL_DEBUG) log("New ISIM application found");
+        }
+        mIsimUiccRecords = newIsimUiccRecords;
+
+        newUiccApplication = getUiccCardApplication();
 
         UiccCardApplication app = mUiccApplication.get();
         if (app != newUiccApplication) {
@@ -1374,12 +1603,17 @@
      * @return true for success; false otherwise.
      */
     public boolean updateCurrentCarrierInProvider() {
-        IccRecords r = mIccRecords.get();
-        if (r != null) {
+        long currentDds = SubscriptionManager.getDefaultDataSubId();
+        String operatorNumeric = getOperatorNumeric();
+
+        log("updateCurrentCarrierInProvider: mSubId = " + getSubId()
+                + " currentDds = " + currentDds + " operatorNumeric = " + operatorNumeric);
+
+        if (!TextUtils.isEmpty(operatorNumeric) && (getSubId() == currentDds)) {
             try {
                 Uri uri = Uri.withAppendedPath(Telephony.Carriers.CONTENT_URI, "current");
                 ContentValues map = new ContentValues();
-                map.put(Telephony.Carriers.NUMERIC, r.getOperatorNumeric());
+                map.put(Telephony.Carriers.NUMERIC, operatorNumeric);
                 mContext.getContentResolver().insert(uri, map);
                 return true;
             } catch (SQLException e) {
@@ -1397,7 +1631,7 @@
         // open the shared preferences editor, and write the value.
         SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
         SharedPreferences.Editor editor = sp.edit();
-        editor.putInt(CLIR_KEY, commandInterfaceCLIRMode);
+        editor.putInt(CLIR_KEY + getPhoneId(), commandInterfaceCLIRMode);
 
         // commit and log the result.
         if (! editor.commit()) {
@@ -1516,6 +1750,40 @@
         pw.println(" mVmNumber=" + mVmNumber);
     }
 
+    /**
+     * @return operator numeric.
+     */
+    public String getOperatorNumeric() {
+        String operatorNumeric = null;
+        IccRecords r = mIccRecords.get();
+        if (r != null) {
+            operatorNumeric = r.getOperatorNumeric();
+        }
+        return operatorNumeric;
+    }
+
+    public void registerForAllDataDisconnected(Handler h, int what, Object obj) {
+        ((DcTracker)mDcTracker)
+                .registerForAllDataDisconnected(h, what, obj);
+    }
+
+    public void unregisterForAllDataDisconnected(Handler h) {
+        ((DcTracker)mDcTracker).unregisterForAllDataDisconnected(h);
+    }
+
+    public void setInternalDataEnabled(boolean enable, Message onCompleteMsg) {
+        ((DcTracker)mDcTracker)
+                .setInternalDataEnabled(enable, onCompleteMsg);
+    }
+
+
+    public boolean setInternalDataEnabledFlag(boolean enable) {
+        return ((DcTracker)mDcTracker)
+                .setInternalDataEnabledFlag(enable);
+    }
+
+    public void resetSubSpecifics() {
+    }
     protected void log(String s) {
         Rlog.d(LOG_TAG, "[GSMPhone] " + s);
     }
diff --git a/src/java/com/android/internal/telephony/gsm/GsmCallTracker.java b/src/java/com/android/internal/telephony/gsm/GsmCallTracker.java
index 4a2566d..516520b 100644
--- a/src/java/com/android/internal/telephony/gsm/GsmCallTracker.java
+++ b/src/java/com/android/internal/telephony/gsm/GsmCallTracker.java
@@ -30,6 +30,7 @@
 import android.util.EventLog;
 import android.telephony.Rlog;
 
+import com.android.internal.telephony.Call;
 import com.android.internal.telephony.CallStateException;
 import com.android.internal.telephony.CallTracker;
 import com.android.internal.telephony.CommandsInterface;
@@ -88,6 +89,8 @@
 
     PhoneConstants.State mState = PhoneConstants.State.IDLE;
 
+    Call.SrvccState mSrvccState = Call.SrvccState.NONE;
+    Connection mHandoverConnection;
 
 
     //***** Events
@@ -504,6 +507,10 @@
                     // it's a ringing call
                     if (mConnections[i].getCall() == mRingingCall) {
                         newRinging = mConnections[i];
+                    } else if (mHandoverConnection != null) {
+                        // Single Radio Voice Call Continuity (SRVCC) completed
+                        mConnections[i].migrateFrom(mHandoverConnection);
+                        mHandoverConnection = null;
                     } else {
                         // Something strange happened: a call appeared
                         // which is neither a ringing call or one we created.
@@ -849,6 +856,15 @@
         return Phone.SuppService.UNKNOWN;
     }
 
+    /* package */
+    void notifySrvccState(Call.SrvccState state, Connection c) {
+        if (state == Call.SrvccState.STARTED) {
+            mHandoverConnection = c;
+        } else if (state != Call.SrvccState.COMPLETED) {
+            mHandoverConnection = null;
+        }
+    }
+
     //****** Overridden from Handler
 
     @Override
diff --git a/src/java/com/android/internal/telephony/gsm/GsmConnection.java b/src/java/com/android/internal/telephony/gsm/GsmConnection.java
index 9f6af51..c78d442 100644
--- a/src/java/com/android/internal/telephony/gsm/GsmConnection.java
+++ b/src/java/com/android/internal/telephony/gsm/GsmConnection.java
@@ -81,6 +81,8 @@
     UUSInfo mUusInfo;
     int mPreciseCause = 0;
 
+    Connection mOrigConnection;
+
     Handler mHandler;
 
     private PowerManager.WakeLock mPartialWakeLock;
@@ -186,6 +188,11 @@
         // and therefore don't need to compare the phone number anyway.
         if (! (mIsIncoming || c.isMT)) return true;
 
+        // A new call appearing by SRVCC may have invalid number
+        //  if IMS service is not tightly coupled with cellular modem stack.
+        // Thus we prefer the preexisting handover connection instance.
+        if (mOrigConnection != null) return true;
+
         // ... but we can compare phone numbers on MT calls, and we have
         // no control over when they begin, so we might as well
 
@@ -214,6 +221,11 @@
     }
 
     @Override
+    public long getConnectTimeReal() {
+        return mConnectTimeReal;
+    }
+
+    @Override
     public long getDisconnectTime() {
         return mDisconnectTime;
     }
@@ -230,6 +242,11 @@
     }
 
     @Override
+    public long getHoldingStartTime() {
+        return mHoldingStartTime;
+    }
+
+    @Override
     public long getHoldDurationMillis() {
         if (getState() != GsmCall.State.HOLDING) {
             // If not holding, return 0
@@ -375,9 +392,7 @@
             default:
                 GSMPhone phone = mOwner.mPhone;
                 int serviceState = phone.getServiceState().getState();
-                UiccCardApplication cardApp = UiccController
-                        .getInstance()
-                        .getUiccCardApplication(UiccController.APP_FAM_3GPP);
+                UiccCardApplication cardApp = phone.getUiccCardApplication();
                 AppState uiccAppState = (cardApp != null) ? cardApp.getState() :
                                                             AppState.APPSTATE_UNKNOWN;
                 if (serviceState == ServiceState.STATE_POWER_OFF) {
@@ -436,6 +451,8 @@
             if (mParent != null) {
                 changed = mParent.connectionDisconnected(this);
             }
+
+            mOrigConnection = null;
         }
         clearPostDialListeners();
         releaseWakeLock();
@@ -452,10 +469,15 @@
 
         newParent = parentFromDCState(dc.state);
 
-        if (!equalsHandlesNulls(mAddress, dc.number)) {
-            if (Phone.DEBUG_PHONE) log("update: phone # changed!");
-            mAddress = dc.number;
-            changed = true;
+        //Ignore dc.number and dc.name in case of a handover connection
+        if (mOrigConnection != null) {
+            if (Phone.DEBUG_PHONE) log("update: mOrigConnection is not null");
+        } else {
+            if (!equalsHandlesNulls(mAddress, dc.number)) {
+                if (Phone.DEBUG_PHONE) log("update: phone # changed!");
+                mAddress = dc.number;
+                changed = true;
+            }
         }
 
         // A null cnapName should be the same as ""
@@ -775,4 +797,45 @@
     public int getPreciseDisconnectCause() {
         return mPreciseCause;
     }
+
+    /* package */ void
+    migrateFrom(Connection c) {
+        if (c == null) return;
+
+        this.mAddress = c.getAddress();
+        this.mNumberPresentation = c.getNumberPresentation();
+
+        this.mDialString = c.getOrigDialString();
+
+        this.mCnapName = c.getCnapName();
+        this.mCnapNamePresentation = c.getCnapNamePresentation();
+
+        this.mIsIncoming = c.isIncoming();
+
+        this.mCreateTime = c.getCreateTime();
+        this.mConnectTime = c.getConnectTime();
+        this.mConnectTimeReal = c.getConnectTimeReal();
+
+        this.mHoldingStartTime = c.getHoldingStartTime();
+
+        this.mUusInfo = c.getUUSInfo();
+
+        this.setUserData(c.getUserData());
+
+        this.mOrigConnection = c;
+    }
+
+    @Override
+    public Connection getOrigConnection() {
+        return mOrigConnection;
+    }
+
+    @Override
+    public boolean isMultiparty() {
+        if (mOrigConnection != null) {
+            return mOrigConnection.isMultiparty();
+        }
+
+        return false;
+    }
 }
diff --git a/src/java/com/android/internal/telephony/gsm/GsmMmiCode.java b/src/java/com/android/internal/telephony/gsm/GsmMmiCode.java
index 3e1b1e8..377cc85 100644
--- a/src/java/com/android/internal/telephony/gsm/GsmMmiCode.java
+++ b/src/java/com/android/internal/telephony/gsm/GsmMmiCode.java
@@ -432,6 +432,11 @@
         return mMessage;
     }
 
+    public Phone
+    getPhone() {
+        return ((Phone) mPhone);
+    }
+
     // inherited javadoc suffices
     @Override
     public void
diff --git a/src/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java b/src/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
index 345abba..b732d44 100644
--- a/src/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
+++ b/src/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
@@ -25,6 +25,7 @@
 import android.provider.Telephony.Sms;
 import android.provider.Telephony.Sms.Intents;
 import android.telephony.Rlog;
+import android.telephony.TelephonyManager;
 
 import com.android.internal.telephony.GsmAlphabet;
 import com.android.internal.telephony.ImsSMSDispatcher;
@@ -35,6 +36,7 @@
 import com.android.internal.telephony.SmsHeader;
 import com.android.internal.telephony.SmsStorageMonitor;
 import com.android.internal.telephony.SmsUsageMonitor;
+import android.telephony.SubscriptionManager;
 import com.android.internal.telephony.TelephonyProperties;
 import com.android.internal.telephony.uicc.IccRecords;
 import com.android.internal.telephony.uicc.IccUtils;
@@ -264,7 +266,10 @@
     }
 
     protected UiccCardApplication getUiccCardApplication() {
-        return mUiccController.getUiccCardApplication(UiccController.APP_FAM_3GPP);
+            Rlog.d(TAG, "GsmSMSDispatcher: subId = " + mPhone.getSubId()
+                    + " slotId = " + mPhone.getPhoneId());
+                return mUiccController.getUiccCardApplication(mPhone.getPhoneId(),
+                        UiccController.APP_FAM_3GPP);
     }
 
     private void onUpdateIccAvailability() {
diff --git a/src/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java b/src/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java
index 5d0d8fb..f69942f 100644
--- a/src/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java
+++ b/src/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java
@@ -49,6 +49,7 @@
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
 import android.telephony.gsm.GsmCellLocation;
+import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.EventLog;
 import android.util.TimeUtils;
@@ -57,11 +58,14 @@
 import com.android.internal.telephony.CommandsInterface;
 import com.android.internal.telephony.EventLogTags;
 import com.android.internal.telephony.MccTable;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.ProxyController;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.RILConstants;
 import com.android.internal.telephony.RestrictedState;
 import com.android.internal.telephony.ServiceStateTracker;
+import android.telephony.SubscriptionManager;
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.internal.telephony.TelephonyProperties;
 import com.android.internal.telephony.dataconnection.DcTrackerBase;
@@ -84,10 +88,11 @@
  * {@hide}
  */
 final class GsmServiceStateTracker extends ServiceStateTracker {
-    private static final String LOG_TAG = "GsmSST";
-    private static final boolean VDBG = false;
-
-    GSMPhone mPhone;
+    static final String LOG_TAG = "GsmSST";
+    static final boolean VDBG = false;
+    //CAF_MSIM make it private ??
+    private static final int EVENT_ALL_DATA_DISCONNECTED = 1001;
+    private GSMPhone mPhone;
     GsmCellLocation mCellLoc;
     GsmCellLocation mNewCellLoc;
     int mPreferredNetworkType;
@@ -178,6 +183,10 @@
             if (intent.getAction().equals(Intent.ACTION_LOCALE_CHANGED)) {
                 // update emergency string whenever locale changed
                 updateSpnDisplay();
+            } else if (intent.getAction().equals(ACTION_RADIO_OFF)) {
+                mAlarmSwitch = false;
+                DcTrackerBase dcTracker = mPhone.mDcTracker;
+                powerOffRadioSafely(dcTracker);
             }
         }
     };
@@ -237,6 +246,11 @@
         filter.addAction(Intent.ACTION_LOCALE_CHANGED);
         phone.getContext().registerReceiver(mIntentReceiver, filter);
 
+        filter = new IntentFilter();
+        Context context = phone.getContext();
+        filter.addAction(ACTION_RADIO_OFF);
+        context.registerReceiver(mIntentReceiver, filter);
+
         // Gsm doesn't support OTASP so its not needed
         phone.notifyOtaspChanged(OTASP_NOT_NEEDED);
     }
@@ -466,6 +480,26 @@
                 onRestrictedStateChanged(ar);
                 break;
 
+            case EVENT_ALL_DATA_DISCONNECTED:
+                long dds = SubscriptionManager.getDefaultDataSubId();
+                ProxyController.getInstance().unregisterForAllDataDisconnected(dds, this);
+                synchronized(this) {
+                    if (mPendingRadioPowerOffAfterDataOff) {
+                        if (DBG) log("EVENT_ALL_DATA_DISCONNECTED, turn radio off now.");
+                        hangupAndPowerOff();
+                        mPendingRadioPowerOffAfterDataOff = false;
+                    } else {
+                        log("EVENT_ALL_DATA_DISCONNECTED is stale");
+                    }
+                }
+                break;
+
+            case EVENT_CHANGE_IMS_STATE:
+                if (DBG) log("EVENT_CHANGE_IMS_STATE:");
+
+                setPowerStateToDesired();
+                break;
+
             default:
                 super.handleMessage(msg);
             break;
@@ -474,15 +508,50 @@
 
     @Override
     protected void setPowerStateToDesired() {
+
+        if (DBG) {
+            log("mDesiredPowerState = " + mDesiredPowerState);
+            log("getRadioState = " + mCi.getRadioState());
+            log("mPowerOffDelayNeed = " + mPowerOffDelayNeed);
+            log("mAlarmSwitch = " + mAlarmSwitch);
+        }
+
+        if (mAlarmSwitch) {
+            if(DBG) log("mAlarmSwitch == true");
+            Context context = mPhone.getContext();
+            AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+            am.cancel(mRadioOffIntent);
+            mAlarmSwitch = false;
+        }
+
         // If we want it on and it's off, turn it on
         if (mDesiredPowerState
-            && mCi.getRadioState() == CommandsInterface.RadioState.RADIO_OFF) {
+                && mCi.getRadioState() == CommandsInterface.RadioState.RADIO_OFF) {
             mCi.setRadioPower(true, null);
         } else if (!mDesiredPowerState && mCi.getRadioState().isOn()) {
             // If it's on and available and we want it off gracefully
-            DcTrackerBase dcTracker = mPhone.mDcTracker;
-            powerOffRadioSafely(dcTracker);
-        } // Otherwise, we're in the desired state
+            if (mPowerOffDelayNeed) {
+                if (mImsRegistrationOnOff && !mAlarmSwitch) {
+                    if(DBG) log("mImsRegistrationOnOff == true");
+                    Context context = mPhone.getContext();
+                    AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+
+                    Intent intent = new Intent(ACTION_RADIO_OFF);
+                    mRadioOffIntent = PendingIntent.getBroadcast(context, 0, intent, 0);
+
+                    mAlarmSwitch = true;
+                    if (DBG) log("Alarm setting");
+                    am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+                            SystemClock.elapsedRealtime() + 3000, mRadioOffIntent);
+                } else {
+                    DcTrackerBase dcTracker = mPhone.mDcTracker;
+                    powerOffRadioSafely(dcTracker);
+                }
+            } else {
+                DcTrackerBase dcTracker = mPhone.mDcTracker;
+                powerOffRadioSafely(dcTracker);
+            }
+        }
     }
 
     @Override
@@ -572,6 +641,7 @@
             intent.putExtra(TelephonyIntents.EXTRA_SPN, spn);
             intent.putExtra(TelephonyIntents.EXTRA_SHOW_PLMN, showPlmn);
             intent.putExtra(TelephonyIntents.EXTRA_PLMN, plmn);
+            SubscriptionManager.putPhoneIdAndSubIdExtra(intent, mPhone.getPhoneId());
             mPhone.getContext().sendStickyBroadcastAsUser(intent, UserHandle.ALL);
         }
 
@@ -1318,7 +1388,7 @@
      * @return true if same operator
      */
     private boolean isSameNamedOperators(ServiceState s) {
-        String spn = SystemProperties.get(TelephonyProperties.PROPERTY_ICC_OPERATOR_ALPHA, "empty");
+        String spn = getSystemProperty(TelephonyProperties.PROPERTY_ICC_OPERATOR_ALPHA, "empty");
 
         String onsl = s.getOperatorAlphaLong();
         String onss = s.getOperatorAlphaShort();
@@ -1336,7 +1406,7 @@
      * @return true if both are same
      */
     private boolean currentMccEqualsSimMcc(ServiceState s) {
-        String simNumeric = SystemProperties.get(
+        String simNumeric = getSystemProperty(
                 TelephonyProperties.PROPERTY_ICC_OPERATOR_NUMERIC, "");
         String operatorNumeric = s.getOperatorNumeric();
         boolean equalsMcc = true;
@@ -1549,7 +1619,7 @@
                 zone = TimeZone.getTimeZone( tzname );
             }
 
-            String iso = SystemProperties.get(TelephonyProperties.PROPERTY_OPERATOR_ISO_COUNTRY);
+            String iso = getSystemProperty(TelephonyProperties.PROPERTY_OPERATOR_ISO_COUNTRY, "");
 
             if (zone == null) {
 
@@ -1785,7 +1855,6 @@
         mNotification.tickerText = title;
         mNotification.setLatestEventInfo(context, title, details,
                 mNotification.contentIntent);
-        mNotification.visibility = Notification.VISIBILITY_PUBLIC;
 
         NotificationManager notificationManager = (NotificationManager)
             context.getSystemService(Context.NOTIFICATION_SERVICE);
@@ -1799,14 +1868,18 @@
         }
     }
 
+    private UiccCardApplication getUiccCardApplication() {
+            return  mUiccController.getUiccCardApplication(mPhone.getPhoneId(),
+                    UiccController.APP_FAM_3GPP);
+    }
+
     @Override
     protected void onUpdateIccAvailability() {
         if (mUiccController == null ) {
             return;
         }
 
-        UiccCardApplication newUiccApplication =
-                mUiccController.getUiccCardApplication(UiccController.APP_FAM_3GPP);
+        UiccCardApplication newUiccApplication = getUiccCardApplication();
 
         if (mUiccApplcation != newUiccApplication) {
             if (mUiccApplcation != null) {
@@ -1874,4 +1947,70 @@
         pw.println(" mCurPlmn=" + mCurPlmn);
         pw.println(" mCurShowPlmn=" + mCurShowPlmn);
     }
+
+
+    /**
+     * Clean up existing voice and data connection then turn off radio power.
+     *
+     * Hang up the existing voice calls to decrease call drop rate.
+     */
+    @Override
+    public void powerOffRadioSafely(DcTrackerBase dcTracker) {
+        synchronized (this) {
+            if (!mPendingRadioPowerOffAfterDataOff) {
+                long dds = SubscriptionManager.getDefaultDataSubId();
+                // To minimize race conditions we call cleanUpAllConnections on
+                // both if else paths instead of before this isDisconnected test.
+                if (dcTracker.isDisconnected()
+                        && (dds == mPhone.getSubId()
+                            || (dds != mPhone.getSubId()
+                                && ProxyController.getInstance().isDataDisconnected(dds)))) {
+                    // To minimize race conditions we do this after isDisconnected
+                    dcTracker.cleanUpAllConnections(Phone.REASON_RADIO_TURNED_OFF);
+                    if (DBG) log("Data disconnected, turn off radio right away.");
+                    hangupAndPowerOff();
+                } else {
+                    dcTracker.cleanUpAllConnections(Phone.REASON_RADIO_TURNED_OFF);
+                    if (dds != mPhone.getSubId()
+                            && !ProxyController.getInstance().isDataDisconnected(dds)) {
+                        if (DBG) log("Data is active on DDS.  Wait for all data disconnect");
+                        // Data is not disconnected on DDS. Wait for the data disconnect complete
+                        // before sending the RADIO_POWER off.
+                        ProxyController.getInstance().registerForAllDataDisconnected(dds, this,
+                                EVENT_ALL_DATA_DISCONNECTED, null);
+                        mPendingRadioPowerOffAfterDataOff = true;
+                    }
+                    Message msg = Message.obtain(this);
+                    msg.what = EVENT_SET_RADIO_POWER_OFF;
+                    msg.arg1 = ++mPendingRadioPowerOffAfterDataOffTag;
+                    if (sendMessageDelayed(msg, 30000)) {
+                        if (DBG) log("Wait upto 30s for data to disconnect, then turn off radio.");
+                        mPendingRadioPowerOffAfterDataOff = true;
+                    } else {
+                        log("Cannot send delayed Msg, turn off radio right away.");
+                        hangupAndPowerOff();
+                        mPendingRadioPowerOffAfterDataOff = false;
+                    }
+                }
+            }
+        }
+
+    }
+
+    public void setImsRegistrationState(boolean registered){
+        if (mImsRegistrationOnOff && !registered) {
+            if (mAlarmSwitch) {
+                mImsRegistrationOnOff = registered;
+
+                Context context = mPhone.getContext();
+                AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+                am.cancel(mRadioOffIntent);
+                mAlarmSwitch = false;
+
+                sendMessage(obtainMessage(EVENT_CHANGE_IMS_STATE));
+                return;
+            }
+        }
+        mImsRegistrationOnOff = registered;
+    }
 }
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhone.java b/src/java/com/android/internal/telephony/imsphone/ImsPhone.java
new file mode 100644
index 0000000..5833393
--- /dev/null
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhone.java
@@ -0,0 +1,1033 @@
+/*
+ * Copyright (C) 2013 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.imsphone;
+
+import android.content.Context;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Registrant;
+import android.os.RegistrantList;
+
+import android.telephony.PhoneNumberUtils;
+import android.telephony.ServiceState;
+import android.telephony.Rlog;
+import android.text.TextUtils;
+
+import com.android.ims.ImsCallForwardInfo;
+import com.android.ims.ImsException;
+import com.android.ims.ImsReasonInfo;
+import com.android.ims.ImsSsInfo;
+import com.android.ims.ImsUtInterface;
+
+import static com.android.internal.telephony.CommandsInterface.CB_FACILITY_BAOC;
+import static com.android.internal.telephony.CommandsInterface.CB_FACILITY_BAOIC;
+import static com.android.internal.telephony.CommandsInterface.CB_FACILITY_BAOICxH;
+import static com.android.internal.telephony.CommandsInterface.CB_FACILITY_BAIC;
+import static com.android.internal.telephony.CommandsInterface.CB_FACILITY_BAICr;
+import static com.android.internal.telephony.CommandsInterface.CB_FACILITY_BA_ALL;
+import static com.android.internal.telephony.CommandsInterface.CB_FACILITY_BA_MO;
+import static com.android.internal.telephony.CommandsInterface.CB_FACILITY_BA_MT;
+
+import static com.android.internal.telephony.CommandsInterface.CF_ACTION_DISABLE;
+import static com.android.internal.telephony.CommandsInterface.CF_ACTION_ENABLE;
+import static com.android.internal.telephony.CommandsInterface.CF_ACTION_ERASURE;
+import static com.android.internal.telephony.CommandsInterface.CF_ACTION_REGISTRATION;
+import static com.android.internal.telephony.CommandsInterface.CF_REASON_ALL;
+import static com.android.internal.telephony.CommandsInterface.CF_REASON_ALL_CONDITIONAL;
+import static com.android.internal.telephony.CommandsInterface.CF_REASON_NO_REPLY;
+import static com.android.internal.telephony.CommandsInterface.CF_REASON_NOT_REACHABLE;
+import static com.android.internal.telephony.CommandsInterface.CF_REASON_BUSY;
+import static com.android.internal.telephony.CommandsInterface.CF_REASON_UNCONDITIONAL;
+import static com.android.internal.telephony.CommandsInterface.SERVICE_CLASS_VOICE;
+import static com.android.internal.telephony.CommandsInterface.SERVICE_CLASS_NONE;
+
+import com.android.internal.telephony.Call;
+import com.android.internal.telephony.CallForwardInfo;
+import com.android.internal.telephony.CallStateException;
+import com.android.internal.telephony.CallTracker;
+import com.android.internal.telephony.CommandException;
+import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.Connection;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneBase;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.PhoneNotifier;
+import com.android.internal.telephony.Subscription;
+import com.android.internal.telephony.VoicePhone;
+import com.android.internal.telephony.uicc.IccRecords;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * {@hide}
+ */
+public class ImsPhone extends ImsPhoneBase implements VoicePhone {
+    private static final String LOG_TAG = "ImsPhone";
+    private static final boolean DBG = true;
+    private static final boolean VDBG = false; // STOPSHIP if true
+
+    protected static final int EVENT_SET_CALL_BARRING_DONE          = EVENT_LAST + 1;
+    protected static final int EVENT_GET_CALL_BARRING_DONE          = EVENT_LAST + 2;
+    protected static final int EVENT_SET_CALL_WAITING_DONE          = EVENT_LAST + 3;
+    protected static final int EVENT_GET_CALL_WAITING_DONE          = EVENT_LAST + 4;
+
+    // Instance Variables
+    PhoneBase mDefaultPhone;
+    ImsPhoneCallTracker mCT;
+    ArrayList <ImsPhoneMmiCode> mPendingMMIs = new ArrayList<ImsPhoneMmiCode>();
+
+    Registrant mPostDialHandler;
+    ServiceState mSS = new ServiceState();
+
+    // To redial silently through GSM or CDMA when dialing through IMS fails
+    private String mLastDialString;
+
+    private final RegistrantList mSilentRedialRegistrants
+            = new RegistrantList();
+
+    // Create Cf (Call forward) so that dialling number &
+    // mIsCfu (true if reason is call forward unconditional)
+    // mOnComplete (Message object passed by client) can be packed &
+    // given as a single Cf object as user data to UtInterface.
+    private static class Cf {
+        final String mSetCfNumber;
+        final Message mOnComplete;
+        final boolean mIsCfu;
+
+        Cf(String cfNumber, boolean isCfu, Message onComplete) {
+            mSetCfNumber = cfNumber;
+            mIsCfu = isCfu;
+            mOnComplete = onComplete;
+        }
+    }
+
+    // Constructors
+
+    ImsPhone(Context context, PhoneNotifier notifier, Phone defaultPhone) {
+        super("ImsPhone", context, notifier);
+
+        mDefaultPhone = (PhoneBase) defaultPhone;
+        mCT = new ImsPhoneCallTracker(this);
+        mSS.setStateOff();
+
+        mPhoneId = mDefaultPhone.getPhoneId();
+    }
+
+    @Override
+    public void dispose() {
+        Rlog.d(LOG_TAG, "dispose");
+        // Nothing to dispose in PhoneBase
+        //super.dispose();
+        mPendingMMIs.clear();
+        mCT.dispose();
+
+        //Force all referenced classes to unregister their former registered events
+    }
+
+    @Override
+    public void removeReferences() {
+        Rlog.d(LOG_TAG, "removeReferences");
+        super.removeReferences();
+
+        mCT = null;
+        mSS = null;
+    }
+
+    @Override
+    public ServiceState
+    getServiceState() {
+        return mSS;
+    }
+
+    /* package */ void setServiceState(int state) {
+        mSS.setState(state);
+    }
+
+    @Override
+    public CallTracker getCallTracker() {
+        return mCT;
+    }
+
+    @Override
+    public List<? extends ImsPhoneMmiCode>
+    getPendingMmiCodes() {
+        return mPendingMMIs;
+    }
+
+
+    @Override
+    public void
+    acceptCall() throws CallStateException {
+        mCT.acceptCall();
+    }
+
+    @Override
+    public void
+    rejectCall() throws CallStateException {
+        mCT.rejectCall();
+    }
+
+    @Override
+    public void
+    switchHoldingAndActive() throws CallStateException {
+        mCT.switchWaitingOrHoldingAndActive();
+    }
+
+    @Override
+    public boolean canConference() {
+        return mCT.canConference();
+    }
+
+    public boolean canDial() {
+        return mCT.canDial();
+    }
+
+    @Override
+    public void conference() {
+        mCT.conference();
+    }
+
+    @Override
+    public void clearDisconnected() {
+        mCT.clearDisconnected();
+    }
+
+    @Override
+    public boolean canTransfer() {
+        return mCT.canTransfer();
+    }
+
+    @Override
+    public void explicitCallTransfer() {
+        mCT.explicitCallTransfer();
+    }
+
+    @Override
+    public ImsPhoneCall
+    getForegroundCall() {
+        return mCT.mForegroundCall;
+    }
+
+    @Override
+    public ImsPhoneCall
+    getBackgroundCall() {
+        return mCT.mBackgroundCall;
+    }
+
+    @Override
+    public ImsPhoneCall
+    getRingingCall() {
+        return mCT.mRingingCall;
+    }
+
+    private boolean handleCallDeflectionIncallSupplementaryService(
+            String dialString) {
+        if (dialString.length() > 1) {
+            return false;
+        }
+
+        if (getRingingCall().getState() != ImsPhoneCall.State.IDLE) {
+            if (DBG) Rlog.d(LOG_TAG, "MmiCode 0: rejectCall");
+            try {
+                mCT.rejectCall();
+            } catch (CallStateException e) {
+                if (DBG) Rlog.d(LOG_TAG, "reject failed", e);
+                notifySuppServiceFailed(Phone.SuppService.REJECT);
+            }
+        } else if (getBackgroundCall().getState() != ImsPhoneCall.State.IDLE) {
+            if (DBG) Rlog.d(LOG_TAG, "MmiCode 0: hangupWaitingOrBackground");
+            try {
+                mCT.hangup(getBackgroundCall());
+            } catch (CallStateException e) {
+                if (DBG) Rlog.d(LOG_TAG, "hangup failed", e);
+            }
+        }
+
+        return true;
+    }
+
+
+    private boolean handleCallWaitingIncallSupplementaryService(
+            String dialString) {
+        int len = dialString.length();
+
+        if (len > 2) {
+            return false;
+        }
+
+        ImsPhoneCall call = getForegroundCall();
+
+        try {
+            if (len > 1) {
+                if (DBG) Rlog.d(LOG_TAG, "not support 1X SEND");
+                notifySuppServiceFailed(Phone.SuppService.HANGUP);
+            } else {
+                if (call.getState() != ImsPhoneCall.State.IDLE) {
+                    if (DBG) Rlog.d(LOG_TAG, "MmiCode 1: hangup foreground");
+                    mCT.hangup(call);
+                } else {
+                    if (DBG) Rlog.d(LOG_TAG, "MmiCode 1: switchWaitingOrHoldingAndActive");
+                    mCT.switchWaitingOrHoldingAndActive();
+                }
+            }
+        } catch (CallStateException e) {
+            if (DBG) Rlog.d(LOG_TAG, "hangup failed", e);
+            notifySuppServiceFailed(Phone.SuppService.HANGUP);
+        }
+
+        return true;
+    }
+
+    private boolean handleCallHoldIncallSupplementaryService(String dialString) {
+        int len = dialString.length();
+
+        if (len > 2) {
+            return false;
+        }
+
+        ImsPhoneCall call = getForegroundCall();
+
+        if (len > 1) {
+            if (DBG) Rlog.d(LOG_TAG, "separate not supported");
+            notifySuppServiceFailed(Phone.SuppService.SEPARATE);
+        } else {
+            try {
+                if (getRingingCall().getState() != ImsPhoneCall.State.IDLE) {
+                    if (DBG) Rlog.d(LOG_TAG, "MmiCode 2: accept ringing call");
+                    mCT.acceptCall();
+                } else {
+                    if (DBG) Rlog.d(LOG_TAG, "MmiCode 2: switchWaitingOrHoldingAndActive");
+                    mCT.switchWaitingOrHoldingAndActive();
+                }
+            } catch (CallStateException e) {
+                if (DBG) Rlog.d(LOG_TAG, "switch failed", e);
+                notifySuppServiceFailed(Phone.SuppService.SWITCH);
+            }
+        }
+
+        return true;
+    }
+
+    private boolean handleMultipartyIncallSupplementaryService(
+            String dialString) {
+        if (dialString.length() > 1) {
+            return false;
+        }
+
+        if (DBG) Rlog.d(LOG_TAG, "MmiCode 3: merge calls");
+        conference();
+        return true;
+    }
+
+    private boolean handleEctIncallSupplementaryService(String dialString) {
+
+        int len = dialString.length();
+
+        if (len != 1) {
+            return false;
+        }
+
+        if (DBG) Rlog.d(LOG_TAG, "MmiCode 4: not support explicit call transfer");
+        notifySuppServiceFailed(Phone.SuppService.TRANSFER);
+        return true;
+    }
+
+    private boolean handleCcbsIncallSupplementaryService(String dialString) {
+        if (dialString.length() > 1) {
+            return false;
+        }
+
+        Rlog.i(LOG_TAG, "MmiCode 5: CCBS not supported!");
+        // Treat it as an "unknown" service.
+        notifySuppServiceFailed(Phone.SuppService.UNKNOWN);
+        return true;
+    }
+
+    @Override
+    public boolean handleInCallMmiCommands(String dialString) {
+        if (!isInCall()) {
+            return false;
+        }
+
+        if (TextUtils.isEmpty(dialString)) {
+            return false;
+        }
+
+        boolean result = false;
+        char ch = dialString.charAt(0);
+        switch (ch) {
+            case '0':
+                result = handleCallDeflectionIncallSupplementaryService(
+                        dialString);
+                break;
+            case '1':
+                result = handleCallWaitingIncallSupplementaryService(
+                        dialString);
+                break;
+            case '2':
+                result = handleCallHoldIncallSupplementaryService(dialString);
+                break;
+            case '3':
+                result = handleMultipartyIncallSupplementaryService(dialString);
+                break;
+            case '4':
+                result = handleEctIncallSupplementaryService(dialString);
+                break;
+            case '5':
+                result = handleCcbsIncallSupplementaryService(dialString);
+                break;
+            default:
+                break;
+        }
+
+        return result;
+    }
+
+    boolean isInCall() {
+        ImsPhoneCall.State foregroundCallState = getForegroundCall().getState();
+        ImsPhoneCall.State backgroundCallState = getBackgroundCall().getState();
+        ImsPhoneCall.State ringingCallState = getRingingCall().getState();
+
+       return (foregroundCallState.isAlive() ||
+               backgroundCallState.isAlive() ||
+               ringingCallState.isAlive());
+    }
+
+    @Override
+    public Connection
+    dial(String dialString) throws CallStateException {
+        // Need to make sure dialString gets parsed properly
+        String newDialString = PhoneNumberUtils.stripSeparators(dialString);
+
+        // handle in-call MMI first if applicable
+        if (handleInCallMmiCommands(newDialString)) {
+            return null;
+        }
+
+        if (mDefaultPhone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
+            return mCT.dial(dialString);
+        }
+
+        // Only look at the Network portion for mmi
+        String networkPortion = PhoneNumberUtils.extractNetworkPortionAlt(newDialString);
+        ImsPhoneMmiCode mmi =
+                ImsPhoneMmiCode.newFromDialString(networkPortion, this);
+        if (DBG) Rlog.d(LOG_TAG,
+                "dialing w/ mmi '" + mmi + "'...");
+
+        if (mmi == null) {
+            return mCT.dial(dialString);
+        } else if (mmi.isTemporaryModeCLIR()) {
+            return mCT.dial(mmi.getDialingNumber(), mmi.getCLIRMode());
+        } else if (!mmi.isSupportedOverImsPhone()) {
+            // If the mmi is not supported by IMS service,
+            // try to initiate dialing with default phone
+            throw new CallStateException(VoicePhone.CS_FALLBACK);
+        } else {
+            mPendingMMIs.add(mmi);
+            mMmiRegistrants.notifyRegistrants(new AsyncResult(null, mmi, null));
+            mmi.processCode();
+
+            return null;
+        }
+    }
+
+
+    @Override
+    public void
+    sendDtmf(char c) {
+        if (!PhoneNumberUtils.is12Key(c)) {
+            Rlog.e(LOG_TAG,
+                    "sendDtmf called with invalid character '" + c + "'");
+        } else {
+            if (mCT.mState ==  PhoneConstants.State.OFFHOOK) {
+                mCT.sendDtmf(c);
+            }
+        }
+    }
+
+    @Override
+    public void
+    startDtmf(char c) {
+        if (!PhoneNumberUtils.is12Key(c)) {
+            Rlog.e(LOG_TAG,
+                    "startDtmf called with invalid character '" + c + "'");
+        } else {
+            sendDtmf(c);
+        }
+    }
+
+    @Override
+    public void
+    stopDtmf() {
+        // no op
+    }
+
+    @Override
+    public void setOnPostDialCharacter(Handler h, int what, Object obj) {
+        mPostDialHandler = new Registrant(h, what, obj);
+    }
+
+    /*package*/ void notifyIncomingRing() {
+        if (DBG) Rlog.d(LOG_TAG, "notifyIncomingRing");
+        AsyncResult ar = new AsyncResult(null, null, null);
+        sendMessage(obtainMessage(EVENT_CALL_RING, ar));
+    }
+
+    @Override
+    public void setMute(boolean muted) {
+        mCT.setMute(muted);
+    }
+
+    @Override
+    public boolean getMute() {
+        return mCT.getMute();
+    }
+
+    @Override
+    public PhoneConstants.State getState() {
+        return mCT.mState;
+    }
+
+    private boolean isValidCommandInterfaceCFReason (int commandInterfaceCFReason) {
+        switch (commandInterfaceCFReason) {
+        case CF_REASON_UNCONDITIONAL:
+        case CF_REASON_BUSY:
+        case CF_REASON_NO_REPLY:
+        case CF_REASON_NOT_REACHABLE:
+        case CF_REASON_ALL:
+        case CF_REASON_ALL_CONDITIONAL:
+            return true;
+        default:
+            return false;
+        }
+    }
+
+    private boolean isValidCommandInterfaceCFAction (int commandInterfaceCFAction) {
+        switch (commandInterfaceCFAction) {
+        case CF_ACTION_DISABLE:
+        case CF_ACTION_ENABLE:
+        case CF_ACTION_REGISTRATION:
+        case CF_ACTION_ERASURE:
+            return true;
+        default:
+            return false;
+        }
+    }
+
+    private  boolean isCfEnable(int action) {
+        return (action == CF_ACTION_ENABLE) || (action == CF_ACTION_REGISTRATION);
+    }
+
+    private int getConditionFromCFReason(int reason) {
+        switch(reason) {
+            case CF_REASON_UNCONDITIONAL: return ImsUtInterface.CDIV_CF_UNCONDITIONAL;
+            case CF_REASON_BUSY: return ImsUtInterface.CDIV_CF_BUSY;
+            case CF_REASON_NO_REPLY: return ImsUtInterface.CDIV_CF_NO_REPLY;
+            case CF_REASON_NOT_REACHABLE: return ImsUtInterface.CDIV_CF_NOT_REACHABLE;
+            default:
+                break;
+        }
+
+        return ImsUtInterface.INVALID;
+    }
+
+    private int getCFReasonFromCondition(int condition) {
+        switch(condition) {
+            case ImsUtInterface.CDIV_CF_UNCONDITIONAL: return CF_REASON_UNCONDITIONAL;
+            case ImsUtInterface.CDIV_CF_BUSY: return CF_REASON_BUSY;
+            case ImsUtInterface.CDIV_CF_NO_REPLY: return CF_REASON_NO_REPLY;
+            case ImsUtInterface.CDIV_CF_NOT_REACHABLE: return CF_REASON_NOT_REACHABLE;
+            default:
+                break;
+        }
+
+        return CF_REASON_NOT_REACHABLE;
+    }
+
+    private int getActionFromCFAction(int action) {
+        switch(action) {
+            case CF_ACTION_DISABLE: return ImsUtInterface.ACTION_DEACTIVATION;
+            case CF_ACTION_ENABLE: return ImsUtInterface.ACTION_ACTIVATION;
+            case CF_ACTION_ERASURE: return ImsUtInterface.ACTION_ERASURE;
+            case CF_ACTION_REGISTRATION: return ImsUtInterface.ACTION_REGISTRATION;
+            default:
+                break;
+        }
+
+        return ImsUtInterface.INVALID;
+    }
+
+    @Override
+    public void getCallForwardingOption(int commandInterfaceCFReason,
+            Message onComplete) {
+        if (DBG) Rlog.d(LOG_TAG, "getCallForwardingOption reason=" + commandInterfaceCFReason);
+        if (isValidCommandInterfaceCFReason(commandInterfaceCFReason)) {
+            if (DBG) Rlog.d(LOG_TAG, "requesting call forwarding query.");
+            Message resp;
+            resp = obtainMessage(EVENT_GET_CALL_FORWARD_DONE, onComplete);
+
+            try {
+                ImsUtInterface ut = mCT.getUtInterface();
+                ut.queryCallForward(getConditionFromCFReason(commandInterfaceCFReason),null,resp);
+            } catch (ImsException e) {
+                sendErrorResponse(onComplete, e);
+            }
+        } else if (onComplete != null) {
+            sendErrorResponse(onComplete);
+        }
+    }
+
+    @Override
+    public void setCallForwardingOption(int commandInterfaceCFAction,
+            int commandInterfaceCFReason,
+            String dialingNumber,
+            int timerSeconds,
+            Message onComplete) {
+        if (DBG) Rlog.d(LOG_TAG, "setCallForwardingOption action=" + commandInterfaceCFAction
+                + ", reason=" + commandInterfaceCFReason);
+        if ((isValidCommandInterfaceCFAction(commandInterfaceCFAction)) &&
+                (isValidCommandInterfaceCFReason(commandInterfaceCFReason))) {
+            Message resp;
+            Cf cf = new Cf(dialingNumber,
+                    (commandInterfaceCFReason == CF_REASON_UNCONDITIONAL ? true : false),
+                    onComplete);
+            resp = obtainMessage(EVENT_SET_CALL_FORWARD_DONE,
+                    isCfEnable(commandInterfaceCFAction) ? 1 : 0, 0, cf);
+
+            try {
+                ImsUtInterface ut = mCT.getUtInterface();
+                ut.updateCallForward(getActionFromCFAction(commandInterfaceCFAction),
+                        getConditionFromCFReason(commandInterfaceCFReason),
+                        dialingNumber,
+                        timerSeconds,
+                        onComplete);
+             } catch (ImsException e) {
+                sendErrorResponse(onComplete, e);
+             }
+        } else if (onComplete != null) {
+            sendErrorResponse(onComplete);
+        }
+    }
+
+    @Override
+    public void getCallWaiting(Message onComplete) {
+        if (DBG) Rlog.d(LOG_TAG, "getCallWaiting");
+        Message resp;
+        resp = obtainMessage(EVENT_GET_CALL_WAITING_DONE, onComplete);
+
+        try {
+            ImsUtInterface ut = mCT.getUtInterface();
+            ut.queryCallWaiting(resp);
+        } catch (ImsException e) {
+            sendErrorResponse(onComplete, e);
+        }
+    }
+
+    @Override
+    public void setCallWaiting(boolean enable, Message onComplete) {
+        if (DBG) Rlog.d(LOG_TAG, "setCallWaiting enable=" + enable);
+        Message resp;
+        resp = obtainMessage(EVENT_SET_CALL_WAITING_DONE, onComplete);
+
+        try {
+            ImsUtInterface ut = mCT.getUtInterface();
+            ut.updateCallWaiting(enable, resp);
+        } catch (ImsException e) {
+            sendErrorResponse(onComplete, e);
+        }
+    }
+
+    private int getCBTypeFromFacility(String facility) {
+        if (CB_FACILITY_BAOC.equals(facility)) {
+            return ImsUtInterface.CB_BAOC;
+        } else if (CB_FACILITY_BAOIC.equals(facility)) {
+            return ImsUtInterface.CB_BOIC;
+        } else if (CB_FACILITY_BAOICxH.equals(facility)) {
+            return ImsUtInterface.CB_BOIC_EXHC;
+        } else if (CB_FACILITY_BAIC.equals(facility)) {
+            return ImsUtInterface.CB_BAIC;
+        } else if (CB_FACILITY_BAICr.equals(facility)) {
+            return ImsUtInterface.CB_BIC_WR;
+        } else if (CB_FACILITY_BA_ALL.equals(facility)) {
+            return 0;
+        } else if (CB_FACILITY_BA_MO.equals(facility)) {
+            return 0;
+        } else if (CB_FACILITY_BA_MT.equals(facility)) {
+            return 0;
+        }
+
+        return 0;
+    }
+
+    /* package */
+    void getCallBarring(String facility, Message onComplete) {
+        if (DBG) Rlog.d(LOG_TAG, "getCallBarring facility=" + facility);
+        Message resp;
+        resp = obtainMessage(EVENT_GET_CALL_BARRING_DONE, onComplete);
+
+        try {
+            ImsUtInterface ut = mCT.getUtInterface();
+            ut.queryCallBarring(getCBTypeFromFacility(facility), resp);
+        } catch (ImsException e) {
+            sendErrorResponse(onComplete, e);
+        }
+    }
+
+    /* package */
+    void setCallBarring(String facility, boolean lockState, String password, Message onComplete) {
+        if (DBG) Rlog.d(LOG_TAG, "setCallBarring facility=" + facility
+                + ", lockState=" + lockState);
+        Message resp;
+        resp = obtainMessage(EVENT_SET_CALL_BARRING_DONE, onComplete);
+
+        try {
+            ImsUtInterface ut = mCT.getUtInterface();
+            // password is not required with Ut interface
+            ut.updateCallBarring(getCBTypeFromFacility(facility), lockState, resp);
+        } catch (ImsException e) {
+            sendErrorResponse(onComplete, e);
+        }
+    }
+
+    @Override
+    public void sendUssdResponse(String ussdMessge) {
+        Rlog.d(LOG_TAG, "sendUssdResponse");
+        ImsPhoneMmiCode mmi = ImsPhoneMmiCode.newFromUssdUserInput(ussdMessge, this);
+        mPendingMMIs.add(mmi);
+        mMmiRegistrants.notifyRegistrants(new AsyncResult(null, mmi, null));
+        mmi.sendUssd(ussdMessge);
+    }
+
+    /* package */
+    void sendUSSD (String ussdString, Message response) {
+        mCT.sendUSSD(ussdString, response);
+    }
+
+    /* package */
+    void cancelUSSD() {
+        mCT.cancelUSSD();
+    }
+
+    /* package */
+    void sendErrorResponse(Message onComplete) {
+        Rlog.d(LOG_TAG, "sendErrorResponse");
+        if (onComplete != null) {
+            AsyncResult.forMessage(onComplete, null,
+                    new CommandException(CommandException.Error.GENERIC_FAILURE));
+            onComplete.sendToTarget();
+        }
+    }
+
+    /* package */
+    void sendErrorResponse(Message onComplete, Throwable e) {
+        Rlog.d(LOG_TAG, "sendErrorResponse");
+        if (onComplete != null) {
+            AsyncResult.forMessage(onComplete, null, getCommandException(e));
+            onComplete.sendToTarget();
+        }
+    }
+
+    /* package */
+    void sendErrorResponse(Message onComplete, ImsReasonInfo reasonInfo) {
+        Rlog.d(LOG_TAG, "sendErrorResponse reasonCode=" + reasonInfo.getCode());
+        if (onComplete != null) {
+            AsyncResult.forMessage(onComplete, null, getCommandException(reasonInfo.getCode()));
+            onComplete.sendToTarget();
+        }
+    }
+
+    /* package */
+    CommandException getCommandException(int code) {
+        Rlog.d(LOG_TAG, "getCommandException code=" + code);
+        CommandException.Error error = CommandException.Error.GENERIC_FAILURE;
+
+        switch(code) {
+            case ImsReasonInfo.CODE_UT_NOT_SUPPORTED:
+                error = CommandException.Error.REQUEST_NOT_SUPPORTED;
+                break;
+            case ImsReasonInfo.CODE_UT_CB_PASSWORD_MISMATCH:
+                error = CommandException.Error.PASSWORD_INCORRECT;
+                break;
+            default:
+                break;
+        }
+
+        return new CommandException(error);
+    }
+
+    /* package */
+    CommandException getCommandException(Throwable e) {
+        CommandException ex = null;
+
+        if (e instanceof ImsException) {
+            ex = getCommandException(((ImsException)e).getCode());
+        } else {
+            Rlog.d(LOG_TAG, "getCommandException generic failure");
+            ex = new CommandException(CommandException.Error.GENERIC_FAILURE);
+        }
+        return ex;
+    }
+
+    private void
+    onNetworkInitiatedUssd(ImsPhoneMmiCode mmi) {
+        Rlog.d(LOG_TAG, "onNetworkInitiatedUssd");
+        mMmiCompleteRegistrants.notifyRegistrants(
+            new AsyncResult(null, mmi, null));
+    }
+
+    /* package */
+    void onIncomingUSSD (int ussdMode, String ussdMessage) {
+        if (DBG) Rlog.d(LOG_TAG, "onIncomingUSSD ussdMode=" + ussdMode);
+
+        boolean isUssdError;
+        boolean isUssdRequest;
+
+        isUssdRequest
+            = (ussdMode == CommandsInterface.USSD_MODE_REQUEST);
+
+        isUssdError
+            = (ussdMode != CommandsInterface.USSD_MODE_NOTIFY
+                && ussdMode != CommandsInterface.USSD_MODE_REQUEST);
+
+        ImsPhoneMmiCode found = null;
+        for (int i = 0, s = mPendingMMIs.size() ; i < s; i++) {
+            if(mPendingMMIs.get(i).isPendingUSSD()) {
+                found = mPendingMMIs.get(i);
+                break;
+            }
+        }
+
+        if (found != null) {
+            // Complete pending USSD
+            if (isUssdError) {
+                found.onUssdFinishedError();
+            } else {
+                found.onUssdFinished(ussdMessage, isUssdRequest);
+            }
+        } else { // pending USSD not found
+            // The network may initiate its own USSD request
+
+            // ignore everything that isnt a Notify or a Request
+            // also, discard if there is no message to present
+            if (!isUssdError && ussdMessage != null) {
+                ImsPhoneMmiCode mmi;
+                mmi = ImsPhoneMmiCode.newNetworkInitiatedUssd(ussdMessage,
+                        isUssdRequest,
+                        ImsPhone.this);
+                onNetworkInitiatedUssd(mmi);
+            }
+        }
+    }
+
+    /**
+     * Removes the given MMI from the pending list and notifies
+     * registrants that it is complete.
+     * @param mmi MMI that is done
+     */
+    /*package*/ void
+    onMMIDone(ImsPhoneMmiCode mmi) {
+        /* Only notify complete if it's on the pending list.
+         * Otherwise, it's already been handled (eg, previously canceled).
+         * The exception is cancellation of an incoming USSD-REQUEST, which is
+         * not on the list.
+         */
+        if (mPendingMMIs.remove(mmi) || mmi.isUssdRequest()) {
+            mMmiCompleteRegistrants.notifyRegistrants(
+                    new AsyncResult(null, mmi, null));
+        }
+    }
+
+    @Override
+    public ImsPhoneConnection getHandoverConnection() {
+        ImsPhoneConnection conn = getForegroundCall().getHandoverConnection();
+
+        if (conn == null) {
+            conn = getBackgroundCall().getHandoverConnection();
+        }
+
+        return conn;
+    }
+
+    @Override
+    public void notifySrvccState(Call.SrvccState state) {
+        mCT.notifySrvccState(state);
+    }
+
+    /* package */ void
+    initiateSilentRedial() {
+        String result = mLastDialString;
+        AsyncResult ar = new AsyncResult(null, result, null);
+        if (ar != null) {
+            mSilentRedialRegistrants.notifyRegistrants(ar);
+        }
+    }
+
+    @Override
+    public void registerForSilentRedial(Handler h, int what, Object obj) {
+        mSilentRedialRegistrants.addUnique(h, what, obj);
+    }
+
+    @Override
+    public void unregisterForSilentRedial(Handler h) {
+        mSilentRedialRegistrants.remove(h);
+    }
+
+    @Override
+    public long getSubId() {
+        return mDefaultPhone.getSubId();
+    }
+
+    @Override
+    public int getPhoneId() {
+        return mDefaultPhone.getPhoneId();
+    }
+
+    @Override
+    public Subscription getSubscriptionInfo() {
+        return mDefaultPhone.getSubscriptionInfo();
+    }
+
+    private IccRecords getIccRecords() {
+        return mDefaultPhone.mIccRecords.get();
+    }
+
+    private CallForwardInfo getCallForwardInfo(ImsCallForwardInfo info) {
+        CallForwardInfo cfInfo = new CallForwardInfo();
+        cfInfo.status = info.mStatus;
+        cfInfo.reason = getCFReasonFromCondition(info.mCondition);
+        cfInfo.serviceClass = SERVICE_CLASS_VOICE;
+        cfInfo.toa = info.mToA;
+        cfInfo.number = info.mNumber;
+        cfInfo.timeSeconds = info.mTimeSeconds;
+        return cfInfo;
+    }
+
+    private CallForwardInfo[] handleCfQueryResult(ImsCallForwardInfo[] infos) {
+        CallForwardInfo[] cfInfos = null;
+
+        if (infos != null && infos.length != 0) {
+            cfInfos = new CallForwardInfo[infos.length];
+        }
+
+        IccRecords r = getIccRecords();
+        if (infos == null || infos.length == 0) {
+            if (r != null) {
+                // Assume the default is not active
+                // Set unconditional CFF in SIM to false
+                r.setVoiceCallForwardingFlag(1, false, null);
+            }
+        } else {
+            for (int i = 0, s = infos.length; i < s; i++) {
+                if (infos[i].mCondition == ImsUtInterface.CDIV_CF_UNCONDITIONAL) {
+                    if (r != null) {
+                        r.setVoiceCallForwardingFlag(1, (infos[i].mStatus == 1),
+                            infos[i].mNumber);
+                    }
+                }
+                cfInfos[i] = getCallForwardInfo(infos[i]);
+            }
+        }
+
+        return cfInfos;
+    }
+
+    private int[] handleCbQueryResult(ImsSsInfo[] infos) {
+        int[] cbInfos = new int[1];
+        cbInfos[0] = SERVICE_CLASS_NONE;
+
+        if (infos[0].mStatus == 1) {
+            cbInfos[0] = SERVICE_CLASS_VOICE;
+        }
+
+        return cbInfos;
+    }
+
+    private int[] handleCwQueryResult(ImsSsInfo[] infos) {
+        int[] cwInfos = new int[2];
+        cwInfos[0] = 0;
+
+        if (infos[0].mStatus == 1) {
+            cwInfos[0] = 1;
+            cwInfos[1] = SERVICE_CLASS_VOICE;
+        }
+
+        return cwInfos;
+    }
+
+    private void
+    sendResponse(Message onComplete, Object result, Throwable e) {
+        if (onComplete != null) {
+            CommandException ex = null;
+            if (e != null) {
+                ex = getCommandException(e);
+            }
+            AsyncResult.forMessage(onComplete, result, ex);
+            onComplete.sendToTarget();
+        }
+    }
+
+    @Override
+    public void handleMessage (Message msg) {
+        AsyncResult ar = (AsyncResult) msg.obj;
+        Message onComplete;
+
+        if (DBG) Rlog.d(LOG_TAG, "handleMessage what=" + msg.what);
+        switch (msg.what) {
+            case EVENT_SET_CALL_FORWARD_DONE:
+                IccRecords r = getIccRecords();
+                Cf cf = (Cf) ar.userObj;
+                if (cf.mIsCfu && ar.exception == null && r != null) {
+                    r.setVoiceCallForwardingFlag(1, msg.arg1 == 1, cf.mSetCfNumber);
+                }
+                sendResponse(cf.mOnComplete, null, ar.exception);
+                break;
+
+            case EVENT_GET_CALL_FORWARD_DONE:
+                CallForwardInfo[] cfInfos = null;
+                if (ar.exception == null) {
+                    cfInfos = handleCfQueryResult((ImsCallForwardInfo[])ar.result);
+                }
+                sendResponse((Message) ar.userObj, cfInfos, ar.exception);
+                break;
+
+             case EVENT_GET_CALL_BARRING_DONE:
+             case EVENT_GET_CALL_WAITING_DONE:
+                int[] ssInfos = null;
+                if (ar.exception == null) {
+                    if (msg.what == EVENT_GET_CALL_BARRING_DONE) {
+                        ssInfos = handleCbQueryResult((ImsSsInfo[])ar.result);
+                    } else if (msg.what == EVENT_GET_CALL_WAITING_DONE) {
+                        ssInfos = handleCwQueryResult((ImsSsInfo[])ar.result);
+                    }
+                }
+                sendResponse((Message) ar.userObj, ssInfos, ar.exception);
+                break;
+
+             case EVENT_SET_CALL_BARRING_DONE:
+             case EVENT_SET_CALL_WAITING_DONE:
+                sendResponse((Message) ar.userObj, null, ar.exception);
+                break;
+
+             default:
+                 super.handleMessage(msg);
+                 break;
+        }
+    }
+}
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneBase.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneBase.java
new file mode 100644
index 0000000..0743412
--- /dev/null
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneBase.java
@@ -0,0 +1,559 @@
+/*
+ * Copyright (C) 2013 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.imsphone;
+
+import android.content.Context;
+import android.net.LinkProperties;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Registrant;
+import android.os.RegistrantList;
+import android.os.SystemProperties;
+import android.telephony.CellInfo;
+import android.telephony.CellLocation;
+import android.telephony.ServiceState;
+import android.telephony.SignalStrength;
+import android.telephony.Rlog;
+
+import com.android.internal.telephony.Call;
+import com.android.internal.telephony.CallStateException;
+import com.android.internal.telephony.Connection;
+import com.android.internal.telephony.dataconnection.DataConnection;
+import com.android.internal.telephony.IccCard;
+import com.android.internal.telephony.IccPhoneBookInterfaceManager;
+import com.android.internal.telephony.MmiCode;
+import com.android.internal.telephony.OperatorInfo;
+import com.android.internal.telephony.PhoneBase;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.PhoneNotifier;
+import com.android.internal.telephony.PhoneSubInfo;
+import com.android.internal.telephony.TelephonyProperties;
+import com.android.internal.telephony.UUSInfo;
+import com.android.internal.telephony.uicc.IccFileHandler;
+
+import java.util.ArrayList;
+import java.util.List;
+
+abstract class ImsPhoneBase extends PhoneBase {
+    private static final String LOG_TAG = "ImsPhoneBase";
+
+    private RegistrantList mRingbackRegistrants = new RegistrantList();
+    private RegistrantList mOnHoldRegistrants = new RegistrantList();
+    private PhoneConstants.State mState = PhoneConstants.State.IDLE;
+
+    public ImsPhoneBase(String name, Context context, PhoneNotifier notifier) {
+        super(name, notifier, context, new ImsPhoneCommandInterface(context), false);
+    }
+
+    @Override
+    public Connection dial(String dialString, UUSInfo uusInfo)
+            throws CallStateException {
+        // ignore UUSInfo
+        return dial(dialString);
+    }
+
+    void migrateFrom(ImsPhoneBase from) {
+        migrate(mRingbackRegistrants, from.mRingbackRegistrants);
+        migrate(mPreciseCallStateRegistrants, from.mPreciseCallStateRegistrants);
+        migrate(mNewRingingConnectionRegistrants, from.mNewRingingConnectionRegistrants);
+        migrate(mIncomingRingRegistrants, from.mIncomingRingRegistrants);
+        migrate(mDisconnectRegistrants, from.mDisconnectRegistrants);
+        migrate(mServiceStateRegistrants, from.mServiceStateRegistrants);
+        migrate(mMmiCompleteRegistrants, from.mMmiCompleteRegistrants);
+        migrate(mMmiRegistrants, from.mMmiRegistrants);
+        migrate(mUnknownConnectionRegistrants, from.mUnknownConnectionRegistrants);
+        migrate(mSuppServiceFailedRegistrants, from.mSuppServiceFailedRegistrants);
+    }
+
+    static void migrate(RegistrantList to, RegistrantList from) {
+        from.removeCleared();
+        for (int i = 0, n = from.size(); i < n; i++) {
+            to.add((Registrant) from.get(i));
+        }
+    }
+
+    @Override
+    public void registerForRingbackTone(Handler h, int what, Object obj) {
+        mRingbackRegistrants.addUnique(h, what, obj);
+    }
+
+    @Override
+    public void unregisterForRingbackTone(Handler h) {
+        mRingbackRegistrants.remove(h);
+    }
+
+    protected void startRingbackTone() {
+        AsyncResult result = new AsyncResult(null, Boolean.TRUE, null);
+        mRingbackRegistrants.notifyRegistrants(result);
+    }
+
+    protected void stopRingbackTone() {
+        AsyncResult result = new AsyncResult(null, Boolean.FALSE, null);
+        mRingbackRegistrants.notifyRegistrants(result);
+    }
+
+    @Override
+    public void registerForOnHoldTone(Handler h, int what, Object obj) {
+        mOnHoldRegistrants.addUnique(h, what, obj);
+    }
+
+    @Override
+    public void unregisterForOnHoldTone(Handler h) {
+        mOnHoldRegistrants.remove(h);
+    }
+
+    protected void startOnHoldTone() {
+        AsyncResult result = new AsyncResult(null, Boolean.TRUE, null);
+        mOnHoldRegistrants.notifyRegistrants(result);
+    }
+
+    protected void stopOnHoldTone() {
+        AsyncResult result = new AsyncResult(null, Boolean.FALSE, null);
+        mOnHoldRegistrants.notifyRegistrants(result);
+    }
+
+    @Override
+    public ServiceState getServiceState() {
+        // FIXME: we may need to provide this when data connectivity is lost
+        // or when server is down
+        ServiceState s = new ServiceState();
+        s.setState(ServiceState.STATE_IN_SERVICE);
+        return s;
+    }
+
+    /**
+     * @return all available cell information or null if none.
+     */
+    @Override
+    public List<CellInfo> getAllCellInfo() {
+        return getServiceStateTracker().getAllCellInfo();
+    }
+
+    @Override
+    public CellLocation getCellLocation() {
+        return null;
+    }
+
+    @Override
+    public PhoneConstants.State getState() {
+        return mState;
+    }
+
+    @Override
+    public int getPhoneType() {
+        return PhoneConstants.PHONE_TYPE_IMS;
+    }
+
+    @Override
+    public SignalStrength getSignalStrength() {
+        return new SignalStrength();
+    }
+
+    @Override
+    public boolean getMessageWaitingIndicator() {
+        return false;
+    }
+
+    @Override
+    public boolean getCallForwardingIndicator() {
+        return false;
+    }
+
+    @Override
+    public List<? extends MmiCode> getPendingMmiCodes() {
+        return new ArrayList<MmiCode>(0);
+    }
+
+    @Override
+    public PhoneConstants.DataState getDataConnectionState() {
+        return PhoneConstants.DataState.DISCONNECTED;
+    }
+
+    @Override
+    public PhoneConstants.DataState getDataConnectionState(String apnType) {
+        return PhoneConstants.DataState.DISCONNECTED;
+    }
+
+    @Override
+    public DataActivityState getDataActivityState() {
+        return DataActivityState.NONE;
+    }
+
+    /**
+     * Notify any interested party of a Phone state change
+     * {@link com.android.internal.telephony.PhoneConstants.State}
+     */
+    /* package */ void notifyPhoneStateChanged() {
+        mNotifier.notifyPhoneState(this);
+    }
+
+    /**
+     * Notify registrants of a change in the call state. This notifies changes in
+     * {@link com.android.internal.telephony.Call.State}. Use this when changes
+     * in the precise call state are needed, else use notifyPhoneStateChanged.
+     */
+    /* package */ void notifyPreciseCallStateChanged() {
+        /* we'd love it if this was package-scoped*/
+        super.notifyPreciseCallStateChangedP();
+    }
+
+    void notifyNewRingingConnection(Connection c) {
+        super.notifyNewRingingConnectionP(c);
+    }
+
+    void notifyDisconnect(Connection cn) {
+        mDisconnectRegistrants.notifyResult(cn);
+    }
+
+    void notifyUnknownConnection() {
+        mUnknownConnectionRegistrants.notifyResult(this);
+    }
+
+    void notifySuppServiceFailed(SuppService code) {
+        mSuppServiceFailedRegistrants.notifyResult(code);
+    }
+
+    void notifyServiceStateChanged(ServiceState ss) {
+        super.notifyServiceStateChangedP(ss);
+    }
+
+    @Override
+    public void notifyCallForwardingIndicator() {
+        mNotifier.notifyCallForwardingChanged(this);
+    }
+
+    public boolean canDial() {
+        int serviceState = getServiceState().getState();
+        Rlog.v(LOG_TAG, "canDial(): serviceState = " + serviceState);
+        if (serviceState == ServiceState.STATE_POWER_OFF) return false;
+
+        String disableCall = SystemProperties.get(
+                TelephonyProperties.PROPERTY_DISABLE_CALL, "false");
+        Rlog.v(LOG_TAG, "canDial(): disableCall = " + disableCall);
+        if (disableCall.equals("true")) return false;
+
+        Rlog.v(LOG_TAG, "canDial(): ringingCall: " + getRingingCall().getState());
+        Rlog.v(LOG_TAG, "canDial(): foregndCall: " + getForegroundCall().getState());
+        Rlog.v(LOG_TAG, "canDial(): backgndCall: " + getBackgroundCall().getState());
+        return !getRingingCall().isRinging()
+                && (!getForegroundCall().getState().isAlive()
+                    || !getBackgroundCall().getState().isAlive());
+    }
+
+    @Override
+    public boolean handleInCallMmiCommands(String dialString) {
+        return false;
+    }
+
+    boolean isInCall() {
+        Call.State foregroundCallState = getForegroundCall().getState();
+        Call.State backgroundCallState = getBackgroundCall().getState();
+        Call.State ringingCallState = getRingingCall().getState();
+
+       return (foregroundCallState.isAlive() || backgroundCallState.isAlive()
+               || ringingCallState.isAlive());
+    }
+
+    @Override
+    public boolean handlePinMmi(String dialString) {
+        return false;
+    }
+
+    @Override
+    public void sendUssdResponse(String ussdMessge) {
+    }
+
+    @Override
+    public void registerForSuppServiceNotification(
+            Handler h, int what, Object obj) {
+    }
+
+    @Override
+    public void unregisterForSuppServiceNotification(Handler h) {
+    }
+
+    @Override
+    public void setRadioPower(boolean power) {
+    }
+
+    @Override
+    public String getVoiceMailNumber() {
+        return null;
+    }
+
+    @Override
+    public String getVoiceMailAlphaTag() {
+        return null;
+    }
+
+    @Override
+    public String getDeviceId() {
+        return null;
+    }
+
+    @Override
+    public String getDeviceSvn() {
+        return null;
+    }
+
+    @Override
+    public String getImei() {
+        return null;
+    }
+
+    @Override
+    public String getEsn() {
+        Rlog.e(LOG_TAG, "[VoltePhone] getEsn() is a CDMA method");
+        return "0";
+    }
+
+    @Override
+    public String getMeid() {
+        Rlog.e(LOG_TAG, "[VoltePhone] getMeid() is a CDMA method");
+        return "0";
+    }
+
+    @Override
+    public String getSubscriberId() {
+        return null;
+    }
+
+    @Override
+    public String getGroupIdLevel1() {
+        return null;
+    }
+
+    @Override
+    public String getIccSerialNumber() {
+        return null;
+    }
+
+    @Override
+    public String getLine1Number() {
+        return null;
+    }
+
+    @Override
+    public String getLine1AlphaTag() {
+        return null;
+    }
+
+    @Override
+    public void setLine1Number(String alphaTag, String number, Message onComplete) {
+        // FIXME: what to reply for Volte?
+        AsyncResult.forMessage(onComplete, null, null);
+        onComplete.sendToTarget();
+    }
+
+    @Override
+    public void setVoiceMailNumber(String alphaTag, String voiceMailNumber,
+            Message onComplete) {
+        // FIXME: what to reply for Volte?
+        AsyncResult.forMessage(onComplete, null, null);
+        onComplete.sendToTarget();
+    }
+
+    @Override
+    public void getCallForwardingOption(int commandInterfaceCFReason, Message onComplete) {
+    }
+
+    @Override
+    public void setCallForwardingOption(int commandInterfaceCFAction,
+            int commandInterfaceCFReason, String dialingNumber,
+            int timerSeconds, Message onComplete) {
+    }
+
+    @Override
+    public void getOutgoingCallerIdDisplay(Message onComplete) {
+        // FIXME: what to reply?
+        AsyncResult.forMessage(onComplete, null, null);
+        onComplete.sendToTarget();
+    }
+
+    @Override
+    public void setOutgoingCallerIdDisplay(int commandInterfaceCLIRMode,
+            Message onComplete) {
+        // FIXME: what's this for Volte?
+        AsyncResult.forMessage(onComplete, null, null);
+        onComplete.sendToTarget();
+    }
+
+    @Override
+    public void getCallWaiting(Message onComplete) {
+        AsyncResult.forMessage(onComplete, null, null);
+        onComplete.sendToTarget();
+    }
+
+    @Override
+    public void setCallWaiting(boolean enable, Message onComplete) {
+        Rlog.e(LOG_TAG, "call waiting not supported");
+    }
+
+    @Override
+    public boolean getIccRecordsLoaded() {
+        return false;
+    }
+
+    @Override
+    public IccCard getIccCard() {
+        return null;
+    }
+
+    @Override
+    public void getAvailableNetworks(Message response) {
+    }
+
+    @Override
+    public void setNetworkSelectionModeAutomatic(Message response) {
+    }
+
+    @Override
+    public void selectNetworkManually(
+            OperatorInfo network,
+            Message response) {
+    }
+
+    @Override
+    public void getNeighboringCids(Message response) {
+    }
+
+    @Override
+    public void getDataCallList(Message response) {
+    }
+
+    public List<DataConnection> getCurrentDataConnectionList () {
+        return null;
+    }
+
+    @Override
+    public void updateServiceLocation() {
+    }
+
+    @Override
+    public void enableLocationUpdates() {
+    }
+
+    @Override
+    public void disableLocationUpdates() {
+    }
+
+    @Override
+    public boolean getDataRoamingEnabled() {
+        return false;
+    }
+
+    @Override
+    public void setDataRoamingEnabled(boolean enable) {
+    }
+
+    @Override
+    public boolean getDataEnabled() {
+        return false;
+    }
+
+    @Override
+    public void setDataEnabled(boolean enable) {
+    }
+
+
+    public boolean enableDataConnectivity() {
+        return false;
+    }
+
+    public boolean disableDataConnectivity() {
+        return false;
+    }
+
+    @Override
+    public boolean isDataConnectivityPossible() {
+        return false;
+    }
+
+    boolean updateCurrentCarrierInProvider() {
+        return false;
+    }
+
+    public void saveClirSetting(int commandInterfaceCLIRMode) {
+    }
+
+    @Override
+    public PhoneSubInfo getPhoneSubInfo(){
+        return null;
+    }
+
+    @Override
+    public IccPhoneBookInterfaceManager getIccPhoneBookInterfaceManager(){
+        return null;
+    }
+
+    @Override
+    public IccFileHandler getIccFileHandler(){
+        return null;
+    }
+
+    @Override
+    public void activateCellBroadcastSms(int activate, Message response) {
+        Rlog.e(LOG_TAG, "Error! This functionality is not implemented for Volte.");
+    }
+
+    @Override
+    public void getCellBroadcastSmsConfig(Message response) {
+        Rlog.e(LOG_TAG, "Error! This functionality is not implemented for Volte.");
+    }
+
+    @Override
+    public void setCellBroadcastSmsConfig(int[] configValuesArray, Message response){
+        Rlog.e(LOG_TAG, "Error! This functionality is not implemented for Volte.");
+    }
+
+    //@Override
+    @Override
+    public boolean needsOtaServiceProvisioning() {
+        // FIXME: what's this for Volte?
+        return false;
+    }
+
+    //@Override
+    @Override
+    public LinkProperties getLinkProperties(String apnType) {
+        // FIXME: what's this for Volte?
+        return null;
+    }
+
+    @Override
+    protected void onUpdateIccAvailability() {
+    }
+
+    void updatePhoneState() {
+        PhoneConstants.State oldState = mState;
+
+        if (getRingingCall().isRinging()) {
+            mState = PhoneConstants.State.RINGING;
+        } else if (getForegroundCall().isIdle()
+                && getBackgroundCall().isIdle()) {
+            mState = PhoneConstants.State.IDLE;
+        } else {
+            mState = PhoneConstants.State.OFFHOOK;
+        }
+
+        if (mState != oldState) {
+            Rlog.d(LOG_TAG, " ^^^ new phone state: " + mState);
+            notifyPhoneStateChanged();
+        }
+    }
+}
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCall.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCall.java
new file mode 100644
index 0000000..a6cab1e
--- /dev/null
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCall.java
@@ -0,0 +1,298 @@
+/*
+ * Copyright (C) 2013 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.imsphone;
+
+import android.telephony.Rlog;
+import android.telephony.DisconnectCause;
+
+import com.android.internal.telephony.Call;
+import com.android.internal.telephony.CallStateException;
+import com.android.internal.telephony.Connection;
+import com.android.internal.telephony.Phone;
+import com.android.ims.ImsCall;
+import com.android.ims.ImsException;
+import com.android.ims.ImsStreamMediaProfile;
+
+import java.util.List;
+
+/**
+ * {@hide}
+ */
+class ImsPhoneCall extends Call {
+    /*************************** Instance Variables **************************/
+
+    private static final String LOG_TAG = "ImsPhoneCall";
+
+    /*package*/ ImsPhoneCallTracker mOwner;
+
+    private boolean mRingbackTonePlayed = false;
+
+    /****************************** Constructors *****************************/
+    /*package*/
+    ImsPhoneCall() {
+    }
+
+    /*package*/
+    ImsPhoneCall(ImsPhoneCallTracker owner) {
+        mOwner = owner;
+    }
+
+    public void dispose() {
+        try {
+            mOwner.hangup(this);
+        } catch (CallStateException ex) {
+            //Rlog.e(LOG_TAG, "dispose: unexpected error on hangup", ex);
+            //while disposing, ignore the exception and clean the connections
+        } finally {
+            for(int i = 0, s = mConnections.size(); i < s; i++) {
+                ImsPhoneConnection c = (ImsPhoneConnection) mConnections.get(i);
+                c.onDisconnect(DisconnectCause.LOST_SIGNAL);
+            }
+        }
+    }
+
+    /************************** Overridden from Call *************************/
+
+    @Override
+    public List<Connection>
+    getConnections() {
+        return mConnections;
+    }
+
+    @Override
+    public Phone
+    getPhone() {
+        return mOwner.mPhone;
+    }
+
+    @Override
+    public boolean
+    isMultiparty() {
+        return mConnections.size() > 1;
+    }
+
+    /** Please note: if this is the foreground call and a
+     *  background call exists, the background call will be resumed.
+     */
+    @Override
+    public void
+    hangup() throws CallStateException {
+        mOwner.hangup(this);
+    }
+
+    @Override
+    public String
+    toString() {
+        return mState.toString();
+    }
+
+    //***** Called from ImsPhoneConnection
+
+    /*package*/ void
+    attach(Connection conn) {
+        clearDisconnected();
+        mConnections.add(conn);
+    }
+
+    /*package*/ void
+    attach(Connection conn, State state) {
+        this.attach(conn);
+        mState = state;
+    }
+
+    /*package*/ void
+    attachFake(Connection conn, State state) {
+        attach(conn, state);
+    }
+
+    /**
+     * Called by ImsPhoneConnection when it has disconnected
+     */
+    boolean
+    connectionDisconnected(ImsPhoneConnection conn) {
+        if (mState != State.DISCONNECTED) {
+            /* If only disconnected connections remain, we are disconnected*/
+
+            boolean hasOnlyDisconnectedConnections = true;
+
+            for (int i = 0, s = mConnections.size()  ; i < s; i ++) {
+                if (mConnections.get(i).getState() != State.DISCONNECTED) {
+                    hasOnlyDisconnectedConnections = false;
+                    break;
+                }
+            }
+
+            if (hasOnlyDisconnectedConnections) {
+                mState = State.DISCONNECTED;
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /*package*/ void
+    detach(ImsPhoneConnection conn) {
+        mConnections.remove(conn);
+
+        if (mConnections.size() == 0) {
+            mState = State.IDLE;
+        }
+    }
+
+    /**
+     * @return true if there's no space in this call for additional
+     * connections to be added via "conference"
+     */
+    /*package*/ boolean
+    isFull() {
+        return mConnections.size() == ImsPhoneCallTracker.MAX_CONNECTIONS_PER_CALL;
+    }
+
+    //***** Called from ImsPhoneCallTracker
+    /**
+     * Called when this Call is being hung up locally (eg, user pressed "end")
+     */
+    void
+    onHangupLocal() {
+        for (int i = 0, s = mConnections.size(); i < s; i++) {
+            ImsPhoneConnection cn = (ImsPhoneConnection)mConnections.get(i);
+            cn.onHangupLocal();
+        }
+        mState = State.DISCONNECTING;
+    }
+
+    /**
+     * Called when it's time to clean up disconnected Connection objects
+     */
+    void
+    clearDisconnected() {
+        for (int i = mConnections.size() - 1 ; i >= 0 ; i--) {
+            ImsPhoneConnection cn = (ImsPhoneConnection)mConnections.get(i);
+
+            if (cn.getState() == State.DISCONNECTED) {
+                mConnections.remove(i);
+            }
+        }
+
+        if (mConnections.size() == 0) {
+            mState = State.IDLE;
+        }
+    }
+
+    /*package*/ ImsPhoneConnection
+    getFirstConnection() {
+        if (mConnections.size() == 0) return null;
+
+        return (ImsPhoneConnection) mConnections.get(0);
+    }
+
+    /*package*/ void
+    setMute(boolean mute) {
+        ImsCall imsCall = getFirstConnection() == null ?
+                null : getFirstConnection().getImsCall();
+        if (imsCall != null) {
+            try {
+                imsCall.setMute(mute);
+            } catch (ImsException e) {
+                Rlog.e(LOG_TAG, "setMute failed : " + e.getMessage());
+            }
+        }
+    }
+
+    /* package */ void
+    merge(ImsPhoneCall that, State state) {
+        ImsPhoneConnection[] cc = that.mConnections.toArray(
+                new ImsPhoneConnection[that.mConnections.size()]);
+        for (ImsPhoneConnection c : cc) {
+            c.update(null, state);
+        }
+    }
+
+    /*package*/ ImsCall
+    getImsCall() {
+        return (getFirstConnection() == null) ? null : getFirstConnection().getImsCall();
+    }
+
+    /*package*/ static boolean isLocalTone(ImsCall imsCall) {
+        if ((imsCall == null) || (imsCall.getCallProfile() == null)
+                || (imsCall.getCallProfile().mMediaProfile == null)) {
+            return false;
+        }
+
+        ImsStreamMediaProfile mediaProfile = imsCall.getCallProfile().mMediaProfile;
+
+        return (mediaProfile.mAudioDirection == ImsStreamMediaProfile.DIRECTION_INACTIVE)
+                ? true : false;
+    }
+
+    /*package*/ boolean
+    update (ImsPhoneConnection conn, ImsCall imsCall, State state) {
+        State newState = state;
+        boolean changed = false;
+
+        //ImsCall.Listener.onCallProgressing can be invoked several times
+        //and ringback tone mode can be changed during the call setup procedure
+        if (state == State.ALERTING) {
+            if (mRingbackTonePlayed && !isLocalTone(imsCall)) {
+                mOwner.mPhone.stopRingbackTone();
+                mRingbackTonePlayed = false;
+            } else if (!mRingbackTonePlayed && isLocalTone(imsCall)) {
+                mOwner.mPhone.startRingbackTone();
+                mRingbackTonePlayed = true;
+            }
+        } else {
+            if (mRingbackTonePlayed) {
+                mOwner.mPhone.stopRingbackTone();
+                mRingbackTonePlayed = false;
+            }
+        }
+
+        if ((newState != mState) && (state != State.DISCONNECTED)) {
+            mState = newState;
+            changed = true;
+        } else if (state == State.DISCONNECTED) {
+            changed = true;
+        }
+
+        return changed;
+    }
+
+    /* package */ ImsPhoneConnection
+    getHandoverConnection() {
+        ImsPhoneConnection conn = (ImsPhoneConnection) getEarliestConnection();
+        conn.setMultiparty(isMultiparty());
+        return conn;
+    }
+
+    void switchWith(ImsPhoneCall that) {
+        synchronized (ImsPhoneCall.class) {
+            ImsPhoneCall tmp = new ImsPhoneCall();
+            tmp.takeOver(this);
+            this.takeOver(that);
+            that.takeOver(tmp);
+        }
+    }
+
+    private void takeOver(ImsPhoneCall that) {
+        mConnections = that.mConnections;
+        mState = that.mState;
+        for (Connection c : mConnections) {
+            ((ImsPhoneConnection) c).changeParent(this);
+        }
+    }
+}
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
new file mode 100644
index 0000000..937020a
--- /dev/null
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
@@ -0,0 +1,1193 @@
+/*
+ * Copyright (C) 2013 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.imsphone;
+
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Registrant;
+import android.os.RegistrantList;
+import android.os.SystemProperties;
+import android.preference.PreferenceManager;
+import android.telephony.DisconnectCause;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.ServiceState;
+import android.telephony.Rlog;
+
+import com.android.internal.telephony.Call;
+import com.android.internal.telephony.CallStateException;
+import com.android.internal.telephony.CallTracker;
+import com.android.internal.telephony.CommandException;
+import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.Connection;
+import com.android.internal.telephony.EventLogTags;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneBase;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.TelephonyProperties;
+
+import com.android.ims.ImsCall;
+import com.android.ims.ImsCallProfile;
+import com.android.ims.ImsConnectionStateListener;
+import com.android.ims.ImsException;
+import com.android.ims.ImsManager;
+import com.android.ims.ImsReasonInfo;
+import com.android.ims.ImsServiceClass;
+import com.android.ims.ImsUtInterface;
+
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ * {@hide}
+ */
+public final class ImsPhoneCallTracker extends CallTracker {
+    static final String LOG_TAG = "ImsPhoneCallTracker";
+
+    private static final boolean DBG = true;
+
+    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (intent.getAction().equals(ImsManager.ACTION_IMS_INCOMING_CALL)) {
+                if (DBG) log("onReceive : incoming call intent");
+
+                if (mImsManager == null) return;
+
+                if (mServiceId < 0) return;
+
+                try {
+                    // Network initiated USSD will be treated by mImsUssdListener
+                    boolean isUssd = intent.getBooleanExtra(ImsManager.EXTRA_USSD, false);
+                    if (isUssd) {
+                        if (DBG) log("onReceive : USSD");
+                        mUssdSession = mImsManager.takeCall(mServiceId, intent, mImsUssdListener);
+                        if (mUssdSession != null) {
+                            mUssdSession.accept();
+                        }
+                        return;
+                    }
+
+                    // Normal MT call
+                    ImsCall imsCall = mImsManager.takeCall(mServiceId, intent, mImsCallListener);
+
+                    ImsPhoneConnection conn = new ImsPhoneConnection(mPhone.getContext(), imsCall,
+                            ImsPhoneCallTracker.this, mRingingCall);
+                    addConnection(conn);
+
+                    if ((mForegroundCall.getState() != ImsPhoneCall.State.IDLE) ||
+                            (mBackgroundCall.getState() != ImsPhoneCall.State.IDLE)) {
+                        conn.update(imsCall, ImsPhoneCall.State.WAITING);
+                    }
+
+                    mPhone.notifyNewRingingConnection(conn);
+                    mPhone.notifyIncomingRing();
+
+                    updatePhoneState();
+                    mPhone.notifyPreciseCallStateChanged();
+                } catch (ImsException e) {
+                    loge("onReceive : exception " + e);
+                }
+            }
+        }
+    };
+
+    //***** Constants
+
+    static final int MAX_CONNECTIONS = 7;
+    static final int MAX_CONNECTIONS_PER_CALL = 5;
+
+    private static final int EVENT_HANGUP_PENDINGMO = 18;
+    private static final int EVENT_RESUME_BACKGROUND = 19;
+    private static final int EVENT_DIAL_PENDINGMO = 20;
+
+    private static final int TIMEOUT_HANGUP_PENDINGMO = 500;
+
+    //***** Instance Variables
+    private ArrayList<ImsPhoneConnection> mConnections = new ArrayList<ImsPhoneConnection>();
+    private RegistrantList mVoiceCallEndedRegistrants = new RegistrantList();
+    private RegistrantList mVoiceCallStartedRegistrants = new RegistrantList();
+
+    ImsPhoneCall mRingingCall = new ImsPhoneCall(this);
+    ImsPhoneCall mForegroundCall = new ImsPhoneCall(this);
+    ImsPhoneCall mBackgroundCall = new ImsPhoneCall(this);
+    ImsPhoneCall mHandoverCall = new ImsPhoneCall(this);
+
+    private ImsPhoneConnection mPendingMO;
+    private int mClirMode = CommandsInterface.CLIR_DEFAULT;
+    private Object mSyncHold = new Object();
+
+    private ImsCall mUssdSession = null;
+    private Message mPendingUssd = null;
+
+    ImsPhone mPhone;
+
+    private boolean mDesiredMute = false;    // false = mute off
+    private boolean mOnHoldToneStarted = false;
+
+    PhoneConstants.State mState = PhoneConstants.State.IDLE;
+
+    private ImsManager mImsManager;
+    private int mServiceId = -1;
+
+    private Call.SrvccState mSrvccState = Call.SrvccState.NONE;
+
+    //***** Events
+
+
+    //***** Constructors
+
+    ImsPhoneCallTracker(ImsPhone phone) {
+        this.mPhone = phone;
+
+        IntentFilter intentfilter = new IntentFilter();
+        intentfilter.addAction(ImsManager.ACTION_IMS_INCOMING_CALL);
+        mPhone.getContext().registerReceiver(mReceiver, intentfilter);
+
+        Thread t = new Thread() {
+            public void run() {
+                getImsService();
+            }
+        };
+        t.start();
+    }
+
+    private PendingIntent createIncomingCallPendingIntent() {
+        Intent intent = new Intent(ImsManager.ACTION_IMS_INCOMING_CALL);
+        return PendingIntent.getBroadcast(mPhone.getContext(), 0, intent,
+                PendingIntent.FLAG_UPDATE_CURRENT);
+    }
+
+    private void getImsService() {
+        if (DBG) log("getImsService");
+        mImsManager = ImsManager.getInstance(mPhone.getContext());
+        try {
+            // If there are multiple GSM phones,
+            // each GSM phone creates IMS phone for itself.
+            // IMS service needs something to determine appropriate IMS phone.
+            //
+            // TODO: add additional parameter to identity the IMS phone's
+            // subscriber's information.
+            mServiceId = mImsManager.open(ImsServiceClass.MMTEL,
+                    createIncomingCallPendingIntent(),
+                    mImsConnectionStateListener);
+        } catch (ImsException e) {
+            loge("getImsService: " + e);
+            //Leave mImsManager as null, then CallStateException will be thrown when dialing
+            mImsManager = null;
+        }
+    }
+
+    public void dispose() {
+        if (DBG) log("dispose");
+        mRingingCall.dispose();
+        mBackgroundCall.dispose();
+        mForegroundCall.dispose();
+        mHandoverCall.dispose();
+
+        clearDisconnected();
+        mPhone.getContext().unregisterReceiver(mReceiver);
+    }
+
+    @Override
+    protected void finalize() {
+        log("ImsPhoneCallTracker finalized");
+    }
+
+    //***** Instance Methods
+
+    //***** Public Methods
+    @Override
+    public void registerForVoiceCallStarted(Handler h, int what, Object obj) {
+        Registrant r = new Registrant(h, what, obj);
+        mVoiceCallStartedRegistrants.add(r);
+    }
+
+    @Override
+    public void unregisterForVoiceCallStarted(Handler h) {
+        mVoiceCallStartedRegistrants.remove(h);
+    }
+
+    @Override
+    public void registerForVoiceCallEnded(Handler h, int what, Object obj) {
+        Registrant r = new Registrant(h, what, obj);
+        mVoiceCallEndedRegistrants.add(r);
+    }
+
+    @Override
+    public void unregisterForVoiceCallEnded(Handler h) {
+        mVoiceCallEndedRegistrants.remove(h);
+    }
+
+    Connection
+    dial(String dialString) throws CallStateException {
+        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mPhone.getContext());
+        int oirMode = sp.getInt(PhoneBase.CLIR_KEY, CommandsInterface.CLIR_DEFAULT);
+        return dial(dialString, oirMode);
+    }
+
+    /**
+     * oirMode is one of the CLIR_ constants
+     */
+    synchronized Connection
+    dial(String dialString, int clirMode) throws CallStateException {
+        if (DBG) log("dial clirMode=" + clirMode);
+
+        // note that this triggers call state changed notif
+        clearDisconnected();
+
+        if (mImsManager == null) {
+            throw new CallStateException("service not available");
+        }
+
+        if (!canDial()) {
+            throw new CallStateException("cannot dial in current state");
+        }
+
+        boolean holdBeforeDial = false;
+
+        // The new call must be assigned to the foreground call.
+        // That call must be idle, so place anything that's
+        // 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");
+            }
+            // foreground call is empty for the newly dialed connection
+            holdBeforeDial = true;
+            switchWaitingOrHoldingAndActive();
+        }
+
+        ImsPhoneCall.State fgState = ImsPhoneCall.State.IDLE;
+        ImsPhoneCall.State bgState = ImsPhoneCall.State.IDLE;
+
+        mClirMode = clirMode;
+
+        synchronized (mSyncHold) {
+            if (holdBeforeDial) {
+                fgState = mForegroundCall.getState();
+                bgState = mBackgroundCall.getState();
+
+                //holding foreground call failed
+                if (fgState == ImsPhoneCall.State.ACTIVE) {
+                    throw new CallStateException("cannot dial in current state");
+                }
+
+                //holding foreground call succeeded
+                if (bgState == ImsPhoneCall.State.HOLDING) {
+                    holdBeforeDial = false;
+                }
+            }
+
+            mPendingMO = new ImsPhoneConnection(mPhone.getContext(),
+                    checkForTestEmergencyNumber(dialString), this, mForegroundCall);
+        }
+        addConnection(mPendingMO);
+
+        if (!holdBeforeDial) {
+            dialInternal(mPendingMO, clirMode);
+        }
+
+        updatePhoneState();
+        mPhone.notifyPreciseCallStateChanged();
+
+        return mPendingMO;
+    }
+
+    private void dialInternal(ImsPhoneConnection conn, int clirMode) {
+        if (conn == null) {
+            return;
+        }
+
+        if (conn.getAddress()== null || conn.getAddress().length() == 0
+                || conn.getAddress().indexOf(PhoneNumberUtils.WILD) >= 0) {
+            // Phone number is invalid
+            conn.setDisconnectCause(DisconnectCause.INVALID_NUMBER);
+            sendEmptyMessageDelayed(EVENT_HANGUP_PENDINGMO, TIMEOUT_HANGUP_PENDINGMO);
+            return;
+        }
+
+        // Always unmute when initiating a new call
+        setMute(false);
+        int serviceType = PhoneNumberUtils.isEmergencyNumber(conn.getAddress()) ?
+        ImsCallProfile.SERVICE_TYPE_EMERGENCY : ImsCallProfile.SERVICE_TYPE_NORMAL;
+
+        try {
+            String[] callees = new String[] { conn.getAddress() };
+            ImsCallProfile profile = mImsManager.createCallProfile(mServiceId,
+                    serviceType, ImsCallProfile.CALL_TYPE_VOICE);
+            profile.setCallExtraInt(ImsCallProfile.EXTRA_OIR, clirMode);
+
+            ImsCall imsCall = mImsManager.makeCall(mServiceId, profile,
+                    callees, mImsCallListener);
+            conn.setImsCall(imsCall);
+        } catch (ImsException e) {
+            loge("dialInternal : " + e);
+            conn.setDisconnectCause(DisconnectCause.ERROR_UNSPECIFIED);
+            sendEmptyMessageDelayed(EVENT_HANGUP_PENDINGMO, TIMEOUT_HANGUP_PENDINGMO);
+        }
+    }
+
+    void
+    acceptCall () throws CallStateException {
+        if (DBG) log("acceptCall");
+
+        if (mForegroundCall.getState().isAlive()
+                && mBackgroundCall.getState().isAlive()) {
+            throw new CallStateException("cannot accept call");
+        }
+
+        if ((mRingingCall.getState() == ImsPhoneCall.State.WAITING)
+                && mForegroundCall.getState().isAlive()) {
+            setMute(false);
+            switchWaitingOrHoldingAndActive();
+        } else if (mRingingCall.getState().isRinging()) {
+            if (DBG) log("acceptCall: incoming...");
+            // Always unmute when answering a new call
+            setMute(false);
+            try {
+                ImsCall imsCall = mRingingCall.getImsCall();
+                if (imsCall != null) {
+                    imsCall.accept();
+                } else {
+                    throw new CallStateException("no valid ims call");
+                }
+            } catch (ImsException e) {
+                throw new CallStateException("cannot accept call");
+            }
+        } else {
+            throw new CallStateException("phone not ringing");
+        }
+    }
+
+    void
+    rejectCall () throws CallStateException {
+        if (DBG) log("rejectCall");
+
+        if (mRingingCall.getState().isRinging()) {
+            hangup(mRingingCall);
+        } else {
+            throw new CallStateException("phone not ringing");
+        }
+    }
+
+    void
+    switchWaitingOrHoldingAndActive() throws CallStateException {
+        if (DBG) log("switchWaitingOrHoldingAndActive");
+
+        if (mRingingCall.getState() == ImsPhoneCall.State.INCOMING) {
+            throw new CallStateException("cannot be in the incoming state");
+        }
+
+        if (mForegroundCall.getState() == ImsPhoneCall.State.ACTIVE) {
+            ImsCall imsCall = mForegroundCall.getImsCall();
+            if (imsCall == null) {
+                throw new CallStateException("no ims call");
+            }
+
+            mForegroundCall.switchWith(mBackgroundCall);
+
+            try {
+                imsCall.hold();
+            } catch (ImsException e) {
+                mForegroundCall.switchWith(mBackgroundCall);
+                throw new CallStateException(e.getMessage());
+            }
+        } else if (mBackgroundCall.getState() == ImsPhoneCall.State.HOLDING) {
+            resumeWaitingOrHolding();
+        }
+    }
+
+    void
+    conference() {
+        if (DBG) log("conference");
+
+        ImsCall fgImsCall = mForegroundCall.getImsCall();
+        if (fgImsCall == null) {
+            log("conference no foreground ims call");
+            return;
+        }
+
+        ImsCall bgImsCall = mBackgroundCall.getImsCall();
+        if (bgImsCall == null) {
+            log("conference no background ims call");
+            return;
+        }
+
+        try {
+            fgImsCall.merge(bgImsCall);
+        } catch (ImsException e) {
+            log("conference " + e.getMessage());
+        }
+    }
+
+    void
+    explicitCallTransfer() {
+        //TODO : implement
+    }
+
+    void
+    clearDisconnected() {
+        if (DBG) log("clearDisconnected");
+
+        internalClearDisconnected();
+
+        updatePhoneState();
+        mPhone.notifyPreciseCallStateChanged();
+    }
+
+    boolean
+    canConference() {
+        return mForegroundCall.getState() == ImsPhoneCall.State.ACTIVE
+            && mBackgroundCall.getState() == ImsPhoneCall.State.HOLDING
+            && !mBackgroundCall.isFull()
+            && !mForegroundCall.isFull();
+    }
+
+    boolean
+    canDial() {
+        boolean ret;
+        int serviceState = mPhone.getServiceState().getState();
+        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());
+
+        return ret;
+    }
+
+    boolean
+    canTransfer() {
+        return mForegroundCall.getState() == ImsPhoneCall.State.ACTIVE
+            && mBackgroundCall.getState() == ImsPhoneCall.State.HOLDING;
+    }
+
+    //***** Private Instance Methods
+
+    private void
+    internalClearDisconnected() {
+        mRingingCall.clearDisconnected();
+        mForegroundCall.clearDisconnected();
+        mBackgroundCall.clearDisconnected();
+        mHandoverCall.clearDisconnected();
+    }
+
+    private void
+    updatePhoneState() {
+        PhoneConstants.State oldState = mState;
+
+        if (mRingingCall.isRinging()) {
+            mState = PhoneConstants.State.RINGING;
+        } else if (mPendingMO != null ||
+                !(mForegroundCall.isIdle() && mBackgroundCall.isIdle())) {
+            mState = PhoneConstants.State.OFFHOOK;
+        } else {
+            mState = PhoneConstants.State.IDLE;
+        }
+
+        if (mState == PhoneConstants.State.IDLE && oldState != mState) {
+            mVoiceCallEndedRegistrants.notifyRegistrants(
+                    new AsyncResult(null, null, null));
+        } else if (oldState == PhoneConstants.State.IDLE && oldState != mState) {
+            mVoiceCallStartedRegistrants.notifyRegistrants (
+                    new AsyncResult(null, null, null));
+        }
+
+        if (DBG) log("updatePhoneState oldState=" + oldState + ", newState=" + mState);
+
+        if (mState != oldState) {
+            mPhone.notifyPhoneStateChanged();
+        }
+    }
+
+    private void
+    handleRadioNotAvailable() {
+        // handlePollCalls will clear out its
+        // call list when it gets the CommandException
+        // error result from this
+        pollCallsWhenSafe();
+    }
+
+    private void
+    dumpState() {
+        List l;
+
+        log("Phone State:" + mState);
+
+        log("Ringing call: " + mRingingCall.toString());
+
+        l = mRingingCall.getConnections();
+        for (int i = 0, s = l.size(); i < s; i++) {
+            log(l.get(i).toString());
+        }
+
+        log("Foreground call: " + mForegroundCall.toString());
+
+        l = mForegroundCall.getConnections();
+        for (int i = 0, s = l.size(); i < s; i++) {
+            log(l.get(i).toString());
+        }
+
+        log("Background call: " + mBackgroundCall.toString());
+
+        l = mBackgroundCall.getConnections();
+        for (int i = 0, s = l.size(); i < s; i++) {
+            log(l.get(i).toString());
+        }
+
+    }
+
+    //***** Called from ImsPhone
+
+    /*package*/ void
+    setMute(boolean mute) {
+        mDesiredMute = mute;
+        mForegroundCall.setMute(mute);
+    }
+
+    /*package*/ boolean
+    getMute() {
+        return mDesiredMute;
+    }
+
+    /*package*/ void
+    sendDtmf(char c) {
+        if (DBG) log("sendDtmf");
+
+        ImsCall imscall = mForegroundCall.getImsCall();
+        if (imscall != null) {
+            imscall.sendDtmf(convertDtmf(c));
+        }
+    }
+
+    private int convertDtmf(char c) {
+        int code = c - '0';
+        if ((code < 0) || (code > 9)) {
+            switch (c) {
+                case '*': return 10;
+                case '#': return 11;
+                case 'A': return 12;
+                case 'B': return 13;
+                case 'C': return 14;
+                case 'D': return 15;
+                default:
+                    throw new IllegalArgumentException(
+                            "invalid DTMF char: " + (int) c);
+            }
+        }
+        return code;
+    }
+
+    //***** Called from ImsPhoneConnection
+
+    /*package*/ void
+    hangup (ImsPhoneConnection conn) throws CallStateException {
+        if (DBG) log("hangup connection");
+
+        if (conn.getOwner() != this) {
+            throw new CallStateException ("ImsPhoneConnection " + conn
+                    + "does not belong to ImsPhoneCallTracker " + this);
+        }
+
+        hangup(conn.getCall());
+    }
+
+    //***** Called from ImsPhoneCall
+
+    /* package */ void
+    hangup (ImsPhoneCall call) throws CallStateException {
+        if (DBG) log("hangup call");
+
+        if (call.getConnections().size() == 0) {
+            throw new CallStateException("no connections");
+        }
+
+        ImsCall imsCall = call.getImsCall();
+        boolean rejectCall = false;
+
+        if (call == mRingingCall) {
+            if (Phone.DEBUG_PHONE) log("(ringing) hangup incoming");
+            rejectCall = true;
+        } else if (call == mForegroundCall) {
+            if (call.isDialingOrAlerting()) {
+                if (Phone.DEBUG_PHONE) {
+                    log("(foregnd) hangup dialing or alerting...");
+                }
+            } else {
+                if (Phone.DEBUG_PHONE) {
+                    log("(foregnd) hangup foreground");
+                }
+                //held call will be resumed by onCallTerminated
+            }
+        } else if (call == mBackgroundCall) {
+            if (Phone.DEBUG_PHONE) {
+                log("(backgnd) hangup waiting or background");
+            }
+        } else {
+            throw new RuntimeException ("ImsPhoneCall " + call +
+                    "does not belong to ImsPhoneCallTracker " + this);
+        }
+
+        call.onHangupLocal();
+
+        try {
+            if (imsCall != null) {
+                if (rejectCall) imsCall.reject(ImsReasonInfo.CODE_USER_DECLINE);
+                else imsCall.terminate(ImsReasonInfo.CODE_USER_TERMINATED);
+            } else if (mPendingMO != null && call == mForegroundCall) {
+                // is holding a foreground call
+                mPendingMO.update(null, ImsPhoneCall.State.DISCONNECTED);
+                mPendingMO.onDisconnect();
+                removeConnection(mPendingMO);
+                mPendingMO = null;
+                updatePhoneState();
+                removeMessages(EVENT_DIAL_PENDINGMO);
+            }
+        } catch (ImsException e) {
+            throw new CallStateException(e.getMessage());
+        }
+
+        mPhone.notifyPreciseCallStateChanged();
+    }
+
+    /* package */
+    void resumeWaitingOrHolding() throws CallStateException {
+        if (DBG) log("resumeWaitingOrHolding");
+
+        try {
+            if (mForegroundCall.getState().isAlive()) {
+                //resume foreground call after holding background call
+                //they were switched before holding
+                ImsCall imsCall = mForegroundCall.getImsCall();
+                if (imsCall != null) imsCall.resume();
+            } else if (mRingingCall.getState() == ImsPhoneCall.State.WAITING) {
+                //accept waiting call after holding background call
+                ImsCall imsCall = mRingingCall.getImsCall();
+                if (imsCall != null) imsCall.accept();
+            } else {
+                //Just resume background call.
+                //To distinguish resuming call with swapping calls
+                //we do not switch calls.here
+                //ImsPhoneConnection.update will chnage the parent when completed
+                ImsCall imsCall = mBackgroundCall.getImsCall();
+                if (imsCall != null) imsCall.resume();
+            }
+        } catch (ImsException e) {
+            throw new CallStateException(e.getMessage());
+        }
+    }
+
+    /* package */
+    void sendUSSD (String ussdString, Message response) {
+        if (DBG) log("sendUSSD");
+
+        try {
+            if (mUssdSession != null) {
+                mUssdSession.sendUssd(ussdString);
+                AsyncResult.forMessage(response, null, null);
+                response.sendToTarget();
+                return;
+            }
+
+            String[] callees = new String[] { ussdString };
+            ImsCallProfile profile = mImsManager.createCallProfile(mServiceId,
+                    ImsCallProfile.SERVICE_TYPE_NORMAL, ImsCallProfile.CALL_TYPE_VOICE);
+            profile.setCallExtraInt(ImsCallProfile.EXTRA_DIALSTRING,
+                    ImsCallProfile.DIALSTRING_USSD);
+
+            mUssdSession = mImsManager.makeCall(mServiceId, profile,
+                    callees, mImsUssdListener);
+        } catch (ImsException e) {
+            loge("sendUSSD : " + e);
+            mPhone.sendErrorResponse(response, e);
+        }
+    }
+
+    /* package */
+    void cancelUSSD() {
+        if (mUssdSession == null) return;
+
+        try {
+            mUssdSession.terminate(ImsReasonInfo.CODE_USER_TERMINATED);
+        } catch (ImsException e) {
+        }
+
+    }
+
+    private synchronized ImsPhoneConnection findConnection(ImsCall imsCall) {
+        for (ImsPhoneConnection conn : mConnections) {
+            if (conn.getImsCall() == imsCall) {
+                return conn;
+            }
+        }
+        return null;
+    }
+
+    private synchronized void removeConnection(ImsPhoneConnection conn) {
+        mConnections.remove(conn);
+    }
+
+    private synchronized void addConnection(ImsPhoneConnection conn) {
+        mConnections.add(conn);
+    }
+
+    private void processCallStateChange(ImsCall imsCall, ImsPhoneCall.State state, int cause) {
+        if (DBG) log("processCallStateChange state=" + state + " cause=" + cause);
+
+        if (imsCall == null) return;
+
+        boolean changed = false;
+        ImsPhoneConnection conn = findConnection(imsCall);
+
+        if (conn == null) {
+            // TODO : what should be done?
+            return;
+        }
+
+        changed = conn.update(imsCall, state);
+
+        if (state == ImsPhoneCall.State.DISCONNECTED) {
+            changed = conn.onDisconnect(cause) || changed;
+            removeConnection(conn);
+        }
+
+        if (changed) {
+            if (conn.getCall() == mHandoverCall) return;
+            updatePhoneState();
+            mPhone.notifyPreciseCallStateChanged();
+        }
+    }
+
+    private int getDisconnectCauseFromReasonInfo(ImsReasonInfo reasonInfo) {
+        int cause = DisconnectCause.ERROR_UNSPECIFIED;
+
+        //int type = reasonInfo.getReasonType();
+        int code = reasonInfo.getCode();
+        switch (code) {
+            case ImsReasonInfo.CODE_SIP_BAD_ADDRESS:
+            case ImsReasonInfo.CODE_SIP_NOT_REACHABLE:
+                return DisconnectCause.NUMBER_UNREACHABLE;
+
+            case ImsReasonInfo.CODE_SIP_BUSY:
+                return DisconnectCause.BUSY;
+
+            case ImsReasonInfo.CODE_USER_TERMINATED:
+                return DisconnectCause.LOCAL;
+
+            case ImsReasonInfo.CODE_USER_TERMINATED_BY_REMOTE:
+                return DisconnectCause.NORMAL;
+
+            case ImsReasonInfo.CODE_SIP_REDIRECTED:
+            case ImsReasonInfo.CODE_SIP_BAD_REQUEST:
+            case ImsReasonInfo.CODE_SIP_FORBIDDEN:
+            case ImsReasonInfo.CODE_SIP_NOT_ACCEPTABLE:
+            case ImsReasonInfo.CODE_SIP_USER_REJECTED:
+            case ImsReasonInfo.CODE_SIP_GLOBAL_ERROR:
+                return DisconnectCause.SERVER_ERROR;
+
+            case ImsReasonInfo.CODE_SIP_SERVICE_UNAVAILABLE:
+            case ImsReasonInfo.CODE_SIP_NOT_FOUND:
+            case ImsReasonInfo.CODE_SIP_SERVER_ERROR:
+                return DisconnectCause.SERVER_UNREACHABLE;
+
+            case ImsReasonInfo.CODE_LOCAL_NETWORK_ROAMING:
+            case ImsReasonInfo.CODE_LOCAL_NETWORK_IP_CHANGED:
+            case ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN:
+            case ImsReasonInfo.CODE_LOCAL_SERVICE_UNAVAILABLE:
+            case ImsReasonInfo.CODE_LOCAL_NOT_REGISTERED:
+            case ImsReasonInfo.CODE_LOCAL_NETWORK_NO_LTE_COVERAGE:
+            case ImsReasonInfo.CODE_LOCAL_NETWORK_NO_SERVICE:
+            case ImsReasonInfo.CODE_LOCAL_CALL_VCC_ON_PROGRESSING:
+                return DisconnectCause.OUT_OF_SERVICE;
+
+            case ImsReasonInfo.CODE_SIP_REQUEST_TIMEOUT:
+            case ImsReasonInfo.CODE_TIMEOUT_1XX_WAITING:
+            case ImsReasonInfo.CODE_TIMEOUT_NO_ANSWER:
+            case ImsReasonInfo.CODE_TIMEOUT_NO_ANSWER_CALL_UPDATE:
+                return DisconnectCause.TIMED_OUT;
+
+            case ImsReasonInfo.CODE_LOCAL_LOW_BATTERY:
+            case ImsReasonInfo.CODE_LOCAL_POWER_OFF:
+                return DisconnectCause.POWER_OFF;
+
+            default:
+        }
+
+        return cause;
+    }
+
+    /**
+     * Listen to the IMS call state change
+     */
+    private ImsCall.Listener mImsCallListener = new ImsCall.Listener() {
+        @Override
+        public void onCallProgressing(ImsCall imsCall) {
+            if (DBG) log("onCallProgressing");
+
+            mPendingMO = null;
+            processCallStateChange(imsCall, ImsPhoneCall.State.ALERTING,
+                    DisconnectCause.NOT_DISCONNECTED);
+        }
+
+        @Override
+        public void onCallStarted(ImsCall imsCall) {
+            if (DBG) log("onCallStarted");
+
+            mPendingMO = null;
+            processCallStateChange(imsCall, ImsPhoneCall.State.ACTIVE,
+                    DisconnectCause.NOT_DISCONNECTED);
+        }
+
+        /**
+         * onCallStartFailed will be invoked when:
+         * case 1) Dialing fails
+         * case 2) Ringing call is disconnected by local or remote user
+         */
+        @Override
+        public void onCallStartFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) {
+            if (DBG) log("onCallStartFailed reasonCode=" + reasonInfo.getCode());
+
+            if (mPendingMO != null) {
+                // To initiate dialing circuit-switched call
+                if (reasonInfo.getCode() == ImsReasonInfo.CODE_LOCAL_CALL_CS_RETRY_REQUIRED
+                        && mBackgroundCall.getState() == ImsPhoneCall.State.IDLE
+                        && mRingingCall.getState() == ImsPhoneCall.State.IDLE) {
+                    mForegroundCall.detach(mPendingMO);
+                    removeConnection(mPendingMO);
+                    mPendingMO.finalize();
+                    mPendingMO = null;
+                    mPhone.initiateSilentRedial();
+                    return;
+                }
+                mPendingMO = null;
+            }
+            onCallTerminated(imsCall, reasonInfo);
+        }
+
+        @Override
+        public void onCallTerminated(ImsCall imsCall, ImsReasonInfo reasonInfo) {
+            if (DBG) log("onCallTerminated reasonCode=" + reasonInfo.getCode());
+
+            ImsPhoneCall.State oldState = mForegroundCall.getState();
+
+            processCallStateChange(imsCall, ImsPhoneCall.State.DISCONNECTED,
+                    getDisconnectCauseFromReasonInfo(reasonInfo));
+
+            if (reasonInfo.getCode() == ImsReasonInfo.CODE_USER_TERMINATED) {
+                if ((oldState == ImsPhoneCall.State.DISCONNECTING)
+                        && (mForegroundCall.getState() == ImsPhoneCall.State.DISCONNECTED)
+                        && (mBackgroundCall.getState() == ImsPhoneCall.State.HOLDING)) {
+                    sendEmptyMessage(EVENT_RESUME_BACKGROUND);
+                }
+            }
+        }
+
+        @Override
+        public void onCallHeld(ImsCall imsCall) {
+            if (DBG) log("onCallHeld");
+
+            synchronized (mSyncHold) {
+                ImsPhoneCall.State oldState = mBackgroundCall.getState();
+                processCallStateChange(imsCall, ImsPhoneCall.State.HOLDING,
+                        DisconnectCause.NOT_DISCONNECTED);
+
+                if (oldState == ImsPhoneCall.State.ACTIVE) {
+                    if ((mForegroundCall.getState() == ImsPhoneCall.State.HOLDING)
+                            || (mRingingCall.getState() == ImsPhoneCall.State.WAITING)) {
+                        sendEmptyMessage(EVENT_RESUME_BACKGROUND);
+                    } else {
+                        //when multiple connections belong to background call,
+                        //only the first callback reaches here
+                        //otherwise the oldState is already HOLDING
+                        if (mPendingMO != null) {
+                            sendEmptyMessage(EVENT_DIAL_PENDINGMO);
+                        }
+                    }
+                }
+            }
+        }
+
+        @Override
+        public void onCallHoldFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) {
+            if (DBG) log("onCallHoldFailed reasonCode=" + reasonInfo.getCode());
+
+            synchronized (mSyncHold) {
+                ImsPhoneCall.State bgState = mBackgroundCall.getState();
+                if (reasonInfo.getCode() == ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED) {
+                    // disconnected while processing hold
+                    if (mPendingMO != null) {
+                        sendEmptyMessage(EVENT_DIAL_PENDINGMO);
+                    }
+                } else if (bgState == ImsPhoneCall.State.ACTIVE) {
+                    mForegroundCall.switchWith(mBackgroundCall);
+
+                    if (mPendingMO != null) {
+                        mPendingMO.setDisconnectCause(DisconnectCause.ERROR_UNSPECIFIED);
+                        sendEmptyMessageDelayed(EVENT_HANGUP_PENDINGMO, TIMEOUT_HANGUP_PENDINGMO);
+                    }
+                }
+            }
+        }
+
+        @Override
+        public void onCallResumed(ImsCall imsCall) {
+            if (DBG) log("onCallResumed");
+
+            processCallStateChange(imsCall, ImsPhoneCall.State.ACTIVE,
+                    DisconnectCause.NOT_DISCONNECTED);
+        }
+
+        @Override
+        public void onCallResumeFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) {
+            // TODO : What should be done?
+        }
+
+        @Override
+        public void onCallResumeReceived(ImsCall imsCall) {
+            if (DBG) log("onCallResumeReceived");
+
+            if (mOnHoldToneStarted) {
+                mPhone.stopOnHoldTone();
+                mOnHoldToneStarted = false;
+            }
+        }
+
+        @Override
+        public void onCallHoldReceived(ImsCall imsCall) {
+            if (DBG) log("onCallHoldReceived");
+
+            ImsPhoneConnection conn = findConnection(imsCall);
+            if (conn != null && conn.getState() == ImsPhoneCall.State.ACTIVE) {
+                if (!mOnHoldToneStarted && ImsPhoneCall.isLocalTone(imsCall)) {
+                    mPhone.startOnHoldTone();
+                    mOnHoldToneStarted = true;
+                }
+            }
+        }
+
+        @Override
+        public void onCallMerged(ImsCall call, ImsCall newCall) {
+            if (DBG) log("onCallMerged");
+
+            mForegroundCall.merge(mBackgroundCall, mForegroundCall.getState());
+            updatePhoneState();
+            mPhone.notifyPreciseCallStateChanged();
+        }
+
+        @Override
+        public void onCallMergeFailed(ImsCall call, ImsReasonInfo reasonInfo) {
+            // TODO : What should be done?
+        }
+    };
+
+    /**
+     * Listen to the IMS call state change
+     */
+    private ImsCall.Listener mImsUssdListener = new ImsCall.Listener() {
+        @Override
+        public void onCallStarted(ImsCall imsCall) {
+            if (DBG) log("mImsUssdListener onCallStarted");
+
+            if (imsCall == mUssdSession) {
+                if (mPendingUssd != null) {
+                    AsyncResult.forMessage(mPendingUssd);
+                    mPendingUssd.sendToTarget();
+                    mPendingUssd = null;
+                }
+            }
+        }
+
+        @Override
+        public void onCallStartFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) {
+            if (DBG) log("mImsUssdListener onCallStartFailed reasonCode=" + reasonInfo.getCode());
+
+            onCallTerminated(imsCall, reasonInfo);
+        }
+
+        @Override
+        public void onCallTerminated(ImsCall imsCall, ImsReasonInfo reasonInfo) {
+            if (DBG) log("mImsUssdListener onCallTerminated reasonCode=" + reasonInfo.getCode());
+
+            if (imsCall == mUssdSession) {
+                mUssdSession = null;
+                if (mPendingUssd != null) {
+                    CommandException ex =
+                            new CommandException(CommandException.Error.GENERIC_FAILURE);
+                    AsyncResult.forMessage(mPendingUssd, null, ex);
+                    mPendingUssd.sendToTarget();
+                    mPendingUssd = null;
+                }
+            }
+            imsCall.close();
+        }
+
+        @Override
+        public void onCallUssdMessageReceived(ImsCall call,
+                int mode, String ussdMessage) {
+            if (DBG) log("mImsUssdListener onCallUssdMessageReceived mode=" + mode);
+
+            int ussdMode = -1;
+
+            switch(mode) {
+                case ImsCall.USSD_MODE_REQUEST:
+                    ussdMode = CommandsInterface.USSD_MODE_REQUEST;
+                    break;
+
+                case ImsCall.USSD_MODE_NOTIFY:
+                    ussdMode = CommandsInterface.USSD_MODE_NOTIFY;
+                    break;
+            }
+
+            mPhone.onIncomingUSSD(ussdMode, ussdMessage);
+        }
+    };
+
+    /**
+     * Listen to the IMS service state change
+     *
+     */
+    private ImsConnectionStateListener mImsConnectionStateListener =
+        new ImsConnectionStateListener() {
+        @Override
+        public void onImsConnected() {
+            if (DBG) log("onImsConnected");
+            mPhone.setServiceState(ServiceState.STATE_IN_SERVICE);
+        }
+
+        @Override
+        public void onImsDisconnected() {
+            if (DBG) log("onImsDisconnected");
+            mPhone.setServiceState(ServiceState.STATE_OUT_OF_SERVICE);
+        }
+
+        @Override
+        public void onImsResumed() {
+            if (DBG) log("onImsResumed");
+            mPhone.setServiceState(ServiceState.STATE_IN_SERVICE);
+        }
+
+        @Override
+        public void onImsSuspended() {
+            if (DBG) log("onImsSuspended");
+            mPhone.setServiceState(ServiceState.STATE_OUT_OF_SERVICE);
+        }
+    };
+
+    /* package */
+    ImsUtInterface getUtInterface() throws ImsException {
+        if (mImsManager == null) {
+            throw new ImsException("no ims manager", ImsReasonInfo.CODE_UNSPECIFIED);
+        }
+
+        ImsUtInterface ut = mImsManager.getSupplementaryServiceConfiguration(mServiceId);
+        return ut;
+    }
+
+    /* package */
+    void notifySrvccState(Call.SrvccState state) {
+        if (DBG) log("notifySrvccState state=" + state);
+
+        mSrvccState = state;
+
+        if (mSrvccState == Call.SrvccState.COMPLETED) {
+            if (mForegroundCall.getConnections().size() > 0) {
+                mHandoverCall.switchWith(mForegroundCall);
+            } else if (mBackgroundCall.getConnections().size() > 0) {
+                mHandoverCall.switchWith(mBackgroundCall);
+            }
+        }
+    }
+
+    //****** Overridden from Handler
+
+    @Override
+    public void
+    handleMessage (Message msg) {
+        AsyncResult ar;
+        if (DBG) log("handleMessage what=" + msg.what);
+
+        switch (msg.what) {
+            case EVENT_HANGUP_PENDINGMO:
+                if (mPendingMO != null) {
+                    mPendingMO.onDisconnect();
+                    removeConnection(mPendingMO);
+                    mPendingMO = null;
+                }
+
+                updatePhoneState();
+                mPhone.notifyPreciseCallStateChanged();
+                break;
+            case EVENT_RESUME_BACKGROUND:
+                try {
+                    resumeWaitingOrHolding();
+                } catch (CallStateException e) {
+                    if (Phone.DEBUG_PHONE) {
+                        loge("handleMessage EVENT_RESUME_BACKGROUND exception=" + e);
+                    }
+                }
+                break;
+            case EVENT_DIAL_PENDINGMO:
+                dialInternal(mPendingMO, mClirMode);
+                break;
+        }
+    }
+
+    @Override
+    protected void log(String msg) {
+        Rlog.d(LOG_TAG, "[ImsPhoneCallTracker] " + msg);
+    }
+
+    protected void loge(String msg) {
+        Rlog.e(LOG_TAG, "[ImsPhoneCallTracker] " + msg);
+    }
+
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("ImsPhoneCallTracker extends:");
+        super.dump(fd, pw, args);
+        pw.println(" mVoiceCallEndedRegistrants=" + mVoiceCallEndedRegistrants);
+        pw.println(" mVoiceCallStartedRegistrants=" + mVoiceCallStartedRegistrants);
+        pw.println(" mRingingCall=" + mRingingCall);
+        pw.println(" mForegroundCall=" + mForegroundCall);
+        pw.println(" mBackgroundCall=" + mBackgroundCall);
+        pw.println(" mHandoverCall=" + mHandoverCall);
+        pw.println(" mPendingMO=" + mPendingMO);
+        //pw.println(" mHangupPendingMO=" + mHangupPendingMO);
+        pw.println(" mPhone=" + mPhone);
+        pw.println(" mDesiredMute=" + mDesiredMute);
+        pw.println(" mState=" + mState);
+    }
+
+    @Override
+    protected void handlePollCalls(AsyncResult ar) {
+    }
+}
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCommandInterface.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCommandInterface.java
new file mode 100644
index 0000000..d59d4a1
--- /dev/null
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCommandInterface.java
@@ -0,0 +1,589 @@
+/*
+ * Copyright (C) 2013 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.imsphone;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Message;
+
+import com.android.internal.telephony.BaseCommands;
+import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.UUSInfo;
+import com.android.internal.telephony.cdma.CdmaSmsBroadcastConfigInfo;
+import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
+
+/**
+ * Volte doesn't need CommandsInterface. The class does nothing but made to work
+ * with PhoneBase's constructor.
+ */
+class ImsPhoneCommandInterface extends BaseCommands implements CommandsInterface {
+    ImsPhoneCommandInterface(Context context) {
+        super(context);
+    }
+
+    @Override public void setOnNITZTime(Handler h, int what, Object obj) {
+    }
+
+    @Override
+    public void getIccCardStatus(Message result) {
+    }
+
+    @Override
+    public void supplyIccPin(String pin, Message result) {
+    }
+
+    @Override
+    public void supplyIccPuk(String puk, String newPin, Message result) {
+    }
+
+    @Override
+    public void supplyIccPin2(String pin, Message result) {
+    }
+
+    @Override
+    public void supplyIccPuk2(String puk, String newPin2, Message result) {
+    }
+
+    @Override
+    public void changeIccPin(String oldPin, String newPin, Message result) {
+    }
+
+    @Override
+    public void changeIccPin2(String oldPin2, String newPin2, Message result) {
+    }
+
+    @Override
+    public void changeBarringPassword(String facility, String oldPwd,
+            String newPwd, Message result) {
+    }
+
+    @Override
+    public void supplyNetworkDepersonalization(String netpin, Message result) {
+    }
+
+    @Override
+    public void getCurrentCalls(Message result) {
+    }
+
+    @Override
+    @Deprecated public void getPDPContextList(Message result) {
+    }
+
+    @Override
+    public void getDataCallList(Message result) {
+    }
+
+    @Override
+    public void dial(String address, int clirMode, Message result) {
+    }
+
+    @Override
+    public void dial(String address, int clirMode, UUSInfo uusInfo,
+            Message result) {
+    }
+
+    @Override
+    public void getIMSI(Message result) {
+    }
+
+    @Override
+    public void getIMSIForApp(String aid, Message result) {
+    }
+
+    @Override
+    public void getIMEI(Message result) {
+    }
+
+    @Override
+    public void getIMEISV(Message result) {
+    }
+
+    @Override
+    public void hangupConnection (int gsmIndex, Message result) {
+    }
+
+    @Override
+    public void hangupWaitingOrBackground (Message result) {
+    }
+
+    @Override
+    public void hangupForegroundResumeBackground (Message result) {
+    }
+
+    @Override
+    public void switchWaitingOrHoldingAndActive (Message result) {
+    }
+
+    @Override
+    public void conference (Message result) {
+    }
+
+    @Override
+    public void setPreferredVoicePrivacy(boolean enable, Message result) {
+    }
+
+    @Override
+    public void getPreferredVoicePrivacy(Message result) {
+    }
+
+    @Override
+    public void separateConnection (int gsmIndex, Message result) {
+    }
+
+    @Override
+    public void acceptCall (Message result) {
+    }
+
+    @Override
+    public void rejectCall (Message result) {
+    }
+
+    @Override
+    public void explicitCallTransfer (Message result) {
+    }
+
+    @Override
+    public void getLastCallFailCause (Message result) {
+    }
+
+    @Deprecated
+    @Override
+    public void getLastPdpFailCause (Message result) {
+    }
+
+    @Override
+    public void getLastDataCallFailCause (Message result) {
+    }
+
+    @Override
+    public void setMute (boolean enableMute, Message response) {
+    }
+
+    @Override
+    public void getMute (Message response) {
+    }
+
+    @Override
+    public void getSignalStrength (Message result) {
+    }
+
+    @Override
+    public void getVoiceRegistrationState (Message result) {
+    }
+
+    @Override
+    public void getDataRegistrationState (Message result) {
+    }
+
+    @Override
+    public void getOperator(Message result) {
+    }
+
+    @Override
+    public void sendDtmf(char c, Message result) {
+    }
+
+    @Override
+    public void startDtmf(char c, Message result) {
+    }
+
+    @Override
+    public void stopDtmf(Message result) {
+    }
+
+    @Override
+    public void sendBurstDtmf(String dtmfString, int on, int off,
+            Message result) {
+    }
+
+    @Override
+    public void sendSMS (String smscPDU, String pdu, Message result) {
+    }
+
+    @Override
+    public void sendCdmaSms(byte[] pdu, Message result) {
+    }
+
+    @Override
+    public void sendImsGsmSms (String smscPDU, String pdu,
+            int retry, int messageRef, Message response) {
+    }
+
+    @Override
+    public void sendImsCdmaSms(byte[] pdu, int retry, int messageRef,
+            Message response) {
+    }
+
+    @Override
+    public void getImsRegistrationState (Message result) {
+    }
+
+    @Override
+    public void deleteSmsOnSim(int index, Message response) {
+    }
+
+    @Override
+    public void deleteSmsOnRuim(int index, Message response) {
+    }
+
+    @Override
+    public void writeSmsToSim(int status, String smsc, String pdu, Message response) {
+    }
+
+    @Override
+    public void writeSmsToRuim(int status, String pdu, Message response) {
+    }
+
+    @Override
+    public void setupDataCall(String radioTechnology, String profile,
+            String apn, String user, String password, String authType,
+            String protocol, Message result) {
+    }
+
+    @Override
+    public void deactivateDataCall(int cid, int reason, Message result) {
+    }
+
+    @Override
+    public void setRadioPower(boolean on, Message result) {
+    }
+
+    @Override
+    public void setSuppServiceNotifications(boolean enable, Message result) {
+    }
+
+    @Override
+    public void acknowledgeLastIncomingGsmSms(boolean success, int cause,
+            Message result) {
+    }
+
+    @Override
+    public void acknowledgeLastIncomingCdmaSms(boolean success, int cause,
+            Message result) {
+    }
+
+    @Override
+    public void acknowledgeIncomingGsmSmsWithPdu(boolean success, String ackPdu,
+            Message result) {
+    }
+
+    @Override
+    public void iccIO (int command, int fileid, String path, int p1, int p2,
+            int p3, String data, String pin2, Message result) {
+    }
+    @Override
+    public void iccIOForApp (int command, int fileid, String path, int p1, int p2,
+            int p3, String data, String pin2, String aid, Message result) {
+    }
+
+    @Override
+    public void getCLIR(Message result) {
+    }
+
+    @Override
+    public void setCLIR(int clirMode, Message result) {
+    }
+
+    @Override
+    public void queryCallWaiting(int serviceClass, Message response) {
+    }
+
+    @Override
+    public void setCallWaiting(boolean enable, int serviceClass,
+            Message response) {
+    }
+
+    @Override
+    public void setNetworkSelectionModeAutomatic(Message response) {
+    }
+
+    @Override
+    public void setNetworkSelectionModeManual(
+            String operatorNumeric, Message response) {
+    }
+
+    @Override
+    public void getNetworkSelectionMode(Message response) {
+    }
+
+    @Override
+    public void getAvailableNetworks(Message response) {
+    }
+
+    @Override
+    public void setCallForward(int action, int cfReason, int serviceClass,
+                String number, int timeSeconds, Message response) {
+    }
+
+    @Override
+    public void queryCallForwardStatus(int cfReason, int serviceClass,
+            String number, Message response) {
+    }
+
+    @Override
+    public void queryCLIP(Message response) {
+    }
+
+    @Override
+    public void getBasebandVersion (Message response) {
+    }
+
+    @Override
+    public void queryFacilityLock(String facility, String password,
+            int serviceClass, Message response) {
+    }
+
+    @Override
+    public void queryFacilityLockForApp(String facility, String password,
+            int serviceClass, String appId, Message response) {
+    }
+
+    @Override
+    public void setFacilityLock(String facility, boolean lockState,
+            String password, int serviceClass, Message response) {
+    }
+
+    @Override
+    public void setFacilityLockForApp(String facility, boolean lockState,
+            String password, int serviceClass, String appId, Message response) {
+    }
+
+    @Override
+    public void sendUSSD (String ussdString, Message response) {
+    }
+
+    @Override
+    public void cancelPendingUssd (Message response) {
+    }
+
+    @Override
+    public void resetRadio(Message result) {
+    }
+
+    @Override
+    public void invokeOemRilRequestRaw(byte[] data, Message response) {
+    }
+
+    @Override
+    public void invokeOemRilRequestStrings(String[] strings, Message response) {
+    }
+
+    @Override
+    public void setBandMode (int bandMode, Message response) {
+    }
+
+    @Override
+    public void queryAvailableBandMode (Message response) {
+    }
+
+    @Override
+    public void sendTerminalResponse(String contents, Message response) {
+    }
+
+    @Override
+    public void sendEnvelope(String contents, Message response) {
+    }
+
+    @Override
+    public void sendEnvelopeWithStatus(String contents, Message response) {
+    }
+
+    @Override
+    public void handleCallSetupRequestFromSim(
+            boolean accept, Message response) {
+    }
+
+    @Override
+    public void setPreferredNetworkType(int networkType , Message response) {
+    }
+
+    @Override
+    public void getPreferredNetworkType(Message response) {
+    }
+
+    @Override
+    public void getNeighboringCids(Message response) {
+    }
+
+    @Override
+    public void setLocationUpdates(boolean enable, Message response) {
+    }
+
+    @Override
+    public void getSmscAddress(Message result) {
+    }
+
+    @Override
+    public void setSmscAddress(String address, Message result) {
+    }
+
+    @Override
+    public void reportSmsMemoryStatus(boolean available, Message result) {
+    }
+
+    @Override
+    public void reportStkServiceIsRunning(Message result) {
+    }
+
+    @Override
+    public void getCdmaSubscriptionSource(Message response) {
+    }
+
+    @Override
+    public void getGsmBroadcastConfig(Message response) {
+    }
+
+    @Override
+    public void setGsmBroadcastConfig(SmsBroadcastConfigInfo[] config, Message response) {
+    }
+
+    @Override
+    public void setGsmBroadcastActivation(boolean activate, Message response) {
+    }
+
+    // ***** Methods for CDMA support
+    @Override
+    public void getDeviceIdentity(Message response) {
+    }
+
+    @Override
+    public void getCDMASubscription(Message response) {
+    }
+
+    @Override
+    public void setPhoneType(int phoneType) { //Set by CDMAPhone and GSMPhone constructor
+    }
+
+    @Override
+    public void queryCdmaRoamingPreference(Message response) {
+    }
+
+    @Override
+    public void setCdmaRoamingPreference(int cdmaRoamingType, Message response) {
+    }
+
+    @Override
+    public void setCdmaSubscriptionSource(int cdmaSubscription , Message response) {
+    }
+
+    @Override
+    public void queryTTYMode(Message response) {
+    }
+
+    @Override
+    public void setTTYMode(int ttyMode, Message response) {
+    }
+
+    @Override
+    public void sendCDMAFeatureCode(String FeatureCode, Message response) {
+    }
+
+    @Override
+    public void getCdmaBroadcastConfig(Message response) {
+    }
+
+    @Override
+    public void setCdmaBroadcastConfig(CdmaSmsBroadcastConfigInfo[] configs, Message response) {
+    }
+
+    @Override
+    public void setCdmaBroadcastActivation(boolean activate, Message response) {
+    }
+
+    @Override
+    public void exitEmergencyCallbackMode(Message response) {
+    }
+
+    @Override
+    public void supplyIccPinForApp(String pin, String aid, Message response) {
+    }
+
+    @Override
+    public void supplyIccPukForApp(String puk, String newPin, String aid, Message response) {
+    }
+
+    @Override
+    public void supplyIccPin2ForApp(String pin2, String aid, Message response) {
+    }
+
+    @Override
+    public void supplyIccPuk2ForApp(String puk2, String newPin2, String aid, Message response) {
+    }
+
+    @Override
+    public void changeIccPinForApp(String oldPin, String newPin, String aidPtr, Message response) {
+    }
+
+    @Override
+    public void changeIccPin2ForApp(String oldPin2, String newPin2, String aidPtr,
+            Message response) {
+    }
+
+    @Override
+    public void requestIsimAuthentication(String nonce, Message response) {
+    }
+
+    @Override
+    public void requestIccSimAuthentication(String data, Message response) {
+    }
+
+    @Override
+    public void getVoiceRadioTechnology(Message result) {
+    }
+
+    @Override
+    public void getCellInfoList(Message result) {
+    }
+
+    @Override
+    public void setCellInfoListRate(int rateInMillis, Message response) {
+    }
+
+    @Override
+    public void setInitialAttachApn(String apn, String protocol, int authType, String username,
+            String password, Message result) {
+    }
+
+    @Override
+    public void iccOpenLogicalChannel(String AID, Message response) {}
+
+    @Override
+    public void iccCloseLogicalChannel(int channel, Message response) {}
+
+    @Override
+    public void iccTransmitApduLogicalChannel(int channel, int cla, int instruction,
+                                              int p1, int p2, int p3, String data,
+                                              Message response) {}
+    @Override
+    public void iccTransmitApduBasicChannel(int cla, int instruction, int p1, int p2,
+                                            int p3, String data, Message response) {}
+
+    @Override
+    public void nvReadItem(int itemID, Message response) {}
+
+    @Override
+    public void nvWriteItem(int itemID, String itemValue, Message response) {}
+
+    @Override
+    public void nvWriteCdmaPrl(byte[] preferredRoamingList, Message response) {}
+
+    @Override
+    public void nvResetConfig(int resetType, Message response) {}
+
+    @Override
+    public void getHardwareConfig(Message result) {}
+}
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java
new file mode 100644
index 0000000..77ae57d
--- /dev/null
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java
@@ -0,0 +1,643 @@
+/*
+ * Copyright (C) 2013 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.imsphone;
+
+import android.content.Context;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.Registrant;
+import android.os.SystemClock;
+import android.telephony.DisconnectCause;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.Rlog;
+
+import com.android.internal.telephony.CallStateException;
+import com.android.internal.telephony.Connection;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.UUSInfo;
+
+import com.android.ims.ImsCall;
+import com.android.ims.ImsCallProfile;
+
+/**
+ * {@hide}
+ */
+public class ImsPhoneConnection extends Connection {
+    private static final String LOG_TAG = "ImsPhoneConnection";
+    private static final boolean DBG = true;
+
+    //***** Instance Variables
+
+    private ImsPhoneCallTracker mOwner;
+    private ImsPhoneCall mParent;
+    private ImsCall mImsCall;
+
+    private String mAddress;     // MAY BE NULL!!!
+    private String mDialString;          // outgoing calls only
+    private String mPostDialString;      // outgoing calls only
+    private boolean mIsIncoming;
+    private boolean mDisconnected;
+
+    /*
+    int mIndex;          // index in ImsPhoneCallTracker.connections[], -1 if unassigned
+                        // The GSM index is 1 + this
+    */
+
+    /*
+     * These time/timespan values are based on System.currentTimeMillis(),
+     * i.e., "wall clock" time.
+     */
+    private long mCreateTime;
+    private long mConnectTime;
+    private long mDisconnectTime;
+
+    /*
+     * These time/timespan values are based on SystemClock.elapsedRealTime(),
+     * i.e., time since boot.  They are appropriate for comparison and
+     * calculating deltas.
+     */
+    private long mConnectTimeReal;
+    private long mDuration;
+    private long mHoldingStartTime;  // The time when the Connection last transitioned
+                            // into HOLDING
+
+    private int mNextPostDialChar;       // index into postDialString
+
+    private int mCause = DisconnectCause.NOT_DISCONNECTED;
+    private PostDialState mPostDialState = PostDialState.NOT_STARTED;
+    private int mNumberPresentation = PhoneConstants.PRESENTATION_ALLOWED;
+    private UUSInfo mUusInfo;
+
+    private boolean mIsMultiparty = false;
+
+    private Handler mHandler;
+
+    private PowerManager.WakeLock mPartialWakeLock;
+
+    //***** Event Constants
+    private static final int EVENT_DTMF_DONE = 1;
+    private static final int EVENT_PAUSE_DONE = 2;
+    private static final int EVENT_NEXT_POST_DIAL = 3;
+    private static final int EVENT_WAKE_LOCK_TIMEOUT = 4;
+
+    //***** Constants
+    private static final int PAUSE_DELAY_MILLIS = 3 * 1000;
+    private static final int WAKE_LOCK_TIMEOUT_MILLIS = 60*1000;
+
+    //***** Inner Classes
+
+    class MyHandler extends Handler {
+        MyHandler(Looper l) {super(l);}
+
+        @Override
+        public void
+        handleMessage(Message msg) {
+
+            switch (msg.what) {
+                case EVENT_NEXT_POST_DIAL:
+                case EVENT_DTMF_DONE:
+                case EVENT_PAUSE_DONE:
+                    processNextPostDialChar();
+                    break;
+                case EVENT_WAKE_LOCK_TIMEOUT:
+                    releaseWakeLock();
+                    break;
+            }
+        }
+    }
+
+    //***** Constructors
+
+    /** This is probably an MT call */
+    /*package*/
+    ImsPhoneConnection(Context context, ImsCall imsCall, ImsPhoneCallTracker ct, ImsPhoneCall parent) {
+        createWakeLock(context);
+        acquireWakeLock();
+
+        mOwner = ct;
+        mHandler = new MyHandler(mOwner.getLooper());
+        mImsCall = imsCall;
+
+        if ((imsCall != null) && (imsCall.getCallProfile() != null)) {
+            mAddress = imsCall.getCallProfile().getCallExtra(ImsCallProfile.EXTRA_OI);
+            mCnapName = imsCall.getCallProfile().getCallExtra(ImsCallProfile.EXTRA_CNA);
+            mNumberPresentation = presentationFromOir(
+                    imsCall.getCallProfile().getCallExtraInt(ImsCallProfile.EXTRA_OIR));
+            mCnapNamePresentation = presentationFromOir(
+                    imsCall.getCallProfile().getCallExtraInt(ImsCallProfile.EXTRA_CNAP));
+        } else {
+            mNumberPresentation = PhoneConstants.PRESENTATION_UNKNOWN;
+            mCnapNamePresentation = PhoneConstants.PRESENTATION_UNKNOWN;
+        }
+
+        mIsIncoming = true;
+        mCreateTime = System.currentTimeMillis();
+        mUusInfo = null;
+
+        //mIndex = index;
+
+        mParent = parent;
+        mParent.attach(this, ImsPhoneCall.State.INCOMING);
+    }
+
+    /** This is an MO call, created when dialing */
+    /*package*/
+    ImsPhoneConnection(Context context, String dialString, ImsPhoneCallTracker ct, ImsPhoneCall parent) {
+        createWakeLock(context);
+        acquireWakeLock();
+
+        mOwner = ct;
+        mHandler = new MyHandler(mOwner.getLooper());
+
+        mDialString = dialString;
+
+        mAddress = PhoneNumberUtils.extractNetworkPortionAlt(dialString);
+        mPostDialString = PhoneNumberUtils.extractPostDialPortion(dialString);
+
+        //mIndex = -1;
+
+        mIsIncoming = false;
+        mCnapName = null;
+        mCnapNamePresentation = PhoneConstants.PRESENTATION_ALLOWED;
+        mNumberPresentation = PhoneConstants.PRESENTATION_ALLOWED;
+        mCreateTime = System.currentTimeMillis();
+
+        mParent = parent;
+        parent.attachFake(this, ImsPhoneCall.State.DIALING);
+    }
+
+    public void dispose() {
+    }
+
+    static boolean
+    equalsHandlesNulls (Object a, Object b) {
+        return (a == null) ? (b == null) : a.equals (b);
+    }
+
+    @Override
+    public String getOrigDialString(){
+        return mDialString;
+    }
+
+    @Override
+    public String getAddress() {
+        return mAddress;
+    }
+
+    @Override
+    public ImsPhoneCall getCall() {
+        return mParent;
+    }
+
+    @Override
+    public long getCreateTime() {
+        return mCreateTime;
+    }
+
+    @Override
+    public long getConnectTime() {
+        return mConnectTime;
+    }
+
+    @Override
+    public long getConnectTimeReal() {
+        return mConnectTimeReal;
+    }
+
+    @Override
+    public long getDisconnectTime() {
+        return mDisconnectTime;
+    }
+
+    @Override
+    public long getDurationMillis() {
+        if (mConnectTimeReal == 0) {
+            return 0;
+        } else if (mDuration == 0) {
+            return SystemClock.elapsedRealtime() - mConnectTimeReal;
+        } else {
+            return mDuration;
+        }
+    }
+
+    @Override
+    public long getHoldingStartTime() {
+        return mHoldingStartTime;
+    }
+
+    @Override
+    public long getHoldDurationMillis() {
+        if (getState() != ImsPhoneCall.State.HOLDING) {
+            // If not holding, return 0
+            return 0;
+        } else {
+            return SystemClock.elapsedRealtime() - mHoldingStartTime;
+        }
+    }
+
+    @Override
+    public int getDisconnectCause() {
+        return mCause;
+    }
+
+    public void setDisconnectCause(int cause) {
+        mCause = cause;
+    }
+
+    public ImsPhoneCallTracker getOwner () {
+        return mOwner;
+    }
+
+    @Override
+    public boolean isIncoming() {
+        return mIsIncoming;
+    }
+
+    @Override
+    public ImsPhoneCall.State getState() {
+        if (mDisconnected) {
+            return ImsPhoneCall.State.DISCONNECTED;
+        } else {
+            return super.getState();
+        }
+    }
+
+    @Override
+    public void hangup() throws CallStateException {
+        if (!mDisconnected) {
+            mOwner.hangup(this);
+        } else {
+            throw new CallStateException ("disconnected");
+        }
+    }
+
+    @Override
+    public void separate() throws CallStateException {
+        throw new CallStateException ("not supported");
+    }
+
+    @Override
+    public PostDialState getPostDialState() {
+        return mPostDialState;
+    }
+
+    @Override
+    public void proceedAfterWaitChar() {
+        if (mPostDialState != PostDialState.WAIT) {
+            Rlog.w(LOG_TAG, "ImsPhoneConnection.proceedAfterWaitChar(): Expected "
+                    + "getPostDialState() to be WAIT but was " + mPostDialState);
+            return;
+        }
+
+        setPostDialState(PostDialState.STARTED);
+
+        processNextPostDialChar();
+    }
+
+    @Override
+    public void proceedAfterWildChar(String str) {
+        if (mPostDialState != PostDialState.WILD) {
+            Rlog.w(LOG_TAG, "ImsPhoneConnection.proceedAfterWaitChar(): Expected "
+                    + "getPostDialState() to be WILD but was " + mPostDialState);
+            return;
+        }
+
+        setPostDialState(PostDialState.STARTED);
+
+        // make a new postDialString, with the wild char replacement string
+        // at the beginning, followed by the remaining postDialString.
+
+        StringBuilder buf = new StringBuilder(str);
+        buf.append(mPostDialString.substring(mNextPostDialChar));
+        mPostDialString = buf.toString();
+        mNextPostDialChar = 0;
+        if (Phone.DEBUG_PHONE) {
+            Rlog.d(LOG_TAG, "proceedAfterWildChar: new postDialString is " +
+                    mPostDialString);
+        }
+
+        processNextPostDialChar();
+    }
+
+    @Override
+    public void cancelPostDial() {
+        setPostDialState(PostDialState.CANCELLED);
+    }
+
+    /**
+     * Called when this Connection is being hung up locally (eg, user pressed "end")
+     */
+    void
+    onHangupLocal() {
+        mCause = DisconnectCause.LOCAL;
+    }
+
+    /** Called when the connection has been disconnected */
+    /*package*/ boolean
+    onDisconnect(int cause) {
+        Rlog.d(LOG_TAG, "onDisconnect: cause=" + cause);
+        if (mCause != DisconnectCause.LOCAL) mCause = cause;
+        return onDisconnect();
+    }
+
+    /*package*/ boolean
+    onDisconnect() {
+        boolean changed = false;
+
+        if (!mDisconnected) {
+            //mIndex = -1;
+
+            mDisconnectTime = System.currentTimeMillis();
+            mDuration = SystemClock.elapsedRealtime() - mConnectTimeReal;
+            mDisconnected = true;
+
+            mOwner.mPhone.notifyDisconnect(this);
+
+            if (mParent != null) {
+                changed = mParent.connectionDisconnected(this);
+            } else {
+                Rlog.d(LOG_TAG, "onDisconnect: no parent");
+            }
+            if (mImsCall != null) mImsCall.close();
+            mImsCall = null;
+        }
+        releaseWakeLock();
+        return changed;
+    }
+
+    /**
+     * An incoming or outgoing call has connected
+     */
+    void
+    onConnectedInOrOut() {
+        mConnectTime = System.currentTimeMillis();
+        mConnectTimeReal = SystemClock.elapsedRealtime();
+        mDuration = 0;
+
+        if (Phone.DEBUG_PHONE) {
+            Rlog.d(LOG_TAG, "onConnectedInOrOut: connectTime=" + mConnectTime);
+        }
+
+        if (!mIsIncoming) {
+            // outgoing calls only
+            processNextPostDialChar();
+        }
+        releaseWakeLock();
+    }
+
+    /*package*/ void
+    onStartedHolding() {
+        mHoldingStartTime = SystemClock.elapsedRealtime();
+    }
+    /**
+     * Performs the appropriate action for a post-dial char, but does not
+     * notify application. returns false if the character is invalid and
+     * should be ignored
+     */
+    private boolean
+    processPostDialChar(char c) {
+        if (PhoneNumberUtils.is12Key(c)) {
+            mOwner.mCi.sendDtmf(c, mHandler.obtainMessage(EVENT_DTMF_DONE));
+        } else if (c == PhoneNumberUtils.PAUSE) {
+            // From TS 22.101:
+            // It continues...
+            // Upon the called party answering the UE shall send the DTMF digits
+            // automatically to the network after a delay of 3 seconds( 20 ).
+            // The digits shall be sent according to the procedures and timing
+            // specified in 3GPP TS 24.008 [13]. The first occurrence of the
+            // "DTMF Control Digits Separator" shall be used by the ME to
+            // distinguish between the addressing digits (i.e. the phone number)
+            // and the DTMF digits. Upon subsequent occurrences of the
+            // separator,
+            // the UE shall pause again for 3 seconds ( 20 ) before sending
+            // any further DTMF digits.
+            mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_PAUSE_DONE),
+                    PAUSE_DELAY_MILLIS);
+        } else if (c == PhoneNumberUtils.WAIT) {
+            setPostDialState(PostDialState.WAIT);
+        } else if (c == PhoneNumberUtils.WILD) {
+            setPostDialState(PostDialState.WILD);
+        } else {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public String
+    getRemainingPostDialString() {
+        if (mPostDialState == PostDialState.CANCELLED
+            || mPostDialState == PostDialState.COMPLETE
+            || mPostDialString == null
+            || mPostDialString.length() <= mNextPostDialChar
+        ) {
+            return "";
+        }
+
+        return mPostDialString.substring(mNextPostDialChar);
+    }
+
+    @Override
+    protected void finalize()
+    {
+        releaseWakeLock();
+    }
+
+    private void
+    processNextPostDialChar() {
+        char c = 0;
+        Registrant postDialHandler;
+
+        if (mPostDialState == PostDialState.CANCELLED) {
+            //Rlog.d(LOG_TAG, "##### processNextPostDialChar: postDialState == CANCELLED, bail");
+            return;
+        }
+
+        if (mPostDialString == null ||
+                mPostDialString.length() <= mNextPostDialChar) {
+            setPostDialState(PostDialState.COMPLETE);
+
+            // notifyMessage.arg1 is 0 on complete
+            c = 0;
+        } else {
+            boolean isValid;
+
+            setPostDialState(PostDialState.STARTED);
+
+            c = mPostDialString.charAt(mNextPostDialChar++);
+
+            isValid = processPostDialChar(c);
+
+            if (!isValid) {
+                // Will call processNextPostDialChar
+                mHandler.obtainMessage(EVENT_NEXT_POST_DIAL).sendToTarget();
+                // Don't notify application
+                Rlog.e(LOG_TAG, "processNextPostDialChar: c=" + c + " isn't valid!");
+                return;
+            }
+        }
+
+        postDialHandler = mOwner.mPhone.mPostDialHandler;
+
+        Message notifyMessage;
+
+        if (postDialHandler != null
+                && (notifyMessage = postDialHandler.messageForRegistrant()) != null) {
+            // The AsyncResult.result is the Connection object
+            PostDialState state = mPostDialState;
+            AsyncResult ar = AsyncResult.forMessage(notifyMessage);
+            ar.result = this;
+            ar.userObj = state;
+
+            // arg1 is the character that was/is being processed
+            notifyMessage.arg1 = c;
+
+            //Rlog.v(LOG_TAG, "##### processNextPostDialChar: send msg to postDialHandler, arg1=" + c);
+            notifyMessage.sendToTarget();
+        }
+    }
+
+    /**
+     * Set post dial state and acquire wake lock while switching to "started"
+     * state, the wake lock will be released if state switches out of "started"
+     * state or after WAKE_LOCK_TIMEOUT_MILLIS.
+     * @param s new PostDialState
+     */
+    private void setPostDialState(PostDialState s) {
+        if (mPostDialState != PostDialState.STARTED
+                && s == PostDialState.STARTED) {
+            acquireWakeLock();
+            Message msg = mHandler.obtainMessage(EVENT_WAKE_LOCK_TIMEOUT);
+            mHandler.sendMessageDelayed(msg, WAKE_LOCK_TIMEOUT_MILLIS);
+        } else if (mPostDialState == PostDialState.STARTED
+                && s != PostDialState.STARTED) {
+            mHandler.removeMessages(EVENT_WAKE_LOCK_TIMEOUT);
+            releaseWakeLock();
+        }
+        mPostDialState = s;
+    }
+
+    private void
+    createWakeLock(Context context) {
+        PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+        mPartialWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, LOG_TAG);
+    }
+
+    private void
+    acquireWakeLock() {
+        Rlog.d(LOG_TAG, "acquireWakeLock");
+        mPartialWakeLock.acquire();
+    }
+
+    private void
+    releaseWakeLock() {
+        synchronized(mPartialWakeLock) {
+            if (mPartialWakeLock.isHeld()) {
+                Rlog.d(LOG_TAG, "releaseWakeLock");
+                mPartialWakeLock.release();
+            }
+        }
+    }
+
+    @Override
+    public int getNumberPresentation() {
+        return mNumberPresentation;
+    }
+
+    @Override
+    public UUSInfo getUUSInfo() {
+        return mUusInfo;
+    }
+
+    @Override
+    public Connection getOrigConnection() {
+        return null;
+    }
+
+    /* package */ void
+    setMultiparty(boolean isMultiparty) {
+        Rlog.d(LOG_TAG, "setMultiparty " + isMultiparty);
+        mIsMultiparty = isMultiparty;
+    }
+
+    @Override
+    public boolean isMultiparty() {
+        return mIsMultiparty;
+    }
+
+    /*package*/ ImsCall getImsCall() {
+        return mImsCall;
+    }
+
+    /*package*/ void setImsCall(ImsCall imsCall) {
+        mImsCall = imsCall;
+    }
+
+    /*package*/ void changeParent(ImsPhoneCall parent) {
+        mParent = parent;
+    }
+
+    /*package*/ boolean
+    update(ImsCall imsCall, ImsPhoneCall.State state) {
+        boolean changed = false;
+
+        if (state == ImsPhoneCall.State.ACTIVE) {
+            if (mParent.getState().isRinging()
+                    || mParent.getState().isDialing()) {
+                onConnectedInOrOut();
+            }
+
+            if (mParent.getState().isRinging()
+                    || mParent == mOwner.mBackgroundCall) {
+                //mForegroundCall should be IDLE
+                //when accepting WAITING call
+                //before accept WAITING call,
+                //the ACTIVE call should be held ahead
+                mParent.detach(this);
+                mParent = mOwner.mForegroundCall;
+                mParent.attach(this);
+            }
+        } else if (state == ImsPhoneCall.State.HOLDING) {
+            onStartedHolding();
+        }
+
+        changed = mParent.update(this, imsCall, state);
+
+        return changed;
+    }
+
+    @Override
+    public int getPreciseDisconnectCause() {
+        return 0;
+    }
+
+    private static int presentationFromOir(int oir) {
+        switch(oir) {
+            case ImsCallProfile.OIR_PRESENTATION_RESTRICTED:
+                return PhoneConstants.PRESENTATION_RESTRICTED;
+            case ImsCallProfile.OIR_PRESENTATION_NOT_RESTRICTED:
+                return PhoneConstants.PRESENTATION_ALLOWED;
+            default:
+                return PhoneConstants.PRESENTATION_UNKNOWN;
+        }
+    }
+}
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneFactory.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneFactory.java
new file mode 100644
index 0000000..6bb5ac9
--- /dev/null
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneFactory.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2013 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.imsphone;
+
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneNotifier;
+
+import android.content.Context;
+import android.telephony.Rlog;
+
+/**
+ * {@hide}
+ */
+public class ImsPhoneFactory {
+
+    /**
+     * Makes a {@link ImsPhone} object.
+     * @param context {@code Context} needed to create a Phone object
+     * @param phoneNotifier {@code PhoneNotifier} needed to create a Phone
+     *      object
+     * @return the {@code ImsPhone} object
+     */
+    public static Phone makePhone(Context context,
+            PhoneNotifier phoneNotifier, Phone defaultPhone) {
+
+        try {
+            return new ImsPhone(context, phoneNotifier, defaultPhone);
+        } catch (Exception e) {
+            Rlog.e("VoltePhoneFactory", "makePhone", e);
+            return null;
+        }
+    }
+}
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneMmiCode.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneMmiCode.java
new file mode 100644
index 0000000..2f33ece
--- /dev/null
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneMmiCode.java
@@ -0,0 +1,1186 @@
+/*
+ * Copyright (C) 2013 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.imsphone;
+
+import android.content.Context;
+import android.content.res.Resources;
+
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
+import android.telephony.PhoneNumberUtils;
+import android.text.SpannableStringBuilder;
+import android.text.TextUtils;
+import android.telephony.Rlog;
+
+import com.android.internal.telephony.CallForwardInfo;
+import com.android.internal.telephony.CommandException;
+import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.uicc.IccRecords;
+
+import static com.android.internal.telephony.CommandsInterface.SERVICE_CLASS_NONE;
+import static com.android.internal.telephony.CommandsInterface.SERVICE_CLASS_VOICE;
+import static com.android.internal.telephony.CommandsInterface.SERVICE_CLASS_DATA;
+import static com.android.internal.telephony.CommandsInterface.SERVICE_CLASS_FAX;
+import static com.android.internal.telephony.CommandsInterface.SERVICE_CLASS_SMS;
+import static com.android.internal.telephony.CommandsInterface.SERVICE_CLASS_DATA_SYNC;
+import static com.android.internal.telephony.CommandsInterface.SERVICE_CLASS_DATA_ASYNC;
+import static com.android.internal.telephony.CommandsInterface.SERVICE_CLASS_PACKET;
+import static com.android.internal.telephony.CommandsInterface.SERVICE_CLASS_PAD;
+import static com.android.internal.telephony.CommandsInterface.SERVICE_CLASS_MAX;
+
+
+import com.android.internal.telephony.MmiCode;
+import com.android.internal.telephony.Phone;
+
+import java.util.regex.Pattern;
+import java.util.regex.Matcher;
+
+/**
+ * The motto for this file is:
+ *
+ * "NOTE:    By using the # as a separator, most cases are expected to be unambiguous."
+ *   -- TS 22.030 6.5.2
+ *
+ * {@hide}
+ *
+ */
+public final class ImsPhoneMmiCode extends Handler implements MmiCode {
+    static final String LOG_TAG = "ImsPhoneMmiCode";
+
+    //***** Constants
+
+    // Max Size of the Short Code (aka Short String from TS 22.030 6.5.2)
+    private static final int MAX_LENGTH_SHORT_CODE = 2;
+
+    // TS 22.030 6.5.2 Every Short String USSD command will end with #-key
+    // (known as #-String)
+    private static final char END_OF_USSD_COMMAND = '#';
+
+    // From TS 22.030 6.5.2
+    private static final String ACTION_ACTIVATE = "*";
+    private static final String ACTION_DEACTIVATE = "#";
+    private static final String ACTION_INTERROGATE = "*#";
+    private static final String ACTION_REGISTER = "**";
+    private static final String ACTION_ERASURE = "##";
+
+    // Supp Service codes from TS 22.030 Annex B
+
+    //Called line presentation
+    private static final String SC_CLIP    = "30";
+    private static final String SC_CLIR    = "31";
+
+    // Call Forwarding
+    private static final String SC_CFU     = "21";
+    private static final String SC_CFB     = "67";
+    private static final String SC_CFNRy   = "61";
+    private static final String SC_CFNR    = "62";
+
+    private static final String SC_CF_All = "002";
+    private static final String SC_CF_All_Conditional = "004";
+
+    // Call Waiting
+    private static final String SC_WAIT     = "43";
+
+    // Call Barring
+    private static final String SC_BAOC         = "33";
+    private static final String SC_BAOIC        = "331";
+    private static final String SC_BAOICxH      = "332";
+    private static final String SC_BAIC         = "35";
+    private static final String SC_BAICr        = "351";
+
+    private static final String SC_BA_ALL       = "330";
+    private static final String SC_BA_MO        = "333";
+    private static final String SC_BA_MT        = "353";
+
+    // Supp Service Password registration
+    private static final String SC_PWD          = "03";
+
+    // PIN/PIN2/PUK/PUK2
+    private static final String SC_PIN          = "04";
+    private static final String SC_PIN2         = "042";
+    private static final String SC_PUK          = "05";
+    private static final String SC_PUK2         = "052";
+
+    //***** Event Constants
+
+    private static final int EVENT_SET_COMPLETE         = 0;
+    private static final int EVENT_QUERY_CF_COMPLETE    = 1;
+    private static final int EVENT_USSD_COMPLETE        = 2;
+    private static final int EVENT_QUERY_COMPLETE       = 3;
+    private static final int EVENT_SET_CFF_COMPLETE     = 4;
+    private static final int EVENT_USSD_CANCEL_COMPLETE = 5;
+
+    //***** Instance Variables
+
+    private ImsPhone mPhone;
+    private Context mContext;
+    private IccRecords mIccRecords;
+
+    private String mAction;              // One of ACTION_*
+    private String mSc;                  // Service Code
+    private String mSia, mSib, mSic;       // Service Info a,b,c
+    private String mPoundString;         // Entire MMI string up to and including #
+    private String mDialingNumber;
+    private String mPwd;                 // For password registration
+
+    private boolean mIsPendingUSSD;
+
+    private boolean mIsUssdRequest;
+
+    private boolean mIsCallFwdReg;
+    private State mState = State.PENDING;
+    private CharSequence mMessage;
+
+    //***** Class Variables
+
+
+    // See TS 22.030 6.5.2 "Structure of the MMI"
+
+    private static Pattern sPatternSuppService = Pattern.compile(
+        "((\\*|#|\\*#|\\*\\*|##)(\\d{2,3})(\\*([^*#]*)(\\*([^*#]*)(\\*([^*#]*)(\\*([^*#]*))?)?)?)?#)(.*)");
+/*       1  2                    3          4  5       6   7         8    9     10  11             12
+
+         1 = Full string up to and including #
+         2 = action (activation/interrogation/registration/erasure)
+         3 = service code
+         5 = SIA
+         7 = SIB
+         9 = SIC
+         10 = dialing number
+*/
+
+    private static final int MATCH_GROUP_POUND_STRING = 1;
+
+    private static final int MATCH_GROUP_ACTION = 2;
+                        //(activation/interrogation/registration/erasure)
+
+    private static final int MATCH_GROUP_SERVICE_CODE = 3;
+    private static final int MATCH_GROUP_SIA = 5;
+    private static final int MATCH_GROUP_SIB = 7;
+    private static final int MATCH_GROUP_SIC = 9;
+    private static final int MATCH_GROUP_PWD_CONFIRM = 11;
+    private static final int MATCH_GROUP_DIALING_NUMBER = 12;
+    static private String[] sTwoDigitNumberPattern;
+
+    //***** Public Class methods
+
+    /**
+     * Some dial strings in GSM are defined to do non-call setup
+     * things, such as modify or query supplementary service settings (eg, call
+     * forwarding). These are generally referred to as "MMI codes".
+     * We look to see if the dial string contains a valid MMI code (potentially
+     * with a dial string at the end as well) and return info here.
+     *
+     * If the dial string contains no MMI code, we return an instance with
+     * only "dialingNumber" set
+     *
+     * Please see flow chart in TS 22.030 6.5.3.2
+     */
+
+    static ImsPhoneMmiCode
+    newFromDialString(String dialString, ImsPhone phone) {
+        Matcher m;
+        ImsPhoneMmiCode ret = null;
+
+        m = sPatternSuppService.matcher(dialString);
+
+        // Is this formatted like a standard supplementary service code?
+        if (m.matches()) {
+            ret = new ImsPhoneMmiCode(phone);
+            ret.mPoundString = makeEmptyNull(m.group(MATCH_GROUP_POUND_STRING));
+            ret.mAction = makeEmptyNull(m.group(MATCH_GROUP_ACTION));
+            ret.mSc = makeEmptyNull(m.group(MATCH_GROUP_SERVICE_CODE));
+            ret.mSia = makeEmptyNull(m.group(MATCH_GROUP_SIA));
+            ret.mSib = makeEmptyNull(m.group(MATCH_GROUP_SIB));
+            ret.mSic = makeEmptyNull(m.group(MATCH_GROUP_SIC));
+            ret.mPwd = makeEmptyNull(m.group(MATCH_GROUP_PWD_CONFIRM));
+            ret.mDialingNumber = makeEmptyNull(m.group(MATCH_GROUP_DIALING_NUMBER));
+            // According to TS 22.030 6.5.2 "Structure of the MMI",
+            // the dialing number should not ending with #.
+            // The dialing number ending # is treated as unique USSD,
+            // eg, *400#16 digit number# to recharge the prepaid card
+            // in India operator(Mumbai MTNL)
+            if (ret.mDialingNumber != null &&
+                    ret.mDialingNumber.endsWith("#") &&
+                    dialString.endsWith("#")){
+                ret = new ImsPhoneMmiCode(phone);
+                ret.mPoundString = dialString;
+            }
+        } else if (dialString.endsWith("#")) {
+            // TS 22.030 sec 6.5.3.2
+            // "Entry of any characters defined in the 3GPP TS 23.038 [8] Default Alphabet
+            // (up to the maximum defined in 3GPP TS 24.080 [10]), followed by #SEND".
+
+            ret = new ImsPhoneMmiCode(phone);
+            ret.mPoundString = dialString;
+        } else if (isTwoDigitShortCode(phone.getContext(), dialString)) {
+            //Is a country-specific exception to short codes as defined in TS 22.030, 6.5.3.2
+            ret = null;
+        } else if (isShortCode(dialString, phone)) {
+            // this may be a short code, as defined in TS 22.030, 6.5.3.2
+            ret = new ImsPhoneMmiCode(phone);
+            ret.mDialingNumber = dialString;
+        }
+
+        return ret;
+    }
+
+    static ImsPhoneMmiCode
+    newNetworkInitiatedUssd (String ussdMessage,
+                                boolean isUssdRequest, ImsPhone phone) {
+        ImsPhoneMmiCode ret;
+
+        ret = new ImsPhoneMmiCode(phone);
+
+        ret.mMessage = ussdMessage;
+        ret.mIsUssdRequest = isUssdRequest;
+
+        // If it's a request, set to PENDING so that it's cancelable.
+        if (isUssdRequest) {
+            ret.mIsPendingUSSD = true;
+            ret.mState = State.PENDING;
+        } else {
+            ret.mState = State.COMPLETE;
+        }
+
+        return ret;
+    }
+
+    static ImsPhoneMmiCode newFromUssdUserInput(String ussdMessge,
+                                           ImsPhone phone) {
+        ImsPhoneMmiCode ret = new ImsPhoneMmiCode(phone);
+
+        ret.mMessage = ussdMessge;
+        ret.mState = State.PENDING;
+        ret.mIsPendingUSSD = true;
+
+        return ret;
+    }
+
+    //***** Private Class methods
+
+    /** make empty strings be null.
+     *  Regexp returns empty strings for empty groups
+     */
+    private static String
+    makeEmptyNull (String s) {
+        if (s != null && s.length() == 0) return null;
+
+        return s;
+    }
+
+    /** returns true of the string is empty or null */
+    private static boolean
+    isEmptyOrNull(CharSequence s) {
+        return s == null || (s.length() == 0);
+    }
+
+    private static int
+    scToCallForwardReason(String sc) {
+        if (sc == null) {
+            throw new RuntimeException ("invalid call forward sc");
+        }
+
+        if (sc.equals(SC_CF_All)) {
+           return CommandsInterface.CF_REASON_ALL;
+        } else if (sc.equals(SC_CFU)) {
+            return CommandsInterface.CF_REASON_UNCONDITIONAL;
+        } else if (sc.equals(SC_CFB)) {
+            return CommandsInterface.CF_REASON_BUSY;
+        } else if (sc.equals(SC_CFNR)) {
+            return CommandsInterface.CF_REASON_NOT_REACHABLE;
+        } else if (sc.equals(SC_CFNRy)) {
+            return CommandsInterface.CF_REASON_NO_REPLY;
+        } else if (sc.equals(SC_CF_All_Conditional)) {
+           return CommandsInterface.CF_REASON_ALL_CONDITIONAL;
+        } else {
+            throw new RuntimeException ("invalid call forward sc");
+        }
+    }
+
+    private static int
+    siToServiceClass(String si) {
+        if (si == null || si.length() == 0) {
+                return  SERVICE_CLASS_NONE;
+        } else {
+            // NumberFormatException should cause MMI fail
+            int serviceCode = Integer.parseInt(si, 10);
+
+            switch (serviceCode) {
+                case 10: return SERVICE_CLASS_SMS + SERVICE_CLASS_FAX  + SERVICE_CLASS_VOICE;
+                case 11: return SERVICE_CLASS_VOICE;
+                case 12: return SERVICE_CLASS_SMS + SERVICE_CLASS_FAX;
+                case 13: return SERVICE_CLASS_FAX;
+
+                case 16: return SERVICE_CLASS_SMS;
+
+                case 19: return SERVICE_CLASS_FAX + SERVICE_CLASS_VOICE;
+
+                case 20: return SERVICE_CLASS_DATA_ASYNC + SERVICE_CLASS_DATA_SYNC;
+
+                case 21: return SERVICE_CLASS_PAD + SERVICE_CLASS_DATA_ASYNC;
+                case 22: return SERVICE_CLASS_PACKET + SERVICE_CLASS_DATA_SYNC;
+                case 24: return SERVICE_CLASS_DATA_SYNC;
+                case 25: return SERVICE_CLASS_DATA_ASYNC;
+                case 26: return SERVICE_CLASS_DATA_SYNC + SERVICE_CLASS_VOICE;
+                case 99: return SERVICE_CLASS_PACKET;
+
+                default:
+                    throw new RuntimeException("unsupported MMI service code " + si);
+            }
+        }
+    }
+
+    private static int
+    siToTime (String si) {
+        if (si == null || si.length() == 0) {
+            return 0;
+        } else {
+            // NumberFormatException should cause MMI fail
+            return Integer.parseInt(si, 10);
+        }
+    }
+
+    static boolean
+    isServiceCodeCallForwarding(String sc) {
+        return sc != null &&
+                (sc.equals(SC_CFU)
+                || sc.equals(SC_CFB) || sc.equals(SC_CFNRy)
+                || sc.equals(SC_CFNR) || sc.equals(SC_CF_All)
+                || sc.equals(SC_CF_All_Conditional));
+    }
+
+    static boolean
+    isServiceCodeCallBarring(String sc) {
+        Resources resource = Resources.getSystem();
+        if (sc != null) {
+            String[] barringMMI = resource.getStringArray(
+                com.android.internal.R.array.config_callBarringMMI);
+            if (barringMMI != null) {
+                for (String match : barringMMI) {
+                    if (sc.equals(match)) return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    static String
+    scToBarringFacility(String sc) {
+        if (sc == null) {
+            throw new RuntimeException ("invalid call barring sc");
+        }
+
+        if (sc.equals(SC_BAOC)) {
+            return CommandsInterface.CB_FACILITY_BAOC;
+        } else if (sc.equals(SC_BAOIC)) {
+            return CommandsInterface.CB_FACILITY_BAOIC;
+        } else if (sc.equals(SC_BAOICxH)) {
+            return CommandsInterface.CB_FACILITY_BAOICxH;
+        } else if (sc.equals(SC_BAIC)) {
+            return CommandsInterface.CB_FACILITY_BAIC;
+        } else if (sc.equals(SC_BAICr)) {
+            return CommandsInterface.CB_FACILITY_BAICr;
+        } else if (sc.equals(SC_BA_ALL)) {
+            return CommandsInterface.CB_FACILITY_BA_ALL;
+        } else if (sc.equals(SC_BA_MO)) {
+            return CommandsInterface.CB_FACILITY_BA_MO;
+        } else if (sc.equals(SC_BA_MT)) {
+            return CommandsInterface.CB_FACILITY_BA_MT;
+        } else {
+            throw new RuntimeException ("invalid call barring sc");
+        }
+    }
+
+    //***** Constructor
+
+    ImsPhoneMmiCode(ImsPhone phone) {
+        // The telephony unit-test cases may create ImsPhoneMmiCode's
+        // in secondary threads
+        super(phone.getHandler().getLooper());
+        mPhone = phone;
+        mContext = phone.getContext();
+        mIccRecords = mPhone.mDefaultPhone.mIccRecords.get();
+    }
+
+    //***** MmiCode implementation
+
+    @Override
+    public State
+    getState() {
+        return mState;
+    }
+
+    @Override
+    public CharSequence
+    getMessage() {
+        return mMessage;
+    }
+
+    @Override
+    public Phone getPhone() { return mPhone; }
+
+    // inherited javadoc suffices
+    @Override
+    public void
+    cancel() {
+        // Complete or failed cannot be cancelled
+        if (mState == State.COMPLETE || mState == State.FAILED) {
+            return;
+        }
+
+        mState = State.CANCELLED;
+
+        if (mIsPendingUSSD) {
+            mPhone.cancelUSSD();
+        } else {
+            mPhone.onMMIDone (this);
+        }
+
+    }
+
+    @Override
+    public boolean isCancelable() {
+        /* Can only cancel pending USSD sessions. */
+        return mIsPendingUSSD;
+    }
+
+    //***** Instance Methods
+
+    String getDialingNumber() {
+        return mDialingNumber;
+    }
+
+    /** Does this dial string contain a structured or unstructured MMI code? */
+    boolean
+    isMMI() {
+        return mPoundString != null;
+    }
+
+    /* Is this a 1 or 2 digit "short code" as defined in TS 22.030 sec 6.5.3.2? */
+    boolean
+    isShortCode() {
+        return mPoundString == null
+                    && mDialingNumber != null && mDialingNumber.length() <= 2;
+
+    }
+
+    static private boolean
+    isTwoDigitShortCode(Context context, String dialString) {
+        Rlog.d(LOG_TAG, "isTwoDigitShortCode");
+
+        if (dialString == null || dialString.length() != 2) return false;
+
+        if (sTwoDigitNumberPattern == null) {
+            sTwoDigitNumberPattern = context.getResources().getStringArray(
+                    com.android.internal.R.array.config_twoDigitNumberPattern);
+        }
+
+        for (String dialnumber : sTwoDigitNumberPattern) {
+            Rlog.d(LOG_TAG, "Two Digit Number Pattern " + dialnumber);
+            if (dialString.equals(dialnumber)) {
+                Rlog.d(LOG_TAG, "Two Digit Number Pattern -true");
+                return true;
+            }
+        }
+        Rlog.d(LOG_TAG, "Two Digit Number Pattern -false");
+        return false;
+    }
+
+    /**
+     * Helper function for newFromDialString. Returns true if dialString appears
+     * to be a short code AND conditions are correct for it to be treated as
+     * such.
+     */
+    static private boolean isShortCode(String dialString, ImsPhone phone) {
+        // Refer to TS 22.030 Figure 3.5.3.2:
+        if (dialString == null) {
+            return false;
+        }
+
+        // Illegal dial string characters will give a ZERO length.
+        // At this point we do not want to crash as any application with
+        // call privileges may send a non dial string.
+        // It return false as when the dialString is equal to NULL.
+        if (dialString.length() == 0) {
+            return false;
+        }
+
+        if (PhoneNumberUtils.isLocalEmergencyNumber(phone.getContext(), dialString)) {
+            return false;
+        } else {
+            return isShortCodeUSSD(dialString, phone);
+        }
+    }
+
+    /**
+     * Helper function for isShortCode. Returns true if dialString appears to be
+     * a short code and it is a USSD structure
+     *
+     * According to the 3PGG TS 22.030 specification Figure 3.5.3.2: A 1 or 2
+     * digit "short code" is treated as USSD if it is entered while on a call or
+     * does not satisfy the condition (exactly 2 digits && starts with '1'), there
+     * are however exceptions to this rule (see below)
+     *
+     * Exception (1) to Call initiation is: If the user of the device is already in a call
+     * and enters a Short String without any #-key at the end and the length of the Short String is
+     * equal or less then the MAX_LENGTH_SHORT_CODE [constant that is equal to 2]
+     *
+     * The phone shall initiate a USSD/SS commands.
+     */
+    static private boolean isShortCodeUSSD(String dialString, ImsPhone phone) {
+        if (dialString != null && dialString.length() <= MAX_LENGTH_SHORT_CODE) {
+            if (phone.isInCall()) {
+                return true;
+            }
+
+            if (dialString.length() != MAX_LENGTH_SHORT_CODE ||
+                    dialString.charAt(0) != '1') {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * @return true if the Service Code is PIN/PIN2/PUK/PUK2-related
+     */
+    boolean isPinPukCommand() {
+        return mSc != null && (mSc.equals(SC_PIN) || mSc.equals(SC_PIN2)
+                              || mSc.equals(SC_PUK) || mSc.equals(SC_PUK2));
+    }
+
+    /**
+     * See TS 22.030 Annex B.
+     * In temporary mode, to suppress CLIR for a single call, enter:
+     *      " * 31 # [called number] SEND "
+     *  In temporary mode, to invoke CLIR for a single call enter:
+     *       " # 31 # [called number] SEND "
+     */
+    boolean
+    isTemporaryModeCLIR() {
+        return mSc != null && mSc.equals(SC_CLIR) && mDialingNumber != null
+                && (isActivate() || isDeactivate());
+    }
+
+    /**
+     * returns CommandsInterface.CLIR_*
+     * See also isTemporaryModeCLIR()
+     */
+    int
+    getCLIRMode() {
+        if (mSc != null && mSc.equals(SC_CLIR)) {
+            if (isActivate()) {
+                return CommandsInterface.CLIR_SUPPRESSION;
+            } else if (isDeactivate()) {
+                return CommandsInterface.CLIR_INVOCATION;
+            }
+        }
+
+        return CommandsInterface.CLIR_DEFAULT;
+    }
+
+    boolean isActivate() {
+        return mAction != null && mAction.equals(ACTION_ACTIVATE);
+    }
+
+    boolean isDeactivate() {
+        return mAction != null && mAction.equals(ACTION_DEACTIVATE);
+    }
+
+    boolean isInterrogate() {
+        return mAction != null && mAction.equals(ACTION_INTERROGATE);
+    }
+
+    boolean isRegister() {
+        return mAction != null && mAction.equals(ACTION_REGISTER);
+    }
+
+    boolean isErasure() {
+        return mAction != null && mAction.equals(ACTION_ERASURE);
+    }
+
+    /**
+     * Returns true if this is a USSD code that's been submitted to the
+     * network...eg, after processCode() is called
+     */
+    public boolean isPendingUSSD() {
+        return mIsPendingUSSD;
+    }
+
+    @Override
+    public boolean isUssdRequest() {
+        return mIsUssdRequest;
+    }
+
+    boolean
+    isSupportedOverImsPhone() {
+        if (isShortCode()) return true;
+        else if (mDialingNumber != null) return false;
+        else if (isServiceCodeCallForwarding(mSc)
+                || isServiceCodeCallBarring(mSc)
+                || (mSc != null && mSc.equals(SC_WAIT))) {
+
+            int serviceClass = siToServiceClass(mSib);
+            if (serviceClass != SERVICE_CLASS_NONE
+                    && serviceClass != SERVICE_CLASS_VOICE) {
+                return false;
+            }
+            return true;
+        } else if (isPinPukCommand()
+                || (mSc != null
+                    && (mSc.equals(SC_PWD) || mSc.equals(SC_CLIP) || mSc.equals(SC_CLIR)))) {
+            return false;
+        } else if (mPoundString != null) return true;
+
+        return false;
+    }
+
+    /** Process a MMI code or short code...anything that isn't a dialing number */
+    void
+    processCode () {
+        try {
+            if (isShortCode()) {
+                Rlog.d(LOG_TAG, "isShortCode");
+
+                // These just get treated as USSD.
+                sendUssd(mDialingNumber);
+            } else if (isServiceCodeCallForwarding(mSc)) {
+                Rlog.d(LOG_TAG, "is CF");
+                // service group is not supported
+
+                String dialingNumber = mSia;
+                int reason = scToCallForwardReason(mSc);
+                int time = siToTime(mSic);
+
+                if (isInterrogate()) {
+                    mPhone.getCallForwardingOption(reason,
+                            obtainMessage(EVENT_QUERY_CF_COMPLETE, this));
+                } else {
+                    int cfAction;
+
+                    if (isActivate()) {
+                        // 3GPP TS 22.030 6.5.2
+                        // a call forwarding request with a single * would be
+                        // interpreted as registration if containing a forwarded-to
+                        // number, or an activation if not
+                        if (isEmptyOrNull(dialingNumber)) {
+                            cfAction = CommandsInterface.CF_ACTION_ENABLE;
+                            mIsCallFwdReg = false;
+                        } else {
+                            cfAction = CommandsInterface.CF_ACTION_REGISTRATION;
+                            mIsCallFwdReg = true;
+                        }
+                    } else if (isDeactivate()) {
+                        cfAction = CommandsInterface.CF_ACTION_DISABLE;
+                    } else if (isRegister()) {
+                        cfAction = CommandsInterface.CF_ACTION_REGISTRATION;
+                    } else if (isErasure()) {
+                        cfAction = CommandsInterface.CF_ACTION_ERASURE;
+                    } else {
+                        throw new RuntimeException ("invalid action");
+                    }
+
+                    int isSettingUnconditional =
+                            ((reason == CommandsInterface.CF_REASON_UNCONDITIONAL) ||
+                             (reason == CommandsInterface.CF_REASON_ALL)) ? 1 : 0;
+
+                    int isEnableDesired =
+                        ((cfAction == CommandsInterface.CF_ACTION_ENABLE) ||
+                                (cfAction == CommandsInterface.CF_ACTION_REGISTRATION)) ? 1 : 0;
+
+                    Rlog.d(LOG_TAG, "is CF setCallForward");
+                    mPhone.setCallForwardingOption(cfAction, reason,
+                            dialingNumber, time, obtainMessage(
+                                    EVENT_SET_CFF_COMPLETE,
+                                    isSettingUnconditional,
+                                    isEnableDesired, this));
+                }
+            } else if (isServiceCodeCallBarring(mSc)) {
+                // sia = password
+                // sib = basic service group
+                // service group is not supported
+
+                String password = mSia;
+                String facility = scToBarringFacility(mSc);
+
+                if (isInterrogate()) {
+                    mPhone.getCallBarring(facility,
+                            obtainMessage(EVENT_QUERY_COMPLETE, this));
+                } else if (isActivate() || isDeactivate()) {
+                    mPhone.setCallBarring(facility, isActivate(), password,
+                            obtainMessage(EVENT_SET_COMPLETE, this));
+                } else {
+                    throw new RuntimeException ("Invalid or Unsupported MMI Code");
+                }
+            } else if (mSc != null && mSc.equals(SC_WAIT)) {
+                // sia = basic service group
+                // service group is not supported
+                if (isActivate() || isDeactivate()) {
+                    mPhone.setCallWaiting(isActivate(),
+                            obtainMessage(EVENT_SET_COMPLETE, this));
+                } else if (isInterrogate()) {
+                    mPhone.getCallWaiting(obtainMessage(EVENT_QUERY_COMPLETE, this));
+                } else {
+                    throw new RuntimeException ("Invalid or Unsupported MMI Code");
+                }
+            } else if (mPoundString != null) {
+                sendUssd(mPoundString);
+            } else {
+                throw new RuntimeException ("Invalid or Unsupported MMI Code");
+            }
+        } catch (RuntimeException exc) {
+            mState = State.FAILED;
+            mMessage = mContext.getText(com.android.internal.R.string.mmiError);
+            mPhone.onMMIDone(this);
+        }
+    }
+
+    /**
+     * Called from ImsPhone
+     *
+     * An unsolicited USSD NOTIFY or REQUEST has come in matching
+     * up with this pending USSD request
+     *
+     * Note: If REQUEST, this exchange is complete, but the session remains
+     *       active (ie, the network expects user input).
+     */
+    void
+    onUssdFinished(String ussdMessage, boolean isUssdRequest) {
+        if (mState == State.PENDING) {
+            if (ussdMessage == null) {
+                mMessage = mContext.getText(com.android.internal.R.string.mmiComplete);
+            } else {
+                mMessage = ussdMessage;
+            }
+            mIsUssdRequest = isUssdRequest;
+            // If it's a request, leave it PENDING so that it's cancelable.
+            if (!isUssdRequest) {
+                mState = State.COMPLETE;
+            }
+
+            mPhone.onMMIDone(this);
+        }
+    }
+
+    /**
+     * Called from ImsPhone
+     *
+     * The radio has reset, and this is still pending
+     */
+
+    void
+    onUssdFinishedError() {
+        if (mState == State.PENDING) {
+            mState = State.FAILED;
+            mMessage = mContext.getText(com.android.internal.R.string.mmiError);
+
+            mPhone.onMMIDone(this);
+        }
+    }
+
+    void sendUssd(String ussdMessage) {
+        // Treat this as a USSD string
+        mIsPendingUSSD = true;
+
+        // Note that unlike most everything else, the USSD complete
+        // response does not complete this MMI code...we wait for
+        // an unsolicited USSD "Notify" or "Request".
+        // The matching up of this is done in ImsPhone.
+
+        mPhone.sendUSSD(ussdMessage,
+            obtainMessage(EVENT_USSD_COMPLETE, this));
+    }
+
+    /** Called from ImsPhone.handleMessage; not a Handler subclass */
+    @Override
+    public void
+    handleMessage (Message msg) {
+        AsyncResult ar;
+
+        switch (msg.what) {
+            case EVENT_SET_COMPLETE:
+                ar = (AsyncResult) (msg.obj);
+
+                onSetComplete(msg, ar);
+                break;
+
+            case EVENT_SET_CFF_COMPLETE:
+                ar = (AsyncResult) (msg.obj);
+
+                /*
+                * msg.arg1 = 1 means to set unconditional voice call forwarding
+                * msg.arg2 = 1 means to enable voice call forwarding
+                */
+                if ((ar.exception == null) && (msg.arg1 == 1)) {
+                    boolean cffEnabled = (msg.arg2 == 1);
+                    if (mIccRecords != null) {
+                        mIccRecords.setVoiceCallForwardingFlag(1, cffEnabled, mDialingNumber);
+                    }
+                }
+
+                onSetComplete(msg, ar);
+                break;
+
+            case EVENT_QUERY_CF_COMPLETE:
+                ar = (AsyncResult) (msg.obj);
+                onQueryCfComplete(ar);
+                break;
+
+            case EVENT_QUERY_COMPLETE:
+                ar = (AsyncResult) (msg.obj);
+                onQueryComplete(ar);
+                break;
+
+            case EVENT_USSD_COMPLETE:
+                ar = (AsyncResult) (msg.obj);
+
+                if (ar.exception != null) {
+                    mState = State.FAILED;
+                    mMessage = getErrorMessage(ar);
+
+                    mPhone.onMMIDone(this);
+                }
+
+                // Note that unlike most everything else, the USSD complete
+                // response does not complete this MMI code...we wait for
+                // an unsolicited USSD "Notify" or "Request".
+                // The matching up of this is done in ImsPhone.
+
+                break;
+
+            case EVENT_USSD_CANCEL_COMPLETE:
+                mPhone.onMMIDone(this);
+                break;
+
+            default:
+                break;
+        }
+    }
+
+    //***** Private instance methods
+
+    private CharSequence getErrorMessage(AsyncResult ar) {
+        return mContext.getText(com.android.internal.R.string.mmiError);
+    }
+
+    private CharSequence getScString() {
+        if (mSc != null) {
+            if (isServiceCodeCallBarring(mSc)) {
+                return mContext.getText(com.android.internal.R.string.BaMmi);
+            } else if (isServiceCodeCallForwarding(mSc)) {
+                return mContext.getText(com.android.internal.R.string.CfMmi);
+            } else if (mSc.equals(SC_PWD)) {
+                return mContext.getText(com.android.internal.R.string.PwdMmi);
+            } else if (mSc.equals(SC_WAIT)) {
+                return mContext.getText(com.android.internal.R.string.CwMmi);
+            }
+        }
+
+        return "";
+    }
+
+    private void
+    onSetComplete(Message msg, AsyncResult ar){
+        StringBuilder sb = new StringBuilder(getScString());
+        sb.append("\n");
+
+        if (ar.exception != null) {
+            mState = State.FAILED;
+            if (ar.exception instanceof CommandException) {
+                CommandException.Error err = ((CommandException)(ar.exception)).getCommandError();
+                if (err == CommandException.Error.PASSWORD_INCORRECT) {
+                    sb.append(mContext.getText(
+                            com.android.internal.R.string.passwordIncorrect));
+                } else {
+                    sb.append(mContext.getText(
+                            com.android.internal.R.string.mmiError));
+                }
+            } else {
+                sb.append(mContext.getText(
+                        com.android.internal.R.string.mmiError));
+            }
+        } else if (isActivate()) {
+            mState = State.COMPLETE;
+            if (mIsCallFwdReg) {
+                sb.append(mContext.getText(
+                        com.android.internal.R.string.serviceRegistered));
+            } else {
+                sb.append(mContext.getText(
+                        com.android.internal.R.string.serviceEnabled));
+            }
+        } else if (isDeactivate()) {
+            mState = State.COMPLETE;
+            sb.append(mContext.getText(
+                    com.android.internal.R.string.serviceDisabled));
+        } else if (isRegister()) {
+            mState = State.COMPLETE;
+            sb.append(mContext.getText(
+                    com.android.internal.R.string.serviceRegistered));
+        } else if (isErasure()) {
+            mState = State.COMPLETE;
+            sb.append(mContext.getText(
+                    com.android.internal.R.string.serviceErased));
+        } else {
+            mState = State.FAILED;
+            sb.append(mContext.getText(
+                    com.android.internal.R.string.mmiError));
+        }
+
+        mMessage = sb;
+        mPhone.onMMIDone(this);
+    }
+
+    /**
+     * @param serviceClass 1 bit of the service class bit vectory
+     * @return String to be used for call forward query MMI response text.
+     *        Returns null if unrecognized
+     */
+
+    private CharSequence
+    serviceClassToCFString (int serviceClass) {
+        switch (serviceClass) {
+            case SERVICE_CLASS_VOICE:
+                return mContext.getText(com.android.internal.R.string.serviceClassVoice);
+            case SERVICE_CLASS_DATA:
+                return mContext.getText(com.android.internal.R.string.serviceClassData);
+            case SERVICE_CLASS_FAX:
+                return mContext.getText(com.android.internal.R.string.serviceClassFAX);
+            case SERVICE_CLASS_SMS:
+                return mContext.getText(com.android.internal.R.string.serviceClassSMS);
+            case SERVICE_CLASS_DATA_SYNC:
+                return mContext.getText(com.android.internal.R.string.serviceClassDataSync);
+            case SERVICE_CLASS_DATA_ASYNC:
+                return mContext.getText(com.android.internal.R.string.serviceClassDataAsync);
+            case SERVICE_CLASS_PACKET:
+                return mContext.getText(com.android.internal.R.string.serviceClassPacket);
+            case SERVICE_CLASS_PAD:
+                return mContext.getText(com.android.internal.R.string.serviceClassPAD);
+            default:
+                return null;
+        }
+    }
+
+    /** one CallForwardInfo + serviceClassMask -> one line of text */
+    private CharSequence
+    makeCFQueryResultMessage(CallForwardInfo info, int serviceClassMask) {
+        CharSequence template;
+        String sources[] = {"{0}", "{1}", "{2}"};
+        CharSequence destinations[] = new CharSequence[3];
+        boolean needTimeTemplate;
+
+        // CF_REASON_NO_REPLY also has a time value associated with
+        // it. All others don't.
+
+        needTimeTemplate =
+            (info.reason == CommandsInterface.CF_REASON_NO_REPLY);
+
+        if (info.status == 1) {
+            if (needTimeTemplate) {
+                template = mContext.getText(
+                        com.android.internal.R.string.cfTemplateForwardedTime);
+            } else {
+                template = mContext.getText(
+                        com.android.internal.R.string.cfTemplateForwarded);
+            }
+        } else if (info.status == 0 && isEmptyOrNull(info.number)) {
+            template = mContext.getText(
+                        com.android.internal.R.string.cfTemplateNotForwarded);
+        } else { /* (info.status == 0) && !isEmptyOrNull(info.number) */
+            // A call forward record that is not active but contains
+            // a phone number is considered "registered"
+
+            if (needTimeTemplate) {
+                template = mContext.getText(
+                        com.android.internal.R.string.cfTemplateRegisteredTime);
+            } else {
+                template = mContext.getText(
+                        com.android.internal.R.string.cfTemplateRegistered);
+            }
+        }
+
+        // In the template (from strings.xmls)
+        //         {0} is one of "bearerServiceCode*"
+        //        {1} is dialing number
+        //      {2} is time in seconds
+
+        destinations[0] = serviceClassToCFString(info.serviceClass & serviceClassMask);
+        destinations[1] = PhoneNumberUtils.stringFromStringAndTOA(info.number, info.toa);
+        destinations[2] = Integer.toString(info.timeSeconds);
+
+        if (info.reason == CommandsInterface.CF_REASON_UNCONDITIONAL &&
+                (info.serviceClass & serviceClassMask)
+                        == CommandsInterface.SERVICE_CLASS_VOICE) {
+            boolean cffEnabled = (info.status == 1);
+            if (mIccRecords != null) {
+                mIccRecords.setVoiceCallForwardingFlag(1, cffEnabled, info.number);
+            }
+        }
+
+        return TextUtils.replace(template, sources, destinations);
+    }
+
+
+    private void
+    onQueryCfComplete(AsyncResult ar) {
+        StringBuilder sb = new StringBuilder(getScString());
+        sb.append("\n");
+
+        if (ar.exception != null) {
+            mState = State.FAILED;
+            sb.append(getErrorMessage(ar));
+        } else {
+            CallForwardInfo infos[];
+
+            infos = (CallForwardInfo[]) ar.result;
+
+            if (infos.length == 0) {
+                // Assume the default is not active
+                sb.append(mContext.getText(com.android.internal.R.string.serviceDisabled));
+
+                // Set unconditional CFF in SIM to false
+                if (mIccRecords != null) {
+                    mIccRecords.setVoiceCallForwardingFlag(1, false, null);
+                }
+            } else {
+
+                SpannableStringBuilder tb = new SpannableStringBuilder();
+
+                // Each bit in the service class gets its own result line
+                // The service classes may be split up over multiple
+                // CallForwardInfos. So, for each service class, find out
+                // which CallForwardInfo represents it and then build
+                // the response text based on that
+
+                for (int serviceClassMask = 1
+                            ; serviceClassMask <= SERVICE_CLASS_MAX
+                            ; serviceClassMask <<= 1
+                ) {
+                    for (int i = 0, s = infos.length; i < s ; i++) {
+                        if ((serviceClassMask & infos[i].serviceClass) != 0) {
+                            tb.append(makeCFQueryResultMessage(infos[i],
+                                            serviceClassMask));
+                            tb.append("\n");
+                        }
+                    }
+                }
+                sb.append(tb);
+            }
+
+            mState = State.COMPLETE;
+        }
+
+        mMessage = sb;
+        mPhone.onMMIDone(this);
+
+    }
+
+    private void
+    onQueryComplete(AsyncResult ar) {
+        StringBuilder sb = new StringBuilder(getScString());
+        sb.append("\n");
+
+        if (ar.exception != null) {
+            mState = State.FAILED;
+            sb.append(getErrorMessage(ar));
+        } else {
+            int[] ints = (int[])ar.result;
+
+            if (ints.length != 0) {
+                if (ints[0] == 0) {
+                    sb.append(mContext.getText(com.android.internal.R.string.serviceDisabled));
+                } else if (mSc.equals(SC_WAIT)) {
+                    // Call Waiting includes additional data in the response.
+                    sb.append(createQueryCallWaitingResultMessage(ints[1]));
+                } else if (isServiceCodeCallBarring(mSc)) {
+                    // ints[0] for Call Barring is a bit vector of services
+                    sb.append(createQueryCallBarringResultMessage(ints[0]));
+                } else if (ints[0] == 1) {
+                    // for all other services, treat it as a boolean
+                    sb.append(mContext.getText(com.android.internal.R.string.serviceEnabled));
+                } else {
+                    sb.append(mContext.getText(com.android.internal.R.string.mmiError));
+                }
+            } else {
+                sb.append(mContext.getText(com.android.internal.R.string.mmiError));
+            }
+            mState = State.COMPLETE;
+        }
+
+        mMessage = sb;
+        mPhone.onMMIDone(this);
+    }
+
+    private CharSequence
+    createQueryCallWaitingResultMessage(int serviceClass) {
+        StringBuilder sb = new StringBuilder(
+                        mContext.getText(com.android.internal.R.string.serviceEnabledFor));
+
+        for (int classMask = 1
+                    ; classMask <= SERVICE_CLASS_MAX
+                    ; classMask <<= 1
+        ) {
+            if ((classMask & serviceClass) != 0) {
+                sb.append("\n");
+                sb.append(serviceClassToCFString(classMask & serviceClass));
+            }
+        }
+        return sb;
+    }
+    private CharSequence
+    createQueryCallBarringResultMessage(int serviceClass)
+    {
+        StringBuilder sb = new StringBuilder(
+                mContext.getText(com.android.internal.R.string.serviceEnabledFor));
+
+        for (int classMask = 1
+                    ; classMask <= SERVICE_CLASS_MAX
+                    ; classMask <<= 1
+        ) {
+            if ((classMask & serviceClass) != 0) {
+                sb.append("\n");
+                sb.append(serviceClassToCFString(classMask & serviceClass));
+            }
+        }
+        return sb;
+    }
+
+    /***
+     * TODO: It would be nice to have a method here that can take in a dialstring and
+     * figure out if there is an MMI code embedded within it.  This code would replace
+     * some of the string parsing functionality in the Phone App's
+     * SpecialCharSequenceMgr class.
+     */
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder("ImsPhoneMmiCode {");
+
+        sb.append("State=" + getState());
+        if (mAction != null) sb.append(" action=" + mAction);
+        if (mSc != null) sb.append(" sc=" + mSc);
+        if (mSia != null) sb.append(" sia=" + mSia);
+        if (mSib != null) sb.append(" sib=" + mSib);
+        if (mSic != null) sb.append(" sic=" + mSic);
+        if (mPoundString != null) sb.append(" poundString=" + mPoundString);
+        if (mDialingNumber != null) sb.append(" dialingNumber=" + mDialingNumber);
+        if (mPwd != null) sb.append(" pwd=" + mPwd);
+        sb.append("}");
+        return sb.toString();
+    }
+}
diff --git a/src/java/com/android/internal/telephony/sip/SipCommandInterface.java b/src/java/com/android/internal/telephony/sip/SipCommandInterface.java
index 07945c8..6223e48 100644
--- a/src/java/com/android/internal/telephony/sip/SipCommandInterface.java
+++ b/src/java/com/android/internal/telephony/sip/SipCommandInterface.java
@@ -540,6 +540,10 @@
     }
 
     @Override
+    public void requestIccSimAuthentication(String data, Message response) {
+    }
+
+    @Override
     public void getVoiceRadioTechnology(Message result) {
     }
 
@@ -589,4 +593,8 @@
     @Override
     public void nvResetConfig(int resetType, Message response) {
     }
+
+    @Override
+    public void getHardwareConfig(Message result) {
+    }
 }
diff --git a/src/java/com/android/internal/telephony/sip/SipConnectionBase.java b/src/java/com/android/internal/telephony/sip/SipConnectionBase.java
index 42978c0..8d8a554 100644
--- a/src/java/com/android/internal/telephony/sip/SipConnectionBase.java
+++ b/src/java/com/android/internal/telephony/sip/SipConnectionBase.java
@@ -198,4 +198,24 @@
     public int getPreciseDisconnectCause() {
         return 0;
     }
+
+    @Override
+    public long getHoldingStartTime() {
+        return mHoldingStartTime;
+    }
+
+    @Override
+    public long getConnectTimeReal() {
+        return mConnectTimeReal;
+    }
+
+    @Override
+    public Connection getOrigConnection() {
+        return null;
+    }
+
+    @Override
+    public boolean isMultiparty() {
+        return false;
+    }
 }
diff --git a/src/java/com/android/internal/telephony/test/SimulatedCommands.java b/src/java/com/android/internal/telephony/test/SimulatedCommands.java
index 90914a0..250e507 100644
--- a/src/java/com/android/internal/telephony/test/SimulatedCommands.java
+++ b/src/java/com/android/internal/telephony/test/SimulatedCommands.java
@@ -1643,6 +1643,11 @@
     }
 
     @Override
+    public void requestIccSimAuthentication(String data, Message response) {
+        unimplemented(response);
+    }
+
+    @Override
     public void getVoiceRadioTechnology(Message response) {
         unimplemented(response);
     }
@@ -1720,4 +1725,9 @@
     public void nvResetConfig(int resetType, Message response) {
         unimplemented(response);
     }
+
+    @Override
+    public void getHardwareConfig(Message result) {
+        unimplemented(result);
+    }
 }
diff --git a/src/java/com/android/internal/telephony/uicc/IccCardApplicationStatus.java b/src/java/com/android/internal/telephony/uicc/IccCardApplicationStatus.java
index 6dfb4be..f1b0e43 100644
--- a/src/java/com/android/internal/telephony/uicc/IccCardApplicationStatus.java
+++ b/src/java/com/android/internal/telephony/uicc/IccCardApplicationStatus.java
@@ -27,6 +27,7 @@
  * {@hide}
  */
 public class IccCardApplicationStatus {
+    // TODO: Replace with constants from PhoneConstants.APPTYPE_xxx
     public enum AppType{
         APPTYPE_UNKNOWN,
         APPTYPE_SIM,
diff --git a/src/java/com/android/internal/telephony/uicc/IccCardProxy.java b/src/java/com/android/internal/telephony/uicc/IccCardProxy.java
index 06eda37..134b02f 100644
--- a/src/java/com/android/internal/telephony/uicc/IccCardProxy.java
+++ b/src/java/com/android/internal/telephony/uicc/IccCardProxy.java
@@ -29,17 +29,21 @@
 import android.os.UserHandle;
 import android.telephony.Rlog;
 import android.telephony.ServiceState;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 
 import com.android.internal.telephony.CommandsInterface;
 import com.android.internal.telephony.IccCard;
 import com.android.internal.telephony.IccCardConstants;
 import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.MccTable;
 import com.android.internal.telephony.RILConstants;
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.internal.telephony.IccCardConstants.State;
 import com.android.internal.telephony.cdma.CdmaSubscriptionSourceManager;
 import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.Subscription;
+import com.android.internal.telephony.SubscriptionController;
 import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppState;
 import com.android.internal.telephony.uicc.IccCardApplicationStatus.PersoSubState;
 import com.android.internal.telephony.uicc.IccCardStatus.CardState;
@@ -49,8 +53,12 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 
+import static com.android.internal.telephony.TelephonyProperties.PROPERTY_ICC_OPERATOR_ALPHA;
+import static com.android.internal.telephony.TelephonyProperties.PROPERTY_ICC_OPERATOR_NUMERIC;
+import static com.android.internal.telephony.TelephonyProperties.PROPERTY_ICC_OPERATOR_ISO_COUNTRY;
 import static com.android.internal.telephony.TelephonyProperties.PROPERTY_SIM_STATE;
 
+
 /**
  * @Deprecated use {@link UiccController}.getUiccCard instead.
  *
@@ -84,6 +92,14 @@
     private static final int EVENT_NETWORK_LOCKED = 9;
     private static final int EVENT_CDMA_SUBSCRIPTION_SOURCE_CHANGED = 11;
 
+    private static final int EVENT_ICC_RECORD_EVENTS = 500;
+    private static final int EVENT_SUBSCRIPTION_ACTIVATED = 501;
+    private static final int EVENT_SUBSCRIPTION_DEACTIVATED = 502;
+
+    // FIXME Rename mCardIndex to mSlotId.
+    private Integer mCardIndex = null;
+    private Subscription mSubscriptionData = null;
+
     private final Object mLock = new Object();
     private Context mContext;
     private CommandsInterface mCi;
@@ -117,6 +133,15 @@
         setExternalState(State.NOT_READY);
     }
 
+    public IccCardProxy(Context context, CommandsInterface ci, int cardIndex) {
+        this(context, ci);
+
+        mCardIndex = cardIndex;
+
+        resetProperties();
+        setExternalState(State.NOT_READY, false);
+    }
+
     public void dispose() {
         synchronized (mLock) {
             log("Disposing");
@@ -226,6 +251,33 @@
                 setExternalState(State.READY);
                 break;
             case EVENT_RECORDS_LOADED:
+                if (mIccRecords != null) {
+                    String operator = mIccRecords.getOperatorNumeric();
+                    int slotId = mCardIndex;
+
+                    log("operator = " + operator + " slotId = " + slotId);
+
+                    if (operator != null) {
+                        log("update icc_operator_numeric=" + operator);
+                        setSystemProperty(PROPERTY_ICC_OPERATOR_NUMERIC, slotId, operator);
+                        String countryCode = operator.substring(0,3);
+                        if (countryCode != null) {
+                            setSystemProperty(PROPERTY_ICC_OPERATOR_ISO_COUNTRY, slotId,
+                                    MccTable.countryCodeForMcc(Integer.parseInt(countryCode)));
+                        } else {
+                            loge("EVENT_RECORDS_LOADED Country code is null");
+                        }
+
+                        long[] subId = SubscriptionController.getInstance().getSubId(slotId);
+                        // Update MCC MNC device configuration information only for default sub.
+                        if (subId[0] == SubscriptionController.getInstance().getDefaultSubId()) {
+                            log("update mccmnc=" + operator + " config for default subscription.");
+                            MccTable.updateMccMncConfiguration(mContext, operator, false);
+                        }
+                    } else {
+                        loge("EVENT_RECORDS_LOADED Operator name is null");
+                    }
+                }
                 broadcastIccStateChangedIntent(IccCardConstants.INTENT_VALUE_ICC_LOADED, null);
                 break;
             case EVENT_IMSI_READY:
@@ -238,15 +290,52 @@
             case EVENT_CDMA_SUBSCRIPTION_SOURCE_CHANGED:
                 updateQuietMode();
                 break;
+            case EVENT_SUBSCRIPTION_ACTIVATED:
+                log("EVENT_SUBSCRIPTION_ACTIVATED");
+                onSubscriptionActivated();
+                break;
+
+            case EVENT_SUBSCRIPTION_DEACTIVATED:
+                log("EVENT_SUBSCRIPTION_DEACTIVATED");
+                onSubscriptionDeactivated();
+                break;
+
+            case EVENT_ICC_RECORD_EVENTS:
+                if ((mCurrentAppType == UiccController.APP_FAM_3GPP) && (mIccRecords != null)) {
+                    int slotId = mCardIndex;
+                    AsyncResult ar = (AsyncResult)msg.obj;
+                    int eventCode = (Integer) ar.result;
+                    if (eventCode == SIMRecords.EVENT_SPN) {
+                        setSystemProperty(PROPERTY_ICC_OPERATOR_ALPHA, slotId,
+                                mIccRecords.getServiceProviderName());
+                    }
+                }
+                break;
+
             default:
                 loge("Unhandled message with number: " + msg.what);
                 break;
         }
     }
 
+    private void onSubscriptionActivated() {
+        //mSubscriptionData = SubscriptionManager.getCurrentSubscription(mCardIndex);
+
+        updateIccAvailability();
+        updateStateProperty();
+    }
+
+    private void onSubscriptionDeactivated() {
+        resetProperties();
+        mSubscriptionData = null;
+        updateIccAvailability();
+        updateStateProperty();
+    }
+
+
     private void updateIccAvailability() {
         synchronized (mLock) {
-            UiccCard newCard = mUiccController.getUiccCard();
+            UiccCard newCard = mUiccController.getUiccCard(mCardIndex);
             CardState state = CardState.CARDSTATE_ABSENT;
             UiccCardApplication newApp = null;
             IccRecords newRecords = null;
@@ -271,6 +360,20 @@
         }
     }
 
+    void resetProperties() {
+        if (mCurrentAppType == UiccController.APP_FAM_3GPP) {
+            log("update icc_operator_numeric=" + "");
+            setSystemProperty(PROPERTY_ICC_OPERATOR_NUMERIC, mCardIndex, "");
+            setSystemProperty(PROPERTY_ICC_OPERATOR_ISO_COUNTRY, mCardIndex, "");
+            setSystemProperty(PROPERTY_ICC_OPERATOR_ALPHA, mCardIndex, "");
+         }
+    }
+
+    private void HandleDetectedState() {
+    // CAF_MSIM SAND
+//        setExternalState(State.DETECTED, false);
+    }
+
     private void updateExternalState() {
         if (mUiccCard == null || mUiccCard.getCardState() == CardState.CARDSTATE_ABSENT) {
             if (mRadioOn) {
@@ -288,14 +391,15 @@
 
         if (mUiccApplication == null) {
             setExternalState(State.NOT_READY);
-            return;
         }
 
         switch (mUiccApplication.getState()) {
             case APPSTATE_UNKNOWN:
-            case APPSTATE_DETECTED:
                 setExternalState(State.UNKNOWN);
                 break;
+            case APPSTATE_DETECTED:
+                HandleDetectedState();
+                break;
             case APPSTATE_PIN:
                 setExternalState(State.PIN_REQUIRED);
                 break;
@@ -303,7 +407,8 @@
                 setExternalState(State.PUK_REQUIRED);
                 break;
             case APPSTATE_SUBSCRIPTION_PERSO:
-                if (mUiccApplication.getPersoSubState() == PersoSubState.PERSOSUBSTATE_SIM_NETWORK) {
+                if (mUiccApplication.getPersoSubState() ==
+                        PersoSubState.PERSOSUBSTATE_SIM_NETWORK) {
                     setExternalState(State.NETWORK_LOCKED);
                 } else {
                     setExternalState(State.UNKNOWN);
@@ -326,6 +431,10 @@
             mIccRecords.registerForImsiReady(this, EVENT_IMSI_READY, null);
             mIccRecords.registerForRecordsLoaded(this, EVENT_RECORDS_LOADED, null);
         }
+
+        if (mIccRecords != null) {
+            mIccRecords.registerForRecordsEvents(this, EVENT_ICC_RECORD_EVENTS, null);
+        }
     }
 
     private void unregisterUiccCardEvents() {
@@ -335,10 +444,20 @@
         if (mUiccApplication != null) mUiccApplication.unregisterForNetworkLocked(this);
         if (mIccRecords != null) mIccRecords.unregisterForImsiReady(this);
         if (mIccRecords != null) mIccRecords.unregisterForRecordsLoaded(this);
+        if (mIccRecords != null) mIccRecords.unregisterForRecordsEvents(this);
+    }
+
+    private void updateStateProperty() {
+        setSystemProperty(PROPERTY_SIM_STATE, mCardIndex,getState().toString());
     }
 
     private void broadcastIccStateChangedIntent(String value, String reason) {
         synchronized (mLock) {
+            if (mCardIndex == null) {
+                loge("broadcastIccStateChangedIntent: Card Index is not set; Return!!");
+                return;
+            }
+
             if (mQuietMode) {
                 log("QuietMode: NOT Broadcasting intent ACTION_SIM_STATE_CHANGED " +  value
                         + " reason " + reason);
@@ -350,14 +469,35 @@
             intent.putExtra(PhoneConstants.PHONE_NAME_KEY, "Phone");
             intent.putExtra(IccCardConstants.INTENT_KEY_ICC_STATE, value);
             intent.putExtra(IccCardConstants.INTENT_KEY_LOCKED_REASON, reason);
-
-            if (DBG) log("Broadcasting intent ACTION_SIM_STATE_CHANGED " +  value
-                    + " reason " + reason);
+            SubscriptionManager.putPhoneIdAndSubIdExtra(intent, mCardIndex);
+            log("Broadcasting intent ACTION_SIM_STATE_CHANGED " +  value
+                + " reason " + reason + " for mCardIndex : " + mCardIndex);
             ActivityManagerNative.broadcastStickyIntent(intent, READ_PHONE_STATE,
                     UserHandle.USER_ALL);
         }
     }
 
+    private void setExternalState(State newState, boolean override) {
+        synchronized (mLock) {
+            if (mCardIndex == null) {
+                loge("setExternalState: Card Index is not set; Return!!");
+                return;
+            }
+
+            if (!override && newState == mExternalState) {
+                return;
+            }
+            mExternalState = newState;
+            setSystemProperty(PROPERTY_SIM_STATE, mCardIndex, getState().toString());
+            broadcastIccStateChangedIntent(getIccStateIntentString(mExternalState),
+                    getIccStateReason(mExternalState));
+            // TODO: Need to notify registrants for other states as well.
+            if ( State.ABSENT == mExternalState) {
+                mAbsentRegistrants.notifyRegistrants();
+            }
+        }
+    }
+
     private void processLockedState() {
         synchronized (mLock) {
             if (mUiccApplication == null) {
@@ -389,18 +529,6 @@
         }
     }
 
-    private void setExternalState(State newState, boolean override) {
-        synchronized (mLock) {
-            if (!override && newState == mExternalState) {
-                return;
-            }
-            mExternalState = newState;
-            SystemProperties.set(PROPERTY_SIM_STATE, mExternalState.toString());
-            broadcastIccStateChangedIntent(getIccStateIntentString(mExternalState),
-                    getIccStateReason(mExternalState));
-        }
-    }
-
     private void setExternalState(State newState) {
         setExternalState(newState, false);
     }
@@ -423,7 +551,6 @@
             case READY: return IccCardConstants.INTENT_VALUE_ICC_READY;
             case NOT_READY: return IccCardConstants.INTENT_VALUE_ICC_NOT_READY;
             case PERM_DISABLED: return IccCardConstants.INTENT_VALUE_ICC_LOCKED;
-            case CARD_IO_ERROR: return IccCardConstants.INTENT_VALUE_ICC_CARD_IO_ERROR;
             default: return IccCardConstants.INTENT_VALUE_ICC_UNKNOWN;
         }
     }
@@ -727,6 +854,11 @@
         }
     }
 
+    private void setSystemProperty(String property, int slotId, String value) {
+        long[] subId = SubscriptionController.getInstance().getSubId(slotId);
+        TelephonyManager.setTelephonyProperty(property, subId[0], value);
+    }
+
     private void log(String s) {
         Rlog.d(LOG_TAG, s);
     }
diff --git a/src/java/com/android/internal/telephony/uicc/IccConstants.java b/src/java/com/android/internal/telephony/uicc/IccConstants.java
index ae1c442..f367f29 100644
--- a/src/java/com/android/internal/telephony/uicc/IccConstants.java
+++ b/src/java/com/android/internal/telephony/uicc/IccConstants.java
@@ -76,6 +76,7 @@
     static final int EF_DOMAIN = 0x6f03;
     static final int EF_IST = 0x6f07;
     static final int EF_PCSCF = 0x6f09;
+    static final int EF_PSI = 0x6fe5;
 
     // SMS record length from TS 51.011 10.5.3
     static public final int SMS_RECORD_LENGTH = 176;
diff --git a/src/java/com/android/internal/telephony/uicc/IccFileHandler.java b/src/java/com/android/internal/telephony/uicc/IccFileHandler.java
index dc42f70..a460819 100644
--- a/src/java/com/android/internal/telephony/uicc/IccFileHandler.java
+++ b/src/java/com/android/internal/telephony/uicc/IccFileHandler.java
@@ -518,6 +518,7 @@
         case EF_EXT1:
         case EF_EXT2:
         case EF_EXT3:
+        case EF_PSI:
             return MF_SIM + DF_TELECOM;
 
         case EF_ICCID:
diff --git a/src/java/com/android/internal/telephony/uicc/IccRecords.java b/src/java/com/android/internal/telephony/uicc/IccRecords.java
index ec920f9..82d8082 100644
--- a/src/java/com/android/internal/telephony/uicc/IccRecords.java
+++ b/src/java/com/android/internal/telephony/uicc/IccRecords.java
@@ -67,6 +67,7 @@
     protected boolean mIsVoiceMailFixed = false;
     protected int mCountVoiceMessages = 0;
     protected String mImsi;
+    private String auth_rsp;
 
     protected int mMncLength = UNINITIALIZED;
     protected int mMailboxIndex = 0; // 0 is no mailbox dailing number associated
@@ -75,6 +76,8 @@
 
     protected String mGid1;
 
+    private final Object mLock = new Object();
+
     // ***** Constants
 
     // Markers for mncLength
@@ -93,6 +96,7 @@
 
     public static final int EVENT_GET_ICC_RECORD_DONE = 100;
     protected static final int EVENT_APP_READY = 1;
+    private static final int EVENT_AKA_AUTHENTICATE_DONE          = 90;
 
     @Override
     public String toString() {
@@ -391,10 +395,12 @@
     //***** Overridden from Handler
     @Override
     public void handleMessage(Message msg) {
+        AsyncResult ar;
+
         switch (msg.what) {
             case EVENT_GET_ICC_RECORD_DONE:
                 try {
-                    AsyncResult ar = (AsyncResult) msg.obj;
+                    ar = (AsyncResult) msg.obj;
                     IccRecordLoaded recordLoaded = (IccRecordLoaded) ar.userObj;
                     if (DBG) log(recordLoaded.getEfName() + " LOADED");
 
@@ -412,6 +418,27 @@
                 }
                 break;
 
+            case EVENT_AKA_AUTHENTICATE_DONE:
+                ar = (AsyncResult)msg.obj;
+                auth_rsp = null;
+                if (DBG) log("EVENT_AKA_AUTHENTICATE_DONE");
+                if (ar.exception != null) {
+                    loge("Exception ICC SIM AKA: " + ar.exception);
+                    break;
+                } else {
+                    try {
+                        auth_rsp = (String)ar.result;
+                        if (DBG) log("ICC SIM AKA: auth_rsp = " + auth_rsp);
+                    } catch (Exception e) {
+                        loge("Failed to parse ICC SIM AKA contents: " + e);
+                    }
+                }
+                synchronized (mLock) {
+                    mLock.notifyAll();
+                }
+
+                break;
+
             default:
                 super.handleMessage(msg);
         }
@@ -505,6 +532,41 @@
         return null;
     }
 
+    /**
+     * Returns the response of the SIM application on the UICC to authentication
+     * challenge/response algorithm. The data string and challenge response are
+     * Base64 encoded Strings.
+     * Can support EAP-SIM, EAP-AKA with results encoded per 3GPP TS 31.102.
+     *
+     * @param data authentication challenge data
+     * @return challenge response
+     */
+    public String getIccSimChallengeResponse(String data) {
+        if (DBG) log("getIccSimChallengeResponse-data: (original) " + data);
+
+        data = data + mParentApp.getAid();
+
+        if (DBG) log("getIccSimChallengeResponse-data: (with AID) " + data);
+
+        try {
+            synchronized(mLock) {
+                mCi.requestIccSimAuthentication(data, obtainMessage(EVENT_AKA_AUTHENTICATE_DONE));
+                try {
+                    mLock.wait();
+                } catch (InterruptedException e) {
+                    loge("interrupted while trying to request Icc Sim Auth");
+                }
+            }
+        } catch(Exception e) {
+            loge( "Fail while trying to request Icc Sim Auth");
+            return null;
+        }
+
+        if (DBG) log("getIccSimChallengeResponse-auth_rsp" + auth_rsp);
+
+        return auth_rsp;
+    }
+
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("IccRecords: " + this);
         pw.println(" mDestroyed=" + mDestroyed);
diff --git a/src/java/com/android/internal/telephony/uicc/IsimFileHandler.java b/src/java/com/android/internal/telephony/uicc/IsimFileHandler.java
index 07c5ec8..6fe16c9 100644
--- a/src/java/com/android/internal/telephony/uicc/IsimFileHandler.java
+++ b/src/java/com/android/internal/telephony/uicc/IsimFileHandler.java
@@ -37,6 +37,8 @@
         case EF_IMPI:
         case EF_IMPU:
         case EF_DOMAIN:
+        case EF_IST:
+        case EF_PCSCF:
             return MF_SIM + DF_ADF;
         }
         String path = getCommonIccEFPath(efid);
diff --git a/src/java/com/android/internal/telephony/uicc/IsimRecords.java b/src/java/com/android/internal/telephony/uicc/IsimRecords.java
index 25376c1..b176d4e 100644
--- a/src/java/com/android/internal/telephony/uicc/IsimRecords.java
+++ b/src/java/com/android/internal/telephony/uicc/IsimRecords.java
@@ -41,4 +41,24 @@
      * @return an array of IMS public user identity strings, or null if not available
      */
     String[] getIsimImpu();
+
+    /**
+     * Returns the IMS Service Table (IST) that was loaded from the ISIM.
+     * @return IMS Service Table or null if not present or not loaded
+     */
+    String getIsimIst();
+
+    /**
+     * Returns the IMS Proxy Call Session Control Function(PCSCF) that were loaded from the ISIM.
+     * @return an array of  PCSCF strings with one PCSCF per string, or null if
+     *      not present or not loaded
+     */
+    String[] getIsimPcscf();
+
+    /**
+     * Returns the response of ISIM Authetification through RIL.
+     * Returns null if the Authentification hasn't been successed or isn't present iphonesubinfo.
+     * @return the response of ISIM Authetification, or null if not available
+     */
+    String getIsimChallengeResponse(String nonce);
 }
diff --git a/src/java/com/android/internal/telephony/uicc/IsimUiccRecords.java b/src/java/com/android/internal/telephony/uicc/IsimUiccRecords.java
index 8e25132..96d19bc 100644
--- a/src/java/com/android/internal/telephony/uicc/IsimUiccRecords.java
+++ b/src/java/com/android/internal/telephony/uicc/IsimUiccRecords.java
@@ -21,6 +21,8 @@
 import android.os.Handler;
 import android.os.Message;
 import android.telephony.Rlog;
+import android.content.Intent;
+
 
 import com.android.internal.telephony.CommandsInterface;
 import com.android.internal.telephony.gsm.SimTlv;
@@ -35,6 +37,8 @@
 import static com.android.internal.telephony.uicc.IccConstants.EF_DOMAIN;
 import static com.android.internal.telephony.uicc.IccConstants.EF_IMPI;
 import static com.android.internal.telephony.uicc.IccConstants.EF_IMPU;
+import static com.android.internal.telephony.uicc.IccConstants.EF_IST;
+import static com.android.internal.telephony.uicc.IccConstants.EF_PCSCF;
 
 /**
  * {@hide}
@@ -43,14 +47,22 @@
     protected static final String LOG_TAG = "IsimUiccRecords";
 
     private static final boolean DBG = true;
-    private static final boolean DUMP_RECORDS = false;   // Note: PII is logged when this is true
+    private static final boolean DUMP_RECORDS = true;   // Note: PII is logged when this is true
+    public static final String INTENT_ISIM_REFRESH = "com.android.intent.isim_refresh";
 
     private static final int EVENT_APP_READY = 1;
+    private static final int EVENT_ISIM_REFRESH = 31;
+    private static final int EVENT_AKA_AUTHENTICATE_DONE          = 90;
 
     // ISIM EF records (see 3GPP TS 31.103)
     private String mIsimImpi;               // IMS private user identity
     private String mIsimDomain;             // IMS home network domain name
     private String[] mIsimImpu;             // IMS public user identity(s)
+    private String mIsimIst;             // IMS Service Table
+    private String[] mIsimPcscf;             // IMS Proxy Call Session Control Function
+    private String auth_rsp;
+
+    private final Object mLock = new Object();
 
     private static final int TAG_ISIM_VALUE = 0x80;     // From 3GPP TS 31.103
 
@@ -59,7 +71,9 @@
         return "IsimUiccRecords: " + super.toString()
                 + " mIsimImpi=" + mIsimImpi
                 + " mIsimDomain=" + mIsimDomain
-                + " mIsimImpu=" + mIsimImpu;
+                + " mIsimImpu=" + mIsimImpu
+                + " mIsimIst=" + mIsimIst
+                + " mIsimPcscf=" + mIsimPcscf;
     }
 
     public IsimUiccRecords(UiccCardApplication app, Context c, CommandsInterface ci) {
@@ -69,6 +83,9 @@
 
         // recordsToLoad is set to 0 because no requests are made yet
         mRecordsToLoad = 0;
+        // Start off by setting empty state
+        resetRecords();
+        mCi.registerForIccRefresh(this, EVENT_ISIM_REFRESH, null);
 
         mParentApp.registerForReady(this, EVENT_APP_READY, null);
         if (DBG) log("IsimUiccRecords X ctor this=" + this);
@@ -78,6 +95,7 @@
     public void dispose() {
         log("Disposing " + this);
         //Unregister for all events
+        mCi.unregisterForIccRefresh(this);
         mParentApp.unregisterForReady(this);
         resetRecords();
         super.dispose();
@@ -85,11 +103,14 @@
 
     // ***** Overridden from Handler
     public void handleMessage(Message msg) {
+        AsyncResult ar;
+
         if (mDestroyed.get()) {
             Rlog.e(LOG_TAG, "Received message " + msg +
                     "[" + msg.what + "] while being destroyed. Ignoring.");
             return;
         }
+        loge("IsimUiccRecords: handleMessage " + msg + "[" + msg.what + "] ");
 
         try {
             switch (msg.what) {
@@ -97,6 +118,37 @@
                     onReady();
                     break;
 
+                case EVENT_ISIM_REFRESH:
+                    ar = (AsyncResult)msg.obj;
+                    loge("ISim REFRESH(EVENT_ISIM_REFRESH) with exception: " + ar.exception);
+                    if (ar.exception == null) {
+                        Intent intent = new Intent(INTENT_ISIM_REFRESH);
+                        loge("send ISim REFRESH: " + INTENT_ISIM_REFRESH);
+                        mContext.sendBroadcast(intent);
+                        handleIsimRefresh((IccRefreshResponse)ar.result);
+                    }
+                    break;
+
+                case EVENT_AKA_AUTHENTICATE_DONE:
+                    ar = (AsyncResult)msg.obj;
+                    log("EVENT_AKA_AUTHENTICATE_DONE");
+                    if (ar.exception != null) {
+                        log("Exception ISIM AKA: " + ar.exception);
+                        break;
+                    } else {
+                        try {
+                            auth_rsp = (String)ar.result;
+                            log("ISIM AKA: auth_rsp = " + auth_rsp);
+                        } catch (Exception e) {
+                            log("Failed to parse ISIM AKA contents: " + e);
+                        }
+                    }
+                    synchronized (mLock) {
+                        mLock.notifyAll();
+                    }
+
+                    break;
+
                 default:
                     super.handleMessage(msg);   // IccRecords handles generic record load responses
 
@@ -121,14 +173,27 @@
         mFh.loadEFTransparent(EF_DOMAIN, obtainMessage(
                 IccRecords.EVENT_GET_ICC_RECORD_DONE, new EfIsimDomainLoaded()));
         mRecordsToLoad++;
+        mFh.loadEFTransparent(EF_IST, obtainMessage(
+                    IccRecords.EVENT_GET_ICC_RECORD_DONE, new EfIsimIstLoaded()));
+        mRecordsToLoad++;
+        mFh.loadEFLinearFixedAll(EF_PCSCF, obtainMessage(
+                    IccRecords.EVENT_GET_ICC_RECORD_DONE, new EfIsimPcscfLoaded()));
+        mRecordsToLoad++;
 
-        log("fetchIsimRecords " + mRecordsToLoad);
+        if (DBG) log("fetchIsimRecords " + mRecordsToLoad + " requested: " + mRecordsRequested);
     }
 
     protected void resetRecords() {
         // recordsRequested is set to false indicating that the SIM
         // read requests made so far are not valid. This is set to
         // true only when fresh set of read requests are made.
+        mIsimImpi = null;
+        mIsimDomain = null;
+        mIsimImpu = null;
+        mIsimIst = null;
+        mIsimPcscf = null;
+        auth_rsp = null;
+
         mRecordsRequested = false;
     }
 
@@ -171,6 +236,33 @@
         }
     }
 
+    private class EfIsimIstLoaded implements IccRecords.IccRecordLoaded {
+        public String getEfName() {
+            return "EF_ISIM_IST";
+        }
+        public void onRecordLoaded(AsyncResult ar) {
+            byte[] data = (byte[]) ar.result;
+            mIsimIst = IccUtils.bytesToHexString(data);
+            if (DUMP_RECORDS) log("EF_IST=" + mIsimIst);
+        }
+    }
+    private class EfIsimPcscfLoaded implements IccRecords.IccRecordLoaded {
+        public String getEfName() {
+            return "EF_ISIM_PCSCF";
+        }
+        public void onRecordLoaded(AsyncResult ar) {
+            ArrayList<byte[]> pcscflist = (ArrayList<byte[]>) ar.result;
+            if (DBG) log("EF_PCSCF record count: " + pcscflist.size());
+            mIsimPcscf = new String[pcscflist.size()];
+            int i = 0;
+            for (byte[] identity : pcscflist) {
+                String pcscf = isimTlvToString(identity);
+                if (DUMP_RECORDS) log("EF_PCSCF[" + i + "]=" + pcscf);
+                mIsimPcscf[i++] = pcscf;
+            }
+        }
+    }
+
     /**
      * ISIM records for IMS are stored inside a Tag-Length-Value record as a UTF-8 string
      * with tag value 0x80.
@@ -194,6 +286,7 @@
         // One record loaded successfully or failed, In either case
         // we need to update the recordsToLoad count
         mRecordsToLoad -= 1;
+        if (DBG) log("onRecordLoaded " + mRecordsToLoad + " requested: " + mRecordsRequested);
 
         if (mRecordsToLoad == 0 && mRecordsRequested == true) {
             onAllRecordsLoaded();
@@ -205,10 +298,94 @@
 
     @Override
     protected void onAllRecordsLoaded() {
+       if (DBG) log("record load complete");
         mRecordsLoadedRegistrants.notifyRegistrants(
                 new AsyncResult(null, null, null));
     }
 
+    private void handleFileUpdate(int efid) {
+        switch (efid) {
+            case EF_IMPI:
+                mFh.loadEFTransparent(EF_IMPI, obtainMessage(
+                            IccRecords.EVENT_GET_ICC_RECORD_DONE, new EfIsimImpiLoaded()));
+                mRecordsToLoad++;
+                break;
+
+            case EF_IMPU:
+                mFh.loadEFLinearFixedAll(EF_IMPU, obtainMessage(
+                            IccRecords.EVENT_GET_ICC_RECORD_DONE, new EfIsimImpuLoaded()));
+                mRecordsToLoad++;
+            break;
+
+            case EF_DOMAIN:
+                mFh.loadEFTransparent(EF_DOMAIN, obtainMessage(
+                            IccRecords.EVENT_GET_ICC_RECORD_DONE, new EfIsimDomainLoaded()));
+                mRecordsToLoad++;
+            break;
+
+            case EF_IST:
+                mFh.loadEFTransparent(EF_IST, obtainMessage(
+                            IccRecords.EVENT_GET_ICC_RECORD_DONE, new EfIsimIstLoaded()));
+                mRecordsToLoad++;
+            break;
+
+            case EF_PCSCF:
+                mFh.loadEFLinearFixedAll(EF_PCSCF, obtainMessage(
+                            IccRecords.EVENT_GET_ICC_RECORD_DONE, new EfIsimPcscfLoaded()));
+                mRecordsToLoad++;
+
+            default:
+                fetchIsimRecords();
+                break;
+        }
+    }
+
+    private void handleIsimRefresh(IccRefreshResponse refreshResponse) {
+        if (refreshResponse == null) {
+            if (DBG) log("handleIsimRefresh received without input");
+            return;
+        }
+
+        if (refreshResponse.aid != null &&
+                !refreshResponse.aid.equals(mParentApp.getAid())) {
+            // This is for different app. Ignore.
+            if (DBG) log("handleIsimRefresh received different app");
+            return;
+        }
+
+        switch (refreshResponse.refreshResult) {
+            case IccRefreshResponse.REFRESH_RESULT_FILE_UPDATE:
+                if (DBG) log("handleIsimRefresh with REFRESH_RESULT_FILE_UPDATE");
+                handleFileUpdate(refreshResponse.efId);
+                break;
+
+            case IccRefreshResponse.REFRESH_RESULT_INIT:
+                if (DBG) log("handleIsimRefresh with REFRESH_RESULT_INIT");
+                // need to reload all files (that we care about)
+                // onIccRefreshInit();
+                fetchIsimRecords();
+                break;
+
+            case IccRefreshResponse.REFRESH_RESULT_RESET:
+                if (DBG) log("handleIsimRefresh with REFRESH_RESULT_RESET");
+                // need to reload all files (that we care about)
+                mCi.setRadioPower(false, null);
+                /* Note: no need to call setRadioPower(true).  Assuming the desired
+                * radio power state is still ON (as tracked by ServiceStateTracker),
+                * ServiceStateTracker will call setRadioPower when it receives the
+                * RADIO_STATE_CHANGED notification for the power off.  And if the
+                * desired power state has changed in the interim, we don't want to
+                * override it with an unconditional power on.
+                */
+                break;
+
+            default:
+                // unknown refresh operation
+                if (DBG) log("handleIsimRefresh with unknown operation");
+                break;
+        }
+    }
+
     /**
      * Return the IMS private user identity (IMPI).
      * Returns null if the IMPI hasn't been loaded or isn't present on the ISIM.
@@ -239,6 +416,52 @@
         return (mIsimImpu != null) ? mIsimImpu.clone() : null;
     }
 
+    /**
+     * Returns the IMS Service Table (IST) that was loaded from the ISIM.
+     * @return IMS Service Table or null if not present or not loaded
+     */
+    @Override
+    public String getIsimIst() {
+        return mIsimIst;
+    }
+
+    /**
+     * Returns the IMS Proxy Call Session Control Function(PCSCF) that were loaded from the ISIM.
+     * @return an array of  PCSCF strings with one PCSCF per string, or null if
+     *      not present or not loaded
+     */
+    @Override
+    public String[] getIsimPcscf() {
+        return (mIsimPcscf != null) ? mIsimPcscf.clone() : null;
+    }
+
+    /**
+     * Returns the response of ISIM Authetification through RIL.
+     * Returns null if the Authentification hasn't been successed or isn't present iphonesubinfo.
+     * @return the response of ISIM Authetification, or null if not available
+     */
+    @Override
+    public String getIsimChallengeResponse(String nonce){
+        if (DBG) log("getIsimChallengeResponse-nonce:"+nonce);
+        try {
+            synchronized(mLock) {
+                mCi.requestIsimAuthentication(nonce,obtainMessage(EVENT_AKA_AUTHENTICATE_DONE));
+                try {
+                    mLock.wait();
+                } catch (InterruptedException e) {
+                    log("interrupted while trying to request Isim Auth");
+                }
+            }
+        } catch(Exception e) {
+            if (DBG) log( "Fail while trying to request Isim Auth");
+            return null;
+        }
+
+        if (DBG) log("getIsimChallengeResponse-auth_rsp"+auth_rsp);
+
+        return auth_rsp;
+    }
+
     @Override
     public int getDisplayRule(String plmn) {
         // Not applicable to Isim
@@ -252,7 +475,12 @@
 
     @Override
     public void onRefresh(boolean fileChanged, int[] fileList) {
-        // We do not handle it in Isim
+        if (fileChanged) {
+            // A future optimization would be to inspect fileList and
+            // only reload those files that we care about.  For now,
+            // just re-fetch all SIM records that we cache.
+            fetchIsimRecords();
+        }
     }
 
     @Override
@@ -284,6 +512,8 @@
         pw.println(" mIsimImpi=" + mIsimImpi);
         pw.println(" mIsimDomain=" + mIsimDomain);
         pw.println(" mIsimImpu[]=" + Arrays.toString(mIsimImpu));
+        pw.println(" mIsimIst" + mIsimIst);
+        pw.println(" mIsimPcscf"+mIsimPcscf);
         pw.flush();
     }
 }
diff --git a/src/java/com/android/internal/telephony/uicc/RuimRecords.java b/src/java/com/android/internal/telephony/uicc/RuimRecords.java
index 49edced..ff2e651 100755
--- a/src/java/com/android/internal/telephony/uicc/RuimRecords.java
+++ b/src/java/com/android/internal/telephony/uicc/RuimRecords.java
@@ -32,6 +32,7 @@
 import android.os.AsyncResult;
 import android.os.Message;
 import android.os.SystemProperties;
+import android.telephony.TelephonyManager;
 import android.telephony.Rlog;
 import android.text.TextUtils;
 
@@ -135,6 +136,7 @@
     protected void resetRecords() {
         mCountVoiceMessages = 0;
         mMncLength = UNINITIALIZED;
+        log("setting0 mMncLength" + mMncLength);
         mIccId = null;
 
         mAdnCache.reset();
@@ -487,9 +489,13 @@
                     String operatorNumeric = getRUIMOperatorNumeric();
                     if (operatorNumeric != null) {
                         if (operatorNumeric.length() <= 6) {
+                            log("update mccmnc=" + operatorNumeric);
                             MccTable.updateMccMncConfiguration(mContext, operatorNumeric, false);
                         }
                     }
+                } else {
+                    String operatorNumeric = getRUIMOperatorNumeric();
+                    log("NO update mccmnc=" + operatorNumeric);
                 }
 
             break;
@@ -642,6 +648,7 @@
             if (!TextUtils.isEmpty(operator)) {
                 log("onAllRecordsLoaded set 'gsm.sim.operator.numeric' to operator='" +
                         operator + "'");
+                log("update icc_operator_numeric=" + operator);
                 SystemProperties.set(PROPERTY_ICC_OPERATOR_NUMERIC, operator);
             } else {
                 log("onAllRecordsLoaded empty 'gsm.sim.operator.numeric' skipping");
@@ -857,4 +864,12 @@
         pw.println(" mHomeNetworkId=" + mHomeNetworkId);
         pw.flush();
     }
+
+    private void setSystemProperty(String key, String val) {
+        // Update the system properties only in case NON-DSDS.
+        // TODO: Shall have a better approach!
+        if (!TelephonyManager.getDefault().isMultiSimEnabled()) {
+            SystemProperties.set(key, val);
+        }
+    }
 }
diff --git a/src/java/com/android/internal/telephony/uicc/SIMRecords.java b/src/java/com/android/internal/telephony/uicc/SIMRecords.java
index b1e744e..dbf573b 100644
--- a/src/java/com/android/internal/telephony/uicc/SIMRecords.java
+++ b/src/java/com/android/internal/telephony/uicc/SIMRecords.java
@@ -23,6 +23,7 @@
 import android.os.AsyncResult;
 import android.os.Message;
 import android.os.SystemProperties;
+import android.telephony.TelephonyManager;
 import android.telephony.PhoneNumberUtils;
 import android.telephony.SmsMessage;
 import android.text.TextUtils;
@@ -227,6 +228,7 @@
         mVoiceMailNum = null;
         mCountVoiceMessages = 0;
         mMncLength = UNINITIALIZED;
+        log("setting0 mMncLength" + mMncLength);
         mIccId = null;
         // -1 means no EF_SPN found; treat accordingly.
         mSpnDisplayCondition = -1;
@@ -239,9 +241,10 @@
         mAdnCache.reset();
 
         log("SIMRecords: onRadioOffOrNotAvailable set 'gsm.sim.operator.numeric' to operator=null");
-        SystemProperties.set(PROPERTY_ICC_OPERATOR_NUMERIC, null);
-        SystemProperties.set(PROPERTY_ICC_OPERATOR_ALPHA, null);
-        SystemProperties.set(PROPERTY_ICC_OPERATOR_ISO_COUNTRY, null);
+        log("update icc_operator_numeric=" + null);
+        setSystemProperty(PROPERTY_ICC_OPERATOR_NUMERIC, null);
+        setSystemProperty(PROPERTY_ICC_OPERATOR_ALPHA, null);
+        setSystemProperty(PROPERTY_ICC_OPERATOR_ISO_COUNTRY, null);
 
         // recordsRequested is set to false indicating that the SIM
         // read requests made so far are not valid. This is set to
@@ -598,7 +601,8 @@
                     mImsi = null;
                 }
 
-                log("IMSI: " + /* imsi.substring(0, 6) +*/ "xxxxxxx");
+                log("IMSI: mMncLength=" + mMncLength);
+                log("IMSI: " + mImsi.substring(0, 6) + "xxxxxxx");
 
                 if (((mMncLength == UNKNOWN) || (mMncLength == 2)) &&
                         ((mImsi != null) && (mImsi.length() >= 6))) {
@@ -606,6 +610,7 @@
                     for (String mccmnc : MCCMNC_CODES_HAVING_3DIGITS_MNC) {
                         if (mccmnc.equals(mccmncCode)) {
                             mMncLength = 3;
+                            log("IMSI: setting1 mMncLength=" + mMncLength);
                             break;
                         }
                     }
@@ -617,13 +622,15 @@
                     try {
                         int mcc = Integer.parseInt(mImsi.substring(0,3));
                         mMncLength = MccTable.smallestDigitsMccForMnc(mcc);
+                        log("setting2 mMncLength=" + mMncLength);
                     } catch (NumberFormatException e) {
                         mMncLength = UNKNOWN;
-                        loge("Corrupt IMSI!");
+                        loge("Corrupt IMSI! setting3 mMncLength=" + mMncLength);
                     }
                 }
 
                 if (mMncLength != UNKNOWN && mMncLength != UNINITIALIZED) {
+                    log("update mccmnc=" + mImsi.substring(0, 3 + mMncLength));
                     // finally have both the imsi and the mncLength and can parse the imsi properly
                     MccTable.updateMccMncConfiguration(mContext,
                             mImsi.substring(0, 3 + mMncLength), false);
@@ -854,17 +861,21 @@
                     }
 
                     mMncLength = data[3] & 0xf;
+                    log("setting4 mMncLength=" + mMncLength);
 
                     if (mMncLength == 0xf) {
                         mMncLength = UNKNOWN;
+                        log("setting5 mMncLength=" + mMncLength);
                     }
                 } finally {
                     if (((mMncLength == UNINITIALIZED) || (mMncLength == UNKNOWN) ||
                             (mMncLength == 2)) && ((mImsi != null) && (mImsi.length() >= 6))) {
                         String mccmncCode = mImsi.substring(0, 6);
+                        log("mccmncCode=" + mccmncCode);
                         for (String mccmnc : MCCMNC_CODES_HAVING_3DIGITS_MNC) {
                             if (mccmnc.equals(mccmncCode)) {
                                 mMncLength = 3;
+                                log("setting6 mMncLength=" + mMncLength);
                                 break;
                             }
                         }
@@ -876,20 +887,21 @@
                                 int mcc = Integer.parseInt(mImsi.substring(0,3));
 
                                 mMncLength = MccTable.smallestDigitsMccForMnc(mcc);
+                                log("setting7 mMncLength=" + mMncLength);
                             } catch (NumberFormatException e) {
                                 mMncLength = UNKNOWN;
-                                loge("Corrupt IMSI!");
+                                loge("Corrupt IMSI! setting8 mMncLength=" + mMncLength);
                             }
                         } else {
                             // Indicate we got this info, but it didn't contain the length.
                             mMncLength = UNKNOWN;
-
-                            log("MNC length not present in EF_AD");
+                            log("MNC length not present in EF_AD setting9 mMncLength=" + mMncLength);
                         }
                     }
                     if (mImsi != null && mMncLength != UNKNOWN) {
                         // finally have both imsi and the length of the mnc and can parse
                         // the imsi properly
+                        log("update mccmnc=" + mImsi.substring(0, 3 + mMncLength));
                         MccTable.updateMccMncConfiguration(mContext,
                                 mImsi.substring(0, 3 + mMncLength), false);
                     }
@@ -1338,14 +1350,15 @@
         if (!TextUtils.isEmpty(operator)) {
             log("onAllRecordsLoaded set 'gsm.sim.operator.numeric' to operator='" +
                     operator + "'");
-            SystemProperties.set(PROPERTY_ICC_OPERATOR_NUMERIC, operator);
+            log("update icc_operator_numeric=" + operator);
+            setSystemProperty(PROPERTY_ICC_OPERATOR_NUMERIC, operator);
         } else {
             log("onAllRecordsLoaded empty 'gsm.sim.operator.numeric' skipping");
         }
 
         if (!TextUtils.isEmpty(mImsi)) {
             log("onAllRecordsLoaded set mcc imsi=" + mImsi);
-            SystemProperties.set(PROPERTY_ICC_OPERATOR_ISO_COUNTRY,
+            setSystemProperty(PROPERTY_ICC_OPERATOR_ISO_COUNTRY,
                     MccTable.countryCodeForMcc(Integer.parseInt(mImsi.substring(0,3))));
         } else {
             log("onAllRecordsLoaded empty imsi skipping setting mcc");
@@ -1582,7 +1595,7 @@
 
                     if (DBG) log("Load EF_SPN: " + mSpn
                             + " spnDisplayCondition: " + mSpnDisplayCondition);
-                    SystemProperties.set(PROPERTY_ICC_OPERATOR_ALPHA, mSpn);
+                    setSystemProperty(PROPERTY_ICC_OPERATOR_ALPHA, mSpn);
 
                     mSpnState = GetSpnFsmState.IDLE;
                 } else {
@@ -1603,7 +1616,7 @@
                     mSpn = IccUtils.adnStringFieldToString(data, 0, data.length);
 
                     if (DBG) log("Load EF_SPN_CPHS: " + mSpn);
-                    SystemProperties.set(PROPERTY_ICC_OPERATOR_ALPHA, mSpn);
+                    setSystemProperty(PROPERTY_ICC_OPERATOR_ALPHA, mSpn);
 
                     mSpnState = GetSpnFsmState.IDLE;
                 } else {
@@ -1620,7 +1633,7 @@
                     mSpn = IccUtils.adnStringFieldToString(data, 0, data.length);
 
                     if (DBG) log("Load EF_SPN_SHORT_CPHS: " + mSpn);
-                    SystemProperties.set(PROPERTY_ICC_OPERATOR_ALPHA, mSpn);
+                    setSystemProperty(PROPERTY_ICC_OPERATOR_ALPHA, mSpn);
                 }else {
                     if (DBG) log("No SPN loaded in either CHPS or 3GPP");
                 }
@@ -1771,4 +1784,12 @@
         pw.println(" mGid1=" + mGid1);
         pw.flush();
     }
+
+    private void setSystemProperty(String key, String val) {
+        // Update the system properties only in case NON-DSDS.
+        // TODO: Shall have a better approach!
+        if (!TelephonyManager.getDefault().isMultiSimEnabled()) {
+            SystemProperties.set(key, val);
+        }
+    }
 }
diff --git a/src/java/com/android/internal/telephony/uicc/UiccCard.java b/src/java/com/android/internal/telephony/uicc/UiccCard.java
index 7a38421..3d8cb3c 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccCard.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccCard.java
@@ -81,12 +81,23 @@
     private static final int EVENT_CLOSE_LOGICAL_CHANNEL_DONE = 16;
     private static final int EVENT_TRANSMIT_APDU_DONE = 17;
 
+    int mSlotId;
+
     public UiccCard(Context c, CommandsInterface ci, IccCardStatus ics) {
         if (DBG) log("Creating");
         mCardState = ics.mCardState;
         update(c, ci, ics);
     }
 
+    public UiccCard(Context c, CommandsInterface ci, IccCardStatus ics, int slotId) {
+        mCardState = ics.mCardState;
+        mSlotId = slotId;
+        update(c, ci, ics);
+    }
+
+    protected UiccCard() {
+    }
+
     public void dispose() {
         synchronized (mLock) {
             if (DBG) log("Disposing card");
@@ -134,18 +145,7 @@
                 }
             }
 
-            if (mUiccApplications.length > 0 && mUiccApplications[0] != null) {
-                // Initialize or Reinitialize CatService
-                mCatService = CatService.getInstance(mCi,
-                                                     mContext,
-                                                     this);
-            } else {
-                if (mCatService != null) {
-                    mCatService.dispose();
-                }
-                mCatService = null;
-            }
-
+            createAndUpdateCatService();
             sanitizeApplicationIndexes();
 
             RadioState radioState = mCi.getRadioState();
@@ -168,6 +168,26 @@
         }
     }
 
+    protected void createAndUpdateCatService() {
+        if (mUiccApplications.length > 0 && mUiccApplications[0] != null) {
+            // Initialize or Reinitialize CatService
+            if (mCatService == null) {
+                mCatService = CatService.getInstance(mCi, mContext, this, mSlotId);
+            } else {
+                ((CatService)mCatService).update(mCi, mContext, this);
+            }
+        } else {
+            if (mCatService != null) {
+                mCatService.dispose();
+            }
+            mCatService = null;
+        }
+    }
+
+    public CatService getCatService() {
+        return mCatService;
+    }
+
     @Override
     protected void finalize() {
         if (DBG) log("UiccCard finalized");
@@ -362,6 +382,24 @@
     }
 
     /**
+     * Returns the SIM application of the specified type.
+     *
+     * @param type ICC application type (@see com.android.internal.telephony.PhoneConstants#APPTYPE_xxx)
+     * @return application corresponding to type or a null if no match found
+     */
+    public UiccCardApplication getApplicationByType(int type) {
+        synchronized (mLock) {
+            for (int i = 0 ; i < mUiccApplications.length; i++) {
+                if (mUiccApplications[i] != null &&
+                        mUiccApplications[i].getType().ordinal() == type) {
+                    return mUiccApplications[i];
+                }
+            }
+            return null;
+        }
+    }
+
+    /**
      * Exposes {@link CommandsInterface.iccOpenLogicalChannel}
      */
     public void iccOpenLogicalChannel(String AID, Message response) {
@@ -393,6 +431,17 @@
         mCi.sendEnvelopeWithStatus(contents, response);
     }
 
+    /* Returns number of applications on this card */
+    public int getNumApplications() {
+        int count = 0;
+        for (UiccCardApplication a : mUiccApplications) {
+            if (a != null) {
+                count++;
+            }
+        }
+        return count;
+    }
+
     private void log(String msg) {
         Rlog.d(LOG_TAG, msg);
     }
diff --git a/src/java/com/android/internal/telephony/uicc/UiccCardApplication.java b/src/java/com/android/internal/telephony/uicc/UiccCardApplication.java
index 448568e..647ef09 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccCardApplication.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccCardApplication.java
@@ -548,6 +548,10 @@
         }
     }
 
+    public String getAppLabel() {
+        return mAppLabel;
+    }
+
     public PinState getPin1State() {
         synchronized (mLock) {
             if (mPin1Replaced) {
diff --git a/src/java/com/android/internal/telephony/uicc/UiccController.java b/src/java/com/android/internal/telephony/uicc/UiccController.java
index 9485e78..073103d 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccController.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccController.java
@@ -22,9 +22,12 @@
 import android.os.Message;
 import android.os.Registrant;
 import android.os.RegistrantList;
+import android.telephony.TelephonyManager;
 import android.telephony.Rlog;
 
 import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.SubscriptionController;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -80,15 +83,21 @@
     private static final int EVENT_ICC_STATUS_CHANGED = 1;
     private static final int EVENT_GET_ICC_STATUS_DONE = 2;
 
+    private CommandsInterface[] mCis;
+    private UiccCard[] mUiccCards = new UiccCard[TelephonyManager.getDefault().getPhoneCount()];
+
     private static final Object mLock = new Object();
     private static UiccController mInstance;
 
     private Context mContext;
+/*
     private CommandsInterface mCi;
     private UiccCard mUiccCard;
+*/
 
-    private RegistrantList mIccChangedRegistrants = new RegistrantList();
+    protected RegistrantList mIccChangedRegistrants = new RegistrantList();
 
+/*
     public static UiccController make(Context c, CommandsInterface ci) {
         synchronized (mLock) {
             if (mInstance != null) {
@@ -98,6 +107,29 @@
             return mInstance;
         }
     }
+*/
+
+    public static UiccController make(Context c, CommandsInterface[] ci) {
+        synchronized (mLock) {
+            if (mInstance != null) {
+                throw new RuntimeException("MSimUiccController.make() should only be called once");
+            }
+            mInstance = new UiccController(c, ci);
+            return (UiccController)mInstance;
+        }
+    }
+
+    private UiccController(Context c, CommandsInterface []ci) {
+        if (DBG) log("Creating UiccController");
+        mContext = c;
+        mCis = ci;
+        for (int i = 0; i < mCis.length; i++) {
+            Integer index = new Integer(i);
+            mCis[i].registerForIccStatusChanged(this, EVENT_ICC_STATUS_CHANGED, index);
+            // TODO remove this once modem correctly notifies the unsols
+            mCis[i].registerForAvailable(this, EVENT_ICC_STATUS_CHANGED, index);
+        }
+    }
 
     public static UiccController getInstance() {
         synchronized (mLock) {
@@ -110,21 +142,32 @@
     }
 
     public UiccCard getUiccCard() {
-        synchronized (mLock) {
-            return mUiccCard;
-        }
+        return getUiccCard(SubscriptionController.getInstance().getPhoneId(SubscriptionController.getInstance().getDefaultSubId()));
     }
 
-    // Easy to use API
-    public UiccCardApplication getUiccCardApplication(int family) {
+    public UiccCard getUiccCard(int slotId) {
         synchronized (mLock) {
-            if (mUiccCard != null) {
-                return mUiccCard.getApplication(family);
+            if (isValidCardIndex(slotId)) {
+                return mUiccCards[slotId];
             }
             return null;
         }
     }
 
+    public UiccCard[] getUiccCards() {
+        // Return cloned array since we don't want to give out reference
+        // to internal data structure.
+        synchronized (mLock) {
+            return mUiccCards.clone();
+        }
+    }
+
+    // Easy to use API
+    public UiccCardApplication getUiccCardApplication(int family) {
+        return getUiccCardApplication(SubscriptionController.getInstance().getPhoneId(SubscriptionController.getInstance().getDefaultSubId()), family);  
+    }
+
+/*
     // Easy to use API
     public IccRecords getIccRecords(int family) {
         synchronized (mLock) {
@@ -137,8 +180,21 @@
             return null;
         }
     }
+*/
 
     // Easy to use API
+    public IccRecords getIccRecords(int slotId, int family) {
+        synchronized (mLock) {
+            UiccCardApplication app = getUiccCardApplication(slotId, family);
+            if (app != null) {
+                return app.getIccRecords();
+            }
+            return null;
+        }
+    }
+
+/*
+    // Easy to use API
     public IccFileHandler getIccFileHandler(int family) {
         synchronized (mLock) {
             if (mUiccCard != null) {
@@ -150,6 +206,19 @@
             return null;
         }
     }
+*/
+
+    // Easy to use API
+    public IccFileHandler getIccFileHandler(int slotId, int family) {
+        synchronized (mLock) {
+            UiccCardApplication app = getUiccCardApplication(slotId, family);
+            if (app != null) {
+                return app.getIccFileHandler();
+            }
+            return null;
+        }
+    }
+
 
     //Notifies when card status changes
     public void registerForIccChanged(Handler h, int what, Object obj) {
@@ -171,15 +240,22 @@
     @Override
     public void handleMessage (Message msg) {
         synchronized (mLock) {
+            Integer index = getCiIndex(msg);
+
+            if (index < 0 || index >= mCis.length) {
+                Rlog.e(LOG_TAG, "Invalid index : " + index + " received with event " + msg.what);
+                return;
+            }
+
             switch (msg.what) {
                 case EVENT_ICC_STATUS_CHANGED:
                     if (DBG) log("Received EVENT_ICC_STATUS_CHANGED, calling getIccCardStatus");
-                    mCi.getIccCardStatus(obtainMessage(EVENT_GET_ICC_STATUS_DONE));
+                    mCis[index].getIccCardStatus(obtainMessage(EVENT_GET_ICC_STATUS_DONE, index));
                     break;
                 case EVENT_GET_ICC_STATUS_DONE:
                     if (DBG) log("Received EVENT_GET_ICC_STATUS_DONE");
                     AsyncResult ar = (AsyncResult)msg.obj;
-                    onGetIccCardStatusDone(ar);
+                    onGetIccCardStatusDone(ar, index);
                     break;
                 default:
                     Rlog.e(LOG_TAG, " Unknown Event " + msg.what);
@@ -187,6 +263,29 @@
         }
     }
 
+    private Integer getCiIndex(Message msg) {
+        AsyncResult ar;
+        Integer index = new Integer(PhoneConstants.DEFAULT_CARD_INDEX);
+
+        /*
+         * The events can be come in two ways. By explicitly sending it using
+         * sendMessage, in this case the user object passed is msg.obj and from
+         * the CommandsInterface, in this case the user object is msg.obj.userObj
+         */
+        if (msg != null) {
+            if (msg.obj != null && msg.obj instanceof Integer) {
+                index = (Integer)msg.obj;
+            } else if(msg.obj != null && msg.obj instanceof AsyncResult) {
+                ar = (AsyncResult)msg.obj;
+                if (ar.userObj != null && ar.userObj instanceof Integer) {
+                    index = (Integer)ar.userObj;
+                }
+            }
+        }
+        return index;
+    }
+
+/*
     private UiccController(Context c, CommandsInterface ci) {
         if (DBG) log("Creating UiccController");
         mContext = c;
@@ -195,39 +294,71 @@
         // This is needed so that we query for sim status in the case when we boot in APM
         mCi.registerForAvailable(this, EVENT_ICC_STATUS_CHANGED, null);
     }
+*/
 
-    private synchronized void onGetIccCardStatusDone(AsyncResult ar) {
+    // Easy to use API
+    public UiccCardApplication getUiccCardApplication(int slotId, int family) {
+        synchronized (mLock) {
+            if (isValidCardIndex(slotId)) {
+                UiccCard c = mUiccCards[slotId];
+                if (c != null) {
+                    return mUiccCards[slotId].getApplication(family);
+                }
+            }
+            return null;
+        }
+    }
+
+    private synchronized void onGetIccCardStatusDone(AsyncResult ar, Integer index) {
         if (ar.exception != null) {
             Rlog.e(LOG_TAG,"Error getting ICC status. "
                     + "RIL_REQUEST_GET_ICC_STATUS should "
                     + "never return an error", ar.exception);
             return;
         }
+        if (!isValidCardIndex(index)) {
+            Rlog.e(LOG_TAG,"onGetIccCardStatusDone: invalid index : " + index);
+            return;
+        }
 
         IccCardStatus status = (IccCardStatus)ar.result;
 
-        if (mUiccCard == null) {
+        if (mUiccCards[index] == null) {
             //Create new card
-            mUiccCard = new UiccCard(mContext, mCi, status);
+            mUiccCards[index] = new UiccCard(mContext, mCis[index], status, index);
+
+/*
+            // Update the UiccCard in base class, so that if someone calls
+            // UiccManager.getUiccCard(), it will return the default card.
+            if (index == PhoneConstants.DEFAULT_CARD_INDEX) {
+                mUiccCard = mUiccCards[index];
+            }
+*/
         } else {
             //Update already existing card
-            mUiccCard.update(mContext, mCi , status);
+            mUiccCards[index].update(mContext, mCis[index] , status);
         }
 
         if (DBG) log("Notifying IccChangedRegistrants");
-        mIccChangedRegistrants.notifyRegistrants();
+        mIccChangedRegistrants.notifyRegistrants(new AsyncResult(null, index, null));
+
+    }
+
+    private boolean isValidCardIndex(int index) {
+        return (index >= 0 && index < mUiccCards.length);
     }
 
     private void log(String string) {
         Rlog.d(LOG_TAG, string);
     }
 
+
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("UiccController: " + this);
         pw.println(" mContext=" + mContext);
         pw.println(" mInstance=" + mInstance);
-        pw.println(" mCi=" + mCi);
-        pw.println(" mUiccCard=" + mUiccCard);
+//        pw.println(" mCi=" + mCi);
+//        pw.println(" mUiccCard=" + mUiccCard);
         pw.println(" mIccChangedRegistrants: size=" + mIccChangedRegistrants.size());
         for (int i = 0; i < mIccChangedRegistrants.size(); i++) {
             pw.println("  mIccChangedRegistrants[" + i + "]="
@@ -235,8 +366,8 @@
         }
         pw.println();
         pw.flush();
-        if (mUiccCard != null) {
-            mUiccCard.dump(fd, pw, args);
-        }
+//        for (int i = 0; i < mUiccCards.length; i++) {
+//            mUiccCards[i].dump(fd, pw, args);
+//        }
     }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/gsm/GSMPhoneTest.java b/tests/telephonytests/src/com/android/internal/telephony/gsm/GSMPhoneTest.java
index 699f113..2e9b88c 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/gsm/GSMPhoneTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/gsm/GSMPhoneTest.java
@@ -23,6 +23,8 @@
 import android.test.AndroidTestCase;
 import android.test.PerformanceTestCase;
 
+import android.telephony.DisconnectCause;
+
 import com.android.internal.telephony.Call;
 import com.android.internal.telephony.CallStateException;
 import com.android.internal.telephony.Connection;
@@ -187,7 +189,7 @@
         assertTrue(!cn.isIncoming());
         assertEquals(Connection.PostDialState.NOT_STARTED, cn.getPostDialState());
 
-        assertEquals(Connection.DisconnectCause.NOT_DISCONNECTED, cn.getDisconnectCause());
+        assertEquals(DisconnectCause.NOT_DISCONNECTED, cn.getDisconnectCause());
 
         assertFalse(mGSMPhone.canConference());
 
@@ -393,7 +395,7 @@
 
         assertEquals(Call.State.DISCONNECTED, cn.getState());
 
-        assertEquals(Connection.DisconnectCause.LOCAL, cn.getDisconnectCause());
+        assertEquals(DisconnectCause.LOCAL, cn.getDisconnectCause());
 
         assertFalse(mGSMPhone.canConference());
 
@@ -404,7 +406,7 @@
         assertFalse(mGSMPhone.getForegroundCall().isDialingOrAlerting());
         assertFalse(mGSMPhone.getRingingCall().isRinging());
 
-        assertEquals(Connection.DisconnectCause.LOCAL, cn.getDisconnectCause());
+        assertEquals(DisconnectCause.LOCAL, cn.getDisconnectCause());
         assertEquals(0, mGSMPhone.getForegroundCall().getConnections().size());
         assertEquals(PhoneConstants.State.IDLE, mGSMPhone.getState());
 
@@ -480,7 +482,7 @@
         cn = mGSMPhone.getRingingCall().getEarliestConnection();
         assertEquals(Call.State.DISCONNECTED, cn.getState());
 
-        assertEquals(Connection.DisconnectCause.INCOMING_MISSED, cn.getDisconnectCause());
+        assertEquals(DisconnectCause.INCOMING_MISSED, cn.getDisconnectCause());
 
         assertFalse(mGSMPhone.canConference());
 
@@ -488,7 +490,7 @@
 
         mGSMPhone.clearDisconnected();
 
-        assertEquals(Connection.DisconnectCause.INCOMING_MISSED, cn.getDisconnectCause());
+        assertEquals(DisconnectCause.INCOMING_MISSED, cn.getDisconnectCause());
         assertEquals(0, mGSMPhone.getForegroundCall().getConnections().size());
         assertEquals(PhoneConstants.State.IDLE, mGSMPhone.getState());
 
@@ -528,7 +530,7 @@
             assertNotNull("Message Time Out", mGSMTestHandler.waitForMessage(ANY_MESSAGE));
         } while (mGSMPhone.getState() != PhoneConstants.State.IDLE);
 
-        assertEquals(Connection.DisconnectCause.INCOMING_MISSED, cn.getDisconnectCause());
+        assertEquals(DisconnectCause.INCOMING_MISSED, cn.getDisconnectCause());
 
         // One Ringing Call
 
@@ -582,7 +584,7 @@
         } while (mGSMPhone.getState() != PhoneConstants.State.IDLE);
 
         assertEquals(Call.State.DISCONNECTED, mGSMPhone.getForegroundCall().getState());
-        assertEquals(Connection.DisconnectCause.NORMAL, cn.getDisconnectCause());
+        assertEquals(DisconnectCause.NORMAL, cn.getDisconnectCause());
 
         // Test missed calls
 
@@ -601,7 +603,7 @@
         ar = (AsyncResult) msg.obj;
         cn = (Connection) ar.result;
 
-        assertEquals(Connection.DisconnectCause.INCOMING_MISSED, cn.getDisconnectCause());
+        assertEquals(DisconnectCause.INCOMING_MISSED, cn.getDisconnectCause());
         assertEquals(PhoneConstants.State.IDLE, mGSMPhone.getState());
         assertEquals(Call.State.DISCONNECTED, mGSMPhone.getRingingCall().getState());
 
@@ -621,7 +623,7 @@
             assertNotNull("Message Time Out", mGSMTestHandler.waitForMessage(ANY_MESSAGE));
         } while (mGSMPhone.getState() != PhoneConstants.State.OFFHOOK);
 
-        assertEquals(Connection.DisconnectCause.NOT_DISCONNECTED, cn.getDisconnectCause());
+        assertEquals(DisconnectCause.NOT_DISCONNECTED, cn.getDisconnectCause());
         assertEquals(Call.State.IDLE, mGSMPhone.getRingingCall().getState());
         assertEquals(Call.State.ACTIVE, mGSMPhone.getForegroundCall().getState());
 
@@ -637,7 +639,7 @@
         } while (mGSMPhone.getForegroundCall().getState()
                 != Call.State.DISCONNECTED);
 
-        assertEquals(Connection.DisconnectCause.LOCAL, cn.getDisconnectCause());
+        assertEquals(DisconnectCause.LOCAL, cn.getDisconnectCause());
 
         //
         // Test held and hangup held calls
@@ -772,7 +774,7 @@
         assertNotNull("Message Time Out", msg);
         assertEquals(PhoneConstants.State.IDLE, mGSMPhone.getState());
 
-        assertEquals(Connection.DisconnectCause.NORMAL, cn.getDisconnectCause());
+        assertEquals(DisconnectCause.NORMAL, cn.getDisconnectCause());
 
         assertEquals(0, mGSMPhone.getRingingCall().getConnections().size());
         assertEquals(1, mGSMPhone.getForegroundCall().getConnections().size());
@@ -809,7 +811,7 @@
         assertEquals(PhoneConstants.State.IDLE, mGSMPhone.getState());
 
         assertEquals(Call.State.DISCONNECTED, mGSMPhone.getForegroundCall().getState());
-        assertEquals(Connection.DisconnectCause.LOCAL, cn.getDisconnectCause());
+        assertEquals(DisconnectCause.LOCAL, cn.getDisconnectCause());
 
         // Test 2: local hangup in "ALERTING" state
         mGSMPhone.dial("+13125551212");
@@ -835,7 +837,7 @@
         assertEquals(PhoneConstants.State.IDLE, mGSMPhone.getState());
 
         assertEquals(Call.State.DISCONNECTED, mGSMPhone.getForegroundCall().getState());
-        assertEquals(Connection.DisconnectCause.LOCAL, cn.getDisconnectCause());
+        assertEquals(DisconnectCause.LOCAL, cn.getDisconnectCause());
 
         // Test 3: local immediate hangup before GSM index is
         // assigned (CallTracker.hangupPendingMO case)
@@ -854,7 +856,7 @@
 
         assertEquals(Call.State.DISCONNECTED, mGSMPhone.getForegroundCall().getState());
 
-        assertEquals(Connection.DisconnectCause.LOCAL,
+        assertEquals(DisconnectCause.LOCAL,
                 mGSMPhone.getForegroundCall().getEarliestConnection().getDisconnectCause());
     }
 
@@ -1521,7 +1523,7 @@
         assertNotNull("Message Time Out", msg);
         assertEquals(PhoneConstants.State.IDLE, mGSMPhone.getState());
 
-        assertEquals(Connection.DisconnectCause.NORMAL, cn.getDisconnectCause());
+        assertEquals(DisconnectCause.NORMAL, cn.getDisconnectCause());
 
         assertEquals(0, mGSMPhone.getRingingCall().getConnections().size());
         assertEquals(1, mGSMPhone.getForegroundCall().getConnections().size());
@@ -1559,7 +1561,7 @@
         assertNotNull("Message Time Out", msg);
         assertEquals(PhoneConstants.State.IDLE, mGSMPhone.getState());
 
-        assertEquals(Connection.DisconnectCause.BUSY, cn.getDisconnectCause());
+        assertEquals(DisconnectCause.BUSY, cn.getDisconnectCause());
 
         assertEquals(0, mGSMPhone.getRingingCall().getConnections().size());
         assertEquals(1, mGSMPhone.getForegroundCall().getConnections().size());
@@ -1606,7 +1608,7 @@
 
         assertEquals(PhoneConstants.State.IDLE, mGSMPhone.getState());
 
-        assertEquals(Connection.DisconnectCause.CONGESTION, cn.getDisconnectCause());
+        assertEquals(DisconnectCause.CONGESTION, cn.getDisconnectCause());
 
         assertEquals(0, mGSMPhone.getRingingCall().getConnections().size());
         assertEquals(1, mGSMPhone.getForegroundCall().getConnections().size());
diff --git a/tests/telephonytests/src/com/android/internal/telephony/gsm/UsimDataDownloadCommands.java b/tests/telephonytests/src/com/android/internal/telephony/gsm/UsimDataDownloadCommands.java
index f179468..e64b597 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/gsm/UsimDataDownloadCommands.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/gsm/UsimDataDownloadCommands.java
@@ -625,6 +625,10 @@
     }
 
     @Override
+    public void requestIccSimAuthentication(String data, Message response) {
+    }
+
+    @Override
     public void getVoiceRadioTechnology(Message response) {
     }