Restrict calling in favor of emergency calling - Incoming Cases

The first series of CLs (1/2) that allow for better interactions
between emergency calls and incoming call types. This includes:
1) Disconnecting incoming calls and marking them as MISSED if an
outgoing emergency call is placed before the call is set to active.
2) Not allowing the pulling of external calls while an emergency
call is active and showing a Toast.
3) If a call is in AUDIO_PROCESSING state, hang it up in favor of
a new outgoing emergency call (and mark disconnected call as
MISSED if this is during an initial call screen).
4) If a call is in SIMULATED_RINGING state, hang it up in favor of
a new outgoing emergency call.

Bug: 138741228
Test: manual; atest TelecomUnitTests; atest CtsTelecomTestCases
Change-Id: I74d2b174121fb9c6402caf806d20eb6c5226873a
diff --git a/Android.bp b/Android.bp
index 629c95c..a4cb2ca 100644
--- a/Android.bp
+++ b/Android.bp
@@ -33,6 +33,7 @@
         "androidx.legacy_legacy-support-core-utils",
         "androidx.core_core",
         "androidx.fragment_fragment",
+        "androidx.test.ext.junit"
     ],
     srcs: [
         "tests/src/**/*.java",
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 3112abe..2c6a84a 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -335,6 +335,11 @@
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt">Call Blocking has been disabled to allow emergency responders to contact you.</string>
     <!-- Window title used for the Telecom Developer Menu -->
     <string name="developer_title">Telecom Developer Menu</string>
+    <!-- Some carriers allow an "external" call that has been placed on another device (another
+    tablet, phone, etc...) to be transferred to this device and taken as a call. In
+    the situation that we are already in an emergency call, notify the user that the call can not
+    be taken at this time.-->
+    <string name="toast_emergency_can_not_pull_call">Calls can not be taken while in an emergency call.</string>
     <!-- Label for a switch in the Telecom Developer Menu which is used to enable the enhanced call
          blocking functionality (for test purposes).
          DO NOT TRANSLATE -->
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index 6e48f56..e48cc84 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -58,6 +58,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telecom.IVideoProvider;
 import com.android.internal.util.Preconditions;
+import com.android.server.telecom.ui.ToastFactory;
 
 import java.io.IOException;
 import java.text.SimpleDateFormat;
@@ -435,6 +436,7 @@
     private final Context mContext;
     private final CallsManager mCallsManager;
     private final ClockProxy mClockProxy;
+    private final ToastFactory mToastFactory;
     private final TelecomSystem.SyncRoot mLock;
     private final String mId;
     private String mConnectionId;
@@ -560,6 +562,17 @@
     private boolean mIsUsingCallFiltering = false;
 
     /**
+     * Indicates whether or not this call has been active before. This is helpful in detecting
+     * situations where we have moved into {@link CallState#SIMULATED_RINGING} or
+     * {@link CallState#AUDIO_PROCESSING} again after being active. If a call has moved into one
+     * of these states again after being active and the user dials an emergency call, we want to
+     * log these calls normally instead of considering them MISSED. If the emergency call was
+     * dialed during initial screening however, we want to treat those calls as MISSED (because the
+     * user never got the chance to explicitly reject).
+     */
+    private boolean mHasGoneActiveBefore = false;
+
+    /**
      * Persists the specified parameters and initializes the new instance.
      * @param context The context.
      * @param repository The connection service repository.
@@ -589,7 +602,8 @@
             int callDirection,
             boolean shouldAttachToExistingConnection,
             boolean isConference,
-            ClockProxy clockProxy) {
+            ClockProxy clockProxy,
+            ToastFactory toastFactory) {
         mId = callId;
         mConnectionId = callId;
         mState = isConference ? CallState.ACTIVE : CallState.NEW;
@@ -610,6 +624,7 @@
                 || callDirection == CALL_DIRECTION_INCOMING;
         maybeLoadCannedSmsResponses();
         mClockProxy = clockProxy;
+        mToastFactory = toastFactory;
         mCreationTimeMillis = mClockProxy.currentTimeMillis();
     }
 
@@ -647,11 +662,12 @@
             boolean isConference,
             long connectTimeMillis,
             long connectElapsedTimeMillis,
-            ClockProxy clockProxy) {
+            ClockProxy clockProxy,
+            ToastFactory toastFactory) {
         this(callId, context, callsManager, lock, repository,
                 phoneNumberUtilsAdapter, handle, gatewayInfo,
                 connectionManagerPhoneAccountHandle, targetPhoneAccountHandle, callDirection,
-                shouldAttachToExistingConnection, isConference, clockProxy);
+                shouldAttachToExistingConnection, isConference, clockProxy, toastFactory);
 
         mConnectTimeMillis = connectTimeMillis;
         mConnectElapsedTimeMillis = connectElapsedTimeMillis;
@@ -943,6 +959,7 @@
                 // We're clearly not disconnected, so reset the disconnected time.
                 mDisconnectTimeMillis = 0;
                 mDisconnectElapsedTimeMillis = 0;
+                mHasGoneActiveBefore = true;
             } else if (mState == CallState.DISCONNECTED) {
                 mDisconnectTimeMillis = mClockProxy.currentTimeMillis();
                 mDisconnectElapsedTimeMillis = mClockProxy.elapsedRealtime();
@@ -1944,10 +1961,11 @@
             Log.v(this, "Aborting call %s", this);
             abort(disconnectionTimeout);
         } else if (mState != CallState.ABORTED && mState != CallState.DISCONNECTED) {
-            if (mState == CallState.AUDIO_PROCESSING) {
+            if (mState == CallState.AUDIO_PROCESSING && !hasGoneActiveBefore()) {
                 mOverrideDisconnectCauseCode = DisconnectCause.REJECTED;
             } else if (mState == CallState.SIMULATED_RINGING) {
                 // This is the case where the dialer calls disconnect() because the call timed out
+                // or an emergency call was dialed while in this state.
                 // Override the disconnect cause to MISSED
                 mOverrideDisconnectCauseCode = DisconnectCause.MISSED;
             }
@@ -2389,6 +2407,7 @@
      * device, so will issue a request to pull the call to the second device.
      * <p>
      * Requests to pull a call which is not external, or a call which is not pullable are ignored.
+     * If there is an ongoing emergency call, pull requests are also ignored.
      */
     public void pullExternalCall() {
         if (mConnectionService == null) {
@@ -2404,6 +2423,15 @@
             Log.w(this, "pullExternalCall - call %s is external but cannot be pulled.", mId);
             return;
         }
+
+        if (mCallsManager.isInEmergencyCall()) {
+            Log.w(this, "pullExternalCall = pullExternalCall - call %s is external but can not be"
+                    + " pulled while an emergency call is in progress.", mId);
+            mToastFactory.makeText(mContext, R.string.toast_emergency_can_not_pull_call,
+                    Toast.LENGTH_LONG).show();
+            return;
+        }
+
         Log.addEvent(this, LogUtils.Events.REQUEST_PULL);
         mConnectionService.pullExternalCall(this);
     }
@@ -2438,8 +2466,9 @@
                     mHandler.post(new Runnable() {
                         @Override
                         public void run() {
-                            Toast.makeText(mContext, "WARNING: Event-based handover APIs are deprecated "
-                                            + "and will no longer function in Android Q.",
+                            mToastFactory.makeText(mContext,
+                                    "WARNING: Event-based handover APIs are deprecated and will no"
+                                            + " longer function in Android Q.",
                                     Toast.LENGTH_LONG).show();
                         }
                     });
@@ -3317,6 +3346,18 @@
     }
 
     /**
+     * In some cases, we need to know if this call has ever gone active (for example, the case
+     * when the call was put into the {@link CallState#AUDIO_PROCESSING} state after being active)
+     * for call logging purposes.
+     *
+     * @return {@code true} if this call has gone active before (even if it isn't now), false if it
+     * has never gone active.
+     */
+    public boolean hasGoneActiveBefore() {
+        return mHasGoneActiveBefore;
+    }
+
+    /**
      * When upgrading a call to video via
      * {@link VideoProviderProxy#onSendSessionModifyRequest(VideoProfile, VideoProfile)}, if the
      * upgrade is from audio to video, potentially auto-engage the speakerphone.
diff --git a/src/com/android/server/telecom/CallAudioManager.java b/src/com/android/server/telecom/CallAudioManager.java
index 87ca26c..3a84407 100644
--- a/src/com/android/server/telecom/CallAudioManager.java
+++ b/src/com/android/server/telecom/CallAudioManager.java
@@ -363,7 +363,7 @@
     @VisibleForTesting
     public void toggleMute() {
         // Don't mute if there are any emergency calls.
-        if (mCallsManager.hasEmergencyCall()) {
+        if (mCallsManager.isInEmergencyCall()) {
             Log.v(this, "ignoring toggleMute for emergency call");
             return;
         }
@@ -382,7 +382,7 @@
         Log.v(this, "mute, shouldMute: %b", shouldMute);
 
         // Don't mute if there are any emergency calls.
-        if (mCallsManager.hasEmergencyCall()) {
+        if (mCallsManager.isInEmergencyCall()) {
             shouldMute = false;
             Log.v(this, "ignoring mute for emergency call");
         }
diff --git a/src/com/android/server/telecom/CallAudioRouteStateMachine.java b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
index 0d070c8..3a5d113 100644
--- a/src/com/android/server/telecom/CallAudioRouteStateMachine.java
+++ b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
@@ -1278,7 +1278,7 @@
             Log.startSession("CARSM.mCR");
             try {
                 if (AudioManager.ACTION_MICROPHONE_MUTE_CHANGED.equals(intent.getAction())) {
-                    if (mCallsManager.hasEmergencyCall()) {
+                    if (mCallsManager.isInEmergencyCall()) {
                         Log.i(this, "Mute was externally changed when there's an emergency call. " +
                                 "Forcing mute back off.");
                         sendInternalMessage(MUTE_OFF);
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index 63fe1f4..e136950 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -95,6 +95,7 @@
 import com.android.server.telecom.ui.CallRedirectionTimeoutDialogActivity;
 import com.android.server.telecom.ui.ConfirmCallDialogActivity;
 import com.android.server.telecom.ui.IncomingCallNotifier;
+import com.android.server.telecom.ui.ToastFactory;
 
 import java.util.Arrays;
 import java.util.Collection;
@@ -203,7 +204,7 @@
      */
     private static final int[] LIVE_CALL_STATES =
             {CallState.CONNECTING, CallState.SELECT_PHONE_ACCOUNT, CallState.DIALING,
-                    CallState.PULLING, CallState.ACTIVE};
+                    CallState.PULLING, CallState.ACTIVE, CallState.AUDIO_PROCESSING};
 
     /**
      * These states determine which calls will cause {@link TelecomManager#isInCall()} or
@@ -214,13 +215,15 @@
      */
     public static final int[] ONGOING_CALL_STATES =
             {CallState.SELECT_PHONE_ACCOUNT, CallState.DIALING, CallState.PULLING, CallState.ACTIVE,
-                    CallState.ON_HOLD, CallState.RINGING, CallState.ANSWERED};
+                    CallState.ON_HOLD, CallState.RINGING,  CallState.SIMULATED_RINGING,
+                    CallState.ANSWERED, CallState.AUDIO_PROCESSING};
 
     private static final int[] ANY_CALL_STATE =
             {CallState.NEW, CallState.CONNECTING, CallState.SELECT_PHONE_ACCOUNT, CallState.DIALING,
-                    CallState.RINGING, CallState.ACTIVE, CallState.ON_HOLD, CallState.DISCONNECTED,
-                    CallState.ABORTED, CallState.DISCONNECTING, CallState.PULLING,
-                    CallState.ANSWERED};
+                    CallState.RINGING, CallState.SIMULATED_RINGING, CallState.ACTIVE,
+                    CallState.ON_HOLD, CallState.DISCONNECTED, CallState.ABORTED,
+                    CallState.DISCONNECTING, CallState.PULLING, CallState.ANSWERED,
+                    CallState.AUDIO_PROCESSING};
 
     public static final String TELECOM_CALL_ID_PREFIX = "TC@";
 
