拨号流程梳理

本文详细梳理了在Android系统中,从拨号盘点击拨号按钮到电话呼出的整个流程,涉及Dialer、Telecom和Telephony组件,包括DialpadFragment、PreCall、TelecomServiceImpl、CallManager等多个关键类的交互,以及IMS框架的使用。

在拨号盘Dialer中点击拨号按钮

packages\apps\Dialer\java\com\android\dialer\dialpadview\DialpadFragment.java
首先从点击事件看起:
点击拨号按钮后调用handleDialButtonPressed()

public void onClick(View view) {
   
   
  int resId = view.getId();
  if (resId == R.id.dialpad_floating_action_button) {
   
   //拨号按钮
    view.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
    handleDialButtonPressed();
  } else if (resId == R.id.deleteButton) {
   
   //回退按钮
    keyPressed(KeyEvent.KEYCODE_DEL);
  } else if (resId == R.id.digits) {
   
   //号码输入框
    if (!isDigitsEmpty()) {
   
   
      digits.setCursorVisible(true);
    }
  } else if (resId == R.id.dialpad_overflow) {
   
   //显示dialPad的悬浮按钮
    overflowPopupMenu.show();
  } else {
   
   
    LogUtil.w("DialpadFragment.onClick", "Unexpected event from: " + view);
  }
}

handleDialButtonPressed()方法中,对获取到的字符串进行校验,并创建intent。最终调用PreCall的start();—区别P版本上直接调用DialUntils

private void handleDialButtonPressed() {
   
   
  //对号码输入框获取到的字符串长度判断
  if (isDigitsEmpty()) {
   
    // No number entered.
    // No real call made, so treat it as a click
    PerformanceReport.recordClick(UiAction.Type.PRESS_CALL_BUTTON_WITHOUT_CALLING);
    //这个地方主要是区别CDMA做了些操作,非CDMA的phone,这个地方会填充上次的拨号号码,用户需要再次手动触发才能进行拨号
    handleDialButtonClickWithEmptyDigits();
  } else {
   
   
    final String number = digits.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(prohibitedPhoneNumberRegexp)
        && number.matches(prohibitedPhoneNumberRegexp)) {
   
   
      PerformanceReport.recordClick(UiAction.Type.PRESS_CALL_BUTTON_WITHOUT_CALLING);
      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 {
   
   
      //这里会将intent传递给PreCall,由PreCall调用DialerUtils的start区别于P上直接调用
      PreCall.start(getContext(), new CallIntentBuilder(number, CallInitiationType.Type.DIALPAD));
      hideAndClearDialpad();//隐藏拨号盘,并清理号码输入框的内容
    }
  }
}

packages\apps\Dialer\java\com\android\dialer\precall\PreCall.java
Precall中start()只是调用到DialerUtils中startActivityWithErrorToast();没有实质性的处理逻辑

static void start(Context context, CallIntentBuilder builder) {
   
   
  DialerUtils.startActivityWithErrorToast(context, getIntent(context, builder));
}

packages\apps\Dialer\java\com\android\dialer\util\DialerUtils.java
startActivityWithErrorToast()做了一系列的非法判断,最终将intent发出去到TeleCommon

public static void startActivityWithErrorToast(
    final Context context, final Intent intent, int msgId) {
   
   
  try {
   
   
    //对intent中的action进行判断,如果是call则进一步处理,不是的话直接startActivity()
    if ((Intent.ACTION_CALL.equals(intent.getAction()))) {
   
   
      // All dialer-initiated calls should pass the touch point to the InCallUI
      Point touchPoint = TouchPointManager.getInstance().getPoint();
      if (touchPoint.x != 0 || touchPoint.y != 0) {
   
   
        Bundle extras;
        // Make sure to not accidentally clobber any existing extras
        if (intent.hasExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS)) {
   
   
          extras = intent.getParcelableExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS);
        } else {
   
   
          extras = new Bundle();
        }
        extras.putParcelable(TouchPointManager.TOUCH_POINT, touchPoint);
        intent.putExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, extras);
        //注意这个extra,这个在后续创建incoming还是outgoing的连接使
      }

      //这个地方对当前的网络模式和通话状态进行判断,是否需要弹框提醒用户。
      if (shouldWarnForOutgoingWps(context, intent.getData().getSchemeSpecificPart())) {
   
   
        LogUtil.i(
            "DialUtils.startActivityWithErrorToast",
            "showing outgoing WPS dialog before placing call");
        AlertDialog.Builder builder = new AlertDialog.Builder(context);
        builder.setMessage(R.string.outgoing_wps_warning);
        //确定按钮点击事件,操作和正常逻辑一致
        builder.setPositiveButton(
            R.string.dialog_continue,
            new OnClickListener() {
   
   
              @Override
              public void onClick(DialogInterface dialog, int which) {
   
   
                placeCallOrMakeToast(context, intent);
              }
            });
        builder.setNegativeButton(android.R.string.cancel, null);
        builder.create().show();
      } else {
   
   
        //正常逻辑
        placeCallOrMakeToast(context, intent);
      }
    } else {
   
   
      context.startActivity(intent);
    }
  } catch (ActivityNotFoundException e) {
   
   
    Toast.makeText(context, msgId, Toast.LENGTH_SHORT).show();
  }
}

