Android N MO流程 并与Android M MO比较

--2017.12.08--csdn不知咋搞的,文中有些内容看不见了(尤其是我加了说明的部分),得重新“上色”,增加维护成本。。。

写在前面的话:

现在时间是2016.12.16,下面所有涉及到的代码都是基于最新CM 14.1。

代码比较部分比较的是今年5月分的CM 13.0。

开始写这篇博客的时候我还没有正式的跑过一次CM 14.1的IMS MO流程,所以后面可能会不定时更新/纠正下面的内容。

正式开始之前也许你会想看上两个版本的比较

Android M MO流程 并与Android L MO对比

----我是分割线---

android N IMS MO整体流程:


以及 比较的图片

相比较M和N的差异,这个算小的。
前半部分
 

变化:1.startCall()去掉了(可忽略);2.调用TelecomManager.placeCall()之前增加了几步。

packages/apps/Dialer/src/com/android/dialer/dialpad/DialpadFragment.java

1. handleDialButtonPressed()

这里没啥说的哈,就是在拨号盘拨号,与CM 13.0 稍有不同的是那个在cm 13.0上拆分出来的startCall()又合并进去了。真是任性啊。

读者也可以打开我之前的博客比较着看(分别占左右半屏方便比较)。

    private void handleDialButtonPressed() {
        if (isDigitsEmpty() && (mRecipients == null || !mRecipients.isShown())) {
            // No number entered.
            handleDialButtonClickWithEmptyDigits();
        } else {
            boolean isDigitsShown = mDigits.isShown();
            final String number = isDigitsShown ? mDigits.getText().toString() :
                    mRecipients.getText().toString().trim();
            if (isDigitsShown && isDigitsEmpty()) {
                handleDialButtonClickWithEmptyDigits();
            } else if (mAddParticipant && isPhoneInUse() && isDigitsEmpty()
                    && mRecipients.isShown() && isRecipientEmpty()) {
                // mRecipients must be empty
                // TODO add support for conference URI in last number dialed
                // use ErrorDialogFragment instead? also see
                // android.app.AlertDialog
                android.widget.Toast.makeText(getActivity(),
                        "Error: Cannot dial.  Please provide conference recipients.",
                        android.widget.Toast.LENGTH_SHORT).show();
            } else {
                // "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)) {
                    Log.i(TAG, "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 = CallUtil.getCallIntent(number);//到这里我又想吐槽了,还是算了。。。
                    if (!isDigitsShown) {
                        // must be dial conference add extra
                        intent.putExtra(EXTRA_DIAL_CONFERENCE_URI, true);
                    }
                    intent.putExtra(ADD_PARTICIPANT_KEY, mAddParticipant && isPhoneInUse());
                    DialerUtils.startActivityWithErrorToast(getActivity(), intent);
                    hideAndClearDialpad(false);
                }
            }
        }
    }


3. startActivityWithErrorToast

    /**
     * Attempts to start an activity and displays a toast with a provided error message if the
     * activity is not found, instead of throwing an exception.
     *
     * @param context to start the activity with.
     * @param intent to start the activity with.
     * @param msgId Resource ID of the string to display in an error message if the activity is
     *              not found.
     */
    public static void startActivityWithErrorToast(Context context, Intent intent, int msgId) {
        try {
            if ((IntentUtil.CALL_ACTION.equals(intent.getAction())
                            && context instanceof Activity)) {
                // 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);
                }

                final boolean hasCallPermission = TelecomUtil.placeCall((Activity) context, intent);//拨号,返回检查权限的结果
                if (!hasCallPermission) {//没权限的话弹个toast
                    // 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);
                }
            } else {
                context.startActivity(intent);
            }
        } catch (ActivityNotFoundException e) {
            Toast.makeText(context, msgId, Toast.LENGTH_SHORT).show();
        }
    }


packages/apps/Dialer/src/com/android/dialer/util/TelecomUtil.java

4. TelecomUtil.placeCall()

    /**
     * Tries to place a call using the {@link TelecomManager}.
     *
     * @param activity a valid activity.
     * @param intent the call intent.
     *
     * @return {@code true} if we successfully attempted to place the call, {@code false} if it
     *         failed due to a permission check.
     */
    public static boolean placeCall(Activity activity, Intent intent) {
        if (hasCallPhonePermission(activity)) {//检查权限
            TelecomManagerCompat.placeCall(activity, getTelecomManager(activity), intent);
            return true;
        }
        return false;
    }


packages/apps/ContactsCommon/src/com/android/contacts/common/compat/telecom/TelecomManagerCompat.java

对的这里跑到ContactsCommon里面来了,前面我想吐槽的CallUtil.getCallIntent(number),也是ContactsCommon里面的。。当然我想吐槽的不止这些

说好的move to Dialer呢?

 

5. TelecomManagerCompat.placeCall()

这里还不是api,下一步才是

    /**
     * Places a new outgoing call to the provided address using the system telecom service with
     * the specified intent.
     *
     * @param activity {@link Activity} used to start another activity for the given intent
     * @param telecomManager the {@link TelecomManager} used to place a call, if possible
     * @param intent the intent for the call
     */
    public static void placeCall(@Nullable Activity activity,
            @Nullable TelecomManager telecomManager, @Nullable Intent intent) {
        if (activity == null || telecomManager == null || intent == null) {
            return;
        }
        if (CompatUtils.isMarshmallowCompatible()) {
            telecomManager.placeCall(intent.getData(), intent.getExtras());
            return;
        }
        activity.startActivityForResult(intent, 0);
    }


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

6. telecomManager.placeCall()

