在最近的工作中,遇到一个需求,需要让第三方应用向MODEM层请求AT命令,且有两种响应方式,分别为同步方式和异步方式,同步的情况下,调用后要等MODEM返回结果后,将结果送给第三方应用, 异步的方式,采用等MODEM响应后,通过广播发送出去,让应用接收。鉴于目前大部分市面的MODEM都是通过AT来交互的,特有此需求。
1. 需求分析:
A. 由于需求要求的是第三方应用可以使用,那就是注定了,不是在PHONE的进程中,需要扩展TelephonyManager相关的接口,以及AIDL供其它调用。复杂。
B. 两种发送模式,归结到一起都是一样的,所以,在RIL层添加一条请求消息即可,扩展RIL层的请求消息。较复杂,
C. 主动上报的消息扩展。 比较简单,very easy.
D. 修改RIL C的请求函数,将请求发送给MODEM,并将结果送回RIL.JAVA
2. 可行性分析:
A. 第三方应用要使用,肯定扩展Telephony.aidl这个文件,这个应该问题不大,可以实现,估计4天。
B. 两种发送模式对于全是异步的RIL层来说,都是一样的,只是处理结果时不一样而已, 估计2天。
C. 该块是需求的重点,可行性问题不大,就是比较烦而已, 主要是调试环境复杂, 估计10天
D. 该处理是直接命令透传即可,不用太费时间,估计1天。
通过以上的个个分析,该需求应该是可以实现的,主要时间消耗在RIL层的扩展,其它都相对来说比较简单。
3. 需求实现
3.1. Framework/base的扩展
3.1.1. ITelephony.aidl扩展。
文件位置:telephony/java/com/android/internal/telephony/ITelephony.aidl。为了方便第三方调用该接口,需要扩展ITelephony.aidl文件中的接口,AIDL说白了,就是一个接口,便于不同进程之间的调用。而AIDL在编译时,会被编译成对应的java文件,最重要的是里面有一个stub, 这个才是进程通讯的核心,具体的内容不在这多说。但大家可以注意下PhoneInterfaceManager.java的声明类型。
public class PhoneInterfaceManager extends ITelephony.Stub
它真是实现第三方应用可以调用该接口的问题所在。
/** * Execute AT command via unsync tunnel * @param cmd AT command to execute * execute successfully return true, * AT command result will send by broadcast with action android.intent.action.AtCommand.result */boolean AtCommandSendUnSync(String cmd);<pre name="code" class="java"> int RIL_UNSOL_RESPONSE_TUNNEL_AT = 1052;
/** * Execute AT command via sync tunnel * @param cmd AT command, time is timeout of process unit is ms */String AtCommandSendSync(String cmd, int time);
3.1.2. telephony/java/com/android/internal/telephony/RILConstants.java
扩展主动请求和主动上报的消息,注意要与ril.h中的定义的变量是一致,否则无法收到了对应的请求。
int RIL_REQUEST_SEND_AT = 336; int RIL_UNSOL_RESPONSE_TUNNEL_AT = 1052;
3.1.3. telephony/java/android/telephony/TelephonyManager.java扩展方法
Teel(TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);
/** * Send AT command via sync tunnel, it should return result until the command execute completely. * @param cmd AT command * @param time max time for executing at command, unit is ms. * return is result of AT. */ public String AtCommandSendSync(String cmd, int time){ try { return getITelephony().AtCommandSendSync(cmd, time); } catch (RemoteException ex) { return null; } catch (NullPointerException ex) { return null; } } /** * Send AT command via unsync tunnel, it should return true or false when AT command has been send. * @param cmd AT command * return boolean */ public boolean AtCommandSendUnSync(String cmd){ try { return getITelephony().AtCommandSendUnSync(cmd); } catch (RemoteException ex) { return false; } catch (NullPointerException ex) { return false; } }
其中的getITelephony()就是调用PhoneInterfaceManager.java中的接口,就是用过这个来达到进程之间的调用。该机制是Android自带的,其根本的方式还是通过binder的方式,来达到。
3.2. Framework/opt/telephony扩展
3.2.1 Phone接口的扩展
//Add AT tunnel void sendAtToModem(String at_string, Message result);为了编译通过,类似的需要扩展PhoneBase.java, PhoneProxy.java。
3.2.2 PhoneBase.java扩展
@Override public void sendAtToModem(String at_string, Message result){ Rlog.e(LOG_TAG, "sendAtToModem Error! This function is only for GSMPhone."); }只允许该接口从PhoneBase的子类调用,如GSMPhone, CDMAPhone.SipPhone等。
3.2.3 PhoneProxy.java扩展
@Override public void sendAtToModem(String at_string, Message result) { mActivePhone.sendAtToModem(at_string, result); }
3.2.4 GSMPhone的扩展
mCi.registerForAtTunnel(this, EVENT_UNSOL_AT_TUNNEL, null);构造GSMPhone的时候,监听该事件。当RIL.java有事情上报时,转到对应的处理代码。
case EVENT_UNSOL_AT_TUNNEL: ar = (AsyncResult)msg.obj; log("receive EVENT_UNSOL_AT_TUNNEL done"); if (ar.exception == null) { String result = (String)ar.result; log("result = " + result); sendResultBroadcast(result); } break;添加发送广播代码,如下:
private void sendResultBroadcast(String result) { Intent intent = new Intent(ACTION_AT_COMMAND_RESULT); intent.putExtra(RESULT_KEY, result); mContext.sendBroadcast(intent); }添加PhoneInterfaceManager.java接口支持代码:
@Override public void sendAtToModem(String at_string, Message result) { mCi.sendAtToModem(at_string, result); }
3.3 RIL代码添加
3.3.1 CommandsInterface.java扩展
//Add for AT tunnel to modem void sendAtToModem(String at_string, Message result); void registerForAtTunnel(Handler h, int what, Object obj); void unregisterForAtTunnel(Handler h);
3.3.2 BaseCommands.java扩展
在BaseCommands.java中添加监控事件的处理,并添加一个新的注册事件集mAtTunnelRegistrant, 代码如下:protected Registrant mAtTunnelRegistrant;
/** * Sets the handler for AT sync tunnel * * @param h Handler for notification message. * @param what User-defined message code. * @param obj User object. */ @Override public void registerForAtTunnel(Handler h, int what, Object obj) { mAtTunnelRegistrant = new Registrant(h, what, obj); } @Override public void unregisterForAtTunnel(Handler h) { mAtTunnelRegistrant.clear(); } @Override public void sendAtToModem(String at_string, Message result) { }其主要作用,当有用户监控该事件后,就在注册事件集中添加该监控。而主动请求,由于发送时,已经明确了消息Handler,就知道消息发送给Message的注册Handler处理。
3.3.3 SipCommandInterface.java扩展
@Override public void sendAtToModem(String at_string, Message result){ }
4 RIL C层的扩展
4.1 ril.h的扩展
#define RIL_REQUEST_SEND_AT 336#define RIL_UNSOL_RESPONSE_TUNNEL_AT 1052
4.2 ril_unsol_commands.h扩展
{RIL_UNSOL_RESPONSE_TUNNEL_AT, responseString, WAKE_PARTIAL}
4.3 ril_commands.h扩展
文件路径:hardware/ril/libril/ril_commands.h。用于指定主动请求返回的类型处理,此处也是用responseString即可。代码如下:{RIL_REQUEST_SEND_AT, dispatchString, responseString}
4.4 reference-ril.c扩展
static void requestSendAt(void *data, size_t datalen, RIL_Token t){ int err; char *cmd; char *response; ATResponse *p_response = NULL; RLOGD("requestSendAt data = %s, datalen = %d", (char *)data, datalen); assert (datalen != 1); asprintf(&cmd, "%s", (char *)data); err = at_send_command(cmd, &p_response); if (cmd != NULL) { free(cmd); cmd = NULL; } RLOGD("requestSendAt err = %d, p_response->success = %d", err, p_response->success); if (p_response->p_intermediates == NULL) { RLOGD("requestSendAt finalResponse = %s", p_response->finalResponse); asprintf(&response, "%s\r\n", p_response->finalResponse); } else { RLOGD("requestSendAt finalResponse = %s, p_intermediates->line = %s", p_response->finalResponse, p_response->p_intermediates->line); asprintf(&response, "%s, %s\r\n", p_response->p_intermediates->line, p_response->finalResponse); } if (err < 0 || p_response->success == 0) /*Maybe the at command from user is invalid, we also send successful response to user, the result should handle it itself*/ goto error; RLOGD("requestSendAt success, response = %s, len = ", response, strlen(response)); RIL_onRequestComplete(t, RIL_E_SUCCESS, response, strlen(response)); free(response); return;error: RLOGE("ERROR: requestSendAt failed, response = %d", response); RIL_onRequestComplete(t, RIL_E_SUCCESS, response, strlen(response)); free(response);}这时对下层上报的字符串进行了处理,判断AT不同情况的时作出的不同处理。调用RIL_onRequestComplelte将结果返回给上层,返回的是一个字符串。该字符串被RIL.JAVA层的消息封装,并发给给PhoneInterfaceManager.java进行后一步的处理。
5. PhoneInterfaceManager.java扩展
/** * Send AT command via unsync tunnel, it should return true or false when AT command has been send. * @param cmd AT command * return boolean */ public boolean AtCommandSendUnSync(String cmd){ Log.d(LOG_TAG, "AtCommandSendUnSync send at command" + cmd); Phone phone = getPhone(0); if (phone == null) return false; final AtSendThread atSendThread = new AtSendThread("AtCommandSendUnSync", cmd, false); atSendThread.start(); String result = atSendThread.sendAt(phone); sendResultBroadcast(result); if (result != null && result.length() > 1 && result.contains("OK")) { return true; } else { return false; } }
/** * Send AT command via sync tunnel, it should return result until the command execute completely. * @param cmd AT command * @param time max time for executing at command, unit is ms. * return is result of AT. */ public String AtCommandSendSync(String cmd, int time){ Log.d(LOG_TAG, "AtCommandSendSync send at command" + cmd + " time = " + time); Phone phone = getPhone(0); if (phone == null) return null; final AtSendThread atSendThread = new AtSendThread("AtCommandSendSync", cmd, true, time); atSendThread.start(); return atSendThread.sendAt(phone); }从代码中可以看中,最重要的东西在起的线程中,将异步的请求转化为同步的返回结果。下面发下该线程的代码,该处是经过多次失败的偿试后得出的,只有这种方式可以最好的解决异步转同步的方法,另外里面设置有超时模式,一旦超时,将立刻返回,而不会被阻塞住。代码如下:
private static class AtSendThread extends Thread { private String mCmd; private long mMaxTimeExcute; private String mAtResult; private boolean mIsSync; private Handler mAtHandler; private boolean mSuccess = false; private static final int SEND_AT_VIA_TUNNEL = 1; AtSendThread(String name, String cmd, boolean isSync) { super(name); mCmd = cmd; mAtResult = null; mMaxTimeExcute = 5; mIsSync = false; } AtSendThread(String name, String cmd, boolean isSync, int max) { super(name); mCmd = cmd; mMaxTimeExcute = (long)(max/100); mAtResult = null; mIsSync = isSync; } public void run() { Looper.prepare(); synchronized (AtSendThread.this) { mAtHandler = new Handler() { @Override public void handleMessage(Message msg) { AsyncResult ar = (AsyncResult) msg.obj; switch (msg.what) { case SEND_AT_VIA_TUNNEL: Log.d("AtSyncThread", "SEND_AT_VIA_TUNNEL"); synchronized (AtSendThread.this) { if (ar.exception == null && ar.result != null) { mAtResult = ar.result.toString(); } mSuccess = true; AtSendThread.this.notifyAll(); } break; } } }; AtSendThread.this.notifyAll(); } Looper.loop(); } synchronized String sendAt(Phone phone) { while (mAtHandler == null) { try { wait(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } Message callback = Message.obtain(mAtHandler, SEND_AT_VIA_TUNNEL); Log.e(LOG_TAG, "mCmd = " + mCmd); phone.sendAtToModem(mCmd, callback); while (!mSuccess) { try { Log.d("AtSendThread", "wait for done"); mMaxTimeExcute--; wait(100); if (mMaxTimeExcute == 0) { mAtResult = "Error AT TIME OUT"; return mAtResult; } } catch (InterruptedException e) { // Restore the interrupted status Thread.currentThread().interrupt(); } } Log.d("AtSendThread", "successfull! result = " + mAtResult); return mAtResult; } }分解下该Thread, 分成两个构造函数,一个发送函数sendAt才是最终启作用的核心,就是这个函数能把异步的请求转给化为同步的结果返回给APK的。在sendAt函数中,使用了
同步锁的机制,当发送完后,该线程处理wait()的状态,等待AT的结果。在循环多次,500MS还没有结果时,就认为超时退出。