Rename Telecom Call Ids to include attempt

Telecom Call Ids will now append an attempt id (in the form of an
integer) that will be used by the CreateConnetionProcessor to name the
Telecom Call Ids that are used during the creation of a Connection to
the ConnectionService. This allows us to better identify which
Connection is calling back to Telecom to update status, disconnect,
etc. Previously, all of the attempted Connections in
CreateConnectionProcessor would be given the same name, which could lead
to multiple Connections trying to manage the same Call object in
Telecom.

Also, a supporting tests were added and a few Unit Tests were fixed.

Bug: 28799824
Change-Id: I7e23497ab671712bc8d3cc9542537ae1c4829afc
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index a6d791e..982b337 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -343,6 +343,7 @@
     private final CallsManager mCallsManager;
     private final TelecomSystem.SyncRoot mLock;
     private final String mId;
+    private String mConnectionId;
     private Analytics.CallInfo mAnalytics;
 
     private boolean mWasConferencePreviouslyMerged = false;
@@ -379,6 +380,8 @@
      */
     private boolean mIsVideoCallingSupported = false;
 
+    private PhoneNumberUtilsAdapter mPhoneNumberUtilsAdapter;
+
     /**
      * Persists the specified parameters and initializes the new instance.
      *
@@ -404,6 +407,7 @@
             ConnectionServiceRepository repository,
             ContactsAsyncHelper contactsAsyncHelper,
             CallerInfoAsyncQueryFactory callerInfoAsyncQueryFactory,
+            PhoneNumberUtilsAdapter phoneNumberUtilsAdapter,
             Uri handle,
             GatewayInfo gatewayInfo,
             PhoneAccountHandle connectionManagerPhoneAccountHandle,
@@ -412,11 +416,13 @@
             boolean shouldAttachToExistingConnection,
             boolean isConference) {
         mId = callId;
+        mConnectionId = callId;
         mState = isConference ? CallState.ACTIVE : CallState.NEW;
         mContext = context;
         mCallsManager = callsManager;
         mLock = lock;
         mRepository = repository;
+        mPhoneNumberUtilsAdapter = phoneNumberUtilsAdapter;
         setHandle(handle);
         mPostDialDigits = handle != null
                 ? PhoneNumberUtils.extractPostDialPortion(handle.getSchemeSpecificPart()) : "";
@@ -458,6 +464,7 @@
             ConnectionServiceRepository repository,
             ContactsAsyncHelper contactsAsyncHelper,
             CallerInfoAsyncQueryFactory callerInfoAsyncQueryFactory,
+            PhoneNumberUtilsAdapter phoneNumberUtilsAdapter,
             Uri handle,
             GatewayInfo gatewayInfo,
             PhoneAccountHandle connectionManagerPhoneAccountHandle,
@@ -467,7 +474,7 @@
             boolean isConference,
             long connectTimeMillis) {
         this(callId, context, callsManager, lock, repository, contactsAsyncHelper,
-                callerInfoAsyncQueryFactory, handle, gatewayInfo,
+                callerInfoAsyncQueryFactory, phoneNumberUtilsAdapter, handle, gatewayInfo,
                 connectionManagerPhoneAccountHandle, targetPhoneAccountHandle, callDirection,
                 shouldAttachToExistingConnection, isConference);
 
@@ -595,6 +602,21 @@
     }
 
     /**
+     * Returns the unique ID for this call (see {@link #getId}) along with an attempt indicator that
+     * iterates based on attempts to establish a {@link Connection} using createConnectionProcessor.
+     * @return The call ID with an appended attempt id.
+     */
+    public String getConnectionId() {
+        if(mCreateConnectionProcessor != null) {
+            mConnectionId = mId + "_" +
+                    String.valueOf(mCreateConnectionProcessor.getConnectionAttempt());
+            return mConnectionId;
+        } else {
+            return mConnectionId;
+        }
+    }
+
+    /**
      * Sets the call state. Although there exists the notion of appropriate state transitions
      * (see {@link CallState}), in practice those expectations break down when cellular systems
      * misbehave and they do this very often. The result is that we do not enforce state transitions
@@ -747,8 +769,9 @@
             // Let's not allow resetting of the emergency flag. Once a call becomes an emergency
             // call, it will remain so for the rest of it's lifetime.
             if (!mIsEmergencyCall) {
-                mIsEmergencyCall = mHandle != null && PhoneNumberUtils.isLocalEmergencyNumber(
-                        mContext, mHandle.getSchemeSpecificPart());
+                mIsEmergencyCall = mHandle != null &&
+                        mPhoneNumberUtilsAdapter.isLocalEmergencyNumber(mContext,
+                                mHandle.getSchemeSpecificPart());
             }
             startCallerInfoLookup();
             for (Listener l : mListeners) {
@@ -1671,7 +1694,7 @@
             return false;
         }
 
-        if (PhoneNumberUtils.isUriNumber(getHandle().toString())) {
+        if (mPhoneNumberUtilsAdapter.isUriNumber(getHandle().toString())) {
             // The incoming number is actually a URI (i.e. a SIP address),
             // not a regular PSTN phone number, and we can't send SMSes to
             // SIP addresses.
diff --git a/src/com/android/server/telecom/CallIdMapper.java b/src/com/android/server/telecom/CallIdMapper.java
index b097bea..da2c199 100644
--- a/src/com/android/server/telecom/CallIdMapper.java
+++ b/src/com/android/server/telecom/CallIdMapper.java
@@ -77,7 +77,16 @@
         }
     }
 
+    public interface ICallInfo {
+        String getCallId(Call call);
+    }
+
     private final BiMap<String, Call> mCalls = new BiMap<>();
+    private ICallInfo mCallInfo;
+
+    public CallIdMapper(ICallInfo callInfo) {
+        mCallInfo = callInfo;
+    }
 
     void replaceCall(Call newCall, Call callToReplace) {
         // Use the old call's ID for the new call.
@@ -93,7 +102,7 @@
     }
 
     void addCall(Call call) {
-        addCall(call, call.getId());
+        addCall(call, mCallInfo.getCallId(call));
     }
 
     void removeCall(Call call) {
@@ -111,7 +120,7 @@
         if (call == null || mCalls.getKey(call) == null) {
             return null;
         }
-        return call.getId();
+        return mCallInfo.getCallId(call);
     }
 
     Call getCall(Object objId) {
diff --git a/src/com/android/server/telecom/CallIntentProcessor.java b/src/com/android/server/telecom/CallIntentProcessor.java
index ffa76ce..1219216 100644
--- a/src/com/android/server/telecom/CallIntentProcessor.java
+++ b/src/com/android/server/telecom/CallIntentProcessor.java
@@ -148,7 +148,7 @@
             // process will be running throughout the duration of the phone call and should never
             // be killed.
             NewOutgoingCallIntentBroadcaster broadcaster = new NewOutgoingCallIntentBroadcaster(
-                    context, callsManager, call, intent, new PhoneNumberUtilsAdapterImpl(),
+                    context, callsManager, call, intent, callsManager.getPhoneNumberUtilsAdapter(),
                     isPrivilegedDialer);
             final int result = broadcaster.processIntent();
             final boolean success = result == DisconnectCause.NOT_DISCONNECTED;
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index 2ebe38c..9cfd8ea 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -189,6 +189,7 @@
     private final CallerInfoLookupHelper mCallerInfoLookupHelper;
     private final DefaultDialerManagerAdapter mDefaultDialerManagerAdapter;
     private final Timeouts.Adapter mTimeoutsAdapter;
+    private final PhoneNumberUtilsAdapter mPhoneNumberUtilsAdapter;
     private final Set<Call> mLocallyDisconnectingCalls = new HashSet<>();
     private final Set<Call> mPendingCallsToDisconnect = new HashSet<>();
     /* Handler tied to thread in which CallManager was initialized. */
