Android call 流程以及其他case整理(2)--MO

对于Call模块最先接触的应该就是MO call,这也是比较常见的case.对于MO call, 在没有正式分析流程前,其实也可以考虑下,这个流程到底干了些什么事:首先通过Dialer 开始拨号,然后Telephony & Telecomm去处理逻辑,InCallUI 显示通话界面,然后通过RIL命令把Dial发给Modem.Modem 返回Dial 成功,电话拨打成功,然后InCallUI 根据Call的状态去更新UI。那接下来就从代码级别依次来查看下流程。

1.开始拨号,构建Intent

      从操作来看,我们会先输入将要拨打号码,然后点击拨号键。其实这个过程就是Dialer build Intent的过程。构建的这个intent 会包含dial的action,且包含将要拨打的号码。这个Intent会传递给Telecomm处理,只有带了这两个extra,telecom才知道收到广播后去干什么。

http://androidxref.com/8.0.0_r4/xref/packages/apps/Dialer/java/com/android/dialer/app/dialpad/DialpadFragment.java#handleDialButtonPressed
  /**
   * In most cases, when the dial button is pressed, there is a number in digits area. Pack it in
   * the intent, start the outgoing call broadcast as a separate task and finish this activity.
   *
   * 

When there is no digit and the phone is CDMA and off hook, we're sending a blank flash for * CDMA. CDMA networks use Flash messages when special processing needs to be done, mainly for * 3-way or call waiting scenarios. Presumably, here we're in a special 3-way scenario where the * network needs a blank flash before being able to add the new participant. (This is not the case * with all 3-way calls, just certain CDMA infrastructures.) * *

Otherwise, there is no digit, display the last dialed number. Don't finish since the user * may want to edit it. The user needs to press the dial button again, to dial it (general case * described above). */ private void handleDialButtonPressed() { if (isDigitsEmpty()) { // No number entered. handleDialButtonClickWithEmptyDigits(); } else { final String number = mDigits.getText().toString(); // "persist.radio.otaspdial" is a temporary hack needed for one carrier's automated // test equipment. // TODO: clean it up. if (number != null && !TextUtils.isEmpty(mProhibitedPhoneNumberRegexp) && number.matches(mProhibitedPhoneNumberRegexp)) { LogUtil.i( "DialpadFragment.handleDialButtonPressed", "The phone number is prohibited explicitly by a rule."); if (getActivity() != null) { DialogFragment dialogFragment = ErrorDialogFragment.newInstance(R.string.dialog_phone_call_prohibited_message); dialogFragment.show(getFragmentManager(), "phone_prohibited_dialog"); } // Clear the digits just in case. clearDialpad(); } else { final Intent intent = new CallIntentBuilder(number, CallInitiationType.Type.DIALPAD).build(); DialerUtils.startActivityWithErrorToast(getActivity(), intent); hideAndClearDialpad(false); } } }

上面的代码逻辑比较清晰,可以看到,正常情况下,我们会走第三种case.可以看到,在此处build了一个intent,然后通过intent去起拨号请求。我们具体查看下该intent的build详情。

http://androidxref.com/8.0.0_r4/s?defs=CallIntentBuilder&project=packages

  public Intent build() {
    //build 该 intent
    Intent intent = new Intent(Intent.ACTION_CALL, uri);
    intent.putExtra(
        TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
        // 此处通过该boolean值来判断是否是videocall
        isVideoCall ? VideoProfile.STATE_BIDIRECTIONAL : VideoProfile.STATE_AUDIO_ONLY);

    Bundle extras = new Bundle();
    extras.putLong(Constants.EXTRA_CALL_CREATED_TIME_MILLIS, SystemClock.elapsedRealtime());
    CallIntentParser.putCallSpecificAppData(extras, callSpecificAppData);
    intent.putExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, extras);

    //将拨号账号放到intent中(确认通过那张卡去拨号)
    if (phoneAccountHandle != null) {
      intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle);
    }

    if (!TextUtils.isEmpty(callSubject)) {
      intent.putExtra(TelecomManager.EXTRA_CALL_SUBJECT, callSubject);
    }

    return intent;
  }

   从上面的代码可以看出几个重点的参数:Intent 的两个参数(action & Uri) , phoneaccountHandle (通话账号)。继续跟踪代码可以看到,后续会由:DialerUtils#startActivityWithErrorToast --> DialerUtils#placeCallOrMakeToast --> TelecomUtil.java#placeCall --> TelecomManager.java # placeCall。 这样就讲build的这个intent传到了Framework。至此,Dialer的工作就结束了。大概的时序图如下:

Android call 流程以及其他case整理(2)--MO_第1张图片 图1:Build MO Intent

