Merge "Fix for call switching." into lmp-dev
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 1a4fae7..43a1f5e 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -33,6 +33,9 @@
     <uses-permission android:name="android.permission.STOP_APP_SWITCHES" />
     <uses-permission android:name="android.permission.VIBRATE" />
     <uses-permission android:name="android.permission.WRITE_CALL_LOG" />
+    <uses-permission android:name="android.permission.BIND_CONNECTION_SERVICE" />
+    <uses-permission android:name="android.permission.BIND_INCALL_SERVICE" />
+
     <!-- Protects the ability to register any PhoneAccount with a capability flags of either
          PhoneAccount#CAPABILITY_CALL_PROVIDER or PhoneAccount#CAPABILITY_SIM_SUBSCRIPTION. -->
     <permission
diff --git a/res/values-my-rMM/strings.xml b/res/values-my-rMM/strings.xml
index 6221c1d..cda9091 100644
--- a/res/values-my-rMM/strings.xml
+++ b/res/values-my-rMM/strings.xml
@@ -36,9 +36,9 @@
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"အမြန်တုံ့ပြန်ချက်"</string>
     <string name="respond_via_sms_menu_reset_default_activity" msgid="1461742052902053466">"ပုံသေ အပ်ပလီကေးရှင်းအား ပြန်ပြောင်းရန်"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"<xliff:g id="PHONE_NUMBER">%s</xliff:g> ထံ စာတိုပို့လိုက်ပါပြီ"</string>
-    <string name="phone_account_preferences_title" msgid="3932310998135189661">"ဖုန်း အကောင့် ဦးစားပေးချက်များ"</string>
-    <string name="default_outgoing_account_title" msgid="8261079649574578970">"ပုံသေ အထွက် အကောင့်"</string>
-    <string name="sim_call_manager_account" msgid="2559930293628077755">"ကြိုးမဲ့ ခေါ်ဆိုမှု အကောင့်"</string>
+    <string name="phone_account_preferences_title" msgid="3932310998135189661">"ဖုန်း အကောင့် ဦးစားပေးချက်များ"</string>
+    <string name="default_outgoing_account_title" msgid="8261079649574578970">"ပုံသေ အထွက် အကောင့်"</string>
+    <string name="sim_call_manager_account" msgid="2559930293628077755">"ကြိုးမဲ့ ခေါ်ဆိုမှု အကောင့်"</string>
     <string name="account_ask_every_time" msgid="944077828070287407">"အကြိမ်တိုင်းမှာ မေးရန်"</string>
-    <string name="do_not_use_sim_call_manager" msgid="5519252524007323694">"ကြိုးမဲ့ ခေါ်ဆိုမှုကို မသုံးပါနှင့်"</string>
+    <string name="do_not_use_sim_call_manager" msgid="5519252524007323694">"ကြိုးမဲ့ ခေါ်ဆိုမှုကို မသုံးပါနှင့်"</string>
 </resources>
diff --git a/res/values/colors.xml b/res/values/colors.xml
new file mode 100644
index 0000000..e93e855
--- /dev/null
+++ b/res/values/colors.xml
@@ -0,0 +1,19 @@
+<!--
+  ~ 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
+  -->
+
+<resources>
+    <color name="theme_color">#0288d1</color>
+</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index f2f80ee..981c131 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -91,6 +91,10 @@
     <!-- Indication to not use a SIM call manager -->
     <string name="do_not_use_sim_call_manager">Do not use Wi-Fi calling</string>
 
+    <!-- Message indicating that the user is not allowed to make non-emergency outgoing phone calls
+         due to a user restriction -->
+    <string name="outgoing_call_not_allowed">This user is not allowed to make non-emergency phone calls</string>
+
     <!-- DO NOT TRANSLATE. Label for test Subscription 0. -->
     <string name="test_account_0_label">Q Mobile</string>
     <!-- DO NOT TRANSLATE. Label for test Subscription 1. -->
diff --git a/src/com/android/telecomm/Call.java b/src/com/android/telecomm/Call.java
index 6d3da71..7718df6 100644
--- a/src/com/android/telecomm/Call.java
+++ b/src/com/android/telecomm/Call.java
@@ -26,7 +26,6 @@
 import android.telecomm.PhoneCapabilities;
 import android.telecomm.PropertyPresentation;
 import android.telecomm.CallState;
-import android.telecomm.ConnectionRequest;
 import android.telecomm.GatewayInfo;
 import android.telecomm.ParcelableConnection;
 import android.telecomm.PhoneAccount;
@@ -53,7 +52,7 @@
 import java.util.Locale;
 import java.util.Objects;
 import java.util.Set;
-import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.concurrent.ConcurrentHashMap;
 
 /**
  *  Encapsulates all aspects of a given phone call throughout its lifecycle, starting
@@ -67,14 +66,11 @@
     interface Listener {
         void onSuccessfulOutgoingCall(Call call, int callState);
         void onFailedOutgoingCall(Call call, int errorCode, String errorMsg);
-        void onCancelledOutgoingCall(Call call);
         void onSuccessfulIncomingCall(Call call);
         void onFailedIncomingCall(Call call);
         void onRequestingRingback(Call call, boolean requestingRingback);
         void onPostDialWait(Call call, String remaining);
         void onCallCapabilitiesChanged(Call call);
-        void onExpiredConferenceCall(Call call);
-        void onConfirmedConferenceCall(Call call);
         void onParentChanged(Call call);
         void onChildrenChanged(Call call);
         void onCannedSmsResponsesLoaded(Call call);
@@ -98,8 +94,6 @@
         @Override
         public void onFailedOutgoingCall(Call call, int errorCode, String errorMsg) {}
         @Override
-        public void onCancelledOutgoingCall(Call call) {}
-        @Override
         public void onSuccessfulIncomingCall(Call call) {}
         @Override
         public void onFailedIncomingCall(Call call) {}
@@ -110,10 +104,6 @@
         @Override
         public void onCallCapabilitiesChanged(Call call) {}
         @Override
-        public void onExpiredConferenceCall(Call call) {}
-        @Override
-        public void onConfirmedConferenceCall(Call call) {}
-        @Override
         public void onParentChanged(Call call) {}
         @Override
         public void onChildrenChanged(Call call) {}
@@ -248,8 +238,14 @@
     /** Info used by the connection services. */
     private Bundle mExtras = Bundle.EMPTY;
 
-    /** Set of listeners on this call. */
-    private Set<Listener> mListeners = new CopyOnWriteArraySet<>();
+    /** Set of listeners on this call.
+     *
+     * ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is
+     * load factor before resizing, 1 means we only expect a single thread to
+     * access the map so make only a single shard
+     */
+    private final Set<Listener> mListeners = Collections.newSetFromMap(
+            new ConcurrentHashMap<Listener, Boolean>(8, 0.9f, 1));
 
     private CreateConnectionProcessor mCreateConnectionProcessor;
 
@@ -322,7 +318,9 @@
     }
 
     void removeListener(Listener listener) {
-        mListeners.remove(listener);
+        if (listener != null) {
+            mListeners.remove(listener);
+        }
     }
 
     /** {@inheritDoc} */
@@ -338,15 +336,7 @@
     }
 
     int getState() {
-        if (mIsConference) {
-            if (!mChildCalls.isEmpty()) {
-                // If we have child calls, just return the child call.
-                return mChildCalls.get(0).getState();
-            }
-            return CallState.ACTIVE;
-        } else {
-            return mState;
-        }
+        return mState;
     }
 
     /**
@@ -618,15 +608,14 @@
     }
 
     @Override
-    public void handleCreateConnectionSuccessful(
-            ConnectionRequest request, ParcelableConnection connection) {
+    public void handleCreateConnectionSuccess(ParcelableConnection connection) {
         Log.v(this, "handleCreateConnectionSuccessful %s", connection);
         mCreateConnectionProcessor = null;
         setTargetPhoneAccount(connection.getPhoneAccount());
         setHandle(connection.getHandle(), connection.getHandlePresentation());
         setCallerDisplayName(
                 connection.getCallerDisplayName(), connection.getCallerDisplayNamePresentation());
-
+        setCallCapabilities(connection.getCapabilities());
         setVideoProvider(connection.getVideoProvider());
         setVideoState(connection.getVideoState());
         setRequestingRingback(connection.isRequestingRingback());
@@ -652,43 +641,20 @@
     }
 
     @Override
-    public void handleCreateConnectionFailed(int code, String msg) {
+    public void handleCreateConnectionFailure(int code, String msg) {
         mCreateConnectionProcessor = null;
-        if (mIsIncoming) {
-            clearConnectionService();
-            setDisconnectCause(code, null);
-            setState(CallState.DISCONNECTED);
+        clearConnectionService();
+        setDisconnectCause(code, msg);
+        CallsManager.getInstance().markCallAsDisconnected(this, code, msg);
 
-            Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]);
-            for (int i = 0; i < listeners.length; i++) {
-                listeners[i].onFailedIncomingCall(this);
+        if (mIsIncoming) {
+            for (Listener listener : mListeners) {
+                listener.onFailedIncomingCall(this);
             }
         } else {
-            Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]);
-            for (int i = 0; i < listeners.length; i++) {
-                listeners[i].onFailedOutgoingCall(this, code, msg);
+            for (Listener listener : mListeners) {
+                listener.onFailedOutgoingCall(this, code, msg);
             }
-            clearConnectionService();
-        }
-    }
-
-    @Override
-    public void handleCreateConnectionCancelled() {
-        mCreateConnectionProcessor = null;
-        if (mIsIncoming) {
-            clearConnectionService();
-            setDisconnectCause(DisconnectCause.OUTGOING_CANCELED, null);
-
-            Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]);
-            for (int i = 0; i < listeners.length; i++) {
-                listeners[i].onFailedIncomingCall(this);
-            }
-        } else {
-            Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]);
-            for (int i = 0; i < listeners.length; i++) {
-                listeners[i].onCancelledOutgoingCall(this);
-            }
-            clearConnectionService();
         }
     }
 
@@ -742,8 +708,11 @@
     void abort() {
         if (mCreateConnectionProcessor != null) {
             mCreateConnectionProcessor.abort();
-        } else if (mState == CallState.PRE_DIAL_WAIT) {
-            handleCreateConnectionFailed(DisconnectCause.LOCAL, null);
+        } else if (mState == CallState.NEW || mState == CallState.PRE_DIAL_WAIT
+                || mState == CallState.CONNECTING) {
+            handleCreateConnectionFailure(DisconnectCause.OUTGOING_CANCELED, null);
+        } else {
+            Log.v(this, "Cannot abort a call which isn't either PRE_DIAL_WAIT or CONNECTING");
         }
     }
 
@@ -847,27 +816,11 @@
         mConnectionService.onPhoneAccountClicked(this);
     }
 
-    void conferenceInto(Call conferenceCall) {
+    void conferenceWith(Call otherCall) {
         if (mConnectionService == null) {
             Log.w(this, "conference requested on a call without a connection service.");
         } else {
-            mConnectionService.conference(conferenceCall, this);
-        }
-    }
-
-    void expireConference() {
-        // The conference call expired before we got a confirmation of the conference from the
-        // connection service...so start shutting down.
-        clearConnectionService();
-        for (Listener l : mListeners) {
-            l.onExpiredConferenceCall(this);
-        }
-    }
-
-    void confirmConference() {
-        Log.v(this, "confirming Conf call %s", mListeners);
-        for (Listener l : mListeners) {
-            l.onConfirmedConferenceCall(this);
+            mConnectionService.conference(this, otherCall);
         }
     }
 
@@ -880,6 +833,10 @@
             Log.e(this, new Exception(), "setting the parent to self");
             return;
         }
+        if (parentCall == mParentCall) {
+            // nothing to do
+            return;
+        }
         Preconditions.checkState(parentCall == null || mParentCall == null);
 
         Call oldParent = mParentCall;
@@ -1206,8 +1163,10 @@
         }
     }
 
-    private int getStateFromConnectionState(int state) {
+    static int getStateFromConnectionState(int state) {
         switch (state) {
+            case Connection.STATE_INITIALIZING:
+                return CallState.CONNECTING;
             case Connection.STATE_ACTIVE:
                 return CallState.ACTIVE;
             case Connection.STATE_DIALING:
diff --git a/src/com/android/telecomm/CallActivity.java b/src/com/android/telecomm/CallActivity.java
index 06cc9cb..89b89e6 100644
--- a/src/com/android/telecomm/CallActivity.java
+++ b/src/com/android/telecomm/CallActivity.java
@@ -23,11 +23,12 @@
 import android.content.res.Configuration;
 import android.net.Uri;
 import android.os.Bundle;
+import android.os.UserManager;
 import android.telecomm.PhoneAccountHandle;
 import android.telecomm.TelecommManager;
 import android.telephony.PhoneNumberUtils;
-import android.telephony.TelephonyManager;
 import android.text.TextUtils;
+import android.widget.Toast;
 
 /**
  * Activity that handles system CALL actions and forwards them to {@link CallsManager}.
@@ -116,10 +117,22 @@
      * @param intent Call intent containing data about the handle to call.
      */
     private void processOutgoingCallIntent(Intent intent) {
-
         String uriString = intent.getData().getSchemeSpecificPart();
         Uri handle = Uri.fromParts(
                 PhoneNumberUtils.isUriNumber(uriString) ? "sip" : "tel", uriString, null);
+
+        UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
+        if (userManager.hasUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS)
+                && !TelephonyUtil.shouldProcessAsEmergency(this, handle)) {
+            // Only emergency calls are allowed for users with the DISALLOW_OUTGOING_CALLS
+            // restriction.
+            Toast.makeText(this, getResources().getString(R.string.outgoing_call_not_allowed),
+                    Toast.LENGTH_SHORT).show();
+            Log.d(this, "Rejecting non-emergency phone call due to DISALLOW_OUTGOING_CALLS "
+                    + "restriction");
+            return;
+        }
+
         PhoneAccountHandle phoneAccountHandle = intent.getParcelableExtra(
                 TelecommManager.EXTRA_PHONE_ACCOUNT_HANDLE);
 