@@ -331,6 +334,7 @@
     private final Timeouts.Adapter mTimeoutsAdapter;
     private final PhoneNumberUtilsAdapter mPhoneNumberUtilsAdapter;
     private final ClockProxy mClockProxy;
+    private final ToastFactory mToastFactory;
     private final Set<Call> mLocallyDisconnectingCalls = new HashSet<>();
     private final Set<Call> mPendingCallsToDisconnect = new HashSet<>();
     private final ConnectionServiceFocusManager mConnectionSvrFocusMgr;
@@ -454,7 +458,8 @@
             CallAudioModeStateMachine.Factory callAudioModeStateMachineFactory,
             InCallControllerFactory inCallControllerFactory,
             RoleManagerAdapter roleManagerAdapter,
-            IncomingCallFilter.Factory incomingCallFilterFactory) {
+            IncomingCallFilter.Factory incomingCallFilterFactory,
+            ToastFactory toastFactory) {
         mContext = context;
         mLock = lock;
         mPhoneNumberUtilsAdapter = phoneNumberUtilsAdapter;
@@ -529,6 +534,7 @@
                 new ConnectionServiceRepository(mPhoneAccountRegistrar, mContext, mLock, this);
         mInCallWakeLockController = inCallWakeLockControllerFactory.create(context, this);
         mClockProxy = clockProxy;
+        mToastFactory = toastFactory;
         mRoleManagerAdapter = roleManagerAdapter;
 
         mListeners.add(mInCallWakeLockController);
@@ -1054,16 +1060,6 @@
         return mPhoneAccountListener;
     }
 
