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

推荐阅读
  • 本文介绍如何使用 Android 的 Canvas 和 View 组件创建一个简单的绘图板应用程序,支持触摸绘画和保存图片功能。 ... [详细]
  • Redux入门指南
    本文介绍Redux的基本概念和工作原理,帮助初学者理解如何使用Redux管理应用程序的状态。Redux是一个用于JavaScript应用的状态管理库,特别适用于React项目。 ... [详细]
  • springMVC JRS303验证 ... [详细]
  • 在 Android 开发中,通过 Intent 启动 Activity 或 Service 时,可以使用 putExtra 方法传递数据。接收方可以通过 getIntent().getExtras() 获取这些数据。本文将介绍如何使用 RoboGuice 框架简化这一过程,特别是 @InjectExtra 注解的使用。 ... [详细]
  • 探讨ChatGPT在法律和版权方面的潜在风险及影响,分析其作为内容创造工具的合法性和合规性。 ... [详细]
  • ListView简单使用
    先上效果:主要实现了Listview的绑定和点击事件。项目资源结构如下:先创建一个动物类,用来装载数据:Animal类如下:packagecom.example.simplelis ... [详细]
  • 黑马头条项目:Vue 文章详情模块与交互功能实现
    本文详细介绍了如何在黑马头条项目中配置文章详情模块的路由、获取和展示文章详情数据,以及实现关注、点赞、不喜欢和评论功能。通过这些步骤,您可以全面了解如何开发一个完整的前端文章详情页面。 ... [详细]
  • 深入解析 Android IPC 中的 Messenger 机制
    本文详细介绍了 Android 中基于消息传递的进程间通信(IPC)机制——Messenger。通过实例和源码分析,帮助开发者更好地理解和使用这一高效的通信工具。 ... [详细]
  • 本文详细介绍了如何在Android 4.4及以上版本中配置WebView以实现内容的自动高度调整和屏幕适配,确保中文显示正常,并提供代码示例。 ... [详细]
  • 本文详细介绍了如何在 Android 中使用值动画(ValueAnimator)来动态调整 ImageView 的高度,并探讨了相关的关键属性和方法,包括图片填充后的高度、原始图片高度、动画变化因子以及布局重置等。 ... [详细]
  • 本文介绍了如何在iOS应用中自定义导航栏按钮,包括使用普通按钮和图片生成导航条专用按钮的方法。同时,探讨了在不同版本的iOS系统中实现多按钮布局的技术方案。 ... [详细]
  • JSOI2010 蔬菜庆典:树结构中的无限大权值问题
    本文探讨了 JSOI2010 的蔬菜庆典问题,主要关注如何处理非根非叶子节点的无限大权值情况。通过分析根节点及其子树的特性,提出了有效的解决方案,并详细解释了算法的实现过程。 ... [详细]
  • 本文介绍了如何使用JavaScript的Fetch API与Express服务器进行交互,涵盖了GET、POST、PUT和DELETE请求的实现,并展示了如何处理JSON响应。 ... [详细]
  • 本文详细介绍了 Android 开发中 layout_gravity 属性的使用方法及其在不同布局下的效果,旨在帮助开发者更好地理解和利用这一属性来精确控制视图的布局。 ... [详细]
  • 当unique验证运到图片上传时
    2019独角兽企业重金招聘Python工程师标准model:public$imageFile;publicfunctionrules(){return[[[na ... [详细]
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社区 版权所有