@@ -219,9 +220,11 @@
             SystemStateProvider systemStateProvider,
             DefaultDialerManagerAdapter defaultDialerAdapter,
             Timeouts.Adapter timeoutsAdapter,
-            AsyncRingtonePlayer asyncRingtonePlayer) {
+            AsyncRingtonePlayer asyncRingtonePlayer,
+            PhoneNumberUtilsAdapter phoneNumberUtilsAdapter) {
         mContext = context;
         mLock = lock;
+        mPhoneNumberUtilsAdapter = phoneNumberUtilsAdapter;
         mContactsAsyncHelper = contactsAsyncHelper;
         mCallerInfoAsyncQueryFactory = callerInfoAsyncQueryFactory;
         mPhoneAccountRegistrar = phoneAccountRegistrar;
@@ -667,6 +670,7 @@
                 mConnectionServiceRepository,
                 mContactsAsyncHelper,
                 mCallerInfoAsyncQueryFactory,
+                mPhoneNumberUtilsAdapter,
                 handle,
                 null /* gatewayInfo */,
                 null /* connectionManagerPhoneAccount */,
@@ -699,6 +703,7 @@
                 mConnectionServiceRepository,
                 mContactsAsyncHelper,
                 mCallerInfoAsyncQueryFactory,
+                mPhoneNumberUtilsAdapter,
                 handle,
                 null /* gatewayInfo */,
                 null /* connectionManagerPhoneAccount */,
@@ -771,6 +776,7 @@
                     mConnectionServiceRepository,
                     mContactsAsyncHelper,
                     mCallerInfoAsyncQueryFactory,
+                    mPhoneNumberUtilsAdapter,
                     handle,
                     null /* gatewayInfo */,
                     null /* connectionManagerPhoneAccount */,
@@ -1543,6 +1549,11 @@
         return getFirstCallWithState(null, states);
     }
 
+    @VisibleForTesting
+    public PhoneNumberUtilsAdapter getPhoneNumberUtilsAdapter() {
+        return mPhoneNumberUtilsAdapter;
+    }
+
     /**
      * Returns the first call that it finds with the given states. The states are treated as having
      * priority order so that any call with the first state will be returned before any call with
@@ -1600,6 +1611,7 @@
                 mConnectionServiceRepository,
                 mContactsAsyncHelper,
                 mCallerInfoAsyncQueryFactory,
+                mPhoneNumberUtilsAdapter,
                 null /* handle */,
                 null /* gatewayInfo */,
                 null /* connectionManagerPhoneAccount */,
@@ -1990,6 +2002,7 @@
                 mConnectionServiceRepository,
                 mContactsAsyncHelper,
                 mCallerInfoAsyncQueryFactory,
