Merge "Remove hidden Telephony API usages from Telecom"
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 9e8cf22..935790c 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -96,6 +96,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;
@@ -205,7 +206,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
@@ -216,13 +217,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@";
 
@@ -333,6 +336,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;
@@ -456,7 +460,8 @@
             CallAudioModeStateMachine.Factory callAudioModeStateMachineFactory,
             InCallControllerFactory inCallControllerFactory,
             RoleManagerAdapter roleManagerAdapter,
-            IncomingCallFilter.Factory incomingCallFilterFactory) {
+            IncomingCallFilter.Factory incomingCallFilterFactory,
+            ToastFactory toastFactory) {
         mContext = context;
         mLock = lock;
         mPhoneNumberUtilsAdapter = phoneNumberUtilsAdapter;
@@ -531,6 +536,7 @@
                 new ConnectionServiceRepository(mPhoneAccountRegistrar, mContext, mLock, this);
         mInCallWakeLockController = inCallWakeLockControllerFactory.create(context, this);
         mClockProxy = clockProxy;
+        mToastFactory = toastFactory;
         mRoleManagerAdapter = roleManagerAdapter;
 
         mListeners.add(mInCallWakeLockController);
@@ -1056,16 +1062,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()) {
@@ -1149,7 +1145,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
@@ -1262,6 +1259,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);
         }
@@ -1286,7 +1290,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);
@@ -1370,7 +1375,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
@@ -1762,7 +1768,8 @@
                 new CallerInfoLookupHelper.OnQueryCompleteListener() {
                     @Override
                     public void onCallerInfoQueryComplete(Uri handle, CallerInfo info) {
-                        if (info.preferredPhoneAccountComponent != null &&
+                        if (info != null &&
+                                info.preferredPhoneAccountComponent != null &&
                                 info.preferredPhoneAccountId != null &&
                                 !info.preferredPhoneAccountId.isEmpty()) {
                             PhoneAccountHandle contactDefaultHandle = new PhoneAccountHandle(
@@ -2490,7 +2497,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;
         }
@@ -3054,7 +3061,8 @@
                 true /* isConference */,
                 connectTime,
                 connectElapsedTime,
-                mClockProxy);
+                mClockProxy,
+                mToastFactory);
 
         setCallState(call, Call.getStateFromConnectionState(parcelableConference.getState()),
                 "new conference call");
@@ -3601,7 +3609,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.
@@ -3638,6 +3647,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)) {
@@ -3694,6 +3710,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;
     }
@@ -3777,7 +3817,8 @@
                 isDowngradedConference /* isConference */,
                 connection.getConnectTimeMillis() /* connectTimeMillis */,
                 connection.getConnectElapsedTimeMillis(), /* connectElapsedTimeMillis */
-                mClockProxy);
+                mClockProxy,
+                mToastFactory);
 
         call.initAnalytics();
         call.getAnalytics().setCreatedFromExistingConnection(true);
@@ -3927,13 +3968,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);
         }
     }
@@ -3964,7 +4005,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))));
@@ -4285,110 +4326,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
@@ -4406,7 +4343,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;
@@ -4435,7 +4372,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
@@ -4639,12 +4576,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);
@@ -4814,8 +4752,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/BasicCallTests.java b/tests/src/com/android/server/telecom/tests/BasicCallTests.java
index 322a3c2..01927f5 100644
--- a/tests/src/com/android/server/telecom/tests/BasicCallTests.java
+++ b/tests/src/com/android/server/telecom/tests/BasicCallTests.java
@@ -358,6 +358,7 @@
                 mInCallServiceFixtureY.getCall(ids.mCallId).getState());
     }
 