diff --git a/src/com/android/telecomm/CallIdMapper.java b/src/com/android/telecomm/CallIdMapper.java
index f5055da..b82d1e1 100644
--- a/src/com/android/telecomm/CallIdMapper.java
+++ b/src/com/android/telecomm/CallIdMapper.java
@@ -78,7 +78,7 @@
         if (objId instanceof String) {
             callId = (String) objId;
         }
-        if (!isValidCallId(callId)) {
+        if (!isValidCallId(callId) && !isValidConferenceId(callId)) {
             return null;
         }
 
@@ -94,6 +94,10 @@
         return callId != null && callId.startsWith(mCallIdPrefix);
     }
 
+    boolean isValidConferenceId(String callId) {
+        return callId != null;
+    }
+
     String getNewId() {
         sIdCount++;
         return mCallIdPrefix + sIdCount;
diff --git a/src/com/android/telecomm/CallLogManager.java b/src/com/android/telecomm/CallLogManager.java
index d0668b9..ee65255 100644
--- a/src/com/android/telecomm/CallLogManager.java
+++ b/src/com/android/telecomm/CallLogManager.java
@@ -23,6 +23,7 @@
 import android.telecomm.CallState;
 import android.telecomm.PhoneAccountHandle;
 import android.telecomm.VideoProfile;
+import android.telephony.DisconnectCause;
 import android.telephony.PhoneNumberUtils;
 
 import com.android.internal.telephony.CallerInfo;
@@ -88,8 +89,9 @@
 
     @Override
     public void onCallStateChanged(Call call, int oldState, int newState) {
-        if ((newState == CallState.DISCONNECTED || newState == CallState.ABORTED) &&
-                oldState != CallState.PRE_DIAL_WAIT) {
+        if ((newState == CallState.DISCONNECTED || newState == CallState.ABORTED)
+                && oldState != CallState.PRE_DIAL_WAIT
+                && call.getDisconnectCause() != DisconnectCause.OUTGOING_CANCELED) {
             int type;
             if (!call.isIncoming()) {
                 type = Calls.OUTGOING_TYPE;
diff --git a/src/com/android/telecomm/CallsManager.java b/src/com/android/telecomm/CallsManager.java
index 7ab48db..4e3a0cb 100644
--- a/src/com/android/telecomm/CallsManager.java
+++ b/src/com/android/telecomm/CallsManager.java
@@ -21,16 +21,17 @@
 import android.telecomm.AudioState;
 import android.telecomm.CallState;
 import android.telecomm.GatewayInfo;
+import android.telecomm.ParcelableConference;
 import android.telecomm.PhoneAccountHandle;
 import android.telephony.DisconnectCause;
 
 import com.google.common.collect.ImmutableCollection;
 import com.google.common.collect.ImmutableList;
 
-import java.util.HashSet;
+import java.util.Collections;
 import java.util.List;
 import java.util.Set;
-import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.concurrent.ConcurrentHashMap;
 
 /**
  * Singleton.
@@ -65,8 +66,13 @@
     /**
      * The main call repository. Keeps an instance of all live calls. New incoming and outgoing
      * calls are added to the map and removed when the calls move to the disconnected state.
+    *
+     * ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is
+     * load factor before resizing, 1 means we only expect a single thread to
+     * access the map so make only a single shard
      */
-    private final Set<Call> mCalls = new CopyOnWriteArraySet<Call>();
+    private final Set<Call> mCalls = Collections.newSetFromMap(
+            new ConcurrentHashMap<Call, Boolean>(8, 0.9f, 1));
 
     private final ConnectionServiceRepository mConnectionServiceRepository =
             new ConnectionServiceRepository();
@@ -74,7 +80,10 @@
     private final InCallController mInCallController = new InCallController();
     private final CallAudioManager mCallAudioManager;
     private final Ringer mRinger;
-    private final Set<CallsManagerListener> mListeners = new HashSet<>();
+    // For this set initial table size to 16 because we add 13 listeners in
+    // the CallsManager constructor.
+    private final Set<CallsManagerListener> mListeners = Collections.newSetFromMap(
+            new ConcurrentHashMap<CallsManagerListener, Boolean>(16, 0.9f, 1));
     private final HeadsetMediaButton mHeadsetMediaButton;
     private final WiredHeadsetManager mWiredHeadsetManager;
     private final TtyManager mTtyManager;
@@ -149,13 +158,6 @@
     }
 
     @Override
-    public void onCancelledOutgoingCall(Call call) {
-        Log.v(this, "onCancelledOutgoingCall, call: %s", call);
-        setCallState(call, CallState.ABORTED);
-        removeCall(call);
-    }
-
-    @Override
     public void onSuccessfulIncomingCall(Call call) {
         Log.d(this, "onSuccessfulIncomingCall");
         setCallState(call, CallState.RINGING);
@@ -181,20 +183,6 @@
     }
 
     @Override
-    public void onExpiredConferenceCall(Call call) {
-        call.removeListener(this);
-    }
-
-    @Override
-    public void onConfirmedConferenceCall(Call call) {
-        addCall(call);
-        Log.v(this, "confirming Conf call %s", call);
-        for (CallsManagerListener listener : mListeners) {
-            listener.onIsConferencedChanged(call);
-        }
-    }
-
-    @Override
     public void onParentChanged(Call call) {
         for (CallsManagerListener listener : mListeners) {
             listener.onIsConferencedChanged(call);
@@ -262,8 +250,8 @@
     /**
      * Starts the process to attach the call to a connection service.
      *
-     * @param phoneAccountHandle The phone account which contains the component name of the connection
-     *                     service to use for this call.
+     * @param phoneAccountHandle The phone account which contains the component name of the
+     *        connection service to use for this call.
      * @param extras The optional extras Bundle passed with the intent used for the incoming call.
      */
     void processIncomingCallIntent(PhoneAccountHandle phoneAccountHandle, Bundle extras) {
@@ -285,7 +273,6 @@
         call.startCreateConnection();
     }
 
-
     /**
      * Kicks off the first steps to creating an outgoing call so that InCallUI can launch.
      *
@@ -293,10 +280,9 @@
      * placeOutgoingCall directly.
      *
      * @param handle Handle to connect the call with.
-     * @param phoneAccountHandle The phone account which contains the component name of the connection
-     *                     service to use for this call.
-     * @param extras The optional extras Bundle passed with the intent used for the outgoing call.
-     *
+     * @param phoneAccountHandle The phone account which contains the component name of the
+     *        connection service to use for this call.
+     * @param extras The optional extras Bundle passed with the intent used for the incoming call.
      */
     Call startOutgoingCall(Uri handle, PhoneAccountHandle phoneAccountHandle, Bundle extras) {
         // We only allow a single outgoing call at any given time. Before placing a call, make sure
@@ -308,6 +294,27 @@
             return null;
         }
 
+        TelecommApp app = TelecommApp.getInstance();
+
+        // Only dial with the requested phoneAccount if it is still valid. Otherwise treat this call
+        // as if a phoneAccount was not specified (does the default behavior instead).
+        if (phoneAccountHandle != null) {
+            List<PhoneAccountHandle> enabledAccounts =
+                    app.getPhoneAccountRegistrar().getOutgoingPhoneAccounts();
+            if (!enabledAccounts.contains(phoneAccountHandle)) {
+                phoneAccountHandle = null;
+            }
+        }
+
+        if (phoneAccountHandle == null) {
+            // No preset account, check if default exists
+            PhoneAccountHandle defaultAccountHandle =
+                    app.getPhoneAccountRegistrar().getDefaultOutgoingPhoneAccount();
+            if (defaultAccountHandle != null) {
+                phoneAccountHandle = defaultAccountHandle;
+            }
+        }
+
         // Create a call with original handle. The handle may be changed when the call is attached
         // to a connection service, but in most cases will remain the same.
         call = new Call(
@@ -319,7 +326,14 @@
                 false /* isIncoming */,
                 false /* isConference */);
         call.setExtras(extras);
-        call.setState(CallState.CONNECTING);
+
+        final boolean emergencyCall = TelephonyUtil.shouldProcessAsEmergency(app, call.getHandle());
+        if (phoneAccountHandle == null && !emergencyCall) {
+            // This is the state where the user is expected to select an account
+            call.setState(CallState.PRE_DIAL_WAIT);
+        } else {
+            call.setState(CallState.CONNECTING);
+        }
 
         if (!isPotentialMMICode(handle)) {
             addCall(call);
@@ -339,15 +353,14 @@
      * @param speakerphoneOn Whether or not to turn the speakerphone on once the call connects.
      * @param videoState The desired video state for the outgoing call.
      */
-    void placeOutgoingCall(Call call, Uri handle, GatewayInfo gatewayInfo,
-            PhoneAccountHandle accountHandle, boolean speakerphoneOn, int videoState) {
+    void placeOutgoingCall(Call call, Uri handle, GatewayInfo gatewayInfo, boolean speakerphoneOn,
+            int videoState) {
         if (call == null) {
             // don't do anything if the call no longer exists
             Log.i(this, "Canceling unknown call.");
             return;
         }
 
-        TelecommApp app = TelecommApp.getInstance();
         final Uri uriHandle = (gatewayInfo == null) ? handle : gatewayInfo.getGatewayHandle();
 
         if (gatewayInfo == null) {
@@ -357,49 +370,22 @@
                     Log.pii(uriHandle), Log.pii(handle));
         }
 
-        //TODO: phone account is already set in {@link #startOutgoingCall}, refactor so this is
-        // not redundant.
-        //
-        // Only dial with the requested phoneAccount if it is still valid. Otherwise treat this call
-        // as if a phoneAccount was not specified (does the default behavior instead).
-        if (accountHandle != null) {
-            List<PhoneAccountHandle> enabledAccounts =
-                    app.getPhoneAccountRegistrar().getOutgoingPhoneAccounts();
-            if (!enabledAccounts.contains(accountHandle)) {
-                accountHandle = null;
-            }
-        }
-
         call.setHandle(uriHandle);
         call.setGatewayInfo(gatewayInfo);
         call.setStartWithSpeakerphoneOn(speakerphoneOn);
         call.setVideoState(videoState);
 
-        // This block of code will attempt to pre-determine a phone account
+        TelecommApp app = TelecommApp.getInstance();
         final boolean emergencyCall = TelephonyUtil.shouldProcessAsEmergency(app, call.getHandle());
         if (emergencyCall) {
             // Emergency -- CreateConnectionProcessor will choose accounts automatically
             call.setTargetPhoneAccount(null);
-        } else if (accountHandle != null) {
-            // NOTE: this is currently not an option because no feature currently exists to
-            // preset a phone account
-            Log.d(this, "CALL with phone account: " + accountHandle);
-            call.setTargetPhoneAccount(accountHandle);
-        } else {
-            // No preset account, check if default exists
-            PhoneAccountHandle defaultAccountHandle =
-                    app.getPhoneAccountRegistrar().getDefaultOutgoingPhoneAccount();
-            if (defaultAccountHandle != null) {
-                call.setTargetPhoneAccount(defaultAccountHandle);
-            }
         }
 
         if (call.getTargetPhoneAccount() != null || emergencyCall) {
-            // If the account is selected, proceed to place the outgoing call
+            // If the account has been set, proceed to place the outgoing call.
+            // Otherwise the connection will be initiated when the account is set by the user.
             call.startCreateConnection();
-        } else {
-            // This is the state where the user is expected to select an account
-            call.setState(CallState.PRE_DIAL_WAIT);
         }
     }
 
@@ -410,16 +396,7 @@
      * @param otherCall The other call to conference with.
      */
     void conference(Call call, Call otherCall) {
-        Call conferenceCall = new Call(
-                mConnectionServiceRepository,
-                null /* handle */,
-                null /* gatewayInfo */,
-                null /* connectionManagerPhoneAccount */,
-                null /* targetPhoneAccount */,
-                false /* isIncoming */,
-                true /* isConference */);
-        conferenceCall.addListener(this);
-        call.conferenceInto(conferenceCall);
+        call.conferenceWith(otherCall);
     }
 
     /**
@@ -526,6 +503,18 @@
     }
 
     /**
+     * Instructs Telecomm to disconnect all calls.
+     */
+    void disconnectAllCalls() {
+        Log.v(this, "disconnectAllCalls");
+
+        for (Call call : mCalls) {
+            disconnectCall(call);
+        }
+    }
+
+
+    /**
      * Instructs Telecomm to put the specified call on hold. Intended to be invoked by the
      * in-call app through {@link InCallAdapter} for an ongoing call. This is usually triggered by
      * the user hitting the hold button during an active call.
@@ -647,6 +636,13 @@
     }
 
     /**
+     * Removes an existing disconnected call, and notifies the in-call app.
+     */
+    void markCallAsRemoved(Call call) {
+        removeCall(call);
+    }
+
+    /**
      * Cleans up any calls currently associated with the specified connection service when the
      * service binder disconnects unexpectedly.
      *
@@ -654,7 +650,7 @@
      */
     void handleConnectionServiceDeath(ConnectionServiceWrapper service) {
         if (service != null) {
-            for (Call call : ImmutableList.copyOf(mCalls)) {
+            for (Call call : mCalls) {
                 if (call.getConnectionService() == service) {
                     markCallAsDisconnected(call, DisconnectCause.ERROR_UNSPECIFIED, null);
                 }
@@ -742,6 +738,28 @@
         return null;
     }
 
+    Call createConferenceCall(
+            PhoneAccountHandle phoneAccount,
+            ParcelableConference parcelableConference) {
+        Call call = new Call(
+                mConnectionServiceRepository,
+                null /* handle */,
+                null /* gatewayInfo */,
+                null /* connectionManagerPhoneAccount */,
+                phoneAccount,
+                false /* isIncoming */,
+                true /* isConference */);
+
+        setCallState(call, Call.getStateFromConnectionState(parcelableConference.getState()));
+        call.setCallCapabilities(parcelableConference.getCapabilities());
+
+        // TODO: Move this to be a part of addCall()
+        call.addListener(this);
+        addCall(call);
+        return call;
+    }
+
+
     /**
      * Adds the specified call to the main list of live calls.
      *
diff --git a/src/com/android/telecomm/ConnectionServiceRepository.java b/src/com/android/telecomm/ConnectionServiceRepository.java
index ec2c90e..efd1ba4 100644
--- a/src/com/android/telecomm/ConnectionServiceRepository.java
+++ b/src/com/android/telecomm/ConnectionServiceRepository.java
@@ -38,21 +38,6 @@
     ConnectionServiceRepository() {
     }
 
-    Collection<ConnectionServiceWrapper> lookupServices() {
-        PackageManager packageManager = TelecommApp.getInstance().getPackageManager();
-        Intent intent = new Intent(ConnectionService.SERVICE_INTERFACE);
-        ArrayList<ConnectionServiceWrapper> services = new ArrayList<>();
-
-        for (ResolveInfo entry : packageManager.queryIntentServices(intent, 0)) {
-            ServiceInfo serviceInfo = entry.serviceInfo;
-            if (serviceInfo != null) {
-                services.add(getService(new ComponentName(
-                        serviceInfo.packageName, serviceInfo.name)));
-            }
-        }
-        return services;
-    }
-
     ConnectionServiceWrapper getService(ComponentName componentName) {
         ConnectionServiceWrapper service = mServiceCache.get(componentName);
         if (service == null) {
diff --git a/src/com/android/telecomm/ConnectionServiceWrapper.java b/src/com/android/telecomm/ConnectionServiceWrapper.java
index 2d5ade2..264ee63 100644
--- a/src/com/android/telecomm/ConnectionServiceWrapper.java
+++ b/src/com/android/telecomm/ConnectionServiceWrapper.java
@@ -25,9 +25,12 @@
 import android.os.Message;
 import android.os.RemoteException;
 import android.telecomm.AudioState;
+import android.telecomm.Connection;
 import android.telecomm.ConnectionRequest;
 import android.telecomm.ConnectionService;
 import android.telecomm.GatewayInfo;
+
+import android.telecomm.ParcelableConference;
 import android.telecomm.ParcelableConnection;
 import android.telecomm.PhoneAccount;
 import android.telecomm.PhoneAccountHandle;
@@ -44,10 +47,10 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 
 /**
  * Wrapper for {@link IConnectionService}s, handles binding to {@link IConnectionService} and keeps
@@ -56,77 +59,40 @@
  * {@link IConnectionService}.
  */
 final class ConnectionServiceWrapper extends ServiceBinder<IConnectionService> {
-    private static final int MSG_HANDLE_CREATE_CONNECTION_SUCCESSFUL = 1;
-    private static final int MSG_HANDLE_CREATE_CONNECTION_FAILED = 2;
-    private static final int MSG_HANDLE_CREATE_CONNECTION_CANCELLED = 3;
-    private static final int MSG_SET_ACTIVE = 4;
-    private static final int MSG_SET_RINGING = 5;
-    private static final int MSG_SET_DIALING = 6;
-    private static final int MSG_SET_DISCONNECTED = 7;
-    private static final int MSG_SET_ON_HOLD = 8;
-    private static final int MSG_SET_REQUESTING_RINGBACK = 9;
-    private static final int MSG_SET_CALL_CAPABILITIES = 10;
-    private static final int MSG_SET_IS_CONFERENCED = 11;
-    private static final int MSG_ADD_CONFERENCE_CALL = 12;
-    private static final int MSG_REMOVE_CALL = 13;
-    private static final int MSG_ON_POST_DIAL_WAIT = 14;
-    private static final int MSG_QUERY_REMOTE_CALL_SERVICES = 15;
-    private static final int MSG_SET_VIDEO_PROVIDER = 16;
-    private static final int MSG_SET_AUDIO_MODE_IS_VOIP = 17;
-    private static final int MSG_SET_STATUS_HINTS = 18;
-    private static final int MSG_SET_HANDLE = 19;
-    private static final int MSG_SET_CALLER_DISPLAY_NAME = 20;
-    private static final int MSG_SET_VIDEO_STATE = 21;
-    private static final int MSG_SET_CONFERENCEABLE_CONNECTIONS = 22;
-    private static final int MSG_START_ACTIVITY_FROM_IN_CALL = 23;
+    private static final int MSG_HANDLE_CREATE_CONNECTION_COMPLETE = 1;
+    private static final int MSG_SET_ACTIVE = 2;
+    private static final int MSG_SET_RINGING = 3;
+    private static final int MSG_SET_DIALING = 4;
+    private static final int MSG_SET_DISCONNECTED = 5;
+    private static final int MSG_SET_ON_HOLD = 6;
+    private static final int MSG_SET_REQUESTING_RINGBACK = 7;
+    private static final int MSG_SET_CALL_CAPABILITIES = 8;
+    private static final int MSG_SET_IS_CONFERENCED = 9;
+    private static final int MSG_ADD_CONFERENCE_CALL = 10;
+    private static final int MSG_REMOVE_CALL = 11;
+    private static final int MSG_ON_POST_DIAL_WAIT = 12;
+    private static final int MSG_QUERY_REMOTE_CALL_SERVICES = 13;
+    private static final int MSG_SET_VIDEO_PROVIDER = 14;
+    private static final int MSG_SET_AUDIO_MODE_IS_VOIP = 15;
+    private static final int MSG_SET_STATUS_HINTS = 16;
+    private static final int MSG_SET_HANDLE = 17;
+    private static final int MSG_SET_CALLER_DISPLAY_NAME = 18;
+    private static final int MSG_SET_VIDEO_STATE = 19;
+    private static final int MSG_SET_CONFERENCEABLE_CONNECTIONS = 20;
+    private static final int MSG_START_ACTIVITY_FROM_IN_CALL = 21;
 
     private final Handler mHandler = new Handler() {
         @Override
         public void handleMessage(Message msg) {
             Call call;
             switch (msg.what) {
-                case MSG_HANDLE_CREATE_CONNECTION_SUCCESSFUL: {
+                case MSG_HANDLE_CREATE_CONNECTION_COMPLETE: {
                     SomeArgs args = (SomeArgs) msg.obj;
                     try {
                         String callId = (String) args.arg1;
                         ConnectionRequest request = (ConnectionRequest) args.arg2;
-                        if (mPendingResponses.containsKey(callId)) {
-                            ParcelableConnection connection = (ParcelableConnection) args.arg3;
-                            mPendingResponses.remove(callId).
-                                    handleCreateConnectionSuccessful(request, connection);
-                        }
-                    } finally {
-                        args.recycle();
-                    }
-                    break;
-                }
-                case MSG_HANDLE_CREATE_CONNECTION_FAILED: {
-                    SomeArgs args = (SomeArgs) msg.obj;
-                    try {
-                        String callId = (String) args.arg1;
-                        ConnectionRequest request = (ConnectionRequest) args.arg2;
-                        int statusCode = args.argi1;
-                        String statusMsg = (String) args.arg3;
-                        removeCall(
-                                mCallIdMapper.getCall(callId),
-                                statusCode,
-                                statusMsg);
-                    } finally {
-                        args.recycle();
-                    }
-                    break;
-                }
-                case MSG_HANDLE_CREATE_CONNECTION_CANCELLED: {
-                    SomeArgs args = (SomeArgs) msg.obj;
-                    try {
-                        String callId = (String) args.arg1;
-                        ConnectionRequest request = (ConnectionRequest) args.arg2;
-                        if (mPendingResponses.containsKey(callId)) {
-                            mPendingResponses.remove(callId)
-                                    .handleCreateConnectionCancelled();
-                        } else {
-                            //Log.w(this, "handleCreateConnectionCancelled, unknown call: %s", callId);
-                        }
+                        ParcelableConnection connection = (ParcelableConnection) args.arg3;
+                        handleCreateConnectionComplete(callId, request, connection);
                     } finally {
                         args.recycle();
                     }
@@ -162,6 +128,7 @@
                         call = mCallIdMapper.getCall(args.arg1);
                         String disconnectMessage = (String) args.arg2;
                         int disconnectCause = args.argi1;
+                        Log.d(this, "disconnect call %s %s", args.arg1, call);
                         if (call != null) {
                             mCallsManager.markCallAsDisconnected(call, disconnectCause,
                                     disconnectMessage);
@@ -207,16 +174,11 @@
                         if (childCall != null) {
                             String conferenceCallId = (String) args.arg2;
                             if (conferenceCallId == null) {
+                                Log.d(this, "unsetting parent: %s", args.arg1);
                                 childCall.setParentCall(null);
                             } else {
                                 Call conferenceCall = mCallIdMapper.getCall(conferenceCallId);
-                                if (conferenceCall != null &&
-                                        !mPendingConferenceCalls.contains(conferenceCall)) {
-                                    childCall.setParentCall(conferenceCall);
-                                } else {
-                                    //Log.w(this, "setIsConferenced, unknown conference id %s",
-                                    //        conferenceCallId);
-                                }
+                                childCall.setParentCall(conferenceCall);
                             }
                         } else {
                             //Log.w(this, "setIsConferenced, unknown call id: %s", args.arg1);
@@ -227,17 +189,47 @@
                     break;
                 }
                 case MSG_ADD_CONFERENCE_CALL: {
-                    Call conferenceCall = mCallIdMapper.getCall(msg.obj);
-                    if (mPendingConferenceCalls.remove(conferenceCall)) {
-                        Log.v(this, "confirming conf call %s", conferenceCall);
-                        conferenceCall.confirmConference();
-                    } else {
-                        //Log.w(this, "addConference, unknown call id: %s", callId);
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    try {
+                        String id = (String) args.arg1;
+                        if (mCallIdMapper.getCall(id) != null) {
+                            Log.w(this, "Attempting to add a conference call using an existing " +
+                                    "call id %s", id);
+                            break;
+                        }
+                        ParcelableConference parcelableConference =
+                                (ParcelableConference) args.arg2;
+                        // need to create a new Call
+                        Call conferenceCall = mCallsManager.createConferenceCall(
+                                null, parcelableConference);
+                        mCallIdMapper.addCall(conferenceCall, id);
+                        conferenceCall.setConnectionService(ConnectionServiceWrapper.this);
+
+                        Log.d(this, "adding children to conference");
+                        for (String callId : parcelableConference.getConnectionIds()) {
+                            Call childCall = mCallIdMapper.getCall(callId);
+                            Log.d(this, "found child: %s", callId);
+                            if (childCall != null) {
+                                childCall.setParentCall(conferenceCall);
+                            }
+                        }
+                    } finally {
+                        args.recycle();
                     }
                     break;
                 }
-                case MSG_REMOVE_CALL:
+                case MSG_REMOVE_CALL: {
+                    call = mCallIdMapper.getCall(msg.obj);
+                    if (call != null) {
+                        if (call.isActive()) {
+                            mCallsManager.markCallAsDisconnected(
+                                    call, DisconnectCause.NORMAL, null);
+                        } else {
+                            mCallsManager.markCallAsRemoved(call);
+                        }
+                    }
                     break;
+                }
                 case MSG_ON_POST_DIAL_WAIT: {
                     SomeArgs args = (SomeArgs) msg.obj;
                     try {
@@ -362,55 +354,25 @@
     private final class Adapter extends IConnectionServiceAdapter.Stub {
 
         @Override
-        public void handleCreateConnectionSuccessful(
+        public void handleCreateConnectionComplete(
                 String callId,
                 ConnectionRequest request,
                 ParcelableConnection connection) {
-            logIncoming("handleCreateConnectionSuccessful %s", request);
+            logIncoming("handleCreateConnectionComplete %s", request);
             if (mCallIdMapper.isValidCallId(callId)) {
                 SomeArgs args = SomeArgs.obtain();
                 args.arg1 = callId;
                 args.arg2 = request;
                 args.arg3 = connection;
-                mHandler.obtainMessage(MSG_HANDLE_CREATE_CONNECTION_SUCCESSFUL, args)
+                mHandler.obtainMessage(MSG_HANDLE_CREATE_CONNECTION_COMPLETE, args)
                         .sendToTarget();
             }
         }
 
         @Override
-        public void handleCreateConnectionFailed(
-                String callId,
-                ConnectionRequest request,
-                int errorCode,
-                String errorMsg) {
-            logIncoming("handleCreateConnectionFailed %s %d %s", request, errorCode, errorMsg);
-            if (mCallIdMapper.isValidCallId(callId)) {
-                SomeArgs args = SomeArgs.obtain();
-                args.arg1 = callId;
-                args.arg2 = request;
-                args.argi1 = errorCode;
-                args.arg3 = errorMsg;
-                mHandler.obtainMessage(MSG_HANDLE_CREATE_CONNECTION_FAILED, args).sendToTarget();
-            }
-        }
-
-        @Override
-        public void handleCreateConnectionCancelled(
-                String callId,
-                ConnectionRequest request) {
-            logIncoming("handleCreateConnectionCancelled %s", request);
-            if (mCallIdMapper.isValidCallId(callId)) {
-                SomeArgs args = SomeArgs.obtain();
-                args.arg1 = callId;
-                args.arg2 = request;
-                mHandler.obtainMessage(MSG_HANDLE_CREATE_CONNECTION_CANCELLED, args).sendToTarget();
-            }
-        }
-
-        @Override
         public void setActive(String callId) {
             logIncoming("setActive %s", callId);
-            if (mCallIdMapper.isValidCallId(callId)) {
+            if (mCallIdMapper.isValidCallId(callId) || mCallIdMapper.isValidConferenceId(callId)) {
                 mHandler.obtainMessage(MSG_SET_ACTIVE, callId).sendToTarget();
             }
         }
@@ -446,7 +408,8 @@
         public void setDisconnected(
                 String callId, int disconnectCause, String disconnectMessage) {
             logIncoming("setDisconnected %s %d %s", callId, disconnectCause, disconnectMessage);
-            if (mCallIdMapper.isValidCallId(callId)) {
+            if (mCallIdMapper.isValidCallId(callId) || mCallIdMapper.isValidConferenceId(callId)) {
+                Log.d(this, "disconnect call %s", callId);
                 SomeArgs args = SomeArgs.obtain();
                 args.arg1 = callId;
                 args.arg2 = disconnectMessage;
@@ -458,7 +421,7 @@
         @Override
         public void setOnHold(String callId) {
             logIncoming("setOnHold %s", callId);
-            if (mCallIdMapper.isValidCallId(callId)) {
+            if (mCallIdMapper.isValidCallId(callId) || mCallIdMapper.isValidConferenceId(callId)) {
                 mHandler.obtainMessage(MSG_SET_ON_HOLD, callId).sendToTarget();
             }
         }
@@ -475,6 +438,10 @@
         @Override
         public void removeCall(String callId) {
             logIncoming("removeCall %s", callId);
+            if (mCallIdMapper.isValidCallId(callId) || mCallIdMapper.isValidConferenceId(callId)) {
+                mHandler.obtainMessage(MSG_REMOVE_CALL, callId).sendToTarget();
+                mHandler.obtainMessage(MSG_REMOVE_CALL, callId);
+            }
         }
 
         @Override
@@ -490,7 +457,7 @@
         public void setIsConferenced(String callId, String conferenceCallId) {
             logIncoming("setIsConferenced %s %s", callId, conferenceCallId);
             if (mCallIdMapper.isValidCallId(callId) &&
-                    mCallIdMapper.isValidCallId(conferenceCallId)) {
+                    mCallIdMapper.isValidConferenceId(conferenceCallId)) {
                 SomeArgs args = SomeArgs.obtain();
                 args.arg1 = callId;
                 args.arg2 = conferenceCallId;
@@ -499,11 +466,14 @@
         }
 
         @Override
-        public void addConferenceCall(String callId) {
-            logIncoming("addConferenceCall %s", callId);
-            if (mCallIdMapper.isValidCallId(callId)) {
-                mHandler.obtainMessage(MSG_ADD_CONFERENCE_CALL, callId).sendToTarget();
-            }
+        public void addConferenceCall(String callId, ParcelableConference parcelableConference) {
+            logIncoming("addConferenceCall %s %s", callId, parcelableConference);
+            // We do not check call Ids here because we do not yet know the call ID for new
+            // conference calls.
+            SomeArgs args = SomeArgs.obtain();
+            args.arg1 = callId;
+            args.arg2 = parcelableConference;
+            mHandler.obtainMessage(MSG_ADD_CONFERENCE_CALL, args).sendToTarget();
         }
 
         @Override
@@ -602,7 +572,13 @@
 
     private final Adapter mAdapter = new Adapter();
     private final CallsManager mCallsManager = CallsManager.getInstance();
-    private final Set<Call> mPendingConferenceCalls = new HashSet<>();
+    /**
+     * ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is
+     * load factor before resizing, 1 means we only expect a single thread to
+     * access the map so make only a single shard
+     */
+    private final Set<Call> mPendingConferenceCalls = Collections.newSetFromMap(
+            new ConcurrentHashMap<Call, Boolean>(8, 0.9f, 1));
     private final CallIdMapper mCallIdMapper = new CallIdMapper("ConnectionService");
     private final Map<String, CreateConnectionResponse> mPendingResponses = new HashMap<>();
 
@@ -677,7 +653,7 @@
                             call.isIncoming());
                 } catch (RemoteException e) {
                     Log.e(this, e, "Failure to createConnection -- %s", getComponentName());
-                    mPendingResponses.remove(callId).handleCreateConnectionFailed(
+                    mPendingResponses.remove(callId).handleCreateConnectionFailure(
                             DisconnectCause.OUTGOING_FAILURE, e.toString());
                 }
             }
@@ -685,7 +661,7 @@
             @Override
             public void onFailure() {
                 Log.e(this, new Exception(), "Failure to call %s", getComponentName());
-                response.handleCreateConnectionFailed(DisconnectCause.OUTGOING_FAILURE, null);
+                response.handleCreateConnectionFailure(DisconnectCause.OUTGOING_FAILURE, null);
             }
         };
 
@@ -823,10 +799,19 @@
         removeCall(call, DisconnectCause.ERROR_UNSPECIFIED, null);
     }
 
+    void removeCall(String callId, int disconnectCause, String disconnectMessage) {
+        CreateConnectionResponse response = mPendingResponses.remove(callId);
+        if (response != null) {
+            response.handleCreateConnectionFailure(disconnectCause, disconnectMessage);
+        }
+
+        mCallIdMapper.removeCall(callId);
+    }
+
     void removeCall(Call call, int disconnectCause, String disconnectMessage) {
         CreateConnectionResponse response = mPendingResponses.remove(mCallIdMapper.getCallId(call));
         if (response != null) {
-            response.handleCreateConnectionFailed(disconnectCause, disconnectMessage);
+            response.handleCreateConnectionFailure(disconnectCause, disconnectMessage);
         }
 
         mCallIdMapper.removeCall(call);
@@ -854,25 +839,13 @@
         }
     }
 
-    void conference(final Call conferenceCall, Call call) {
-        final String conferenceId = mCallIdMapper.getCallId(call);
+    void conference(final Call call, Call otherCall) {
         final String callId = mCallIdMapper.getCallId(call);
-        if (conferenceId != null && callId != null &&
-                isServiceValid("conference")) {
+        final String otherCallId = mCallIdMapper.getCallId(otherCall);
+        if (callId != null && otherCallId != null && isServiceValid("conference")) {
             try {
-                conferenceCall.setConnectionService(this);
-                mPendingConferenceCalls.add(conferenceCall);
-                mHandler.postDelayed(new Runnable() {
-                    @Override public void run() {
-                        if (mPendingConferenceCalls.remove(conferenceCall)) {
-                            conferenceCall.expireConference();
-                            Log.i(this, "Conference call expired: %s", conferenceCall);
-                        }
-                    }
-                }, Timeouts.getConferenceCallExpireMillis());
-
-                logOutgoing("conference %s %s", conferenceId, callId);
-                mServiceInterface.conference(conferenceId, callId);
+                logOutgoing("conference %s %s", callId, otherCallId);
+                mServiceInterface.conference(callId, otherCallId);
             } catch (RemoteException ignored) {
             }
         }
@@ -906,6 +879,25 @@
         }
     }
 
+    private void handleCreateConnectionComplete(
+            String callId,
+            ConnectionRequest request,
+            ParcelableConnection connection) {
+        // TODO: Note we are not using parameter "request", which is a side effect of our tacit
+        // assumption that we have at most one outgoing connection attempt per ConnectionService.
+        // This may not continue to be the case.
+        if (connection.getState() == Connection.STATE_DISCONNECTED) {
+            // A connection that begins in the DISCONNECTED state is an indication of
+            // failure to connect; we handle all failures uniformly
+            removeCall(callId, connection.getDisconnectCause(), connection.getDisconnectMessage());
+        } else {
+            // Successful connection
+            if (mPendingResponses.containsKey(callId)) {
+                mPendingResponses.remove(callId).handleCreateConnectionSuccess(connection);
+            }
+        }
+    }
+
     /**
      * Called when the associated connection service dies.
      */
@@ -915,7 +907,7 @@
                     new CreateConnectionResponse[mPendingResponses.values().size()]);
             mPendingResponses.clear();
             for (int i = 0; i < responses.length; i++) {
-                responses[i].handleCreateConnectionFailed(DisconnectCause.ERROR_UNSPECIFIED, null);
+                responses[i].handleCreateConnectionFailure(DisconnectCause.ERROR_UNSPECIFIED, null);
             }
         }
         mCallIdMapper.clear();
@@ -943,7 +935,8 @@
         }
 
         // Make a list of ConnectionServices that are listed as being associated with SIM accounts
-        final Set<ConnectionServiceWrapper> simServices = new HashSet<>();
+        final Set<ConnectionServiceWrapper> simServices = Collections.newSetFromMap(
+                new ConcurrentHashMap<ConnectionServiceWrapper, Boolean>(8, 0.9f, 1));
         for (PhoneAccountHandle handle : registrar.getOutgoingPhoneAccounts()) {
             PhoneAccount account = registrar.getPhoneAccount(handle);
             if ((account.getCapabilities() & PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION) != 0) {
diff --git a/src/com/android/telecomm/CreateConnectionProcessor.java b/src/com/android/telecomm/CreateConnectionProcessor.java
index cc7808e..aacb80b 100644
--- a/src/com/android/telecomm/CreateConnectionProcessor.java
+++ b/src/com/android/telecomm/CreateConnectionProcessor.java
@@ -16,7 +16,6 @@
 
 package com.android.telecomm;
 
-import android.telecomm.ConnectionRequest;
 import android.telecomm.ParcelableConnection;
 import android.telecomm.PhoneAccount;
 import android.telecomm.PhoneAccountHandle;
@@ -102,15 +101,38 @@
             mCall.clearConnectionService();
         }
         if (response != null) {
-            response.handleCreateConnectionCancelled();
+            response.handleCreateConnectionFailure(DisconnectCause.OUTGOING_CANCELED, null);
         }
     }
 
     private void attemptNextPhoneAccount() {
         Log.v(this, "attemptNextPhoneAccount");
+        PhoneAccountRegistrar registrar = TelecommApp.getInstance().getPhoneAccountRegistrar();
+        CallAttemptRecord attempt = null;
+        if (mAttemptRecordIterator.hasNext()) {
+            attempt = mAttemptRecordIterator.next();
 
-        if (mResponse != null && mAttemptRecordIterator.hasNext()) {
-            CallAttemptRecord attempt = mAttemptRecordIterator.next();
+            if (!registrar.phoneAccountHasPermission(attempt.connectionManagerPhoneAccount)) {
+                Log.w(this,
+                        "Connection mgr does not have BIND_CONNECTION_SERVICE for attempt: %s",
+                        attempt);
+                attemptNextPhoneAccount();
+                return;
+            }
+
+            // If the target PhoneAccount differs from the ConnectionManager phone acount, ensure it
+            // also has BIND_CONNECTION_SERVICE permission.
+            if (!attempt.connectionManagerPhoneAccount.equals(attempt.targetPhoneAccount) &&
+                    !registrar.phoneAccountHasPermission(attempt.targetPhoneAccount)) {
+                Log.w(this,
+                        "Target PhoneAccount does not have BIND_CONNECTION_SERVICE for attempt: %s",
+                        attempt);
+                attemptNextPhoneAccount();
+                return;
+            }
+        }
+
+        if (mResponse != null && attempt != null) {
             Log.i(this, "Trying attempt %s", attempt);
             ConnectionServiceWrapper service =
                     mRepository.getService(
@@ -128,7 +150,7 @@
         } else {
             Log.v(this, "attemptNextPhoneAccount, no more accounts, failing");
             if (mResponse != null) {
-                mResponse.handleCreateConnectionFailed(mLastErrorCode, mLastErrorMsg);
+                mResponse.handleCreateConnectionFailure(mLastErrorCode, mLastErrorMsg);
                 mResponse = null;
                 mCall.clearConnectionService();
             }
@@ -211,30 +233,26 @@
         }
 
         @Override
-        public void handleCreateConnectionSuccessful(
-                ConnectionRequest request, ParcelableConnection connection) {
+        public void handleCreateConnectionSuccess(ParcelableConnection connection) {
             if (mResponse == null) {
+                // Nobody is listening for this connection attempt any longer; ask the responsible
+                // ConnectionService to tear down any resources associated with the call
                 mService.abort(mCall);
             } else {
-                mResponse.handleCreateConnectionSuccessful(request, connection);
+                // Success -- share the good news and remember that we are no longer interested
+                // in hearing about any more attempts
+                mResponse.handleCreateConnectionSuccess(connection);
                 mResponse = null;
             }
         }
 
         @Override
-        public void handleCreateConnectionFailed(int code, String msg) {
+        public void handleCreateConnectionFailure(int code, String msg) {
+            // Failure of some sort; record the reasons for failure and try again if possible
+            Log.d(CreateConnectionProcessor.this, "Connection failed: %d (%s)", code, msg);
             mLastErrorCode = code;
             mLastErrorMsg = msg;
-            Log.d(CreateConnectionProcessor.this, "Connection failed: %d (%s)", code, msg);
             attemptNextPhoneAccount();
         }
-
-        @Override
-        public void handleCreateConnectionCancelled() {
-            if (mResponse != null) {
-                mResponse.handleCreateConnectionCancelled();
-                mResponse = null;
-            }
-        }
     }
 }
diff --git a/src/com/android/telecomm/CreateConnectionResponse.java b/src/com/android/telecomm/CreateConnectionResponse.java
index 380f42c..b907e3a 100644
--- a/src/com/android/telecomm/CreateConnectionResponse.java
+++ b/src/com/android/telecomm/CreateConnectionResponse.java
@@ -16,15 +16,12 @@
 
 package com.android.telecomm;
 
-import android.telecomm.ConnectionRequest;
 import android.telecomm.ParcelableConnection;
 
 /**
  * A callback for providing the result of creating a connection.
  */
 interface CreateConnectionResponse {
-    void handleCreateConnectionSuccessful(
-            ConnectionRequest request, ParcelableConnection connection);
-    void handleCreateConnectionFailed(int code, String msg);
-    void handleCreateConnectionCancelled();
+    void handleCreateConnectionSuccess(ParcelableConnection connection);
+    void handleCreateConnectionFailure(int code, String message);
 }
diff --git a/src/com/android/telecomm/InCallController.java b/src/com/android/telecomm/InCallController.java
index 47f7f2b..4f022c0 100644
--- a/src/com/android/telecomm/InCallController.java
+++ b/src/com/android/telecomm/InCallController.java
@@ -16,28 +16,36 @@
 
 package com.android.telecomm;
 
+import android.Manifest;
 import android.app.PendingIntent;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
-
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
 import android.content.res.Resources;
 import android.net.Uri;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.telecomm.AudioState;
+import android.telecomm.CallState;
+import android.telecomm.InCallService;
+import android.telecomm.ParcelableCall;
 import android.telecomm.PhoneCapabilities;
 import android.telecomm.PropertyPresentation;
-import android.telecomm.CallState;
-import android.telecomm.ParcelableCall;
+import android.util.ArrayMap;
 
 import com.android.internal.telecomm.IInCallService;
 import com.google.common.collect.ImmutableCollection;
 
 import java.util.ArrayList;
+import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
 
 /**
  * Binds to {@link IInCallService} and provides the service to {@link CallsManager} through which it
@@ -52,12 +60,12 @@
     private class InCallServiceConnection implements ServiceConnection {
         /** {@inheritDoc} */
         @Override public void onServiceConnected(ComponentName name, IBinder service) {
-            onConnected(service);
+            onConnected(name, service);
         }
 
         /** {@inheritDoc} */
         @Override public void onServiceDisconnected(ComponentName name) {
-            onDisconnected();
+            onDisconnected(name);
         }
     }
 
@@ -99,11 +107,13 @@
 
         @Override
         public void onStartActivityFromInCall(Call call, PendingIntent intent) {
-            if (mInCallService != null) {
+            if (!mInCallServices.isEmpty()) {
                 Log.i(this, "Calling startActivity, intent: %s", intent);
-                try {
-                    mInCallService.startActivity(mCallIdMapper.getCallId(call), intent);
-                } catch (RemoteException ignored) {
+                for (IInCallService inCallService : mInCallServices.values()) {
+                    try {
+                        inCallService.startActivity(mCallIdMapper.getCallId(call), intent);
+                    } catch (RemoteException ignored) {
+                    }
                 }
             }
         }
@@ -119,29 +129,45 @@
         }
     };
 
-    /** Maintains a binding connection to the in-call app. */
-    private final InCallServiceConnection mConnection = new InCallServiceConnection();
+    /**
+     * Maintains a binding connection to the in-call app(s).
+     * ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is
+     * load factor before resizing, 1 means we only expect a single thread to
+     * access the map so make only a single shard
+     */
+    private final Map<ComponentName, InCallServiceConnection> mServiceConnections =
+            new ConcurrentHashMap<ComponentName, InCallServiceConnection>(8, 0.9f, 1);
 
-    /** The in-call app implementation, see {@link IInCallService}. */
-    private IInCallService mInCallService;
+    /** The in-call app implementations, see {@link IInCallService}. */
+    private final Map<ComponentName, IInCallService> mInCallServices = new ArrayMap<>();
 
     private final CallIdMapper mCallIdMapper = new CallIdMapper("InCall");
 
-    IInCallService getService() {
-        return mInCallService;
+    /** The {@link ComponentName} of the default InCall UI */
+    private ComponentName mInCallComponentName;
+
+    public InCallController() {
+        Context context = TelecommApp.getInstance();
+        Resources resources = context.getResources();
+
+        mInCallComponentName = new ComponentName(
+                resources.getString(R.string.ui_default_package),
+                resources.getString(R.string.incall_default_class));
     }
 
     @Override
     public void onCallAdded(Call call) {
-        if (mInCallService == null) {
+        if (mInCallServices.isEmpty()) {
             bind();
         } else {
             Log.i(this, "Adding call: %s", call);
-            if (mCallIdMapper.getCallId(call) == null) {
-                mCallIdMapper.addCall(call);
-                call.addListener(mCallListener);
+            // Track the call if we don't already know about it.
+            addCall(call);
+
+            ParcelableCall parcelableCall = toParcelableCall(call);
+            for (IInCallService inCallService : mInCallServices.values()) {
                 try {
-                    mInCallService.addCall(toParcelableCall(call));
+                    inCallService.addCall(parcelableCall);
                 } catch (RemoteException ignored) {
                 }
             }
@@ -173,37 +199,43 @@
 
     @Override
     public void onAudioStateChanged(AudioState oldAudioState, AudioState newAudioState) {
-        if (mInCallService != null) {
+        if (!mInCallServices.isEmpty()) {
             Log.i(this, "Calling onAudioStateChanged, audioState: %s -> %s", oldAudioState,
                     newAudioState);
-            try {
-                mInCallService.onAudioStateChanged(newAudioState);
-            } catch (RemoteException ignored) {
+            for (IInCallService inCallService : mInCallServices.values()) {
+                try {
+                    inCallService.onAudioStateChanged(newAudioState);
+                } catch (RemoteException ignored) {
+                }
             }
         }
     }
 
     void onPostDialWait(Call call, String remaining) {
-        if (mInCallService != null) {
+        if (!mInCallServices.isEmpty()) {
             Log.i(this, "Calling onPostDialWait, remaining = %s", remaining);
-            try {
-                mInCallService.setPostDialWait(mCallIdMapper.getCallId(call), remaining);
-            } catch (RemoteException ignored) {
+            for (IInCallService inCallService : mInCallServices.values()) {
+                try {
+                    inCallService.setPostDialWait(mCallIdMapper.getCallId(call), remaining);
+                } catch (RemoteException ignored) {
+                }
             }
         }
     }
 
     @Override
     public void onIsConferencedChanged(Call call) {
-        Log.v(this, "onIsConferencedChanged %s", call);
+        Log.d(this, "onIsConferencedChanged %s", call);
         updateCall(call);
     }
 
     void bringToForeground(boolean showDialpad) {
-        if (mInCallService != null) {
-            try {
-                mInCallService.bringToForeground(showDialpad);
-            } catch (RemoteException ignored) {
+        if (!mInCallServices.isEmpty()) {
+            for (IInCallService inCallService : mInCallServices.values()) {
+                try {
+                    inCallService.bringToForeground(showDialpad);
+                } catch (RemoteException ignored) {
+                }
             }
         } else {
             Log.w(this, "Asking to bring unbound in-call UI to foreground.");
@@ -215,10 +247,12 @@
      */
     private void unbind() {
         ThreadUtil.checkOnMainThread();
-        if (mInCallService != null) {
+        if (!mInCallServices.isEmpty()) {
             Log.i(this, "Unbinding from InCallService");
-            TelecommApp.getInstance().unbindService(mConnection);
-            mInCallService = null;
+            for (InCallServiceConnection connection : mServiceConnections.values()) {
+                TelecommApp.getInstance().unbindService(connection);
+            }
+            mInCallServices.clear();
         }
     }
 
@@ -228,22 +262,46 @@
      */
     private void bind() {
         ThreadUtil.checkOnMainThread();
-        if (mInCallService == null) {
+        if (mInCallServices.isEmpty()) {
+            mServiceConnections.clear();
             Context context = TelecommApp.getInstance();
-            Resources resources = context.getResources();
-            ComponentName component = new ComponentName(
-                    resources.getString(R.string.ui_default_package),
-                    resources.getString(R.string.incall_default_class));
-            Log.i(this, "Attempting to bind to InCallService: %s", component);
+            PackageManager packageManager = TelecommApp.getInstance().getPackageManager();
+            Intent intent = new Intent(InCallService.SERVICE_INTERFACE);
 
-            Intent serviceIntent = new Intent(IInCallService.class.getName());
-            serviceIntent.setComponent(component);
+            for (ResolveInfo entry : packageManager.queryIntentServices(intent, 0)) {
+                ServiceInfo serviceInfo = entry.serviceInfo;
+                if (serviceInfo != null) {
+                    boolean hasServiceBindPermission = serviceInfo.permission != null &&
+                            serviceInfo.permission.equals(
+                                    Manifest.permission.BIND_INCALL_SERVICE);
+                    boolean hasControlInCallPermission = packageManager.checkPermission(
+                            Manifest.permission.CONTROL_INCALL_EXPERIENCE,
+                            serviceInfo.packageName) == PackageManager.PERMISSION_GRANTED;
 
-            if (!context.bindServiceAsUser(serviceIntent, mConnection, Context.BIND_AUTO_CREATE,
-                    UserHandle.CURRENT)) {
-                Log.w(this, "Could not connect to the in-call app (%s)", component);
+                    if (!hasServiceBindPermission) {
+                        Log.w(this, "InCallService does not have BIND_INCALL_SERVICE permission: " +
+                                serviceInfo.packageName);
+                        continue;
+                    }
 
-                // TODO: Implement retry or fall-back-to-default logic.
+                    if (!hasControlInCallPermission) {
+                        Log.w(this,
+                                "InCall UI does not have CONTROL_INCALL_EXPERIENCE permission: " +
+                                        serviceInfo.packageName);
+                        continue;
+                    }
+
+                    Log.i(this, "Attempting to bind to InCall " + serviceInfo.packageName);
+                    InCallServiceConnection inCallServiceConnection = new InCallServiceConnection();
+                    ComponentName componentName = new ComponentName(serviceInfo.packageName,
+                            serviceInfo.name);
+                    intent.setComponent(componentName);
+
+                    if (context.bindServiceAsUser(intent, inCallServiceConnection,
+                            Context.BIND_AUTO_CREATE, UserHandle.CURRENT)) {
+                        mServiceConnections.put(componentName, inCallServiceConnection);
+                    }
+                }
             }
         }
     }
@@ -253,26 +311,34 @@
      * this class and in-call app by sending the first update to in-call app. This method is
      * called after a successful binding connection is established.
      *
+     * @param componentName The service {@link ComponentName}.
      * @param service The {@link IInCallService} implementation.
      */
-    private void onConnected(IBinder service) {
+    private void onConnected(ComponentName componentName, IBinder service) {
         ThreadUtil.checkOnMainThread();
-        mInCallService = IInCallService.Stub.asInterface(service);
+
+        IInCallService inCallService = IInCallService.Stub.asInterface(service);
 
         try {
-            mInCallService.setInCallAdapter(new InCallAdapter(CallsManager.getInstance(),
+            inCallService.setInCallAdapter(new InCallAdapter(CallsManager.getInstance(),
                     mCallIdMapper));
+            mInCallServices.put(componentName, inCallService);
         } catch (RemoteException e) {
             Log.e(this, e, "Failed to set the in-call adapter.");
-            mInCallService = null;
             return;
         }
 
-        // Upon successful connection, send the state of the world to the in-call app.
+        // Upon successful connection, send the state of the world to the service.
         ImmutableCollection<Call> calls = CallsManager.getInstance().getCalls();
         if (!calls.isEmpty()) {
             for (Call call : calls) {
-                onCallAdded(call);
+                try {
+                    // Track the call if we don't already know about it.
+                    addCall(call);
+
+                    inCallService.addCall(toParcelableCall(call));
+                } catch (RemoteException ignored) {
+                }
             }
             onAudioStateChanged(null, CallsManager.getInstance().getAudioState());
         } else {
@@ -281,20 +347,50 @@
     }
 
     /**
-     * Cleans up the instance of in-call app after the service has been unbound.
+     * Cleans up an instance of in-call app after the service has been unbound.
+     *
+     * @param disconnectedComponent The {@link ComponentName} of the service which disconnected.
      */
-    private void onDisconnected() {
+    private void onDisconnected(ComponentName disconnectedComponent) {
         ThreadUtil.checkOnMainThread();
-        mInCallService = null;
+        if (mInCallServices.containsKey(disconnectedComponent)) {
+            mInCallServices.remove(disconnectedComponent);
+        }
+
+        // If the default in-call UI has disconnected, disconnect all calls and un-bind all other
+        // InCallService implementations.
+        if (disconnectedComponent.equals(mInCallComponentName)) {
+            Log.i(this, "In-call UI %s disconnected.", disconnectedComponent);
+            CallsManager.getInstance().disconnectAllCalls();
+
+            // Iterate through the in-call services, removing them as they are un-bound.
+            Iterator<Map.Entry<ComponentName, IInCallService>> it =
+                    mInCallServices.entrySet().iterator();
+            while (it.hasNext()) {
+                Map.Entry<ComponentName, IInCallService> entry = it.next();
+                ComponentName componentName = entry.getKey();
+
+                InCallServiceConnection connection =  mServiceConnections.remove(componentName);
+                it.remove();
+                if (connection == null) {
+                    continue;
+                }
+
+                Log.i(this, "Unbinding other InCallService %s", componentName);
+                TelecommApp.getInstance().unbindService(connection);
+            }
+        }
     }
 
     private void updateCall(Call call) {
-        if (mInCallService != null) {
-            try {
-                ParcelableCall parcelableCall = toParcelableCall(call);
-                Log.v(this, "updateCall %s ==> %s", call, parcelableCall);
-                mInCallService.updateCall(parcelableCall);
-            } catch (RemoteException ignored) {
+        if (!mInCallServices.isEmpty()) {
+            ParcelableCall parcelableCall = toParcelableCall(call);
+            Log.v(this, "updateCall %s ==> %s", call, parcelableCall);
+            for (IInCallService inCallService : mInCallServices.values()) {
+                try {
+                    inCallService.updateCall(parcelableCall);
+                } catch (RemoteException ignored) {
+                }
             }
         }
     }
@@ -372,4 +468,15 @@
                 conferenceableCallIds,
                 call.getExtras());
     }
+
+    /**
+     * Adds the call to the list of calls tracked by the {@link InCallController}.
+     * @param call The call to add.
+     */
+    private void addCall(Call call) {
+        if (mCallIdMapper.getCallId(call) == null) {
+            mCallIdMapper.addCall(call);
+            call.addListener(mCallListener);
+        }
+    }
 }
diff --git a/src/com/android/telecomm/MissedCallNotifier.java b/src/com/android/telecomm/MissedCallNotifier.java
index 94de312..eede65c 100644
--- a/src/com/android/telecomm/MissedCallNotifier.java
+++ b/src/com/android/telecomm/MissedCallNotifier.java
@@ -121,6 +121,7 @@
         // Create the notification.
         Notification.Builder builder = new Notification.Builder(mContext);
         builder.setSmallIcon(android.R.drawable.stat_notify_missed_call)
+                .setColor(mContext.getResources().getColor(R.color.theme_color))
                 .setWhen(call.getCreationTimeMillis())
                 .setContentTitle(mContext.getText(titleResId))
                 .setContentText(expandedText)
diff --git a/src/com/android/telecomm/NewOutgoingCallIntentBroadcaster.java b/src/com/android/telecomm/NewOutgoingCallIntentBroadcaster.java
index a3aa740..a45f4ef 100644
--- a/src/com/android/telecomm/NewOutgoingCallIntentBroadcaster.java
+++ b/src/com/android/telecomm/NewOutgoingCallIntentBroadcaster.java
@@ -24,7 +24,6 @@
 import android.net.Uri;
 import android.os.UserHandle;
 import android.telecomm.GatewayInfo;
-import android.telecomm.PhoneAccountHandle;
 import android.telecomm.TelecommManager;
 import android.telecomm.VideoProfile;
 import android.telephony.PhoneNumberUtils;
@@ -100,11 +99,19 @@
             String resultHandle = getResultData();
             Log.v(this, "- got number from resultData: %s", Log.pii(resultHandle));
 
+            boolean endEarly = false;
             if (resultHandle == null) {
                 Log.v(this, "Call cancelled (null number), returning...");
-                return;
+                endEarly = true;
             } else if (PhoneNumberUtils.isPotentialLocalEmergencyNumber(context, resultHandle)) {
                 Log.w(this, "Cannot modify outgoing call to emergency number %s.", resultHandle);
+                endEarly = true;
+            }
+
+            if (endEarly) {
+                if (mCall != null) {
+                    mCall.disconnect();
+                }
                 return;
             }
 
@@ -125,8 +132,7 @@
             }
 
             GatewayInfo gatewayInfo = getGateWayInfoFromIntent(intent, resultHandleUri);
-            PhoneAccountHandle accountHandle = getAccountHandleFromIntent(intent);
-            mCallsManager.placeOutgoingCall(mCall, resultHandleUri, gatewayInfo, accountHandle,
+            mCallsManager.placeOutgoingCall(mCall, resultHandleUri, gatewayInfo,
                     mIntent.getBooleanExtra(TelecommManager.EXTRA_START_CALL_WITH_SPEAKERPHONE,
                             false),
                     mIntent.getIntExtra(TelecommManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
@@ -207,7 +213,7 @@
             int videoState = mIntent.getIntExtra(
                     TelecommManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
                     VideoProfile.VideoState.AUDIO_ONLY);
-            mCallsManager.placeOutgoingCall(mCall, Uri.fromParts(scheme, handle, null), null, null,
+            mCallsManager.placeOutgoingCall(mCall, Uri.fromParts(scheme, handle, null), null,
                     speakerphoneOn, videoState);
 
             // Don't return but instead continue and send the ACTION_NEW_OUTGOING_CALL broadcast
@@ -277,12 +283,6 @@
             Log.d(this, "Found and copied gateway provider extras to broadcast intent.");
             return;
         }
-        PhoneAccountHandle extraAccountHandle =
-                src.getParcelableExtra(TelecommManager.EXTRA_PHONE_ACCOUNT_HANDLE);
-        if (extraAccountHandle != null) {
-            dst.putExtra(TelecommManager.EXTRA_PHONE_ACCOUNT_HANDLE, extraAccountHandle);
-            Log.d(this, "Found and copied account extra to broadcast intent.");
-        }
 
         Log.d(this, "No provider extras found in call intent.");
     }
@@ -327,20 +327,6 @@
         return null;
     }
 
-    /**
-     * Extracts account/connection provider information from a provided intent..
-     *
-     * @param intent to extract account information from.
-     * @return Account object containing extracted account information
-     */
-    public static PhoneAccountHandle getAccountHandleFromIntent(Intent intent) {
-        if (intent == null) {
-            return null;
-        }
-
-        return intent.getParcelableExtra(TelecommManager.EXTRA_PHONE_ACCOUNT_HANDLE);
-    }
-
     private void launchSystemDialer(Context context, Uri handle) {
         Intent systemDialerIntent = new Intent();
         final Resources resources = context.getResources();
diff --git a/src/com/android/telecomm/PhoneAccountRegistrar.java b/src/com/android/telecomm/PhoneAccountRegistrar.java
index 98dcdf3..f3a5a0b 100644
--- a/src/com/android/telecomm/PhoneAccountRegistrar.java
+++ b/src/com/android/telecomm/PhoneAccountRegistrar.java
@@ -16,9 +16,11 @@
 
 package com.android.telecomm;
 
+import android.Manifest;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
 import android.telecomm.ConnectionService;
 import android.telecomm.PhoneAccount;
 import android.telecomm.PhoneAccountHandle;
@@ -45,6 +47,7 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.lang.SecurityException;
 import java.lang.String;
 import java.util.ArrayList;
 import java.util.Iterator;
@@ -231,6 +234,15 @@
     // TODO: Should we implement an artificial limit for # of accounts associated with a single
     // ComponentName?
     public void registerPhoneAccount(PhoneAccount account) {
+        // Enforce the requirement that a connection service for a phone account has the correct
+        // permission.
+        if (!phoneAccountHasPermission(account.getAccountHandle())) {
+            Log.w(this, "Phone account %s does not have BIND_CONNECTION_SERVICE permission.",
+                    account.getAccountHandle());
+            throw new SecurityException(
+                    "PhoneAccount connection service requires BIND_CONNECTION_SERVICE permission.");
+        }
+
         mState.accounts.add(account);
         // Search for duplicates and remove any that are found.
         for (int i = 0; i < mState.accounts.size() - 1; i++) {
@@ -288,7 +300,9 @@
     }
 
     public void removeListener(Listener l) {
-        mListeners.remove(l);
+        if (l != null) {
+            mListeners.remove(l);
+        }
     }
 
     private void fireAccountsChanged() {
@@ -309,6 +323,27 @@
         }
     }
 
+    /**
+     * Determines if the connection service specified by a {@link PhoneAccountHandle} has the
+     * {@link Manifest.permission#BIND_CONNECTION_SERVICE} permission.
+     *
+     * @param phoneAccountHandle The phone account to check.
+     * @return {@code True} if the phone account has permission.
+     */
+    public boolean phoneAccountHasPermission(PhoneAccountHandle phoneAccountHandle) {
+        PackageManager packageManager = TelecommApp.getInstance().getPackageManager();
+        try {
+            ServiceInfo serviceInfo = packageManager.getServiceInfo(
+                    phoneAccountHandle.getComponentName(), 0);
+
+            return serviceInfo.permission != null &&
+                    serviceInfo.permission.equals(Manifest.permission.BIND_CONNECTION_SERVICE);
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.w(this, "Name not found %s", e);
+            return false;
+        }
+    }
+
     ////////////////////////////////////////////////////////////////////////////////////////////////
 
     // TODO: Add a corresponding has(...) method to class PhoneAccount itself and remove this one
diff --git a/src/com/android/telecomm/ServiceBinder.java b/src/com/android/telecomm/ServiceBinder.java
index e918593..48442de 100644
--- a/src/com/android/telecomm/ServiceBinder.java
+++ b/src/com/android/telecomm/ServiceBinder.java
@@ -26,10 +26,11 @@
 import com.google.common.base.Preconditions;
 import com.google.common.base.Strings;
 
-import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
 
+import java.util.Collections;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 
 /**
  * Abstract class to perform the work of binding and unbinding to the specified service interface.
@@ -153,8 +154,12 @@
 
     /**
      * Set of currently registered listeners.
+     * ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is
+     * load factor before resizing, 1 means we only expect a single thread to
+     * access the map so make only a single shard
      */
-    private Set<Listener> mListeners = Sets.newHashSet();
+    private final Set<Listener> mListeners = Collections.newSetFromMap(
+            new ConcurrentHashMap<Listener, Boolean>(8, 0.9f, 1));
 
     /**
      * Persists the specified parameters and initializes the new instance.
@@ -231,7 +236,9 @@
     }
 
     final void removeListener(Listener listener) {
-        mListeners.remove(listener);
+        if (listener != null) {
+            mListeners.remove(listener);
+        }
     }
 
     /**
@@ -291,9 +298,7 @@
             setServiceInterface(binder);
 
             if (binder == null) {
-                // Use a copy of the listener list to allow the listeners to unregister themselves
-                // as part of the unbind without causing issues.
-                for (Listener l : ImmutableSet.copyOf(mListeners)) {
+                for (Listener l : mListeners) {
                     l.onUnbind(this);
                 }
             }
diff --git a/src/com/android/telecomm/Timeouts.java b/src/com/android/telecomm/Timeouts.java
index 3ae3f1d..3ff0575 100644
--- a/src/com/android/telecomm/Timeouts.java
+++ b/src/com/android/telecomm/Timeouts.java
@@ -51,11 +51,4 @@
     public static long getDirectToVoicemailMillis() {
         return get("direct_to_voicemail_ms", 500L);
     }
-
-    /**
-     * Returns the amount of time that a connection service has to respond to a "conference" action.
-     */
-    public static long getConferenceCallExpireMillis() {
-        return get("conference_call_expire_ms", 15 * 1000L);
-    }
 }
diff --git a/src/com/android/telecomm/WiredHeadsetManager.java b/src/com/android/telecomm/WiredHeadsetManager.java
index e59f1a5..aa2bbd1 100644
--- a/src/com/android/telecomm/WiredHeadsetManager.java
+++ b/src/com/android/telecomm/WiredHeadsetManager.java
@@ -22,7 +22,9 @@
 import android.content.IntentFilter;
 import android.media.AudioManager;
 
-import java.util.HashSet;
+import java.util.Collections;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 
 /** Listens for and caches headset state. */
 class WiredHeadsetManager {
@@ -45,7 +47,13 @@
 
     private final WiredHeadsetBroadcastReceiver mReceiver;
     private boolean mIsPluggedIn;
-    private final HashSet<Listener> mListeners = new HashSet<>();
+    /**
+     * ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is
+     * load factor before resizing, 1 means we only expect a single thread to
+     * access the map so make only a single shard
+     */
+    private final Set<Listener> mListeners = Collections.newSetFromMap(
+            new ConcurrentHashMap<Listener, Boolean>(8, 0.9f, 1));
 
     WiredHeadsetManager(Context context) {
         mReceiver = new WiredHeadsetBroadcastReceiver();
@@ -63,7 +71,9 @@
     }
 
     void removeListener(Listener listener) {
-        mListeners.remove(listener);
+        if (listener != null) {
+            mListeners.remove(listener);
+        }
     }
 
     boolean isPluggedIn() {
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index bbc3292..d8877fb 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -22,24 +22,35 @@
     <uses-permission android:name="android.permission.CAMERA" />
     <uses-permission
             android:name="com.android.telecomm.permission.REGISTER_PROVIDER_OR_SUBSCRIPTION" />
+    <uses-permission android:name="android.permission.CONTROL_INCALL_EXPERIENCE" />
 
     <application android:label="@string/app_name">
         <uses-library android:name="android.test.runner" />
 
         <!-- Miscellaneous telecomm app-related test activities. -->
 
-        <service android:name="com.android.telecomm.testapps.TestConnectionService">
+        <service android:name="com.android.telecomm.testapps.TestConnectionService"
+                 android:permission="android.permission.BIND_CONNECTION_SERVICE" >
             <intent-filter>
                 <action android:name="android.telecomm.ConnectionService" />
             </intent-filter>
         </service>
 
-        <service android:name="com.android.telecomm.testapps.TestConnectionManager">
+        <service android:name="com.android.telecomm.testapps.TestConnectionManager"
+                 android:permission="android.permission.BIND_CONNECTION_SERVICE" >
             <intent-filter>
                 <action android:name="android.telecomm.ConnectionService" />
             </intent-filter>
         </service>
 
+        <service android:name="com.android.telecomm.testapps.TestInCallServiceImpl"
+                 android:process="com.android.telecomm.testapps.TestInCallService"
+                 android:permission="android.permission.BIND_INCALL_SERVICE" >
+            <intent-filter>
+                <action android:name="android.telecomm.InCallService"/>
+            </intent-filter>
+        </service>
+
         <activity android:name="com.android.telecomm.testapps.TestCallActivity"
                 android:label="@string/testCallActivityLabel">
             <intent-filter>
@@ -58,7 +69,8 @@
         </receiver>
 
         <activity android:name="com.android.telecomm.testapps.TestDialerActivity"
-                android:label="@string/testDialerActivityLabel" >
+                android:label="@string/testDialerActivityLabel"
+                android:process="com.android.telecomm.testapps.TestInCallService">
             <intent-filter>
                 <action android:name="android.intent.action.DIAL" />
                 <category android:name="android.intent.category.DEFAULT" />
@@ -89,8 +101,6 @@
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
-
-
     </application>
 
     <!--
diff --git a/tests/src/com/android/telecomm/testapps/CallServiceNotifier.java b/tests/src/com/android/telecomm/testapps/CallServiceNotifier.java
index 0688f3b..5fd75e5 100644
--- a/tests/src/com/android/telecomm/testapps/CallServiceNotifier.java
+++ b/tests/src/com/android/telecomm/testapps/CallServiceNotifier.java
@@ -96,6 +96,8 @@
         TelecommManager telecommManager =
                 (TelecommManager) context.getSystemService(Context.TELECOMM_SERVICE);
 
+        telecommManager.clearAccounts(context.getPackageName());
+
         telecommManager.registerPhoneAccount(PhoneAccount.builder()
                 .withAccountHandle(
                         new PhoneAccountHandle(
@@ -105,21 +107,22 @@
                 .withSubscriptionNumber("555-TEST")
                 .withCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
                 .withIconResId(R.drawable.stat_sys_phone_call)
-                .withLabel("Dummy Service")
-                .withShortDescription("a short description for the dummy service")
+                .withLabel("TelecommTestApp Call Provider")
+                .withShortDescription("a short description for the call provider")
                 .build());
 
         telecommManager.registerPhoneAccount(PhoneAccount.builder()
                 .withAccountHandle(
                         new PhoneAccountHandle(
-                                new ComponentName(context, TestConnectionManager.class),
+                                new ComponentName(context, TestConnectionService.class),
                                 SIM_SUBSCRIPTION_ID))
-                .withHandle(Uri.parse("tel:555-CMGR"))
-                .withSubscriptionNumber("555-CMGR")
-                .withCapabilities(PhoneAccount.CAPABILITY_CONNECTION_MANAGER)
+                .withHandle(Uri.parse("tel:555-TSIM"))
+                .withSubscriptionNumber("555-TSIM")
+                .withCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER |
+                    PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)
                 .withIconResId(R.drawable.stat_sys_phone_call)
-                .withLabel("Dummy Connection Manager")
-                .withShortDescription("a short description for the dummy connection manager")
+                .withLabel("TelecommTestApp SIM Subscription")
+                .withShortDescription("a short description for the sim subscription")
                 .build());
 
         telecommManager.registerPhoneAccount(PhoneAccount.builder()
diff --git a/tests/src/com/android/telecomm/testapps/TestConnectionManager.java b/tests/src/com/android/telecomm/testapps/TestConnectionManager.java
index 69c5ea6..902ace7 100644
--- a/tests/src/com/android/telecomm/testapps/TestConnectionManager.java
+++ b/tests/src/com/android/telecomm/testapps/TestConnectionManager.java
@@ -181,7 +181,7 @@
     }
 
     private static void log(String msg) {
-        Log.w("telecomtestcs", "[TestConnectionService] " + msg);
+        Log.w("telecomtestcs", "[TestConnectionManager] " + msg);
     }
 
     @Override
@@ -200,7 +200,7 @@
             PhoneAccountHandle connectionManagerAccount,
             final ConnectionRequest request) {
         return new TestManagedConnection(
-                createRemoteOutgoingConnection(
+                createRemoteIncomingConnection(
                         request.getAccountHandle(),
                         request),
                 true);
diff --git a/tests/src/com/android/telecomm/testapps/TestInCallServiceImpl.java b/tests/src/com/android/telecomm/testapps/TestInCallServiceImpl.java
new file mode 100644
index 0000000..e48d5fb
--- /dev/null
+++ b/tests/src/com/android/telecomm/testapps/TestInCallServiceImpl.java
@@ -0,0 +1,60 @@
+/*
+ * 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.telecomm.testapps;
+
+import android.telecomm.InCallService;
+import android.telecomm.Phone;
+import android.util.Log;
+
+import java.lang.Override;
+import java.lang.String;
+
+/**
+ * Test In-Call service implementation.  Logs incoming events.  Mainly used to test binding to
+ * multiple {@link InCallService} implementations.
+ */
+public class TestInCallServiceImpl extends InCallService {
+    private static final String TAG = "TestInCallServiceImpl";
+
+    private Phone mPhone;
+
+    private Phone.Listener mPhoneListener = new Phone.Listener() {
+        @Override
+        public void onCallAdded(Phone phone, android.telecomm.Call call) {
+            Log.i(TAG, "onCallAdded: "+call.toString());
+        }
+        @Override
+        public void onCallRemoved(Phone phone, android.telecomm.Call call) {
+            Log.i(TAG, "onCallRemoved: "+call.toString());
+        }
+    };
+
+    @Override
+    public void onPhoneCreated(Phone phone) {
+        Log.i(TAG, "onPhoneCreated");
+        mPhone = phone;
+        mPhone.addListener(mPhoneListener);
+
+    }
+
+    @Override
+    public void onPhoneDestroyed(Phone phone) {
+        Log.i(TAG, "onPhoneDestroyed");
+        mPhone.removeListener(mPhoneListener);
+        mPhone = null;
+    }
+}