Merge "Fixing a comment (changing phoneId to subId)" into lmp-mr1-dev
diff --git a/res/drawable-xxxhdpi/ic_arrow_back_24dp.png b/res/drawable-xxxhdpi/ic_arrow_back_24dp.png
index 037b6f1..3b72636 100644
--- a/res/drawable-xxxhdpi/ic_arrow_back_24dp.png
+++ b/res/drawable-xxxhdpi/ic_arrow_back_24dp.png
Binary files differ
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 23f44e3..274d74c 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -651,6 +651,24 @@
         <item>"10"</item>
     </string-array>
 
+    <string-array name="enabled_networks_cdma_no_lte_choices" translatable="false">
+        <item>@string/network_3G</item>
+        <item>@string/network_1x</item>
+    </string-array>
+    <string-array name="enabled_networks_cdma_no_lte_values" translatable="false">
+        <item>"4"</item>
+        <item>"5"</item>
+    </string-array>
+
+    <string-array name="enabled_networks_cdma_only_lte_choices" translatable="false">
+        <item>@string/network_lte</item>
+        <item>@string/network_global</item>
+    </string-array>
+    <string-array name="enabled_networks_cdma_only_lte_values" translatable="false">
+        <item>"8"</item>
+        <item>"10"</item>
+    </string-array>
+
     <!-- CDMA System select strings -->
     <!-- Mobile network settings screen, setting option name -->
     <string name="cdma_system_select_title">System select</string>
@@ -968,7 +986,7 @@
     <!-- In-call screen: message displayed in an error dialog -->
     <string name="incall_error_supp_service_transfer">Can\'t transfer.</string>
     <!-- In-call screen: message displayed in an error dialog -->
-    <string name="incall_error_supp_service_conference">Can\'t conference.</string>
+    <string name="incall_error_supp_service_conference">Unable to conference calls.</string>
     <!-- In-call screen: message displayed in an error dialog -->
     <string name="incall_error_supp_service_reject">Can\'t reject call.</string>
     <!-- In-call screen: message displayed in an error dialog -->
@@ -1066,7 +1084,9 @@
     </string-array>
 
     <!-- Title for the dialog used to display CDMA DisplayInfo -->
-    <string name="network_message">Network message</string>
+    <string name="network_info_message">Network message</string>
+    <!-- Title for the dialog used to display a network error message to the user -->
+    <string name="network_error_message">Error message</string>
 
     <!-- OTA-specific strings -->
     <!-- Title shown on OTA screen -->
diff --git a/src/com/android/phone/CallFeaturesSetting.java b/src/com/android/phone/CallFeaturesSetting.java
index 7e670aa..0286e71 100644
--- a/src/com/android/phone/CallFeaturesSetting.java
+++ b/src/com/android/phone/CallFeaturesSetting.java
@@ -1246,29 +1246,38 @@
 
             int phoneType = mPhone.getPhoneType();
             Preference fdnButton = prefSet.findPreference(BUTTON_FDN_KEY);
-            if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
+            boolean shouldHideCarrierSettings = Settings.Global.getInt(
+                    getContentResolver(), Settings.Global.HIDE_CARRIER_NETWORK_SETTINGS, 0) == 1;
+            if (shouldHideCarrierSettings) {
                 prefSet.removePreference(fdnButton);
-
-                if (!getResources().getBoolean(R.bool.config_voice_privacy_disable)) {
-                    addPreferencesFromResource(R.xml.cdma_call_privacy);
-                }
-            } else if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
-                fdnButton.setIntent(mSubscriptionInfoHelper.getIntent(this, FdnSetting.class));
-
-                if (getResources().getBoolean(R.bool.config_additional_call_setting)) {
-                    addPreferencesFromResource(R.xml.gsm_umts_call_options);
-
-                    Preference callForwardingPref = prefSet.findPreference(CALL_FORWARDING_KEY);
-                    callForwardingPref.setIntent(mSubscriptionInfoHelper.getIntent(
-                            this, GsmUmtsCallForwardOptions.class));
-
-                    Preference additionalGsmSettingsPref =
-                            prefSet.findPreference(ADDITIONAL_GSM_SETTINGS_KEY);
-                    additionalGsmSettingsPref.setIntent(mSubscriptionInfoHelper.getIntent(
-                            this, GsmUmtsAdditionalCallOptions.class));
+                if (mButtonDTMF != null) {
+                    prefSet.removePreference(mButtonDTMF);
                 }
             } else {
-                throw new IllegalStateException("Unexpected phone type: " + phoneType);
+                if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
+                    prefSet.removePreference(fdnButton);
+
+                    if (!getResources().getBoolean(R.bool.config_voice_privacy_disable)) {
+                        addPreferencesFromResource(R.xml.cdma_call_privacy);
+                    }
+                } else if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
+                    fdnButton.setIntent(mSubscriptionInfoHelper.getIntent(this, FdnSetting.class));
+
+                    if (getResources().getBoolean(R.bool.config_additional_call_setting)) {
+                        addPreferencesFromResource(R.xml.gsm_umts_call_options);
+
+                        Preference callForwardingPref = prefSet.findPreference(CALL_FORWARDING_KEY);
+                        callForwardingPref.setIntent(mSubscriptionInfoHelper.getIntent(
+                                this, GsmUmtsCallForwardOptions.class));
+
+                        Preference additionalGsmSettingsPref =
+                                prefSet.findPreference(ADDITIONAL_GSM_SETTINGS_KEY);
+                        additionalGsmSettingsPref.setIntent(mSubscriptionInfoHelper.getIntent(
+                                this, GsmUmtsAdditionalCallOptions.class));
+                    }
+                } else {
+                    throw new IllegalStateException("Unexpected phone type: " + phoneType);
+                }
             }
         }
 
diff --git a/src/com/android/phone/CallNotifier.java b/src/com/android/phone/CallNotifier.java
index f22913c..bc13010 100644
--- a/src/com/android/phone/CallNotifier.java
+++ b/src/com/android/phone/CallNotifier.java
@@ -65,8 +65,8 @@
             (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
     private static final boolean VDBG = (PhoneGlobals.DBG_LEVEL >= 2);
 
-    // Time to display the  DisplayInfo Record sent by CDMA network
-    private static final int DISPLAYINFO_NOTIFICATION_TIME = 2000; // msec
+    // Time to display the message from the underlying phone layers.
+    private static final int SHOW_MESSAGE_NOTIFICATION_TIME = 3000; // msec
 
     private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder()
             .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
@@ -86,12 +86,6 @@
     // object used to synchronize access to mCallerInfoQueryState
     private Object mCallerInfoQueryStateGuard = new Object();
 
-    // Events generated internally:
-    private static final int PHONE_MWI_CHANGED = 21;
-    private static final int DISPLAYINFO_NOTIFICATION_DONE = 24;
-    private static final int UPDATE_IN_CALL_NOTIFICATION = 27;
-
-
     private PhoneGlobals mApplication;
     private CallManager mCM;
     private BluetoothHeadset mBluetoothHeadset;
@@ -197,7 +191,7 @@
                 onUnknownConnectionAppeared((AsyncResult) msg.obj);
                 break;
 
-            case PHONE_MWI_CHANGED:
+            case CallStateMonitor.INTERNAL_PHONE_MWI_CHANGED:
                 onMwiChanged(mApplication.phone.getMessageWaitingIndicator());
                 break;
 
@@ -211,9 +205,9 @@
                 onSignalInfo((AsyncResult) msg.obj);
                 break;
 
-            case DISPLAYINFO_NOTIFICATION_DONE:
+            case CallStateMonitor.INTERNAL_SHOW_MESSAGE_NOTIFICATION_DONE:
                 if (DBG) log("Received Display Info notification done event ...");
-                CdmaDisplayInfo.dismissDisplayInfoRecord();
+                PhoneDisplayMessage.dismissMessage();
                 break;
 
             case CallStateMonitor.EVENT_OTA_PROVISION_CHANGE:
@@ -239,6 +233,11 @@
                 }
                 break;
 
+            case CallStateMonitor.PHONE_SUPP_SERVICE_FAILED:
+                if (DBG) log("PHONE_SUPP_SERVICE_FAILED...");
+                onSuppServiceFailed((AsyncResult) msg.obj);
+                break;
+
             default:
                 // super.handleMessage(msg);
         }
@@ -485,7 +484,8 @@
         }
 
         int autoretrySetting = 0;
