Snap for 13256841 from b0df908717e5e7b1cf27dc84bf1d4f522e84072c to 25Q2-release

Change-Id: Ib13fb5529dc5b8eb0ac0fd35c546baac7c6e8408
diff --git a/flags/telecom_call_flags.aconfig b/flags/telecom_call_flags.aconfig
index 8e15910..0000f32 100644
--- a/flags/telecom_call_flags.aconfig
+++ b/flags/telecom_call_flags.aconfig
@@ -77,3 +77,11 @@
       purpose: PURPOSE_BUGFIX
     }
 }
+
+# OWNER=pmadapurmath TARGET=25Q4
+flag {
+  name: "call_sequencing_call_resume_failed"
+  namespace: "telecom"
+  description: "Connection event received when a call resume fails"
+  bug: "390116261"
+}
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index 6fe476d..a54a3b6 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -205,6 +205,7 @@
         default void onHoldToneRequested(Call call) {};
         default void onCallHoldFailed(Call call) {};
         default void onCallSwitchFailed(Call call) {};
+        default void onCallResumeFailed(Call call) {};
         default void onConnectionEvent(Call call, String event, Bundle extras) {};
         default void onCallStreamingStateChanged(Call call, boolean isStreaming) {}
         default void onExternalCallChanged(Call call, boolean isExternalCall) {};
@@ -295,6 +296,8 @@
         @Override
         public void onCallSwitchFailed(Call call) {}
         @Override
+        public void onCallResumeFailed(Call call) {}
+        @Override
         public void onConnectionEvent(Call call, String event, Bundle extras) {}
         @Override
         public void onCallStreamingStateChanged(Call call, boolean isStreaming) {}
@@ -644,6 +647,7 @@
 
     private boolean mIsTransactionalCall = false;
     private CallingPackageIdentity mCallingPackageIdentity = new CallingPackageIdentity();
+    private boolean mSkipAutoUnhold = false;
 
     /**
      * CallingPackageIdentity is responsible for storing properties about the calling package that
@@ -4544,6 +4548,10 @@
             for (Listener l : mListeners) {
                 l.onCallSwitchFailed(this);
             }
+        } else if (Connection.EVENT_CALL_RESUME_FAILED.equals(event)) {
+            for (Listener l : mListeners) {
+                l.onCallResumeFailed(this);
+            }
         } else if (Connection.EVENT_DEVICE_TO_DEVICE_MESSAGE.equals(event)
                 && extras != null && extras.containsKey(
                 Connection.EXTRA_DEVICE_TO_DEVICE_MESSAGE_TYPE)
@@ -5108,4 +5116,21 @@
     public boolean hasVideoCall() {
         return mHasVideoCall;
     }
+
+    /**
+     * Used only for call sequencing for cases when we may end up auto-unholding the held call while
+     * processing an outgoing (emergency) call. We want to refrain from unholding the held call so
+     * that we don't end up with two active calls. Once the outgoing call is disconnected (either
+     * from a successful disconnect by the user or a failed call), the auto-unhold logic will be
+     * triggered again and successfully unhold the held call at that point. Note, that this only
+     * applies to non-holdable phone accounts (i.e. Verizon). Refer to
+     * {@link CallsManagerCallSequencingAdapter#maybeMoveHeldCallToForeground} for details.
+     */
+    public void setSkipAutoUnhold(boolean result) {
+        mSkipAutoUnhold = result;
+    }
+
+    public boolean getSkipAutoUnhold() {
+        return mSkipAutoUnhold;
+    }
 }
diff --git a/src/com/android/server/telecom/CallAudioRouteController.java b/src/com/android/server/telecom/CallAudioRouteController.java
index 8416533..ce06d55 100644
--- a/src/com/android/server/telecom/CallAudioRouteController.java
+++ b/src/com/android/server/telecom/CallAudioRouteController.java
@@ -849,13 +849,15 @@
     private void handleBtActiveDevicePresent(@AudioRoute.AudioRouteType int type,
             String deviceAddress) {
         AudioRoute bluetoothRoute = getBluetoothRoute(type, deviceAddress);
-        if (bluetoothRoute != null) {
+        boolean isBtDeviceCurrentActive = Objects.equals(bluetoothRoute,
+                getArbitraryBluetoothDevice());
+        if (bluetoothRoute != null && isBtDeviceCurrentActive) {
             Log.i(this, "request to route to bluetooth route: %s (active=%b)", bluetoothRoute,
                     mIsActive);
             routeTo(mIsActive, bluetoothRoute);
         } else {
-            Log.i(this, "request to route to unavailable bluetooth route - type (%s), address (%s)",
-                    type, deviceAddress);
+            Log.i(this, "request to route to unavailable bluetooth route or the route isn't the "
+                    + "currently active device - type (%s), address (%s)", type, deviceAddress);
         }
     }
 
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index 247fd0b..4d59fc8 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -1477,6 +1477,14 @@
         markAllAnsweredCallAsRinging(call, "switch");
     }
 
