1. 程式人生 > >(O)Telephony分析之通話流程分析(二)撥打電話流程分析(上)

(O)Telephony分析之通話流程分析(二)撥打電話流程分析(上)

撥打電話,是從通話的撥號盤開始的,而撥號盤的介面是在DialpadFragment,因此,需要從這個地方進行分析

一.撥號盤介面撥號流程

public void onClick(View view) {
    ......
    if (resId == R.id.dialpad_floating_action_button) {
        view.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
        // 處理Dial Button點選事件
        handleDialButtonPressed();
    }
    ......
}
通過呼叫handleDialButtonPressed方法,處理點選撥號按鈕後的流程
private void handleDialButtonPressed() {
	// 沒有輸入號碼的情況下,暫不討論
    if (isDigitsEmpty()) { // No number entered.
        handleDialButtonClickWithEmptyDigits();
    } else {
    	......
      // uri加上tel字首,並且type設定為INITIATION_DIALPAD,生成URI
      final Intent intent = new CallIntentBuilder(number).setCallInitiationType(LogState.INITIATION_DIALPAD).build();
            // Leo, 嘗試去啟動一個Activity,如果啟動不了,會彈出一個 "Activity is not found" 的Toast框
      DialerUtils.startActivityWithErrorToast(getActivity(), intent);
      // Leo,隱藏撥號盤,並且清除號碼盤上的號碼
      hideAndClearDialpad(false);  
    }
}
可以看到此處通過初始化CallIntentBuilder,然後呼叫其build方法,生成一個Intent物件,並作為引數,呼叫DialerUtils的startActivityWithErrorToast方法
public static class CallIntentBuilder {
    ......
    public CallIntentBuilder(Uri uri) {
        mUri = uri;
    }

    public CallIntentBuilder(String number) {
        this(CallUtil.getCallUri(number));
    }

    public CallIntentBuilder setCallInitiationType(int initiationType) {
        mCallInitiationType = initiationType;
        return this;
    }

    public Intent build() {
    	// Leo, 初始化一個Intent
        return getCallIntent(
                mUri,
                mPhoneAccountHandle,
                mIsVideoCall ? VideoProfile.STATE_BIDIRECTIONAL : VideoProfile.STATE_AUDIO_ONLY,
                mCallInitiationType);
    }
}
從這邊來看,首先通過構造方法,將number生成一個Uri,然後通過build方法呼叫getCallIntent方法,返回一個Intent,而通過上述瞭解,
知道getCallIntent的引數分別為,
第一個引數為剛剛生成的Uri物件,其是以tel:為字首的
第二個引數由於並未設定過,因此為null
第三個引數由於沒有設定過視訊通話,因此為VideoProfile.STATE_AUDIO_ONLY,語音通話
第四個引數為傳入的LogState.INITIATION_DIALPAD
public static Intent getCallIntent(
        Uri uri, PhoneAccountHandle accountHandle, int videoState, int callIntiationType) {
	// 初始化一個Intent,action為CALL_ACTION,Data為uri
    final Intent intent = new Intent(CALL_ACTION, uri);
    // 目前應該是沒有videoState的,即為VideoProfile.STATE_AUDIO_ONLY
    intent.putExtra(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, videoState);

    final Bundle b = new Bundle();
    // 設定其INITIATION_TYPE,此處第一次為LogState.INITIATION_DIALPAD
    b.putInt(EXTRA_CALL_INITIATION_TYPE, callIntiationType);
    // 設定其Extra
    intent.putExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, b);

    // 此前沒有設定,應該為null
    if (accountHandle != null) {
        intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, accountHandle);
    }

    return intent;
}
確認Intent中所包含的資料
接下來會呼叫DialerUtils的startActivityWithErrorToast方法
public static void startActivityWithErrorToast(Context context, Intent intent) {
    startActivityWithErrorToast(context, intent, R.string.activity_not_available);
}
public static void startActivityWithErrorToast(Context context, Intent intent, int msgId) {
    try {
        if ((IntentUtil.CALL_ACTION.equals(intent.getAction())
                        && context instanceof Activity)) {
            ......
            // 呼叫TelecomUtil的placeCall函式,傳入的引數為intent
            final boolean hasCallPermission = TelecomUtil.placeCall((Activity) 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);
            }
        }
        ......
    } catch (ActivityNotFoundException e) {
        Toast.makeText(context, msgId, Toast.LENGTH_SHORT).show();
    }
}
呼叫TelecomUtil的placeCall方法
二.Telecom階段
public static boolean placeCall(Activity activity, Intent intent) {
    if (hasCallPhonePermission(activity)) {
    	// 呼叫TelecomManagerCompat的placeCall函式,注意其傳入的引數
    	// 第一個引數為DialpadFragment中的getActivity
    	// 第二個引數為TelecomManager物件
    	// 第三個引數中包含了撥號的所有引數
        TelecomManagerCompat.placeCall(activity, getTelecomManager(activity), intent);
        return true;
    }
    return false;
}
(一)getTelecomManager是什麼
看下其程式碼
private static TelecomManager getTelecomManager(Context context) {
    return (TelecomManager) context.getSystemService(Context.TELECOM_SERVICE);
}
此前,我們有對getSystemService進行過分析,是在SystemServiceRegistry中進行註冊的,如下:
registerService(Context.TELECOM_SERVICE, TelecomManager.class,
                new CachedServiceFetcher<TelecomManager>() {
						    @Override
						    public TelecomManager createService(ContextImpl ctx) {
						        return new TelecomManager(ctx.getOuterContext());
						    }});