这个api建议细看一下,获得电话拨打权限后调用这个api可以直接不经过dialer直接拨打电话,我写过demo测试,已传github。

    /**
     * Places a new outgoing call to the provided address using the system telecom service with
     * the specified extras.
     *
     * This method is equivalent to placing an outgoing call using {@link Intent#ACTION_CALL},
     * except that the outgoing call will always be sent via the system telecom service. If
     * method-caller is either the user selected default dialer app or preloaded system dialer
     * app, then emergency calls will also be allowed.
     *
     * Requires permission: {@link android.Manifest.permission#CALL_PHONE}
     *
     * Usage example://假如你对这个不熟,建议看一下这段注释
     * <pre>
     * Uri uri = Uri.fromParts("tel", "12345", null);
     * Bundle extras = new Bundle();
     * extras.putBoolean(TelecomManager.EXTRA_START_CALL_WITH_SPEAKERPHONE, true);
     * telecomManager.placeCall(uri, extras);
     * </pre>
     *
     * The following keys are supported in the supplied extras.
     * <ul>
     *   <li>{@link #EXTRA_OUTGOING_CALL_EXTRAS}</li>
     *   <li>{@link #EXTRA_PHONE_ACCOUNT_HANDLE}</li>
     *   <li>{@link #EXTRA_START_CALL_WITH_SPEAKERPHONE}</li>//默认打开Speaker(亲测)
     *   <li>{@link #EXTRA_START_CALL_WITH_VIDEO_STATE}</li>//视频通话
     * </ul>
     *
     * @param address The address to make the call to.
     * @param extras Bundle of extras to use with the call.
     */
    @RequiresPermission(android.Manifest.permission.CALL_PHONE)
    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/services/Telecomm/src/com/android/server/telecom/TelecomServiceImpl.java

7.service.placeCall

        @Override
        public void placeCall(Uri handle, Bundle extras, String callingPackage) {
            try {
                Log.startSession("TSI.pC");
                enforceCallingPackage(callingPackage);
                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}.
                // 默认/系统拨号应用即使没有权限也可以执行到这里
                // 避免紧急号码因权限问题被阻止
                // 如果是非紧急号码,后面会被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 {
                        final Intent intent = new Intent(Intent.ACTION_CALL, handle);
                        if (extras != null) {
                            extras.setDefusable(true);
                            intent.putExtras(extras);
                        }
                        //此处有变化,M上是new UserCallIntentProcessor
                        mUserCallIntentProcessorFactory.create(mContext, userHandle)
                                .processIntent(
                                        intent, callingPackage, hasCallAppOp && hasCallPermission);
                    } finally {
                        Binder.restoreCallingIdentity(token);
                    }
                }
            } finally {
                Log.endSession();
            }
        }

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

8. mUserCallIntentProcessorFactory.create.processIntent

    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();
        // 只允许这三种action拨打电话
        if (Intent.ACTION_CALL.equals(action) ||//判断三种ACTION_CALL(反正只会出现两种)
                Intent.ACTION_CALL_PRIVILEGED.equals(action) ||
                Intent.ACTION_CALL_EMERGENCY.equals(action)) {
            processOutgoingCallIntent(intent, callingPackageName, canCallNonEmergency);
        }
    }

9. processOutgoingCallIntent()

private void processOutgoingCallIntent(Intent intent, String callingPackageName,
            boolean canCallNonEmergency) {
       //略过一部分然后下面的代码相较于M有删减
        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);
    }

相同的部分我们就略过

packages/services/Telecomm/src/com/android/server/telecom/CallIntentProcessor.java

13. processOutgoingCallIntent()

 /**
     * Processes CALL, CALL_PRIVILEGED, and CALL_EMERGENCY intents.
     *
     * @param intent Call intent containing data about the handle to call.
     */
    static void processOutgoingCallIntent(
            Context context,
            CallsManager callsManager,
            Intent intent) {
        Uri handle = intent.getData();
        String scheme = handle.getScheme();
        String uriString = handle.getSchemeSpecificPart();
        Bundle clientExtras = null;
        //略去一部分代码
        Log.i(CallIntentProcessor.class, " processOutgoingCallIntent handle = " + handle
                + ",scheme = " + scheme + ", uriString = " + uriString
                + ", isSkipSchemaParsing = " + isSkipSchemaParsing
                + ", isAddParticipant = " + isAddParticipant);
        final boolean isPrivilegedDialer = intent.getBooleanExtra(KEY_IS_PRIVILEGED_DIALER, false);
        boolean fixedInitiatingUser = fixInitiatingUserIfNecessary(context, intent);
        // Show the toast to warn user that it is a personal call though initiated in work profile.//这段是新增
        if (fixedInitiatingUser) { 
        // string的值 Using the personal dialer to make the call
            Toast.makeText(context, R.string.toast_personal_call_msg, Toast.LENGTH_LONG).show();
        }
        UserHandle initiatingUser = intent.getParcelableExtra(KEY_INITIATING_USER);
        // Send to CallsManager to ensure the InCallUI gets kicked off before the broadcast returns
	// 这里看来是不等广播完成就先启动InCallUI,视觉上给人一种立刻拨打和响应速度很快的感觉
        Call call = callsManager
                .startOutgoingCall(handle, phoneAccountHandle, clientExtras, initiatingUser);
        if (call != null) {
            // Asynchronous calls should not usually be made inside a BroadcastReceiver because once
            // onReceive is complete, the BroadcastReceiver's process runs the risk of getting
            // killed if memory is scarce. However, this is OK here because the entire Telecom
            // process will be running throughout the duration of the phone call and should never
            // be killed.
            NewOutgoingCallIntentBroadcaster broadcaster = new NewOutgoingCallIntentBroadcaster(
                    context, callsManager, call, intent, new PhoneNumberUtilsAdapterImpl(),
                    isPrivilegedDialer);
            final int result = broadcaster.processIntent();
            final boolean success = result == DisconnectCause.NOT_DISCONNECTED;
            if (!success && call != null) {
                callsManager.clearPendingMOEmergencyCall();
                disconnectCallAndShowErrorDialog(context, call, result);
            }
        }
    }

结合InCallUI里面的一些提交,cm 14.1上似乎是对电话做了personal和work的区分,看到实际效果后我考虑要不要专门总结一下那个。

