Refactor Telephony to new framework

Refactors Telephony to the new-style framework, where the framework classes still live
inside the Telephony package here, but we are getting ready to move them into the actual
frameworks/base directory.

Change-Id: Id93771d20f682e3ebf225e2f4225d24072dce28b
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 59eb0a3..2b15c13 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -565,17 +565,17 @@
                 <action android:name="android.telecomm.CallServiceSelector" />
             </intent-filter>
         </service>
-        <service android:name="com.android.services.telephony.GsmCallService">
+        <service android:name="com.android.services.telephony.GsmConnectionService">
             <intent-filter>
                 <action android:name="android.telecomm.CallService" />
             </intent-filter>
         </service>
-        <service android:name="com.android.services.telephony.CdmaCallService">
+        <service android:name="com.android.services.telephony.CdmaConnectionService">
             <intent-filter>
                 <action android:name="android.telecomm.CallService" />
             </intent-filter>
         </service>
-        <service android:name="com.android.services.telephony.SipCallService">
+        <service android:name="com.android.services.telephony.SipConnectionService">
             <intent-filter>
                 <action android:name="android.telecomm.CallService" />
             </intent-filter>
diff --git a/src/com/android/services/telecomm/Connection.java b/src/com/android/services/telecomm/Connection.java
index 1d8684a..072d97d 100644
--- a/src/com/android/services/telecomm/Connection.java
+++ b/src/com/android/services/telecomm/Connection.java
@@ -18,6 +18,8 @@
 
 import com.google.android.collect.Sets;
 
+import com.android.services.telephony.Log;
+
 import android.net.Uri;
 import android.os.Bundle;
 import android.telecomm.CallAudioState;
@@ -34,9 +36,36 @@
         void onAudioStateChanged(Connection c, CallAudioState state);
         void onHandleChanged(Connection c, Uri newHandle);
         void onSignalChanged(Connection c, Bundle details);
+        void onDisconnected(Connection c, int cause, String message);
         void onDestroyed(Connection c);
     }
 
