Android 电话部分综述
主要部分:
呼叫
短信
数据连接
SIM卡
电话本
电话部分分为以下几层:
Modem驱动
RIL(radio interface layer)
电话服务框架
应用层
Modem通信模块
Chip-on-board
在Modem硬件上一般使用两个渠道:
一个是用于AT命令: 一般使用UART或USB方式, AT命令由Hayes公司发明
以AT开头,用于完成调制解调器之间的交互
另一个用于数据传输: 通过usb方式传输数据
如果基带与应用处理器集成一般通过共享内存方式传输
本地的RIL代码
本地代码的路径:\hardware\ril
部分文件;
include: RIL头文件
Libril: RIL库,最终生成libril.so 是一个辅助库
Rild: 守护进程 安装在system/bin目录下
Rdference-ril: 参考库, 生成动态库 libreference-ril.so
Include 目录中ril.h头文件是RIL框架结构和接口
Rild守护进程
Rild是一个可执行程序 : 获取参数------->r打开功能库------->建立事件循环----------->执行RIL-Init------>register
在ril.c中
static struct RIL_Env s_rilEnv = {
RIL_onRequestComplete, //RIL请求完成
RIL_onUnsolicitedResponse, //主动上报的响应
RIL_requestTimedCallback //用户定义响应的回调函数
};
主函数部分代码如下:
int main(int argc, char **argv)
{
...
//获取参数并解析
dlHandle = dlopen(rilLibPath, RTLD_NOW);
//启动线程进入事件循环
RIL_startEventLoop();
rilInit = (const RIL_RadioFunctions *(*)(const struct RIL_Env *, int, char **))dlsym(dlHandle, "RIL_Init");
//处理参数
funcs = rilInit(&s_rilEnv, argc, rilArgv);
RIL_register(funcs);
...
}
通过select多路复用机制,读取来自上层的Socket接口的具体操作命令
在init.rc中启动这个守护进程, 如果使用-l 则可以指定所使用的功能库
Libril库
Libril.so 主要提供了用于注册的RIL_register(),
几个回调函数
RIL_onRequestComplete(RIL_Token t, RIL_Errno e, void *response, size_t responselen) {}
void RIL_onUnsolicitedResponse(int unsolResponse, void *data,size_t datalen){}
static UserCallbackInfo *internalRequestTimedCallback (RIL_TimedCallback callback, void *param,
const struct timeval *relativeTime){}
字符串转换函数
extern "C" const char * requestToString(int request);
extern "C" const char * failCauseToString(RIL_Errno);
extern "C" const char * callStateToString(RIL_CallState);
extern "C" const char * radioStateToString(RIL_RadioState);
RIL的实现库Reference RIL
libreference-ril.so 功能库的一个参考实现,完成从具体电话服务命令到实际的AT命令之间的转换
Request请求流程
首先由java层通过socket将命令发送到RIL层的RILD守护进程,负责监听的ril_event_loop消息循环中
的RILD Socket有了请求链接信号,会建立起一个record_system, 打通与上层的数据通道开始接收请求数据
数据通道的回调函数processCommandsCallback()会保证收到一个完整的Request后(Request包的完整性由record_stream的机制保证) ,将其送达processCommandBuffer()函数,
static int
processCommandBuffer(void *buffer, size_t buflen) {
Parcel p;
status_t status;
int32_t request;
int32_t token;
RequestInfo *pRI;
int ret;
p.setData((uint8_t *) buffer, buflen);
// status checked at end
status = p.readInt32(&request);
status = p.readInt32 (&token);
if (status != NO_ERROR) {
LOGE("invalid request block");
return 0;
}
if (request < 1 || request >= (int32_t)NUM_ELEMS(s_commands)) {
LOGE("unsupported request code %d token %d", request, token);
// FIXME this should perhaps return a response
return 0;
}
pRI = (RequestInfo *)calloc(1, sizeof(RequestInfo));
pRI->token = token;
pRI->pCI = &(s_commands[request]);
//互斥量上锁
ret = pthread_mutex_lock(&s_pendingRequestsMutex);
assert (ret == 0);
pRI->p_next = s_pendingRequests;
s_pendingRequests = pRI;
//互斥量解锁
ret = pthread_mutex_unlock(&s_pendingRequestsMutex);
assert (ret == 0);
/* sLastDispatchedToken = token; */
//调用分派函数
pRI->pCI->dispatchFunction(p, pRI);
return 0;
}
processCommandBuffer()函数正式进入命令的解析流程 它从socket中序列化的数据流里还原信息,将其组织到RequestInfo中,每个命令将以下形式存在
typedef struct RequestInfo {
int32_t token; //this is not RIL_Token
CommandInfo *pCI;
struct RequestInfo *p_next;
char cancelled;
char local; // responses to local commands do not go back to command process
} RequestInfo;
在还原出来的信息中,最重要的是request号 它在RIL层和上层之间, 在ril_commands.h中定义
/** Index == requestNumber */
static CommandInfo s_commands[] = {
#include "ril_commands.h"
};
RIL层中采用表驱动方式分派请求,分派的基础是ruquest号,头文件按以下方式被包含
commandInfo结枸表示命令信息, 关联了request号和实际的请求函数,及响应函数之间的关系
typedef struct {
int requestNumber;
void (*dispatchFunction) (Parcel &p, struct RequestInfo *pRI);
int(*responseFunction) (Parcel &p, void *response, size_t responselen);
} CommandInfo;
接下来的分发流程,
由s_callbacks.onReauest()完成这一操作,s-callbacks是获取libreference-ril的RILRadioFunctions的结构指针,请求在这里转入底层libreference-ril处理,handler是referece-ril.c的Request
例如:RIL——REQUEST——DIAL请求 流程如下
onRequest()---->requestDial()-这里将命令和参数转换成对应的AT命令----------->at_send_connand------->at_command_full()------>at_send_command_full_nolock()---->writeline()
Response 流程
RIL的移植工作
当宏RIL——SHLIB被定义时,将使用库的行式,
没有被定义时,将使用守护进程的方式
移植需要考虑的问题:
RIL设备所使用的不同端口
在RIL_RadioFunctions的onRequest函数中需要处理的不同命令(差异部分在数据连接和呼叫状态查询等方面)
电话部分Java框加及应用
代码路径为:\frameworks\base\telephony\java
Java层与RIL本地代码的接口, 是一个名为rild的socket接口
RIL.java中的几个类
RILRequest代表一个电话服务命令请求
RILSender 负责命令的发送
RILReceiver处理信命令响应和主动上报信息的响应
RILConstants.java定义了电话报务的具体命令
class RILRequest {
static RILRequest obtain(int request, Message result) { }
void release() {}
Static void resetSerial() {}
String serialString() {}
void onError(int error, Object ret) {}
}
RIL RIL.RIL_Sender RIL.RILReceiver几个类的关系
public final class RIL extends BaseCommands implements CommandsInterface {}
Connamds 为命令响应或主动上报提供回调函数的注册机制
commandInterface 提供具体的电话服务接口
在RIL。Java中一个命令的发送标准流程是:
RILRequest.obtain--->复制参数----->通过send()函数发送EVENT_SEND----->RILSender线程中处理EVENT_SEND------>将命令写到out stream(socket)
Socket的取得 ,是在RILReceiver中建立
static final String SOCKET_NAME_RIL = "rild";
s = new LocalSocket();
l = new LocalSocketAddress(SOCKET_NAME_RIL,
LocalSocketAddress.Namespace.RESERVED);
s.connect(l);
响应和主动上报消息的流程:
RILReceiver线程监视mSocket input------>readRilMessage(读取完整响应)---------->processResponse---------->分别处理RESPONSE_UNSOLICITED与RESPONSE_SOLICITED(前者为主动上报,后者为命令响应)
注:send和processResponse 是异步的,电话服务的发送和响应是异步的
RIL直接同RIL本地代码打交道,对上层应用来说他并不是直接的接口,GSMPhone.java 他继承了PhoneBase,而phoneBase实现了phone接口, GSMPhone对RIL进行了一层封装,通过phone接口来实现功能
在Phone应用程式序中:
通过PhoneFactory来获取GSMPhone
流程是:
PhoneFactory.makeDefaultPhones------>PhoenFactory.useNewRIL------>PhoneFactory.registerPhone
然后Phone就可以获取GSMPhone的实例(Phone接口)
拔打电话和获取网络状态,是由telephonyManager来完成
TelephoneManager通过两个IBander接口ITelephony和ITelephonyRegistry来完成
ITelephony是电话服务的用户主动进行RIL访问的路径(如拔打电话), 它的服务实现类不在框架代码中,而在phone应用中 phoneInterfaceManager.java
ITelephonyRegistry提供一个通知机制,将底层状态或变更通知给电话服务 如网络状态/信号强弱
底层通知的来源是GSMPhone通过PhoneNotifier的实现者DefaultPhoneNotifier.java
telephonyRegistry通过两种方式通知用户
一种是Broadcast
另一种是在TelephoneRegistry.java中注册的IPhoneStateListener接口实现回调机制
interface ITelephonyRegistry {
void listen(String pkg, IPhoneStateListener callback, int events, boolean notifyNow);
}
传递的是IPhoneStateListener callback参数
呼叫
ITelephony接口实现在Phone应用中的phone服务,通过TelephonyManager提供访问接口
服务内部通过PhoneFactory获取GSMPhone来访问URI提供如拔号/接通/挂断
在呼叫部分中,从GSMPhone到RIL的路径中用到的几个数据结构如下:
GSMCall 基本的呼叫控制结构,每个接通的GSMCall 都拥有一个GSMConnection结构
GSMConnection 该结构用于存放呼叫时长等信息
CallTracker 是呼叫模块的核心 它提供与呼叫相关的接口
GSMPhone拥有CallTracker的实例,通过调用CommandsInterface实现
维护当前的GSMCall,实现追踪的方法为:pollCallsWhenSafe
getCurrentCalls取得当前活动的呼叫列表
短信部分
SMSManager实现短信的发送与sim卡短信相关的操作 通过ISms接口实现对应操作
ISms的服务端实现是:simsmsInterfaceManager
SMSDispatacher是短信的核心部分,负责发送接收短信,同样也集成在GSMPhone中
发送方面
提供了text 和pdu两种方式.SmsTracker跟踪短信的发送过程
发送结果 根据PendingIntent 回传
接收方面
SMSDispatcher启动时会通过CommandInterface注册新短信和返回报告的回调接口
mCm.setOnNewSMS(this, EVENT_NEW_SMS, null);
mCm.setOnSmsStatus(this, EVENT_NEW_SMS_STATUS_REPORT, null);
mCm.setOnIccSmsFull(this, EVENT_ICC_FULL, null);
mCm.registerForOn(this, EVENT_RADIO_ON, null);
有新消息上来时,相应的消息会被发送到SMSDispatcher的消息处理函数
dispatchMessage会读取SMSHeader分析是否需要拼接,完在后由dispatchPdus通过
Intent intent = new Intent(Intents.SMS_REJECTED_ACTION);
intent.putExtra("result", result);
mWakeLock.acquire(WAKE_LOCK_TIMEOUT);
mContext.sendBroadcast(intent, "android.permission.RECEIVE_SMS");
广播出去
数据联接
Android 数据联接是通过ppp方式实现的
数据联接分两个步骤:
首先是通过AT命令激活PDP联接
pppd通过数据端口完成拔号联接
GSMPhone 拥有其实例, 基入口点是:
DataConnectionTrackr.trySetupData------>setupData------->pdpConnection.connect------>commandsInterface.setupDefaultPDP
数据连接部分的结构
注: pppd是一个单独的进程,需要在init.rc里注册此服务