-    @VisibleForTesting
-    public boolean hasEmergencyCall() {
-        for (Call call : mCalls) {
-            if (call.isEmergencyCall()) {
-                return true;
-            }
-        }
-        return false;
-    }
-
     public boolean hasEmergencyRttCall() {
         for (Call call : mCalls) {
             if (call.isEmergencyCall() && call.isRttCall()) {
@@ -1147,7 +1143,8 @@
                 Call.CALL_DIRECTION_INCOMING /* callDirection */,
                 false /* forceAttachToExistingConnection */,
                 false, /* isConference */
-                mClockProxy);
+                mClockProxy,
+                mToastFactory);
 
         // Ensure new calls related to self-managed calls/connections are set as such.  This will
         // be overridden when the actual connection is returned in startCreateConnection, however
@@ -1260,6 +1257,13 @@
         if (!isHandoverAllowed || (call.isSelfManaged() && !isIncomingCallPermitted(call,
                 call.getTargetPhoneAccount()))) {
             notifyCreateConnectionFailed(phoneAccountHandle, call);
+        } else if (isInEmergencyCall()) {
+            // The incoming call is implicitly being rejected so the user does not get any incoming
+            // call UI during an emergency call. In this case, log the call as missed instead of
+            // rejected since the user did not explicitly reject.
+            mCallLogManager.logCall(call, Calls.MISSED_TYPE,
+                    true /*showNotificationForMissedCall*/, null /*CallFilteringResult*/);
+            notifyCreateConnectionFailed(phoneAccountHandle, call);
         } else {
             call.startCreateConnection(mPhoneAccountRegistrar);
         }
@@ -1284,7 +1288,8 @@
                 // to the existing connection instead of trying to create a new one.
                 true /* forceAttachToExistingConnection */,
                 false, /* isConference */
-                mClockProxy);
+                mClockProxy,
+                mToastFactory);
         call.initAnalytics();
 
         setIntentExtrasAndStartTime(call, extras);
@@ -1368,7 +1373,8 @@
                     Call.CALL_DIRECTION_OUTGOING /* callDirection */,
                     false /* forceAttachToExistingConnection */,
                     false, /* isConference */
-                    mClockProxy);
+                    mClockProxy,
+                    mToastFactory);
             call.initAnalytics(callingPackage);
 
             // Ensure new calls related to self-managed calls/connections are set as such.  This
@@ -2486,7 +2492,7 @@
 
     /** Called by the in-call UI to change the mute state. */
     void mute(boolean shouldMute) {
-        if (hasEmergencyCall() && shouldMute) {
+        if (isInEmergencyCall() && shouldMute) {
             Log.i(this, "Refusing to turn on mute because we're in an emergency call");
             shouldMute = false;
         }
@@ -3050,7 +3056,8 @@
                 true /* isConference */,
                 connectTime,
                 connectElapsedTime,
-                mClockProxy);
+                mClockProxy,
+                mToastFactory);
 
         setCallState(call, Call.getStateFromConnectionState(parcelableConference.getState()),
                 "new conference call");
@@ -3597,7 +3604,8 @@
                 && incomingCall.getHandoverSourceCall() == null;
     }
 
-    private boolean makeRoomForOutgoingCall(Call call, boolean isEmergency) {
+    @VisibleForTesting
+    public boolean makeRoomForOutgoingCall(Call call, boolean isEmergency) {
         if (hasMaximumLiveCalls(call)) {
             // NOTE: If the amount of live calls changes beyond 1, this logic will probably
             // have to change.
@@ -3634,6 +3642,13 @@
                 return false;
             }
 
+            if (liveCall.getState() == CallState.AUDIO_PROCESSING && isEmergency) {
+                call.getAnalytics().setCallIsAdditional(true);
+                liveCall.getAnalytics().setCallIsInterrupted(true);
+                liveCall.disconnect(0, "disconnecting audio processing call for emergency");
+                return true;
+            }
+
             // If we have the max number of held managed calls and we're placing an emergency call,
             // we'll disconnect the ongoing call if it cannot be held.
             if (hasMaximumManagedHoldingCalls(call) && isEmergency && !canHold(liveCall)) {
@@ -3690,6 +3705,30 @@
 
             // The live call cannot be held so we're out of luck here.  There's no room.
             return false;
+        } else if (hasRingingOrSimulatedRingingCall() && isEmergency) {
+            Call ringingCall = getRingingOrSimulatedRingingCall();
+            ringingCall.getAnalytics().setCallIsAdditional(true);
+            ringingCall.getAnalytics().setCallIsInterrupted(true);
+            if (ringingCall.getState() == CallState.SIMULATED_RINGING) {
+                    if (!ringingCall.hasGoneActiveBefore()) {
+                        // If this is an incoming call that is currently in SIMULATED_RINGING only
+                        // after a call screen, disconnect to make room and mark as missed, since
+                        // the user didn't get a chance to accept/reject.
+                        ringingCall.disconnect("emergency call dialed during simulated ringing "
+                                + "after screen.");
+                    } else {
+                        // If this is a simulated ringing call after being active and put in
+                        // AUDIO_PROCESSING state again, disconnect normally.
+                        ringingCall.reject(false, null, "emergency call dialed during simulated "
+                                + "ringing.");
+                    }
+            } else { // normal incoming ringing call.
+                // Hang up the ringing call to make room for the emergency call and mark as missed,
+                // since the user did not reject.
+                ringingCall.setOverrideDisconnectCauseCode(DisconnectCause.MISSED);
+                ringingCall.reject(false, null, "emergency call dialed during ringing.");
+            }
+            return true;
         }
         return true;
     }
@@ -3773,7 +3812,8 @@
                 isDowngradedConference /* isConference */,
                 connection.getConnectTimeMillis() /* connectTimeMillis */,
                 connection.getConnectElapsedTimeMillis(), /* connectElapsedTimeMillis */
-                mClockProxy);
+                mClockProxy,
+                mToastFactory);
 
         call.initAnalytics();
         call.getAnalytics().setCreatedFromExistingConnection(true);
@@ -3923,13 +3963,13 @@
         if (phoneAccount == null) {
             return false;
         }
+        if (isInEmergencyCall()) return false;
 
         if (!phoneAccount.isSelfManaged()) {
             return !hasMaximumManagedRingingCalls(excludeCall) &&
                     !hasMaximumManagedHoldingCalls(excludeCall);
         } else {
-            return !hasEmergencyCall() &&
-                    !hasMaximumSelfManagedRingingCalls(excludeCall, phoneAccountHandle) &&
+            return !hasMaximumSelfManagedRingingCalls(excludeCall, phoneAccountHandle) &&
                     !hasMaximumSelfManagedCalls(excludeCall, phoneAccountHandle);
         }
     }
@@ -3960,7 +4000,7 @@
             // 2. The outgoing call is an handover call or it not hit the self-managed call limit
             // and the current active call can be held.
             Call activeCall = (Call) mConnectionSvrFocusMgr.getCurrentFocusCall();
-            return !hasEmergencyCall() &&
+            return !isInEmergencyCall() &&
                     ((excludeCall != null && excludeCall.getHandoverSourceCall() != null) ||
                             (!hasMaximumSelfManagedCalls(excludeCall, phoneAccountHandle) &&
                                     (activeCall == null || canHold(activeCall))));
@@ -4281,110 +4321,6 @@
         Log.addEvent(handoverFromCall, LogUtils.Events.HANDOVER_REQUEST, "legacy request denied");
     }
 
