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

Commit b2d91514 authored by Hall Liu's avatar Hall Liu Committed by Gerrit Code Review
Browse files

Merge "Accept timeout from apps requesting call disconnect"

parents 0bee0ffb 63e690cb
Loading
Loading
Loading
Loading
+14 −11
Original line number Diff line number Diff line
@@ -114,7 +114,7 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable {
        void onConnectionManagerPhoneAccountChanged(Call call);
        void onPhoneAccountChanged(Call call);
        void onConferenceableCallsChanged(Call call);
        boolean onCanceledViaNewOutgoingCallBroadcast(Call call);
        boolean onCanceledViaNewOutgoingCallBroadcast(Call call, long disconnectionTimeout);
        void onHoldToneRequested(Call call);
        void onConnectionEvent(Call call, String event, Bundle extras);
        void onExternalCallChanged(Call call, boolean isExternalCall);
@@ -176,7 +176,7 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable {
        @Override
        public void onConferenceableCallsChanged(Call call) {}
        @Override
        public boolean onCanceledViaNewOutgoingCallBroadcast(Call call) {
        public boolean onCanceledViaNewOutgoingCallBroadcast(Call call, long disconnectionTimeout) {
            return false;
        }

@@ -1355,14 +1355,14 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable {

    @VisibleForTesting
    public void disconnect() {
        disconnect(false);
        disconnect(0);
    }

    /**
     * Attempts to disconnect the call through the connection service.
     */
    @VisibleForTesting
    public void disconnect(boolean wasViaNewOutgoingCallBroadcaster) {
    public void disconnect(long disconnectionTimeout) {
        Log.addEvent(this, LogUtils.Events.REQUEST_DISCONNECT);

        // Track that the call is now locally disconnecting.
@@ -1371,7 +1371,7 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable {
        if (mState == CallState.NEW || mState == CallState.SELECT_PHONE_ACCOUNT ||
                mState == CallState.CONNECTING) {
            Log.v(this, "Aborting call %s", this);
            abort(wasViaNewOutgoingCallBroadcaster);
            abort(disconnectionTimeout);
        } else if (mState != CallState.ABORTED && mState != CallState.DISCONNECTED) {
            if (mConnectionService == null) {
                Log.e(this, new Exception(), "disconnect() request on a call without a"
@@ -1387,22 +1387,25 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable {
        }
    }

    void abort(boolean wasViaNewOutgoingCallBroadcaster) {
    void abort(long disconnectionTimeout) {
        if (mCreateConnectionProcessor != null &&
                !mCreateConnectionProcessor.isProcessingComplete()) {
            mCreateConnectionProcessor.abort();
        } else if (mState == CallState.NEW || mState == CallState.SELECT_PHONE_ACCOUNT
                || 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
            if (disconnectionTimeout > 0) {
                // If the cancelation was from NEW_OUTGOING_CALL with a timeout of > 0
                // milliseconds, do not destroy the call.
                // Instead, we announce the cancellation 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.
                // jank altogether. The app will also be able to set the timeout via an extra on
                // the ordered broadcast.
                for (Listener listener : mListeners) {
                    if (listener.onCanceledViaNewOutgoingCallBroadcast(this)) {
                    if (listener.onCanceledViaNewOutgoingCallBroadcast(
                            this, disconnectionTimeout)) {
                        // 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.
+3 −2
Original line number Diff line number Diff line
@@ -533,7 +533,8 @@ public class CallsManager extends Call.ListenerBase
    }

    @Override
    public boolean onCanceledViaNewOutgoingCallBroadcast(final Call call) {
    public boolean onCanceledViaNewOutgoingCallBroadcast(final Call call,
            long disconnectionTimeout) {
        mPendingCallsToDisconnect.add(call);
        mHandler.postDelayed(new Runnable("CM.oCVNOCB", mLock) {
            @Override
@@ -543,7 +544,7 @@ public class CallsManager extends Call.ListenerBase
                    call.disconnect();
                }
            }
        }.prepare(), Timeouts.getNewOutgoingCallCancelMillis(mContext.getContentResolver()));
        }.prepare(), disconnectionTimeout);

        return true;
    }
+21 −1
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.net.Uri;
import android.os.Bundle;
import android.os.Trace;
import android.os.UserHandle;
import android.telecom.GatewayInfo;
@@ -113,19 +114,24 @@ public class NewOutgoingCallIntentBroadcaster {
                            Log.pii(resultNumber));

                    boolean endEarly = false;
                    long disconnectTimeout =
                            Timeouts.getNewOutgoingCallCancelMillis(mContext.getContentResolver());
                    if (resultNumber == null) {
                        Log.v(this, "Call cancelled (null number), returning...");
                        disconnectTimeout = getDisconnectTimeoutFromApp(
                                getResultExtras(false), disconnectTimeout);
                        endEarly = true;
                    } else if (mPhoneNumberUtilsAdapter.isPotentialLocalEmergencyNumber(
                            mContext, resultNumber)) {
                        Log.w(this, "Cannot modify outgoing call to emergency number %s.",
                                resultNumber);
                        disconnectTimeout = 0;
                        endEarly = true;
                    }

                    if (endEarly) {
                        if (mCall != null) {
                            mCall.disconnect(true /* wasViaNewOutgoingCall */);
                            mCall.disconnect(disconnectTimeout);
                        }
                        return;
                    }
@@ -446,4 +452,18 @@ public class NewOutgoingCallIntentBroadcaster {
            intent.setAction(action);
        }
    }

    private long getDisconnectTimeoutFromApp(Bundle resultExtras, long defaultTimeout) {
        if (resultExtras != null) {
            long disconnectTimeout = resultExtras.getLong(
                    TelecomManager.EXTRA_NEW_OUTGOING_CALL_CANCEL_TIMEOUT, defaultTimeout);
            if (disconnectTimeout < 0) {
                disconnectTimeout = 0;
            }
            return Math.min(disconnectTimeout,
                    Timeouts.getMaxNewOutgoingCallCancelMillis(mContext.getContentResolver()));
        } else {
            return defaultTimeout;
        }
    }
}
+10 −1
Original line number Diff line number Diff line
@@ -71,7 +71,16 @@ public final class Timeouts {
     * in-call UI.
     */
    public static long getNewOutgoingCallCancelMillis(ContentResolver contentResolver) {
        return get(contentResolver, "new_outgoing_call_cancel_ms", 100000L);
        return get(contentResolver, "new_outgoing_call_cancel_ms", 500L);
    }

    /**
     * Returns the maximum amount of time to wait before disconnecting a call that was canceled via
     * NEW_OUTGOING_CALL broadcast. This prevents malicious or poorly configured apps from
     * forever tying up the Telecom stack.
     */
    public static long getMaxNewOutgoingCallCancelMillis(ContentResolver contentResolver) {
        return get(contentResolver, "max_new_outgoing_call_cancel_ms", 10000L);
    }

    /**
+23 −2
Original line number Diff line number Diff line
@@ -313,7 +313,28 @@ public class NewOutgoingCallIntentBroadcasterTest extends TelecomTestCase {

        result.receiver.onReceive(mContext, result.intent);
        verifyNoCallPlaced();
        verify(mCall).disconnect(true);
        ArgumentCaptor<Long> timeoutCaptor = ArgumentCaptor.forClass(Long.class);
        verify(mCall).disconnect(timeoutCaptor.capture());
        assertTrue(timeoutCaptor.getValue() > 0);
    }

    @SmallTest
    public void testCallNumberModifiedToNullWithLongCustomTimeout() {
        Uri handle = Uri.parse("tel:6505551234");
        Intent callIntent = buildIntent(handle, Intent.ACTION_CALL, null);
        ReceiverIntentPair result = regularCallTestHelper(callIntent, null);

        long customTimeout = 100000000;
        Bundle bundle = new Bundle();
        bundle.putLong(TelecomManager.EXTRA_NEW_OUTGOING_CALL_CANCEL_TIMEOUT, customTimeout);
        result.receiver.setResultData(null);
        result.receiver.setResultExtras(bundle);

        result.receiver.onReceive(mContext, result.intent);
        verifyNoCallPlaced();
        ArgumentCaptor<Long> timeoutCaptor = ArgumentCaptor.forClass(Long.class);
        verify(mCall).disconnect(timeoutCaptor.capture());
        assertTrue(timeoutCaptor.getValue() < customTimeout);
    }

    @SmallTest
@@ -328,7 +349,7 @@ public class NewOutgoingCallIntentBroadcasterTest extends TelecomTestCase {
        doReturn(true).when(mPhoneNumberUtilsAdapterSpy).isPotentialLocalEmergencyNumber(
                any(Context.class), eq(newEmergencyNumber));
        result.receiver.onReceive(mContext, result.intent);
        verify(mCall).disconnect(true);
        verify(mCall).disconnect(eq(0L));
    }

    private ReceiverIntentPair regularCallTestHelper(Intent intent,