/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.telecom;

import android.annotation.SdkConstant;
import android.app.Service;
import android.content.ComponentName;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.telecom.Logging.Session;

import com.android.internal.os.SomeArgs;
import com.android.internal.telecom.IConnectionService;
import com.android.internal.telecom.IConnectionServiceAdapter;
import com.android.internal.telecom.RemoteServiceCallback;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

An abstract service that should be implemented by any apps which either:
  1. Can make phone calls (VoIP or otherwise) and want those calls to be integrated into the built-in phone app. Referred to as a system managed ConnectionService.
  2. Are a standalone calling app and don't want their calls to be integrated into the built-in phone app. Referred to as a self managed ConnectionService.
Once implemented, the ConnectionService needs to take the following steps so that Telecom will bind to it:

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_TELECOM_CONNECTION_SERVICE">
 <intent-filter>
  <action android:name="android.telecom.ConnectionService" />
 </intent-filter>
</service>

2. Registration of PhoneAccount with TelecomManager.
See PhoneAccount and TelecomManager.registerPhoneAccount for more information.

System managed ConnectionServices must be enabled by the user in the phone app settings before Telecom will bind to them. Self-managed ConnectionServices must be granted the appropriate permission before Telecom will bind to them.

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

When there are no more live calls, telecom will unbind from the ConnectionService.

/** * An abstract service that should be implemented by any apps which either: * <ol> * <li>Can make phone calls (VoIP or otherwise) and want those calls to be integrated into the * built-in phone app. Referred to as a <b>system managed</b> {@link ConnectionService}.</li> * <li>Are a standalone calling app and don't want their calls to be integrated into the * built-in phone app. Referred to as a <b>self managed</b> {@link ConnectionService}.</li> * </ol> * Once implemented, the {@link ConnectionService} needs to take the following steps so that Telecom * will bind to it: * <p> * 1. <i>Registration in AndroidManifest.xml</i> * <br/> * <pre> * &lt;service android:name="com.example.package.MyConnectionService" * android:label="@string/some_label_for_my_connection_service" * android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"&gt; * &lt;intent-filter&gt; * &lt;action android:name="android.telecom.ConnectionService" /&gt; * &lt;/intent-filter&gt; * &lt;/service&gt; * </pre> * <p> * 2. <i> Registration of {@link PhoneAccount} with {@link TelecomManager}.</i> * <br/> * See {@link PhoneAccount} and {@link TelecomManager#registerPhoneAccount} for more information. * <p> * System managed {@link ConnectionService}s must be enabled by the user in the phone app settings * before Telecom will bind to them. Self-managed {@link ConnectionService}s must be granted the * appropriate permission before Telecom will bind to them. * <p> * Once registered and enabled by the user in the phone app settings or granted permission, telecom * will bind to a {@link ConnectionService} implementation when it wants that * {@link ConnectionService} to place a call or the service has indicated that is has an incoming * call through {@link TelecomManager#addNewIncomingCall}. The {@link 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 {@link ConnectionService} * receives call-commands such as answer, reject, hold and disconnect. * <p> * When there are no more live calls, telecom will unbind from the {@link ConnectionService}. */
public abstract class ConnectionService extends Service {
The Intent that must be declared as handled by the service.
/** * The {@link Intent} that must be declared as handled by the service. */
@SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) public static final String SERVICE_INTERFACE = "android.telecom.ConnectionService";
Boolean extra used by Telecom to inform a ConnectionService that the purpose of it being asked to create a new outgoing Connection is to perform a handover of an ongoing call on the device from another PhoneAccount/ConnectionService. Will be specified in the ConnectionRequest.getExtras() passed by Telecom when onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest) is called.

When your ConnectionService receives this extra, it should communicate the fact that this is a handover to the other device's matching ConnectionService. That ConnectionService will continue the handover using TelecomManager.addNewIncomingCall(PhoneAccountHandle, Bundle), specifying TelecomManager.EXTRA_IS_HANDOVER. Telecom will match the phone numbers of the handover call on the other device with ongoing calls for ConnectionServices which support PhoneAccount.EXTRA_SUPPORTS_HANDOVER_FROM.

