Merge "Fix holding behavior across self-manged ConnectionServices." 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 e2d8489..6ae8834 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);
             }
@@ -4575,7 +4605,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
@@ -4595,7 +4625,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 e0e17cc..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);
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/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 413989b..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;
@@ -2415,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;
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.
      */