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

Commit 7c364909 authored by Pranav Madapurmath's avatar Pranav Madapurmath Committed by Android (Google) Code Review
Browse files

Merge "DSDA: Call sequencing support implementation" into main

parents 6daa8260 a4746e53
Loading
Loading
Loading
Loading
+100 −39
Original line number Diff line number Diff line
@@ -80,6 +80,7 @@ import com.android.server.telecom.flags.FeatureFlags;
import com.android.server.telecom.stats.CallFailureCause;
import com.android.server.telecom.stats.CallStateChangedAtomWriter;
import com.android.server.telecom.ui.ToastFactory;
import com.android.server.telecom.callsequencing.CallTransaction;
import com.android.server.telecom.callsequencing.TransactionManager;
import com.android.server.telecom.callsequencing.VerifyCallStateChangeTransaction;
import com.android.server.telecom.callsequencing.CallTransactionResult;
@@ -2832,20 +2833,20 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
    }

    @VisibleForTesting
    public void disconnect() {
        disconnect(0);
    public CompletableFuture<Boolean> disconnect() {
        return disconnect(0);
    }

    public void disconnect(String reason) {
        disconnect(0, reason);
    public CompletableFuture<Boolean> disconnect(String reason) {
        return disconnect(0, reason);
    }

    /**
     * Attempts to disconnect the call through the connection service.
     */
    @VisibleForTesting
    public void disconnect(long disconnectionTimeout) {
        disconnect(disconnectionTimeout, "internal" /** reason */);
    public CompletableFuture<Boolean> disconnect(long disconnectionTimeout) {
        return disconnect(disconnectionTimeout, "internal" /* reason */);
    }

    /**
@@ -2855,16 +2856,24 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
     *               as TelecomManager.
     */
    @VisibleForTesting
    public void disconnect(long disconnectionTimeout, String reason) {
    public CompletableFuture<Boolean> disconnect(long disconnectionTimeout,
            String reason) {
        Log.addEvent(this, LogUtils.Events.REQUEST_DISCONNECT, reason);

        // Track that the call is now locally disconnecting.
        setLocallyDisconnecting(true);
        maybeSetCallAsDisconnectingChild();

        CompletableFuture<Boolean> disconnectFutureHandler =
                CompletableFuture.completedFuture(false);
        if (mState == CallState.NEW || mState == CallState.SELECT_PHONE_ACCOUNT ||
                mState == CallState.CONNECTING) {
            Log.i(this, "disconnect: Aborting call %s", getId());
            if (mFlags.enableCallSequencing()) {
                disconnectFutureHandler = awaitCallStateChangeAndMaybeDisconnectCall(
                        false /* shouldDisconnectUponTimeout */, "disconnect",
                        CallState.DISCONNECTED, CallState.ABORTED);
            }
            abort(disconnectionTimeout);
        } else if (mState != CallState.ABORTED && mState != CallState.DISCONNECTED) {
            if (mState == CallState.AUDIO_PROCESSING && !hasGoneActiveBefore()) {
@@ -2876,7 +2885,8 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
                setOverrideDisconnectCauseCode(new DisconnectCause(DisconnectCause.MISSED));
            }
            if (mTransactionalService != null) {
                mTransactionalService.onDisconnect(this, getDisconnectCause());
                disconnectFutureHandler = mTransactionalService.onDisconnect(this,
                        getDisconnectCause());
                Log.i(this, "Send Disconnect to transactional service for call");
            } else if (mConnectionService == null) {
                Log.e(this, new Exception(), "disconnect() request on a call without a"
@@ -2887,9 +2897,15 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
                // confirms that the call was actually disconnected. Only then is the
                // association between call and connection service severed, see
                // {@link CallsManager#markCallAsDisconnected}.
                if (mFlags.enableCallSequencing()) {
                    disconnectFutureHandler = awaitCallStateChangeAndMaybeDisconnectCall(
                            false /* shouldDisconnectUponTimeout */, "disconnect",
                            CallState.DISCONNECTED);
                }
                mConnectionService.disconnect(this);
            }
        }
        return disconnectFutureHandler;
    }

    void abort(long disconnectionTimeout) {
@@ -2932,29 +2948,35 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
     * @param videoState The video state in which to answer the call.
     */
    @VisibleForTesting
    public void answer(int videoState) {
    public CompletableFuture<Boolean> answer(int videoState) {
        CompletableFuture<Boolean> answerCallFuture = CompletableFuture.completedFuture(false);
        // Check to verify that the call is still in the ringing state. A call can change states
        // between the time the user hits 'answer' and Telecom receives the command.
        if (isRinging("answer")) {
            Log.addEvent(this, LogUtils.Events.REQUEST_ACCEPT);
            if (!isVideoCallingSupportedByPhoneAccount() && VideoProfile.isVideo(videoState)) {
                // Video calling is not supported, yet the InCallService is attempting to answer as
                // video.  We will simply answer as audio-only.
                videoState = VideoProfile.STATE_AUDIO_ONLY;
            }
            // At this point, we are asking the connection service to answer but we don't assume
            // that it will work. Instead, we wait until confirmation from the connectino service
            // that it will work. Instead, we wait until confirmation from the connection service
            // that the call is in a non-STATE_RINGING state before changing the UI. See
            // {@link ConnectionServiceAdapter#setActive} and other set* methods.
            if (mConnectionService != null) {
                if (mFlags.enableCallSequencing()) {
                    answerCallFuture = awaitCallStateChangeAndMaybeDisconnectCall(
                            false /* shouldDisconnectUponTimeout */, "answer", CallState.ACTIVE);
                }
                mConnectionService.answer(this, videoState);
            } else if (mTransactionalService != null) {
                mTransactionalService.onAnswer(this, videoState);
                return mTransactionalService.onAnswer(this, videoState);
            } else {
                Log.e(this, new NullPointerException(),
                        "answer call failed due to null CS callId=%s", getId());
            }
            Log.addEvent(this, LogUtils.Events.REQUEST_ACCEPT);
        }
        return answerCallFuture;
    }

    /**
@@ -3034,74 +3056,101 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
     *               if the reject is initiated from an API such as TelecomManager.
     */
    @VisibleForTesting
    public void reject(boolean rejectWithMessage, String textMessage, String reason) {
    public CompletableFuture<Boolean> reject(boolean rejectWithMessage,
            String textMessage, String reason) {
        CompletableFuture<Boolean> rejectFutureHandler = CompletableFuture.completedFuture(false);
        if (mState == CallState.SIMULATED_RINGING) {
            Log.addEvent(this, LogUtils.Events.REQUEST_REJECT, reason);
            // This handles the case where the user manually rejects a call that's in simulated
            // ringing. Since the call is already active on the connectionservice side, we want to
            // hangup, not reject.
            setOverrideDisconnectCauseCode(new DisconnectCause(DisconnectCause.REJECTED));
            if (mTransactionalService != null) {
                mTransactionalService.onDisconnect(this,
                return mTransactionalService.onDisconnect(this,
                        new DisconnectCause(DisconnectCause.REJECTED));
            } else if (mConnectionService != null) {
                if (mFlags.enableCallSequencing()) {
                    rejectFutureHandler = awaitCallStateChangeAndMaybeDisconnectCall(
                            false /* shouldDisconnectUponTimeout */, "reject",
                            CallState.DISCONNECTED);
                }
                mConnectionService.disconnect(this);
                return rejectFutureHandler;
            } else {
                Log.e(this, new NullPointerException(),
                        "reject call failed due to null CS callId=%s", getId());
            }
            Log.addEvent(this, LogUtils.Events.REQUEST_REJECT, reason);
        } else if (isRinging("reject") || isAnswered("reject")) {
            Log.addEvent(this, LogUtils.Events.REQUEST_REJECT, reason);
            // Ensure video state history tracks video state at time of rejection.
            mVideoStateHistory |= mVideoState;

            if (mTransactionalService != null) {
                mTransactionalService.onDisconnect(this,
                return mTransactionalService.onDisconnect(this,
                        new DisconnectCause(DisconnectCause.REJECTED));
            } else if (mConnectionService != null) {
                if (mFlags.enableCallSequencing()) {
                    rejectFutureHandler = awaitCallStateChangeAndMaybeDisconnectCall(
                            false /* shouldDisconnectUponTimeout */, "reject",
                            CallState.DISCONNECTED);
                }
                mConnectionService.reject(this, rejectWithMessage, textMessage);
                return rejectFutureHandler;
            } else {
                Log.e(this, new NullPointerException(),
                        "reject call failed due to null CS callId=%s", getId());
            }
            Log.addEvent(this, LogUtils.Events.REQUEST_REJECT, reason);
        }
        return rejectFutureHandler;
    }

    /**
     * Reject this Telecom call with the user-indicated reason.
     * @param rejectReason The user-indicated reason fore rejecting the call.
     */
    public void reject(@android.telecom.Call.RejectReason int rejectReason) {
    public CompletableFuture<Boolean> reject(@android.telecom.Call.RejectReason int rejectReason) {
        CompletableFuture<Boolean> rejectFutureHandler = CompletableFuture.completedFuture(false);
        if (mState == CallState.SIMULATED_RINGING) {
            Log.addEvent(this, LogUtils.Events.REQUEST_REJECT);
            // This handles the case where the user manually rejects a call that's in simulated
            // ringing. Since the call is already active on the connectionservice side, we want to
            // hangup, not reject.
            // Since its simulated reason we can't pass along the reject reason.
            setOverrideDisconnectCauseCode(new DisconnectCause(DisconnectCause.REJECTED));
            if (mTransactionalService != null) {
                mTransactionalService.onDisconnect(this,
                return mTransactionalService.onDisconnect(this,
                        new DisconnectCause(DisconnectCause.REJECTED));
            } else if (mConnectionService != null) {
                if (mFlags.enableCallSequencing()) {
                    rejectFutureHandler = awaitCallStateChangeAndMaybeDisconnectCall(
                            false /* shouldDisconnectUponTimeout */, "reject",
                            CallState.DISCONNECTED);
                }
                mConnectionService.disconnect(this);
            } else {
                Log.e(this, new NullPointerException(),
                        "reject call failed due to null CS callId=%s", getId());
            }
            Log.addEvent(this, LogUtils.Events.REQUEST_REJECT);
        } else if (isRinging("reject") || isAnswered("reject")) {
            Log.addEvent(this, LogUtils.Events.REQUEST_REJECT, rejectReason);
            // Ensure video state history tracks video state at time of rejection.
            mVideoStateHistory |= mVideoState;
            if (mTransactionalService != null) {
                mTransactionalService.onDisconnect(this,
                return mTransactionalService.onDisconnect(this,
                        new DisconnectCause(DisconnectCause.REJECTED));
            } else if (mConnectionService != null) {
                if (mFlags.enableCallSequencing()) {
                    rejectFutureHandler = awaitCallStateChangeAndMaybeDisconnectCall(
                            false /* shouldDisconnectUponTimeout */, "reject",
                            CallState.DISCONNECTED);
                }
                mConnectionService.rejectWithReason(this, rejectReason);
            } else {
                Log.e(this, new NullPointerException(),
                        "reject call failed due to null CS callId=%s", getId());
            }
            Log.addEvent(this, LogUtils.Events.REQUEST_REJECT, rejectReason);
        }
        return rejectFutureHandler;
    }

    /**
@@ -3151,41 +3200,46 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
     * Puts the call on hold if it is currently active.
     */
    @VisibleForTesting
    public void hold() {
        hold(null /* reason */);
    public CompletableFuture<Boolean> hold() {
        return hold(null /* reason */);
    }

    /**
     * This method requests the ConnectionService or TransactionalService hosting the call to put
     * the call on hold
     */
    public void hold(String reason) {
    public CompletableFuture<Boolean> hold(String reason) {
        CompletableFuture<Boolean> holdFutureHandler = CompletableFuture.completedFuture(false);
        if (mState == CallState.ACTIVE) {
            Log.addEvent(this, LogUtils.Events.REQUEST_HOLD, reason);
            if (mTransactionalService != null) {
                mTransactionalService.onSetInactive(this);
                return mTransactionalService.onSetInactive(this);
            } else if (mConnectionService != null) {
                if (mFlags.transactionalCsVerifier()) {
                    awaitCallStateChangeAndMaybeDisconnectCall(CallState.ON_HOLD, isSelfManaged(),
                            "hold");
                if (mFlags.transactionalCsVerifier() || mFlags.enableCallSequencing()) {
                    holdFutureHandler = awaitCallStateChangeAndMaybeDisconnectCall(isSelfManaged(),
                            "hold", CallState.ON_HOLD);
                }
                mConnectionService.hold(this);
                return holdFutureHandler;
            } else {
                Log.e(this, new NullPointerException(),
                        "hold call failed due to null CS callId=%s", getId());
            }
            Log.addEvent(this, LogUtils.Events.REQUEST_HOLD, reason);
        }
        return holdFutureHandler;
    }

    /**
     * helper that can be used for any callback that requests a call state change and wants to
     * verify the change
     */
    public void awaitCallStateChangeAndMaybeDisconnectCall(int targetCallState,
            boolean shouldDisconnectUponTimeout, String callingMethod) {
    public CompletableFuture<Boolean> awaitCallStateChangeAndMaybeDisconnectCall(
            boolean shouldDisconnectUponTimeout, String callingMethod, int... targetCallStates) {
        TransactionManager tm = TransactionManager.getInstance();
        tm.addTransaction(new VerifyCallStateChangeTransaction(mCallsManager.getLock(),
                this, targetCallState), new OutcomeReceiver<>() {
        CallTransaction callTransaction = new VerifyCallStateChangeTransaction(
                mCallsManager.getLock(), this, targetCallStates);
        return tm.addTransaction(callTransaction,
                new OutcomeReceiver<>() {
            @Override
            public void onResult(CallTransactionResult result) {
                Log.i(this, "awaitCallStateChangeAndMaybeDisconnectCall: %s: onResult:"
@@ -3210,22 +3264,29 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
     * Releases the call from hold if it is currently active.
     */
    @VisibleForTesting
    public void unhold() {
        unhold(null /* reason */);
    public CompletableFuture<Boolean> unhold() {
        return unhold(null /* reason */);
    }

    public void unhold(String reason) {
    public CompletableFuture<Boolean> unhold(String reason) {
        CompletableFuture<Boolean> unholdFutureHandler = CompletableFuture.completedFuture(false);
        if (mState == CallState.ON_HOLD) {
            Log.addEvent(this, LogUtils.Events.REQUEST_UNHOLD, reason);
            if (mTransactionalService != null){
                mTransactionalService.onSetActive(this);
                return mTransactionalService.onSetActive(this);
            } else if (mConnectionService != null){
                if (mFlags.enableCallSequencing()) {
                    unholdFutureHandler = awaitCallStateChangeAndMaybeDisconnectCall(
                            false /* shouldDisconnectUponTimeout */, "unhold", CallState.ACTIVE);
                }
                mConnectionService.unhold(this);
                return unholdFutureHandler;
            } else {
                Log.e(this, new NullPointerException(),
                        "unhold call failed due to null CS callId=%s", getId());
            }
            Log.addEvent(this, LogUtils.Events.REQUEST_UNHOLD, reason);
        }
        return unholdFutureHandler;
    }

    /** Checks if this is a live call or not. */
+203 −105

File changed.

Preview size limit exceeded, changes collapsed.

+54 −51
Original line number Diff line number Diff line
@@ -52,6 +52,8 @@ import android.net.Uri;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.OutcomeReceiver;
import android.os.ParcelFileDescriptor;
import android.os.Process;
@@ -81,6 +83,7 @@ import com.android.internal.telecom.ICallControl;
import com.android.internal.telecom.ICallEventCallback;
import com.android.internal.telecom.ITelecomService;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.telecom.callsequencing.voip.OutgoingCallTransactionSequencing;
import com.android.server.telecom.components.UserCallIntentProcessorFactory;
import com.android.server.telecom.flags.FeatureFlags;
import com.android.server.telecom.metrics.ApiStats;
@@ -102,6 +105,7 @@ import java.util.Locale;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;

// TODO: Needed for move to system service: import com.android.internal.R;

@@ -193,24 +197,18 @@ public class TelecomServiceImpl {
                extras.putInt(CallAttributes.CALLER_UID_KEY, Binder.getCallingUid());
                extras.putInt(CallAttributes.CALLER_PID_KEY, Binder.getCallingPid());

                CallTransaction transaction = null;
                // create transaction based on the call direction
                switch (callAttributes.getDirection()) {
                    case DIRECTION_OUTGOING:
                        transaction = new OutgoingCallTransaction(callId, mContext, callAttributes,
                                mCallsManager, extras, mFeatureFlags);
                        break;
                    case DIRECTION_INCOMING:
                        transaction = new IncomingCallTransaction(callId, callAttributes,
                                mCallsManager, extras, mFeatureFlags);
                        break;
                    default:
                        throw new IllegalArgumentException(String.format("Invalid Call Direction. "
                                        + "Was [%d] but should be within [%d,%d]",
                                callAttributes.getDirection(), DIRECTION_INCOMING,
                                DIRECTION_OUTGOING));

                CompletableFuture<CallTransaction> transactionFuture;
                long token = Binder.clearCallingIdentity();
                try {
                    transactionFuture = mCallsManager.createTransactionalCall(callId,
                            callAttributes, extras, callingPackage);
                } finally {
                    Binder.restoreCallingIdentity(token);
                }

                transactionFuture.thenCompose((transaction) -> {
                    if (transaction != null) {
                        mTransactionManager.addTransaction(transaction, new OutcomeReceiver<>() {
                            @Override
                            public void onResult(CallTransactionResult result) {
@@ -220,7 +218,8 @@ public class TelecomServiceImpl {
                                if (call == null || !call.getId().equals(callId)) {
                                    Log.i(TAG, "addCall: onResult: call is null or id mismatch");
                                    onAddCallControl(callId, callEventCallback, null,
                                    new CallException(ADD_CALL_ERR_MSG, CODE_ERROR_UNKNOWN));
                                            new CallException(ADD_CALL_ERR_MSG,
                                                    CODE_ERROR_UNKNOWN));
                                    return;
                                }

@@ -242,7 +241,8 @@ public class TelecomServiceImpl {
                                }

                                // finally, send objects back to the client
                        onAddCallControl(callId, callEventCallback, clientCallControl, null);
                                onAddCallControl(callId, callEventCallback, clientCallControl,
                                        null);
                            }

                            @Override
@@ -251,7 +251,10 @@ public class TelecomServiceImpl {
                                onAddCallControl(callId, callEventCallback, null, exception);
                            }
                        });
                    }
                    event.setResult(ApiStats.RESULT_NORMAL);
                    return CompletableFuture.completedFuture(transaction);
                });
            } finally {
                logEvent(event);
                Log.endSession();
+4 −2
Original line number Diff line number Diff line
@@ -401,11 +401,12 @@ public class TransactionalServiceWrapper implements
        return onSetActiveFuture;
    }

    public void onAnswer(Call call, int videoState) {
    public CompletableFuture<Boolean> onAnswer(Call call, int videoState) {
        CompletableFuture<Boolean> onAnswerFuture;
        try {
            Log.startSession("TSW.oA");
            Log.d(TAG, String.format(Locale.US, "onAnswer: callId=[%s]", call.getId()));
            mCallSequencingAdapter.onSetAnswered(call, videoState,
            onAnswerFuture = mCallSequencingAdapter.onSetAnswered(call, videoState,
                    new CallEventCallbackAckTransaction(mICallEventCallback,
                            ON_ANSWER, call.getId(), videoState, mLock),
                    result -> Log.i(TAG, String.format(Locale.US,
@@ -414,6 +415,7 @@ public class TransactionalServiceWrapper implements
        } finally {
            Log.endSession();
        }
        return onAnswerFuture;
    }

    public CompletableFuture<Boolean> onSetInactive(Call call) {
+777 −31

File changed.

Preview size limit exceeded, changes collapsed.

Loading