@hide
/** * Boolean extra used by Telecom to inform a {@link ConnectionService} that the purpose of it * being asked to create a new outgoing {@link Connection} is to perform a handover of an * ongoing call on the device from another {@link PhoneAccount}/{@link ConnectionService}. Will * be specified in the {@link ConnectionRequest#getExtras()} passed by Telecom when * {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)} is called. * <p> * When your {@link ConnectionService} receives this extra, it should communicate the fact that * this is a handover to the other device's matching {@link ConnectionService}. That * {@link ConnectionService} will continue the handover using * {@link TelecomManager#addNewIncomingCall(PhoneAccountHandle, Bundle)}, specifying * {@link TelecomManager#EXTRA_IS_HANDOVER}. Telecom will match the phone numbers of the * handover call on the other device with ongoing calls for {@link ConnectionService}s which * support {@link PhoneAccount#EXTRA_SUPPORTS_HANDOVER_FROM}. * @hide */
public static final String EXTRA_IS_HANDOVER = TelecomManager.EXTRA_IS_HANDOVER; // Flag controlling whether PII is emitted into the logs private static final boolean PII_DEBUG = Log.isLoggable(android.util.Log.DEBUG); // Session Definitions private static final String SESSION_HANDLER = "H."; private static final String SESSION_ADD_CS_ADAPTER = "CS.aCSA"; private static final String SESSION_REMOVE_CS_ADAPTER = "CS.rCSA"; private static final String SESSION_CREATE_CONN = "CS.crCo"; private static final String SESSION_CREATE_CONN_COMPLETE = "CS.crCoC"; private static final String SESSION_CREATE_CONN_FAILED = "CS.crCoF"; private static final String SESSION_ABORT = "CS.ab"; private static final String SESSION_ANSWER = "CS.an"; private static final String SESSION_ANSWER_VIDEO = "CS.anV"; private static final String SESSION_DEFLECT = "CS.def"; private static final String SESSION_REJECT = "CS.r"; private static final String SESSION_REJECT_MESSAGE = "CS.rWM"; private static final String SESSION_SILENCE = "CS.s"; private static final String SESSION_DISCONNECT = "CS.d"; private static final String SESSION_HOLD = "CS.h"; private static final String SESSION_UNHOLD = "CS.u"; private static final String SESSION_CALL_AUDIO_SC = "CS.cASC"; private static final String SESSION_PLAY_DTMF = "CS.pDT"; private static final String SESSION_STOP_DTMF = "CS.sDT"; private static final String SESSION_CONFERENCE = "CS.c"; private static final String SESSION_SPLIT_CONFERENCE = "CS.sFC"; private static final String SESSION_MERGE_CONFERENCE = "CS.mC"; private static final String SESSION_SWAP_CONFERENCE = "CS.sC"; private static final String SESSION_POST_DIAL_CONT = "CS.oPDC"; private static final String SESSION_PULL_EXTERNAL_CALL = "CS.pEC"; private static final String SESSION_SEND_CALL_EVENT = "CS.sCE"; private static final String SESSION_HANDOVER_COMPLETE = "CS.hC"; private static final String SESSION_EXTRAS_CHANGED = "CS.oEC"; private static final String SESSION_START_RTT = "CS.+RTT"; private static final String SESSION_UPDATE_RTT_PIPES = "CS.uRTT"; private static final String SESSION_STOP_RTT = "CS.-RTT"; private static final String SESSION_RTT_UPGRADE_RESPONSE = "CS.rTRUR"; private static final String SESSION_CONNECTION_SERVICE_FOCUS_LOST = "CS.cSFL"; private static final String SESSION_CONNECTION_SERVICE_FOCUS_GAINED = "CS.cSFG"; private static final String SESSION_HANDOVER_FAILED = "CS.haF"; private static final int MSG_ADD_CONNECTION_SERVICE_ADAPTER = 1; private static final int MSG_CREATE_CONNECTION = 2; private static final int MSG_ABORT = 3; private static final int MSG_ANSWER = 4; private static final int MSG_REJECT = 5; private static final int MSG_DISCONNECT = 6; private static final int MSG_HOLD = 7; private static final int MSG_UNHOLD = 8; private static final int MSG_ON_CALL_AUDIO_STATE_CHANGED = 9; private static final int MSG_PLAY_DTMF_TONE = 10; private static final int MSG_STOP_DTMF_TONE = 11; private static final int MSG_CONFERENCE = 12; private static final int MSG_SPLIT_FROM_CONFERENCE = 13; private static final int MSG_ON_POST_DIAL_CONTINUE = 14; private static final int MSG_REMOVE_CONNECTION_SERVICE_ADAPTER = 16; private static final int MSG_ANSWER_VIDEO = 17; private static final int MSG_MERGE_CONFERENCE = 18; private static final int MSG_SWAP_CONFERENCE = 19; private static final int MSG_REJECT_WITH_MESSAGE = 20; private static final int MSG_SILENCE = 21; private static final int MSG_PULL_EXTERNAL_CALL = 22; private static final int MSG_SEND_CALL_EVENT = 23; private static final int MSG_ON_EXTRAS_CHANGED = 24; private static final int MSG_CREATE_CONNECTION_FAILED = 25; private static final int MSG_ON_START_RTT = 26; private static final int MSG_ON_STOP_RTT = 27; private static final int MSG_RTT_UPGRADE_RESPONSE = 28; private static final int MSG_CREATE_CONNECTION_COMPLETE = 29; private static final int MSG_CONNECTION_SERVICE_FOCUS_LOST = 30; private static final int MSG_CONNECTION_SERVICE_FOCUS_GAINED = 31; private static final int MSG_HANDOVER_FAILED = 32; private static final int MSG_HANDOVER_COMPLETE = 33; private static final int MSG_DEFLECT = 34; private static Connection sNullConnection; private final Map<String, Connection> mConnectionById = new ConcurrentHashMap<>(); private final Map<Connection, String> mIdByConnection = new ConcurrentHashMap<>(); private final Map<String, Conference> mConferenceById = new ConcurrentHashMap<>(); private final Map<Conference, String> mIdByConference = new ConcurrentHashMap<>(); private final RemoteConnectionManager mRemoteConnectionManager = new RemoteConnectionManager(this); private final List<Runnable> mPreInitializationConnectionRequests = new ArrayList<>(); private final ConnectionServiceAdapter mAdapter = new ConnectionServiceAdapter(); private boolean mAreAccountsInitialized = false; private Conference sNullConference; private Object mIdSyncRoot = new Object(); private int mId = 0; private final IBinder mBinder = new IConnectionService.Stub() { @Override public void addConnectionServiceAdapter(IConnectionServiceAdapter adapter, Session.Info sessionInfo) { Log.startSession(sessionInfo, SESSION_ADD_CS_ADAPTER); try { SomeArgs args = SomeArgs.obtain(); args.arg1 = adapter; args.arg2 = Log.createSubsession(); mHandler.obtainMessage(MSG_ADD_CONNECTION_SERVICE_ADAPTER, args).sendToTarget(); } finally { Log.endSession(); } } public void removeConnectionServiceAdapter(IConnectionServiceAdapter adapter, Session.Info sessionInfo) { Log.startSession(sessionInfo, SESSION_REMOVE_CS_ADAPTER); try { SomeArgs args = SomeArgs.obtain(); args.arg1 = adapter; args.arg2 = Log.createSubsession(); mHandler.obtainMessage(MSG_REMOVE_CONNECTION_SERVICE_ADAPTER, args).sendToTarget(); } finally { Log.endSession(); } } @Override public void createConnection( PhoneAccountHandle connectionManagerPhoneAccount, String id, ConnectionRequest request, boolean isIncoming, boolean isUnknown, Session.Info sessionInfo) { Log.startSession(sessionInfo, SESSION_CREATE_CONN); try { SomeArgs args = SomeArgs.obtain(); args.arg1 = connectionManagerPhoneAccount; args.arg2 = id; args.arg3 = request; args.arg4 = Log.createSubsession(); args.argi1 = isIncoming ? 1 : 0; args.argi2 = isUnknown ? 1 : 0; mHandler.obtainMessage(MSG_CREATE_CONNECTION, args).sendToTarget(); } finally { Log.endSession(); } } @Override public void createConnectionComplete(String id, Session.Info sessionInfo) { Log.startSession(sessionInfo, SESSION_CREATE_CONN_COMPLETE); try { SomeArgs args = SomeArgs.obtain(); args.arg1 = id; args.arg2 = Log.createSubsession(); mHandler.obtainMessage(MSG_CREATE_CONNECTION_COMPLETE, args).sendToTarget(); } finally { Log.endSession(); } } @Override public void createConnectionFailed( PhoneAccountHandle connectionManagerPhoneAccount, String callId, ConnectionRequest request, boolean isIncoming, Session.Info sessionInfo) { Log.startSession(sessionInfo, SESSION_CREATE_CONN_FAILED); try { SomeArgs args = SomeArgs.obtain(); args.arg1 = callId; args.arg2 = request; args.arg3 = Log.createSubsession(); args.arg4 = connectionManagerPhoneAccount; args.argi1 = isIncoming ? 1 : 0; mHandler.obtainMessage(MSG_CREATE_CONNECTION_FAILED, args).sendToTarget(); } finally { Log.endSession(); } } @Override public void handoverFailed(String callId, ConnectionRequest request, int reason, Session.Info sessionInfo) { Log.startSession(sessionInfo, SESSION_HANDOVER_FAILED); try { SomeArgs args = SomeArgs.obtain(); args.arg1 = callId; args.arg2 = request; args.arg3 = Log.createSubsession(); args.arg4 = reason; mHandler.obtainMessage(MSG_HANDOVER_FAILED, args).sendToTarget(); } finally { Log.endSession(); } } @Override public void handoverComplete(String callId, Session.Info sessionInfo) { Log.startSession(sessionInfo, SESSION_HANDOVER_COMPLETE); try { SomeArgs args = SomeArgs.obtain(); args.arg1 = callId; args.arg2 = Log.createSubsession(); mHandler.obtainMessage(MSG_HANDOVER_COMPLETE, args).sendToTarget(); } finally { Log.endSession(); } } @Override public void abort(String callId, Session.Info sessionInfo) { Log.startSession(sessionInfo, SESSION_ABORT); try { SomeArgs args = SomeArgs.obtain(); args.arg1 = callId; args.arg2 = Log.createSubsession(); mHandler.obtainMessage(MSG_ABORT, args).sendToTarget(); } finally { Log.endSession(); } } @Override public void answerVideo(String callId, int videoState, Session.Info sessionInfo) { Log.startSession(sessionInfo, SESSION_ANSWER_VIDEO); try { SomeArgs args = SomeArgs.obtain(); args.arg1 = callId; args.arg2 = Log.createSubsession(); args.argi1 = videoState; mHandler.obtainMessage(MSG_ANSWER_VIDEO, args).sendToTarget(); } finally { Log.endSession(); } } @Override public void answer(String callId, Session.Info sessionInfo) { Log.startSession(sessionInfo, SESSION_ANSWER); try { SomeArgs args = SomeArgs.obtain(); args.arg1 = callId; args.arg2 = Log.createSubsession(); mHandler.obtainMessage(MSG_ANSWER, args).sendToTarget(); } finally { Log.endSession(); } } @Override public void deflect(String callId, Uri address, Session.Info sessionInfo) { Log.startSession(sessionInfo, SESSION_DEFLECT); try { SomeArgs args = SomeArgs.obtain(); args.arg1 = callId; args.arg2 = address; args.arg3 = Log.createSubsession(); mHandler.obtainMessage(MSG_DEFLECT, args).sendToTarget(); } finally { Log.endSession(); } } @Override public void reject(String callId, Session.Info sessionInfo) { Log.startSession(sessionInfo, SESSION_REJECT); try { SomeArgs args = SomeArgs.obtain(); args.arg1 = callId; args.arg2 = Log.createSubsession(); mHandler.obtainMessage(MSG_REJECT, args).sendToTarget(); } finally { Log.endSession(); } } @Override public void rejectWithMessage(String callId, String message, Session.Info sessionInfo) { Log.startSession(sessionInfo, SESSION_REJECT_MESSAGE); try { SomeArgs args = SomeArgs.obtain(); args.arg1 = callId; args.arg2 = message; args.arg3 = Log.createSubsession(); mHandler.obtainMessage(MSG_REJECT_WITH_MESSAGE, args).sendToTarget(); } finally { Log.endSession(); } } @Override public void silence(String callId, Session.Info sessionInfo) { Log.startSession(sessionInfo, SESSION_SILENCE); try { SomeArgs args = SomeArgs.obtain(); args.arg1 = callId; args.arg2 = Log.createSubsession(); mHandler.obtainMessage(MSG_SILENCE, args).sendToTarget(); } finally { Log.endSession(); } } @Override public void disconnect(String callId, Session.Info sessionInfo) { Log.startSession(sessionInfo, SESSION_DISCONNECT); try { SomeArgs args = SomeArgs.obtain(); args.arg1 = callId; args.arg2 = Log.createSubsession(); mHandler.obtainMessage(MSG_DISCONNECT, args).sendToTarget(); } finally { Log.endSession(); } } @Override public void hold(String callId, Session.Info sessionInfo) { Log.startSession(sessionInfo, SESSION_HOLD); try { SomeArgs args = SomeArgs.obtain(); args.arg1 = callId; args.arg2 = Log.createSubsession(); mHandler.obtainMessage(MSG_HOLD, args).sendToTarget(); } finally { Log.endSession(); } } @Override public void unhold(String callId, Session.Info sessionInfo) { Log.startSession(sessionInfo, SESSION_UNHOLD); try { SomeArgs args = SomeArgs.obtain(); args.arg1 = callId; args.arg2 = Log.createSubsession(); mHandler.obtainMessage(MSG_UNHOLD, args).sendToTarget(); } finally { Log.endSession(); } } @Override public void onCallAudioStateChanged(String callId, CallAudioState callAudioState, Session.Info sessionInfo) { Log.startSession(sessionInfo, SESSION_CALL_AUDIO_SC); try { SomeArgs args = SomeArgs.obtain(); args.arg1 = callId; args.arg2 = callAudioState; args.arg3 = Log.createSubsession(); mHandler.obtainMessage(MSG_ON_CALL_AUDIO_STATE_CHANGED, args).sendToTarget(); } finally { Log.endSession(); } } @Override public void playDtmfTone(String callId, char digit, Session.Info sessionInfo) { Log.startSession(sessionInfo, SESSION_PLAY_DTMF); try { SomeArgs args = SomeArgs.obtain(); args.arg1 = digit; args.arg2 = callId; args.arg3 = Log.createSubsession(); mHandler.obtainMessage(MSG_PLAY_DTMF_TONE, args).sendToTarget(); } finally { Log.endSession(); } } @Override public void stopDtmfTone(String callId, Session.Info sessionInfo) { Log.startSession(sessionInfo, SESSION_STOP_DTMF); try { SomeArgs args = SomeArgs.obtain(); args.arg1 = callId; args.arg2 = Log.createSubsession(); mHandler.obtainMessage(MSG_STOP_DTMF_TONE, args).sendToTarget(); } finally { Log.endSession(); } } @Override public void conference(String callId1, String callId2, Session.Info sessionInfo) { Log.startSession(sessionInfo, SESSION_CONFERENCE); try { SomeArgs args = SomeArgs.obtain(); args.arg1 = callId1; args.arg2 = callId2; args.arg3 = Log.createSubsession(); mHandler.obtainMessage(MSG_CONFERENCE, args).sendToTarget(); } finally { Log.endSession(); } } @Override public void splitFromConference(String callId, Session.Info sessionInfo) { Log.startSession(sessionInfo, SESSION_SPLIT_CONFERENCE); try { SomeArgs args = SomeArgs.obtain(); args.arg1 = callId; args.arg2 = Log.createSubsession(); mHandler.obtainMessage(MSG_SPLIT_FROM_CONFERENCE, args).sendToTarget(); } finally { Log.endSession(); } } @Override public void mergeConference(String callId, Session.Info sessionInfo) { Log.startSession(sessionInfo, SESSION_MERGE_CONFERENCE); try { SomeArgs args = SomeArgs.obtain(); args.arg1 = callId; args.arg2 = Log.createSubsession(); mHandler.obtainMessage(MSG_MERGE_CONFERENCE, args).sendToTarget(); } finally { Log.endSession(); } } @Override public void swapConference(String callId, Session.Info sessionInfo) { Log.startSession(sessionInfo, SESSION_SWAP_CONFERENCE); try { SomeArgs args = SomeArgs.obtain(); args.arg1 = callId; args.arg2 = Log.createSubsession(); mHandler.obtainMessage(MSG_SWAP_CONFERENCE, args).sendToTarget(); } finally { Log.endSession(); } } @Override public void onPostDialContinue(String callId, boolean proceed, Session.Info sessionInfo) { Log.startSession(sessionInfo, SESSION_POST_DIAL_CONT); try { SomeArgs args = SomeArgs.obtain(); args.arg1 = callId; args.arg2 = Log.createSubsession(); args.argi1 = proceed ? 1 : 0; mHandler.obtainMessage(MSG_ON_POST_DIAL_CONTINUE, args).sendToTarget(); } finally { Log.endSession(); } } @Override public void pullExternalCall(String callId, Session.Info sessionInfo) { Log.startSession(sessionInfo, SESSION_PULL_EXTERNAL_CALL); try { SomeArgs args = SomeArgs.obtain(); args.arg1 = callId; args.arg2 = Log.createSubsession(); mHandler.obtainMessage(MSG_PULL_EXTERNAL_CALL, args).sendToTarget(); } finally { Log.endSession(); } } @Override public void sendCallEvent(String callId, String event, Bundle extras, Session.Info sessionInfo) { Log.startSession(sessionInfo, SESSION_SEND_CALL_EVENT); try { SomeArgs args = SomeArgs.obtain(); args.arg1 = callId; args.arg2 = event; args.arg3 = extras; args.arg4 = Log.createSubsession(); mHandler.obtainMessage(MSG_SEND_CALL_EVENT, args).sendToTarget(); } finally { Log.endSession(); } } @Override public void onExtrasChanged(String callId, Bundle extras, Session.Info sessionInfo) { Log.startSession(sessionInfo, SESSION_EXTRAS_CHANGED); try { SomeArgs args = SomeArgs.obtain(); args.arg1 = callId; args.arg2 = extras; args.arg3 = Log.createSubsession(); mHandler.obtainMessage(MSG_ON_EXTRAS_CHANGED, args).sendToTarget(); } finally { Log.endSession(); } } @Override public void startRtt(String callId, ParcelFileDescriptor fromInCall, ParcelFileDescriptor toInCall, Session.Info sessionInfo) throws RemoteException { Log.startSession(sessionInfo, SESSION_START_RTT); try { SomeArgs args = SomeArgs.obtain(); args.arg1 = callId; args.arg2 = new Connection.RttTextStream(toInCall, fromInCall); args.arg3 = Log.createSubsession(); mHandler.obtainMessage(MSG_ON_START_RTT, args).sendToTarget(); } finally { Log.endSession(); } } @Override public void stopRtt(String callId, Session.Info sessionInfo) throws RemoteException { Log.startSession(sessionInfo, SESSION_STOP_RTT); try { SomeArgs args = SomeArgs.obtain(); args.arg1 = callId; args.arg2 = Log.createSubsession(); mHandler.obtainMessage(MSG_ON_STOP_RTT, args).sendToTarget(); } finally { Log.endSession(); } } @Override public void respondToRttUpgradeRequest(String callId, ParcelFileDescriptor fromInCall, ParcelFileDescriptor toInCall, Session.Info sessionInfo) throws RemoteException { Log.startSession(sessionInfo, SESSION_RTT_UPGRADE_RESPONSE); try { SomeArgs args = SomeArgs.obtain(); args.arg1 = callId; if (toInCall == null || fromInCall == null) { args.arg2 = null; } else { args.arg2 = new Connection.RttTextStream(toInCall, fromInCall); } args.arg3 = Log.createSubsession(); mHandler.obtainMessage(MSG_RTT_UPGRADE_RESPONSE, args).sendToTarget(); } finally { Log.endSession(); } } @Override public void connectionServiceFocusLost(Session.Info sessionInfo) throws RemoteException { Log.startSession(sessionInfo, SESSION_CONNECTION_SERVICE_FOCUS_LOST); try { mHandler.obtainMessage(MSG_CONNECTION_SERVICE_FOCUS_LOST).sendToTarget(); } finally { Log.endSession(); } } @Override public void connectionServiceFocusGained(Session.Info sessionInfo) throws RemoteException { Log.startSession(sessionInfo, SESSION_CONNECTION_SERVICE_FOCUS_GAINED); try { mHandler.obtainMessage(MSG_CONNECTION_SERVICE_FOCUS_GAINED).sendToTarget(); } finally { Log.endSession(); } } }; private final Handler mHandler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_ADD_CONNECTION_SERVICE_ADAPTER: { SomeArgs args = (SomeArgs) msg.obj; try { IConnectionServiceAdapter adapter = (IConnectionServiceAdapter) args.arg1; Log.continueSession((Session) args.arg2, SESSION_HANDLER + SESSION_ADD_CS_ADAPTER); mAdapter.addAdapter(adapter); onAdapterAttached(); } finally { args.recycle(); Log.endSession(); } break; } case MSG_REMOVE_CONNECTION_SERVICE_ADAPTER: { SomeArgs args = (SomeArgs) msg.obj; try { Log.continueSession((Session) args.arg2, SESSION_HANDLER + SESSION_REMOVE_CS_ADAPTER); mAdapter.removeAdapter((IConnectionServiceAdapter) args.arg1); } finally { args.recycle(); Log.endSession(); } break; } case MSG_CREATE_CONNECTION: { SomeArgs args = (SomeArgs) msg.obj; Log.continueSession((Session) args.arg4, SESSION_HANDLER + SESSION_CREATE_CONN); try { final PhoneAccountHandle connectionManagerPhoneAccount = (PhoneAccountHandle) args.arg1; final String id = (String) args.arg2; final ConnectionRequest request = (ConnectionRequest) args.arg3; final boolean isIncoming = args.argi1 == 1; final boolean isUnknown = args.argi2 == 1; if (!mAreAccountsInitialized) { Log.d(this, "Enqueueing pre-init request %s", id); mPreInitializationConnectionRequests.add( new android.telecom.Logging.Runnable( SESSION_HANDLER + SESSION_CREATE_CONN + ".pICR", null /*lock*/) { @Override public void loggedRun() { createConnection( connectionManagerPhoneAccount, id, request, isIncoming, isUnknown); } }.prepare()); } else { createConnection( connectionManagerPhoneAccount, id, request, isIncoming, isUnknown); } } finally { args.recycle(); Log.endSession(); } break; } case MSG_CREATE_CONNECTION_COMPLETE: { SomeArgs args = (SomeArgs) msg.obj; Log.continueSession((Session) args.arg2, SESSION_HANDLER + SESSION_CREATE_CONN_COMPLETE); try { final String id = (String) args.arg1; if (!mAreAccountsInitialized) { Log.d(this, "Enqueueing pre-init request %s", id); mPreInitializationConnectionRequests.add( new android.telecom.Logging.Runnable( SESSION_HANDLER + SESSION_CREATE_CONN_COMPLETE + ".pICR", null /*lock*/) { @Override public void loggedRun() { notifyCreateConnectionComplete(id); } }.prepare()); } else { notifyCreateConnectionComplete(id); } } finally { args.recycle(); Log.endSession(); } break; } case MSG_CREATE_CONNECTION_FAILED: { SomeArgs args = (SomeArgs) msg.obj; Log.continueSession((Session) args.arg3, SESSION_HANDLER + SESSION_CREATE_CONN_FAILED); try { final String id = (String) args.arg1; final ConnectionRequest request = (ConnectionRequest) args.arg2; final boolean isIncoming = args.argi1 == 1; final PhoneAccountHandle connectionMgrPhoneAccount = (PhoneAccountHandle) args.arg4; if (!mAreAccountsInitialized) { Log.d(this, "Enqueueing pre-init request %s", id); mPreInitializationConnectionRequests.add( new android.telecom.Logging.Runnable( SESSION_HANDLER + SESSION_CREATE_CONN_FAILED + ".pICR", null /*lock*/) { @Override public void loggedRun() { createConnectionFailed(connectionMgrPhoneAccount, id, request, isIncoming); } }.prepare()); } else { Log.i(this, "createConnectionFailed %s", id); createConnectionFailed(connectionMgrPhoneAccount, id, request, isIncoming); } } finally { args.recycle(); Log.endSession(); } break; } case MSG_HANDOVER_FAILED: { SomeArgs args = (SomeArgs) msg.obj; Log.continueSession((Session) args.arg3, SESSION_HANDLER + SESSION_HANDOVER_FAILED); try { final String id = (String) args.arg1; final ConnectionRequest request = (ConnectionRequest) args.arg2; final int reason = (int) args.arg4; if (!mAreAccountsInitialized) { Log.d(this, "Enqueueing pre-init request %s", id); mPreInitializationConnectionRequests.add( new android.telecom.Logging.Runnable( SESSION_HANDLER + SESSION_HANDOVER_FAILED + ".pICR", null /*lock*/) { @Override public void loggedRun() { handoverFailed(id, request, reason); } }.prepare()); } else { Log.i(this, "createConnectionFailed %s", id); handoverFailed(id, request, reason); } } finally { args.recycle(); Log.endSession(); } break; } case MSG_ABORT: { SomeArgs args = (SomeArgs) msg.obj; Log.continueSession((Session) args.arg2, SESSION_HANDLER + SESSION_ABORT); try { abort((String) args.arg1); } finally { args.recycle(); Log.endSession(); } break; } case MSG_ANSWER: { SomeArgs args = (SomeArgs) msg.obj; Log.continueSession((Session) args.arg2, SESSION_HANDLER + SESSION_ANSWER); try { answer((String) args.arg1); } finally { args.recycle(); Log.endSession(); } break; } case MSG_ANSWER_VIDEO: { SomeArgs args = (SomeArgs) msg.obj; Log.continueSession((Session) args.arg2, SESSION_HANDLER + SESSION_ANSWER_VIDEO); try { String callId = (String) args.arg1; int videoState = args.argi1; answerVideo(callId, videoState); } finally { args.recycle(); Log.endSession(); } break; } case MSG_DEFLECT: { SomeArgs args = (SomeArgs) msg.obj; Log.continueSession((Session) args.arg3, SESSION_HANDLER + SESSION_DEFLECT); try { deflect((String) args.arg1, (Uri) args.arg2); } finally { args.recycle(); Log.endSession(); } break; } case MSG_REJECT: { SomeArgs args = (SomeArgs) msg.obj; Log.continueSession((Session) args.arg2, SESSION_HANDLER + SESSION_REJECT); try { reject((String) args.arg1); } finally { args.recycle(); Log.endSession(); } break; } case MSG_REJECT_WITH_MESSAGE: { SomeArgs args = (SomeArgs) msg.obj; Log.continueSession((Session) args.arg3, SESSION_HANDLER + SESSION_REJECT_MESSAGE); try { reject((String) args.arg1, (String) args.arg2); } finally { args.recycle(); Log.endSession(); } break; } case MSG_DISCONNECT: { SomeArgs args = (SomeArgs) msg.obj; Log.continueSession((Session) args.arg2, SESSION_HANDLER + SESSION_DISCONNECT); try { disconnect((String) args.arg1); } finally { args.recycle(); Log.endSession(); } break; } case MSG_SILENCE: { SomeArgs args = (SomeArgs) msg.obj; Log.continueSession((Session) args.arg2, SESSION_HANDLER + SESSION_SILENCE); try { silence((String) args.arg1); } finally { args.recycle(); Log.endSession(); } break; } case MSG_HOLD: { SomeArgs args = (SomeArgs) msg.obj; Log.continueSession((Session) args.arg2, SESSION_HANDLER + SESSION_REJECT); try { hold((String) args.arg1); } finally { args.recycle(); Log.endSession(); } break; } case MSG_UNHOLD: { SomeArgs args = (SomeArgs) msg.obj; Log.continueSession((Session) args.arg2, SESSION_HANDLER + SESSION_UNHOLD); try { unhold((String) args.arg1); } finally { args.recycle(); Log.endSession(); } break; } case MSG_ON_CALL_AUDIO_STATE_CHANGED: { SomeArgs args = (SomeArgs) msg.obj; Log.continueSession((Session) args.arg3, SESSION_HANDLER + SESSION_CALL_AUDIO_SC); try { String callId = (String) args.arg1; CallAudioState audioState = (CallAudioState) args.arg2; onCallAudioStateChanged(callId, new CallAudioState(audioState)); } finally { args.recycle(); Log.endSession(); } break; } case MSG_PLAY_DTMF_TONE: { SomeArgs args = (SomeArgs) msg.obj; try { Log.continueSession((Session) args.arg3, SESSION_HANDLER + SESSION_PLAY_DTMF); playDtmfTone((String) args.arg2, (char) args.arg1); } finally { args.recycle(); Log.endSession(); } break; } case MSG_STOP_DTMF_TONE: { SomeArgs args = (SomeArgs) msg.obj; try { Log.continueSession((Session) args.arg2, SESSION_HANDLER + SESSION_STOP_DTMF); stopDtmfTone((String) args.arg1); } finally { args.recycle(); Log.endSession(); } break; } case MSG_CONFERENCE: { SomeArgs args = (SomeArgs) msg.obj; try { Log.continueSession((Session) args.arg3, SESSION_HANDLER + SESSION_CONFERENCE); String callId1 = (String) args.arg1; String callId2 = (String) args.arg2; conference(callId1, callId2); } finally { args.recycle(); Log.endSession(); } break; } case MSG_SPLIT_FROM_CONFERENCE: { SomeArgs args = (SomeArgs) msg.obj; try { Log.continueSession((Session) args.arg2, SESSION_HANDLER + SESSION_SPLIT_CONFERENCE); splitFromConference((String) args.arg1); } finally { args.recycle(); Log.endSession(); } break; } case MSG_MERGE_CONFERENCE: { SomeArgs args = (SomeArgs) msg.obj; try { Log.continueSession((Session) args.arg2, SESSION_HANDLER + SESSION_MERGE_CONFERENCE); mergeConference((String) args.arg1); } finally { args.recycle(); Log.endSession(); } break; } case MSG_SWAP_CONFERENCE: { SomeArgs args = (SomeArgs) msg.obj; try { Log.continueSession((Session) args.arg2, SESSION_HANDLER + SESSION_SWAP_CONFERENCE); swapConference((String) args.arg1); } finally { args.recycle(); Log.endSession(); } break; } case MSG_ON_POST_DIAL_CONTINUE: { SomeArgs args = (SomeArgs) msg.obj; try { Log.continueSession((Session) args.arg2, SESSION_HANDLER + SESSION_POST_DIAL_CONT); String callId = (String) args.arg1; boolean proceed = (args.argi1 == 1); onPostDialContinue(callId, proceed); } finally { args.recycle(); Log.endSession(); } break; } case MSG_PULL_EXTERNAL_CALL: { SomeArgs args = (SomeArgs) msg.obj; try { Log.continueSession((Session) args.arg2, SESSION_HANDLER + SESSION_PULL_EXTERNAL_CALL); pullExternalCall((String) args.arg1); } finally { args.recycle(); Log.endSession(); } break; } case MSG_SEND_CALL_EVENT: { SomeArgs args = (SomeArgs) msg.obj; try { Log.continueSession((Session) args.arg4, SESSION_HANDLER + SESSION_SEND_CALL_EVENT); String callId = (String) args.arg1; String event = (String) args.arg2; Bundle extras = (Bundle) args.arg3; sendCallEvent(callId, event, extras); } finally { args.recycle(); Log.endSession(); } break; } case MSG_HANDOVER_COMPLETE: { SomeArgs args = (SomeArgs) msg.obj; try { Log.continueSession((Session) args.arg2, SESSION_HANDLER + SESSION_HANDOVER_COMPLETE); String callId = (String) args.arg1; notifyHandoverComplete(callId); } finally { args.recycle(); Log.endSession(); } break; } case MSG_ON_EXTRAS_CHANGED: { SomeArgs args = (SomeArgs) msg.obj; try { Log.continueSession((Session) args.arg3, SESSION_HANDLER + SESSION_EXTRAS_CHANGED); String callId = (String) args.arg1; Bundle extras = (Bundle) args.arg2; handleExtrasChanged(callId, extras); } finally { args.recycle(); Log.endSession(); } break; } case MSG_ON_START_RTT: { SomeArgs args = (SomeArgs) msg.obj; try { Log.continueSession((Session) args.arg3, SESSION_HANDLER + SESSION_START_RTT); String callId = (String) args.arg1; Connection.RttTextStream rttTextStream = (Connection.RttTextStream) args.arg2; startRtt(callId, rttTextStream); } finally { args.recycle(); Log.endSession(); } break; } case MSG_ON_STOP_RTT: { SomeArgs args = (SomeArgs) msg.obj; try { Log.continueSession((Session) args.arg2, SESSION_HANDLER + SESSION_STOP_RTT); String callId = (String) args.arg1; stopRtt(callId); } finally { args.recycle(); Log.endSession(); } break; } case MSG_RTT_UPGRADE_RESPONSE: { SomeArgs args = (SomeArgs) msg.obj; try { Log.continueSession((Session) args.arg3, SESSION_HANDLER + SESSION_RTT_UPGRADE_RESPONSE); String callId = (String) args.arg1; Connection.RttTextStream rttTextStream = (Connection.RttTextStream) args.arg2; handleRttUpgradeResponse(callId, rttTextStream); } finally { args.recycle(); Log.endSession(); } break; } case MSG_CONNECTION_SERVICE_FOCUS_GAINED: onConnectionServiceFocusGained(); break; case MSG_CONNECTION_SERVICE_FOCUS_LOST: onConnectionServiceFocusLost(); break; default: break; } } }; private final Conference.Listener mConferenceListener = new Conference.Listener() { @Override public void onStateChanged(Conference conference, int oldState, int newState) { String id = mIdByConference.get(conference); switch (newState) { case Connection.STATE_ACTIVE: mAdapter.setActive(id); break; case Connection.STATE_HOLDING: mAdapter.setOnHold(id); break; case Connection.STATE_DISCONNECTED: // handled by onDisconnected break; } } @Override public void onDisconnected(Conference conference, DisconnectCause disconnectCause) { String id = mIdByConference.get(conference); mAdapter.setDisconnected(id, disconnectCause); } @Override public void onConnectionAdded(Conference conference, Connection connection) { } @Override public void onConnectionRemoved(Conference conference, Connection connection) { } @Override public void onConferenceableConnectionsChanged( Conference conference, List<Connection> conferenceableConnections) { mAdapter.setConferenceableConnections( mIdByConference.get(conference), createConnectionIdList(conferenceableConnections)); } @Override public void onDestroyed(Conference conference) { removeConference(conference); } @Override public void onConnectionCapabilitiesChanged( Conference conference, int connectionCapabilities) { String id = mIdByConference.get(conference); Log.d(this, "call capabilities: conference: %s", Connection.capabilitiesToString(connectionCapabilities)); mAdapter.setConnectionCapabilities(id, connectionCapabilities); } @Override public void onConnectionPropertiesChanged( Conference conference, int connectionProperties) { String id = mIdByConference.get(conference); Log.d(this, "call capabilities: conference: %s", Connection.propertiesToString(connectionProperties)); mAdapter.setConnectionProperties(id, connectionProperties); } @Override public void onVideoStateChanged(Conference c, int videoState) { String id = mIdByConference.get(c); Log.d(this, "onVideoStateChanged set video state %d", videoState); mAdapter.setVideoState(id, videoState); } @Override public void onVideoProviderChanged(Conference c, Connection.VideoProvider videoProvider) { String id = mIdByConference.get(c); Log.d(this, "onVideoProviderChanged: Connection: %s, VideoProvider: %s", c, videoProvider); mAdapter.setVideoProvider(id, videoProvider); } @Override public void onStatusHintsChanged(Conference conference, StatusHints statusHints) { String id = mIdByConference.get(conference); if (id != null) { mAdapter.setStatusHints(id, statusHints); } } @Override public void onExtrasChanged(Conference c, Bundle extras) { String id = mIdByConference.get(c); if (id != null) { mAdapter.putExtras(id, extras); } } @Override public void onExtrasRemoved(Conference c, List<String> keys) { String id = mIdByConference.get(c); if (id != null) { mAdapter.removeExtras(id, keys); } } }; private final Connection.Listener mConnectionListener = new Connection.Listener() { @Override public void onStateChanged(Connection c, int state) { String id = mIdByConnection.get(c); Log.d(this, "Adapter set state %s %s", id, Connection.stateToString(state)); switch (state) { case Connection.STATE_ACTIVE: mAdapter.setActive(id); break; case Connection.STATE_DIALING: mAdapter.setDialing(id); break; case Connection.STATE_PULLING_CALL: mAdapter.setPulling(id); break; case Connection.STATE_DISCONNECTED: // Handled in onDisconnected() break; case Connection.STATE_HOLDING: mAdapter.setOnHold(id); break; case Connection.STATE_NEW: // Nothing to tell Telecom break; case Connection.STATE_RINGING: mAdapter.setRinging(id); break; } } @Override public void onDisconnected(Connection c, DisconnectCause disconnectCause) { String id = mIdByConnection.get(c); Log.d(this, "Adapter set disconnected %s", disconnectCause); mAdapter.setDisconnected(id, disconnectCause); } @Override public void onVideoStateChanged(Connection c, int videoState) { String id = mIdByConnection.get(c); Log.d(this, "Adapter set video state %d", videoState); mAdapter.setVideoState(id, videoState); } @Override public void onAddressChanged(Connection c, Uri address, int presentation) { String id = mIdByConnection.get(c); mAdapter.setAddress(id, address, presentation); } @Override public void onCallerDisplayNameChanged( Connection c, String callerDisplayName, int presentation) { String id = mIdByConnection.get(c); mAdapter.setCallerDisplayName(id, callerDisplayName, presentation); } @Override public void onDestroyed(Connection c) { removeConnection(c); } @Override public void onPostDialWait(Connection c, String remaining) { String id = mIdByConnection.get(c); Log.d(this, "Adapter onPostDialWait %s, %s", c, remaining); mAdapter.onPostDialWait(id, remaining); } @Override public void onPostDialChar(Connection c, char nextChar) { String id = mIdByConnection.get(c); Log.d(this, "Adapter onPostDialChar %s, %s", c, nextChar); mAdapter.onPostDialChar(id, nextChar); } @Override public void onRingbackRequested(Connection c, boolean ringback) { String id = mIdByConnection.get(c); Log.d(this, "Adapter onRingback %b", ringback); mAdapter.setRingbackRequested(id, ringback); } @Override public void onConnectionCapabilitiesChanged(Connection c, int capabilities) { String id = mIdByConnection.get(c); Log.d(this, "capabilities: parcelableconnection: %s", Connection.capabilitiesToString(capabilities)); mAdapter.setConnectionCapabilities(id, capabilities); } @Override public void onConnectionPropertiesChanged(Connection c, int properties) { String id = mIdByConnection.get(c); Log.d(this, "properties: parcelableconnection: %s", Connection.propertiesToString(properties)); mAdapter.setConnectionProperties(id, properties); } @Override public void onVideoProviderChanged(Connection c, Connection.VideoProvider videoProvider) { String id = mIdByConnection.get(c); Log.d(this, "onVideoProviderChanged: Connection: %s, VideoProvider: %s", c, videoProvider); mAdapter.setVideoProvider(id, videoProvider); } @Override public void onAudioModeIsVoipChanged(Connection c, boolean isVoip) { String id = mIdByConnection.get(c); mAdapter.setIsVoipAudioMode(id, isVoip); } @Override public void onStatusHintsChanged(Connection c, StatusHints statusHints) { String id = mIdByConnection.get(c); mAdapter.setStatusHints(id, statusHints); } @Override public void onConferenceablesChanged( Connection connection, List<Conferenceable> conferenceables) { mAdapter.setConferenceableConnections( mIdByConnection.get(connection), createIdList(conferenceables)); } @Override public void onConferenceChanged(Connection connection, Conference conference) { String id = mIdByConnection.get(connection); if (id != null) { String conferenceId = null; if (conference != null) { conferenceId = mIdByConference.get(conference); } mAdapter.setIsConferenced(id, conferenceId); } } @Override public void onConferenceMergeFailed(Connection connection) { String id = mIdByConnection.get(connection); if (id != null) { mAdapter.onConferenceMergeFailed(id); } } @Override public void onExtrasChanged(Connection c, Bundle extras) { String id = mIdByConnection.get(c); if (id != null) { mAdapter.putExtras(id, extras); } } @Override public void onExtrasRemoved(Connection c, List<String> keys) { String id = mIdByConnection.get(c); if (id != null) { mAdapter.removeExtras(id, keys); } } @Override public void onConnectionEvent(Connection connection, String event, Bundle extras) { String id = mIdByConnection.get(connection); if (id != null) { mAdapter.onConnectionEvent(id, event, extras); } } @Override public void onAudioRouteChanged(Connection c, int audioRoute, String bluetoothAddress) { String id = mIdByConnection.get(c); if (id != null) { mAdapter.setAudioRoute(id, audioRoute, bluetoothAddress); } } @Override public void onRttInitiationSuccess(Connection c) { String id = mIdByConnection.get(c); if (id != null) { mAdapter.onRttInitiationSuccess(id); } } @Override public void onRttInitiationFailure(Connection c, int reason) { String id = mIdByConnection.get(c); if (id != null) { mAdapter.onRttInitiationFailure(id, reason); } } @Override public void onRttSessionRemotelyTerminated(Connection c) { String id = mIdByConnection.get(c); if (id != null) { mAdapter.onRttSessionRemotelyTerminated(id); } } @Override public void onRemoteRttRequest(Connection c) { String id = mIdByConnection.get(c); if (id != null) { mAdapter.onRemoteRttRequest(id); } } @Override public void onPhoneAccountChanged(Connection c, PhoneAccountHandle pHandle) { String id = mIdByConnection.get(c); if (id != null) { mAdapter.onPhoneAccountChanged(id, pHandle); } } };
{@inheritDoc}
/** {@inheritDoc} */
@Override public final IBinder onBind(Intent intent) { return mBinder; }
{@inheritDoc}
/** {@inheritDoc} */
@Override public boolean onUnbind(Intent intent) { endAllConnections(); return super.onUnbind(intent); }
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.
/** * 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. */
private void createConnection( final PhoneAccountHandle callManagerAccount, final String callId, final ConnectionRequest request, boolean isIncoming, boolean isUnknown) { boolean isLegacyHandover = request.getExtras() != null && request.getExtras().getBoolean(TelecomManager.EXTRA_IS_HANDOVER, false); boolean isHandover = request.getExtras() != null && request.getExtras().getBoolean( TelecomManager.EXTRA_IS_HANDOVER_CONNECTION, false); Log.d(this, "createConnection, callManagerAccount: %s, callId: %s, request: %s, " + "isIncoming: %b, isUnknown: %b, isLegacyHandover: %b, isHandover: %b", callManagerAccount, callId, request, isIncoming, isUnknown, isLegacyHandover, isHandover); Connection connection = null; if (isHandover) { PhoneAccountHandle fromPhoneAccountHandle = request.getExtras() != null ? (PhoneAccountHandle) request.getExtras().getParcelable( TelecomManager.EXTRA_HANDOVER_FROM_PHONE_ACCOUNT) : null; if (!isIncoming) { connection = onCreateOutgoingHandoverConnection(fromPhoneAccountHandle, request); } else { connection = onCreateIncomingHandoverConnection(fromPhoneAccountHandle, request); } } else { connection = isUnknown ? onCreateUnknownConnection(callManagerAccount, request) : isIncoming ? onCreateIncomingConnection(callManagerAccount, request) : onCreateOutgoingConnection(callManagerAccount, request); } Log.d(this, "createConnection, connection: %s", connection); if (connection == null) { Log.i(this, "createConnection, implementation returned null connection."); connection = Connection.createFailedConnection( new DisconnectCause(DisconnectCause.ERROR, "IMPL_RETURNED_NULL_CONNECTION")); } connection.setTelecomCallId(callId); if (connection.getState() != Connection.STATE_DISCONNECTED) { addConnection(request.getAccountHandle(), callId, connection); } Uri address = connection.getAddress(); String number = address == null ? "null" : address.getSchemeSpecificPart(); Log.v(this, "createConnection, number: %s, state: %s, capabilities: %s, properties: %s", Connection.toLogSafePhoneNumber(number), Connection.stateToString(connection.getState()), Connection.capabilitiesToString(connection.getConnectionCapabilities()), Connection.propertiesToString(connection.getConnectionProperties())); Log.d(this, "createConnection, calling handleCreateConnectionSuccessful %s", callId); mAdapter.handleCreateConnectionComplete( callId, request, new ParcelableConnection( request.getAccountHandle(), connection.getState(), connection.getConnectionCapabilities(), connection.getConnectionProperties(), connection.getSupportedAudioRoutes(), connection.getAddress(), connection.getAddressPresentation(), connection.getCallerDisplayName(), connection.getCallerDisplayNamePresentation(), connection.getVideoProvider() == null ? null : connection.getVideoProvider().getInterface(), connection.getVideoState(), connection.isRingbackRequested(), connection.getAudioModeIsVoip(), connection.getConnectTimeMillis(), connection.getConnectElapsedTimeMillis(), connection.getStatusHints(), connection.getDisconnectCause(), createIdList(connection.getConferenceables()), connection.getExtras())); if (isIncoming && request.shouldShowIncomingCallUi() && (connection.getConnectionProperties() & Connection.PROPERTY_SELF_MANAGED) == Connection.PROPERTY_SELF_MANAGED) { // Tell ConnectionService to show its incoming call UX. connection.onShowIncomingCallUi(); } if (isUnknown) { triggerConferenceRecalculate(); } } private void createConnectionFailed(final PhoneAccountHandle callManagerAccount, final String callId, final ConnectionRequest request, boolean isIncoming) { Log.i(this, "createConnectionFailed %s", callId); if (isIncoming) { onCreateIncomingConnectionFailed(callManagerAccount, request); } else { onCreateOutgoingConnectionFailed(callManagerAccount, request); } } private void handoverFailed(final String callId, final ConnectionRequest request, int reason) { Log.i(this, "handoverFailed %s", callId); onHandoverFailed(request, reason); }
Called by Telecom when the creation of a new Connection has completed and it is now added to Telecom.
Params:
  • callId – The ID of the connection.
/** * Called by Telecom when the creation of a new Connection has completed and it is now added * to Telecom. * @param callId The ID of the connection. */
private void notifyCreateConnectionComplete(final String callId) { Log.i(this, "notifyCreateConnectionComplete %s", callId); if (callId == null) { // This could happen if the connection fails quickly and is removed from the // ConnectionService before Telecom sends the create connection complete callback. Log.w(this, "notifyCreateConnectionComplete: callId is null."); return; } onCreateConnectionComplete(findConnectionForAction(callId, "notifyCreateConnectionComplete")); } private void abort(String callId) { Log.d(this, "abort %s", callId); findConnectionForAction(callId, "abort").onAbort(); } private void answerVideo(String callId, int videoState) { Log.d(this, "answerVideo %s", callId); findConnectionForAction(callId, "answer").onAnswer(videoState); } private void answer(String callId) { Log.d(this, "answer %s", callId); findConnectionForAction(callId, "answer").onAnswer(); } private void deflect(String callId, Uri address) { Log.d(this, "deflect %s", callId); findConnectionForAction(callId, "deflect").onDeflect(address); } private void reject(String callId) { Log.d(this, "reject %s", callId); findConnectionForAction(callId, "reject").onReject(); } private void reject(String callId, String rejectWithMessage) { Log.d(this, "reject %s with message", callId); findConnectionForAction(callId, "reject").onReject(rejectWithMessage); } private void silence(String callId) { Log.d(this, "silence %s", callId); findConnectionForAction(callId, "silence").onSilence(); } private void disconnect(String callId) { Log.d(this, "disconnect %s", callId); if (mConnectionById.containsKey(callId)) { findConnectionForAction(callId, "disconnect").onDisconnect(); } else { findConferenceForAction(callId, "disconnect").onDisconnect(); } } private void hold(String callId) { Log.d(this, "hold %s", callId); if (mConnectionById.containsKey(callId)) { findConnectionForAction(callId, "hold").onHold(); } else { findConferenceForAction(callId, "hold").onHold(); } } private void unhold(String callId) { Log.d(this, "unhold %s", callId); if (mConnectionById.containsKey(callId)) { findConnectionForAction(callId, "unhold").onUnhold(); } else { findConferenceForAction(callId, "unhold").onUnhold(); } } private void onCallAudioStateChanged(String callId, CallAudioState callAudioState) { Log.d(this, "onAudioStateChanged %s %s", callId, callAudioState); if (mConnectionById.containsKey(callId)) { findConnectionForAction(callId, "onCallAudioStateChanged").setCallAudioState( callAudioState); } else { findConferenceForAction(callId, "onCallAudioStateChanged").setCallAudioState( callAudioState); } } private void playDtmfTone(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 void stopDtmfTone(String callId) { Log.d(this, "stopDtmfTone %s", callId); if (mConnectionById.containsKey(callId)) { findConnectionForAction(callId, "stopDtmfTone").onStopDtmfTone(); } else { findConferenceForAction(callId, "stopDtmfTone").onStopDtmfTone(); } } private void conference(String callId1, 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); } } } private void splitFromConference(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 void mergeConference(String callId) { Log.d(this, "mergeConference(%s)", callId); Conference conference = findConferenceForAction(callId, "mergeConference"); if (conference != null) { conference.onMerge(); } } private void swapConference(String callId) { Log.d(this, "swapConference(%s)", callId); Conference conference = findConferenceForAction(callId, "swapConference"); if (conference != null) { conference.onSwap(); } }
Notifies a Connection of a request to pull an external call. See Call.pullExternalCall().
Params:
  • callId – The ID of the call to pull.
/** * Notifies a {@link Connection} of a request to pull an external call. * * See {@link Call#pullExternalCall()}. * * @param callId The ID of the call to pull. */
private void pullExternalCall(String callId) { Log.d(this, "pullExternalCall(%s)", callId); Connection connection = findConnectionForAction(callId, "pullExternalCall"); if (connection != null) { connection.onPullExternalCall(); } }
Notifies a Connection of a call event. See Call.sendCallEvent(String, Bundle).
Params:
  • callId – The ID of the call receiving the event.
  • event – The event.
  • extras – Extras associated with the event.
/** * Notifies a {@link Connection} of a call event. * * See {@link Call#sendCallEvent(String, Bundle)}. * * @param callId The ID of the call receiving the event. * @param event The event. * @param extras Extras associated with the event. */
private void sendCallEvent(String callId, String event, Bundle extras) { Log.d(this, "sendCallEvent(%s, %s)", callId, event); Connection connection = findConnectionForAction(callId, "sendCallEvent"); if (connection != null) { connection.onCallEvent(event, extras); } }
Notifies a Connection that a handover has completed.
Params:
  • callId – The ID of the call which completed handover.
/** * Notifies a {@link Connection} that a handover has completed. * * @param callId The ID of the call which completed handover. */
private void notifyHandoverComplete(String callId) { Log.d(this, "notifyHandoverComplete(%s)", callId); Connection connection = findConnectionForAction(callId, "notifyHandoverComplete"); if (connection != null) { connection.onHandoverComplete(); } }
Notifies a Connection or Conference of a change to the extras from Telecom.

These extra changes can originate from Telecom itself, or from an InCallService via the Call.putExtra(String, boolean), Call.putExtra(String, int), Call.putExtra(String, String), Call.removeExtras(List<String>).

Params:
  • callId – The ID of the call receiving the event.
  • extras – The new extras bundle.
/** * Notifies a {@link Connection} or {@link Conference} of a change to the extras from Telecom. * <p> * These extra changes can originate from Telecom itself, or from an {@link InCallService} via * the {@link android.telecom.Call#putExtra(String, boolean)}, * {@link android.telecom.Call#putExtra(String, int)}, * {@link android.telecom.Call#putExtra(String, String)}, * {@link Call#removeExtras(List)}. * * @param callId The ID of the call receiving the event. * @param extras The new extras bundle. */
private void handleExtrasChanged(String callId, Bundle extras) { Log.d(this, "handleExtrasChanged(%s, %s)", callId, extras); if (mConnectionById.containsKey(callId)) { findConnectionForAction(callId, "handleExtrasChanged").handleExtrasChanged(extras); } else if (mConferenceById.containsKey(callId)) { findConferenceForAction(callId, "handleExtrasChanged").handleExtrasChanged(extras); } } private void startRtt(String callId, Connection.RttTextStream rttTextStream) { Log.d(this, "startRtt(%s)", callId); if (mConnectionById.containsKey(callId)) { findConnectionForAction(callId, "startRtt").onStartRtt(rttTextStream); } else if (mConferenceById.containsKey(callId)) { Log.w(this, "startRtt called on a conference."); } } private void stopRtt(String callId) { Log.d(this, "stopRtt(%s)", callId); if (mConnectionById.containsKey(callId)) { findConnectionForAction(callId, "stopRtt").onStopRtt(); } else if (mConferenceById.containsKey(callId)) { Log.w(this, "stopRtt called on a conference."); } } private void handleRttUpgradeResponse(String callId, Connection.RttTextStream rttTextStream) { Log.d(this, "handleRttUpgradeResponse(%s, %s)", callId, rttTextStream == null); if (mConnectionById.containsKey(callId)) { findConnectionForAction(callId, "handleRttUpgradeResponse") .handleRttUpgradeResponse(rttTextStream); } else if (mConferenceById.containsKey(callId)) { Log.w(this, "handleRttUpgradeResponse called on a conference."); } } private void onPostDialContinue(String callId, boolean proceed) { Log.d(this, "onPostDialContinue(%s)", callId); findConnectionForAction(callId, "stopDtmfTone").onPostDialContinue(proceed); } private void onAdapterAttached() { 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 android.telecom.Logging.Runnable("oAA.qRCS.oR", null /*lock*/) { @Override public void loggedRun() { 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); } }.prepare()); } @Override public void onError() { mHandler.post(new android.telecom.Logging.Runnable("oAA.qRCS.oE", null /*lock*/) { @Override public void loggedRun() { mAreAccountsInitialized = true; } }.prepare()); } }); }
Ask some other ConnectionService to create a RemoteConnection given an incoming request. This is used by ConnectionServices that are registered with PhoneAccount.CAPABILITY_CONNECTION_MANAGER and want to be able to manage SIM-based incoming calls.
Params:
Returns:The Connection object to satisfy this call, or null to not handle the call.
/** * 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. */
public final RemoteConnection createRemoteIncomingConnection( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request) { return mRemoteConnectionManager.createRemoteConnection( connectionManagerPhoneAccount, request, true); }
Ask some other ConnectionService to create a RemoteConnection given an outgoing request. This is used by ConnectionServices that are registered with PhoneAccount.CAPABILITY_CONNECTION_MANAGER and want to be able to use the SIM-based ConnectionService to place its outgoing calls.
Params:
Returns:The Connection object to satisfy this call, or null to not handle the call.
/** * 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 outgoing call. * @return The {@code Connection} object to satisfy this call, or {@code null} to * not handle the call. */
public final RemoteConnection createRemoteOutgoingConnection( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request) { return mRemoteConnectionManager.createRemoteConnection( connectionManagerPhoneAccount, request, false); }
Indicates to the relevant RemoteConnectionService that the specified RemoteConnections should be merged into a conference call.

