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

Commit 962fb038 authored by Brad Ebinger's avatar Brad Ebinger
Browse files

Moves VerifyCallStateChangeTransaction to use existing timeout

Adds the ability for a VoipCallTransaction to specify a different
timeout from the default and brings VerifyCallStateChangeTransaction
impl to use that timeout instead of redefining a new timeout
again in the subclass.

Bug: 327038818
Test: atest TelecomUnitTests
Change-Id: Ic7ae1ca2892f071a5ab5d38fee46a95f060bbbd8
parent 32260704
Loading
Loading
Loading
Loading
+10 −2
Original line number Diff line number Diff line
@@ -3053,16 +3053,24 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
    public void awaitCallStateChangeAndMaybeDisconnectCall(int targetCallState,
            boolean shouldDisconnectUponTimeout, String callingMethod) {
        TransactionManager tm = TransactionManager.getInstance();
        tm.addTransaction(new VerifyCallStateChangeTransaction(mCallsManager,
                this, targetCallState, shouldDisconnectUponTimeout), new OutcomeReceiver<>() {
        tm.addTransaction(new VerifyCallStateChangeTransaction(mCallsManager.getLock(),
                this, targetCallState), new OutcomeReceiver<>() {
            @Override
            public void onResult(VoipCallTransactionResult result) {
                Log.i(this, "awaitCallStateChangeAndMaybeDisconnectCall: %s: onResult:"
                        + " due to CallException=[%s]", callingMethod, result);
            }

            @Override
            public void onError(CallException e) {
                Log.i(this, "awaitCallStateChangeAndMaybeDisconnectCall: %s: onError"
                        + " due to CallException=[%s]", callingMethod, e);
                if (shouldDisconnectUponTimeout) {
                    mCallsManager.markCallAsDisconnected(Call.this,
                            new DisconnectCause(DisconnectCause.ERROR,
                                    "did not hold in timeout window"));
                    mCallsManager.markCallAsRemoved(Call.this);
                }
            }
        });
    }
+1 −1
Original line number Diff line number Diff line
@@ -125,7 +125,7 @@ public class CallEventCallbackAckTransaction extends VoipCallTransaction {

        try {
            // wait for the client to ack that CallEventCallback
            boolean success = latch.await(VoipCallTransaction.TIMEOUT_LIMIT, TimeUnit.MILLISECONDS);
            boolean success = latch.await(mTransactionTimeoutMs, TimeUnit.MILLISECONDS);
            if (!success) {
                // client send onError and failed to complete transaction
                Log.i(TAG, String.format("CallEventCallbackAckTransaction:"
+45 −61
Original line number Diff line number Diff line
@@ -33,24 +33,11 @@ public class ParallelTransaction extends VoipCallTransaction {
    }

    @Override
    public void start() {
        if (mStats != null) mStats.markStarted();
        // post timeout work
        CompletableFuture<Void> future = new CompletableFuture<>();
        mHandler.postDelayed(() -> future.complete(null), TIMEOUT_LIMIT);
        future.thenApplyAsync((x) -> {
            if (mCompleted.getAndSet(true)) {
                return null;
            }
            if (mCompleteListener != null) {
                mCompleteListener.onTransactionTimeout(mTransactionName);
    public void processTransactions() {
        if (mSubTransactions == null || mSubTransactions.isEmpty()) {
            scheduleTransaction();
            return;
        }
            timeout();
            return null;
        }, new LoggedHandlerExecutor(mHandler, mTransactionName + "@" + hashCode()
                + ".s", mLock));

        if (mSubTransactions != null && mSubTransactions.size() > 0) {
        TransactionManager.TransactionCompleteListener subTransactionListener =
                new TransactionManager.TransactionCompleteListener() {
                    private final AtomicInteger mCount = new AtomicInteger(mSubTransactions.size());
@@ -67,9 +54,9 @@ public class ParallelTransaction extends VoipCallTransaction {
                                                        String.format(
                                                                "sub transaction %s failed",
                                                                transactionName));
                                        finish(mainResult);
                                        mCompleteListener.onTransactionCompleted(mainResult,
                                                mTransactionName);
                                            finish(mainResult);
                                        return null;
                                    }, new LoggedHandlerExecutor(mHandler,
                                            mTransactionName + "@" + hashCode()
@@ -90,9 +77,9 @@ public class ParallelTransaction extends VoipCallTransaction {
                                            VoipCallTransactionResult.RESULT_FAILED,
                                            String.format("sub transaction %s timed out",
                                                    transactionName));
                                    finish(mainResult);
                                    mCompleteListener.onTransactionCompleted(mainResult,
                                            mTransactionName);
                                        finish(mainResult);
                                    return null;
                                }, new LoggedHandlerExecutor(mHandler,
                                        mTransactionName + "@" + hashCode()
@@ -103,8 +90,5 @@ public class ParallelTransaction extends VoipCallTransaction {
            transaction.setCompleteListener(subTransactionListener);
            transaction.start();
        }
        } else {
            scheduleTransaction();
        }
    }
}
+53 −68
Original line number Diff line number Diff line
@@ -37,24 +37,11 @@ public class SerialTransaction extends VoipCallTransaction {
    }

    @Override
    public void start() {
        if (mStats != null) mStats.markStarted();
        // post timeout work
        CompletableFuture<Void> future = new CompletableFuture<>();
        mHandler.postDelayed(() -> future.complete(null), TIMEOUT_LIMIT);
        future.thenApplyAsync((x) -> {
            if (mCompleted.getAndSet(true)) {
                return null;
            }
            if (mCompleteListener != null) {
                mCompleteListener.onTransactionTimeout(mTransactionName);
    public void processTransactions() {
        if (mSubTransactions == null || mSubTransactions.isEmpty()) {
            scheduleTransaction();
            return;
        }
            timeout();
            return null;
        }, new LoggedHandlerExecutor(mHandler, mTransactionName + "@" + hashCode()
                + ".s", mLock));

        if (mSubTransactions != null && mSubTransactions.size() > 0) {
        TransactionManager.TransactionCompleteListener subTransactionListener =
                new TransactionManager.TransactionCompleteListener() {
                    private final AtomicInteger mTransactionIndex = new AtomicInteger(0);
@@ -72,9 +59,9 @@ public class SerialTransaction extends VoipCallTransaction {
                                                        String.format(
                                                                "sub transaction %s failed",
                                                                transactionName));
                                        finish(mainResult);
                                        mCompleteListener.onTransactionCompleted(mainResult,
                                                mTransactionName);
                                            finish(mainResult);
                                        return null;
                                    }, new LoggedHandlerExecutor(mHandler,
                                            mTransactionName + "@" + hashCode()
@@ -102,9 +89,9 @@ public class SerialTransaction extends VoipCallTransaction {
                                            VoipCallTransactionResult.RESULT_FAILED,
                                            String.format("sub transaction %s timed out",
                                                    transactionName));
                                    finish(mainResult);
                                    mCompleteListener.onTransactionCompleted(mainResult,
                                            mTransactionName);
                                        finish(mainResult);
                                    return null;
                                }, new LoggedHandlerExecutor(mHandler,
                                        mTransactionName + "@" + hashCode()
@@ -114,9 +101,7 @@ public class SerialTransaction extends VoipCallTransaction {
        VoipCallTransaction transaction = mSubTransactions.get(0);
        transaction.setCompleteListener(subTransactionListener);
        transaction.start();
        } else {
            scheduleTransaction();
        }

    }

    public void handleTransactionFailure() {}
+16 −70
Original line number Diff line number Diff line
@@ -18,14 +18,12 @@ package com.android.server.telecom.voip;

import com.android.internal.annotations.VisibleForTesting;
import com.android.server.telecom.Call;
import com.android.server.telecom.CallsManager;
import com.android.server.telecom.TelecomSystem;

import android.telecom.DisconnectCause;
import android.telecom.Log;

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

/**
 * VerifyCallStateChangeTransaction is a transaction that verifies a CallState change and has
@@ -35,37 +33,30 @@ import java.util.concurrent.TimeUnit;
 */
public class VerifyCallStateChangeTransaction extends VoipCallTransaction {
    private static final String TAG = VerifyCallStateChangeTransaction.class.getSimpleName();
    public static final int FAILURE_CODE = 0;
    public static final int SUCCESS_CODE = 1;
    public static final int TIMEOUT_SECONDS = 2;
    private static final long CALL_STATE_TIMEOUT_MILLISECONDS = 2000L;
    private final Call mCall;
    private final CallsManager mCallsManager;
    private final int mTargetCallState;
    private final boolean mShouldDisconnectUponFailure;
    private final CompletableFuture<Integer> mCallStateOrTimeoutResult = new CompletableFuture<>();
    private final CompletableFuture<VoipCallTransactionResult> mTransactionResult =
            new CompletableFuture<>();

    @VisibleForTesting
    public Call.CallStateListener mCallStateListenerImpl = new Call.CallStateListener() {
    private final Call.CallStateListener mCallStateListenerImpl = new Call.CallStateListener() {
        @Override
        public void onCallStateChanged(int newCallState) {
            Log.d(TAG, "newState=[%d], expectedState=[%d]", newCallState, mTargetCallState);
            if (newCallState == mTargetCallState) {
                mCallStateOrTimeoutResult.complete(SUCCESS_CODE);
                mTransactionResult.complete(new VoipCallTransactionResult(
                        VoipCallTransactionResult.RESULT_SUCCEED, TAG));
            }
            // NOTE:: keep listening to the call state until the timeout is reached. It's possible
            // another call state is reached in between...
        }
    };

    public VerifyCallStateChangeTransaction(CallsManager callsManager, Call call,
            int targetCallState, boolean shouldDisconnectUponFailure) {
        super(callsManager.getLock());
        mCallsManager = callsManager;
    public VerifyCallStateChangeTransaction(TelecomSystem.SyncRoot lock,  Call call,
            int targetCallState) {
        super(lock, CALL_STATE_TIMEOUT_MILLISECONDS);
        mCall = call;
        mTargetCallState = targetCallState;
        mShouldDisconnectUponFailure = shouldDisconnectUponFailure;
    }

    @Override
@@ -73,66 +64,21 @@ public class VerifyCallStateChangeTransaction extends VoipCallTransaction {
        Log.d(TAG, "processTransaction:");
        // It's possible the Call is already in the expected call state
        if (isNewCallStateTargetCallState()) {
            mTransactionResult.complete(
                    new VoipCallTransactionResult(VoipCallTransactionResult.RESULT_SUCCEED,
                            TAG));
            mTransactionResult.complete(new VoipCallTransactionResult(
                    VoipCallTransactionResult.RESULT_SUCCEED, TAG));
            return mTransactionResult;
        }
        initCallStateListenerOnTimeout();
        // At this point, the mCallStateOrTimeoutResult has been completed. There are 2 scenarios:
        // (1) newCallState == targetCallState --> the transaction is successful
        // (2) timeout is reached --> evaluate the current call state and complete the t accordingly
        // also need to do cleanup for the transaction
        evaluateCallStateUponChangeOrTimeout();

        return mTransactionResult;
    }

    private boolean isNewCallStateTargetCallState() {
        return mCall.getState() == mTargetCallState;
    }

    private void initCallStateListenerOnTimeout() {
        mCall.addCallStateListener(mCallStateListenerImpl);
        mCallStateOrTimeoutResult.completeOnTimeout(FAILURE_CODE, TIMEOUT_SECONDS,
                TimeUnit.SECONDS);
        return mTransactionResult;
    }

    private void evaluateCallStateUponChangeOrTimeout() {
        mCallStateOrTimeoutResult.thenAcceptAsync((result) -> {
            Log.i(TAG, "processTransaction: thenAcceptAsync: result=[%s]", result);
    @Override
    public void finishTransaction() {
        mCall.removeCallStateListener(mCallStateListenerImpl);
            if (isNewCallStateTargetCallState()) {
                mTransactionResult.complete(
                        new VoipCallTransactionResult(VoipCallTransactionResult.RESULT_SUCCEED,
                                TAG));
            } else {
                maybeDisconnectCall();
                mTransactionResult.complete(
                        new VoipCallTransactionResult(VoipCallTransactionResult.RESULT_FAILED,
                                TAG));
            }
        }).exceptionally(exception -> {
            Log.i(TAG, "hit exception=[%s] while completing future", exception);
            mTransactionResult.complete(
                    new VoipCallTransactionResult(VoipCallTransactionResult.RESULT_FAILED,
                            TAG));
            return null;
        });
    }

    private void maybeDisconnectCall() {
        if (mShouldDisconnectUponFailure) {
            mCallsManager.markCallAsDisconnected(mCall,
                    new DisconnectCause(DisconnectCause.ERROR,
                            "did not hold in timeout window"));
            mCallsManager.markCallAsRemoved(mCall);
        }
    }

    @VisibleForTesting
    public CompletableFuture<Integer> getCallStateOrTimeoutResult() {
        return mCallStateOrTimeoutResult;
    private boolean isNewCallStateTargetCallState() {
        return mCall.getState() == mTargetCallState;
    }

    @VisibleForTesting
Loading