其返回的是新初始化的TelecomManager物件,再看下TelecomManager的構造方法
public TelecomManager(Context context) {
    this(context, null);
}
public TelecomManager(Context context, ITelecomService telecomServiceImpl) {
    Context appContext = context.getApplicationContext();
    if (appContext != null) {
        mContext = appContext;
    } else {
        mContext = context;
    }
    mTelecomServiceOverride = telecomServiceImpl;
    android.telecom.Log.initMd5Sum();
}
因此,此處的TelecomManager物件中的telecomServiceImpl為null
(二)TelecomManagerCompat的placeCall方法分析
public static void placeCall(@Nullable Activity activity,
        @Nullable TelecomManager telecomManager, @Nullable Intent intent) {
    ......
    // Android M以上的版本
    if (CompatUtils.isMarshmallowCompatible()) {
    	// 呼叫了TelecomManager物件的placeCall函式
    	// 第一個引數為包含號碼的URI
        telecomManager.placeCall(intent.getData(), intent.getExtras());
        return;
    }
    activity.startActivityForResult(intent, 0);
}
由於是O專案,高於M專案,因此呼叫的是telecomManager的placeCall方法,而telecomManager是剛剛看到的TelecomManager物件,因此
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);
        }
    }
}
從此前的分析,TelecomManager的getTelecomService方法,在telecomServiceImpl為null的時候,返回的是TelecomManagerImpl的mBinderImpl
因此,呼叫了mBinderImpl的placeCall
public void placeCall(Uri handle, Bundle extras, String callingPackage) {
    try {
        ......
        // 判斷是否擁有撥打電話的許可權
        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);
                }
                // mUserCallIntentProcessorFactory的create方法是在TelecomSystem的構造方法中重寫的
                // 返回的是UserCallIntentProcessor物件
                mUserCallIntentProcessorFactory.create(mContext, userHandle)
                        .processIntent(
                                intent, callingPackage, isSelfManaged ||
                                        (hasCallAppOp && hasCallPermission));
            } finally {
                Binder.restoreCallingIdentity(token);
            }
        }
    }
    ......
}
呼叫mUserCallIntentProcessorFactory的create方法,並且呼叫返回物件的processIntent方法,那麼mUserCallIntentProcessorFactory是什麼呢?
檢視其定義的地方
public TelecomServiceImpl(
            Context context,
            CallsManager callsManager,
            PhoneAccountRegistrar phoneAccountRegistrar,
            CallIntentProcessor.Adapter callIntentProcessorAdapter,
            UserCallIntentProcessorFactory userCallIntentProcessorFactory,
            DefaultDialerCache defaultDialerCache,
            SubscriptionManagerAdapter subscriptionManagerAdapter,
            TelecomSystem.SyncRoot lock) {
        ......
        mUserCallIntentProcessorFactory = userCallIntentProcessorFactory;
        ......
    }
也就是說,這個是由TelecomServiceImpl物件在初始化的時候,傳入的,那麼TelecomServiceImpl是在什麼地方初始化的呢?
還記得前篇中關於TelecomSystem的初始化麼?在該類的構造方法中,可以看到
mTelecomServiceImpl = new TelecomServiceImpl(
        mContext, mCallsManager, mPhoneAccountRegistrar,
        new CallIntentProcessor.AdapterImpl(),
        new UserCallIntentProcessorFactory() {
            @Override
            public UserCallIntentProcessor create(Context context, UserHandle userHandle) {
                return new UserCallIntentProcessor(context, userHandle);
            }
        },
        defaultDialerCache,
        new TelecomServiceImpl.SubscriptionManagerAdapterImpl(),
        mLock);