+                mPhoneNumberUtilsAdapter,
                 connection.getHandle() /* handle */,
                 null /* gatewayInfo */,
                 null /* connectionManagerPhoneAccount */,
diff --git a/src/com/android/server/telecom/ConnectionServiceWrapper.java b/src/com/android/server/telecom/ConnectionServiceWrapper.java
index 9fddd07..2d4a635 100644
--- a/src/com/android/server/telecom/ConnectionServiceWrapper.java
+++ b/src/com/android/server/telecom/ConnectionServiceWrapper.java
@@ -636,7 +636,7 @@
     }
 
     private final Adapter mAdapter = new Adapter();
-    private final CallIdMapper mCallIdMapper = new CallIdMapper();
+    private final CallIdMapper mCallIdMapper = new CallIdMapper(Call::getConnectionId);
     private final Map<String, CreateConnectionResponse> mPendingResponses = new HashMap<>();
 
     private Binder2 mBinder = new Binder2();
@@ -684,6 +684,17 @@
         }
     }
 
+    /** See {@link IConnectionService#removeConnectionServiceAdapter}. */
+    private void removeConnectionServiceAdapter(IConnectionServiceAdapter adapter) {
+        if (isServiceValid("removeConnectionServiceAdapter")) {
+            try {
+                logOutgoing("removeConnectionServiceAdapter %s", adapter);
+                mServiceInterface.removeConnectionServiceAdapter(adapter);
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
     /**
      * Creates a new connection for a new outgoing call or to attach to an existing incoming call.
      */
@@ -1003,18 +1014,23 @@
     /** {@inheritDoc} */
     @Override
     protected void setServiceInterface(IBinder binder) {
-        if (binder == null) {
-            // We have lost our service connection. Notify the world that this service is done.
-            // We must notify the adapter before CallsManager. The adapter will force any pending
-            // outgoing calls to try the next service. This needs to happen before CallsManager
-            // tries to clean up any calls still associated with this service.
-            handleConnectionServiceDeath();
-            mCallsManager.handleConnectionServiceDeath(this);
-            mServiceInterface = null;
-        } else {
-            mServiceInterface = IConnectionService.Stub.asInterface(binder);
-            addConnectionServiceAdapter(mAdapter);
-        }
+        mServiceInterface = IConnectionService.Stub.asInterface(binder);
+        Log.v(this, "Adding Connection Service Adapter.");
+        addConnectionServiceAdapter(mAdapter);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void removeServiceInterface() {
+        Log.v(this, "Removing Connection Service Adapter.");
+        removeConnectionServiceAdapter(mAdapter);
+        // We have lost our service connection. Notify the world that this service is done.
+        // We must notify the adapter before CallsManager. The adapter will force any pending
+        // outgoing calls to try the next service. This needs to happen before CallsManager
+        // tries to clean up any calls still associated with this service.
+        handleConnectionServiceDeath();
+        mCallsManager.handleConnectionServiceDeath(this);
+        mServiceInterface = null;
     }
 
     private void handleCreateConnectionComplete(
diff --git a/src/com/android/server/telecom/CreateConnectionProcessor.java b/src/com/android/server/telecom/CreateConnectionProcessor.java
index c89a896..22ec3c6 100644
--- a/src/com/android/server/telecom/CreateConnectionProcessor.java
+++ b/src/com/android/server/telecom/CreateConnectionProcessor.java
@@ -96,6 +96,7 @@
     private final Context mContext;
     private CreateConnectionTimeout mTimeout;
     private ConnectionServiceWrapper mService;
+    private int mConnectionAttempt;
 
     @VisibleForTesting
     public CreateConnectionProcessor(
@@ -107,6 +108,7 @@
         mCallResponse = response;
         mPhoneAccountRegistrar = phoneAccountRegistrar;
         mContext = context;
+        mConnectionAttempt = 0;
     }
 
     boolean isProcessingComplete() {
@@ -117,6 +119,10 @@
         return mTimeout != null && mTimeout.isCallTimedOut();
     }
 
+    public int getConnectionAttempt() {
+        return mConnectionAttempt;
+    }
+
     @VisibleForTesting
     public void process() {
         Log.v(this, "process");
@@ -200,6 +206,7 @@
                 Log.i(this, "Found no connection service for attempt %s", attempt);
                 attemptNextPhoneAccount();
             } else {
+                mConnectionAttempt++;
                 mCall.setConnectionManagerPhoneAccount(attempt.connectionManagerPhoneAccount);
                 mCall.setTargetPhoneAccount(attempt.targetPhoneAccount);
                 mCall.setConnectionService(mService);
diff --git a/src/com/android/server/telecom/InCallController.java b/src/com/android/server/telecom/InCallController.java
index 9bca355..2a43158 100644
--- a/src/com/android/server/telecom/InCallController.java
+++ b/src/com/android/server/telecom/InCallController.java
@@ -589,7 +589,7 @@
      */
     private ComponentName mInCallUIComponentName;
 
-    private final CallIdMapper mCallIdMapper = new CallIdMapper();
+    private final CallIdMapper mCallIdMapper = new CallIdMapper(Call::getId);
 
     /** The {@link ComponentName} of the default InCall UI. */
     private final ComponentName mSystemInCallComponentName;
diff --git a/src/com/android/server/telecom/PhoneNumberUtilsAdapter.java b/src/com/android/server/telecom/PhoneNumberUtilsAdapter.java
index 41284cb..8e59a64 100644
--- a/src/com/android/server/telecom/PhoneNumberUtilsAdapter.java
+++ b/src/com/android/server/telecom/PhoneNumberUtilsAdapter.java
@@ -24,6 +24,7 @@
  * refactoring.
  */
 public interface PhoneNumberUtilsAdapter {
+    boolean isLocalEmergencyNumber(Context context, String number);
     boolean isPotentialLocalEmergencyNumber(Context context, String number);
     boolean isUriNumber(String number);
     String getNumberFromIntent(Intent intent, Context context);
diff --git a/src/com/android/server/telecom/PhoneNumberUtilsAdapterImpl.java b/src/com/android/server/telecom/PhoneNumberUtilsAdapterImpl.java
index 640d814..fa316a5 100644
--- a/src/com/android/server/telecom/PhoneNumberUtilsAdapterImpl.java
+++ b/src/com/android/server/telecom/PhoneNumberUtilsAdapterImpl.java
@@ -22,6 +22,11 @@
 
 public class PhoneNumberUtilsAdapterImpl implements PhoneNumberUtilsAdapter {
     @Override
+    public boolean isLocalEmergencyNumber(Context context, String number) {
+            return PhoneNumberUtils.isLocalEmergencyNumber(context, number);
+    }
+
+    @Override
     public boolean isPotentialLocalEmergencyNumber(Context context, String number) {
         return PhoneNumberUtils.isPotentialLocalEmergencyNumber(context, number);
     }
diff --git a/src/com/android/server/telecom/ServiceBinder.java b/src/com/android/server/telecom/ServiceBinder.java
index 18d6581..9a0f7b4 100644
--- a/src/com/android/server/telecom/ServiceBinder.java
+++ b/src/com/android/server/telecom/ServiceBinder.java
@@ -336,22 +336,28 @@
      */
     private void setBinder(IBinder binder) {
         if (mBinder != binder) {
-            mBinder = binder;
-
-            setServiceInterface(binder);
-
             if (binder == null) {
+                removeServiceInterface();
+                mBinder = null;
                 for (Listener l : mListeners) {
                     l.onUnbind(this);
                 }
+            } else {
+                mBinder = binder;
+                setServiceInterface(binder);
             }
         }
     }
 
     /**
-     * Sets the service interface after the service is bound or unbound.
+     * Sets the service interface after the service is bound.
      *
-     * @param binder The actual bound service implementation.
+     * @param binder The new binder interface that is being set.
      */
     protected abstract void setServiceInterface(IBinder binder);
+
+    /**
+     * Removes the service interface before the service is unbound.
+     */
+    protected abstract void removeServiceInterface();
 }
diff --git a/src/com/android/server/telecom/TelecomSystem.java b/src/com/android/server/telecom/TelecomSystem.java
index 304f13b..67321d4 100644
--- a/src/com/android/server/telecom/TelecomSystem.java
+++ b/src/com/android/server/telecom/TelecomSystem.java
@@ -154,7 +154,8 @@
             BluetoothPhoneServiceImplFactory
                     bluetoothPhoneServiceImplFactory,
             Timeouts.Adapter timeoutsAdapter,
-            AsyncRingtonePlayer asyncRingtonePlayer) {
+            AsyncRingtonePlayer asyncRingtonePlayer,
+            PhoneNumberUtilsAdapter phoneNumberUtilsAdapter) {
         mContext = context.getApplicationContext();
         Log.setContext(mContext);
         Log.initMd5Sum();
@@ -196,7 +197,8 @@
                 systemStateProvider,
                 defaultDialerAdapter,
                 timeoutsAdapter,
-                asyncRingtonePlayer);
+                asyncRingtonePlayer,
+                phoneNumberUtilsAdapter);
 
         mRespondViaSmsManager = new RespondViaSmsManager(mCallsManager, mLock);
         mCallsManager.setRespondViaSmsManager(mRespondViaSmsManager);
diff --git a/src/com/android/server/telecom/components/TelecomService.java b/src/com/android/server/telecom/components/TelecomService.java
index bef8453..a5790fd 100644
--- a/src/com/android/server/telecom/components/TelecomService.java
+++ b/src/com/android/server/telecom/components/TelecomService.java
@@ -36,6 +36,7 @@
 import com.android.server.telecom.InCallWakeLockControllerFactory;
 import com.android.server.telecom.CallAudioManager;
 import com.android.server.telecom.PhoneAccountRegistrar;
+import com.android.server.telecom.PhoneNumberUtilsAdapterImpl;
 import com.android.server.telecom.ProximitySensorManagerFactory;
 import com.android.server.telecom.InCallWakeLockController;
 import com.android.server.telecom.Log;
@@ -148,7 +149,8 @@
                                 }
                             },
                             new Timeouts.Adapter(),
-                            new AsyncRingtonePlayer()
+                            new AsyncRingtonePlayer(),
+                            new PhoneNumberUtilsAdapterImpl()
                     ));
         }
         if (BluetoothAdapter.getDefaultAdapter() != null) {
diff --git a/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java b/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java
index f9d2793..1e97695 100644
--- a/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java
+++ b/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java
@@ -600,7 +600,7 @@
                                 // Convert the data to a call object
                                 Call call = new Call(Call.CALL_ID_UNKNOWN, mContext, callsManager,
                                         lock, null, contactsAsyncHelper,
-                                        callerInfoAsyncQueryFactory, null, null, null, null,
+                                        callerInfoAsyncQueryFactory, null, null, null, null, null,
                                         Call.CALL_DIRECTION_INCOMING, false, false);
                                 call.setDisconnectCause(
                                         new DisconnectCause(DisconnectCause.MISSED));
diff --git a/tests/src/com/android/server/telecom/tests/BasicCallTests.java b/tests/src/com/android/server/telecom/tests/BasicCallTests.java
index 9211d05..ceec91b 100644
--- a/tests/src/com/android/server/telecom/tests/BasicCallTests.java
+++ b/tests/src/com/android/server/telecom/tests/BasicCallTests.java
@@ -122,7 +122,7 @@
         telecomManager.acceptRingingCall();
 
         verify(mConnectionServiceFixtureA.getTestDouble(), timeout(TEST_TIMEOUT))
-                .answer(ids.mCallId);
+                .answer(ids.mConnectionId);
         mConnectionServiceFixtureA.sendSetActive(ids.mConnectionId);
 
         mInCallServiceFixtureX.mInCallAdapter.disconnectCall(ids.mCallId);
@@ -150,7 +150,7 @@
 
         // Answer video API should be called
         verify(mConnectionServiceFixtureA.getTestDouble(), timeout(TEST_TIMEOUT))
-                .answerVideo(eq(ids.mCallId), eq(VideoProfile.STATE_BIDIRECTIONAL));
+                .answerVideo(eq(ids.mConnectionId), eq(VideoProfile.STATE_BIDIRECTIONAL));
         mConnectionServiceFixtureA.sendSetActive(ids.mConnectionId);
 
         mInCallServiceFixtureX.mInCallAdapter.disconnectCall(ids.mCallId);
@@ -177,7 +177,7 @@
 
         // The generic answer method on the ConnectionService is used to answer audio-only calls.
         verify(mConnectionServiceFixtureA.getTestDouble(), timeout(TEST_TIMEOUT))
-                .answer(eq(ids.mCallId));
+                .answer(eq(ids.mConnectionId));
         mConnectionServiceFixtureA.sendSetActive(ids.mConnectionId);
 
         mInCallServiceFixtureX.mInCallAdapter.disconnectCall(ids.mCallId);
@@ -205,7 +205,7 @@
 
         // Answer video API should be called
         verify(mConnectionServiceFixtureA.getTestDouble(), timeout(TEST_TIMEOUT))
-                .answerVideo(eq(ids.mCallId), eq(VideoProfile.STATE_BIDIRECTIONAL));
+                .answerVideo(eq(ids.mConnectionId), eq(VideoProfile.STATE_BIDIRECTIONAL));
         mConnectionServiceFixtureA.sendSetActive(ids.mConnectionId);
         mInCallServiceFixtureX.mInCallAdapter.disconnectCall(ids.mCallId);
     }
