android Telephony學(xué)習(xí) --- 第五篇 android7.0 呼叫(MO)流程

我們先看下7.0 MO大致流程:

android7.0呼叫流程.png
  • package/app/Dialer -- DialpadFragment

用戶點擊撥號盤的撥號按鈕猜年,此時開始呼叫長征第一步奄喂,dialpadfragment的onclick方法會響應(yīng)點擊事件。

@Override
public void onClick(View view) {
    int resId = view.getId();
    if (resId == R.id.dialpad_floating_action_button) {
        handleDialButtonPressed();
    }
}

我們只看關(guān)鍵方法,點擊事件調(diào)用handleDialButtonPressed方法,號碼不為空時氓润,調(diào)用DialerUtils的startActivityWithErrorToast方法。當(dāng)然其方法內(nèi)部還有一些其他的判斷條件憋沿,比如號碼是空時旺芽,會調(diào)用handleDialButtonClickWithEmptyDigits方法,顯示上次呼叫的號碼辐啄。

private void handleDialButtonPressed() {
    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);
}
  • package/app/Dialer -- DialerUtils
    攜帶Intent.ACTION_CALL的Intent Action會走到TelecomUtil placeCall流程采章,否則直接context.startActivity(intent);
public static void startActivityWithErrorToast(Context context, Intent intent, int msgId) {
  if ((IntentUtil.CALL_ACTION.equals(intent.getAction())
                            && context instanceof Activity)) {
     final boolean hasCallPermission =  TelecomUtil.placeCall((Activity) context, intent);
  }
}
  • package/app/Dialer -- TelecomUtil
public static boolean placeCall(Activity activity, Intent intent) {
      if (hasCallPhonePermission(activity)) {
           TelecomManagerCompat.placeCall(activity, getTelecomManager(activity), intent);
           return true;
       }
       return false;
}

hasCallPhonePermission()會檢查是否有呼叫權(quán)限,包含是否是默認(rèn)dialer壶辜,和是否有Manifest.permission.CALL_PHONE)權(quán)限:

    public static boolean hasCallPhonePermission(Context context) {
        return isDefaultDialer(context)
                || hasPermission(context, Manifest.permission.CALL_PHONE);
    }
  • package/app/ContactsCommon -- TelecomManagerCompat

當(dāng)版本大于等于6.0時悯舟,調(diào)用telecomManager.placeCall()方法,否則直接startActivity執(zhí)行intent砸民。所以我們接著看telecomManager.placeCall的方法:

    public static void placeCall(@Nullable Activity activity,
            @Nullable TelecomManager telecomManager, @Nullable Intent intent) {
        if (CompatUtils.isMarshmallowCompatible()) {
            telecomManager.placeCall(intent.getData(), intent.getExtras());
            return;
        }
        activity.startActivityForResult(intent, 0);
    }
  • frameworks/base/telecomm -- TelecomManager

調(diào)用ITelecomService的placeCall方法抵怎,此處是aidl調(diào)用,對應(yīng)的我們需要找到接收的地方岭参,按照命名規(guī)則應(yīng)該是TelecomServiceImpl:

    @RequiresPermission(android.Manifest.permission.CALL_PHONE)
    public void placeCall(Uri address, Bundle extras) {
        ITelecomService service = getTelecomService();
        if (service != null) {
            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 -- TelecomServiceImpl

創(chuàng)建UserCallIntentProcessorFactory反惕,調(diào)用processIntent方法處理呼叫:

private final ITelecomService.Stub mBinderImpl = new ITelecomService.Stub() {

        @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);
                        mUserCallIntentProcessorFactory.create(mContext, userHandle)
                                .processIntent(
                                        intent, callingPackage, hasCallAppOp && hasCallPermission);
                    }
                }
            } 
        }
}
  • packages/services/Telecomm -- UserCallIntentProcessor

processIntent判斷是否是呼叫請求:

public void processIntent(Intent intent, String callingPackageName,
            boolean canCallNonEmergency) {
   if (Intent.ACTION_CALL.equals(action) ||
           Intent.ACTION_CALL_PRIVILEGED.equals(action) ||
           Intent.ACTION_CALL_EMERGENCY.equals(action)) {
      processOutgoingCallIntent(intent, callingPackageName, canCallNonEmergency);
   }
}

  • Intent.ACTION_CALL: 可以撥打普通呼叫
public static final String ACTION_CALL = "android.intent.action.CALL";
  • Intent.ACTION_CALL_PRIVILEGED:可以撥打任意類型號碼
public static final String ACTION_CALL_PRIVILEGED = "android.intent.action.CALL_PRIVILEGED";
  • Intent.ACTION_CALL_EMERGENCY:可以撥打緊急呼叫
