FileDocCategorySizeDatePackage
ConnectionService.javaAPI DocAndroid 5.1 API48557Thu Mar 12 22:22:42 GMT 2015android.telecom

ConnectionService

public abstract class ConnectionService extends android.app.Service
{@code ConnectionService} is an abstract service that should be implemented by any app which can make phone calls and want those calls to be integrated into the built-in phone app. Once implemented, the {@code ConnectionService} needs two additional steps before it will be integrated into the phone app:

1. Registration in AndroidManifest.xml

<service android:name="com.example.package.MyConnectionService"
android:label="@string/some_label_for_my_connection_service"
android:permission="android.permission.BIND_CONNECTION_SERVICE">
<intent-filter>
<action android:name="android.telecom.ConnectionService" />
</intent-filter>
</service>

2. Registration of {@link PhoneAccount} with {@link TelecomManager}.
See {@link PhoneAccount} and {@link TelecomManager#registerPhoneAccount} for more information.

Once registered and enabled by the user in the dialer settings, telecom will bind to a {@code ConnectionService} implementation when it wants that {@code ConnectionService} to place a call or the service has indicated that is has an incoming call through {@link TelecomManager#addNewIncomingCall}. The {@code ConnectionService} can then expect a call to {@link #onCreateIncomingConnection} or {@link #onCreateOutgoingConnection} wherein it should provide a new instance of a {@link Connection} object. It is through this {@link Connection} object that telecom receives state updates and the {@code ConnectionService} receives call-commands such as answer, reject, hold and disconnect.

When there are no more live calls, telecom will unbind from the {@code ConnectionService}.

hide

Fields Summary
public static final String
SERVICE_INTERFACE
The {@link Intent} that must be declared as handled by the service.
private static final boolean
PII_DEBUG
private static final int
MSG_ADD_CONNECTION_SERVICE_ADAPTER
private static final int
MSG_CREATE_CONNECTION
private static final int
MSG_ABORT
private static final int
MSG_ANSWER
private static final int
MSG_REJECT
private static final int
MSG_DISCONNECT
private static final int
MSG_HOLD
private static final int
MSG_UNHOLD
private static final int
MSG_ON_AUDIO_STATE_CHANGED
private static final int
MSG_PLAY_DTMF_TONE
private static final int
MSG_STOP_DTMF_TONE
private static final int
MSG_CONFERENCE
private static final int
MSG_SPLIT_FROM_CONFERENCE
private static final int
MSG_ON_POST_DIAL_CONTINUE
private static final int
MSG_REMOVE_CONNECTION_SERVICE_ADAPTER
private static final int
MSG_ANSWER_VIDEO
private static final int
MSG_MERGE_CONFERENCE
private static final int
MSG_SWAP_CONFERENCE
private static Connection
sNullConnection
private final Map
mConnectionById
private final Map
mIdByConnection
private final Map
mConferenceById
private final Map
mIdByConference
private final RemoteConnectionManager
mRemoteConnectionManager
private final List
mPreInitializationConnectionRequests
private final ConnectionServiceAdapter
mAdapter
private boolean
mAreAccountsInitialized
private Conference
sNullConference
private final android.os.IBinder
mBinder
private final android.os.Handler
mHandler
private final Conference.Listener
mConferenceListener
private final Connection.Listener
mConnectionListener
Constructors Summary
Methods Summary
private voidabort(java.lang.String callId)

        Log.d(this, "abort %s", callId);
        findConnectionForAction(callId, "abort").onAbort();
    
public final voidaddConference(Conference conference)
Adds a new conference call. When a conference call is created either as a result of an explicit request via {@link #onConference} or otherwise, the connection service should supply an instance of {@link Conference} by invoking this method. A conference call provided by this method will persist until {@link Conference#destroy} is invoked on the conference instance.

param
conference The new conference object.

        String id = addConferenceInternal(conference);
        if (id != null) {
            List<String> connectionIds = new ArrayList<>(2);
            for (Connection connection : conference.getConnections()) {
                if (mIdByConnection.containsKey(connection)) {
                    connectionIds.add(mIdByConnection.get(connection));
                }
            }
            ParcelableConference parcelableConference = new ParcelableConference(
                    conference.getPhoneAccountHandle(),
                    conference.getState(),
                    conference.getConnectionCapabilities(),
                    connectionIds,
                    conference.getConnectTimeMillis());
            mAdapter.addConferenceCall(id, parcelableConference);

            // Go through any child calls and set the parent.
            for (Connection connection : conference.getConnections()) {
                String connectionId = mIdByConnection.get(connection);
                if (connectionId != null) {
                    mAdapter.setIsConferenced(connectionId, id);
                }
            }
        }
    
private java.lang.StringaddConferenceInternal(Conference conference)

        if (mIdByConference.containsKey(conference)) {
            Log.w(this, "Re-adding an existing conference: %s.", conference);
        } else if (conference != null) {
            String id = UUID.randomUUID().toString();
            mConferenceById.put(id, conference);
            mIdByConference.put(conference, id);
            conference.addListener(mConferenceListener);
            return id;
        }

        return null;
    
private voidaddConnection(java.lang.String callId, Connection connection)

        mConnectionById.put(callId, connection);
        mIdByConnection.put(connection, callId);
        connection.addConnectionListener(mConnectionListener);
        connection.setConnectionService(this);
    
public final voidaddExistingConnection(PhoneAccountHandle phoneAccountHandle, Connection connection)
Adds a connection created by the {@link ConnectionService} and informs telecom of the new connection.

param
phoneAccountHandle The phone account handle for the connection.
param
connection The connection to add.


        String id = addExistingConnectionInternal(connection);
        if (id != null) {
            List<String> emptyList = new ArrayList<>(0);

            ParcelableConnection parcelableConnection = new ParcelableConnection(
                    phoneAccountHandle,
                    connection.getState(),
                    connection.getConnectionCapabilities(),
                    connection.getAddress(),
                    connection.getAddressPresentation(),
                    connection.getCallerDisplayName(),
                    connection.getCallerDisplayNamePresentation(),
                    connection.getVideoProvider() == null ?
                            null : connection.getVideoProvider().getInterface(),
                    connection.getVideoState(),
                    connection.isRingbackRequested(),
                    connection.getAudioModeIsVoip(),
                    connection.getStatusHints(),
                    connection.getDisconnectCause(),
                    emptyList);
            mAdapter.addExistingConnection(id, parcelableConnection);
        }
    
private java.lang.StringaddExistingConnectionInternal(Connection connection)
Adds an existing connection to the list of connections, identified by a new UUID.

param
connection The connection.
return
The UUID of the connection (e.g. the call-id).

        String id = UUID.randomUUID().toString();
        addConnection(id, connection);
        return id;
    
voidaddRemoteConference(RemoteConference remoteConference)
{@hide}

        onRemoteConferenceAdded(remoteConference);
    
voidaddRemoteExistingConnection(RemoteConnection remoteConnection)
{@hide}

        onRemoteExistingConnectionAdded(remoteConnection);
    
private voidanswer(java.lang.String callId)

        Log.d(this, "answer %s", callId);
        findConnectionForAction(callId, "answer").onAnswer();
    
private voidanswerVideo(java.lang.String callId, int videoState)

        Log.d(this, "answerVideo %s", callId);
        findConnectionForAction(callId, "answer").onAnswer(videoState);
    
private voidconference(java.lang.String callId1, java.lang.String callId2)

        Log.d(this, "conference %s, %s", callId1, callId2);

        // Attempt to get second connection or conference.
        Connection connection2 = findConnectionForAction(callId2, "conference");
        Conference conference2 = getNullConference();
        if (connection2 == getNullConnection()) {
            conference2 = findConferenceForAction(callId2, "conference");
            if (conference2 == getNullConference()) {
                Log.w(this, "Connection2 or Conference2 missing in conference request %s.",
                        callId2);
                return;
            }
        }

        // Attempt to get first connection or conference and perform merge.
        Connection connection1 = findConnectionForAction(callId1, "conference");
        if (connection1 == getNullConnection()) {
            Conference conference1 = findConferenceForAction(callId1, "addConnection");
            if (conference1 == getNullConference()) {
                Log.w(this,
                        "Connection1 or Conference1 missing in conference request %s.",
                        callId1);
            } else {
                // Call 1 is a conference.
                if (connection2 != getNullConnection()) {
                    // Call 2 is a connection so merge via call 1 (conference).
                    conference1.onMerge(connection2);
                } else {
                    // Call 2 is ALSO a conference; this should never happen.
                    Log.wtf(this, "There can only be one conference and an attempt was made to " +
                            "merge two conferences.");
                    return;
                }
            }
        } else {
            // Call 1 is a connection.
            if (conference2 != getNullConference()) {
                // Call 2 is a conference, so merge via call 2.
                conference2.onMerge(connection1);
            } else {
                // Call 2 is a connection, so merge together.
                onConference(connection1, connection2);
            }
        }
    
public final voidconferenceRemoteConnections(RemoteConnection remoteConnection1, RemoteConnection remoteConnection2)
Indicates to the relevant {@code RemoteConnectionService} that the specified {@link RemoteConnection}s should be merged into a conference call.

If the conference request is successful, the method {@link #onRemoteConferenceAdded} will be invoked.

param
remoteConnection1 The first of the remote connections to conference.
param
remoteConnection2 The second of the remote connections to conference.

        mRemoteConnectionManager.conferenceRemoteConnections(remoteConnection1, remoteConnection2);
    
public booleancontainsConference(Conference conference)

hide

        return mIdByConference.containsKey(conference);
    
private voidcreateConnection(PhoneAccountHandle callManagerAccount, java.lang.String callId, ConnectionRequest request, boolean isIncoming, boolean isUnknown)
This can be used by telecom to either create a new outgoing call or attach to an existing incoming call. In either case, telecom will cycle through a set of services and call createConnection util a connection service cancels the process or completes it successfully.

        Log.d(this, "createConnection, callManagerAccount: %s, callId: %s, request: %s, " +
                "isIncoming: %b, isUnknown: %b", callManagerAccount, callId, request, isIncoming,
                isUnknown);

        Connection connection = isUnknown ? onCreateUnknownConnection(callManagerAccount, request)
                : isIncoming ? onCreateIncomingConnection(callManagerAccount, request)
                : onCreateOutgoingConnection(callManagerAccount, request);
        Log.d(this, "createConnection, connection: %s", connection);
        if (connection == null) {
            connection = Connection.createFailedConnection(
                    new DisconnectCause(DisconnectCause.ERROR));
        }

        if (connection.getState() != Connection.STATE_DISCONNECTED) {
            addConnection(callId, connection);
        }

        Uri address = connection.getAddress();
        String number = address == null ? "null" : address.getSchemeSpecificPart();
        Log.v(this, "createConnection, number: %s, state: %s, capabilities: %s",
                Connection.toLogSafePhoneNumber(number),
                Connection.stateToString(connection.getState()),
                Connection.capabilitiesToString(connection.getConnectionCapabilities()));

        Log.d(this, "createConnection, calling handleCreateConnectionSuccessful %s", callId);
        mAdapter.handleCreateConnectionComplete(
                callId,
                request,
                new ParcelableConnection(
                        request.getAccountHandle(),
                        connection.getState(),
                        connection.getConnectionCapabilities(),
                        connection.getAddress(),
                        connection.getAddressPresentation(),
                        connection.getCallerDisplayName(),
                        connection.getCallerDisplayNamePresentation(),
                        connection.getVideoProvider() == null ?
                                null : connection.getVideoProvider().getInterface(),
                        connection.getVideoState(),
                        connection.isRingbackRequested(),
                        connection.getAudioModeIsVoip(),
                        connection.getStatusHints(),
                        connection.getDisconnectCause(),
                        createIdList(connection.getConferenceables())));
    
private java.util.ListcreateConnectionIdList(java.util.List connections)

        List<String> ids = new ArrayList<>();
        for (Connection c : connections) {
            if (mIdByConnection.containsKey(c)) {
                ids.add(mIdByConnection.get(c));
            }
        }
        Collections.sort(ids);
        return ids;
    
private java.util.ListcreateIdList(java.util.List conferenceables)
Builds a list of {@link Connection} and {@link Conference} IDs based on the list of {@link IConferenceable}s passed in.

param
conferenceables The {@link IConferenceable} connections and conferences.
return
List of string conference and call Ids.

        List<String> ids = new ArrayList<>();
        for (IConferenceable c : conferenceables) {
            // Only allow Connection and Conference conferenceables.
            if (c instanceof Connection) {
                Connection connection = (Connection) c;
                if (mIdByConnection.containsKey(connection)) {
                    ids.add(mIdByConnection.get(connection));
                }
            } else if (c instanceof Conference) {
                Conference conference = (Conference) c;
                if (mIdByConference.containsKey(conference)) {
                    ids.add(mIdByConference.get(conference));
                }
            }
        }
        Collections.sort(ids);
        return ids;
    
public final RemoteConnectioncreateRemoteIncomingConnection(PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)
Ask some other {@code ConnectionService} to create a {@code RemoteConnection} given an incoming request. This is used by {@code ConnectionService}s that are registered with {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER} and want to be able to manage SIM-based incoming calls.

param
connectionManagerPhoneAccount See description at {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}.
param
request Details about the incoming call.
return
The {@code Connection} object to satisfy this call, or {@code null} to not handle the call.

        return mRemoteConnectionManager.createRemoteConnection(
                connectionManagerPhoneAccount, request, true);
    
public final RemoteConnectioncreateRemoteOutgoingConnection(PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)
Ask some other {@code ConnectionService} to create a {@code RemoteConnection} given an outgoing request. This is used by {@code ConnectionService}s that are registered with {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER} and want to be able to use the SIM-based {@code ConnectionService} to place its outgoing calls.

param
connectionManagerPhoneAccount See description at {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}.
param
request Details about the incoming call.
return
The {@code Connection} object to satisfy this call, or {@code null} to not handle the call.

        return mRemoteConnectionManager.createRemoteConnection(
                connectionManagerPhoneAccount, request, false);
    
private voiddisconnect(java.lang.String callId)

        Log.d(this, "disconnect %s", callId);
        if (mConnectionById.containsKey(callId)) {
            findConnectionForAction(callId, "disconnect").onDisconnect();
        } else {
            findConferenceForAction(callId, "disconnect").onDisconnect();
        }
    
private voidendAllConnections()

        // Unbound from telecomm.  We should end all connections and conferences.
        for (Connection connection : mIdByConnection.keySet()) {
            // only operate on top-level calls. Conference calls will be removed on their own.
            if (connection.getConference() == null) {
                connection.onDisconnect();
            }
        }
        for (Conference conference : mIdByConference.keySet()) {
            conference.onDisconnect();
        }
    
private ConferencefindConferenceForAction(java.lang.String conferenceId, java.lang.String action)

        if (mConferenceById.containsKey(conferenceId)) {
            return mConferenceById.get(conferenceId);
        }
        Log.w(this, "%s - Cannot find conference %s", action, conferenceId);
        return getNullConference();
    
private ConnectionfindConnectionForAction(java.lang.String callId, java.lang.String action)

        if (mConnectionById.containsKey(callId)) {
            return mConnectionById.get(callId);
        }
        Log.w(this, "%s - Cannot find Connection %s", action, callId);
        return getNullConnection();
    
public final java.util.CollectiongetAllConnections()
Returns all the active {@code Connection}s for which this {@code ConnectionService} has taken responsibility.

return
A collection of {@code Connection}s created by this {@code ConnectionService}.

        return mConnectionById.values();
    
private ConferencegetNullConference()

        if (sNullConference == null) {
            sNullConference = new Conference(null) {};
        }
        return sNullConference;
    
static synchronized ConnectiongetNullConnection()

        if (sNullConnection == null) {
            sNullConnection = new Connection() {};
        }
        return sNullConnection;
    
private voidhold(java.lang.String callId)

        Log.d(this, "hold %s", callId);
        if (mConnectionById.containsKey(callId)) {
            findConnectionForAction(callId, "hold").onHold();
        } else {
            findConferenceForAction(callId, "hold").onHold();
        }
    
private voidmergeConference(java.lang.String callId)

        Log.d(this, "mergeConference(%s)", callId);
        Conference conference = findConferenceForAction(callId, "mergeConference");
        if (conference != null) {
            conference.onMerge();
        }
    
private voidonAccountsInitialized()

        mAreAccountsInitialized = true;
        for (Runnable r : mPreInitializationConnectionRequests) {
            r.run();
        }
        mPreInitializationConnectionRequests.clear();
    
private voidonAdapterAttached()

        if (mAreAccountsInitialized) {
            // No need to query again if we already did it.
            return;
        }

        mAdapter.queryRemoteConnectionServices(new RemoteServiceCallback.Stub() {
            @Override
            public void onResult(
                    final List<ComponentName> componentNames,
                    final List<IBinder> services) {
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        for (int i = 0; i < componentNames.size() && i < services.size(); i++) {
                            mRemoteConnectionManager.addConnectionService(
                                    componentNames.get(i),
                                    IConnectionService.Stub.asInterface(services.get(i)));
                        }
                        onAccountsInitialized();
                        Log.d(this, "remote connection services found: " + services);
                    }
                });
            }

            @Override
            public void onError() {
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        mAreAccountsInitialized = true;
                    }
                });
            }
        });
    