If the conference request is successful, the method onRemoteConferenceAdded will be invoked.

Params:
  • remoteConnection1 – The first of the remote connections to conference.
  • remoteConnection2 – The second of the remote connections to conference.
/** * Indicates to the relevant {@code RemoteConnectionService} that the specified * {@link RemoteConnection}s should be merged into a conference call. * <p> * 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. */
public final void conferenceRemoteConnections( RemoteConnection remoteConnection1, RemoteConnection remoteConnection2) { mRemoteConnectionManager.conferenceRemoteConnections(remoteConnection1, remoteConnection2); }
Adds a new conference call. When a conference call is created either as a result of an explicit request via onConference or otherwise, the connection service should supply an instance of Conference by invoking this method. A conference call provided by this method will persist until Conference.destroy is invoked on the conference instance.
Params:
  • conference – The new conference object.
/** * 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. */
public final void addConference(Conference conference) { Log.d(this, "addConference: conference=%s", conference); 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)); } } conference.setTelecomCallId(id); ParcelableConference parcelableConference = new ParcelableConference( conference.getPhoneAccountHandle(), conference.getState(), conference.getConnectionCapabilities(), conference.getConnectionProperties(), connectionIds, conference.getVideoProvider() == null ? null : conference.getVideoProvider().getInterface(), conference.getVideoState(), conference.getConnectTimeMillis(), conference.getConnectionStartElapsedRealTime(), conference.getStatusHints(), conference.getExtras()); mAdapter.addConferenceCall(id, parcelableConference); mAdapter.setVideoProvider(id, conference.getVideoProvider()); mAdapter.setVideoState(id, conference.getVideoState()); // 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); } } onConferenceAdded(conference); } }
Adds a connection created by the ConnectionService and informs telecom of the new connection.
Params:
  • phoneAccountHandle – The phone account handle for the connection.
  • connection – The connection to add.
