Android 8.0 拨号流程分析

本文详细分析了Android 8.0中拨打电话的完整流程,从Dialer应用点击拨号按钮开始,经过Telecom Manager、Telecom Service、PhoneAccount和Telephony Framework,最终在RIL层面将请求传递给Modem发起呼叫。关键涉及的类包括DialpadFragment、CallIntentBuilder、DialerUtils、TelecomUtil、UserCallIntentProcessor、PrimaryCallReceiver、CallIntentProcessor、CallsManager等。
摘要由CSDN通过智能技术生成

转载请注明出处:https://blog.csdn.net/turtlejj/article/details/81240892,谢谢~

 

       由于工作中需要熟悉Android拨打电话的完整流程,特将学习的过程记录下来,以便将来进行回顾,同时也欢迎大家对文章中不正确的地方加以指正。

       在代码中,我在关键地方都添加了自己的对于代码理解的中文注释,已方便更好的理解代码的含义,以下就开始我们对拨号流程的梳理。

 

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

/packages/apps/Dialer/java/com/android/dialer/app/dialpad/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()) {
            mDigits.setCursorVisible(true);
        }
    } else if (resId == R.id.dialpad_overflow) {
        mOverflowPopupMenu.show();
    } else {
        LogUtil.w("DialpadFragment.onClick", "Unexpected event from: " + view);
        return;
    }
}

在handleDialButtonPressed()方法中,创建intent,并调用DialerUtils的startActivityWithErrorToast()方法

private void handleDialButtonPressed() {
  if (isDigitsEmpty()) { // 如果没有输入号码
    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);
    }
  }
}

/packages/apps/Dialer/java/com/android/dialer/callintent/CallintentBuilder.java

创建intent的具体流程如下

public CallIntentBuilder(@NonNull String number, CallInitiationType.Type callInitiationType) {  // 调用CallUtil的getCallUri()方法,将号码封装成Uri
    this(CallUtil.getCallUri(Assert.isNotNull(number)), callInitiationType);
}

->

public static Uri getCallUri(String number) {
    if (PhoneNumberHelper.isUriNumber(number)) {  // 网络电话流程
      return Uri.fromParts(PhoneAccount.SCHEME_SIP, number, null);
    }
    // 普通电话流程
    return Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null);
}

->

public CallIntentBuilder(@NonNull Uri uri, CallInitiationType.Type callInitiationType) {
    // 调用CcreateCallSpecificAppData()方法,对callInitiationType进行转换
    this(uri, createCallSpecificAppData(callInitiationType));
}

->

private static @NonNull CallSpecificAppData createCallSpecificAppData(
      CallInitiationType.Type callInitiationType) {
    CallSpecificAppData callSpecificAppData = CallSpecificAppData.newBuilder().setCallInitiationType(callInitiationType).build();
    return callSpecificAppData;
}

->

public Intent build() {
    // 设置intent的action为ACTION_CALL
    Intent intent = new Intent(Intent.ACTION_CALL, uri);
    // 普通电话为VideoProfile.STATE_AUDIO_ONLY
    intent.putExtra(
        TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
        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);

    // 由于没有设置PhoneAccountHandle,因此为null
    if (phoneAccountHandle != null) {
      intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle);
    }

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

    return intent;
}

/packages/apps/Dialer/java/com/android/dialer/util/DialerUtils.java

intent创建完成后,将其传入DialerUtils的startActivityWithErrorToast()方法中,并调用placeCallOrMakeToast()方法

public static void startActivityWithErrorToast(Context context, Intent intent) {
    startActivityWithErrorToast(context, intent, R.string.activity_not_available);
}


public static void startActivityWithErrorToast(
      final Context context, final Intent intent, int msgId) {
    try {
      // action为ACTION_CALL,进入
      if ((Intent.ACTION_CALL.equals(intent.getAction()))) {
        ......
        // 不会弹出警告,进入else分支
        if (shouldWarnForOutgoingWps(context, intent.getData().getSchemeSpecificPart())) {
          ......
        } else {
          placeCallOrMakeToast(context, intent);
        }
      } else {
        context.startActivity(intent);
      }
    } catch (ActivityNotFoundException e) {
      Toast.makeText(context, msgId, Toast.LENGTH_SHORT).show();
    }
}

