Telephony上层的代码主要由六部分组成:
frameworks/opt/telephony/ telephony-common.jar
frameworks/base/telecomm/ framework-telecomm framework.jar
frameworks/base/telephony/ framework-telephony framework.jar
packages/services/Telephony/ TeleService.apk
packages/services/Telecomm/ Telecom.apk
packages/apps/Dialer/ Dialer.apk
本文中代码来源于MTK Android 14源码。
MO 显示 InCall UI 流程
显示 InCall UI 流程主要分为 3 部分:
1.通过CallIntentProcessor处理Call Intent ;
2.InCallController 绑定 InCallService;
3.InCallService 通过 InCallPresenter 拉起UI;
1.拨出电话从 DialpadFragment 的拨号按钮按下开始,直到调用到 TelecomManager 的 placeCall()方法进入到真正的流程入口
packages/apps/Dialer/java/com/android/dialer/dialpadview/DialpadFragment.java
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) {
...
}
}
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);
handleDialButtonClickWithEmptyDigits();
} else {
final String number = digits.getText().toString();
...
} else{
PreCall.start(getContext(), new CallIntentBuilder(number, CallInitiationType.Type.DIALPAD));
hideAndClearDialpad();
}
}
packages/apps/Dialer/java/com/android/dialer/precall/PreCall.java
static void start(Context context, CallIntentBuilder builder) {
DialerUtils.startActivityWithErrorToast(context, getIntent(context, builder));
}
packages/apps/Dialer/java/com/android/dialer/util/DialerUtils.java
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
Point touchPoint = TouchPointManager.getInstance().getPoint();
...
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();
}
}
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();
}
}
2.TelecomManager 的服务实现 TelecomServiceImpl 将检查发起拨号的应用是否有拨号的权限,如果有则开始处理 拨号的 Intent
packages/apps/Dialer/java/com/android/dialer/telecom/TelecomUtil.java
public static boolean placeCall(Context context, Intent intent) {
if (hasCallPhonePermission(context)) {
//第一次跨进程调用
getTelecomManager(context).placeCall(intent.getData(), intent.getExtras());
return true;
}
return false;
}
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(), mContext.getAttributionTag());
} catch (RemoteException e) {
Log.e(TAG, "Error calling ITelecomService#placeCall", e);
}
}
}
3.拨号 Intent 由 UserCallIntentProcessor 处理,最终调用到 CallIntentProcessor 进行进一步处理
packages/services/Telecomm/src/com/android/server/telecom/TelecomServiceImpl.java
public void placeCall(Uri handle, Bundle extras, String callingPackage,
String callingFeatureId) {
try {
Log.startSession("TSI.pC");
enforceCallingPackage(callingPackage);
...
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();
...
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;
}
4.CallIntentProcessor 有两个流程分支,其中拉起 InCall UI 是直接进入 CallsManager 中调用 addCall 方法,并唤醒监听该消息的 listener
packages/services/Telecomm/src/com/android/server/telecom/CallIntentProcessor.java
public void processIntent(Intent intent, String callingPackage) {
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 processOutgoingCallIntent(
Context context,
CallsManager callsManager,
Intent intent,
String callingPackage,
DefaultDialerCache defaultDialerCache) {
Uri handle = intent.getData();
String scheme = handle.getScheme();
String uriString = handle.getSchemeSpecificPart();
...
boolean isPrivilegedDialer = defaultDialerCache.isDefaultOrSystemDialer(callingPackage,
initiatingUser.getIdentifier());
NewOutgoingCallIntentBroadcaster broadcaster = new NewOutgoingCallIntentBroadcaster(
context, callsManager, intent, callsManager.getPhoneNumberUtilsAdapter(),
isPrivilegedDialer, defaultDialerCache);
// If the broadcaster comes back with an immediate error, disconnect and show a dialog.
NewOutgoingCallIntentBroadcaster.CallDisposition disposition = broadcaster.evaluateCall();
if (disposition.disconnectCause != DisconnectCause.NOT_DISCONNECTED) {
showErrorDialog(context, disposition.disconnectCause);
return;
}
// Send to CallsManager to ensure the InCallUI gets kicked off before the broadcast returns
CompletableFuture<Call> callFuture = callsManager
.startOutgoingCall(handle, phoneAccountHandle, clientExtras, initiatingUser,
intent, callingPackage);
final Session logSubsession = Log.createSubsession();
callFuture.thenAccept((call) -> {
if (call != null) {
Log.continueSession(logSubsession, "CIP.sNOCI");
try {
broadcaster.processCall(call, disposition);
} finally {
Log.endSession();
}
}
});
}
packages/services/Telecomm/src/com/android/server/telecom/CallsManager.java
private CompletableFuture<Call> startOutgoingCall(List<Uri> participants,
PhoneAccountHandle requestedAccountHandle,
Bundle extras, UserHandle initiatingUser, Intent originalIntent,
String callingPackage, boolean isConference) {
boolean isReusedCall;
Uri handle = isConference ? Uri.parse("tel:conf-factory") : participants.get(0);
Call call = reuseOutgoingCall(handle);
PhoneAccount account =
mPhoneAccountRegistrar.getPhoneAccount(requestedAccountHandle, initiatingUser);
Bundle phoneAccountExtra = account != null ? account.getExtras() : null;
boolean isSelfManaged = account != null && account.isSelfManaged();
...
mLatestPostSelectionProcessingFuture = dialerSelectPhoneAccountFuture
.thenComposeAsync(args -> {
if (args == null) {
return CompletableFuture.completedFuture(null);
}
Log.i(CallsManager.this, "post acct selection stage");
Call callToUse = args.first;
PhoneAccountHandle phoneAccountHandle = args.second;
PhoneAccount accountToUse = mPhoneAccountRegistrar
.getPhoneAccount(phoneAccountHandle, initiatingUser);
callToUse.setTargetPhoneAccount(phoneAccountHandle);
if (accountToUse != null && accountToUse.getExtras() != null) {
if (accountToUse.getExtras()
.getBoolean(PhoneAccount.EXTRA_ALWAYS_USE_VOIP_AUDIO_MODE)) {
Log.d(this, "startOutgoingCall: defaulting to voip mode for call %s",
callToUse.getId());
callToUse.setIsVoipAudioMode(true);
}
}
callToUse.setState(
CallState.CONNECTING,
phoneAccountHandle == null ? "no-handle"
: phoneAccountHandle.toString());
boolean isVoicemail = isVoicemail(callToUse.getHandle(), accountToUse);
boolean isRttSettingOn = isRttSettingOn(phoneAccountHandle);
if (!isVoicemail && (isRttSettingOn || (extras != null
&& extras.getBoolean(TelecomManager.EXTRA_START_CALL_WITH_RTT,
false)))) {
Log.d(this, "Outgoing call requesting RTT, rtt setting is %b",
isRttSettingOn);
if (callToUse.isEmergencyCall() || (accountToUse != null
&& accountToUse.hasCapabilities(PhoneAccount.CAPABILITY_RTT))) {
// If the call requested RTT and it's an emergency call, ignore the
// capability and hope that the modem will deal with it somehow.
callToUse.createRttStreams();
}
// Even if the phone account doesn't support RTT yet,
// the connection manager might change that. Set this to check it later.
callToUse.setRequestedToStartWithRtt();
}
setIntentExtrasAndStartTime(callToUse, extras);
setCallSourceToAnalytics(callToUse, originalIntent);
if (isPotentialMMICode(handle) && !isSelfManaged) {
// Do not add the call if it is a potential MMI code.
callToUse.addListener(this);
} else if (!mCalls.contains(callToUse)) {
// We check if mCalls already contains the call because we could
// potentially be reusing
// a call which was previously added (See {@link #reuseOutgoingCall}).
addCall(callToUse);
}
return CompletableFuture.completedFuture(callToUse);
}, new LoggedHandlerExecutor(outgoingCallHandler, "CM.pASP", mLock));
return mLatestPostSelectionProcessingFuture;
}
public void addCall(Call call) {
Trace.beginSection("addCall");
Log.i(this, "addCall(%s)", call);
call.addListener(this);
mCalls.add(call);
// Specifies the time telecom finished routing the call. This is used by the dialer for
// analytics.
Bundle extras = call.getIntentExtras();
extras.putLong(TelecomManager.EXTRA_CALL_TELECOM_ROUTING_END_TIME_MILLIS,
SystemClock.elapsedRealtime());
updateCanAddCall();
updateHasActiveRttCall();
updateExternalCallCanPullSupport();
// 快速设置call对象为前端通话,即第一通电话
for (CallsManagerListener listener : mListeners) {
if (LogUtils.SYSTRACE_DEBUG) {
Trace.beginSection(listener.getClass().toString() + " addCall");
}
//通知InCallController调用onCallAdded
listener.onCallAdded(call);
if (LogUtils.SYSTRACE_DEBUG) {
Trace.endSection();
}
}
Trace.endSection();
}
5.InCallController 监听到 onCallAdded()消息,framework_telecom属于framework,需要绑定InCallService才能元撑调用,开始绑定 InCallService,最终流程进入到InCallServiceImpl 子类实现中
packages/services/Telecomm/src/com/android/server/telecom/InCallController.java
public void onCallAdded(Call call) {
if (!isBoundAndConnectedToServices()) { //判断是否已绑定
Log.i(this, "onCallAdde