packages/services/Telecomm/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java

14. broadcaster.processIntent

15. rewriteCallIntentAction

16. broadcastIntent

 
  1.    /**
  2.     * Processes the supplied intent and starts the outgoing call broadcast process relevant to the
  3.     * intent.
  4.     *
  5.     * This method will handle three kinds of actions:
  6.     *//加粗加黑划重点
  7.     * - CALL (intent launched by all third party dialers)
  8.     * - CALL_PRIVILEGED (intent launched by system apps e.g. system Dialer, voice Dialer)//这个几乎不会出现了
  9.     * - CALL_EMERGENCY (intent launched by lock screen emergency dialer)
  10.     *
  11.     * @return {@link DisconnectCause#NOT_DISCONNECTED} if the call succeeded, and an appropriate
  12.     *         {@link DisconnectCause} if the call did not, describing why it failed.
  13.     */
  14.    @VisibleForTesting
  15.    public int processIntent() {
  16.        Log.v(this, "Processing call intent in OutgoingCallIntentBroadcaster.");
  17.        Intent intent = mIntent;
  18.        String action = intent.getAction();
  19.        final Uri handle = intent.getData();
  20.        //略过一些代码
  21.        final boolean isPotentialEmergencyNumber = isPotentialEmergencyNumber(number);
  22.        Log.v(this, "isPotentialEmergencyNumber = %s", isPotentialEmergencyNumber);
  23. // 重写Call Intent action。关于这部分的详解看M和L版本的比较
  24.        rewriteCallIntentAction(intent, isPotentialEmergencyNumber);
  25.        action = intent.getAction();
  26.        // True for certain types of numbers that are not intended to be intercepted or modified
  27.        // by third parties (e.g. emergency numbers).
  28.        boolean callImmediately = false;
  29.        if (Intent.ACTION_CALL.equals(action)) {
  30.            if (isPotentialEmergencyNumber) {
  31.                if (!mIsDefaultOrSystemPhoneApp) {
  32.                    Log.w(this, "Cannot call potential emergency number %s with CALL Intent %s "
  33.                            + "unless caller is system or default dialer.", number, intent);
  34.                    launchSystemDialer(intent.getData());
  35.                    return DisconnectCause.OUTGOING_CANCELED;
  36.                } else {
  37. // “可能是”紧急号码 并且 默认/系统拨号应用为 true
  38.                    callImmediately = true;
  39.                }
  40.            }
  41.        } else if (Intent.ACTION_CALL_EMERGENCY.equals(action)) {
  42.            if (!isPotentialEmergencyNumber) {
  43.                Log.w(this, "Cannot call non-potential-emergency number %s with EMERGENCY_CALL "
  44.                        + "Intent %s.", number, intent);
  45.                return DisconnectCause.OUTGOING_CANCELED;
  46.            }
  47. // 不是紧急号码,但是 action 标记为emergency 为 treu,我记得这个actino是在锁屏界面拨打紧急号码使用的
  48.            callImmediately = true;
  49.        } else {
  50.            Log.w(this, "Unhandled Intent %s. Ignoring and not placing call.", intent);
  51.            return DisconnectCause.INVALID_NUMBER;
  52.        }
  53.        if (callImmediately) {
  54.            Log.i(this, "Placing call immediately instead of waiting for "
  55.                    + " OutgoingCallBroadcastReceiver: %s", intent);
  56.            String scheme = isUriNumber ? PhoneAccount.SCHEME_SIP : PhoneAccount.SCHEME_TEL;
  57.            boolean speakerphoneOn = mIntent.getBooleanExtra(
  58.                    TelecomManager.EXTRA_START_CALL_WITH_SPEAKERPHONE, false);
  59.            int videoState = mIntent.getIntExtra(
  60.                    TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
  61.                    VideoProfile.STATE_AUDIO_ONLY);
  62.            // Since we will not start NewOutgoingCallBroadcastIntentReceiver in case of
  63.            // callImmediately is true, make sure to mark it as ready, so that when user
  64.            // selects account, call can go ahead in case of numbers which are potential emergency
  65.            // but not actual emergeny.
  66. // 这里其实蛮重要的,注意看上面英文注释,
  67. // 有个bug跟这个有关,好像是双卡设定拨打前询问后,拨打紧急号码开的号码会直接拨出去
  68. // 需要标记的地方不止一个,可以从git log中查看相关提交,为了防止你的代码不是最新的,建议去LineageOS的review上查看
  69.            mCall.setNewOutgoingCallIntentBroadcastIsDone();
  70.            mCallsManager.placeOutgoingCall(mCall, Uri.fromParts(scheme, number, null), null,
  71.                    speakerphoneOn, videoState);
  72.            // Don't return but instead continue and send the ACTION_NEW_OUTGOING_CALL broadcast
  73.            // so that third parties can still inspect (but not intercept) the outgoing call. When
  74.            // the broadcast finally reaches the OutgoingCallBroadcastReceiver, we'll know not to
  75.            // initiate the call again because of the presence of the EXTRA_ALREADY_CALLED extra.
  76.        }
  77.        UserHandle targetUser = mCall.getInitiatingUser();
  78.        Log.i(this, "Sending NewOutgoingCallBroadcast for %s to %s", mCall, targetUser);
  79.        if (isSkipSchemaParsing) {
  80.            broadcastIntent(intent, handle.toString(), !callImmediately, targetUser);
  81.        } else {
  82.            broadcastIntent(intent, number, !callImmediately, targetUser);
  83.        }
  84.        return DisconnectCause.NOT_DISCONNECTED;
  85.    }

前面部分略微小结一下,由于代码“历代更改”,现如今在新建intent的时候,默认的action已经成为ACTION_CALL,而不是ACTION_CALL_PRIVILEGED,也因此rewriteCallIntentAction成了一个无用的方法。

packages/services/Telecomm/src/com/android/server/telecom/CallsManager.java

19. mCallsManager.placeOutgoingCall

 
  1.    @VisibleForTesting
  2.    public void placeOutgoingCall(Call call, Uri handle, GatewayInfo gatewayInfo,
  3.            boolean speakerphoneOn, int videoState) {
  4.        final Uri uriHandle = (gatewayInfo == null) ? handle : gatewayInfo.getGatewayAddress();

  5.        if (gatewayInfo == null) {
  6.            Log.i(this, "Creating a new outgoing call with handle: %s", Log.piiHandle(uriHandle));
  7.        } else {
  8.            Log.i(this, "Creating a new outgoing call with gateway handle: %s, original handle: %s",
  9.                    Log.pii(uriHandle), Log.pii(handle));
  10.        }

  11.        call.setHandle(uriHandle);
  12.        call.setGatewayInfo(gatewayInfo);

  13.        final boolean useSpeakerWhenDocked = mContext.getResources().getBoolean(
  14.                R.bool.use_speaker_when_docked);
  15.        final boolean isDocked = mDockManager.isDocked();
  16.        final boolean useSpeakerForVideoCall = isSpeakerphoneAutoEnabled(videoState);

  17.        // 建议大家注意一下这部分代码,这里影响(不是决定)了通话是否要自动开speaker,就我个人来看,关于Spekaer开关这一块的
  18.        // 代码逻辑还不够清晰准确,或者说Spekaer这部分(尤其是视频通话中)没有一个统一的期望结果,尤其再结合低电的情况,
  19. // 所以总会挑出一些小猫病。算了这次说MO的,Speaker的不多说了。
  20.        // Auto-enable speakerphone if the originating intent specified to do so, if the call
  21.        // is a video call, of if using speaker when docked
  22.        call.setStartWithSpeakerphoneOn(speakerphoneOn || useSpeakerForVideoCall
  23.                || (useSpeakerWhenDocked && isDocked));
  24.        call.setVideoState(videoState);

  25.        if (speakerphoneOn) {
  26.            Log.i(this, "%s Starting with speakerphone as requested", call);
  27.        } else if (useSpeakerWhenDocked && useSpeakerWhenDocked) {
  28.            Log.i(this, "%s Starting with speakerphone because car is docked.", call);
  29.        } else if (useSpeakerForVideoCall) {
  30.            Log.i(this, "%s Starting with speakerphone because its a video call.", call);
  31.        }

  32.        if (call.isEmergencyCall()) {
  33.            // Emergency -- CreateConnectionProcessor will choose accounts automatically
  34.            call.setTargetPhoneAccount(null);
  35.            new AsyncEmergencyContactNotifier(mContext).execute();
  36.        }

  37.        final boolean requireCallCapableAccountByHandle = mContext.getResources().getBoolean(
  38.                com.android.internal.R.bool.config_requireCallCapableAccountForHandle);

  39.        if (call.getTargetPhoneAccount() != null || call.isEmergencyCall()) {
  40.            if (!call.isEmergencyCall()) {
  41.                updateLchStatus(call.getTargetPhoneAccount().getId());
  42.            }
  43.            //Block to initiate Emregency call now as the current active call
  44.            //is not yet disconnected.
  45.            if (mPendingMOEmerCall == null) {
  46.            // If the account has been set, proceed to place the outgoing call.
  47.            // Otherwise the connection will be initiated when the account is set by the user.
  48.                call.startCreateConnection(mPhoneAccountRegistrar);
  49.            }
  50.        } else if (mPhoneAccountRegistrar.getCallCapablePhoneAccounts(
  51.                requireCallCapableAccountByHandle ? call.getHandle().getScheme() : null, false,
  52.                call.getInitiatingUser()).isEmpty()) {
  53.            // If there are no call capable accounts, disconnect the call.
  54.            markCallAsDisconnected(call, new DisconnectCause(DisconnectCause.CANCELED,
  55.                    "No registered PhoneAccounts"));
  56.            markCallAsRemoved(call);
  57.        }
  58.    }

packages/services/Telecomm/src/com/android/server/telecom/CreateConnectionProcessor.java

21. mCreateConnectionProcessor.process

22. adjustAttemptsForEmergency

    @VisibleForTesting
    public void process() {
        Log.v(this, "process");
        clearTimeout();
        mAttemptRecords = new ArrayList<>();
        if (mCall.getTargetPhoneAccount() != null) {
            mAttemptRecords.add(new CallAttemptRecord(
                    mCall.getTargetPhoneAccount(), mCall.getTargetPhoneAccount()));
        }
        adjustAttemptsForConnectionManager();
        adjustAttemptsForEmergency();
        mAttemptRecordIterator = mAttemptRecords.iterator();
        attemptNextPhoneAccount();
    }



31. crateConnection

    /**
     * This can be used by telecom to either create a new outgoing call or attach to an existing
     * incoming call. In either case, telecom will cycle through a set of services and call
     * createConnection util a connection service cancels the process or completes it successfully.
     */
    private void createConnection(
            final PhoneAccountHandle callManagerAccount,
            final String callId,
            final ConnectionRequest request,
            boolean isIncoming,
            boolean isUnknown) {
        Log.d(this, "createConnection, callManagerAccount: %s, callId: %s, request: %s, " +
                        "isIncoming: %b, isUnknown: %b", callManagerAccount, callId, request,
                isIncoming,
                isUnknown);
        Connection connection = isUnknown ? onCreateUnknownConnection(callManagerAccount, request)
                : isIncoming ? onCreateIncomingConnection(callManagerAccount, request)
                :onCreateOutgoingConnection(callManagerAccount, request);//根据isIncoming创建不同connection
        Log.d(this, "createConnection, connection: %s", connection);
        if (connection == null) {
            connection = Connection.createFailedConnection(
                    new DisconnectCause(DisconnectCause.ERROR));
        }
        connection.setTelecomCallId(callId);
        if (connection.getState() != Connection.STATE_DISCONNECTED) {
            addConnection(callId, connection);
        }
        Uri address = connection.getAddress();
        String number = address == null ? "null" : address.getSchemeSpecificPart();
        Log.v(this, "createConnection, number: %s, state: %s, capabilities: %s, properties: %s",
                Connection.toLogSafePhoneNumber(number),
                Connection.stateToString(connection.getState()),
                Connection.capabilitiesToString(connection.getConnectionCapabilities()),
                Connection.propertiesToString(connection.getConnectionProperties()));
        Log.d(this, "createConnection, calling handleCreateConnectionSuccessful %s", callId);
        mAdapter.handleCreateConnectionComplete(
                callId,
                request,
                new ParcelableConnection( // 瞧一下这里面的参数,想看什么可以log打印出来
                        request.getAccountHandle(),
                        connection.getState(),
                        connection.getConnectionCapabilities(),
                        connection.getConnectionProperties(),
                        connection.getAddress(),
                        connection.getAddressPresentation(),
                        connection.getCallerDisplayName(),
                        connection.getCallerDisplayNamePresentation(),
                        connection.getVideoProvider() == null ?
                                null : connection.getVideoProvider().getInterface(),
                        connection.getVideoState(),
                        connection.isRingbackRequested(),
                        connection.getAudioModeIsVoip(),
                        connection.getConnectTimeMillis(),
                        connection.getStatusHints(),
                        connection.getDisconnectCause(),
                        createIdList(connection.getConferenceables()),
                        connection.getExtras()));
        if (isUnknown) {
            triggerConferenceRecalculate();
        }
    }


TelephonyConnectionService.java

32. onCreateOutgoingConnection

    @Override
    public Connection onCreateOutgoingConnection(
            PhoneAccountHandle connectionManagerPhoneAccount,
            final ConnectionRequest request) {
        Log.i(this, "onCreateOutgoingConnection, request: " + request);

        Bundle bundle = request.getExtras();
        boolean isSkipSchemaOrConfUri = (bundle != null) && (bundle.getBoolean(
                TelephonyProperties.EXTRA_SKIP_SCHEMA_PARSING, false) ||
                bundle.getBoolean(TelephonyProperties.EXTRA_DIAL_CONFERENCE_URI, false));
        Uri handle = request.getAddress();
        if (!isSkipSchemaOrConfUri && handle == null) {
            Log.d(this, "onCreateOutgoingConnection, handle is null");
            return Connection.createFailedConnection(
                    DisconnectCauseUtil.toTelecomDisconnectCause(
                            android.telephony.DisconnectCause.NO_PHONE_NUMBER_SUPPLIED,
                            "No phone number supplied"));
        }
        if (handle == null) handle = Uri.EMPTY;
        String scheme = handle.getScheme();
        String number;
...
        final String numberToDial = number;

        final boolean isEmergencyNumber =
                PhoneNumberUtils.isLocalEmergencyNumber(this, numberToDial);
        //isLocalEmergencyNumber比isPotentialEmergencyNumber靠谱多了

        if (isEmergencyNumber && !isRadioOn()) {
            //emergency的也略过
            // Return the still unconnected GsmConnection and wait for the Radios to boot before
            // connecting it to the underlying Phone.
            return emergencyConnection;
        } else {
            if (!canAddCall() && !isEmergencyNumber) {
                Log.d(this, "onCreateOutgoingConnection, cannot add call .");
                return Connection.createFailedConnection(
                        new DisconnectCause(DisconnectCause.ERROR,
                                getApplicationContext().getText(
                                        R.string.incall_error_cannot_add_call),
                                getApplicationContext().getText(
                                        R.string.incall_error_cannot_add_call),
                                "Add call restricted due to ongoing video call"));
            }

            // Get the right phone object from the account data passed in.
            //返回的是GSMCDMAPhone
            final Phone phone =getPhoneForAccount(request.getAccountHandle(), isEmergencyNumber);
            Connection resultConnection = getTelephonyConnection(request, numberToDial,
                    isEmergencyNumber, handle, phone);
            // If there was a failure, the resulting connection will not be a TelephonyConnection,
            // so don't place the call!
            if(resultConnection instanceof TelephonyConnection) {
                placeOutgoingConnection((TelephonyConnection) resultConnection, phone, request);
            }
            return resultConnection;
        }
    }


33. placeOutgoingConnection

    private void placeOutgoingConnection(
            TelephonyConnection connection, Phone phone, ConnectionRequest request) {
        placeOutgoingConnection(connection, phone, request.getVideoState(), request.getExtras());
    }
    private void placeOutgoingConnection(
            TelephonyConnection connection, Phone phone, int videoState, Bundle extras) {
        String number = connection.getAddress().getSchemeSpecificPart();
        boolean isAddParticipant = (extras != null) && extras
                .getBoolean(TelephonyProperties.ADD_PARTICIPANT_KEY, false);
        Log.d(this, "placeOutgoingConnection isAddParticipant = " + isAddParticipant);
        com.android.internal.telephony.Connection originalConnection = null;
        try {
            if (phone != null) {
                if (isAddParticipant) {
                    phone.addParticipant(number);
                    return;
                } else {
                    // 这里phone的类型是GSMCDMAPhone, android N的新变化
                    originalConnection = phone.dial(number, null, videoState, extras);

                }
            }
        } catch (CallStateException e) {
           ...
        }
        if (originalConnection == null) {
            int telephonyDisconnectCause = android.telephony.DisconnectCause.OUTGOING_FAILURE;
            // 没有connection的话意味着是拨了一个MMIcode
            // On GSM phones, null connection means that we dialed an MMI code
            if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_GSM) {
                Log.d(this, "dialed MMI code");
                int subId = phone.getSubId();
                Log.d(this, "subId: " + subId);
                telephonyDisconnectCause = android.telephony.DisconnectCause.DIALED_MMI;
                final Intent intent = new Intent(this, MMIDialogActivity.class);
                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
                        Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
                if (SubscriptionManager.isValidSubscriptionId(subId)) {
                    intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId);
                }
                startActivity(intent);
            }
            Log.d(this, "placeOutgoingConnection, phone.dial returned null");
            connection.setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(
                    telephonyDisconnectCause, "Connection is null"));
        } else {
            connection.setOriginalConnection(originalConnection);
        }
    }