调用TelecomUtil的placeCall()方法,判断是否拥有呼出电话的权限,如果有,则继续流程;否则,将弹出Toast提示无权限

private static void placeCallOrMakeToast(Context context, Intent intent) {
    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();
    }
}

/packages/apps/Dialer/java/com/android/dialer/telecom/TelecomUtil.java

调用hasCallPhonePermission()方法判断是否具有Manifest.permission.CALL_PHONE权限,如果有,则调用TelecomManager中的placeCall()方法继续处理通话流程

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

二、Telecom处理通话流程

/frameworks/base/telecomm/java/android/telecom/TelecomManger.java

在placeCall()方法中,调用了ITelecomService接口中的placeCall()方法,而ITelecomService接口中的placeCall()方法在TelecomServiceImpl.java中被实现(此处用到了一些AIDL的知识,不了解的同学可以自行学习一下)

public void placeCall(Uri address, Bundle extras) {
    ITelecomService service = getTelecomService();
    if (service != null) {
        if (address == null) {
            Log.w(TAG, "Cannot place call to empty address.");
        }
        try {
            service.placeCall(address, extras == null ? new Bundle() : extras,
                    mContext.getOpPackageName());
        } catch (RemoteException e) {
            Log.e(TAG, "Error calling ITelecomService#placeCall", e);
        }
    }
}

/packages/servies/Telecomm/src/com/android/server/telecom/TelecomServiceImpl.java

调用UserCallIntentProcessor的processIntent()方法

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

        PhoneAccountHandle phoneAccountHandle = null;
        if (extras != null) {
            // 由于没有设置PhoneAccountHandle,因此PhoneAccountHandle变量为null
            phoneAccountHandle = extras.getParcelable(
                    TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
        }
        // 由于PhoneAccountHandle变量为null,因此isSelfManaged变量为false
        boolean isSelfManaged = phoneAccountHandle != null &&
                isSelfManagedConnectionService(phoneAccountHandle);
        if (isSelfManaged) {
            ......
        } 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;

        synchronized (mLock) {
            final UserHandle userHandle = Binder.getCallingUserHandle();
            long token = Binder.clearCallingIdentity();
            try {
                // 新创建一个intent对象,并设置action为Intent.ACTION_CALL
                final Intent intent = new Intent(Intent.ACTION_CALL, handle);
                if (extras != null) {
                    extras.setDefusable(true);
                    intent.putExtras(extras);
                }
                // mUserCallIntentProcessorFactory.create()方法返回的是一个UserCallIntentProcessor对象
                mUserCallIntentProcessorFactory.create(mContext, userHandle)
                        .processIntent(
                                intent, callingPackage, isSelfManaged ||
                                        (hasCallAppOp && hasCallPermission));
            } finally {
                Binder.restoreCallingIdentity(token);
            }
        }
    } finally {
        Log.endSession();
    }
}

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

调用processOutgoingCallIntent()方法

public void processIntent(Intent intent, String callingPackageName,
        boolean canCallNonEmergency) {
    // 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);
    }
}

调用sendBroadcastToReceiver()方法

private void processOutgoingCallIntent(Intent intent, String callingPackageName,
        boolean canCallNonEmergency) {
    Uri handle = intent.getData();                        // tel:13012123434
    String scheme = handle.getScheme();                   // tel
    String uriString = handle.getSchemeSpecificPart();    // 13012123434

    if (!PhoneAccount.SCHEME_VOICEMAIL.equals(scheme)) {
        handle = Uri.fromParts(PhoneNumberUtils.isUriNumber(uriString) ?
                PhoneAccount.SCHEME_SIP : PhoneAccount.SCHEME_TEL, uriString, null);
    }

    // Check DISALLOW_OUTGOING_CALLS restriction. Note: We are skipping this check 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 (!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);

    intent.putExtra(CallIntentProcessor.KEY_IS_PRIVILEGED_DIALER,
            isDefaultOrSystemDialer(callingPackageName));

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

    sendBroadcastToReceiver(intent);
}

设置广播接收者为PrimaryCallReveiver.class,并发送广播

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");
    mCo
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值