OPhone系統(tǒng)之電話功能詳解
概述
支持OPhone系統(tǒng)完成基本電話功能的底層硬件基礎(chǔ)是:無線通訊模塊(例如:GSM/GPRSmodem),而在其之上的抽象——無線接口層(RIL:RadioInterfaceLayer)是本文討論的相關(guān)API的靈魂,它完成了對基本電話功能與Radio硬件之間的抽象,負(fù)責(zé)提供數(shù)據(jù)的可靠傳輸、AT命令發(fā)送,以及命令回應(yīng)(response)的解析等工作。它是通訊網(wǎng)絡(luò)無關(guān)的,共包含兩個(gè)基本部件:RIL守護(hù)進(jìn)程(RILDaemon)和RIL廠商專用實(shí)現(xiàn)(VendorRIL)。前者負(fù)責(zé)初始化VendorRIL實(shí)例,并管理來自應(yīng)用層API的調(diào)用——將其轉(zhuǎn)化為“主動(dòng)請求命令”分派給VendorRIL實(shí)現(xiàn);而后者是具體無線通訊網(wǎng)絡(luò)的專用實(shí)現(xiàn),掌管并驅(qū)動(dòng)著無線網(wǎng)絡(luò)硬件模塊的通訊工作,并把“被動(dòng)請求命令”上報(bào)給RIL守護(hù)進(jìn)程,從而達(dá)成網(wǎng)絡(luò)通訊。
本文將從撥打電話、接聽電話、發(fā)送短消息等多個(gè)基本功能出發(fā),向大家介紹相關(guān)API的具體使用方法。
電話功能
OPhone系統(tǒng)本身有內(nèi)置的電話應(yīng)用(Phone.apk)提供撥打和接聽電話的能力,它提供了虛擬數(shù)字鍵盤幫助用戶撥號(hào)并發(fā)起呼叫。當(dāng)有來電呼入時(shí),它會(huì)振鈴提示并顯示來電信息提示界面,用戶通過提示界面上的功能按鈕,接聽或者拒絕來電。但對于開發(fā)者而言,我們真正關(guān)心的是如何通過API調(diào)用這些基本的電話功能?
首先,讓我們一起來看與撥打電話相關(guān)的功能吧。撥打電話是用戶自主發(fā)起的動(dòng)作,所以是“主動(dòng)請求命令”,它首先在JavaAPI層通過Socket與RIL守護(hù)進(jìn)程建立連接并發(fā)送請求命令,然后命令被轉(zhuǎn)發(fā)至VendorRIL實(shí)現(xiàn),并且最終建立通話呼叫的網(wǎng)絡(luò)鏈接。這是底層的原理和流程,如何通過API實(shí)現(xiàn)呢?#t#
還得先從OPhone系統(tǒng)中的Intent(意圖)說起,“意圖”被用來描述和表達(dá)某個(gè)要求或者目地,針對撥打電話的意圖,有兩個(gè)與之相關(guān)的Intent常量可以使用:Intent.ACTION_CALL和Intent.ACTION_DIAL,區(qū)別在于前者會(huì)導(dǎo)航到數(shù)字撥號(hào)鍵盤,后者直接進(jìn)行呼叫,具體請看示例代碼:
- //取得目標(biāo)號(hào)碼
- String number = editText.getText().toString();
- Uri data = Uri.parse("tel:" + number);
- //調(diào)用撥打電話功能
- if (v.getId() == R.id.btn_dial) {
- //進(jìn)入撥號(hào)界面
- Intent intent = new Intent(Intent.ACTION_DIAL, data);
- intent.addCategory(Intent.CATEGORY_DEFAULT);
- startActivity(intent);
- } else if (v.getId() == R.id.btn_call) {
- //直接進(jìn)行呼叫
- Intent intent = new Intent(Intent.ACTION_CALL, data);
- intent.addCategory(Intent.CATEGORY_DEFAULT);
- startActivity(intent);
- }
代碼非常簡單,關(guān)鍵是使用Intent.ACTION_DIAL或Intent.ACTION_CALL做為Intent的action名稱,并把Uri類型的data數(shù)據(jù)作為目標(biāo)電話號(hào)碼附加到Intent中。
除直接撥打電話之外,還有一種需求特別常見——即捕獲撥打電話的動(dòng)作并獲取目標(biāo)電話號(hào)碼,以便做出進(jìn)一步的具體處理。要實(shí)現(xiàn)該需求還得先了解一下背后的故事:在OPhone系統(tǒng)中有一種被稱為廣播接收器(BroadcastReceiver)的應(yīng)用程序類型,它會(huì)選擇接收某個(gè)“頻道”上的廣播數(shù)據(jù)并做出相應(yīng)處理,廣播數(shù)據(jù)的產(chǎn)生源頭可以是系統(tǒng)中的任何應(yīng)用程序。對“撥打電話”而言,廣播數(shù)據(jù)在Intent.ACTION_NEW_OUTGOING_CALL頻道上,所以要監(jiān)聽該動(dòng)作,就需要編寫廣播接收器,并將其指向以上的特定頻道。
請看示例代碼:
- public class OutgoingCallReceiver extends BroadcastReceiver {
- // logger name
- private static final String tag = "OutgoingCallReceiver";
- @Override
- public void onReceive(Context context, Intent intent) {
- // 監(jiān)聽到撥打電話動(dòng)作,并獲取目標(biāo)電話號(hào)碼
- String name = Intent.EXTRA_PHONE_NUMBER;
- String phoneNumber = intent.getStringExtra(name);
- Log.d(tag, "Outgoing call -> " + phoneNumber);
- // TODO:you code....
- }
- }
然后,再將以上廣播接收器申明在AndroidManifest.xml文件中:
- <receiver android:name="OutgoingCallReceiver">
- <intent-filter>
- <action android:name=
- "android.intent.action.NEW_OUTGOING_CALL"/>
- <category android:name="android.intent.category.DEFAULT"/>
- </intent-filter>
- </receiver>
其中action.name指向的正是常量Intent.ACTION_NEW_OUTGOING_CALL的字符串字面值。當(dāng)再次撥號(hào)時(shí),無論使用上文介紹的API或通過內(nèi)置的電話應(yīng)用程序,該接收器都會(huì)自動(dòng)捕捉到并且獲取目標(biāo)電話號(hào)碼。
再進(jìn)一步考慮,假設(shè)需要將某特定號(hào)碼屏蔽掉——禁止用戶撥打該號(hào)碼,要怎么才能做到呢?其實(shí),只需要在廣播接收器的實(shí)現(xiàn)代碼中,有選擇的終止廣播數(shù)據(jù)傳播即可。請看示例代碼:
- //禁止向[139999999]號(hào)碼撥打電話
- String target = "139999999";
- if (phoneNumber.equalsIgnoreCase(target)) {
- // 終止廣播數(shù)據(jù)的傳播
- abortBroadcast();
- // 顯示提示信息
- String msg = "號(hào)碼[" + target + "]被禁止~!";
- Toast.makeText(context, msg, Toast.LENGTH_LONG).show();
- }
需要注意的是,并不是所有廣播都可以終止,只有有序廣播(Orderedbroadcasts)才能夠被終止。什么是有序廣播呢?#p#
讓我們來回顧一下關(guān)于BroadcastReceiver的基礎(chǔ)知識(shí):廣播被分為兩種不同的類型:“普通廣播(Normalbroadcasts)”和“有序廣播(Orderedbroadcasts)”。前者是完全異步的,所有接收器(邏輯上)都在同一時(shí)刻運(yùn)行,對消息傳遞的效率而言這是很好的做法,但缺點(diǎn)是:接收器不能返回結(jié)果并且無法終止廣播數(shù)據(jù)的傳播;然而后者是逐個(gè)的執(zhí)行接收器——系統(tǒng)會(huì)按照接收器申明的優(yōu)先級別(申明在intent-filter元素的android:priority屬性中),按順序逐次執(zhí)行。OPhone系統(tǒng)定義:使用Context.sendBroadcast方法發(fā)起的都是普通廣播,而有序廣播必須使用另一個(gè)方法:Context.sendOrderedBroadcast。幸運(yùn)的是“向外撥打電話”時(shí)系統(tǒng)發(fā)出的正是有序廣播,因此我們可以輕松地阻止它。
繼續(xù)深入“接聽電話”功能,它是用戶被動(dòng)響應(yīng)的活動(dòng),所以屬于“被動(dòng)請求命令”。來電首先會(huì)被VendorRIL實(shí)現(xiàn)模塊發(fā)現(xiàn),然后上報(bào)給RIL守護(hù)進(jìn)程,由守護(hù)進(jìn)程再向更高抽象層次的應(yīng)用程序報(bào)告,最終將依次觸發(fā)振鈴提示、顯示來電信息界面等一系列的動(dòng)作。我們關(guān)注的焦點(diǎn)依然是:如何監(jiān)聽到來電并獲取電話號(hào)碼?解決該問題的方法之一是,通過系統(tǒng)服務(wù)Context.TELEPHONY_SERVICE注冊關(guān)注電話狀態(tài)變化的監(jiān)聽器,從而監(jiān)測到來電信息。
OPhone系統(tǒng)提供了PhoneStateListener對象做為監(jiān)聽器的抽象,它是用于即時(shí)監(jiān)測:服務(wù)狀態(tài)、信號(hào)強(qiáng)度、消息等待指示等各方面有關(guān)電話功能狀態(tài)變化的回調(diào)方法機(jī)制。想要監(jiān)測來電呼叫,PhoneStateListener的onCallStateChanged方法是入口點(diǎn),它把電話呼叫狀態(tài)分為三種類型:空閑(IDLE)、振鈴(RINGING)和摘機(jī)(OFFHOOK),其中振鈴狀態(tài)正是來電呼入的標(biāo)志,因此具體的方法是:重新實(shí)現(xiàn)PhoneStateListener對象的onCallStateChanged方法,并關(guān)注RINGING狀態(tài)。請看示例代碼:
- class MyPhoneStateListener extends PhoneStateListener {
- public void onCallStateChanged(int state, String incoming) {
- switch (state) {
- case TelephonyManager.CALL_STATE_RINGING:
- // Ringing-振鈴,有電話呼入
- Log.d(tag, "RINGING~");
- Log.d(tag, "獲得來電號(hào)碼:" + incoming);
- // TODO:YOU CODE
- break;
- case TelephonyManager.CALL_STATE_OFFHOOK:
- // Offhook-摘機(jī),呼出電話已接通或呼入電話已接起
- Log.d(tag, "OFFHOOK~");
- break;
- case TelephonyManager.CALL_STATE_IDLE:
- // IDLE-空閑,結(jié)束通話狀態(tài)
- Log.d(tag, "IDLE~");
- break;
- }
- }
- }
然后,再將該監(jiān)聽器對象注冊到系統(tǒng)服務(wù)TelephonyManager中,以下示例代碼演示了注冊和解除監(jiān)聽器的方法:
- //取得系統(tǒng)服務(wù):TelephonyManager
- String sname = Context.TELEPHONY_SERVICE;
- TelephonyManager tm = (TelephonyManager)
- getSystemService(sname);
- //注冊監(jiān)聽器
- phoneStateListener = new MyPhoneStateListener();
- tm.listen(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
- Log.d(tag, "listen ok~!");
- // 解除監(jiān)聽器
- tm.listen(phoneStateListener, PhoneStateListener.LISTEN_NONE);
- Log.d(tag, "listen cancel~!");
系統(tǒng)服務(wù)TelephonyManager位于android.telephony包下,其listen方法用于注冊和解除自定義的監(jiān)聽器對象,注冊與解除的區(qū)別體現(xiàn)在listen方法的第二個(gè)參數(shù)——int類型的標(biāo)志(flag)位上。
遺憾的是,雖然我翻閱了大量文檔,但仍未能找到在監(jiān)聽器中拒絕來電的方法——即如何拒接的問題,有興趣的讀者請繼續(xù)研究。
短消息功能
除電話功能之外,短消息功能也是特別常用的基本電話功能。同樣在OPhone系統(tǒng)中內(nèi)置有短消息應(yīng)用(Mms.apk),提供了編寫、發(fā)送、接收和閱讀短消息等功能。本文將要討論的重點(diǎn)是:在API級別使用短消息功能的具體方法。
通過API發(fā)送短消息的代理是android.telephony包下的SmsManager類,它提供的getDefault()靜態(tài)方法可以獲得對象實(shí)例,sendTextMessage方法用于發(fā)送短消息,sendMultipartTextMessage方法用于發(fā)送多條短信。具體用法請看示例代碼:
- // 準(zhǔn)備短消息內(nèi)容及相關(guān)信息
- String to = "5554";// 目標(biāo)號(hào)碼
- String from = null;// 發(fā)送人號(hào)碼
- String content = "message text content~~";
- // 短消息內(nèi)容 // 發(fā)送后 執(zhí)行的Intent
- Intent i = new Intent();
- PendingIntent sent = PendingIntent.getBroadcast
- (this, 0, i, 0);
- // 發(fā)送給接收人之后(此處應(yīng)該指:信息報(bào)告或回執(zhí))
- 執(zhí)行的Intent
- PendingIntent delivery = PendingIntent.getBroadcast(this, 0, i, 0);
- // 發(fā)送短消息
- SmsManager manager = SmsManager.getDefault();
- manager.sendTextMessage(to, from, content, sent, delivery);
值得注意的是做為參數(shù)的PendingIntent對象,可以把它簡單的理解成:“即將發(fā)生的Intent”,也就是說在短信發(fā)送完成后和短信送達(dá)后,分別向系統(tǒng)發(fā)出廣播通知,關(guān)注這一事件的應(yīng)用將監(jiān)聽此廣播。當(dāng)然也可以用PendingIntent.getActivity或者getService來創(chuàng)建PendingIntent對象,從而做出其它形式的響應(yīng)。
本來對于短消息功能而言,同樣會(huì)面臨要監(jiān)聽“收到”和“發(fā)出”短消息事件的需求,然而,實(shí)現(xiàn)方法與監(jiān)聽電話功能的方法卻完全不同——系統(tǒng)并未提供監(jiān)聽器注冊接口和底層廣播消息等途徑,而是要依賴于“內(nèi)容提供者(Contentproviders)”類型的應(yīng)用程序,它具有觀察者模式機(jī)制,該機(jī)制的基本思路是:OPhone系統(tǒng)會(huì)把短消息數(shù)據(jù)(包括發(fā)出的和接收到的)保存在一個(gè)內(nèi)容提供者應(yīng)用程序中(你可以理解成Database或者文件系統(tǒng)的資料庫),該程序允許開發(fā)者向其注冊“觀察者對象(ObserverObject)”,以即時(shí)了解資料庫中數(shù)據(jù)的變化,因此只要編寫自己的觀察者對象,并注冊到“發(fā)出”或“收到”短信所在的資料庫中,就可以監(jiān)聽到相關(guān)事件了。
簡單說一下“內(nèi)容提供者(Contentproviders)”,它是一種獨(dú)特的應(yīng)用程序類型,是保存和讀取數(shù)據(jù)的高級抽象接口,接口實(shí)現(xiàn)的背后可以使用數(shù)據(jù)庫、文件,甚至是遠(yuǎn)程網(wǎng)絡(luò)來持久保存數(shù)據(jù),并且它在一定程度上也呼應(yīng)著REST架構(gòu)的思想,例如:使用URI命名標(biāo)識(shí)具體數(shù)據(jù);具有增(insert)、刪(delete)、改(update)、查(query)4個(gè)基本操作方法,等等。在OPhone系統(tǒng)中,用來存儲(chǔ)短消息數(shù)據(jù)的內(nèi)容提供者程序是:TelephonyProvider。
然而令人遺憾的是,以上方法在當(dāng)前OPhone版本(OPhoneSDK_1.5.beta)中并不可用。通過編寫代碼實(shí)際測試發(fā)現(xiàn),TelephonyProvider應(yīng)用并未響應(yīng)觀察者機(jī)制,雖然注冊新的觀察者不會(huì)導(dǎo)致錯(cuò)誤,但也并未按預(yù)想對onChange方法進(jìn)行回調(diào),初步判斷可能是當(dāng)前的TelephonyProvider應(yīng)用有特殊的定制和實(shí)現(xiàn)。具體原因還有待進(jìn)一步學(xué)習(xí)和研究,同時(shí)也歡迎對此有興趣的讀者與我一道共同尋找解決方法。
總結(jié)
本文介紹了OPhone平臺(tái)中基本電話功能的基礎(chǔ)知識(shí),并通過示例代碼演示了在API級別,使用和控制基本電話功能的具體方法和技巧。