Android 通话流程相关分析

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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值