Merge "send call events directly to InCallSerivces (for transactional calls)" into udc-dev
diff --git a/res/values-as/strings.xml b/res/values-as/strings.xml
index 9226599..75d3416 100644
--- a/res/values-as/strings.xml
+++ b/res/values-as/strings.xml
@@ -108,7 +108,7 @@
     <string name="phone_settings_call_blocking_txt" msgid="7311523114822507178">"কল অৱৰোধ"</string>
     <string name="phone_settings_number_not_in_contact_txt" msgid="2602249106007265757">"আপোনাৰ সর্ম্পকসূচীত নথকা"</string>
     <string name="phone_settings_number_not_in_contact_summary_txt" msgid="963327038085718969">"আপোনাৰ সর্ম্পকসূচীত নথকা নম্বৰ অৱৰোধ কৰক"</string>
-    <string name="phone_settings_private_num_txt" msgid="6339272760338475619">"ব্য়ক্তিগত"</string>
+    <string name="phone_settings_private_num_txt" msgid="6339272760338475619">"ব্যক্তিগত"</string>
     <string name="phone_settings_private_num_summary_txt" msgid="6755758240544021037">"যিসকল কল কৰোঁতাই তেওঁলোকৰ নম্বৰ প্ৰকাশ নকৰে তেওঁলোকক অৱৰোধ কৰক"</string>
     <string name="phone_settings_payphone_txt" msgid="5003987966052543965">"পে\'ফ\'ন"</string>
     <string name="phone_settings_payphone_summary_txt" msgid="3936631076065563665">"পে\'ফ\'নৰ পৰা অহা কল অৱৰোধ কৰক"</string>
diff --git a/res/values-kk/strings.xml b/res/values-kk/strings.xml
index 7c07654..29fdc4a 100644
--- a/res/values-kk/strings.xml
+++ b/res/values-kk/strings.xml
@@ -75,10 +75,10 @@
     <string name="blocked_numbers_butter_bar_title" msgid="582982373755950791">"Тыйым уақытша алынды"</string>
     <string name="blocked_numbers_butter_bar_body" msgid="1261213114919301485">"Төтенше жағдай нөмірін терген немесе мәтіндік хабар жіберген соң, төтенше жағдай қызметтері сізге хабарласа алуы үшін тыйым алынады."</string>
     <string name="blocked_numbers_butter_bar_button" msgid="2704456308072489793">"Қазір қайта қосу"</string>
-    <string name="blocked_numbers_number_blocked_message" msgid="4314736791180919167">"<xliff:g id="BLOCKED_NUMBER">%1$s</xliff:g> бөгелген"</string>
+    <string name="blocked_numbers_number_blocked_message" msgid="4314736791180919167">"<xliff:g id="BLOCKED_NUMBER">%1$s</xliff:g> блокталған"</string>
     <string name="blocked_numbers_number_unblocked_message" msgid="2933071624674945601">"<xliff:g id="UNBLOCKED_NUMBER">%1$s</xliff:g> бөгеуден шығарылды"</string>
     <string name="blocked_numbers_block_emergency_number_message" msgid="4198550501500893890">"Жедел қызмет нөмірін бөгеу мүмкін емес."</string>
-    <string name="blocked_numbers_number_already_blocked_message" msgid="2301270825735665458">"<xliff:g id="BLOCKED_NUMBER">%1$s</xliff:g> бұрыннан бөгелген."</string>
+    <string name="blocked_numbers_number_already_blocked_message" msgid="2301270825735665458">"<xliff:g id="BLOCKED_NUMBER">%1$s</xliff:g> бұрыннан блокталған."</string>
     <string name="toast_personal_call_msg" msgid="5817631570381795610">"Қоңырау шалу үшін жеке нөмір тергішті пайдалану"</string>
     <string name="notification_incoming_call" msgid="1233481138362230894">"<xliff:g id="CALL_VIA">%1$s</xliff:g> қоңырауы: <xliff:g id="CALL_FROM">%2$s</xliff:g>"</string>
     <string name="notification_incoming_video_call" msgid="5795968314037063900">"<xliff:g id="CALL_VIA">%1$s</xliff:g> бейне қоңырауы: <xliff:g id="CALL_FROM">%2$s</xliff:g>"</string>
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index 4b67e05..25890d9 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -414,6 +414,16 @@
 
     private boolean mIsEmergencyCall;
 
+    /**
+     * Flag indicating if ECBM is active for the target phone account. This only applies to MT calls
+     * in the scenario of work profiles (when the profile is paused and the user has only registered
+     * a work sim). Normally, MT calls made to the work sim should be rejected when the work apps
+     * are paused. However, when the admin makes a MO ecall, ECBM should be enabled for that sim to
+     * allow non-emergency MT calls. MO calls don't apply because the phone account would be
+     * rejected from selection if the owner is not placing the call.
+     */
+    private boolean mIsInECBM;
+
     // The Call is considered an emergency call for testing, but will not actually connect to
     // emergency services.
     private boolean mIsTestEmergencyCall;