@@ -643,7 +643,7 @@
 
         mInCallServiceFixtureX.mInCallAdapter.sendCallEvent(ids.mCallId, TEST_EVENT, null);
         verify(mConnectionServiceFixtureA.getTestDouble(), timeout(TEST_TIMEOUT))
-                .sendCallEvent(ids.mCallId, TEST_EVENT, null);
+                .sendCallEvent(ids.mConnectionId, TEST_EVENT, null);
     }
 
     /**
@@ -664,7 +664,7 @@
         mInCallServiceFixtureX.mInCallAdapter.sendCallEvent(ids.mCallId, TEST_EVENT,
                 testBundle);
         verify(mConnectionServiceFixtureA.getTestDouble(), timeout(TEST_TIMEOUT))
-                .sendCallEvent(eq(ids.mCallId), eq(TEST_EVENT),
+                .sendCallEvent(eq(ids.mConnectionId), eq(TEST_EVENT),
                         bundleArgumentCaptor.capture());
         assert (bundleArgumentCaptor.getValue().containsKey(TEST_BUNDLE_KEY));
     }
@@ -762,7 +762,7 @@
         // Attempt to pull the call and verify the API call makes it through
         mInCallServiceFixtureX.mInCallAdapter.pullExternalCall(ids.mCallId);
         verify(mConnectionServiceFixtureA.getTestDouble(), timeout(TEST_TIMEOUT))
-                .pullExternalCall(ids.mCallId);
+                .pullExternalCall(ids.mConnectionId);
     }
 
     /**
@@ -786,7 +786,7 @@
         // Attempt to pull the call and verify the API call makes it through
         mInCallServiceFixtureX.mInCallAdapter.pullExternalCall(ids.mCallId);
         verify(mConnectionServiceFixtureA.getTestDouble(), timeout(TEST_TIMEOUT).never())
-                .pullExternalCall(ids.mCallId);
+                .pullExternalCall(ids.mConnectionId);
     }
 
     public void testMergeFailedAndNotifyInCallUi() throws Exception {
@@ -818,4 +818,24 @@
         verify(mInCallServiceFixtureY.getTestDouble(), timeout(TEST_TIMEOUT)).onConnectionEvent(
                 eq(testCall2.mCallId), eq(Connection.EVENT_CALL_MERGE_FAILED), any(Bundle.class));
     }
+
+    @LargeTest
+    public void testEmergencyCallFailMoveToSecondSim() throws Exception {
+        IdPair ids = startAndMakeDialingEmergencyCall("650-555-1212",
+                mPhoneAccountE0.getAccountHandle(), mConnectionServiceFixtureA);
+        assertEquals(Call.STATE_DIALING, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
+        assertEquals(Call.STATE_DIALING, mInCallServiceFixtureY.getCall(ids.mCallId).getState());
+
+        // The Emergency Call has failed on the default SIM with an ERROR Disconnect Cause. Retry
+        // with the other SIM PhoneAccount
+        IdPair newIds = triggerEmergencyRedial(mPhoneAccountE1.getAccountHandle(),
+                mConnectionServiceFixtureA, ids);
+
+        // Call should be active on the E1 PhoneAccount
+        mConnectionServiceFixtureA.sendSetActive(newIds.mConnectionId);
+        assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureX.getCall(newIds.mCallId).getState());
+        assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureY.getCall(newIds.mCallId).getState());
+        assertEquals(mInCallServiceFixtureX.getCall(ids.mCallId).getAccountHandle(),
+                mPhoneAccountE1.getAccountHandle());
+    }
 }
diff --git a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
index fba4fb0..5ed941e 100644
--- a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
+++ b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
@@ -519,6 +519,9 @@
         for (ComponentName componentName : mComponentNamesByAction.get(intent.getAction())) {
             ResolveInfo resolveInfo = new ResolveInfo();
             resolveInfo.serviceInfo = mServiceInfoByComponentName.get(componentName);
+            resolveInfo.serviceInfo.metaData = new Bundle();
+            resolveInfo.serviceInfo.metaData.putBoolean(
+                    TelecomManager.METADATA_INCLUDE_EXTERNAL_CALLS, true);
             result.add(resolveInfo);
         }
         return result;
diff --git a/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java b/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
index 03ec2b2..962ab10 100644
--- a/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
+++ b/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
@@ -147,7 +147,7 @@
             capabilities |= CAPABILITY_HOLD;
             setVideoState(videoState);
             setConnectionCapabilities(capabilities);
-            setActive();
+            setDialing();
             setAddress(address, TelecomManager.PRESENTATION_ALLOWED);
         }
 
diff --git a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
index 7081aed..0173ed8 100644
--- a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
+++ b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
@@ -90,7 +90,7 @@
 
     private UserHandle mUserHandle = UserHandle.of(CURRENT_USER_ID);
     private InCallController mInCallController;
-    private TelecomSystem.SyncRoot mLock;
+    private TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() {};
 
     @Override
     public void setUp() throws Exception {
@@ -386,36 +386,6 @@
         assertEquals(DEF_CLASS, bindIntent.getComponent().getClassName());
     }
 
-    /**
-     * Ensures that the {@link InCallController} will not bind to an incall service for an external
-     * call if the incall service does not support it.
-     */
-    @MediumTest
-    public void testBindToService_ExcludeExternal() throws Exception {
-        setupMocks(true /* isExternalCall */);
-        setupMockPackageManager(true /* default */, true /* system */, false /* external calls */);
-        mInCallController.bindToServices(mMockCall);
-
-        // Query for the different InCallServices
-        ArgumentCaptor<Intent> queryIntentCaptor = ArgumentCaptor.forClass(Intent.class);
-        verify(mMockPackageManager, times(4)).queryIntentServicesAsUser(
-                queryIntentCaptor.capture(),
-                eq(PackageManager.GET_META_DATA), eq(CURRENT_USER_ID));
-
-        // Verify call for default dialer InCallService
-        assertEquals(DEF_PKG, queryIntentCaptor.getAllValues().get(0).getPackage());
-        // Verify call for car-mode InCallService
-        assertEquals(null, queryIntentCaptor.getAllValues().get(1).getPackage());
-        // Verify call for non-UI InCallServices
-        assertEquals(null, queryIntentCaptor.getAllValues().get(2).getPackage());
-
-        verify(mMockContext, never()).bindServiceAsUser(
-                any(Intent.class),
-                any(ServiceConnection.class),
-                eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE),
-                eq(UserHandle.CURRENT));
-    }
-
     private void setupMocks(boolean isExternalCall) {
         when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
diff --git a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
index fc8e1a5..9c47d3f 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
@@ -50,6 +50,7 @@
 import android.provider.BlockedNumberContract;
 import android.telecom.Call;
 import android.telecom.ConnectionRequest;
+import android.telecom.DisconnectCause;
 import android.telecom.ParcelableCall;
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
@@ -70,6 +71,8 @@
 import com.android.server.telecom.InCallWakeLockControllerFactory;
 import com.android.server.telecom.MissedCallNotifier;
 import com.android.server.telecom.PhoneAccountRegistrar;
+import com.android.server.telecom.PhoneNumberUtilsAdapter;
+import com.android.server.telecom.PhoneNumberUtilsAdapterImpl;
 import com.android.server.telecom.ProximitySensorManager;
 import com.android.server.telecom.ProximitySensorManagerFactory;
 import com.android.server.telecom.TelecomSystem;
@@ -145,6 +148,19 @@
     }
 
     MissedCallNotifierFakeImpl mMissedCallNotifier = new MissedCallNotifierFakeImpl();
