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

Commit 8ca7f713 authored by Thomas Stuart's avatar Thomas Stuart Committed by Android (Google) Code Review
Browse files

Merge "fix toggling hold for a single transactional call" into udc-dev

parents 06a9f628 79c8c007
Loading
Loading
Loading
Loading
+40 −15
Original line number Diff line number Diff line
@@ -6090,34 +6090,53 @@ public class CallsManager extends Call.ListenerBase
    }

    /**
     * Intended for ongoing or new calls that would like to go active/answered and need to
     * update the mConnectionSvrFocusMgr before setting the state
     * This helper mainly requests mConnectionSvrFocusMgr to update the call focus via a
     * {@link TransactionalFocusRequestCallback}.  However, in the case of a held call, the
     * state must be set first and then a request must be made.
     *
     * @param newCallFocus          to set active/answered
     * @param resultCallback        that back propagates the focusManager result
     *
     * Note: This method should only be called if there are no active calls.
     */
    public void transactionRequestNewFocusCall(Call call, int newCallState,
            OutcomeReceiver<Boolean, CallException> callback) {
        Log.d(this, "transactionRequestNewFocusCall");
        PendingAction pendingAction = new ActionSetCallState(call, newCallState,
                "transactional ActionSetCallState");
    public void requestNewCallFocusAndVerify(Call newCallFocus,
            OutcomeReceiver<Boolean, CallException> resultCallback) {
        int currentCallState = newCallFocus.getState();
        PendingAction pendingAction = null;

        // if the current call is in a state that can become the new call focus, we can set the
        // state afterwards...
        if (ConnectionServiceFocusManager.PRIORITY_FOCUS_CALL_STATE.contains(currentCallState)) {
            pendingAction = new ActionSetCallState(newCallFocus, CallState.ACTIVE,
                    "vCFC: pending action set state");
        } else {
            // However, HELD calls need to be set to ACTIVE before requesting call focus.
            setCallState(newCallFocus, CallState.ACTIVE, "vCFC: immediately set active");
        }

        mConnectionSvrFocusMgr
                .requestFocus(call,
                        new TransactionalFocusRequestCallback(pendingAction, call, callback));
                .requestFocus(newCallFocus,
                        new TransactionalFocusRequestCallback(pendingAction, currentCallState,
                                newCallFocus, resultCallback));
    }

    /**
     * Request a new call focus and ensure the request was successful via an OutcomeReceiver. Also,
     * include a PendingAction that will execute if the call focus change is successful.
     * conditionally include a PendingAction that will execute if and only if the call focus change
     * is successful.
     */
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
    public class TransactionalFocusRequestCallback implements
            ConnectionServiceFocusManager.RequestFocusCallback {
        private PendingAction mPendingAction;
        @NonNull
        private Call mTargetCallFocus;
        private int mPreviousCallState;
        @NonNull private Call mTargetCallFocus;
        private OutcomeReceiver<Boolean, CallException> mCallback;

        TransactionalFocusRequestCallback(PendingAction pendingAction, @NonNull Call call,
                OutcomeReceiver<Boolean, CallException> callback) {
        TransactionalFocusRequestCallback(PendingAction pendingAction, int previousState,
                @NonNull Call call, OutcomeReceiver<Boolean, CallException> callback) {
            mPendingAction = pendingAction;
            mPreviousCallState = previousState;
            mTargetCallFocus = call;
            mCallback = callback;
        }
@@ -6130,12 +6149,18 @@ public class CallsManager extends Call.ListenerBase
                    mTargetCallFocus, currentCallFocus);
            if (currentCallFocus == null ||
                    !currentCallFocus.getId().equals(mTargetCallFocus.getId())) {
                // possibly reset the call state
                if (mTargetCallFocus.getState() != mPreviousCallState) {
                    mTargetCallFocus.setState(mPreviousCallState, "resetting call state");
                }
                mCallback.onError(new CallException("failed to switch focus to requested call",
                        CallException.CODE_CALL_CANNOT_BE_SET_TO_ACTIVE));
                return;
            }
            // at this point, we know the FocusManager is able to update successfully
            if (mPendingAction != null) {
                mPendingAction.performAction(); // set the call state
            }
            mCallback.onResult(true); // complete the transaction
        }
    }
