欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

拨号流程分析(第五篇)

程序员文章站 2024-03-07 14:36:45
...

本文基于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的
onCreateOutgoing­Connection方法,其关键逻辑如下:

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 发送消息,将同步的服务调用接口转换成异步的消息处理。