+    private class EmergencyNumberUtilsAdapter extends PhoneNumberUtilsAdapterImpl {
+
+        @Override
+        public boolean isLocalEmergencyNumber(Context context, String number) {
+            return mIsEmergencyCall;
+        }
+
+        @Override
+        public boolean isPotentialLocalEmergencyNumber(Context context, String number) {
+            return mIsEmergencyCall;
+        }
+    }
+    PhoneNumberUtilsAdapter mPhoneNumberUtilsAdapter = new EmergencyNumberUtilsAdapter();
     @Mock HeadsetMediaButton mHeadsetMediaButton;
     @Mock ProximitySensorManager mProximitySensorManager;
     @Mock InCallWakeLockController mInCallWakeLockController;
@@ -205,6 +221,31 @@
                             PhoneAccount.CAPABILITY_CALL_PROVIDER |
                             PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)
                     .build();
+    final PhoneAccount mPhoneAccountE0 =
+            PhoneAccount.builder(
+                    new PhoneAccountHandle(
+                            mConnectionServiceComponentNameA,
+                            "id E 0"),
+                    "Phone account service E ID 0")
+                    .addSupportedUriScheme("tel")
+                    .setCapabilities(
+                            PhoneAccount.CAPABILITY_CALL_PROVIDER |
+                                    PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION |
+                                    PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS)
+                    .build();
+
+    final PhoneAccount mPhoneAccountE1 =
+            PhoneAccount.builder(
+                    new PhoneAccountHandle(
+                            mConnectionServiceComponentNameA,
+                            "id E 1"),
+                    "Phone account service E ID 1")
+                    .addSupportedUriScheme("tel")
+                    .setCapabilities(
+                            PhoneAccount.CAPABILITY_CALL_PROVIDER |
+                                    PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION |
+                                    PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS)
+                    .build();
 
     ConnectionServiceFixture mConnectionServiceFixtureA;
     ConnectionServiceFixture mConnectionServiceFixtureB;
