Telephony cleanup do not merge

This CL removes the PSTN and SIP call service providers.
The connection services will now be looked up by package
manager instead.

This CL also cleanups the telephony connection service code.
Now that the SIP code has been moved out the code can be much
simpler and PstnConnection* and TelephonyConnection* can be
merged.

Change-Id: I2e40c5c64c0d242dc41b680943d7e9209142db5b
(cherry picked from commit 8306b5e6d829c0aa3eacaa12a1cddcf3df7194dc)
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 d50d92e..c5d1c23 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -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/SipConnectionService.java b/sip/src/com/android/services/telephony/sip/SipConnectionService.java
index 513fbfd..3c32604 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,6 +45,14 @@
     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(
             final ConnectionRequest request,
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..f34cf27 100644
--- a/src/com/android/services/telephony/GsmConnection.java
+++ b/src/com/android/services/telephony/GsmConnection.java
@@ -20,30 +20,32 @@
 
 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) {
@@ -53,12 +55,14 @@
         }
     }
 
-    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..4662536 100644
--- a/src/com/android/services/telephony/TelephonyConnection.java
+++ b/src/com/android/services/telephony/TelephonyConnection.java
@@ -16,6 +16,7 @@
 
 package com.android.services.telephony;
 
+import android.os.AsyncResult;
 import android.os.Handler;
 import android.os.Message;
 import android.telecomm.CallAudioState;
@@ -23,45 +24,88 @@
 
 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;
+
 /**
- * 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) {
+            // 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;
+            }
+
+            switch (msg.what) {
+                case MSG_PRECISE_CALL_STATE_CHANGED:
+                    Log.v(TelephonyConnection.this, "MSG_PRECISE_CALL_STATE_CHANGED");
+                    updateState();
+                    break;
+                case MSG_RINGBACK_TONE:
+                    Log.v(TelephonyConnection.this, "MSG_RINGBACK_TONE");
+                    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);
+        getPhone().registerForPreciseCallStateChanged(
+                mHandler, MSG_PRECISE_CALL_STATE_CHANGED, null);
+        getPhone().registerForRingbackTone(mHandler, MSG_RINGBACK_TONE, null);
+        mOriginalConnection.addPostDialListener(mPostDialListener);
         updateState();
     }
 
-    com.android.internal.telephony.Connection getOriginalConnection() {
-        return mOriginalConnection;
+    @Override
+    protected void onSetAudioState(CallAudioState audioState) {
+        // TODO: update TTY mode.
+        if (getPhone() != null) {
+            getPhone().setEchoSuppressionEnabled();
+        }
     }
 
     @Override
-    protected void onAbort() {
-        hangup(DisconnectCause.LOCAL);
-        super.onAbort();
+    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,19 +168,57 @@
         } 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();
@@ -144,7 +230,7 @@
         }
     }
 
-    final void onAddedToCallService() {
+    void onAddedToCallService() {
         updateCallCapabilities();
         if (mOriginalConnection != null) {
             setCallerDisplayName(
@@ -153,7 +239,10 @@
         }
     }
 
-    protected void hangup(int disconnectCause) {
+    void onRemovedFromCallService() {
+    }
+
+    private void hangup(int disconnectCause) {
         if (mOriginalConnection != null) {
             try {
                 Call call = mOriginalConnection.getCall();
@@ -172,17 +261,67 @@
         close();
     }
 
+    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() {
         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 (mOriginalConnectionState != newState) {
+            mOriginalConnectionState = newState;
             switch (newState) {
                 case IDLE:
                     break;
@@ -211,24 +350,12 @@
     }
 
     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..782f864 100644
--- a/src/com/android/services/telephony/TelephonyConnectionService.java
+++ b/src/com/android/services/telephony/TelephonyConnectionService.java
@@ -16,240 +16,253 @@
 
 package com.android.services.telephony;
 
+import android.content.ComponentName;
+import android.content.Context;
 import android.net.Uri;
 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 onCreateConnections(
+            final ConnectionRequest request,
+            final OutgoingCallResponse<Connection> response) {
+        Log.v(this, "onCreateConnections, request: " + request);
+
+        Uri handle = request.getHandle();
+        if (handle == null) {
+            Log.d(this, "onCreateConnections, 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, "onCreateConnections, 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, "onCreateConnections, 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, "onCreateConnections, 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, "onCreateConnections, Unrecognized service state: %d", state);
+                    response.onFailure(request, DisconnectCause.ERROR_UNSPECIFIED,
+                            "Unrecognized service state " + state);
+                    return;
+            }
+        }
+
+        if (isEmergencyNumber) {
+            Log.d(this, "onCreateConnections, 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, "onCreateConnections, 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;
+            Response<ConnectionRequest, 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.onError(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.onError(request, DisconnectCause.ERROR_UNSPECIFIED,
+                    "Connection already registered");
+            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.onResult(telephonyRequest, new GsmConnection(originalConnection));
+        } else if (phone.getPhoneType() == TelephonyManager.PHONE_TYPE_GSM) {
+            response.onResult(telephonyRequest, new CdmaConnection(originalConnection));
+        } else {
+            response.onError(request, DisconnectCause.ERROR_UNSPECIFIED, "Invalid phone type");
+        }
+    }
+
+    @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,
+            OutgoingCallResponse<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_GSM) {
+            response.onSuccess(telephonyRequest, new CdmaConnection(originalConnection));
+        } else {
+            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);
+    private 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);
 }