我们知道originalConnection = phone.dial(number, null, videoState, extras)是到了拨号的阶段了。

那么当前这个phone是怎么来的?是什么类型的呢?

这里这个phone是作为一个参数传进来的,在32可以看到这两行代码

// Get the right phone object from the account data passed in.
final Phone phone =getPhoneForAccount(request.getAccountHandle(), isEmergencyNumber);
调用的是PhoneFactory.getPhone()的方法返回的phone,具体的类型
                for (int i = 0; i < numPhones; i++) {
                    Phone phone = null;
                    int phoneType = TelephonyManager.getPhoneType(networkModes[i]);
                    if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
                        phone = telephonyComponentFactory.makePhone(context,
                                sCommandsInterfaces[i], sPhoneNotifier, i,
                                PhoneConstants.PHONE_TYPE_GSM,
                                telephonyComponentFactory);
                    } else if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
                        phone = telephonyComponentFactory.makePhone(context,
                                sCommandsInterfaces[i], sPhoneNotifier, i,
                                PhoneConstants.PHONE_TYPE_CDMA,
                                telephonyComponentFactory);
                    }
                    Rlog.i(LOG_TAG, "Creating Phone with type = " + phoneType + " sub = " + i);
                    sPhones[i] = phone;
                }
可以看到这里就两种phoneType,然后根据两种phonType创建phone,调用的是makePhone()方法。然后下面是android M上的代码,分别有makeGSMPhone和makeCDMAPhone
		for (int i = 0; i < numPhones; i++) {
                   PhoneBase phone = null;
                   int phoneType = TelephonyManager.getPhoneType(networkModes[i]);
                   if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
                       phone = TelephonyPluginDelegate.getInstance().makeGSMPhone(context,
                               sCommandsInterfaces[i], sPhoneNotifier, i);
                   } else if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
                       phone = TelephonyPluginDelegate.getInstance().makeCDMALTEPhone(context,
                               sCommandsInterfaces[i], sPhoneNotifier, i);
                   }
                   Rlog.i(LOG_TAG, "Creating Phone with type = " + phoneType + " sub = " + i);
                   sProxyPhones[i] = TelephonyPluginDelegate.getInstance().makePhoneProxy(phone);
               }

 

