1package com.android.bluetooth.sap;
2
3import java.io.IOException;
4import java.io.InputStream;
5import java.io.OutputStream;
6
7import org.android.btsap.SapApi.MsgHeader;
8
9import com.google.protobuf.micro.CodedInputStreamMicro;
10import com.google.protobuf.micro.CodedOutputStreamMicro;
11
12import android.net.LocalSocket;
13import android.net.LocalSocketAddress;
14import android.os.Handler;
15import android.os.Message;
16import android.util.Log;
17
18public class SapRilReceiver implements Runnable {
19
20    private static final String TAG = "SapRilReceiver";
21    public static final boolean DEBUG = true;
22    public static final boolean VERBOSE = true;
23
24    private static final String SOCKET_NAME_RIL_BT = "sap_uim_socket1";
25    // match with constant in ril.cpp - as in RIL.java
26    private static final int SOCKET_OPEN_RETRY_MILLIS = 4 * 1000;
27
28    LocalSocket mSocket = null;
29    CodedOutputStreamMicro mRilBtOutStream = null;
30    InputStream mRilBtInStream = null;
31    private Handler mSapServerMsgHandler = null;
32    private Handler mSapServiceHandler = null;
33
34    public static final int RIL_MAX_COMMAND_BYTES = (8 * 1024);
35    byte[] buffer = new byte[RIL_MAX_COMMAND_BYTES];
36
37    public SapRilReceiver(Handler SapServerMsgHandler, Handler sapServiceHandler) {
38        mSapServerMsgHandler = SapServerMsgHandler;
39        mSapServiceHandler = sapServiceHandler;
40    }
41
42    /**
43     * Open the RIL-BT socket in rild. Will continuously try to open the BT socket until
44     * success. (Based on the approach used to open the rild socket in telephony)
45     * @return The socket handle
46     */
47    public static LocalSocket openRilBtSocket() {
48        int retryCount = 0;
49        LocalSocket rilSocket = null;
50
51        for (;;) {
52            LocalSocketAddress address;
53
54            try {
55                rilSocket = new LocalSocket();
56                address = new LocalSocketAddress(SOCKET_NAME_RIL_BT,
57                        LocalSocketAddress.Namespace.RESERVED);
58                rilSocket.connect(address);
59                break; // Socket opened
60            } catch (IOException ex){
61                try {
62                    if (rilSocket != null) {
63                        rilSocket.close();
64                    }
65                } catch (IOException ex2) {
66                    //ignore failure to close after failure to connect
67                }
68
69                // don't print an error message after the the first time
70                // or after the 8th time
71                if (retryCount == 8) {
72                    Log.e (TAG,
73                        "Couldn't find '" + SOCKET_NAME_RIL_BT
74                        + "' socket after " + retryCount
75                        + " times, continuing to retry silently");
76                } else if (retryCount > 0 && retryCount < 8) {
77                    Log.i (TAG,
78                        "Couldn't find '" + SOCKET_NAME_RIL_BT
79                        + "' socket; retrying after timeout");
80                    if (VERBOSE) Log.w(TAG, ex);
81                }
82
83                try {
84                    Thread.sleep(SOCKET_OPEN_RETRY_MILLIS);
85                } catch (InterruptedException er) {
86                }
87
88                retryCount++;
89                continue;
90            }
91        }
92        return rilSocket;
93    }
94
95
96    public CodedOutputStreamMicro getRilBtOutStream() {
97        return mRilBtOutStream;
98    }
99
100    /**
101     * Notify SapServer that this class is ready for shutdown.
102     */
103    private void notifyShutdown() {
104        if (DEBUG) Log.i(TAG, "notifyShutdown()");
105        // If we are already shutdown, don't bother sending a notification.
106        synchronized (this) {
107            if (mSocket != null) sendShutdownMessage();
108        }
109    }
110
111    /**
112     * This will terminate the SapRilReceiver thread, by closing the RIL-BT in-/output
113     * streams.
114     */
115    public void shutdown() {
116        if (DEBUG) Log.i(TAG, "shutdown()");
117
118        /* On Android you need to close the IOstreams using Socket.shutdown*
119         * The IOstream close must not be used, as it some how decouples the
120         * stream from the socket, and when the socket is closed, the pending
121         * reads never return nor throw and exception.
122         * Hence here we use the shutdown method: */
123        synchronized (this) {
124            if (mSocket != null) {
125                try {
126                    mSocket.shutdownOutput();
127                } catch (IOException e) {}
128                try {
129                    mSocket.shutdownInput();
130                } catch (IOException e) {}
131                try {
132                    mSocket.close();
133                } catch (IOException ex) {
134                    if (VERBOSE) Log.e(TAG,"Uncaught exception", ex);
135                } finally {
136                    mSocket = null;
137                }
138            }
139        }
140    }
141
142    /**
143     * Read the message into buffer
144     * @param is
145     * @param buffer
146     * @return the length of the message
147     * @throws IOException
148     */
149    private static int readMessage(InputStream is, byte[] buffer) throws IOException {
150        int countRead;
151        int offset;
152        int remaining;
153        int messageLength;
154
155        // Read in the length of the message
156        offset = 0;
157        remaining = 4;
158        do {
159            countRead = is.read(buffer, offset, remaining);
160
161            if (countRead < 0 ) {
162                Log.e(TAG, "Hit EOS reading message length");
163                return -1;
164            }
165
166            offset += countRead;
167            remaining -= countRead;
168        } while (remaining > 0);
169
170        messageLength = ((buffer[0] & 0xff) << 24)
171                | ((buffer[1] & 0xff) << 16)
172                | ((buffer[2] & 0xff) << 8)
173                | (buffer[3] & 0xff);
174        if (VERBOSE) Log.e(TAG,"Message length found to be: "+messageLength);
175        // Read the message
176        offset = 0;
177        remaining = messageLength;
178        do {
179            countRead = is.read(buffer, offset, remaining);
180
181            if (countRead < 0 ) {
182                Log.e(TAG, "Hit EOS reading message.  messageLength=" + messageLength
183                        + " remaining=" + remaining);
184                return -1;
185            }
186
187            offset += countRead;
188            remaining -= countRead;
189        } while (remaining > 0);
190
191        return messageLength;
192    }
193
194    /**
195     * The RIL reader thread. Will handle open of the RIL-BT socket, and notify
196     * SapServer when done.
197     */
198    @Override
199    public void run() {
200
201        try {
202            if (VERBOSE) Log.i(TAG, "Starting RilBtReceiverThread...");
203
204            mSocket = openRilBtSocket();
205            mRilBtInStream = mSocket.getInputStream();
206            mRilBtOutStream = CodedOutputStreamMicro.newInstance(mSocket.getOutputStream());
207
208            // Notify the SapServer that we have connected to the RilBtSocket
209            sendRilConnectMessage();
210
211            // The main loop - read messages and forward to SAP server
212            for (;;) {
213                SapMessage sapMsg = null;
214                MsgHeader rilMsg;
215
216                if (VERBOSE) Log.i(TAG, "Waiting for incoming message...");
217                int length = readMessage(mRilBtInStream, buffer);
218
219                SapService.notifyUpdateWakeLock(mSapServiceHandler);
220
221                if (length == -1) {
222                    if (DEBUG) Log.i(TAG, "EOF reached - closing down.");
223                    break;
224                }
225
226                CodedInputStreamMicro msgStream =
227                        CodedInputStreamMicro.newInstance(buffer, 0, length);
228
229                rilMsg = MsgHeader.parseFrom(msgStream);
230
231                if (VERBOSE) Log.i(TAG, "Message received.");
232
233                sapMsg = SapMessage.newInstance(rilMsg);
234
235                if (sapMsg != null && sapMsg.getMsgType() != SapMessage.INVALID_VALUE)
236                {
237                    if (sapMsg.getMsgType() < SapMessage.ID_RIL_BASE) {
238                        sendClientMessage(sapMsg);
239                    } else {
240                        sendRilIndMessage(sapMsg);
241                    }
242                } // else simply ignore it
243            }
244
245        } catch (IOException e) {
246            notifyShutdown(); /* Only needed in case of a connection error */
247            Log.i(TAG, "'" + SOCKET_NAME_RIL_BT + "' socket inputStream closed", e);
248
249        } finally {
250            Log.i(TAG, "Disconnected from '" + SOCKET_NAME_RIL_BT + "' socket");
251        }
252    }
253
254    /**
255     * Notify SapServer that the RIL socket is connected
256     */
257    private void sendRilConnectMessage() {
258        if (mSapServerMsgHandler != null) {
259            mSapServerMsgHandler.sendEmptyMessage(SapServer.SAP_MSG_RIL_CONNECT);
260        }
261    }
262
263    /**
264     * Send reply (solicited) message from the RIL to the Sap Server Handler Thread
265     * @param sapMsg The message to send
266     */
267    private void sendClientMessage(SapMessage sapMsg) {
268        Message newMsg = mSapServerMsgHandler.obtainMessage(SapServer.SAP_MSG_RFC_REPLY, sapMsg);
269        mSapServerMsgHandler.sendMessage(newMsg);
270    }
271
272    /**
273     * Send a shutdown signal to SapServer to indicate the
274     */
275    private void sendShutdownMessage() {
276        if (mSapServerMsgHandler != null) {
277            mSapServerMsgHandler.sendEmptyMessage(SapServer.SAP_RIL_SOCK_CLOSED);
278        }
279    }
280
281    /**
282     * Send indication (unsolicited) message from RIL to the Sap Server Handler Thread
283     * @param sapMsg The message to send
284     */
285    private void sendRilIndMessage(SapMessage sapMsg) {
286        Message newMsg = mSapServerMsgHandler.obtainMessage(SapServer.SAP_MSG_RIL_IND, sapMsg);
287        mSapServerMsgHandler.sendMessage(newMsg);
288    }
289
290}
291