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

Commit 85fdd1ae authored by Brad Ebinger's avatar Brad Ebinger Committed by Android (Google) Code Review
Browse files

Merge "Cache call removal future to enable canceling it when we retry" into main

parents fc86020f 230735ed
Loading
Loading
Loading
Loading
+12 −1
Original line number Diff line number Diff line
@@ -15,3 +15,14 @@ flag {
  description: "cache call audio callbacks if the service is not available and execute when set"
  bug: "321369729"
}

# OWNER = breadley TARGET=24Q3
flag {
  name: "cancel_removal_on_emergency_redial"
  namespace: "telecom"
  description: "When redialing an emergency call on another connection service, ensure any pending removal operation is cancelled"
  bug: "341157874"
  metadata {
      purpose: PURPOSE_BUGFIX
    }
}
+64 −14
Original line number Diff line number Diff line
@@ -826,7 +826,22 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
     * disconnect message via {@link CallDiagnostics#onCallDisconnected(ImsReasonInfo)} or
     * {@link CallDiagnostics#onCallDisconnected(int, int)}.
     */
    private CompletableFuture<Boolean> mDisconnectFuture;
    private CompletableFuture<Boolean> mDiagnosticCompleteFuture;

    /**
     * {@link CompletableFuture} used to perform disconnect operations after
     * {@link #mDiagnosticCompleteFuture} has completed.
     */
    private CompletableFuture<Void> mDisconnectFuture;

    /**
     * {@link CompletableFuture} used to perform call removal operations after the
     * {@link #mDisconnectFuture} has completed.
     * <p>
     * Note: It is possible for this future to be cancelled in the case that an internal operation
     * will be handling clean up. (See {@link #setState}.)
     */
    private CompletableFuture<Void> mRemovalFuture;

    /**
     * {@link CompletableFuture} used to delay audio routing change for a ringing call until the
@@ -1316,7 +1331,7 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
                        message, null));
            }

            mDisconnectFuture.complete(true);
            mDiagnosticCompleteFuture.complete(true);
        } else {
            Log.w(this, "handleOverrideDisconnectMessage; callid=%s - got override when unbound",
                    getId());
@@ -1338,6 +1353,12 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,

            if (newState == CallState.DISCONNECTED && shouldContinueProcessingAfterDisconnect()) {
                Log.w(this, "continuing processing disconnected call with another service");
                if (mFlags.cancelRemovalOnEmergencyRedial() && isDisconnectHandledViaFuture()
                        && isRemovalPending()) {
                    Log.i(this, "cancelling removal future in favor of "
                            + "CreateConnectionProcessor handling removal");
                    mRemovalFuture.cancel(true);
                }
                mCreateConnectionProcessor.continueProcessingIfPossible(this, mDisconnectCause);
                return false;
            } else if (newState == CallState.ANSWERED && mState == CallState.ACTIVE) {
@@ -4758,17 +4779,17 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
     * @param timeoutMillis Timeout we use for waiting for the response.
     * @return the {@link CompletableFuture}.
     */
    public CompletableFuture<Boolean> initializeDisconnectFuture(long timeoutMillis) {
        if (mDisconnectFuture == null) {
            mDisconnectFuture = new CompletableFuture<Boolean>()
    public CompletableFuture<Boolean> initializeDiagnosticCompleteFuture(long timeoutMillis) {
        if (mDiagnosticCompleteFuture == null) {
            mDiagnosticCompleteFuture = new CompletableFuture<Boolean>()
                    .completeOnTimeout(false, timeoutMillis, TimeUnit.MILLISECONDS);
            // After all the chained stuff we will report where the CDS timed out.
            mDisconnectFuture.thenRunAsync(() -> {
            mDiagnosticCompleteFuture.thenRunAsync(() -> {
                if (!mReceivedCallDiagnosticPostCallResponse) {
                    Log.addEvent(this, LogUtils.Events.CALL_DIAGNOSTIC_SERVICE_TIMEOUT);
                }
                // Clear the future as a final step.
                mDisconnectFuture = null;
                mDiagnosticCompleteFuture = null;
                },
                new LoggedHandlerExecutor(mHandler, "C.iDF", mLock))
                    .exceptionally((throwable) -> {
@@ -4776,14 +4797,14 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
                        return null;
                    });
        }
        return mDisconnectFuture;
        return mDiagnosticCompleteFuture;
    }

    /**
     * @return the disconnect future, if initialized.  Used for chaining operations after creation.
     */
    public CompletableFuture<Boolean> getDisconnectFuture() {
        return mDisconnectFuture;
    public CompletableFuture<Boolean> getDiagnosticCompleteFuture() {
        return mDiagnosticCompleteFuture;
    }

    /**
@@ -4791,7 +4812,7 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
     * if this is handled immediately.
     */
    public boolean isDisconnectHandledViaFuture() {
        return mDisconnectFuture != null;
        return mDiagnosticCompleteFuture != null;
    }

    /**
@@ -4799,10 +4820,39 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
     * {@code cleanupStuckCalls} request.
     */
    public void cleanup() {
        if (mDisconnectFuture != null) {
            mDisconnectFuture.complete(false);
            mDisconnectFuture = null;
        if (mDiagnosticCompleteFuture != null) {
            mDiagnosticCompleteFuture.complete(false);
            mDiagnosticCompleteFuture = null;
        }
    }

    /**
     * Set the pending future to use when the call is disconnected.
     */
    public void setDisconnectFuture(CompletableFuture<Void> future) {
        mDisconnectFuture = future;
    }

    /**
     * @return The future that will be executed when the call is disconnected.
     */
    public CompletableFuture<Void> getDisconnectFuture() {
        return mDisconnectFuture;
    }

    /**
     * Set the future that will be used when call removal is taking place.
     */
    public void setRemovalFuture(CompletableFuture<Void> future) {
        mRemovalFuture = future;
    }

    /**
     * @return {@code true} if there is a pending removal operation that hasn't taken place yet, or
     * {@code false} if there is no removal pending.
     */
    public boolean isRemovalPending() {
        return mRemovalFuture != null && !mRemovalFuture.isDone();
    }

    /**
+52 −16
Original line number Diff line number Diff line
@@ -4012,20 +4012,21 @@ public class CallsManager extends Call.ListenerBase
            Log.addEvent(call, LogUtils.Events.SET_DISCONNECTED_ORIG, disconnectCause);

            // Setup the future with a timeout so that the CDS is time boxed.
            CompletableFuture<Boolean> future = call.initializeDisconnectFuture(
            CompletableFuture<Boolean> future = call.initializeDiagnosticCompleteFuture(
                    mTimeoutsAdapter.getCallDiagnosticServiceTimeoutMillis(
                            mContext.getContentResolver()));

            // Post the disconnection updates to the future for completion once the CDS returns
            // with it's overridden disconnect message.
            future.thenRunAsync(() -> {
            CompletableFuture<Void> disconnectFuture = future.thenRunAsync(() -> {
                call.setDisconnectCause(disconnectCause);
                setCallState(call, CallState.DISCONNECTED, "disconnected set explicitly");
            }, new LoggedHandlerExecutor(mHandler, "CM.mCAD", mLock))
                    .exceptionally((throwable) -> {
            }, new LoggedHandlerExecutor(mHandler, "CM.mCAD", mLock));
            disconnectFuture.exceptionally((throwable) -> {
                Log.e(TAG, throwable, "Error while executing disconnect future.");
                return null;
            });
            call.setDisconnectFuture(disconnectFuture);
        } else {
            // No CallDiagnosticService, or it doesn't handle this call, so just do this
            // synchronously as always.
@@ -4045,25 +4046,60 @@ public class CallsManager extends Call.ListenerBase
    public void markCallAsRemoved(Call call) {
        if (call.isDisconnectHandledViaFuture()) {
            Log.i(this, "markCallAsRemoved; callid=%s, postingToFuture.", call.getId());
            // A future is being used due to a CallDiagnosticService handling the call.  We will
            // chain the removal operation to the end of any outstanding disconnect work.
            call.getDisconnectFuture().thenRunAsync(() -> {
            configureRemovalFuture(call);
        } else {
            Log.i(this, "markCallAsRemoved; callid=%s, immediate.", call.getId());
            performRemoval(call);
            }, new LoggedHandlerExecutor(mHandler, "CM.mCAR", mLock))
        }
    }

    /**
     * Configure the removal as a dependent stage after the disconnect future completes, which could
     * be cancelled as part of {@link Call#setState(int, String)} when need to retry dial on another
     * ConnectionService.
     * <p>
     * We can not remove the call yet, we need to wait for the DisconnectCause to be processed and
     * potentially re-written via the {@link android.telecom.CallDiagnosticService} first.
     *
     * @param call The call to configure the removal future for.
     */
    private void configureRemovalFuture(Call call) {
        if (!mFeatureFlags.cancelRemovalOnEmergencyRedial()) {
            call.getDiagnosticCompleteFuture().thenRunAsync(() -> performRemoval(call),
                            new LoggedHandlerExecutor(mHandler, "CM.cRF-O", mLock))
                    .exceptionally((throwable) -> {
                        Log.e(TAG, throwable, "Error while executing disconnect future");
                        return null;
                    });

        } else {
            Log.i(this, "markCallAsRemoved; callid=%s, immediate.", call.getId());
            performRemoval(call);
            // A future is being used due to a CallDiagnosticService handling the call.  We will
            // chain the removal operation to the end of any outstanding disconnect work.
            CompletableFuture<Void> removalFuture;
            if (call.getDisconnectFuture() == null) {
                // Unexpected - can not get the disconnect future, attach to the diagnostic complete
                // future in this case.
                removalFuture = call.getDiagnosticCompleteFuture().thenRun(() ->
                        Log.w(this, "configureRemovalFuture: remove called without disconnecting"
                                + " first."));
            } else {
                removalFuture = call.getDisconnectFuture();
            }
            removalFuture = removalFuture.thenRunAsync(() -> performRemoval(call),
                    new LoggedHandlerExecutor(mHandler, "CM.cRF-N", mLock));
            removalFuture.exceptionally((throwable) -> {
                Log.e(TAG, throwable, "Error while executing disconnect future");
                return null;
            });
            // Cache the future to remove the call initiated by the ConnectionService in case we
            // need to cancel it in favor of removing the call internally as part of creating a
            // new connection (CreateConnectionProcessor#continueProcessingIfPossible)
            call.setRemovalFuture(removalFuture);
        }
    }

    /**
     * Work which is completed when a call is to be removed. Can either be be run synchronously or
     * posted to a {@link Call#getDisconnectFuture()}.
     * posted to a {@link Call#getDiagnosticCompleteFuture()}.
     * @param call The call.
     */
    private void performRemoval(Call call) {
+6 −1
Original line number Diff line number Diff line
@@ -381,7 +381,12 @@ public class ConnectionServiceWrapper extends ServiceBinder implements
                    logIncoming("removeCall %s", callId);
                    Call call = mCallIdMapper.getCall(callId);
                    if (call != null) {
                        if (call.isAlive() && !call.isDisconnectHandledViaFuture()) {
                        boolean isRemovalPending = mFlags.cancelRemovalOnEmergencyRedial()
                                && call.isRemovalPending();
                        if (call.isAlive() && !call.isDisconnectHandledViaFuture()
                                && !isRemovalPending) {
                            Log.w(this, "call not disconnected when removeCall"
                                    + " called, marking disconnected first.");
                            mCallsManager.markCallAsDisconnected(
                                    call, new DisconnectCause(DisconnectCause.REMOTE));
                        }
+1 −1
Original line number Diff line number Diff line
@@ -241,7 +241,7 @@ public abstract class ServiceBinder {
     * Abbreviated form of the package name from {@link #mComponentName}; used for session logging.
     */
    protected final String mPackageAbbreviation;
    private final FeatureFlags mFlags;
    protected final FeatureFlags mFlags;


    /** The set of callbacks waiting for notification of the binding's success or failure. */