上图N的GSMCDMAPhone对应M的GSMPhone


GsmCdmaPhone.java

34. phone.dial

 
  1.    @Override
  2.    public Connection dial(String dialString, UUSInfo uusInfo, int videoState, Bundle intentExtras)
  3.            throws CallStateException {
  4.        if (!isPhoneTypeGsm() && uusInfo != null) {
  5.            throw new CallStateException("Sending UUS information NOT supported in CDMA!");
  6.        }
  7. // 前面判断了那么多次emergency,这里还在判断,emergency相关方法很多
  8.        boolean isEmergency = PhoneNumberUtils.isEmergencyNumber(dialString);
  9.        Phone imsPhone = mImsPhone;
  10.        CarrierConfigManager configManager =
  11.                (CarrierConfigManager) mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
  12.        boolean alwaysTryImsForEmergencyCarrierConfig = configManager.getConfigForSubId(getSubId())
  13.                .getBoolean(CarrierConfigManager.KEY_CARRIER_USE_IMS_FIRST_FOR_EMERGENCY_BOOL);
  14.        boolean imsUseEnabled = isImsUseEnabled()// 瞧这众多条件
  15.                 && imsPhone != null
  16.                 && (imsPhone.isVolteEnabled() || imsPhone.isWifiCallingEnabled() ||
  17.                 (imsPhone.isVideoEnabled() && VideoProfile.isVideo(videoState)))
  18.                 && (imsPhone.getServiceState().getState() == ServiceState.STATE_IN_SERVICE)
  19.                 && !shallDialOnCircuitSwitch(intentExtras);
  20.        boolean useImsForEmergency = imsPhone != null
  21.                && isEmergency
  22.                && alwaysTryImsForEmergencyCarrierConfig
  23.                && ImsManager.isNonTtyOrTtyOnVolteEnabled(mContext)
  24.                && (imsPhone.getServiceState().getState() != ServiceState.STATE_POWER_OFF);
  25.        String dialPart = PhoneNumberUtils.extractNetworkPortionAlt(PhoneNumberUtils.
  26.                stripSeparators(dialString));
  27.        boolean isUt = (dialPart.startsWith("*") || dialPart.startsWith("#"))
  28.                && dialPart.endsWith("#");
  29.        boolean useImsForUt = imsPhone != null && imsPhone.isUtEnabled();
  30.        if (DBG) {// log很详细
  31.            logd("imsUseEnabled=" + imsUseEnabled
  32.                    + ", useImsForEmergency=" + useImsForEmergency
  33.                    + ", useImsForUt=" + useImsForUt
  34.                    + ", isUt=" + isUt
  35.                    + ", imsPhone=" + imsPhone
  36.                    + ", imsPhone.isVolteEnabled()="
  37.                    + ((imsPhone != null) ? imsPhone.isVolteEnabled() : "N/A")
  38.                    + ", imsPhone.isVowifiEnabled()="
  39.                    + ((imsPhone != null) ? imsPhone.isWifiCallingEnabled() : "N/A")
  40.                    + ", imsPhone.isVideoEnabled()="
  41.                    + ((imsPhone != null) ? imsPhone.isVideoEnabled() : "N/A")
  42.                    + ", imsPhone.getServiceState().getState()="
  43.                    + ((imsPhone != null) ? imsPhone.getServiceState().getState() : "N/A"));
  44.        }
  45.        Phone.checkWfcWifiOnlyModeBeforeDial(mImsPhone, mContext);// VoWiFi//检查wifi call、、
  46. // 这里判断ims可用的话,会通过imsPhone dial
  47.        if ((imsUseEnabled && (!isUt || useImsForUt)) || useImsForEmergency) {
  48.            try {
  49.                if (DBG) logd("Trying IMS PS call");
  50.                return imsPhone.dial(dialString, uusInfo, videoState, intentExtras);
  51.            } catch (CallStateException e) {
  52.                if (DBG) logd("IMS PS call exception " + e +
  53.                        "imsUseEnabled =" + imsUseEnabled + ", imsPhone =" + imsPhone);
  54.                if (!Phone.CS_FALLBACK.equals(e.getMessage())) {
  55.                    CallStateException ce = new CallStateException(e.getMessage());
  56.                    ce.setStackTrace(e.getStackTrace());
  57.                    throw ce;
  58.                }
  59.            }
  60.        }
  61. //...
  62.    }

