转载请注明出处: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