Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5e342633 authored by Thomas Stuart's avatar Thomas Stuart
Browse files

disconnect ongoing unholdable call for transactional calls

Previously, if an ongoing unholdable call was active and a new
incoming transactional call came in, the notifications answer
button would actually disconnect the transactional call in favor of
keeping the active unholdable call.

Now, CallControlCallback requests for transactional calls can disconnect
the ongoing unholdable calls. This behavior makes things more consistent
with the legacy ConnectionService way.

Fixes: 340621152
Flag: com.android.server.telecom.flags.transactional_hold_disconnects_unholdable
Test:  7 unit tests + manual testing
        (1) start unholdable call
	(2) create incoming transactional call
	(3) answer via notification
	observe/expect: unholdable call disconnects &
	          transactional call becomes active
Change-Id: Id941d7f34454ca31dddf67c121762ee392c790e8
parent 4737d783
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -24,3 +24,14 @@ flag {
  description: "Enables simultaneous call sequencing for SIM PhoneAccounts"
  bug: "327038818"
}

# OWNER=tjstuart TARGET=24Q4
flag {
  name: "transactional_hold_disconnects_unholdable"
  namespace: "telecom"
  description: "Disconnect ongoing unholdable calls for CallControlCallbacks"
  bug: "340621152"
  metadata {
    purpose: PURPOSE_BUGFIX
  }
}
+69 −28
Original line number Diff line number Diff line
@@ -3836,8 +3836,7 @@ public class CallsManager extends Call.ListenerBase
            if (canHold(activeCall)) {
                activeCall.hold("swap to " + call.getId());
                return true;
            } else if (supportsHold(activeCall)
                    && areFromSameSource(activeCall, call)) {
            } else if (sameSourceHoldCase(activeCall, call)) {

                // Handle the case where the active call and the new call are from the same CS or
                // connection manager, and the currently active call supports hold but cannot
@@ -3886,22 +3885,55 @@ public class CallsManager extends Call.ListenerBase
        return false;
    }

    // attempt to hold the requested call and complete the callback on the result
    /**
     * attempt to hold or swap the current active call in favor of a new call request. The
     * OutcomeReceiver will return onResult if the current active call is held or disconnected.
     * Otherwise, the OutcomeReceiver will fail.
     */
    public void transactionHoldPotentialActiveCallForNewCall(Call newCall,
            OutcomeReceiver<Boolean, CallException> callback) {
            boolean isCallControlRequest, OutcomeReceiver<Boolean, CallException> callback) {
        String mTag = "transactionHoldPotentialActiveCallForNewCall: ";
        Call activeCall = (Call) mConnectionSvrFocusMgr.getCurrentFocusCall();
        Log.i(this, "transactionHoldPotentialActiveCallForNewCall: "
                + "newCall=[%s], activeCall=[%s]", newCall, activeCall);
        Log.i(this, mTag + "newCall=[%s], activeCall=[%s]", newCall, activeCall);

        // early exit if there is no need to hold an active call
        if (activeCall == null || activeCall == newCall) {
            Log.i(this, "transactionHoldPotentialActiveCallForNewCall:"
                    + " no need to hold activeCall");
            Log.i(this, mTag + "no need to hold activeCall");
            callback.onResult(true);
            return;
        }

        // before attempting CallsManager#holdActiveCallForNewCall(Call), check if it'll fail early
        if (mFeatureFlags.transactionalHoldDisconnectsUnholdable()) {
            // prevent bad actors from disconnecting the activeCall. Instead, clients will need to
            // notify the user that they need to disconnect the ongoing call before making the
            // new call ACTIVE.
            if (isCallControlRequest && !canHoldOrSwapActiveCall(activeCall, newCall)) {
                Log.i(this, mTag + "CallControlRequest exit");
                callback.onError(new CallException("activeCall is NOT holdable or swappable, please"
                        + " request the user disconnect the call.",
                        CallException.CODE_CANNOT_HOLD_CURRENT_ACTIVE_CALL));
                return;
            }

            if (holdActiveCallForNewCall(newCall)) {
                // Transactional clients do not call setHold but the request was sent to set the
                // call as inactive and it has already been acked by this point.
                markCallAsOnHold(activeCall);
                callback.onResult(true);
            } else {
                // It's possible that holdActiveCallForNewCall disconnected the activeCall.
                // Therefore, the activeCalls state should be checked before failing.
                if (activeCall.isLocallyDisconnecting()) {
                    callback.onResult(true);
                } else {
                    Log.i(this, mTag + "active call could not be held or disconnected");
                    callback.onError(
                            new CallException("activeCall could not be held or disconnected",
                                    CallException.CODE_CANNOT_HOLD_CURRENT_ACTIVE_CALL));
                }
            }
        } else {
            // before attempting CallsManager#holdActiveCallForNewCall(Call), check if it'll fail
            // early
            if (!canHold(activeCall) &&
                    !(supportsHold(activeCall) && areFromSameSource(activeCall, newCall))) {
                Log.i(this, "transactionHoldPotentialActiveCallForNewCall: "
@@ -3924,6 +3956,15 @@ public class CallsManager extends Call.ListenerBase
            markCallAsOnHold(activeCall);
            callback.onResult(true);
        }
    }

    private boolean canHoldOrSwapActiveCall(Call activeCall, Call newCall) {
        return canHold(activeCall) || sameSourceHoldCase(activeCall, newCall);
    }

    private boolean sameSourceHoldCase(Call activeCall, Call call) {
        return supportsHold(activeCall) && areFromSameSource(activeCall, call);
    }

    @VisibleForTesting
    public void markCallAsActive(Call call) {
+7 −4
Original line number Diff line number Diff line
@@ -325,7 +325,8 @@ public class TransactionalServiceWrapper implements
        // This request is originating from the VoIP application.
        private void handleCallControlNewCallFocusTransactions(Call call, String action,
                boolean isAnswer, int potentiallyNewVideoState, ResultReceiver callback) {
            mTransactionManager.addTransaction(createSetActiveTransactions(call),
            mTransactionManager.addTransaction(
                    createSetActiveTransactions(call, true /* isCallControlRequest */),
                    new OutcomeReceiver<>() {
                        @Override
                        public void onResult(VoipCallTransactionResult result) {
@@ -445,7 +446,8 @@ public class TransactionalServiceWrapper implements
        Call foregroundCallBeforeSwap = mCallsManager.getForegroundCall();
        boolean wasActive = foregroundCallBeforeSwap != null && foregroundCallBeforeSwap.isActive();

        SerialTransaction serialTransactions = createSetActiveTransactions(call);
        SerialTransaction serialTransactions = createSetActiveTransactions(call,
                false /* isCallControlRequest */);
        // 3. get ack from client (that the requested call can go active)
        if (isAnswerRequest) {
            serialTransactions.appendTransaction(
@@ -654,12 +656,13 @@ public class TransactionalServiceWrapper implements
        mCallsManager.removeCall(call);
    }

    private SerialTransaction createSetActiveTransactions(Call call) {
    private SerialTransaction createSetActiveTransactions(Call call, boolean isCallControlRequest) {
        // create list for multiple transactions
        List<VoipCallTransaction> transactions = new ArrayList<>();

        // potentially hold the current active call in order to set a new call (active/answered)
        transactions.add(new MaybeHoldCallForNewCallTransaction(mCallsManager, call));
        transactions.add(
                new MaybeHoldCallForNewCallTransaction(mCallsManager, call, isCallControlRequest));
        // And request a new focus call update
        transactions.add(new RequestNewActiveCallTransaction(mCallsManager, call));

+10 −2
Original line number Diff line number Diff line
@@ -26,16 +26,23 @@ import com.android.server.telecom.CallsManager;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;

/**
 * This VoipCallTransaction is responsible for holding any active call in favor of a new call
 * request. If the active call cannot be held or disconnected, the transaction will fail.
 */
public class MaybeHoldCallForNewCallTransaction extends VoipCallTransaction {

    private static final String TAG = MaybeHoldCallForNewCallTransaction.class.getSimpleName();
    private final CallsManager mCallsManager;
    private final Call mCall;
    private final boolean mIsCallControlRequest;

    public MaybeHoldCallForNewCallTransaction(CallsManager callsManager, Call call) {
    public MaybeHoldCallForNewCallTransaction(CallsManager callsManager, Call call,
            boolean isCallControlRequest) {
        super(callsManager.getLock());
        mCallsManager = callsManager;
        mCall = call;
        mIsCallControlRequest = isCallControlRequest;
    }

    @Override
@@ -43,7 +50,8 @@ public class MaybeHoldCallForNewCallTransaction extends VoipCallTransaction {
        Log.d(TAG, "processTransaction");
        CompletableFuture<VoipCallTransactionResult> future = new CompletableFuture<>();

        mCallsManager.transactionHoldPotentialActiveCallForNewCall(mCall, new OutcomeReceiver<>() {
        mCallsManager.transactionHoldPotentialActiveCallForNewCall(mCall, mIsCallControlRequest,
                new OutcomeReceiver<>() {
            @Override
            public void onResult(Boolean result) {
                Log.d(TAG, "processTransaction: onResult");
+3 −2
Original line number Diff line number Diff line
@@ -17,7 +17,6 @@
package com.android.server.telecom.voip;

import android.os.OutcomeReceiver;
import android.telecom.CallAttributes;
import android.telecom.CallException;
import android.util.Log;

@@ -25,6 +24,7 @@ import com.android.server.telecom.Call;
import com.android.server.telecom.CallState;
import com.android.server.telecom.CallsManager;
import com.android.server.telecom.ConnectionServiceFocusManager;
import com.android.server.telecom.flags.Flags;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
@@ -69,7 +69,8 @@ public class RequestNewActiveCallTransaction extends VoipCallTransaction {
            return future;
        }

        if (mCallsManager.getActiveCall() != null) {
        if (!Flags.transactionalHoldDisconnectsUnholdable() &&
                mCallsManager.getActiveCall() != null) {
            future.complete(new VoipCallTransactionResult(
                    CallException.CODE_CALL_CANNOT_BE_SET_TO_ACTIVE,
                    "Already an active call. Request hold on current active call."));
Loading