-        if ((c != null) && (c.getCall().getPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA)) {
+        if ((c != null) &&
+                (c.getCall().getPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA)) {
             autoretrySetting = android.provider.Settings.Global.getInt(mApplication.
                     getContentResolver(),android.provider.Settings.Global.CALL_AUTO_RETRY, 0);
         }
@@ -493,7 +493,8 @@
         // Stop any signalInfo tone being played when a call gets ended
         stopSignalInfoTone();
 
-        if ((c != null) && (c.getCall().getPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA)) {
+        if ((c != null) &&
+                (c.getCall().getPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA)) {
             // Resetting the CdmaPhoneCallState members
             mApplication.cdmaPhoneCallState.resetCdmaPhoneCallState();
         }
@@ -632,7 +633,7 @@
      * failed NotificationMgr.updateMwi() call.
      */
     /* package */ void sendMwiChangedDelayed(long delayMillis) {
-        Message message = Message.obtain(this, PHONE_MWI_CHANGED);
+        Message message = Message.obtain(this, CallStateMonitor.INTERNAL_PHONE_MWI_CHANGED);
         sendMessageDelayed(message, delayMillis);
     }
 
@@ -916,15 +917,37 @@
         if (displayInfoRec != null) {
             String displayInfo = displayInfoRec.alpha;
             if (DBG) log("onDisplayInfo: displayInfo=" + displayInfo);
-            CdmaDisplayInfo.displayInfoRecord(mApplication, displayInfo);
+            PhoneDisplayMessage.displayNetworkMessage(mApplication, displayInfo);
 
-            // start a 2 second timer
-            sendEmptyMessageDelayed(DISPLAYINFO_NOTIFICATION_DONE,
-                    DISPLAYINFO_NOTIFICATION_TIME);
+            // start a timer that kills the dialog
+            sendEmptyMessageDelayed(CallStateMonitor.INTERNAL_SHOW_MESSAGE_NOTIFICATION_DONE,
+                    SHOW_MESSAGE_NOTIFICATION_TIME);
         }
     }
 
     /**
+     * Displays a notification when the phone receives a notice that a supplemental
+     * service has failed.
+     * TODO: This is a NOOP if it isn't for conferences right now.
+     */
+    private void onSuppServiceFailed(AsyncResult r) {
+        if (r.result != Phone.SuppService.CONFERENCE) {
+            if (DBG) log("onSuppServiceFailed: not a merge failure event");
+            return;
+        }
+
+        if (DBG) log("onSuppServiceFailed: displaying merge failure message");
+
+        String mergeFailedString = mApplication.getResources().getString(
+                R.string.incall_error_supp_service_conference);
+        PhoneDisplayMessage.displayErrorMessage(mApplication, mergeFailedString);
+
+        // start a timer that kills the dialog
+        sendEmptyMessageDelayed(CallStateMonitor.INTERNAL_SHOW_MESSAGE_NOTIFICATION_DONE,
+                SHOW_MESSAGE_NOTIFICATION_TIME);
+    }
+
+    /**
      * Helper class to play SignalInfo tones using the ToneGenerator.
      *
      * To use, just instantiate a new SignalInfoTonePlayer
diff --git a/src/com/android/phone/CallStateMonitor.java b/src/com/android/phone/CallStateMonitor.java
index eda45b9..9b8a653 100644
--- a/src/com/android/phone/CallStateMonitor.java
+++ b/src/com/android/phone/CallStateMonitor.java
@@ -56,6 +56,13 @@
     public static final int PHONE_RINGBACK_TONE = 11;
     public static final int PHONE_RESEND_MUTE = 12;
     public static final int PHONE_ON_DIAL_CHARS = 13;
+    public static final int PHONE_SUPP_SERVICE_FAILED = 14;
+    // Events generated internally.
+    // We should store all the possible event type values in one place to make sure that
+    // they don't step on each others' toes.
+    public static final int INTERNAL_PHONE_MWI_CHANGED = 21;
+    public static final int INTERNAL_SHOW_MESSAGE_NOTIFICATION_DONE = 22;
+    public static final int INTERNAL_UPDATE_IN_CALL_NOTIFICATION = 23;
 
     // Other events from call manager
     public static final int EVENT_OTA_PROVISION_CHANGE = 20;
@@ -89,6 +96,7 @@
         callManager.registerForSignalInfo(this, PHONE_STATE_SIGNALINFO, null);
         callManager.registerForInCallVoicePrivacyOn(this, PHONE_ENHANCED_VP_ON, null);
         callManager.registerForInCallVoicePrivacyOff(this, PHONE_ENHANCED_VP_OFF, null);
+        callManager.registerForSuppServiceFailed(this, PHONE_SUPP_SERVICE_FAILED, null);
         //callManager.registerForRingbackTone(this, PHONE_RINGBACK_TONE, null);
         //callManager.registerForResendIncallMute(this, PHONE_RESEND_MUTE, null);
         //callManager.registerForPostDialCharacter(this, PHONE_ON_DIAL_CHARS, null);
@@ -136,6 +144,7 @@
         callManager.unregisterForInCallVoicePrivacyOn(this);
         callManager.unregisterForInCallVoicePrivacyOff(this);
         //callManager.unregisterForPostDialCharacter(this);
+        callManager.unregisterForSuppServiceFailed(this);
 
         registerForNotifications();
     }
diff --git a/src/com/android/phone/CdmaDisplayInfo.java b/src/com/android/phone/CdmaDisplayInfo.java
deleted file mode 100644
index 1a88333..0000000
--- a/src/com/android/phone/CdmaDisplayInfo.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 2009 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.phone;
-
-import android.app.AlertDialog;
-import android.content.Context;
-import android.os.SystemProperties;
-import android.util.Log;
-import android.view.WindowManager;
-
-/**
- * Helper class for displaying the DisplayInfo sent by CDMA network.
- */
-public class CdmaDisplayInfo {
-    private static final String LOG_TAG = "CdmaDisplayInfo";
-    private static final boolean DBG = (SystemProperties.getInt("ro.debuggable", 0) == 1);
-
-    /** CDMA DisplayInfo dialog */
-    private static AlertDialog sDisplayInfoDialog = null;
-
-    /**
-     * Handle the DisplayInfo record and display the alert dialog with
-     * the network message.
-     *
-     * @param context context to get strings.
-     * @param infoMsg Text message from Network.
-     */
-    public static void displayInfoRecord(Context context, String infoMsg) {
-
-        if (DBG) log("displayInfoRecord: infoMsg=" + infoMsg);
-
-        if (sDisplayInfoDialog != null) {
-            sDisplayInfoDialog.dismiss();
-        }
-
-        // displaying system alert dialog on the screen instead of
-        // using another activity to display the message.  This
-        // places the message at the forefront of the UI.
-        sDisplayInfoDialog = new AlertDialog.Builder(context)
-                .setIcon(android.R.drawable.ic_dialog_info)
-                .setTitle(context.getText(R.string.network_message))
-                .setMessage(infoMsg)
-                .setCancelable(true)
-                .create();
-
-        sDisplayInfoDialog.getWindow().setType(
-                WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
-        sDisplayInfoDialog.getWindow().addFlags(
-                WindowManager.LayoutParams.FLAG_DIM_BEHIND);
-
-        sDisplayInfoDialog.show();
-        PhoneGlobals.getInstance().wakeUpScreen();
-
-    }
-
-    /**
-     * Dismiss the DisplayInfo record
-     */
-    public static void dismissDisplayInfoRecord() {
-
-        if (DBG) log("Dissmissing Display Info Record...");
-
-        if (sDisplayInfoDialog != null) {
-            sDisplayInfoDialog.dismiss();
-            sDisplayInfoDialog = null;
-        }
-    }
-
-    private static void log(String msg) {
-        Log.d(LOG_TAG, "[CdmaDisplayInfo] " + msg);
-    }
-}
diff --git a/src/com/android/phone/MobileNetworkSettings.java b/src/com/android/phone/MobileNetworkSettings.java
index 5a64528..afeebd8 100644
--- a/src/com/android/phone/MobileNetworkSettings.java
+++ b/src/com/android/phone/MobileNetworkSettings.java
@@ -379,8 +379,6 @@
 
     private void updateBody() {
         final Context context = getApplicationContext();
-        final TelephonyManager tm =
-                (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
         PreferenceScreen prefSet = getPreferenceScreen();
         boolean isLteOnCdma = mPhone.getLteOnCdmaMode() == PhoneConstants.LTE_ON_CDMA_TRUE;
         final int phoneSubId = mPhone.getSubId();
@@ -416,13 +414,46 @@
             mGsmUmtsOptions = new GsmUmtsOptions(this, prefSet);
         } else {
             prefSet.removePreference(mButtonPreferredNetworkMode);
-            int phoneType = mPhone.getPhoneType();
+            final int phoneType = mPhone.getPhoneType();
             if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
+                int lteForced = android.provider.Settings.Global.getInt(
+                        mPhone.getContext().getContentResolver(),
+                        android.provider.Settings.Global.LTE_SERVICE_FORCED + mPhone.getSubId(),
+                        0);
+
                 if (isLteOnCdma) {
-                    mButtonEnabledNetworks.setEntries(
-                            R.array.enabled_networks_cdma_choices);
-                    mButtonEnabledNetworks.setEntryValues(
-                            R.array.enabled_networks_cdma_values);
+                    if (lteForced == 0) {
+                        mButtonEnabledNetworks.setEntries(
+                                R.array.enabled_networks_cdma_choices);
+                        mButtonEnabledNetworks.setEntryValues(
+                                R.array.enabled_networks_cdma_values);
+                    } else {
+                        switch (settingsNetworkMode) {
+                            case Phone.NT_MODE_CDMA:
+                            case Phone.NT_MODE_CDMA_NO_EVDO:
+                            case Phone.NT_MODE_EVDO_NO_CDMA:
+                                mButtonEnabledNetworks.setEntries(
+                                        R.array.enabled_networks_cdma_no_lte_choices);
+                                mButtonEnabledNetworks.setEntryValues(
+                                        R.array.enabled_networks_cdma_no_lte_values);
+                                break;
+                            case Phone.NT_MODE_GLOBAL:
+                            case Phone.NT_MODE_LTE_CDMA_AND_EVDO:
+                            case Phone.NT_MODE_LTE_CDMA_EVDO_GSM_WCDMA:
+                            case Phone.NT_MODE_LTE_ONLY:
+                                mButtonEnabledNetworks.setEntries(
+                                        R.array.enabled_networks_cdma_only_lte_choices);
+                                mButtonEnabledNetworks.setEntryValues(
+                                        R.array.enabled_networks_cdma_only_lte_values);
+                                break;
+                            default:
+                                mButtonEnabledNetworks.setEntries(
+                                        R.array.enabled_networks_cdma_choices);
+                                mButtonEnabledNetworks.setEntryValues(
+                                        R.array.enabled_networks_cdma_values);
+                                break;
+                        }
+                    }
                 }
                 mCdmaOptions = new CdmaOptions(this, prefSet, mPhone);
             } else if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
diff --git a/src/com/android/phone/PhoneDisplayMessage.java b/src/com/android/phone/PhoneDisplayMessage.java
new file mode 100644
index 0000000..2b86a61
--- /dev/null
+++ b/src/com/android/phone/PhoneDisplayMessage.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2009 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.phone;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.os.SystemProperties;
+import android.util.Log;
+import android.view.WindowManager;
+
+/**
+ * Helper class for displaying the a string triggered by a lower level Phone request
+ */
+public class PhoneDisplayMessage {
+    private static final String LOG_TAG = "PhoneDisplayMessage";
+    private static final boolean DBG = (SystemProperties.getInt("ro.debuggable", 0) == 1);
+
+    /** Display message dialog */
+    private static AlertDialog sDisplayMessageDialog = null;
+
+    /**
+     * Display the alert dialog with the network message.
+     *
+     * @param context context to get strings.
+     * @param infoMsg Text message from Network.
+     */
+    public static void displayNetworkMessage(Context context, String infoMsg) {
+        if (DBG) log("displayInfoRecord: infoMsg=" + infoMsg);
+
+        String title = (String)context.getText(R.string.network_info_message);
+        displayMessage(context, title, infoMsg);
+    }
+
+    /**
+     * Display the alert dialog with the error message.
+     *
+     * @param context context to get strings.
+     * @param errorMsg Error message describing the network triggered error
+     */
+    public static void displayErrorMessage(Context context, String errorMsg) {
+        if (DBG) log("displayErrorMessage: errorMsg=" + errorMsg);
+
+        String title = (String)context.getText(R.string.network_error_message);
+        displayMessage(context, title, errorMsg);
+    }
+
+    public static void displayMessage(Context context, String title, String msg) {
+        if (DBG) log("displayMessage: msg=" + msg);
+
+        if (sDisplayMessageDialog != null) {
+            sDisplayMessageDialog.dismiss();
+        }
+
+        // displaying system alert dialog on the screen instead of
+        // using another activity to display the message.  This
+        // places the message at the forefront of the UI.
+        sDisplayMessageDialog = new AlertDialog.Builder(context)
+                .setIcon(android.R.drawable.ic_dialog_info)
+                .setTitle(title)
+                .setMessage(msg)
+                .setCancelable(true)
+                .create();
+
+        sDisplayMessageDialog.getWindow().setType(
+                WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
+        sDisplayMessageDialog.getWindow().addFlags(
+                WindowManager.LayoutParams.FLAG_DIM_BEHIND);
+
+        sDisplayMessageDialog.show();
+        PhoneGlobals.getInstance().wakeUpScreen();
+    }
+
+    /**
+     * Dismiss the DisplayInfo record
+     */
+    public static void dismissMessage() {
+        if (DBG) log("Dissmissing Display Info Record...");
+
+        if (sDisplayMessageDialog != null) {
+            sDisplayMessageDialog.dismiss();
+            sDisplayMessageDialog = null;
+        }
+    }
+
+    private static void log(String msg) {
+        Log.d(LOG_TAG, "[PhoneDisplayMessage] " + msg);
+    }
+}
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index cbd5ee6..1ca4a8c 100644
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -1360,7 +1360,7 @@
         }
 
         log("No modify permission, check carrier privilege next.");
-        if (hasCarrierPrivileges() != TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
+        if (getCarrierPrivilegeStatus() != TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
             loge("No Carrier Privilege.");
             throw new SecurityException("No modify permission or carrier privilege.");
         }
@@ -1372,7 +1372,7 @@
      * @throws SecurityException if the caller does not have the required permission
      */
     private void enforceCarrierPrivilege() {
-        if (hasCarrierPrivileges() != TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
+        if (getCarrierPrivilegeStatus() != TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
             loge("No Carrier Privilege.");
             throw new SecurityException("No Carrier Privilege.");
         }
@@ -1497,7 +1497,7 @@
      */
     @Override
     public boolean setVoiceMailNumber(int subId, String alphaTag, String number) {
-        enforceModifyPermissionOrCarrierPrivilege();
+        enforceCarrierPrivilege();
         Boolean success = (Boolean) sendRequest(CMD_SET_VOICEMAIL_NUMBER,
                 new Pair<String, String>(alphaTag, number), new Integer(subId));
         return success;
@@ -1928,10 +1928,10 @@
     }
 
     @Override
-    public int hasCarrierPrivileges() {
+    public int getCarrierPrivilegeStatus() {
         UiccCard card = UiccController.getInstance().getUiccCard();
         if (card == null) {
-            loge("hasCarrierPrivileges: No UICC");
+            loge("getCarrierPrivilegeStatus: No UICC");
             return TelephonyManager.CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED;
         }
         return card.getCarrierPrivilegeStatusForCurrentTransaction(
@@ -1975,7 +1975,7 @@
 
     @Override
     public void setLine1NumberForDisplayForSubscriber(int subId, String alphaTag, String number) {
-        enforceModifyPermissionOrCarrierPrivilege();
+        enforceCarrierPrivilege();
 
         String iccId = getIccId(subId);
         if (iccId != null) {
@@ -2023,7 +2023,7 @@
 
     @Override
     public boolean setOperatorBrandOverride(String brand) {
-        enforceModifyPermissionOrCarrierPrivilege();
+        enforceCarrierPrivilege();
         return mPhone.setOperatorBrandOverride(brand);
     }
 
diff --git a/src/com/android/phone/settings/PhoneAccountSettingsFragment.java b/src/com/android/phone/settings/PhoneAccountSettingsFragment.java
index a99c5f0..784be0f 100644
--- a/src/com/android/phone/settings/PhoneAccountSettingsFragment.java
+++ b/src/com/android/phone/settings/PhoneAccountSettingsFragment.java
@@ -50,7 +50,6 @@
     private String LOG_TAG = PhoneAccountSettingsFragment.class.getSimpleName();
 
     private TelecomManager mTelecomManager;
-    private Context mApplicationContext;
 
     private PreferenceCategory mAccountList;
 
@@ -67,7 +66,6 @@
         super.onCreate(icicle);
 
         mTelecomManager = TelecomManager.from(getActivity());
-        mApplicationContext = getActivity().getApplicationContext();
     }
 
     @Override
@@ -243,13 +241,19 @@
     }
 
     private synchronized void handleSipReceiveCallsOption(boolean isEnabled) {
+        Context context = getActivity();
+        if (context == null) {
+            // Return if the fragment is detached from parent activity before executed by thread.
+            return;
+        }
+
         mSipSharedPreferences.setReceivingCallsEnabled(isEnabled);
 
-        SipUtil.useSipToReceiveIncomingCalls(mApplicationContext, isEnabled);
+        SipUtil.useSipToReceiveIncomingCalls(context, isEnabled);
 
         // Restart all Sip services to ensure we reflect whether we are receiving calls.
         SipAccountRegistry sipAccountRegistry = SipAccountRegistry.getInstance();
-        sipAccountRegistry.restartSipService(mApplicationContext);
+        sipAccountRegistry.restartSipService(context);
     }
 
     /**
@@ -303,7 +307,7 @@
             intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
             SubscriptionInfoHelper.addExtrasToIntent(intent, subscription);
 
-            Preference accountPreference = new Preference(mApplicationContext);
+            Preference accountPreference = new Preference(getActivity());
             accountPreference.setTitle(label);
             accountPreference.setIntent(intent);
             mAccountList.addPreference(accountPreference);
diff --git a/src/com/android/services/telephony/CdmaConference.java b/src/com/android/services/telephony/CdmaConference.java
index 2d5ee47..a0d6dd2 100755
--- a/src/com/android/services/telephony/CdmaConference.java
+++ b/src/com/android/services/telephony/CdmaConference.java
@@ -34,16 +34,16 @@
  * CDMA-based conference call.
  */
 public class CdmaConference extends Conference {
-
-    private int mCapabilities = PhoneCapabilities.MUTE;
+    private int mCapabilities;
 
     public CdmaConference(PhoneAccountHandle phoneAccount) {
         super(phoneAccount);
         setActive();
     }
 
-    private void updateCapabilities() {
-        setCapabilities(mCapabilities);
+    public void updateCapabilities(int capabilities) {
+        capabilities |=  PhoneCapabilities.MUTE | PhoneCapabilities.GENERIC_CONFERENCE;
+        setCapabilities(capabilities);
     }
 
     /**
@@ -89,7 +89,7 @@
         if (isSwapSupportedAfterMerge()){
             mCapabilities |= PhoneCapabilities.SWAP_CONFERENCE;
         }
-        updateCapabilities();
+        updateCapabilities(mCapabilities);
         sendFlash();
     }
 
diff --git a/src/com/android/services/telephony/CdmaConferenceController.java b/src/com/android/services/telephony/CdmaConferenceController.java
index e2f0b50..f7b9793 100644
--- a/src/com/android/services/telephony/CdmaConferenceController.java
+++ b/src/com/android/services/telephony/CdmaConferenceController.java
@@ -156,11 +156,11 @@
             CdmaConnection newConnection = mCdmaConnections.get(mCdmaConnections.size() - 1);
             if (newConnection.isOutgoing()) {
                 // Only an outgoing call can be merged with an ongoing call.
-                mConference.setCapabilities(PhoneCapabilities.MERGE_CONFERENCE);
+                mConference.updateCapabilities(PhoneCapabilities.MERGE_CONFERENCE);
             } else {
                 // If the most recently added connection was an incoming call, enable
                 // swap instead of merge.
-                mConference.setCapabilities(PhoneCapabilities.SWAP_CONFERENCE);
+                mConference.updateCapabilities(PhoneCapabilities.SWAP_CONFERENCE);
             }
 
             // 2) Add any new connections to the conference
diff --git a/src/com/android/services/telephony/ConferenceParticipantConnection.java b/src/com/android/services/telephony/ConferenceParticipantConnection.java
index e748634..49307ba 100644
--- a/src/com/android/services/telephony/ConferenceParticipantConnection.java
+++ b/src/com/android/services/telephony/ConferenceParticipantConnection.java
@@ -19,8 +19,8 @@
 import com.android.internal.telephony.PhoneConstants;
 
 import android.net.Uri;
-import android.telecom.ConferenceParticipant;
 import android.telecom.Connection;
+import android.telecom.ConferenceParticipant;
 import android.telecom.DisconnectCause;
 import android.telecom.PhoneCapabilities;
 
@@ -37,7 +37,7 @@
     /**
      * The connection which owns this participant.
      */
-    private final Connection mParentConnection;
+    private final com.android.internal.telephony.Connection mParentConnection;
 
     /**
      * Creates a new instance.
@@ -45,7 +45,8 @@
      * @param participant The conference participant to create the instance for.
      */
     public ConferenceParticipantConnection(
-            Connection parentConnection, ConferenceParticipant participant) {
+            com.android.internal.telephony.Connection parentConnection,
+            ConferenceParticipant participant) {
 
         mParentConnection = parentConnection;
         setAddress(participant.getHandle(), PhoneConstants.PRESENTATION_ALLOWED);
@@ -61,6 +62,8 @@
      * @param newState The new state.
      */
     public void updateState(int newState) {
+        Log.v(this, "updateState endPoint: %s state: %s", Log.pii(mEndpoint),
+                Connection.stateToString(newState));
         if (newState == getState()) {
             return;
         }
@@ -100,12 +103,19 @@
      */
     @Override
     public void onDisconnect() {
-        Log.v(this, "onDisconnect");
-
         mParentConnection.onDisconnectConferenceParticipant(mEndpoint);
     }
 
     /**
+     * Retrieves the endpoint for this connection.
+     *
+     * @return The endpoint.
+     */
+    public Uri getEndpoint() {
+        return mEndpoint;
+    }
+
+    /**
      * Configures the {@link android.telecom.PhoneCapabilities} applicable to this connection.  A
      * conference participant can only be disconnected from a conference since there is not
      * actual connection to the participant which could be split from the conference.
@@ -114,4 +124,25 @@
         int capabilities = PhoneCapabilities.DISCONNECT_FROM_CONFERENCE;
         setCallCapabilities(capabilities);
     }
+
+    /**
+     * Builds a string representation of this conference participant connection.
+     *
+     * @return String representation of connection.
+     */
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("[ConferenceParticipantConnection objId:");
+        sb.append(System.identityHashCode(this));
+        sb.append(" endPoint:");
+        sb.append(Log.pii(mEndpoint));
+        sb.append(" parentConnection:");
+        sb.append(Log.pii(mParentConnection.getAddress()));
+        sb.append(" state:");
+        sb.append(Connection.stateToString(getState()));
+        sb.append("]");
+
+        return sb.toString();
+    }
 }
diff --git a/src/com/android/services/telephony/ImsConference.java b/src/com/android/services/telephony/ImsConference.java
new file mode 100644
index 0000000..5fc3e41
--- /dev/null
+++ b/src/com/android/services/telephony/ImsConference.java
@@ -0,0 +1,577 @@
+/*
+ * 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.services.telephony;
+
+import com.android.internal.telephony.Call;
+import com.android.internal.telephony.CallStateException;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.imsphone.ImsPhoneConnection;
+
+import android.net.Uri;
+import android.telecom.Connection;
+import android.telecom.Conference;
+import android.telecom.ConferenceParticipant;
+import android.telecom.DisconnectCause;
+import android.telecom.PhoneAccountHandle;
+import android.telecom.PhoneCapabilities;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Represents an IMS conference call.
+ * <P>
+ * An IMS conference call consists of a conference host connection and potentially a list of
+ * conference participants.  The conference host connection represents the radio connection to the
+ * IMS conference server.  Since it is not a connection to any one individual, it is not represented
+ * in Telecom/InCall as a call.  The conference participant information is received via the host
+ * connection via a conference event package.  Conference participant connections do not represent
+ * actual radio connections to the participants; they act as a virtual representation of the
+ * participant, keyed by a unique endpoint {@link android.net.Uri}.
+ * <p>
+ * The {@link ImsConference} listens for conference event package data received via the host
+ * connection and is responsible for managing the conference participant connections which represent
+ * the participants.
+ */
+public class ImsConference extends Conference {
+
+    /**
+     * TelephonyConnection class used to represent the connection to the conference server.
+     */
+    private class ConferenceHostConnection extends TelephonyConnection {
+        protected ConferenceHostConnection(
+                com.android.internal.telephony.Connection originalConnection) {
+            super(originalConnection);
+        }
+    }
+
+    /**
+     * Listener used to respond to changes to conference participants.  At the conference level we
+     * are most concerned with handling destruction of a conference participant.
+     */
+    private final Connection.Listener mParticipantListener = new Connection.Listener() {
+        /**
+         * Participant has been destroyed.  Remove it from the conference.
+         *
+         * @param connection The participant which was destroyed.
+         */
+        @Override
+        public void onDestroyed(Connection connection) {
+            ConferenceParticipantConnection participant =
+                    (ConferenceParticipantConnection) connection;
+            removeConferenceParticipant(participant);
+            updateManageConference();
+        }
+
+    };
+
+    /**
+     * Listener used to respond to changes to the underlying radio connection for the conference
+     * host connection.  Used to respond to SRVCC changes.
+     */
+    private final TelephonyConnection.TelephonyConnectionListener mTelephonyConnectionListener =
+            new TelephonyConnection.TelephonyConnectionListener() {
+
+        @Override
+        public void onOriginalConnectionConfigured(TelephonyConnection c) {
+            if (c == mConferenceHost) {
+               handleOriginalConnectionChange();
+            }
+        }
+    };
+
+    /**
+     * Listener used to respond to changes to the connection to the IMS conference server.
+     */
+    private final android.telecom.Connection.Listener mConferenceHostListener =
+            new android.telecom.Connection.Listener() {
+
+        /**
+         * Updates the state of the conference based on the new state of the host.
+         *
+         * @param c The host connection.
+         * @param state The new state
+         */
+        @Override
+        public void onStateChanged(android.telecom.Connection c, int state) {
+            setState(state);
+        }
+
+        /**
+         * Disconnects the conference when its host connection disconnects.
+         *
+         * @param c The host connection.
+         * @param disconnectCause The host connection disconnect cause.
+         */
+        @Override
+        public void onDisconnected(android.telecom.Connection c, DisconnectCause disconnectCause) {
+            setDisconnected(disconnectCause);
+        }
+
+        /**
+         * Handles destruction of the host connection; once the host connection has been
+         * destroyed, cleans up the conference participant connection.
+         *
+         * @param connection The host connection.
+         */
+        @Override
+        public void onDestroyed(android.telecom.Connection connection) {
+            disconnectConferenceParticipants();
+        }
+
+        /**
+         * Handles changes to conference participant data as reported by the conference host
+         * connection.
+         *
+         * @param c The connection.
+         * @param participants The participant information.
+         */
+        @Override
+        public void onConferenceParticipantsChanged(android.telecom.Connection c,
+                List<ConferenceParticipant> participants) {
+
+            if (c == null) {
+                return;
+            }
+            Log.v(this, "onConferenceParticipantsChanged: %d participants", participants.size());
+            TelephonyConnection telephonyConnection = (TelephonyConnection) c;
+            handleConferenceParticipantsUpdate(telephonyConnection, participants);
+        }
+    };
+
+    /**
+     * The telephony connection service; used to add new participant connections to Telecom.
+     */
+    private TelephonyConnectionService mTelephonyConnectionService;
+
+    /**
+     * The connection to the conference server which is hosting the conference.
+     */
+    private TelephonyConnection mConferenceHost;
+
+    /**
+     * The known conference participant connections.  The HashMap is keyed by endpoint Uri.
+     * A {@link ConcurrentHashMap} is used as there is a possibility for radio events impacting the
+     * available participants to occur at the same time as an access via the connection service.
+     */
+    private final ConcurrentHashMap<Uri, ConferenceParticipantConnection>
+            mConferenceParticipantConnections =
+                    new ConcurrentHashMap<Uri, ConferenceParticipantConnection>(8, 0.9f, 1);
+
+    /**
+     * Initializes a new {@link ImsConference}.
+     *
+     * @param telephonyConnectionService The connection service responsible for adding new
+     *                                   conferene participants.
+     * @param conferenceHost The IMS radio connection hosting the conference.
+     */
+    public ImsConference(TelephonyConnectionService telephonyConnectionService,
+            com.android.internal.telephony.Connection conferenceHost) {
+
+        super(null);
+        mTelephonyConnectionService = telephonyConnectionService;
+        setConferenceHost(conferenceHost);
+        setCapabilities(
+                PhoneCapabilities.SUPPORT_HOLD |
+                        PhoneCapabilities.HOLD |
+                        PhoneCapabilities.MUTE
+        );
+    }
+
+    /**
+     * Not used by the IMS conference controller.
+     *
+     * @return {@code Null}.
+     */
+    @Override
+    public android.telecom.Connection getPrimaryConnection() {
+        return null;
+    }
+
+    /**
+     * Invoked when the Conference and all its {@link Connection}s should be disconnected.
+     * <p>
+     * Hangs up the call via the conference host connection.  When the host connection has been
+     * successfully disconnected, the {@link #mConferenceHostListener} listener receives an
+     * {@code onDestroyed} event, which triggers the conference participant connections to be
+     * disconnected.
+     */
+    @Override
+    public void onDisconnect() {
+        Log.v(this, "onDisconnect: hanging up conference host.");
+        if (mConferenceHost == null) {
+            return;
+        }
+
+        Call call = mConferenceHost.getCall();
+        if (call != null) {
+            try {
+                call.hangup();
+            } catch (CallStateException e) {
+                Log.e(this, e, "Exception thrown trying to hangup conference");
+            }
+        }
+    }
+
+    /**
+     * Invoked when the specified {@link android.telecom.Connection} should be separated from the
+     * conference call.
+     * <p>
+     * IMS does not support separating connections from the conference.
+     *
+     * @param connection The connection to separate.
+     */
+    @Override
+    public void onSeparate(android.telecom.Connection connection) {
+        Log.wtf(this, "Cannot separate connections from an IMS conference.");
+    }
+
+    /**
+     * Invoked when the specified {@link android.telecom.Connection} should be merged into the
+     * conference call.
+     *
+     * @param connection The {@code Connection} to merge.
+     */
+    @Override
+    public void onMerge(android.telecom.Connection connection) {
+        try {
+            Phone phone = ((TelephonyConnection) connection).getPhone();
+            if (phone != null) {
+                phone.conference();
+            }
+        } catch (CallStateException e) {
+            Log.e(this, e, "Exception thrown trying to merge call into a conference");
+        }
+    }
+
+    /**
+     * Invoked when the conference should be put on hold.
+     */
+    @Override
+    public void onHold() {
+        if (mConferenceHost == null) {
+            return;
+        }
+        mConferenceHost.performHold();
+    }
+
+    /**
+     * Invoked when the conference should be moved from hold to active.
+     */
+    @Override
+    public void onUnhold() {
+        if (mConferenceHost == null) {
+            return;
+        }
+        mConferenceHost.performUnhold();
+    }
+
+    /**
+     * Invoked to play a DTMF tone.
+     *
+     * @param c A DTMF character.
+     */
+    @Override
+    public void onPlayDtmfTone(char c) {
+        if (mConferenceHost == null) {
+            return;
+        }
+        mConferenceHost.onPlayDtmfTone(c);
+    }
+
+    /**
+     * Invoked to stop playing a DTMF tone.
+     */
+    @Override
+    public void onStopDtmfTone() {
+        if (mConferenceHost == null) {
+            return;
+        }
+        mConferenceHost.onStopDtmfTone();
+    }
+
+    /**
+     * Handles the addition of connections to the {@link ImsConference}.  The
+     * {@link ImsConferenceController} does not add connections to the conference.
+     *
+     * @param connection The newly added connection.
+     */
+    @Override
+    public void onConnectionAdded(android.telecom.Connection connection) {
+        // No-op
+    }
+
+    /**
+     * Updates the manage conference capability of the conference.  Where there are one or more
+     * conference event package participants, the conference management is permitted.  Where there
+     * are no conference event package participants, conference management is not permitted.
+     */
+    private void updateManageConference() {
+        boolean couldManageConference = PhoneCapabilities.can(getCapabilities(),
+                PhoneCapabilities.MANAGE_CONFERENCE);
+        boolean canManageConference = !mConferenceParticipantConnections.isEmpty();
+        Log.v(this, "updateManageConference was:%s is:%s", couldManageConference ? "Y" : "N",
+                canManageConference ? "Y" : "N");
+
+        if (couldManageConference != canManageConference) {
+            int newCapabilities = getCapabilities();
+
+            if (canManageConference) {
+                newCapabilities |= PhoneCapabilities.MANAGE_CONFERENCE;
+            } else {
+                newCapabilities = PhoneCapabilities.remove(newCapabilities,
+                        PhoneCapabilities.MANAGE_CONFERENCE);
+            }
+            setCapabilities(newCapabilities);
+        }
+    }
+
+    /**
+     * Sets the connection hosting the conference and registers for callbacks.
+     *
+     * @param conferenceHost The connection hosting the conference.
+     */
+    private void setConferenceHost(com.android.internal.telephony.Connection conferenceHost) {
+        if (Log.VERBOSE) {
+            Log.v(this, "setConferenceHost " + conferenceHost);
+        }
+
+        mConferenceHost = new ConferenceHostConnection(conferenceHost);
+        mConferenceHost.addConnectionListener(mConferenceHostListener);
+        mConferenceHost.addTelephonyConnectionListener(mTelephonyConnectionListener);
+    }
+
+    /**
+     * Handles state changes for conference participant(s).  The participants data passed in
+     *
+     * @param parent The connection which was notified of the conference participant.
+     * @param participants The conference participant information.
+     */
+    private void handleConferenceParticipantsUpdate(
+            TelephonyConnection parent, List<ConferenceParticipant> participants) {
+
+        boolean newParticipantsAdded = false;
+        boolean oldParticipantsRemoved = false;
+        ArrayList<ConferenceParticipant> newParticipants = new ArrayList<>(participants.size());
+        HashSet<Uri> participantEndpoints = new HashSet<>(participants.size());
+
+        // Add any new participants and update existing.
+        for (ConferenceParticipant participant : participants) {
+            Uri endpoint = participant.getEndpoint();
+            participantEndpoints.add(endpoint);
+            if (!mConferenceParticipantConnections.containsKey(endpoint)) {
+                createConferenceParticipantConnection(parent, participant);
+                newParticipants.add(participant);
+                newParticipantsAdded = true;
+            } else {
+                ConferenceParticipantConnection connection =
+                        mConferenceParticipantConnections.get(endpoint);
+                connection.updateState(participant.getState());
+            }
+        }
+
+        // Set state of new participants.
+        if (newParticipantsAdded) {
+            // Set the state of the new participants at once and add to the conference
+            for (ConferenceParticipant newParticipant : newParticipants) {
+                ConferenceParticipantConnection connection =
+                        mConferenceParticipantConnections.get(newParticipant.getEndpoint());
+                connection.updateState(newParticipant.getState());
+            }
+        }
+
+        // Finally, remove any participants from the conference that no longer exist in the
+        // conference event package data.
+        Iterator<Map.Entry<Uri, ConferenceParticipantConnection>> entryIterator =
+                mConferenceParticipantConnections.entrySet().iterator();
+        while (entryIterator.hasNext()) {
+            Map.Entry<Uri, ConferenceParticipantConnection> entry = entryIterator.next();
+
+            if (!participantEndpoints.contains(entry.getKey())) {
+                ConferenceParticipantConnection participant = entry.getValue();
+                participant.removeConnectionListener(mParticipantListener);
+                removeConnection(participant);
+                entryIterator.remove();
+                oldParticipantsRemoved = true;
+            }
+        }
+
+        // If new participants were added or old ones were removed, we need to ensure the state of
+        // the manage conference capability is updated.
+        if (newParticipantsAdded || oldParticipantsRemoved) {
+            updateManageConference();
+        }
+    }
+
+    /**
+     * Creates a new {@link ConferenceParticipantConnection} to represent a
+     * {@link ConferenceParticipant}.
+     * <p>
+     * The new connection is added to the conference controller and connection service.
+     *
+     * @param parent The connection which was notified of the participant change (e.g. the
+     *                         parent connection).
+     * @param participant The conference participant information.
+     */
+    private void createConferenceParticipantConnection(
+            TelephonyConnection parent, ConferenceParticipant participant) {
+
+        // Create and add the new connection in holding state so that it does not become the
+        // active call.
+        ConferenceParticipantConnection connection = new ConferenceParticipantConnection(
+                parent.getOriginalConnection(), participant);
+        connection.addConnectionListener(mParticipantListener);
+
+        if (Log.VERBOSE) {
+            Log.v(this, "createConferenceParticipantConnection: %s", connection);
+        }
+
+        mConferenceParticipantConnections.put(participant.getEndpoint(), connection);
+        PhoneAccountHandle phoneAccountHandle =
+                TelecomAccountRegistry.makePstnPhoneAccountHandle(parent.getPhone());
+        mTelephonyConnectionService.addExistingConnection(phoneAccountHandle, connection);
+        addConnection(connection);
+    }
+
+    /**
+     * Removes a conference participant from the conference.
+     *
+     * @param participant The participant to remove.
+     */
+    private void removeConferenceParticipant(ConferenceParticipantConnection participant) {
+        if (Log.VERBOSE) {
+            Log.v(this, "removeConferenceParticipant: %s", participant);
+        }
+
+        participant.removeConnectionListener(mParticipantListener);
+        participant.getEndpoint();
+        mConferenceParticipantConnections.remove(participant.getEndpoint());
+    }
+
+    /**
+     * Disconnects all conference participants from the conference.
+     */
+    private void disconnectConferenceParticipants() {
+        Log.v(this, "disconnectConferenceParticipants");
+
+        for (ConferenceParticipantConnection connection :
+                mConferenceParticipantConnections.values()) {
+
+            removeConferenceParticipant(connection);
+
+            // Mark disconnect cause as cancelled to ensure that the call is not logged in the
+            // call log.
+            connection.setDisconnected(new DisconnectCause(DisconnectCause.CANCELED));
+            connection.destroy();
+        }
+        mConferenceParticipantConnections.clear();
+    }
+
+    /**
+     * Handles a change in the original connection backing the conference host connection.  This can
+     * happen if an SRVCC event occurs on the original IMS connection, requiring a fallback to
+     * GSM or CDMA.
+     * <p>
+     * If this happens, we will add the conference host connection to telecom and tear down the
+     * conference.
+     */
+    private void handleOriginalConnectionChange() {
+        if (mConferenceHost == null) {
+            Log.w(this, "handleOriginalConnectionChange; conference host missing.");
+            return;
+        }
+
+        com.android.internal.telephony.Connection originalConnection =
+                mConferenceHost.getOriginalConnection();
+
+        if (!(originalConnection instanceof ImsPhoneConnection)) {
+            if (Log.VERBOSE) {
+                Log.v(this,
+                        "Original connection for conference host is no longer an IMS connection; " +
+                                "new connection: %s", originalConnection);
+            }
+
+            PhoneAccountHandle phoneAccountHandle =
+                    TelecomAccountRegistry.makePstnPhoneAccountHandle(mConferenceHost.getPhone());
+            mTelephonyConnectionService.addExistingConnection(phoneAccountHandle, mConferenceHost);
+            mConferenceHost.removeConnectionListener(mConferenceHostListener);
+            mConferenceHost.removeTelephonyConnectionListener(mTelephonyConnectionListener);
+            mConferenceHost = null;
+            setDisconnected(new DisconnectCause(DisconnectCause.OTHER));
+            destroy();
+        }
+    }
+
+    /**
+     * Changes the state of the Ims conference.
+     *
+     * @param state the new state.
+     */
+    public void setState(int state) {
+        Log.v(this, "setState %s", Connection.stateToString(state));
+
+        switch (state) {
+            case Connection.STATE_INITIALIZING:
+            case Connection.STATE_NEW:
+            case Connection.STATE_RINGING:
+            case Connection.STATE_DIALING:
+                // No-op -- not applicable.
+                break;
+            case Connection.STATE_DISCONNECTED:
+                DisconnectCause disconnectCause;
+                if (mConferenceHost == null) {
+                    disconnectCause = new DisconnectCause(DisconnectCause.CANCELED);
+                } else {
+                    disconnectCause = DisconnectCauseUtil.toTelecomDisconnectCause(
+                            mConferenceHost.getOriginalConnection().getDisconnectCause());
+                }
+                setDisconnected(disconnectCause);
+                destroy();
+                break;
+            case Connection.STATE_ACTIVE:
+                setActive();
+                break;
+            case Connection.STATE_HOLDING:
+                setOnHold();
+                break;
+        }
+    }
+
+    /**
+     * Builds a string representation of the {@link ImsConference}.
+     *
+     * @return String representing the conference.
+     */
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("[ImsConference objId:");
+        sb.append(System.identityHashCode(this));
+        sb.append(" state:");
+        sb.append(Connection.stateToString(getState()));
+        sb.append(" hostConnection:");
+        sb.append(mConferenceHost);
+        sb.append(" participants:");
+        sb.append(mConferenceParticipantConnections.size());
+        sb.append("]");
+        return sb.toString();
+    }
+}
diff --git a/src/com/android/services/telephony/ImsConferenceController.java b/src/com/android/services/telephony/ImsConferenceController.java
new file mode 100644
index 0000000..e924687
--- /dev/null
+++ b/src/com/android/services/telephony/ImsConferenceController.java
@@ -0,0 +1,275 @@
+/*
+ * 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.services.telephony;
+
+import android.net.Uri;
+import android.telecom.Conference;
+import android.telecom.Connection;
+import android.telecom.ConnectionService;
+import android.telecom.DisconnectCause;
+import android.telecom.IConferenceable;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Manages conferences for IMS connections.
+ */
+public class ImsConferenceController {
+
+    /**
+     * Conference listener; used to receive notification when a conference has been disconnected.
+     */
+    private final Conference.Listener mConferenceListener = new Conference.Listener() {
+        @Override
+        public void onDestroyed(Conference conference) {
+            if (Log.VERBOSE) {
+                Log.v(ImsConferenceController.class, "onDestroyed: %s", conference);
+            }
+
+            mImsConferences.remove(conference);
+        }
+    };
+
+    /**
+     * Ims conference controller connection listener.  Used to respond to changes in state of the
+     * Telephony connections the controller is aware of.
+     */
+    private final Connection.Listener mConnectionListener = new Connection.Listener() {
+        @Override
+        public void onStateChanged(Connection c, int state) {
+            Log.v(this, "onStateChanged: %s", Log.pii(c.getAddress()));
+            recalculate();
+        }
+
+        @Override
+        public void onDisconnected(Connection c, DisconnectCause disconnectCause) {
+            Log.v(this, "onDisconnected: %s", Log.pii(c.getAddress()));
+            recalculate();
+        }
+
+        @Override
+        public void onDestroyed(Connection connection) {
+            remove(connection);
+        }
+    };
+
+    /**
+     * The current {@link ConnectionService}.
+     */
+    private final TelephonyConnectionService mConnectionService;
+
+    /**
+     * List of known {@link TelephonyConnection}s.
+     */
+    private final ArrayList<TelephonyConnection> mTelephonyConnections = new ArrayList<>();
+
+    /**
+     * List of known {@link ImsConference}s.  Realistically there will only ever be a single
+     * concurrent IMS conference.
+     */
+    private final ArrayList<ImsConference> mImsConferences = new ArrayList<>(1);
+
+    /**
+     * Creates a new instance of the Ims conference controller.
+     *
+     * @param connectionService The current connection service.
+     */
+    public ImsConferenceController(TelephonyConnectionService connectionService) {
+        mConnectionService = connectionService;
+    }
+
+    /**
+     * Adds a new connection to the IMS conference controller.
+     *
+     * @param connection
+     */
+    void add(TelephonyConnection connection) {
+        // Note: Wrap in Log.VERBOSE to avoid calling connection.toString if we are not going to be
+        // outputting the value.
+        if (Log.VERBOSE) {
+            Log.v(this, "add connection %s", connection);
+        }
+
+        mTelephonyConnections.add(connection);
+        connection.addConnectionListener(mConnectionListener);
+        recalculateConference();
+    }
+
+    /**
+     * Removes a connection from the IMS conference controller.
+     *
+     * @param connection
+     */
+    void remove(Connection connection) {
+        if (Log.VERBOSE) {
+            Log.v(this, "remove connection: %s", connection);
+        }
+
+        mTelephonyConnections.remove(connection);
+        recalculateConferenceable();
+    }
+
+    /**
+     * Triggers both a re-check of conferenceable connections, as well as checking for new
+     * conferences.
+     */
+    private void recalculate() {
+        recalculateConferenceable();
+        recalculateConference();
+    }
+
+    /**
+     * Calculates the conference-capable state of all GSM connections in this connection service.
+     */
+    private void recalculateConferenceable() {
+        Log.v(this, "recalculateConferenceable : %d", mTelephonyConnections.size());
+        List<IConferenceable> activeConnections = new ArrayList<>(mTelephonyConnections.size());
+        List<IConferenceable> backgroundConnections = new ArrayList<>(mTelephonyConnections.size());
+
+        // Loop through and collect all calls which are active or holding
+        for (Connection connection : mTelephonyConnections) {
+            if (Log.DEBUG) {
+                Log.d(this, "recalc - %s %s", connection.getState(), connection);
+            }
+
+            switch (connection.getState()) {
+                case Connection.STATE_ACTIVE:
+                    activeConnections.add(connection);
+                    continue;
+                case Connection.STATE_HOLDING:
+                    backgroundConnections.add(connection);
+                    continue;
+                default:
+                    break;
+            }
+            connection.setConferenceableConnections(Collections.<Connection>emptyList());
+        }
+
+        for (Conference conference : mImsConferences) {
+            if (Log.DEBUG) {
+                Log.d(this, "recalc - %s %s", conference.getState(), conference);
+            }
+
+            switch (conference.getState()) {
+                case Connection.STATE_ACTIVE:
+                    activeConnections.add(conference);
+                    continue;
+                case Connection.STATE_HOLDING:
+                    backgroundConnections.add(conference);
+                    continue;
+                default:
+                    break;
+            }
+        }
+
+        Log.v(this, "active: %d, holding: %d", activeConnections.size(),
+                backgroundConnections.size());
+
+        // Go through all the active connections and set the background connections as
+        // conferenceable.
+        for (IConferenceable conferenceable : activeConnections) {
+            if (conferenceable instanceof Connection) {
+                Connection connection = (Connection) conferenceable;
+                connection.setConferenceables(backgroundConnections);
+            }
+        }
+
+        // Go through all the background connections and set the active connections as
+        // conferenceable.
+        for (IConferenceable conferenceable : backgroundConnections) {
+            if (conferenceable instanceof Connection) {
+                Connection connection = (Connection) conferenceable;
+                connection.setConferenceables(activeConnections);
+            }
+
+        }
+
+        // Set the conference as conferenceable with all the connections
+        for (ImsConference conference : mImsConferences) {
+            List<Connection> nonConferencedConnections =
+                new ArrayList<>(mTelephonyConnections.size());
+            for (Connection c : mTelephonyConnections) {
+                if (c.getConference() == null) {
+                    nonConferencedConnections.add(c);
+                }
+            }
+            if (Log.VERBOSE) {
+                Log.v(this, "conference conferenceable: %s", nonConferencedConnections);
+            }
+            conference.setConferenceableConnections(nonConferencedConnections);
+        }
+    }
+
+    /**
+     * Starts a new ImsConference for a connection which just entered a multiparty state.
+     */
+    private void recalculateConference() {
+        Log.v(this, "recalculateConference");
+
+        Iterator<TelephonyConnection> it = mTelephonyConnections.iterator();
+        while (it.hasNext()) {
+            TelephonyConnection connection = it.next();
+
+            if (connection.isImsConnection() && connection.getOriginalConnection() != null &&
+                    connection.getOriginalConnection().isMultiparty()) {
+
+                startConference(connection);
+                it.remove();
+            }
+        }
+    }
+
+    /**
+     * Starts a new {@link ImsConference} for the given IMS connection.
+     * <p>
+     * Creates a new IMS Conference to manage the conference represented by the connection.
+     * Internally the ImsConference wraps the radio connection with a new TelephonyConnection
+     * which is NOT reported to the connection service and Telecom.
+     * <p>
+     * Once the new IMS Conference has been created, the connection passed in is held and removed
+     * from the connection service (removing it from Telecom).  The connection is put into a held
+     * state to ensure that telecom removes the connection without putting it into a disconnected
+     * state first.
+     *
+     * @param connection The connection to the Ims server.
+     */
+    private void startConference(TelephonyConnection connection) {
+        com.android.internal.telephony.Connection originalConnection =
+                connection.getOriginalConnection();
+        if (Log.VERBOSE) {
+            Log.v(this, "Start new ImsConference - connection: %s", connection);
+        }
+
+        // Create conference and add to telecom
+        ImsConference conference = new ImsConference(mConnectionService, originalConnection);
+        conference.setState(connection.getState());
+        mConnectionService.addConference(conference);
+        conference.addListener(mConferenceListener);
+
+        // Cleanup TelephonyConnection which backed the original connection and remove from telecom.
+        // Use the "Other" disconnect cause to ensure the call is logged to the call log but the
+        // disconnect tone is not played.
+        connection.removeConnectionListener(mConnectionListener);
+        connection.clearOriginalConnection();
+        connection.setDisconnected(new DisconnectCause(DisconnectCause.OTHER));
+        connection.destroy();
+        mImsConferences.add(conference);
+    }
+}
diff --git a/src/com/android/services/telephony/TelephonyConference.java b/src/com/android/services/telephony/TelephonyConference.java
index fc009bc..3a80097 100644
--- a/src/com/android/services/telephony/TelephonyConference.java
+++ b/src/com/android/services/telephony/TelephonyConference.java
@@ -33,12 +33,6 @@
  */
 public class TelephonyConference extends Conference {
 
-    /**
-     * When {@code true}, indicates that conference participant information from an IMS conference
-     * event package has been received.
-     */
-    private boolean mParticipantsReceived = false;
-
     public TelephonyConference(PhoneAccountHandle phoneAccount) {
         super(phoneAccount);
         setCapabilities(
@@ -55,20 +49,33 @@
     @Override
     public void onDisconnect() {
         for (Connection connection : getConnections()) {
-            Call call = getMultipartyCallForConnection(connection, "onDisconnect");
-            if (call != null) {
-                Log.d(this, "Found multiparty call to hangup for conference.");
-                try {
-                    call.hangup();
-                    break;
-                } catch (CallStateException e) {
-                    Log.e(this, e, "Exception thrown trying to hangup conference");
-                }
+            if (disconnectCall(connection)) {
+                break;
             }
         }
     }
 
     /**
+     * Disconnect the underlying Telephony Call for a connection.
+     *
+     * @param connection The connection.
+     * @return {@code True} if the call was disconnected.
+     */
+    private boolean disconnectCall(Connection connection) {
+        Call call = getMultipartyCallForConnection(connection, "onDisconnect");
+        if (call != null) {
+            Log.d(this, "Found multiparty call to hangup for conference.");
+            try {
+                call.hangup();
+                return true;
+            } catch (CallStateException e) {
+                Log.e(this, e, "Exception thrown trying to hangup conference");
+            }
+        }
+        return false;
+    }
+
+    /**
      * Invoked when the specified {@link Connection} should be separated from the conference call.
      *
      * @param connection The connection to separate.
@@ -140,8 +147,7 @@
         // as the default behavior. If there is a conference event package, this may be overridden.
         // If a conference event package was received, do not attempt to remove manage conference.
         if (connection instanceof TelephonyConnection &&
-                ((TelephonyConnection) connection).wasImsConnection() &&
-                !mParticipantsReceived) {
+                ((TelephonyConnection) connection).wasImsConnection()) {
             int capabilities = getCapabilities();
             if (PhoneCapabilities.can(capabilities, PhoneCapabilities.MANAGE_CONFERENCE)) {
                 int newCapabilities =
@@ -153,11 +159,17 @@
 
     @Override
     public Connection getPrimaryConnection() {
+
+        List<Connection> connections = getConnections();
+        if (connections == null || connections.isEmpty()) {
+            return null;
+        }
+
         // Default to the first connection.
-        Connection primaryConnection = getConnections().get(0);
+        Connection primaryConnection = connections.get(0);
 
         // Otherwise look for a connection where the radio connection states it is multiparty.
-        for (Connection connection : getConnections()) {
+        for (Connection connection : connections) {
             com.android.internal.telephony.Connection radioConnection =
                     getOriginalConnection(connection);
 
@@ -182,7 +194,7 @@
         return null;
     }
 
-    private com.android.internal.telephony.Connection getOriginalConnection(
+    protected com.android.internal.telephony.Connection getOriginalConnection(
             Connection connection) {
 
         if (connection instanceof TelephonyConnection) {
@@ -199,17 +211,4 @@
         }
         return (TelephonyConnection) connections.get(0);
     }
-
-    /**
-     * Flags the conference to indicate that a conference event package has been received and there
-     * is now participant data present which would permit conference management.
-     */
-    public void setParticipantsReceived() {
-        if (!mParticipantsReceived) {
-            int capabilities = getCapabilities();
-            capabilities |= PhoneCapabilities.MANAGE_CONFERENCE;
-            setCapabilities(capabilities);
-        }
-        mParticipantsReceived = true;
-    }
 }
diff --git a/src/com/android/services/telephony/TelephonyConferenceController.java b/src/com/android/services/telephony/TelephonyConferenceController.java
index 55c8338..2af10a6 100644
--- a/src/com/android/services/telephony/TelephonyConferenceController.java
+++ b/src/com/android/services/telephony/TelephonyConferenceController.java
@@ -59,36 +59,11 @@
         public void onDestroyed(Connection connection) {
             remove(connection);
         }
-
-        /**
-         * Handles notifications from an connection that participant(s) in a conference have changed
-         * state.
-         *
-         * @param c The connection.
-         * @param participants The participant information.
-         */
-        @Override
-        public void onConferenceParticipantsChanged(Connection c,
-                List<ConferenceParticipant> participants) {
-
-            if (c == null) {
-                return;
-            }
-            Log.v(this, "onConferenceParticipantsChanged: %d participants", participants.size());
-            TelephonyConnection telephonyConnection = (TelephonyConnection) c;
-            handleConferenceParticipantsUpdate(telephonyConnection, participants);
-        }
     };
 
     /** The known connections. */
     private final List<TelephonyConnection> mTelephonyConnections = new ArrayList<>();
 
-    /**
-     * The known conference participant connections.  The HashMap is keyed by endpoint Uri.
-     */
-    private final HashMap<Uri, ConferenceParticipantConnection> mConferenceParticipantConnections =
-            new HashMap<>();
-
     private final TelephonyConnectionService mConnectionService;
 
     public TelephonyConferenceController(TelephonyConnectionService connectionService) {
@@ -106,12 +81,7 @@
 
     void remove(Connection connection) {
         connection.removeConnectionListener(mConnectionListener);
-
-        if (connection instanceof ConferenceParticipantConnection) {
-            mConferenceParticipantConnections.remove(connection);
-        } else {
-            mTelephonyConnections.remove(connection);
-        }
+        mTelephonyConnections.remove(connection);
 
         recalculate();
     }
@@ -192,9 +162,7 @@
 
     private void recalculateConference() {
         Set<Connection> conferencedConnections = new HashSet<>();
-
         int numGsmConnections = 0;
-        int numImsConnections = 0;
 
         for (TelephonyConnection connection : mTelephonyConnections) {
             com.android.internal.telephony.Connection radioConnection =
@@ -206,11 +174,7 @@
                 if ((state == Call.State.ACTIVE || state == Call.State.HOLDING) &&
                         (call != null && call.isMultiparty())) {
 
-                    if (radioConnection instanceof GsmConnection) {
-                        numGsmConnections++;
-                    } else if (radioConnection instanceof ImsPhoneConnection) {
-                        numImsConnections++;
-                    }
+                    numGsmConnections++;
                     conferencedConnections.add(connection);
                 }
             }
@@ -219,18 +183,11 @@
         Log.d(this, "Recalculate conference calls %s %s.",
                 mTelephonyConference, conferencedConnections);
 
-        boolean wasParticipantsAdded = false;
-
-        // If the number of telephony connections drops below the limit, the conference can be
-        // considered terminated.
-        // We must have less than 2 GSM connections and less than 1 IMS connection.
-        if (numGsmConnections < 2 && numImsConnections < 1) {
+        // If this is a GSM conference and the number of connections drops below 2, we will
+        // terminate the conference.
+        if (numGsmConnections < 2) {
             Log.d(this, "not enough connections to be a conference!");
 
-            // The underlying telephony connections have been disconnected -- disconnect the
-            // conference participants now.
-            disconnectConferenceParticipants();
-
             // No more connections are conferenced, destroy any existing conference.
             if (mTelephonyConference != null) {
                 Log.d(this, "with a conference to destroy!");
@@ -254,131 +211,30 @@
                         mTelephonyConference.addConnection(connection);
                     }
                 }
-
-                // Add new conference participants
-                for (Connection conferenceParticipant :
-                        mConferenceParticipantConnections.values()) {
-
-                    if (conferenceParticipant.getState() == Connection.STATE_NEW) {
-                        if (!existingConnections.contains(conferenceParticipant)) {
-                            wasParticipantsAdded = true;
-                            mTelephonyConference.addConnection(conferenceParticipant);
-                        }
-                    }
-                }
             } else {
                 mTelephonyConference = new TelephonyConference(null);
+
                 for (Connection connection : conferencedConnections) {
                     Log.d(this, "Adding a connection to a conference call: %s %s",
                             mTelephonyConference, connection);
                     mTelephonyConference.addConnection(connection);
                 }
 
-                // Add the conference participants
-                for (Connection conferenceParticipant :
-                        mConferenceParticipantConnections.values()) {
-                    wasParticipantsAdded = true;
-                    mTelephonyConference.addConnection(conferenceParticipant);
-                }
-
                 mConnectionService.addConference(mTelephonyConference);
             }
 
-            // If we added conference participants (e.g. via an IMS conference event package),
-            // notify the conference so that the MANAGE_CONFERENCE capability can be added.
-            if (wasParticipantsAdded) {
-                mTelephonyConference.setParticipantsReceived();
-            }
-
             // Set the conference state to the same state as its child connections.
             Connection conferencedConnection = mTelephonyConference.getPrimaryConnection();
-            switch (conferencedConnection.getState()) {
-                case Connection.STATE_ACTIVE:
-                    mTelephonyConference.setActive();
-                    break;
-                case Connection.STATE_HOLDING:
-                    mTelephonyConference.setOnHold();
-                    break;
+            if (conferencedConnection != null) {
+                switch (conferencedConnection.getState()) {
+                    case Connection.STATE_ACTIVE:
+                        mTelephonyConference.setActive();
+                        break;
+                    case Connection.STATE_HOLDING:
+                        mTelephonyConference.setOnHold();
+                        break;
+                }
             }
         }
     }
-
-    /**
-     * Disconnects all conference participants from the conference.
-     */
-    private void disconnectConferenceParticipants() {
-        for (Connection connection : mConferenceParticipantConnections.values()) {
-            // Disconnect listener so that the connection doesn't fire events on the conference
-            // controller, causing a recursive call.
-            connection.removeConnectionListener(mConnectionListener);
-            mConferenceParticipantConnections.remove(connection);
-
-            // Mark disconnect cause as cancelled to ensure that the call is not logged in the
-            // call log.
-            connection.setDisconnected(new DisconnectCause(DisconnectCause.CANCELED));
-            connection.destroy();
-        }
-    }
-
-    /**
-     * Handles state changes for conference participant(s).
-     *
-     * @param parent The connection which was notified of the conference participant.
-     * @param participants The conference participant information.
-     */
-    private void handleConferenceParticipantsUpdate(
-            TelephonyConnection parent, List<ConferenceParticipant> participants) {
-
-        boolean recalculateConference = false;
-        ArrayList<ConferenceParticipant> newParticipants = new ArrayList<>(participants.size());
-
-        for (ConferenceParticipant participant : participants) {
-            Uri endpoint = participant.getEndpoint();
-            if (!mConferenceParticipantConnections.containsKey(endpoint)) {
-                createConferenceParticipantConnection(parent, participant);
-                newParticipants.add(participant);
-                recalculateConference = true;
-            } else {
-                ConferenceParticipantConnection connection =
-                        mConferenceParticipantConnections.get(endpoint);
-                connection.updateState(participant.getState());
-            }
-        }
-
-        if (recalculateConference) {
-            // Recalculate to add new connections to the conference.
-            recalculateConference();
-
-            // Now that conference is established, set the state for all participants.
-            for (ConferenceParticipant newParticipant : newParticipants) {
-                ConferenceParticipantConnection connection =
-                        mConferenceParticipantConnections.get(newParticipant.getEndpoint());
-                connection.updateState(newParticipant.getState());
-            }
-        }
-    }
-
-    /**
-     * Creates a new {@link ConferenceParticipantConnection} to represent a
-     * {@link ConferenceParticipant}.
-     * <p>
-     * The new connection is added to the conference controller and connection service.
-     *
-     * @param parent The connection which was notified of the participant change (e.g. the
-     *                         parent connection).
-     * @param participant The conference participant information.
-     */
-    private void createConferenceParticipantConnection(
-            TelephonyConnection parent, ConferenceParticipant participant) {
-
-        // Create and add the new connection in holding state so that it does not become the
-        // active call.
-        ConferenceParticipantConnection connection = new ConferenceParticipantConnection(
-                parent, participant);
-        connection.addConnectionListener(mConnectionListener);
-        mConferenceParticipantConnections.put(participant.getEndpoint(), connection);
-        PhoneAccountHandle phoneAccountHandle =
-                 TelecomAccountRegistry.makePstnPhoneAccountHandle(parent.getPhone());
-        mConnectionService.addExistingConnection(phoneAccountHandle, connection);
-    }
 }
diff --git a/src/com/android/services/telephony/TelephonyConnection.java b/src/com/android/services/telephony/TelephonyConnection.java
index 056f62f..77a76d0 100644
--- a/src/com/android/services/telephony/TelephonyConnection.java
+++ b/src/com/android/services/telephony/TelephonyConnection.java
@@ -24,6 +24,7 @@
 import android.telecom.Conference;
 import android.telecom.ConferenceParticipant;
 import android.telecom.Connection;
+import android.telecom.DisconnectCause;
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneCapabilities;
 
@@ -31,6 +32,9 @@
 import com.android.internal.telephony.CallStateException;
 import com.android.internal.telephony.Connection.PostDialListener;
 import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.cdma.CdmaCall;
+import com.android.internal.telephony.gsm.*;
+import com.android.internal.telephony.gsm.GsmConnection;
 import com.android.internal.telephony.imsphone.ImsPhoneConnection;
 
 import java.lang.Override;
@@ -442,12 +446,8 @@
 
     void setOriginalConnection(com.android.internal.telephony.Connection originalConnection) {
         Log.v(this, "new TelephonyConnection, originalConnection: " + originalConnection);
-        if (mOriginalConnection != null) {
-            getPhone().unregisterForPreciseCallStateChanged(mHandler);
-            getPhone().unregisterForRingbackTone(mHandler);
-            getPhone().unregisterForHandoverStateChanged(mHandler);
-            getPhone().unregisterForDisconnect(mHandler);
-        }
+        clearOriginalConnection();
+
         mOriginalConnection = originalConnection;
         getPhone().registerForPreciseCallStateChanged(
                 mHandler, MSG_PRECISE_CALL_STATE_CHANGED, null);
@@ -473,6 +473,19 @@
         updateAddress();
     }
 
+    /**
+     * Un-sets the underlying radio connection.
+     */
+    void clearOriginalConnection() {
+        if (mOriginalConnection != null) {
+            getPhone().unregisterForPreciseCallStateChanged(mHandler);
+            getPhone().unregisterForRingbackTone(mHandler);
+            getPhone().unregisterForHandoverStateChanged(mHandler);
+            getPhone().unregisterForDisconnect(mHandler);
+            mOriginalConnection = null;
+        }
+    }
+
     protected void hangup(int telephonyDisconnectCode) {
         if (mOriginalConnection != null) {
             try {
@@ -871,4 +884,41 @@
             l.onOriginalConnectionConfigured(this);
         }
     }
+
+    /**
+     * Creates a string representation of this {@link TelephonyConnection}.  Primarily intended for
+     * use in log statements.
+     *
+     * @return String representation of the connection.
+     */
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("[TelephonyConnection objId:");
+        sb.append(System.identityHashCode(this));
+        sb.append(" type:");
+        if (isImsConnection()) {
+            sb.append("ims");
+        } else if (this instanceof com.android.services.telephony.GsmConnection) {
+            sb.append("gsm");
+        } else if (this instanceof CdmaConnection) {
+            sb.append("cdma");
+        }
+        sb.append(" state:");
+        sb.append(Connection.stateToString(getState()));
+        sb.append(" capabilities:");
+        sb.append(PhoneCapabilities.toString(getCallCapabilities()));
+        sb.append(" address:");
+        sb.append(Log.pii(getAddress()));
+        sb.append(" originalConnection:");
+        sb.append(mOriginalConnection);
+        sb.append(" partOfConf:");
+        if (getConference() == null) {
+            sb.append("N");
+        } else {
+            sb.append("Y");
+        }
+        sb.append("]");
+        return sb.toString();
+    }
 }
diff --git a/src/com/android/services/telephony/TelephonyConnectionService.java b/src/com/android/services/telephony/TelephonyConnectionService.java
index 8ef9ae8..7ce5005 100644
--- a/src/com/android/services/telephony/TelephonyConnectionService.java
+++ b/src/com/android/services/telephony/TelephonyConnectionService.java
@@ -52,6 +52,8 @@
             new TelephonyConferenceController(this);
     private final CdmaConferenceController mCdmaConferenceController =
             new CdmaConferenceController(this);
+    private final ImsConferenceController mImsConferenceController =
+            new ImsConferenceController(this);
     private ComponentName mExpectedComponentName = null;
     private EmergencyCallHelper mEmergencyCallHelper;
     private EmergencyTonePlayer mEmergencyTonePlayer;
@@ -448,7 +450,7 @@
         // conference controllers first before re-adding it.
         if (connection.isImsConnection()) {
             Log.d(this, "Adding IMS connection to conference controller: " + connection);
-            mTelephonyConferenceController.add(connection);
+            mImsConferenceController.add(connection);
         } else {
             int phoneType = connection.getCall().getPhone().getPhoneType();
             if (phoneType == TelephonyManager.PHONE_TYPE_GSM) {