热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

android拨号流程

今天学习”android中的拨号流程”,大部分情况,用户是通过dialer输入号码,拨号通话的,那么就从dialer开始吧。

今天学习”android中的拨号流程”,大部分情况,用户是通过dialer输入号码,拨号通话的,那么就从dialer开始吧。


DialpadFragment

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);....}

CallUtil#getCallIntent

可以看到这里构造了一个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

此时程序进入了OutgoingCallBroadcaster类

&#64;Override
protected void onCreate(Bundle icicle) {super.onCreate(icicle);setContentView(R.layout.outgoing_call_broadcaster);....// 调用processIntent处理传递过来的intentprocessIntent(intent);....
}

processIntent方法主要处理下面三种action


  • CALL (action for usual outgoing voice calls)
  • CALL_PRIVILEGED (can come from built-in apps like contacts / voice dialer / bluetooth)
  • CALL_EMERGENCY (from the EmergencyDialer that’s reachable from the lockscreen.)
  • 对于数据为tel: URI的电话处理流程为&#xff1a;OutgoingCallReceiver -> SipCallOptionHandler ->InCallScreen.
  • 对于数据为sip: URI的网络电话&#xff0c;则跳过NEW_OUTGOING_CALL广播&#xff0c;直接调用SipCallOptionHandler ->InCallScreen
  • 对于数据为voicemail: URIs的语音信箱处理同电话处理流程类似

OutgoingCallBroadcaster#processIntent

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
}

CallController#placeCall

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,并且根据当前错误码状态显示指定的错误提示信息}

CallController#placeCallInternal

private CallStatusCode placeCallInternal(Intent intent) {....int callStatus &#61; PhoneUtils.placeCall(mApp,phone,number,contactUri,(isEmergencyNumber || isEmergencyIntent),rawGatewayInfo,mCallGatewayManager);....
}

PhoneUtils#placeCall

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;
}

CallManager#dial

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;
}

以GMSPhone#dial为例


GMSPhone#dial

&#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);
}

GMSPhone#dialInternal

&#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;}}

GsmCallTracker#dial

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;可以参考上一篇短信的发送流程


RIL#dial

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处理

推荐阅读
  • 在1995年,Simon Plouffe 发现了一种特殊的求和方法来表示某些常数。两年后,Bailey 和 Borwein 在他们的论文中发表了这一发现,这种方法被命名为 Bailey-Borwein-Plouffe (BBP) 公式。该问题要求计算圆周率 π 的第 n 个十六进制数字。 ... [详细]
  • 处理Android EditText中数字输入与parseInt方法
    本文探讨了如何在Android应用中从EditText组件安全地获取并解析用户输入的数字,特别是用于设置端口号的情况。通过示例代码和异常处理策略,展示了有效的方法来避免因非法输入导致的应用崩溃。 ... [详细]
  • 利用Node.js实现PSD文件的高效切图
    本文介绍了如何通过Node.js及其psd2json模块,快速实现PSD文件的自动化切图过程,以适应项目中频繁的界面更新需求。此方法不仅提高了工作效率,还简化了从设计稿到实际应用的转换流程。 ... [详细]
  • 本文详细介绍了如何使用C#实现不同类型的系统服务账户(如Windows服务、计划任务和IIS应用池)的密码重置方法。 ... [详细]
  • Java 中的十进制样式 getZeroDigit()方法,示例 ... [详细]
  • JUnit下的测试和suite
    nsitionalENhttp:www.w3.orgTRxhtml1DTDxhtml1-transitional.dtd ... [详细]
  • 本文基于Java官方文档进行了适当修改,旨在介绍如何实现一个能够同时处理多个客户端请求的服务端程序。在前文中,我们探讨了单客户端访问的服务端实现,而本篇将深入讲解多客户端环境下的服务端设计与实现。 ... [详细]
  • 本文探讨了一种常见的C++面试题目——实现自己的String类。通过此过程,不仅能够检验开发者对C++基础知识的掌握程度,还能加深对其高级特性的理解。文章详细介绍了如何实现基本的功能,如构造函数、析构函数、拷贝构造函数及赋值运算符重载等。 ... [详细]
  • 深入解析 C++ 中的 String 和 Vector
    本文详细介绍了 C++ 编程语言中 String 和 Vector 的使用方法及特性,旨在帮助开发者更好地理解和应用这两个重要的容器。 ... [详细]
  • 本文详细介绍了在Luat OS中如何实现C与Lua的混合编程,包括在C环境中运行Lua脚本、封装可被Lua调用的C语言库,以及C与Lua之间的数据交互方法。 ... [详细]
  • 尽管在WPF中工作了一段时间,但在菜单控件的样式设置上遇到了一些基础问题,特别是关于如何正确配置前景色和背景色。 ... [详细]
  • 深入解析C语言中的关键字及其分类
    本文将全面介绍C语言中的关键字,并按照功能将其分为数据类型关键字、控制结构关键字、存储类别关键字和其他关键字四大类,旨在帮助读者更好地理解和运用这些基本元素。C语言中共有32个关键字。 ... [详细]
  • 使用TabActivity实现Android顶部选项卡功能
    本文介绍如何通过继承TabActivity来创建Android应用中的顶部选项卡。通过简单的步骤,您可以轻松地添加多个选项卡,并实现基本的界面切换功能。 ... [详细]
  • 二维码的实现与应用
    本文介绍了二维码的基本概念、分类及其优缺点,并详细描述了如何使用Java编程语言结合第三方库(如ZXing和qrcode.jar)来实现二维码的生成与解析。 ... [详细]
  • Go从入门到精通系列视频之go编程语言密码学哈希算法(二) ... [详细]
author-avatar
Rocky柱子
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有