+    @Override
+    public void onCallResumeFailed(Call call) {
+        Call heldCall = getFirstCallWithState(call, true /* skipSelfManaged */, CallState.ON_HOLD);
+        if (heldCall != null) {
+            mCallSequencingAdapter.handleCallResumeFailed(call, heldCall);
+        }
+    }
+
     private void markAllAnsweredCallAsRinging(Call call, String actionName) {
         // Normally, we don't care whether a call hold or switch has failed.
         // However, if a call was held or switched in order to answer an incoming call, that
diff --git a/src/com/android/server/telecom/InCallController.java b/src/com/android/server/telecom/InCallController.java
index 3f8f579..6cfa4fd 100644
--- a/src/com/android/server/telecom/InCallController.java
+++ b/src/com/android/server/telecom/InCallController.java
@@ -44,6 +44,7 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.PackageTagsList;
+import android.os.Parcel;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -1723,7 +1724,8 @@
 
                     try {
                         inCallService.updateCall(
-                                sanitizeParcelableCallForService(info, parcelableCall));
+                                copyIfLocal(sanitizeParcelableCallForService(info, parcelableCall),
+                                        inCallService));
                     } catch (RemoteException ignored) {
                     }
                 }
@@ -2854,7 +2856,8 @@
             ParcelableCall parcelableCall, ComponentName componentName) {
         try {
             inCallService.updateCall(
-                    sanitizeParcelableCallForService(info, parcelableCall));
+                    copyIfLocal(sanitizeParcelableCallForService(info, parcelableCall),
+                            inCallService));
         } catch (RemoteException exception) {
             Log.w(this, "Call status update did not send to: "
                     + componentName + " successfully with error " + exception);
@@ -3435,4 +3438,43 @@
         }
         return false;
     }
+
+    /**
+     * Given a {@link ParcelableCall} and a {@link IInCallService}, determines if the ICS binder is
+     * local or remote.  If the binder is remote, we just return the parcelable call instance
+     * already constructed.
+     * If the binder if local, as will be the case for
+     * {@code EnhancedConfirmationCallTrackerService} (or any other ICS in the system server, the
+     * underlying Binder implementation is NOT going to parcel and unparcel the
+     * {@link ParcelableCall} instance automatically.  This means that the parcelable call instance
+     * is passed by reference and that the ICS in the system server could potentially try to access
+     * internals in the {@link ParcelableCall} in an unsafe manner.  As a workaround, we will
+     * manually parcel and unparcel the {@link ParcelableCall} instance so that they get a fresh
+     * copy that they can use safely.
+     *
+     * @param parcelableCall The ParcelableCall instance we want to maybe copy.
+     * @param remote the binder the call is going out over.
+     * @return either the original {@link ParcelableCall} or a deep copy of it if the destination
+     * binder is local.
+     */
+    private ParcelableCall copyIfLocal(ParcelableCall parcelableCall, IInCallService remote) {
+        // We care more about parceling than local (though they should be the same); so, use
+        // queryLocalInterface since that's what Binder uses to decide if it needs to parcel.
+        if (remote.asBinder().queryLocalInterface(IInCallService.Stub.DESCRIPTOR) == null) {
+            // No local interface, so binder itself will parcel and thus we don't need to.
+            return parcelableCall;
+        }
+        // Binder won't be parceling; however, the remotes assume they have their own native
+        // objects (and don't know if caller is local or not), so we need to make a COPY here so
+        // that the remote can clean it up without clearing the original transaction.
+        // Since there's no direct `copy` for Transaction, we have to parcel/unparcel instead.
+        final Parcel p = Parcel.obtain();
+        try {
+            parcelableCall.writeToParcel(p, 0);
+            p.setDataPosition(0);
+            return ParcelableCall.CREATOR.createFromParcel(p);
+        } finally {
+            p.recycle();
+        }
+    }
 }
diff --git a/src/com/android/server/telecom/callsequencing/CallSequencingController.java b/src/com/android/server/telecom/callsequencing/CallSequencingController.java
index 8122787..d418cff 100644
--- a/src/com/android/server/telecom/callsequencing/CallSequencingController.java
+++ b/src/com/android/server/telecom/callsequencing/CallSequencingController.java
@@ -465,14 +465,12 @@
      *         made for the emergency call.
      */
     private CompletableFuture<Boolean> makeRoomForOutgoingEmergencyCall(Call emergencyCall) {
-        // Disconnect all self-managed + transactional calls. We will never use these accounts for
-        // emergency calling. Disconnect non-holdable calls (in the dual-sim case) as well. For
-        // the single sim case (like Verizon), we should support the existing behavior of
-        // disconnecting the active call; refrain from disconnecting the held call in this case if
-        // it exists.
-        boolean areMultiplePhoneAccountsActive = areMultiplePhoneAccountsActive();
+        // Disconnect all self-managed + transactional calls + calls that don't support holding for
+        // emergency. We will never use these accounts for emergency calling. For the single sim
+        // case (like Verizon), we should support the existing behavior of disconnecting the active
+        // call; refrain from disconnecting the held call in this case if it exists.
         Pair<Set<Call>, CompletableFuture<Boolean>> disconnectCallsForEmergencyPair =
-                disconnectCallsForEmergencyCall(emergencyCall, areMultiplePhoneAccountsActive);
+                disconnectCallsForEmergencyCall(emergencyCall);
         // The list of calls that were disconnected
         Set<Call> disconnectedCalls = disconnectCallsForEmergencyPair.first;
         // The future encompassing the result of the disconnect transaction(s). Because of the
@@ -518,6 +516,7 @@
                         new LoggedHandlerExecutor(mHandler, "CSC.mRFOEC",
                                 mCallsManager.getLock()));
             }
+            disconnectedCalls.add(ringingCall);
         } else {
             ringingCall = null;
         }
@@ -537,6 +536,25 @@
             return transactionFuture;
         }
 