我们继续看imsPhone这条路

ImsPhone.java

37. imsPhone.dial

38. dialInternal

 
  1.    @Override
  2.    public Connection
  3.    dial(String dialString, UUSInfo uusInfo, int videoState, Bundle intentExtras)
  4.            throws CallStateException {
  5.        // ignore UUSInfo
  6.        return dialInternal (dialString, videoState, intentExtras);
  7.    }

  8.    private Connection dialInternal(String dialString, int videoState, Bundle intentExtras)
  9.            throws CallStateException {
  10.        boolean isConferenceUri = false;
  11.        boolean isSkipSchemaParsing = false;
  12.        //略
  13.        // Only look at the Network portion for mmi
  14.        String networkPortion = PhoneNumberUtils.extractNetworkPortionAlt(newDialString);
  15.        ImsPhoneMmiCode mmi =
  16.                ImsPhoneMmiCode.newFromDialString(networkPortion, this);
  17.        if (DBG) Rlog.d(LOG_TAG,
  18.                "dialing w/ mmi '" + mmi + "'...");

  19.        if (mmi == null) {
  20.            return mCT.dial(dialString, videoState, intentExtras);
  21.        } else if (mmi.isTemporaryModeCLIR()) {
  22.            return mCT.dial(mmi.getDialingNumber(), mmi.getCLIRMode(), videoState, intentExtras);
  23.        } else if (!mmi.isSupportedOverImsPhone()) {
  24.            // If the mmi is not supported by IMS service,
  25.            // try to initiate dialing with default phone
  26.            throw new CallStateException(CS_FALLBACK);
  27.        } else {
  28.            mPendingMMIs.add(mmi);
  29.            mMmiRegistrants.notifyRegistrants(new AsyncResult(null, mmi, null));
  30.            mmi.processCode();

  31.            return null;
  32.        }
  33.    }


