Merge "Add tones for call disconnection." into master-nova
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 780906c..d58c898 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -161,5 +161,8 @@
             </intent-filter>
         </activity-alias>
 
+        <!-- Selects call services to place emergency calls. -->
+        <service android:name="EmergencyCallServiceSelector" android:exported="false" />
+
     </application>
 </manifest>
diff --git a/src/com/android/telecomm/Call.java b/src/com/android/telecomm/Call.java
index b57ec80..f79a362 100644
--- a/src/com/android/telecomm/Call.java
+++ b/src/com/android/telecomm/Call.java
@@ -17,7 +17,9 @@
 package com.android.telecomm;
 
 import android.net.Uri;
+import android.os.Bundle;
 import android.telecomm.CallInfo;
+import android.telecomm.CallServiceDescriptor;
 import android.telecomm.CallState;
 import android.telecomm.GatewayInfo;
 import android.telephony.DisconnectCause;
@@ -91,6 +93,18 @@
      */
     private String mDisconnectMessage;
 
+    /** Info used by the call services. */
+    private Bundle mExtras;
+
+    /** The Uri to dial to perform the handoff. If this is null then handoff is not supported. */
+    private Uri mHandoffHandle;
+
+    /**
+     * References the call that is being handed off. This value is non-null for untracked calls
+     * that are being used to perform a handoff.
+     */
+    private Call mOriginalCall;
+
     /**
      * Creates an empty call object.
      *
@@ -116,6 +130,7 @@
         mIsIncoming = isIncoming;
         mCreationTime = new Date();
         mDisconnectCause = DisconnectCause.NOT_VALID;
+        mExtras = Bundle.EMPTY;
     }
 
     /** {@inheritDoc} */
@@ -242,6 +257,10 @@
         }
     }
 
+    CallServiceSelectorWrapper getCallServiceSelector() {
+        return mCallServiceSelector;
+    }
+
     void setCallServiceSelector(CallServiceSelectorWrapper selector) {
         Preconditions.checkNotNull(selector);
 
@@ -395,7 +414,13 @@
      * @return An object containing read-only information about this call.
      */
     CallInfo toCallInfo(String callId) {
-        return new CallInfo(callId, mState, mHandle, mGatewayInfo);
+        CallServiceDescriptor descriptor = null;
+        if (mCallService != null) {
+            descriptor = mCallService.getDescriptor();
+        } else if (mOriginalCall != null && mOriginalCall.mCallService != null) {
+            descriptor = mOriginalCall.mCallService.getDescriptor();
+        }
+        return new CallInfo(callId, mState, mHandle, mGatewayInfo, mExtras, descriptor);
     }
 
     /** Checks if this is a live call or not. */
@@ -411,6 +436,30 @@
         }
     }
 
+    Bundle getExtras() {
+        return mExtras;
+    }
+
+    void setExtras(Bundle extras) {
+        mExtras = extras;
+    }
+
+    Uri getHandoffHandle() {
+        return mHandoffHandle;
+    }
+
+    void setHandoffHandle(Uri handoffHandle) {
+        mHandoffHandle = handoffHandle;
+    }
+
+    Call getOriginalCall() {
+        return mOriginalCall;
+    }
+
+    void setOriginalCall(Call originalCall) {
+        mOriginalCall = originalCall;
+    }
+
     /**
      * @return True if the call is ringing, else logs the action name.
      */
diff --git a/src/com/android/telecomm/CallActivity.java b/src/com/android/telecomm/CallActivity.java
index 43c4cf3..77e64bc 100644
--- a/src/com/android/telecomm/CallActivity.java
+++ b/src/com/android/telecomm/CallActivity.java
@@ -22,7 +22,6 @@
 import android.os.Bundle;
 import android.telecomm.CallServiceDescriptor;
 import android.telecomm.TelecommConstants;