private static void placeCallOrMakeToast(Context context, Intent intent) {
   
   
  //通过调用placeCall的返回结果,来处理是否需要弹框提示没有权限
  final boolean hasCallPermission = TelecomUtil.placeCall(context, intent);
  if (!hasCallPermission) {
   
   
    // TODO: Make calling activity show request permission dialog and handle
    // callback results appropriately.
    Toast.makeText(context, "Cannot place call without Phone permission", Toast.LENGTH_SHORT)
        .show();
  }
}

DialerUtils调用TelecomUtils的placeCall()

public static boolean placeCall(Context context, Intent intent) {
   
   
  if (hasCallPhonePermission(context)) {
   
   
    getTelecomManager(context).placeCall(intent.getData(), intent.getExtras());
    return true;
  }
  return false;
}

通过serviceManager获取TelecomManager过程

  private static TelecomManager getTelecomManager(Context context) {
   
   
    //ServiceManager在service注册的时候调用addService()将service维护在列表中
    return (TelecomManager) context.getSystemService(Context.TELECOM_SERVICE);
  }

二、拨号流程到达TeleCom
通过binder获取到ITelecomService的具体实现,并继续向下调用placeCall()

    public void placeCall(Uri address, Bundle extras) {
   
   
        //binder间调用,获取回来的是TeleService的proxy,通过proxy调用service
        ITelecomService service = getTelecomService();
        if (service != null) {
   
   
            if (address == null) {
   
   
                Log.w(TAG, "Cannot place call to empty address.");
            }
            try {
   
   
                //再调用TeleComServiceImpl的placeCall()
                service.placeCall(address, extras == null ? new Bundle() : extras,
                        mContext.getOpPackageName());
            } catch (RemoteException e) {
   
   
                Log.e(TAG, "Error calling ITelecomService#placeCall", e);
            }
        }
    }

获取 ITelecomService.Stub具体的实现类

    private ITelecomService getTelecomService() {
   
   
        if (mTelecomServiceOverride != null) {
   
   
            return mTelecomServiceOverride;
        }
        return ITelecomService.Stub.asInterface(ServiceManager.getService(Context.TELECOM_SERVICE));
    }

