Merge "Add hidden systemapi hasCarrierPrivileges(pkgname) to PhoneInterfaceManager."
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 4279616..f54b63a 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -466,9 +466,16 @@
             </intent-filter>
         </activity>
 
-        <service android:singleUser="true"
-                 android:name="com.android.services.telephony.sip.SipConnectionService"
-                 android:label="@string/sip_connection_service_label">
+        <!-- Start SIP -->
+        <service android:name="com.android.services.telephony.sip.SipCallServiceProvider"
+                 android:singleUser="true" >
+            <intent-filter>
+                <action android:name="android.telecomm.CallServiceProvider" />
+            </intent-filter>
+        </service>
+        <service android:name="com.android.services.telephony.sip.SipConnectionService"
+                 android:label="@string/sip_connection_service_label"
+                 android:singleUser="true" >
             <intent-filter>
                 <action android:name="android.telecomm.ConnectionService" />
             </intent-filter>
@@ -497,13 +504,15 @@
                 android:configChanges="orientation|screenSize|keyboardHidden"
                 android:uiOptions="splitActionBarWhenNarrow">
         </activity>
-        <activity android:name="com.android.services.telephony.SipCallOptionHandler"
+        <activity android:name="com.android.services.telephony.sip.SipProfileChooserDialogs"
                 android:theme="@style/SipCallOptionHandlerTheme"
                 android:screenOrientation="nosensor"
                 android:configChanges="orientation|screenSize|keyboardHidden"
                 android:excludeFromRecents="true">
         </activity>
 
+        <!-- End SIP -->
+
         <activity android:name="ErrorDialogActivity"
                 android:configChanges="orientation|screenSize|keyboardHidden"
                 android:excludeFromRecents="true"
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 0e0042b..aa60664 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1306,15 +1306,6 @@
          Do not translate. -->
     <string name="call_settings_title_font_family">sans-serif-light</string>
 
-    <!-- Title of the dialog to choose WiFi calling. -->
-    <string name ="choose_wifi_for_call_msg">Use Wi-Fi to place call?</string>
-
-    <!-- Title for button to use WiFi calling. -->
-    <string name ="choose_wifi_for_call_yes">Yes</string>
-
-    <!-- Title for button to not use WiFi calling. -->
-    <string name ="choose_wifi_for_call_no">No</string>
-
     <!-- Label for PSTN connection service. -->
     <string name="pstn_connection_service_label">Built-in SIM cards</string>
 
diff --git a/sip/src/com/android/services/telephony/sip/SipBroadcastReceiver.java b/sip/src/com/android/services/telephony/sip/SipBroadcastReceiver.java
index b949351..62b8266 100644
--- a/sip/src/com/android/services/telephony/sip/SipBroadcastReceiver.java
+++ b/sip/src/com/android/services/telephony/sip/SipBroadcastReceiver.java
@@ -16,18 +16,15 @@
 
 package com.android.services.telephony.sip;
 
-import com.android.internal.telephony.Phone;
-import com.android.internal.telephony.PhoneConstants;
-import com.android.internal.telephony.PhoneFactory;
-import com.android.internal.telephony.sip.SipPhone;
-
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
-import android.net.sip.SipAudioCall;
 import android.net.sip.SipException;
 import android.net.sip.SipManager;
 import android.net.sip.SipProfile;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.telecomm.TelecommConstants;
 import android.util.Log;
 
 import java.util.List;
@@ -59,7 +56,17 @@
 
     private void takeCall(Context context, Intent intent) {
         if (VERBOSE) log("takeCall, intent: " + intent);
-        // TODO(sail): Add support for incoming SIP calls.
+
+        Bundle extras = new Bundle();
+        extras.putParcelable(SipUtil.EXTRA_INCOMING_CALL_INTENT, intent);
+
+        Intent telecommIntent = new Intent(TelecommConstants.ACTION_INCOMING_CALL);
+        telecommIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        telecommIntent.putExtra(TelecommConstants.EXTRA_CALL_SERVICE_DESCRIPTOR,
+                SipCallServiceProvider.getDescriptor(context));
+        telecommIntent.putExtra(TelecommConstants.EXTRA_INCOMING_CALL_EXTRAS, extras);
+
+        context.startActivityAsUser(telecommIntent, UserHandle.CURRENT);
     }
 
     private void registerAllProfiles(final Context context) {
diff --git a/sip/src/com/android/services/telephony/sip/SipCallServiceProvider.java b/sip/src/com/android/services/telephony/sip/SipCallServiceProvider.java
new file mode 100644
index 0000000..cc467e9
--- /dev/null
+++ b/sip/src/com/android/services/telephony/sip/SipCallServiceProvider.java
@@ -0,0 +1,40 @@
+/*
+ * 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.sip;
+
+import android.content.Context;
+import android.telecomm.CallServiceDescriptor;
+import android.telecomm.CallServiceLookupResponse;
+import android.telecomm.CallServiceProvider;
+
+import java.util.ArrayList;
+
+public class SipCallServiceProvider extends CallServiceProvider {
+    @Override
+    public void lookupCallServices(CallServiceLookupResponse response) {
+        ArrayList<CallServiceDescriptor> descriptors = new ArrayList<CallServiceDescriptor>();
+        descriptors.add(getDescriptor(this));
+        response.setCallServiceDescriptors(descriptors);
+    }
+
+    static CallServiceDescriptor getDescriptor(Context context) {
+        return CallServiceDescriptor.newBuilder(context)
+                .setConnectionService(SipConnectionService.class)
+                .setNetworkType(CallServiceDescriptor.FLAG_WIFI | CallServiceDescriptor.FLAG_MOBILE)
+                .build();
+    }
+}
diff --git a/sip/src/com/android/services/telephony/sip/SipConnection.java b/sip/src/com/android/services/telephony/sip/SipConnection.java
index 40239a4..c6e1be6 100644
--- a/sip/src/com/android/services/telephony/sip/SipConnection.java
+++ b/sip/src/com/android/services/telephony/sip/SipConnection.java
@@ -16,26 +16,54 @@
 
 package com.android.services.telephony.sip;
 
+import android.os.Handler;
+import android.os.Message;
 import android.telecomm.CallAudioState;
+import android.telecomm.CallCapabilities;
 import android.telecomm.Connection;
 import android.util.Log;
 
+import com.android.internal.telephony.Call;
+import com.android.internal.telephony.CallStateException;
+import com.android.internal.telephony.sip.SipPhone;
+
 import java.util.List;
 
-public class SipConnection extends Connection {
+final class SipConnection extends Connection {
     private static final String PREFIX = "[SipConnection] ";
     private static final boolean VERBOSE = true; /* STOP SHIP if true */
 
-    private final com.android.internal.telephony.Connection mConnection;
+    private static final int MSG_PRECISE_CALL_STATE_CHANGED = 1;
 
-    public SipConnection(com.android.internal.telephony.Connection connection) {
+    private final Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_PRECISE_CALL_STATE_CHANGED:
+                    updateState();
+                    break;
+            }
+        }
+    };
+
+    private com.android.internal.telephony.Connection mOriginalConnection;
+    private Call.State mOriginalConnectionState = Call.State.IDLE;
+
+    SipConnection(com.android.internal.telephony.Connection connection) {
         if (VERBOSE) log("new SipConnection, connection: " + connection);
-        mConnection = connection;
+        mOriginalConnection = connection;
+        if (getPhone() != null) {
+            getPhone().registerForPreciseCallStateChanged(mHandler, MSG_PRECISE_CALL_STATE_CHANGED,
+                    null);
+        }
     }
 
     @Override
     protected void onSetAudioState(CallAudioState state) {
         if (VERBOSE) log("onSetAudioState: " + state);
+        if (getPhone() != null) {
+            getPhone().setEchoSuppressionEnabled();
+        }
     }
 
     @Override
