拨号流程分析(第五篇)
本文基于Android 8.0
IConnectionService服务的响应过程
根据AndroidManifest.xml中对android.telecom.ConnectionService服务的定义,其服务的Java类为com.android.services.telephony.TelephonyConnectionService ,继承自android.telecom.ConnectionService抽象类。 在 frameworks/base工程下,代码文件为frameworks/base/telecomm/java/android/telecom/ConnectionService.java ,其服务响应的关键逻辑简化后的代码框架详情如下:
public abstract class ConnectionService extends Service {
...
private final IBinder mBinder = new IConnectionService.Stub() {
// addConnectionServiceAdapter、createConnection、answer、hold
}
...
@Override
public final IBinder onBind(Intent intent) {
return mBinder;
}
}
frameworks/base/telecomm/java/com/android/internal/telecom/IConnectionService.aidl文件作为IConnectionService服务的接口定义,主要定义了addConnectionServiceAdapter、CreateConnection、answer、hold等接口。通过这些接口的名字,可以知道此服务主要提供了Call状态管理的接口供Telecom应用调用 , 比如接昕电话、保持呼叫、挂断电话等 。
Telecom应用的ConnectionServiceWrapper对象在拨号流程中,首先绑定服务,接着调用服务的addConnectionServiceAdapter和 CreateConnection接口。 TelephonyConnectionService服务的响应逻辑是什么呢?接下来,我们分别从 IConnectionService的 onBind、addConnectionServiceAdapter和createConnection服务接口加以解析。
1. onBind服务被绑定的响应方法
TelephonyConnectionService继承于ConnectionService类,并未重写父类的onBind方法 。onBind逻辑简单,返回了IConnectionService.Stub类型的mBinder对象
2. addConnectionServiceAdapter设置Adapter
// 使用Handler的异步消息处理机制,将服务调用的同步方式转为异步方式处理,addConnectionServiceAdapter服务接口将立即返回
@Override
public void addConnectionServiceAdapter(IConnectionServiceAdapter adapter,
Session.Info sessionInfo) {
...
mHandler.obtainMessage(MSG_ADD_CONNECTION_SERVICE_ADAPTER, args).sendToTarget();
...
}
// 查看mHandler对消息MSG_ADD_CONNECTION SERVICE ADAPTER的响应逻辑
case MSG_ADD_CONNECTION_SERVICE_ADAPTER: {
...
IConnectionServiceAdapter adapter = (IConnectionServiceAdapter) args.arg1;
mAdapter.addAdapter(adapter);// mAdapter再嵌套一层Adapter
onAdapterAttached();// 验证Adapter是否可用
...
}
addConnectionServiceAdapter接口的响应逻辑相对比较简单,我们只需重点关注使用 Handler的异步消息处理机制,将服务调用的同步方式转为异步处理即可,与lnCallService的处理机制相同
3. createConnection继续发送拨号请求
ConnectionService服务的接口CreateConnection的响应逻辑仍然是通过mHandler将同步调用转为异步处理。 mHandler发出MSG_ CREATE_CONNECTION消息,并在handleMessage中响应此方法 , 再调用父类的createConnection方法,此方法的 关键逻辑如下:
private void createConnection(
final PhoneAccountHandle callManagerAccount,
final String callId,
final ConnectionRequest request,
boolean isIncoming,
boolean isUnknown) {
// 将调用onCreateOutgoingConnection创建Connection对象,注意与ConnectionService的区别
connection = isUnknown ? onCreateUnknownConnection(callManagerAccount, request)
: isIncoming ? onCreateIncomingConnection(callManagerAccount, request)
: onCreateOutgoingConnection(callManagerAccount, request);
... // 跨进程调用Telecom服务接口,通过Connection已经成功创建
mAdapter.handleCreateConnectionComplete(
callId,
request,
new ParcelableConnection(
request.getAccountHandle(),
connection.getState(),
connection.getConnectionCapabilities(),
connection.getConnectionProperties(),
connection.getSupportedAudioRoutes(),
connection.getAddress(),
connection.getAddressPresentation(),
...
createIdList(connection.getConferenceables()),
connection.getExtras()));
...
}
上面的处理逻辑主要有两个 : 利用onCreateXXXConnection创建Connection对象和通过mAdapter传递过来的Binder对象进行handleCreateConnectionComplete接口回调
首先看看 Connection对象的创建过程, TelephonyConnectionService重写了父类ConnectionService的
onCreateOutgoingConnection方法,其关键逻辑如下:
public Connection onCreateOutgoingConnection(
PhoneAccountHandle connectionManagerPhoneAccount,
final ConnectionRequest request) {
...// 首先是对EmergencyCall紧急呼叫进行判断和处理,这里同样得到Phone对象,还记得Dialer中的Phone对象吗,有何区别
final Phone phone = getPhoneForAccount(request.getAccountHandle(), isEmergencyNumber);
Connection resultConnection = getTelephonyConnection(request, numberToDial,
isEmergencyNumber, handle, phone);
// 如果失败,最终的连接将不是TelephonyConnection,所以将不能打电话
if (resultConnection instanceof TelephonyConnection) {
...
placeOutgoingConnection((TelephonyConnection) resultConnection, phone, request);
}
return resultConnection;
}
// 继续跟进placeOutgoingConnection的处理逻辑
private void placeOutgoingConnection(
TelephonyConnection connection, Phone phone, int videoState, Bundle extras) {
String number = connection.getAddress().getSchemeSpecificPart();
com.android.internal.telephony.Connection originalConnection = null;
try {
if (phone != null) {
originalConnection = phone.dial(number, new ImsPhone.ImsDialArgs.Builder()
.setVideoState(videoState)
.setIntentExtras(extras)
.setRttTextStream(connection.getRttTextStream())
.build());
}
} catch (CallStateException e) {
...// 拨号失败的处理逻辑,设置DisconnectCause
connection.setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(
cause, e.getMessage()));
...
return;
}
if (originalConnection == null) {
...// 拨号失败的处理逻辑,设置DisconnectCause
connection.setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(
telephonyDisconnectCause, "Connection is null"));
...
} else { // 设置Connection
connection.setOriginalConnection(originalConnection);
}
}
重点跟进phone对象以及phone.dial方法的调用,原来phone是 com.android.internal.telephony.GsmCdmaPhone类型对象,其代码为frameworks/opt/telephony/src/java/com/android/internal/telephony/GsmCdmaPhone.java。
frameworks/opt/telephony/是我们涉及的第四个代码库,查看Android.mk文件, 此代码库将编译出telephony-common.jar,我们以后统一称其为Telephony。
继续跟进phone对象的dial方法 ,可以发现dial -> dialInternal -> mCT.dial的调用过程 , mCT即GsmCdmaCallTracker ,其dial 方法的关键代码逻辑如下:
public synchronized Connection dial(String dialString, int clirMode, UUSInfo uusInfo,
Bundle intentExtras)
throws CallStateException {
...// 又出现一个Connection类型
mPendingMO = new GsmCdmaConnection(mPhone, checkForTestEmergencyNumber(dialString),
this, mForegroundCall, isEmergencyCall);
...// 继续调用dial拨号请求
mCi.dial(mPendingMO.getAddress(), clirMode, uusInfo, obtainCompleteMessage());
...// 更新通话状态,并发出通知
updatePhoneState();
mPhone.notifyPreciseCallStateChanged();
return mPendingMO;
}
这里的mCi即RIL对象,其Java代码是frameworks/opt/telephony/src/java/com/android/internal/telephony/RIL.java ,这里将发出 RIL的拨号请求。到此,我们跟踪拨号流程已经到了HAL(硬件抽象层),在这一层不同的芯片厂家将完成不同的实现,比如高通平台将RIL请求转为QMI消息与Modem交互, MTK平台则采用AT命令的方式与 Modem 交互 。
TeleService拨号流程
TeleService应用中的拨号流程已经分析完成,总结如图所示:
图中所示的TeleService拨号流程,我们重点关注以下步骤:
- 异步处理
步骤 6 和步骤 10 通过 Handler 消息处理将同步转为异步 - RIL拨号请求
将拨号请求消息转换为RIL拨号请求,对应步骤 15、步骤 17 和步骤 18 。 步骤 18 在RIL Java 对象中处理,将发出 RIL 请求,交给HAL层的rild 进程处理
RIL真机调试日志
我们采用真机作为调试的工具,发起拨号请求,同步查看手机在RIL处理的日志,详情如下:
// GsmCdmaPhone发起CS call
D/GsmCdmaPhone(1109):[GsmCdmaPhone] Trying (non-IMS) CS call
// 获取的国家编码 CN即中国, 紧急呼救号码110、119、112等
D/PhoneNurnberUtils(1109): slotid:O subid:1 country:CN emergencyNurnbers:
110, 119, 120, 112, 911, 122, 999
// RIL Java对象发起DIAL RIL请求
D/RILJ (1109): [3952]> DIAL [SUBO]
// 更新 phone 状态为摘机状态
D/GsmCdmaCallTracker(1109): [GsmCdmaCallTracker] update phone state, old=IDLE new=OFFHOOK
// QMI消息处理,这部分逻辑为厂家定制,未开源
I/RILQ (604): (01604): RIL[ O][event] qcril_qrni_voice_all_call status_ ind_hdlr: call state CC IN PROGRESS for conn id 1
I/RILQ (604): (01604): RIL[O][event] qcril_qrni_voice_all_call_status_ind_hdlr: RILVIMS: update 1 calls[uncached qrni call id 1 , call state 4]
I/RILQ (604): (01604): RIL[O][event] qcril_qrni_voice_all_call_status_ ind_hdlr: call state ORIGINATING for conn id 1
// RIL Java对象接收到DIAL的返回消息
D/RILJ (1109): [3952]< DIAL [SUBO]
通过上面的日志可以分析出几个关键信息:
- 进程信息
GsmCdmaPhone、 RIL等代码均运行在PID为1109的进程com.android.phone空间; QMI相关消息处理运行在PID为604的进程rild空间 - RIL消息配对
DIAL拨号请求的RIL信息编号为3952 ,在请求和接收返回消息时将通过此消息编号进行匹配,“>”是 RIL请求消息下发,“ < ”是接收到返回消息 - QMI消息
qcril_qmi_XXX为与消息相关的内容, 604号进程即代表在rild进程中运行,在Android源码中未找到对应的开源代码
TelecomAdapter接收消息回调
ConnectionServiceWrapper.Adapter将接收TeleService应用的接口回调,其中将通过this调用ConnectionServiceWrapper对象的handleCreateConnectionComplete方法,接着是mPendingResponses属性对象的handleCreateConnectionSuccess方法调用,即CreateConnectionProcessor对象,最后是mCallResponse.handleCreateConnectionSuccess对象,即Call对象的 handleCreateConnectionSuccess方法响应TeleService应用的接口回调,其中的处理逻辑详情如下:
public void handleCreateConnectionSuccess(
CallIdMapper idMapper,
ParcelableConnection connection) {
...// 根据connection对象使用setXXX方法设置Call对象的对应属性
for (String id : connection.getConferenceableConnectionIds()) {
mConferenceableCalls.add(idMapper.getCall(id));
}
switch (mCallDirection) {
case CALL_DIRECTION_INCOMING:// 来电流程处理
for (Listener l : mListeners) {
l.onSuccessfulIncomingCall(this);
}
break;
case CALL_DIRECTION_OUTGOING:// 拨号流程处理
for (Listener l : mListeners) {
l.onSuccessfulOutgoingCall(this,
getStateFromConnectionState(connection.getState()));
}
break;
...
}
}
我们重点关注拨号流程,而与来电相关的处理逻辑暂不解析。但是,也请读者思考一下,这里的来电流程将承载什么业务逻辑。
Listener究竟是什么呢?
Call类中有Listener的接口定义,同时也定义了ListenerBase抽象类 ,它实现了Listener接口 。 ListenerBase 抽象类实现了 Listener接口的所有方法,并且这些方法都是空实现,没有具体逻辑。ListenerBase抽象类有三个子类,分别是:
- CallsManage
- lnCallController匿名内部类对象mCallListener
- lncomingCallNotifier匿名内部类对象mCalllLstener
这三个类中,仅有CallsManager重写了父类ListenerBase的onSuccessfulOutgoingCall方法 ,因此,我们接着查看CallsManager对象的onSuccessfulOutgoingCall方法逻辑,修改 Call对象的状态时,经过
markCallAsDialing -> setCallState的调用,又找到Listener的消息回调,详情如下:
public void onSuccessfulOutgoingCall(Call call, int callState) {
...
for (CallsManagerListener listener : mListeners) {
listener.onConnectionServiceChanged(call, null, call.getConnectionService());
}
...
}
CallsManagerlistener在CallsManager类中定义了接口,而CallsManagerlistenerBase类实现了其接口,并且这些接口基本上都是空实现,它有12 个子类 : CallLogManager、 HeadsetMediaButton 、lncomingCallNotifier和lnCallController等。 通过前面的流程分析,还记得 lnCallController与 Dialr的交互吗?它们将启动和更新通话界面。 难道这里要更改通话界面的通话状态为拨号中?我们先做一个推论 。
继续对InCallController的onCallStateChanged调用updateCall方法,将Call信息传递给 Dialer, 具体逻辑详情如下:
private void updateCall(Call call, boolean videoProviderChanged, boolean rttInfoChanged) {
...// 通过已经修改状态的Call对象构造跨进程传递的ParcelableCall对象
ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(
call,
videoProviderChanged /* includeVideoProvider */,
mCallsManager.getPhoneAccountRegistrar(),
info.isExternalCallsSupported(),
rttInfoChanged && info.equals(mInCallServiceConnection.getInfo()));
...
try {
inCallService.updateCall(parcelableCall);
} catch (RemoteException ignored) {
}
}
...
}
mAdapter的接口回调是将当前呼出的电话状态进行更新,更新为dialing ,即正在拨号的状态,最终会调用lnCallService的接口去更新通话界面,其流程总结如图所示:
拨号流程总结
在拨号流程中,需要把拨号请求发送到RIL ,我们跟踪到了五个代码库和三个Android系统应用 拨号入口DialpadFragment
如何找到DialpadFragment 是拨号界面的过程,还请读者认真思考和总结,找到属于自己的查找和调试代码的方法
Dialer应用中的拨号流程总结可参考其余篇,将发起第一次的跨进程服务接口调用,即TelecomManager
的placeCall 方法调用
-
第一次跨进程访问
拨号流程中的第一次跨进程访问,将从 Dialer应用访问到Telecom 应用中的 ITelecomService 服
务接口,重点参考 framework/base 代码库下的 TelecomManager.java 代码中的处理逻辑。 -
Telecom应用第一次绑定服务
Telecom 应用中 lnCallController 对象的 bindToServices 方法将绑定 Dialer 应用中的InCallService服务,并调用该服务提供的setlnCallAdapter 和 addCall 等方法.
Dialer 应用中将展示和更新InCallActivity 进行通话界面的显示 。 其余篇有总结了 Telecom 应用第一次绑定服务的核心流程。 -
Telecom应用第二次绑定服务
Telecom应用中ConnectionServiceWrapper对象的createConnection方法将绑定TeleService应用中的IConnectionService 服务,并调用该服务提供的 addConnectionServiceAdapter 和 createConnection等方法, TeleService应用将通过RIL对象发出拨号的RIL请求 。 其余篇总结了Telecom 应用第二次绑定服务的核心流程。 -
Adapter第一次回调
TeleService 应用发出拨号请求后,将通过 AdapterBinder对象跨进程调用 ConnectionServiceWrapper的 mAdapter对象中的服务接口,再通过Telecom 中的处理,最终调用 Dialer应用中 llnCallService服务的 updateCall 接口来更新通话状态。其余篇 总结了TeleService 应用调用 Telecom 应用 Adapter来回调消息以更新 Call 状态的核心流程。 -
Telecom应用中的拨号流程
CallsManager对象的 startOutgoingCall 和 placeOutgoingCall 方法,分别进行两次绑定服务的操作,并且两次绑定过程非常相似,都是分三步走,绑定服务、 setInCallAdapter 、addConnectionServiceAdapter 和 addCall/createConnection ,其中的核心流程可参考其余篇。 -
Dialer和TeleService应用中对应服务的响应
IlnCallService和IConnectionService分别是Dialer应用和 TeleService应用中提供的服务,重点关注这两个服务提供的接口的处理方式是通过 Handler 发送消息,将同步的服务调用接口转换成异步的消息处理。