1、图形显示
挂断电话分为本地挂断和远程对方挂断
2、本地挂断
1)、点击按钮
先看按键的监听事件 CallCardFragment.java 中有对按钮的监听事件
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mPulseAnimation =
AnimationUtils.loadAnimation(view.getContext(), R.anim.call_status_pulse);
mPhoneNumber = (TextView) view.findViewById(R.id.phoneNumber);
mConnectedAddress = (TextView) view.findViewById(R.id.connectedAddress);
mPrimaryName = (TextView) view.findViewById(R.id.name);
mNumberLabel = (TextView) view.findViewById(R.id.label);
mPrimarySimIndication = (TextView) view.findViewById(R.id.simIndication);
mPrimaryLocation = (TextView) view.findViewById(R.id.location);
mSecondaryCallInfo = view.findViewById(R.id.secondary_call_info);
mSecondaryCallProviderInfo = view.findViewById(R.id.secondary_call_provider_info);
mCallCardContent = view.findViewById(R.id.call_card_content);
mPhotoLarge = (ImageView) view.findViewById(R.id.photoLarge);
mPhotoLarge.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
getPresenter().onContactPhotoClick();
}
});
mContactContext = view.findViewById(R.id.contact_context);
mContactContextTitle = (TextView) view.findViewById(R.id.contactContextTitle);
mContactContextListView = (ListView) view.findViewById(R.id.contactContextInfo);
// This layout stores all the list header layouts so they can be easily removed.
mContactContextListHeaders = new LinearLayout(getView().getContext());
mContactContextListView.addHeaderView(mContactContextListHeaders);
mCallStateIcon = (ImageView) view.findViewById(R.id.callStateIcon);
mCallStateVideoCallIcon = (ImageView) view.findViewById(R.id.videoCallIcon);
mWorkProfileIcon = (ImageView) view.findViewById(R.id.workProfileIcon);
mCallStateLabel = (TextView) view.findViewById(R.id.callStateLabel);
mHdAudioIcon = (ImageView) view.findViewById(R.id.hdAudioIcon);
mForwardIcon = (ImageView) view.findViewById(R.id.forwardIcon);
mCallNumberAndLabel = view.findViewById(R.id.labelAndNumber);
mCallLocationAndType = view.findViewById(R.id.locationAndType);
mCallTypeLabel = (TextView) view.findViewById(R.id.callTypeLabel);
mRecordingLabel = (TextView) view.findViewById(R.id.recordinglabel);
mElapsedTime = (TextView) view.findViewById(R.id.elapsedTime);
mPrimaryCallCardContainer = view.findViewById(R.id.primary_call_info_container);
mPrimaryCallInfo = (ViewGroup) view.findViewById(R.id.primary_call_banner);
mCallButtonsContainer = view.findViewById(R.id.callButtonFragment);
mPhotoSmall = (ImageView) view.findViewById(R.id.photoSmall);
mPhotoSmall.setVisibility(View.GONE);
mInCallMessageLabel = (TextView) view.findViewById(R.id.connectionServiceMessage);
mProgressSpinner = view.findViewById(R.id.progressSpinner);
mFloatingActionButtonContainer = view.findViewById(
R.id.floating_end_call_action_button_container);
mFloatingActionButton = (ImageButton) view.findViewById(
R.id.floating_end_call_action_button);
mFloatingActionButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//调用的是这一个函数
getPresenter().endCallClicked();
}
});
mFloatingActionButtonController = new FloatingActionButtonController(getActivity(),
mFloatingActionButtonContainer, mFloatingActionButton);
mSecondaryCallInfo.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
getPresenter().secondaryInfoClicked();
updateFabPositionForSecondaryCallInfo();
}
});
mCallStateButton = view.findViewById(R.id.callStateButton);
mCallStateButton.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
getPresenter().onCallStateButtonTouched();
return false;
}
});
mManageConferenceCallButton = view.findViewById(R.id.manage_conference_call_button);
mManageConferenceCallButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
InCallActivity activity = (InCallActivity) getActivity();
activity.showConferenceFragment(true);
}
});
mPrimaryName.setElegantTextHeight(false);
mCallStateLabel.setElegantTextHeight(false);
mCallSubject = (TextView) view.findViewById(R.id.callSubject);
}
2)、CallCardPresenter.java的endCallClicked()方法。
/**
* endCallClicked.
*/
public void endCallClicked() {
if (mPrimary == null) {
Log.i(TAG, "(mPrimary == null)");
return;
}
Log.i(TAG, "Disconnecting call: " + mPrimary);
Log.i(TAG, "mPrimary.getDisconnectCause().getCode(): "
+ mPrimary.getDisconnectCause().getCode());
if (mPrimary.getDisconnectCause().getCode() != DisconnectCause.BUSY) {
final String callId = mPrimary.getId();
mPrimary.setState(Call.State.DISCONNECTING);
CallList.getInstance().onUpdate(mPrimary);
TelecomAdapter.getInstance().disconnectCall(callId);
} else {
mPrimary.setState(Call.State.DISCONNECTED);
CallList.getInstance().onDisconnect(mPrimary);
//if has held call, unhold call.
/* if (mSecondary != null && mSecondary.getState() == Call.State.ONHOLD) {
final String callId = mSecondary.getId();
TelecomAdapter.getInstance().unholdCall(callId);
}*/
}
}
这里先把call的状态设置成DISCONNECTING,然后通过CallList更新UI界面,最后继续挂断流程。
3)、TelecomAdapter.java文件的disconnectCall()方法
void disconnectCall(String callId) {
android.telecom.Call call = getTelecomCallById(callId);
if (call != null) {
//执行这一句
call.disconnect();
} else {
Log.e(TAG, "error disconnectCall, call not in call list " + callId);
}
}
通过Callid找到对应的Call对象(android.telephone.Call)
4)、调用到framework里call.java的disconnect()方法
/**
* Instructs this {@code Call} to disconnect.
*/
public void disconnect() {
mInCallAdapter.disconnectCall(mTelecomCallId);
}
5)、调用到framework里InCallAdapter.java的disconnectCall()方法
/**
* Instructs Telecom to disconnect the specified call.
*
* @param callId The identifier of the call to disconnect.
*/
public void disconnectCall(String callId) {
try {
mAdapter.disconnectCall(callId);
} catch (RemoteException e) {
}
}
mAdapter就是incallui与telecom通信的AIDL接口
跨进程调用,进入telecom进程,该AIDL接口具体实现类是InCallAdapter,相同的类名不一样的包名。
6)、调用到packages里InCallAdapter.java的disconnectCall()方法
@Override
public void disconnectCall(String callId) {
try {
Log.startSession("ICA.dC", mOwnerComponentName);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
Log.v(this, "disconnectCall: %s", callId);
Call call = mCallIdMapper.getCall(callId);
if (call != null) {
//调用到这句话 mCallsManager.disconnectCall(call);
} else {
Log.w(this, "disconnectCall, unknown call id: %s", callId);
}
}
} finally {
Binder.restoreCallingIdentity(token);
}
} finally {
Log.endSession();
}
}
同样是根据callid取出对应的 Call(com.android.server.telecom.Call),最后调用CallsManager的disconnectCall方法传入call
7)、CallsManager.java文件的diaconnectCall()方法
/**
* Instructs Telecom to disconnect the specified call. Intended to be invoked by the
* in-call app through {@link InCallAdapter} for an ongoing call. This is usually triggered by
* the user hitting the end-call button.
*/
@VisibleForTesting
public void disconnectCall(Call call) {
Log.v(this, "disconnectCall %s", call);
if (!mCalls.contains(call)) {
Log.w(this, "Unknown call (%s) asked to disconnect", call);
} else {
mLocallyDisconnectingCalls.add(call);
call.disconnect();
}
}
将该call对象加入本地断开call列表,之后进入Call的disconnect方法。
8)、call.java的disconnect()方法
@VisibleForTesting
public void disconnect() {
disconnect(false);
}
继续调用
/**
* Attempts to disconnect the call through the connection service.
*/
@VisibleForTesting
public void disconnect(boolean wasViaNewOutgoingCallBroadcaster) {
Log.event(this, Log.Events.REQUEST_DISCONNECT);
// Track that the call is now locally disconnecting.
setLocallyDisconnecting(true);
if (mState == CallState.NEW || mState == CallState.SELECT_PHONE_ACCOUNT ||
mState == CallState.CONNECTING) {
Log.v(this, "Aborting call %s", this);
abort(wasViaNewOutgoingCallBroadcaster);
} else if (mState != CallState.ABORTED && mState != CallState.DISCONNECTED) {
if (mConnectionService == null) {
Log.e(this, new Exception(), "disconnect() request on a call without a"
+ " connection service.");
} else {
Log.i(this, "Send disconnect to connection service for call: %s", this);
// The call isn't officially disconnected until the connection service
// confirms that the call was actually disconnected. Only then is the
// association between call and connection service severed, see
// {@link CallsManager#markCallAsDisconnected}.
//调用这个函数
mConnectionService.disconnect(this);
}
}
}
setLocallyDisconnecting(true); 先设置本地挂断标志为true
由于mState这个时候是CallState.ACTIVE状态,进入mConnectionService的disconnect方法。
这里的mConnectionService是ConnectionServiceWrapper类,是telecom与telephony通信的代理类
9)、ConnectionServiceWrapper.java的disconnect()方法
/** @see IConnectionService#disconnect(String) */
void disconnect(Call call) {
final String callId = mCallIdMapper.getCallId(call);
if (callId != null && isServiceValid("disconnect")) {
try {
logOutgoing("disconnect %s", callId);
mServiceInterface.disconnect(callId);
} catch (RemoteException e) {
}
}
}
这里的mServiceInterface就是telephony提供给telecom调用的AIDL接口
跨进程进入telephony,AIDL接口具体实现是其父类ConnectionService的mBinder成员变量
10)、ConnectionService.java文件
/**
* An abstract service that should be implemented by any apps which can make phone calls (VoIP or
* otherwise) and want those calls to be integrated into the built-in phone app.
* Once implemented, the {@code ConnectionService} needs two additional steps before it will be
* integrated into the phone app:
* <p>
* 1. <i>Registration in AndroidManifest.xml</i>
* <br/>
* <pre>
* <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>
* </pre>
* <p>
* 2. <i> Registration of {@link PhoneAccount} with {@link TelecomManager}.</i>
* <br/>
* See {@link PhoneAccount} and {@link TelecomManager#registerPhoneAccount} for more information.
* <p>
* Once registered and enabled by the user in the phone app settings, telecom will bind to a
* {@code ConnectionService} implementation when it wants that {@code ConnectionService} to place
* a call or the service has indicated that is has an incoming call through
* {@link TelecomManager#addNewIncomingCall}. The {@code ConnectionService} can then expect a call
* to {@link #onCreateIncomingConnection} or {@link #onCreateOutgoingConnection} wherein it
* should provide a new instance of a {@link Connection} object. It is through this
* {@link Connection} object that telecom receives state updates and the {@code ConnectionService}
* receives call-commands such as answer, reject, hold and disconnect.
* <p>
* When there are no more live calls, telecom will unbind from the {@code ConnectionService}.
*/
public abstract class ConnectionService extends 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";
// Flag controlling whether PII is emitted into the logs
private static final boolean PII_DEBUG = Log.isLoggable(android.util.Log.DEBUG);
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 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) {
mHandler.obtainMessage(MSG_ADD_CONNECTION_SERVICE_ADAPTER, adapter).sendToTarget();
}
public void removeConnectionServiceAdapter(IConnectionServiceAdapter adapter) {
mHandler.obtainMessage(MSG_REMOVE_CONNECTION_SERVICE_ADAPTER, adapter).sendToTarget();
}
@Override
public void createConnection(
PhoneAccountHandle connectionManagerPhoneAccount,
String id,
ConnectionRequest request,
boolean isIncoming,
boolean isUnknown) {
SomeArgs args = SomeArgs.obtain();
args.arg1 = connectionManagerPhoneAccount;
args.arg2 = id;
args.arg3 = request;
args.argi1 = isIncoming ? 1 : 0;
args.argi2 = isUnknown ? 1 : 0;
mHandler.obtainMessage(MSG_CREATE_CONNECTION, args).sendToTarget();
}
@Override
public void abort(String callId) {
mHandler.obtainMessage(MSG_ABORT, callId).sendToTarget();
}
@Override
public void answerVideo(String callId, int videoState) {
SomeArgs args = SomeArgs.obtain();
args.arg1 = callId;
args.argi1 = videoState;
mHandler.obtainMessage(MSG_ANSWER_VIDEO, args).sendToTarget();
}
@Override
public void answer(String callId) {
mHandler.obtainMessage(MSG_ANSWER, callId).sendToTarget();
}
@Override
public void reject(String callId) {
mHandler.obtainMessage(MSG_REJECT, callId).sendToTarget();
}
@Override
public void rejectWithMessage(String callId, String message) {
SomeArgs args = SomeArgs.obtain();
args.arg1 = callId;
args.arg2 = message;
mHandler.obtainMessage(MSG_REJECT_WITH_MESSAGE, args).sendToTarget();
}
@Override
public void silence(String callId) {
mHandler.obtainMessage(MSG_SILENCE, callId).sendToTarget();
}
//调用这个函数
@Override
public void disconnect(String callId) {
mHandler.obtainMessage(MSG_DISCONNECT, callId).sendToTarget();
}
@Override
public void hold(String callId) {
mHandler.obtainMessage(MSG_HOLD, callId).sendToTarget();
}
@Override
public void unhold(String callId) {
mHandler.obtainMessage(MSG_UNHOLD, callId).sendToTarget();
}
@Override
public void onCallAudioStateChanged(String callId, CallAudioState callAudioState) {
SomeArgs args = SomeArgs.obtain();
args.arg1 = callId;
args.arg2 = callAudioState;
mHandler.obtainMessage(MSG_ON_CALL_AUDIO_STATE_CHANGED, args).sendToTarget();
}
@Override
public void playDtmfTone(String callId, char digit) {
mHandler.obtainMessage(MSG_PLAY_DTMF_TONE, digit, 0, callId).sendToTarget();
}
@Override
public void stopDtmfTone(String callId) {
mHandler.obtainMessage(MSG_STOP_DTMF_TONE, callId).sendToTarget();
}
@Override
public void conference(String callId1, String callId2) {
SomeArgs args = SomeArgs.obtain();
args.arg1 = callId1;
args.arg2 = callId2;
mHandler.obtainMessage(MSG_CONFERENCE, args).sendToTarget();
}
@Override
public void splitFromConference(String callId) {
mHandler.obtainMessage(MSG_SPLIT_FROM_CONFERENCE, callId).sendToTarget();
}
@Override
public void mergeConference(String callId) {
mHandler.obtainMessage(MSG_MERGE_CONFERENCE, callId).sendToTarget();
}
@Override
public void swapConference(String callId) {
mHandler.obtainMessage(MSG_SWAP_CONFERENCE, callId).sendToTarget();
}
@Override
public void onPostDialContinue(String callId, boolean proceed) {
SomeArgs args = SomeArgs.obtain();
args.arg1 = callId;
args.argi1 = proceed ? 1 : 0;
mHandler.obtainMessage(MSG_ON_POST_DIAL_CONTINUE, args).sendToTarget();
}
@Override
public void pullExternalCall(String callId) {
mHandler.obtainMessage(MSG_PULL_EXTERNAL_CALL, callId).sendToTarget();
}
@Override
public void sendCallEvent(String callId, String event, Bundle extras) {
SomeArgs args = SomeArgs.obtain();
args.arg1 = callId;
args.arg2 = event;
args.arg3 = extras;
mHandler.obtainMessage(MSG_SEND_CALL_EVENT, args).sendToTarget();
}
@Override
public void onExtrasChanged(String callId, Bundle extras) {
SomeArgs args = SomeArgs.obtain();
args.arg1 = callId;
args.arg2 = extras;
mHandler.obtainMessage(MSG_ON_EXTRAS_CHANGED, args).sendToTarget();
}
};
private final Handler mHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_ADD_CONNECTION_SERVICE_ADAPTER:
mAdapter.addAdapter((IConnectionServiceAdapter) msg.obj);
onAdapterAttached();
break;
case MSG_REMOVE_CONNECTION_SERVICE_ADAPTER:
mAdapter.removeAdapter((IConnectionServiceAdapter) msg.obj);
break;
case MSG_CREATE_CONNECTION: {
SomeArgs args = (SomeArgs) msg.obj;
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 Runnable() {
@Override
public void run() {
createConnection(
connectionManagerPhoneAccount,
id,
request,
isIncoming,
isUnknown);
}
});
} else {
createConnection(
connectionManagerPhoneAccount,
id,
request,
isIncoming,
isUnknown);
}
} finally {
args.recycle();
}
break;
}
case MSG_ABORT:
abort((String) msg.obj);
break;
case MSG_ANSWER:
answer((String) msg.obj);
break;
case MSG_ANSWER_VIDEO: {
SomeArgs args = (SomeArgs) msg.obj;
try {
String callId = (String) args.arg1;
int videoState = args.argi1;
answerVideo(callId, videoState);
} finally {
args.recycle();
}
break;
}
case MSG_REJECT:
reject((String) msg.obj);
break;
case MSG_REJECT_WITH_MESSAGE: {
SomeArgs args = (SomeArgs) msg.obj;
try {
reject((String) args.arg1, (String) args.arg2);
} finally {
args.recycle();
}
break;
}
case MSG_DISCONNECT:
disconnect((String) msg.obj);
break;
case MSG_SILENCE:
silence((String) msg.obj);
break;
case MSG_HOLD:
hold((String) msg.obj);
break;
case MSG_UNHOLD:
unhold((String) msg.obj);
break;
case MSG_ON_CALL_AUDIO_STATE_CHANGED: {
SomeArgs args = (SomeArgs) msg.obj;
try {
String callId = (String) args.arg1;
CallAudioState audioState = (CallAudioState) args.arg2;
onCallAudioStateChanged(callId, new CallAudioState(audioState));
} finally {
args.recycle();
}
break;
}
case MSG_PLAY_DTMF_TONE:
playDtmfTone((String) msg.obj, (char) msg.arg1);
break;
case MSG_STOP_DTMF_TONE:
stopDtmfTone((String) msg.obj);
break;
case MSG_CONFERENCE: {
SomeArgs args = (SomeArgs) msg.obj;
try {
String callId1 = (String) args.arg1;
String callId2 = (String) args.arg2;
conference(callId1, callId2);
} finally {
args.recycle();
}
break;
}
case MSG_SPLIT_FROM_CONFERENCE:
splitFromConference((String) msg.obj);
break;
case MSG_MERGE_CONFERENCE:
mergeConference((String) msg.obj);
break;
case MSG_SWAP_CONFERENCE:
swapConference((String) msg.obj);
break;
case MSG_ON_POST_DIAL_CONTINUE: {
SomeArgs args = (SomeArgs) msg.obj;
try {
String callId = (String) args.arg1;
boolean proceed = (args.argi1 == 1);
onPostDialContinue(callId, proceed);
} finally {
args.recycle();
}
break;
}
case MSG_PULL_EXTERNAL_CALL: {
pullExternalCall((String) msg.obj);
break;
}
case MSG_SEND_CALL_EVENT: {
SomeArgs args = (SomeArgs) msg.obj;
try {
String callId = (String) args.arg1;
String event = (String) args.arg2;
Bundle extras = (Bundle) args.arg3;
sendCallEvent(callId, event, extras);
} finally {
args.recycle();
}
break;
}
case MSG_ON_EXTRAS_CHANGED: {
SomeArgs args = (SomeArgs) msg.obj;
try {
String callId = (String) args.arg1;
Bundle extras = (Bundle) args.arg2;
handleExtrasChanged(callId, extras);
} finally {
args.recycle();
}
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_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 onConnectedAddressChanged(Connection c, Uri connectedAddress, int connectedAddressPresentation) {
String id = mIdByConnection.get(c);
mAdapter.setConnectedAddress(id, connectedAddress, connectedAddressPresentation);
}
@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);
}
}
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);
}
}
};
/** {@inheritDoc} */
@Override
public final IBinder onBind(Intent intent) {
return mBinder;
}
/** {@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.
*/
private void createConnection(
final PhoneAccountHandle callManagerAccount,
final String callId,
final ConnectionRequest request,
boolean isIncoming,
boolean isUnknown) {
Log.d(this, "lum_1 createConnection, callManagerAccount: %s, callId: %s, request: %s, " +
"isIncoming: %b, isUnknown: %b", callManagerAccount, callId, request,
isIncoming,
isUnknown);
Connection connection = isUnknown ? onCreateUnknownConnection(callManagerAccount, request)
: isIncoming ? onCreateIncomingConnection(callManagerAccount, request)
: onCreateOutgoingConnection(callManagerAccount, request);
Log.d(this, "lum_2 createConnection, connection: %s", connection);
if (connection == null) {
Log.d(this, "lum_3");
connection = Connection.createFailedConnection(
new DisconnectCause(DisconnectCause.ERROR));
}
connection.setTelecomCallId(callId);
if (connection.getState() != Connection.STATE_DISCONNECTED) {
addConnection(callId, connection);
}
Uri address = connection.getAddress();
String number = address == null ? "null" : address.getSchemeSpecificPart();
Log.v(this, "createConnection, number: %s, state: %s, capabilities: %s, 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.getAddress(),
connection.getAddressPresentation(),
connection.getCallerDisplayName(),
connection.getCallerDisplayNamePresentation(),
connection.getVideoProvider() == null ?
null : connection.getVideoProvider().getInterface(),
connection.getVideoState(),
connection.isRingbackRequested(),
connection.getAudioModeIsVoip(),
connection.getConnectTimeMillis(),
connection.getStatusHints(),
connection.getDisconnectCause(),
createIdList(connection.getConferenceables()),
connection.getExtras()));
if (isUnknown) {
triggerConferenceRecalculate();
}
}
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 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 {@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 {@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 {@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 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 Runnable() {
@Override
public void run() {
for (int i = 0; i < componentNames.size() && i < services.size(); i++) {
mRemoteConnectionManager.addConnectionService(
componentNames.get(i),
IConnectionService.Stub.asInterface(services.get(i)));
}
onAccountsInitialized();
Log.d(this, "remote connection services found: " + services);
}
});
}
@Override
public void onError() {
mHandler.post(new Runnable() {
@Override
public void run() {
mAreAccountsInitialized = true;
}
});
}
});
}
/**
* 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 {@code ConnectionService} to create a {@code RemoteConnection} given an
* outgoing request. This is used by {@code ConnectionService}s that are registered with
* {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER} and want to be able to use the
* SIM-based {@code ConnectionService} to place its outgoing calls.
*
* @param connectionManagerPhoneAccount See description at
* {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}.
* @param request Details about the incoming call.
* @return The {@code Connection} object to satisfy this call, or {@code null} to
* not handle the call.
*/
public final RemoteConnection createRemoteOutgoingConnection(
PhoneAccountHandle connectionManagerPhoneAccount,
ConnectionRequest request) {
return mRemoteConnectionManager.createRemoteConnection(
connectionManagerPhoneAccount, request, false);
}
/**
* 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 {@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.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);
}
}
}
}
/**
* 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) {
String id = addExistingConnectionInternal(phoneAccountHandle, connection);
if (id != null) {
List<String> emptyList = new ArrayList<>(0);
ParcelableConnection parcelableConnection = new ParcelableConnection(
phoneAccountHandle,
connection.getState(),
connection.getConnectionCapabilities(),
connection.getConnectionProperties(),
connection.getAddress(),
connection.getAddressPresentation(),
connection.getCallerDisplayName(),
connection.getCallerDisplayNamePresentation(),
connection.getVideoProvider() == null ?
null : connection.getVideoProvider().getInterface(),
connection.getVideoState(),
connection.isRingbackRequested(),
connection.getAudioModeIsVoip(),
connection.getConnectTimeMillis(),
connection.getStatusHints(),
connection.getDisconnectCause(),
emptyList,
connection.getExtras());
mAdapter.addExistingConnection(id, parcelableConnection);
}
}
/**
* 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 {@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 {@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;
}
/**
* 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 {@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;
}
/**
* Create a {@code Connection} for a new unknown call. An unknown call is a call originating
* from the ConnectionService that was neither a user-initiated outgoing call, nor an incoming
* call created using
* {@code TelecomManager#addNewIncomingCall(PhoneAccountHandle, android.os.Bundle)}.
*
* @param connectionManagerPhoneAccount
* @param request
* @return
*
* @hide
*/
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 {@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) {}
/**
* 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.
* @param connection The existing connection which was added.
*/
public void onRemoteExistingConnectionAdded(RemoteConnection connection) {}
/**
* @hide
*/
public boolean containsConference(Conference conference) {
return mIdByConference.containsKey(conference);
}
/** {@hide} */
void addRemoteConference(RemoteConference remoteConference) {
onRemoteConferenceAdded(remoteConference);
}
/** {@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.
*
* @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 (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(id, connection);
return id;
}
private void addConnection(String callId, Connection connection) {
connection.setTelecomCallId(callId);
mConnectionById.put(callId, connection);
mIdByConnection.put(connection, callId);
connection.addConnectionListener(mConnectionListener);
connection.setConnectionService(this);
}
/** {@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);
}
}
private String addConferenceInternal(Conference conference) {
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 = UUID.randomUUID().toString();
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);
}
}
private Connection findConnectionForAction(String callId, String action) {
if (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 {@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.
*
* @return The call ID.
*/
private int getNextCallId() {
synchronized(mIdSyncRoot) {
return ++mId;
}
}
}
发送MES_DISCONNECT消息到队列了处理
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 Connection findConnectionForAction(String callId, String action) {
if (mConnectionById.containsKey(callId)) {
return mConnectionById.get(callId);
}
Log.w(this, "%s - Cannot find Connection %s", action, callId);
return getNullConnection();
}
根据callid找到对应的connection对象(android.telecom.Connection),调用onDisconnect()方法
11)、TelephonyConnection.java文件的onDisconnect()方法
@Override
public void onDisconnect() {
Log.v(this, "onDisconnect");
hangup(android.telephony.DisconnectCause.LOCAL);
}
接着调用
protected void hangup(int telephonyDisconnectCode) {
if (mOriginalConnection != null) {
int connectionState = getState();
Log.w(this, "hangup connection state: " + connectionState);
try {
// Hanging up a ringing call requires that we invoke call.hangup() as opposed to
// connection.hangup(). Without this change, the party originating the call will not
// get sent to voicemail if the user opts to reject the call.
if (isValidRingingCall()) {
Log.v(this, "The call is Valid Ringing call");
Call call = getCall();
if (call != null) {
call.hangup();
} else {
Log.w(this, "Attempting to hangup a connection without backing call.");
}
} else if (isValidWaitingCall()) {
Log.w(this, "isValidWaitingCall");
if (connectionState == STATE_HOLDING) {
mNeedAccept = true;
mOriginalConnection.hangup();
} else if (connectionState == STATE_ACTIVE) {
Call call = getCall(); // this call is foreground call
if (call != null) {
//调用到这个函数
call.hangup();
} else {
Log.w(this, "Foreground call does not exist.");
}
}
} else if (isSrvccCompleted()) {
Log.d(this, "hangup: After handover, trigger pending request");
mPendingRequest = PENDING_REQUEST_TERMINATE;
} else {
// We still prefer to call connection.hangup() for non-ringing calls in order
// to support hanging-up specific calls within a conference call. If we invoked
// call.hangup() while in a conference, we would end up hanging up the entire
// conference call instead of the specific connection.
mOriginalConnection.hangup();
}
} catch (CallStateException e) {
Log.e(this, e, "Call to Connection.hangup failed with exception");
}
}
}
设置断开链接类型DisconnectCause.LOCAL,并调用mOriginalConnection的hangup方法,实际Connection类是GsmCdmaConnection
12)、GsmCdmaConnection.java文件的hangup()方法
@Override
public void hangup() throws CallStateException {
if (!mDisconnected) {
mOwner.hangup(this);
} else {
throw new CallStateException ("disconnected");
}
}
13)、调用GsmCdmaCallTracker.java的hangup()方法
//***** Called from GsmCdmaConnection
public void hangup(GsmCdmaConnection conn) throws CallStateException {
if (conn.mOwner != this) {
throw new CallStateException ("GsmCdmaConnection " + conn
+ "does not belong to GsmCdmaCallTracker " + this);
}
if (conn == mPendingMO) {
// We're hanging up an outgoing call that doesn't have it's
// GsmCdma index assigned yet
if (Phone.DEBUG_PHONE) log("hangup: set hangupPendingMO to true");
mHangupPendingMO = true;
} else if (!isPhoneTypeGsm()
&& conn.getCall() == mRingingCall
&& mRingingCall.getState() == GsmCdmaCall.State.WAITING) {
// Handle call waiting hang up case.
//
// The ringingCall state will change to IDLE in GsmCdmaCall.detach
// if the ringing call connection size is 0. We don't specifically
// set the ringing call state to IDLE here to avoid a race condition
// where a new call waiting could get a hang up from an old call
// waiting ringingCall.
//
// PhoneApp does the call log itself since only PhoneApp knows
// the hangup reason is user ignoring or timing out. So conn.onDisconnect()
// is not called here. Instead, conn.onLocalDisconnect() is called.
conn.onLocalDisconnect();
updatePhoneState();
mPhone.notifyPreciseCallStateChanged();
return;
} else {
try {
//调用这个函数(注意参数)
mCi.hangupConnection (conn.getGsmCdmaIndex(), obtainCompleteMessage());
} catch (CallStateException ex) {
// Ignore "connection not found"
// Call may have hung up already
Rlog.w(LOG_TAG,"GsmCdmaCallTracker WARN: hangup() on absent connection "
+ conn);
}
}
conn.onHangupLocal();
}
由于是通话挂断,这里调用 mCi.hangupConnection(conn.getGsmCdmaIndex(),obtainCompleteMessage());
这里调用conn的getGsmCdmaIndex方法先索取索引
14)、GsmCdmaConnection.java中getGsmCdmaIndex()函数
/*package*/ int
getGsmCdmaIndex() throws CallStateException {
if (mIndex >= 0) {
return mIndex + 1;
} else {
throw new CallStateException ("GsmCdma index not yet assigned");
}
}
这个索引对应的就是DriveCall里面的index的值,匹配的modem里CallList里对于Call对象
mCi是CommandsInterface即RILJ接口,包装了一个EVENT_OPERATION_COMPLETE回调的消息,发送给RIL
15)、RIL.java 文件的hangupConnection()方法
@Override
public void
hangupConnection (int gsmIndex, Message result) {
if (RILJ_LOGD) riljLog("hangupConnection: gsmIndex=" + gsmIndex);
RILRequest rr = RILRequest.obtain(RIL_REQUEST_HANGUP, result);
if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest) + " " +
gsmIndex);
mEventLog.writeRilHangup(rr.mSerial, RIL_REQUEST_HANGUP, gsmIndex);
rr.mParcel.writeInt(1);
rr.mParcel.writeInt(gsmIndex);
Rlog.d(RILJ_LOG_TAG, "lum_52");
send(rr);
}
给RIL层发送RIL_REQUEST_HANGUP消息并附带index参数
收到RIL层的回应消息并处理,最后发送回调消息 EVENT_OPERATION_COMPLETE给GsmCdmaCallTracker
GsmCdmaCallTracker处理回调消息EVENT_OPERATION_COMPLETE
16)、GsmCdmaCallTracker.java文件operationComplete()方法
private void operationComplete() {
mPendingOperations--;
if (DBG_POLL) log("operationComplete: pendingOperations=" +
mPendingOperations + ", needsPoll=" + mNeedsPoll);
if (mPendingOperations == 0 && mNeedsPoll) {
mLastRelevantPoll = obtainMessage(EVENT_POLL_CALLS_RESULT);
mCi.getCurrentCalls(mLastRelevantPoll);
} else if (mPendingOperations < 0) {
// this should never happen
Rlog.e(LOG_TAG,"GsmCdmaCallTracker.pendingOperations < 0");
mPendingOperations = 0;
}
}
这里再次向RIL发送消息主动获取当前call状态,包装的回调消息为EVENT_POLL_CALLS_RESULT
RIL 返回消息,GsmCdmaCallTracker接受EVENT_POLL_CALLS_RESULT消息并处理
// ***** Overwritten from CallTracker
@Override
protected synchronized void handlePollCalls(AsyncResult ar) {
List polledCalls;
if (VDBG) log("handlePollCalls");
if (ar.exception == null) {
polledCalls = (List)ar.result;
} else if (isCommandExceptionRadioNotAvailable(ar.exception)) {
// just a dummy empty ArrayList to cause the loop
// to hang up all the calls
polledCalls = new ArrayList();
} else {
// Radio probably wasn't ready--try again in a bit
// But don't keep polling if the channel is closed
pollCallsAfterDelay();
return;
}
Connection newRinging = null; //or waiting
ArrayList<Connection> newUnknownConnectionsGsm = new ArrayList<Connection>();
Connection newUnknownConnectionCdma = null;
boolean hasNonHangupStateChanged = false; // Any change besides
// a dropped connection
boolean hasAnyCallDisconnected = false;
boolean needsPollDelay = false;
boolean unknownConnectionAppeared = false;
int ignoredUpdateRequest = ImsCall.UPDATE_NONE;
// countOfIgnoredRequest can be 0 ~ 3.
// 0 means there's no ignored request.
// 1 means there's hangup or hold or resume or accept.
// 2 means there's hangup active or hold and accept waiting.
// 3 means there's hangup active and hangup hold and accept waiting.
int countOfIgnoredRequest = 0;
GsmCdmaConnection pendingConnection = null;
// pendingHoldConnection uses to distinguish whether to hangup active or hold ignored request,
// when countOfIgnoredRequest is 2.
GsmCdmaConnection pendingHoldConnection = null;
//CDMA
boolean noConnectionExists = true;
for (int i = 0, curDC = 0, dcSize = polledCalls.size()
; i < mConnections.length; i++) {
GsmCdmaConnection conn = mConnections[i];
DriverCall dc = null;
// polledCall list is sparse
if (curDC < dcSize) {
dc = (DriverCall) polledCalls.get(curDC);
if (dc.index == i+1) {
curDC++;
} else {
dc = null;
}
}
//CDMA
if (conn != null || dc != null) {
noConnectionExists = false;
}
if (DBG_POLL) log("poll: conn[i=" + i + "]=" +
conn+", dc=" + dc);
if (conn == null && dc != null) {
// Connection appeared in CLCC response that we don't know about
if (mPendingMO != null && mPendingMO.compareTo(dc)) {
if (DBG_POLL) log("poll: pendingMO=" + mPendingMO);
// It's our pending mobile originating call
mConnections[i] = mPendingMO;
mPendingMO.mIndex = i;
mPendingMO.update(dc);
mPendingMO = null;
// Someone has already asked to hangup this call
if (mHangupPendingMO) {
mHangupPendingMO = false;
// Re-start Ecm timer when an uncompleted emergency call ends
if (!isPhoneTypeGsm() && mIsEcmTimerCanceled) {
handleEcmTimer(GsmCdmaPhone.RESTART_ECM_TIMER);
}
try {
if (Phone.DEBUG_PHONE) log(
"poll: hangupPendingMO, hangup conn " + i);
hangup(mConnections[i]);
} catch (CallStateException ex) {
Rlog.e(LOG_TAG, "unexpected error on hangup");
}
// Do not continue processing this poll
// Wait for hangup and repoll
return;
}
// Check update state of foreground ImsCall.
// If it is UPDATE_TERMINATE, it means user wanted hangup the call.
// But there was no response. it may be ignored by silent redial or SRVCC.
// In here, handing silent redial case.
ImsPhone imsPhone = (ImsPhone)mPhone.getImsPhone();
if (imsPhone != null) {
ImsCall imsCall = imsPhone.getForegroundCall().getImsCall();
if (imsCall != null && imsCall.getUpdateRequest() == ImsCall.UPDATE_TERMINATE) {
try {
Rlog.e(LOG_TAG, "hangup after silent redial");
hangup(mConnections[i]);
} catch (CallStateException ex) {
Rlog.e(LOG_TAG, "unexpected error on hangup");
}
// clear ImsCall update state.
// set ImsReasonInfo as CODE_UNSPECIFIED.
imsCall.clear(new ImsReasonInfo());
// Do not continue processing this poll
// Wait for hangup and repoll
return;
}
}
} else {
if (Phone.DEBUG_PHONE) {
log("pendingMo=" + mPendingMO + ", dc=" + dc);
}
mConnections[i] = new GsmCdmaConnection(mPhone, dc, this, i);
Connection hoConnection = getHoConnection(dc);
if (hoConnection != null) {
// Single Radio Voice Call Continuity (SRVCC) completed
mConnections[i].migrateFrom(hoConnection);
// Updating connect time for silent redial cases (ex: Calls are transferred
// from DIALING/ALERTING/INCOMING/WAITING to ACTIVE)
if (hoConnection.mPreHandoverState != GsmCdmaCall.State.ACTIVE &&
hoConnection.mPreHandoverState != GsmCdmaCall.State.HOLDING &&
dc.state == DriverCall.State.ACTIVE) {
mConnections[i].onConnectedInOrOut();
}
if (hoConnection instanceof ImsPhoneConnection) {
ImsCall imsCall = ((ImsPhoneConnection)hoConnection).getImsCall();
// Find ignored request. (HOLD, RESUME, TERMINATED, ACCEPT)
if (imsCall != null &&
findIgnoredRequest(imsCall.getUpdateRequest(), mConnections[i].getState())) {
ignoredUpdateRequest = imsCall.getUpdateRequest();
pendingConnection = mConnections[i];
if (mConnections[i].getState() == Call.State.HOLDING) {
pendingHoldConnection = mConnections[i];
}
countOfIgnoredRequest++;
}
}
mHandoverConnections.remove(hoConnection);
if (isPhoneTypeGsm()) {
for (Iterator<Connection> it = mHandoverConnections.iterator();
it.hasNext(); ) {
Connection c = it.next();
Rlog.i(LOG_TAG, "HO Conn state is " + c.mPreHandoverState);
if (c.mPreHandoverState == mConnections[i].getState()) {
Rlog.i(LOG_TAG, "Removing HO conn "
+ hoConnection + c.mPreHandoverState);
it.remove();
}
}
}
mPhone.notifyHandoverStateChanged(mConnections[i]);
} else {
// find if the MT call is a new ring or unknown connection
newRinging = checkMtFindNewRinging(dc,i);
if (newRinging == null) {
unknownConnectionAppeared = true;
if (isPhoneTypeGsm()) {
newUnknownConnectionsGsm.add(mConnections[i]);
} else {
newUnknownConnectionCdma = mConnections[i];
}
}
}
}
hasNonHangupStateChanged = true;
} else if (conn != null && dc == null) {
if (isPhoneTypeGsm()) {
// Connection missing in CLCC response that we were
// tracking.
mDroppedDuringPoll.add(conn);
// Dropped connections are removed from the CallTracker
// list but kept in the GsmCdmaCall list
mConnections[i] = null;
} else {
// This case means the RIL has no more active call anymore and
// we need to clean up the foregroundCall and ringingCall.
// Loop through foreground call connections as
// it contains the known logical connections.
int count = mForegroundCall.mConnections.size();
for (int n = 0; n < count; n++) {
if (Phone.DEBUG_PHONE) log("adding fgCall cn " + n + " to droppedDuringPoll");
GsmCdmaConnection cn = (GsmCdmaConnection)mForegroundCall.mConnections.get(n);
mDroppedDuringPoll.add(cn);
}
count = mRingingCall.mConnections.size();
// Loop through ringing call connections as
// it may contain the known logical connections.
for (int n = 0; n < count; n++) {
if (Phone.DEBUG_PHONE) log("adding rgCall cn " + n + " to droppedDuringPoll");
GsmCdmaConnection cn = (GsmCdmaConnection)mRingingCall.mConnections.get(n);
mDroppedDuringPoll.add(cn);
}
// Re-start Ecm timer when the connected emergency call ends
if (mIsEcmTimerCanceled) {
handleEcmTimer(GsmCdmaPhone.RESTART_ECM_TIMER);
}
// If emergency call is not going through while dialing
checkAndEnableDataCallAfterEmergencyCallDropped();
// Dropped connections are removed from the CallTracker
// list but kept in the Call list
mConnections[i] = null;
}
} else if (conn != null && dc != null && !conn.compareTo(dc) && isPhoneTypeGsm()) {
// Connection in CLCC response does not match what
// we were tracking. Assume dropped call and new call
mDroppedDuringPoll.add(conn);
mConnections[i] = new GsmCdmaConnection (mPhone, dc, this, i);
if (mConnections[i].getCall() == mRingingCall) {
newRinging = mConnections[i];
} // else something strange happened
hasNonHangupStateChanged = true;
} else if (conn != null && dc != null) { /* implicit conn.compareTo(dc) */
// Call collision case
if (!isPhoneTypeGsm() && conn.isIncoming() != dc.isMT) {
if (dc.isMT == true) {
// Mt call takes precedence than Mo,drops Mo
mDroppedDuringPoll.add(conn);
// find if the MT call is a new ring or unknown connection
newRinging = checkMtFindNewRinging(dc,i);
if (newRinging == null) {
unknownConnectionAppeared = true;
newUnknownConnectionCdma = conn;
}
checkAndEnableDataCallAfterEmergencyCallDropped();
} else {
// Call info stored in conn is not consistent with the call info from dc.
// We should follow the rule of MT calls taking precedence over MO calls
// when there is conflict, so here we drop the call info from dc and
// continue to use the call info from conn, and only take a log.
Rlog.e(LOG_TAG,"Error in RIL, Phantom call appeared " + dc);
}
} else {
boolean changed;
changed = conn.update(dc);
hasNonHangupStateChanged = hasNonHangupStateChanged || changed;
}
}
if (REPEAT_POLLING) {
if (dc != null) {
// FIXME with RIL, we should not need this anymore
if ((dc.state == DriverCall.State.DIALING
/*&& cm.getOption(cm.OPTION_POLL_DIALING)*/)
|| (dc.state == DriverCall.State.ALERTING
/*&& cm.getOption(cm.OPTION_POLL_ALERTING)*/)
|| (dc.state == DriverCall.State.INCOMING
/*&& cm.getOption(cm.OPTION_POLL_INCOMING)*/)
|| (dc.state == DriverCall.State.WAITING
/*&& cm.getOption(cm.OPTION_POLL_WAITING)*/)) {
// Sometimes there's no unsolicited notification
// for state transitions
needsPollDelay = true;
}
}
}
}
// SRVCC Solution
Rlog.d(LOG_TAG, "countOfIgnoredRequest is " + countOfIgnoredRequest);
// this means hangup active and hangup hold and accept waiting.
if (countOfIgnoredRequest == 3) {
if (pendingHoldConnection != null) {
try {
Rlog.d(LOG_TAG, "Trigger ignored hangup active, accept waiting call request");
hangup(pendingHoldConnection);
hangupForegroundResumeBackground();
// Do not continue processing this poll
// Wait for hangup and repoll
return;
} catch (CallStateException ex) {
Rlog.d(LOG_TAG, "hangup failed with exception: " + ex);
}
} else {
Rlog.d(LOG_TAG, "pendingHoldConnection is null.");
}
// this means hangup active or hold and accept waiting.
} else if (countOfIgnoredRequest == 2) {
if (pendingHoldConnection != null) {
try {
Rlog.d(LOG_TAG, "Trigger ignored hangup hold, accept waiting call request");
hangup(pendingHoldConnection);
switchWaitingOrHoldingAndActive();
// Do not continue processing this poll
// Wait for hangup and repoll
return;
} catch (CallStateException ex) {
Rlog.d(LOG_TAG, "hangup or switching failed with exception: " + ex);
}
} else {
Rlog.d(LOG_TAG, "Trigger ignored hangup active, accept waiting call request");
hangupForegroundResumeBackground();
// Do not continue processing this poll
// Wait for hangup and repoll
return;
}
} else if (countOfIgnoredRequest == 1) { // other cases.
if (ignoredUpdateRequest == ImsCall.UPDATE_TERMINATE) {
if (pendingConnection != null) {
try {
Rlog.d(LOG_TAG, "Trigger ignored terminate request.");
pendingConnection.hangup();
// Do not continue processing this poll
// Wait for hangup and repoll
return;
} catch (CallStateException ex) {
Rlog.d(LOG_TAG, "hangup failed with exception: " + ex);
}
}
} else if (ignoredUpdateRequest == ImsCall.UPDATE_HOLD ||
ignoredUpdateRequest == ImsCall.UPDATE_RESUME) {
try {
Rlog.d(LOG_TAG, "Trigger ignored hold/resume request.");
switchWaitingOrHoldingAndActive();
// Do not continue processing this poll
// Wait for switching and repoll
return;
} catch (CallStateException ex) {
Rlog.d(LOG_TAG, "switching failed with exception: " + ex);
}
} else if (ignoredUpdateRequest == ImsCall.UPDATE_ACCEPT) {
try {
Rlog.d(LOG_TAG, "Trigger ignored accept request.");
acceptCall();
// Do not continue processing this poll
// Wait for accept and repoll
return;
} catch (CallStateException ex) {
Rlog.d(LOG_TAG, "accept failed with exception: " + ex);
}
}
}
// Safety check so that obj is not stuck with mIsInEmergencyCall set to true (and data
// disabled). This should never happen though.
if (!isPhoneTypeGsm() && noConnectionExists) {
checkAndEnableDataCallAfterEmergencyCallDropped();
}
// This is the first poll after an ATD.
// We expect the pending call to appear in the list
// If it does not, we land here
if (mPendingMO != null) {
Rlog.d(LOG_TAG, "Pending MO dropped before poll fg state:"
+ mForegroundCall.getState());
mDroppedDuringPoll.add(mPendingMO);
mPendingMO = null;
mHangupPendingMO = false;
if (!isPhoneTypeGsm()) {
if( mPendingCallInEcm) {
mPendingCallInEcm = false;
}
checkAndEnableDataCallAfterEmergencyCallDropped();
}
}
if (newRinging != null) {
mPhone.notifyNewRingingConnection(newRinging);
}
// clear the "local hangup" and "missed/rejected call"
// cases from the "dropped during poll" list
// These cases need no "last call fail" reason
for (int i = mDroppedDuringPoll.size() - 1; i >= 0 ; i--) {
GsmCdmaConnection conn = mDroppedDuringPoll.get(i);
//CDMA
boolean wasDisconnected = false;
if (conn.isIncoming() && conn.getConnectTime() == 0) {
// Missed or rejected call
int cause;
if (conn.mCause == DisconnectCause.LOCAL) {
cause = DisconnectCause.INCOMING_REJECTED;
} else {
cause = DisconnectCause.INCOMING_MISSED;
}
if (Phone.DEBUG_PHONE) {
log("missed/rejected call, conn.cause=" + conn.mCause);
log("setting cause to " + cause);
}
mDroppedDuringPoll.remove(i);
hasAnyCallDisconnected |= conn.onDisconnect(cause);
wasDisconnected = true;
} else if (conn.mCause == DisconnectCause.LOCAL
|| conn.mCause == DisconnectCause.INVALID_NUMBER) {
mDroppedDuringPoll.remove(i);
hasAnyCallDisconnected |= conn.onDisconnect(conn.mCause);
wasDisconnected = true;
}
if (!isPhoneTypeGsm() && wasDisconnected && unknownConnectionAppeared
&& conn == newUnknownConnectionCdma) {
unknownConnectionAppeared = false;
newUnknownConnectionCdma = null;
}
}
/* Disconnect any pending Handover connections */
for (Iterator<Connection> it = mHandoverConnections.iterator();
it.hasNext();) {
Connection hoConnection = it.next();
log("handlePollCalls - disconnect hoConn= " + hoConnection +
" hoConn.State= " + hoConnection.getState());
if (hoConnection.getState().isRinging()) {
hoConnection.onDisconnect(DisconnectCause.INCOMING_MISSED);
} else {
hoConnection.onDisconnect(DisconnectCause.NOT_VALID);
}
it.remove();
}
// for STK function SET_UP_CALL
Intent intent = new Intent("com.android.call.CALL_STATE_CHANGED");
// Any non-local disconnects: determine cause
if (mDroppedDuringPoll.size() > 0) {
mCi.getLastCallFailCause(
obtainNoPollCompleteMessage(EVENT_GET_LAST_CALL_FAIL_CAUSE));
intent.putExtra("CALL_SUCCESSFUL", false);
} else {
intent.putExtra("CALL_SUCCESSFUL", true);
}
mPhone.getContext().sendBroadcast(intent);
if (needsPollDelay) {
pollCallsAfterDelay();
}
if (mDroppedDuringPoll.size() > 0 || hasAnyCallDisconnected) {
processEmcStatusAfterCsEnd(EmcStatus.CALL_END);
}
// Cases when we can no longer keep disconnected Connection's
// with their previous calls
// 1) the phone has started to ring
// 2) A Call/Connection object has changed state...
// we may have switched or held or answered (but not hung up)
if (newRinging != null || hasNonHangupStateChanged || hasAnyCallDisconnected) {
internalClearDisconnected();
}
if (VDBG) log("handlePollCalls calling updatePhoneState()");
updatePhoneState();
if (unknownConnectionAppeared) {
if (isPhoneTypeGsm()) {
for (Connection c : newUnknownConnectionsGsm) {
log("Notify unknown for " + c);
mPhone.notifyUnknownConnection(c);
}
} else {
mPhone.notifyUnknownConnection(newUnknownConnectionCdma);
}
}
if (hasNonHangupStateChanged || newRinging != null || hasAnyCallDisconnected) {
mPhone.notifyPreciseCallStateChanged();
}
//dumpState();
}
这里是通话断开事件,将connection放入mDroppedDurungPoll列表,由于断开类型DisconnectCause.LOCAL,直接调用GsmCdmaConnection的onDisconnect方法传入cause参数
16)、GsmCdmaConnection.java的onDisconnect()方法
/**
* Called when the radio indicates the connection has been disconnected.
* @param cause call disconnect cause; values are defined in {@link DisconnectCause}
*/
@Override
public boolean onDisconnect(int cause) {
boolean changed = false;
mCause = cause;
if (!mDisconnected) {
doDisconnect();
if (DBG) Rlog.d(LOG_TAG, "onDisconnect: cause=" + cause);
mOwner.getPhone().notifyDisconnect(this);
if (mParent != null) {
changed = mParent.connectionDisconnected(this);
}
mOrigConnection = null;
}
clearPostDialListeners();
releaseWakeLock();
return changed;
}
doDisconnect()方法 设置断开时间以及通话时长
private void
doDisconnect() {
mIndex = -1;
mDisconnectTime = System.currentTimeMillis();
mDuration = SystemClock.elapsedRealtime() - mConnectTimeReal;
mDisconnected = true;
clearPostDialListeners();
}
最后通知注册者断开事件mOwner.getPhone().notifyDisconnect(this);
之后的流程就跟上篇讲解phone拒接流程一样,这里就不重复描述了
详见http://www.cnblogs.com/lance2016/p/6391096.html
文章参考:
Android7.0 Phone应用源码分析(三) phone拒接流程分析