ImsPhoneCallTracker

39. mCT.dial

 
  1.    public Connection dial(String dialString, int videoState, Bundle intentExtras) throws
  2.            CallStateException {
  3.        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mPhone.getContext());
  4.        int oirMode = sp.getInt(Phone.CLIR_KEY, CommandsInterface.CLIR_DEFAULT);
  5.        return dial(dialString, oirMode, videoState, intentExtras);
  6.    }

40. dial

 
  1.   /**
  2.     * oirMode is one of the CLIR_ constants
  3.     */
  4.    synchronized Connection
  5.    dial(String dialString, int clirMode, int videoState, Bundle intentExtras)
  6.            throws CallStateException {
  7.        boolean isPhoneInEcmMode = isPhoneInEcbMode();
  8.        boolean isEmergencyNumber = PhoneNumberUtils.isEmergencyNumber(dialString);

  9. //...
  10.        boolean holdBeforeDial = false;

  11.        // The new call must be assigned to the foreground call.
  12.        // That call must be idle, so place anything that's
  13.        // there on hold
  14.        if (mForegroundCall.getState() == ImsPhoneCall.State.ACTIVE) {
  15.            if (mBackgroundCall.getState() != ImsPhoneCall.State.IDLE) {
  16.                //we should have failed in !canDial() above before we get here
  17.                throw new CallStateException("cannot dial in current state");
  18.            }
  19.            // foreground call is empty for the newly dialed connection
  20.            holdBeforeDial = true;
  21.            // Cache the video state for pending MO call.
  22.            mPendingCallVideoState = videoState;
  23.            mPendingIntentExtras = intentExtras;
  24.            switchWaitingOrHoldingAndActive();
  25.        }

  26.        ImsPhoneCall.State fgState = ImsPhoneCall.State.IDLE;
  27.        ImsPhoneCall.State bgState = ImsPhoneCall.State.IDLE;

  28.        mClirMode = clirMode;

  29.        synchronized (mSyncHold) {
  30.            if (holdBeforeDial) {
  31.                fgState = mForegroundCall.getState();
  32.                bgState = mBackgroundCall.getState();

  33.                //holding foreground call failed
  34.                if (fgState == ImsPhoneCall.State.ACTIVE) {
  35.                    throw new CallStateException("cannot dial in current state");
  36.                }

  37.                //holding foreground call succeeded
  38.                if (bgState == ImsPhoneCall.State.HOLDING) {
  39.                    holdBeforeDial = false;
  40.                }
  41.            }

  42.            mPendingMO = new ImsPhoneConnection(mPhone,//创建ImsConnection,作为dialInternal的参数
  43.                    checkForTestEmergencyNumber(dialString), this, mForegroundCall,
  44.                    isEmergencyNumber, intentExtras);
  45.            mPendingMO.setVideoState(videoState);
  46.        }
  47.        addConnection(mPendingMO);

  48.        if (!holdBeforeDial) {
  49.            if ((!isPhoneInEcmMode) || (isPhoneInEcmMode && isEmergencyNumber)) {
  50.                dialInternal(mPendingMO, clirMode, videoState, intentExtras);
  51.            } else {
  52.                try {
  53.                    getEcbmInterface().exitEmergencyCallbackMode();
  54.                } catch (ImsException e) {
  55.                    e.printStackTrace();
  56.                    throw new CallStateException("service not available");
  57.                }
  58.                mPhone.setOnEcbModeExitResponse(this, EVENT_EXIT_ECM_RESPONSE_CDMA, null);
  59.                pendingCallClirMode = clirMode;
  60.                mPendingCallVideoState = videoState;
  61.                mPendingIntentExtras = intentExtras;
  62.                pendingCallInEcm = true;
  63.            }
  64.        }

  65.        updatePhoneState();
  66.        mPhone.notifyPreciseCallStateChanged();

  67.        return mPendingMO;
  68.    }