@@ -1592,6 +1602,21 @@
     }
 
     /**
+     * @return {@code true} if the target phone account is in ECBM.
+     */
+    public boolean isInECBM() {
+        return mIsInECBM;
+    }
+
+    /**
+     * Set if the target phone account is in ECBM.
+     * @param isInEcbm {@code true} if target phone account is in ECBM, {@code false} otherwise.
+     */
+    public void setIsInECBM(boolean isInECBM) {
+        mIsInECBM = isInECBM;
+    }
+
+    /**
      * @return {@code true} if the network has identified this call as an emergency call.
      */
     public boolean isNetworkIdentifiedEmergencyCall() {
@@ -1682,6 +1707,11 @@
     public void setTargetPhoneAccount(PhoneAccountHandle accountHandle) {
         if (!Objects.equals(mTargetPhoneAccountHandle, accountHandle)) {
             mTargetPhoneAccountHandle = accountHandle;
+            // Update the last MO emergency call in the helper, if applicable.
+            if (isEmergencyCall() && !isIncoming()) {
+                mCallsManager.getEmergencyCallHelper().setLastOutgoingEmergencyCallPAH(
+                        accountHandle);
+            }
             for (Listener l : mListeners) {
                 l.onTargetPhoneAccountChanged(this);
             }
@@ -4584,7 +4614,7 @@
             throw new UnsupportedOperationException(
                     "Can't streaming call created by non voip apps");
         }
-
+        Log.addEvent(this, LogUtils.Events.START_STREAMING);
         synchronized (mLock) {
             if (mIsStreaming) {
                 // ignore
@@ -4604,7 +4634,7 @@
                 // ignore
                 return;
             }
-
+            Log.addEvent(this, LogUtils.Events.STOP_STREAMING);
             mIsStreaming = false;
             for (Listener listener : mListeners) {
                 listener.onCallStreamingStateChanged(this, false /** isStreaming */);
diff --git a/src/com/android/server/telecom/CallAudioManager.java b/src/com/android/server/telecom/CallAudioManager.java
index 8cac314..38e6b00 100644
--- a/src/com/android/server/telecom/CallAudioManager.java
+++ b/src/com/android/server/telecom/CallAudioManager.java
@@ -146,6 +146,9 @@
 
     @Override
     public void onCallRemoved(Call call) {
+        if (mStreamingCall == call) {
+            mStreamingCall = null;
+        }
         if (shouldIgnoreCallForAudio(call)) {
             return; // Don't do audio handling for calls in a conference, or external calls.
         }
@@ -238,7 +241,7 @@
                         makeArgsForModeStateMachine());
             } else {
                 Log.w(LOG_TAG, "Unexpected streaming call request for call %s while call "
-                        + "s is streaming.", call.getId(), mStreamingCall.getId());
+                        + "%s is streaming.", call.getId(), mStreamingCall.getId());
             }
         } else {
             if (mStreamingCall == call) {
diff --git a/src/com/android/server/telecom/CallAudioModeStateMachine.java b/src/com/android/server/telecom/CallAudioModeStateMachine.java
index 3ced36d..9ad9094 100644
--- a/src/com/android/server/telecom/CallAudioModeStateMachine.java
+++ b/src/com/android/server/telecom/CallAudioModeStateMachine.java
@@ -665,7 +665,9 @@
         @Override
         public void enter() {
             Log.i(LOG_TAG, "Audio focus entering streaming state");
-            mAudioManager.setMode(AudioManager.MODE_CALL_REDIRECT);
+            mLocalLog.log("Enter Streaming");
+            mLocalLog.log("Mode MODE_COMMUNICATION_REDIRECT");
+            mAudioManager.setMode(AudioManager.MODE_COMMUNICATION_REDIRECT);
             mMostRecentMode = AudioManager.MODE_NORMAL;
             mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.ACTIVE_FOCUS);
             mCallAudioManager.getCallAudioRouteStateMachine().sendMessageWithSessionInfo(
@@ -685,7 +687,8 @@
             MessageArgs args = (MessageArgs) msg.obj;
             switch (msg.what) {
                 case NO_MORE_ACTIVE_OR_DIALING_CALLS:
-                    // Do nothing.
+                    // Switch to either ringing, holding, or inactive
+                    transitionTo(calculateProperStateFromArgs(args));
                     return HANDLED;
                 case NO_MORE_RINGING_CALLS:
                     // Do nothing.
diff --git a/src/com/android/server/telecom/CallEndpointController.java b/src/com/android/server/telecom/CallEndpointController.java
index 60827e2..82164b3 100644
--- a/src/com/android/server/telecom/CallEndpointController.java
+++ b/src/com/android/server/telecom/CallEndpointController.java
@@ -25,7 +25,9 @@
 import android.telecom.CallEndpoint;
 import android.telecom.CallEndpointException;
 import android.telecom.Log;
+
 import com.android.internal.annotations.VisibleForTesting;
+
 import java.util.HashMap;
 import java.util.Map;
 import java.util.HashSet;
@@ -96,6 +98,12 @@
             return;
         }
 
+        if (isCurrentEndpointRequestedEndpoint(route, bluetoothAddress)) {
+            Log.d(this, "requestCallEndpointChange: requested endpoint is already active");
+            callback.send(CallEndpoint.ENDPOINT_OPERATION_SUCCESS, new Bundle());
+            return;
+        }
+
         if (mPendingChangeRequest != null && !mPendingChangeRequest.isDone()) {
             mPendingChangeRequest.complete(RESULT_ANOTHER_REQUEST);
             mPendingChangeRequest = null;
@@ -116,6 +124,27 @@
         mCallsManager.getCallAudioManager().setAudioRoute(route, bluetoothAddress);
     }
 
+    public boolean isCurrentEndpointRequestedEndpoint(int requestedRoute, String requestedAddress) {
+        if (mCallsManager.getCallAudioManager() == null
+                || mCallsManager.getCallAudioManager().getCallAudioState() == null) {
+            return false;
+        }
+        CallAudioState currentAudioState = mCallsManager.getCallAudioManager().getCallAudioState();
+        // requested non-bt endpoint is already active
+        if (requestedRoute != CallAudioState.ROUTE_BLUETOOTH &&
+                requestedRoute == currentAudioState.getRoute()) {
+            return true;
+        }
+        // requested bt endpoint is already active
+        if (requestedRoute == CallAudioState.ROUTE_BLUETOOTH &&
+                currentAudioState.getActiveBluetoothDevice() != null &&
+                requestedAddress.equals(
+                        currentAudioState.getActiveBluetoothDevice().getAddress())) {
+            return true;
+        }
+        return false;
+    }
+
     private Bundle getErrorResult(int result) {
         String message;
         int resultCode;
@@ -165,8 +194,7 @@
         for (Call call : calls) {
             if (call != null && call.getConnectionService() != null) {
                 call.getConnectionService().onCallEndpointChanged(call, mActiveCallEndpoint);
-            }
-            else if (call != null && call.getTransactionServiceWrapper() != null) {
+            } else if (call != null && call.getTransactionServiceWrapper() != null) {
                 call.getTransactionServiceWrapper()
                         .onCallEndpointChanged(call, mActiveCallEndpoint);
             }
@@ -181,8 +209,7 @@
             if (call != null && call.getConnectionService() != null) {
                 call.getConnectionService().onAvailableCallEndpointsChanged(call,
                         mAvailableCallEndpoints);
-            }
-            else if (call != null && call.getTransactionServiceWrapper() != null) {
+            } else if (call != null && call.getTransactionServiceWrapper() != null) {
                 call.getTransactionServiceWrapper()
                         .onAvailableCallEndpointsChanged(call, mAvailableCallEndpoints);
             }
@@ -196,8 +223,7 @@
         for (Call call : calls) {
             if (call != null && call.getConnectionService() != null) {
                 call.getConnectionService().onMuteStateChanged(call, isMuted);
-            }
-            else if (call != null && call.getTransactionServiceWrapper() != null) {
+            } else if (call != null && call.getTransactionServiceWrapper() != null) {
                 call.getTransactionServiceWrapper().onMuteStateChanged(call, isMuted);
             }
         }
@@ -207,7 +233,7 @@
         Set<CallEndpoint> newAvailableEndpoints = new HashSet<>();
         Map<ParcelUuid, String> newBluetoothDevices = new HashMap<>();
 
-        mRouteToTypeMap.forEach((route, type)->{
+        mRouteToTypeMap.forEach((route, type) -> {
             if ((state.getSupportedRouteMask() & route) != 0) {
                 if (type == CallEndpoint.TYPE_STREAMING) {
                     if (state.getRoute() == CallAudioState.ROUTE_STREAMING) {
diff --git a/src/com/android/server/telecom/CallStreamingController.java b/src/com/android/server/telecom/CallStreamingController.java
index 31b2235..6276a7d 100644
--- a/src/com/android/server/telecom/CallStreamingController.java
+++ b/src/com/android/server/telecom/CallStreamingController.java
@@ -36,7 +36,7 @@
 import android.telecom.CallException;
 import android.telecom.CallStreamingService;
 import android.telecom.StreamingCall;
-import android.util.Log;
+import android.telecom.Log;
 
 import com.android.internal.telecom.ICallStreamingService;
 import com.android.server.telecom.voip.VoipCallTransaction;
@@ -65,6 +65,9 @@
     private void onConnectedInternal(Call call, TransactionalServiceWrapper wrapper,
             IBinder service) throws RemoteException {
         synchronized (mLock) {
+            Log.i(this, "onConnectedInternal: callid=%s", call.getId());
+            Bundle extras = new Bundle();
+            extras.putString(StreamingCall.EXTRA_CALL_ID, call.getId());
             mStreamingCall = call;
             mTransactionalServiceWrapper = wrapper;
             mService = ICallStreamingService.Stub.asInterface(service);
@@ -74,7 +77,7 @@
             mService.onCallStreamingStarted(new StreamingCall(
                     mTransactionalServiceWrapper.getComponentName(),
                     mStreamingCall.getCallerDisplayName(),
-                    mStreamingCall.getContactUri(), new Bundle()));
+                    mStreamingCall.getHandle(), extras));
             mIsStreaming = true;
         }
     }
@@ -99,7 +102,6 @@
     }
 
     public static class QueryCallStreamingTransaction extends VoipCallTransaction {
-        private static final String TAG = QueryCallStreamingTransaction.class.getSimpleName();
         private final CallsManager mCallsManager;
 
         public QueryCallStreamingTransaction(CallsManager callsManager) {
@@ -109,7 +111,7 @@
 
         @Override
         public CompletableFuture<VoipCallTransactionResult> processTransaction(Void v) {
-            Log.d(TAG, "processTransaction");
+            Log.i(this, "processTransaction");
             CompletableFuture<VoipCallTransactionResult> future = new CompletableFuture<>();
 
             if (mCallsManager.getCallStreamingController().isStreaming()) {
@@ -126,8 +128,6 @@
     }
 
     public static class AudioInterceptionTransaction extends VoipCallTransaction {
-        private static final String TAG = AudioInterceptionTransaction.class.getSimpleName();
-
         private Call mCall;
         private boolean mEnterInterception;
 
@@ -140,7 +140,7 @@
 
         @Override
         public CompletableFuture<VoipCallTransactionResult> processTransaction(Void v) {
-            Log.d(TAG, "processTransaction");
+            Log.d(this, "processTransaction");
             CompletableFuture<VoipCallTransactionResult> future = new CompletableFuture<>();
 
             if (mEnterInterception) {
@@ -160,7 +160,6 @@
     }
 
     public class StreamingServiceTransaction extends VoipCallTransaction {
-        private static final String TAG = "StreamingServiceTransaction";
         public static final String MESSAGE = "STREAMING_FAILED_NO_SENDER";
         private final TransactionalServiceWrapper mWrapper;
         private final Context mContext;
@@ -179,13 +178,13 @@
         @SuppressLint("LongLogTag")
         @Override
         public CompletionStage<VoipCallTransactionResult> processTransaction(Void v) {
-            Log.d(TAG, "processTransaction");
+            Log.d(this, "processTransaction");
             CompletableFuture<VoipCallTransactionResult> future = new CompletableFuture<>();
 
             RoleManager roleManager = mContext.getSystemService(RoleManager.class);
             PackageManager packageManager = mContext.getPackageManager();
             if (roleManager == null || packageManager == null) {
-                Log.e(TAG, "Can't find system service");
+                Log.w(this, "processTransaction: Can't find system service");
                 future.complete(new VoipCallTransactionResult(
                         VoipCallTransactionResult.RESULT_FAILED, MESSAGE));
                 return future;
@@ -194,7 +193,7 @@
             List<String> holders = roleManager.getRoleHoldersAsUser(
                     RoleManager.ROLE_SYSTEM_CALL_STREAMING, mUserHandle);
             if (holders.isEmpty()) {
-                Log.e(TAG, "Can't find streaming app");
+                Log.w(this, "processTransaction: Can't find streaming app");
                 future.complete(new VoipCallTransactionResult(
                         VoipCallTransactionResult.RESULT_FAILED, MESSAGE));
                 return future;
@@ -205,7 +204,7 @@
             List<ResolveInfo> infos = packageManager.queryIntentServicesAsUser(serviceIntent,
                     PackageManager.GET_META_DATA, mUserHandle);
             if (infos.isEmpty()) {
-                Log.e(TAG, "Can't find streaming service");
+                Log.w(this, "processTransaction: Can't find streaming service");
                 future.complete(new VoipCallTransactionResult(
                         VoipCallTransactionResult.RESULT_FAILED, MESSAGE));
                 return future;
@@ -215,7 +214,7 @@
 
             if (serviceInfo.permission == null || !serviceInfo.permission.equals(
                     Manifest.permission.BIND_CALL_STREAMING_SERVICE)) {
-                android.telecom.Log.w(TAG, "Must require BIND_CALL_STREAMING_SERVICE: " +
+                Log.w(this, "Must require BIND_CALL_STREAMING_SERVICE: " +
                         serviceInfo.packageName);
                 future.complete(new VoipCallTransactionResult(
                         VoipCallTransactionResult.RESULT_FAILED, MESSAGE));
@@ -228,7 +227,7 @@
             if (!mContext.bindServiceAsUser(intent, mConnection, Context.BIND_AUTO_CREATE
                     | Context.BIND_FOREGROUND_SERVICE
                     | Context.BIND_SCHEDULE_LIKE_TOP_APP, mUserHandle)) {
-                Log.e(TAG, "Can't bind to streaming service");
+                Log.w(this, "Can't bind to streaming service");
                 future.complete(new VoipCallTransactionResult(
                         VoipCallTransactionResult.RESULT_FAILED,
                         "STREAMING_FAILED_SENDER_BINDING_ERROR"));
@@ -243,8 +242,6 @@
     }
 
     public class UnbindStreamingServiceTransaction extends VoipCallTransaction {
-        private static final String TAG = "UnbindStreamingServiceTransaction";
-
         public UnbindStreamingServiceTransaction() {
             super(mTelecomLock);
         }
@@ -252,7 +249,7 @@
         @SuppressLint("LongLogTag")
         @Override
         public CompletionStage<VoipCallTransactionResult> processTransaction(Void v) {
-            Log.d(TAG, "processTransaction");
+            Log.d(this, "processTransaction");
             CompletableFuture<VoipCallTransactionResult> future = new CompletableFuture<>();
 
             resetController();
@@ -285,6 +282,7 @@
                             StreamingCall.STATE_HOLDING);
                 case CallState.DISCONNECTING:
                 case CallState.DISCONNECTED:
+                    Log.addEvent(call, LogUtils.Events.STOP_STREAMING);
                     transaction = new CallStreamingStateChangeTransaction(
                             StreamingCall.STATE_DISCONNECTED);
                 default:
@@ -300,8 +298,8 @@
 
                             @Override
                             public void onError(CallException exception) {
-                                Log.e(String.valueOf(this), "Exception when set call "
-                                        + "streaming state to streaming app: " + exception);
+                                Log.e(this, exception, "Exception when set call "
+                                        + "streaming state to streaming app");
                             }
                         });
             }
@@ -348,6 +346,7 @@
         @Override
         public void onServiceConnected(ComponentName name, IBinder service) {
             try {
+                Log.i(this, "onServiceConnected: " + name);
                 onConnectedInternal(mCall, mWrapper, service);
                 mFuture.complete(new VoipCallTransactionResult(
                         VoipCallTransactionResult.RESULT_SUCCEED, null));
@@ -380,7 +379,7 @@
                     mService.onCallStreamingStopped();
                 }
             } catch (RemoteException e) {
-                Log.w(String.valueOf(this), "Exception when stop call streaming:" + e);
+                Log.e(this, e, "Exception when stop call streaming");
             }
             resetController();
             if (!mFuture.isDone()) {
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index ccc8e59..d457cc8 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -24,6 +24,7 @@
 import static android.provider.CallLog.Calls.USER_MISSED_CALL_FILTERS_TIMEOUT;
 import static android.provider.CallLog.Calls.USER_MISSED_CALL_SCREENING_SERVICE_SILENCED;
 import static android.provider.CallLog.Calls.USER_MISSED_NEVER_RANG;
+import static android.provider.CallLog.Calls.USER_MISSED_NOT_RUNNING;
 import static android.provider.CallLog.Calls.USER_MISSED_NO_ANSWER;
 import static android.provider.CallLog.Calls.USER_MISSED_SHORT_RING;
 import static android.telecom.TelecomManager.ACTION_POST_CALL;
@@ -446,6 +447,7 @@
     private final CallStreamingController mCallStreamingController;
     private final BlockedNumbersAdapter mBlockedNumbersAdapter;
     private final TransactionManager mTransactionManager;
+    private final UserManager mUserManager;
 
     private final ConnectionServiceFocusManager.CallsManagerRequester mRequester =
             new ConnectionServiceFocusManager.CallsManagerRequester() {
@@ -685,6 +687,7 @@
 
         mCallAnomalyWatchdog = callAnomalyWatchdog;
         mAsyncTaskExecutor = asyncTaskExecutor;
+        mUserManager = mContext.getSystemService(UserManager.class);
     }
 
     public void setIncomingCallNotifier(IncomingCallNotifier incomingCallNotifier) {
@@ -1534,7 +1537,22 @@
 
         CallFailureCause startFailCause =
                 checkIncomingCallPermitted(call, call.getTargetPhoneAccount());
-        if (!isHandoverAllowed ||
+        // Check if the target phone account is possibly in ECBM.
+        call.setIsInECBM(getEmergencyCallHelper()
+                .isLastOutgoingEmergencyCallPAH(call.getTargetPhoneAccount()));
+        if (mUserManager.isQuietModeEnabled(call.getUserHandleFromTargetPhoneAccount())
+                && !call.isEmergencyCall() && !call.isInECBM()) {
+            Log.d(TAG, "Rejecting non-emergency call because the owner %s is not running.",
+                    phoneAccountHandle.getUserHandle());
+            call.setMissedReason(USER_MISSED_NOT_RUNNING);
+            call.setStartFailCause(CallFailureCause.INVALID_USE);
+            if (isConference) {
+                notifyCreateConferenceFailed(phoneAccountHandle, call);
+            } else {
+                notifyCreateConnectionFailed(phoneAccountHandle, call);
+            }
+        }
+        else if (!isHandoverAllowed ||
                 (call.isSelfManaged() && !startFailCause.isSuccess())) {
             if (isConference) {
                 notifyCreateConferenceFailed(phoneAccountHandle, call);
@@ -3412,10 +3430,11 @@
      */
     boolean holdActiveCallForNewCall(Call call) {
         Call activeCall = (Call) mConnectionSvrFocusMgr.getCurrentFocusCall();
-        Log.i(this, "holdActiveCallForNewCall, newCall: %s, activeCall: %s", call, activeCall);
+        Log.i(this, "holdActiveCallForNewCall, newCall: %s, activeCall: %s", call.getId(),
+                (activeCall == null ? "<none>" : activeCall.getId()));
         if (activeCall != null && activeCall != call) {
             if (canHold(activeCall)) {
-                activeCall.hold();
+                activeCall.hold("swap to " + call.getId());
                 return true;
             } else if (supportsHold(activeCall)
                     && areFromSameSource(activeCall, call)) {
@@ -4900,12 +4919,25 @@
                     liveCallPhoneAccount);
         }
 
-        // First thing, if we are trying to make a call with the same phone account as the live
-        // call, then allow it so that the connection service can make its own decision about
-        // how to handle the new call relative to the current one.
+        // First thing, for managed calls, if we are trying to make a call with the same phone
+        // account as the live call, then allow it so that the connection service can make its own
+        // decision about how to handle the new call relative to the current one.
+        // Note: This behavior is primarily in place because Telephony historically manages the
+        // state of the calls it tracks by itself, holding and unholding as needed.  Self-managed
+        // calls, even though from the same package are normally held/unheld automatically by
+        // Telecom.  Calls within a single ConnectionService get held/unheld automatically during
+        // "swap" operations by CallsManager#holdActiveCallForNewCall.  There is, however, a quirk
+        // in that if an app declares TWO different ConnectionServices, holdActiveCallForNewCall
+        // would not work correctly because focus switches between ConnectionServices, yet we
+        // tended to assume that if the calls are from the same package that the hold/unhold should
+        // be done by the app.  That was a bad assumption as it meant that we could have two active
+        // calls.
+        // TODO(b/280826075): We need to come back and revisit all this logic in a holistic manner.
         if (PhoneAccountHandle.areFromSamePackage(liveCallPhoneAccount,
-                call.getTargetPhoneAccount())) {
-            Log.i(this, "makeRoomForOutgoingCall: phoneAccount matches.");
+                call.getTargetPhoneAccount())
+                && !call.isSelfManaged()
+                && !liveCall.isSelfManaged()) {
+            Log.i(this, "makeRoomForOutgoingCall: managed phoneAccount matches");
             call.getAnalytics().setCallIsAdditional(true);
             liveCall.getAnalytics().setCallIsInterrupted(true);
             return true;
@@ -5485,6 +5517,13 @@
             impl.dump(pw);
             pw.decreaseIndent();
         }
+
+        if (mConnectionSvrFocusMgr != null) {
+            pw.println("mConnectionSvrFocusMgr:");
+            pw.increaseIndent();
+            mConnectionSvrFocusMgr.dump(pw);
+            pw.decreaseIndent();
+        }
     }
 
     /**
diff --git a/src/com/android/server/telecom/ConnectionServiceFocusManager.java b/src/com/android/server/telecom/ConnectionServiceFocusManager.java
index 6fbc494..3694727 100644
--- a/src/com/android/server/telecom/ConnectionServiceFocusManager.java
+++ b/src/com/android/server/telecom/ConnectionServiceFocusManager.java
@@ -25,8 +25,10 @@
 import android.telecom.Log;
 import android.telecom.Logging.Session;
 import android.text.TextUtils;
+import android.util.LocalLog;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -41,6 +43,7 @@
 public class ConnectionServiceFocusManager {
     private static final String TAG = "ConnectionSvrFocusMgr";
     private static final int GET_CURRENT_FOCUS_TIMEOUT_MILLIS = 1000;
+    private final LocalLog mLocalLog = new LocalLog(20);
 
     /** Factory interface used to create the {@link ConnectionServiceFocusManager} instance. */
     public interface ConnectionServiceFocusManagerFactory {
@@ -124,6 +127,11 @@
          * @return {@code True} if this call can receive focus, {@code false} otherwise.
          */
         boolean isFocusable();
+
+        /**
+         * @return the ID of the focusable for debug purposes.
+         */
+        String getId();
     }
 
     /** Interface define a call back for focus request event. */
@@ -361,10 +369,11 @@
     }
 
     private void updateCurrentFocusCall() {
+        CallFocus previousFocus = mCurrentFocusCall;
         mCurrentFocusCall = null;
 
         if (mCurrentFocus == null) {
-            Log.d(this, "updateCurrentFocusCall: mCurrentFocus is null");
+            Log.i(this, "updateCurrentFocusCall: mCurrentFocus is null");
             return;
         }
 
@@ -377,11 +386,16 @@
         for (CallFocus call : calls) {
             if (PRIORITY_FOCUS_CALL_STATE.contains(call.getState())) {
                 mCurrentFocusCall = call;
+                if (previousFocus != call) {
+                    mLocalLog.log(call.getId());
+                }
                 Log.i(this, "updateCurrentFocusCall %s", mCurrentFocusCall);
                 return;
             }
         }
-
+        if (previousFocus != null) {
+            mLocalLog.log("<none>");
+        }
         Log.i(this, "updateCurrentFocusCall = null");
     }
 
@@ -477,6 +491,11 @@
         }
     }
 
+    public void dump(IndentingPrintWriter pw) {
+        pw.println("Call Focus History:");
+        mLocalLog.dump(pw);
+    }
+
     private final class FocusManagerHandler extends Handler {
         FocusManagerHandler(Looper looper) {
             super(looper);
diff --git a/src/com/android/server/telecom/ConnectionServiceWrapper.java b/src/com/android/server/telecom/ConnectionServiceWrapper.java
index cc5932c..5b727ab 100644
--- a/src/com/android/server/telecom/ConnectionServiceWrapper.java
+++ b/src/com/android/server/telecom/ConnectionServiceWrapper.java
@@ -919,6 +919,9 @@
                         callingPhoneAccountHandle.getComponentName().getPackageName());
             }
 
+            boolean hasCrossUserAccess = mContext.checkCallingOrSelfPermission(
+                    android.Manifest.permission.INTERACT_ACROSS_USERS)
+                    == PackageManager.PERMISSION_GRANTED;
             long token = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
@@ -930,7 +933,7 @@
                     // an emergency call.
                             mPhoneAccountRegistrar.getCallCapablePhoneAccounts(null /*uriScheme*/,
                             false /*includeDisabledAccounts*/, userHandle, 0 /*capabilities*/,
-                            0 /*excludedCapabilities*/, false);
+                            0 /*excludedCapabilities*/, hasCrossUserAccess);
                     PhoneAccountHandle phoneAccountHandle = null;
                     for (PhoneAccountHandle accountHandle : accountHandles) {
                         if(accountHandle.equals(callingPhoneAccountHandle)) {
diff --git a/src/com/android/server/telecom/EmergencyCallHelper.java b/src/com/android/server/telecom/EmergencyCallHelper.java
index a213e26..fbb666d 100644
--- a/src/com/android/server/telecom/EmergencyCallHelper.java
+++ b/src/com/android/server/telecom/EmergencyCallHelper.java
@@ -21,6 +21,8 @@
 import android.content.pm.PackageManager;
 import android.os.UserHandle;
 import android.telecom.Log;
+import android.telecom.PhoneAccountHandle;
+
 import com.android.internal.annotations.VisibleForTesting;
 
 /**
@@ -34,6 +36,7 @@
     private final DefaultDialerCache mDefaultDialerCache;
     private final Timeouts.Adapter mTimeoutsAdapter;
     private UserHandle mLocationPermissionGrantedToUser;
+    private PhoneAccountHandle mLastOutgoingEmergencyCallPAH;
 
     //stores the original state of permissions that dialer had
     private boolean mHadFineLocation = false;
@@ -46,6 +49,7 @@
     private boolean mBackgroundLocationGranted = false;
 
     private long mLastEmergencyCallTimestampMillis;
+    private long mLastOutgoingEmergencyCallTimestampMillis;
 
     @VisibleForTesting
     public EmergencyCallHelper(
@@ -63,7 +67,7 @@
             grantLocationPermission(userHandle);
         }
         if (call != null && call.isEmergencyCall()) {
-            recordEmergencyCallTime();
+            recordEmergencyCall(call);
         }
     }
 
@@ -78,15 +82,37 @@
         return mLastEmergencyCallTimestampMillis;
     }
 
-    private void recordEmergencyCallTime() {
-        mLastEmergencyCallTimestampMillis = System.currentTimeMillis();
+    void setLastOutgoingEmergencyCallPAH(PhoneAccountHandle accountHandle) {
+        mLastOutgoingEmergencyCallPAH = accountHandle;
     }
 
-    private boolean isInEmergencyCallbackWindow() {
-        return System.currentTimeMillis() - getLastEmergencyCallTimeMillis()
+    public boolean isLastOutgoingEmergencyCallPAH(PhoneAccountHandle currentCallHandle) {
+        boolean ecbmActive = mLastOutgoingEmergencyCallPAH != null
+                && isInEmergencyCallbackWindow(mLastOutgoingEmergencyCallTimestampMillis)
+                && currentCallHandle != null
+                && currentCallHandle.equals(mLastOutgoingEmergencyCallPAH);
+        if (ecbmActive) {
+            Log.i(this, "ECBM is enabled for %s. The last recorded call timestamp was at %s",
+                    currentCallHandle, mLastOutgoingEmergencyCallTimestampMillis);
+        }
+
+        return ecbmActive;
+    }
+
+    boolean isInEmergencyCallbackWindow(long lastEmergencyCallTimestampMillis) {
+        return System.currentTimeMillis() - lastEmergencyCallTimestampMillis
                 < mTimeoutsAdapter.getEmergencyCallbackWindowMillis(mContext.getContentResolver());
     }
 
+    private void recordEmergencyCall(Call call) {
+        mLastEmergencyCallTimestampMillis = System.currentTimeMillis();
+        if (!call.isIncoming()) {
+            // ECBM is applicable to MO emergency calls
+            mLastOutgoingEmergencyCallTimestampMillis = mLastEmergencyCallTimestampMillis;
+            mLastOutgoingEmergencyCallPAH = call.getTargetPhoneAccount();
+        }
+    }
+
     private boolean shouldGrantTemporaryLocationPermission(Call call) {
         if (!mContext.getResources().getBoolean(R.bool.grant_location_permission_enabled)) {
             Log.i(this, "ShouldGrantTemporaryLocationPermission, disabled by config");
@@ -96,7 +122,8 @@
             Log.i(this, "ShouldGrantTemporaryLocationPermission, no call");
             return false;
         }
-        if (!call.isEmergencyCall() && !isInEmergencyCallbackWindow()) {
+        if (!call.isEmergencyCall() && !isInEmergencyCallbackWindow(
+                getLastEmergencyCallTimeMillis())) {
             Log.i(this, "ShouldGrantTemporaryLocationPermission, not emergency");
             return false;
         }
diff --git a/src/com/android/server/telecom/InCallController.java b/src/com/android/server/telecom/InCallController.java
index 3215605..3d3e3b4 100644
--- a/src/com/android/server/telecom/InCallController.java
+++ b/src/com/android/server/telecom/InCallController.java
@@ -338,7 +338,10 @@
             UserHandle userToBind = getUserFromCall(call);
             boolean isManagedProfile = UserUtil.isManagedProfile(mContext, userToBind);
             // Note that UserHandle.CURRENT fails to capture the work profile, so we need to handle
-            // it separately to ensure that the ICS is bound to the appropriate user.
+            // it separately to ensure that the ICS is bound to the appropriate user. If ECBM is
+            // active, we know that a work sim was previously used to place a MO emergency call. We
+            // need to ensure that we bind to the CURRENT_USER in this case, as the work user would
+            // not be running (handled in getUserFromCall).
             userToBind = isManagedProfile ? userToBind : UserHandle.CURRENT;
             if (!mContext.bindServiceAsUser(intent, mServiceConnection,
                     Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
@@ -1191,6 +1194,11 @@
     @Override
     public void onCallAdded(Call call) {
         UserHandle userFromCall = getUserFromCall(call);
+
+        Log.i(this, "onCallAdded: %s", call);
+        // Track the call if we don't already know about it.
+        addCall(call);
+
         if (!isBoundAndConnectedToServices(userFromCall)) {
             Log.i(this, "onCallAdded: %s; not bound or connected.", call);
             // We are not bound, or we're not connected.
@@ -1206,10 +1214,6 @@
             mEmergencyCallHelper.maybeGrantTemporaryLocationPermission(call,
                     userFromCall);
 
-            Log.i(this, "onCallAdded: %s", call);
-            // Track the call if we don't already know about it.
-            addCall(call);
-
             if (inCallServiceConnection != null) {
                 Log.i(this, "mInCallServiceConnection isConnected=%b",
                         inCallServiceConnection.isConnected());
@@ -2600,7 +2604,8 @@
             UserManager userManager = mContext.getSystemService(UserManager.class);
             // Emergency call should never be blocked, so if the user associated with call is in
             // quite mode, use the primary user for the emergency call.
-            if (call.isEmergencyCall() && userManager.isQuietModeEnabled(userFromCall)) {
+            if ((call.isEmergencyCall() || call.isInECBM())
+                    && userManager.isQuietModeEnabled(userFromCall)) {
                 return mCallsManager.getCurrentUserHandle();
             }
             return userFromCall;
diff --git a/src/com/android/server/telecom/LogUtils.java b/src/com/android/server/telecom/LogUtils.java
index 8ce5dc3..4e7546f 100644
--- a/src/com/android/server/telecom/LogUtils.java
+++ b/src/com/android/server/telecom/LogUtils.java
@@ -225,6 +225,8 @@
         public static final String FLASH_NOTIFICATION_STOP = "FLASH_NOTIFICATION_STOP";
         public static final String GAINED_FGS_DELEGATION = "GAINED_FGS_DELEGATION";
         public static final String LOST_FGS_DELEGATION = "LOST_FGS_DELEGATION";
+        public static final String START_STREAMING = "START_STREAMING";
+        public static final String STOP_STREAMING = "STOP_STREAMING";
 
         public static class Timings {
             public static final String ACCEPT_TIMING = "accept";
diff --git a/src/com/android/server/telecom/TransactionalServiceWrapper.java b/src/com/android/server/telecom/TransactionalServiceWrapper.java
index 1e6403e..d83e551 100644
--- a/src/com/android/server/telecom/TransactionalServiceWrapper.java
+++ b/src/com/android/server/telecom/TransactionalServiceWrapper.java
@@ -425,6 +425,15 @@
 
                     @Override
                     public void onError(CallException exception) {
+                        if (isAnswerRequest) {
+                            // This also sends the signal to untrack from TSW and the client_TSW
+                            removeCallFromCallsManager(call,
+                                    new DisconnectCause(DisconnectCause.REJECTED,
+                                            "client rejected to answer the call;"
+                                                    + " force disconnecting"));
+                        } else {
+                            mCallsManager.markCallAsOnHold(call);
+                        }
                         maybeResetForegroundCall(foregroundCallBeforeSwap, wasActive);
                     }
                 });
diff --git a/src/com/android/server/telecom/voip/CallEventCallbackAckTransaction.java b/src/com/android/server/telecom/voip/CallEventCallbackAckTransaction.java
index 8b4ffed..93d9836 100644
--- a/src/com/android/server/telecom/voip/CallEventCallbackAckTransaction.java
+++ b/src/com/android/server/telecom/voip/CallEventCallbackAckTransaction.java
@@ -128,6 +128,8 @@
             boolean success = latch.await(VoipCallTransaction.TIMEOUT_LIMIT, TimeUnit.MILLISECONDS);
             if (!success) {
                 // client send onError and failed to complete transaction
+                Log.i(TAG, String.format("CallEventCallbackAckTransaction:"
+                        + " client failed to complete the [%s] transaction", mAction));
                 return CompletableFuture.completedFuture(TRANSACTION_FAILED);
             } else {
                 // success
diff --git a/src/com/android/server/telecom/voip/TransactionManager.java b/src/com/android/server/telecom/voip/TransactionManager.java
index 98faf3d..773dfb8 100644
--- a/src/com/android/server/telecom/voip/TransactionManager.java
+++ b/src/com/android/server/telecom/voip/TransactionManager.java
@@ -26,6 +26,8 @@
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Queue;
 
 public class TransactionManager {
@@ -63,30 +65,31 @@
             OutcomeReceiver<VoipCallTransactionResult, CallException> receiver) {
         synchronized (sLock) {
             mTransactions.add(transaction);
-            transaction.setCompleteListener(new TransactionCompleteListener() {
-                @Override
-                public void onTransactionCompleted(VoipCallTransactionResult result,
-                        String transactionName){
-                    Log.i(TAG, String.format("transaction completed: with result=[%d]",
-                            result.getResult()));
-                    if (result.getResult() == TelecomManager.TELECOM_TRANSACTION_SUCCESS) {
-                        receiver.onResult(result);
-                    } else {
-                        receiver.onError(
-                                new CallException(result.getMessage(),
-                                        result.getResult()));
-                    }
-                    finishTransaction();
-                }
-
-                @Override
-                public void onTransactionTimeout(String transactionName){
-                    receiver.onError(new CallException(transactionName + " timeout",
-                            CODE_OPERATION_TIMED_OUT));
-                    finishTransaction();
-                }
-            });
         }
+        transaction.setCompleteListener(new TransactionCompleteListener() {
+            @Override
+            public void onTransactionCompleted(VoipCallTransactionResult result,
+                    String transactionName){
+                Log.i(TAG, String.format("transaction completed: with result=[%d]",
+                        result.getResult()));
+                if (result.getResult() == TelecomManager.TELECOM_TRANSACTION_SUCCESS) {
+                    receiver.onResult(result);
+                } else {
+                    receiver.onError(
+                            new CallException(result.getMessage(),
+                                    result.getResult()));
+                }
+                finishTransaction();
+            }
+
+            @Override
+            public void onTransactionTimeout(String transactionName){
+                receiver.onError(new CallException(transactionName + " timeout",
+                        CODE_OPERATION_TIMED_OUT));
+                finishTransaction();
+            }
+        });
+
         startTransactions();
     }
 
@@ -102,8 +105,8 @@
                 return;
             }
             mCurrentTransaction = mTransactions.poll();
-            mCurrentTransaction.start();
         }
+        mCurrentTransaction.start();
     }
 
     private void finishTransaction() {
@@ -115,10 +118,12 @@
 
     @VisibleForTesting
     public void clear() {
+        List<VoipCallTransaction> pendingTransactions;
         synchronized (sLock) {
-            for (VoipCallTransaction transaction : mTransactions) {
-                transaction.finish();
-            }
+            pendingTransactions = new ArrayList<>(mTransactions);
+        }
+        for (VoipCallTransaction transaction : pendingTransactions) {
+            transaction.finish();
         }
     }
 }
diff --git a/src/com/android/server/telecom/voip/VoipCallMonitor.java b/src/com/android/server/telecom/voip/VoipCallMonitor.java
index d0304a9..2a81051 100644
--- a/src/com/android/server/telecom/voip/VoipCallMonitor.java
+++ b/src/com/android/server/telecom/voip/VoipCallMonitor.java
@@ -207,8 +207,10 @@
         synchronized (mLock) {
             Log.i(this, "stopFGSDelegation of handle %s", handle);
             Set<Call> calls = mPhoneAccountHandleListMap.get(handle);
-            for (Call call : calls) {
-                stopMonitorWorks(call);
+            if (calls != null) {
+                for (Call call : calls) {
+                    stopMonitorWorks(call);
+                }
             }
             mPhoneAccountHandleListMap.remove(handle);
 
diff --git a/testapps/AndroidManifest.xml b/testapps/AndroidManifest.xml
index dd8258a..645a42b 100644
--- a/testapps/AndroidManifest.xml
+++ b/testapps/AndroidManifest.xml
@@ -259,6 +259,15 @@
           </intent-filter>
         </service>
 
+        <service android:name="com.android.server.telecom.testapps.OtherSelfManagedConnectionService"
+                 android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"
+                 android:process="com.android.server.telecom.testapps.SelfMangingCallingApp"
+                 android:exported="true">
+            <intent-filter>
+                <action android:name="android.telecom.ConnectionService"/>
+            </intent-filter>
+        </service>
+
         <receiver android:exported="false"
              android:process="com.android.server.telecom.testapps.SelfMangingCallingApp"
              android:name="com.android.server.telecom.testapps.SelfManagedCallNotificationReceiver"/>
diff --git a/testapps/res/layout/self_managed_sample_main.xml b/testapps/res/layout/self_managed_sample_main.xml
index d26d629..98b879a 100644
--- a/testapps/res/layout/self_managed_sample_main.xml
+++ b/testapps/res/layout/self_managed_sample_main.xml
@@ -55,6 +55,12 @@
                 android:layout_height="wrap_content"
                 android:background="@color/test_call_b_color"
                 android:text="2"/>
+            <RadioButton
+                android:id="@+id/useAcct3Button"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:background="@color/test_call_c_color"
+                android:text="3"/>
         </RadioGroup>
         <TextView
             android:id="@+id/hasFocus"
diff --git a/testapps/res/values/colors.xml b/testapps/res/values/colors.xml
index 3939e78..9447ac8 100644
--- a/testapps/res/values/colors.xml
+++ b/testapps/res/values/colors.xml
@@ -17,4 +17,5 @@
 <resources>
     <color name="test_call_a_color">#f2eebf</color>
     <color name="test_call_b_color">#afc5e6</color>
+    <color name="test_call_c_color">#c5afe6</color>
 </resources>
diff --git a/testapps/src/com/android/server/telecom/testapps/OtherSelfManagedConnectionService.java b/testapps/src/com/android/server/telecom/testapps/OtherSelfManagedConnectionService.java
new file mode 100644
index 0000000..7bb9830
--- /dev/null
+++ b/testapps/src/com/android/server/telecom/testapps/OtherSelfManagedConnectionService.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2023 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.server.telecom.testapps;
+
+public class OtherSelfManagedConnectionService extends SelfManagedConnectionService {
+}
diff --git a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallList.java b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallList.java
index d4661ff..273b060 100644
--- a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallList.java
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallList.java
@@ -46,20 +46,27 @@
 
     public static String SELF_MANAGED_ACCOUNT_1 = "1";
     public static String SELF_MANAGED_ACCOUNT_2 = "2";
+    public static String SELF_MANAGED_ACCOUNT_1A = "1A";
     public static String SELF_MANAGED_ACCOUNT_3 = "3";
     public static String SELF_MANAGED_NAME_1 = "SuperCall";
     public static String SELF_MANAGED_NAME_2 = "Mega Call";
-    public static String SELF_MANAGED_NAME_3 = "SM Call";
+    public static String SELF_MANAGED_NAME_1A = "SM Call";
+    public static String SELF_MANAGED_NAME_3 = "Sep Process";
     public static String CUSTOM_URI_SCHEME = "custom";
 
     private static SelfManagedCallList sInstance;
     private static ComponentName COMPONENT_NAME = new ComponentName(
             SelfManagedCallList.class.getPackage().getName(),
             SelfManagedConnectionService.class.getName());
+    private static ComponentName OTHER_COMPONENT_NAME = new ComponentName(
+            SelfManagedCallList.class.getPackage().getName(),
+            OtherSelfManagedConnectionService.class.getName());
     private static Uri SELF_MANAGED_ADDRESS_1 = Uri.fromParts(PhoneAccount.SCHEME_TEL, "555-1212",
             "");
     private static Uri SELF_MANAGED_ADDRESS_2 = Uri.fromParts(PhoneAccount.SCHEME_SIP,
             "me@test.org", "");
+    private static Uri SELF_MANAGED_ADDRESS_3 = Uri.fromParts(PhoneAccount.SCHEME_SIP,
+            "hilda@test.org", "");
     private static Map<String, PhoneAccountHandle> mPhoneAccounts = new ArrayMap();
 
     public static SelfManagedCallList getInstance() {
@@ -101,20 +108,29 @@
                 SELF_MANAGED_NAME_1, true /* areCallsLogged */);
         registerPhoneAccount(context, SELF_MANAGED_ACCOUNT_2, SELF_MANAGED_ADDRESS_2,
                 SELF_MANAGED_NAME_2, false /* areCallsLogged */);
-        registerPhoneAccount(context, SELF_MANAGED_ACCOUNT_3, SELF_MANAGED_ADDRESS_1,
-                SELF_MANAGED_NAME_3, true /* areCallsLogged */);
+        registerPhoneAccount(context, SELF_MANAGED_ACCOUNT_1A, SELF_MANAGED_ADDRESS_1,
+                SELF_MANAGED_NAME_1A, true /* areCallsLogged */);
+        registerPhoneAccount(context, SELF_MANAGED_ACCOUNT_1A, SELF_MANAGED_ADDRESS_1,
+                SELF_MANAGED_NAME_1A, true /* areCallsLogged */);
+        registerPhoneAccount(context, OTHER_COMPONENT_NAME, SELF_MANAGED_ACCOUNT_3,
+                SELF_MANAGED_ADDRESS_3, SELF_MANAGED_NAME_3, false /* areCallsLogged */);
     }
 
     public void registerPhoneAccount(Context context, String id, Uri address, String name,
-                                     boolean areCallsLogged) {
-        PhoneAccountHandle handle = new PhoneAccountHandle(COMPONENT_NAME, id);
+            boolean areCallsLogged) {
+        registerPhoneAccount(context, COMPONENT_NAME, id, address, name, areCallsLogged);
+    }
+
+    public void registerPhoneAccount(Context context, ComponentName componentName, String id,
+            Uri address, String name, boolean areCallsLogged) {
+        PhoneAccountHandle handle = new PhoneAccountHandle(componentName, id);
         mPhoneAccounts.put(id, handle);
         Bundle extras = new Bundle();
         extras.putBoolean(PhoneAccount.EXTRA_SUPPORTS_HANDOVER_TO, true);
         if (areCallsLogged) {
             extras.putBoolean(PhoneAccount.EXTRA_LOG_SELF_MANAGED_CALLS, true);
         }
-        if (id.equals(SELF_MANAGED_ACCOUNT_3)) {
+        if (id.equals(SELF_MANAGED_ACCOUNT_1A)) {
             extras.putBoolean(PhoneAccount.EXTRA_ADD_SELF_MANAGED_CALLS_TO_INCALLSERVICE, true);
         }
         PhoneAccount.Builder builder = PhoneAccount.builder(handle, name)
diff --git a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallListAdapter.java b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallListAdapter.java
index 75ceb62..475f255 100644
--- a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallListAdapter.java
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallListAdapter.java
@@ -166,8 +166,10 @@
                 SelfManagedConnection.EXTRA_PHONE_ACCOUNT_HANDLE);
         if (phoneAccountHandle.getId().equals(SelfManagedCallList.SELF_MANAGED_ACCOUNT_1)) {
             result.setBackgroundColor(result.getContext().getColor(R.color.test_call_a_color));
-        } else {
+        } else if (phoneAccountHandle.getId().equals(SelfManagedCallList.SELF_MANAGED_ACCOUNT_2)) {
             result.setBackgroundColor(result.getContext().getColor(R.color.test_call_b_color));
+        } else {
+            result.setBackgroundColor(result.getContext().getColor(R.color.test_call_c_color));
         }
 
         CallAudioState audioState = connection.getCallAudioState();
diff --git a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallingActivity.java b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallingActivity.java
index 44410d2..5cdaf3d 100644
--- a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallingActivity.java
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallingActivity.java
@@ -43,8 +43,6 @@
 import android.widget.TextView;
 import android.widget.Toast;
 
-import com.android.server.telecom.testapps.R;
-
 import java.util.Objects;
 
 /**
@@ -66,6 +64,7 @@
     private Button mDisableCarMode;
     private RadioButton mUseAcct1Button;
     private RadioButton mUseAcct2Button;
+    private RadioButton mUseAcct3Button;
     private CheckBox mHoldableCheckbox;
     private CheckBox mVideoCallCheckbox;
     private EditText mNumber;
@@ -165,6 +164,7 @@
         }));
         mUseAcct1Button = findViewById(R.id.useAcct1Button);
         mUseAcct2Button = findViewById(R.id.useAcct2Button);
+        mUseAcct3Button = findViewById(R.id.useAcct3Button);
         mHasFocus = findViewById(R.id.hasFocus);
         mVideoCallCheckbox = findViewById(R.id.videoCall);
         mHoldableCheckbox = findViewById(R.id.holdable);
@@ -183,6 +183,8 @@
             return mCallList.getPhoneAccountHandle(SelfManagedCallList.SELF_MANAGED_ACCOUNT_1);
         } else if (mUseAcct2Button.isChecked()) {
             return mCallList.getPhoneAccountHandle(SelfManagedCallList.SELF_MANAGED_ACCOUNT_2);
+        } else if (mUseAcct3Button.isChecked()) {
+            return mCallList.getPhoneAccountHandle(SelfManagedCallList.SELF_MANAGED_ACCOUNT_3);
         }
         return null;
     }
@@ -214,8 +216,7 @@
 
     private void placeSelfManagedOutgoingCall() {
         TelecomManager tm = TelecomManager.from(this);
-        PhoneAccountHandle phoneAccountHandle = mCallList.getPhoneAccountHandle(
-                SelfManagedCallList.SELF_MANAGED_ACCOUNT_3);
+        PhoneAccountHandle phoneAccountHandle = getSelectedPhoneAccountHandle();
 
         if (mCheckIfPermittedBeforeCalling.isChecked()) {
             Toast.makeText(this, R.string.outgoingCallNotPermitted, Toast.LENGTH_SHORT).show();
@@ -264,7 +265,7 @@
     private void placeSelfManagedIncomingCall() {
         TelecomManager tm = TelecomManager.from(this);
         PhoneAccountHandle phoneAccountHandle = mCallList.getPhoneAccountHandle(
-                SelfManagedCallList.SELF_MANAGED_ACCOUNT_3);
+                SelfManagedCallList.SELF_MANAGED_ACCOUNT_1A);
 
         if (mCheckIfPermittedBeforeCalling.isChecked()) {
             if (!tm.isIncomingCallPermitted(phoneAccountHandle)) {
diff --git a/testapps/src/com/android/server/telecom/testapps/SelfManagedConnectionService.java b/testapps/src/com/android/server/telecom/testapps/SelfManagedConnectionService.java
index 6670095..3ef8fbb 100644
--- a/testapps/src/com/android/server/telecom/testapps/SelfManagedConnectionService.java
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedConnectionService.java
@@ -142,11 +142,6 @@
         connection.setVideoState(request.getVideoState());
         Log.i(this, "createSelfManagedConnection %s", connection);
         mCallList.addConnection(connection);
-        try {
-            Thread.sleep(8000);
-        } catch (InterruptedException e) {
-            e.printStackTrace();
-        }
         return connection;
     }
 }
diff --git a/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/InCallActivity.java b/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/InCallActivity.java
index 4c9f52d..b868b70 100644
--- a/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/InCallActivity.java
+++ b/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/InCallActivity.java
@@ -254,14 +254,14 @@
                 new OutcomeReceiver<Void, CallException>() {
                     @Override
                     public void onResult(Void result) {
-                        Log.i(TAG, String.format("success w/ %s", tag));
+                        Log.i(TAG, String.format("requestEndpointChange: success w/ %s", tag));
                         updateCurrentEndpointWithOnResult(endpoint);
                     }
 
                     @Override
                     public void onError(CallException e) {
-                        Log.i(TAG, String.format("%s :failed to switch to endpoint=[%s],"
-                                + " due to exception=[%s]", tag, endpoint, e.toString()));
+                        Log.i(TAG, String.format("requestEndpointChange: %s failed to switch to "
+                                + "endpoint=[%s] due to exception=[%s]", tag, endpoint, e));
                     }
                 });
     }
diff --git a/tests/src/com/android/server/telecom/tests/BasicCallTests.java b/tests/src/com/android/server/telecom/tests/BasicCallTests.java
index 453450d..9047da3 100644
--- a/tests/src/com/android/server/telecom/tests/BasicCallTests.java
+++ b/tests/src/com/android/server/telecom/tests/BasicCallTests.java
@@ -44,6 +44,7 @@
 import android.os.Bundle;
 import android.os.Process;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.provider.BlockedNumberContract;
 import android.telecom.Call;
 import android.telecom.CallAudioState;
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioModeStateMachineTest.java b/tests/src/com/android/server/telecom/tests/CallAudioModeStateMachineTest.java
index 0f38ca5..d0a1d8b 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioModeStateMachineTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioModeStateMachineTest.java
@@ -131,7 +131,7 @@
         assertEquals(CallAudioModeStateMachine.STREAMING_STATE_NAME, sm.getCurrentStateName());
 
         verify(mAudioManager, never()).requestAudioFocusForCall(anyInt(), anyInt());
-        verify(mAudioManager).setMode(eq(AudioManager.MODE_CALL_REDIRECT));
+        verify(mAudioManager).setMode(eq(AudioManager.MODE_COMMUNICATION_REDIRECT));
     }
 
     @SmallTest
diff --git a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
index fc4e935..22a850f 100644
--- a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.telecom.tests;
 
+import static android.provider.CallLog.Calls.USER_MISSED_NOT_RUNNING;
+
 import static junit.framework.Assert.assertNotNull;
 import static junit.framework.TestCase.fail;
 
@@ -58,7 +60,9 @@
 import android.os.ResultReceiver;
 import android.os.SystemClock;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.provider.BlockedNumberContract;
+import android.provider.Telephony;
 import android.telecom.CallException;
 import android.telecom.CallScreeningService;
 import android.telecom.CallerInfo;
@@ -166,6 +170,8 @@
             ComponentName.unflattenFromString("com.voip/.Stuff"), "Voip1");
     private static final PhoneAccountHandle SELF_MANAGED_HANDLE = new PhoneAccountHandle(
             ComponentName.unflattenFromString("com.baz/.Self"), "Self");
+    private static final PhoneAccountHandle SELF_MANAGED_2_HANDLE = new PhoneAccountHandle(
+            ComponentName.unflattenFromString("com.baz/.Self2"), "Self2");
     private static final PhoneAccount SIM_1_ACCOUNT = new PhoneAccount.Builder(SIM_1_HANDLE, "Sim1")
             .setCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION
                     | PhoneAccount.CAPABILITY_CALL_PROVIDER
@@ -190,6 +196,11 @@
             .setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)
             .setIsEnabled(true)
             .build();
+    private static final PhoneAccount SELF_MANAGED_2_ACCOUNT = new PhoneAccount.Builder(
+            SELF_MANAGED_2_HANDLE, "Self2")
+            .setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)
+            .setIsEnabled(true)
+            .build();
     private static final Uri TEST_ADDRESS = Uri.parse("tel:555-1212");
     private static final Uri TEST_ADDRESS2 = Uri.parse("tel:555-1213");
     private static final Uri TEST_ADDRESS3 = Uri.parse("tel:555-1214");
@@ -818,7 +829,7 @@
         mCallsManager.answerCall(incomingCall, VideoProfile.STATE_AUDIO_ONLY);
 
         // THEN the ongoing call is held and the focus request for incoming call is sent
-        verify(ongoingCall).hold();
+        verify(ongoingCall).hold(anyString());
         verifyFocusRequestAndExecuteCallback(incomingCall);
 
         // and the incoming call is answered.
@@ -1026,7 +1037,7 @@
         mCallsManager.markCallAsActive(newCall);
 
         // THEN the ongoing call is held
-        verify(ongoingCall).hold();
+        verify(ongoingCall).hold(anyString());
         verifyFocusRequestAndExecuteCallback(newCall);
 
         // and the new call is active
@@ -1683,6 +1694,57 @@
         assertTrue(mCallsManager.makeRoomForOutgoingCall(ongoingCall2));
     }
 
+    /**
+     * Test where a VoIP app adds another new call and has one active already; ensure we hold the
+     * active call.  This assumes same connection service in the same app.
+     */
+    @SmallTest
+    @Test
+    public void testMakeRoomForOutgoingCallForSameVoipApp() {
+        Call activeCall = addSpyCall(SELF_MANAGED_HANDLE, null /* connMgr */,
+                CallState.ACTIVE, Connection.CAPABILITY_HOLD | Connection.CAPABILITY_SUPPORT_HOLD,
+                0 /* properties */);
+        Call newDialingCall = createCall(SELF_MANAGED_HANDLE, CallState.DIALING);
+        newDialingCall.setConnectionProperties(Connection.CAPABILITY_HOLD
+                        | Connection.CAPABILITY_SUPPORT_HOLD);
+        assertTrue(mCallsManager.makeRoomForOutgoingCall(newDialingCall));
+        verify(activeCall).hold(anyString());
+    }
+
+    /**
+     * Test where a VoIP app adds another new call and has one active already; ensure we hold the
+     * active call.  This assumes different connection services in the same app.
+     */
+    @SmallTest
+    @Test
+    public void testMakeRoomForOutgoingCallForSameVoipAppDifferentConnectionService() {
+        Call activeCall = addSpyCall(SELF_MANAGED_HANDLE, null /* connMgr */,
+                CallState.ACTIVE, Connection.CAPABILITY_HOLD | Connection.CAPABILITY_SUPPORT_HOLD,
+                0 /* properties */);
+        Call newDialingCall = createCall(SELF_MANAGED_2_HANDLE, CallState.DIALING);
+        newDialingCall.setConnectionProperties(Connection.CAPABILITY_HOLD
+                | Connection.CAPABILITY_SUPPORT_HOLD);
+        assertTrue(mCallsManager.makeRoomForOutgoingCall(newDialingCall));
+        verify(activeCall).hold(anyString());
+    }
+
+    /**
+     * Test where a VoIP app adds another new call and has one active already; ensure we hold the
+     * active call.  This assumes different connection services in the same app.
+     */
+    @SmallTest
+    @Test
+    public void testMakeRoomForOutgoingCallForSameNonVoipApp() {
+        Call activeCall = addSpyCall(SIM_1_HANDLE, null /* connMgr */,
+                CallState.ACTIVE, Connection.CAPABILITY_HOLD | Connection.CAPABILITY_SUPPORT_HOLD,
+                0 /* properties */);
+        Call newDialingCall = createCall(SIM_1_HANDLE, CallState.DIALING);
+        newDialingCall.setConnectionProperties(Connection.CAPABILITY_HOLD
+                | Connection.CAPABILITY_SUPPORT_HOLD);
+        assertTrue(mCallsManager.makeRoomForOutgoingCall(newDialingCall));
+        verify(activeCall, never()).hold(anyString());
+    }
+
     @SmallTest
     @Test
     public void testMakeRoomForOutgoingCallHasOutgoingCallSelectingAccount() {
@@ -2357,6 +2419,65 @@
         assertEquals(DEFAULT_CALL_SCREENING_APP, outgoingCall.getPostCallPackageName());
     }
 
+    @SmallTest
+    @Test
+    public void testRejectIncomingCallOnPAHInactive() throws Exception {
+        ConnectionServiceWrapper service = mock(ConnectionServiceWrapper.class);
+        doReturn(SIM_2_HANDLE.getComponentName()).when(service).getComponentName();
+        mCallsManager.addConnectionServiceRepositoryCache(SIM_2_HANDLE.getComponentName(),
+                SIM_2_HANDLE.getUserHandle(), service);
+
+        UserManager um = mContext.getSystemService(UserManager.class);
+        when(um.isQuietModeEnabled(eq(SIM_2_HANDLE.getUserHandle()))).thenReturn(true);
+        Call newCall = mCallsManager.processIncomingCallIntent(
+                SIM_2_HANDLE, new Bundle(), false);
+
+        verify(service, timeout(TEST_TIMEOUT)).createConnectionFailed(any());
+        assertFalse(newCall.isInECBM());
+        assertEquals(USER_MISSED_NOT_RUNNING, newCall.getMissedReason());
+    }
+
+    @SmallTest
+    @Test
+    public void testAcceptIncomingCallOnPAHInactiveAndECBMActive() throws Exception {
+        ConnectionServiceWrapper service = mock(ConnectionServiceWrapper.class);
+        doReturn(SIM_2_HANDLE.getComponentName()).when(service).getComponentName();
+        mCallsManager.addConnectionServiceRepositoryCache(SIM_2_HANDLE.getComponentName(),
+                SIM_2_HANDLE.getUserHandle(), service);
+
+        when(mEmergencyCallHelper.isLastOutgoingEmergencyCallPAH(eq(SIM_2_HANDLE)))
+                .thenReturn(true);
+        UserManager um = mContext.getSystemService(UserManager.class);
+        when(um.isQuietModeEnabled(eq(SIM_2_HANDLE.getUserHandle()))).thenReturn(true);
+        Call newCall = mCallsManager.processIncomingCallIntent(
+                SIM_2_HANDLE, new Bundle(), false);
+
+        assertTrue(newCall.isInECBM());
+        verify(service, timeout(TEST_TIMEOUT).times(0)).createConnectionFailed(any());
+    }
+
+    @SmallTest
+    @Test
+    public void testAcceptIncomingEmergencyCallOnPAHInactive() throws Exception {
+        ConnectionServiceWrapper service = mock(ConnectionServiceWrapper.class);
+        doReturn(SIM_2_HANDLE.getComponentName()).when(service).getComponentName();
+        mCallsManager.addConnectionServiceRepositoryCache(SIM_2_HANDLE.getComponentName(),
+                SIM_2_HANDLE.getUserHandle(), service);
+
+        Bundle extras = new Bundle();
+        extras.putParcelable(TelecomManager.EXTRA_INCOMING_CALL_ADDRESS, TEST_ADDRESS);
+        TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
+        UserManager um = mContext.getSystemService(UserManager.class);
+        when(um.isQuietModeEnabled(eq(SIM_2_HANDLE.getUserHandle()))).thenReturn(true);
+        when(tm.isEmergencyNumber(any(String.class))).thenReturn(true);
+        Call newCall = mCallsManager.processIncomingCallIntent(
+                SIM_2_HANDLE, extras, false);
+
+        assertFalse(newCall.isInECBM());
+        assertTrue(newCall.isEmergencyCall());
+        verify(service, timeout(TEST_TIMEOUT).times(0)).createConnectionFailed(any());
+    }
+
     public class LatchedOutcomeReceiver implements OutcomeReceiver<Boolean,
             CallException> {
         CountDownLatch mCountDownLatch;
@@ -2968,6 +3089,10 @@
                 mClockProxy,
                 mToastFactory);
         ongoingCall.setState(initialState, "just cuz");
+        if (targetPhoneAccount == SELF_MANAGED_HANDLE
+                || targetPhoneAccount == SELF_MANAGED_2_HANDLE) {
+            ongoingCall.setIsSelfManaged(true);
+        }
         return ongoingCall;
     }
 
diff --git a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
index e61bc2a..16fd630 100644
--- a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
+++ b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
@@ -595,6 +595,34 @@
     @MediumTest
     @Test
     public void
+    testBindToService_UserAssociatedWithCallIsInQuietMode_NonEmergCallECBM_BindsToPrimaryUser()
+            throws Exception {
+        when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
+        when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+        when(mMockCall.isEmergencyCall()).thenReturn(false);
+        when(mMockCall.isInECBM()).thenReturn(true);
+        when(mMockCall.getUserHandleFromTargetPhoneAccount()).thenReturn(DUMMY_USER_HANDLE);
+        when(mMockContext.getSystemService(eq(UserManager.class)))
+                .thenReturn(mMockUserManager);
+        when(mMockUserManager.isQuietModeEnabled(any(UserHandle.class))).thenReturn(true);
+        setupMockPackageManager(true /* default */, true /* system */, false /* external calls */);
+        setupMockPackageManagerLocationPermission(SYS_PKG, false /* granted */);
+
+        mInCallController.bindToServices(mMockCall);
+
+        ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mMockContext, times(1)).bindServiceAsUser(
+                bindIntentCaptor.capture(),
+                any(ServiceConnection.class),
+                eq(serviceBindingFlags),
+                eq(mUserHandle));
+        Intent bindIntent = bindIntentCaptor.getValue();
+        assertEquals(InCallService.SERVICE_INTERFACE, bindIntent.getAction());
+    }
+
+    @MediumTest
+    @Test
+    public void
     testBindToService_UserAssociatedWithCallNotInQuietMode_EmergCallInCallUi_BindsToAssociatedUser()
         throws Exception {
         when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
@@ -1002,6 +1030,107 @@
     }
 
     /**
+     * Tests a case where InCallController DOES NOT bind to ANY InCallServices when the call is
+     * first added, but then one becomes available after the call starts.  This test was originally
+     * added to reproduce a bug which would cause the call id mapper in the InCallController to not
+     * track a newly added call unless something was bound when the call was first added.
+     * @throws Exception
+     */
+    @MediumTest
+    @Test
+    public void testNoInitialBinding() throws Exception {
+        Bundle callExtras = new Bundle();
+        callExtras.putBoolean("whatever", true);
+
+        // Make a basic call
+        when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
+        when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+        when(mMockCallsManager.isInEmergencyCall()).thenReturn(true);
+        when(mMockCall.isEmergencyCall()).thenReturn(true);
+        when(mMockContext.getSystemService(eq(UserManager.class)))
+                .thenReturn(mMockUserManager);
+        when(mMockUserManager.isQuietModeEnabled(any(UserHandle.class))).thenReturn(false);
+        when(mMockCall.isIncoming()).thenReturn(false);
+        when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
+        when(mMockCall.getIntentExtras()).thenReturn(callExtras);
+        when(mMockCall.isExternalCall()).thenReturn(false);
+        when(mMockCall.isSelfManaged()).thenReturn(true);
+        when(mMockCall.visibleToInCallService()).thenReturn(true);
+
+        // Dialer doesn't handle these calls, but non-UI ICS does.
+        when(mDefaultDialerCache.getDefaultDialerApplication(CURRENT_USER_ID))
+                .thenReturn(DEF_PKG);
+        ArgumentCaptor<ServiceConnection> serviceConnectionCaptor =
+                ArgumentCaptor.forClass(ServiceConnection.class);
+        when(mMockContext.bindServiceAsUser(any(Intent.class), serviceConnectionCaptor.capture(),
+                eq(serviceBindingFlags),
+                eq(mUserHandle))).thenReturn(true);
+        when(mTimeoutsAdapter.getEmergencyCallbackWindowMillis(any(ContentResolver.class)))
+                .thenReturn(300_000L);
+
+        // Setup package manager; there is a dialer and disable non-ui ICS
+        when(mMockPackageManager.queryIntentServicesAsUser(
+                any(Intent.class), anyInt(), anyInt())).thenReturn(
+                Arrays.asList(
+                        getDefResolveInfo(false /* externalCalls */, false /* selfMgd */),
+                        getNonUiResolveinfo(true /* selfManaged */,
+                                false /* isEnabled */)
+                )
+        );
+        when(mMockPackageManager
+                .getComponentEnabledSetting(new ComponentName(DEF_PKG, DEF_CLASS)))
+                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
+        when(mMockPackageManager
+                .getComponentEnabledSetting(new ComponentName(NONUI_PKG, NONUI_CLASS)))
+                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DISABLED);
+
+        // Add the call.
+        mInCallController.onCallAdded(mMockCall);
+
+        // There will be 4 calls for the various types of ICS; this is normal.
+        verify(mMockPackageManager, times(4)).queryIntentServicesAsUser(
+                any(Intent.class),
+                eq(PackageManager.GET_META_DATA | PackageManager.MATCH_DISABLED_COMPONENTS),
+                eq(CURRENT_USER_ID));
+
+        // Verify no bind at this point
+        ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mMockContext, never()).bindServiceAsUser(
+                bindIntentCaptor.capture(),
+                any(ServiceConnection.class),
+                eq(serviceBindingFlags),
+                eq(mUserHandle));
+
+        // Setup mocks to enable non-ui ICS
+        when(mMockPackageManager.queryIntentServicesAsUser(
+                any(Intent.class), anyInt(), anyInt())).thenReturn(
+                Arrays.asList(
+                        getDefResolveInfo(false /* externalCalls */, false /* selfMgd */),
+                        getNonUiResolveinfo(true /* selfManaged */,
+                                true /* isEnabled */)
+                )
+        );
+        when(mMockPackageManager
+                .getComponentEnabledSetting(new ComponentName(NONUI_PKG, NONUI_CLASS)))
+                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
+
+        // Emulate a late enable of the non-ui ICS
+        Intent packageUpdated = new Intent(Intent.ACTION_PACKAGE_CHANGED);
+        packageUpdated.setData(Uri.fromParts("package", NONUI_PKG, null));
+        packageUpdated.putExtra(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST,
+                new String[] {NONUI_CLASS});
+        packageUpdated.putExtra(Intent.EXTRA_UID, NONUI_UID);
+        mRegisteredReceiver.onReceive(mMockContext, packageUpdated);
+
+        // Make sure we bound to it.
+        verify(mMockContext, times(1)).bindServiceAsUser(
+                bindIntentCaptor.capture(),
+                any(ServiceConnection.class),
+                eq(serviceBindingFlags),
+                eq(mUserHandle));
+    }
+
+    /**
      * Ensures that the {@link InCallController} will bind to an {@link InCallService} which
      * supports external calls.
      */