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