/** * 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. */
public final void addExistingConnection(PhoneAccountHandle phoneAccountHandle, Connection connection) { addExistingConnection(phoneAccountHandle, connection, null /* conference */); }
Call to inform Telecom that your ConnectionService has released call resources (e.g microphone, camera).
See Also:
/** * Call to inform Telecom that your {@link ConnectionService} has released call resources (e.g * microphone, camera). * * @see ConnectionService#onConnectionServiceFocusLost() */
public final void connectionServiceFocusReleased() { mAdapter.onConnectionServiceFocusReleased(); }
Adds a connection created by the ConnectionService and informs telecom of the new connection.
Params:
  • phoneAccountHandle – The phone account handle for the connection.
  • connection – The connection to add.
  • conference – The parent conference of the new connection.
@hide
/** * 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. * @param conference The parent conference of the new connection. * @hide */
public final void addExistingConnection(PhoneAccountHandle phoneAccountHandle, Connection connection, Conference conference) { String id = addExistingConnectionInternal(phoneAccountHandle, connection); if (id != null) { List<String> emptyList = new ArrayList<>(0); String conferenceId = null; if (conference != null) { conferenceId = mIdByConference.get(conference); } ParcelableConnection parcelableConnection = new ParcelableConnection( phoneAccountHandle, connection.getState(), connection.getConnectionCapabilities(), connection.getConnectionProperties(), connection.getSupportedAudioRoutes(), connection.getAddress(), connection.getAddressPresentation(), connection.getCallerDisplayName(), connection.getCallerDisplayNamePresentation(), connection.getVideoProvider() == null ? null : connection.getVideoProvider().getInterface(), connection.getVideoState(), connection.isRingbackRequested(), connection.getAudioModeIsVoip(), connection.getConnectTimeMillis(), connection.getConnectElapsedTimeMillis(), connection.getStatusHints(), connection.getDisconnectCause(), emptyList, connection.getExtras(), conferenceId); mAdapter.addExistingConnection(id, parcelableConnection); } }
Returns all the active Connections for which this ConnectionService has taken responsibility.
Returns:A collection of Connections created by this ConnectionService.
/** * 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}. */
public final Collection<Connection> getAllConnections() { return mConnectionById.values(); }
Returns all the active Conferences for which this ConnectionService has taken responsibility.
Returns:A collection of Conferences created by this ConnectionService.
/** * Returns all the active {@code Conference}s for which this {@code ConnectionService} * has taken responsibility. * * @return A collection of {@code Conference}s created by this {@code ConnectionService}. */
public final Collection<Conference> getAllConferences() { return mConferenceById.values(); }
Create a Connection given an incoming request. This is used to attach to existing incoming calls.
Params:
Returns:The Connection object to satisfy this call, or null to not handle the call.
/** * 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. */
public Connection onCreateIncomingConnection( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request) { return null; }
Params:
@hide
/** * Called after the {@link Connection} returned by * {@link #onCreateIncomingConnection(PhoneAccountHandle, ConnectionRequest)} * or {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)} has been * added to the {@link ConnectionService} and sent to Telecom. * * @param connection the {@link Connection}. * @hide */
public void onCreateConnectionComplete(Connection connection) { }
Called by Telecom to inform the ConnectionService that its request to create a new incoming Connection was denied.

