Android kitkat RIL 請求擴展及主動上報擴展

    在最近的工作中,遇到一個需求,需要讓第三方應用向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的擴展

該塊修改需要重新編譯framework/base,將編出來的framework.jar. framework2.jar推到手機中即可。

    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擴展

該處修改最終最需要PUSH一個JAR即可,telephony-common.jar包到system/framework即可。

3.2.1 Phone接口的擴展

文件路徑:src/java/com/android/internal/telephony/Phone.java
        由於ITelephony中最後都是調用的Phone接口,所以必須擴展Phone.java。
    //Add AT tunnel
    void sendAtToModem(String at_string, Message result);
爲了編譯通過,類似的需要擴展PhoneBase.java, PhoneProxy.java。

3.2.2 PhoneBase.java擴展

文件路徑:src/java/com/android/internal/telephony/PhoneBase.java
        因PhoneBase.java是一個抽象類,所以不能實現化,所以當有人調用PhoneBase.java中對應的接口時,我們一律都認爲是非法的,所以返回一個錯誤的LOG。
    @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擴展

文件路徑:src/java/com/android/internal/telephony/PhoneProxy.java,該處PhoneProxy最終也是調用GSMPhone中的接口。

    @Override
    public void sendAtToModem(String at_string, Message result) {
        mActivePhone.sendAtToModem(at_string, result);
    }

3.2.4 GSMPhone的擴展

文件路徑:src/java/com/android/internal/telephony/PhoneProxy.java
GSMPhone中需要做三件事情,分別列舉如下:
A. 註冊主動上報的事件,當有主動上報時,轉到處理主動上報的代碼。
B. 發送廣播,當收到主動上報時,發送主動上報內容給APP.
C. 給PhoneInterfaceManager.java提供接口支持。
代碼如下:
        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代碼添加

A. 添加CommandInterface.java一個發送請求接口,完成消息監控,及消息分發,並保證編譯通過。
B. RIL添加一個處理MODEM主動上報的接口,並將內容發送給GSMPhone.java處理。
C. RIL添加一個處理髮送請求的接口,將結果返回給PhoneInterfaceManager.java。

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擴展

爲了編譯通過,將SipCommandInterface.java實現對應的CommandInterface接口。
    @Override
    public void sendAtToModem(String at_string, Message result){

    }

3.3.4 RIL.java的擴展

增加一個向RIL發送請求的接口,如下代碼
    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的擴展

文件路徑:hardware/ril/include/telephony/ril.h
添加主動請求和主動上報的兩個消息,需要與RILConstants.java中的定義變量一致。代碼如下:
#define RIL_REQUEST_SEND_AT 336
#define RIL_UNSOL_RESPONSE_TUNNEL_AT 1052

4.2 ril_unsol_commands.h擴展

文件路徑:hardware/ril/libril/ril_unsol_commands.h。添加一個處理該主動上報類型,由於該上報的爲一個字符串,所以使用responseString即可。代碼如下:
    {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擴展

文件路徑: leadcore-ril/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擴展

PhoneInterfaceManager的擴展主要有以下:
A. 給TelephonyManager.java提供實現兩個接口,對於下層的Phone來說,直接將該調用的參數(AT)傳遞給GSMPhone進行處理。
B. 提供返回值的等待過程。發送完命令後,等RIL層的響應。
C. 將結果處理後,返回給第三方應用。
給TelephonyManager提供接口。
    /**
     * 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還沒有結果時,就認爲超時退出。
總結一下,該需求的方式,APK-> TelephonyManager-> PhoneInterfaceManager-> GSMPhone->RIL.JAVA->RILD->Reference-RIL-> MODEM, 請求完成後,PhoneInterfaceManager中的線程處於阻塞等待狀態。每過100MS去檢查下,是否有結果返回,直到500MS超時退出。而在迴路時,MODEM -> Reference-Ril -> RILD ->
RIL.java -> 通過消息註冊方式,直接回到PhoneInterfaceManager -> APK.
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章