-import android.widget.Toast;
 
 /**
  * Activity that handles system CALL actions and forwards them to {@link CallsManager}.
diff --git a/src/com/android/telecomm/CallServiceAdapter.java b/src/com/android/telecomm/CallServiceAdapter.java
index 2db6255..39de235 100644
--- a/src/com/android/telecomm/CallServiceAdapter.java
+++ b/src/com/android/telecomm/CallServiceAdapter.java
@@ -55,7 +55,7 @@
                         mOutgoingCallsManager.setIsCompatibleWith(call,
                                 msg.arg1 == 1 ? true : false);
                     } else {
-                        Log.w(this, "Unknown call: %s, id: %s", call, msg.obj);
+                        Log.w(this, "setIsCompatibleWith, unknown call: %s, id: %s", call, msg.obj);
                     }
                     break;
                 case MSG_NOTIFY_INCOMING_CALL:
@@ -66,7 +66,7 @@
                                 clientCallInfo.getHandle());
                         mIncomingCallsManager.handleSuccessfulIncomingCall(call, callInfo);
                     } else {
-                        Log.w(this, "Unknown incoming call: %s, id: %s", call,
+                        Log.w(this, "notifyIncomingCall, unknown incoming call: %s, id: %s", call,
                                 clientCallInfo.getId());
                     }
                     break;
@@ -76,7 +76,9 @@
                         mOutgoingCallsManager.handleSuccessfulCallAttempt(call);
                     } else {
                         // TODO(gilad): Figure out how to wire up the callService.abort() call.
-                        Log.w(this, "Unknown outgoing call: %s, id: %s", call, msg.obj);
+                        Log.w(this,
+                                "handleSuccessfulOutgoingCall, unknown outgoing call: %s, id: %s",
+                                call, msg.obj);
                     }
                     break;
                 case MSG_HANDLE_FAILED_OUTGOING_CALL: {
@@ -87,7 +89,9 @@
                         if (call != null && mPendingCalls.remove(call) && !call.isIncoming()) {
                             mOutgoingCallsManager.handleFailedCallAttempt(call, reason);
                         } else {
-                            Log.w(this, "Unknown outgoing call: %s, id: %s", call, args.arg1);
+                            Log.w(this,
+                                    "handleFailedOutgoingCall, unknown outgoing call: %s, id: %s",
+                                    call, args.arg1);
                         }
                     } finally {
                         args.recycle();
@@ -99,7 +103,7 @@
                     if (call != null) {
                         mCallsManager.markCallAsActive(call);
                     } else {
-                        Log.w(this, "Unknown call id: %s", msg.obj);
+                        Log.w(this, "setActive, unknown call id: %s", msg.obj);
                     }
                     break;
                 case MSG_SET_RINGING:
@@ -107,7 +111,7 @@
                     if (call != null) {
                         mCallsManager.markCallAsRinging(call);
                     } else {
-                        Log.w(this, "Unknown call id: %s", msg.obj);
+                        Log.w(this, "setRinging, unknown call id: %s", msg.obj);
                     }
                     break;
                 case MSG_SET_DIALING:
@@ -115,7 +119,7 @@
                     if (call != null) {
                         mCallsManager.markCallAsDialing(call);
                     } else {
-                        Log.w(this, "Unknown call id: %s", msg.obj);
+                        Log.w(this, "setDialing, unknown call id: %s", msg.obj);
                     }
                     break;
                 case MSG_SET_DISCONNECTED: {
@@ -128,7 +132,7 @@
                             mCallsManager.markCallAsDisconnected(call, disconnectCause,
                                     disconnectMessage);
                         } else {
-                            Log.w(this, "Unknown call id: %s", args.arg1);
+                            Log.w(this, "setDisconnected, unknown call id: %s", args.arg1);
                         }
                     } finally {
                         args.recycle();
@@ -140,7 +144,7 @@
                     if (call != null) {
                         mCallsManager.markCallAsOnHold(call);
                     } else {
-                        Log.w(this, "Unknown call id: %s", msg.obj);
+                        Log.w(this, "setOnHold, unknown call id: %s", msg.obj);
                     }
                     break;
             }
@@ -174,7 +178,7 @@
     /** {@inheritDoc} */
     @Override
     public void setIsCompatibleWith(String callId, boolean isCompatible) {
-        Log.v(this, "setIsCompatibleWith id: %d, isCompatible: %b", callId, isCompatible);
+        Log.v(this, "setIsCompatibleWith id: %s, isCompatible: %b", callId, isCompatible);
         mCallIdMapper.checkValidCallId(callId);
         mHandler.obtainMessage(MSG_SET_IS_COMPATIBLE_WITH, isCompatible ? 1 : 0, 0, callId).
                 sendToTarget();
diff --git a/src/com/android/telecomm/CallServiceSelectorRepository.java b/src/com/android/telecomm/CallServiceSelectorRepository.java
index 50d26e8..709d440 100644
--- a/src/com/android/telecomm/CallServiceSelectorRepository.java
+++ b/src/com/android/telecomm/CallServiceSelectorRepository.java
@@ -93,8 +93,9 @@
             ServiceInfo serviceInfo = entry.serviceInfo;
             if (serviceInfo != null) {
                 // The entry resolves to a proper service, add it to the list of selector names.
-                selectorNames.add(
-                        new ComponentName(serviceInfo.packageName, serviceInfo.name));
+                ComponentName componentName =
+                        new ComponentName(serviceInfo.packageName, serviceInfo.name);
+                selectorNames.add(componentName);
             }
         }
 