+        // After having rejected any potential ringing call as well as calls that aren't supported
+        // during emergency calls (refer to disconnectCallsForEmergencyCall logic), we can
+        // re-evaluate whether we still have multiple phone accounts in use in order to disconnect
+        // non-holdable calls:
+        // If (yes) - disconnect call the non-holdable calls (this would be just the active call)
+        // If (no)  - skip the disconnect and instead let the logic be handled explicitly for the
+        //            single sim behavior.
+        boolean areMultiplePhoneAccountsActive = areMultiplePhoneAccountsActive(disconnectedCalls);
+        if (areMultiplePhoneAccountsActive && !liveCall.can(Connection.CAPABILITY_SUPPORT_HOLD)) {
+            // After disconnecting, we should be able to place the ECC now (we either have no calls
+            // or a held call after this point).
+            String disconnectReason = "disconnecting non-holdable call to make room "
+                    + "for emergency call";
+            emergencyCall.getAnalytics().setCallIsAdditional(true);
+            liveCall.getAnalytics().setCallIsInterrupted(true);
+            return disconnectOngoingCallForEmergencyCall(transactionFuture, liveCall,
+                    disconnectReason);
+        }
+
         // If we already disconnected the outgoing call, then don't perform any additional ops on
         // it.
         if (mCallsManager.hasMaximumOutgoingCalls(emergencyCall) && !disconnectedCalls
@@ -559,10 +577,16 @@
                         + " of new outgoing call.";
             }
             if (disconnectReason != null) {
+                // Skip auto-unhold for when the outgoing call is disconnected. Consider a scenario
+                // where we have a held non-holdable call (VZW) and the dialing call (also VZW). If
+                // we auto unhold the VZW while placing the emergency call, then we may end up with
+                // two active calls. The auto-unholding logic really only applies for the
+                // non-holdable phone account.
+                outgoingCall.setSkipAutoUnhold(true);
                 boolean isSequencingRequiredRingingAndOutgoing = ringingCall == null
                         || !arePhoneAccountsSame(ringingCall, outgoingCall);
                 return disconnectOngoingCallForEmergencyCall(transactionFuture, outgoingCall,
-                        disconnectReason, isSequencingRequiredRingingAndOutgoing);
+                        disconnectReason);
             }
             //  If the user tries to make two outgoing calls to different emergency call numbers,
             //  we will try to connect the first outgoing call and reject the second.
@@ -570,14 +594,18 @@
             return CompletableFuture.completedFuture(false);
         }
 
-        boolean isSequencingRequiredLive = ringingCall == null
-                || !arePhoneAccountsSame(ringingCall, liveCall);
         if (liveCall.getState() == CallState.AUDIO_PROCESSING) {
             emergencyCall.getAnalytics().setCallIsAdditional(true);
             liveCall.getAnalytics().setCallIsInterrupted(true);
+            // Skip auto-unhold for when the live call is disconnected. Consider a scenario where
+            // we have a held non-holdable call (VZW) and the live call (also VZW) is stuck in
+            // audio processing. If we auto unhold the VZW while placing the emergency call, then we
+            // may end up with two active calls. The auto-unholding logic really only applies for
+            // the non-holdable phone account.
+            liveCall.setSkipAutoUnhold(true);
             final String disconnectReason = "disconnecting audio processing call for emergency";
             return disconnectOngoingCallForEmergencyCall(transactionFuture, liveCall,
-                    disconnectReason, isSequencingRequiredLive);
+                    disconnectReason);
         }
 
         // If the live call is stuck in a connecting state, prompt the user to generate a bugreport.
@@ -587,40 +615,41 @@
         }
 
         // 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 we have a self-managed call
+        // we'll disconnect the active call if it cannot be held. If we have a self-managed call
         // that can't be held, then we should disconnect the call in favor of the emergency call.