@@ -220,6 +261,8 @@
 
     private int mNumOutgoingCallsMade;
 
+    private boolean mIsEmergencyCall;
+
     class IdPair {
         final String mConnectionId;
         final String mCallId;
@@ -238,6 +281,8 @@
 
         mNumOutgoingCallsMade = 0;
 
+        mIsEmergencyCall = false;
+
         // First set up information about the In-Call services in the mock Context, since
         // Telecom will search for these as soon as it is instantiated
         setupInCallServices();
@@ -327,7 +372,8 @@
                     }
                 },
                 mTimeoutsAdapter,
-                mAsyncRingtonePlayer);
+                mAsyncRingtonePlayer,
+                mPhoneNumberUtilsAdapter);
 
         mComponentContextFixture.setTelecomManager(new TelecomManager(
                 mComponentContextFixture.getTestDouble(),
@@ -357,6 +403,8 @@
         mTelecomSystem.getPhoneAccountRegistrar().registerPhoneAccount(mPhoneAccountA0);
         mTelecomSystem.getPhoneAccountRegistrar().registerPhoneAccount(mPhoneAccountA1);
         mTelecomSystem.getPhoneAccountRegistrar().registerPhoneAccount(mPhoneAccountB0);
+        mTelecomSystem.getPhoneAccountRegistrar().registerPhoneAccount(mPhoneAccountE0);
+        mTelecomSystem.getPhoneAccountRegistrar().registerPhoneAccount(mPhoneAccountE1);
 
         mTelecomSystem.getPhoneAccountRegistrar().setUserSelectedOutgoingPhoneAccount(
                 mPhoneAccountA0.getAccountHandle(), Process.myUserHandle());
@@ -460,10 +508,46 @@
                 phoneAccountHandle, connectionServiceFixture);
     }
 
