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命令那一部分,具體實現要看晶片廠商,表現上不完全一致。關於去電的流程就寫這些,儘量寫詳細點,不過也有點長了,如仍有遺漏的地方,歡迎補充。