ITelecomService 的实现类实际上就是TelecomServiceImpl.java,这个时候实际上从FWK已经到了应用层
packages\services\Telecomm\src\com\android\server\telecom\TelecomServiceImpl.java

        @Override
        public void placeCall(Uri handle, Bundle extras, String callingPackage) {
   
   
            try {
   
   
                Log.startSession("TSI.pC");
                enforceCallingPackage(callingPackage);

                PhoneAccountHandle phoneAccountHandle = null;
                if (extras != null) {
   
   
                    phoneAccountHandle = extras.getParcelable(
                            TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
                    if (extras.containsKey(TelecomManager.EXTRA_IS_HANDOVER)) {
   
   
                        // This extra is for Telecom use only so should never be passed in.
                        extras.remove(TelecomManager.EXTRA_IS_HANDOVER);
                    }
                }
                boolean isSelfManaged = phoneAccountHandle != null &&
                        isSelfManagedConnectionService(phoneAccountHandle);
                if (isSelfManaged) {
   
   
                    mContext.enforceCallingOrSelfPermission(Manifest.permission.MANAGE_OWN_CALLS,
                            "Self-managed ConnectionServices require MANAGE_OWN_CALLS permission.");

                    if (!callingPackage.equals(
                            phoneAccountHandle.getComponentName().getPackageName())
                            && !canCallPhone(callingPackage,
                            "CALL_PHONE permission required to place calls.")) {
   
   
                        // The caller is not allowed to place calls, so we want to ensure that it
                        // can only place calls through itself.
                        throw new SecurityException("Self-managed ConnectionServices can only "
                                + "place calls through their own ConnectionService.");
                    }
                } else if (!canCallPhone(callingPackage, "placeCall")) {
   
   
                    throw new SecurityException("Package " + callingPackage
                            + " is not allowed to place phone calls");
                }

                // Note: we can still get here for the default/system dialer, even if the Phone
                // permission is turned off. This is because the default/system dialer is always
                // allowed to attempt to place a call (regardless of permission state), in case
                // it turns out to be an emergency call. If the permission is denied and the
                // call is being made to a non-emergency number, the call will be denied later on
                // by {@link UserCallIntentProcessor}.

                final boolean hasCallAppOp = mAppOpsManager.noteOp(AppOpsManager.OP_CALL_PHONE,
                        Binder.getCallingUid(), callingPackage) == AppOpsManager.MODE_ALLOWED;

                final boolean hasCallPermission = mContext.checkCallingPermission(CALL_PHONE) ==
                        PackageManager.PERMISSION_GRANTED;
                // The Emergency Dialer has call privileged permission and uses this to place
                // emergency calls.  We ensure permission checks in
                // NewOutgoingCallIntentBroadcaster#process pass by sending this to
                // Telecom as an ACTION_CALL_PRIVILEGED intent (which makes sense since the
                // com.android.phone process has that permission).
                final boolean hasCallPrivilegedPermission = mContext.checkCallingPermission(
                        CALL_PRIVILEGED) == PackageManager.PERMISSION_GRANTED;

                synchronized (mLock) {
   
   
                    final UserHandle userHandle = Binder.getCallingUserHandle();
                    long token = Binder.clearCallingIdentity();
                    try {
   
   
                        final Intent intent = new Intent(hasCallPrivilegedPermission ?
                                Intent.ACTION_CALL_PRIVILEGED : Intent.ACTION_CALL, handle);
                        if (extras != null) {
   
   
                            extras.setDefusable(true);
                            intent.putExtras(extras);
                        }
                        //正常的拨号流程
                        mUserCallIntentProcessorFactory.create(mContext, userHandle)
                                .processIntent(
                                        intent, callingPackage, isSelfManaged ||
                                                (hasCallAppOp && hasCallPermission),
                                        true /* isLocalInvocation */);
                    } finally {
   
   
                        Binder.restoreCallingIdentity(token);
                    }
                }
            } finally {
   
   
                Log.endSession();
            }
        }

packages\services\Telecomm\src\com\android\server\telecom\components\UserCallIntentProcessor.java

    public void processIntent(Intent intent, String callingPackageName,
            boolean canCallNonEmergency, boolean isLocalInvocation) {
   
   
        // Ensure call intents are not processed on devices that are not capable of calling.
        if (!isVoiceCapable()) {
   
   
            return;
        }

        String action = intent.getAction();

        if (Intent.ACTION_CALL.equals(action) ||
                Intent.ACTION_CALL_PRIVILEGED.equals(action) ||
                Intent.ACTION_CALL_EMERGENCY.equals(action)) {
   
   
            processOutgoingCallIntent(intent, callingPackageName, canCallNonEmergency,
                    isLocalInvocation);
        }
    }
    private void processOutgoingCallIntent(Intent intent, String callingPackageName,
            boolean canCallNonEmergency, boolean isLocalInvocation) {
   
   
        Uri handle = intent.getData();
        String scheme = handle.getScheme();
        String uriString = handle.getSchemeSpecificPart();

        // Ensure sip URIs dialed using TEL scheme get converted to SIP scheme.
        if (PhoneAccount.SCHEME_TEL.equals(scheme) && PhoneNumberUtils.isUriNumber(uriString)) {
   
   
            handle = Uri.fromParts(PhoneAccount.SCHEME_SIP, uriString, null);
        }

        // Check DISALLOW_OUTGOING_CALLS restriction. Note: We are skipping this check in a managed
        // profile user because this check can always be bypassed by copying and pasting the phone
        // number into the personal dialer.
        if (!UserUtil.isManagedProfile(mContext, mUserHandle)) {
   
   
            // Only emergency calls are allowed for users with the DISALLOW_OUTGOING_CALLS
            // restriction.
            if (!TelephonyUtil.shouldProcessAsEmergency(mContext, handle)) {
   
   
                final UserManager userManager = (UserManager) mContext.getSystemService(
                        Context.USER_SERVICE);
                if (userManager.hasBaseUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS,
                        mUserHandle)) {
   
   
                    showErrorDialogForRestrictedOutgoingCall(mContext,
                            R.string.outgoing_call_not_allowed_user_restriction);
                    Log.w(this, "Rejecting non-emergency phone call due to DISALLOW_OUTGOING_CALLS "
                            + "restriction");
                    return;
                } else if (userManager.hasUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS,
                        mUserHandle)) {
   
   
                    final DevicePolicyManager dpm =
                            mContext.getSystemService(DevicePolicyManager.class);
                    if (dpm == null) {
   
   
                        return;
                    }
                    final Intent adminSupportIntent = dpm.createAdminSupportIntent(
                            UserManager.DISALLOW_OUTGOING_CALLS);
                    if (adminSupportIntent != null) {
   
   
                        mContext.startActivity(adminSupportIntent);
                    }
                    return;
                }
            }
        }

        if (!canCallNonEmergency && !TelephonyUtil.shouldProcessAsEmergency(mContext, handle)) {
   
   
            showErrorDialogForRestrictedOutgoingCall(mContext,
                    R.string.outgoing_call_not_allowed_no_permission);
            Log.w(this, "Rejecting non-emergency phone call because "
                    + android.Manifest.permission.CALL_PHONE + " permission is not granted.");
            return;
        }

        int videoState = intent.getIntExtra(
                TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
                VideoProfile.STATE_AUDIO_ONLY);
        Log.d(this, "processOutgoingCallIntent videoState = " + videoState);

        // Save the user handle of current user before forwarding the intent to primary user.
        intent.putExtra(CallIntentProcessor.KEY_INITIATING_USER, mUserHandle);

        sendIntentToDestination(intent, isLocalInvocation, callingPackageName);
    }

   private boolean sendIntentToDestination(Intent intent, boolean isLocalInvocation,
            String callingPackage) {
   
   
        intent.putExtra(CallIntentProcessor.KEY_IS_INCOMING_CALL, false);
        intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
        if (isLocalInvocation) {
   
   
            // We are invoking this from TelecomServiceImpl, so TelecomSystem is available.  Don't
            // bother trampolining the intent, just sent it directly to the call intent processor.
            // TODO: We should not be using an intent here; this whole flows needs cleanup.
            Log.i(this, "sendIntentToDestination: send intent to Telecom directly.");
            synchronized (TelecomSystem.getInstance().getLock()) {
   
   
                TelecomSystem.getInstance().getCallIntentProcessor().processIntent(intent,
                        callingPackage);
            }
        } else {
   
   
            // We're calling from the UserCallActivity, so the TelecomSystem is not in the same
            // process; we need to trampoline to TelecomSystem in the system server process.
            Log.i(this, "sendIntentToDestination: trampoline to Telecom.");
            TelecomManager tm = (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE);
            tm.handleCallIntent(intent, callingPackage);
        }
        return true;
    }

