对于Call模块最先接触的应该就是MO call,这也是比较常见的case.对于MO call, 在没有正式分析流程前,其实也可以考虑下,这个流程到底干了些什么事:首先通过Dialer 开始拨号,然后Telephony & Telecomm去处理逻辑,InCallUI 显示通话界面,然后通过RIL命令把Dial发给Modem.Modem 返回Dial 成功,电话拨打成功,然后InCallUI 根据Call的状态去更新UI。那接下来就从代码级别依次来查看下流程。
从操作来看,我们会先输入将要拨打号码,然后点击拨号键。其实这个过程就是Dialer build Intent的过程。构建的这个intent 会包含dial的action,且包含将要拨打的号码。这个Intent会传递给Telecomm处理,只有带了这两个extra,telecom才知道收到广播后去干什么。
http://androidxref.com/8.0.0_r4/xref/packages/apps/Dialer/java/com/android/dialer/app/dialpad/DialpadFragment.java#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).
*/
private void handleDialButtonPressed() {
if (isDigitsEmpty()) { // No number entered.
handleDialButtonClickWithEmptyDigits();
} else {
final String number = mDigits.getText().toString();
// "persist.radio.otaspdial" is a temporary hack needed for one carrier's automated
// test equipment.
// TODO: clean it up.
if (number != null
&& !TextUtils.isEmpty(mProhibitedPhoneNumberRegexp)
&& number.matches(mProhibitedPhoneNumberRegexp)) {
LogUtil.i(
"DialpadFragment.handleDialButtonPressed",
"The phone number is prohibited explicitly by a rule.");
if (getActivity() != null) {
DialogFragment dialogFragment =
ErrorDialogFragment.newInstance(R.string.dialog_phone_call_prohibited_message);
dialogFragment.show(getFragmentManager(), "phone_prohibited_dialog");
}
// Clear the digits just in case.
clearDialpad();
} else {
final Intent intent =
new CallIntentBuilder(number, CallInitiationType.Type.DIALPAD).build();
DialerUtils.startActivityWithErrorToast(getActivity(), intent);
hideAndClearDialpad(false);
}
}
}
上面的代码逻辑比较清晰,可以看到,正常情况下,我们会走第三种case.可以看到,在此处build了一个intent,然后通过intent去起拨号请求。我们具体查看下该intent的build详情。
http://androidxref.com/8.0.0_r4/s?defs=CallIntentBuilder&project=packages
public Intent build() {
//build 该 intent
Intent intent = new Intent(Intent.ACTION_CALL, uri);
intent.putExtra(
TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
// 此处通过该boolean值来判断是否是videocall
isVideoCall ? VideoProfile.STATE_BIDIRECTIONAL : VideoProfile.STATE_AUDIO_ONLY);
Bundle extras = new Bundle();
extras.putLong(Constants.EXTRA_CALL_CREATED_TIME_MILLIS, SystemClock.elapsedRealtime());
CallIntentParser.putCallSpecificAppData(extras, callSpecificAppData);
intent.putExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, extras);
//将拨号账号放到intent中(确认通过那张卡去拨号)
if (phoneAccountHandle != null) {
intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle);
}
if (!TextUtils.isEmpty(callSubject)) {
intent.putExtra(TelecomManager.EXTRA_CALL_SUBJECT, callSubject);
}
return intent;
}
从上面的代码可以看出几个重点的参数:Intent 的两个参数(action & Uri) , phoneaccountHandle (通话账号)。继续跟踪代码可以看到,后续会由:DialerUtils#startActivityWithErrorToast --> DialerUtils#placeCallOrMakeToast --> TelecomUtil.java#placeCall --> TelecomManager.java # placeCall。 这样就讲build的这个intent传到了Framework。至此,Dialer的工作就结束了。大概的时序图如下:
图1:Build MO Intent从第一节可以知道,Dialer创建的intent传递到了Framework,那么后续是怎么进行的呢? 看到代码可以知道,在Framework层是通过ITelecomService去placecall,这是个AIDL,所以得找到最终真正实现的地方TelecomServiceImpl.查看这里的placeCall.这里的代码逻辑也不是很复杂,最终走到UserCallIntentProcessor#sendBroadcastToReceiver.
http://androidxref.com/8.0.0_r4/xref/packages/services/Telecomm/src/com/android/server/telecom/components/UserCallIntentProcessor.java#sendBroadcastToReceiver
/**
* Trampolines the intent to the broadcast receiver that runs only as the primary user.
*/
private boolean sendBroadcastToReceiver(Intent intent) {
intent.putExtra(CallIntentProcessor.KEY_IS_INCOMING_CALL, false);
intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
intent.setClass(mContext, PrimaryCallReceiver.class);
Log.d(this, "Sending broadcast as user to CallReceiver");
mContext.sendBroadcastAsUser(intent, UserHandle.SYSTEM);
return true;
}
通过广播,继续传递这个intent。传递的方向为:UserCallIntentProcessor.java # sendBroadcastToReceiver -->--> PrimaryCallReceiver.java # onReceive --> -->CallIntentProcessor.java # processOutgoingCallIntent.
http://androidxref.com/8.0.0_r4/xref/packages/services/Telecomm/src/com/android/server/telecom/CallIntentProcessor.java
90 /**
91 * Processes CALL, CALL_PRIVILEGED, and CALL_EMERGENCY intents.
92 *
93 * @param intent Call intent containing data about the handle to call.
94 */
95 static void processOutgoingCallIntent(
96 Context context,
97 CallsManager callsManager,
98 Intent intent) {
99
....
137
138 // Send to CallsManager to ensure the InCallUI gets kicked off before the broadcast returns
139 Call call = callsManager
140 .startOutgoingCall(handle, phoneAccountHandle, clientExtras, initiatingUser,
141 intent);
142
143 if (call != null) {
144 sendNewOutgoingCallIntent(context, call, callsManager, intent);
145 }
146 }
从上面的代码可以看出,从里开始,逻辑和UI开始分开。在UI层面,去从这里去起InCallUI,并根据call的状态去更改InCallUI的U;而在逻辑层面,则继续向发起Dial流程。所以下面将分开来分析这两个过程。大概的时序图如下:
图2:prepare to dial3.构建InCallUI
可以设想下,如果我们想起InCallUI的话,我们需要些什么并且展示些什么:想拨打的号码信息,是否是VT,那张卡拨号的,当前通话的状态等等(其实这些都是Call对象的属性)。这些都是在APP层面展示的,。那我们当前有些什么? 从之前的代码可以看到
http://androidxref.com/8.0.0_r4/xref/packages/services/Telecomm/src/com/android/server/telecom/CallIntentProcessor.java
// Send to CallsManager to ensure the InCallUI gets kicked off before the broadcast returns
Call call = CallsManager.startOutgoingCall(handle,phoneAccountHandle,clientExtras,initiatingUser,intent);
所以就需要将这些我们现在已经有的内容传递给APP。那么startOutgoingCall到底在干些什么(我只保留了一些比较重要的过程)
http://androidxref.com/8.0.0_r4/xref/packages/services/Telecomm/src/com/android/server/telecom/CallsManager.java#960
/**
* Kicks off the first steps to creating an outgoing call.
*
* For managed connections, this is the first step to launching the Incall UI.
* For self-managed connections, we don't expect the Incall UI to launch, but this is still a
* first step in getting the self-managed ConnectionService to create the connection.
* @param handle Handle to connect the call with.
* @param phoneAccountHandle The phone account which contains the component name of the
* connection service to use for this call.
* @param extras The optional extras Bundle passed with the intent used for the incoming call.
* @param initiatingUser {@link UserHandle} of user that place the outgoing call.
* @param originalIntent
*/
Call startOutgoingCall(Uri handle, PhoneAccountHandle phoneAccountHandle, Bundle extras,
UserHandle initiatingUser, Intent originalIntent) {
boolean isReusedCall = true;
/** 判断当前是否有等待disconnect的call对象来复用
* 可以查看 {@link Call#abort} 和{@link #onCanceledViaNewOutgoingCall}
*/
Call call = reuseOutgoingCall(handle);
//创建call对象
if (call == null) {
call = new Call(getNextCallId(), mContext,
this,
mLock,
mConnectionServiceRepository,
mContactsAsyncHelper,
mCallerInfoAsyncQueryFactory,
mPhoneNumberUtilsAdapter,
handle,
null /* gatewayInfo */,
null /* connectionManagerPhoneAccount */,
null /* phoneAccountHandle */,
Call.CALL_DIRECTION_OUTGOING /* callDirection */,
false /* forceAttachToExistingConnection */,
false /* isConference */
);
if (needsAccountSelection) {
...
} else {
call.setState(
CallState.CONNECTING,
phoneAccountHandle == null ? "no-handle" : phoneAccountHandle.toString());
if ((isPotentialMMICode(handle) || isPotentialInCallMMICode)
&& !needsAccountSelection) {
// 此处为处理MMI code
call.addListener(this);
} else if (!isSelfManaged && hasSelfManagedCalls() && !call.isEmergencyCall()) {
...
} else if (!mCalls.contains(call)) {
// We check if mCalls already contains the call because we could potentially be reusing
// a call which was previously added (See {@link #reuseOutgoingCall}).
addCall(call);
}
return call;
}
可以看到,最终的函数是addCall(call)后续的过程就比较简单了,通过一系列的调用,最终将Call对象传递给APP。大概的调用流程如下:
CallsManager.java#startOutgoingCall --> CallsManager.java # addCall
--> InCallController.java # onCallAdded
--> InCallService.java # addCall # MSG_ADD_CALL
--> Phone.java # internalAddCall --> fireCallAdded
--> InCallService.java # onCallAdded
InCallServiceImpl.java # onCallAdded
--> InCallPresenter.java # onCallAdded
--> CallList.java # onCallAdded --> CallList.java # onDialerCallUpdate --> CallList.java # notifyGenericListeners
--> InCallPresenter.java # onCallListChange --> InCallPresenter.java # startOrFinishUi --> InCallPresenter.java # showInCall,大概的时序图如下:
在第三节中,已经将Call的UI build成功了,但仅仅有UI显示还是不够的,Call的最终目的还是通讯,所以还得真正将Call拨出。那么就需要将dial 命令发给modem了。
在第二节中说过,在CallIntentprocessor#processOutgoingCallIntent中UI和逻辑分开,那么逻辑是怎么处理的呢?
http://androidxref.com/8.0.0_r4/xref/packages/services/Telecomm/src/com/android/server/telecom/CallIntentProcessor.java#144
if (call != null) {
sendNewOutgoingCallIntent(context, call, callsManager, intent);
}
在此处真正的开始创建connection,并且向modem传递dial命令。
CallIntentProcessor去创建NewOutgoingCallIntentBroadcaster,然后由mNewOutgoingCallIntentBroadcaster去向外发送。NewOutgoingCallBroadcastIntentReceiver收到广播后开始通过CallsManager去placeOutgoingCall.
http://androidxref.com/8.0.0_r4/xref/packages/services/Telecomm/src/com/android/server/telecom/CallsManager.java#placeOutgoingCall
/**
* Attempts to issue/connect the specified call.
* 在此处有个参数可能平时不太注意:speakphoneOn,这个参数可以控制Call链接时是否直接打开
* speaker,可能有些特殊运营商有这个需求。
*/
@VisibleForTesting
public void placeOutgoingCall(Call call, Uri handle, GatewayInfo gatewayInfo,
boolean speakerphoneOn, int videoState) {
if (call == null) {
// don't do anything if the call no longer exists
Log.i(this, "Canceling unknown call.");
return;
}
...
if (call.getTargetPhoneAccount() != null || call.isEmergencyCall()) {
....
// 通过call 对象去创建connection
call.startCreateConnection(mPhoneAccountRegistrar);
....
}
}
在CallsManager#placeOutgoingCall首先需要创建connection,通过一系列的调用,最终会调用到TelephonyConnectionService.java # onCreateOutgoingConnection 。然后调用phone#dial,最终调用到Ril的dial,这块的逻辑调用也比较简单,大概的调用逻辑如下:
ConnectionService.java # createConnection --> TelephonyConnectionService.java # onCreateOutgoingConnection
--> TelephonyConnectionService.java # placeOutgoingConnection
--> GsmCdmaPhone.java # dial --> GsmCdmaPhone.java # dialInternal
--> GsmCdmaCallTracker.java # dial --> Ril.java # dial
大概的时序图如下:
图4:Send Dial request至此,MO的UI & dial命令都发下了,Tele的MO流程结束。