-    public Call createHandoverCall(Uri handle, PhoneAccountHandle handoverToHandle,
-            Bundle extras, UserHandle initiatingUser) {
-        boolean isReusedCall = true;
-        Call call = reuseOutgoingCall(handle);
-
-        PhoneAccount account =
-                mPhoneAccountRegistrar.getPhoneAccount(handoverToHandle, initiatingUser);
-        boolean isSelfManaged = account != null && account.isSelfManaged();
-
-        // Create a call with original handle. The handle may be changed when the call is attached
-        // to a connection service, but in most cases will remain the same.
-        if (call == null) {
-            call = new Call(getNextCallId(), mContext,
-                    this,
-                    mLock,
-                    mConnectionServiceRepository,
-                    mPhoneNumberUtilsAdapter,
-                    handle,
-                    null /* gatewayInfo */,
-                    null /* connectionManagerPhoneAccount */,
-                    null /* handoverToHandle */,
-                    Call.CALL_DIRECTION_OUTGOING /* callDirection */,
-                    false /* forceAttachToExistingConnection */,
-                    false, /* isConference */
-                    mClockProxy);
-            call.initAnalytics(null);
-
-            // Ensure new calls related to self-managed calls/connections are set as such.  This
-            // will be overridden when the actual connection is returned in startCreateConnection,
-            // however doing this now ensures the logs and any other logic will treat this call as
-            // self-managed from the moment it is created.
-            call.setIsSelfManaged(isSelfManaged);
-            if (isSelfManaged) {
-                // Self-managed calls will ALWAYS use voip audio mode.
-                call.setIsVoipAudioMode(true);
-            }
-            call.setInitiatingUser(initiatingUser);
-            isReusedCall = false;
-        }
-
-        int videoState = VideoProfile.STATE_AUDIO_ONLY;
-        if (extras != null) {
-            // Set the video state on the call early so that when it is added to the InCall UI the
-            // UI knows to configure itself as a video call immediately.
-            videoState = extras.getInt(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
-                    VideoProfile.STATE_AUDIO_ONLY);
-
-            // If this is an emergency video call, we need to check if the phone account supports
-            // emergency video calling.
-            // Also, ensure we don't try to place an outgoing call with video if video is not
-            // supported.
-            if (VideoProfile.isVideo(videoState)) {
-                if (call.isEmergencyCall() && account != null &&
-                        !account.hasCapabilities(PhoneAccount.CAPABILITY_EMERGENCY_VIDEO_CALLING)) {
-                    // Phone account doesn't support emergency video calling, so fallback to
-                    // audio-only now to prevent the InCall UI from setting up video surfaces
-                    // needlessly.
-                    Log.i(this, "startOutgoingCall - emergency video calls not supported; " +
-                            "falling back to audio-only");
-                    videoState = VideoProfile.STATE_AUDIO_ONLY;
-                } else if (account != null &&
-                        !account.hasCapabilities(PhoneAccount.CAPABILITY_VIDEO_CALLING)) {
-                    // Phone account doesn't support video calling, so fallback to audio-only.
-                    Log.i(this, "startOutgoingCall - video calls not supported; fallback to " +
-                            "audio-only.");
-                    videoState = VideoProfile.STATE_AUDIO_ONLY;
-                }
-            }
-
-            call.setVideoState(videoState);
-        }
-
-        call.setTargetPhoneAccount(handoverToHandle);
-
-        // If there's no more room for a handover, just fail.
-        if ((!isReusedCall && !makeRoomForOutgoingCall(call, call.isEmergencyCall()))) {
-            return null;
-        }
-
-        PhoneAccount accountToUse =
-                mPhoneAccountRegistrar.getPhoneAccount(handoverToHandle, initiatingUser);
-        if (accountToUse != null && accountToUse.getExtras() != null) {
-            if (accountToUse.getExtras()
-                    .getBoolean(PhoneAccount.EXTRA_ALWAYS_USE_VOIP_AUDIO_MODE)) {
-                Log.d(this, "startOutgoingCall: defaulting to voip mode for call %s",
-                        call.getId());
-                call.setIsVoipAudioMode(true);
-            }
-        }
-
-        call.setState(
-                CallState.CONNECTING,
-                handoverToHandle == null ? "no-handle" : handoverToHandle.toString());
-
-        setIntentExtrasAndStartTime(call, extras);
-
-        if (!mCalls.contains(call)) {
-            // We check if mCalls already contains the call because we could potentially be reusing
-            // a call which was previously added (See {@link #reuseOutgoingCall}).
-            addCall(call);
-        }
-
-        return call;
-    }
     /**
      * Called in response to a {@link Call} receiving a {@link Call#handoverTo(PhoneAccountHandle,
      * int, Bundle)} indicating the {@link android.telecom.InCallService} has requested a
@@ -4402,7 +4338,7 @@
                                  int videoState, Bundle extras) {
 
         // Send an error back if there are any ongoing emergency calls.
-        if (hasEmergencyCall()) {
+        if (isInEmergencyCall()) {
             handoverFromCall.onHandoverFailed(
                     android.telecom.Call.Callback.HANDOVER_FAILURE_ONGOING_EMERGENCY_CALL);
             return;
@@ -4431,7 +4367,7 @@
                 handoverFromCall.getHandle(), null,
                 null, null,
                 Call.CALL_DIRECTION_OUTGOING, false,
-                false, mClockProxy);
+                false, mClockProxy, mToastFactory);
         call.initAnalytics();
 
         // Set self-managed and voipAudioMode if destination is self-managed CS
@@ -4635,12 +4571,13 @@
                 Call.CALL_DIRECTION_INCOMING /* callDirection */,
                 false /* forceAttachToExistingConnection */,
                 false, /* isConference */
