概述
支持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级别,使用和控制基本电话功能的具体方法和技巧。