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

Commit 660ee723 authored by Hall Liu's avatar Hall Liu
Browse files

Implement parts of the audio call screening API

Add code to enable the APIs that let a call screening service enter the
background audio processing audio state, as well as the APIs that handle
simulated ringing and answering.

Test: CTS
Bug: 140317205
Change-Id: I05a51fcaf7c69bda6dd13f358e1d8667d8f463d4
parent 8b1c1342
Loading
Loading
Loading
Loading
+55 −3
Original line number Diff line number Diff line
@@ -365,6 +365,12 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
     */
    private DisconnectCause mDisconnectCause = new DisconnectCause(DisconnectCause.UNKNOWN);

    /**
     * Override the disconnect cause set by the connection service. Used for audio processing and
     * simulated ringing calls.
     */
    private int mOverrideDisconnectCauseCode = DisconnectCause.UNKNOWN;

    private Bundle mIntentExtras = new Bundle();

    /**
@@ -1144,6 +1150,11 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
    public void setDisconnectCause(DisconnectCause disconnectCause) {
        // TODO: Consider combining this method with a setDisconnected() method that is totally
        // separate from setState.
        if (mOverrideDisconnectCauseCode != DisconnectCause.UNKNOWN) {
            disconnectCause = new DisconnectCause(mOverrideDisconnectCauseCode,
                    disconnectCause.getLabel(), disconnectCause.getDescription(),
                    disconnectCause.getReason(), disconnectCause.getTone());
        }
        mAnalytics.setCallDisconnectCause(disconnectCause);
        mDisconnectCause = disconnectCause;
    }
@@ -1922,6 +1933,13 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
            Log.v(this, "Aborting call %s", this);
            abort(disconnectionTimeout);
        } else if (mState != CallState.ABORTED && mState != CallState.DISCONNECTED) {
            if (mState == CallState.AUDIO_PROCESSING) {
                mOverrideDisconnectCauseCode = DisconnectCause.REJECTED;
            } else if (mState == CallState.SIMULATED_RINGING) {
                // This is the case where the dialer calls disconnect() because the call timed out
                // Override the disconnect cause to MISSED
                mOverrideDisconnectCauseCode = DisconnectCause.MISSED;
            }
            if (mConnectionService == null) {
                Log.e(this, new Exception(), "disconnect() request on a call without a"
                        + " connection service.");
@@ -1999,6 +2017,30 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
        }
    }

    /**
     * Answers the call on the connectionservice side in order to start audio processing.
     *
     * This pathway keeps the call in the ANSWERED state until the connection service confirms the
     * answer, at which point we'll set it to AUDIO_PROCESSING. However, to prevent any other
     * components from seeing the churn between RINGING -> ANSWERED -> AUDIO_PROCESSING, we'll
     * refrain from tracking this call in CallsManager until we've stabilized in AUDIO_PROCESSING
     */
    public void answerForAudioProcessing() {
        if (mState != CallState.RINGING) {
            Log.w(this, "Trying to audio-process a non-ringing call: id=%s", mId);
            return;
        }

        if (mConnectionService != null) {
            mConnectionService.answer(this, VideoProfile.STATE_AUDIO_ONLY);
        } else {
            Log.e(this, new NullPointerException(),
                    "answer call (audio processing) failed due to null CS callId=%s", getId());
        }

        Log.addEvent(this, LogUtils.Events.REQUEST_PICKUP_FOR_AUDIO_PROCESSING);
    }

    /**
     * Deflects the call if it is ringing.
     *
@@ -2045,9 +2087,19 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
     */
    @VisibleForTesting
    public void reject(boolean rejectWithMessage, String textMessage, String reason) {
        // Check to verify that the call is still in the ringing state. A call can change states
        // between the time the user hits 'reject' and Telecomm receives the command.
        if (isRinging("reject")) {
        if (mState == CallState.SIMULATED_RINGING) {
            // This handles the case where the user manually rejects a call that's in simulated
            // ringing. Since the call is already active on the connectionservice side, we want to
            // hangup, not reject.
            mOverrideDisconnectCauseCode = DisconnectCause.REJECTED;
            if (mConnectionService != null) {
                mConnectionService.disconnect(this);
            } else {
                Log.e(this, new NullPointerException(),
                        "reject call failed due to null CS callId=%s", getId());
            }
            Log.addEvent(this, LogUtils.Events.REQUEST_REJECT, reason);
        } else if (isRinging("reject")) {
            // Ensure video state history tracks video state at time of rejection.
            mVideoStateHistory |= mVideoState;

+85 −1
Original line number Diff line number Diff line
@@ -252,6 +252,13 @@ public class CallsManager extends Call.ListenerBase
     * Used by {@link #onCallRedirectionComplete}.
     */
    private Call mPendingRedirectedOutgoingCall;

    /**
     * Cached call that's been answered but will be added to mCalls pending confirmation of active
     * status from the connection service.
     */
    private Call mPendingAudioProcessingCall;

    /**
     * Cached latest pending redirected call information which require user-intervention in order
     * to be placed. Used by {@link #onCallRedirectionComplete}.
@@ -682,6 +689,9 @@ public class CallsManager extends Call.ListenerBase
                Log.i(this, "onCallFilteringCompleted: setting the call to silent ringing state");
                incomingCall.setSilentRingingRequested(true);
                addCall(incomingCall);
            } else if (result.shouldScreenViaAudio) {
                Log.i(this, "onCallFilteringCompleted: starting background audio processing");
                answerCallForAudioProcessing(incomingCall);
            } else {
                addCall(incomingCall);
            }
@@ -975,6 +985,10 @@ public class CallsManager extends Call.ListenerBase
        return mEmergencyCallHelper;
    }

    public DefaultDialerCache getDefaultDialerCache() {
        return mDefaultDialerCache;
    }

    @VisibleForTesting
    public PhoneAccountRegistrar.Listener getPhoneAccountListener() {
        return mPhoneAccountListener;
@@ -1977,6 +1991,62 @@ public class CallsManager extends Call.ListenerBase
        }
    }

    private void answerCallForAudioProcessing(Call call) {
        // We don't check whether the call has been added to the internal lists yet -- it's optional
        // until the call is actually in the AUDIO_PROCESSING state.
        Call activeCall = (Call) mConnectionSvrFocusMgr.getCurrentFocusCall();
        if (activeCall != null && activeCall != call) {
            Log.w(this, "answerCallForAudioProcessing: another active call already exists. "
                    + "Ignoring request for audio processing and letting the incoming call "
                    + "through.");
            // The call should already be in the RINGING state, so all we have to do is add the
            // call to the internal tracker.
            addCall(call);
            return;
        }
        Log.d(this, "answerCallForAudioProcessing: Incoming call = %s", call);
        mConnectionSvrFocusMgr.requestFocus(
                call,
                new RequestCallback(() -> {
                    synchronized (mLock) {
                        Log.d(this, "answering call %s for audio processing with cs focus", call);
                        call.answerForAudioProcessing();
                        // Skip setting the call state to ANSWERED -- that's only for calls that
                        // were answered by user intervention.
                        mPendingAudioProcessingCall = call;
                    }
                }));

    }

    /**
     * Instructs Telecom to bring a call out of the AUDIO_PROCESSING state.
     *
     * Used by the background audio call screener (also the default dialer) to signal that it's
     * finished doing its thing and the user should be made aware of the call.
     *
     * @param call The call to manipulate
     * @param shouldRing if true, puts the call into SIMULATED_RINGING. Otherwise, makes the call
     *                   active.
     */
    public void exitBackgroundAudioProcessing(Call call, boolean shouldRing) {
        if (!mCalls.contains(call)) {
            Log.w(this, "Trying to exit audio processing on an untracked call");
            return;
        }

        Call activeCall = getActiveCall();
        if (activeCall != null) {
            Log.w(this, "Ignoring exit audio processing because there's already a call active");
        }

        if (shouldRing) {
            setCallState(call, CallState.SIMULATED_RINGING, "exitBackgroundAudioProcessing");
        } else {
            setCallState(call, CallState.ACTIVE, "exitBackgroundAudioProcessing");
        }
    }

    /**
     * Instructs Telecom to deflect the specified call. Intended to be invoked by the in-call
     * app through {@link InCallAdapter} after Telecom notifies it of an incoming call followed by
@@ -2482,6 +2552,15 @@ public class CallsManager extends Call.ListenerBase
                            CallState.ACTIVE,
                            "active set explicitly for self-managed")));
        } else {
            if (mPendingAudioProcessingCall == call) {
                if (mCalls.contains(call)) {
                    setCallState(call, CallState.AUDIO_PROCESSING, "active set explicitly");
                } else {
                    call.setState(CallState.AUDIO_PROCESSING, "active set explicitly and adding");
                    addCall(call);
                }
                return;
            }
            setCallState(call, CallState.ACTIVE, "active set explicitly");
            maybeMoveToSpeakerPhone(call);
            ensureCallAudible();
@@ -4553,9 +4632,14 @@ public class CallsManager extends Call.ListenerBase

                // We do not update the UI until we get confirmation of the answer() through
                // {@link #markCallAsActive}.
                mCall.answer(mVideoState);
                if (mCall.getState() == CallState.RINGING) {
                    mCall.answer(mVideoState);
                    setCallState(mCall, CallState.ANSWERED, "answered");
                } else if (mCall.getState() == CallState.SIMULATED_RINGING) {
                    // If the call's in simulated ringing, we don't have to wait for the CS --
                    // we can just declare it active.
                    setCallState(mCall, CallState.ACTIVE, "answering simulated ringing");
                    Log.addEvent(mCall, LogUtils.Events.REQUEST_SIMULATED_ACCEPT);
                }
                if (isSpeakerphoneAutoEnabledForVideoCalls(mVideoState)) {
                    mCall.setStartWithSpeakerphoneOn(true);
+1 −1
Original line number Diff line number Diff line
@@ -154,7 +154,7 @@ public class ConnectionServiceFocusManager {
    }

    private static final int[] PRIORITY_FOCUS_CALL_STATE = new int[] {
            CallState.ACTIVE, CallState.CONNECTING, CallState.DIALING
            CallState.ACTIVE, CallState.CONNECTING, CallState.DIALING, CallState.AUDIO_PROCESSING
    };

    private static final int MSG_REQUEST_FOCUS = 1;
+16 −1
Original line number Diff line number Diff line
@@ -326,7 +326,22 @@ class InCallAdapter extends IInCallAdapter.Stub {

    @Override
    public void exitBackgroundAudioProcessing(String callId, boolean shouldRing) {
        // TODO: implement this
        try {
            Log.startSession(LogUtils.Sessions.ICA_EXIT_AUDIO_PROCESSING, mOwnerComponentName);
            Binder.withCleanCallingIdentity(() -> {
                synchronized (mLock) {
                    Call call = mCallIdMapper.getCall(callId);
                    if (call != null) {
                        mCallsManager.exitBackgroundAudioProcessing(call, shouldRing);
                    } else {
                        Log.w(InCallAdapter.this,
                                "exitBackgroundAudioProcessing, unknown call id: %s", callId);
                    }
                }
            });
        } finally {
            Log.endSession();
        }
    }

    @Override
+5 −0
Original line number Diff line number Diff line
@@ -68,6 +68,8 @@ public class LogUtils {
        public static final String ICA_UNHOLD_CALL = "ICA.uC";
        public static final String ICA_MUTE = "ICA.m";
        public static final String ICA_SET_AUDIO_ROUTE = "ICA.sAR";
        public static final String ICA_ENTER_AUDIO_PROCESSING = "ICA.enBAP";
        public static final String ICA_EXIT_AUDIO_PROCESSING = "ICA.exBAP";
        public static final String ICA_CONFERENCE = "ICA.c";
        public static final String CSW_HANDLE_CREATE_CONNECTION_COMPLETE = "CSW.hCCC";
        public static final String CSW_SET_ACTIVE = "CSW.sA";
@@ -101,6 +103,9 @@ public class LogUtils {
        public static final String REQUEST_UNHOLD = "REQUEST_UNHOLD";
        public static final String REQUEST_DISCONNECT = "REQUEST_DISCONNECT";
        public static final String REQUEST_ACCEPT = "REQUEST_ACCEPT";
        public static final String REQUEST_SIMULATED_ACCEPT = "REQUEST_SIMULATED_ACCEPT";
        public static final String REQUEST_PICKUP_FOR_AUDIO_PROCESSING =
                "REQUEST_PICKUP_FOR_AUDIO_PROCESSING";
        public static final String REQUEST_DEFLECT = "REQUEST_DEFLECT";
        public static final String REQUEST_REJECT = "REQUEST_REJECT";
        public static final String START_DTMF = "START_DTMF";
Loading