diff --git a/src/com/android/telecomm/CallServiceSelectorWrapper.java b/src/com/android/telecomm/CallServiceSelectorWrapper.java
index f4ddf19..b5adda0 100644
--- a/src/com/android/telecomm/CallServiceSelectorWrapper.java
+++ b/src/com/android/telecomm/CallServiceSelectorWrapper.java
@@ -17,7 +17,6 @@
 package com.android.telecomm;
 
 import android.content.ComponentName;
-import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.telecomm.CallInfo;
@@ -25,7 +24,6 @@
 import android.telecomm.CallServiceSelector;
 import android.telecomm.TelecommConstants;
 
-import com.google.common.base.Preconditions;
 import com.android.internal.telecomm.ICallServiceSelector;
 import com.android.internal.telecomm.ICallServiceSelectorAdapter;
 
@@ -38,20 +36,46 @@
 final class CallServiceSelectorWrapper extends ServiceBinder<ICallServiceSelector> {
     private ICallServiceSelector mSelectorInterface;
     private final Binder mBinder = new Binder();
-    private final Handler mHandler = new Handler();
     private final CallIdMapper mCallIdMapper = new CallIdMapper("CallServiceSelector");
     private final CallServiceSelectorAdapter mAdapter;
 
     /**
-     * Creates a call-service selector for the specified component.
+     * Creates a call-service selector for the specified component using the specified action to
+     * bind to it.
+     *
+     * @param action The action used to bind to the selector.
+     * @param componentName The component name of the service.
+     * @param callsManager The calls manager.
+     * @param outgoingCallsManager The outgoing calls manager.
+     */
+    CallServiceSelectorWrapper(
+            String action,
+            ComponentName componentName,
+            CallsManager callsManager,
+            OutgoingCallsManager outgoingCallsManager) {
+
+        super(action, componentName);
+        mAdapter =
+                new CallServiceSelectorAdapter(callsManager, outgoingCallsManager, mCallIdMapper);
+    }
+
+    /**
+     * Creates a call-service selector for specified component and uses
+     * {@link TelecommConstants#ACTION_CALL_SERVICE_SELECTOR} as the action to bind.
      *
      * @param componentName The component name of the service.
+     * @param callsManager The calls manager.
+     * @param outgoingCallsManager The outgoing calls manager.
      */
-    CallServiceSelectorWrapper(ComponentName componentName, CallsManager callsManager,
+    CallServiceSelectorWrapper(
+            ComponentName componentName,
+            CallsManager callsManager,
             OutgoingCallsManager outgoingCallsManager) {
-        super(TelecommConstants.ACTION_CALL_SERVICE_SELECTOR, componentName);
-        mAdapter = new CallServiceSelectorAdapter(callsManager, outgoingCallsManager,
-                mCallIdMapper);
+
+        this(TelecommConstants.ACTION_CALL_SERVICE_SELECTOR,
+                componentName,
+                callsManager,
+                outgoingCallsManager);
     }
 
     /** See {@link CallServiceSelector#setCallServiceSelectorAdapter}. */
@@ -96,12 +120,51 @@
         mBinder.bind(callback);
     }
 
+    private void onCallUpdated(final CallInfo callInfo) {
+        BindCallback callback = new BindCallback() {
+            @Override
+            public void onSuccess() {
+                if (isServiceValid("onCallUpdated")) {
+                    try {
+                        mSelectorInterface.onCallUpdated(callInfo);
+                    } catch (RemoteException e) {
+                    }
+                }
+            }
+            @Override
+            public void onFailure() {
+            }
+        };
+        mBinder.bind(callback);
+    }
+
+    private void onCallRemoved(final String callId) {
+        BindCallback callback = new BindCallback() {
+            @Override
+            public void onSuccess() {
+                if (isServiceValid("onCallRemoved")) {
+                    try {
+                        mSelectorInterface.onCallRemoved(callId);
+                    } catch (RemoteException e) {
+                    }
+                }
+            }
+            @Override
+            public void onFailure() {
+            }
+        };
+        mBinder.bind(callback);
+    }
+
     void addCall(Call call) {
         mCallIdMapper.addCall(call);
+        onCallUpdated(call.toCallInfo(mCallIdMapper.getCallId(call)));
     }
 
     void removeCall(Call call) {
+        String callId = mCallIdMapper.getCallId(call);
         mCallIdMapper.removeCall(call);
+        onCallRemoved(callId);
     }
 
     /** {@inheritDoc} */
diff --git a/src/com/android/telecomm/CallsManager.java b/src/com/android/telecomm/CallsManager.java
index bb130d1..b7f7aec 100644
--- a/src/com/android/telecomm/CallsManager.java
+++ b/src/com/android/telecomm/CallsManager.java
@@ -53,6 +53,7 @@
         void onCallStateChanged(Call call, CallState oldState, CallState newState);
         void onIncomingCallAnswered(Call call);
         void onIncomingCallRejected(Call call);
+        void onCallHandoffHandleChanged(Call call, Uri oldHandle, Uri newHandle);
         void onForegroundCallChanged(Call oldForegroundCall, Call newForegroundCall);
         void onAudioStateChanged(CallAudioState oldAudioState, CallAudioState newAudioState);
     }
