Merge "Remote Connection implementation."
diff --git a/Android.mk b/Android.mk
index b784f3d..43b7e59 100644
--- a/Android.mk
+++ b/Android.mk
@@ -333,6 +333,7 @@
 	telecomm/java/com/android/internal/telecomm/IInCallAdapter.aidl \
 	telecomm/java/com/android/internal/telecomm/IInCallService.aidl \
 	telecomm/java/com/android/internal/telecomm/ITelecommService.aidl \
+	telecomm/java/com/android/internal/telecomm/RemoteServiceCallback.aidl \
 	telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl \
 	telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl \
 	telephony/java/com/android/internal/telephony/ITelephony.aidl \
diff --git a/api/current.txt b/api/current.txt
index 4db4c5a..75db903 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -27595,8 +27595,9 @@
     method public abstract void unhold(java.lang.String);
   }
 
-  public final class CallServiceAdapter {
+  public final class CallServiceAdapter implements android.os.IBinder.DeathRecipient {
     method public void addConferenceCall(java.lang.String);
+    method public void binderDied();
     method public void handleFailedOutgoingCall(android.telecomm.ConnectionRequest, int, java.lang.String);
     method public void handleSuccessfulOutgoingCall(java.lang.String);
     method public void handoffCall(java.lang.String);
@@ -27728,10 +27729,12 @@
   public final class ConnectionRequest implements android.os.Parcelable {
     ctor public ConnectionRequest(android.net.Uri, android.os.Bundle);
     ctor public ConnectionRequest(java.lang.String, android.net.Uri, android.os.Bundle);
+    ctor public ConnectionRequest(android.telecomm.Subscription, java.lang.String, android.net.Uri, android.os.Bundle);
     method public int describeContents();
     method public java.lang.String getCallId();
     method public android.os.Bundle getExtras();
     method public android.net.Uri getHandle();
+    method public android.telecomm.Subscription getSubscription();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator CREATOR;
   }
@@ -27741,9 +27744,12 @@
     method public final void abort(java.lang.String);
     method public final void answer(java.lang.String);
     method public final void call(android.telecomm.CallInfo);
+    method public void createRemoteOutgoingConnection(android.telecomm.ConnectionRequest, android.telecomm.SimpleResponse<android.telecomm.ConnectionRequest, android.telecomm.RemoteConnection>);
     method public final void disconnect(java.lang.String);
     method public java.util.Collection<android.telecomm.Connection> getAllConnections();
     method public final void hold(java.lang.String);
+    method public void lookupRemoteSubscriptions(android.net.Uri, android.telecomm.SimpleResponse<android.net.Uri, java.util.List<android.telecomm.Subscription>>);
+    method public void maybeRespondToSubscriptionLookup();
     method public final void onAudioStateChanged(java.lang.String, android.telecomm.CallAudioState);
     method public void onConnectionAdded(android.telecomm.Connection);
     method public void onConnectionRemoved(android.telecomm.Connection);
@@ -27813,11 +27819,46 @@
     method protected abstract void updateCall(android.telecomm.InCallCall);
   }
 
+  public final class RemoteConnection {
+    method public void addListener(android.telecomm.RemoteConnection.Listener);
+    method public void answer();
+    method public void disconnect();
+    method public int getDisconnectCause();
+    method public java.lang.String getDisconnectMessage();
+    method public void hold();
+    method public void playDtmf(char);
+    method public void postDialContinue(boolean);
+    method public void reject();
+    method public void removeListener(android.telecomm.RemoteConnection.Listener);
+    method public void stopDtmf();
+    method public void unhold();
+  }
+
+  public static abstract interface RemoteConnection.Listener {
+    method public abstract void onAudioStateChanged(android.telecomm.RemoteConnection, android.telecomm.CallAudioState);
+    method public abstract void onDestroyed(android.telecomm.RemoteConnection);
+    method public abstract void onDisconnected(android.telecomm.RemoteConnection, int, java.lang.String);
+    method public abstract void onPostDialWait(android.telecomm.RemoteConnection, java.lang.String);
+    method public abstract void onRequestingRingback(android.telecomm.RemoteConnection, boolean);
+    method public abstract void onStateChanged(android.telecomm.RemoteConnection, int);
+  }
+
+  public class RemoteConnectionService implements android.os.IBinder.DeathRecipient {
+    method public void binderDied();
+    method public void createOutgoingConnection(android.telecomm.ConnectionRequest, android.telecomm.SimpleResponse<android.telecomm.ConnectionRequest, android.telecomm.RemoteConnection>);
+    method public java.util.List<android.telecomm.Subscription> lookupSubscriptions(android.net.Uri);
+  }
+
   public abstract interface Response {
     method public abstract void onError(IN, int, java.lang.String);
     method public abstract void onResult(IN, OUT...);
   }
 
+  public abstract interface SimpleResponse {
+    method public abstract void onError(IN);
+    method public abstract void onResult(IN, OUT);
+  }
+
   public class Subscription implements android.os.Parcelable {
     ctor public Subscription(android.content.ComponentName, java.lang.String, android.net.Uri, int, int, int, boolean, boolean);
     method public int describeContents();
@@ -38354,6 +38395,22 @@
 
 }
 
+package com.android.internal.telecomm {
+
+  public abstract interface RemoteServiceCallback implements android.os.IInterface {
+    method public abstract void onError() throws android.os.RemoteException;
+    method public abstract void onResult(java.util.List<android.content.ComponentName>, java.util.List<android.os.IBinder>) throws android.os.RemoteException;
+  }
+
+  public static abstract class RemoteServiceCallback.Stub extends android.os.Binder implements com.android.internal.telecomm.RemoteServiceCallback {
+    ctor public RemoteServiceCallback.Stub();
+    method public android.os.IBinder asBinder();
+    method public static com.android.internal.telecomm.RemoteServiceCallback asInterface(android.os.IBinder);
+    method public boolean onTransact(int, android.os.Parcel, android.os.Parcel, int) throws android.os.RemoteException;
+  }
+
+}
+
 package com.android.internal.util {
 
   public abstract interface Predicate {
diff --git a/telecomm/java/android/telecomm/CallService.java b/telecomm/java/android/telecomm/CallService.java
index cf7c901..12ba1ff 100644
--- a/telecomm/java/android/telecomm/CallService.java
+++ b/telecomm/java/android/telecomm/CallService.java
@@ -27,8 +27,6 @@
 import com.android.internal.telecomm.ICallService;
 import com.android.internal.telecomm.ICallServiceAdapter;
 
-import java.util.List;
-
 /**
  * Base implementation of CallService which can be used to provide calls for the system
  * in-call UI. CallService is a one-way service from the framework's CallsManager to any app
@@ -72,7 +70,7 @@
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case MSG_SET_CALL_SERVICE_ADAPTER:
-                    mAdapter = new CallServiceAdapter((ICallServiceAdapter) msg.obj);
+                    mAdapter.addAdapter((ICallServiceAdapter) msg.obj);
                     onAdapterAttached(mAdapter);
                     break;
                 case MSG_CALL:
@@ -259,7 +257,7 @@
      */
     private final CallServiceBinder mBinder = new CallServiceBinder();
 
-    private CallServiceAdapter mAdapter = null;
+    private CallServiceAdapter mAdapter = new CallServiceAdapter();
 
     /** {@inheritDoc} */
     @Override
diff --git a/telecomm/java/android/telecomm/CallServiceAdapter.java b/telecomm/java/android/telecomm/CallServiceAdapter.java
index 2c5f078..30084d0 100644
--- a/telecomm/java/android/telecomm/CallServiceAdapter.java
+++ b/telecomm/java/android/telecomm/CallServiceAdapter.java
@@ -16,38 +16,86 @@
 
 package android.telecomm;
 
+import android.content.ComponentName;
+import android.os.IBinder;
+import android.os.IBinder.DeathRecipient;
 import android.os.RemoteException;
 
+import com.android.internal.telecomm.ICallService;
 import com.android.internal.telecomm.ICallServiceAdapter;
+import com.android.internal.telecomm.RemoteServiceCallback;
 
+import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 /**
  * Provides methods for ICallService implementations to interact with the system phone app.
  * TODO(santoscordon): Need final public-facing comments in this file.
+ * TODO(santoscordon): Rename this to CallServiceAdapterDemultiplexer (or something).
  */
-public final class CallServiceAdapter {
-    private final ICallServiceAdapter mAdapter;
+public final class CallServiceAdapter implements DeathRecipient {
+    private final Set<ICallServiceAdapter> mAdapters = new HashSet<>();
 
     /**
-     * {@hide}
+     * @hide
      */
-    public CallServiceAdapter(ICallServiceAdapter adapter) {
-        mAdapter = adapter;
+    public CallServiceAdapter() {
+    }
+
+    /**
+     * @hide
+     */
+    public void addAdapter(ICallServiceAdapter adapter) {
+        if (mAdapters.add(adapter)) {
+            try {
+                adapter.asBinder().linkToDeath(this, 0);
+            } catch (RemoteException e) {
+                mAdapters.remove(adapter);
+            }
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public void removeAdapter(ICallServiceAdapter adapter) {
+        if (mAdapters.remove(adapter)) {
+            adapter.asBinder().unlinkToDeath(this, 0);
+        }
+    }
+
+    /** ${inheritDoc} */
+    @Override
+    public void binderDied() {
+        ICallServiceAdapter adapterToRemove = null;
+        for (ICallServiceAdapter adapter : mAdapters) {
+            if (!adapter.asBinder().isBinderAlive()) {
+                adapterToRemove = adapter;
+                break;
+            }
+        }
+
+        if (adapterToRemove != null) {
+            removeAdapter(adapterToRemove);
+        }
     }
 
     /**
      * Provides Telecomm with the details of an incoming call. An invocation of this method must
-     * follow {@link CallService#setIncomingCallId} and use the call ID specified therein. Upon
-     * the invocation of this method, Telecomm will bring up the incoming-call interface where the
-     * user can elect to answer or reject a call.
+     * follow {@link CallService#setIncomingCallId} and use the call ID specified therein. Upon the
+     * invocation of this method, Telecomm will bring up the incoming-call interface where the user
+     * can elect to answer or reject a call.
      *
      * @param callInfo The details of the relevant call.
      */
     public void notifyIncomingCall(CallInfo callInfo) {
-        try {
-            mAdapter.notifyIncomingCall(callInfo);
-        } catch (RemoteException e) {
+        for (ICallServiceAdapter adapter : mAdapters) {
+            try {
+                adapter.notifyIncomingCall(callInfo);
+            } catch (RemoteException e) {
+            }
         }
     }
 
@@ -59,9 +107,11 @@
      * @param callId The ID of the outgoing call.
      */
     public void handleSuccessfulOutgoingCall(String callId) {
-        try {
-            mAdapter.handleSuccessfulOutgoingCall(callId);
-        } catch (RemoteException e) {
+        for (ICallServiceAdapter adapter : mAdapters) {
+            try {
+                adapter.handleSuccessfulOutgoingCall(callId);
+            } catch (RemoteException e) {
+            }
         }
     }
 
@@ -76,9 +126,11 @@
             ConnectionRequest request,
             int errorCode,
             String errorMsg) {
-        try {
-            mAdapter.handleFailedOutgoingCall(request, errorCode, errorMsg);
-        } catch (RemoteException e) {
+        for (ICallServiceAdapter adapter : mAdapters) {
+            try {
+                adapter.handleFailedOutgoingCall(request, errorCode, errorMsg);
+            } catch (RemoteException e) {
+            }
         }
     }
 
@@ -89,9 +141,11 @@
      * @param callId The unique ID of the call whose state is changing to active.
      */
     public void setActive(String callId) {
-        try {
-            mAdapter.setActive(callId);
-        } catch (RemoteException e) {
+        for (ICallServiceAdapter adapter : mAdapters) {
+            try {
+                adapter.setActive(callId);
+            } catch (RemoteException e) {
+            }
         }
     }
 
@@ -101,9 +155,11 @@
      * @param callId The unique ID of the call whose state is changing to ringing.
      */
     public void setRinging(String callId) {
-        try {
-            mAdapter.setRinging(callId);
-        } catch (RemoteException e) {
+        for (ICallServiceAdapter adapter : mAdapters) {
+            try {
+                adapter.setRinging(callId);
+            } catch (RemoteException e) {
+            }
         }
     }
 
@@ -113,9 +169,11 @@
      * @param callId The unique ID of the call whose state is changing to dialing.
      */
     public void setDialing(String callId) {
-        try {
-            mAdapter.setDialing(callId);
-        } catch (RemoteException e) {
+        for (ICallServiceAdapter adapter : mAdapters) {
+            try {
+                adapter.setDialing(callId);
+            } catch (RemoteException e) {
+            }
         }
     }
 
@@ -124,13 +182,15 @@
      *
      * @param callId The unique ID of the call whose state is changing to disconnected.
      * @param disconnectCause The reason for the disconnection, any of
-     *         {@link android.telephony.DisconnectCause}.
+     *            {@link android.telephony.DisconnectCause}.
      * @param disconnectMessage Optional call-service-provided message about the disconnect.
      */
     public void setDisconnected(String callId, int disconnectCause, String disconnectMessage) {
-        try {
-            mAdapter.setDisconnected(callId, disconnectCause, disconnectMessage);
-        } catch (RemoteException e) {
+        for (ICallServiceAdapter adapter : mAdapters) {
+            try {
+                adapter.setDisconnected(callId, disconnectCause, disconnectMessage);
+            } catch (RemoteException e) {
+            }
         }
     }
 
@@ -140,9 +200,11 @@
      * @param callId - The unique ID of the call whose state is changing to be on hold.
      */
     public void setOnHold(String callId) {
-        try {
-            mAdapter.setOnHold(callId);
-        } catch (RemoteException e) {
+        for (ICallServiceAdapter adapter : mAdapters) {
+            try {
+                adapter.setOnHold(callId);
+            } catch (RemoteException e) {
+            }
         }
     }
 
@@ -153,9 +215,11 @@
      * @param ringback Whether Telecomm should start playing a ringback tone.
      */
     public void setRequestingRingback(String callId, boolean ringback) {
-        try {
-            mAdapter.setRequestingRingback(callId, ringback);
-        } catch (RemoteException e) {
+        for (ICallServiceAdapter adapter : mAdapters) {
+            try {
+                adapter.setRequestingRingback(callId, ringback);
+            } catch (RemoteException e) {
+            }
         }
     }
 
@@ -167,9 +231,11 @@
      * @hide
      */
     public void setCanConference(String callId, boolean canConference) {
-        try {
-            mAdapter.setCanConference(callId, canConference);
-        } catch (RemoteException ignored) {
+        for (ICallServiceAdapter adapter : mAdapters) {
+            try {
+                adapter.setCanConference(callId, canConference);
+            } catch (RemoteException ignored) {
+            }
         }
     }
 
@@ -179,13 +245,15 @@
      *
      * @param callId The unique ID of the call being conferenced.
      * @param conferenceCallId The unique ID of the conference call. Null if call is not
-     *         conferenced.
+     *            conferenced.
      * @hide
      */
     public void setIsConferenced(String callId, String conferenceCallId) {
-        try {
-            mAdapter.setIsConferenced(callId, conferenceCallId);
-        } catch (RemoteException ignored) {
+        for (ICallServiceAdapter adapter : mAdapters) {
+            try {
+                adapter.setIsConferenced(callId, conferenceCallId);
+            } catch (RemoteException ignored) {
+            }
         }
     }
 
@@ -197,16 +265,20 @@
      * @hide
      */
     public void removeCall(String callId) {
-        try {
-            mAdapter.removeCall(callId);
-        } catch (RemoteException ignored) {
+        for (ICallServiceAdapter adapter : mAdapters) {
+            try {
+                adapter.removeCall(callId);
+            } catch (RemoteException ignored) {
+            }
         }
     }
 
     public void onPostDialWait(String callId, String remaining) {
-        try {
-            mAdapter.onPostDialWait(callId, remaining);
-        } catch (RemoteException ignored) {
+        for (ICallServiceAdapter adapter : mAdapters) {
+            try {
+                adapter.onPostDialWait(callId, remaining);
+            } catch (RemoteException ignored) {
+            }
         }
     }
 
@@ -216,9 +288,11 @@
      * @param callId The identifier of the call to handoff.
      */
     public void handoffCall(String callId) {
-        try {
-            mAdapter.handoffCall(callId);
-        } catch (RemoteException e) {
+        for (ICallServiceAdapter adapter : mAdapters) {
+            try {
+                adapter.handoffCall(callId);
+            } catch (RemoteException e) {
+            }
         }
     }
 
@@ -228,9 +302,26 @@
      * @param callId The unique ID of the conference call.
      */
     public void addConferenceCall(String callId) {
-        try {
-            mAdapter.addConferenceCall(callId, null);
-        } catch (RemoteException ignored) {
+        for (ICallServiceAdapter adapter : mAdapters) {
+            try {
+                adapter.addConferenceCall(callId, null);
+            } catch (RemoteException ignored) {
+            }
+        }
+    }
+
+    /**
+     * Retrieves a list of remote connection services usable to place calls.
+     * @hide
+     */
+    public void queryRemoteConnectionServices(RemoteServiceCallback callback) {
+        // Only supported when there is only one adapter.
+        if (mAdapters.size() == 1) {
+            try {
+                mAdapters.iterator().next().queryRemoteConnectionServices(callback);
+            } catch (RemoteException e) {
+                Log.e(this, e, "Exception trying to query for remote CSs");
+            }
         }
     }
 }
diff --git a/telecomm/java/android/telecomm/ConnectionRequest.java b/telecomm/java/android/telecomm/ConnectionRequest.java
index bf5727b..61ac816 100644
--- a/telecomm/java/android/telecomm/ConnectionRequest.java
+++ b/telecomm/java/android/telecomm/ConnectionRequest.java
@@ -33,18 +33,29 @@
     private final String mCallId;
     private final Uri mHandle;
     private final Bundle mExtras;
+    private final Subscription mSubscription;
 
     public ConnectionRequest(Uri handle, Bundle extras) {
         this(null, handle, extras);
     }
 
     public ConnectionRequest(String callId, Uri handle, Bundle extras) {
+        this(null, callId, handle, extras);
+    }
+
+    public ConnectionRequest(Subscription subscription, String callId, Uri handle, Bundle extras) {
         mCallId = callId;
         mHandle = handle;
         mExtras = extras;
+        mSubscription = subscription;
     }
 
     /**
+     * The subscription which should be used to place the call.
+     */
+    public Subscription getSubscription() { return mSubscription; }
+
+    /**
      * An identifier for this call.
      */
     public String getCallId() { return mCallId; }
diff --git a/telecomm/java/android/telecomm/ConnectionService.java b/telecomm/java/android/telecomm/ConnectionService.java
index 5aba941..4b69a3c 100644
--- a/telecomm/java/android/telecomm/ConnectionService.java
+++ b/telecomm/java/android/telecomm/ConnectionService.java
@@ -16,12 +16,21 @@
 
 package android.telecomm;
 
+import android.content.ComponentName;
 import android.net.Uri;
 import android.os.Bundle;
+
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
 import android.telephony.DisconnectCause;
 
+import com.android.internal.telecomm.ICallService;
+import com.android.internal.telecomm.RemoteServiceCallback;
+
 import java.util.Collection;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -36,6 +45,12 @@
     // Mappings from Connections to IDs as understood by the current CallService implementation
     private final Map<String, Connection> mConnectionById = new HashMap<>();
     private final Map<Connection, String> mIdByConnection = new HashMap<>();
+    private final RemoteConnectionManager mRemoteConnectionManager = new RemoteConnectionManager();
+    private final Handler mHandler = new Handler(Looper.getMainLooper());
+
+    private SimpleResponse<Uri, List<Subscription>> mSubscriptionLookupResponse;
+    private Uri mSubscriptionLookupHandle;
+    private boolean mAreSubscriptionsInitialized = false;
 
     private final Connection.Listener mConnectionListener = new Connection.Listener() {
         @Override
@@ -314,6 +329,71 @@
     }
 
     /**
+     * @hide
+     */
+    @Override
+    protected void onAdapterAttached(CallServiceAdapter adapter) {
+        if (mAreSubscriptionsInitialized) {
+            // No need to query again if we already did it.
+            return;
+        }
+
+        getAdapter().queryRemoteConnectionServices(new RemoteServiceCallback.Stub() {
+            @Override
+            public void onResult(
+                    final List<ComponentName> componentNames,
+                    final List<IBinder> callServices) {
+                mHandler.post(new Runnable() {
+                    @Override public void run() {
+                        for (int i = 0; i < componentNames.size() && i < callServices.size(); i++) {
+                            mRemoteConnectionManager.addConnectionService(
+                                    componentNames.get(i),
+                                    ICallService.Stub.asInterface(callServices.get(i)));
+                        }
+                        mAreSubscriptionsInitialized = true;
+                        Log.d(this, "remote call services found: " + callServices);
+                        maybeRespondToSubscriptionLookup();
+                    }
+                });
+            }
+
+            @Override
+            public void onError() {
+                mHandler.post(new Runnable() {
+                    @Override public void run() {
+                        mAreSubscriptionsInitialized = true;
+                        maybeRespondToSubscriptionLookup();
+                    }
+                });
+            }
+        });
+    }
+
+    public void lookupRemoteSubscriptions(
+            Uri handle, SimpleResponse<Uri, List<Subscription>> response) {
+        mSubscriptionLookupResponse = response;
+        mSubscriptionLookupHandle = handle;
+        maybeRespondToSubscriptionLookup();
+    }
+
+    public void maybeRespondToSubscriptionLookup() {
+        if (mAreSubscriptionsInitialized && mSubscriptionLookupResponse != null) {
+            mSubscriptionLookupResponse.onResult(
+                    mSubscriptionLookupHandle,
+                    mRemoteConnectionManager.getSubscriptions(mSubscriptionLookupHandle));
+
+            mSubscriptionLookupHandle = null;
+            mSubscriptionLookupResponse = null;
+        }
+    }
+
+    public void createRemoteOutgoingConnection(
+            ConnectionRequest request,
+            SimpleResponse<ConnectionRequest, RemoteConnection> response) {
+        mRemoteConnectionManager.createOutgoingConnection(request, response);
+    }
+
+    /**
      * Returns all connections currently associated with this connection service.
      */
     public Collection<Connection> getAllConnections() {
diff --git a/telecomm/java/android/telecomm/RemoteConnection.java b/telecomm/java/android/telecomm/RemoteConnection.java
new file mode 100644
index 0000000..92db0d8
--- /dev/null
+++ b/telecomm/java/android/telecomm/RemoteConnection.java
@@ -0,0 +1,227 @@
+/*
+ * 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 android.telecomm;
+
+import android.net.Uri;
+
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.telephony.DisconnectCause;
+
+import com.android.internal.telecomm.ICallService;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * RemoteConnection object used by RemoteConnectionService.
+ */
+public final class RemoteConnection {
+    public interface Listener {
+        void onStateChanged(RemoteConnection connection, int state);
+        void onAudioStateChanged(RemoteConnection connection, CallAudioState state);
+        void onDisconnected(RemoteConnection connection, int cause, String message);
+        void onRequestingRingback(RemoteConnection connection, boolean ringback);
+        void onPostDialWait(RemoteConnection connection, String remainingDigits);
+        void onDestroyed(RemoteConnection connection);
+    }
+
+    private final ICallService mCallService;
+    private final String mConnectionId;
+    private final Set<Listener> mListeners = new HashSet<>();
+
+    private int mState;
+    private int mDisconnectCause = DisconnectCause.NOT_VALID;
+    private String mDisconnectMessage;
+    private boolean mRequestingRingback;
+    private boolean mConnected;
+
+    /**
+     * @hide
+     */
+    RemoteConnection(ICallService callService, String connectionId) {
+        mCallService = callService;
+        mConnectionId = connectionId;
+
+        mConnected = true;
+    }
+
+    public void addListener(Listener listener) {
+        mListeners.add(listener);
+    }
+
+    public void removeListener(Listener listener) {
+        mListeners.remove(listener);
+    }
+
+    public int getDisconnectCause() {
+        return mDisconnectCause;
+    }
+
+    public String getDisconnectMessage() {
+        return mDisconnectMessage;
+    }
+
+    public void answer() {
+        try {
+            if (mConnected) {
+                mCallService.answer(mConnectionId);
+            }
+        } catch (RemoteException ignored) {
+        }
+    }
+
+    public void reject() {
+        try {
+            if (mConnected) {
+                mCallService.reject(mConnectionId);
+            }
+        } catch (RemoteException ignored) {
+        }
+    }
+
+    public void hold() {
+        try {
+            if (mConnected) {
+                mCallService.hold(mConnectionId);
+            }
+        } catch (RemoteException ignored) {
+        }
+    }
+
+    public void unhold() {
+        try {
+            if (mConnected) {
+                mCallService.unhold(mConnectionId);
+            }
+        } catch (RemoteException ignored) {
+        }
+    }
+
+    public void disconnect() {
+        try {
+            if (mConnected) {
+                mCallService.disconnect(mConnectionId);
+            }
+        } catch (RemoteException ignored) {
+        }
+    }
+
+    public void playDtmf(char digit) {
+        try {
+            if (mConnected) {
+                mCallService.playDtmfTone(mConnectionId, digit);
+            }
+        } catch (RemoteException ignored) {
+        }
+    }
+
+    public void stopDtmf() {
+        try {
+            if (mConnected) {
+                mCallService.stopDtmfTone(mConnectionId);
+            }
+        } catch (RemoteException ignored) {
+        }
+    }
+
+    public void postDialContinue(boolean proceed) {
+        try {
+            if (mConnected) {
+                mCallService.onPostDialContinue(mConnectionId, proceed);
+            }
+        } catch (RemoteException ignored) {
+        }
+    }
+
+    /**
+     * @hide
+     */
+    void setState(int state) {
+        if (mState != state) {
+            mState = state;
+            for (Listener l: mListeners) {
+                l.onStateChanged(this, state);
+            }
+        }
+    }
+
+    /**
+     * @hide
+     */
+    void setAudioState(CallAudioState state) {
+        for (Listener l: mListeners) {
+            l.onAudioStateChanged(this, state);
+        }
+    }
+
+    /**
+     * @hide
+     */
+    void setDisconnected(int cause, String message) {
+        if (mState != Connection.State.DISCONNECTED) {
+            mState = Connection.State.DISCONNECTED;
+            mDisconnectCause = cause;
+            mDisconnectMessage = message;
+
+            for (Listener l : mListeners) {
+                l.onDisconnected(this, cause, message);
+            }
+        }
+    }
+
+    /**
+     * @hide
+     */
+    void setRequestingRingback(boolean ringback) {
+        if (mRequestingRingback != ringback) {
+            mRequestingRingback = ringback;
+            for (Listener l : mListeners) {
+                l.onRequestingRingback(this, ringback);
+            }
+        }
+    }
+
+    /**
+     * @hide
+     */
+    void setDestroyed() {
+        if (!mListeners.isEmpty()) {
+            // Make sure that the listeners are notified that the call is destroyed first.
+            if (mState != Connection.State.DISCONNECTED) {
+                setDisconnected(DisconnectCause.ERROR_UNSPECIFIED, "Connection destroyed.");
+            }
+
+            Set<Listener> listeners = new HashSet<Listener>(mListeners);
+            mListeners.clear();
+            for (Listener l : listeners) {
+                l.onDestroyed(this);
+            }
+
+            mConnected = false;
+        }
+    }
+
+    /**
+     * @hide
+     */
+    void setPostDialWait(String remainingDigits) {
+        for (Listener l : mListeners) {
+            l.onPostDialWait(this, remainingDigits);
+        }
+    }
+}
diff --git a/telecomm/java/android/telecomm/RemoteConnectionManager.java b/telecomm/java/android/telecomm/RemoteConnectionManager.java
new file mode 100644
index 0000000..4201f23
--- /dev/null
+++ b/telecomm/java/android/telecomm/RemoteConnectionManager.java
@@ -0,0 +1,74 @@
+/*
+ * 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
+ R* limitations under the License.
+ */
+
+package android.telecomm;
+
+import android.content.ComponentName;
+import android.net.Uri;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import com.android.internal.telecomm.ICallService;
+
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @hide
+ */
+public class RemoteConnectionManager {
+    private Map<ComponentName, RemoteConnectionService> mRemoteConnectionServices = new HashMap<>();
+
+    void addConnectionService(ComponentName componentName, ICallService callService) {
+        if (!mRemoteConnectionServices.containsKey(componentName)) {
+            try {
+                RemoteConnectionService remoteConnectionService =
+                        new RemoteConnectionService(componentName, callService);
+                mRemoteConnectionServices.put(componentName, remoteConnectionService);
+            } catch (RemoteException ignored) {
+            }
+        }
+    }
+
+    List<Subscription> getSubscriptions(Uri handle) {
+        List<Subscription> subscriptions = new LinkedList<>();
+        Log.d(this, "Getting subscriptions: " + mRemoteConnectionServices.keySet());
+        for (RemoteConnectionService remoteService : mRemoteConnectionServices.values()) {
+            // TODO(santoscordon): Eventually this will be async.
+            subscriptions.addAll(remoteService.lookupSubscriptions(handle));
+        }
+        return subscriptions;
+    }
+
+    public void createOutgoingConnection(
+            ConnectionRequest request,
+            final SimpleResponse<ConnectionRequest, RemoteConnection> response) {
+        Subscription subscription = request.getSubscription();
+        if (subscription == null) {
+            throw new IllegalArgumentException("subscription must be specified.");
+        }
+
+        ComponentName componentName = request.getSubscription().getComponentName();
+        if (!mRemoteConnectionServices.containsKey(componentName)) {
+            throw new UnsupportedOperationException("subscription not supported: " + componentName);
+        } else {
+            RemoteConnectionService remoteService = mRemoteConnectionServices.get(componentName);
+            remoteService.createOutgoingConnection(request, response);
+        }
+    }
+}
diff --git a/telecomm/java/android/telecomm/RemoteConnectionService.java b/telecomm/java/android/telecomm/RemoteConnectionService.java
new file mode 100644
index 0000000..86a43cb
--- /dev/null
+++ b/telecomm/java/android/telecomm/RemoteConnectionService.java
@@ -0,0 +1,274 @@
+/*
+ * 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
+ R* limitations under the License.
+ */
+
+package android.telecomm;
+
+import android.content.ComponentName;
+import android.net.Uri;
+import android.os.IBinder.DeathRecipient;
+import android.os.RemoteException;
+import android.telephony.DisconnectCause;
+
+import android.text.TextUtils;
+
+import com.android.internal.telecomm.ICallService;
+import com.android.internal.telecomm.ICallServiceAdapter;
+import com.android.internal.telecomm.RemoteServiceCallback;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * Remote connection service which other connection services can use to place calls on their behalf.
+ */
+public class RemoteConnectionService implements DeathRecipient {
+    private final ICallService mCallService;
+    private final ComponentName mComponentName;
+
+    private String mConnectionId;
+    private ConnectionRequest mPendingRequest;
+    private SimpleResponse<ConnectionRequest, RemoteConnection> mPendingResponse;
+    // Remote connection services only support a single connection.
+    private RemoteConnection mConnection;
+
+    private final ICallServiceAdapter mAdapter = new ICallServiceAdapter.Stub() {
+
+        /** ${inheritDoc} */
+            @Override
+        public void notifyIncomingCall(CallInfo callInfo) {
+            Log.w(this, "notifyIncomingCall not implemented in Remote connection");
+        }
+
+        /** ${inheritDoc} */
+            @Override
+        public void handleSuccessfulOutgoingCall(String connectionId) {
+            if (isPendingConnection(connectionId)) {
+                mConnection = new RemoteConnection(mCallService, connectionId);
+                mPendingResponse.onResult(mPendingRequest, mConnection);
+                clearPendingInformation();
+            }
+        }
+
+        /** ${inheritDoc} */
+            @Override
+        public void handleFailedOutgoingCall(
+                ConnectionRequest request, int errorCode, String errorMessage) {
+            if (isPendingConnection(request.getCallId())) {
+                // Use mPendingRequest instead of request so that we use the same object that was
+                // passed in to us.
+                mPendingResponse.onError(request);
+                mConnectionId = null;
+                clearPendingInformation();
+            }
+        }
+
+        /** ${inheritDoc} */
+            @Override
+        public void setActive(String connectionId) {
+            if (isCurrentConnection(connectionId)) {
+                mConnection.setState(Connection.State.ACTIVE);
+            }
+        }
+
+        /** ${inheritDoc} */
+            @Override
+        public void setRinging(String connectionId) {
+            if (isCurrentConnection(connectionId)) {
+                mConnection.setState(Connection.State.RINGING);
+            }
+        }
+
+        /** ${inheritDoc} */
+            @Override
+        public void setDialing(String connectionId) {
+            if (isCurrentConnection(connectionId)) {
+                mConnection.setState(Connection.State.DIALING);
+            }
+        }
+
+        /** ${inheritDoc} */
+            @Override
+        public void setDisconnected(
+                String connectionId, int disconnectCause, String disconnectMessage) {
+            if (isCurrentConnection(connectionId)) {
+                mConnection.setDisconnected(disconnectCause, disconnectMessage);
+            }
+        }
+
+        /** ${inheritDoc} */
+            @Override
+        public void setOnHold(String connectionId) {
+            if (isCurrentConnection(connectionId)) {
+                mConnection.setState(Connection.State.HOLDING);
+            }
+        }
+
+        /** ${inheritDoc} */
+            @Override
+        public void setRequestingRingback(String connectionId, boolean isRequestingRingback) {
+            if (isCurrentConnection(connectionId)) {
+                mConnection.setRequestingRingback(isRequestingRingback);
+            }
+        }
+
+        /** ${inheritDoc} */
+            @Override
+        public void setCanConference(String connectionId, boolean canConference) {
+            // not supported for remote connections.
+        }
+
+        /** ${inheritDoc} */
+            @Override
+        public void setIsConferenced(String connectionId, String conferenceConnectionId) {
+            // not supported for remote connections.
+        }
+
+        /** ${inheritDoc} */
+            @Override
+        public void addConferenceCall(String connectionId, CallInfo callInfo) {
+            // not supported for remote connections.
+        }
+
+        /** ${inheritDoc} */
+            @Override
+        public void removeCall(String connectionId) {
+            if (isCurrentConnection(connectionId)) {
+                destroyConnection();
+            }
+        }
+
+        /** ${inheritDoc} */
+            @Override
+        public void onPostDialWait(String connectionId, String remainingDigits) {
+            if (isCurrentConnection(connectionId)) {
+                mConnection.setPostDialWait(remainingDigits);
+            }
+        }
+
+        /** ${inheritDoc} */
+            @Override
+        public void handoffCall(String connectionId) {
+            // unnecessary.
+        }
+
+        /** ${inheritDoc} */
+        @Override
+        public void queryRemoteConnectionServices(RemoteServiceCallback callback) {
+            try {
+                // Not supported from remote connection service.
+                callback.onError();
+            } catch (RemoteException e) {
+            }
+        }
+    };
+
+    RemoteConnectionService(ComponentName componentName, ICallService callService)
+            throws RemoteException {
+        mComponentName = componentName;
+        mCallService = callService;
+
+        // TODO(santoscordon): Rename from setCallServiceAdapter to addCallServiceAdapter.
+        mCallService.setCallServiceAdapter(mAdapter);
+        mCallService.asBinder().linkToDeath(this, 0);
+    }
+
+    @Override
+    public String toString() {
+        return "[RemoteCS - " + mCallService.asBinder().toString() + "]";
+    }
+
+    /** ${inheritDoc} */
+    @Override
+    public void binderDied() {
+        if (mConnection != null) {
+            destroyConnection();
+        }
+
+        release();
+    }
+
+    /**
+     * Places an outgoing call.
+     */
+    public void createOutgoingConnection(
+            ConnectionRequest request,
+            SimpleResponse<ConnectionRequest, RemoteConnection> response) {
+
+        if (mConnectionId == null) {
+            String id = UUID.randomUUID().toString();
+            CallInfo callInfo = new CallInfo(id, CallState.NEW, request.getHandle());
+            try {
+                mCallService.call(callInfo);
+                mConnectionId = id;
+                mPendingResponse = response;
+                mPendingRequest = request;
+            } catch (RemoteException e) {
+                response.onError(request);
+            }
+        } else {
+            response.onError(request);
+        }
+    }
+
+    // TODO(santoscordon): Handle incoming connections
+    // public void handleIncomingConnection() {}
+
+    public List<Subscription> lookupSubscriptions(Uri handle) {
+        // TODO(santoscordon): Update this so that is actually calls into the RemoteConnection
+        // each time.
+        List<Subscription> subscriptions = new LinkedList<>();
+        subscriptions.add(new Subscription(
+                mComponentName,
+                null /* id */,
+                null /* handle */,
+                0 /* labelResId */,
+                0 /* shortDescriptionResId */,
+                0 /* iconResId */,
+                true /* isEnabled */,
+                false /* isSystemDefault */));
+        return subscriptions;
+    }
+
+    /**
+     * Releases the resources associated with this Remote connection service. Should be called when
+     * the remote service is no longer being used.
+     */
+    void release() {
+        mCallService.asBinder().unlinkToDeath(this, 0);
+    }
+
+    private boolean isPendingConnection(String id) {
+        return TextUtils.equals(mConnectionId, id) && mPendingResponse != null;
+    }
+
+    private boolean isCurrentConnection(String id) {
+        return mConnection != null && TextUtils.equals(mConnectionId, id);
+    }
+
+    private void clearPendingInformation() {
+        mPendingRequest = null;
+        mPendingResponse = null;
+    }
+
+    private void destroyConnection() {
+        mConnection.setDestroyed();
+        mConnection = null;
+        mConnectionId = null;
+    }
+}
diff --git a/telecomm/java/android/telecomm/SimpleResponse.java b/telecomm/java/android/telecomm/SimpleResponse.java
new file mode 100644
index 0000000..8e84adb
--- /dev/null
+++ b/telecomm/java/android/telecomm/SimpleResponse.java
@@ -0,0 +1,38 @@
+/*
+ * 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 android.telecomm;
+
+/**
+ * Used to inform a client of asynchronously returned results.
+ */
+public interface SimpleResponse<IN, OUT> {
+
+    /**
+     * Provide a set of results.
+     *
+     * @param request The original request.
+     * @param result The results.
+     */
+    void onResult(IN request, OUT result);
+
+    /**
+     * Indicates the inability to provide results.
+     *
+     * @param request The original request.
+     */
+    void onError(IN request);
+}
diff --git a/telecomm/java/com/android/internal/telecomm/ICallServiceAdapter.aidl b/telecomm/java/com/android/internal/telecomm/ICallServiceAdapter.aidl
index 270c551..87c8859 100644
--- a/telecomm/java/com/android/internal/telecomm/ICallServiceAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecomm/ICallServiceAdapter.aidl
@@ -19,6 +19,8 @@
 import android.telecomm.CallInfo;
 import android.telecomm.ConnectionRequest;
 
+import com.android.internal.telecomm.RemoteServiceCallback;
+
 /**
  * Internal remote callback interface for call services.
  *
@@ -56,4 +58,6 @@
     void onPostDialWait(String callId, String remaining);
 
     void handoffCall(String callId);
+
+    void queryRemoteConnectionServices(RemoteServiceCallback callback);
 }
diff --git a/telecomm/java/com/android/internal/telecomm/RemoteServiceCallback.aidl b/telecomm/java/com/android/internal/telecomm/RemoteServiceCallback.aidl
new file mode 100644
index 0000000..42c77d7
--- /dev/null
+++ b/telecomm/java/com/android/internal/telecomm/RemoteServiceCallback.aidl
@@ -0,0 +1,27 @@
+/*
+ * 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.internal.telecomm;
+
+import android.content.ComponentName;
+
+/**
+ * Simple response callback object.
+ */
+oneway interface RemoteServiceCallback {
+    void onError();
+    void onResult(in List<ComponentName> components, in List<IBinder> callServices);
+}