也就是說,UserCallIntentProcessorFactory的create方法中,初始化了一個UserCallIntentProcessor物件,因此
上述的TelecomManager的placeCall方法,最終是呼叫了UserCallIntentProcessor物件的processIntent方法
public void processIntent(Intent intent, String callingPackageName,
        boolean canCallNonEmergency) {
    ......
    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);
    }
}
由於此前傳入的Intent的ACTION為Intent.ACTION_CALL,因此會呼叫processOutgoingCallIntent,此處我們需要注意的是,其傳入的引數是什麼
第一個引數為帶有資料的Intent,這個無需多言
第二個引數為callingPackageName,是在TelecomManager中使用mContext.getOpPackageName()得到的,而mContext是由DialpadFragment中使用getActivity得到的,因此,它的值就一目瞭然了
第三個引數為canCallNonEmergency,是在TelecomServiceImpl中定義的,這個無需多言了
接下來,繼續分析程式碼processOutgoingCallIntent
private void processOutgoingCallIntent(Intent intent, String callingPackageName,
            boolean canCallNonEmergency) {
        Uri handle = intent.getData(); // tel:號碼
        String scheme = handle.getScheme(); // tel
        String uriString = handle.getSchemeSpecificPart();

        ......

        // 通話狀態,預設是語音通話
        int videoState = intent.getIntExtra(
                TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
                VideoProfile.STATE_AUDIO_ONLY);
        Log.d(this, "processOutgoingCallIntent videoState = " + videoState);

        // Leo, 傳入的package是否為系統預設的Dialer應用
        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);
    }
