本文是作为本人学习总结。
首先挂一下流程图。
通话相关界面的显示和控制从/packages/apps/Dialer目录下。
一 /packages/apps/Dialer 层
dial 从DialpadFragment 开始,DialpadFragment 显示一个12键拨号键盘。
这里说明一下, DialpadFragment 是由DialtactsActivity 控制。
DialtactsActivity 控制了DialpadFragment (拨号盘界面),RegularSearchFragment(联系人搜索界面),SmartDialSearchFragment(拨号搜索界面),ListsFragment(最近通话记录,联系人列表显示)。
在DialpadFragment 的handleDialButtonPressed 方法中, 检查号码是否是被禁止的号码, 然后进一步处理。
public void onClick(View view) {
int resId = view.getId();
if (resId == R.id.dialpad_floating_action_button) {
view.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
handleDialButtonPressed();
// 点击delete 键
} 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;
}
}
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); //隐藏拨号键盘
}
}
}
看一下 call intent 的结构。
uri 是由String number 包装得到。
public Intent build() {
Intent intent = new Intent(Intent.ACTION_CALL, uri);
// 是否视频通话
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);
if (phoneAccountHandle != null) {
intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle);
}
if (!TextUtils.isEmpty(callSubject)) {
intent.putExtra(TelecomManager.EXTRA_CALL_SUBJECT, callSubject);
}
return intent;
}
DialerUtils.java
startActivityWithErrorToast 方法主要是将键盘的触点位置传入intent.
public static void startActivityWithErrorToast(
final Context context, final Intent intent, int msgId) {
try {
if ((Intent.ACTION_CALL.equals(intent.getAction()))) {
// All dialer-initiated calls should pass the touch point to the InCallUI
// 加入用户在键盘上的触点,后续启动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);
}
// wps 网络是美国的高优先级的网络,使得紧急通话不会被无线通话网络阻塞。
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) {
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();
}
}
public static boolean placeCall(Context context, Intent intent) {
if (hasCallPhonePermission(context)) {
getTelecomManager(context).placeCall(intent.getData(), intent.getExtras());
return true;
}
return false;
}
二 /packages/services/Telecomm 层
/frameworks/base/telecomm/java/android/telecom/TelecomManager.java
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);
}
}
}
ITelecomService 在TelecomServiceImpl.java 中实现
/packages/services/Telecomm/src/com/android/server/telecom/TelecomServiceImpl.java
public class TelecomServiceImpl {
private static final int DEFAULT_VIDEO_STATE = -1;
private final ITelecomService.Stub mBinderImpl = new ITelecomService.Stub() {
......
/**
* @see android.telecom.TelecomManager#placeCall
*/
@Override
public void placeCall(Uri handle, Bundle extras, String callingPackage) {
try {
//权限检查和包检查
......
synchronized (mLock) {
final UserHandle userHandle = Binder.getCallingUserHandle();
long token = Binder.clearCallingIdentity();
try {
final Intent intent = new Intent(Intent.ACTION_CALL, handle);
if (extras != null) {
extras.setDefusable(true);
intent.putExtras(extras);
}
mUserCallIntentProcessorFactory.create(mContext, userHandle)
.processIntent(
intent, callingPackage, isSelfManaged ||
(hasCallAppOp && hasCallPermission));
} finally {
Binder.restoreCallingIdentity(token);
}
}
} finally {
Log.endSession();
}
}
这里包含了/packages/services/Telecomm 暴露给外层的接口,比如acceptRingingCall,addNewIncomingCall,silenceRinger等方法。
/packages/services/Telecomm/src/com/android/server/telecom/components/UserCallIntentProcessor.java
processOutgoingCallIntent 将Intent 保存的data,Scheme 等信息重新取出并重新包装发送给PrimaryCallReceiver。
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_EMERGEN