-                mClockProxy);
+                mClockProxy,
+                mToastFactory);
 
         if (fromCall == null || isHandoverInProgress() ||
                 !isHandoverFromPhoneAccountSupported(fromCall.getTargetPhoneAccount()) ||
                 !isHandoverToPhoneAccountSupported(destAcct) ||
-                hasEmergencyCall()) {
+                isInEmergencyCall()) {
             Log.w(this, "acceptHandover: Handover not supported");
             notifyHandoverFailed(call,
                     android.telecom.Call.Callback.HANDOVER_FAILURE_NOT_SUPPORTED);
@@ -4806,8 +4743,8 @@
      * @return {@code true} if there is an ongoing emergency call, {@code false} otherwise.
      */
     public boolean isInEmergencyCall() {
-        return mCalls.stream().filter(c -> c.isEmergencyCall()
-                || c.isNetworkIdentifiedEmergencyCall()).count() > 0;
+        return mCalls.stream().filter(c -> (c.isEmergencyCall()
+                || c.isNetworkIdentifiedEmergencyCall()) && !c.isDisconnected()).count() > 0;
     }
 
     /**
diff --git a/src/com/android/server/telecom/ConnectionServiceWrapper.java b/src/com/android/server/telecom/ConnectionServiceWrapper.java
index cf7abad..d608852 100644
--- a/src/com/android/server/telecom/ConnectionServiceWrapper.java
+++ b/src/com/android/server/telecom/ConnectionServiceWrapper.java
@@ -1527,7 +1527,8 @@
         }
     }
 
-    void pullExternalCall(Call call) {
+    @VisibleForTesting
+    public void pullExternalCall(Call call) {
         final String callId = mCallIdMapper.getCallId(call);
         if (callId != null && isServiceValid("pullExternalCall")) {
             try {
diff --git a/src/com/android/server/telecom/InCallController.java b/src/com/android/server/telecom/InCallController.java
index c4df1d3..b0ac9d7 100644
--- a/src/com/android/server/telecom/InCallController.java
+++ b/src/com/android/server/telecom/InCallController.java
@@ -24,7 +24,6 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
-import android.content.res.Resources;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
@@ -1107,7 +1106,7 @@
                     mDefaultDialerCache.getSystemDialerComponent(), IN_CALL_SERVICE_TYPE_SYSTEM_UI);
             EmergencyInCallServiceConnection systemInCall =
                     new EmergencyInCallServiceConnection(systemInCallInfo, dialerInCall);
-            systemInCall.setHasEmergency(mCallsManager.hasEmergencyCall());
+            systemInCall.setHasEmergency(mCallsManager.isInEmergencyCall());
 
             InCallServiceConnection carModeInCall = null;
             InCallServiceInfo carModeComponentInfo = getCarModeComponent();
@@ -1334,7 +1333,7 @@
     private void adjustServiceBindingsForEmergency() {
         // The connected UI is not the system UI, so lets check if we should switch them
         // if there exists an emergency number.
-        if (mCallsManager.hasEmergencyCall()) {
+        if (mCallsManager.isInEmergencyCall()) {
             mInCallServiceConnection.setHasEmergency(true);
         }
     }
diff --git a/src/com/android/server/telecom/TelecomSystem.java b/src/com/android/server/telecom/TelecomSystem.java
index f3b7b25..7700852 100644
--- a/src/com/android/server/telecom/TelecomSystem.java
+++ b/src/com/android/server/telecom/TelecomSystem.java
@@ -29,6 +29,7 @@
 import com.android.server.telecom.BluetoothPhoneServiceImpl.BluetoothPhoneServiceImplFactory;
 import com.android.server.telecom.CallAudioManager.AudioServiceFactory;
 import com.android.server.telecom.DefaultDialerCache.DefaultDialerManagerAdapter;
+import com.android.server.telecom.ui.ToastFactory;
 
 import android.Manifest;
 import android.content.BroadcastReceiver;
@@ -42,6 +43,7 @@
 import android.os.UserManager;
 import android.telecom.Log;
 import android.telecom.PhoneAccountHandle;
+import android.widget.Toast;
 
 import java.io.FileNotFoundException;
 import java.io.InputStream;
@@ -271,6 +273,19 @@
         AudioProcessingNotification audioProcessingNotification =
                 new AudioProcessingNotification(mContext);
 
+        ToastFactory toastFactory = new ToastFactory() {
+            @Override
+            public Toast makeText(Context context, int resId, int duration) {
+                return Toast.makeText(context, context.getMainLooper(), context.getString(resId),
+                        duration);
+            }
+
+            @Override
+            public Toast makeText(Context context, CharSequence text, int duration) {
+                return Toast.makeText(context, context.getMainLooper(), text, duration);
+            }
+        };
+
         mCallsManager = new CallsManager(
                 mContext,
                 mLock,
@@ -298,7 +313,8 @@
                 callAudioModeStateMachineFactory,
                 inCallControllerFactory,
                 roleManagerAdapter,
-                incomingCallFilterFactory);
+                incomingCallFilterFactory,
+                toastFactory);
 
         mIncomingCallNotifier = incomingCallNotifier;
         incomingCallNotifier.setCallsManagerProxy(new IncomingCallNotifier.CallsManagerProxy() {
diff --git a/src/com/android/server/telecom/ui/ToastFactory.java b/src/com/android/server/telecom/ui/ToastFactory.java
new file mode 100644
index 0000000..75b69da
--- /dev/null
+++ b/src/com/android/server/telecom/ui/ToastFactory.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2019 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.ui;
+
+import android.annotation.StringRes;
+import android.content.Context;
+import android.widget.Toast;
+
+public interface ToastFactory {
+    Toast makeText(Context context, @StringRes int resId, int duration);
+    Toast makeText(Context context, CharSequence text, int duration);
+}
diff --git a/testapps/src/com/android/server/telecom/testapps/TestConnectionService.java b/testapps/src/com/android/server/telecom/testapps/TestConnectionService.java
index 90509f6..f6fa116 100644
--- a/testapps/src/com/android/server/telecom/testapps/TestConnectionService.java
+++ b/testapps/src/com/android/server/telecom/testapps/TestConnectionService.java
@@ -182,7 +182,7 @@
             setConnectionProperties(properties);
 
             if (isIncoming) {
-                Bundle newExtras = getExtras();
+                Bundle newExtras = (getExtras() == null) ? new Bundle() : getExtras();
                 newExtras.putBoolean(Connection.EXTRA_ANSWERING_DROPS_FG_CALL, true);
                 putExtras(newExtras);
             }
diff --git a/tests/src/com/android/server/telecom/tests/CallTest.java b/tests/src/com/android/server/telecom/tests/CallTest.java
new file mode 100644
index 0000000..0566999
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/CallTest.java
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2019 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.tests;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.content.ComponentName;
+import android.net.Uri;
+import android.telecom.Connection;
+import android.telecom.DisconnectCause;
+import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.widget.Toast;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallState;
+import com.android.server.telecom.CallerInfoLookupHelper;
+import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.ClockProxy;
+import com.android.server.telecom.ConnectionServiceWrapper;
+import com.android.server.telecom.PhoneAccountRegistrar;
+import com.android.server.telecom.PhoneNumberUtilsAdapter;
+import com.android.server.telecom.TelecomSystem;
+import com.android.server.telecom.ui.ToastFactory;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+@RunWith(AndroidJUnit4.class)
+public class CallTest extends TelecomTestCase {
+    private static final Uri TEST_ADDRESS = Uri.parse("tel:555-1212");
+    private static final PhoneAccountHandle SIM_1_HANDLE = new PhoneAccountHandle(
+            ComponentName.unflattenFromString("com.foo/.Blah"), "Sim1");
+    private static final PhoneAccount SIM_1_ACCOUNT = new PhoneAccount.Builder(SIM_1_HANDLE, "Sim1")
+            .setCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION
+                    | PhoneAccount.CAPABILITY_CALL_PROVIDER)
+            .setIsEnabled(true)
+            .build();
+
+    @Mock private CallsManager mMockCallsManager;
+    @Mock private CallerInfoLookupHelper mMockCallerInfoLookupHelper;
+    @Mock private PhoneAccountRegistrar mMockPhoneAccountRegistrar;
+    @Mock private ClockProxy mMockClockProxy;
+    @Mock private ToastFactory mMockToastProxy;
+    @Mock private Toast mMockToast;
+    @Mock private PhoneNumberUtilsAdapter mMockPhoneNumberUtilsAdapter;
+    @Mock private ConnectionServiceWrapper mMockConnectionService;
+
+    private final TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() { };
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        doReturn(mMockCallerInfoLookupHelper).when(mMockCallsManager).getCallerInfoLookupHelper();
+        doReturn(mMockPhoneAccountRegistrar).when(mMockCallsManager).getPhoneAccountRegistrar();
+        doReturn(SIM_1_ACCOUNT).when(mMockPhoneAccountRegistrar).getPhoneAccountUnchecked(
+                eq(SIM_1_HANDLE));
+        doReturn(new ComponentName(mContext, CallTest.class))
+                .when(mMockConnectionService).getComponentName();
+        doReturn(mMockToast).when(mMockToastProxy).makeText(any(), anyInt(), anyInt());
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    @Test
+    @SmallTest
+    public void testSetHasGoneActive() {
+        Call call = new Call(
+                "1", /* callId */
+                mContext,
+                mMockCallsManager,
+                mLock,
+                null /* ConnectionServiceRepository */,
+                mMockPhoneNumberUtilsAdapter,
+                TEST_ADDRESS,
+                null /* GatewayInfo */,
+                null /* connectionManagerPhoneAccountHandle */,
+                SIM_1_HANDLE,
+                Call.CALL_DIRECTION_INCOMING,
+                false /* shouldAttachToExistingConnection*/,
+                false /* isConference */,
+                mMockClockProxy,
+                mMockToastProxy);
+
+        assertFalse(call.hasGoneActiveBefore());
+        call.setState(CallState.ACTIVE, "");
+        assertTrue(call.hasGoneActiveBefore());
+        call.setState(CallState.AUDIO_PROCESSING, "");
+        assertTrue(call.hasGoneActiveBefore());
+    }
+
+    @Test
+    @SmallTest
+    public void testDisconnectCauseWhenAudioProcessing() {
+        Call call = new Call(
+                "1", /* callId */
+                mContext,
+                mMockCallsManager,
+                mLock,
+                null /* ConnectionServiceRepository */,
+                mMockPhoneNumberUtilsAdapter,
+                TEST_ADDRESS,
+                null /* GatewayInfo */,
+                null /* connectionManagerPhoneAccountHandle */,
+                SIM_1_HANDLE,
+                Call.CALL_DIRECTION_INCOMING,
+                false /* shouldAttachToExistingConnection*/,
+                false /* isConference */,
+                mMockClockProxy,
+                mMockToastProxy);
+        call.setState(CallState.AUDIO_PROCESSING, "");
+        call.disconnect();
+        call.setDisconnectCause(new DisconnectCause(DisconnectCause.LOCAL));
+        assertEquals(DisconnectCause.REJECTED, call.getDisconnectCause().getCode());
+    }
+
+    @Test
+    @SmallTest
+    public void testDisconnectCauseWhenAudioProcessingAfterActive() {
+        Call call = new Call(
+                "1", /* callId */
+                mContext,
+                mMockCallsManager,
+                mLock,
+                null /* ConnectionServiceRepository */,
+                mMockPhoneNumberUtilsAdapter,
+                TEST_ADDRESS,
+                null /* GatewayInfo */,
+                null /* connectionManagerPhoneAccountHandle */,
+                SIM_1_HANDLE,
+                Call.CALL_DIRECTION_INCOMING,
+                false /* shouldAttachToExistingConnection*/,
+                false /* isConference */,
+                mMockClockProxy,
+                mMockToastProxy);
+        call.setState(CallState.AUDIO_PROCESSING, "");
+        call.setState(CallState.ACTIVE, "");
+        call.setState(CallState.AUDIO_PROCESSING, "");
+        call.disconnect();
+        call.setDisconnectCause(new DisconnectCause(DisconnectCause.LOCAL));
+        assertEquals(DisconnectCause.LOCAL, call.getDisconnectCause().getCode());
+    }
+
+    @Test
+    @SmallTest
+    public void testDisconnectCauseWhenSimulatedRingingAndDisconnect() {
+        Call call = new Call(
+                "1", /* callId */
+                mContext,
+                mMockCallsManager,
+                mLock,
+                null /* ConnectionServiceRepository */,
+                mMockPhoneNumberUtilsAdapter,
+                TEST_ADDRESS,
+                null /* GatewayInfo */,
+                null /* connectionManagerPhoneAccountHandle */,
+                SIM_1_HANDLE,
+                Call.CALL_DIRECTION_INCOMING,
+                false /* shouldAttachToExistingConnection*/,
+                false /* isConference */,
+                mMockClockProxy,
+                mMockToastProxy);
+        call.setState(CallState.SIMULATED_RINGING, "");
+        call.disconnect();
+        call.setDisconnectCause(new DisconnectCause(DisconnectCause.LOCAL));
+        assertEquals(DisconnectCause.MISSED, call.getDisconnectCause().getCode());
+    }
+
+    @Test
+    @SmallTest
+    public void testDisconnectCauseWhenSimulatedRingingAndReject() {
+        Call call = new Call(
+                "1", /* callId */
+                mContext,
+                mMockCallsManager,
+                mLock,
+                null /* ConnectionServiceRepository */,
+                mMockPhoneNumberUtilsAdapter,
+                TEST_ADDRESS,
+                null /* GatewayInfo */,
+                null /* connectionManagerPhoneAccountHandle */,
+                SIM_1_HANDLE,
+                Call.CALL_DIRECTION_INCOMING,
+                false /* shouldAttachToExistingConnection*/,
+                false /* isConference */,
+                mMockClockProxy,
+                mMockToastProxy);
+        call.setState(CallState.SIMULATED_RINGING, "");
+        call.reject(false, "");
+        call.setDisconnectCause(new DisconnectCause(DisconnectCause.LOCAL));
+        assertEquals(DisconnectCause.REJECTED, call.getDisconnectCause().getCode());
+    }
+
+    @Test
+    @SmallTest
+    public void testCanNotPullCallDuringEmergencyCall() {
+        Call call = new Call(
+                "1", /* callId */
+                mContext,
+                mMockCallsManager,
+                mLock,
+                null /* ConnectionServiceRepository */,
+                mMockPhoneNumberUtilsAdapter,
+                TEST_ADDRESS,
+                null /* GatewayInfo */,
+                null /* connectionManagerPhoneAccountHandle */,
+                SIM_1_HANDLE,
+                Call.CALL_DIRECTION_INCOMING,
+                false /* shouldAttachToExistingConnection*/,
+                false /* isConference */,
+                mMockClockProxy,
+                mMockToastProxy);
+        call.setConnectionService(mMockConnectionService);
+        call.setConnectionProperties(Connection.PROPERTY_IS_EXTERNAL_CALL);
+        call.setConnectionCapabilities(Connection.CAPABILITY_CAN_PULL_CALL);
+        call.setState(CallState.ACTIVE, "");
+        // Emergency call in progress, this should show a toast and never call pullExternalCall
+        // on the ConnectionService.
+        doReturn(true).when(mMockCallsManager).isInEmergencyCall();
+        call.pullExternalCall();
+        verify(mMockConnectionService, never()).pullExternalCall(any());
+        verify(mMockToast).show();
+    }
+}
diff --git a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
index 71ad721..110b58e 100644
--- a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
@@ -25,6 +25,8 @@
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyChar;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Mockito.doAnswer;
@@ -35,10 +37,12 @@
 import static org.mockito.Mockito.when;
 
 import android.content.ComponentName;