public static final String ACTION_CALL_EMERGENCY = "android.intent.action.CALL_EMERGENCY";

processOutgoingCallIntent方法:

private void processOutgoingCallIntent(Intent intent, String callingPackageName,
            boolean canCallNonEmergency) {
         ......
        sendBroadcastToReceiver(intent);
}

sendBroadcastToReceiver方法,發(fā)送廣播PrimaryCallReceiver:

    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");
        mContext.sendBroadcastAsUser(intent, UserHandle.SYSTEM);
        return true;
    }
  • packages/services/Telecomm -- PrimaryCallReceiver

調(diào)用getTelecomSystem方法返回TelecomSystem對象演侯,調(diào)用getCallIntentProcessor()返回CallIntentProcessor對象姿染,然后調(diào)用processIntent方法

    @Override
    public void onReceive(Context context, Intent intent) {
        Log.startSession("PCR.oR");
        synchronized (getTelecomSystem().getLock()) {
            getTelecomSystem().getCallIntentProcessor().processIntent(intent);
        }
        Log.endSession();
    }
  • packages/services/Telecomm -- CallIntentProcessor
    public void processIntent(Intent intent) {
        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);
        }
        Trace.endSection();
    }

processOutgoingCallIntent方法,調(diào)用CallsManager的startOutgoingCall()方法創(chuàng)建Call秒际,后new NewOutgoingCallIntentBroadcaster悬赏,調(diào)用processIntent()方法:

    static void processOutgoingCallIntent(
            Context context,
            CallsManager callsManager,
            Intent intent) {
        // Send to CallsManager to ensure the InCallUI gets kicked off before the broadcast returns
        Call call = callsManager
                .startOutgoingCall(handle, phoneAccountHandle, clientExtras, initiatingUser);

        if (call != null) {
            NewOutgoingCallIntentBroadcaster broadcaster = new NewOutgoingCallIntentBroadcaster(
                    context, callsManager, call, intent, callsManager.getPhoneNumberUtilsAdapter(),
                    isPrivilegedDialer);
            final int result = broadcaster.processIntent();
        }
    }

看創(chuàng)建Call的備注狡汉,貌似和UI有關(guān)呢,不過我們先關(guān)注呼叫流程闽颇,回頭再整理UI啟動流程盾戴,這里加個*標(biāo)記下,先接著查看processIntent方法:

   public int processIntent() {
        ......
        if (callImmediately) {
            mCall.setNewOutgoingCallIntentBroadcastIsDone();
            mCallsManager.placeOutgoingCall(mCall, Uri.fromParts(scheme, number, null), null,
                    speakerphoneOn, videoState);
        }
        ......
        broadcastIntent(intent, number, !callImmediately, targetUser);
    }
    private void broadcastIntent(
            Intent originalCallIntent,
            String number,
            boolean receiverRequired,
            UserHandle targetUser) {
            
        mContext.sendOrderedBroadcastAsUser(
                broadcastIntent,
                targetUser,
                android.Manifest.permission.PROCESS_OUTGOING_CALLS,
                AppOpsManager.OP_PROCESS_OUTGOING_CALLS,
                receiverRequired ? new NewOutgoingCallBroadcastIntentReceiver() : null,
                null,  // scheduler
                Activity.RESULT_OK,  // initialCode
                number,  // initialData: initial value for the result data (number to be modified)
                null);  // initialExtras
    }

callImmediately為true時兵多,直接調(diào)用CallsManager的placeOutgoingCall()方法尖啡;
callImmediately為false時,創(chuàng)建NewOutgoingCallBroadcastIntentReceiver實例來接收該廣播中鼠,而后調(diào)用到CallsManager的placeOutgoingCall()方法:

   public class NewOutgoingCallBroadcastIntentReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            try {
                    mCallsManager.placeOutgoingCall(mCall, resultHandleUri, gatewayInfo,
                            mIntent.getBooleanExtra(
                                    TelecomManager.EXTRA_START_CALL_WITH_SPEAKERPHONE, false),
                            mIntent.getIntExtra(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
                                    VideoProfile.STATE_AUDIO_ONLY));
                }
            }
        }
    }
  • packages/services/Telecomm -- CallsManager
   public void placeOutgoingCall(Call call, Uri handle, GatewayInfo gatewayInfo,
            boolean speakerphoneOn, int videoState) {
        if (call.getTargetPhoneAccount() != null || call.isEmergencyCall()) {
                call.startCreateConnection(mPhoneAccountRegistrar);
        } 
    }
  • packages/services/Telecomm -- Call