@@ -68,6 +69,12 @@
     private final Set<Call> mCalls = Sets.newLinkedHashSet();
 
     /**
+     * Set of new calls created to perform a handoff. The calls are added when handoff is initiated
+     * and removed when hadnoff is complete.
+     */
+    private final Set<Call> mPendingHandoffCalls = Sets.newLinkedHashSet();
+
+    /**
      * The call the user is currently interacting with. This is the call that should have audio
      * focus and be visible in the in-call UI.
      */
@@ -191,7 +198,7 @@
      * @param handle Handle to connect the call with.
      * @param contactInfo Information about the entity being called.
      * @param gatewayInfo Optional gateway information that can be used to route the call to the
-     *     actual dialed handle via a gateway provider. May be null.
+     *         actual dialed handle via a gateway provider. May be null.
      */
     void placeOutgoingCall(Uri handle, ContactInfo contactInfo, GatewayInfo gatewayInfo) {
         for (OutgoingCallValidator validator : mOutgoingCallValidators) {
@@ -210,7 +217,7 @@
             Log.i(this, "Creating a new outgoing call with gateway handle: %s, original handle: %s",
                     Log.pii(uriHandle), Log.pii(handle));
         }
-        Call call = new Call(uriHandle, contactInfo, gatewayInfo, false /*isIncoming*/);
+        Call call = new Call(uriHandle, contactInfo, gatewayInfo, false /* isIncoming */);
         setCallState(call, CallState.DIALING);
         addCall(call);
         mSwitchboard.placeOutgoingCall(call);
@@ -369,6 +376,33 @@
         mCallAudioManager.setAudioRoute(route);
     }
 
+    void startHandoffForCall(Call originalCall) {
+        if (!mCalls.contains(originalCall)) {
+            Log.w(this, "Unknown call %s asked to be handed off", originalCall);
+            return;
+        }
+
+        for (Call handoffCall : mPendingHandoffCalls) {
+            if (handoffCall.getOriginalCall() == originalCall) {
+                Log.w(this, "Call %s is already being handed off, skipping", originalCall);
+                return;
+            }
+        }
+
+        // Create a new call to be placed in the background. If handoff is successful then the
+        // original call will live on but its state will be updated to the new call's state. In
+        // particular the original call's call service will be updated to the new call's call
+        // service.
+        Call tempCall = new Call(originalCall.getHandoffHandle(), originalCall.getContactInfo(),
+                originalCall.getGatewayInfo(), false);
+        tempCall.setOriginalCall(originalCall);
+        tempCall.setExtras(originalCall.getExtras());
+        tempCall.setCallServiceSelector(originalCall.getCallServiceSelector());
+        mPendingHandoffCalls.add(tempCall);
+        Log.d(this, "Placing handoff call");
+        mSwitchboard.placeOutgoingCall(tempCall);
+    }
+
     /** Called when the audio state changes. */
     void onAudioStateChanged(CallAudioState oldAudioState, CallAudioState newAudioState) {
         Log.v(this, "onAudioStateChanged, audioState: %s -> %s", oldAudioState, newAudioState);
@@ -387,6 +421,10 @@
 
     void markCallAsActive(Call call) {
         setCallState(call, CallState.ACTIVE);
+
+        if (mPendingHandoffCalls.contains(call)) {
+            completeHandoff(call);
+        }
     }
 
     void markCallAsOnHold(Call call) {
@@ -407,7 +445,25 @@
     }
 
     void setHandoffInfo(Call call, Uri handle, Bundle extras) {
-        // TODO(sail): Implement this.
+        if (!mCalls.contains(call)) {
+            Log.w(this, "Unknown call (%s) asked to set handoff info", call);
+            return;
+        }
+
+        if (extras == null) {
+            call.setExtras(Bundle.EMPTY);
+        } else {
+            call.setExtras(extras);
+        }
+
+        Uri oldHandle = call.getHandoffHandle();
+        Log.v(this, "set handoff handle %s -> %s, for call: %s", oldHandle, handle, call);
+        if (!areUriEqual(oldHandle, handle)) {
+            call.setHandoffHandle(handle);
+            for (CallsManagerListener listener : mListeners) {
+                listener.onCallHandoffHandleChanged(call, oldHandle, handle);
+            }
+        }
     }
 
     /**
@@ -446,6 +502,10 @@
         if (mCalls.contains(call)) {
             mCalls.remove(call);
             shouldNotify = true;
+            cleanUpHandoffCallsForOriginalCall(call);
+        } else if (mPendingHandoffCalls.contains(call)) {
+            Log.v(this, "silently removing handoff call %s", call);
+            mPendingHandoffCalls.remove(call);
         }
 
         // Only broadcast changes for calls that are being tracked.
@@ -512,4 +572,50 @@
             }
         }
     }
+
+    private void completeHandoff(Call handoffCall) {
+        Call originalCall = handoffCall.getOriginalCall();
+        Log.v(this, "complete handoff, %s -> %s", handoffCall, originalCall);
+
+        // Disconnect.
+        originalCall.disconnect();
+
+        // Synchronize.
+        originalCall.setCallService(handoffCall.getCallService());
+        setCallState(originalCall, handoffCall.getState());
+
+        // Remove the transient handoff call object (don't disconnect because the call is still
+        // live).
+        removeCall(handoffCall);
+
+        // Force the foreground call changed notification to be sent.
+        for (CallsManagerListener listener : mListeners) {
+            listener.onForegroundCallChanged(mForegroundCall, mForegroundCall);
+        }
+    }
+
+    /** Makes sure there are no dangling handoff calls. */
+    private void cleanUpHandoffCallsForOriginalCall(Call originalCall) {
+        for (Call handoffCall : ImmutableList.copyOf((mPendingHandoffCalls))) {
+            if (handoffCall.getOriginalCall() == originalCall) {
+                Log.d(this, "cancelling handoff call %s for originalCall: %s", handoffCall,
+                        originalCall);
+                if (handoffCall.getState() == CallState.NEW) {
+                    handoffCall.abort();
+                    handoffCall.setState(CallState.ABORTED);
+                } else {
+                    handoffCall.disconnect();
+                    handoffCall.setState(CallState.DISCONNECTED);
+                }
+                removeCall(handoffCall);
+            }
+        }
+    }
+
+    private static boolean areUriEqual(Uri handle1, Uri handle2) {
+        if (handle1 == null) {
+            return handle2 == null;
+        }
+        return handle1.equals(handle2);
+    }
 }
