热门标签 | 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处理

推荐阅读
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 本文介绍了设计师伊振华受邀参与沈阳市智慧城市运行管理中心项目的整体设计,并以数字赋能和创新驱动高质量发展的理念,建设了集成、智慧、高效的一体化城市综合管理平台,促进了城市的数字化转型。该中心被称为当代城市的智能心脏,为沈阳市的智慧城市建设做出了重要贡献。 ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 本文介绍了Android 7的学习笔记总结,包括最新的移动架构视频、大厂安卓面试真题和项目实战源码讲义。同时还分享了开源的完整内容,并提醒读者在使用FileProvider适配时要注意不同模块的AndroidManfiest.xml中配置的xml文件名必须不同,否则会出现问题。 ... [详细]
  • 本文介绍了深入浅出Linux设备驱动编程的重要性,以及两种加载和删除Linux内核模块的方法。通过一个内核模块的例子,展示了模块的编译和加载过程,并讨论了模块对内核大小的控制。深入理解Linux设备驱动编程对于开发者来说非常重要。 ... [详细]
  • 本文介绍了如何使用Express App提供静态文件,同时提到了一些不需要使用的文件,如package.json和/.ssh/known_hosts,并解释了为什么app.get('*')无法捕获所有请求以及为什么app.use(express.static(__dirname))可能会提供不需要的文件。 ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • 本文主要解析了Open judge C16H问题中涉及到的Magical Balls的快速幂和逆元算法,并给出了问题的解析和解决方法。详细介绍了问题的背景和规则,并给出了相应的算法解析和实现步骤。通过本文的解析,读者可以更好地理解和解决Open judge C16H问题中的Magical Balls部分。 ... [详细]
  • Linux环境变量函数getenv、putenv、setenv和unsetenv详解
    本文详细解释了Linux中的环境变量函数getenv、putenv、setenv和unsetenv的用法和功能。通过使用这些函数,可以获取、设置和删除环境变量的值。同时给出了相应的函数原型、参数说明和返回值。通过示例代码演示了如何使用getenv函数获取环境变量的值,并打印出来。 ... [详细]
  • 前景:当UI一个查询条件为多项选择,或录入多个条件的时候,比如查询所有名称里面包含以下动态条件,需要模糊查询里面每一项时比如是这样一个数组条件:newstring[]{兴业银行, ... [详细]
  • 本文介绍了一个题目的解法,通过二分答案来解决问题,但困难在于如何进行检查。文章提供了一种逃逸方式,通过移动最慢的宿管来锁门时跑到更居中的位置,从而使所有合格的寝室都居中。文章还提到可以分开判断两边的情况,并使用前缀和的方式来求出在任意时刻能够到达宿管即将锁门的寝室的人数。最后,文章提到可以改成O(n)的直接枚举来解决问题。 ... [详细]
  • Go GUIlxn/walk 学习3.菜单栏和工具栏的具体实现
    本文介绍了使用Go语言的GUI库lxn/walk实现菜单栏和工具栏的具体方法,包括消息窗口的产生、文件放置动作响应和提示框的应用。部分代码来自上一篇博客和lxn/walk官方示例。文章提供了学习GUI开发的实际案例和代码示例。 ... [详细]
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
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社区 版权所有