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

Commit a06984a6 authored by Tyler Gunn's avatar Tyler Gunn Committed by Android (Google) Code Review
Browse files

Merge "Handle providing disconnect message through CallRedirectionService." into sc-dev

parents bdfea1ea 5af368bf
Loading
Loading
Loading
Loading
+103 −0
Original line number Diff line number Diff line
@@ -39,6 +39,7 @@ import android.provider.CallLog;
import android.provider.ContactsContract.Contacts;
import android.telecom.BluetoothCallQualityReport;
import android.telecom.CallAudioState;
import android.telecom.CallDiagnosticService;
import android.telecom.CallerInfo;
import android.telecom.Conference;
import android.telecom.Connection;
@@ -60,6 +61,7 @@ import android.telecom.VideoProfile;
import android.telephony.PhoneNumberUtils;
import android.telephony.TelephonyManager;
import android.telephony.emergency.EmergencyNumber;
import android.telephony.ims.ImsReasonInfo;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.widget.Toast;
@@ -81,7 +83,9 @@ import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

/**
 *  Encapsulates all aspects of a given phone call throughout its lifecycle, starting
@@ -661,6 +665,22 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
     */
    private boolean mIsSimCall;

    /**
     * Set to {@code true} if we received a valid response ({@code null} or otherwise) from
     * the {@link DiagnosticCall#onCallDisconnected(ImsReasonInfo)} or
     * {@link DiagnosticCall#onCallDisconnected(int, int)} calls.  This is used to detect a timeout
     * when awaiting a response from the call diagnostic service.
     */
    private boolean mReceivedCallDiagnosticPostCallResponse = false;

    /**
     * {@link CompletableFuture} used to delay posting disconnection and removal to a call until
     * after a {@link CallDiagnosticService} is able to handle the disconnection and provide a
     * disconnect message via {@link DiagnosticCall#onCallDisconnected(ImsReasonInfo)} or
     * {@link DiagnosticCall#onCallDisconnected(int, int)}.
     */
    private CompletableFuture<Boolean> mDisconnectFuture;

    /**
     * Persists the specified parameters and initializes the new instance.
     * @param context The context.
@@ -1092,8 +1112,29 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
        }
    }

    /**
     * Handles an incoming overridden disconnect message for this call.
     *
     * We only care if the disconnect is handled via a future.
     * @param message the overridden disconnect message.
     */
    public void handleOverrideDisconnectMessage(@Nullable CharSequence message) {
        Log.i(this, "handleOverrideDisconnectMessage; callid=%s, msg=%s", getId(), message);

        if (isDisconnectHandledViaFuture()) {
            mReceivedCallDiagnosticPostCallResponse = true;
            if (message != null) {
                Log.addEvent(this, LogUtils.Events.OVERRIDE_DISCONNECT_MESSAGE, message);
                // Replace the existing disconnect cause in this call
                setOverrideDisconnectCauseCode(new DisconnectCause(DisconnectCause.ERROR, message,
                        message, null));
            }

            mDisconnectFuture.complete(true);
        } else {
            Log.w(this, "handleOverrideDisconnectMessage; callid=%s - got override when unbound",
                    getId());
        }
    }

    /**
@@ -4088,4 +4129,66 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
    public boolean isSimCall() {
        return mIsSimCall;
    }

    /**
     * Sets whether this is a sim call or not.
     * @param isSimCall {@code true} if this is a SIM call, {@code false} otherwise.
     */
    public void setIsSimCall(boolean isSimCall) {
        mIsSimCall = isSimCall;
    }

    /**
     * Initializes a disconnect future which is used to chain up pending operations which take
     * place when the {@link CallDiagnosticService} returns the result of the
     * {@link DiagnosticCall#onCallDisconnected(int, int)} or
     * {@link DiagnosticCall#onCallDisconnected(ImsReasonInfo)} invocation via
     * {@link CallDiagnosticServiceAdapter}.  If no {@link CallDiagnosticService} is in use, we
     * would not try to make a disconnect future.
     * @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>()
                    .completeOnTimeout(false, timeoutMillis, TimeUnit.MILLISECONDS);
            // After all the chained stuff we will report where the CDS timed out.
            mDisconnectFuture.thenRunAsync(() -> {
                if (!mReceivedCallDiagnosticPostCallResponse) {
                    Log.addEvent(this, LogUtils.Events.CALL_DIAGNOSTIC_SERVICE_TIMEOUT);
                }},
                new LoggedHandlerExecutor(mHandler, "C.iDF", mLock))
                    .exceptionally((throwable) -> {
                        Log.e(this, throwable, "Error while executing disconnect future");
                        return null;
                    });
        }
        return mDisconnectFuture;
    }

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

    /**
     * @return {@code true} if disconnection and removal is handled via a future, or {@code false}
     * if this is handled immediately.
     */
    public boolean isDisconnectHandledViaFuture() {
        return mDisconnectFuture != null && !mDisconnectFuture.isDone();
    }

    /**
     * Perform any cleanup on this call as a result of a {@link TelecomServiceImpl}
     * {@code cleanupStuckCalls} request.
     */
    public void cleanup() {
        if (mDisconnectFuture != null) {
            mDisconnectFuture.complete(false);
            mDisconnectFuture = null;
        }
    }
}
+28 −1
Original line number Diff line number Diff line
@@ -36,6 +36,7 @@ import android.telecom.CallAudioState;
import android.telecom.CallDiagnosticService;
import android.telecom.ConnectionService;
import android.telecom.DiagnosticCall;
import android.telecom.DisconnectCause;
import android.telecom.InCallService;
import android.telecom.Log;
import android.telecom.ParcelableCall;
@@ -254,6 +255,32 @@ public class CallDiagnosticServiceController extends CallsManagerListenerBase {
        }
    }

    /**
     * Handles a newly disconnected call signalled from {@link CallsManager}.
     * @param call The call
     * @param disconnectCause The disconnect cause
     * @return {@code true} if the {@link CallDiagnosticService} was sent the call, {@code false}
     * if the call was not applicable to the CDS or if there was an issue sending it.
     */
    public boolean onCallDisconnected(@NonNull Call call,
            @NonNull DisconnectCause disconnectCause) {
        if (!call.isSimCall() || call.isExternalCall()) {
            Log.i(this, "onCallDisconnected: skipping call %s as non-sim or external.",
                    call.getId());
            return false;
        }
        String callId = mCallIdMapper.getCallId(call);
        try {
            if (isConnected()) {
                mCallDiagnosticService.notifyCallDisconnected(callId, disconnectCause);
                return true;
            }
        } catch (RemoteException e) {
            Log.w(this, "onCallDisconnected: callId=%s, exception=%s", call.getId(), e);
        }
        return false;
    }

    /**
     * Handles Telecom removal of calls; will remove the call from the bound service and if the
     * number of tracked calls falls to zero, unbind from the service.
@@ -569,7 +596,7 @@ public class CallDiagnosticServiceController extends CallsManagerListenerBase {
    /**
     * @return {@code true} if the call diagnostic service is bound/connected.
     */
    private boolean isConnected() {
    public boolean isConnected() {
        return mCallDiagnosticService != null;
    }

+63 −7
Original line number Diff line number Diff line
@@ -3099,27 +3099,82 @@ public class CallsManager extends Call.ListenerBase
            // be marked as missed.
            call.setOverrideDisconnectCauseCode(new DisconnectCause(DisconnectCause.MISSED));
        }

        // If a call diagnostic service is in use, we will log the original telephony-provided
        // disconnect cause, inform the CDS of the disconnection, and then chain the update of the
        // call state until AFTER the CDS reports it's result back.
        if (oldState == CallState.ACTIVE && disconnectCause.getCode() != DisconnectCause.MISSED
                && mCallDiagnosticServiceController.isConnected()
                && mCallDiagnosticServiceController.onCallDisconnected(call, disconnectCause)) {
            Log.i(this, "markCallAsDisconnected; callid=%s, postingToFuture.", call.getId());

            // Log the original disconnect reason prior to calling into the
            // CallDiagnosticService.
            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(
                    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(() -> {
                call.setDisconnectCause(disconnectCause);
                setCallState(call, CallState.DISCONNECTED, "disconnected set explicitly");
            }, new LoggedHandlerExecutor(mHandler, "CM.mCAD", mLock))
                    .exceptionally((throwable) -> {
                        Log.e(TAG, throwable, "Error while executing disconnect future.");
                        return null;
                    });
        } else {
            // No CallDiagnosticService, or it doesn't handle this call, so just do this
            // synchronously as always.
            call.setDisconnectCause(disconnectCause);
            setCallState(call, CallState.DISCONNECTED, "disconnected set explicitly");
        }

        if (oldState == CallState.NEW && disconnectCause.getCode() == DisconnectCause.MISSED) {
            Log.i(this, "markCallAsDisconnected: logging missed call ");
            mCallLogManager.logCall(call, Calls.MISSED_TYPE, true, null);
        }

    }

    /**
     * Removes an existing disconnected call, and notifies the in-call app.
     */
    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(() -> {
                performRemoval(call);
            }, new LoggedHandlerExecutor(mHandler, "CM.mCAR", 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);
        }
    }

    /**
     * Work which is completed when a call is to be removed. Can either be be run synchronously or
     * posted to a {@link Call#getDisconnectFuture()}.
     * @param call The call.
     */
    private void performRemoval(Call call) {
        mInCallController.getBindingFuture().thenRunAsync(() -> {
            call.maybeCleanupHandover();
            removeCall(call);
            Call foregroundCall = mCallAudioManager.getPossiblyHeldForegroundCall();
            if (mLocallyDisconnectingCalls.contains(call)) {
                boolean isDisconnectingChildCall = call.isDisconnectingChildCall();
                Log.v(this, "markCallAsRemoved: isDisconnectingChildCall = "
                Log.v(this, "performRemoval: isDisconnectingChildCall = "
                        + isDisconnectingChildCall + "call -> %s", call);
                mLocallyDisconnectingCalls.remove(call);
                // Auto-unhold the foreground call due to a locally disconnected call, except if the
@@ -3136,10 +3191,11 @@ public class CallsManager extends Call.ListenerBase
                // The new foreground call is on hold, however the carrier does not display the hold
                // button in the UI.  Therefore, we need to auto unhold the held call since the user
                // has no means of unholding it themselves.
                Log.i(this, "Auto-unholding held foreground call (call doesn't support hold)");
                Log.i(this, "performRemoval: Auto-unholding held foreground call (call doesn't "
                        + "support hold)");
                foregroundCall.unhold();
            }
        }, new LoggedHandlerExecutor(mHandler, "CM.mCAR", mLock))
        }, new LoggedHandlerExecutor(mHandler, "CM.pR", mLock))
                .exceptionally((throwable) -> {
                    Log.e(TAG, throwable, "Error while executing call removal");
                    return null;
+1 −1
Original line number Diff line number Diff line
@@ -352,7 +352,7 @@ public class ConnectionServiceWrapper extends ServiceBinder implements
                    logIncoming("removeCall %s", callId);
                    Call call = mCallIdMapper.getCall(callId);
                    if (call != null) {
                        if (call.isAlive()) {
                        if (call.isAlive() && !call.isDisconnectHandledViaFuture()) {
                            mCallsManager.markCallAsDisconnected(
                                    call, new DisconnectCause(DisconnectCause.REMOTE));
                        } else {
+4 −0
Original line number Diff line number Diff line
@@ -197,6 +197,10 @@ public class LogUtils {
        public static final String REDIRECTION_USER_CONFIRMED = "REDIRECTION_USER_CONFIRMED";
        public static final String REDIRECTION_USER_CANCELLED = "REDIRECTION_USER_CANCELLED";
        public static final String BT_QUALITY_REPORT = "BT_QUALITY_REPORT";
        public static final String SET_DISCONNECTED_ORIG = "SET_DISCONNECTED_ORIG";
        public static final String OVERRIDE_DISCONNECT_MESSAGE = "OVERRIDE_DISCONNECT_MSG";
        public static final String CALL_DIAGNOSTIC_SERVICE_TIMEOUT =
                "CALL_DIAGNOSTIC_SERVICE_TIMEOUT";

        public static class Timings {
            public static final String ACCEPT_TIMING = "accept";
Loading