diff --git a/src/com/android/telecomm/CallsManagerListenerBase.java b/src/com/android/telecomm/CallsManagerListenerBase.java
index 04b78fe..6144651 100644
--- a/src/com/android/telecomm/CallsManagerListenerBase.java
+++ b/src/com/android/telecomm/CallsManagerListenerBase.java
@@ -16,6 +16,7 @@
 
 package com.android.telecomm;
 
+import android.net.Uri;
 import android.telecomm.CallAudioState;
 import android.telecomm.CallState;
 
@@ -44,6 +45,10 @@
     }
 
     @Override
+    public void onCallHandoffHandleChanged(Call call, Uri oldHandle, Uri newHandle) {
+    }
+
+    @Override
     public void onForegroundCallChanged(Call oldForegroundCall, Call newForegroundCall) {
     }
 
diff --git a/src/com/android/telecomm/EmergencyCallServiceSelector.java b/src/com/android/telecomm/EmergencyCallServiceSelector.java
new file mode 100644
index 0000000..18a5b15
--- /dev/null
+++ b/src/com/android/telecomm/EmergencyCallServiceSelector.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 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.telecomm;
+
+import android.net.Uri;
+import android.telecomm.CallInfo;
+import android.telecomm.CallServiceDescriptor;
+import android.telecomm.CallServiceSelector;
+import android.telecomm.CallServiceSelectorAdapter;
+import android.telephony.PhoneNumberUtils;
+
+import com.google.common.collect.Lists;
+
+import java.util.List;
+
+/**
+ * Selects call-services which can place emergency calls. For emergency handles, this selector is
+ * always the first selector attempted when placing a call, see
+ * {@link Switchboard#processNewOutgoingCall}. If this is ever invoked for a non-emergency handle,
+ * this selector returns zero call services so that call-service selection continues to the next
+ * selector. For emergency calls, it selects telephony's PSTN call services so that they are the
+ * first ones to try to place the call.
+ */
+public class EmergencyCallServiceSelector extends CallServiceSelector {
+
+    /**
+     * Returns true if the handle passed in is to a potential emergency number.
+     */
+    static boolean shouldUseSelector(Uri handle) {
+        return PhoneNumberUtils.isPotentialLocalEmergencyNumber(
+                handle.getSchemeSpecificPart(), TelecommApp.getInstance());
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void select(CallInfo callInfo, List<CallServiceDescriptor> descriptors) {
+        List<CallServiceDescriptor> selectedDescriptors = Lists.newArrayList();
+
+        // We check to see if the handle is potentially for an emergency call. *Potentially* means
+        // that we match both the full number and prefix (e.g., "911444" matches 911). After the
+        // call is made, the telephony call services will inform us of the actual number that was
+        // connected, which would be 911. This is why we check *potential* APIs before, but use the
+        // exact {@link PhoneNumberUtils#isLocalEmergencyNumber} once the call is connected.
+        if (shouldUseSelector(callInfo.getHandle())) {
+            // Search for and return the pstn call services if found.
+            for (CallServiceDescriptor descriptor : descriptors) {
+                // TODO(santoscordon): Consider adding some type of CAN_MAKE_EMERGENCY_CALLS
+                // capability for call services and relying on that. Also consider combining this
+                // with a permission so that we can check that instead of relying on hardcoded
+                // paths.
+                if (TelephonyUtil.isPstnCallService(descriptor)) {
+                    selectedDescriptors.add(descriptor);
+                }
+            }
+        }
+
+        getAdapter().setSelectedCallServices(callInfo.getId(), selectedDescriptors);
+    }
+}
diff --git a/src/com/android/telecomm/InCallAdapter.java b/src/com/android/telecomm/InCallAdapter.java
index 14669c6..da7cf18 100644
--- a/src/com/android/telecomm/InCallAdapter.java
+++ b/src/com/android/telecomm/InCallAdapter.java
@@ -35,8 +35,9 @@
     private static final int MSG_DISCONNECT_CALL = 5;
     private static final int MSG_HOLD_CALL = 6;
     private static final int MSG_UNHOLD_CALL = 7;
-    private static final int MSG_MUTE = 8;
-    private static final int MSG_SET_AUDIO_ROUTE = 9;
+    private static final int MSG_HANDOFF_CALL = 8;
+    private static final int MSG_MUTE = 9;
+    private static final int MSG_SET_AUDIO_ROUTE = 10;
 
     private final class InCallAdapterHandler extends Handler {
         @Override
@@ -75,6 +76,9 @@
                 case MSG_UNHOLD_CALL:
                     mCallsManager.unholdCall(call);
                     break;
+                case MSG_HANDOFF_CALL:
+                    mCallsManager.startHandoffForCall(call);
+                    break;
                 case MSG_MUTE:
                     mCallsManager.mute(msg.arg1 == 1 ? true : false);
                     break;
@@ -160,6 +164,13 @@
 
     /** {@inheritDoc} */
     @Override
+    public void handoffCall(String callId) {
+        mCallIdMapper.checkValidCallId(callId);
+        mHandler.obtainMessage(MSG_HANDOFF_CALL, callId).sendToTarget();
+    }
+
+    /** {@inheritDoc} */
+    @Override
     public void mute(boolean shouldMute) {
         mHandler.obtainMessage(MSG_MUTE, shouldMute ? 1 : 0, 0).sendToTarget();
     }
diff --git a/src/com/android/telecomm/InCallController.java b/src/com/android/telecomm/InCallController.java
index d975b0b..beac0d5 100644
--- a/src/com/android/telecomm/InCallController.java
+++ b/src/com/android/telecomm/InCallController.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
+import android.net.Uri;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.telecomm.CallAudioState;
@@ -136,6 +137,17 @@
     }
 
     @Override
+    public void onCallHandoffHandleChanged(Call call, Uri oldHandle, Uri newHandle) {
+        if (mInCallService != null) {
+            try {
+                mInCallService.setHandoffEnabled(mCallIdMapper.getCallId(call), newHandle != null);
+            } catch (RemoteException e) {
+                Log.e(this, e, "Exception attempting to call setHandoffEnabled.");
+            }
+        }
+    }
+
+    @Override
     public void onAudioStateChanged(CallAudioState oldAudioState, CallAudioState newAudioState) {
         if (mInCallService != null) {
             Log.i(this, "Calling onAudioStateChanged, audioState: %s -> %s", oldAudioState,
@@ -207,6 +219,7 @@
         if (!calls.isEmpty()) {
             for (Call call : calls) {
                 onCallAdded(call);
+                onCallHandoffHandleChanged(call, null, call.getHandoffHandle());
             }
             onAudioStateChanged(null, CallsManager.getInstance().getAudioState());
         } else {
diff --git a/src/com/android/telecomm/Switchboard.java b/src/com/android/telecomm/Switchboard.java
index 77c3c16..c2df63e 100644
--- a/src/com/android/telecomm/Switchboard.java
+++ b/src/com/android/telecomm/Switchboard.java
@@ -16,12 +16,7 @@
 
 package com.android.telecomm;
 
-import com.google.common.base.Preconditions;
-import com.google.common.collect.ImmutableCollection;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
-
+import android.content.ComponentName;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
@@ -29,18 +24,21 @@
 import android.telecomm.CallServiceDescriptor;
 import android.telecomm.TelecommConstants;
 
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableCollection;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Sets;
+
+import java.util.Collection;
 import java.util.Iterator;
-import java.util.List;
 import java.util.Set;
 
 /**
  * Switchboard is responsible for:
- * <ul>
- * <li> gathering the {@link CallServiceWrapper}s and {@link CallServiceSelectorWrapper}s through which to place
- * outgoing calls
- * <li> starting outgoing calls (via {@link OutgoingCallsManager}
- * <li> switching active calls between call services.
- * </ul>
+ * - gathering the {@link CallServiceWrapper}s and {@link CallServiceSelectorWrapper}s through
+ *       which to place outgoing calls
+ * - starting outgoing calls (via {@link OutgoingCallsManager}
+ * - switching active calls between call services.
  */
 final class Switchboard {
 
@@ -149,7 +147,7 @@
      * @param call The call object.
      * @param descriptor The relevant call-service descriptor.
      * @param extras The optional extras passed via
-     *     {@link TelecommConstants#EXTRA_INCOMING_CALL_EXTRAS}
+     *         {@link TelecommConstants#EXTRA_INCOMING_CALL_EXTRAS}
      */
     void retrieveIncomingCall(Call call, CallServiceDescriptor descriptor, Bundle extras) {
         Log.d(this, "retrieveIncomingCall");
@@ -295,7 +293,40 @@
      * @param call The call to place.
      */
     private void processNewOutgoingCall(Call call) {
-        mOutgoingCallsManager.placeCall(call, mCallServices, mSelectors);
+        Collection<CallServiceSelectorWrapper> selectors;
+
+        // Use the call's selector if it's already tied to one. This is the case for handoff calls.
+        if (call.getCallServiceSelector() != null) {
+            selectors = ImmutableList.of(call.getCallServiceSelector());
+        } else {
+            selectors = mSelectors;
+        }
+
+        boolean useEmergencySelector =
+                EmergencyCallServiceSelector.shouldUseSelector(call.getHandle());
+        Log.d(this, "processNewOutgoingCall, isEmergency=%b", useEmergencySelector);
+
+        if (useEmergencySelector) {
+            // This is potentially an emergency call so add the emergency selector before the
+            // other selectors.
+            ImmutableList.Builder<CallServiceSelectorWrapper> selectorsBuilder =
+                    ImmutableList.builder();
+
+            ComponentName componentName = new ComponentName(
+                    TelecommApp.getInstance(), EmergencyCallServiceSelector.class);
+            CallServiceSelectorWrapper emergencySelector =
+                    new CallServiceSelectorWrapper(
+                            componentName.flattenToShortString(),
+                            componentName,
+                            mCallsManager,
+                            mOutgoingCallsManager);
+
+            selectorsBuilder.add(emergencySelector);
+            selectorsBuilder.addAll(selectors);
+            selectors = selectorsBuilder.build();
+        }
+
+        mOutgoingCallsManager.placeCall(call, mCallServices, selectors);
     }
 
     /**
diff --git a/src/com/android/telecomm/TelephonyUtil.java b/src/com/android/telecomm/TelephonyUtil.java
index 7a9ac22..be501f0 100644
--- a/src/com/android/telecomm/TelephonyUtil.java
+++ b/src/com/android/telecomm/TelephonyUtil.java
@@ -43,6 +43,17 @@
         return selector.getComponentName().getPackageName().equals(TELEPHONY_PACKAGE_NAME);
     }
 
+    static boolean isPstnCallService(CallServiceDescriptor descriptor) {
+        ComponentName componentName = descriptor.getServiceComponent();
+        if (TELEPHONY_PACKAGE_NAME.equals(componentName.getPackageName())) {
+            String className = componentName.getClassName();
+            return GSM_CALL_SERVICE_CLASS_NAME.equals(className) ||
+                    CDMA_CALL_SERVICE_CLASS_NAME.equals(className);
+        }
+
+        return false;
+    }
+
     /**
      * Returns whether or not the call is currently connected as a cellular call (through the
      * device's cellular radio).
@@ -57,10 +68,7 @@
         if (callService == null) {
             return false;
         }
-        CallServiceDescriptor descriptor = callService.getDescriptor();
-        String className = descriptor.getServiceComponent().getClassName();
-        return className.equals(GSM_CALL_SERVICE_CLASS_NAME) ||
-                className.equals(CDMA_CALL_SERVICE_CLASS_NAME);
+        return isPstnCallService(callService.getDescriptor());
     }
 
     private static void verifyCallServiceExists(String serviceName) {
diff --git a/tests/src/com/android/telecomm/testcallservice/DummyCallServiceSelector.java b/tests/src/com/android/telecomm/testcallservice/DummyCallServiceSelector.java
index f759291..c267867 100644
--- a/tests/src/com/android/telecomm/testcallservice/DummyCallServiceSelector.java
+++ b/tests/src/com/android/telecomm/testcallservice/DummyCallServiceSelector.java
@@ -30,12 +30,6 @@
  * was given. Also returns false for every request on switchability.
  */
 public class DummyCallServiceSelector extends CallServiceSelector {
-    private CallServiceSelectorAdapter mAdapter;
-
-    @Override
-    protected void setCallServiceSelectorAdapter(CallServiceSelectorAdapter adapter) {
-        mAdapter = adapter;
-    }
 
     @Override
     protected void select(CallInfo callInfo, List<CallServiceDescriptor> descriptors) {
@@ -49,6 +43,6 @@
             }
         }
 
-        mAdapter.setSelectedCallServices(callInfo.getId(), orderedList);
+        getAdapter().setSelectedCallServices(callInfo.getId(), orderedList);
     }
 }
diff --git a/tests/src/com/android/telecomm/testcallservice/TestCallService.java b/tests/src/com/android/telecomm/testcallservice/TestCallService.java
index 4e459a3..f6e2da7 100644
--- a/tests/src/com/android/telecomm/testcallservice/TestCallService.java
+++ b/tests/src/com/android/telecomm/testcallservice/TestCallService.java
@@ -49,21 +49,15 @@
     private Set<String> mLiveCallIds;
 
     /**
-     * Adapter to call back into CallsManager.
-     */
-    private CallServiceAdapter mTelecommAdapter;
-
-    /**
      * Used to play an audio tone during a call.
      */
     private MediaPlayer mMediaPlayer;
 
     /** {@inheritDoc} */
     @Override
-    public void setCallServiceAdapter(CallServiceAdapter callServiceAdapter) {
+    public void onAdapterAttached(CallServiceAdapter callServiceAdapter) {
         Log.i(TAG, "setCallServiceAdapter()");
 
-        mTelecommAdapter = callServiceAdapter;
         mLiveCallIds = Sets.newHashSet();
 
         mMediaPlayer = createMediaPlayer();
@@ -87,7 +81,7 @@
         // Returning positively on setCompatibleWith() doesn't guarantee that we will be chosen
         // to place the call. If we *are* chosen then CallsManager will execute the call()
         // method below.
-        mTelecommAdapter.setIsCompatibleWith(callInfo.getId(), isCompatible);
+        getAdapter().setIsCompatibleWith(callInfo.getId(), isCompatible);
     }
 
     /**
@@ -107,7 +101,7 @@
         }
 
         createCall(callInfo.getId());
-        mTelecommAdapter.handleSuccessfulOutgoingCall(callInfo.getId());
+        getAdapter().handleSuccessfulOutgoingCall(callInfo.getId());
     }
 
     /** {@inheritDoc} */
@@ -126,20 +120,20 @@
         Uri handle = Uri.fromParts("tel", "5551234", null);
 
         CallInfo callInfo = new CallInfo(callId, CallState.RINGING, handle);
-        mTelecommAdapter.notifyIncomingCall(callInfo);
+        getAdapter().notifyIncomingCall(callInfo);
     }
 
     /** {@inheritDoc} */
     @Override
     public void answer(String callId) {
-        mTelecommAdapter.setActive(callId);
+        getAdapter().setActive(callId);
         createCall(callId);
     }
 
     /** {@inheritDoc} */
     @Override
     public void reject(String callId) {
-        mTelecommAdapter.setDisconnected(callId, DisconnectCause.INCOMING_REJECTED, null);
+        getAdapter().setDisconnected(callId, DisconnectCause.INCOMING_REJECTED, null);
     }
 
     /** {@inheritDoc} */
@@ -148,21 +142,21 @@
         Log.i(TAG, "disconnect(" + callId + ")");
 
         destroyCall(callId);
-        mTelecommAdapter.setDisconnected(callId, DisconnectCause.LOCAL, null);
+        getAdapter().setDisconnected(callId, DisconnectCause.LOCAL, null);
     }
 
     /** {@inheritDoc} */
     @Override
     public void hold(String callId) {
         Log.i(TAG, "hold(" + callId + ")");
-        mTelecommAdapter.setOnHold(callId);
+        getAdapter().setOnHold(callId);
     }
 
     /** {@inheritDoc} */
     @Override
     public void unhold(String callId) {
         Log.i(TAG, "unhold(" + callId + ")");
-        mTelecommAdapter.setActive(callId);
+        getAdapter().setActive(callId);
     }
 
     /** {@inheritDoc} */