2.拨号准备与数据传递

  从第一节可以知道,Dialer创建的intent传递到了Framework,那么后续是怎么进行的呢? 看到代码可以知道,在Framework层是通过ITelecomService去placecall,这是个AIDL,所以得找到最终真正实现的地方TelecomServiceImpl.查看这里的placeCall.这里的代码逻辑也不是很复杂,最终走到UserCallIntentProcessor#sendBroadcastToReceiver.

http://androidxref.com/8.0.0_r4/xref/packages/services/Telecomm/src/com/android/server/telecom/components/UserCallIntentProcessor.java#sendBroadcastToReceiver

    /**
     * Trampolines the intent to the broadcast receiver that runs only as the primary user.
     */
    private boolean sendBroadcastToReceiver(Intent intent) {
        intent.putExtra(CallIntentProcessor.KEY_IS_INCOMING_CALL, false);
        intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
        intent.setClass(mContext, PrimaryCallReceiver.class);
        Log.d(this, "Sending broadcast as user to CallReceiver");
        mContext.sendBroadcastAsUser(intent, UserHandle.SYSTEM);
        return true;
    }

   通过广播,继续传递这个intent。传递的方向为:UserCallIntentProcessor.java # sendBroadcastToReceiver  -->--> PrimaryCallReceiver.java # onReceive --> -->CallIntentProcessor.java #  processOutgoingCallIntent. 

http://androidxref.com/8.0.0_r4/xref/packages/services/Telecomm/src/com/android/server/telecom/CallIntentProcessor.java

90    /**
91     * Processes CALL, CALL_PRIVILEGED, and CALL_EMERGENCY intents.
92     *
93     * @param intent Call intent containing data about the handle to call.
94     */
95    static void processOutgoingCallIntent(
96            Context context,
97            CallsManager callsManager,
98            Intent intent) {
99
           ....
137
138        // Send to CallsManager to ensure the InCallUI gets kicked off before the broadcast returns
139        Call call = callsManager
140                .startOutgoingCall(handle, phoneAccountHandle, clientExtras, initiatingUser,
141                        intent);
142
143        if (call != null) {
144            sendNewOutgoingCallIntent(context, call, callsManager, intent);
145        }
146    }

从上面的代码可以看出,从里开始,逻辑和UI开始分开。在UI层面,去从这里去起InCallUI,并根据call的状态去更改InCallUI的U;而在逻辑层面,则继续向发起Dial流程。所以下面将分开来分析这两个过程。大概的时序图如下:

Android call 流程以及其他case整理(2)--MO_第2张图片 图2:prepare to dial

 3.构建InCallUI

可以设想下,如果我们想起InCallUI的话,我们需要些什么并且展示些什么:想拨打的号码信息,是否是VT,那张卡拨号的,当前通话的状态等等(其实这些都是Call对象的属性)。这些都是在APP层面展示的,。那我们当前有些什么? 从之前的代码可以看到

http://androidxref.com/8.0.0_r4/xref/packages/services/Telecomm/src/com/android/server/telecom/CallIntentProcessor.java

        // Send to CallsManager to ensure the InCallUI gets kicked off before the broadcast returns
        Call call = CallsManager.startOutgoingCall(handle,phoneAccountHandle,clientExtras,initiatingUser,intent);

所以就需要将这些我们现在已经有的内容传递给APP。那么startOutgoingCall到底在干些什么(我只保留了一些比较重要的过程)