+import android.content.Context;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Process;
 import android.os.SystemClock;
+import android.telecom.CallerInfo;
 import android.telecom.Connection;
 import android.telecom.Log;
 import android.telecom.PhoneAccount;
@@ -48,8 +52,8 @@
 import android.telephony.TelephonyManager;
 import android.test.suitebuilder.annotation.MediumTest;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.widget.Toast;
 
-import android.telecom.CallerInfo;
 import com.android.server.telecom.AsyncRingtonePlayer;
 import com.android.server.telecom.Call;
 import com.android.server.telecom.CallAudioManager;
@@ -84,6 +88,7 @@
 import com.android.server.telecom.bluetooth.BluetoothStateReceiver;
 import com.android.server.telecom.callfiltering.IncomingCallFilter;
 import com.android.server.telecom.ui.AudioProcessingNotification;
+import com.android.server.telecom.ui.ToastFactory;
 
 import org.junit.After;
 import org.junit.Before;
@@ -176,6 +181,8 @@
     @Mock private RoleManagerAdapter mRoleManagerAdapter;
     @Mock private IncomingCallFilter.Factory mIncomingCallFilterFactory;
     @Mock private IncomingCallFilter mIncomingCallFilter;
+    @Mock private ToastFactory mToastFactory;
+    @Mock private Toast mToast;
 
     private CallsManager mCallsManager;
 