这里又回到了FWK中
packages\services\Telecomm\src\com\android\server\telecom\CallIntentProcessor.java

    public void processIntent(Intent intent, String callingPackage) {
   
   
        //正常的拨号流程并没有对KEY_IS_UNKNOWN_CALL进行赋值,所以默认是false
        final boolean isUnknownCall = intent.getBooleanExtra(KEY_IS_UNKNOWN_CALL, false);
        Log.i(this, "onReceive - isUnknownCall: %s", isUnknownCall);

        Trace.beginSection("processNewCallCallIntent");
        if (isUnknownCall) {
   
   
            processUnknownCallIntent(mCallsManager, intent);
        } else {
   
   
            processOutgoingCallIntent(mContext, mCallsManager, intent, callingPackage,
                    mDefaultDialerCache);
        }
        Trace.endSection();
    }
    static void process
<think>嗯,用户想了解Android手机接电话的流程,需要做详细的分析。首先,我需要回忆Android电话系统的架构。记得Android的通话流程涉及多个层次,从RIL层到应用层。用户提到的引用里有些线索,比如引用[1]讲了去电流程,引用[2]提到connect函数,引用[3]涉及拒接电话,这些可能有助于对比理解接电话的流程。 接下来,应该梳理接电话的流程步骤。从RIL开始,当基站有来电时,RIL会收到通知,然后通过Telephony框架传递到应用层。Telecom Service作为核心,负责管理通话状态和路由。这里需要明确Telecom和TeleService的作用,以及它们如何与InCallUI交互。 然后,用户界面部分,比如InCallUI的显示和用户操作的处理。用户接听或拒接的操作如何反馈到底层。特别是引用[3]中的rejectCall方法,可能和接听流程有相似路径,只是调用的方法不同。需要确认TelecomAdapter在接听流程中的角色。 另外,状态同步和广播发送也是关键点。比如NEW_OUTGOING_CALL广播在去电中提及,接电话时是否有类似广播?比如ACTION_ANSWER之类的。还要考虑不同Android版本的差异,比如引用[2]基于Android11,而用户的问题可能需要通用流程,但版本差异可能影响模块名称或方法调用。 可能遗漏的细节包括RILJ如何将事件传递给Telephony Registry,以及状态机如GsmCdmaCallTracker的处理流程。需要确保每个步骤的衔接正确,比如从RIL事件到CallManager更新状态,再到应用层的通知。 最后,生成相关问题时要覆盖用户可能的深入疑问,比如UI定制、性能优化、异常处理等。确保问题与接电话流程相关,比如来电显示原理、延迟接听的处理机制,或者多SIM卡场景下的流程差异。</think>以下是Android手机接电话(来电响应)的详细流程分析: --- ### **接电话核心流程** 1. **RIL层接收来电信号** - 基带芯片收到基站来电通知,通过硬件抽象层(HAL)传递至`RILD`守护进程。 - `RILD`将二进制信令转换为Java框架可识别的`RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED`事件[^2]。 2. **Telephony框架处理** - `RIL.java`将事件传递至`GsmCdmaCallTracker`,更新`Call`状态机为`INCOMING`。 - `CallManager`通知`TelephonyRegistry`广播`ACTION_PHONE_STATE_CHANGED`系统意图(包含`EXTRA_STATE_RINGING`)[^1]。 3. **Telecom服务路由** - `TelecomService`作为系统级通话控制器: - 通过`CallsManager`创建`Call`对象并分配唯一ID。 - 调用`InCallController`绑定`InCallService`(如系统默认UI或第三方拨号应用)。 - 触发`RingtonePlayer`播放铃声(遵循勿扰模式规则)[^3]。 4. **用户界面响应** - `InCallUI`应用收到绑定请求后: - 显示来电界面(包含联系人信息、接听/拒接按钮)。 - 用户点击**接听**按钮时,调用`TelecomAdapter.answerCall(callId)`。 5. **接听指令下发** ```java // InCallUI触发接听 TelecomAdapter.getInstance().answerCall(callId, videoState); ``` - `TelecomService`通过`CallsManager`找到对应`Call`对象。 - 调用`ConnectionService`(如`TelephonyConnectionService`)执行`onAnswer()`方法。 6. **底层连接建立** - `TelephonyConnectionService`通过`GsmCdmaConnection`向`GsmCdmaCallTracker`发送`ACCEPT`指令。 - `RIL.java`将`RIL_REQUEST_ANSWER`转换为AT命令发送至调制解调器。 - 基带芯片响应基站,建立语音通道。 --- ### **关键组件交互图** ```mermaid graph TD A[基站信号] --> B(RIL守护进程) B --> C{RIL.java} C --> D[GsmCdmaCallTracker] D --> E[TelephonyRegistry广播] E --> F[TelecomService] F --> G[CallsManager] G --> H[绑定InCallService] H --> I[显示来电界面] I --> J{用户操作} J -->|接听| K[TelecomAdapter.answerCall] K --> L[TelephonyConnectionService] L --> M[发送AT+ATA指令] M --> N[基带建立通话通道] ``` --- ### **异常处理机制** - **超时未接听**:`CallsManager`在30秒后触发`onDisconnect()`,发送`REJECTED`状态至网络侧[^3]。 - **并发冲突**:若通话中另有来电,`CallManager`通过`HOLD`/`SWAP`命令管理多路通话。 - **权限拦截**:`TelecomService`在`onAnswer()`前校验`ANSWER_PHONE_CALLS`权限。 --- ### **跨版本差异** - **Android 9+**:强制使用`TelecomManager`作为唯一通话入口,第三方拨号需实现`InCallService`[^1]。 - **Android 11**:`ConnectionService`新增`onShowIncomingCallUi()`,支持定制化来电界面[^2]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值