創(chuàng)建CreateConnectionProcessor可婶,并調(diào)用process方法:

    void startCreateConnection(PhoneAccountRegistrar phoneAccountRegistrar) {
        mCreateConnectionProcessor = new CreateConnectionProcessor(this, mRepository, this,
                phoneAccountRegistrar, mContext);
        mCreateConnectionProcessor.process();
    }
  • packages/services/Telecomm -- CreateConnectionProcessor

主要關(guān)注attemptNextPhoneAccount方法:

    public void process() {
        adjustAttemptsForConnectionManager();
        adjustAttemptsForEmergency(mCall.getTargetPhoneAccount());
        mAttemptRecordIterator = mAttemptRecords.iterator();
        attemptNextPhoneAccount();
    }

調(diào)用ConnectionServiceWrapper mService的createConnection方法:

    private void attemptNextPhoneAccount() {
           ......
                mConnectionAttempt++;
                mCall.setConnectionManagerPhoneAccount(attempt.connectionManagerPhoneAccount);
                mCall.setTargetPhoneAccount(attempt.targetPhoneAccount);
                mCall.setConnectionService(mService);
                setTimeoutIfNeeded(mService, attempt);

                mService.createConnection(mCall, this);
        }
    }
  • packages/services/Telecomm -- ConnectionServiceWrapper

通過bind建立成功后沿癞,會回調(diào)onSuccess方法援雇,調(diào)用ConnectionService的createConnection()方法:

    public void createConnection(final Call call, final CreateConnectionResponse response) {
        Log.d(this, "createConnection(%s) via %s.", call, getComponentName());
        BindCallback callback = new BindCallback() {
            @Override
            public void onSuccess() {
                String callId = mCallIdMapper.getCallId(call);
                mPendingResponses.put(callId, response);
                try {
                    mServiceInterface.createConnection(
                            call.getConnectionManagerPhoneAccount(),
                            callId,
                            new ConnectionRequest(
                                    call.getTargetPhoneAccount(),
                                    call.getHandle(),
                                    extras,
                                    call.getVideoState(),
                                    callId),
                            call.shouldAttachToExistingConnection(),
                            call.isUnknown());
                } 
            }
        mBinder.bind(callback, call);
    }
  • frameworks/base/telecomm -- ConnectionService
    由于TelephonyConnectionService是ConnectionService的實例,所以我們接著跟蹤
    TelephonyConnectionService的onCreateOutgoingConnection方法:
                case MSG_CREATE_CONNECTION: {
                    SomeArgs args = (SomeArgs) msg.obj;
                    try {
                        else {
                            createConnection(
                                    connectionManagerPhoneAccount,
                                    id,
                                    request,
                                    isIncoming,
                                    isUnknown);
                        }
                    }
                    break;
                }

這里可以看到有來電和去電的邏輯椎扬,我們先跟蹤呼出的邏輯:

    private void createConnection(
            final PhoneAccountHandle callManagerAccount,
            final String callId,
            final ConnectionRequest request,
            boolean isIncoming,
            boolean isUnknown) {

        Connection connection = isUnknown ? onCreateUnknownConnection(callManagerAccount, request)
                : isIncoming ? onCreateIncomingConnection(callManagerAccount, request)
                : onCreateOutgoingConnection(callManagerAccount, request);
  • packages/services/Telecomm -- TelephonyConnectionService
    @Override
    public Connection onCreateOutgoingConnection(
            PhoneAccountHandle connectionManagerPhoneAccount,
            final ConnectionRequest request) {
            .......
            placeOutgoingConnection((TelephonyConnection) resultConnection, phone, request);
   }

吼吼惫搏,是不是看到曙光了呢,有木有看到dial()方法蚕涤,快接近曙光了有木有:

    private void placeOutgoingConnection(
            TelephonyConnection connection, Phone phone, int videoState, Bundle extras,
            ConnectionRequest request) {
            ......
            originalConnection = phone.dial(number, null, request.getVideoState(), bundle);
   }
  • frameworks/opt/telephony -- GsmCdmaPhone
    @Override
    public Connection dial(String dialString, UUSInfo uusInfo, int videoState, Bundle intentExtras)
            throws CallStateException {
        if (isPhoneTypeGsm()) {
            return dialInternal(dialString, null, VideoProfile.STATE_AUDIO_ONLY, intentExtras);
        } else {
            return dialInternal(dialString, null, videoState, intentExtras);
        }
    }

接著就是調(diào)用GsmCdmaCallTracker的dial方法:

    @Override
    protected Connection dialInternal(String dialString, UUSInfo uusInfo, int videoState,
                                      Bundle intentExtras)
            throws CallStateException {
            
            return mCT.dial(newDialString);
    }
  • frameworks/opt/telephony -- GsmCdmaCallTracker

mCi就是RIL的實例筐赔,也就是說調(diào)用的是RIL的dial()方法:

 public synchronized Connection dial(String dialString, int clirMode, UUSInfo uusInfo,
                                        Bundle intentExtras)
            throws CallStateException {
        } else {
            // Always unmute when initiating a new call
            setMute(false);

            mCi.dial(mPendingMO.getAddress(), clirMode, uusInfo, obtainCompleteMessage());
        }
        updatePhoneState();
        mPhone.notifyPreciseCallStateChanged();

        return mPendingMO;
  }
  • frameworks/opt/telephony -- RIL

到這就是常見的 > DIAL命令了,有木有:

    @Override
    public void
    dial(String address, int clirMode, UUSInfo uusInfo, Message result) {
        RILRequest rr = RILRequest.obtain(RIL_REQUEST_DIAL, result);

        rr.mParcel.writeString(address);
        rr.mParcel.writeInt(clirMode);

        if (uusInfo == null) {
            rr.mParcel.writeInt(0); // UUS information is absent
        } else {
            rr.mParcel.writeInt(1); // UUS information is present
            rr.mParcel.writeInt(uusInfo.getType());
            rr.mParcel.writeInt(uusInfo.getDcs());
            rr.mParcel.writeByteArray(uusInfo.getUserData());
        }

        if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));

        send(rr);
    }

