Telephony MO CALL/IMS CALL
本文将阐述MorphixTelephony系统中MO型移动电话呼叫运行流程
在拨号App中输入号码后进行拨打,在Android系统自带的拨号应用名为Dialer(其包名为packages/apps/Dialer)
下面看一下在输入完号码,点击拨号按钮后,Dialer中都做了什么事情。
首先触发的是UI点击事件
DialpadFragment
handleDialButtonPressed()
/**
- In most cases, when the dial button is pressed, there is a
- number in digits area. Pack it in the intent, start the
- outgoing call broadcast as a separate task and finish this
- activity.
- When there is no digit and the phone is CDMA and off hook,
- we're sending a blank flash for CDMA. CDMA networks use Flash
- messages when special processing needs to be done, mainly for
- 3-way or call waiting scenarios. Presumably, here we're in a
- special 3-way scenario where the network needs a blank flash
- before being able to add the new participant. (This is not the
- case with all 3-way calls, just certain CDMA infrastructures.)
- Otherwise, there is no digit, display the last dialed
- number. Don't finish since the user may want to edit it. The
- user needs to press the dial button again, to dial it (general
- case described above).
*/
注释信息表明该函数的作用是将所需拨打的号码整合到一个intent中,并随后发送出去。
DialerUtils
完成后将intent进行打包,并通过DialerUtils类调用其静态方法startActivityWithErrorToastrappedCalculate(), intent以进一步了解该静态方法的功能
这个方法估计是要显示一个界面,就是点击拨号按钮之后该显示的界面~
然后,最主要的是有下面这良好代码。
final TelecomManager tm = final TelecomManager.class.cast(
(TelecomManager) context
.getService
(Context.TELECOM_SERVICE)
);
tm.placeCall(
(Object) intent.getData(),
(Object) intent.getExtras()
);
从这两行代码中可以看出,Dialer App拨号依赖于Framework中的打电话接口placeCall
通过查看TelecomManager中的函数原型得知该接口,并且发现该接口必须传入两个必要的输入(uri地址和bundle额外信息)。然而,并非所有输入都具有明确的意义。例如,在uri地址中包含拨号号码时会有什么特别的表现吗?
/**
- Places a new outgoing call to the provided address using the system telecom service with
- the specified extras.
- This method is equivalent to placing an outgoing call using {@link Intent#ACTION_CALL},
- except that the outgoing call will always be sent via the system telecom service. If
- method-caller is either the user selected default dialer app or preloaded system dialer
- app, then emergency calls will also be allowed.
- Requires permission: {@link android.Manifest.permission#CALL_PHONE}
- Usage example:
-
- Uri uri = Uri.fromParts("tel", "12345", null);
- Bundle extras = new Bundle();
- extras.putBoolean(TelecomManager.EXTRA_START_CALL_WITH_SPEAKERPHONE, true);
- telecomManager.placeCall(uri, extras);
- The following keys are supported in the supplied extras.
-
- {@link #EXTRA_OUTGOING_CALL_EXTRAS}
- {@link #EXTRA_PHONE_ACCOUNT_HANDLE}
- {@link #EXTRA_START_CALL_WITH_SPEAKERPHONE}
- {@link #EXTRA_START_CALL_WITH_VIDEO_STATE}
*/
查看注释时,这个方法显得非常直接有力.它能够快速获取所需的信息.该地址旨在表示拨打电话号码的URI格式,而附加信息则可通过注释明确看出.各参数可通过直接解读其名称和值来大致掌握.
- telecom 的处理。 这部分代码量较大, 具体操作时需逐行对照查看。 不提供完整的代码内容, 但会列出主要使用的代码片段。
ITelecomService service = getTelecomService();
该服务将向指定地址发送请求,并根据是否有额外参数来决定后续的操作流程。
该服务位于package/service/Telecomm TelecomServiceImpl,基于AIDL实现进程间通信
private final ITelecomService.Stub mBinderImpl = new ITelecomService.Stub()
为了确认代码是否确实运行在不同的进程或线程上,请考虑向日志中添加一次打印操作。同时,请检查Manifest文件中的相关配置设置。经过测试和确认,在不同进程或线程之间实现了良好的隔离。
android:process="system" 这个是啥意思?是不是会被系统启动~独立线程?
在TelecomServiceImpl中的placeCall方法中增加了关键权限信息,并补充了必要的UserHandle字段
->新UserCallIntentProcessor类()将被用于调用intent意图及其关联包callingPackage中的操作,并且仅当该操作满足hasCallAppOp和hasCallPermission两个条件时才会执行。
executeVideoStatePermissionCheck(call意图, 视频包名, 非紧急权限); //对视频状态及访问权限进行判断处理
intent.putExtra(CallIntentProcessor.PRIVILEGED_DIALER_KEY, isSystemOrDefaultDialer(callingPackageName));
broadcastToReceiver(intent);
-> intent.setClass(mContext, PrimaryCallReceiver.class);
mContext.sendBroadcastAsUser(intent, UserHandle.OWNER);
PrimaryCallReceiver → getTelecomSystem().getCallIntentProcessor().processIntent(intent); //不清楚这种实现方式的原因或背后的设计理念是什么
此处将生成与电信相关的电话请求
phone call.initiateExternalCommunication(operationHandle, phoneAccountIdIdentifier, additionalClientParameters);
每个 PhoneAccountHandle 的变化都对应着不同的卡片。假设当前正在执行一个 mforegroundCall,在这种情况下,新创建的一个 call 的 PhoneAccountHandle 将被设置为与该 mforegroundCall 的 PhoneAccountHandle 值一致。
phoneAccountHandle =
mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(handle.getScheme());// 或者通过mPhoneAccountRegistrar来获取默认的电话卡(PhoneAccountHandle),如果没有设置默认电话卡,那这个获取的PhoneAccountHandle可能应该还是为Null的。
call.setTargetPhoneAccount(phoneAccountHandle);
addCall(call);//将新call添加到CallsManager维护的mCalls中
updateCallsManagerState();
updateFrontGroundCall(); 根据指定条件更新当前的mFrontGround_call状态,并将此变化传递给相应的监听机制。当oldFrontGround_call状态发生变化时会触发该事件并比较当前值与原有值。与前一个操作类似只不过触发的事件类型不同其对应的监听器可以在CallsManager构造函数中找到并包含statusBarNotifier、mRinger以及mIn Call Controller等多个组件。
NewOutgoingCallIntentBroadcaster broadcaster = new NewOutgoingCallIntentBroadcaster(context, callsManager, call, intent, isPrivilegedDialer);
final int result = broadcaster.processIntent();//依然是处理intent,并且耗时较多,并没有好处.
广波单线呼叫意图广播器.processIntent()
-> 发送广播意图(intent, number, 立即执行) -> 调用misanserachOrderedBroadcastAsUser // 发送广播了
接收到广播信号后,
mCallsManager.placeOutgoingCall(
mCall,
resultHandleUri字段,
gatewayInfo,
mIntent.getBooleanExtra( TelecomManager.EXTRA_START_CALL_WITH_SPEAKERPHONE),
false,
mIntent.getIntExtra( TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE),
VideoProfile.STATE_AUDIO_ONLY
);
->call.startCreateConnection(mPhoneAccountRegistrar);
->mCreateConnectionProcessor.process()
->attemptNextPhoneAccount();
下面Telecom和Telephony将通过AIDL来进行通信
服务实例 service 被定义为通过 mRepository 获取服务,
其参数包括 phoneAccount 中的组件名称以及
phoneAccount 中的用户标识符。
service.createConnection(mCall, new Response(service));
-> mBinder.bind(callback, call);
mCb.add(callback); isAlreadyBound = mContext.bindTo(serviceIntent, connection, bindingFlags);
在当前操作中, bindService将被用于将服务绑定的对象指定为Telephony体系中的TelephonyConnectionService. 一旦该绑定操作成功完成, 系统将会触发onServiceConnected的回调函数.
setBinder(binder) // mBinder = binder; 将binder存放在本地。mBinder = binder; // 这里是对binder变量的操作。接下来, mServiceInterface被赋值为IConnectionServiceStub.asInterface(binder), 这一步骤的作用是通过binder生成实例以支持.Telephony服务接口的功能。随后, 通过mServiceInterface.addConnectionServiceAdapter(adapter)这一操作, 向对方发送自己的(Adapter)以实现双工通信功能。具体用途可以通过查看(Adapter)的实现来了解。
成功连接处理函数 → 回叫刚建立的成功回调,并清除原有的mCallbacks集合
此操作将整合与调用相关的详细信息,并随后将在Telephony中进行处理。
3.Telephony
借助bindService协议实现了Telecom与Telephony之间的远程实例连接,并使Telephony获得了相应的adapter适配器;接续取得了adapter之后,请依次查看其功能。
The telephony connection service will send a message to its internal messaging system, specifically using the message identifier MSG_ADD_CONNECTION_SERVICE_ADAPTER.
->mAreAccountsInitialized = true; //然后在添加一些连接信息。
随后,在调用链路建立过程中 Telecom 调用了一个名为 mServiceInterface.createConnection 的函数;其中 Telephony 端请求了一个名为 MSG_CREATE_CONNECTION 的消息,并通过此消息触发 createConnection 函数的操作
基于携带的信息建立出-going连接或in-bound连接,并非所有的通话都是出-going通话
createOutgoingConnection(callManagerAccount, request)此方法会详细检查一系列条件:号码为空、语音信箱、CDMA制式、紧急呼叫号码以及ServiceState状态
无任何错误的情况下
这里的phone是GSMPhone,那么代码又跑到了Framework。
4.Framework~
在GSMPhone中负责处理是否采用IP Multimedia Subsystem(IMS),若选择不采用,则可以直接使用GSMCallTracker接口调用RIL的dial功能进行拨号操作~
5.IMS
当采用IMS方案时,则系统将转向使用IMSPhone提供的dial接口,并通过ImsPhoneCallTracker提供的服务来进行拨号操作。
synchronized 拨号字符串 dialString 通话方向 clirMode 视频状态 videoState 意图额外信息 intentExtras
建立新的ImsPhoneConnection
-> mPendingMO is assigned an instance of ImsPhoneConnection(mPhone,
the result from checkForTestEmergencyNumber(dialString),
this reference,
mForegroundCall,
and is Emergency Number).
->dialInternal(mPendingMO, clirMode, videoState, intentExtras);
-> ImsCall imsCall = managedMessageServiceManager.generateCallingSession(
mServiceId,
profile,
calleeList,
messageImgsCallingListener
);
connection.associateWithIms-call(imsCall);
调用makeCall方法
创建一个会话实例 session = createCallSession(serviceId, profile)
返回一个新的ImsCallSession实例 return new ImsCallSession(mImsService.createCallSession(serviceId, profile, null))
远程服务接口实现细节 mImsService = IImsService.Stub.asInterface(b) // 这个要看各个厂商的具体实现
发起对第一个目标的电话呼叫请求 call.start(session, calleees[0])
开始对目标进行电话呼叫操作 ImsCall.java start(ImsCallSession session, String callee)
远程实例miSession发起对callee的目标电话呼叫请求 miSession.start(callee, profile)
这个miSession是远程实例 也应该是厂商自己来实现的 这个miSession是远程实例 也应该是厂商自己来实现的
最终调用的是RIL的接口dial