+    public static class ListenerBase implements Listener {
+        /** {@inheritDoc} */
+        @Override
+        public void onStateChanged(Connection c, int state) {}
+
+        /** {@inheritDoc} */
+         @Override
+        public void onAudioStateChanged(Connection c, CallAudioState state) {}
+
+        /** {@inheritDoc} */
+        @Override
+        public void onHandleChanged(Connection c, Uri newHandle) {}
+
+        /** {@inheritDoc} */
+        @Override
+        public void onSignalChanged(Connection c, Bundle details) {}
+
+        /** {@inheritDoc} */
+        @Override
+        public void onDisconnected(Connection c, int cause, String message) {}
+
+        /** {@inheritDoc} */
+        @Override
+        public void onDestroyed(Connection c) {}
+    }
+
     public final class State {
         private State() {}
 
@@ -52,14 +81,11 @@
     private int mState = State.NEW;
     private CallAudioState mCallAudioState;
     private Uri mHandle;
-    private long mStartTime = -1L;
-    private long mEndTime = -1L;
 
     /**
      * Create a new Connection.
      */
-    protected Connection() {
-    }
+    protected Connection() {}
 
     /**
      * @return The handle (e.g., phone number) to which this Connection
@@ -95,44 +121,26 @@
      * Assign a listener to be notified of state changes.
      *
      * @param l A listener.
+     * @return This Connection.
      *
      * @hide
      */
-    public final void addConnectionListener(Listener l) {
+    public final Connection addConnectionListener(Listener l) {
         mListeners.add(l);
+        return this;
     }
 
     /**
      * Remove a previously assigned listener that was being notified of state changes.
      *
      * @param l A Listener.
+     * @return This Connection.
      *
      * @hide
      */
-    public final void removeConnectionListener(Listener l) {
+    public final Connection removeConnectionListener(Listener l) {
         mListeners.remove(l);
-    }
-
-    /**
-     * @return The system time at which this Connection transitioned into the
-     *         {@link State#ACTIVE} state. This value is {@code -1L}
-     *         if it has not been explicitly assigned.
-     *
-     * @hide
-     */
-    public final long getStartTime() {
-        return mStartTime;
-    }
-
-    /**
-     * @return The system time at which this Connection transitioned into the
-     *         {@link State#DISCONNECTED} state. This value is
-     *         {@code -1L} if it has not been explicitly assigned.
-     *
-     * @hide
-     */
-    public final long getEndTime() {
-        return mEndTime;
+        return this;
     }
 
     /**
@@ -143,6 +151,7 @@
      * @hide
      */
     public final void playDtmfTone(char c) {
+        Log.d(this, "playDtmfTone %c", c);
         onPlayDtmfTone(c);
     }
 
@@ -152,6 +161,7 @@
      * @hide
      */
     public final void stopDtmfTone() {
+        Log.d(this, "stopDtmfTone");
         onStopDtmfTone();
     }
 
@@ -163,6 +173,7 @@
      * @hide
      */
     public final void disconnect() {
+        Log.d(this, "disconnect");
         onDisconnect();
     }
 
@@ -174,6 +185,7 @@
      * @hide
      */
     public final void abort() {
+        Log.d(this, "abort");
         onAbort();
     }
 
@@ -185,6 +197,7 @@
      * @hide
      */
     public final void hold() {
+        Log.d(this, "hold");
         onHold();
     }
 
@@ -196,6 +209,7 @@
      * @hide
      */
     public final void unhold() {
+        Log.d(this, "unhold");
         onUnhold();
     }
 
@@ -207,6 +221,7 @@
      * @hide
      */
     public final void answer() {
+        Log.d(this, "answer");
         if (mState == State.RINGING) {
             onAnswer();
         }
@@ -220,7 +235,10 @@
      * @hide
      */
     public final void reject() {
-        if (mState == State.RINGING) { onReject(); }
+        Log.d(this, "reject");
+        if (mState == State.RINGING) {
+            onReject();
+        }
     }
 
     /**
@@ -229,16 +247,41 @@
      * @param state The new audio state.
      */
     public void setAudioState(CallAudioState state) {
+        Log.d(this, "setAudioState %s", state);
         onSetAudioState(state);
     }
 
     /**
-     * Notifies this Connection and listeners that the {@link #getHandle()} property
-     * has a new value.
+     * @param state An integer value from {@link State}.
+     * @return A string representation of the value.
+     */
+    public static String stateToString(int state) {
+        switch (state) {
+            case State.NEW:
+                return "NEW";
+            case State.RINGING:
+                return "RINGING";
+            case State.DIALING:
+                return "DIALING";
+            case State.ACTIVE:
+                return "ACTIVE";
+            case State.HOLDING:
+                return "HOLDING";
+            case State.DISCONNECTED:
+                return "DISCONNECTED";
+            default:
+                Log.wtf(Connection.class, "Unknown state %d", state);
+                return "UNKNOWN";
+        }
+    }
+
+    /**
+     * Sets the value of the {@link #getHandle()} property and notifies listeners.
      *
      * @param handle The new handle.
      */
-    protected void onSetHandle(Uri handle) {
+    protected void setHandle(Uri handle) {
+        Log.d(this, "setHandle %s", handle);
         // TODO: Enforce super called
         mHandle = handle;
         for (Listener l : mListeners) {
@@ -247,37 +290,50 @@
     }
 
     /**
-     * Notifies this Connection and listeners that the {@link #getState()} property
-     * has a new value.
-     *
-     * @param state The new state.
+     * Sets state to active (e.g., an ongoing call where two or more parties can actively
+     * communicate).
      */
-    protected void onSetState(int state) {
-        // TODO: Enforce super called
-        this.mState = state;
-        // TODO: This can also check for only VALID state transitions
-        if (state == State.ACTIVE) {
-            mStartTime = System.currentTimeMillis();
-        }
-        if (state == State.DISCONNECTED) {
-            mEndTime = System.currentTimeMillis();
-        }
-        for (Listener l : mListeners) {
-            l.onStateChanged(this, state);
-        }
+    protected void setActive() {
+        setState(State.ACTIVE);
     }
 
     /**
-     * Notifies this Connection and listeners that the {@link #getState()} property
-     * has a new value, and specifies a reason.
-     *
-     * TODO: needed for disconnect cause -- consider how that will be supported
-     *
-     * @param state The new state.
-     * @param reason The reason for the change.
+     * Sets state to ringing (e.g., an inbound ringing call).
      */
-    protected void onSetState(int state, String reason) {
-        // TODO: Enforce super called
+    protected void setRinging() {
+        setState(State.RINGING);
+    }
+
+    /**
+     * Sets state to dialing (e.g., dialing an outbound call).
+     */
+    protected void setDialing() {
+        setState(State.DIALING);
+    }
+
+    /**
+     * Sets state to be on hold.
+     */
+    protected void setOnHold() {
+        setState(State.HOLDING);
+    }
+
+    /**
+     * Sets state to disconnected. This will first notify listeners with an
+     * {@link Listener#onStateChanged(Connection, int)} event, then will fire an
+     * {@link Listener#onDisconnected(Connection, int, String)} event with additional
+     * details.
+     *
+     * @param cause The reason for the disconnection, any of
+     *         {@link android.telephony.DisconnectCause}.
+     * @param message Optional call-service-provided message about the disconnect.
+     */
+    protected void setDisconnected(int cause, String message) {
+        setState(State.DISCONNECTED);
+        Log.d(this, "Disconnected with cause %d, message \"%s\"", cause, message);
+        for (Listener l : mListeners) {
+            l.onDisconnected(this, cause, message);
+        }
     }
 
     /**
@@ -350,4 +406,12 @@
      * a request to reject.
      */
     protected void onReject() {}
+
+    private void setState(int state) {
+        Log.d(this, "setState: %s", stateToString(state));
+        this.mState = state;
+        for (Listener l : mListeners) {
+            l.onStateChanged(this, state);
+        }
+    }
 }
diff --git a/src/com/android/services/telecomm/ConnectionRequest.java b/src/com/android/services/telecomm/ConnectionRequest.java
index 37db505..2027330 100644
--- a/src/com/android/services/telecomm/ConnectionRequest.java
+++ b/src/com/android/services/telecomm/ConnectionRequest.java
@@ -45,4 +45,12 @@
      * and servant {@code ConnectionService} which agree on a vocabulary for such data.
      */
     public Bundle getExtras() { return mExtras; }
+
+    public String toString() {
+        return String.format("PhoneConnectionRequest %s %s",
+                mHandle == null
+                        ? Uri.EMPTY
+                        : ConnectionService.toLogSafePhoneNumber(mHandle.toString()),
+                mExtras == null ? "" : mExtras);
+    }
 }
diff --git a/src/com/android/services/telecomm/ConnectionService.java b/src/com/android/services/telecomm/ConnectionService.java
index 9a5ff22..5f2389e 100644
--- a/src/com/android/services/telecomm/ConnectionService.java
+++ b/src/com/android/services/telecomm/ConnectionService.java
@@ -28,12 +28,14 @@
 import android.util.Log;
 
 /**
- * Provides actual voice connections to the Android framework and to other processes
- * running on this device.
+ * A {@link android.app.Service} that provides telephone connections to
+ * processes running on an Android device.
  */
 public abstract class ConnectionService extends CallService {
+    private static final String TAG = ConnectionService.class.getSimpleName();
 
-    private static final String TAG = ConnectionService.class.getName();
+    // Flag controlling whether PII is emitted into the logs
+    private static final boolean PII_DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     private static final Connection NULL_CONNECTION = new Connection() {};
 
@@ -43,30 +45,38 @@
     private final Connection.Listener mConnectionListener = new Connection.Listener() {
         @Override
         public void onStateChanged(Connection c, int state) {
+            String id = mConnectionById.inverse().get(c);
+            Log.d(TAG, "Adapter set state " + id + " " + Connection.stateToString(state));
             switch (state) {
                 case Connection.State.ACTIVE:
-                    getAdapter().setActive(mConnectionById.inverse().get(c));
+                    getAdapter().setActive(id);
                     break;
                 case Connection.State.DIALING:
-                    getAdapter().setDialing(mConnectionById.inverse().get(c));
+                    getAdapter().setDialing(id);
                     break;
                 case Connection.State.DISCONNECTED:
-                    // TODO: Disconnect cause and description
-                    getAdapter().setDisconnected(mConnectionById.inverse().get(c), 0, null);
+                    // Handled in onDisconnected()
                     break;
                 case Connection.State.HOLDING:
-                    getAdapter().setOnHold(mConnectionById.inverse().get(c));
+                    getAdapter().setOnHold(id);
                     break;
                 case Connection.State.NEW:
                     // Nothing to tell Telecomm
                     break;
                 case Connection.State.RINGING:
-                    getAdapter().setRinging(mConnectionById.inverse().get(c));
+                    getAdapter().setRinging(id);
                     break;
             }
         }
 
         @Override
+        public void onDisconnected(Connection c, int cause, String message) {
+            String id = mConnectionById.inverse().get(c);
+            Log.d(TAG, "Adapter set disconnected " + cause + " " + message);
+            getAdapter().setDisconnected(id, cause, message);
+        }
+
+        @Override
         public void onHandleChanged(Connection c, Uri newHandle) {
             // TODO: Unsupported yet
         }
@@ -83,27 +93,29 @@
 
         @Override
         public void onDestroyed(Connection c) {
-            c.removeConnectionListener(this);
-            String id = mConnectionById.inverse().get(c);
-            mConnectionById.get(id).removeConnectionListener(mConnectionListener);
-            mConnectionById.remove(id);
+            removeConnection(c);
         }
     };
 
     @Override
     public final void isCompatibleWith(final CallInfo callInfo) {
+        Log.d(TAG, "isCompatibleWith " + callInfo);
         onFindSubscriptions(
                 callInfo.getHandle(),
                 new Response<Uri, Subscription>() {
                     @Override
                     public void onResult(Uri handle, Subscription... result) {
-                        getAdapter().setIsCompatibleWith(callInfo.getId(), result.length > 0);
+                        boolean isCompatible = result.length > 0;
+                        Log.d(TAG, "adapter setIsCompatibleWith "
+                                + callInfo.getId() + " " + isCompatible);
+                        getAdapter().setIsCompatibleWith(callInfo.getId(), isCompatible);
                     }
 
                     @Override
                     public void onError(Uri handle, String reason) {
                         Log.wtf(TAG, "Error in onFindSubscriptions " + callInfo.getHandle()
                                 + " error: " + reason);
+                        getAdapter().setIsCompatibleWith(callInfo.getId(), false);
                     }
                 }
         );
@@ -111,6 +123,7 @@
 
     @Override
     public final void call(final CallInfo callInfo) {
+        Log.d(TAG, "call " + callInfo);
         onCreateConnections(
                 new ConnectionRequest(
                         callInfo.getHandle(),
@@ -119,15 +132,17 @@
                     @Override
                     public void onResult(ConnectionRequest request, Connection... result) {
                         if (result.length != 1) {
+                            Log.d(TAG, "adapter handleFailedOutgoingCall " + callInfo);
                             getAdapter().handleFailedOutgoingCall(
                                     callInfo.getId(),
                                     "Created " + result.length + " Connections, expected 1");
-                            for (int i = 0; i < result.length; i++) {
-                                result[i].abort();
+                            for (Connection c : result) {
+                                c.abort();
                             }
                         } else {
-                            mConnectionById.put(callInfo.getId(), result[0]);
-                            result[0].addConnectionListener(mConnectionListener);
+                            addConnection(callInfo.getId(), result[0]);
+                            Log.d(TAG, "adapter handleSuccessfulOutgoingCall "
+                                    + callInfo.getId());
                             getAdapter().handleSuccessfulOutgoingCall(callInfo.getId());
                         }
                     }
@@ -142,37 +157,44 @@
 
     @Override
     public final void abort(String callId) {
+        Log.d(TAG, "abort " + callId);
         findConnectionForAction(callId, "abort").abort();
     }
 
     @Override
     public final void setIncomingCallId(final String callId, Bundle extras) {
+        Log.d(TAG, "setIncomingCallId " + callId + " " + extras);
         onCreateIncomingConnection(
                 new ConnectionRequest(
-                        null /* todo getHandle() */,
+                        null,  // TODO: Can we obtain this from "extras"?
                         extras),
                 new Response<ConnectionRequest, Connection>() {
                     @Override
                     public void onResult(ConnectionRequest request, Connection... result) {
                         if (result.length != 1) {
+                            Log.d(TAG, "adapter handleFailedOutgoingCall " + callId);
                             getAdapter().handleFailedOutgoingCall(
                                     callId,
                                     "Created " + result.length + " Connections, expected 1");
-                            for (int i = 0; i < result.length; i++) {
-                                result[i].abort();
+                            for (Connection c : result) {
+                                c.abort();
                             }
                         } else {
-                            mConnectionById.put(callId, result[0]);
+                            addConnection(callId, result[0]);
+                            Log.d(TAG, "adapter notifyIncomingCall " + callId);
+                            // TODO: Uri.EMPTY is because CallInfo crashes when Parceled with a
+                            // null URI ... need to fix that at its cause!
                             getAdapter().notifyIncomingCall(new CallInfo(
                                     callId,
-                                    CallState.NEW /* TODO ? */,
-                                    null /* TODO handle */));
+                                    connectionStateToCallState(result[0].getState()),
+                                    request.getHandle() /* result[0].getHandle() == null
+                                            ? Uri.EMPTY : result[0].getHandle() */));
                         }
                     }
 
                     @Override
                     public void onError(ConnectionRequest request, String reason) {
-                        getAdapter().handleFailedOutgoingCall(callId, reason);
+                        Log.d(TAG, "adapter failed setIncomingCallId " + request + " " + reason);
                     }
                 }
         );
@@ -180,48 +202,56 @@
 
     @Override
     public final void answer(String callId) {
+        Log.d(TAG, "answer " + callId);
         findConnectionForAction(callId, "answer").answer();
     }
 
     @Override
     public final void reject(String callId) {
+        Log.d(TAG, "reject " + callId);
         findConnectionForAction(callId, "reject").reject();
     }
 
     @Override
     public final void disconnect(String callId) {
+        Log.d(TAG, "disconnect " + callId);
         findConnectionForAction(callId, "disconnect").disconnect();
     }
 
     @Override
     public final void hold(String callId) {
+        Log.d(TAG, "hold " + callId);
         findConnectionForAction(callId, "hold").hold();
     }
 
     @Override
     public final void unhold(String callId) {
+        Log.d(TAG, "unhold " + callId);
         findConnectionForAction(callId, "unhold").unhold();
     }
 
     @Override
     public final void playDtmfTone(String callId, char digit) {
+        Log.d(TAG, "playDtmfTone " + callId + " " + Character.toString(digit));
         findConnectionForAction(callId, "playDtmfTone").playDtmfTone(digit);
     }
 
     @Override
     public final void stopDtmfTone(String callId) {
+        Log.d(TAG, "stopDtmfTone " + callId);
         findConnectionForAction(callId, "stopDtmfTone").stopDtmfTone();
     }
 
     @Override
     public final void onAudioStateChanged(String callId, CallAudioState audioState) {
+        Log.d(TAG, "onAudioStateChanged " + callId + " " + audioState);
         findConnectionForAction(callId, "onAudioStateChanged").setAudioState(audioState);
     }
 
     /**
-     * Find a set of Subscriptions matching a given handle (phone number).
+     * Find a set of Subscriptions matching a given handle (e.g. phone number).
      *
-     * @param handle A phone number.
+     * @param handle A handle (e.g. phone number) with which to connect.
      * @param callback A callback for providing the result.
      */
     public void onFindSubscriptions(
@@ -231,7 +261,7 @@
     /**
      * Create a Connection given a request.
      *
-     * @param request Some data encapsulating details of the desired Connection.
+     * @param request Data encapsulating details of the desired Connection.
      * @param callback A callback for providing the result.
      */
     public void onCreateConnections(
@@ -241,14 +271,69 @@
     /**
      * Create a Connection to match an incoming connection notification.
      *
-     * @param request Some data encapsulating details of the desired Connection.
+     * @param request Data encapsulating details of the desired Connection.
      * @param callback A callback for providing the result.
      */
     public void onCreateIncomingConnection(
             ConnectionRequest request,
             Response<ConnectionRequest, Connection> callback) {}
 
-    private final Connection findConnectionForAction(String callId, String action) {
+    static String toLogSafePhoneNumber(String number) {
+        // For unknown number, log empty string.
+        if (number == null) {
+            return "";
+        }
+
+        if (PII_DEBUG) {
+            // When PII_DEBUG is true we emit PII.
+            return number;
+        }
+
+        // Do exactly same thing as Uri#toSafeString() does, which will enable us to compare
+        // sanitized phone numbers.
+        StringBuilder builder = new StringBuilder();
+        for (int i = 0; i < number.length(); i++) {
+            char c = number.charAt(i);
+            if (c == '-' || c == '@' || c == '.') {
+                builder.append(c);
+            } else {
+                builder.append('x');
+            }
+        }
+        return builder.toString();
+    }
+
+    private CallState connectionStateToCallState(int connectionState) {
+        switch (connectionState) {
+            case Connection.State.NEW:
+                return CallState.NEW;
+            case Connection.State.RINGING:
+                return CallState.RINGING;
+            case Connection.State.DIALING:
+                return CallState.DIALING;
+            case Connection.State.ACTIVE:
+                return CallState.ACTIVE;
+            case Connection.State.HOLDING:
+                return CallState.ON_HOLD;
+            case Connection.State.DISCONNECTED:
+                return CallState.DISCONNECTED;
+            default:
+                Log.wtf(TAG, "Unknown PhoneConnection.State " + connectionState);
+                return CallState.NEW;
+        }
+    }
+
+    private void addConnection(String callId, Connection connection) {
+        mConnectionById.put(callId, connection);
+        connection.addConnectionListener(mConnectionListener);
+    }
+
+    private void removeConnection(Connection connection) {
+        connection.removeConnectionListener(mConnectionListener);
+        mConnectionById.inverse().remove(connection);
+    }
+
+    private Connection findConnectionForAction(String callId, String action) {
         if (mConnectionById.containsKey(callId)) {
             return mConnectionById.get(callId);
         }
diff --git a/src/com/android/services/telecomm/Subscription.java b/src/com/android/services/telecomm/Subscription.java
index 2456ce6..1cf02f1 100644
--- a/src/com/android/services/telecomm/Subscription.java
+++ b/src/com/android/services/telecomm/Subscription.java
@@ -19,8 +19,14 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
+/**
+ * Represents a distinct subscription, line of service or call placement method that
+ * a {@link ConnectionService} can use to place phone calls.
+ */
 public class Subscription implements Parcelable {
 
+    public Subscription() {}
+
     public int describeContents() {
         return 0;
     }
diff --git a/src/com/android/services/telephony/BaseTelephonyCallService.java b/src/com/android/services/telephony/BaseTelephonyCallService.java
deleted file mode 100644
index 9cb4661..0000000
--- a/src/com/android/services/telephony/BaseTelephonyCallService.java
+++ /dev/null
@@ -1,123 +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.telecomm.CallAudioState;
-import android.telecomm.CallInfo;
-import android.telecomm.CallService;
-import android.telecomm.CallServiceAdapter;
-import android.text.TextUtils;
-
-import com.android.internal.telephony.CallStateException;
-import com.android.internal.telephony.Connection;
-import com.android.internal.telephony.Phone;
-
-/**
- * The parent class for telephony-based call services. Subclasses provide the specific phone (GSM,
- * CDMA, etc...) to use.
- */
-public abstract class BaseTelephonyCallService extends CallService {
-    /** {@inheritDoc} */
-    @Override
-    public void abort(String callId) {
-        TelephonyCallConnection callConnection = CallRegistrar.get(callId);
-        if (callConnection != null) {
-            callConnection.disconnect(true);
-        }
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public void disconnect(String callId) {
-        TelephonyCallConnection callConnection = CallRegistrar.get(callId);
-        if (callConnection != null) {
-            callConnection.disconnect(false);
-        }
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public void hold(String callId) {
-        Log.d(this, "Attempting to put call on hold: %s", callId);
-        TelephonyCallConnection callConnection = CallRegistrar.get(callId);
-        if (callConnection != null) {
-            callConnection.hold();
-        }
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public void unhold(String callId) {
-        Log.d(this, "Attempting to release call from hold: %s", callId);
-        TelephonyCallConnection callConnection = CallRegistrar.get(callId);
-        if (callConnection != null) {
-            callConnection.unhold();
-        }
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public void onAudioStateChanged(String activeCallId, CallAudioState audioState) {
-        TelephonyCallConnection callConnection = CallRegistrar.get(activeCallId);
-        if (callConnection != null) {
-            callConnection.onAudioStateChanged(audioState);
-        }
-    }
-
-    /**
-     * Initiates the call, should be called by the subclass.
-     */
-    protected void startCallWithPhone(Phone phone, CallInfo callInfo) {
-        String callId = callInfo.getId();
-        if (phone == null) {
-            getAdapter().handleFailedOutgoingCall(callId, "Phone is null");
-            return;
-        }
-
-        if (callInfo.getHandle() == null) {
-            getAdapter().handleFailedOutgoingCall(callInfo.getId(), "Handle is null");
-            return;
-        }
-
-        String number = callInfo.getHandle().getSchemeSpecificPart();
-        if (TextUtils.isEmpty(number)) {
-            getAdapter().handleFailedOutgoingCall(callId, "Unable to parse number");
-            return;
-        }
-
-        Connection connection;
-        try {
-            connection = phone.dial(number);
-        } catch (CallStateException e) {
-            Log.e(this, e, "Call to Phone.dial failed with exception");
-            getAdapter().handleFailedOutgoingCall(callId, e.getMessage());
-            return;
-        }
-
-        if (connection == null) {
-            getAdapter().handleFailedOutgoingCall(callId, "Call to phone.dial failed");
-            return;
-        }
-
-        TelephonyCallConnection callConnection =
-                new TelephonyCallConnection(getAdapter(), callId, connection);
-        CallRegistrar.register(callId, callConnection);
-
-        getAdapter().handleSuccessfulOutgoingCall(callId);
-    }
-}
diff --git a/src/com/android/services/telephony/CallRegistrar.java b/src/com/android/services/telephony/CallRegistrar.java
deleted file mode 100644
index a1a41c2..0000000
--- a/src/com/android/services/telephony/CallRegistrar.java
+++ /dev/null
@@ -1,91 +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 com.google.common.base.Preconditions;
-import com.google.common.collect.Maps;
-import com.android.internal.telephony.Connection;
-
-import java.util.Collection;
-import java.util.HashMap;
-
-/**
- * Maintains global associations between a Telecomm-supplied call ID and a
- * {@link TelephonyCallConnection} object.
- */
-final class CallRegistrar {
-    private static final String TAG = CallRegistrar.class.getSimpleName();
-
-    /** Map of all call connections keyed by the call ID.  */
-    private static HashMap<String, TelephonyCallConnection> sCallConnections = Maps.newHashMap();
-
-    /**
-     * Registers the specified call ID with the specified call connection.
-     *
-     * @param callId The call ID from Telecomm.
-     * @param callConnection The call connection.
-     */
-    static void register(String callId, TelephonyCallConnection callConnection) {
-        Preconditions.checkNotNull(callId);
-        Preconditions.checkNotNull(callConnection);
-
-        if (sCallConnections.containsKey(callId)) {
-            Log.wtf(TAG, "Reregistering the call: %s", callId);
-        } else {
-            sCallConnections.put(callId, callConnection);
-        }
-    }
-
-    /**
-     * Unregisters the specified call ID.
-     *
-     * @param callId The call ID from Telecomm.
-     */
-    static void unregister(String callId) {
-        Preconditions.checkNotNull(callId);
-        sCallConnections.remove(callId);
-    }
-
-    /**
-     * Returns true if the specified connection has already been registered with a call ID.
-     *
-     * @param connection The connection to test.
-     */
-    static boolean isConnectionRegistered(Connection connection) {
-        for (TelephonyCallConnection callConnection : CallRegistrar.getCallConnections()) {
-            if (callConnection.getOriginalConnection() == connection) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    static TelephonyCallConnection get(String callId) {
-        return sCallConnections.get(callId);
-    }
-
-    static boolean isEmpty() {
-        return sCallConnections.isEmpty();
-    }
-
-    /**
-     * Returns all the registered call connections as a collection.
-     */
-    static Collection<TelephonyCallConnection> getCallConnections() {
-        return sCallConnections.values();
-    }
-}
diff --git a/src/com/android/services/telephony/CdmaCallService.java b/src/com/android/services/telephony/CdmaCallService.java
deleted file mode 100644
index 6ddfeee..0000000
--- a/src/com/android/services/telephony/CdmaCallService.java
+++ /dev/null
@@ -1,64 +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.content.Context;
-import android.telecomm.CallInfo;
-import android.telephony.TelephonyManager;
-
-import com.android.internal.telephony.Phone;
-import com.android.internal.telephony.PhoneFactory;
-
-/**
- * Call service that uses the CDMA phone.
- */
-public class CdmaCallService extends PstnCallService {
-    static boolean shouldSelect(Context context, CallInfo callInfo) {
-        TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(
-                Context.TELEPHONY_SERVICE);
-        return telephonyManager.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA;
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public void isCompatibleWith(CallInfo callInfo) {
-        getAdapter().setIsCompatibleWith(callInfo.getId(), shouldSelect(this, callInfo));
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    protected Phone getPhone() {
-        return CachedPhoneFactory.getCdmaPhone();
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public void playDtmfTone(String callId, 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);
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public void stopDtmfTone(String callId) {
-        // no-op, we only play timed dtmf tones for cdma.
-    }
-}
diff --git a/src/com/android/services/telephony/CdmaConnection.java b/src/com/android/services/telephony/CdmaConnection.java
new file mode 100644
index 0000000..f46e64f
--- /dev/null
+++ b/src/com/android/services/telephony/CdmaConnection.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.services.telephony;
+
+import com.android.internal.telephony.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);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public 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);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void onStopDtmfTone() {
+        // no-op, we only play timed dtmf tones for cdma.
+        super.onStopDtmfTone();
+    }
+}
diff --git a/src/com/android/services/telephony/CdmaConnectionService.java b/src/com/android/services/telephony/CdmaConnectionService.java
new file mode 100644
index 0000000..06add1b
--- /dev/null
+++ b/src/com/android/services/telephony/CdmaConnectionService.java
@@ -0,0 +1,59 @@
+/*
+ * 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.content.Context;
+import android.net.Uri;
+import android.telephony.TelephonyManager;
+
+import com.android.internal.telephony.Connection;
+import com.android.internal.telephony.Phone;
+import com.android.phone.Constants;
+import com.android.services.telecomm.ConnectionRequest;
+
+/**
+ * Connection service that uses CDMA.
+ */
+public class CdmaConnectionService extends PstnConnectionService {
+
+    /** {@inheritDoc} */
+    @Override
+    protected Phone getPhone() {
+        return CachedPhoneFactory.getCdmaPhone();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected boolean canCall(Uri handle) {
+        return canCall(this, handle);
+    }
+
+    // TODO: Refactor this out when CallServiceSelector is deprecated
+    /* package */ static boolean canCall(Context context, Uri handle) {
+        TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(
+                Context.TELEPHONY_SERVICE);
+        return telephonyManager.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA
+                && Constants.SCHEME_TEL.equals(handle.getScheme());
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected TelephonyConnection onCreateTelephonyConnection(
+            ConnectionRequest request, Connection connection) {
+        return new CdmaConnection(getPhone(), connection);
+    }
+}
diff --git a/src/com/android/services/telephony/GsmConnection.java b/src/com/android/services/telephony/GsmConnection.java
new file mode 100644
index 0000000..50ef154
--- /dev/null
+++ b/src/com/android/services/telephony/GsmConnection.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.services.telephony;
+
+import com.android.internal.telephony.Connection;
+import com.android.internal.telephony.Phone;
+
+/**
+ * Manages a single phone call handled by GSM.
+ */
+public class GsmConnection extends PstnConnection {
+
+    public GsmConnection(Phone phone, Connection connection) {
+        super(phone, connection);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void onPlayDtmfTone(char digit) {
+        getPhone().startDtmf(digit);
+        super.onPlayDtmfTone(digit);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void onStopDtmfTone() {
+        getPhone().stopDtmf();
+        super.onStopDtmfTone();
+    }
+}
diff --git a/src/com/android/services/telephony/GsmCallService.java b/src/com/android/services/telephony/GsmConnectionService.java
similarity index 61%
rename from src/com/android/services/telephony/GsmCallService.java
rename to src/com/android/services/telephony/GsmConnectionService.java
index 7470405..bf4fe7d 100644
--- a/src/com/android/services/telephony/GsmCallService.java
+++ b/src/com/android/services/telephony/GsmConnectionService.java
@@ -17,28 +17,18 @@
 package com.android.services.telephony;
 
 import android.content.Context;
-import android.telecomm.CallInfo;
+import android.net.Uri;
 import android.telephony.TelephonyManager;
 
+import com.android.internal.telephony.Connection;
 import com.android.internal.telephony.Phone;
-import com.android.internal.telephony.PhoneFactory;
+import com.android.phone.Constants;
+import com.android.services.telecomm.ConnectionRequest;
 
 /**
- * Call service that uses the GSM phone.
+ * Connnection service that uses GSM.
  */
-public class GsmCallService extends PstnCallService {
-    static boolean shouldSelect(Context context, CallInfo callInfo) {
-        TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(
-                Context.TELEPHONY_SERVICE);
-        return telephonyManager.getPhoneType() == TelephonyManager.PHONE_TYPE_GSM;
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public void isCompatibleWith(CallInfo callInfo) {
-        getAdapter().setIsCompatibleWith(callInfo.getId(), shouldSelect(this, callInfo));
-    }
-
+public class GsmConnectionService extends PstnConnectionService {
     /** {@inheritDoc} */
     @Override
     protected Phone getPhone() {
@@ -47,13 +37,22 @@
 
     /** {@inheritDoc} */
     @Override
-    public void playDtmfTone(String callId, char digit) {
-        getPhone().startDtmf(digit);
+    protected boolean canCall(Uri handle) {
+        return canCall(this, handle);
+    }
+
+    // TODO: Refactor this out when CallServiceSelector is deprecated
+    /* package */ static boolean canCall(Context context, Uri handle) {
+        TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(
+                Context.TELEPHONY_SERVICE);
+        return telephonyManager.getPhoneType() == TelephonyManager.PHONE_TYPE_GSM
+                && Constants.SCHEME_TEL.equals(handle.getScheme());
     }
 
     /** {@inheritDoc} */
     @Override
-    public void stopDtmfTone(String callId) {
-        getPhone().stopDtmf();
+    protected TelephonyConnection onCreateTelephonyConnection(
+            ConnectionRequest request, Connection connection) {
+        return new GsmConnection(getPhone(), connection);
     }
 }
diff --git a/src/com/android/services/telephony/PstnCallService.java b/src/com/android/services/telephony/PstnCallService.java
deleted file mode 100644
index 8a458f7..0000000
--- a/src/com/android/services/telephony/PstnCallService.java
+++ /dev/null
@@ -1,198 +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.os.Bundle;
-import android.telecomm.CallInfo;
-import android.telecomm.CallState;
-
-import android.telephony.PhoneNumberUtils;
-
-import com.android.internal.telephony.Call;
-import com.android.internal.telephony.CallStateException;
-import com.android.internal.telephony.Connection;
-import com.android.internal.telephony.Phone;
-import com.android.phone.Constants;
-import com.google.android.collect.Sets;
-
-import java.util.Set;
-
-/**
- * The parent class for PSTN-based call services. Handles shared functionality between all PSTN
- * call services.
- */
-public abstract class PstnCallService extends BaseTelephonyCallService {
-    private EmergencyCallHelper mEmergencyCallHelper;
-    private Set<String> mPendingOutgoingEmergencyCalls = Sets.newHashSet();
-
-    @Override
-    public void onCreate() {
-        super.onCreate();
-        mEmergencyCallHelper = new EmergencyCallHelper(this);
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public final void call(final CallInfo callInfo) {
-        // TODO: Consider passing call emergency information as part of CallInfo so that we dont
-        // have to make the check here once again.
-        String handle = callInfo.getHandle().getSchemeSpecificPart();
-        final Phone phone = getPhone();
-        if (PhoneNumberUtils.isPotentialEmergencyNumber(handle)) {
-            final String callId = callInfo.getId();
-
-            EmergencyCallHelper.Callback callback = new EmergencyCallHelper.Callback() {
-                @Override
-                public void onComplete(boolean isRadioReady) {
-                    if (mPendingOutgoingEmergencyCalls.remove(callId)) {
-                        // The emergency call was still pending (not aborted) so continue with the
-                        // rest of the logic.
-
-                        if (isRadioReady) {
-                            startCallWithPhone(phone, callInfo);
-                        } else {
-                            getAdapter().handleFailedOutgoingCall(
-                                    callInfo.getId(), "Failed to turn on radio.");
-                        }
-                    }
-                }
-            };
-
-            mPendingOutgoingEmergencyCalls.add(callId);
-
-            // If the radio is already on, this will call us back fairly quickly.
-            mEmergencyCallHelper.startTurnOnRadioSequence(phone, callback);
-        } else {
-            startCallWithPhone(phone, callInfo);
-        }
-    }
-
-    /**
-     * Looks for a new incoming call and if one is found, tells Telecomm to associate the incoming
-     * call with the specified call ID.
-     *
-     * {@inheritDoc}
-     */
-    @Override
-    public final void setIncomingCallId(String callId, Bundle extras) {
-        Log.d(this, "setIncomingCallId: %s", callId);
-        Phone phone = getPhone();
-        Call call = getPhone().getRingingCall();
-
-        // The ringing call is always not-null, check if it is truly ringing by checking its state.
-        if (call.getState().isRinging()) {
-            Connection connection = call.getEarliestConnection();
-
-            if (CallRegistrar.isConnectionRegistered(connection)) {
-                Log.w(this, "Cannot set incoming call ID, ringing connection already registered.");
-            } else {
-                // Create and register a new call connection.
-                TelephonyCallConnection callConnection =
-                        new TelephonyCallConnection(getAdapter(), callId, connection);
-                CallRegistrar.register(callId, callConnection);
-
-                // Address can be null for blocked calls.
-                String address = connection.getAddress();
-                if (address == null) {
-                    address = "";
-                }
-
-                // Notify Telecomm of the incoming call.
-                Uri handle = Uri.fromParts(Constants.SCHEME_TEL, address, null);
-                CallInfo callInfo = new CallInfo(callId, CallState.RINGING, handle);
-                getAdapter().notifyIncomingCall(callInfo);
-            }
-        } else {
-            Log.w(this, "Found no ringing call, call state: %s", call.getState());
-        }
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public void answer(String callId) {
-        // TODO(santoscordon): Tons of hairy logic is missing here around multiple active calls on
-        // CDMA devices. See {@link CallManager.acceptCall}.
-
-        Log.i(this, "answer: %s", callId);
-        if (isValidRingingCall(callId)) {
-            try {
-                getPhone().acceptCall();
-            } catch (CallStateException e) {
-                Log.e(this, e, "Failed to accept call: %s", callId);
-            }
-        }
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public void reject(String callId) {
-        Log.i(this, "reject: %s", callId);
-        if (isValidRingingCall(callId)) {
-            try {
-                getPhone().rejectCall();
-            } catch (CallStateException e) {
-                Log.e(this, e, "Failed to reject call: %s", callId);
-            }
-        }
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public void abort(String callId) {
-        mPendingOutgoingEmergencyCalls.remove(callId);
-        super.abort(callId);
-    }
-
-    /**
-     * @return The current phone object behind this call service.
-     */
-    protected abstract Phone getPhone();
-
-    /**
-     * Checks to see if the specified call ID corresponds to an active incoming call. Returns false
-     * if there is no association between the specified call ID and an actual call, or if the
-     * associated call is not incoming (See {@link Call.State#isRinging}).
-     *
-     * @param callId The ID of the call.
-     */
-    private boolean isValidRingingCall(String callId) {
-        TelephonyCallConnection callConnection = CallRegistrar.get(callId);
-
-        if (callConnection == null) {
-            Log.d(this, "Unknown call ID while testing for a ringing call.");
-        } else {
-            Phone phone = getPhone();
-            Call ringingCall = phone.getRingingCall();
-
-            // The ringingCall object is always not-null so we have to check its current state.
-            if (ringingCall.getState().isRinging()) {
-                Connection connection = callConnection.getOriginalConnection();
-                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/PstnConnection.java b/src/com/android/services/telephony/PstnConnection.java
new file mode 100644
index 0000000..8157fca
--- /dev/null
+++ b/src/com/android/services/telephony/PstnConnection.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.services.telephony;
+
+import com.android.internal.telephony.Call;
+import com.android.internal.telephony.CallStateException;
+import com.android.internal.telephony.Connection;
+import com.android.internal.telephony.Phone;
+
+/**
+ * Manages a single phone call handled by the PSTN infrastructure.
+ */
+public abstract class PstnConnection extends TelephonyConnection {
+
+    private final Phone mPhone;
+
+    public PstnConnection(Phone phone, Connection connection) {
+        super(connection);
+        mPhone = phone;
+    }
+
+    /** {@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())) {
+            try {
+                mPhone.rejectCall();
+            } catch (CallStateException e) {
+                Log.e(this, e, "Failed to reject call.");
+            }
+        }
+        super.onReject();
+    }
+
+    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
new file mode 100644
index 0000000..fc59bda
--- /dev/null
+++ b/src/com/android/services/telephony/PstnConnectionService.java
@@ -0,0 +1,136 @@
+/*
+ * 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.PhoneNumberUtils;
+
+import com.android.internal.telephony.Call;
+import com.android.internal.telephony.Phone;
+import com.android.phone.Constants;
+import com.android.services.telecomm.Connection;
+import com.android.services.telecomm.ConnectionRequest;
+import com.android.services.telecomm.Response;
+
+import com.google.android.collect.Sets;
+
+import java.util.Set;
+
+/**
+ * The parent class for PSTN-based call services. Handles shared functionality between all PSTN
+ * call services.
+ */
+public abstract class PstnConnectionService extends TelephonyConnectionService {
+    private EmergencyCallHelper mEmergencyCallHelper;
+    private final Set<ConnectionRequest> mPendingOutgoingEmergencyCalls = Sets.newHashSet();
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        mEmergencyCallHelper = new EmergencyCallHelper(this);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void onCreateConnections(
+            final ConnectionRequest request,
+            final Response<ConnectionRequest, Connection> response) {
+        // 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 = getPhone();
+        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 {
+                            responseError(request, response, "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 = getPhone().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)) {
+                responseError(
+                        request,
+                        response,
+                        "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, connection);
+                } catch (Exception e) {
+                    responseError(request, response, e.getMessage());
+                    return;
+                }
+
+                responseResult(
+                        new ConnectionRequest(handle, request.getExtras()),
+                        response,
+                        telephonyConnection);
+            }
+        } else {
+            responseError(
+                    request,
+                    response,
+                    String.format("Found no ringing call, call state: %s", call.getState()));
+        }
+        super.onCreateIncomingConnection(request, response);
+    }
+
+    /**
+     * @return The current phone object behind this call service.
+     */
+    protected abstract Phone getPhone();
+}
diff --git a/src/com/android/services/telephony/SipConnection.java b/src/com/android/services/telephony/SipConnection.java
new file mode 100644
index 0000000..0545613
--- /dev/null
+++ b/src/com/android/services/telephony/SipConnection.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.services.telephony;
+
+import com.android.services.telecomm.Connection;
+
+/**
+ * A {@link Connection} object for SIP calls.
+ */
+public class SipConnection extends TelephonyConnection {
+
+    public SipConnection(com.android.internal.telephony.Connection connection) {
+        super(connection);
+    }
+
+    // TODO: Fill in the below methods
+
+    /** {@inheritDoc} */
+    @Override
+    protected void onPlayDtmfTone(char c) {
+        super.onPlayDtmfTone(c);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void onStopDtmfTone() {
+        super.onStopDtmfTone();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void onDisconnect() {
+        super.onDisconnect();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void onAbort() {
+        super.onAbort();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void onHold() {
+        super.onHold();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void onUnhold() {
+        super.onUnhold();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void onAnswer() {
+        super.onAnswer();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void onReject() {
+        super.onReject();
+    }
+}
diff --git a/src/com/android/services/telephony/SipCallService.java b/src/com/android/services/telephony/SipConnectionService.java
similarity index 69%
rename from src/com/android/services/telephony/SipCallService.java
rename to src/com/android/services/telephony/SipConnectionService.java
index 9d87655..8a61acf 100644
--- a/src/com/android/services/telephony/SipCallService.java
+++ b/src/com/android/services/telephony/SipConnectionService.java
@@ -22,30 +22,62 @@
 import android.net.sip.SipProfile;
 import android.net.Uri;
 import android.os.AsyncTask;
-import android.os.Bundle;
 import android.provider.Settings;
-import android.telecomm.CallInfo;
 import android.telephony.PhoneNumberUtils;
 
-import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.sip.SipPhone;
 import com.android.phone.Constants;
 import com.android.phone.PhoneUtils;
 import com.android.phone.sip.SipProfileDb;
 import com.android.phone.sip.SipSharedPreferences;
+import com.android.services.telecomm.Connection;
+import com.android.services.telecomm.ConnectionRequest;
+import com.android.services.telecomm.Response;
 
 import java.util.HashMap;
 
 /**
  * Call service that uses the SIP phone.
  */
-public class SipCallService extends BaseTelephonyCallService {
+public class SipConnectionService extends TelephonyConnectionService {
     private static HashMap<String, SipPhone> sSipPhones = new HashMap<String, SipPhone>();
 
-    static boolean shouldSelect(Context context, CallInfo callInfo) {
-        Uri uri = callInfo.getHandle();
-        return shouldUseSipPhone(context, uri.getScheme(), uri.getSchemeSpecificPart());
+    /** {@inheritDoc} */
+    @Override
+    public void onCreateConnections(
+            ConnectionRequest request,
+            Response<ConnectionRequest, Connection> callback) {
+        new GetSipProfileTask(this, request, callback).execute();
+        super.onCreateConnections(request, callback);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void onCreateIncomingConnection(
+            ConnectionRequest request,
+            Response<ConnectionRequest, Connection> callback) {
+        super.onCreateIncomingConnection(request, callback);
+        // TODO: fill in
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected boolean canCall(Uri handle) {
+        return canCall(this, handle);
+    }
+
+    // TODO: Refactor this out when CallServiceSelector is deprecated
+    /* package */ static boolean canCall(Context context, Uri handle) {
+        return shouldUseSipPhone(context, handle.getScheme(), handle.getSchemeSpecificPart());
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected TelephonyConnection onCreateTelephonyConnection(
+            ConnectionRequest request,
+            com.android.internal.telephony.Connection connection) {
+        return new SipConnection(connection);
     }
 
     private static boolean shouldUseSipPhone(Context context, String scheme, String number) {
@@ -80,56 +112,21 @@
         return true;
     }
 
-    /** {@inheritDoc} */
-    @Override
-    public void isCompatibleWith(CallInfo callInfo) {
-        getAdapter().setIsCompatibleWith(callInfo.getId(), shouldSelect(this, callInfo));
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public void call(CallInfo callInfo) {
-        new GetSipProfileTask(this, callInfo).execute();
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public void setIncomingCallId(String callId, Bundle extras) {
-        // TODO(santoscordon): fill in.
-    }
-
-    /** {@inheritDoc} */
-    public void answer(String callId) {
-        // TODO(santoscordon): fill in.
-    }
-
-    /** {@inheritDoc} */
-    public void reject(String callId) {
-        // TODO(santoscordon): fill in.
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public void playDtmfTone(String callId, char digit) {
-        // TODO(ihab): fill in
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public void stopDtmfTone(String callId) {
-        // TODO(ihab): fill in
-    }
-
     /**
      * Asynchronously looks up the SIP profile to use for the given call.
      */
     private class GetSipProfileTask extends AsyncTask<Void, Void, SipProfile> {
-        private final CallInfo mCallInfo;
+        private final ConnectionRequest mRequest;
+        private final Response<ConnectionRequest, Connection> mResponse;
         private final SipProfileDb mSipProfileDb;
         private final SipSharedPreferences mSipSharedPreferences;
 
-        GetSipProfileTask(Context context, CallInfo callInfo) {
-            mCallInfo = callInfo;
+        GetSipProfileTask(
+                Context context,
+                ConnectionRequest request,
+                Response<ConnectionRequest, Connection> response) {
+            mRequest = request;
+            mResponse = response;
             mSipProfileDb = new SipProfileDb(context);
             mSipSharedPreferences = new SipSharedPreferences(context);
         }
@@ -148,11 +145,14 @@
 
         @Override
         protected void onPostExecute(SipProfile profile) {
-            onSipProfileChosen(profile, mCallInfo);
+            onSipProfileChosen(profile, mRequest, mResponse);
         }
     }
 
-    private void onSipProfileChosen(SipProfile profile, CallInfo callInfo) {
+    private void onSipProfileChosen(
+            SipProfile profile,
+            ConnectionRequest request,
+            Response<ConnectionRequest, Connection> response) {
         SipPhone phone = null;
         if (profile != null) {
             String sipUri = profile.getUriString();
@@ -167,6 +167,6 @@
                 }
             }
         }
-        startCallWithPhone(phone, callInfo);
+        startCallWithPhone(phone, request, response);
     }
 }
diff --git a/src/com/android/services/telephony/TelephonyCallServiceProvider.java b/src/com/android/services/telephony/TelephonyCallServiceProvider.java
index 12a537a..a299913 100644
--- a/src/com/android/services/telephony/TelephonyCallServiceProvider.java
+++ b/src/com/android/services/telephony/TelephonyCallServiceProvider.java
@@ -16,7 +16,6 @@
 
 package com.android.services.telephony;
 
-import android.os.IBinder;
 import android.telecomm.CallServiceDescriptor;
 import android.telecomm.CallServiceLookupResponse;
 import android.telecomm.CallServiceProvider;
@@ -32,18 +31,18 @@
     public void lookupCallServices(CallServiceLookupResponse response) {
         ArrayList<CallServiceDescriptor> descriptors = new ArrayList<CallServiceDescriptor>();
         descriptors.add(CallServiceDescriptor.newBuilder(this)
-                   .setCallService(GsmCallService.class)
+                   .setCallService(GsmConnectionService.class)
                    .setNetworkType(CallServiceDescriptor.FLAG_PSTN)
                    .build());
         descriptors.add(CallServiceDescriptor.newBuilder(this)
-                   .setCallService(CdmaCallService.class)
+                   .setCallService(CdmaConnectionService.class)
                    .setNetworkType(CallServiceDescriptor.FLAG_PSTN)
                    .build());
         descriptors.add(CallServiceDescriptor.newBuilder(this)
-                   .setCallService(SipCallService.class)
-                   .setNetworkType(CallServiceDescriptor.FLAG_WIFI |
-                           CallServiceDescriptor.FLAG_MOBILE)
-                   .build());
+                .setCallService(SipConnectionService.class)
+                .setNetworkType(CallServiceDescriptor.FLAG_WIFI |
+                        CallServiceDescriptor.FLAG_MOBILE)
+                .build());
         response.setCallServiceDescriptors(descriptors);
     }
 }
diff --git a/src/com/android/services/telephony/TelephonyCallServiceSelector.java b/src/com/android/services/telephony/TelephonyCallServiceSelector.java
index 5673694..8bfe5ef 100644
--- a/src/com/android/services/telephony/TelephonyCallServiceSelector.java
+++ b/src/com/android/services/telephony/TelephonyCallServiceSelector.java
@@ -19,7 +19,6 @@
 import android.telecomm.CallInfo;
 import android.telecomm.CallServiceDescriptor;
 import android.telecomm.CallServiceSelector;
-import android.telecomm.CallServiceSelectorAdapter;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -40,16 +39,16 @@
             }
 
             String name = descriptor.getServiceComponent().getClassName();
-            if (name.equals(CdmaCallService.class.getName())) {
-                if (CdmaCallService.shouldSelect(this, callInfo)) {
+            if (name.equals(CdmaConnectionService.class.getName())) {
+                if (CdmaConnectionService.canCall(this, callInfo.getHandle())) {
                     selectedDescriptors.add(descriptor);
                 }
-            } else if (name.equals(GsmCallService.class.getName())) {
-                if (GsmCallService.shouldSelect(this, callInfo)) {
+            } else if (name.equals(GsmConnectionService.class.getName())) {
+                if (GsmConnectionService.canCall(this, callInfo.getHandle())) {
                     selectedDescriptors.add(descriptor);
                 }
-            } else if (name.equals(SipCallService.class.getName())) {
-                if (SipCallService.shouldSelect(this, callInfo)) {
+            } else if (name.equals(SipConnectionService.class.getName())) {
+                if (SipConnectionService.canCall(this, callInfo.getHandle())) {
                     selectedDescriptors.add(descriptor);
                 }
             }
diff --git a/src/com/android/services/telephony/TelephonyCallConnection.java b/src/com/android/services/telephony/TelephonyConnection.java
similarity index 76%
rename from src/com/android/services/telephony/TelephonyCallConnection.java
rename to src/com/android/services/telephony/TelephonyConnection.java
index de4a3f7..e11e3ef 100644
--- a/src/com/android/services/telephony/TelephonyCallConnection.java
+++ b/src/com/android/services/telephony/TelephonyConnection.java
@@ -19,65 +19,54 @@
 import android.os.Handler;
 import android.os.Message;
 import android.telecomm.CallAudioState;
-import android.telecomm.CallServiceAdapter;
+import android.telephony.DisconnectCause;
 
 import com.android.internal.telephony.Call;
 import com.android.internal.telephony.CallStateException;
-import com.android.internal.telephony.Connection;
 import com.android.internal.telephony.Phone;
+import com.android.services.telecomm.Connection;
 
 /**
- * Manages a single phone call. Listens to the call's state changes and updates the
- * CallServiceAdapter.
+ * Manages a single phone call in Telephony.
  */
-class TelephonyCallConnection {
+class TelephonyConnection extends Connection {
     private static final int EVENT_PRECISE_CALL_STATE_CHANGED = 1;
 
-    private final String mCallId;
     private final StateHandler mHandler = new StateHandler();
 
-    private CallServiceAdapter mCallServiceAdapter;
-
-    private Connection mOriginalConnection;
+    private com.android.internal.telephony.Connection mOriginalConnection;
     private Call.State mState = Call.State.IDLE;
 
-    TelephonyCallConnection(CallServiceAdapter callServiceAdapter, String callId,
-            Connection connection) {
-        mCallServiceAdapter = callServiceAdapter;
-        mCallId = callId;
-        mOriginalConnection = connection;
+    protected TelephonyConnection(com.android.internal.telephony.Connection originalConnection) {
+        mOriginalConnection = originalConnection;
         mOriginalConnection.getCall().getPhone().registerForPreciseCallStateChanged(mHandler,
                 EVENT_PRECISE_CALL_STATE_CHANGED, null);
         updateState();
     }
 
-    String getCallId() {
-        return mCallId;
-    }
-
-    Connection getOriginalConnection() {
+    com.android.internal.telephony.Connection getOriginalConnection() {
         return mOriginalConnection;
     }
 
-    void disconnect(boolean shouldAbort) {
-        if (shouldAbort) {
-            mCallServiceAdapter = null;
-            close();
-        }
-        if (mOriginalConnection != null) {
-            try {
-                mOriginalConnection.hangup();
-            } catch (CallStateException e) {
-                Log.e(this, e, "Call to Connection.hangup failed with exception");
-            }
-        }
+    @Override
+    protected void onAbort() {
+        hangup();
+        super.onAbort();
     }
 
-    void hold() {
+    @Override
+    protected void onDisconnect() {
+        hangup();
+        super.onDisconnect();
+    }
+
+    @Override
+    protected void onHold() {
+        Log.d(this, "Attempting to put call on hold");
         // TODO(santoscordon): Can dialing calls be put on hold as well since they take up the
         // foreground call slot?
         if (Call.State.ACTIVE == mState) {
-            Log.v(this, "Holding active call %s.", mCallId);
+            Log.v(this, "Holding active call");
             try {
                 Phone phone = mOriginalConnection.getCall().getPhone();
                 Call ringingCall = phone.getRingingCall();
@@ -103,9 +92,12 @@
         } else {
             Log.w(this, "Cannot put a call that is not currently active on hold.");
         }
+        super.onHold();
     }
 
-    void unhold() {
+    @Override
+    protected void onUnhold() {
+        Log.d(this, "Attempting to release call from hold");
         if (Call.State.HOLDING == mState) {
             try {
                 // TODO: This doesn't handle multiple calls across call services yet
@@ -116,9 +108,11 @@
         } else {
             Log.w(this, "Cannot release a call that is not already on hold from hold.");
         }
+        super.onUnhold();
     }
 
-    void onAudioStateChanged(CallAudioState audioState) {
+    @Override
+    protected void onSetAudioState(CallAudioState audioState) {
         // TODO: update TTY mode.
         if (mOriginalConnection != null) {
             Call call = mOriginalConnection.getCall();
@@ -126,10 +120,25 @@
                 call.getPhone().setEchoSuppressionEnabled();
             }
         }
+        super.onSetAudioState(audioState);
+    }
+
+    private void hangup() {
+        if (mOriginalConnection != null) {
+            try {
+                mOriginalConnection.hangup();
+                // Set state deliberately since we are going to close() and will no longer be
+                // listening to state updates from mOriginalConnection
+                setDisconnected(DisconnectCause.NORMAL, null);
+            } catch (CallStateException e) {
+                Log.e(this, e, "Call to Connection.hangup failed with exception");
+            }
+        }
+        close();
     }
 
     private void updateState() {
-        if (mOriginalConnection == null || mCallServiceAdapter == null) {
+        if (mOriginalConnection == null) {
             return;
         }
 
@@ -138,28 +147,28 @@
             return;
         }
 
+        Log.d(this, "mOriginalConnection new state = %s", newState);
+
         mState = newState;
         switch (newState) {
             case IDLE:
                 break;
             case ACTIVE:
-                mCallServiceAdapter.setActive(mCallId);
+                setActive();
                 break;
             case HOLDING:
-                mCallServiceAdapter.setOnHold(mCallId);
+                setOnHold();
                 break;
             case DIALING:
             case ALERTING:
-                mCallServiceAdapter.setDialing(mCallId);
+                setDialing();
                 break;
             case INCOMING:
             case WAITING:
-                mCallServiceAdapter.setRinging(mCallId);
+                setRinging();
                 break;
             case DISCONNECTED:
-                mCallServiceAdapter.setDisconnected(
-                        mCallId, mOriginalConnection.getDisconnectCause(), null);
-                close();
+                setDisconnected(mOriginalConnection.getDisconnectCause(), null);
                 break;
             case DISCONNECTING:
                 break;
@@ -174,7 +183,6 @@
             }
             mOriginalConnection = null;
         }
-        CallRegistrar.unregister(mCallId);
     }
 
     private class StateHandler extends Handler {
diff --git a/src/com/android/services/telephony/TelephonyConnectionService.java b/src/com/android/services/telephony/TelephonyConnectionService.java
new file mode 100644
index 0000000..d575f05
--- /dev/null
+++ b/src/com/android/services/telephony/TelephonyConnectionService.java
@@ -0,0 +1,170 @@
+/*
+ * 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.text.TextUtils;
+
+import com.android.internal.telephony.CallStateException;
+import com.android.internal.telephony.Phone;
+import com.android.services.telecomm.Connection;
+import com.android.services.telecomm.ConnectionRequest;
+import com.android.services.telecomm.ConnectionService;
+import com.android.services.telecomm.Response;
+import com.android.services.telecomm.Subscription;
+
+import com.google.android.collect.Sets;
+
+import java.util.Set;
+
+/**
+ * The parent class for telephony-based call services. Subclasses provide the specific phone (GSM,
+ * CDMA, etc...) to use.
+ */
+public abstract class TelephonyConnectionService extends ConnectionService {
+    private static final Set<com.android.internal.telephony.Connection> sKnownConnections
+            = Sets.newHashSet();
+
+    /** {@inheritDoc} */
+    @Override
+    public void onFindSubscriptions(
+            Uri handle,
+            Response<Uri, Subscription> response) {
+        try {
+            responseResult(handle, response, canCall(handle) ? new Subscription() : null);
+        } catch (Exception e) {
+            responseError(handle, response, "onFindSubscriptions error: " + e.toString());
+        }
+    }
+
+    /**
+     * Initiates the underlying Telephony call, then creates a {@link TelephonyConnection}
+     * by calling
+     * {@link #createTelephonyConnection(ConnectionRequest,
+     *         com.android.internal.telephony.Connection)}
+     * at the appropriate time. Should be called by the subclass.
+     */
+    protected void startCallWithPhone(
+            Phone phone,
+            ConnectionRequest request,
+            Response<ConnectionRequest, Connection> response) {
+        Log.d(this, "startCallWithPhone: %s.", request);
+
+        if (phone == null) {
+            responseError(request, response, "Phone is null");
+            return;
+        }
+
+        if (request.getHandle() == null) {
+            responseError(request, response, "Handle is null");
+            return;
+        }
+
+        String number = request.getHandle().getSchemeSpecificPart();
+        if (TextUtils.isEmpty(number)) {
+            responseError(request, response, "Unable to parse number");
+            return;
+        }
+
+        com.android.internal.telephony.Connection connection;
+        try {
+            connection = phone.dial(number);
+        } catch (CallStateException e) {
+            Log.e(this, e, "Call to Phone.dial failed with exception");
+            responseError(request, response, e.getMessage());
+            return;
+        }
+
+        if (connection == null) {
+            responseError(request, response, "Call to phone.dial failed");
+            return;
+        }
+
+        try {
+            responseResult(request, response, createTelephonyConnection(request, connection));
+        } catch (Exception e) {
+            Log.e(this, e, "Call to createConnection failed with exception");
+            responseError(request, response, e.getMessage());
+        }
+    }
+
+    protected <REQUEST, RESULT> void responseError(
+            REQUEST request,
+            Response<REQUEST, RESULT> response,
+            String reason) {
+        Log.d(this, "responseError %s: %s", request, reason);
+        response.onError(request, reason);
+    }
+
+    protected void responseResult(
+            Uri request,
+            Response<Uri, Subscription> response,
+            Subscription result) {
+        Log.d(this, "responseResult %s -> %s", request, result);
+        response.onResult(request, result);
+    }
+
+    protected void responseResult(
+            ConnectionRequest request,
+            Response<ConnectionRequest, Connection> response,
+            Connection result) {
+        Log.d(this, "responseResult %s -> %s", request, result);
+        response.onResult(request, result);
+    }
+
+    protected final TelephonyConnection createTelephonyConnection(
+            ConnectionRequest request,
+            final com.android.internal.telephony.Connection connection) {
+        final TelephonyConnection telephonyConnection =
+                onCreateTelephonyConnection(request, connection);
+        sKnownConnections.add(connection);
+        telephonyConnection.addConnectionListener(new Connection.ListenerBase() {
+            @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 connection An underlying Telephony {@link com.android.internal.telephony.Connection}
+     *         to use.
+     * @return A new {@link TelephonyConnection}.
+     */
+    protected abstract TelephonyConnection onCreateTelephonyConnection(
+            ConnectionRequest request,
+            com.android.internal.telephony.Connection connection);
+}
diff --git a/src/com/android/services/telephony/TelephonyGlobals.java b/src/com/android/services/telephony/TelephonyGlobals.java
index 042d1c6..8ca7e03 100644
--- a/src/com/android/services/telephony/TelephonyGlobals.java
+++ b/src/com/android/services/telephony/TelephonyGlobals.java
@@ -66,12 +66,12 @@
         if (TelephonyManager.PHONE_TYPE_GSM == phoneType) {
             Log.d(this, "Phone type GSM found");
             mGsmIncomingCallNotifier = new IncomingCallNotifier(
-                    GsmCallService.class, CachedPhoneFactory.getGsmPhone());
+                    GsmConnectionService.class, CachedPhoneFactory.getGsmPhone());
 
         } else if (TelephonyManager.PHONE_TYPE_CDMA == phoneType) {
             Log.d(this, "Phone type CDMA found");
             mCdmaIncomingCallNotifier = new IncomingCallNotifier(
-                    CdmaCallService.class, CachedPhoneFactory.getCdmaPhone());
+                    CdmaConnectionService.class, CachedPhoneFactory.getCdmaPhone());
         }
 
         // TODO(santoscordon): Do SIP.  SIP will require a slightly different solution since it