织梦CMS - 轻松建站从此开始!

罗索

当前位置: 主页>嵌入式开发>Android>

OPhone系统之电话功能详解

jackyhwei 发布于 2011-04-25 20:13 点击:次 
OPhone作为一个嵌入式操作系统,目前主要运行在有电话功能的手持设备上,因此最基本的电话功能——如:拨打、接听电话,接收、发送短信,就成为它最重要的核心功能了。本文将从开发者的角度、从API级别,来观察OPhone系统中使用基本电话功能的方法和技巧。
TAG:

概述
支持OPhone系统完成基本电话功能的底层硬件基础是:无线通讯模块(例如:GSM/GPRS modem),而在其之上的抽象——无线接口层(RIL:Radio Interface Layer)是本文讨论的相关API的灵魂,它完成了对基本电话功能与Radio硬件之间的抽象,负责提供数据的可靠传输、AT命令发送,以及命令回应(response)的解析等工作。它是通讯网络无关的,共包含两个基本部件:RIL守护进程(RIL Daemon)和RIL厂商专用实现(Vendor RIL)。前者负责初始化Vendor RIL实例,并管理来自应用层API的调用——将其转化为“主动请求命令”分派给Vendor RIL实现;而后者是具体无线通讯网络的专用实现,掌管并驱动着无线网络硬件模块的通讯工作,并把“被动请求命令”上报给RIL守护进程,从而达成网络通讯。
 
本文将从拨打电话、接听电话、发送短消息等多个基本功能出发,向大家介绍相关API的具体使用方法。
 
电话功能
OPhone系统本身有内置的电话应用(Phone.apk)提供拨打和接听电话的能力,它提供了虚拟数字键盘帮助用户拨号并发起呼叫。当有来电呼入时,它会振铃提示并显示来电信息提示界面,用户通过提示界面上的功能按钮,接听或者拒绝来电。但对于开发者而言,我们真正关心的是如何通过API调用这些基本的电话功能?
 
首先,让我们一起来看与拨打电话相关的功能吧。拨打电话是用户自主发起的动作,所以是“主动请求命令”,它首先在Java API层通过Socket与RIL守护进程建立连接并发送请求命令,然后命令被转发至Vendor RIL实现,并且最终建立通话呼叫的网络链接。这是底层的原理和流程,如何通过API实现呢?
 
还得先从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(); } 

 需要注意的是,并不是所有广播都可以终止,只有有序广播(Ordered broadcasts)才能够被终止。什么是有序广播呢?
 
让我们来回顾一下关于BroadcastReceiver的基础知识:广播被分为两种不同的类型:“普通广播(Normal broadcasts)”和“有序广播(Ordered broadcasts)”。前者是完全异步的,所有接收器(逻辑上)都在同一时刻运行,对消息传递的效率而言这是很好的做法,但缺点是:接收器不能返回结果并且无法终止广播数据的传播;然而后者是逐个的执行接收器——系统会按照接收器申明的优先级别(申明在intent-filter元素的android:priority属性中),按顺序逐次执行。OPhone系统定义:使用Context.sendBroadcast方法发起的都是普通广播,而有序广播必须使用另一个方法:Context.sendOrderedBroadcast。幸运的是“向外拨打电话”时系统发出的正是有序广播,因此我们可以轻松地阻止它。
 
继续深入“接听电话”功能,它是用户被动响应的活动,所以属于“被动请求命令”。来电首先会被Vendor RIL实现模块发现,然后上报给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对象,从而做出其它形式的响应。
      
本来对于短消息功能而言,同样会面临要监听“收到”和“发出” 短消息事件的需求,然而,实现方法与监听电话功能的方法却完全不同——系统并未提供监听器注册接口和底层广播消息等途径,而是要依赖于“内容提供者(Content providers)”类型的应用程序,它具有观察者模式机制,该机制的基本思路是:OPhone系统会把短消息数据(包括发出的和接收到的)保存在一个内容提供者应用程序中(你可以理解成Database或者文件系统的资料库),该程序允许开发者向其注册“观察者对象(Observer Object)”,以即时了解资料库中数据的变化,因此只要编写自己的观察者对象,并注册到“发出”或“收到”短信所在的资料库中,就可以监听到相关事件了。
 
简单说一下“内容提供者(Content providers)”,它是一种独特的应用程序类型,是保存和读取数据的高级抽象接口,接口实现的背后可以使用数据库、文件,甚至是远程网络来持久保存数据,并且它在一定程度上也呼应着REST架构的思想,例如:使用URI命名标识具体数据;具有增(insert)、删(delete)、改(update)、查(query)4个基本操作方法,等等。在OPhone系统中,用来存储短消息数据的内容提供者程序是:TelephonyProvider。
 
 
然而令人遗憾的是,以上方法在当前OPhone版本(OPhoneSDK_1.5.beta)中并不可用。通过编写代码实际测试发现,TelephonyProvider应用并未响应观察者机制,虽然注册新的观察者不会导致错误,但也并未按预想对onChange方法进行回调,初步判断可能是当前的TelephonyProvider应用有特殊的定制和实现。具体原因还有待进一步学习和研究,同时也欢迎对此有兴趣的读者与我一道共同寻找解决方法。
 
总结
本文介绍了OPhone平台中基本电话功能的基础知识,并通过示例代码演示了在API级别,使用和控制基本电话功能的具体方法和技巧。
 
作者介绍
        王伟,英華達(西安)通信科技有限公司软件开发工程师。具多年Java编程经验,熟悉Javame,OPhone移动开发平台。

(王伟)
本站文章除注明转载外,均为本站原创或编译欢迎任何形式的转载,但请务必注明出处,尊重他人劳动,同学习共成长。转载请注明:文章转载自:罗索实验室 [http://www.rosoo.net/a/201104/11298.html]
本文出处:英華達(西安)通信科技有限公司 作者:王伟
顶一下
(0)
0%
踩一下
(0)
0%
------分隔线----------------------------
发表评论
请自觉遵守互联网相关的政策法规,严禁发布色情、暴力、反动的言论。
评价:
表情:
用户名: 验证码:点击我更换图片
栏目列表
将本文分享到微信
织梦二维码生成器
推荐内容