Merge "Moving carrier checking privilege logic into UiccCarrierPrivilegeRules." into lmp-dev
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index f54b63a..ee47f18 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -567,7 +567,7 @@
         </service>
         <service
                 android:singleUser="true"
-                android:name="com.android.services.telephony.PstnConnectionService"
+                android:name="com.android.services.telephony.TelephonyConnectionService"
                 android:label="@string/pstn_connection_service_label">
             <intent-filter>
                 <action android:name="android.telecomm.ConnectionService" />
diff --git a/res/values/config.xml b/res/values/config.xml
index 5dea4dd..59d9728 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -142,7 +142,7 @@
     <!-- Default connection service setting.
          TODO: This is GSM specific. Need to define a generic "use the builtin SIMs" Connection
          Service and use it as the default. -->
-    <string name="connection_service_default" translatable="false">com.android.phone/com.android.services.telephony.PstnConnectionService</string>
+    <string name="connection_service_default" translatable="false">com.android.phone/com.android.services.telephony.TelephonyConnectionService</string>
 
     <!-- Does not display additional call seting for IMS phone based on GSM Phone -->
     <bool name="config_additional_call_setting">true</bool>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 4caeef2..2825a7e 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -115,7 +115,7 @@
     <!-- settings strings -->
 
     <!-- Error message for users that aren't allowed to modify Mobile Network settings [CHAR LIMIT=none] -->
-    <string name="mobile_network_settings_not_available">Mobile network settings are not available for this user</string>
+    <string name="mobile_network_settings_not_available">Cellular network settings are not available for this user</string>
     <!-- GSM Call settings screen, setting option name -->
     <string name="labelGSMMore">GSM call settings</string>
     <!-- CDM Call settings screen, setting option name -->
@@ -278,7 +278,7 @@
 
     <!-- networks setting strings --><skip/>
     <!-- Mobile network settings screen title -->
-    <string name="mobile_networks">Mobile network settings</string>
+    <string name="mobile_networks">Cellular network settings</string>
     <!-- Available networks screen title/heading -->
     <string name="label_available">Available networks</string>
     <!-- Mobile network settings screen, toast when searching for available networks -->
@@ -371,8 +371,8 @@
     </string-array>
     <!-- Mobile network settings screen, data enabling checkbox name -->
     <string name="data_enabled">Data enabled</string>
-    <!-- Mobile network settings screen, setting summary text when check box is not selected (explains what selecting it would do) -->
-    <string name="data_enable_summary">Enable data access over Mobile network</string>
+    <!-- Mobile network settings screen, setting summary text when check box is not selected (explains what selecting it would do) [CHAR LIMITS=40] -->
+    <string name="data_enable_summary">Allow data usage</string>
     <!-- Mobile network settings screen, setting check box name -->
     <string name="roaming">Data roaming</string>
     <!-- Mobile network settings screen, setting summary text when check box is selected -->
@@ -413,7 +413,7 @@
 
     <string name="throttle_rate_subtext">Data rate reduced to <xliff:g id="used">%1$d</xliff:g> Kb/s if data use limit is exceeded</string>
 
-    <string name="throttle_help_subtext">More information about your carrier\'s mobile network data use policy</string>
+    <string name="throttle_help_subtext">More information about your carrier\'s cellular network data use policy</string>
 
     <string name="cell_broadcast_sms">Cell Broadcast SMS</string>
 
@@ -892,7 +892,7 @@
          This string is currently unused (see comments in InCallScreen.java.) -->
     <string name="incall_error_emergency_only">Not registered on network.</string>
     <!-- In-call screen: call failure message displayed in an error dialog -->
-    <string name="incall_error_out_of_service">Mobile network not available.</string>
+    <string name="incall_error_out_of_service">Cellular network not available.</string>
     <!-- In-call screen: call failure message displayed in an error dialog -->
     <string name="incall_error_no_phone_number_supplied">Call not sent, no valid number entered.</string>
     <!-- In-call screen: call failure message displayed in an error dialog -->
@@ -1196,15 +1196,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 62b8266..5ca050e 100644
--- a/sip/src/com/android/services/telephony/sip/SipBroadcastReceiver.java
+++ b/sip/src/com/android/services/telephony/sip/SipBroadcastReceiver.java
@@ -62,8 +62,8 @@
 
         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_PHONE_ACCOUNT,
+                SipConnectionService.getPhoneAccount(context));
         telecommIntent.putExtra(TelecommConstants.EXTRA_INCOMING_CALL_EXTRAS, extras);
 
         context.startActivityAsUser(telecommIntent, UserHandle.CURRENT);
diff --git a/sip/src/com/android/services/telephony/sip/SipCallServiceProvider.java b/sip/src/com/android/services/telephony/sip/SipCallServiceProvider.java
deleted file mode 100644
index cc467e9..0000000
--- a/sip/src/com/android/services/telephony/sip/SipCallServiceProvider.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * 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 5b065fd..5bca97b 100644
--- a/sip/src/com/android/services/telephony/sip/SipConnection.java
+++ b/sip/src/com/android/services/telephony/sip/SipConnection.java
@@ -40,7 +40,7 @@
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case MSG_PRECISE_CALL_STATE_CHANGED:
-                    updateState();
+                    updateState(false);
                     break;
             }
         }
@@ -210,14 +210,14 @@
                 call.getEarliestConnection() == mOriginalConnection;
     }
 
