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

Commit cf5b2918 authored by Santos Cordon's avatar Santos Cordon
Browse files

Reuse calls canceled through NEW_OUTGOING_CALL broadcast.

When a call is canceled because of NEW_OUTGOING_CALL broadcast,
we no longer remove it immediately.  This code adds a 200ms delay.
The 200ms delay is long enough that if an app reposts another call
to the same number (with a gateway number, for example), instead of
destroying and recreating the call, we attempt to reuse the existing
one. This eliminates the jank in the UI caused by the call being
destroyed and a new one recreated.

Bug: 17692408
Change-Id: I225a4fcc6f00d764cf76595f15f6d2d0785b9fe2
parent 64883a94
Loading
Loading
Loading
Loading
+31 −3
Original line number Diff line number Diff line
@@ -89,6 +89,7 @@ final class Call implements CreateConnectionResponse {
        void onConnectionManagerPhoneAccountChanged(Call call);
        void onPhoneAccountChanged(Call call);
        void onConferenceableCallsChanged(Call call);
        boolean onCanceledViaNewOutgoingCallBroadcast(Call call);
    }

    abstract static class ListenerBase implements Listener {
@@ -138,6 +139,10 @@ final class Call implements CreateConnectionResponse {
        public void onPhoneAccountChanged(Call call) {}
        @Override
        public void onConferenceableCallsChanged(Call call) {}
        @Override
        public boolean onCanceledViaNewOutgoingCallBroadcast(Call call) {
            return false;
        }
    }

    private static final OnQueryCompleteListener sCallerInfoQueryListener =
@@ -758,17 +763,21 @@ final class Call implements CreateConnectionResponse {
        }
    }

    void disconnect() {
        disconnect(false);
    }

    /**
     * Attempts to disconnect the call through the connection service.
     */
    void disconnect() {
    void disconnect(boolean wasViaNewOutgoingCallBroadcaster) {
        // Track that the call is now locally disconnecting.
        setLocallyDisconnecting(true);

        if (mState == CallState.NEW || mState == CallState.PRE_DIAL_WAIT ||
                mState == CallState.CONNECTING) {
            Log.v(this, "Aborting call %s", this);
            abort();
            abort(wasViaNewOutgoingCallBroadcaster);
        } else if (mState != CallState.ABORTED && mState != CallState.DISCONNECTED) {
            if (mConnectionService == null) {
                Log.e(this, new Exception(), "disconnect() request on a call without a"
@@ -784,11 +793,30 @@ final class Call implements CreateConnectionResponse {
        }
    }

    void abort() {
    void abort(boolean wasViaNewOutgoingCallBroadcaster) {
        if (mCreateConnectionProcessor != null) {
            mCreateConnectionProcessor.abort();
        } else if (mState == CallState.NEW || mState == CallState.PRE_DIAL_WAIT
                || mState == CallState.CONNECTING) {
            if (wasViaNewOutgoingCallBroadcaster) {
                // If the cancelation was from NEW_OUTGOING_CALL, then we do not automatically
                // destroy the call.  Instead, we announce the cancelation and CallsManager handles
                // it through a timer. Since apps often cancel calls through NEW_OUTGOING_CALL and
                // then re-dial them quickly using a gateway, allowing the first call to end
                // causes jank. This timeout allows CallsManager to transition the first call into
                // the second call so that in-call only ever sees a single call...eliminating the
                // jank altogether.
                for (Listener listener : mListeners) {
                    if (listener.onCanceledViaNewOutgoingCallBroadcast(this)) {
                        // The first listener to handle this wins. A return value of true means that
                        // the listener will handle the disconnection process later and so we
                        // should not continue it here.
                        setLocallyDisconnecting(false);
                        return;
                    }
                }
            }

            handleCreateConnectionFailure(new DisconnectCause(DisconnectCause.CANCELED));
        } else {
            Log.v(this, "Cannot abort a call which isn't either PRE_DIAL_WAIT or CONNECTING");
+53 −13
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.provider.CallLog.Calls;
import android.telecom.AudioState;
import android.telecom.CallState;
@@ -37,7 +38,6 @@ import android.telecom.VideoProfile;
import android.telephony.TelephonyManager;

import com.android.internal.util.IndentingPrintWriter;

import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;

@@ -124,6 +124,9 @@ public final class CallsManager extends Call.ListenerBase {
    private final PhoneAccountRegistrar mPhoneAccountRegistrar;
    private final MissedCallNotifier mMissedCallNotifier;
    private final Set<Call> mLocallyDisconnectingCalls = new HashSet<>();
    private final Set<Call> mPendingCallsToDisconnect = new HashSet<>();
    /* Handler tied to thread in which CallManager was initialized. */
    private final Handler mHandler = new Handler();

    /**
     * The call the user is currently interacting with. This is the call that should have audio
@@ -289,6 +292,22 @@ public final class CallsManager extends Call.ListenerBase {
        }
    }

    @Override
    public boolean onCanceledViaNewOutgoingCallBroadcast(final Call call) {
        mPendingCallsToDisconnect.add(call);
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                if (mPendingCallsToDisconnect.remove(call)) {
                    Log.i(this, "Delayed disconnection of call: %s", call);
                    call.disconnect();
                }
            }
        }, Timeouts.getNewOutgoingCallCancelMillis(mContext.getContentResolver()));

        return true;
    }

    ImmutableCollection<Call> getCalls() {
        return ImmutableList.copyOf(mCalls);
    }
@@ -390,18 +409,20 @@ public final class CallsManager extends Call.ListenerBase {
        call.startCreateConnection(mPhoneAccountRegistrar);
    }

    /**
     * Kicks off the first steps to creating an outgoing call so that InCallUI can launch.
     *
     * @param handle Handle to connect the call with.
     * @param phoneAccountHandle The phone account which contains the component name of the
     *        connection service to use for this call.
     * @param extras The optional extras Bundle passed with the intent used for the incoming call.
     */
    Call startOutgoingCall(Uri handle, PhoneAccountHandle phoneAccountHandle, Bundle extras) {
    private Call getNewOutgoingCall(Uri handle) {
        // First check to see if we can reuse any of the calls that are waiting to disconnect.
        // See {@link Call#abort} and {@link #onCanceledViaNewOutgoingCall} for more information.
        for (Call pendingCall : mPendingCallsToDisconnect) {
            if (Objects.equals(pendingCall.getHandle(), handle)) {
                mPendingCallsToDisconnect.remove(pendingCall);
                Log.i(this, "Reusing disconnected call %s", pendingCall);
                return pendingCall;
            }
        }

        // Create a call with original handle. The handle may be changed when the call is attached
        // to a connection service, but in most cases will remain the same.
        Call call = new Call(
        return new Call(
                mContext,
                mConnectionServiceRepository,
                handle,
@@ -410,6 +431,18 @@ public final class CallsManager extends Call.ListenerBase {
                null /* phoneAccountHandle */,
                false /* isIncoming */,
                false /* isConference */);
    }

    /**
     * Kicks off the first steps to creating an outgoing call so that InCallUI can launch.
     *
     * @param handle Handle to connect the call with.
     * @param phoneAccountHandle The phone account which contains the component name of the
     *        connection service to use for this call.
     * @param extras The optional extras Bundle passed with the intent used for the incoming call.
     */
    Call startOutgoingCall(Uri handle, PhoneAccountHandle phoneAccountHandle, Bundle extras) {
        Call call = getNewOutgoingCall(handle);

        List<PhoneAccountHandle> accounts =
                mPhoneAccountRegistrar.getCallCapablePhoneAccounts(handle.getScheme());
@@ -444,6 +477,11 @@ public final class CallsManager extends Call.ListenerBase {
        // a call, or cancel this call altogether.
        if (!isPotentialInCallMMICode && !makeRoomForOutgoingCall(call, isEmergencyCall)) {
            // just cancel at this point.
            if (mCalls.contains(call)) {
                // This call can already exist if it is a reused call,
                // See {@link #getNewOutgoingCall}.
                call.disconnect();
            }
            return null;
        }

@@ -463,7 +501,9 @@ public final class CallsManager extends Call.ListenerBase {
        // Do not add the call if it is a potential MMI code.
        if ((isPotentialMMICode(handle) || isPotentialInCallMMICode) && !needsAccountSelection) {
            call.addListener(this);
        } else {
        } else if (!mCalls.contains(call)) {
            // We check if mCalls already contains the call because we could potentially be reusing
            // a call which was previously added (See {@link #getNewOutgoingCall}).
            addCall(call);
        }

+1 −1
Original line number Diff line number Diff line
@@ -114,7 +114,7 @@ class NewOutgoingCallIntentBroadcaster {

            if (endEarly) {
                if (mCall != null) {
                    mCall.disconnect();
                    mCall.disconnect(true /* wasViaNewOutgoingCall */);
                }
                return;
            }
+10 −0
Original line number Diff line number Diff line
@@ -52,4 +52,14 @@ public final class Timeouts {
    public static long getDirectToVoicemailMillis(ContentResolver contentResolver) {
        return get(contentResolver, "direct_to_voicemail_ms", 500L);
    }

    /**
     * Returns the amount of time to wait before disconnecting a call that was canceled via
     * NEW_OUTGOING_CALL broadcast. This timeout allows apps which repost the call using a gateway
     * to reuse the existing call, preventing the call from causing a start->end->start jank in the
     * in-call UI.
     */
    public static long getNewOutgoingCallCancelMillis(ContentResolver contentResolver) {
        return get(contentResolver, "new_outgoing_call_cancel_ms", 200L);
    }
}