Used when a self-managed ConnectionService attempts to create a new incoming Connection, but Telecom has determined that the call cannot be allowed at this time. The ConnectionService is responsible for silently rejecting the new incoming Connection.

See TelecomManager.isIncomingCallPermitted(PhoneAccountHandle) for more information.

Params:
/** * Called by Telecom to inform the {@link ConnectionService} that its request to create a new * incoming {@link Connection} was denied. * <p> * Used when a self-managed {@link ConnectionService} attempts to create a new incoming * {@link Connection}, but Telecom has determined that the call cannot be allowed at this time. * The {@link ConnectionService} is responsible for silently rejecting the new incoming * {@link Connection}. * <p> * See {@link TelecomManager#isIncomingCallPermitted(PhoneAccountHandle)} for more information. * * @param connectionManagerPhoneAccount See description at * {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}. * @param request The incoming connection request. */
public void onCreateIncomingConnectionFailed(PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request) { }
Called by Telecom to inform the ConnectionService that its request to create a new outgoing Connection was denied.

Used when a self-managed ConnectionService attempts to create a new outgoing Connection, but Telecom has determined that the call cannot be placed at this time. The ConnectionService is responisible for informing the user that the Connection cannot be made at this time.

See TelecomManager.isOutgoingCallPermitted(PhoneAccountHandle) for more information.