+    @FlakyTest
     @LargeTest
     @Test
     public void testIncomingCallFromContactWithSendToVoicemailIsRejected() throws Exception {
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
diff --git a/tests/src/com/android/server/telecom/tests/SessionTest.java b/tests/src/com/android/server/telecom/tests/SessionTest.java
index 337041b..6a14a64 100644
--- a/tests/src/com/android/server/telecom/tests/SessionTest.java
+++ b/tests/src/com/android/server/telecom/tests/SessionTest.java
@@ -18,6 +18,7 @@
 
 import static junit.framework.Assert.fail;
 
+import android.telecom.Log;
 import android.telecom.Logging.Session;
 import android.test.suitebuilder.annotation.SmallTest;
 
@@ -48,30 +49,189 @@
 
     /**
      * Ensure creating two sessions that are parent/child of each other does not lead to a crash
-     * or infinite recursion.
+     * or infinite recursion when using Session#printFullSessionTree.
+     */
+    @SmallTest
+    @Test
+    public void testRecursion_printFullSessionTree() {
+        Log.startSession("testParent");
+        // Running in the same thread, so mark as invisible subsession
+        Session childSession = Log.getSessionManager()
+                .createSubsession(true /*isStartedFromActiveSession*/);
+        Log.continueSession(childSession, "child");
+        Session parentSession = childSession.getParentSession();
+        // Create a circular dependency and ensure we do not crash
+        parentSession.setParentSession(childSession);
+        childSession.addChild(parentSession);
+
+        // Make sure calling these methods does not result in a crash
+        try {
+            parentSession.printFullSessionTree();
+            childSession.printFullSessionTree();
+        } catch (Exception e) {
+            fail("Exception: " + e.getMessage());
+        } finally {
+            // End child
+            Log.endSession();
+            // End parent
+            Log.endSession();
+        }
+    }
+
+    /**
+     * Ensure creating two sessions that are parent/child of each other does not lead to a crash
+     * or infinite recursion when using Session#getFullMethodPath.
+     */
+    @SmallTest
+    @Test
+    public void testRecursion_getFullMethodPath() {
+        Log.startSession("testParent");
+        // Running in the same thread, so mark as invisible subsession
+        Session childSession = Log.getSessionManager()
+                .createSubsession(true /*isStartedFromActiveSession*/);
+        Log.continueSession(childSession, "child");
+        Session parentSession = childSession.getParentSession();
+        // Create a circular dependency and ensure we do not crash
+        parentSession.setParentSession(childSession);
+        childSession.addChild(parentSession);
+
+        // Make sure calling these methods does not result in a crash
+        try {
+            parentSession.getFullMethodPath(false /*truncatePath*/);
+            childSession.getFullMethodPath(false /*truncatePath*/);
+        } catch (Exception e) {
+            fail("Exception: " + e.getMessage());
+        } finally {
+            // End child
+            Log.endSession();
+            // End parent
+            Log.endSession();
+        }
+    }
+
+    /**
+     * Ensure creating two sessions that are parent/child of each other does not lead to a crash
+     * or infinite recursion when using Session#getFullMethodPath.
+     */
+    @SmallTest
+    @Test
+    public void testRecursion_getFullMethodPathTruncated() {
+        Log.startSession("testParent");
+        // Running in the same thread, so mark as invisible subsession
+        Session childSession = Log.getSessionManager()
+                .createSubsession(true /*isStartedFromActiveSession*/);
+        Log.continueSession(childSession, "child");
+        Session parentSession = childSession.getParentSession();
+        // Create a circular dependency and ensure we do not crash
+        parentSession.setParentSession(childSession);
+        childSession.addChild(parentSession);
+
+        // Make sure calling these methods does not result in a crash
+        try {
+            parentSession.getFullMethodPath(true /*truncatePath*/);
+            childSession.getFullMethodPath(true /*truncatePath*/);
+        } catch (Exception e) {
+            fail("Exception: " + e.getMessage());
+        } finally {
+            // End child
+            Log.endSession();
+            // End parent
+            Log.endSession();
+        }
+    }
+
+    /**
+     * Ensure creating two sessions that are parent/child of each other does not lead to a crash
+     * or infinite recursion when using Session#toString.
+     */
+    @SmallTest
+    @Test
+    public void testRecursion_toString() {
+        Log.startSession("testParent");
+        // Running in the same thread, so mark as invisible subsession
+        Session childSession = Log.getSessionManager()
+                .createSubsession(true /*isStartedFromActiveSession*/);
+        Log.continueSession(childSession, "child");
+        Session parentSession = childSession.getParentSession();
+        // Create a circular dependency and ensure we do not crash
+        parentSession.setParentSession(childSession);
+        childSession.addChild(parentSession);
+
+        // Make sure calling these methods does not result in a crash
+        try {
+
+            parentSession.toString();
+            childSession.toString();
+        } catch (Exception e) {
+            fail("Exception: " + e.getMessage());
+        } finally {
+            // End child
+            Log.endSession();
+            // End parent
+            Log.endSession();
+        }
+    }
+
+    /**
+     * Ensure creating two sessions that are parent/child of each other does not lead to a crash
+     * or infinite recursion when using Session#getInfo.
+     */
+    @SmallTest
+    @Test
+    public void testRecursion_getInfo() {
+        Log.startSession("testParent");
+        // Running in the same thread, so mark as invisible subsession
+        Session childSession = Log.getSessionManager()
+                .createSubsession(true /*isStartedFromActiveSession*/);
+        Log.continueSession(childSession, "child");
+        Session parentSession = childSession.getParentSession();
+        // Create a circular dependency and ensure we do not crash
+        parentSession.setParentSession(childSession);
+        childSession.addChild(parentSession);
+
+        // Make sure calling these methods does not result in a crash
+        try {
+            Session.Info.getInfo(parentSession);
+            Session.Info.getInfo(childSession);
+        } catch (Exception e) {
+            fail("Exception: " + e.getMessage());
+        } finally {
+            // End child
+            Log.endSession();
+            // End parent
+            Log.endSession();
+        }
+    }
+
+    /**
+     * Ensure creating two sessions that are parent/child of each other does not lead to a crash
+     * or infinite recursion in the general case.
      */
     @SmallTest
     @Test
     public void testRecursion() {
         Session parentSession =  createTestSession("parent", "p");
         Session childSession =  createTestSession("child", "c");
+        // Create a circular dependency
         parentSession.addChild(childSession);
-        parentSession.setParentSession(childSession);
         childSession.addChild(parentSession);
+        parentSession.setParentSession(childSession);
         childSession.setParentSession(parentSession);
 
         // Make sure calling these methods does not result in a crash
         try {
             parentSession.printFullSessionTree();
             childSession.printFullSessionTree();
-            parentSession.getFullMethodPath(false);
-            childSession.getFullMethodPath(false);
+            parentSession.getFullMethodPath(false /*truncatePath*/);
+            childSession.getFullMethodPath(false /*truncatePath*/);
+            parentSession.getFullMethodPath(true /*truncatePath*/);
+            childSession.getFullMethodPath(true /*truncatePath*/);
             parentSession.toString();
             childSession.toString();
             Session.Info.getInfo(parentSession);
             Session.Info.getInfo(childSession);
         } catch (Exception e) {
-            fail();
+            fail("Exception: " + e.getMessage());
         }
     }