@@ -46,51 +74,103 @@
     @Override
     protected void onPlayDtmfTone(char c) {
         if (VERBOSE) log("onPlayDtmfTone");
+        if (getPhone() != null) {
+            getPhone().startDtmf(c);
+        }
     }
 
     @Override
     protected void onStopDtmfTone() {
         if (VERBOSE) log("onStopDtmfTone");
+        if (getPhone() != null) {
+            getPhone().stopDtmf();
+        }
     }
 
     @Override
     protected void onDisconnect() {
         if (VERBOSE) log("onDisconnect");
+        try {
+            if (getCall() != null && !getCall().isMultiparty()) {
+                getCall().hangup();
+            } else if (mOriginalConnection != null) {
+                mOriginalConnection.hangup();
+            }
+        } catch (CallStateException e) {
+            log("onDisconnect, exception: " + e);
+        }
     }
 
     @Override
     protected void onSeparate() {
         if (VERBOSE) log("onSeparate");
+        try {
+            if (mOriginalConnection != null) {
+                mOriginalConnection.separate();
+            }
+        } catch (CallStateException e) {
+            log("onSeparate, exception: " + e);
+        }
     }
 
     @Override
     protected void onAbort() {
         if (VERBOSE) log("onAbort");
+        onDisconnect();
     }
 
     @Override
     protected void onHold() {
         if (VERBOSE) log("onHold");
+        try {
+            if (getPhone() != null && getState() == State.ACTIVE) {
+                getPhone().switchHoldingAndActive();
+            }
+        } catch (CallStateException e) {
+            log("onHold, exception: " + e);
+        }
     }
 
     @Override
     protected void onUnhold() {
         if (VERBOSE) log("onUnhold");
+        try {
+            if (getPhone() != null && getState() == State.HOLDING) {
+                getPhone().switchHoldingAndActive();
+            }
+        } catch (CallStateException e) {
+            log("onUnhold, exception: " + e);
+        }
     }
 
     @Override
     protected void onAnswer() {
         if (VERBOSE) log("onAnswer");
+        try {
+            if (isValidRingingCall() && getPhone() != null) {
+                getPhone().acceptCall();
+            }
+        } catch (CallStateException e) {
+            log("onAnswer, exception: " + e);
+        }
     }
 
     @Override
     protected void onReject() {
         if (VERBOSE) log("onReject");
+        try {
+            if (isValidRingingCall() && getPhone() != null) {
+                getPhone().rejectCall();
+            }
+        } catch (CallStateException e) {
+            log("onReject, exception: " + e);
+        }
     }
 
     @Override
     protected void onPostDialContinue(boolean proceed) {
         if (VERBOSE) log("onPostDialContinue, proceed: " + proceed);
+        // SIP doesn't have post dial support.
     }
 
     @Override
@@ -103,6 +183,93 @@
         if (VERBOSE) log("onPhoneAccountClicked");
     }
 
+    private Call getCall() {
+        if (mOriginalConnection != null) {
+            return mOriginalConnection.getCall();
+        }
+        return null;
+    }
+
+    SipPhone getPhone() {
+        Call call = getCall();
+        if (call != null) {
+            return (SipPhone) call.getPhone();
+        }
+        return null;
+    }
+
+    private boolean isValidRingingCall() {
+        Call call = getCall();
+        return call != null && call.getState().isRinging() &&
+                call.getEarliestConnection() == mOriginalConnection;
+    }
+
+    private void updateState() {
+        if (mOriginalConnection == null) {
+            return;
+        }
+
+        Call.State newState = mOriginalConnection.getState();
+        if (VERBOSE) log("updateState, " + mOriginalConnectionState + " -> " + newState);
+        if (mOriginalConnectionState != newState) {
+            mOriginalConnectionState = newState;
+            switch (newState) {
+                case IDLE:
+                    break;
+                case ACTIVE:
+                    setActive();
+                    break;
+                case HOLDING:
+                    setOnHold();
+                    break;
+                case DIALING:
+                case ALERTING:
+                    setDialing();
+                    break;
+                case INCOMING:
+                case WAITING:
+                    setRinging();
+                    break;
+                case DISCONNECTED:
+                    setDisconnected(mOriginalConnection.getDisconnectCause(), null);
+                    close();
+                    break;
+                case DISCONNECTING:
+                    break;
+            }
+            updateCallCapabilities();
+        }
+    }
+
+    private int buildCallCapabilities() {
+        int capabilities = CallCapabilities.MUTE | CallCapabilities.SUPPORT_HOLD;
+        if (getState() == State.ACTIVE || getState() == State.HOLDING) {
+            capabilities |= CallCapabilities.HOLD;
+        }
+        return capabilities;
+    }
+
+    void updateCallCapabilities() {
+        int newCallCapabilities = buildCallCapabilities();
+        if (getCallCapabilities() != newCallCapabilities) {
+            setCallCapabilities(newCallCapabilities);
+        }
+    }
+
+    void onAddedToCallService() {
+        if (VERBOSE) log("onAddedToCallService");
+        updateCallCapabilities();
+        setAudioModeIsVoip(true);
+    }
+
+    private void close() {
+        if (getPhone() != null) {
+            getPhone().unregisterForPreciseCallStateChanged(mHandler);
+        }
+        mOriginalConnection = null;
+        setDestroyed();
+    }
+
     private static void log(String msg) {
         Log.d(SipUtil.LOG_TAG, PREFIX + msg);
     }
diff --git a/sip/src/com/android/services/telephony/sip/SipConnectionService.java b/sip/src/com/android/services/telephony/sip/SipConnectionService.java
index 9b7b33f..1dc78e3 100644
--- a/sip/src/com/android/services/telephony/sip/SipConnectionService.java
+++ b/sip/src/com/android/services/telephony/sip/SipConnectionService.java
@@ -17,6 +17,8 @@
 package com.android.services.telephony.sip;
 
 import android.content.Context;
+import android.content.Intent;
+import android.net.sip.SipAudioCall;
 import android.net.sip.SipException;
 import android.net.sip.SipManager;
 import android.net.sip.SipProfile;
@@ -32,76 +34,106 @@
 import android.util.Log;
 
 import com.android.internal.telephony.CallStateException;
-import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.sip.SipPhone;
 
 import java.util.HashMap;
 
