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

Commit b9694ff9 authored by Thomas Stuart's avatar Thomas Stuart
Browse files

Telecom Transactional APIs Implementation

Transactional APIs are defined as APIs that use
android.os.OutcomeReceivers.  The receivers are to be completed by
Telecom for CallControl opertaions and the Client for CallEventCallback
operations.  Doing so ensures the client and telecom are in sync with
call states and that operations can be completed on both ends.

bug: 249779561
Test: unit + CTS

Change-Id: Ib4cc7c7b05491e4f61c011a5d7af5bc24125d34d
parent 99319b3b
Loading
Loading
Loading
Loading
+108 −26
Original line number Diff line number Diff line
@@ -29,9 +29,11 @@ import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.OutcomeReceiver;
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.SystemClock;
import android.os.Trace;
import android.os.UserHandle;
@@ -92,7 +94,6 @@ import java.util.stream.Collectors;
 *  from the time the call intent was received by Telecom (vs. the time the call was
 *  connected etc).
 */
@VisibleForTesting
public class Call implements CreateConnectionResponse, EventManager.Loggable,
        ConnectionServiceFocusManager.CallFocus {
    public final static String CALL_ID_UNKNOWN = "-1";
@@ -381,6 +382,8 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
     */
    private ConnectionServiceWrapper mConnectionService;

    private TransactionalServiceWrapper mTransactionalService;

    private boolean mIsEmergencyCall;

    // The Call is considered an emergency call for testing, but will not actually connect to
@@ -529,6 +532,8 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
     */
    private boolean mIsSelfManaged = false;

    private boolean mIsTransactionalCall = false;

    /**
     * Indicates whether the {@link PhoneAccount} associated with an self-managed call want to
     * expose the call to an {@link android.telecom.InCallService} which declares the metadata
@@ -1038,7 +1043,7 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,

    @Override
    public ConnectionServiceFocusManager.ConnectionServiceFocus getConnectionServiceWrapper() {
        return mConnectionService;
        return (!mIsTransactionalCall ? mConnectionService : mTransactionalService);
    }

    @VisibleForTesting
@@ -1767,6 +1772,25 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
        setConnectionProperties(getConnectionProperties());
    }

    public boolean isTransactionalCall() {
        return mIsTransactionalCall;
    }

    public void setIsTransactionalCall(boolean isTransactionalCall) {
        mIsTransactionalCall = isTransactionalCall;

        // Connection properties will add/remove the PROPERTY_SELF_MANAGED.
        setConnectionProperties(getConnectionProperties());
    }

    public void setTransactionServiceWrapper(TransactionalServiceWrapper service) {
        mTransactionalService = service;
    }

    public TransactionalServiceWrapper getTransactionServiceWrapper() {
        return mTransactionalService;
    }

    public boolean visibleToInCallService() {
        return mVisibleToInCallService;
    }
@@ -2044,8 +2068,10 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
                    createRttStreams();
                    // Call startRtt to pass the RTT pipes down to the connection service.
                    // They already turned on the RTT property so no request should be sent.
                    if (mConnectionService != null) {
                        mConnectionService.startRtt(this,
                                getInCallToCsRttPipeForCs(), getCsToInCallRttPipeForCs());
                    }
                    mWasEverRtt = true;
                    if (isEmergencyCall()) {
                        mCallsManager.mute(false);
@@ -2146,7 +2172,6 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
        return mConferenceLevelActiveCall;
    }

    @VisibleForTesting
    public ConnectionServiceWrapper getConnectionService() {
        return mConnectionService;
    }
@@ -2462,7 +2487,10 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
                // Override the disconnect cause to MISSED
                setOverrideDisconnectCauseCode(new DisconnectCause(DisconnectCause.MISSED));
            }
            if (mConnectionService == null) {
            if (mTransactionalService != null) {
                mTransactionalService.onDisconnect(this, getDisconnectCause());
                Log.i(this, "Send Disconnect to transactional service for call");
            } else if (mConnectionService == null) {
                Log.e(this, new Exception(), "disconnect() request on a call without a"
                        + " connection service.");
            } else {
@@ -2531,6 +2559,8 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
            // {@link ConnectionServiceAdapter#setActive} and other set* methods.
            if (mConnectionService != null) {
                mConnectionService.answer(this, videoState);
            } else if (mTransactionalService != null) {
                mTransactionalService.onAnswer(this, videoState);
            } else {
                Log.e(this, new NullPointerException(),
                        "answer call failed due to null CS callId=%s", getId());
@@ -2622,7 +2652,9 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
            // ringing. Since the call is already active on the connectionservice side, we want to
            // hangup, not reject.
            setOverrideDisconnectCauseCode(new DisconnectCause(DisconnectCause.REJECTED));
            if (mConnectionService != null) {
            if (mTransactionalService != null) {
                mTransactionalService.onReject(this, DisconnectCause.REJECTED);
            } else if (mConnectionService != null) {
                mConnectionService.disconnect(this);
            } else {
                Log.e(this, new NullPointerException(),
@@ -2633,7 +2665,9 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
            // Ensure video state history tracks video state at time of rejection.
            mVideoStateHistory |= mVideoState;

            if (mConnectionService != null) {
            if (mTransactionalService != null) {
                mTransactionalService.onReject(this, DisconnectCause.REJECTED);
            } else if (mConnectionService != null) {
                mConnectionService.reject(this, rejectWithMessage, textMessage);
            } else {
                Log.e(this, new NullPointerException(),
@@ -2654,7 +2688,9 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
            // hangup, not reject.
            // Since its simulated reason we can't pass along the reject reason.
            setOverrideDisconnectCauseCode(new DisconnectCause(DisconnectCause.REJECTED));
            if (mConnectionService != null) {
            if (mTransactionalService != null) {
                mTransactionalService.onReject(this, DisconnectCause.REJECTED);
            } else if (mConnectionService != null) {
                mConnectionService.disconnect(this);
            } else {
                Log.e(this, new NullPointerException(),
@@ -2664,8 +2700,9 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
        } else if (isRinging("reject") || isAnswered("reject")) {
            // Ensure video state history tracks video state at time of rejection.
            mVideoStateHistory |= mVideoState;

            if (mConnectionService != null) {
            if (mTransactionalService != null) {
                mTransactionalService.onReject(this, rejectReason);
            } else if (mConnectionService != null) {
                mConnectionService.rejectWithReason(this, rejectReason);
            } else {
                Log.e(this, new NullPointerException(),
@@ -2684,7 +2721,9 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
    @VisibleForTesting
    public void transfer(Uri number, boolean isConfirmationRequired) {
        if (mState == CallState.ACTIVE || mState == CallState.ON_HOLD) {
            if (mConnectionService != null) {
            if (mTransactionalService != null) {
                Log.i(this, "transfer: called on TransactionalService. doing nothing");
            } else if (mConnectionService != null) {
                mConnectionService.transfer(this, number, isConfirmationRequired);
            } else {
                Log.e(this, new NullPointerException(),
@@ -2704,7 +2743,9 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
    public void transfer(Call otherCall) {
        if (mState == CallState.ACTIVE &&
                (otherCall != null && otherCall.getState() == CallState.ON_HOLD)) {
            if (mConnectionService != null) {
            if (mTransactionalService != null) {
                Log.i(this, "transfer: called on TransactionalService. doing nothing");
            } else if (mConnectionService != null) {
                mConnectionService.transfer(this, otherCall);
            } else {
                Log.e(this, new NullPointerException(),
@@ -2724,7 +2765,9 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,

    public void hold(String reason) {
        if (mState == CallState.ACTIVE) {
            if (mConnectionService != null) {
            if (mTransactionalService != null) {
                mTransactionalService.onSetInactive(this);
            } else if (mConnectionService != null) {
                mConnectionService.hold(this);
            } else {
                Log.e(this, new NullPointerException(),
@@ -2744,7 +2787,9 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,

    public void unhold(String reason) {
        if (mState == CallState.ON_HOLD) {
            if (mConnectionService != null) {
            if (mTransactionalService != null){
                mTransactionalService.onSetActive(this);
            } else if (mConnectionService != null){
                mConnectionService.unhold(this);
            } else {
                Log.e(this, new NullPointerException(),
@@ -2769,7 +2814,6 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
        }
    }

    @VisibleForTesting
    public boolean isActive() {
        return mState == CallState.ACTIVE;
    }
@@ -2835,7 +2879,9 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,

        // If the change originated from an InCallService, notify the connection service.
        if (source == SOURCE_INCALL_SERVICE) {
            if (mConnectionService != null) {
            if (mTransactionalService != null) {
                Log.i(this, "putExtras: called on TransactionalService. doing nothing");
            } else if (mConnectionService != null) {
                mConnectionService.onExtrasChanged(this, mExtras);
            } else {
                Log.e(this, new NullPointerException(),
@@ -2870,7 +2916,9 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,

        // If the change originated from an InCallService, notify the connection service.
        if (source == SOURCE_INCALL_SERVICE) {
            if (mConnectionService != null) {
            if (mTransactionalService != null) {
                Log.i(this, "removeExtras: called on TransactionalService. doing nothing");
            } else if (mConnectionService != null) {
                mConnectionService.onExtrasChanged(this, mExtras);
            } else {
                Log.e(this, new NullPointerException(),
@@ -2924,7 +2972,9 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
    }

    void postDialContinue(boolean proceed) {
        if (mConnectionService != null) {
        if (mTransactionalService != null) {
            Log.i(this, "postDialContinue: called on TransactionalService. doing nothing");
        } else if (mConnectionService != null) {
            mConnectionService.onPostDialContinue(this, proceed);
        } else {
            Log.e(this, new NullPointerException(),
@@ -2933,7 +2983,9 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
    }

    void conferenceWith(Call otherCall) {
        if (mConnectionService == null) {
        if (mTransactionalService != null) {
            Log.i(this, "conferenceWith: called on TransactionalService. doing nothing");
        } else if (mConnectionService == null) {
            Log.w(this, "conference requested on a call without a connection service.");
        } else {
            Log.addEvent(this, LogUtils.Events.CONFERENCE_WITH, otherCall);
@@ -2942,7 +2994,9 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
    }

    void splitFromConference() {
        if (mConnectionService == null) {
        if (mTransactionalService != null) {
            Log.i(this, "splitFromConference: called on TransactionalService. doing nothing");
        } else if (mConnectionService == null) {
            Log.w(this, "splitting from conference call without a connection service");
        } else {
            Log.addEvent(this, LogUtils.Events.SPLIT_FROM_CONFERENCE);
@@ -2952,7 +3006,9 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,

    @VisibleForTesting
    public void mergeConference() {
        if (mConnectionService == null) {
        if (mTransactionalService != null) {
            Log.i(this, "mergeConference: called on TransactionalService. doing nothing");
        } else if (mConnectionService == null) {
            Log.w(this, "merging conference calls without a connection service.");
        } else if (can(Connection.CAPABILITY_MERGE_CONFERENCE)) {
            Log.addEvent(this, LogUtils.Events.CONFERENCE_WITH);
@@ -2963,7 +3019,9 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,

    @VisibleForTesting
    public void swapConference() {
        if (mConnectionService == null) {
        if (mTransactionalService != null) {
            Log.i(this, "swapConference: called on TransactionalService. doing nothing");
        } else if (mConnectionService == null) {
            Log.w(this, "swapping conference calls without a connection service.");
        } else if (can(Connection.CAPABILITY_SWAP_CONFERENCE)) {
            Log.addEvent(this, LogUtils.Events.SWAP);
@@ -2989,7 +3047,9 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
    }

    public void addConferenceParticipants(List<Uri> participants) {
        if (mConnectionService == null) {
        if (mTransactionalService != null) {
            Log.i(this, "addConferenceParticipants: called on TransactionalService. doing nothing");
        } else if (mConnectionService == null) {
            Log.w(this, "adding conference participants without a connection service.");
        } else if (can(Connection.CAPABILITY_ADD_PARTICIPANT)) {
            Log.addEvent(this, LogUtils.Events.ADD_PARTICIPANT);
@@ -3017,6 +3077,11 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
     * If there is an ongoing emergency call, pull requests are also ignored.
     */
    public void pullExternalCall() {
        if (mTransactionalService != null) {
            Log.i(this, "transfer: called on TransactionalService. doing nothing");
            return;
        }

        if (mConnectionService == null) {
            Log.w(this, "pulling a call without a connection service.");
        }
@@ -3064,7 +3129,9 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
     * @param extras Associated extras.
     */
    public void sendCallEvent(String event, int targetSdkVer, Bundle extras) {
        if (mConnectionService != null) {
        if (mTransactionalService != null) {
            Log.i(this, "sendCallEvent: called on TransactionalService. doing nothing");
        } else if (mConnectionService != null) {
            if (android.telecom.Call.EVENT_REQUEST_HANDOVER.equals(event)) {
                if (targetSdkVer > Build.VERSION_CODES.P) {
                    Log.e(this, new Exception(), "sendCallEvent failed. Use public api handoverTo" +
@@ -3497,7 +3564,9 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
    }

    public void stopRtt() {
        if (mConnectionService != null) {
        if (mTransactionalService != null) {
            Log.i(this, "stopRtt: called on TransactionalService. doing nothing");
        } else if (mConnectionService != null) {
            mConnectionService.stopRtt(this);
        } else {
            // If this gets called by the in-call app before the connection service is set, we'll
@@ -3507,6 +3576,10 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
    }

    public void sendRttRequest() {
        if (mTransactionalService != null) {
            Log.i(this, "sendRttRequest: called on TransactionalService. doing nothing");
            return;
        }
        createRttStreams();
        mConnectionService.startRtt(this, getInCallToCsRttPipeForCs(), getCsToInCallRttPipeForCs());
    }
@@ -3556,6 +3629,10 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
            Log.w(this, "Response ID %d does not match expected %d", id, mPendingRttRequestId);
            return;
        }
        if (mTransactionalService != null) {
            Log.i(this, "handleRttRequestResponse: called on TransactionalService. doing nothing");
            return;
        }
        if (accept) {
            createRttStreams();
            Log.i(this, "RTT request %d accepted.", id);
@@ -4223,6 +4300,11 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
    }

    public void maybeOnInCallServiceTrackingChanged(boolean isTracking, boolean hasUi) {
        if (mTransactionalService != null) {
            Log.i(this,
                    "maybeOnInCallServiceTrackingChanged: called on TransactionalService");
            return;
        }
        if (mConnectionService == null) {
            Log.w(this, "maybeOnInCallServiceTrackingChanged() request on a call"
                    + " without a connection service.");
+6 −1
Original line number Diff line number Diff line
@@ -1775,10 +1775,15 @@ public class CallAudioRouteStateMachine extends StateMachine {
        Set<Call> calls = mCallsManager.getTrackedCalls();
        for (Call call : calls) {
            if (call != null && call.getConnectionService() != null) {
                if (call.isTransactionalCall() && call.getTransactionServiceWrapper() != null) {
                    call.getTransactionServiceWrapper().onCallAudioStateChanged(call,
                            newCallAudioState);
                } else {
                    call.getConnectionService().onCallAudioStateChanged(call, newCallAudioState);
                }
            }
        }
    }

    private int calculateSupportedRoutes() {
        int routeMask = CallAudioState.ROUTE_SPEAKER;
+148 −10

File changed.

Preview size limit exceeded, changes collapsed.

+5 −2
Original line number Diff line number Diff line
@@ -154,7 +154,8 @@ public class ConnectionServiceFocusManager {
    }

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

    private static final int MSG_REQUEST_FOCUS = 1;
@@ -348,13 +349,14 @@ public class ConnectionServiceFocusManager {
    public List<CallFocus> getAllCall() { return mCalls; }

    private void updateConnectionServiceFocus(ConnectionServiceFocus connSvrFocus) {
        Log.i(this, "updateConnectionServiceFocus connSvr = %s", connSvrFocus);
        if (!Objects.equals(mCurrentFocus, connSvrFocus)) {
            if (connSvrFocus != null) {
                connSvrFocus.setConnectionServiceFocusListener(mConnectionServiceFocusListener);
                connSvrFocus.connectionServiceFocusGained();
            }
            mCurrentFocus = connSvrFocus;
            Log.d(this, "updateConnectionServiceFocus connSvr = %s", connSvrFocus);
            Log.i(this, "updateConnectionServiceFocus connSvr = %s", connSvrFocus);
        }
    }

@@ -362,6 +364,7 @@ public class ConnectionServiceFocusManager {
        mCurrentFocusCall = null;

        if (mCurrentFocus == null) {
            Log.d(this, "updateCurrentFocusCall: mCurrentFocus is null");
            return;
        }

+25 −7
Original line number Diff line number Diff line
@@ -41,7 +41,6 @@ import android.os.UserManager;
import android.provider.Settings;
import android.telecom.CallAudioState;
import android.telecom.ConnectionService;
import android.telecom.DefaultDialerManager;
import android.telecom.Log;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
@@ -58,7 +57,6 @@ import android.util.Xml;

// TODO: Needed for move to system service: import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.XmlUtils;

@@ -66,7 +64,6 @@ import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
@@ -85,12 +82,9 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * Handles writing and reading PhoneAccountHandle registration entries. This is a simple verbatim
@@ -859,7 +853,8 @@ public class PhoneAccountRegistrar {
    public void registerPhoneAccount(PhoneAccount account) {
        // Enforce the requirement that a connection service for a phone account has the correct
        // permission.
        if (!phoneAccountRequiresBindPermission(account.getAccountHandle())) {
        if (!hasTransactionalCallCapabilites(account) &&
                !phoneAccountRequiresBindPermission(account.getAccountHandle())) {
            Log.w(this,
                    "Phone account %s does not have BIND_TELECOM_CONNECTION_SERVICE permission.",
                    account.getAccountHandle());
@@ -898,6 +893,15 @@ public class PhoneAccountRegistrar {
        boolean isEnabled = false;
        boolean isNewAccount;

        // add self-managed capability for transactional accounts that are missing it
        if (hasTransactionalCallCapabilites(account) &&
                !account.hasCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)) {
            account = account.toBuilder()
                    .setCapabilities(account.getCapabilities()
                            | PhoneAccount.CAPABILITY_SELF_MANAGED)
                    .build();
        }

        PhoneAccount oldAccount = getPhoneAccountUnchecked(account.getAccountHandle());
        if (oldAccount != null) {
            enforceSelfManagedAccountUnmodified(account, oldAccount);
@@ -1196,6 +1200,11 @@ public class PhoneAccountRegistrar {
            Log.w(this, "phoneAccount %s not found", phoneAccountHandle.getComponentName());
            return false;
        }

        if (hasTransactionalCallCapabilites(getPhoneAccountUnchecked(phoneAccountHandle))) {
            return false;
        }

        for (ResolveInfo resolveInfo : resolveInfos) {
            ServiceInfo serviceInfo = resolveInfo.serviceInfo;
            if (serviceInfo == null) {
@@ -1214,6 +1223,15 @@ public class PhoneAccountRegistrar {
        return true;
    }

    @VisibleForTesting
    public boolean hasTransactionalCallCapabilites(PhoneAccount phoneAccount) {
        if (phoneAccount == null) {
            return false;
        }
        return phoneAccount.hasCapabilities(
                PhoneAccount.CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS);
    }

    //
    // Methods for retrieving PhoneAccounts and PhoneAccountHandles
    //
Loading