Params:
/** * Called by Telecom to inform the {@link ConnectionService} that its request to create a new * outgoing {@link Connection} was denied. * <p> * Used when a self-managed {@link ConnectionService} attempts to create a new outgoing * {@link Connection}, but Telecom has determined that the call cannot be placed at this time. * The {@link ConnectionService} is responisible for informing the user that the * {@link Connection} cannot be made at this time. * <p> * See {@link TelecomManager#isOutgoingCallPermitted(PhoneAccountHandle)} for more information. * * @param connectionManagerPhoneAccount See description at * {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}. * @param request The outgoing connection request. */
public void onCreateOutgoingConnectionFailed(PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request) { }
Trigger recalculate functinality for conference calls. This is used when a Telephony Connection is part of a conference controller but is not yet added to Connection Service and hence cannot be added to the conference call.
@hide
/** * Trigger recalculate functinality for conference calls. This is used when a Telephony * Connection is part of a conference controller but is not yet added to Connection * Service and hence cannot be added to the conference call. * * @hide */
public void triggerConferenceRecalculate() { }
Create a Connection given an outgoing request. This is used to initiate new outgoing calls.
Params:
  • connectionManagerPhoneAccount – The connection manager account to use for managing this call.

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

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

  • request – Details about the outgoing call.
Returns:The Connection object to satisfy this call, or the result of an invocation of Connection.createFailedConnection(DisconnectCause) to not handle the call.
/** * 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. * <p> * 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. * <p> * 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. */
public Connection onCreateOutgoingConnection( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request) { return null; }
Called by Telecom to request that a ConnectionService creates an instance of an outgoing handover Connection.