private voidonAudioStateChanged(java.lang.String callId, AudioState audioState)

        Log.d(this, "onAudioStateChanged %s %s", callId, audioState);
        if (mConnectionById.containsKey(callId)) {
            findConnectionForAction(callId, "onAudioStateChanged").setAudioState(audioState);
        } else {
            findConferenceForAction(callId, "onAudioStateChanged").setAudioState(audioState);
        }
    
public final android.os.IBinderonBind(android.content.Intent intent)
{@inheritDoc}


      
    
         
        return mBinder;
    
public voidonConference(Connection connection1, Connection connection2)
Conference two specified connections. Invoked when the user has made a request to merge the specified connections into a conference call. In response, the connection service should create an instance of {@link Conference} and pass it into {@link #addConference}.

param
connection1 A connection to merge into a conference call.
param
connection2 A connection to merge into a conference call.

public ConnectiononCreateIncomingConnection(PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)
Create a {@code Connection} given an incoming request. This is used to attach to existing incoming calls.

param
connectionManagerPhoneAccount See description at {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}.
param
request Details about the incoming call.
return
The {@code Connection} object to satisfy this call, or {@code null} to not handle the call.

        return null;
    
public ConnectiononCreateOutgoingConnection(PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)
Create a {@code Connection} given an outgoing request. This is used to initiate new outgoing calls.

param
connectionManagerPhoneAccount The connection manager account to use for managing this call.

If this parameter is not {@code null}, it means that this {@code ConnectionService} has registered one or more {@code PhoneAccount}s having {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER}. This parameter will contain one of these {@code PhoneAccount}s, while the {@code request} will contain another (usually but not always distinct) {@code PhoneAccount} to be used for actually making the connection.

If this parameter is {@code null}, it means that this {@code ConnectionService} is being asked to make a direct connection. The {@link ConnectionRequest#getAccountHandle()} of parameter {@code request} will be a {@code PhoneAccount} registered by this {@code ConnectionService} to use for making the connection.

param
request Details about the outgoing call.
return
The {@code Connection} object to satisfy this call, or the result of an invocation of {@link Connection#createFailedConnection(DisconnectCause)} to not handle the call.

        return null;
    
public ConnectiononCreateUnknownConnection(PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)
Create a {@code Connection} for a new unknown call. An unknown call is a call originating from the ConnectionService that was neither a user-initiated outgoing call, nor an incoming call created using {@code TelecomManager#addNewIncomingCall(PhoneAccountHandle, android.os.Bundle)}.

param
connectionManagerPhoneAccount
param
request
return
hide

       return null;
    
private voidonPostDialContinue(java.lang.String callId, boolean proceed)

        Log.d(this, "onPostDialContinue(%s)", callId);
        findConnectionForAction(callId, "stopDtmfTone").onPostDialContinue(proceed);
    
public voidonRemoteConferenceAdded(RemoteConference conference)
Indicates that a remote conference has been created for existing {@link RemoteConnection}s. When this method is invoked, this {@link ConnectionService} should create its own representation of the conference call and send it to telecom using {@link #addConference}.

This is only relevant to {@link ConnectionService}s which are registered with {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER}.

param
conference The remote conference call.

public voidonRemoteExistingConnectionAdded(RemoteConnection connection)
Called when an existing connection is added remotely.

param
connection The existing connection which was added.

public booleanonUnbind(android.content.Intent intent)
{@inheritDoc}

        endAllConnections();
        return super.onUnbind(intent);
    
private voidplayDtmfTone(java.lang.String callId, char digit)

        Log.d(this, "playDtmfTone %s %c", callId, digit);
        if (mConnectionById.containsKey(callId)) {
            findConnectionForAction(callId, "playDtmfTone").onPlayDtmfTone(digit);
        } else {
            findConferenceForAction(callId, "playDtmfTone").onPlayDtmfTone(digit);
        }
    
private voidreject(java.lang.String callId)

        Log.d(this, "reject %s", callId);
        findConnectionForAction(callId, "reject").onReject();
    
private voidremoveConference(Conference conference)

        if (mIdByConference.containsKey(conference)) {
            conference.removeListener(mConferenceListener);

            String id = mIdByConference.get(conference);
            mConferenceById.remove(id);
            mIdByConference.remove(conference);
            mAdapter.removeCall(id);
        }
    
protected voidremoveConnection(Connection connection)
{@hide}

        String id = mIdByConnection.get(connection);
        connection.unsetConnectionService(this);
        connection.removeConnectionListener(mConnectionListener);
        mConnectionById.remove(mIdByConnection.get(connection));
        mIdByConnection.remove(connection);
        mAdapter.removeCall(id);
    
private voidsplitFromConference(java.lang.String callId)

        Log.d(this, "splitFromConference(%s)", callId);

        Connection connection = findConnectionForAction(callId, "splitFromConference");
        if (connection == getNullConnection()) {
            Log.w(this, "Connection missing in conference request %s.", callId);
            return;
        }

        Conference conference = connection.getConference();
        if (conference != null) {
            conference.onSeparate(connection);
        }
    
private voidstopDtmfTone(java.lang.String callId)

        Log.d(this, "stopDtmfTone %s", callId);
        if (mConnectionById.containsKey(callId)) {
            findConnectionForAction(callId, "stopDtmfTone").onStopDtmfTone();
        } else {
            findConferenceForAction(callId, "stopDtmfTone").onStopDtmfTone();
        }
    
private voidswapConference(java.lang.String callId)

        Log.d(this, "swapConference(%s)", callId);
        Conference conference = findConferenceForAction(callId, "swapConference");
        if (conference != null) {
            conference.onSwap();
        }
    
private voidunhold(java.lang.String callId)

        Log.d(this, "unhold %s", callId);
        if (mConnectionById.containsKey(callId)) {
            findConnectionForAction(callId, "unhold").onUnhold();
        } else {
            findConferenceForAction(callId, "unhold").onUnhold();
        }