42. dialInternal

 
 
  1.    private void dialInternal(ImsPhoneConnection conn, int clirMode, int videoState,
  2.            Bundle intentExtras) {
  3.        if (conn == null) {
  4.            return;
  5.        }
  6.        boolean isConferenceUri = false;
  7.        boolean isSkipSchemaParsing = false;
  8.        boolean isCallPull = false;
  9. //略
  10.        // Always unmute when initiating a new call
  11.        setMute(false);
  12.        int serviceType = PhoneNumberUtils.isEmergencyNumber(conn.getAddress()) ?
  13.                ImsCallProfile.SERVICE_TYPE_EMERGENCY : ImsCallProfile.SERVICE_TYPE_NORMAL;
  14.        int callType = ImsCallProfile.getCallTypeFromVideoState(videoState);
  15.        //TODO(vt): Is this sufficient?  At what point do we know the video state of the call?
  16.        conn.setVideoState(videoState);
  17.        try {
  18.            String[] callees = new String[] { conn.getAddress() };
  19.            ImsCallProfile profile = mImsManager.createCallProfile(mServiceId,
  20.                    serviceType, callType);
  21.            profile.setCallExtraInt(ImsCallProfile.EXTRA_OIR, clirMode);
  22.            profile.setCallExtraBoolean(TelephonyProperties.EXTRAS_IS_CONFERENCE_URI,
  23.                    isConferenceUri);
  24.            //略
  25.            // Translate call subject intent-extra from Telecom-specific extra key to the
  26.            // ImsCallProfile key.
  27.            
  28.            ImsCall imsCall = mImsManager.makeCall(mServiceId, profile,
  29.                    callees, mImsCallListener);
  30.            conn.setImsCall(imsCall);
  31.            mMetrics.writeOnImsCallStart(mPhone.getPhoneId(),
  32.                    imsCall.getSession());
  33.            setVideoCallProvider(conn, imsCall);
  34.            conn.setAllowAddCallDuringVideoCall(mAllowAddCallDuringVideoCall);
  35.        } catch (ImsException e) {
  36.            loge("dialInternal : " + e);
  37.            conn.setDisconnectCause(DisconnectCause.ERROR_UNSPECIFIED);
  38.            sendEmptyMessageDelayed(EVENT_HANGUP_PENDINGMO, TIMEOUT_HANGUP_PENDINGMO);
  39.        } catch (RemoteException e) {
  40.        }
  41.    }
ImsManager.java
43. makeCall
 
  
  1.    /**
  2.     * Creates a {@link ImsCall} to make a call.
  3.     *
  4.     * @param serviceId a service id which is obtained from {@link ImsManager#open}
  5.     * @param profile a call profile to make the call
  6.     *      (it contains service type, call type, media information, etc.)
  7.     * @param participants participants to invite the conference call
  8.     * @param listener listen to the call events from {@link ImsCall}
  9.     * @return a {@link ImsCall} object
  10.     * @throws ImsException if calling the IMS service results in an error
  11.     */
  12.    public ImsCall makeCall(int serviceId, ImsCallProfile profile, String[] callees,
  13.            ImsCall.Listener listener) throws ImsException {
  14.        if (DBG) {
  15.            log("makeCall :: serviceId=" + serviceId
  16.                    + ", profile=" + profile);
  17.        }

  18.        checkAndThrowExceptionIfServiceUnavailable();

  19.        ImsCall call = new ImsCall(mContext, profile);//new ImsCall

  20.        call.setListener(listener);
  21.        ImsCallSession session = createCallSession(serviceId, profile);
  22.        boolean isConferenceUri = profile.getCallExtraBoolean(
  23.                TelephonyProperties.EXTRAS_IS_CONFERENCE_URI, false);
  24.        if (!isConferenceUri && (callees != null) && (callees.length == 1)) {
  25.            call.start(session, callees[0]);
  26.        } else {
  27.            call.start(session, callees);
  28.        }

  29.        return call;
  30.    }


44. createCallSession

 
  
  1.    /**
  2.     * Creates a {@link ImsCallSession} with the specified call profile.
  3.     * Use other methods, if applicable, instead of interacting with
  4.     * {@link ImsCallSession} directly.
  5.     *
  6.     * @param serviceId a service id which is obtained from {@link ImsManager#open}
  7.     * @param profile a call profile to make the call
  8.     */
  9.    private ImsCallSession createCallSession(int serviceId,
  10.            ImsCallProfile profile) throws ImsException {
  11.        try {
  12.            return new ImsCallSession(mImsService.createCallSession(serviceId, profile, null));
  13.        } catch (RemoteException e) {
  14.            return null;
  15.        }
  16.    }
后面会到vendor下面的私有代码里面,不写了。

---


小结:

从现在的内容看android N 上的MO 变化不大,placeCall()这个在M上新增的API,在N上的MO流程中细化了一下,估计三方app调用应该没影响;GSMPhone和CDMAPhone和并成GSMCDMAPhone;在上层似乎区分了personal和work(待验证)。



  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值