-    private void updateState() {
+    private void updateState(boolean force) {
         if (mOriginalConnection == null) {
             return;
         }
 
         Call.State newState = mOriginalConnection.getState();
         if (VERBOSE) log("updateState, " + mOriginalConnectionState + " -> " + newState);
-        if (mOriginalConnectionState != newState) {
+        if (force || mOriginalConnectionState != newState) {
             mOriginalConnectionState = newState;
             switch (newState) {
                 case IDLE:
@@ -243,7 +243,7 @@
                 case DISCONNECTING:
                     break;
             }
-            updateCallCapabilities();
+            updateCallCapabilities(force);
         }
     }
 
@@ -255,16 +255,17 @@
         return capabilities;
     }
 
-    void updateCallCapabilities() {
+    void updateCallCapabilities(boolean force) {
         int newCallCapabilities = buildCallCapabilities();
-        if (getCallCapabilities() != newCallCapabilities) {
+        if (force || getCallCapabilities() != newCallCapabilities) {
             setCallCapabilities(newCallCapabilities);
         }
     }
 
     void onAddedToCallService() {
         if (VERBOSE) log("onAddedToCallService");
-        updateCallCapabilities();
+        updateState(true);
+        updateCallCapabilities(true);
         setAudioModeIsVoip(true);
         if (mOriginalConnection != null) {
             setCallerDisplayName(mOriginalConnection.getCnapName(),
diff --git a/sip/src/com/android/services/telephony/sip/SipConnectionService.java b/sip/src/com/android/services/telephony/sip/SipConnectionService.java
index 513fbfd..a46180d 100644
--- a/sip/src/com/android/services/telephony/sip/SipConnectionService.java
+++ b/sip/src/com/android/services/telephony/sip/SipConnectionService.java
@@ -16,6 +16,7 @@
 
 package com.android.services.telephony.sip;
 
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.net.sip.SipAudioCall;
@@ -28,6 +29,7 @@
 import android.telecomm.Connection;
 import android.telecomm.ConnectionRequest;
 import android.telecomm.ConnectionService;
+import android.telecomm.PhoneAccount;
 import android.telecomm.Response;
 import android.telephony.DisconnectCause;
 import android.telephony.PhoneNumberUtils;
@@ -43,16 +45,24 @@
     private static final String PREFIX = "[SipConnectionService] ";
     private static final boolean VERBOSE = true; /* STOP SHIP if true */
 
+    static PhoneAccount getPhoneAccount(Context context) {
+        return new PhoneAccount(
+                new ComponentName(context, SipConnectionService.class),
+                null /* id */,
+                null /* handle */,
+                PhoneAccount.CAPABILITY_CALL_PROVIDER);
+    }
+
     @Override
-    protected void onCreateConnections(
+    protected void onCreateOutgoingConnection(
             final ConnectionRequest request,
-            final OutgoingCallResponse<Connection> response) {
-        if (VERBOSE) log("onCreateConnections, request: " + request);
+            final CreateConnectionResponse<Connection> response) {
+        if (VERBOSE) log("onCreateOutgoingConnection, request: " + request);
 
         SipProfileChooser.Callback callback = new SipProfileChooser.Callback() {
             @Override
             public void onSipChosen(SipProfile profile) {
-                if (VERBOSE) log("onCreateConnections, onSipChosen: " + profile);
+                if (VERBOSE) log("onCreateOutgoingConnection, onSipChosen: " + profile);
                 SipConnection connection = createConnectionForProfile(profile, request);
                 if (connection == null) {
                     response.onCancel(request);
@@ -63,13 +73,13 @@
 
             @Override
             public void onSipNotChosen() {
-                if (VERBOSE) log("onCreateConnections, onSipNotChosen");
+                if (VERBOSE) log("onCreateOutgoingConnection, onSipNotChosen");
                 response.onFailure(request, DisconnectCause.ERROR_UNSPECIFIED, null);
             }
 
             @Override
             public void onCancelCall() {
-                if (VERBOSE) log("onCreateConnections, onCancelCall");
+                if (VERBOSE) log("onCreateOutgoingConnection, onCancelCall");
                 response.onCancel(request);
             }
         };
@@ -89,14 +99,20 @@
     @Override
     protected void onCreateIncomingConnection(
             ConnectionRequest request,
-            Response<ConnectionRequest, Connection> response) {
+            CreateConnectionResponse<Connection> response) {
         if (VERBOSE) log("onCreateIncomingConnection, request: " + request);
 
+        if (request.getExtras() == null) {
+            if (VERBOSE) log("onCreateIncomingConnection, no extras");
+            response.onFailure(request, DisconnectCause.ERROR_UNSPECIFIED, null);
+            return;
+        }
+
         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);
+            response.onFailure(request, DisconnectCause.ERROR_UNSPECIFIED, null);
             return;
         }
 
@@ -104,8 +120,8 @@
         try {
             sipAudioCall = SipManager.newInstance(this).takeAudioCall(sipIntent, null);
         } catch (SipException e) {
-            log("onCreateConferenceConnection, takeAudioCall exception: " + e);
-            response.onError(request, DisconnectCause.ERROR_UNSPECIFIED, null);
+            log("onCreateIncomingConnection, takeAudioCall exception: " + e);
+            response.onCancel(request);
             return;
         }
 
@@ -119,11 +135,11 @@
             if (VERBOSE) log("onCreateIncomingConnection, new connection: " + originalConnection);
             if (originalConnection != null) {
                 SipConnection connection = new SipConnection(originalConnection);
-                response.onResult(getConnectionRequestForIncomingCall(request, originalConnection),
+                response.onSuccess(getConnectionRequestForIncomingCall(request, originalConnection),
                         connection);
             } else {
                 if (VERBOSE) log("onCreateIncomingConnection, takingIncomingCall failed");
-                response.onError(request, DisconnectCause.ERROR_UNSPECIFIED, null);
+                response.onCancel(request);
             }
         }
     }
diff --git a/src/com/android/phone/CallFeaturesSetting.java b/src/com/android/phone/CallFeaturesSetting.java
index 95d648c..c3d0191 100644
--- a/src/com/android/phone/CallFeaturesSetting.java
+++ b/src/com/android/phone/CallFeaturesSetting.java
@@ -2251,7 +2251,7 @@
         CharSequence label = mConnectionServiceLabelByComponentName.get(
                 value == null ? mConnectionService.getValue() : value);
         if (label == null) {
-            Log.wtf(LOG_TAG, "Unknown default connection service entry " +
+            Log.w(LOG_TAG, "Unknown default connection service entry " +
                     mConnectionService.getValue());
             mConnectionService.setSummary("");
         }
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index 721f7e2..5e8c9f5 100644
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -1693,7 +1693,7 @@
 
     @Override
     public void enableSimplifiedNetworkSettings(long subId, boolean enable) {
-        enforceModifyPermission();
+        enforceModifyPermissionOrCarrierPrivilege();
         if (enable) {
             mSimplifiedNetworkSettings.add(subId);
         } else {
@@ -1709,7 +1709,7 @@
 
     @Override
     public void setLine1NumberForDisplay(long subId, String alphaTag, String number) {
-        enforceModifyPermission();
+        enforceModifyPermissionOrCarrierPrivilege();
         mAdnRecordsForDisplay.put(subId, new AdnRecord(alphaTag, number));
     }
 
diff --git a/src/com/android/services/telephony/CdmaConnection.java b/src/com/android/services/telephony/CdmaConnection.java
index abd0a08..52717f9 100644
--- a/src/com/android/services/telephony/CdmaConnection.java
+++ b/src/com/android/services/telephony/CdmaConnection.java
@@ -19,34 +19,32 @@
 import android.telecomm.CallCapabilities;
 
 import com.android.internal.telephony.Connection;
-import com.android.internal.telephony.Phone;
 
 /**
  * Manages a single phone call handled by CDMA.
  */
-public class CdmaConnection extends PstnConnection {
-
-    public CdmaConnection(Phone phone, Connection connection) {
-        super(phone, connection);
+final class CdmaConnection extends TelephonyConnection {
+    CdmaConnection(Connection connection) {
+        super(connection);
     }
 
     /** {@inheritDoc} */
     @Override
-    public void onPlayDtmfTone(char digit) {
+    protected void onPlayDtmfTone(char digit) {
         // TODO(santoscordon): There are conditions where we should play dtmf tones with different
         // timeouts.
         // TODO(santoscordon): We get explicit response from the phone via a Message when the burst
         // tone has completed. During this time we can get subsequent requests. We need to stop
         // passing in null as the message and start handling it to implement a queue.
-        getPhone().sendBurstDtmf(Character.toString(digit), 0, 0, null);
-        super.onPlayDtmfTone(digit);
+        if (getPhone() != null) {
+            getPhone().sendBurstDtmf(Character.toString(digit), 0, 0, null);
+        }
     }
 
     /** {@inheritDoc} */
     @Override
-    public void onStopDtmfTone() {
+    protected void onStopDtmfTone() {
         // no-op, we only play timed dtmf tones for cdma.
-        super.onStopDtmfTone();
     }
 
     @Override
diff --git a/src/com/android/services/telephony/ConferenceConnection.java b/src/com/android/services/telephony/ConferenceConnection.java
index 3d62f6a..dbcdbfe 100644
--- a/src/com/android/services/telephony/ConferenceConnection.java
+++ b/src/com/android/services/telephony/ConferenceConnection.java
@@ -26,7 +26,7 @@
 /**
  * Manages state for a conference call.
  */
-class ConferenceConnection extends Connection {
+final class ConferenceConnection extends Connection {
     @Override
     protected void onChildrenChanged(List<Connection> children) {
         if (children.isEmpty()) {
diff --git a/src/com/android/services/telephony/GsmConferenceController.java b/src/com/android/services/telephony/GsmConferenceController.java
index 593c2dc..375f34d 100644
--- a/src/com/android/services/telephony/GsmConferenceController.java
+++ b/src/com/android/services/telephony/GsmConferenceController.java
@@ -28,7 +28,8 @@
  * Maintains a list of all the known GSM connections and implements GSM-specific conference
  * call functionality.
  */
-public class GsmConferenceController {
+final class GsmConferenceController {
+    private static GsmConferenceController sInstance;
 
     private final Connection.Listener mConnectionListener = new Connection.Listener() {
                 @Override
@@ -54,27 +55,41 @@
     /** The GSM conference connection object. */
     private ConferenceConnection mGsmConferenceConnection;
 
-    public void add(GsmConnection connection) {
-        connection.addConnectionListener(mConnectionListener);
-        mGsmConnections.add(connection);
-        recalculate();
+    static void add(GsmConnection connection) {
+        if (sInstance == null) {
+            sInstance = new GsmConferenceController();
+        }
+        connection.addConnectionListener(sInstance.mConnectionListener);
+        sInstance.mGsmConnections.add(connection);
+        sInstance.recalculate();
     }
 
-    public void remove(GsmConnection connection) {
-        connection.removeConnectionListener(mConnectionListener);
-        mGsmConnections.remove(connection);
-        recalculate();
+    static void remove(GsmConnection connection) {
+        if (sInstance != null) {
+            connection.removeConnectionListener(sInstance.mConnectionListener);
+            sInstance.mGsmConnections.remove(connection);
+            sInstance.recalculate();
+
+            if (sInstance.mGsmConnections.size() == 0 &&
+                    sInstance.mGsmConferenceConnection == null) {
+                sInstance = null;
+            }
+        }
     }
 
-    public ConferenceConnection createConferenceConnection(Connection rootConnection) {
-        if (mGsmConferenceConnection == null) {
-            mGsmConferenceConnection = new ConferenceConnection();
-            Log.d(this, "creating the conference connection: %s", mGsmConferenceConnection);
+    static ConferenceConnection createConferenceConnection(Connection rootConnection) {
+        if (sInstance != null) {
+            if (sInstance.mGsmConferenceConnection == null) {
+                sInstance.mGsmConferenceConnection = new ConferenceConnection();
+                Log.d(sInstance, "creating the conference connection: %s",
+                        sInstance.mGsmConferenceConnection);
+            }
+            if (rootConnection instanceof GsmConnection) {
+                ((GsmConnection) rootConnection).performConference();
+            }
+            return sInstance.mGsmConferenceConnection;
         }
-        if (rootConnection instanceof GsmConnection) {
-            ((GsmConnection) rootConnection).performConference();
-        }
-        return mGsmConferenceConnection;
+        return null;
     }
 
     /**
diff --git a/src/com/android/services/telephony/GsmConnection.java b/src/com/android/services/telephony/GsmConnection.java
index f7ab344..8b02b52 100644
--- a/src/com/android/services/telephony/GsmConnection.java
+++ b/src/com/android/services/telephony/GsmConnection.java
@@ -20,45 +20,49 @@
 
 import com.android.internal.telephony.CallStateException;
 import com.android.internal.telephony.Connection;
-import com.android.internal.telephony.Phone;
 
 /**
  * Manages a single phone call handled by GSM.
  */
-public class GsmConnection extends PstnConnection {
+final class GsmConnection extends TelephonyConnection {
     private boolean mIsConferenceCapable;
 
-    public GsmConnection(Phone phone, Connection connection) {
-        super(phone, connection);
+    GsmConnection(Connection connection) {
+        super(connection);
+        GsmConferenceController.add(this);
     }
 
     /** {@inheritDoc} */
     @Override
-    public void onPlayDtmfTone(char digit) {
-        getPhone().startDtmf(digit);
-        super.onPlayDtmfTone(digit);
+    protected void onPlayDtmfTone(char digit) {
+        if (getPhone() != null) {
+            getPhone().startDtmf(digit);
+        }
     }
 
     /** {@inheritDoc} */
     @Override
-    public void onStopDtmfTone() {
-        getPhone().stopDtmf();
-        super.onStopDtmfTone();
+    protected void onStopDtmfTone() {
+        if (getPhone() != null) {
+            getPhone().stopDtmf();
+        }
     }
 
     void setIsConferenceCapable(boolean isConferenceCapable) {
         if (mIsConferenceCapable != isConferenceCapable) {
             mIsConferenceCapable = isConferenceCapable;
-            updateCallCapabilities();
+            updateCallCapabilities(false);
         }
     }
 
-    public void performConference() {
-        try {
-            Log.d(this, "conference - %s", this);
-            getPhone().conference();
-        } catch (CallStateException e) {
-            Log.e(this, e, "Failed to conference call.");
+    void performConference() {
+        Log.d(this, "performConference - %s", this);
+        if (getPhone() != null) {
+            try {
+                getPhone().conference();
+            } catch (CallStateException e) {
+                Log.e(this, e, "Failed to conference call.");
+            }
         }
     }
 
@@ -73,4 +77,10 @@
         }
         return capabilities;
     }
+
+    @Override
+    void onRemovedFromCallService() {
+        super.onRemovedFromCallService();
+        GsmConferenceController.remove(this);
+    }
 }
diff --git a/src/com/android/services/telephony/PstnConnection.java b/src/com/android/services/telephony/PstnConnection.java
deleted file mode 100644
index b11c9d0..0000000
--- a/src/com/android/services/telephony/PstnConnection.java
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * 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.os.AsyncResult;
-import android.os.Handler;
-import android.os.Message;
-
-import android.telephony.DisconnectCause;
-
-import com.android.internal.telephony.Call;
-import com.android.internal.telephony.CallStateException;
-import com.android.internal.telephony.Connection;
-import com.android.internal.telephony.Phone;
-
-/**
- * Manages a single phone call handled by the PSTN infrastructure.
- */
-public abstract class PstnConnection extends TelephonyConnection {
-
-    private static final int EVENT_RINGBACK_TONE = 1;
-
-    private final Handler mHandler = new Handler() {
-        private Connection getForegroundConnection() {
-            return mPhone.getForegroundCall().getEarliestConnection();
-        }
-
-        public void handleMessage(Message msg) {
-            // TODO: This code assumes that there is only one connection in the foreground call,
-            // in other words, it punts on network-mediated conference calling.
-            if (getOriginalConnection() != getForegroundConnection()) {
-                return;
-            }
-            switch (msg.what) {
-                case EVENT_RINGBACK_TONE:
-                    setRequestingRingback((Boolean) ((AsyncResult) msg.obj).result);
-                    break;
-            }
-        }
-    };
-
-    private final Phone mPhone;
-
-    public PstnConnection(Phone phone, Connection connection) {
-        super(connection);
-        mPhone = phone;
-        phone.registerForRingbackTone(mHandler, EVENT_RINGBACK_TONE, null);
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    protected void onAnswer() {
-        // TODO(santoscordon): Tons of hairy logic is missing here around multiple active calls on
-        // CDMA devices. See {@link CallManager.acceptCall}.
-
-        Log.i(this, "Answer call.");
-        if (isValidRingingCall(getOriginalConnection())) {
-            try {
-                mPhone.acceptCall();
-            } catch (CallStateException e) {
-                Log.e(this, e, "Failed to accept call.");
-            }
-        }
-        super.onAnswer();
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    protected void onReject() {
-        Log.i(this, "Reject call.");
-        if (isValidRingingCall(getOriginalConnection())) {
-            hangup(DisconnectCause.INCOMING_REJECTED);
-        }
-        super.onReject();
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    protected void onDisconnect() {
-        mPhone.unregisterForRingbackTone(mHandler);
-        super.onDisconnect();
-    }
-
-    @Override
-    public void onPostDialContinue(boolean proceed) {
-        if (proceed) {
-            getOriginalConnection().proceedAfterWaitChar();
-        } else {
-            getOriginalConnection().cancelPostDial();
-        }
-    }
-
-    protected Phone getPhone() {
-        return mPhone;
-    }
-
-    /**
-     * Checks to see if the specified low-level Telephony {@link Connection} corresponds to an
-     * active incoming call. Returns false if there is no such actual call, or if the
-     * associated call is not incoming (See {@link Call.State#isRinging}).
-     *
-     * @param connection The connection to ask about.
-     */
-    private boolean isValidRingingCall(Connection connection) {
-        Call ringingCall = mPhone.getRingingCall();
-
-        if (ringingCall.getState().isRinging()) {
-            // The ringingCall object is always not-null so we have to check its current state.
-            if (ringingCall.getEarliestConnection() == connection) {
-                // The ringing connection is the same one for this call. We have a match!
-                return true;
-            } else {
-                Log.w(this, "A ringing connection exists, but it is not the same connection.");
-            }
-        } else {
-            Log.i(this, "There is no longer a ringing call.");
-        }
-
-        return false;
-    }
-}
diff --git a/src/com/android/services/telephony/PstnConnectionService.java b/src/com/android/services/telephony/PstnConnectionService.java
deleted file mode 100644
index a38fc8d..0000000
--- a/src/com/android/services/telephony/PstnConnectionService.java
+++ /dev/null
@@ -1,222 +0,0 @@
-/*
- * 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.telephony.DisconnectCause;
-import android.telecomm.CallCapabilities;
-import android.telecomm.Connection;
-import android.telecomm.ConnectionRequest;
-import android.telecomm.Response;
-import android.telephony.PhoneNumberUtils;
-import android.telephony.TelephonyManager;
-
-import com.android.internal.telephony.Call;
-import com.android.internal.telephony.Phone;
-import com.android.internal.telephony.PhoneFactory;
-import com.android.phone.Constants;
-
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * The connection service for making PSTN connections using built-in SIM cards.
- */
-public final class PstnConnectionService extends TelephonyConnectionService {
-
-    private EmergencyCallHelper mEmergencyCallHelper;
-
-    private final Set<ConnectionRequest> mPendingOutgoingEmergencyCalls = new HashSet<>();
-
-    private final GsmConferenceController mGsmConferenceController = new GsmConferenceController();
-
-    @Override
-    public void onCreate() {
-        super.onCreate();
-        mEmergencyCallHelper = new EmergencyCallHelper(this);
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public void onCreateConnections(
-            final ConnectionRequest request,
-            final OutgoingCallResponse<Connection> response) {
-
-        if (!canCall(request.getHandle())) {
-            Log.d(this, "Cannot place the call with %s", this.getClass().getSimpleName());
-            respondWithError(
-                    request,
-                    response,
-                    DisconnectCause.ERROR_UNSPECIFIED,  // TODO: Code for "ConnSvc cannot call"
-                    "Cannot place call.");
-            return;
-        }
-
-        // TODO: Consider passing call emergency information as part of ConnectionRequest so
-        // that we do not have to make the check here once again.
-        String handle = request.getHandle().getSchemeSpecificPart();
-        final Phone phone = PhoneFactory.getDefaultPhone();
-        if (PhoneNumberUtils.isPotentialEmergencyNumber(handle)) {
-            EmergencyCallHelper.Callback callback = new EmergencyCallHelper.Callback() {
-                @Override
-                public void onComplete(boolean isRadioReady) {
-                    if (mPendingOutgoingEmergencyCalls.remove(request)) {
-                        // The emergency call was still pending (not aborted) so continue with the
-                        // rest of the logic.
-
-                        if (isRadioReady) {
-                            startCallWithPhone(phone, request, response);
-                        } else {
-                            respondWithError(
-                                    request,
-                                    response,
-                                    DisconnectCause.POWER_OFF,
-                                    "Failed to turn on radio.");
-                        }
-                    }
-                }
-            };
-
-            mPendingOutgoingEmergencyCalls.add(request);
-
-            // If the radio is already on, this will call us back fairly quickly.
-            mEmergencyCallHelper.startTurnOnRadioSequence(phone, callback);
-        } else {
-            startCallWithPhone(phone, request, response);
-        }
-        super.onCreateConnections(request, response);
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public void onCreateIncomingConnection(
-            ConnectionRequest request,
-            Response<ConnectionRequest, Connection> response) {
-        Log.d(this, "onCreateIncomingConnection");
-        Call call = PhoneFactory.getDefaultPhone().getRingingCall();
-
-        // The ringing call is always not-null, check if it is truly ringing by checking its state.
-        if (call.getState().isRinging()) {
-            com.android.internal.telephony.Connection connection = call.getEarliestConnection();
-
-            if (isConnectionKnown(connection)) {
-                respondWithError(
-                        request,
-                        response,
-                        DisconnectCause.ERROR_UNSPECIFIED,  // Internal error
-                        "Cannot set incoming call ID, ringing connection already registered.");
-            } else {
-                // Address can be null for blocked calls.
-                String address = connection.getAddress();
-                if (address == null) {
-                    address = "";
-                }
-
-                Uri handle = Uri.fromParts(Constants.SCHEME_TEL, address, null);
-
-                TelephonyConnection telephonyConnection;
-                try {
-                    telephonyConnection = createTelephonyConnection(
-                            request,
-                            PhoneFactory.getDefaultPhone(),
-                            connection);
-                } catch (Exception e) {
-                    respondWithError(
-                            request,
-                            response,
-                            DisconnectCause.ERROR_UNSPECIFIED,  // Internal error
-                            e.getMessage());
-                    return;
-                }
-
-                respondWithResult(
-                        new ConnectionRequest(
-                                request.getAccount(),
-                                request.getCallId(),
-                                handle,
-                                connection.getNumberPresentation(),
-                                request.getExtras(),
-                                request.getVideoState()),
-                        response,
-                        telephonyConnection);
-            }
-        } else {
-            respondWithError(
-                    request,
-                    response,
-                    DisconnectCause.INCOMING_MISSED,  // Most likely cause
-                    String.format("Found no ringing call, call state: %s", call.getState()));
-        }
-        super.onCreateIncomingConnection(request, response);
-    }
-
-    @Override
-    protected void onConnectionAdded(Connection connection) {
-        if (connection instanceof TelephonyConnection) {
-            ((TelephonyConnection) connection).onAddedToCallService();
-        }
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    protected boolean canCall(Uri handle) {
-        return Constants.SCHEME_TEL.equals(handle.getScheme());
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    protected TelephonyConnection onCreateTelephonyConnection(
-            ConnectionRequest request,
-            Phone phone,
-            com.android.internal.telephony.Connection connection) {
-        switch (phone.getPhoneType()) {
-            case TelephonyManager.PHONE_TYPE_GSM: {
-                final GsmConnection gsmConn = new GsmConnection(phone, connection);
-                mGsmConferenceController.add(gsmConn);
-                gsmConn.addConnectionListener(new Connection.Listener() {
-                    @Override
-                    public void onDestroyed(Connection c) {
-                        mGsmConferenceController.remove(gsmConn);
-                    }
-                });
-                return gsmConn;
-            }
-            case TelephonyManager.PHONE_TYPE_CDMA:
-                return new CdmaConnection(phone, connection);
-            default:
-                Log.d(this, "Inappropriate phone type for PSTN: %d", phone.getPhoneType());
-                return null;
-        }
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public void onCreateConferenceConnection(
-            String token,
-            Connection connection,
-            Response<String, Connection> callback) {
-        // 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.getCallCapabilities() & CallCapabilities.MERGE_CALLS) != 0) {
-                callback.onResult(token,
-                        mGsmConferenceController.createConferenceConnection(connection));
-            }
-        }
-    }
-}
diff --git a/src/com/android/services/telephony/PstnIncomingCallNotifier.java b/src/com/android/services/telephony/PstnIncomingCallNotifier.java
index 0d29ed9..e19991a 100644
--- a/src/com/android/services/telephony/PstnIncomingCallNotifier.java
+++ b/src/com/android/services/telephony/PstnIncomingCallNotifier.java
@@ -20,11 +20,11 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.net.Uri;
 import android.os.AsyncResult;
 import android.os.Handler;
 import android.os.Message;
 import android.os.UserHandle;
-import android.telecomm.CallServiceDescriptor;
 import android.telecomm.TelecommConstants;
 
 import com.android.internal.telephony.Call;
@@ -143,7 +143,7 @@
 
             // Final verification of the ringing state before sending the intent to Telecomm.
             if (call != null && call.getState().isRinging()) {
-                sendIncomingCallIntent();
+                sendIncomingCallIntent(connection);
             }
         }
     }
@@ -151,16 +151,13 @@
     /**
      * Sends the incoming call intent to telecomm.
      */
-    private void sendIncomingCallIntent() {
+    private void sendIncomingCallIntent(Connection connection) {
         Context context = mPhoneProxy.getContext();
 
-        CallServiceDescriptor.Builder builder = CallServiceDescriptor.newBuilder(context);
-        builder.setConnectionService(PstnConnectionService.class);
-        builder.setNetworkType(CallServiceDescriptor.FLAG_PSTN);
-
         Intent intent = new Intent(TelecommConstants.ACTION_INCOMING_CALL);
         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        intent.putExtra(TelecommConstants.EXTRA_CALL_SERVICE_DESCRIPTOR, builder.build());
+        intent.putExtra(TelecommConstants.EXTRA_PHONE_ACCOUNT,
+                TelephonyConnectionService.getPhoneAccount(context));
 
         Log.d(this, "Sending incoming call intent: %s", intent);
         context.startActivityAsUser(intent, UserHandle.CURRENT);
diff --git a/src/com/android/services/telephony/TelephonyCallServiceProvider.java b/src/com/android/services/telephony/TelephonyCallServiceProvider.java
deleted file mode 100644
index 5225759..0000000
--- a/src/com/android/services/telephony/TelephonyCallServiceProvider.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * 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.telecomm.CallServiceDescriptor;
-import android.telecomm.CallServiceLookupResponse;
-import android.telecomm.CallServiceProvider;
-
-import java.util.ArrayList;
-
-/**
- * This class is used to get a list of all CallServices.
- */
-public class TelephonyCallServiceProvider extends CallServiceProvider {
-    /** {@inheritDoc} */
-    @Override
-    public void lookupCallServices(CallServiceLookupResponse response) {
-        ArrayList<CallServiceDescriptor> descriptors = new ArrayList<CallServiceDescriptor>();
-        descriptors.add(CallServiceDescriptor.newBuilder(this)
-                   .setConnectionService(PstnConnectionService.class)
-                   .setNetworkType(CallServiceDescriptor.FLAG_PSTN)
-                   .build());
-        response.setCallServiceDescriptors(descriptors);
-    }
-}
diff --git a/src/com/android/services/telephony/TelephonyConnection.java b/src/com/android/services/telephony/TelephonyConnection.java
index 551e018..5886ef2 100644
--- a/src/com/android/services/telephony/TelephonyConnection.java
+++ b/src/com/android/services/telephony/TelephonyConnection.java
@@ -16,6 +16,8 @@
 
 package com.android.services.telephony;
 
+import android.net.Uri;
+import android.os.AsyncResult;
 import android.os.Handler;
 import android.os.Message;
 import android.telecomm.CallAudioState;
@@ -23,45 +25,87 @@
 
 import com.android.internal.telephony.Call;
 import com.android.internal.telephony.CallStateException;
+import com.android.internal.telephony.Connection.PostDialListener;
 import com.android.internal.telephony.Phone;
 import android.telecomm.Connection;
 
+import java.util.List;
+import java.util.Objects;
+
 /**
- * Manages a single phone call in Telephony.
+ * Base class for CDMA and GSM connections.
  */
 abstract class TelephonyConnection extends Connection {
-    private static final int EVENT_PRECISE_CALL_STATE_CHANGED = 1;
+    private static final int MSG_PRECISE_CALL_STATE_CHANGED = 1;
+    private static final int MSG_RINGBACK_TONE = 2;
 
-    private final StateHandler mHandler = new StateHandler();
+    private final Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_PRECISE_CALL_STATE_CHANGED:
+                    Log.v(TelephonyConnection.this, "MSG_PRECISE_CALL_STATE_CHANGED");
+                    updateState(false);
+                    break;
+                case MSG_RINGBACK_TONE:
+                    Log.v(TelephonyConnection.this, "MSG_RINGBACK_TONE");
+                    // TODO: This code assumes that there is only one connection in the foreground
+                    // call, in other words, it punts on network-mediated conference calling.
+                    if (getOriginalConnection() != getForegroundConnection()) {
+                        Log.v(TelephonyConnection.this, "handleMessage, original connection is " +
+                                "not foreground connection, skipping");
+                        return;
+                    }
+                    setRequestingRingback((Boolean) ((AsyncResult) msg.obj).result);
+                    break;
+            }
+        }
+    };
+
+    private final PostDialListener mPostDialListener = new PostDialListener() {
+        @Override
+        public void onPostDialWait() {
+            Log.v(TelephonyConnection.this, "onPostDialWait");
+            if (mOriginalConnection != null) {
+                setPostDialWait(mOriginalConnection.getRemainingPostDialString());
+            }
+        }
+    };
 
     private com.android.internal.telephony.Connection mOriginalConnection;
-    private Call.State mState = Call.State.IDLE;
+    private Call.State mOriginalConnectionState = Call.State.IDLE;
 
     protected TelephonyConnection(com.android.internal.telephony.Connection originalConnection) {
+        Log.v(this, "new TelephonyConnection, originalConnection: " + originalConnection);
         mOriginalConnection = originalConnection;
-        mOriginalConnection.getCall().getPhone().registerForPreciseCallStateChanged(mHandler,
-                EVENT_PRECISE_CALL_STATE_CHANGED, null);
-        updateState();
-    }
-
-    com.android.internal.telephony.Connection getOriginalConnection() {
-        return mOriginalConnection;
+        getPhone().registerForPreciseCallStateChanged(
+                mHandler, MSG_PRECISE_CALL_STATE_CHANGED, null);
+        getPhone().registerForRingbackTone(mHandler, MSG_RINGBACK_TONE, null);
+        mOriginalConnection.addPostDialListener(mPostDialListener);
     }
 
     @Override
-    protected void onAbort() {
-        hangup(DisconnectCause.LOCAL);
-        super.onAbort();
+    protected void onSetAudioState(CallAudioState audioState) {
+        // TODO: update TTY mode.
+        if (getPhone() != null) {
+            getPhone().setEchoSuppressionEnabled();
+        }
+    }
+
+    @Override
+    protected void onSetState(int state) {
+        Log.v(this, "onSetState, state: " + Connection.stateToString(state));
     }
 
     @Override
     protected void onDisconnect() {
+        Log.v(this, "onDisconnect");
         hangup(DisconnectCause.LOCAL);
-        super.onDisconnect();
     }
 
     @Override
     protected void onSeparate() {
+        Log.v(this, "onSeparate");
         if (mOriginalConnection != null) {
             try {
                 mOriginalConnection.separate();
@@ -69,15 +113,20 @@
                 Log.e(this, e, "Call to Connection.separate failed with exception");
             }
         }
-        super.onSeparate();
     }
 
     @Override
-    public void onHold() {
-        Log.d(this, "Attempting to put call on hold");
+    protected void onAbort() {
+        Log.v(this, "onAbort");
+        hangup(DisconnectCause.LOCAL);
+    }
+
+    @Override
+    protected void onHold() {
+        Log.v(this, "onHold");
         // TODO(santoscordon): Can dialing calls be put on hold as well since they take up the
         // foreground call slot?
-        if (Call.State.ACTIVE == mState) {
+        if (Call.State.ACTIVE == mOriginalConnectionState) {
             Log.v(this, "Holding active call");
             try {
                 Phone phone = mOriginalConnection.getCall().getPhone();
@@ -104,13 +153,12 @@
         } else {
             Log.w(this, "Cannot put a call that is not currently active on hold.");
         }
-        super.onHold();
     }
 
     @Override
     protected void onUnhold() {
-        Log.d(this, "Attempting to release call from hold");
-        if (Call.State.HOLDING == mState) {
+        Log.v(this, "onUnhold");
+        if (Call.State.HOLDING == mOriginalConnectionState) {
             try {
                 // TODO: This doesn't handle multiple calls across connection services yet
                 mOriginalConnection.getCall().getPhone().switchHoldingAndActive();
@@ -120,40 +168,98 @@
         } else {
             Log.w(this, "Cannot release a call that is not already on hold from hold.");
         }
-        super.onUnhold();
     }
 
     @Override
-    public void onSetAudioState(CallAudioState audioState) {
-        // TODO: update TTY mode.
-        if (mOriginalConnection != null) {
-            Call call = mOriginalConnection.getCall();
-            if (call != null) {
-                call.getPhone().setEchoSuppressionEnabled();
+    protected void onAnswer() {
+        Log.v(this, "onAnswer");
+        // TODO(santoscordon): Tons of hairy logic is missing here around multiple active calls on
+        // CDMA devices. See {@link CallManager.acceptCall}.
+
+        if (isValidRingingCall() && getPhone() != null) {
+            try {
+                getPhone().acceptCall();
+            } catch (CallStateException e) {
+                Log.e(this, e, "Failed to accept call.");
             }
         }
-        super.onSetAudioState(audioState);
+    }
+
+    @Override
+    protected void onReject() {
+        Log.v(this, "onReject");
+        if (isValidRingingCall()) {
+            hangup(DisconnectCause.INCOMING_REJECTED);
+        }
+        super.onReject();
+    }
+
+    @Override
+    protected void onPostDialContinue(boolean proceed) {
+        Log.v(this, "onPostDialContinue, proceed: " + proceed);
+        if (mOriginalConnection != null) {
+            if (proceed) {
+                mOriginalConnection.proceedAfterWaitChar();
+            } else {
+                mOriginalConnection.cancelPostDial();
+            }
+        }
+    }
+
+    @Override
+    protected void onSwapWithBackgroundCall() {
+        Log.v(this, "onSwapWithBackgroundCall");
+    }
+
+    @Override
+    protected void onChildrenChanged(List<Connection> children) {
+        Log.v(this, "onChildrenChanged, children: " + children);
+    }
+
+    @Override
+    protected void onPhoneAccountClicked() {
+        Log.v(this, "onPhoneAccountClicked");
     }
 
     protected abstract int buildCallCapabilities();
 
-    final void updateCallCapabilities() {
+    protected final void updateCallCapabilities(boolean force) {
         int newCallCapabilities = buildCallCapabilities();
-        if (getCallCapabilities() != newCallCapabilities) {
+        if (force || getCallCapabilities() != newCallCapabilities) {
             setCallCapabilities(newCallCapabilities);
         }
     }
 
-    final void onAddedToCallService() {
-        updateCallCapabilities();
+    protected final void updateHandle(boolean force) {
         if (mOriginalConnection != null) {
-            setCallerDisplayName(
-                    mOriginalConnection.getCnapName(),
-                    mOriginalConnection.getCnapNamePresentation());
+            Uri handle = TelephonyConnectionService.getHandleFromAddress(
+                    mOriginalConnection.getAddress());
+            int presentation = mOriginalConnection.getNumberPresentation();
+            if (force || !Objects.equals(handle, getHandle()) ||
+                    presentation != getHandlePresentation()) {
+                Log.v(this, "updateHandle, handle changed");
+                setHandle(handle, presentation);
+            }
+
+            String name = mOriginalConnection.getCnapName();
+            int namePresentation = mOriginalConnection.getCnapNamePresentation();
+            if (force || !Objects.equals(name, getCallerDisplayName()) ||
+                    namePresentation != getCallerDisplayNamePresentation()) {
+                Log.v(this, "updateHandle, caller display name changed");
+                setCallerDisplayName(name, namePresentation);
+            }
         }
     }
 
-    protected void hangup(int disconnectCause) {
+    void onAddedToCallService() {
+        updateState(false);
+    }
+
+    void onRemovedFromCallService() {
+        // Subclass can override this to do cleanup.
+    }
+
+    private void hangup(int disconnectCause) {
         if (mOriginalConnection != null) {
             try {
                 Call call = mOriginalConnection.getCall();
@@ -172,17 +278,67 @@
         close();
     }
 
-    private void updateState() {
+    com.android.internal.telephony.Connection getOriginalConnection() {
+        return mOriginalConnection;
+    }
+
+    protected Call getCall() {
+        if (mOriginalConnection != null) {
+            return mOriginalConnection.getCall();
+        }
+        return null;
+    }
+
+    Phone getPhone() {
+        Call call = getCall();
+        if (call != null) {
+            return call.getPhone();
+        }
+        return null;
+    }
+
+    private com.android.internal.telephony.Connection getForegroundConnection() {
+        if (getPhone() != null) {
+            return getPhone().getForegroundCall().getEarliestConnection();
+        }
+        return null;
+    }
+
+    /**
+     * Checks to see the original connection corresponds to an active incoming call. Returns false
+     * if there is no such actual call, or if the associated call is not incoming (See
+     * {@link Call.State#isRinging}).
+     */
+    private boolean isValidRingingCall() {
+        if (getPhone() == null) {
+            Log.v(this, "isValidRingingCall, phone is null");
+            return false;
+        }
+
+        Call ringingCall = getPhone().getRingingCall();
+        if (!ringingCall.getState().isRinging()) {
+            Log.v(this, "isValidRingingCall, ringing call is not in ringing state");
+            return false;
+        }
+
+        if (ringingCall.getEarliestConnection() != mOriginalConnection) {
+            Log.v(this, "isValidRingingCall, ringing call connection does not match");
+            return false;
+        }
+
+        Log.v(this, "isValidRingingCall, returning true");
+        return true;
+    }
+
+    private void updateState(boolean force) {
         if (mOriginalConnection == null) {
             return;
         }
 
         Call.State newState = mOriginalConnection.getState();
-        Log.v(this, "Update state from %s to %s for %s", mState, newState, this);
-        if (mState != newState) {
-            Log.d(this, "mOriginalConnection new state = %s", newState);
-
-            mState = newState;
+        Log.v(this, "Update state from %s to %s for %s", mOriginalConnectionState, newState, this);
+        if (force || mOriginalConnectionState != newState) {
+            mOriginalConnectionState = newState;
             switch (newState) {
                 case IDLE:
                     break;
@@ -202,33 +358,23 @@
                     break;
                 case DISCONNECTED:
                     setDisconnected(mOriginalConnection.getDisconnectCause(), null);
+                    close();
                     break;
                 case DISCONNECTING:
                     break;
             }
-            updateCallCapabilities();
         }
+        updateCallCapabilities(force);
+        updateHandle(force);
     }
 
     private void close() {
-        if (mOriginalConnection != null) {
-            Call call = mOriginalConnection.getCall();
-            if (call != null) {
-                call.getPhone().unregisterForPreciseCallStateChanged(mHandler);
-            }
-            mOriginalConnection = null;
-            setDestroyed();
+        Log.v(this, "close");
+        if (getPhone() != null) {
+            getPhone().unregisterForPreciseCallStateChanged(mHandler);
+            getPhone().unregisterForRingbackTone(mHandler);
         }
-    }
-
-    private class StateHandler extends Handler {
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case EVENT_PRECISE_CALL_STATE_CHANGED:
-                    updateState();
-                    break;
-            }
-        }
+        mOriginalConnection = null;
+        setDestroyed();
     }
 }
diff --git a/src/com/android/services/telephony/TelephonyConnectionService.java b/src/com/android/services/telephony/TelephonyConnectionService.java
index 9a0d5a4..2d55165 100644
--- a/src/com/android/services/telephony/TelephonyConnectionService.java
+++ b/src/com/android/services/telephony/TelephonyConnectionService.java
@@ -16,240 +16,257 @@
 
 package com.android.services.telephony;
 
+import android.content.ComponentName;
+import android.content.Context;
 import android.net.Uri;
+import android.os.Debug;
 import android.telephony.DisconnectCause;
 import android.telephony.ServiceState;
 import android.text.TextUtils;
-
-import com.android.internal.telephony.CallStateException;
-import com.android.internal.telephony.Connection.PostDialListener;
-import com.android.internal.telephony.Phone;
-
+import android.telecomm.CallCapabilities;
 import android.telecomm.Connection;
 import android.telecomm.ConnectionRequest;
 import android.telecomm.ConnectionService;
+import android.telecomm.PhoneAccount;
 import android.telecomm.Response;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.TelephonyManager;
 
-import java.util.HashSet;
-import java.util.Set;
+import com.android.internal.telephony.Call;
+import com.android.internal.telephony.CallStateException;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneFactory;
 
 /**
- * The parent class for Android's built-in connection services.
+ * Service for making GSM and CDMA connections.
  */
-public abstract class TelephonyConnectionService extends ConnectionService {
-    private static final Set<com.android.internal.telephony.Connection> sKnownConnections
-            = new HashSet<>();
+public class TelephonyConnectionService extends ConnectionService {
+    private static String SCHEME_TEL = "tel";
 
-    /**
-     * Initiates the underlying Telephony call, then creates a {@link TelephonyConnection}
-     * by calling
-     * {@link #createTelephonyConnection(
-     *         ConnectionRequest,Phone,com.android.internal.telephony.Connection)}
-     * at the appropriate time. This method should be called by the subclass.
-     */
-    protected void startCallWithPhone(
-            Phone phone,
-            ConnectionRequest request,
-            OutgoingCallResponse<Connection> response) {
-        Log.d(this, "startCallWithPhone: %s.", request);
+    private EmergencyCallHelper mEmergencyCallHelper;
 
-        if (phone == null) {
-            respondWithError(
-                    request,
-                    response,
-                    DisconnectCause.ERROR_UNSPECIFIED,  // Generic internal error
-                    "Phone is null");
+    static PhoneAccount getPhoneAccount(Context context) {
+        return new PhoneAccount(
+                new ComponentName(context, TelephonyConnectionService.class),
+                null /* id */,
+                null,
+                PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION);
+    }
+
+    @Override
+    protected void onCreateOutgoingConnection(
+            final ConnectionRequest request,
+            final CreateConnectionResponse<Connection> response) {
+        Log.v(this, "onCreateOutgoingConnection, request: " + request);
+
+        Uri handle = request.getHandle();
+        if (handle == null) {
+            Log.d(this, "onCreateOutgoingConnection, handle is null");
+            response.onFailure(request, DisconnectCause.NO_PHONE_NUMBER_SUPPLIED, "Handle is null");
             return;
         }
 
-        if (request.getHandle() == null) {
-            respondWithError(
-                    request,
-                    response,
-                    DisconnectCause.NO_PHONE_NUMBER_SUPPLIED,
-                    "Handle is null");
+        if (!SCHEME_TEL.equals(handle.getScheme())) {
+            Log.d(this, "onCreateOutgoingConnection, Handle %s is not type tel",
+                    handle.getScheme());
+            response.onFailure(request, DisconnectCause.ERROR_UNSPECIFIED,
+                    "Handle scheme is not type tel");
             return;
         }
 
-        String number = request.getHandle().getSchemeSpecificPart();
+        final String number = handle.getSchemeSpecificPart();
         if (TextUtils.isEmpty(number)) {
-            respondWithError(
-                    request,
-                    response,
-                    DisconnectCause.INVALID_NUMBER,
-                    "Unable to parse number");
+            Log.d(this, "onCreateOutgoingConnection, unable to parse number");
+            response.onFailure(request, DisconnectCause.INVALID_NUMBER, "Unable to parse number");
             return;
         }
 
-        if (!checkServiceStateForOutgoingCall(phone, request, response)) {
+        final Phone phone = PhoneFactory.getDefaultPhone();
+        if (phone == null) {
+            Log.d(this, "onCreateOutgoingConnection, phone is null");
+            response.onFailure(request, DisconnectCause.ERROR_UNSPECIFIED, "Phone is null");
             return;
         }
 
-        com.android.internal.telephony.Connection connection;
-        try {
-            connection = phone.dial(number, request.getVideoState());
-        } catch (CallStateException e) {
-            Log.e(this, e, "Call to Phone.dial failed with exception");
-            respondWithError(
-                    request,
-                    response,
-                    DisconnectCause.ERROR_UNSPECIFIED,  // Generic internal error
-                    e.getMessage());
+        boolean isEmergencyNumber = PhoneNumberUtils.isPotentialEmergencyNumber(number);
+        if (!isEmergencyNumber) {
+            int state = phone.getServiceState().getState();
+            switch (state) {
+                case ServiceState.STATE_IN_SERVICE:
+                    break;
+                case ServiceState.STATE_OUT_OF_SERVICE:
+                    response.onFailure(request, DisconnectCause.OUT_OF_SERVICE,
+                            "ServiceState.STATE_OUT_OF_SERVICE");
+                    return;
+                case ServiceState.STATE_EMERGENCY_ONLY:
+                    response.onFailure(request, DisconnectCause.EMERGENCY_ONLY,
+                            "ServiceState.STATE_EMERGENCY_ONLY");
+                    return;
+                case ServiceState.STATE_POWER_OFF:
+                    response.onFailure(request, DisconnectCause.POWER_OFF,
+                            "ServiceState.STATE_POWER_OFF");
+                    return;
+                default:
+                    Log.d(this, "onCreateOutgoingConnection, unkown service state: %d", state);
+                    response.onFailure(request, DisconnectCause.ERROR_UNSPECIFIED,
+                            "Unkown service state " + state);
+                    return;
+            }
+        }
+
+        if (isEmergencyNumber) {
+            Log.d(this, "onCreateOutgoingConnection, doing startTurnOnRadioSequence for " +
+                    "emergency number");
+            if (mEmergencyCallHelper == null) {
+                mEmergencyCallHelper = new EmergencyCallHelper(this);
+            }
+            mEmergencyCallHelper.startTurnOnRadioSequence(phone,
+                    new EmergencyCallHelper.Callback() {
+                        @Override
+                        public void onComplete(boolean isRadioReady) {
+                            if (isRadioReady) {
+                                startOutgoingCall(request, response, phone, number);
+                            } else {
+                                Log.d(this, "onCreateOutgoingConnection, failed to turn on radio");
+                                response.onFailure(request, DisconnectCause.POWER_OFF,
+                                        "Failed to turn on radio.");
+                            }
+                        }
+            });
             return;
         }
 
-        if (connection == null) {
-            respondWithError(
-                    request,
-                    response,
-                    DisconnectCause.ERROR_UNSPECIFIED,  // Generic internal error
-                    "Call to phone.dial failed");
-            return;
-        }
+        startOutgoingCall(request, response, phone, number);
+    }
 
-        try {
-            final TelephonyConnection telephonyConnection =
-                    createTelephonyConnection(request, phone, connection);
-            respondWithResult(request, response, telephonyConnection);
-
-            final com.android.internal.telephony.Connection connectionFinal = connection;
-            PostDialListener postDialListener = new PostDialListener() {
-                @Override
-                public void onPostDialWait() {
-                    telephonyConnection.setPostDialWait(
-                            connectionFinal.getRemainingPostDialString());
-                }
-            };
-            connection.addPostDialListener(postDialListener);
-        } catch (Exception e) {
-            Log.e(this, e, "Call to createConnection failed with exception");
-            respondWithError(
-                    request,
-                    response,
-                    DisconnectCause.ERROR_UNSPECIFIED,  // Generic internal error
-                    e.getMessage());
+    @Override
+    protected void onCreateConferenceConnection(
+            String token,
+            Connection connection,
+            Response<String, Connection> response) {
+        Log.v(this, "onCreateConferenceConnection, connection: " + connection);
+        if (connection instanceof GsmConnection || connection instanceof ConferenceConnection) {
+            if ((connection.getCallCapabilities() & CallCapabilities.MERGE_CALLS) != 0) {
+                response.onResult(token,
+                        GsmConferenceController.createConferenceConnection(connection));
+            }
         }
     }
 
-    private boolean checkServiceStateForOutgoingCall(
-            Phone phone,
+    @Override
+    protected void onCreateIncomingConnection(
             ConnectionRequest request,
-            OutgoingCallResponse<Connection> response) {
-        int state = phone.getServiceState().getState();
-        switch (state) {
-            case ServiceState.STATE_IN_SERVICE:
-                return true;
-            case ServiceState.STATE_OUT_OF_SERVICE:
-                respondWithError(
-                        request,
-                        response,
-                        DisconnectCause.OUT_OF_SERVICE,
-                        null);
-                break;
-            case ServiceState.STATE_EMERGENCY_ONLY:
-                respondWithError(
-                        request,
-                        response,
-                        DisconnectCause.EMERGENCY_ONLY,
-                        null);
-                break;
-            case ServiceState.STATE_POWER_OFF:
-                respondWithError(
-                        request,
-                        response,
-                        DisconnectCause.POWER_OFF,
-                        null);
-                break;
-            default:
-                // Internal error, but we pass it upwards and do not crash.
-                Log.d(this, "Unrecognized service state %d", state);
-                respondWithError(
-                        request,
-                        response,
-                        DisconnectCause.ERROR_UNSPECIFIED,
-                        "Unrecognized service state " + state);
-                break;
+            CreateConnectionResponse<Connection> response) {
+        Log.v(this, "onCreateIncomingConnection, request: " + request);
+
+        Phone phone = PhoneFactory.getDefaultPhone();
+        Call call = phone.getRingingCall();
+        if (!call.getState().isRinging()) {
+            Log.v(this, "onCreateIncomingConnection, no ringing call");
+            response.onFailure(request, DisconnectCause.INCOMING_MISSED, "Found no ringing call");
+            return;
+        }
+
+        com.android.internal.telephony.Connection originalConnection = call.getEarliestConnection();
+        if (isOriginalConnectionKnown(originalConnection)) {
+            Log.v(this, "onCreateIncomingConnection, original connection already registered");
+            response.onCancel(request);
+            return;
+        }
+
+        Uri handle = getHandleFromAddress(originalConnection.getAddress());
+        ConnectionRequest telephonyRequest = new ConnectionRequest(
+                getPhoneAccount(this),
+                request.getCallId(),
+                handle,
+                originalConnection.getNumberPresentation(),
+                request.getExtras(),
+                request.getVideoState());
+
+        if (phone.getPhoneType() == TelephonyManager.PHONE_TYPE_GSM) {
+            response.onSuccess(telephonyRequest, new GsmConnection(originalConnection));
+        } else if (phone.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) {
+            response.onSuccess(telephonyRequest, new CdmaConnection(originalConnection));
+        } else {
+            response.onCancel(request);
+        }
+    }
+
+    @Override
+    protected void onConnectionAdded(Connection connection) {
+        Log.v(this, "onConnectionAdded, connection: " + connection);
+        if (connection instanceof TelephonyConnection) {
+            ((TelephonyConnection) connection).onAddedToCallService();
+        }
+    }
+
+    @Override
+    protected void onConnectionRemoved(Connection connection) {
+        Log.v(this, "onConnectionRemoved, connection: " + connection);
+        if (connection instanceof TelephonyConnection) {
+            ((TelephonyConnection) connection).onRemovedFromCallService();
+        }
+    }
+
+    private void startOutgoingCall(
+            ConnectionRequest request,
+            CreateConnectionResponse<Connection> response,
+            Phone phone,
+            String number) {
+        Log.v(this, "startOutgoingCall");
+
+        com.android.internal.telephony.Connection originalConnection;
+        try {
+            originalConnection = phone.dial(number, request.getVideoState());
+        } catch (CallStateException e) {
+            Log.e(this, e, "startOutgoingCall, phone.dial exception: " + e);
+            response.onFailure(request, DisconnectCause.ERROR_UNSPECIFIED, e.getMessage());
+            return;
+        }
+
+        if (originalConnection == null) {
+            Log.d(this, "startOutgoingCall, phone.dial returned null");
+            response.onFailure(request, DisconnectCause.ERROR_UNSPECIFIED, "Connection is null");
+            return;
+        }
+
+        ConnectionRequest telephonyRequest = new ConnectionRequest(
+                getPhoneAccount(this),
+                request.getCallId(),
+                request.getHandle(),
+                request.getHandlePresentation(),
+                request.getExtras(),
+                request.getVideoState());
+
+        if (phone.getPhoneType() == TelephonyManager.PHONE_TYPE_GSM) {
+            response.onSuccess(telephonyRequest, new GsmConnection(originalConnection));
+        } else if (phone.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) {
+            response.onSuccess(telephonyRequest, new CdmaConnection(originalConnection));
+        } else {
+            // TODO(ihab): Tear down 'originalConnection' here, or move recognition of
+            // getPhoneType() earlier in this method before we've already asked phone to dial()
+            response.onFailure(request, DisconnectCause.ERROR_UNSPECIFIED, "Invalid phone type");
+        }
+    }
+
+    private boolean isOriginalConnectionKnown(
+            com.android.internal.telephony.Connection originalConnection) {
+        for (Connection connection : getAllConnections()) {
+            TelephonyConnection telephonyConnection = (TelephonyConnection) connection;
+            if (connection instanceof TelephonyConnection) {
+                if (telephonyConnection.getOriginalConnection() == originalConnection) {
+                    return true;
+                }
+            }
         }
         return false;
     }
 
-    protected <REQUEST, RESULT> void respondWithError(
-            REQUEST request,
-            Response<REQUEST, RESULT> response,
-            int errorCode,
-            String errorMsg) {
-        Log.d(this, "respondWithError %s: %d %s", request, errorCode, errorMsg);
-        response.onError(request, errorCode, errorMsg);
+    static Uri getHandleFromAddress(String address) {
+        // Address can be null for blocked calls.
+        if (address == null) {
+            address = "";
+        }
+        return Uri.fromParts(SCHEME_TEL, address, null);
     }
-
-    protected void respondWithResult(
-            ConnectionRequest request,
-            Response<ConnectionRequest, Connection> response,
-            Connection result) {
-        Log.d(this, "respondWithResult %s -> %s", request, result);
-        response.onResult(request, result);
-    }
-
-    protected void respondWithResult(
-            ConnectionRequest request,
-            OutgoingCallResponse<Connection> response,
-            Connection result) {
-        Log.d(this, "respondWithResult %s -> %s", request, result);
-        response.onSuccess(request, result);
-    }
-
-    protected void respondWithError(
-            ConnectionRequest request,
-            OutgoingCallResponse<Connection> response,
-            int errorCode,
-            String errorMsg) {
-        Log.d(this, "respondWithError %s: %d %s", request, errorCode, errorMsg);
-        response.onFailure(request, errorCode, errorMsg);
-    }
-
-    protected final TelephonyConnection createTelephonyConnection(
-            ConnectionRequest request,
-            Phone phone,
-            final com.android.internal.telephony.Connection connection) {
-        final TelephonyConnection telephonyConnection =
-                onCreateTelephonyConnection(request, phone, connection);
-        sKnownConnections.add(connection);
-        telephonyConnection.addConnectionListener(new Connection.Listener() {
-            @Override
-            public void onDestroyed(Connection c) {
-                telephonyConnection.removeConnectionListener(this);
-                sKnownConnections.remove(connection);
-            }
-        });
-
-        return telephonyConnection;
-    }
-
-    protected static boolean isConnectionKnown(
-            com.android.internal.telephony.Connection connection) {
-        return sKnownConnections.contains(connection);
-    }
-
-    /**
-     * Determine whether this {@link TelephonyConnectionService} can place a call
-     * to the supplied handle (phone number).
-     *
-     * @param handle The proposed handle.
-     * @return {@code true} if the handle can be called.
-     */
-    protected abstract boolean canCall(Uri handle);
-
-    /**
-     * Create a Telephony-specific {@link Connection} object.
-     *
-     * @param request A request for creating a {@link Connection}.
-     * @param phone A {@code Phone} object to use.
-     * @param connection An underlying Telephony {@link com.android.internal.telephony.Connection}
-     *         to use.
-     * @return A new {@link TelephonyConnection}.
-     */
-    protected abstract TelephonyConnection onCreateTelephonyConnection(
-            ConnectionRequest request,
-            Phone phone,
-            com.android.internal.telephony.Connection connection);
 }