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

Commit 4a04e84a authored by Santos Cordon's avatar Santos Cordon Committed by Android (Google) Code Review
Browse files

Merge "Reuse calls canceled through NEW_OUTGOING_CALL broadcast." into lmp-mr1-dev

parents e4696770 cf5b2918
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 =
@@ -768,17 +773,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"
@@ -794,11 +803,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);
    }
}