這個mUserHandle,追述到上面,可以看到是在TelecomServiceImpl中進行定義的,具體的程式碼為
final UserHandle userHandle = Binder.getCallingUserHandle();
暫時不提
private boolean sendBroadcastToReceiver(Intent intent) {
	// 非incoming call
    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;
}
可以看到,其實是將通話的基本資訊傳送給PrimaryCallReceiver進行處理
public void onReceive(Context context, Intent intent) {
    ......
    synchronized (getTelecomSystem().getLock()) {
    	// 接收到訊息後處理,呼叫TelecomSystem的getCallIntentPRocessor方法,獲取CallIntentProcessor物件
    	// 並且呼叫起的processIntent方法
        getTelecomSystem().getCallIntentProcessor().processIntent(intent);
    }
    ......
}
public TelecomSystem getTelecomSystem() {
    return TelecomSystem.getInstance();
}
這個TelecomSystem的getInstance方法,返回了一個TelecomSystem物件,就是我們前篇所瞭解到的TelecomSystem物件,那麼
public CallIntentProcessor getCallIntentProcessor() {
    return mCallIntentProcessor;
}
TelecomSystem的建構函式中
mCallIntentProcessor = new CallIntentProcessor(mContext, mCallsManager);
初始化CallIntentProcessor物件,並呼叫其processIntent方法
public void processIntent(Intent intent) {
	// 預設false
    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方法,注意其引數mCallsManager是在TelecomSystem中初始化CallIntentProcessor時傳入的


















相關推薦

OTelephony分析通話流程分析撥打電話流程分析

撥打電話,是從通話的撥號盤開始的,而撥號盤的介面是在DialpadFragment,因此,需要從這個地方進行分析一.撥號盤介面撥號流程 public void onClick(View view) { ...... if (resId == R.id.dia

Android5.1 Telephony流程分析——撥打電話流程MO CALL

本文程式碼以MTK平臺Android 5.1為分析物件,與Google原生AOSP有些許差異,請讀者知悉。 此圖主要是根據Android原始碼撥打電話流程來繪製,記錄了電話撥打的主要過程: 參考部

[轉載]PM和程式設計師RD的相處道--寫給那些血氣方剛的產品經理PM

最近有位剛做 PM(產品經理)的小夥跑來跟我控訴,說公司技術部的 RD 們(程式設計師)個個不給力。需求過了千百遍還是理解錯,或者就是簡單回一句“做不了”,表情如死灰。這位 PM 血氣方剛,張牙舞抓,腦子裡總有一千萬個新產品需求的想法撲騰著。他咄咄不停的抱怨 RD 們不配合,能力差,懶惰,沒思考能力,沒品位,

高通sensor架構例項分析三(adsp上報資料詳解、校準流程詳解)

本系列導航: 從adsp獲取資料的方法分為同步、非同步兩種方式,但一般在實際使用中使用非同步方式,因為同步獲取資料會因外設匯流排速率低的問題阻塞smgr,降低效率,增加功耗。 Sensor上報資料的方式分為如下幾種 sync          同步資料上報,(每次上報一個數據) async      

android N 撥打電話流程MO

本流程圖基於MTK平臺 Android 7.0,撥打的普通電話,本流程只作為溝通學習使用 整體流程圖 流程中部分重點知識 packages-apps目錄 dialer應用的DialpadFragment.onClick中,通過使用者

android6.0原始碼分析Camera API2.0下的Preview(預覽)流程分析

本文將基於android6.0的原始碼,對Camera API2.0下Camera的preview的流程進行分析。在文章andro

Stanford CS231n實踐筆記課時22卷積神經網絡工程實踐技巧與註意點 cnn in practise

self ash 2gb ble 一個 time 縮放 對比度 cati 本課主要2個實踐內容:1、keras中數據集豐富,從數據集中提取更多特征(Data augmentation)2、遷移學習(Tranform learning)代碼:https://github.co

Ijkplayer播放器源碼分析音視頻輸出()——音頻篇

andro c中 table 播放器 handle att mut wake 判斷 Ijkplayer播放器源碼分析之音視頻輸出(二)——音頻篇 這篇文章的ijkplayer音頻源碼研究我們還是選擇Android平臺,它的音頻解碼是不支持硬解的,音頻播放使用的API是Ope

死磕Netty原始碼記憶體分配詳解()PoolArena記憶體分配結構分析

前言 在應用層通過設定PooledByteBufAllocator來執行ByteBuf的分配,但是最終的記憶體分配工作被委託給PoolArena。由於Netty通常用於高併發系統所以各個執行緒進行記憶體分配時競爭不可避免,這可能會極大的影響記憶體分配的效率,為

Android Wi-Fi原始碼分析WifiService操作Wi-Fi():WifiStateMachine.java中的SUP_CONNECTION_EVENT分析

Wi-Fi原始碼分析之WifiService操作Wi-Fi(二) 一. SupplicantStartingState中的的processMessage方法分析 public boolean processMessage(Messag

Spring原始碼分析BeanPostProcessor介面和BeanFactoryPostProcessor介面方法不執行原因分析

首先下面是我的Bean /* * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License

ios學習筆記-點選一個按鈕彈出撥打電話提示框

按鈕的程式碼就不寫了。直接寫主要程式碼。 <key>LSApplicationQueriesSchemes</key> <array> <string>tel</string> <string>telp

Android VOIP撥打電話機制分析

建立.aidl檔案 ISipService.aidl內容如下: [java] view plaincopyprint? /**   * Copyright (C) 2010-2012 Regis Montoya (aka r3gis 

HBase源代碼分析HRegionMemStore的flsuh流程

初始化 back represent 代碼分析 讀數 ott pass expect 出現異常 繼上篇《HBase源代碼分析之HRegion上MemStore的flsuh流程(一)》之後。我們繼續分析下HRegion上MemStore flush的核心方

Android O Settings原始碼流程分析搜尋欄篇

Android O Settings  靜態介面篇 介面渲染篇 資料載入篇之一級選單 資料載入篇之二級選單 資料載入篇之獲取及修改預設設定屬性值 搜尋欄篇 Settings 搜尋欄 上篇——介面 中篇——實現原理 下篇—

HBase原始碼分析HRegioncompact流程分析

  2016年03月03日 21:38:04 辰辰爸的技術部落格 閱讀數:2767 版權宣告:本文為博主原創文章,未經博主允許不得轉載。 https://blog.csdn.net/lipeng_bigdata/article/details/50791205

大資料storm --- storm簡介,核心元件,工作流程,安裝和部署,電話通訊案例分析,叢集執行,單詞統計案例分析,調整併發度

一、storm簡介 --------------------------------------------------------- 1.開源,分散式,實時計算 2.實時可靠的處理無限資料流,可以使用任何語言開發 3.適用於實時分析,線上機器學習

Open vSwitchOvS原始碼分析工作流程flow流表查詢

前面分析了Open vSwitch幾部分原始碼,對於Open vSwitch也有了個大概的理解,今天要分析的程式碼將是整個Open vSwitch的重中之重。整個Open vSwitch的核心程式碼在datapath檔案中;而datapath檔案中的核心程式碼又在ovs_dp_process_re

以太坊原始碼分析 P2P網路、節點發現流程

區塊鏈特輯 :https://blog.csdn.net/fusan2004/article/details/80879343,歡迎查閱,原創作品,轉載請標明!上一篇文章簡單介紹了下一些基礎的型別定義,從這一篇開始我們將描述p2p網路的更多細節。從關於節點的定義來看,其實不同

Yarn原始碼分析MapReduce作業中任務Task排程整體流程

        v2版本的MapReduce作業中,作業JOB_SETUP_COMPLETED事件的發生,即作業SETUP階段完成事件,會觸發作業由SETUP狀態轉換到RUNNING狀態,而作業狀態轉換中涉及作業資訊的處理,是由SetupCompletedTransition