-public class SipConnectionService extends ConnectionService {
+public final class SipConnectionService extends ConnectionService {
     private static final String PREFIX = "[SipConnectionService] ";
     private static final boolean VERBOSE = true; /* STOP SHIP if true */
 
-    private class GetSipProfileTask extends AsyncTask<Void, Void, SipProfile> {
-        private final ConnectionRequest mRequest;
-        private final OutgoingCallResponse mResponse;
-        private final SipProfileDb mSipProfileDb;
-        private final SipSharedPreferences mSipSharedPreferences;
-
-        GetSipProfileTask(
-                Context context,
-                ConnectionRequest request,
-                OutgoingCallResponse response) {
-            mRequest = request;
-            mResponse = response;
-            mSipProfileDb = new SipProfileDb(context);
-            mSipSharedPreferences = new SipSharedPreferences(context);
-        }
-
-        @Override
-        protected SipProfile doInBackground(Void... params) {
-            String primarySipUri = mSipSharedPreferences.getPrimaryAccount();
-            for (SipProfile profile : mSipProfileDb.retrieveSipProfileList()) {
-                if (profile.getUriString().equals(primarySipUri)) {
-                    return profile;
-                }
-            }
-            // TODO(sail): Handle non-primary profiles by showing dialog.
-            return null;
-        }
-
-        @Override
-        protected void onPostExecute(SipProfile profile) {
-            onSipProfileChosen(profile, mRequest, mResponse);
-        }
-    }
-
     @Override
     protected void onCreateConnections(
-            ConnectionRequest request,
-            OutgoingCallResponse<Connection> callback) {
+            final ConnectionRequest request,
+            final OutgoingCallResponse<Connection> response) {
         if (VERBOSE) log("onCreateConnections, request: " + request);
-        new GetSipProfileTask(this, request, callback).execute();
+
+        SipProfileChooser.Callback callback = new SipProfileChooser.Callback() {
+            @Override
+            public void onSipChosen(SipProfile profile) {
+                if (VERBOSE) log("onCreateConnections, onSipChosen: " + profile);
+                SipConnection connection = createConnectionForProfile(profile, request);
+                if (connection == null) {
+                    response.onCancel(request);
+                } else {
+                    response.onSuccess(request, connection);
+                }
+            }
+
+            @Override
+            public void onSipNotChosen() {
+                if (VERBOSE) log("onCreateConnections, onSipNotChosen");
+                response.onFailure(request, DisconnectCause.ERROR_UNSPECIFIED, null);
+            }
+
+            @Override
+            public void onCancelCall() {
+                if (VERBOSE) log("onCreateConnections, onCancelCall");
+                response.onCancel(request);
+            }
+        };
+
+        SipProfileChooser chooser = new SipProfileChooser(this, callback);
+        chooser.start(request.getHandle(), request.getExtras());
     }
 
     @Override
     protected void onCreateConferenceConnection(
             String token,
             Connection connection,
-            Response<String, Connection> callback) {
+            Response<String, Connection> response) {
         if (VERBOSE) log("onCreateConferenceConnection, connection: " + connection);
     }
 
     @Override
     protected void onCreateIncomingConnection(
             ConnectionRequest request,
-            Response<ConnectionRequest, Connection> callback) {
+            Response<ConnectionRequest, Connection> response) {
         if (VERBOSE) log("onCreateIncomingConnection, request: " + request);
+
+        Intent sipIntent = (Intent) request.getExtras().getParcelable(
+                SipUtil.EXTRA_INCOMING_CALL_INTENT);
+        if (sipIntent == null) {
+            if (VERBOSE) log("onCreateIncomingConnection, no SIP intent");
+            response.onError(request, DisconnectCause.ERROR_UNSPECIFIED, null);
+            return;
+        }
+
+        SipAudioCall sipAudioCall;
+        try {
+            sipAudioCall = SipManager.newInstance(this).takeAudioCall(sipIntent, null);
+        } catch (SipException e) {
+            log("onCreateConferenceConnection, takeAudioCall exception: " + e);
+            response.onError(request, DisconnectCause.ERROR_UNSPECIFIED, null);
+            return;
+        }
+
+        SipPhone phone = findPhoneForProfile(sipAudioCall.getLocalProfile());
+        if (phone == null) {
+            phone = createPhoneForProfile(sipAudioCall.getLocalProfile());
+        }
+        if (phone != null) {
+            com.android.internal.telephony.Connection originalConnection = phone.takeIncomingCall(
+                    sipAudioCall);
+            if (VERBOSE) log("onCreateIncomingConnection, new connection: " + originalConnection);
+            if (originalConnection != null) {
+                SipConnection connection = new SipConnection(originalConnection);
+                response.onResult(getConnectionRequestForIncomingCall(request, sipAudioCall),
+                        connection);
+            } else {
+                if (VERBOSE) log("onCreateIncomingConnection, takingIncomingCall failed");
+                response.onError(request, DisconnectCause.ERROR_UNSPECIFIED, null);
+            }
+        }
     }
 
     @Override
     protected void onConnectionAdded(Connection connection) {
         if (VERBOSE) log("onConnectionAdded, connection: " + connection);
+        if (connection instanceof SipConnection) {
+            ((SipConnection) connection).onAddedToCallService();
+        }
     }
 
     @Override
@@ -109,45 +141,71 @@
         if (VERBOSE) log("onConnectionRemoved, connection: " + connection);
     }
 
-    private void onSipProfileChosen(
+    private SipConnection createConnectionForProfile(
             SipProfile profile,
-            ConnectionRequest request,
-            OutgoingCallResponse response) {
-        if (profile != null) {
-            String sipUri = profile.getUriString();
-            SipPhone phone = null;
-            try {
-                SipManager.newInstance(this).open(profile);
-                phone = (SipPhone) PhoneFactory.makeSipPhone(sipUri);
-                startCallWithPhone(phone, request, response);
-            } catch (SipException e) {
-                log("Failed to make a SIP phone: " + e);
-                response.onFailure(request, DisconnectCause.ERROR_UNSPECIFIED,
-                        "Failed to make a SIP phone: " + e);
+            ConnectionRequest request) {
+        SipPhone phone = findPhoneForProfile(profile);
+        if (phone == null) {
+            phone = createPhoneForProfile(profile);
+        }
+        if (phone != null) {
+            return startCallWithPhone(phone, request);
+        }
+        return null;
+    }
+
+    private SipPhone findPhoneForProfile(SipProfile profile) {
+        if (VERBOSE) log("findPhoneForProfile, profile: " + profile);
+        for (Connection connection : getAllConnections()) {
+            if (connection instanceof SipConnection) {
+                SipPhone phone = ((SipConnection) connection).getPhone();
+                if (phone != null && phone.getSipUri().equals(profile.getUriString())) {
+                    if (VERBOSE) log("findPhoneForProfile, found existing phone: " + phone);
+                    return phone;
+                }
             }
-        } else {
-            response.onFailure(request, DisconnectCause.ERROR_UNSPECIFIED,
-                    "Failed to find SIP profile");
+        }
+        if (VERBOSE) log("findPhoneForProfile, no phone found");
+        return null;
+    }
+
+    private SipPhone createPhoneForProfile(SipProfile profile) {
+        if (VERBOSE) log("createPhoneForProfile, profile: " + profile);
+        try {
+            SipManager.newInstance(this).open(profile);
+            return (SipPhone) PhoneFactory.makeSipPhone(profile.getUriString());
+        } catch (SipException e) {
+            log("createPhoneForProfile, exception: " + e);
+            return null;
         }
     }
 
-    protected void startCallWithPhone(
-            Phone phone,
-            ConnectionRequest request,
-            OutgoingCallResponse<Connection> response) {
+    private SipConnection startCallWithPhone(SipPhone phone, ConnectionRequest request) {
         String number = request.getHandle().getSchemeSpecificPart();
+        if (VERBOSE) log("startCallWithPhone, number: " + number);
+
         try {
-            com.android.internal.telephony.Connection connection =
+            com.android.internal.telephony.Connection originalConnection =
                     phone.dial(number, request.getVideoState());
-            SipConnection sipConnection = new SipConnection(connection);
-            response.onSuccess(request, sipConnection);
+            return new SipConnection(originalConnection);
         } catch (CallStateException e) {
-            log("Call to Phone.dial failed with exception: " + e);
-            response.onFailure(request, DisconnectCause.ERROR_UNSPECIFIED,
-                    "Call to Phone.dial failed with exception: " + e);
+            log("startCallWithPhone, exception: " + e);
+            return null;
         }
     }
 
+    private ConnectionRequest getConnectionRequestForIncomingCall(ConnectionRequest request,
+            SipAudioCall audioCall) {
+        SipProfile callee = audioCall.getPeerProfile();
+        String domain = callee.getSipDomain();
+        if (domain.endsWith(":5060")) {
+            domain = domain.substring(0, domain.length() - 5);
+        }
+        Uri uri = Uri.fromParts(SipUtil.SCHEME_SIP, callee.getUserName() + "@" + domain, null);
+        return new ConnectionRequest(request.getAccount(), request.getCallId(), uri,
+                request.getExtras(), 0);
+    }
+
     private static void log(String msg) {
         Log.d(SipUtil.LOG_TAG, PREFIX + msg);
     }
diff --git a/sip/src/com/android/services/telephony/sip/SipEditor.java b/sip/src/com/android/services/telephony/sip/SipEditor.java
index a765296..2f2bdef 100644
--- a/sip/src/com/android/services/telephony/sip/SipEditor.java
+++ b/sip/src/com/android/services/telephony/sip/SipEditor.java
@@ -16,7 +16,6 @@
 
 package com.android.services.telephony.sip;
 
-import com.android.internal.telephony.CallManager;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneConstants;
 import com.android.services.telephony.sip.SipUtil;
@@ -75,7 +74,6 @@
     private SipManager mSipManager;
     private SipProfileDb mProfileDb;
     private SipProfile mOldProfile;
-    private CallManager mCallManager;
     private Button mRemoveButton;
 
     enum PreferenceKey {
@@ -146,7 +144,7 @@
     public void onResume() {
         super.onResume();
         mHomeButtonClicked = false;
-        if (mCallManager.getState() != PhoneConstants.State.IDLE) {
+        if (!SipUtil.isPhoneIdle(this)) {
             mAdvancedSettings.show();
             getPreferenceScreen().setEnabled(false);
             if (mRemoveButton != null) mRemoveButton.setEnabled(false);
@@ -164,7 +162,6 @@
         mSipManager = SipManager.newInstance(this);
         mSharedPreferences = new SipSharedPreferences(this);
         mProfileDb = new SipProfileDb(this);
-        mCallManager = CallManager.getInstance();
 
         setContentView(R.layout.sip_settings_ui);
         addPreferencesFromResource(R.xml.sip_edit);
diff --git a/sip/src/com/android/services/telephony/sip/SipProfileChooser.java b/sip/src/com/android/services/telephony/sip/SipProfileChooser.java
new file mode 100644
index 0000000..687edfa
--- /dev/null
+++ b/sip/src/com/android/services/telephony/sip/SipProfileChooser.java
@@ -0,0 +1,259 @@
+/*
+ * 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.sip;
+
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.net.sip.SipProfile;
+import android.net.sip.SipManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.ResultReceiver;
+import android.provider.Settings;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import java.util.List;
+
+/** Find a SIP profile to use for the an outgoing call. */
+final class SipProfileChooser {
+    private static final String PREFIX = "[SipProfileChooser] ";
+    private static final boolean VERBOSE = true; /* STOP SHIP if true */
+
+    interface Callback {
+        // Call will be completed by the given SIP profile.
+        void onSipChosen(SipProfile profile);
+        // Call will be tried by another connection service (GSM, CDMA, etc...).
+        void onSipNotChosen();
+        // Call will be aborted.
+        void onCancelCall();
+    }
+
+    private final Context mContext;
+    private final Callback mCallback;
+    private final SipProfileDb mSipProfileDb;
+    private List<SipProfile> mProfileList;
+    private SipProfile mPrimaryProfile;
+    private final Handler mHandler = new Handler();
+
+    SipProfileChooser(Context context, Callback callback) {
+        mContext = context;
+        mCallback = callback;
+        mSipProfileDb = new SipProfileDb(mContext);
+    }
+
+    void start(Uri handle, Bundle extras) {
+        if (VERBOSE) log("start, handle: " + handle);
+
+        String scheme = handle.getScheme();
+        if (!SipUtil.SCHEME_SIP.equals(scheme) && !SipUtil.SCHEME_TEL.equals(scheme)) {
+            if (VERBOSE) log("start, unknown scheme");
+            mCallback.onSipNotChosen();
+            return;
+        }
+
+        String phoneNumber = handle.getSchemeSpecificPart();
+        // Consider "tel:foo@exmaple.com" a SIP number.
+        boolean isSipNumber = SipUtil.SCHEME_SIP.equals(scheme) ||
+                PhoneNumberUtils.isUriNumber(phoneNumber);
+        if (!SipUtil.isVoipSupported(mContext)) {
+            if (isSipNumber) {
+                if (VERBOSE) log("start, VOIP not supported, dropping call");
+                SipProfileChooserDialogs.showNoVoip(mContext, new ResultReceiver(mHandler) {
+                        @Override
+                        protected void onReceiveResult(int choice, Bundle resultData) {
+                            mCallback.onCancelCall();
+                        }
+                });
+            } else {
+                if (VERBOSE) log("start, VOIP not supported");
+                mCallback.onSipNotChosen();
+            }
+            return;
+        }
+
+        // Don't use SIP for numbers modified by a gateway.
+        if (extras != null && extras.getString(SipUtil.GATEWAY_PROVIDER_PACKAGE) != null) {
+            if (VERBOSE) log("start, not using SIP for numbers modified by gateway");
+            mCallback.onSipNotChosen();
+            return;
+        }
+
+        if (!isNetworkConnected()) {
+            if (isSipNumber) {
+                if (VERBOSE) log("start, network not connected, dropping call");
+                SipProfileChooserDialogs.showNoInternetError(mContext,
+                        new ResultReceiver(mHandler) {
+                            @Override
+                            protected void onReceiveResult(int choice, Bundle resultData) {
+                                mCallback.onCancelCall();
+                            }
+                });
+            } else {
+                if (VERBOSE) log("start, network not connected");
+                mCallback.onSipNotChosen();
+            }
+            return;
+        }
+
+        // Only ask user to pick SIP or Cell if they're actually connected to a cell network.
+        SipSharedPreferences sipPreferences = new SipSharedPreferences(mContext);
+        String callOption = sipPreferences.getSipCallOption();
+        if (callOption.equals(Settings.System.SIP_ASK_ME_EACH_TIME) && !isSipNumber &&
+                isInCellNetwork()) {
+            if (VERBOSE) log("start, call option set to ask, asking");
+            ResultReceiver receiver = new ResultReceiver(mHandler) {
+                @Override
+                protected void onReceiveResult(int choice, Bundle resultData) {
+                    if (choice == DialogInterface.BUTTON_NEGATIVE) {
+                        mCallback.onCancelCall();
+                    } else if (SipProfileChooserDialogs.isSelectedPhoneTypeSip(mContext,
+                            choice)) {
+                        buildProfileList();
+                    } else {
+                        mCallback.onSipNotChosen();
+                    }
+                }
+            };
+            SipProfileChooserDialogs.showSelectPhoneType(mContext, receiver);
+            return;
+        }
+
+        if (callOption.equals(Settings.System.SIP_ADDRESS_ONLY) && !isSipNumber) {
+            if (VERBOSE) log("start, call option set to SIP only, not a sip number");
+            mCallback.onSipNotChosen();
+            return;
+        }
+
+        if ((mSipProfileDb.getProfilesCount() == 0) && !isSipNumber) {
+            if (VERBOSE) log("start, no SIP accounts and not sip number");
+            mCallback.onSipNotChosen();
+            return;
+        }
+
+        if (VERBOSE) log("start, building profile list");
+        buildProfileList();
+    }
+
+    private boolean isNetworkConnected() {
+        ConnectivityManager cm = (ConnectivityManager) mContext.getSystemService(
+                Context.CONNECTIVITY_SERVICE);
+        if (cm != null) {
+            NetworkInfo ni = cm.getActiveNetworkInfo();
+            if (ni != null && ni.isConnected()) {
+                return ni.getType() == ConnectivityManager.TYPE_WIFI ||
+                        !SipManager.isSipWifiOnly(mContext);
+            }
+        }
+        return false;
+    }
+
+    private boolean isInCellNetwork() {
+        TelephonyManager telephonyManager = (TelephonyManager) mContext.getSystemService(
+                Context.TELEPHONY_SERVICE);
+        if (telephonyManager != null) {
+            // We'd also like to check the radio's power state but there's no public API to do this.
+            int phoneType = telephonyManager.getPhoneType();
+            return phoneType != TelephonyManager.PHONE_TYPE_NONE &&
+                    phoneType != TelephonyManager.PHONE_TYPE_SIP;
+        }
+        return false;
+    }
+
+    private void buildProfileList() {
+        if (VERBOSE) log("buildProfileList");
+        final SipSharedPreferences preferences = new SipSharedPreferences(mContext);
+        new Thread(new Runnable() {
+            @Override
+            public void run() {
+                mProfileList = mSipProfileDb.retrieveSipProfileList();
+                if (mProfileList != null) {
+                    String primarySipUri = preferences.getPrimaryAccount();
+                    for (SipProfile profile : mProfileList) {
+                        if (profile.getUriString().equals(primarySipUri)) {
+                            mPrimaryProfile = profile;
+                            break;
+                        }
+                    }
+                }
+                mHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        onBuildProfileListDone();
+                    }
+                });
+            }
+        }).start();
+    }
+
+    /**
+      * At this point we definitely want to make a SIP call, we just need to figure out which
+      * profile to use.
+      */
+    private void onBuildProfileListDone() {
+        if (VERBOSE) log("onBuildProfileListDone");
+        if (mProfileList == null || mProfileList.size() == 0) {
+            if (VERBOSE) log("onBuildProfileListDone, no profiles, showing settings dialog");
+            ResultReceiver receiver = new ResultReceiver(mHandler) {
+                @Override
+                protected void onReceiveResult(int choice, Bundle resultData) {
+                    if (choice == DialogInterface.BUTTON_POSITIVE) {
+                        openSipSettings();
+                    }
+                    mCallback.onCancelCall();
+                }
+            };
+            SipProfileChooserDialogs.showStartSipSettings(mContext, receiver);
+        } else if (mPrimaryProfile == null) {
+            if (VERBOSE) log("onBuildProfileListDone, no primary profile, showing select dialog");
+            ResultReceiver receiver = new ResultReceiver(mHandler) {
+                @Override
+                protected void onReceiveResult(int choice, Bundle resultData) {
+                    if (choice >= 0 && choice < mProfileList.size()) {
+                        SipProfile profile = mProfileList.get(choice);
+                        if (SipProfileChooserDialogs.shouldMakeSelectedProflePrimary(mContext,
+                                resultData)) {
+                            SipSharedPreferences pref = new SipSharedPreferences(mContext);
+                            pref.setPrimaryAccount(profile.getUriString());
+                        }
+                        mCallback.onSipChosen(profile);
+                    } else {
+                        mCallback.onCancelCall();
+                    }
+                }
+            };
+            SipProfileChooserDialogs.showSelectProfile(mContext, mProfileList, receiver);
+        } else {
+            mCallback.onSipChosen(mPrimaryProfile);
+        }
+    }
+
+    private void openSipSettings() {
+        Intent newIntent = new Intent(mContext, SipSettings.class);
+        newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        mContext.startActivity(newIntent);
+    }
+
+    private static void log(String msg) {
+        Log.d(SipUtil.LOG_TAG, PREFIX + msg);
+    }
+}
diff --git a/sip/src/com/android/services/telephony/sip/SipProfileChooserDialogs.java b/sip/src/com/android/services/telephony/sip/SipProfileChooserDialogs.java
new file mode 100644
index 0000000..3ba633e
--- /dev/null
+++ b/sip/src/com/android/services/telephony/sip/SipProfileChooserDialogs.java
@@ -0,0 +1,266 @@
+/*
+ * 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.sip;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.net.sip.SipManager;
+import android.net.sip.SipProfile;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.ResultReceiver;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.TextView;
+
+import java.util.List;
+
+public final class SipProfileChooserDialogs extends Activity
+        implements DialogInterface.OnClickListener,
+        DialogInterface.OnCancelListener, CompoundButton.OnCheckedChangeListener {
+    private static final String PREFIX = "[SipProfileChooserDialogs] ";
+    private static final boolean VERBOSE = true; /* STOP SHIP if true */
+
+    private static final String EXTRA_RESULT_RECEIVER = "result_receiver";
+    private static final String EXTRA_DIALOG_ID = "dialog_id";
+    private static final String EXTRA_PROFILE_NAMES = "profile_names";
+    private static final String EXTRA_MAKE_PRIMARY = "make_primary";
+
+    private static final int DIALOG_SELECT_PHONE_TYPE = 0;
+    private static final int DIALOG_SELECT_PROFILE = 1;
+    private static final int DIALOG_START_SIP_SETTINGS = 2;
+    private static final int DIALOG_NO_INTERNET_ERROR = 3;
+    private static final int DIALOG_NO_VOIP = 4;
+
+    private TextView mUnsetPriamryHint;
+    private boolean mMakePrimary;
+
+    static void showSelectPhoneType(Context context, ResultReceiver resultReceiver) {
+        show(context, DIALOG_SELECT_PHONE_TYPE, null, resultReceiver);
+    }
+
+    static void showSelectProfile(Context context, List<SipProfile> profiles,
+            ResultReceiver resultReceiver) {
+        show(context, DIALOG_SELECT_PROFILE, profiles, resultReceiver);
+    }
+
+    static void showStartSipSettings(Context context, ResultReceiver resultReceiver) {
+        show(context, DIALOG_START_SIP_SETTINGS, null, resultReceiver);
+    }
+
+    static void showNoInternetError(Context context, ResultReceiver resultReceiver) {
+        show(context, DIALOG_NO_INTERNET_ERROR, null, resultReceiver);
+    }
+
+    static void showNoVoip(Context context, ResultReceiver resultReceiver) {
+        show(context, DIALOG_NO_VOIP, null, resultReceiver);
+    }
+
+    static boolean isSelectedPhoneTypeSip(Context context, int choice) {
+        String[] phoneTypes = context.getResources().getStringArray(R.array.phone_type_values);
+        if (choice >= 0 && choice < phoneTypes.length) {
+            return phoneTypes[choice].equals(context.getString(R.string.internet_phone));
+        }
+        return false;
+    }
+
+    static boolean shouldMakeSelectedProflePrimary(Context context, Bundle extras) {
+        return extras.getBoolean(EXTRA_MAKE_PRIMARY);
+    }
+
+    static private void show(final Context context, final int dialogId,
+            final List<SipProfile> profiles,
+            final ResultReceiver resultReceiver) {
+        if (VERBOSE) log("show, starting delayed show, dialogId: " + dialogId);
+
+        // Wait for 1 second before showing the dialog. The sometimes prevents the InCallUI from
+        // popping up on top of the dialog. See http://b/16184268
+        new Handler().postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                if (VERBOSE) log("show, starting activity");
+                Intent intent = new Intent(context, SipProfileChooserDialogs.class)
+                        .putExtra(EXTRA_RESULT_RECEIVER, resultReceiver)
+                        .putExtra(EXTRA_DIALOG_ID, dialogId)
+                        .putExtra(EXTRA_PROFILE_NAMES, getProfileNames(profiles))
+                        .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+                context.startActivity(intent);
+            }
+        }, 1000);
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        int dialogId = getIntent().getIntExtra(EXTRA_DIALOG_ID, 0);
+        if (VERBOSE) log("onCreate, dialogId: " + dialogId);
+
+        // Allow this activity to be visible in front of the keyguard. (This is only necessary for
+        // obscure scenarios like the user initiating a call and then immediately pressing the Power
+        // button.)
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
+
+        showDialog(dialogId);
+    }
+
+    @Override
+    protected Dialog onCreateDialog(int id, Bundle args) {
+        Dialog dialog;
+        switch(id) {
+            case DIALOG_SELECT_PHONE_TYPE:
+                dialog = new AlertDialog.Builder(this)
+                        .setTitle(R.string.pick_outgoing_call_phone_type)
+                        .setIconAttribute(android.R.attr.alertDialogIcon)
+                        .setSingleChoiceItems(R.array.phone_type_values, -1, this)
+                        .setNegativeButton(android.R.string.cancel, this)
+                        .setOnCancelListener(this)
+                        .create();
+                break;
+            case DIALOG_SELECT_PROFILE:
+                String[] profileNames = getIntent().getStringArrayExtra(EXTRA_PROFILE_NAMES);
+                dialog = new AlertDialog.Builder(this)
+                        .setTitle(R.string.pick_outgoing_sip_phone)
+                        .setIconAttribute(android.R.attr.alertDialogIcon)
+                        .setSingleChoiceItems(profileNames, -1, this)
+                        .setNegativeButton(android.R.string.cancel, this)
+                        .setOnCancelListener(this)
+                        .create();
+                addMakeDefaultCheckBox(dialog);
+                break;
+            case DIALOG_START_SIP_SETTINGS:
+                dialog = new AlertDialog.Builder(this)
+                        .setTitle(R.string.no_sip_account_found_title)
+                        .setMessage(R.string.no_sip_account_found)
+                        .setIconAttribute(android.R.attr.alertDialogIcon)
+                        .setPositiveButton(R.string.sip_menu_add, this)
+                        .setNegativeButton(android.R.string.cancel, this)
+                        .setOnCancelListener(this)
+                        .create();
+                break;
+            case DIALOG_NO_INTERNET_ERROR:
+                boolean wifiOnly = SipManager.isSipWifiOnly(this);
+                dialog = new AlertDialog.Builder(this)
+                        .setTitle(wifiOnly ? R.string.no_wifi_available_title
+                                           : R.string.no_internet_available_title)
+                        .setMessage(wifiOnly ? R.string.no_wifi_available
+                                             : R.string.no_internet_available)
+                        .setIconAttribute(android.R.attr.alertDialogIcon)
+                        .setPositiveButton(android.R.string.ok, this)
+                        .setOnCancelListener(this)
+                        .create();
+                break;
+            case DIALOG_NO_VOIP:
+                dialog = new AlertDialog.Builder(this)
+                        .setTitle(R.string.no_voip)
+                        .setIconAttribute(android.R.attr.alertDialogIcon)
+                        .setPositiveButton(android.R.string.ok, this)
+                        .setOnCancelListener(this)
+                        .create();
+                break;
+            default:
+                dialog = null;
+        }
+        if (dialog != null) {
+            //mDialogs[id] = dialog;
+        }
+        return dialog;
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        if (VERBOSE) log("onPause");
+    }
+
+    @Override
+    public void finish() {
+        if (VERBOSE) log("finish");
+        super.finish();
+    }
+
+    @Override
+    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+        if (VERBOSE) log("onCheckedChanged, isChecked: " + isChecked);
+        mMakePrimary = isChecked;
+        if (isChecked) {
+            mUnsetPriamryHint.setVisibility(View.VISIBLE);
+        } else {
+            mUnsetPriamryHint.setVisibility(View.INVISIBLE);
+        }
+    }
+
+    @Override
+    public void onClick(DialogInterface dialog, int id) {
+        if (VERBOSE) log("onClick, id: " + id);
+        onChoiceMade(id);
+    }
+
+    @Override
+    public void onCancel(DialogInterface dialog) {
+        if (VERBOSE) log("onCancel");
+        onChoiceMade(DialogInterface.BUTTON_NEGATIVE);
+    }
+
+    private void onChoiceMade(int choice) {
+        ResultReceiver resultReceiver = getIntent().getParcelableExtra(EXTRA_RESULT_RECEIVER);
+        if (resultReceiver != null) {
+            Bundle extras = new Bundle();
+            extras.putBoolean(EXTRA_MAKE_PRIMARY, mMakePrimary);
+            resultReceiver.send(choice, extras);
+        }
+        finish();
+    }
+
+    private void addMakeDefaultCheckBox(Dialog dialog) {
+        LayoutInflater inflater = (LayoutInflater) getSystemService(
+                Context.LAYOUT_INFLATER_SERVICE);
+        View view = inflater.inflate(com.android.internal.R.layout.always_use_checkbox, null);
+        CheckBox makePrimaryCheckBox =
+                (CheckBox) view.findViewById(com.android.internal.R.id.alwaysUse);
+        makePrimaryCheckBox.setText(R.string.remember_my_choice);
+        makePrimaryCheckBox.setOnCheckedChangeListener(this);
+        mUnsetPriamryHint = (TextView)view.findViewById(com.android.internal.R.id.clearDefaultHint);
+        mUnsetPriamryHint.setText(R.string.reset_my_choice_hint);
+        mUnsetPriamryHint.setVisibility(View.GONE);
+        ((AlertDialog)dialog).setView(view);
+    }
+
+    static private String[] getProfileNames(List<SipProfile> profiles) {
+        if (profiles == null) {
+            return null;
+        }
+
+        String[] entries = new String[profiles.size()];
+        int i = 0;
+        for (SipProfile p : profiles) {
+            entries[i++] = p.getProfileName();
+        }
+        return entries;
+    }
+
+    private static void log(String msg) {
+        Log.d(SipUtil.LOG_TAG, PREFIX + msg);
+    }
+}
diff --git a/sip/src/com/android/services/telephony/sip/SipProfileDb.java b/sip/src/com/android/services/telephony/sip/SipProfileDb.java
index bf4b6bb..7533d16 100644
--- a/sip/src/com/android/services/telephony/sip/SipProfileDb.java
+++ b/sip/src/com/android/services/telephony/sip/SipProfileDb.java
@@ -35,7 +35,7 @@
 /**
  * Utility class that helps perform operations on the SipProfile database.
  */
-public class SipProfileDb {
+class SipProfileDb {
     private static final String PREFIX = "[SipProfileDb] ";
     private static final boolean VERBOSE = true; /* STOP SHIP if true */
 
diff --git a/sip/src/com/android/services/telephony/sip/SipSettings.java b/sip/src/com/android/services/telephony/sip/SipSettings.java
index cf35e31..e4fca2b 100644
--- a/sip/src/com/android/services/telephony/sip/SipSettings.java
+++ b/sip/src/com/android/services/telephony/sip/SipSettings.java
@@ -16,7 +16,6 @@
 
 package com.android.services.telephony.sip;
 
-import com.android.internal.telephony.CallManager;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneConstants;
 
@@ -73,7 +72,6 @@
 
     private PackageManager mPackageManager;
     private SipManager mSipManager;
-    private CallManager mCallManager;
     private SipProfileDb mProfileDb;
 
     private SipProfile mProfile; // profile that's being edited
@@ -154,7 +152,6 @@
         addPreferencesFromResource(R.xml.sip_setting);
         mSipListContainer = (PreferenceCategory) findPreference(PREF_SIP_LIST);
         registerForReceiveCallsCheckBox();
-        mCallManager = CallManager.getInstance();
 
         updateProfilesStatus();
 
@@ -168,11 +165,7 @@
     public void onResume() {
         super.onResume();
 
-        if (mCallManager.getState() != PhoneConstants.State.IDLE) {
-            mButtonSipReceiveCalls.setEnabled(false);
-        } else {
-            mButtonSipReceiveCalls.setEnabled(true);
-        }
+        mButtonSipReceiveCalls.setEnabled(SipUtil.isPhoneIdle(this));
     }
 
     @Override
@@ -489,8 +482,7 @@
 
     @Override
     public boolean onPrepareOptionsMenu(Menu menu) {
-        menu.findItem(MENU_ADD_ACCOUNT).setEnabled(
-                mCallManager.getState() == PhoneConstants.State.IDLE);
+        menu.findItem(MENU_ADD_ACCOUNT).setEnabled(SipUtil.isPhoneIdle(this));
         return super.onPrepareOptionsMenu(menu);
     }
 
diff --git a/sip/src/com/android/services/telephony/sip/SipUtil.java b/sip/src/com/android/services/telephony/sip/SipUtil.java
index 39b5655..7a1fcf7 100644
--- a/sip/src/com/android/services/telephony/sip/SipUtil.java
+++ b/sip/src/com/android/services/telephony/sip/SipUtil.java
@@ -20,9 +20,16 @@
 import android.content.Context;
 import android.content.Intent;
 import android.net.sip.SipManager;
+import android.telecomm.TelecommManager;
 
 public class SipUtil {
-    public static final String LOG_TAG = "SIP";
+    static final String LOG_TAG = "SIP";
+    static final String EXTRA_INCOMING_CALL_INTENT =
+            "com.android.services.telephony.sip.incoming_call_intent";
+    static final String GATEWAY_PROVIDER_PACKAGE =
+            "com.android.phone.extra.GATEWAY_PROVIDER_PACKAGE";
+    static final String SCHEME_TEL = "tel";
+    static final String SCHEME_SIP = "sip";
 
     private static boolean sIsVoipSupported;
     private static boolean sIsVoipSupportedInitialized;
@@ -41,10 +48,19 @@
         return sIsVoipSupported;
     }
 
-    public static PendingIntent createIncomingCallPendingIntent(Context context) {
+    static PendingIntent createIncomingCallPendingIntent(Context context) {
         Intent intent = new Intent(context, SipBroadcastReceiver.class);
         intent.setAction(SipManager.ACTION_SIP_INCOMING_CALL);
         return PendingIntent.getBroadcast(context, 0, intent,
                 PendingIntent.FLAG_UPDATE_CURRENT);
     }
+
+    static boolean isPhoneIdle(Context context) {
+        TelecommManager manager = (TelecommManager) context.getSystemService(
+                Context.TELECOMM_SERVICE);
+        if (manager != null) {
+            return !manager.isInAPhoneCall();
+        }
+        return true;
+    }
 }
diff --git a/src/com/android/phone/ContactsAsyncHelper.java b/src/com/android/phone/ContactsAsyncHelper.java
deleted file mode 100644
index 10a6950..0000000
--- a/src/com/android/phone/ContactsAsyncHelper.java
+++ /dev/null
@@ -1,358 +0,0 @@
-/*
- * Copyright (C) 2008 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.Notification;
-import android.content.ContentUris;
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.Message;
-import android.provider.ContactsContract.Contacts;
-import android.util.Log;
-
-import com.android.internal.telephony.CallerInfo;
-import com.android.internal.telephony.Connection;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-/**
- * Helper class for loading contacts photo asynchronously.
- */
-public class ContactsAsyncHelper {
-
-    private static final boolean DBG = false;
-    private static final String LOG_TAG = "ContactsAsyncHelper";
-
-    /**
-     * Interface for a WorkerHandler result return.
-     */
-    public interface OnImageLoadCompleteListener {
-        /**
-         * Called when the image load is complete.
-         *
-         * @param token Integer passed in {@link ContactsAsyncHelper#startObtainPhotoAsync(int,
-         * Context, Uri, OnImageLoadCompleteListener, Object)}.
-         * @param photo Drawable object obtained by the async load.
-         * @param photoIcon Bitmap object obtained by the async load.
-         * @param cookie Object passed in {@link ContactsAsyncHelper#startObtainPhotoAsync(int,
-         * Context, Uri, OnImageLoadCompleteListener, Object)}. Can be null iff. the original
-         * cookie is null.
-         */
-        public void onImageLoadComplete(int token, Drawable photo, Bitmap photoIcon,
-                Object cookie);
-    }
-
-    // constants
-    private static final int EVENT_LOAD_IMAGE = 1;
-
-    private final Handler mResultHandler = new Handler() {
-        /** Called when loading is done. */
-        @Override
-        public void handleMessage(Message msg) {
-            WorkerArgs args = (WorkerArgs) msg.obj;
-            switch (msg.arg1) {
-                case EVENT_LOAD_IMAGE:
-                    if (args.listener != null) {
-                        if (DBG) {
-                            Log.d(LOG_TAG, "Notifying listener: " + args.listener.toString() +
-                                    " image: " + args.uri + " completed");
-                        }
-                        args.listener.onImageLoadComplete(msg.what, args.photo, args.photoIcon,
-                                args.cookie);
-                    }
-                    break;
-                default:
-            }
-        }
-    };
-
-    /** Handler run on a worker thread to load photo asynchronously. */
-    private static Handler sThreadHandler;
-
-    /** For forcing the system to call its constructor */
-    @SuppressWarnings("unused")
-    private static ContactsAsyncHelper sInstance;
-
-    static {
-        sInstance = new ContactsAsyncHelper();
-    }
-
-    private static final class WorkerArgs {
-        public Context context;
-        public Uri uri;
-        public Drawable photo;
-        public Bitmap photoIcon;
-        public Object cookie;
-        public OnImageLoadCompleteListener listener;
-    }
-
-    /**
-     * public inner class to help out the ContactsAsyncHelper callers
-     * with tracking the state of the CallerInfo Queries and image
-     * loading.
-     *
-     * Logic contained herein is used to remove the race conditions
-     * that exist as the CallerInfo queries run and mix with the image
-     * loads, which then mix with the Phone state changes.
-     */
-    public static class ImageTracker {
-
-        // Image display states
-        public static final int DISPLAY_UNDEFINED = 0;
-        public static final int DISPLAY_IMAGE = -1;
-        public static final int DISPLAY_DEFAULT = -2;
-
-        // State of the image on the imageview.
-        private CallerInfo mCurrentCallerInfo;
-        private int displayMode;
-
-        public ImageTracker() {
-            mCurrentCallerInfo = null;
-            displayMode = DISPLAY_UNDEFINED;
-        }
-
-        /**
-         * Used to see if the requested call / connection has a
-         * different caller attached to it than the one we currently
-         * have in the CallCard.
-         */
-        public boolean isDifferentImageRequest(CallerInfo ci) {
-            // note, since the connections are around for the lifetime of the
-            // call, and the CallerInfo-related items as well, we can
-            // definitely use a simple != comparison.
-            return (mCurrentCallerInfo != ci);
-        }
-
-        public boolean isDifferentImageRequest(Connection connection) {
-            // if the connection does not exist, see if the
-            // mCurrentCallerInfo is also null to match.
-            if (connection == null) {
-                if (DBG) Log.d(LOG_TAG, "isDifferentImageRequest: connection is null");
-                return (mCurrentCallerInfo != null);
-            }
-            Object o = connection.getUserData();
-
-            // if the call does NOT have a callerInfo attached
-            // then it is ok to query.
-            boolean runQuery = true;
-            if (o instanceof CallerInfo) {
-                runQuery = isDifferentImageRequest((CallerInfo) o);
-            }
-            return runQuery;
-        }
-
-        /**
-         * Simple setter for the CallerInfo object.
-         */
-        public void setPhotoRequest(CallerInfo ci) {
-            mCurrentCallerInfo = ci;
-        }
-
-        /**
-         * Convenience method used to retrieve the URI
-         * representing the Photo file recorded in the attached
-         * CallerInfo Object.
-         */
-        public Uri getPhotoUri() {
-            if (mCurrentCallerInfo != null) {
-                return ContentUris.withAppendedId(Contacts.CONTENT_URI,
-                        mCurrentCallerInfo.person_id);
-            }
-            return null;
-        }
-
-        /**
-         * Simple setter for the Photo state.
-         */
-        public void setPhotoState(int state) {
-            displayMode = state;
-        }
-
-        /**
-         * Simple getter for the Photo state.
-         */
-        public int getPhotoState() {
-            return displayMode;
-        }
-    }
-
-    /**
-     * Thread worker class that handles the task of opening the stream and loading
-     * the images.
-     */
-    private class WorkerHandler extends Handler {
-        public WorkerHandler(Looper looper) {
-            super(looper);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            WorkerArgs args = (WorkerArgs) msg.obj;
-
-            switch (msg.arg1) {
-                case EVENT_LOAD_IMAGE:
-                    InputStream inputStream = null;
-                    try {
-                        try {
-                            inputStream = Contacts.openContactPhotoInputStream(
-                                    args.context.getContentResolver(), args.uri, true);
-                        } catch (Exception e) {
-                            Log.e(LOG_TAG, "Error opening photo input stream", e);
-                        }
-
-                        if (inputStream != null) {
-                            args.photo = Drawable.createFromStream(inputStream,
-                                    args.uri.toString());
-
-                            // This assumes Drawable coming from contact database is usually
-                            // BitmapDrawable and thus we can have (down)scaled version of it.
-                            args.photoIcon = getPhotoIconWhenAppropriate(args.context, args.photo);
-
-                            if (DBG) {
-                                Log.d(LOG_TAG, "Loading image: " + msg.arg1 +
-                                        " token: " + msg.what + " image URI: " + args.uri);
-                            }
-                        } else {
-                            args.photo = null;
-                            args.photoIcon = null;
-                            if (DBG) {
-                                Log.d(LOG_TAG, "Problem with image: " + msg.arg1 +
-                                        " token: " + msg.what + " image URI: " + args.uri +
-                                        ", using default image.");
-                            }
-                        }
-                    } finally {
-                        if (inputStream != null) {
-                            try {
-                                inputStream.close();
-                            } catch (IOException e) {
-                                Log.e(LOG_TAG, "Unable to close input stream.", e);
-                            }
-                        }
-                    }
-                    break;
-                default:
-            }
-
-            // send the reply to the enclosing class.
-            Message reply = ContactsAsyncHelper.this.mResultHandler.obtainMessage(msg.what);
-            reply.arg1 = msg.arg1;
-            reply.obj = msg.obj;
-            reply.sendToTarget();
-        }
-
-        /**
-         * Returns a Bitmap object suitable for {@link Notification}'s large icon. This might
-         * return null when the given Drawable isn't BitmapDrawable, or if the system fails to
-         * create a scaled Bitmap for the Drawable.
-         */
-        private Bitmap getPhotoIconWhenAppropriate(Context context, Drawable photo) {
-            if (!(photo instanceof BitmapDrawable)) {
-                return null;
-            }
-            int iconSize = context.getResources()
-                    .getDimensionPixelSize(R.dimen.notification_icon_size);
-            Bitmap orgBitmap = ((BitmapDrawable) photo).getBitmap();
-            int orgWidth = orgBitmap.getWidth();
-            int orgHeight = orgBitmap.getHeight();
-            int longerEdge = orgWidth > orgHeight ? orgWidth : orgHeight;
-            // We want downscaled one only when the original icon is too big.
-            if (longerEdge > iconSize) {
-                float ratio = ((float) longerEdge) / iconSize;
-                int newWidth = (int) (orgWidth / ratio);
-                int newHeight = (int) (orgHeight / ratio);
-                // If the longer edge is much longer than the shorter edge, the latter may
-                // become 0 which will cause a crash.
-                if (newWidth <= 0 || newHeight <= 0) {
-                    Log.w(LOG_TAG, "Photo icon's width or height become 0.");
-                    return null;
-                }
-
-                // It is sure ratio >= 1.0f in any case and thus the newly created Bitmap
-                // should be smaller than the original.
-                return Bitmap.createScaledBitmap(orgBitmap, newWidth, newHeight, true);
-            } else {
-                return orgBitmap;
-            }
-        }
-    }
-
-    /**
-     * Private constructor for static class
-     */
-    private ContactsAsyncHelper() {
-        HandlerThread thread = new HandlerThread("ContactsAsyncWorker");
-        thread.start();
-        sThreadHandler = new WorkerHandler(thread.getLooper());
-    }
-
-    /**
-     * Starts an asynchronous image load. After finishing the load,
-     * {@link OnImageLoadCompleteListener#onImageLoadComplete(int, Drawable, Bitmap, Object)}
-     * will be called.
-     *
-     * @param token Arbitrary integer which will be returned as the first argument of
-     * {@link OnImageLoadCompleteListener#onImageLoadComplete(int, Drawable, Bitmap, Object)}
-     * @param context Context object used to do the time-consuming operation.
-     * @param personUri Uri to be used to fetch the photo
-     * @param listener Callback object which will be used when the asynchronous load is done.
-     * Can be null, which means only the asynchronous load is done while there's no way to
-     * obtain the loaded photos.
-     * @param cookie Arbitrary object the caller wants to remember, which will become the
-     * fourth argument of {@link OnImageLoadCompleteListener#onImageLoadComplete(int, Drawable,
-     * Bitmap, Object)}. Can be null, at which the callback will also has null for the argument.
-     */
-    public static final void startObtainPhotoAsync(int token, Context context, Uri personUri,
-            OnImageLoadCompleteListener listener, Object cookie) {
-        // in case the source caller info is null, the URI will be null as well.
-        // just update using the placeholder image in this case.
-        if (personUri == null) {
-            Log.wtf(LOG_TAG, "Uri is missing");
-            return;
-        }
-
-        // Added additional Cookie field in the callee to handle arguments
-        // sent to the callback function.
-
-        // setup arguments
-        WorkerArgs args = new WorkerArgs();
-        args.cookie = cookie;
-        args.context = context;
-        args.uri = personUri;
-        args.listener = listener;
-
-        // setup message arguments
-        Message msg = sThreadHandler.obtainMessage(token);
-        msg.arg1 = EVENT_LOAD_IMAGE;
-        msg.obj = args;
-
-        if (DBG) Log.d(LOG_TAG, "Begin loading image: " + args.uri +
-                ", displaying default image for now.");
-
-        // notify the thread to begin working
-        sThreadHandler.sendMessage(msg);
-    }
-
-
-}
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index f4d82c6..15e2ccc 100644
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -1160,6 +1160,35 @@
     }
 
     /**
+     * Make sure either system app or the caller has carrier privilege.
+     *
+     * @throws SecurityException if the caller does not have the required permission/privilege
+     */
+    private void enforceModifyPermissionOrCarrierPrivilege() {
+        try {
+          mApp.enforceCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE, null);
+        } catch (SecurityException e) {
+            log("No modify permission, check carrier privilege next.");
+            if (hasCarrierPrivileges() != TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
+              loge("No Carrier Privilege.");
+              throw new SecurityException("No modify permission or carrier privilege.");
+            }
+        }
+    }
+
+    /**
+     * Make sure the caller has carrier privilege.
+     *
+     * @throws SecurityException if the caller does not have the required permission
+     */
+    private void enforceCarrierPrivilege() {
+        if (hasCarrierPrivileges() != TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
+          loge("No Carrier Privilege.");
+          throw new SecurityException("No Carrier Privilege.");
+        }
+    }
+
+    /**
      * Make sure the caller has the CALL_PHONE permission.
      *
      * @throws SecurityException if the caller does not have the required permission
@@ -1380,7 +1409,7 @@
 
     @Override
     public int iccOpenLogicalChannel(String AID) {
-        enforceModifyPermission();
+        enforceModifyPermissionOrCarrierPrivilege();
 
         if (DBG) log("iccOpenLogicalChannel: " + AID);
         Integer channel = (Integer)sendRequest(CMD_OPEN_CHANNEL, AID);
@@ -1390,7 +1419,7 @@
 
     @Override
     public boolean iccCloseLogicalChannel(int channel) {
-        enforceModifyPermission();
+        enforceModifyPermissionOrCarrierPrivilege();
 
         if (DBG) log("iccCloseLogicalChannel: " + channel);
         if (channel < 0) {
@@ -1404,7 +1433,7 @@
     @Override
     public String iccTransmitApduLogicalChannel(int channel, int cla,
             int command, int p1, int p2, int p3, String data) {
-        enforceModifyPermission();
+        enforceModifyPermissionOrCarrierPrivilege();
 
         if (DBG) {
             log("iccTransmitApduLogicalChannel: chnl=" + channel + " cla=" + cla +
@@ -1435,7 +1464,7 @@
 
     @Override
     public String sendEnvelopeWithStatus(String content) {
-        enforceModifyPermission();
+        enforceModifyPermissionOrCarrierPrivilege();
 
         IccIoResult response = (IccIoResult)sendRequest(CMD_SEND_ENVELOPE, content);
         if (response.payload == null) {
@@ -1458,7 +1487,7 @@
      */
     @Override
     public String nvReadItem(int itemID) {
-        enforceModifyPermission();
+        enforceModifyPermissionOrCarrierPrivilege();
         if (DBG) log("nvReadItem: item " + itemID);
         String value = (String) sendRequest(CMD_NV_READ_ITEM, itemID);
         if (DBG) log("nvReadItem: item " + itemID + " is \"" + value + '"');
@@ -1475,7 +1504,7 @@
      */
     @Override
     public boolean nvWriteItem(int itemID, String itemValue) {
-        enforceModifyPermission();
+        enforceModifyPermissionOrCarrierPrivilege();
         if (DBG) log("nvWriteItem: item " + itemID + " value \"" + itemValue + '"');
         Boolean success = (Boolean) sendRequest(CMD_NV_WRITE_ITEM,
                 new Pair<Integer, String>(itemID, itemValue));
@@ -1492,7 +1521,7 @@
      */
     @Override
     public boolean nvWriteCdmaPrl(byte[] preferredRoamingList) {
-        enforceModifyPermission();
+        enforceModifyPermissionOrCarrierPrivilege();
         if (DBG) log("nvWriteCdmaPrl: value: " + HexDump.toHexString(preferredRoamingList));
         Boolean success = (Boolean) sendRequest(CMD_NV_WRITE_CDMA_PRL, preferredRoamingList);
         if (DBG) log("nvWriteCdmaPrl: " + (success ? "ok" : "fail"));
@@ -1508,7 +1537,7 @@
      */
     @Override
     public boolean nvResetConfig(int resetType) {
-        enforceModifyPermission();
+        enforceModifyPermissionOrCarrierPrivilege();
         if (DBG) log("nvResetConfig: type " + resetType);
         Boolean success = (Boolean) sendRequest(CMD_NV_RESET_CONFIG, resetType);
         if (DBG) log("nvResetConfig: type " + resetType + ' ' + (success ? "ok" : "fail"));
@@ -1554,7 +1583,7 @@
      */
     @Override
     public int getPreferredNetworkType() {
-        enforceModifyPermission();
+        enforceModifyPermissionOrCarrierPrivilege();
         if (DBG) log("getPreferredNetworkType");
         int[] result = (int[]) sendRequest(CMD_GET_PREFERRED_NETWORK_TYPE, null);
         int networkType = (result != null ? result[0] : -1);
@@ -1571,7 +1600,7 @@
      */
     @Override
     public boolean setPreferredNetworkType(int networkType) {
-        enforceModifyPermission();
+        enforceModifyPermissionOrCarrierPrivilege();
         if (DBG) log("setPreferredNetworkType: type " + networkType);
         Boolean success = (Boolean) sendRequest(CMD_SET_PREFERRED_NETWORK_TYPE, networkType);
         if (DBG) log("setPreferredNetworkType: " + (success ? "ok" : "fail"));
@@ -1587,7 +1616,7 @@
      */
     @Override
     public boolean setCdmaSubscription(int subscriptionType) {
-        enforceModifyPermission();
+        enforceModifyPermissionOrCarrierPrivilege();
         if (DBG) log("setCdmaSubscription: type " + subscriptionType);
         if (subscriptionType != mPhone.CDMA_SUBSCRIPTION_RUIM_SIM &&
             subscriptionType != mPhone.CDMA_SUBSCRIPTION_NV) {
diff --git a/src/com/android/services/telephony/CdmaConnection.java b/src/com/android/services/telephony/CdmaConnection.java
index f46e64f..abd0a08 100644
--- a/src/com/android/services/telephony/CdmaConnection.java
+++ b/src/com/android/services/telephony/CdmaConnection.java
@@ -16,6 +16,8 @@
 
 package com.android.services.telephony;
 
+import android.telecomm.CallCapabilities;
+
 import com.android.internal.telephony.Connection;
 import com.android.internal.telephony.Phone;
 
@@ -46,4 +48,10 @@
         // no-op, we only play timed dtmf tones for cdma.
         super.onStopDtmfTone();
     }
+
+    @Override
+    protected int buildCallCapabilities() {
+        int capabilities = CallCapabilities.MUTE;
+        return capabilities;
+    }
 }
diff --git a/src/com/android/services/telephony/GsmConnection.java b/src/com/android/services/telephony/GsmConnection.java
index 7019068..f7ab344 100644
--- a/src/com/android/services/telephony/GsmConnection.java
+++ b/src/com/android/services/telephony/GsmConnection.java
@@ -16,6 +16,8 @@
 
 package com.android.services.telephony;
 
+import android.telecomm.CallCapabilities;
+
 import com.android.internal.telephony.CallStateException;
 import com.android.internal.telephony.Connection;
 import com.android.internal.telephony.Phone;
@@ -24,6 +26,7 @@
  * Manages a single phone call handled by GSM.
  */
 public class GsmConnection extends PstnConnection {
+    private boolean mIsConferenceCapable;
 
     public GsmConnection(Phone phone, Connection connection) {
         super(phone, connection);
@@ -43,6 +46,13 @@
         super.onStopDtmfTone();
     }
 
+    void setIsConferenceCapable(boolean isConferenceCapable) {
+        if (mIsConferenceCapable != isConferenceCapable) {
+            mIsConferenceCapable = isConferenceCapable;
+            updateCallCapabilities();
+        }
+    }
+
     public void performConference() {
         try {
             Log.d(this, "conference - %s", this);
@@ -51,4 +61,16 @@
             Log.e(this, e, "Failed to conference call.");
         }
     }
+
+    @Override
+    protected int buildCallCapabilities() {
+        int capabilities = CallCapabilities.MUTE | CallCapabilities.SUPPORT_HOLD;
+        if (getState() == State.ACTIVE || getState() == State.HOLDING) {
+            capabilities |= CallCapabilities.HOLD;
+        }
+        if (mIsConferenceCapable) {
+            capabilities |= CallCapabilities.MERGE_CALLS;
+        }
+        return capabilities;
+    }
 }
diff --git a/src/com/android/services/telephony/PstnConnectionService.java b/src/com/android/services/telephony/PstnConnectionService.java
index 82165da..f60cd73 100644
--- a/src/com/android/services/telephony/PstnConnectionService.java
+++ b/src/com/android/services/telephony/PstnConnectionService.java
@@ -19,6 +19,7 @@
 import android.net.Uri;
 
 import android.telephony.DisconnectCause;
+import android.telecomm.CallCapabilities;
 import android.telecomm.Connection;
 import android.telecomm.ConnectionRequest;
 import android.telecomm.Response;
@@ -200,7 +201,7 @@
         // TODO: Create a more general framework for conferencing. At the moment, our goal is
         // simply not to break the previous GSM-specific conferencing functionality.
         if (connection instanceof GsmConnection || connection instanceof ConferenceConnection) {
-            if (connection.isConferenceCapable()) {
+            if ((connection.getCallCapabilities() & CallCapabilities.MERGE_CALLS) != 0) {
                 callback.onResult(token,
                         mGsmConferenceController.createConferenceConnection(connection));
             }
diff --git a/src/com/android/services/telephony/TelephonyConnection.java b/src/com/android/services/telephony/TelephonyConnection.java
index 1cfabbb..17ab1c9 100644
--- a/src/com/android/services/telephony/TelephonyConnection.java
+++ b/src/com/android/services/telephony/TelephonyConnection.java
@@ -29,7 +29,7 @@
 /**
  * Manages a single phone call in Telephony.
  */
-class TelephonyConnection extends Connection {
+abstract class TelephonyConnection extends Connection {
     private static final int EVENT_PRECISE_CALL_STATE_CHANGED = 1;
 
     private final StateHandler mHandler = new StateHandler();
@@ -135,6 +135,15 @@
         super.onSetAudioState(audioState);
     }
 
+    protected abstract int buildCallCapabilities();
+
+    final void updateCallCapabilities() {
+        int newCallCapabilities = buildCallCapabilities();
+        if (getCallCapabilities() != newCallCapabilities) {
+            setCallCapabilities(newCallCapabilities);
+        }
+    }
+
     protected void hangup(int disconnectCause) {
         if (mOriginalConnection != null) {
             try {
@@ -188,6 +197,7 @@
                 case DISCONNECTING:
                     break;
             }
+            updateCallCapabilities();
         }
     }
 
diff --git a/src/com/android/services/telephony/TelephonyConnectionService.java b/src/com/android/services/telephony/TelephonyConnectionService.java
index 717049c..0cefab6 100644
--- a/src/com/android/services/telephony/TelephonyConnectionService.java
+++ b/src/com/android/services/telephony/TelephonyConnectionService.java
@@ -111,12 +111,14 @@
             final TelephonyConnection telephonyConnection =
                     createTelephonyConnection(request, phone, connection);
             respondWithResult(request, response, telephonyConnection);
+            telephonyConnection.updateCallCapabilities();
 
             final com.android.internal.telephony.Connection connectionFinal = connection;
             PostDialListener postDialListener = new PostDialListener() {
                 @Override
                 public void onPostDialWait() {
-                    telephonyConnection.setPostDialWait(connectionFinal.getRemainingPostDialString());
+                    telephonyConnection.setPostDialWait(
+                            connectionFinal.getRemainingPostDialString());
                 }
             };
             connection.addPostDialListener(postDialListener);