-        // Likewise, if there's only one active managed call which can't be held, then it should
-        // also be disconnected. This will only happen for the single sim scenario to support
-        // backwards compatibility. For dual sim, we should try disconnecting the held call and
-        // hold the active call.
-        Call heldCall = null;
+        // This will only happen for the single sim scenario to support backwards compatibility.
+        // For dual sim, we should try disconnecting the held call and hold the active call. Also
+        // note that in a scenario where we don't have any held calls and the live call can't be
+        // held (only applies for single sim case), we should try holding the active call (and
+        // disconnect on fail) before placing the ECC (i.e. Verizon swap case). The latter is being
+        // handled further down in this method.
+        Call heldCall = mCallsManager.getFirstCallWithState(CallState.ON_HOLD);
         if (mCallsManager.hasMaximumManagedHoldingCalls(emergencyCall)
-                || !mCallsManager.canHold(liveCall)) {
+                && !disconnectedCalls.contains(heldCall)) {
             final String disconnectReason = "disconnecting to make room for emergency call "
                     + emergencyCall.getId();
             emergencyCall.getAnalytics().setCallIsAdditional(true);
             // Single sim case
             if (!areMultiplePhoneAccountsActive) {
                 liveCall.getAnalytics().setCallIsInterrupted(true);
+                // Skip auto-unhold for when the live call is disconnected. Consider a scenario
+                // where we have a held non-holdable call (VZW) and an active call (also VZW). If
+                // we auto unhold the VZW while placing the emergency call, then we may end up with
+                // two active calls. The auto-unholding logic really only applies for the
+                // non-holdable phone account.
+                liveCall.setSkipAutoUnhold(true);
                 // Disconnect the active call instead of the holding call because it is historically
-                // easier to do, rather than disconnect a held call.
+                // easier to do, rather than disconnecting a held call and holding the active call.
                 return disconnectOngoingCallForEmergencyCall(transactionFuture, liveCall,
-                        disconnectReason, isSequencingRequiredLive);
-            } else { // Dual sim case
-                // If the live call can't be held, we would've already disconnected it
-                // in disconnectCallsForEmergencyCall. Note at this point, we should always have
-                // a held call then that should be disconnected (over the active call).
-                if (!mCallsManager.canHold(liveCall)) {
-                    return transactionFuture;
-                }
-                heldCall = mCallsManager.getFirstCallWithState(CallState.ON_HOLD);
-                boolean isSequencingRequiredRingingAndHeld = ringingCall == null
-                        || !arePhoneAccountsSame(ringingCall, heldCall);
-                isSequencingRequiredLive = !arePhoneAccountsSame(heldCall, liveCall);
+                        disconnectReason);
+            } else if (heldCall != null) { // Dual sim case
+                // Note at this point, we should always have a held call then that should
+                // be disconnected (over the active call) but still enforce with a null check and
+                // ensure we haven't disconnected it already.
                 heldCall.getAnalytics().setCallIsInterrupted(true);
                 // Disconnect the held call.
                 transactionFuture = disconnectOngoingCallForEmergencyCall(transactionFuture,
-                        heldCall, disconnectReason, isSequencingRequiredRingingAndHeld);
+                        heldCall, disconnectReason);
             }
         }
 
@@ -654,46 +683,26 @@
                 final String disconnectReason = "outgoing call does not support emergency calls, "
                         + "disconnecting.";
                 return disconnectOngoingCallForEmergencyCall(transactionFuture, liveCall,
-                        disconnectReason, isSequencingRequiredLive);
+                        disconnectReason);
             }
         }
 
-        // If we are trying to make an emergency call with the same package name as
-        // the live call, then attempt to hold the call if the carrier config supports holding
-        // emergency calls. Otherwise, disconnect the live call in order to make room for the
-        // emergency call.
-        if (PhoneAccountHandle.areFromSamePackage(liveCallPhoneAccount,
-                emergencyCall.getTargetPhoneAccount())) {
-            Log.i(this, "makeRoomForOutgoingEmergencyCall: phoneAccounts are from same "
-                    + "package. Attempting to hold live call before placing emergency call.");
-            return maybeHoldLiveCallForEmergency(transactionFuture,
-                    isSequencingRequiredLive, liveCall, emergencyCall,
-                    shouldHoldForEmergencyCall(liveCallPhoneAccount) /* shouldHoldForEmergency */);
-        } else if (emergencyCall.getTargetPhoneAccount() == null) {
-            // Without a phone account, we can't say reliably that the call will fail.
-            // If the user chooses the same phone account as the live call, then it's
-            // still possible that the call can be made (like with CDMA calls not supporting
-            // hold but they still support adding a call by going immediately into conference
-            // mode). Return true here and we'll run this code again after user chooses an
-            // account.
-            return transactionFuture;
-        }
+        // At this point, if we still have an active call, then it supports holding for emergency
+        // and is a managed call. It may not support holding but we will still try to hold anyway
+        // (i.e. swap for Verizon). Note that there will only be one call at this stage which is
+        // the active call so that means that we will attempt to place the emergency call on the
+        // same phone account unless it's not using a Telephony phone account (Fi wifi call), in
+        // which case, we would want to verify holding happened. For cases like backup calling, the
+        // shared data call will be over Telephony as well as the emergency call, so the shared
+        // data call would get disconnected by the CS.
 