http://androidxref.com/8.0.0_r4/xref/packages/services/Telecomm/src/com/android/server/telecom/CallsManager.java#960

    /**
     * Kicks off the first steps to creating an outgoing call.
     *
     * For managed connections, this is the first step to launching the Incall UI.
     * For self-managed connections, we don't expect the Incall UI to launch, but this is still a
     * first step in getting the self-managed ConnectionService to create the connection.
     * @param handle Handle to connect the call with.
     * @param phoneAccountHandle The phone account which contains the component name of the
     *        connection service to use for this call.
     * @param extras The optional extras Bundle passed with the intent used for the incoming call.
     * @param initiatingUser {@link UserHandle} of user that place the outgoing call.
     * @param originalIntent
     */
    Call startOutgoingCall(Uri handle, PhoneAccountHandle phoneAccountHandle, Bundle extras,
            UserHandle initiatingUser, Intent originalIntent) {
        boolean isReusedCall = true;
        /** 判断当前是否有等待disconnect的call对象来复用
         *  可以查看 {@link Call#abort} 和{@link #onCanceledViaNewOutgoingCall} 
         */
        Call call = reuseOutgoingCall(handle);

        //创建call对象
        if (call == null) {
            call = new Call(getNextCallId(), mContext,
                    this,
                    mLock,
                    mConnectionServiceRepository,
                    mContactsAsyncHelper,
                    mCallerInfoAsyncQueryFactory,
                    mPhoneNumberUtilsAdapter,
                    handle,
                    null /* gatewayInfo */,
                    null /* connectionManagerPhoneAccount */,
                    null /* phoneAccountHandle */,
                    Call.CALL_DIRECTION_OUTGOING /* callDirection */,
                    false /* forceAttachToExistingConnection */,
                    false /* isConference */
            );
            
        if (needsAccountSelection) {
            ...
        } else {
            call.setState(
                    CallState.CONNECTING,
                    phoneAccountHandle == null ? "no-handle" : phoneAccountHandle.toString());



        if ((isPotentialMMICode(handle) || isPotentialInCallMMICode)
                && !needsAccountSelection) {
            // 此处为处理MMI code
            call.addListener(this);
        } else if (!isSelfManaged && hasSelfManagedCalls() && !call.isEmergencyCall()) {
          ...
        } else if (!mCalls.contains(call)) {
            // We check if mCalls already contains the call because we could potentially be reusing
            // a call which was previously added (See {@link #reuseOutgoingCall}).
            addCall(call);
        }

        return call;
    }

可以看到,最终的函数是addCall(call)后续的过程就比较简单了,通过一系列的调用,最终将Call对象传递给APP。大概的调用流程如下:

CallsManager.java#startOutgoingCall --> CallsManager.java # addCall
--> InCallController.java # onCallAdded 
--> InCallService.java # addCall # MSG_ADD_CALL
--> Phone.java # internalAddCall --> fireCallAdded
--> InCallService.java # onCallAdded 
InCallServiceImpl.java #  onCallAdded
--> InCallPresenter.java # onCallAdded
--> CallList.java # onCallAdded --> CallList.java # onDialerCallUpdate --> CallList.java # notifyGenericListeners 
--> InCallPresenter.java # onCallListChange  --> InCallPresenter.java # startOrFinishUi --> InCallPresenter.java # showInCall,大概的时序图如下:

Android call 流程以及其他case整理(2)--MO_第3张图片 图3:Start InCallUI


4. Dial命令发送

   在第三节中,已经将Call的UI build成功了,但仅仅有UI显示还是不够的,Call的最终目的还是通讯,所以还得真正将Call拨出。那么就需要将dial 命令发给modem了。

    在第二节中说过,在CallIntentprocessor#processOutgoingCallIntent中UI和逻辑分开,那么逻辑是怎么处理的呢?

http://androidxref.com/8.0.0_r4/xref/packages/services/Telecomm/src/com/android/server/telecom/CallIntentProcessor.java#144

        if (call != null) {
            sendNewOutgoingCallIntent(context, call, callsManager, intent);
        }

在此处真正的开始创建connection,并且向modem传递dial命令。

CallIntentProcessor去创建NewOutgoingCallIntentBroadcaster,然后由mNewOutgoingCallIntentBroadcaster去向外发送。NewOutgoingCallBroadcastIntentReceiver收到广播后开始通过CallsManager去placeOutgoingCall.

http://androidxref.com/8.0.0_r4/xref/packages/services/Telecomm/src/com/android/server/telecom/CallsManager.java#placeOutgoingCall

    /**
     * Attempts to issue/connect the specified call.
     * 在此处有个参数可能平时不太注意:speakphoneOn,这个参数可以控制Call链接时是否直接打开
     * speaker,可能有些特殊运营商有这个需求。
     */
    @VisibleForTesting
    public void placeOutgoingCall(Call call, Uri handle, GatewayInfo gatewayInfo,
            boolean speakerphoneOn, int videoState) {
        if (call == null) {
            // don't do anything if the call no longer exists
            Log.i(this, "Canceling unknown call.");
            return;
        }
         ...
        if (call.getTargetPhoneAccount() != null || call.isEmergencyCall()) {
              ....
                // 通过call 对象去创建connection
                call.startCreateConnection(mPhoneAccountRegistrar);
              ....
        } 
    }

在CallsManager#placeOutgoingCall首先需要创建connection,通过一系列的调用,最终会调用到TelephonyConnectionService.java # onCreateOutgoingConnection 。然后调用phone#dial,最终调用到Ril的dial,这块的逻辑调用也比较简单,大概的调用逻辑如下:

ConnectionService.java # createConnection --> TelephonyConnectionService.java # onCreateOutgoingConnection 
--> TelephonyConnectionService.java # placeOutgoingConnection
--> GsmCdmaPhone.java # dial --> GsmCdmaPhone.java # dialInternal
--> GsmCdmaCallTracker.java # dial --> Ril.java # dial

大概的时序图如下:

Android call 流程以及其他case整理(2)--MO_第4张图片 图4:Send Dial request

至此,MO的UI & dial命令都发下了,Tele的MO流程结束。

 

 

 

 

 

你可能感兴趣的:(Call-学习)