@@ -229,7 +236,8 @@
                 mCallAudioModeStateMachineFactory,
                 mInCallControllerFactory,
                 mRoleManagerAdapter,
-                mIncomingCallFilterFactory);
+                mIncomingCallFilterFactory,
+                mToastFactory);
 
         when(mPhoneAccountRegistrar.getPhoneAccount(
                 eq(SELF_MANAGED_HANDLE), any())).thenReturn(SELF_MANAGED_ACCOUNT);
@@ -237,6 +245,8 @@
                 eq(SIM_1_HANDLE), any())).thenReturn(SIM_1_ACCOUNT);
         when(mPhoneAccountRegistrar.getPhoneAccount(
                 eq(SIM_2_HANDLE), any())).thenReturn(SIM_2_ACCOUNT);
+        when(mToastFactory.makeText(any(), anyInt(), anyInt())).thenReturn(mToast);
+        when(mToastFactory.makeText(any(), any(), anyInt())).thenReturn(mToast);
     }
 
     @Override
@@ -276,7 +286,8 @@
                 Call.CALL_DIRECTION_INCOMING,
                 false /* shouldAttachToExistingConnection*/,
                 false /* isConference */,
-                mClockProxy);
+                mClockProxy,
+                mToastFactory);
         ongoingCall.setState(CallState.ACTIVE, "just cuz");
         mCallsManager.addCall(ongoingCall);
 
@@ -1041,6 +1052,102 @@
         assertTrue(mCallsManager.isInEmergencyCall());
     }
 
+    @SmallTest
+    @Test
+    public void testIsInEmergencyCallLocalDisconnected() {
+        // Setup a call which is considered emergency based on its phone number.
+        Call ongoingCall = addSpyCall();
+        when(mComponentContextFixture.getTelephonyManager().isEmergencyNumber(any()))
+                .thenReturn(true);
+        ongoingCall.setHandle(Uri.fromParts("tel", "5551212", null),
+                TelecomManager.PRESENTATION_ALLOWED);
+
+        // and then set it as disconnected.
+        ongoingCall.setState(CallState.DISCONNECTED, "");
+        assertTrue(ongoingCall.isEmergencyCall());
+        assertFalse(ongoingCall.isNetworkIdentifiedEmergencyCall());
+        assertFalse(mCallsManager.isInEmergencyCall());
+    }
+
+    @SmallTest
+    @Test
+    public void testHasEmergencyCallIncomingCallPermitted() {
+        // Setup a call which is considered emergency based on its phone number.
+        Call ongoingCall = addSpyCall();
+        when(mComponentContextFixture.getTelephonyManager().isEmergencyNumber(any()))
+                .thenReturn(true);
+        ongoingCall.setHandle(Uri.fromParts("tel", "5551212", null),
+                TelecomManager.PRESENTATION_ALLOWED);
+        when(mPhoneAccountRegistrar.getPhoneAccountUnchecked(SELF_MANAGED_HANDLE))
+                .thenReturn(SELF_MANAGED_ACCOUNT);
+        when(mPhoneAccountRegistrar.getPhoneAccountUnchecked(SIM_1_HANDLE))
+                .thenReturn(SIM_1_ACCOUNT);
+
+        assertFalse(mCallsManager.isIncomingCallPermitted(null, SELF_MANAGED_HANDLE));
+        assertFalse(mCallsManager.isIncomingCallPermitted(null, SIM_1_HANDLE));
+    }
+
+    @SmallTest
+    @Test
+    public void testMakeRoomForOutgoingCallAudioProcessingInProgress() {
+        Call ongoingCall = addSpyCall(SIM_2_HANDLE, CallState.AUDIO_PROCESSING);
+
+        Call newEmergencyCall = createCall(SIM_1_HANDLE, CallState.NEW);
+        when(mComponentContextFixture.getTelephonyManager().isEmergencyNumber(any()))
+                .thenReturn(true);
+        newEmergencyCall.setHandle(Uri.fromParts("tel", "5551213", null),
+                TelecomManager.PRESENTATION_ALLOWED);
+
+        assertTrue(mCallsManager.makeRoomForOutgoingCall(newEmergencyCall, true /*isEmergency*/));
+        verify(ongoingCall).disconnect(anyLong(), anyString());
+    }
+
+    @SmallTest
+    @Test
+    public void testMakeRoomForEmergencyDuringIncomingCall() {
+        Call ongoingCall = addSpyCall(SIM_2_HANDLE, CallState.RINGING);
+
+        Call newEmergencyCall = createCall(SIM_1_HANDLE, CallState.NEW);
+        when(mComponentContextFixture.getTelephonyManager().isEmergencyNumber(any()))
+                .thenReturn(true);
+        newEmergencyCall.setHandle(Uri.fromParts("tel", "5551213", null),
+                TelecomManager.PRESENTATION_ALLOWED);
+
+        assertTrue(mCallsManager.makeRoomForOutgoingCall(newEmergencyCall, true /*isEmergency*/));
+        verify(ongoingCall).reject(anyBoolean(), any(), any());
+    }
+
+    @SmallTest
+    @Test
+    public void testMakeRoomForEmergencyCallSimulatedRingingInProgress() {
+        Call ongoingCall = addSpyCall(SIM_2_HANDLE, CallState.SIMULATED_RINGING);
+
+        Call newEmergencyCall = createCall(SIM_1_HANDLE, CallState.NEW);
+        when(mComponentContextFixture.getTelephonyManager().isEmergencyNumber(any()))
+                .thenReturn(true);
+        newEmergencyCall.setHandle(Uri.fromParts("tel", "5551213", null),
+                TelecomManager.PRESENTATION_ALLOWED);
+
+        assertTrue(mCallsManager.makeRoomForOutgoingCall(newEmergencyCall, true /*isEmergency*/));
+        verify(ongoingCall).disconnect(anyString());
+    }
+
+    @SmallTest
+    @Test
+    public void testMakeRoomForEmergencyCallSimulatedRingingInProgressHasBeenActive() {
+        Call ongoingCall = addSpyCall(SIM_2_HANDLE, CallState.ACTIVE);
+        ongoingCall.setState(CallState.SIMULATED_RINGING, "");
+
+        Call newEmergencyCall = createCall(SIM_1_HANDLE, CallState.NEW);
+        when(mComponentContextFixture.getTelephonyManager().isEmergencyNumber(any()))
+                .thenReturn(true);
+        newEmergencyCall.setHandle(Uri.fromParts("tel", "5551213", null),
+                TelecomManager.PRESENTATION_ALLOWED);
+
+        assertTrue(mCallsManager.makeRoomForOutgoingCall(newEmergencyCall, true /*isEmergency*/));
+        verify(ongoingCall).reject(anyBoolean(), any(), any());
+    }
+
     /**
      * Verifies that changes to a {@link PhoneAccount}'s
      * {@link PhoneAccount#CAPABILITY_VIDEO_CALLING} capability will be reflected on a call.
@@ -1112,6 +1219,21 @@
     }
 
     private Call addSpyCall(PhoneAccountHandle targetPhoneAccount, int initialState) {
+        Call ongoingCall = createCall(targetPhoneAccount, initialState);
+        Call callSpy = Mockito.spy(ongoingCall);
+
+        // Mocks some methods to not call the real method.
+        doNothing().when(callSpy).unhold();
+        doNothing().when(callSpy).hold();
+        doNothing().when(callSpy).disconnect();
+        doNothing().when(callSpy).answer(Matchers.anyInt());
+        doNothing().when(callSpy).setStartWithSpeakerphoneOn(Matchers.anyBoolean());
+
+        mCallsManager.addCall(callSpy);
+        return callSpy;
+    }
+
+    private Call createCall(PhoneAccountHandle targetPhoneAccount, int initialState) {
         Call ongoingCall = new Call(String.format("TC@%d", sCallId++), /* callId */
                 mContext,
                 mCallsManager,