+10 −12
Original line number Diff line number Diff line
@@ -32,6 +32,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
@@ -153,10 +154,9 @@ public class ConnectionServiceFocusManager {
        void setCallsManagerListener(CallsManager.CallsManagerListener listener);
    }

    private static final int[] PRIORITY_FOCUS_CALL_STATE = new int[] {
            CallState.ACTIVE, CallState.CONNECTING, CallState.DIALING, CallState.AUDIO_PROCESSING,
            CallState.RINGING
    };
    public static final Set<Integer> PRIORITY_FOCUS_CALL_STATE
            = Set.of(CallState.ACTIVE, CallState.CONNECTING, CallState.DIALING,
            CallState.AUDIO_PROCESSING, CallState.RINGING);

    private static final int MSG_REQUEST_FOCUS = 1;
    private static final int MSG_RELEASE_CONNECTION_FOCUS = 2;
@@ -374,17 +374,15 @@ public class ConnectionServiceFocusManager {
                        && call.isFocusable())
                .collect(Collectors.toList());

        for (int i = 0; i < PRIORITY_FOCUS_CALL_STATE.length; i++) {
        for (CallFocus call : calls) {
                if (call.getState() == PRIORITY_FOCUS_CALL_STATE[i]) {
            if (PRIORITY_FOCUS_CALL_STATE.contains(call.getState())) {
                mCurrentFocusCall = call;
                    Log.d(this, "updateCurrentFocusCall %s", mCurrentFocusCall);
                Log.i(this, "updateCurrentFocusCall %s", mCurrentFocusCall);
                return;
            }
        }
        }

        Log.d(this, "updateCurrentFocusCall = null");
        Log.i(this, "updateCurrentFocusCall = null");
    }

    private void onRequestFocusDone(FocusRequest focusRequest) {
+72 −54
Original line number Diff line number Diff line
@@ -38,14 +38,13 @@ import androidx.annotation.VisibleForTesting;

import com.android.internal.telecom.ICallControl;
import com.android.internal.telecom.ICallEventCallback;
import com.android.server.telecom.voip.AnswerCallTransaction;
import com.android.server.telecom.voip.CallEventCallbackAckTransaction;
import com.android.server.telecom.voip.EndpointChangeTransaction;
import com.android.server.telecom.voip.HoldCallTransaction;
import com.android.server.telecom.voip.EndCallTransaction;
import com.android.server.telecom.voip.HoldActiveCallForNewCallTransaction;
import com.android.server.telecom.voip.MaybeHoldCallForNewCallTransaction;
import com.android.server.telecom.voip.ParallelTransaction;
import com.android.server.telecom.voip.RequestFocusTransaction;
import com.android.server.telecom.voip.RequestNewActiveCallTransaction;
import com.android.server.telecom.voip.SerialTransaction;
import com.android.server.telecom.voip.TransactionManager;
import com.android.server.telecom.voip.VoipCallTransaction;
@@ -247,11 +246,12 @@ public class TransactionalServiceWrapper implements
            if (call != null) {
                switch (action) {
                    case SET_ACTIVE:
                        addTransactionsToManager(createSetActiveTransactions(call), callback);
                        handleCallControlNewCallFocusTransactions(call, SET_ACTIVE,
                                false /* isAnswer */, 0/*VideoState (ignored)*/, callback);
                        break;
                    case ANSWER:
                        addTransactionsToManager(createSetAnswerTransactions(call,
                                (int) objects[0]), callback);
                        handleCallControlNewCallFocusTransactions(call, ANSWER,
                                true /* isAnswer */, (int) objects[0] /*VideoState*/, callback);
                        break;
                    case DISCONNECT:
                        addTransactionsToManager(new EndCallTransaction(mCallsManager,
@@ -278,6 +278,32 @@ public class TransactionalServiceWrapper implements
            }
        }

        // The client is request their VoIP call state go ACTIVE/ANSWERED.
        // 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),
                    new OutcomeReceiver<>() {
                        @Override
                        public void onResult(VoipCallTransactionResult result) {
                            Log.i(TAG, String.format(Locale.US,
                                    "%s: onResult: callId=[%s]", action, call.getId()));
                            if (isAnswer) {
                                call.setVideoState(potentiallyNewVideoState);
                            }
                            callback.send(TELECOM_TRANSACTION_SUCCESS, new Bundle());
                        }

                        @Override
                        public void onError(CallException exception) {
                            Bundle extras = new Bundle();
                            extras.putParcelable(TRANSACTION_EXCEPTION_KEY, exception);
                            callback.send(exception == null ? CallException.CODE_ERROR_UNKNOWN :
                                    exception.getCode(), extras);
                        }
                    });
        }

        @Override
        public void requestCallEndpointChange(CallEndpoint endpoint, ResultReceiver callback) {
            try {
@@ -299,14 +325,12 @@ public class TransactionalServiceWrapper implements
                Call call = mTrackedCalls.get(callId);
                if (call != null) {
                    call.onConnectionEvent(event, extras);
                }
                else{
                } else {
                    Log.i(TAG,
                            "sendEvent: was called but there is no call with id=[%s] cannot be "
                                    + "found. Most likely the call has been disconnected");
                }
            }
            finally {
            } finally {
                Log.endSession();
            }
        }
@@ -348,7 +372,8 @@ public class TransactionalServiceWrapper implements
        try {
            Log.startSession("TSW.oSA");
            Log.d(TAG, String.format(Locale.US, "onSetActive: callId=[%s]", call.getId()));
            handleNewActiveCallCallbacks(call, ON_SET_ACTIVE, 0);
            handleCallEventCallbackNewFocus(call, ON_SET_ACTIVE, false /*isAnswerRequest*/,
                    0 /*VideoState*/);
        } finally {
            Log.endSession();
        }
@@ -358,35 +383,44 @@ public class TransactionalServiceWrapper implements
        try {
            Log.startSession("TSW.oA");
            Log.d(TAG, String.format(Locale.US, "onAnswer: callId=[%s]", call.getId()));
            handleNewActiveCallCallbacks(call, ON_ANSWER, videoState);
            handleCallEventCallbackNewFocus(call, ON_ANSWER, true /*isAnswerRequest*/,
                    videoState /*VideoState*/);
        } finally {
            Log.endSession();
        }
    }

    // need to create multiple transactions for onSetActive and onAnswer which both seek to set
    // the call to active
    private void handleNewActiveCallCallbacks(Call call, String action, int videoState) {
    // handle a CallEventCallback to set a call ACTIVE/ANSWERED. Must get ack from client since the
    // request has come from another source (ex. Android Auto is requesting a call to go active)
    private void handleCallEventCallbackNewFocus(Call call, String action, boolean isAnswerRequest,
            int potentiallyNewVideoState) {
        // save CallsManager state before sending client state changes
        Call foregroundCallBeforeSwap = mCallsManager.getForegroundCall();
        boolean wasActive = foregroundCallBeforeSwap != null && foregroundCallBeforeSwap.isActive();

        // create 3 serial transactions:
        // -- hold active
        // -- set newCall as active
        // -- ack from client
        SerialTransaction serialTransactions = createSetActiveTransactions(call);
        // 3. get ack from client (that the requested call can go active)
        if (isAnswerRequest) {
            serialTransactions.appendTransaction(
                    new CallEventCallbackAckTransaction(mICallEventCallback,
                            action, call.getId(), potentiallyNewVideoState, mLock));
        } else {
            serialTransactions.appendTransaction(
                    new CallEventCallbackAckTransaction(mICallEventCallback,
                        action, call.getId(), videoState, mLock));
                            action, call.getId(), mLock));
        }

        // do CallsManager workload before asking client and
        //   reset CallsManager state if client does NOT ack
        mTransactionManager.addTransaction(serialTransactions, new OutcomeReceiver<>() {
        mTransactionManager.addTransaction(serialTransactions,
                new OutcomeReceiver<>() {
                    @Override
                    public void onResult(VoipCallTransactionResult result) {
                        Log.i(TAG, String.format(Locale.US,
                                "%s: onResult: callId=[%s]", action, call.getId()));
                        if (isAnswerRequest) {
                            call.setVideoState(potentiallyNewVideoState);
                        }
                    }

                    @Override
@@ -553,27 +587,11 @@ public class TransactionalServiceWrapper implements
        // create list for multiple transactions
        List<VoipCallTransaction> transactions = new ArrayList<>();

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

        // add t2. send request to set the current call active
        transactions.add(new RequestFocusTransaction(mCallsManager, call));

        // send off to Transaction Manager to process
        return new SerialTransaction(transactions, mLock);
    }

    private SerialTransaction createSetAnswerTransactions(Call call, int videoState) {
        // create list for multiple transactions
        List<VoipCallTransaction> transactions = new ArrayList<>();

        // add t1. hold potential active call
        transactions.add(new HoldActiveCallForNewCallTransaction(mCallsManager, call));

        // add t2. answer current call
        transactions.add(new AnswerCallTransaction(mCallsManager, call, videoState));

        // send off to Transaction Manager to process
        return new SerialTransaction(transactions, mLock);
    }

+0 −76
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.voip;

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

import com.android.server.telecom.Call;
import com.android.server.telecom.CallState;
import com.android.server.telecom.CallsManager;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;

/**
 * This transaction should be created for new incoming calls that request to go from
 * CallState.Ringing to CallState.Answered.  Before changing the CallState, the focus manager must
 * be updated. Once the focus manager updates, the call state will be set.  If there is an issue
 * answering the call, the transaction will fail.
 */
public class AnswerCallTransaction extends VoipCallTransaction {

    private static final String TAG = AnswerCallTransaction.class.getSimpleName();
    private final CallsManager mCallsManager;
    private final Call mCall;
    private final int mVideoState;

    public AnswerCallTransaction(CallsManager callsManager, Call call, int videoState) {
        super(callsManager.getLock());
        mCallsManager = callsManager;
        mCall = call;
        mVideoState = videoState;
    }

    @Override
    public CompletionStage<VoipCallTransactionResult> processTransaction(Void v) {
        Log.d(TAG, "processTransaction");
        CompletableFuture<VoipCallTransactionResult> future = new CompletableFuture<>();

        mCall.setVideoState(mVideoState);

        mCallsManager.transactionRequestNewFocusCall(mCall, CallState.ANSWERED,
                new OutcomeReceiver<>() {
            @Override
            public void onResult(Boolean result) {
                Log.d(TAG, "processTransaction: onResult");
                future.complete(new VoipCallTransactionResult(
                        VoipCallTransactionResult.RESULT_SUCCEED, null));
            }

            @Override
            public void onError(CallException exception) {
                Log.d(TAG, "processTransaction: onError");
                future.complete(new VoipCallTransactionResult(
                        exception.getCode(), exception.getMessage()));
            }
        });

        return future;
    }
}
 No newline at end of file
+2 −1
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import static android.telecom.CallException.CODE_OPERATION_TIMED_OUT;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.telecom.CallAttributes;
import android.telecom.DisconnectCause;
import android.util.Log;

@@ -44,7 +45,7 @@ public class CallEventCallbackAckTransaction extends VoipCallTransaction {
    private final String mAction;
    private final String mCallId;
    // optional values
    private int mVideoState = 0;
    private int mVideoState = CallAttributes.AUDIO_CALL;
    private DisconnectCause mDisconnectCause = null;

    private final VoipCallTransactionResult TRANSACTION_FAILED = new VoipCallTransactionResult(
Loading