-        // Hold the live call if possible before attempting the new outgoing emergency call. Also,
-        // ensure that we try holding if we disconnected a held call and the live call supports
-        // holding.
-        if (mCallsManager.canHold(liveCall) || (heldCall != null
-                && mCallsManager.supportsHold(liveCall))) {
-            Log.i(this, "makeRoomForOutgoingEmergencyCall: holding live call.");
-            return maybeHoldLiveCallForEmergency(transactionFuture, isSequencingRequiredLive,
-                    liveCall, emergencyCall, true /* shouldHoldForEmergency */);
-        }
-
-        // Refrain from failing the call in Telecom if possible. Additional processing will be done
-        // in the Telephony layer to hold/disconnect calls (across subs, if needed) and we will fail
-        // there instead. This should be treated as the preprocessing steps required to set up the
-        // ability to place an emergency call.
-        return transactionFuture;
+        // We want to verify if the live call was placed via the connection manager. Don't use
+        // the manipulated liveCallPhoneAccount since the delegate would pull directly from the
+        // target phone account.
+        boolean isLiveUsingConnectionManager = !Objects.equals(liveCall.getTargetPhoneAccount(),
+                liveCall.getDelegatePhoneAccountHandle());
+        return maybeHoldLiveCallForEmergency(transactionFuture, liveCall,
+                emergencyCall, isLiveUsingConnectionManager);
     }
 
     /**
@@ -747,6 +756,12 @@
             }
             mAnomalyReporter.reportAnomaly(LIVE_CALL_STUCK_CONNECTING_ERROR_UUID,
                     LIVE_CALL_STUCK_CONNECTING_ERROR_MSG);
+            // Skip auto-unhold for when the live call is disconnected. Consider a scenario where
+            // we have a held non-holdable call (VZW) and the live call (also VZW) is stuck in
+            // connecting. If we auto unhold the VZW while placing the emergency call, then we may
+            // end up with two active calls. The auto-unholding logic really only applies for
+            // the non-holdable phone account.
+            liveCall.setSkipAutoUnhold(true);
             return liveCall.disconnect("Force disconnect CONNECTING call.");
         }
 
@@ -757,6 +772,12 @@
                 // state, just disconnect it since the user has explicitly started a new call.
                 call.getAnalytics().setCallIsAdditional(true);
                 outgoingCall.getAnalytics().setCallIsInterrupted(true);
+                // Skip auto-unhold for when the outgoing call is disconnected. Consider a scenario
+                // where we have a held non-holdable call (VZW) and a dialing call (also VZW). If we
+                // auto unhold the VZW while placing the emergency call, then we may end up with
+                // two active calls. The auto-unholding logic really only applies for the
+                // non-holdable phone account.
+                outgoingCall.setSkipAutoUnhold(true);
                 return outgoingCall.disconnect(
                         "Disconnecting call in SELECT_PHONE_ACCOUNT in favor of new "
                                 + "outgoing call.");
@@ -844,35 +865,41 @@
 
     /**
      * Tries to hold the live call before placing the emergency call. If the hold fails, then we
-     * will instead disconnect the call.
+     * will instead disconnect the call. This only applies for when the emergency call and live call
+     * are from the same phone account or there's only one ongoing call, in which case, we should
+     * place the emergency call on the ongoing call's phone account.
      *
      * Note: This only applies when the live call and emergency call are from the same phone
      * account.
      */
     private CompletableFuture<Boolean> maybeHoldLiveCallForEmergency(
-            CompletableFuture<Boolean> transactionFuture, boolean isSequencingRequired,
-            Call liveCall, Call emergencyCall, boolean shouldHoldForEmergency) {
+            CompletableFuture<Boolean> transactionFuture,
+            Call liveCall, Call emergencyCall, boolean isLiveUsingConnectionManager) {
         emergencyCall.getAnalytics().setCallIsAdditional(true);
         liveCall.getAnalytics().setCallIsInterrupted(true);
         final String holdReason = "calling " + emergencyCall.getId();
-        CompletableFuture<Boolean> holdResultFuture = CompletableFuture.completedFuture(false);
-        if (shouldHoldForEmergency) {
-            if (transactionFuture != null && isSequencingRequired) {
-                holdResultFuture = transactionFuture.thenComposeAsync((result) -> {
-                    if (result) {
-                        Log.i(this, "makeRoomForOutgoingEmergencyCall: Request to disconnect "
-                                + "previous call succeeded. Attempting to hold live call.");
-                    } else { // Log the failure but proceed with hold transaction.
-                        Log.i(this, "makeRoomForOutgoingEmergencyCall: Request to disconnect "
-                                + "previous call failed. Still attempting to hold live call.");
-                    }
-                    return liveCall.hold(holdReason);
-                }, new LoggedHandlerExecutor(mHandler, "CSC.mRFOEC",
-                        mCallsManager.getLock()));
-            } else {
-                holdResultFuture = liveCall.hold(holdReason);
+        CompletableFuture<Boolean> holdResultFuture;
+        holdResultFuture = transactionFuture.thenComposeAsync((result) -> {
+            if (result) {
+                Log.i(this, "makeRoomForOutgoingEmergencyCall: Previous transaction "
+                        + "succeeded. Attempting to hold live call.");
+            } else { // Log the failure but proceed with hold transaction.
+                Log.i(this, "makeRoomForOutgoingEmergencyCall: Previous transaction "
+                        + "failed. Still attempting to hold live call.");
             }
+            Log.i(this, "makeRoomForOutgoingEmergencyCall: Attempt to hold live call. "
+                    + "Verifying hold: %b", isLiveUsingConnectionManager);
+            return liveCall.hold(holdReason);
+        }, new LoggedHandlerExecutor(mHandler, "CSC.mRFOEC", mCallsManager.getLock()));
+
+        // If the live call was placed using a connection manager, we should verify that holding
+        // happened before placing the emergency call. We should disconnect the call if hold fails.
+        // Otherwise, let Telephony handle additional sequencing that may be required.
+        if (!isLiveUsingConnectionManager) {
+            return transactionFuture;
         }
+
+        // Otherwise, verify hold succeeded and if it didn't, then hangup the call.
         return holdResultFuture.thenComposeAsync((result) -> {
             if (!result) {
                 Log.i(this, "makeRoomForOutgoingEmergencyCall: Attempt to hold live call "
@@ -888,7 +915,7 @@
     }
 
     /**
-     * Disconnects all VOIP + non-holdable calls as well as those that don't support placing
+     * Disconnects all VOIP (SM + Transactional) as well as those that don't support placing
      * emergency calls before placing an emergency call.
      *
      * Note: If a call can't be held, it will be active to begin with.
@@ -896,38 +923,15 @@
      *         disconnect transaction.
      */
     private Pair<Set<Call>, CompletableFuture<Boolean>> disconnectCallsForEmergencyCall(
-            Call emergencyCall, boolean areMultiplePhoneAccountsActive) {
+            Call emergencyCall) {
         Set<Call> callsDisconnected = new HashSet<>();
         Call previousCall = null;
         Call ringingCall = mCallsManager.getRingingOrSimulatedRingingCall();
         CompletableFuture<Boolean> disconnectFuture = CompletableFuture.completedFuture(true);
         for (Call call: mCallsManager.getCalls()) {
-            // Conditions for checking if call doesn't need to be disconnected immediately.
-            boolean isVoip = isVoipCall(call);
-            boolean callSupportsHold = call.can(Connection.CAPABILITY_SUPPORT_HOLD);
-            boolean callSupportsHoldingEmergencyCall = shouldHoldForEmergencyCall(
-                    call.getTargetPhoneAccount());
-
-            // Skip the ringing call; we'll handle the disconnect explicitly later.
-            if (call.equals(ringingCall)) {
+            if (skipDisconnectForEmergencyCall(call, ringingCall)) {
                 continue;
             }
-
-            // If the call is not VOIP and supports holding + capability to place emergency calls,
-            // don't disconnect the call.
-            if (!isVoip && callSupportsHoldingEmergencyCall) {
-                // If call supports hold, we can skip. Other condition we check here is if calls
-                // are on single sim, in which case we will refrain from disconnecting a potentially
-                // held call (i.e. Verizon ACTIVE + HOLD case) here and let that be determined later
-                // down in makeRoomForOutgoingEmergencyCall.
-                if (callSupportsHold || (!areMultiplePhoneAccountsActive)) {
-                    continue;
-                }
-            }
-
-            Log.i(this, "Disconnecting call (%s). isManaged: %b, call supports hold: %b, call "
-                            + "supports holding emergency call: %b", call.getId(), !isVoip,
-                    callSupportsHold, callSupportsHoldingEmergencyCall);
             emergencyCall.getAnalytics().setCallIsAdditional(true);
             call.getAnalytics().setCallIsInterrupted(true);
             call.setOverrideDisconnectCauseCode(new DisconnectCause(
@@ -950,37 +954,64 @@
         return new Pair<>(callsDisconnected, disconnectFuture);
     }
 
+    private boolean skipDisconnectForEmergencyCall(Call call, Call ringingCall) {
+        // Conditions for checking if call doesn't need to be disconnected immediately.
+        boolean isVoip = isVoipCall(call);
+        boolean callSupportsHoldingEmergencyCall = shouldHoldForEmergencyCall(
+                call.getTargetPhoneAccount());
+
+        // Skip the ringing call; we'll handle the disconnect explicitly later. Also, if we have
+        // a conference call, only disconnect the host call.
+        if (call.equals(ringingCall) || call.getParentCall() != null) {
+            return true;
+        }
+
+        // If the call is managed and supports holding for emergency calls, don't disconnect the
+        // call.
+        if (!isVoip && callSupportsHoldingEmergencyCall) {
+            return true;
+        }
+        // Otherwise, we will disconnect the call because it doesn't meet one of the conditions
+        // above.
+        Log.i(this, "Disconnecting call (%s). isManaged: %b, call "
+                + "supports holding emergency call: %b", call.getId(), !isVoip,
+                callSupportsHoldingEmergencyCall);
+        return false;
+    }
+
     /**
      * Waiting on the passed future completion when sequencing is required, this will try to the
      * disconnect the call passed in.
      */
     private CompletableFuture<Boolean> disconnectOngoingCallForEmergencyCall(
             CompletableFuture<Boolean> transactionFuture, Call callToDisconnect,
-            String disconnectReason, boolean isSequencingRequired) {
-        if (isSequencingRequired) {
-            return transactionFuture.thenComposeAsync((result) -> {
-                if (result) {
-                    Log.i(this, "makeRoomForOutgoingEmergencyCall: Request to disconnect "
-                            + "previous call succeeded. Attempting to disconnect ongoing call"
-                            + " %s.", callToDisconnect);
-                } else {
-                    Log.i(this, "makeRoomForOutgoingEmergencyCall: Request to disconnect "
-                            + "previous call failed. Still attempting to disconnect ongoing call"
-                            + " %s.", callToDisconnect);
-                }
-                return callToDisconnect.disconnect(disconnectReason);
-            }, new LoggedHandlerExecutor(mHandler, "CSC.mRFOEC",
-                    mCallsManager.getLock()));
-        } else {
+            String disconnectReason) {
+        return transactionFuture.thenComposeAsync((result) -> {
+            if (result) {
+                Log.i(this, "makeRoomForOutgoingEmergencyCall: Request to disconnect "
+                        + "previous call succeeded. Attempting to disconnect ongoing call"
+                        + " %s.", callToDisconnect);
+            } else {
+                Log.i(this, "makeRoomForOutgoingEmergencyCall: Request to disconnect "
+                        + "previous call failed. Still attempting to disconnect ongoing call"
+                        + " %s.", callToDisconnect);
+            }
             return callToDisconnect.disconnect(disconnectReason);
-        }
+        }, new LoggedHandlerExecutor(mHandler, "CSC.mRFOEC", mCallsManager.getLock()));
     }
 
     /**
      * Determines if DSDA is being used (i.e. calls present on more than one phone account).
+     * @param callsToExclude The list of calls to exclude (these will be calls that have been
+     *                       disconnected but may still be being tracked by CallsManager depending
+     *                       on timing).
      */
-    private boolean areMultiplePhoneAccountsActive() {
-        List<Call> calls = mCallsManager.getCalls().stream().toList();
+    private boolean areMultiplePhoneAccountsActive(Set<Call> callsToExclude) {
+        for (Call excludedCall: callsToExclude) {
+            Log.i(this, "Calls to exclude: %s", excludedCall);
+        }
+        List<Call> calls = mCallsManager.getCalls().stream()
+                .filter(c -> !callsToExclude.contains(c)).toList();
         PhoneAccountHandle handle1 = null;
         if (!calls.isEmpty()) {
             // Find the first handle different from the one retrieved from the first call in
@@ -1034,8 +1065,11 @@
     private CompletableFuture<Boolean> disconnectAllCallsWithPhoneAccount(
             PhoneAccountHandle handle, boolean excludeAccount) {
         CompletableFuture<Boolean> disconnectFuture = CompletableFuture.completedFuture(true);
+        // Filter out the corresponding phone account and ensure that we don't consider conference
+        // participants as part of the bulk disconnect (we'll just disconnect the host directly).
         List<Call> calls = mCallsManager.getCalls().stream()
-                .filter(c -> excludeAccount != c.getTargetPhoneAccount().equals(handle)).toList();
+                .filter(c -> excludeAccount != c.getTargetPhoneAccount().equals(handle)
+                        && c.getParentCall() == null).toList();
         for (Call call: calls) {
             // Wait for all disconnects before we accept the new call.
             disconnectFuture = disconnectFuture.thenComposeAsync((result) -> {
diff --git a/src/com/android/server/telecom/callsequencing/CallsManagerCallSequencingAdapter.java b/src/com/android/server/telecom/callsequencing/CallsManagerCallSequencingAdapter.java
index 2de3b22..611bb9e 100644
--- a/src/com/android/server/telecom/callsequencing/CallsManagerCallSequencingAdapter.java
+++ b/src/com/android/server/telecom/callsequencing/CallsManagerCallSequencingAdapter.java
@@ -234,6 +234,16 @@
     public void maybeMoveHeldCallToForeground(Call removedCall, boolean isLocallyDisconnecting) {
         CompletableFuture<Boolean> unholdForegroundCallFuture = null;
         Call foregroundCall = mCallAudioManager.getPossiblyHeldForegroundCall();
+        // There are some cases (non-holdable calls) where we may want to skip auto-unholding when
+        // we're processing a new outgoing call and waiting for it to go active. Skip the
+        // auto-unholding in this case so that we don't end up with two active calls. If the new
+        // call fails, we will auto-unhold on that removed call. This is only set in
+        // CallSequencingController because the legacy code doesn't wait for disconnects to occur
+        // in order to place an outgoing (emergency) call, so we don't see this issue.
+        if (removedCall.getSkipAutoUnhold()) {
+            return;
+        }
+
         if (isLocallyDisconnecting) {
             boolean isDisconnectingChildCall = removedCall.isDisconnectingChildCall();
             Log.v(this, "maybeMoveHeldCallToForeground: isDisconnectingChildCall = "
@@ -247,7 +257,6 @@
             if (!isDisconnectingChildCall && foregroundCall != null
                     && foregroundCall.getState() == CallState.ON_HOLD
                     && CallsManager.areFromSameSource(foregroundCall, removedCall)) {
-
                 unholdForegroundCallFuture = foregroundCall.unhold();
             }
         } else if (foregroundCall != null &&
@@ -355,6 +364,20 @@
         });
     }
 
+    /**
+     * Upon a call resume failure, we will auto-unhold the foreground call that was held. Note that
+     * this should only apply for calls across phone accounts as the ImsPhoneCallTracker handles
+     * this for a single phone.
+     * @param callResumeFailed The call that failed to resume.
+     * @param callToUnhold The fg call that was held.
+     */
+    public void handleCallResumeFailed(Call callResumeFailed, Call callToUnhold) {
+        if (mIsCallSequencingEnabled && !mSequencingController.arePhoneAccountsSame(
+                callResumeFailed, callToUnhold)) {
+            unholdCall(callToUnhold);
+        }
+    }
+
     public Handler getHandler() {
         return mHandler;
     }
diff --git a/src/com/android/server/telecom/callsequencing/VerifyCallStateChangeTransaction.java b/src/com/android/server/telecom/callsequencing/VerifyCallStateChangeTransaction.java
index 7bebb55..b7e4f04 100644
--- a/src/com/android/server/telecom/callsequencing/VerifyCallStateChangeTransaction.java
+++ b/src/com/android/server/telecom/callsequencing/VerifyCallStateChangeTransaction.java
@@ -18,8 +18,10 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.telecom.Call;
+import com.android.server.telecom.CallState;
 import com.android.server.telecom.TelecomSystem;
 
+import android.telecom.CallException;
 import android.telecom.Log;
 
 import java.util.Set;
@@ -56,6 +58,26 @@
         }
     };
 
+    private final Call.ListenerBase mCallListenerImpl = new Call.ListenerBase() {
+        @Override
+        public void onCallHoldFailed(Call call) {
+            if (call.equals(mCall) && mTargetCallStates.contains(CallState.ON_HOLD)) {
+                // Fail the transaction if a call hold failure is received.
+                mTransactionResult.complete(new CallTransactionResult(
+                        CallException.CODE_CANNOT_HOLD_CURRENT_ACTIVE_CALL, "error holding call"));
+            }
+        }
+        @Override
+        public void onCallResumeFailed(Call call) {
+            if (call.equals(mCall) && mTargetCallStates.contains(CallState.ACTIVE)) {
+                // Fail the transaction if a call resume failure is received (this means that the
+                // current call could not be unheld).
+                mTransactionResult.complete(new CallTransactionResult(
+                        CallException.CODE_CALL_CANNOT_BE_SET_TO_ACTIVE, "error unholding call"));
+            }
+        }
+    };
+
     public VerifyCallStateChangeTransaction(TelecomSystem.SyncRoot lock,  Call call,
             int... targetCallStates) {
         super(lock, CALL_STATE_TIMEOUT_MILLISECONDS);
@@ -73,12 +95,14 @@
             return mTransactionResult;
         }
         mCall.addCallStateListener(mCallStateListenerImpl);
+        mCall.addListener(mCallListenerImpl);
         return mTransactionResult;
     }
 
     @Override
     public void finishTransaction() {
         mCall.removeCallStateListener(mCallStateListenerImpl);
+        mCall.removeListener(mCallListenerImpl);
     }
 
     private boolean isNewCallStateTargetCallState() {
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java b/tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java
index 9daa7cf..1b1ca56 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java
@@ -1357,6 +1357,56 @@
         verify(mCallAudioManager, timeout(TEST_TIMEOUT)).notifyAudioOperationsComplete();
     }
 
+    @Test
+    @SmallTest
+    public void testActiveDevicePresentRoutesOnCurrentActive() {
+        when(mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()).thenReturn(true);
+        // Connect first BT device.
+        verifyConnectBluetoothDevice(AudioRoute.TYPE_BLUETOOTH_SCO);
+        // Connect another BT device.
+        String scoDeviceAddress = "00:00:00:00:00:03";
+        BluetoothDevice scoDevice2 =
+                BluetoothRouteManagerTest.makeBluetoothDevice(scoDeviceAddress);
+        BLUETOOTH_DEVICES.add(scoDevice2);
+
+        // Signal second BT device added in controller and verify routing to that device upon
+        // receiving active focus.
+        mController.sendMessageWithSessionInfo(BT_DEVICE_ADDED, AudioRoute.TYPE_BLUETOOTH_SCO,
+                scoDevice2);
+        CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH,
+                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER
+                        | CallAudioState.ROUTE_BLUETOOTH, BLUETOOTH_DEVICE_1, BLUETOOTH_DEVICES);
+        mController.sendMessageWithSessionInfo(SWITCH_FOCUS, ACTIVE_FOCUS, 0);
+        verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+                any(CallAudioState.class), eq(expectedState));
+
+        // Update the currently tracked active device to be BLUETOOTH_DEVICE_1.
+        mController.updateActiveBluetoothDevice(
+                new Pair<>(AudioRoute.TYPE_BLUETOOTH_SCO, BLUETOOTH_DEVICE_1.getAddress()));
+        // Verify that sending BT_ACTIVE_DEVICE_PRESENT when BLUETOOTH_DEVICE_1 isn't the currently
+        // tracked active device, that we ignore routing.
+        mController.sendMessageWithSessionInfo(BT_ACTIVE_DEVICE_PRESENT,
+                AudioRoute.TYPE_BLUETOOTH_SCO, scoDevice2.getAddress());
+        verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+                any(CallAudioState.class), eq(expectedState));
+
+        // Now update the active device so that it's scoDevice2 and verify that
+        // BT_ACTIVE_DEVICE_PRESENT is properly processed and that we route into the device.
+        mController.updateActiveBluetoothDevice(
+                new Pair<>(AudioRoute.TYPE_BLUETOOTH_SCO, scoDevice2.getAddress()));
+        mController.sendMessageWithSessionInfo(BT_ACTIVE_DEVICE_PRESENT,
+                AudioRoute.TYPE_BLUETOOTH_SCO, scoDevice2.getAddress());
+        mController.sendMessageWithSessionInfo(BT_AUDIO_DISCONNECTED, 0,
+                BLUETOOTH_DEVICE_1);
+        mController.sendMessageWithSessionInfo(BT_AUDIO_CONNECTED,
+                0, scoDevice2);
+        expectedState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH,
+                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER
+                        | CallAudioState.ROUTE_BLUETOOTH, scoDevice2, BLUETOOTH_DEVICES);
+        verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+                any(CallAudioState.class), eq(expectedState));
+    }
+
     private void verifyConnectBluetoothDevice(int audioType) {
         mController.initialize();
         mController.setActive(true);
diff --git a/tests/src/com/android/server/telecom/tests/CallTest.java b/tests/src/com/android/server/telecom/tests/CallTest.java
index 3a7a822..b2cdd7d 100644
--- a/tests/src/com/android/server/telecom/tests/CallTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallTest.java
@@ -999,6 +999,7 @@
         @Test
     @SmallTest
     public void testOnConnectionEventNotifiesListener() {
+        when(mFeatureFlags.enableCallSequencing()).thenReturn(true);
         Call.Listener listener = mock(Call.Listener.class);
         Call call = createCall("1");
         call.addListener(listener);
@@ -1017,6 +1018,9 @@
         call.onConnectionEvent(Connection.EVENT_CALL_SWITCH_FAILED, null);
         verify(listener).onCallSwitchFailed(call);
 
+        call.onConnectionEvent(Connection.EVENT_CALL_RESUME_FAILED, null);
+        verify(listener).onCallResumeFailed(call);
+
         final int d2dType = 1;
         final int d2dValue = 2;
         final Bundle d2dExtras = new Bundle();