-    protected String startOutgoingPhoneCallPendingCreateConnection(String number,
+    protected IdPair triggerEmergencyRedial(PhoneAccountHandle phoneAccountHandle,
+            ConnectionServiceFixture connectionServiceFixture, IdPair emergencyIds)
+            throws Exception {
+        int startingNumConnections = connectionServiceFixture.mConnectionById.size();
+        int startingNumCalls = mInCallServiceFixtureX.mCallById.size();
+
+        // Send the message to disconnect the Emergency call due to an error.
+        // CreateConnectionProcessor should now try the second SIM account
+        connectionServiceFixture.sendSetDisconnected(emergencyIds.mConnectionId,
+                DisconnectCause.ERROR);
+        waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
+        assertEquals(Call.STATE_DIALING, mInCallServiceFixtureX.getCall(
+                emergencyIds.mCallId).getState());
+        assertEquals(Call.STATE_DIALING, mInCallServiceFixtureY.getCall(
+                emergencyIds.mCallId).getState());
+
+        return redialingCallCreateConnectionComplete(startingNumConnections, startingNumCalls,
+                phoneAccountHandle, connectionServiceFixture);
+    }
+
+    protected IdPair startOutgoingEmergencyCall(String number,
             PhoneAccountHandle phoneAccountHandle,
             ConnectionServiceFixture connectionServiceFixture, UserHandle initiatingUser,
             int videoState) throws Exception {
+        int startingNumConnections = connectionServiceFixture.mConnectionById.size();
+        int startingNumCalls = mInCallServiceFixtureX.mCallById.size();
+
+        mIsEmergencyCall = true;
+        // Call will not use the ordered broadcaster, since it is an Emergency Call
+        startOutgoingPhoneCallWaitForBroadcaster(number, phoneAccountHandle,
+                connectionServiceFixture, initiatingUser, videoState, true /*isEmergency*/);
+
+        return outgoingCallCreateConnectionComplete(startingNumConnections, startingNumCalls,
+                phoneAccountHandle, connectionServiceFixture);
+    }
+
+    protected void startOutgoingPhoneCallWaitForBroadcaster(String number,
+            PhoneAccountHandle phoneAccountHandle,
+            ConnectionServiceFixture connectionServiceFixture, UserHandle initiatingUser,
+            int videoState, boolean isEmergency) throws Exception {
         reset(connectionServiceFixture.getTestDouble(), mInCallServiceFixtureX.getTestDouble(),
                 mInCallServiceFixtureY.getTestDouble());
 
@@ -479,7 +563,11 @@
         Intent actionCallIntent = new Intent();
         actionCallIntent.setData(Uri.parse("tel:" + number));
         actionCallIntent.putExtra(Intent.EXTRA_PHONE_NUMBER, number);
-        actionCallIntent.setAction(Intent.ACTION_CALL);
+        if(isEmergency) {
+            actionCallIntent.setAction(Intent.ACTION_CALL_EMERGENCY);
+        } else {
+            actionCallIntent.setAction(Intent.ACTION_CALL);
+        }
         if (phoneAccountHandle != null) {
             actionCallIntent.putExtra(
                     TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
@@ -507,6 +595,14 @@
                     .setInCallAdapter(
                             any(IInCallAdapter.class));
         }
+    }
+
+    protected String startOutgoingPhoneCallPendingCreateConnection(String number,
+            PhoneAccountHandle phoneAccountHandle,
+            ConnectionServiceFixture connectionServiceFixture, UserHandle initiatingUser,
+            int videoState) throws Exception {
+        startOutgoingPhoneCallWaitForBroadcaster(number,phoneAccountHandle,
+                connectionServiceFixture, initiatingUser, videoState, false /*isEmergency*/);
 
         ArgumentCaptor<Intent> newOutgoingCallIntent =
                 ArgumentCaptor.forClass(Intent.class);
@@ -538,6 +634,30 @@
         return mInCallServiceFixtureX.mLatestCallId;
     }
 
+    // When Telecom is redialing due to an error, we need to make sure the number of connections
+    // increase, but not the number of Calls in the InCallService.
+    protected IdPair redialingCallCreateConnectionComplete(int startingNumConnections,
+            int startingNumCalls, PhoneAccountHandle phoneAccountHandle,
+            ConnectionServiceFixture connectionServiceFixture) throws Exception {
+
+        assertEquals(startingNumConnections + 1, connectionServiceFixture.mConnectionById.size());
+
+        verify(connectionServiceFixture.getTestDouble())
+                .createConnection(eq(phoneAccountHandle), anyString(), any(ConnectionRequest.class),
+                        eq(false)/*isIncoming*/, anyBoolean());
+        // Wait for handleCreateConnectionComplete
+        waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
+
+        // Make sure the number of registered InCallService Calls stays the same.
+        assertEquals(startingNumCalls, mInCallServiceFixtureX.mCallById.size());
+        assertEquals(startingNumCalls, mInCallServiceFixtureY.mCallById.size());
+
+        assertEquals(mInCallServiceFixtureX.mLatestCallId, mInCallServiceFixtureY.mLatestCallId);
+
+        return new IdPair(connectionServiceFixture.mLatestConnectionId,
+                mInCallServiceFixtureX.mLatestCallId);
+    }
+
     protected IdPair outgoingCallCreateConnectionComplete(int startingNumConnections,
             int startingNumCalls, PhoneAccountHandle phoneAccountHandle,
             ConnectionServiceFixture connectionServiceFixture) throws Exception {
@@ -736,6 +856,20 @@
         return ids;
     }
 
+    protected IdPair startAndMakeDialingEmergencyCall(
+            String number,
+            PhoneAccountHandle phoneAccountHandle,
+            ConnectionServiceFixture connectionServiceFixture) throws Exception {
+        IdPair ids = startOutgoingEmergencyCall(number, phoneAccountHandle,
+                connectionServiceFixture, Process.myUserHandle(), VideoProfile.STATE_AUDIO_ONLY);
+
+        connectionServiceFixture.sendSetDialing(ids.mConnectionId);
+        assertEquals(Call.STATE_DIALING, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
+        assertEquals(Call.STATE_DIALING, mInCallServiceFixtureY.getCall(ids.mCallId).getState());
+
+        return ids;
+    }
+
     protected static void assertTrueWithTimeout(Predicate<Void> predicate) {
         int elapsed = 0;
         while (elapsed < TEST_TIMEOUT) {