@@ -1125,19 +1247,10 @@
                 Call.CALL_DIRECTION_INCOMING,
                 false /* shouldAttachToExistingConnection*/,
                 false /* isConference */,
-                mClockProxy);
+                mClockProxy,
+                mToastFactory);
         ongoingCall.setState(initialState, "just cuz");
-        Call callSpy = Mockito.spy(ongoingCall);
-
-        // Mocks some methods to not call the real method.
-        doNothing().when(callSpy).unhold();
-        doNothing().when(callSpy).hold();
-        doNothing().when(callSpy).disconnect();
-        doNothing().when(callSpy).answer(Matchers.anyInt());
-        doNothing().when(callSpy).setStartWithSpeakerphoneOn(Matchers.anyBoolean());
-
-        mCallsManager.addCall(callSpy);
-        return callSpy;
+        return ongoingCall;
     }
 
     private void verifyFocusRequestAndExecuteCallback(Call call) {
diff --git a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
index 2577631..6a97c27 100644
--- a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
+++ b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
@@ -93,17 +93,10 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.matches;
-import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.when;
 import static org.mockito.Mockito.verify;
 
 @RunWith(JUnit4.class)
@@ -179,7 +172,7 @@
     public void testBindToService_NoServicesFound_IncomingCall() throws Exception {
         when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
-        when(mMockCallsManager.hasEmergencyCall()).thenReturn(false);
+        when(mMockCallsManager.isInEmergencyCall()).thenReturn(false);
         when(mMockCall.isIncoming()).thenReturn(true);
         when(mMockCall.isExternalCall()).thenReturn(false);
         when(mTimeoutsAdapter.getEmergencyCallbackWindowMillis(any(ContentResolver.class)))
@@ -211,7 +204,7 @@
 
         when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
-        when(mMockCallsManager.hasEmergencyCall()).thenReturn(false);
+        when(mMockCallsManager.isInEmergencyCall()).thenReturn(false);
         when(mMockCall.isIncoming()).thenReturn(false);
         when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
         when(mMockCall.getIntentExtras()).thenReturn(callExtras);
@@ -249,7 +242,7 @@
 
         when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
-        when(mMockCallsManager.hasEmergencyCall()).thenReturn(false);
+        when(mMockCallsManager.isInEmergencyCall()).thenReturn(false);
         when(mMockCall.isIncoming()).thenReturn(false);
         when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
         when(mMockCall.getIntentExtras()).thenReturn(callExtras);
@@ -301,7 +294,7 @@
 
         when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
-        when(mMockCallsManager.hasEmergencyCall()).thenReturn(true);
+        when(mMockCallsManager.isInEmergencyCall()).thenReturn(true);
         when(mMockCall.isEmergencyCall()).thenReturn(true);
         when(mMockCall.isIncoming()).thenReturn(false);
         when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
@@ -371,7 +364,7 @@
 
         when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
-        when(mMockCallsManager.hasEmergencyCall()).thenReturn(false);
+        when(mMockCallsManager.isInEmergencyCall()).thenReturn(false);
         when(mMockCallsManager.getCalls()).thenReturn(Collections.singletonList(mMockCall));
         when(mMockCallsManager.getAudioState()).thenReturn(null);
         when(mMockCallsManager.canAddCall()).thenReturn(false);
@@ -497,7 +490,7 @@
     public void testUnbindDueToCallDisconnect() throws Exception {
         when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
-        when(mMockCallsManager.hasEmergencyCall()).thenReturn(false);
+        when(mMockCallsManager.isInEmergencyCall()).thenReturn(false);
         when(mMockCall.isIncoming()).thenReturn(true);
         when(mMockCall.isExternalCall()).thenReturn(false);
         when(mDefaultDialerCache.getDefaultDialerApplication(CURRENT_USER_ID)).thenReturn(DEF_PKG);
@@ -699,7 +692,7 @@
     public void testBindingFuture() throws Exception {
         when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
-        when(mMockCallsManager.hasEmergencyCall()).thenReturn(false);
+        when(mMockCallsManager.isInEmergencyCall()).thenReturn(false);
         when(mMockCall.isIncoming()).thenReturn(false);
         when(mMockCall.isExternalCall()).thenReturn(false);
         when(mDefaultDialerCache.getDefaultDialerApplication(CURRENT_USER_ID)).thenReturn(DEF_PKG);
@@ -745,7 +738,7 @@
     private void setupMocks(boolean isExternalCall) {
         when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
-        when(mMockCallsManager.hasEmergencyCall()).thenReturn(false);
+        when(mMockCallsManager.isInEmergencyCall()).thenReturn(false);
         when(mMockCall.isIncoming()).thenReturn(false);
         when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
         when(mDefaultDialerCache.getDefaultDialerApplication(CURRENT_USER_ID)).thenReturn(DEF_PKG);
diff --git a/tests/src/com/android/server/telecom/tests/ParcelableCallUtilsTest.java b/tests/src/com/android/server/telecom/tests/ParcelableCallUtilsTest.java
index 7104e3a..30b870e 100644
--- a/tests/src/com/android/server/telecom/tests/ParcelableCallUtilsTest.java
+++ b/tests/src/com/android/server/telecom/tests/ParcelableCallUtilsTest.java
@@ -31,6 +31,7 @@
 import com.android.server.telecom.PhoneAccountRegistrar;
 import com.android.server.telecom.PhoneNumberUtilsAdapter;
 import com.android.server.telecom.TelecomSystem;
+import com.android.server.telecom.ui.ToastFactory;
 
 import org.junit.After;
 import org.junit.Before;
@@ -45,6 +46,7 @@
 
     private SyncRoot mLock = new SyncRoot() {};
     @Mock private ClockProxy mClockProxy;
+    @Mock private ToastFactory mToastProxy;
     @Mock private CallsManager mCallsManager;
     @Mock private CallerInfoLookupHelper mCallerInfoLookupHelper;
     @Mock private PhoneNumberUtilsAdapter mPhoneNumberUtilsAdapter;
@@ -77,7 +79,8 @@
                 Call.CALL_DIRECTION_INCOMING,
                 false /* shouldAttachToExistingConnection */,
                 false /* isConference */,
-                mClockProxy /* ClockProxy */);
+                mClockProxy /* ClockProxy */,
+                mToastProxy);
     }
 
     @Override