A call handover is the process where an ongoing call is transferred from one app (i.e. ConnectionService to another app. The user could, for example, choose to continue a mobile network call in a video calling app. The mobile network call via the Telephony stack is referred to as the source of the handover, and the video calling app is referred to as the destination.

When considering a handover scenario the initiating device is where a user initiated the handover process (e.g. by calling Call.handoverTo(PhoneAccountHandle, int, Bundle), and the other device is considered the receiving device.

This method is called on the destination ConnectionService on initiating device when the user initiates a handover request from one app to another. The user request originates in the InCallService via Call.handoverTo(PhoneAccountHandle, int, Bundle).

For a full discussion of the handover process and the APIs involved, see Call.handoverTo(PhoneAccountHandle, int, Bundle).

Implementations of this method should return an instance of Connection which represents the handover. If your app does not wish to accept a handover to it at this time, you can return null. The code below shows an example of how this is done.


public Connection onCreateIncomingHandoverConnection(PhoneAccountHandle
    fromPhoneAccountHandle, ConnectionRequest request) {
  if (!isHandoverAvailable()) {
      return null;
  }
  MyConnection connection = new MyConnection();
  connection.setAddress(request.getAddress(), TelecomManager.PRESENTATION_ALLOWED);
  connection.setVideoState(request.getVideoState());
  return connection;
 }
 
Params:
  • fromPhoneAccountHandle – PhoneAccountHandle associated with the ConnectionService which needs to handover the call.
  • request – Details about the call to handover.
Returns:Connection instance corresponding to the handover call.
/** * Called by Telecom to request that a {@link ConnectionService} creates an instance of an * outgoing handover {@link Connection}. * <p> * A call handover is the process where an ongoing call is transferred from one app (i.e. * {@link ConnectionService} to another app. The user could, for example, choose to continue a * mobile network call in a video calling app. The mobile network call via the Telephony stack * is referred to as the source of the handover, and the video calling app is referred to as the * destination. * <p> * When considering a handover scenario the <em>initiating</em> device is where a user initiated * the handover process (e.g. by calling {@link android.telecom.Call#handoverTo( * PhoneAccountHandle, int, Bundle)}, and the other device is considered the <em>receiving</em> * device. * <p> * This method is called on the destination {@link ConnectionService} on <em>initiating</em> * device when the user initiates a handover request from one app to another. The user request * originates in the {@link InCallService} via * {@link android.telecom.Call#handoverTo(PhoneAccountHandle, int, Bundle)}. * <p> * For a full discussion of the handover process and the APIs involved, see * {@link android.telecom.Call#handoverTo(PhoneAccountHandle, int, Bundle)}. * <p> * Implementations of this method should return an instance of {@link Connection} which * represents the handover. If your app does not wish to accept a handover to it at this time, * you can return {@code null}. The code below shows an example of how this is done. * <pre> * {@code * public Connection onCreateIncomingHandoverConnection(PhoneAccountHandle * fromPhoneAccountHandle, ConnectionRequest request) { * if (!isHandoverAvailable()) { * return null; * } * MyConnection connection = new MyConnection(); * connection.setAddress(request.getAddress(), TelecomManager.PRESENTATION_ALLOWED); * connection.setVideoState(request.getVideoState()); * return connection; * } * } * </pre> * * @param fromPhoneAccountHandle {@link PhoneAccountHandle} associated with the * ConnectionService which needs to handover the call. * @param request Details about the call to handover. * @return {@link Connection} instance corresponding to the handover call. */
public Connection onCreateOutgoingHandoverConnection(PhoneAccountHandle fromPhoneAccountHandle, ConnectionRequest request) { return null; }
Called by Telecom to request that a ConnectionService creates an instance of an incoming handover Connection.

A call handover is the process where an ongoing call is transferred from one app (i.e. ConnectionService to another app. The user could, for example, choose to continue a mobile network call in a video calling app. The mobile network call via the Telephony stack is referred to as the source of the handover, and the video calling app is referred to as the destination.

When considering a handover scenario the initiating device is where a user initiated the handover process (e.g. by calling Call.handoverTo(PhoneAccountHandle, int, Bundle), and the other device is considered the receiving device.

This method is called on the destination app on the receiving device when the destination app calls TelecomManager.acceptHandover(Uri, int, PhoneAccountHandle) to accept an incoming handover from the initiating device.

For a full discussion of the handover process and the APIs involved, see Call.handoverTo(PhoneAccountHandle, int, Bundle).

Implementations of this method should return an instance of Connection which represents the handover. The code below shows an example of how this is done.


public Connection onCreateIncomingHandoverConnection(PhoneAccountHandle
    fromPhoneAccountHandle, ConnectionRequest request) {
  // Given that your app requested to accept the handover, you should not return null here.
  MyConnection connection = new MyConnection();
  connection.setAddress(request.getAddress(), TelecomManager.PRESENTATION_ALLOWED);
  connection.setVideoState(request.getVideoState());
  return connection;
 }
 
Params:
  • fromPhoneAccountHandle – PhoneAccountHandle associated with the ConnectionService which needs to handover the call.
  • request – Details about the call which needs to be handover.
Returns:Connection instance corresponding to the handover call.
/** * Called by Telecom to request that a {@link ConnectionService} creates an instance of an * incoming handover {@link Connection}. * <p> * A call handover is the process where an ongoing call is transferred from one app (i.e. * {@link ConnectionService} to another app. The user could, for example, choose to continue a * mobile network call in a video calling app. The mobile network call via the Telephony stack * is referred to as the source of the handover, and the video calling app is referred to as the * destination. * <p> * When considering a handover scenario the <em>initiating</em> device is where a user initiated * the handover process (e.g. by calling {@link android.telecom.Call#handoverTo( * PhoneAccountHandle, int, Bundle)}, and the other device is considered the <em>receiving</em> * device. * <p> * This method is called on the destination app on the <em>receiving</em> device when the * destination app calls {@link TelecomManager#acceptHandover(Uri, int, PhoneAccountHandle)} to * accept an incoming handover from the <em>initiating</em> device. * <p> * For a full discussion of the handover process and the APIs involved, see * {@link android.telecom.Call#handoverTo(PhoneAccountHandle, int, Bundle)}. * <p> * Implementations of this method should return an instance of {@link Connection} which * represents the handover. The code below shows an example of how this is done. * <pre> * {@code * public Connection onCreateIncomingHandoverConnection(PhoneAccountHandle * fromPhoneAccountHandle, ConnectionRequest request) { * // Given that your app requested to accept the handover, you should not return null here. * MyConnection connection = new MyConnection(); * connection.setAddress(request.getAddress(), TelecomManager.PRESENTATION_ALLOWED); * connection.setVideoState(request.getVideoState()); * return connection; * } * } * </pre> * * @param fromPhoneAccountHandle {@link PhoneAccountHandle} associated with the * ConnectionService which needs to handover the call. * @param request Details about the call which needs to be handover. * @return {@link Connection} instance corresponding to the handover call. */
public Connection onCreateIncomingHandoverConnection(PhoneAccountHandle fromPhoneAccountHandle, ConnectionRequest request) { return null; }
Called by Telecom in response to a TelecomManager#acceptHandover() invocation which failed.

For a full discussion of the handover process and the APIs involved, see Call.handoverTo(PhoneAccountHandle, int, Bundle)

Params:
  • request – Details about the call which failed to handover.
  • error – Reason for handover failure. Will be one of the
/** * Called by Telecom in response to a {@code TelecomManager#acceptHandover()} * invocation which failed. * <p> * For a full discussion of the handover process and the APIs involved, see * {@link android.telecom.Call#handoverTo(PhoneAccountHandle, int, Bundle)} * * @param request Details about the call which failed to handover. * @param error Reason for handover failure. Will be one of the */
public void onHandoverFailed(ConnectionRequest request, @Call.Callback.HandoverFailureErrors int error) { return; }
Create a 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 TelecomManager#addNewIncomingCall(PhoneAccountHandle, android.os.Bundle).
@hide
/** * 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)}. * * @hide */
public Connection onCreateUnknownConnection(PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request) { return null; }
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 Conference and pass it into addConference.
Params:
  • connection1 – A connection to merge into a conference call.
  • connection2 – A connection to merge into a conference call.
/** * 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 void onConference(Connection connection1, Connection connection2) {}
Called when a connection is added.
@hide
/** * Called when a connection is added. * @hide */
public void onConnectionAdded(Connection connection) {}
Called when a connection is removed.
@hide
/** * Called when a connection is removed. * @hide */
public void onConnectionRemoved(Connection connection) {}
Called when a conference is added.
@hide
/** * Called when a conference is added. * @hide */
public void onConferenceAdded(Conference conference) {}
Called when a conference is removed.
@hide
/** * Called when a conference is removed. * @hide */
public void onConferenceRemoved(Conference conference) {}
Indicates that a remote conference has been created for existing RemoteConnections. When this method is invoked, this ConnectionService should create its own representation of the conference call and send it to telecom using addConference.

This is only relevant to ConnectionServices which are registered with PhoneAccount.CAPABILITY_CONNECTION_MANAGER.

Params:
  • conference – The remote conference call.
/** * 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}. * <p> * This is only relevant to {@link ConnectionService}s which are registered with * {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER}. * * @param conference The remote conference call. */
public void onRemoteConferenceAdded(RemoteConference conference) {}
Called when an existing connection is added remotely.
Params:
  • connection – The existing connection which was added.
/** * Called when an existing connection is added remotely. * @param connection The existing connection which was added. */
public void onRemoteExistingConnectionAdded(RemoteConnection connection) {}
Called when the ConnectionService has lost the call focus. The ConnectionService should release the call resources and invokes connectionServiceFocusReleased() to inform telecom that it has released the call resources.
/** * Called when the {@link ConnectionService} has lost the call focus. * The {@link ConnectionService} should release the call resources and invokes * {@link ConnectionService#connectionServiceFocusReleased()} to inform telecom that it has * released the call resources. */
public void onConnectionServiceFocusLost() {}
Called when the ConnectionService has gained the call focus. The ConnectionService can acquire the call resources at this time.
/** * Called when the {@link ConnectionService} has gained the call focus. The * {@link ConnectionService} can acquire the call resources at this time. */
public void onConnectionServiceFocusGained() {}
@hide
/** * @hide */
public boolean containsConference(Conference conference) { return mIdByConference.containsKey(conference); }
{@hide}
/** {@hide} */
void addRemoteConference(RemoteConference remoteConference) { onRemoteConferenceAdded(remoteConference); }
{@hide}
/** {@hide} */
void addRemoteExistingConnection(RemoteConnection remoteConnection) { onRemoteExistingConnectionAdded(remoteConnection); } private void onAccountsInitialized() { mAreAccountsInitialized = true; for (Runnable r : mPreInitializationConnectionRequests) { r.run(); } mPreInitializationConnectionRequests.clear(); }
Adds an existing connection to the list of connections, identified by a new call ID unique to this connection service.
Params:
  • connection – The connection.
Returns:The ID of the connection (e.g. the call-id).
/** * Adds an existing connection to the list of connections, identified by a new call ID unique * to this connection service. * * @param connection The connection. * @return The ID of the connection (e.g. the call-id). */
private String addExistingConnectionInternal(PhoneAccountHandle handle, Connection connection) { String id; if (connection.getExtras() != null && connection.getExtras() .containsKey(Connection.EXTRA_ORIGINAL_CONNECTION_ID)) { id = connection.getExtras().getString(Connection.EXTRA_ORIGINAL_CONNECTION_ID); Log.d(this, "addExistingConnectionInternal - conn %s reusing original id %s", connection.getTelecomCallId(), id); } else if (handle == null) { // If no phone account handle was provided, we cannot be sure the call ID is unique, // so just use a random UUID. id = UUID.randomUUID().toString(); } else { // Phone account handle was provided, so use the ConnectionService class name as a // prefix for a unique incremental call ID. id = handle.getComponentName().getClassName() + "@" + getNextCallId(); } addConnection(handle, id, connection); return id; } private void addConnection(PhoneAccountHandle handle, String callId, Connection connection) { connection.setTelecomCallId(callId); mConnectionById.put(callId, connection); mIdByConnection.put(connection, callId); connection.addConnectionListener(mConnectionListener); connection.setConnectionService(this); connection.setPhoneAccountHandle(handle); onConnectionAdded(connection); }
{@hide}
/** {@hide} */
protected void removeConnection(Connection connection) { connection.unsetConnectionService(this); connection.removeConnectionListener(mConnectionListener); String id = mIdByConnection.get(connection); if (id != null) { mConnectionById.remove(id); mIdByConnection.remove(connection); mAdapter.removeCall(id); onConnectionRemoved(connection); } } private String addConferenceInternal(Conference conference) { String originalId = null; if (conference.getExtras() != null && conference.getExtras() .containsKey(Connection.EXTRA_ORIGINAL_CONNECTION_ID)) { originalId = conference.getExtras().getString(Connection.EXTRA_ORIGINAL_CONNECTION_ID); Log.d(this, "addConferenceInternal: conf %s reusing original id %s", conference.getTelecomCallId(), originalId); } if (mIdByConference.containsKey(conference)) { Log.w(this, "Re-adding an existing conference: %s.", conference); } else if (conference != null) { // Conferences do not (yet) have a PhoneAccountHandle associated with them, so we // cannot determine a ConnectionService class name to associate with the ID, so use // a unique UUID (for now). String id = originalId == null ? UUID.randomUUID().toString() : originalId; mConferenceById.put(id, conference); mIdByConference.put(conference, id); conference.addListener(mConferenceListener); return id; } return null; } private void removeConference(Conference conference) { if (mIdByConference.containsKey(conference)) { conference.removeListener(mConferenceListener); String id = mIdByConference.get(conference); mConferenceById.remove(id); mIdByConference.remove(conference); mAdapter.removeCall(id); onConferenceRemoved(conference); } } private Connection findConnectionForAction(String callId, String action) { if (callId != null && mConnectionById.containsKey(callId)) { return mConnectionById.get(callId); } Log.w(this, "%s - Cannot find Connection %s", action, callId); return getNullConnection(); } static synchronized Connection getNullConnection() { if (sNullConnection == null) { sNullConnection = new Connection() {}; } return sNullConnection; } private Conference findConferenceForAction(String conferenceId, String action) { if (mConferenceById.containsKey(conferenceId)) { return mConferenceById.get(conferenceId); } Log.w(this, "%s - Cannot find conference %s", action, conferenceId); return getNullConference(); } private List<String> createConnectionIdList(List<Connection> connections) { List<String> ids = new ArrayList<>(); for (Connection c : connections) { if (mIdByConnection.containsKey(c)) { ids.add(mIdByConnection.get(c)); } } Collections.sort(ids); return ids; }
Builds a list of Connection and Conference IDs based on the list of Conferenceables passed in.
Params:
Returns:List of string conference and call Ids.
/** * Builds a list of {@link Connection} and {@link Conference} IDs based on the list of * {@link Conferenceable}s passed in. * * @param conferenceables The {@link Conferenceable} connections and conferences. * @return List of string conference and call Ids. */
private List<String> createIdList(List<Conferenceable> conferenceables) { List<String> ids = new ArrayList<>(); for (Conferenceable 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; } private Conference getNullConference() { if (sNullConference == null) { sNullConference = new Conference(null) {}; } return sNullConference; } private void endAllConnections() { // 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(); } }
Retrieves the next call ID as maintainted by the connection service.
Returns:The call ID.
/** * Retrieves the next call ID as maintainted by the connection service. * * @return The call ID. */
private int getNextCallId() { synchronized (mIdSyncRoot) { return ++mId; } } }