1. 程式人生 > >android -- phone (二) 去電流程

android -- phone (二) 去電流程

        這篇是關於外撥電話的具體流程,也就是去電流程,雖然網上的資料很多(重複的也很多),但作為電話的主要操作之一,為了保證phone系列的完整性,還是要把它寫一下的。開始看程式碼。

TwelveKeyDialer.java,既然要打電話,總要先輸入號碼才撥出,這個類就是撥號盤的介面,只是這個phone用到的類卻是放在com.android.contacts包下,應該是出於程式碼結構的考慮吧。畢竟聯絡人、撥號盤、通話記錄和收藏都是在一個Tab標籤裡的。這個介面沒什麼好說的,0-9數字鍵,P和W(也可能是*和#),P表示直接撥打帶有分機號的號碼(如2345-0000P1234)時會直接撥分機號,無需要再輸入分機號碼,而W撥號(如23450000W1234)則會有對話方塊提示你確認是否撥分機號,就這點區別。

       按完電話號碼點撥號鍵,接下來就開始去電流程了,離開TwelveKeyDialer.java前的程式碼,

    void dialButtonPressed(String ipPrefix) {
        Log.d(TAG, "dialButtonPressed");   
        //注意這個action,只有從撥號盤撥出的才是Intent.ACTION_CALL_PRIVILEGED
        //外部呼叫的是Intent.ACTION_CALL
        Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED);  
        //省略次要程式碼……
        intent.setData(Uri.fromParts("tel", number, null));//拼一個電話的Uri,關鍵程式碼
        StickyTabs.saveTab(this, getIntent());//與撥號無關
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        startActivity(intent);
        mDigits.getText().clear(); //清除輸入
     }

         發出來的Intent誰來接收呢,是OutgoingCallBroadcaster.java類,這是一箇中間類,實際我們是看不到它的,這個類先判斷號碼是否為緊急號碼,如果是緊急號碼,啟動InCallScree.java,併發送廣播;如果不是,傳送廣播“Intent.ACTION_NEW_OUTGOING_CALL”由它的內部類OutgoingCallReceiver接收,從onReceiver()再到doReceiver(),會把action裡的字串統一替換成Intent.ACTION_CALL,啟動InCallScreen.java,在這個類也可以做一些其它的業務邏輯判斷(比如固定撥號,視訊電話等),IncallScreen.java是電話應用的主介面,這個介面負責的東西比較多,所需要做的判斷也不少,值得一提的是它重寫finish()方法,當呼叫這個方法時它又把自己放回棧中,這樣可提高下次啟動的響應速度。

    如果是第一次進入IncallScreen,會執行onCreate()
    protected void onCreate(Bundle icicle) {
        Profiler.callScreenOnCreate();//獲得通話介面被建立的時間。
        …..省略程式碼...       
        setPhone(app.phone);  // Sets mPhone
        mCM =  PhoneApp.getInstance().mCM;
        mBluetoothHandsfree = app.getBluetoothHandsfree();//設定藍芽
        if (mBluetoothHandsfree != null) {           
            mBluetoothHeadset = new BluetoothHeadset(this, null); 
        }
        initInCallScreen();   //載入介面元素
     …..省略程式碼...   
        registerForPhoneStates();//註冊各種狀態
        if (icicle == null) {            
    mInCallInitialStatus = internalResolveIntent(getIntent());  //這個是關鍵程式碼         
        } else {
            mInCallInitialStatus = InCallInitStatus.SUCCESS;
        }
        mUseTouchLockOverlay = !app.proximitySensorModeEnabled();
        Profiler.callScreenCreated();//記錄通話介面建立完成後的時間
    }
    如果是第二次進入會執行onNewIntent()
    protected void onNewIntent(Intent intent) {      
        mInCallInitialStatus = internalResolveIntent(intent);//這個是關鍵程式碼
        ……去掉log程式碼
    }

        兩個方法都會走到internalResolveIntent(intent),這裡我們關心電話撥出的動作是怎麼跑下去的,所以InCallScreen裡的onResume()方法就不細看了,那裡面有關於鎖屏和藍芽連線等邏輯判斷。回來繼續看internalResolveIntent()的程式碼。

    InCallInitStatus internalResolveIntent(Intent intent) {
        ……省略很多程式碼…   傳過來的action是Intent.ACTION_CALL,直接看重點
        } else if (action.equals(Intent.ACTION_CALL)
                || action.equals(Intent.ACTION_CALL_EMERGENCY)) {
            app.setRestoreMuteOnInCallResume(false);
            if (PhoneUtils.hasPhoneProviderExtras(intent)) {               
            InCallInitStatus status = placeCall(intent);//這是我們要找的
            if (status == InCallInitStatus.SUCCESS) {        
                app.setBeginningCall(true);
            }
            return status;
        } }

        進入placeCall()方法後,會做一些關於是否緊急號碼和OTA(Over-the-Air Technology空中下載技術,是通過行動通訊(GSM或CDMA)的空中介面對SIM卡資料及應用進行遠端管理的技術)的判斷,不過我們更關心下面的程式碼。

    if (null != mProviderGatewayUri && !(isEmergencyNumber || isEmergencyIntent) &&
         PhoneUtils.isRoutableViaGateway(number)) {  
         callStatus = PhoneUtils.placeCallVia(this, phone, number,
           contactUri, mProviderGatewayUri);//多了個閘道器引數
    } else {
         callStatus = PhoneUtils.placeCall(phone, number, contactUri);//多數呼叫這裡
    }

       後面switch分支會根據callStatus的值完成相應的功能或提示。到這裡到程式碼就走到PhoneUtils.java裡了,placeCall()是一個靜態方法呼叫。裡面最重要的程式碼是     

    Connection cn = PhoneApp.getInstance().mCM.dial(phone, number);
    //也有可能看到下面的程式碼
    Connection cn = phone.dial(number)//如果是這種,會先走到GsmCallTracker.java

        順便提下在這個方法裡還會有setAudioMode(),關於音訊通道和模式的設定就在這裡,這裡面的故事也不少,不過要先放放了。下面的程式碼已經在Framework層了,不管之前的程式碼在哪裡dial,最後都會來到RIL.java。        

     public void dial(String address, int clirMode, UUSInfo uusInfo, Message result) {
        RILRequest rr = RILRequest.obtain(RIL_REQUEST_DIAL, result);
        rr.mp.writeString(address);
        rr.mp.writeInt(clirMode);
        rr.mp.writeInt(0); // UUS information is absent

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

       跟著RIL_REQUEST_DIAL這個TAG標誌向下走來到Reference-ril.c找到相應的case分支,

   case RIL_REQUEST_DIAL:
            requestDial(data, datalen, t);

   static void requestDial(void *data, size_t datalen, RIL_Token t){
    p_dial = (RIL_Dial *)data;

    switch (p_dial->clir) {
        case 1: clir = "I"; break;  /*invocation*/
        case 2: clir = "i"; break;  /*suppression*/
        default:
        case 0: clir = ""; break;   /*subscription default*/
    }
    asprintf(&cmd, "ATD%s%s;", p_dial->address, clir); 
    ret = at_send_command(cmd, NULL);
    free(cmd);  
    RIL_onRequestComplete(t, RIL_E_SUCCESS, NULL, 0);
   }

      到這裡我們的電話基本上算是撥出去了,至於是否成功還要看返回結果,通常形式上的流程是這樣的(下面只是很多可能中的一種,也是比較常見的一種),

    ===>>[SendAT] ATD15812345678 //撥號,看到atd是不是會想到貝爾實驗室和AT&T呢?
    <<====[RecvAT] OK
    <<====[RecvAT] +CLCC: 1, 0, 2, 0, 0, "15812345678", 129, //主動上報clcc
    ===>>[SendAT] AT+CMUT=0, time=   //設定話簡靜音關
    <<====[RecvAT] OK, time=
     ===>>[SendAT] AT+CLCC           //主動查詢,
    <<====[RecvAT] +CLCC: 1, 0, 2, 0, 0, "15812345678", 129, //以這一次的clcc為準
        這段程式碼描述這樣一個事實,不管模組報上來CLCC資料如何,我們都會重新查詢後再上報給應用層,上層收到訊息後更新介面和維護Connection裡每一路電話的狀態。到這一步,去電的流程也算是基本走完了,後面還有些介面狀態重新整理的程式碼省略了,對於AT命令那一部分,具體實現要看晶片廠商,表現上不完全一致。關於去電的流程就寫這些,儘量寫詳細點,不過也有點長了,如仍有遺漏的地方,歡迎補充。