在最近的工作中,遇到一個需求,需要讓第三方應用向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){
}
3.3.4 RIL.java的擴展
public void sendAtToModem(String at_string, Message result) {
RILRequest rr = RILRequest.obtain(RILConstants.RIL_REQUEST_SEND_AT, result);
if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
rr.mParcel.writeString(at_string);
if (RILJ_LOGD) riljLog("at_string = " + at_string);
send(rr);
}
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還沒有結果時,就認爲超時退出。