今天学习”android中的拨号流程”,大部分情况,用户是通过dialer输入号码,拨号通话的,那么就从dialer开始吧。
DialpadFragment是拨打电话界面,当点击拨打电话按钮会回调其onClick方法:
public void onClick(View view) {switch (view.getId()) {case R.id.dialpad_floating_action_button:view.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);handleDialButtonPressed();break;....}
}
handleDialButtonPress方法中,主要代码如下:
private void handleDialButtonPressed() {....final Intent intent = CallUtil.getCallIntent(number);if (!isDigitsShown) {// must be dial conference add extraintent.putExtra(EXTRA_DIAL_CONFERENCE_URI, true);}intent.putExtra(ADD_PARTICIPANT_KEY, mAddParticipant && isPhoneInUse());DialerUtils.startActivityWithErrorToast(getActivity(), intent);hideAndClearDialpad(false);....}
可以看到这里构造了一个intent,并且启动了该intent对应的activity
public static Intent getCallIntent(String number) {return getCallIntent(getCallUri(number));
}public static Intent getCallIntent(Uri uri) {return new Intent(Intent.ACTION_CALL, uri);
}
Intent.ACTION_CALL这样的action对应的是/packages/services/Telephony模块中的OutgoingCallBroadcaster类,该类是一个activity
<activity android:name&#61;"OutgoingCallBroadcaster"android:enabled&#61;"false"android:theme&#61;"&#64;style/OutgoingCallBroadcasterTheme"android:permission&#61;"android.permission.CALL_PHONE"android:screenOrientation&#61;"nosensor"android:configChanges&#61;"orientation|screenSize|keyboardHidden"android:excludeFromRecents&#61;"true"><intent-filter><action android:name&#61;"android.intent.action.CALL" /><category android:name&#61;"android.intent.category.DEFAULT" /><data android:scheme&#61;"tel" />intent-filter><intent-filter android:icon&#61;"&#64;drawable/ic_launcher_sip_call"><action android:name&#61;"android.intent.action.CALL" /><category android:name&#61;"android.intent.category.DEFAULT" /><data android:scheme&#61;"sip" />intent-filter><intent-filter><action android:name&#61;"android.intent.action.CALL" /><category android:name&#61;"android.intent.category.DEFAULT" /><data android:scheme&#61;"voicemail" />intent-filter><intent-filter><action android:name&#61;"android.intent.action.CALL" /><category android:name&#61;"android.intent.category.DEFAULT" /><data android:mimeType&#61;"vnd.android.cursor.item/phone" /><data android:mimeType&#61;"vnd.android.cursor.item/phone_v2" /><data android:mimeType&#61;"vnd.android.cursor.item/person" />intent-filter>
activity>
此时程序进入了OutgoingCallBroadcaster类
&#64;Override
protected void onCreate(Bundle icicle) {super.onCreate(icicle);setContentView(R.layout.outgoing_call_broadcaster);....// 调用processIntent处理传递过来的intentprocessIntent(intent);....
}
processIntent方法主要处理下面三种action
private void processIntent(Intent intent) {final Configuration configuration &#61; getResources().getConfiguration();// 如果当前设备不具有语音通信能力,则直接返回if (!PhoneGlobals.sVoiceCapable) {handleNonVoiceCapable(intent);return;}String action &#61; intent.getAction();String number &#61; PhoneNumberUtils.getNumberFromIntent(intent, this);// Check the number, don&#39;t convert for sip uriif (number !&#61; null) {if (!PhoneNumberUtils.isUriNumber(number)) {number &#61; PhoneNumberUtils.convertKeypadLettersToDigits(number);number &#61; PhoneNumberUtils.stripSeparators(number);}} else {Log.w(TAG, "The number obtained from Intent is null.");}// 下面代码获取调用Intent.ACTION_CALL所在包&#xff0c;检查当前包是否具有拨打电话的权限AppOpsManager appOps &#61; (AppOpsManager)getSystemService(Context.APP_OPS_SERVICE);int launchedFromUid;String launchedFromPackage;try {// 获取启动"ACTION_CALL"的uid和packagelaunchedFromUid &#61; ActivityManagerNative.getDefault().getLaunchedFromUid(getActivityToken());launchedFromPackage &#61; ActivityManagerNative.getDefault().getLaunchedFromPackage(getActivityToken());} catch (RemoteException e) {launchedFromUid &#61; -1;launchedFromPackage &#61; null;}// 若当前UID和所在的package不具有"OP_CALL_PHONE"权限&#xff0c;则直接返回if (appOps.noteOpNoThrow(AppOpsManager.OP_CALL_PHONE, launchedFromUid, launchedFromPackage)!&#61; AppOpsManager.MODE_ALLOWED) {Log.w(TAG, "Rejecting call from uid " &#43; launchedFromUid &#43; " package "&#43; launchedFromPackage);finish();return;}// 如果callNow是true,表示当前是一个类似于紧急拨号的特殊通话,此时直接开启通话,就不会走NEW_OUTGOING_CALLboolean callNow;// 对于紧急号码和非紧急号码设置不同的actionfinal boolean isExactEmergencyNumber &#61;(number !&#61; null) && PhoneNumberUtils.isLocalEmergencyNumber(this, number);final boolean isPotentialEmergencyNumber &#61;(number !&#61; null) && PhoneNumberUtils.isPotentialLocalEmergencyNumber(this, number);if (Intent.ACTION_CALL_PRIVILEGED.equals(action)) {if (isPotentialEmergencyNumber) {action &#61; Intent.ACTION_CALL_EMERGENCY;} else {action &#61; Intent.ACTION_CALL;}intent.setAction(action);}// 当用户输入的号码为空的时候&#xff0c;intent.getBooleanExtra(EXTRA_SEND_EMPTY_FLASH, false) &#61;&#61; trueif (intent.getBooleanExtra(EXTRA_SEND_EMPTY_FLASH, false)) {PhoneUtils.sendEmptyFlash(PhoneGlobals.getPhone());finish();return;} else {callNow &#61; true;}....if (callNow) {// 如果是紧急号码或者输入的号码合法&#xff0c;则直接跳转到InCallScreen界面PhoneGlobals.getInstance().callController.placeCall(intent);}// 构造一个"ACTION_NEW_OUTGOING_CALL" intentIntent broadcastIntent &#61; new Intent(Intent.ACTION_NEW_OUTGOING_CALL);if (number !&#61; null) {broadcastIntent.putExtra(Intent.EXTRA_PHONE_NUMBER, number);}CallGatewayManager.checkAndCopyPhoneProviderExtras(intent, broadcastIntent);broadcastIntent.putExtra(EXTRA_ALREADY_CALLED, callNow);broadcastIntent.putExtra(EXTRA_ORIGINAL_URI, uri.toString());broadcastIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);// 发送一个打电话超时的messagemHandler.sendEmptyMessageDelayed(EVENT_OUTGOING_CALL_TIMEOUT,OUTGOING_CALL_TIMEOUT_THRESHOLD);// 主要会发送根据构造出的intent,发送一个有序广播,并且在OutgoingCallReceiver中处理sendOrderedBroadcastAsUser(broadcastIntent, UserHandle.OWNER,android.Manifest.permission.PROCESS_OUTGOING_CALLS,AppOpsManager.OP_PROCESS_OUTGOING_CALLS,new OutgoingCallReceiver(),null, // schedulerActivity.RESULT_OK, // initialCodenumber, // initialData: initial value for the result datanull); // initialExtras
}
public void placeCall(Intent intent) {....if (!(Intent.ACTION_CALL.equals(action)|| Intent.ACTION_CALL_EMERGENCY.equals(action)|| Intent.ACTION_CALL_PRIVILEGED.equals(action))) {Log.wtf(TAG, "placeCall: unexpected intent action " &#43; action);throw new IllegalArgumentException("Unexpected action: " &#43; action);}// Check to see if this is an OTASP call (the "activation" call// used to provision CDMA devices), and if so, do some// OTASP-specific setup.Phone phone &#61; mApp.mCM.getDefaultPhone();if (TelephonyCapabilities.supportsOtasp(phone)) {checkForOtaspCall(intent);}mApp.setRestoreMuteOnInCallResume(false);CallStatusCode status &#61; placeCallInternal(intent);switch (status) {// Call was placed successfully:case SUCCESS:case EXITED_ECM:if (DBG) log("&#61;&#61;> placeCall(): success from placeCallInternal(): " &#43; status);break;default:log("&#61;&#61;> placeCall(): failure code from placeCallInternal(): " &#43; status);handleOutgoingCallError(status);break;}// 最终无论如何都会显示InCallScreen,并且根据当前错误码状态显示指定的错误提示信息}
private CallStatusCode placeCallInternal(Intent intent) {....int callStatus &#61; PhoneUtils.placeCall(mApp,phone,number,contactUri,(isEmergencyNumber || isEmergencyIntent),rawGatewayInfo,mCallGatewayManager);....
}
public static int placeCall(Context context, Phone phone, String number, Uri contactRef,boolean isEmergencyCall, RawGatewayInfo gatewayInfo, CallGatewayManager callGateway) {....int status &#61; CALL_STATUS_DIALED;try {// 和RIL建立连接,mCM是PhoneGlobals的属性同时是CallManager类型connection &#61; app.mCM.dial(phone, numberToDial, VideoProfile.STATE_AUDIO_ONLY);} catch (CallStateException ex) {return CALL_STATUS_FAILED;}if (null &#61;&#61; connection) {status &#61; CALL_STATUS_FAILED;} //startGetCallerInfo(context, connection, null, null, gatewayInfo);// 设置音频相关setAudioMode();final boolean speakerActivated &#61; activateSpeakerIfDocked(phone);final BluetoothManager btManager &#61; app.getBluetoothManager();if (initiallyIdle && !speakerActivated && isSpeakerOn(app)&& !btManager.isBluetoothHeadsetAudioOn()) {PhoneUtils.turnOnSpeaker(app, false, true);}....return status;
}
public Connection dial(Phone phone, String dialString, int videoState)throws CallStateException {Phone basePhone &#61; getPhoneBase(phone);int subId &#61; phone.getSubId();Connection result;// 检查当前手机状态是否可以拨打电话if (!canDial(phone)) {String newDialString &#61; PhoneNumberUtils.stripSeparators(dialString);if (basePhone.handleInCallMmiCommands(newDialString)) {return null;} else {throw new CallStateException("cannot dial in current state");}}result &#61; basePhone.dial(dialString, videoState);return result;
}
basePhone是一个phone对象&#xff0c;大部分情况是Phone的一个代理类PhoneProxy&#xff0c;然后根据PhoneProxy的mActivePhone判断具体是那个Phone的子类的实现&#xff0c;有可能是下面类型&#xff1a;
com/android/internal/telephony/gsm/GSMPhone.java
com.android.internal.telephony.cdma.CDMAPhone
com.android.internal.telephony.sip.SipPhone
....
private static Phone getPhoneBase(Phone phone) {if (phone instanceof PhoneProxy) {return phone.getForegroundCall().getPhone();}return phone;
}
&#64;Override
public Connectiondial(String dialString, int videoState) throws CallStateException {return dial(dialString, null, videoState, null);}&#64;Overridepublic Connectiondial (String dialString, UUSInfo uusInfo, int videoState, Bundle intentExtras)throws CallStateException {....return dialInternal(dialString, null, VideoProfile.STATE_AUDIO_ONLY, intentExtras);
}
&#64;Overrideprotected ConnectiondialInternal (String dialString, UUSInfo uusInfo, int videoState, Bundle intentExtras)throws CallStateException {// Need to make sure dialString gets parsed properlyString newDialString &#61; PhoneNumberUtils.stripSeparators(dialString);// handle in-call MMI first if applicableif (handleInCallMmiCommands(newDialString)) {return null;}// Only look at the Network portion for mmiString networkPortion &#61; PhoneNumberUtils.extractNetworkPortionAlt(newDialString);GsmMmiCode mmi &#61;GsmMmiCode.newFromDialString(networkPortion, this, mUiccApplication.get());// mCT是GsmCallTracker类对象,这里调用GsmCallTracker#dialif (mmi &#61;&#61; null) {return mCT.dial(newDialString, uusInfo, intentExtras);} else if (mmi.isTemporaryModeCLIR()) {return mCT.dial(mmi.mDialingNumber, mmi.getCLIRMode(), uusInfo, intentExtras);} else {mPendingMMIs.add(mmi);mMmiRegistrants.notifyRegistrants(new AsyncResult(null, mmi, null));mmi.processCode();return null;}}
synchronized Connectiondial (String dialString, int clirMode, UUSInfo uusInfo, Bundle intentExtras)throws CallStateException {....if ( mPendingMO.getAddress() &#61;&#61; null || mPendingMO.getAddress().length() &#61;&#61; 0|| mPendingMO.getAddress().indexOf(PhoneNumberUtils.WILD) >&#61; 0) {// 无效的号码pollCallsWhenSafe();} else {// Always unmute when initiating a new callsetMute(false);// mCi是CommandsInterface接口&#xff0c;其具体的实现类是com.android.internal.telephony.RILmCi.dial(mPendingMO.getAddress(), clirMode, uusInfo, obtainCompleteMessage());}....}
关于mCi的初始化工作&#xff0c;可以参考上一篇短信的发送流程
public voiddial(String address, int clirMode, UUSInfo uusInfo, Message result) {RILRequest rr &#61; RILRequest.obtain(RIL_REQUEST_DIAL, result);rr.mParcel.writeString(address);rr.mParcel.writeInt(clirMode);if (uusInfo &#61;&#61; null) {rr.mParcel.writeInt(0); // UUS information is absent} else {rr.mParcel.writeInt(1); // UUS information is presentrr.mParcel.writeInt(uusInfo.getType());rr.mParcel.writeInt(uusInfo.getDcs());rr.mParcel.writeByteArray(uusInfo.getUserData());}if (RILJ_LOGD) riljLog(rr.serialString() &#43; "> " &#43; requestToString(rr.mRequest));send(rr);}
可以看到&#xff0c;最终也是通过send方法发送”EVENT_SEND”消息&#xff0c;给到自己处理&#xff0c;厉害了&#xff0c;天啦鲁&#xff0c;居然和短信的发送走到同一条路上&#xff0c;然后就是交给modem去具体操作了。
1. com.android.dialer.dialpad.DialpadFragment#onClick
2. com.android.dialer.dialpad.DialpadFragment#handleDialButtonPressed
3. com.android.phone.OutgoingCallBroadcaster#processIntent
4. com.android.phone.CallController#placeCall
5. com.android.phone.CallController#placeCallInternal
6. com.android.phone.PhoneUtils#placeCall(android.content.Context, com.android.internal.telephony.Phone, java.lang.String, android.net.Uri, boolean, com.android.phone.CallGatewayManager.RawGatewayInfo, com.android.phone.CallGatewayManager)
7. com.android.internal.telephony.CallManager#dial(com.android.internal.telephony.Phone, java.lang.String, int)
8. com.android.internal.telephony.PhoneProxy#dial(java.lang.String, int)
9. 以GSMPhone为例com.android.internal.telephony.gsm.GSMPhone#dialInternal
10.com.android.internal.telephony.RIL#dial(java.lang.String, int, com.android.internal.telephony.UUSInfo, android.os.Message)
11.com.android.internal.telephony.RIL#send
发送msg &#61; mSender.obtainMessage(EVENT_SEND, rr);这样的message给到RILSender处理