當(dāng)然上面都是上層的流程揖铜,接著就是RIL層和modem信令的事情嘍茴丰。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市天吓,隨后出現(xiàn)的幾起案子贿肩,更是在濱河造成了極大的恐慌,老刑警劉巖龄寞,帶你破解...
    沈念sama閱讀 206,723評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件汰规,死亡現(xiàn)場離奇詭異,居然都是意外死亡物邑,警方通過查閱死者的電腦和手機(jī)溜哮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來色解,“玉大人茂嗓,你說我怎么就攤上這事】蒲郑” “怎么了述吸?”我有些...
    開封第一講書人閱讀 152,998評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長萧恕。 經(jīng)常有香客問我刚梭,道長肠阱,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,323評論 1 279
  • 正文 為了忘掉前任朴读,我火速辦了婚禮屹徘,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘衅金。我一直安慰自己噪伊,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,355評論 5 374
  • 文/花漫 我一把揭開白布氮唯。 她就那樣靜靜地躺著鉴吹,像睡著了一般。 火紅的嫁衣襯著肌膚如雪惩琉。 梳的紋絲不亂的頭發(fā)上豆励,一...
    開封第一講書人閱讀 49,079評論 1 285
  • 那天,我揣著相機(jī)與錄音瞒渠,去河邊找鬼良蒸。 笑死,一個胖子當(dāng)著我的面吹牛伍玖,可吹牛的內(nèi)容都是我干的嫩痰。 我是一名探鬼主播,決...
    沈念sama閱讀 38,389評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼窍箍,長吁一口氣:“原來是場噩夢啊……” “哼串纺!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起椰棘,我...
    開封第一講書人閱讀 37,019評論 0 259
  • 序言:老撾萬榮一對情侶失蹤纺棺,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后晰搀,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體五辽,經(jīng)...
    沈念sama閱讀 43,519評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,971評論 2 325
  • 正文 我和宋清朗相戀三年外恕,在試婚紗的時候發(fā)現(xiàn)自己被綠了既峡。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片狈孔。...
    茶點故事閱讀 38,100評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡滋迈,死狀恐怖版确,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情尚洽,我是刑警寧澤悔橄,帶...
    沈念sama閱讀 33,738評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響癣疟,放射性物質(zhì)發(fā)生泄漏挣柬。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,293評論 3 307
  • 文/蒙蒙 一睛挚、第九天 我趴在偏房一處隱蔽的房頂上張望邪蛔。 院中可真熱鬧,春花似錦扎狱、人聲如沸侧到。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽匠抗。三九已至,卻和暖如春污抬,著一層夾襖步出監(jiān)牢的瞬間汞贸,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評論 1 262
  • 我被黑心中介騙來泰國打工壕吹, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留著蛙,地道東北人。 一個月前我還...
    沈念sama閱讀 45,547評論 2 354
  • 正文 我出身青樓耳贬,卻偏偏與公主長得像,于是被迫代替她去往敵國和親猎唁。 傳聞我的和親對象是個殘疾皇子咒劲,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,834評論 2 345

推薦閱讀更多精彩內(nèi)容