热门标签 | 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中org.eclipse.ui.forms.widgets.ExpandableComposite类的addExpansionListener()方法,并提供了多个实际代码示例,帮助开发者更好地理解和使用该方法。这些示例来源于多个知名开源项目,具有很高的参考价值。 ... [详细]
  • 本文探讨了在Java中实现系统托盘最小化的两种方法:使用SWT库和JDK6自带的功能。通过这两种方式,开发者可以创建跨平台的应用程序,使窗口能够最小化到系统托盘,并提供丰富的交互功能。 ... [详细]
  • 优化ListView性能
    本文深入探讨了如何通过多种技术手段优化ListView的性能,包括视图复用、ViewHolder模式、分批加载数据、图片优化及内存管理等。这些方法能够显著提升应用的响应速度和用户体验。 ... [详细]
  • Explore how Matterverse is redefining the metaverse experience, creating immersive and meaningful virtual environments that foster genuine connections and economic opportunities. ... [详细]
  • 1.如何在运行状态查看源代码?查看函数的源代码,我们通常会使用IDE来完成。比如在PyCharm中,你可以Ctrl+鼠标点击进入函数的源代码。那如果没有IDE呢?当我们想使用一个函 ... [详细]
  • 本文详细介绍了Akka中的BackoffSupervisor机制,探讨其在处理持久化失败和Actor重启时的应用。通过具体示例,展示了如何配置和使用BackoffSupervisor以实现更细粒度的异常处理。 ... [详细]
  • Android 渐变圆环加载控件实现
    本文介绍了如何在 Android 中创建一个自定义的渐变圆环加载控件,该控件已在多个知名应用中使用。我们将详细探讨其工作原理和实现方法。 ... [详细]
  • 在使用 DataGridView 时,如果在当前单元格中输入内容但光标未移开,点击保存按钮后,输入的内容可能无法保存。只有当光标离开单元格后,才能成功保存数据。本文将探讨如何通过调用 DataGridView 的内置方法解决此问题。 ... [详细]
  • RecyclerView初步学习(一)
    RecyclerView初步学习(一)ReCyclerView提供了一种插件式的编程模式,除了提供ViewHolder缓存模式,还可以自定义动画,分割符,布局样式,相比于传统的ListVi ... [详细]
  • 本文介绍如何使用阿里云的fastjson库解析包含时间戳、IP地址和参数等信息的JSON格式文本,并进行数据处理和保存。 ... [详细]
  • 本文详细介绍了 com.facebook.drawee.view.SimpleDraweeView 中的 setScaleType 方法,提供了多个实际代码示例,并解释了其在不同场景下的应用。 ... [详细]
  • ASP.NET MVC中Area机制的实现与优化
    本文探讨了在ASP.NET MVC框架中,如何通过Area机制有效地组织和管理大规模应用程序的不同功能模块。通过合理的文件夹结构和命名规则,开发人员可以更高效地管理和扩展项目。 ... [详细]
  • dotnet 通过 Elmish.WPF 使用 F# 编写 WPF 应用
    本文来安利大家一个有趣而且强大的库,通过F#和C#混合编程编写WPF应用,可以在WPF中使用到F#强大的数据处理能力在GitHub上完全开源Elmis ... [详细]
  • 本文介绍如何使用布局文件在Android应用中排列多行TextView和Button,使其占据屏幕的特定比例,并提供示例代码以帮助理解和实现。 ... [详细]
  • IneedtofocusTextCellsonebyoneviaabuttonclick.ItriedlistView.ScrollTo.我需要通过点击按钮逐个关注Tex ... [详细]
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社区 版权所有