SapServer.java revision 549c5a7e95c7934d8d3ab10a8653564e42b0c937
1package com.android.bluetooth.sap;
2
3import android.app.AlarmManager;
4import android.app.Notification;
5import android.app.NotificationManager;
6import android.app.PendingIntent;
7import android.bluetooth.BluetoothSap;
8import android.content.BroadcastReceiver;
9import android.content.Context;
10import android.content.Intent;
11import android.content.IntentFilter;
12import android.hardware.radio.V1_0.ISap;
13import android.os.Handler;
14import android.os.Handler.Callback;
15import android.os.HandlerThread;
16import android.os.Looper;
17import android.os.Message;
18import android.os.SystemClock;
19import android.os.SystemProperties;
20import android.telephony.TelephonyManager;
21import android.util.Log;
22
23import com.android.bluetooth.R;
24
25import java.io.BufferedInputStream;
26import java.io.BufferedOutputStream;
27import java.io.IOException;
28import java.io.InputStream;
29import java.io.OutputStream;
30import java.util.concurrent.CountDownLatch;
31
32
33/**
34 * The SapServer uses two threads, one for reading messages from the RFCOMM socket and
35 * one for writing the responses.
36 * Incoming requests are decoded in the "main" SapServer, by the decoder build into SapMEssage.
37 * The relevant RIL calls are made from the message handler thread through the rild-bt socket.
38 * The RIL replies are read in the SapRilReceiver, and passed to the SapServer message handler
39 * to be written to the RFCOMM socket.
40 * All writes to the RFCOMM and rild-bt socket must be synchronized, hence to write e.g. an error
41 * response, send a message to the Sap Handler thread. (There are helper functions to do this)
42 * Communication to the RIL is through an intent, and a BroadcastReceiver.
43 */
44public class SapServer extends Thread implements Callback {
45    private static final String TAG = "SapServer";
46    private static final String TAG_HANDLER = "SapServerHandler";
47    public static final boolean DEBUG = SapService.DEBUG;
48    public static final boolean VERBOSE = SapService.VERBOSE;
49
50    private enum SAP_STATE    {
51        DISCONNECTED, CONNECTING, CONNECTING_CALL_ONGOING, CONNECTED,
52        CONNECTED_BUSY, DISCONNECTING;
53    }
54
55    private SAP_STATE mState = SAP_STATE.DISCONNECTED;
56
57    private Context mContext = null;
58    /* RFCOMM socket I/O streams */
59    private BufferedOutputStream mRfcommOut = null;
60    private BufferedInputStream mRfcommIn = null;
61    /* References to the SapRilReceiver object */
62    private SapRilReceiver mRilBtReceiver = null;
63    private Thread mRilBtReceiverThread = null;
64    /* The message handler members */
65    private Handler mSapHandler = null;
66    private HandlerThread mHandlerThread = null;
67    /* Reference to the SAP service - which created this instance of the SAP server */
68    private Handler mSapServiceHandler = null;
69
70    /* flag for when user forces disconnect of rfcomm */
71    private boolean mIsLocalInitDisconnect = false;
72    private CountDownLatch mDeinitSignal = new CountDownLatch(1);
73
74    /* Message ID's handled by the message handler */
75    public static final int SAP_MSG_RFC_REPLY =   0x00;
76    public static final int SAP_MSG_RIL_CONNECT = 0x01;
77    public static final int SAP_MSG_RIL_REQ =     0x02;
78    public static final int SAP_MSG_RIL_IND =     0x03;
79    public static final int SAP_RIL_SOCK_CLOSED = 0x04;
80
81    public static final String SAP_DISCONNECT_ACTION =
82            "com.android.bluetooth.sap.action.DISCONNECT_ACTION";
83    public static final String SAP_DISCONNECT_TYPE_EXTRA =
84            "com.android.bluetooth.sap.extra.DISCONNECT_TYPE";
85    public static final int NOTIFICATION_ID = android.R.drawable.stat_sys_data_bluetooth;
86    private static final int DISCONNECT_TIMEOUT_IMMEDIATE = 5000; /* ms */
87    private static final int DISCONNECT_TIMEOUT_RFCOMM = 2000; /* ms */
88    private PendingIntent pDiscIntent = null; // Holds a reference to disconnect timeout intents
89
90    /* We store the mMaxMessageSize, as we need a copy of it when the init. sequence completes */
91    private int mMaxMsgSize = 0;
92    /* keep track of the current RIL test mode */
93    private int mTestMode = SapMessage.INVALID_VALUE; // used to set the RIL in test mode
94
95    /**
96     * SapServer constructor
97     * @param serviceHandler The handler to send a SapService.MSG_SERVERSESSION_CLOSE when closing
98     * @param inStream The socket input stream
99     * @param outStream The socket output stream
100     */
101    public SapServer(Handler serviceHandler, Context context, InputStream inStream,
102            OutputStream outStream) {
103        mContext = context;
104        mSapServiceHandler = serviceHandler;
105
106        /* Open in- and output streams */
107        mRfcommIn = new BufferedInputStream(inStream);
108        mRfcommOut = new BufferedOutputStream(outStream);
109
110        /* Register for phone state change and the RIL cfm message */
111        IntentFilter filter = new IntentFilter();
112        filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
113        filter.addAction(SAP_DISCONNECT_ACTION);
114        mContext.registerReceiver(mIntentReceiver, filter);
115    }
116
117    /**
118     * This handles the response from RIL.
119     */
120    BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
121        @Override
122        public void onReceive(Context context, Intent intent) {
123            if(intent.getAction().equals(TelephonyManager.ACTION_PHONE_STATE_CHANGED)) {
124                if(VERBOSE) Log.i(TAG, "ACTION_PHONE_STATE_CHANGED intent received in state "
125                                        + mState.name()
126                                        + "PhoneState: "
127                                        + intent.getStringExtra(TelephonyManager.EXTRA_STATE));
128                if(mState == SAP_STATE.CONNECTING_CALL_ONGOING) {
129                    String state = intent.getStringExtra(TelephonyManager.EXTRA_STATE);
130                    if(state != null) {
131                        if(state.equals(TelephonyManager.EXTRA_STATE_IDLE)) {
132                            if(DEBUG) Log.i(TAG, "sending RIL.ACTION_RIL_RECONNECT_OFF_REQ intent");
133                            SapMessage fakeConReq = new SapMessage(SapMessage.ID_CONNECT_REQ);
134                            fakeConReq.setMaxMsgSize(mMaxMsgSize);
135                            onConnectRequest(fakeConReq);
136                        }
137                    }
138                }
139            } else if (intent.getAction().equals(SAP_DISCONNECT_ACTION)) {
140                int disconnectType = intent.getIntExtra(SapServer.SAP_DISCONNECT_TYPE_EXTRA,
141                        SapMessage.DISC_GRACEFULL);
142                Log.v(TAG, " - Received SAP_DISCONNECT_ACTION type: " + disconnectType);
143
144                if(disconnectType == SapMessage.DISC_RFCOMM) {
145                    // At timeout we need to close the RFCOMM socket to complete shutdown
146                    shutdown();
147                } else if( mState != SAP_STATE.DISCONNECTED
148                    && mState != SAP_STATE.DISCONNECTING ) {
149                    // The user pressed disconnect - initiate disconnect sequence.
150                    sendDisconnectInd(disconnectType);
151                }
152            } else {
153                Log.w(TAG, "RIL-BT received unexpected Intent: " + intent.getAction());
154            }
155        }
156    };
157
158    /**
159     * Set RIL driver in test mode - only possible if SapMessage is build with TEST == true
160     * The value set by this function will take effect at the next connect request received
161     * in DISCONNECTED state.
162     * @param testMode Use SapMessage.TEST_MODE_XXX
163     */
164    public void setTestMode(int testMode) {
165        if(SapMessage.TEST) {
166            mTestMode = testMode;
167        }
168    }
169
170    private void sendDisconnectInd(int discType) {
171        if(VERBOSE) Log.v(TAG, "in sendDisconnectInd()");
172
173        if(discType != SapMessage.DISC_FORCED){
174            if(VERBOSE) Log.d(TAG, "Sending  disconnect ("+discType+") indication to client");
175            /* Send disconnect to client */
176            SapMessage discInd = new SapMessage(SapMessage.ID_DISCONNECT_IND);
177            discInd.setDisconnectionType(discType);
178            sendClientMessage(discInd);
179
180            /* Handle local disconnect procedures */
181            if (discType == SapMessage.DISC_GRACEFULL)
182            {
183                /* Update the notification to allow the user to initiate a force disconnect */
184                setNotification(SapMessage.DISC_IMMEDIATE, PendingIntent.FLAG_CANCEL_CURRENT);
185
186            } else if (discType == SapMessage.DISC_IMMEDIATE){
187                /* Request an immediate disconnect, but start a timer to force disconnect if the
188                 * client do not obey our request. */
189                startDisconnectTimer(SapMessage.DISC_FORCED, DISCONNECT_TIMEOUT_IMMEDIATE);
190            }
191
192        } else {
193            SapMessage msg = new SapMessage(SapMessage.ID_DISCONNECT_REQ);
194            /* Force disconnect of RFCOMM - but first we need to clean up. */
195            clearPendingRilResponses(msg);
196
197            /* We simply need to forward to RIL, but not change state to busy - hence send and set
198               message to null. */
199            changeState(SAP_STATE.DISCONNECTING);
200            sendRilThreadMessage(msg);
201            mIsLocalInitDisconnect = true;
202        }
203    }
204
205    void setNotification(int type, int flags)
206    {
207        String title, text, button, ticker;
208        Notification notification;
209        if(VERBOSE) Log.i(TAG, "setNotification type: " + type);
210        /* For PTS TC_SERVER_DCN_BV_03_I we need to expose the option to send immediate disconnect
211         * without first sending a graceful disconnect.
212         * To enable this option set
213         * bt.sap.pts="true" */
214        String pts_enabled = SystemProperties.get("bt.sap.pts");
215        Boolean pts_test = Boolean.parseBoolean(pts_enabled);
216
217        /* put notification up for the user to be able to disconnect from the client*/
218        Intent sapDisconnectIntent = new Intent(SapServer.SAP_DISCONNECT_ACTION);
219        if(type == SapMessage.DISC_GRACEFULL){
220            title = mContext.getString(R.string.bluetooth_sap_notif_title);
221            button = mContext.getString(R.string.bluetooth_sap_notif_disconnect_button);
222            text = mContext.getString(R.string.bluetooth_sap_notif_message);
223            ticker = mContext.getString(R.string.bluetooth_sap_notif_ticker);
224        }else{
225            title = mContext.getString(R.string.bluetooth_sap_notif_title);
226            button = mContext.getString(R.string.bluetooth_sap_notif_force_disconnect_button);
227            text = mContext.getString(R.string.bluetooth_sap_notif_disconnecting);
228            ticker = mContext.getString(R.string.bluetooth_sap_notif_ticker);
229        }
230        if(!pts_test)
231        {
232            sapDisconnectIntent.putExtra(SapServer.SAP_DISCONNECT_TYPE_EXTRA, type);
233            PendingIntent pIntentDisconnect = PendingIntent.getBroadcast(mContext, type,
234                    sapDisconnectIntent,flags);
235            notification = new Notification.Builder(mContext).setOngoing(true)
236                .addAction(android.R.drawable.stat_sys_data_bluetooth, button, pIntentDisconnect)
237                .setContentTitle(title)
238                .setTicker(ticker)
239                .setContentText(text)
240                .setSmallIcon(android.R.drawable.stat_sys_data_bluetooth)
241                .setAutoCancel(false)
242                .setPriority(Notification.PRIORITY_MAX)
243                .setOnlyAlertOnce(true)
244                .build();
245        }else{
246
247            sapDisconnectIntent.putExtra(SapServer.SAP_DISCONNECT_TYPE_EXTRA,
248                    SapMessage.DISC_GRACEFULL);
249            Intent sapForceDisconnectIntent = new Intent(SapServer.SAP_DISCONNECT_ACTION);
250            sapForceDisconnectIntent.putExtra(SapServer.SAP_DISCONNECT_TYPE_EXTRA,
251                    SapMessage.DISC_IMMEDIATE);
252            PendingIntent pIntentDisconnect = PendingIntent.getBroadcast(mContext,
253                    SapMessage.DISC_GRACEFULL, sapDisconnectIntent,flags);
254            PendingIntent pIntentForceDisconnect = PendingIntent.getBroadcast(mContext,
255                    SapMessage.DISC_IMMEDIATE, sapForceDisconnectIntent,flags);
256            notification = new Notification.Builder(mContext).setOngoing(true)
257                    .addAction(android.R.drawable.stat_sys_data_bluetooth,
258                            mContext.getString(R.string.bluetooth_sap_notif_disconnect_button),
259                            pIntentDisconnect)
260                    .addAction(android.R.drawable.stat_sys_data_bluetooth,
261                            mContext.getString(R.string.bluetooth_sap_notif_force_disconnect_button),
262                            pIntentForceDisconnect)
263                    .setContentTitle(title)
264                    .setTicker(ticker)
265                    .setContentText(text)
266                    .setSmallIcon(android.R.drawable.stat_sys_data_bluetooth)
267                    .setAutoCancel(false)
268                    .setPriority(Notification.PRIORITY_MAX)
269                    .setOnlyAlertOnce(true)
270                    .build();
271        }
272
273        // cannot be set with the builder
274        notification.flags |= Notification.FLAG_NO_CLEAR |Notification.FLAG_ONLY_ALERT_ONCE;
275
276        NotificationManager notificationManager =
277                (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
278
279        notificationManager.notify(NOTIFICATION_ID, notification);
280    }
281
282    void clearNotification() {
283        NotificationManager notificationManager =
284                (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
285        notificationManager.cancel(SapServer.NOTIFICATION_ID);
286    }
287
288    /**
289     * The SapServer RFCOMM reader thread. Sets up the handler thread and handle
290     * all read from the RFCOMM in-socket. This thread also handle writes to the RIL socket.
291     */
292    @Override
293    public void run() {
294        try {
295            /* SAP is not time critical, hence lowering priority to ensure critical tasks are
296             * executed in a timely manner. */
297            android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND);
298
299            /* Start the SAP message handler thread */
300            mHandlerThread = new HandlerThread("SapServerHandler",
301                    android.os.Process.THREAD_PRIORITY_BACKGROUND);
302            mHandlerThread.start();
303
304            // This will return when the looper is ready
305            Looper sapLooper = mHandlerThread.getLooper();
306            mSapHandler = new Handler(sapLooper, this);
307
308            mRilBtReceiver = new SapRilReceiver(mSapHandler, mSapServiceHandler);
309            mRilBtReceiverThread = new Thread(mRilBtReceiver, "RilBtReceiver");
310            boolean done = false;
311            while (!done) {
312                if(VERBOSE) Log.i(TAG, "Waiting for incomming RFCOMM message...");
313                int requestType = mRfcommIn.read();
314                if (VERBOSE) Log.i(TAG, "RFCOMM message read...");
315                if(requestType == -1) {
316                    if (VERBOSE) Log.i(TAG, "requestType == -1");
317                    done = true; // EOF reached
318                } else {
319                    if (VERBOSE) Log.i(TAG, "requestType != -1");
320                    SapMessage msg = SapMessage.readMessage(requestType, mRfcommIn);
321                    /* notify about an incoming message from the BT Client */
322                    SapService.notifyUpdateWakeLock(mSapServiceHandler);
323                    if(msg != null && mState != SAP_STATE.DISCONNECTING)
324                    {
325                        switch (requestType) {
326                        case SapMessage.ID_CONNECT_REQ:
327                            if(VERBOSE) Log.d(TAG, "CONNECT_REQ - MaxMsgSize: "
328                                    + msg.getMaxMsgSize());
329                            onConnectRequest(msg);
330                            msg = null; /* don't send ril connect yet */
331                            break;
332                        case SapMessage.ID_DISCONNECT_REQ: /* No params */
333                            /*
334                             * 1) send RIL_REQUEST_SIM_SAP_DISCONNECT
335                             *      (block for all incoming requests, as they are not
336                             *       allowed, don't even send an error_resp)
337                             * 2) on response disconnect ril socket.
338                             * 3) when disconnected send RIL.ACTION_RIL_RECONNECT_OFF_REQ
339                             * 4) on RIL.ACTION_RIL_RECONNECT_CFM
340                             *       send SAP_DISCONNECT_RESP to client.
341                             * 5) Start RFCOMM disconnect timer
342                             * 6.a) on rfcomm disconnect:
343                             *       cancel timer and initiate cleanup
344                             * 6.b) on rfcomm disc. timeout:
345                             *       close socket-streams and initiate cleanup */
346                            if(VERBOSE) Log.d(TAG, "DISCONNECT_REQ");
347
348                            if (mState ==  SAP_STATE.CONNECTING_CALL_ONGOING) {
349                                Log.d(TAG, "disconnect received when call was ongoing, " +
350                                     "send disconnect response");
351                                changeState(SAP_STATE.DISCONNECTING);
352                                SapMessage reply = new SapMessage(SapMessage.ID_DISCONNECT_RESP);
353                                sendClientMessage(reply);
354                            } else {
355                                clearPendingRilResponses(msg);
356                                changeState(SAP_STATE.DISCONNECTING);
357                                sendRilThreadMessage(msg);
358                                /*cancel the timer for the hard-disconnect intent*/
359                                stopDisconnectTimer();
360                            }
361                            msg = null; // No message needs to be sent to RIL
362                            break;
363                        case SapMessage.ID_POWER_SIM_OFF_REQ: // Fall through
364                        case SapMessage.ID_RESET_SIM_REQ:
365                            /* Forward these to the RIL regardless of the state, and clear any
366                             * pending resp */
367                            clearPendingRilResponses(msg);
368                            break;
369                        case SapMessage.ID_SET_TRANSPORT_PROTOCOL_REQ:
370                            /* The RIL might support more protocols that specified in the SAP,
371                             * allow only the valid values. */
372                            if(mState == SAP_STATE.CONNECTED
373                                    && msg.getTransportProtocol() != 0
374                                    && msg.getTransportProtocol() != 1) {
375                                Log.w(TAG, "Invalid TransportProtocol received:"
376                                        + msg.getTransportProtocol());
377                                // We shall only handle one request at the time, hence return error
378                                SapMessage errorReply = new SapMessage(SapMessage.ID_ERROR_RESP);
379                                sendClientMessage(errorReply);
380                                msg = null;
381                            }
382                            // Fall through
383                        default:
384                            /* Remaining cases just needs to be forwarded to the RIL unless we are
385                             * in busy state. */
386                            if(mState != SAP_STATE.CONNECTED) {
387                                Log.w(TAG, "Message received in STATE != CONNECTED - state = "
388                                        + mState.name());
389                                // We shall only handle one request at the time, hence return error
390                                SapMessage errorReply = new SapMessage(SapMessage.ID_ERROR_RESP);
391                                sendClientMessage(errorReply);
392                                msg = null;
393                            }
394                        }
395
396                        if(msg != null && msg.getSendToRil() == true) {
397                            changeState(SAP_STATE.CONNECTED_BUSY);
398                            sendRilThreadMessage(msg);
399                        }
400
401                    } else {
402                        //An unknown message or in disconnecting state - send error indication
403                        Log.e(TAG, "Unable to parse message.");
404                        SapMessage atrReply = new SapMessage(SapMessage.ID_ERROR_RESP);
405                        sendClientMessage(atrReply);
406                    }
407                }
408            } // end while
409        } catch (NullPointerException e) {
410            Log.w(TAG, e);
411        } catch (IOException e) {
412            /* This is expected during shutdown */
413            Log.i(TAG, "IOException received, this is probably a shutdown signal, cleaning up...");
414        } catch (Exception e) {
415            /* TODO: Change to the needed Exception types when done testing */
416            Log.w(TAG, e);
417        } finally {
418            // Do cleanup even if an exception occurs
419            stopDisconnectTimer();
420            /* In case of e.g. a RFCOMM close while connected:
421             *        - Initiate a FORCED shutdown
422             *        - Wait for RIL deinit to complete
423             */
424            if (mState == SAP_STATE.CONNECTING_CALL_ONGOING) {
425                /* Most likely remote device closed rfcomm, update state */
426                changeState(SAP_STATE.DISCONNECTED);
427            } else if (mState != SAP_STATE.DISCONNECTED) {
428                if(mState != SAP_STATE.DISCONNECTING &&
429                        mIsLocalInitDisconnect != true) {
430                    sendDisconnectInd(SapMessage.DISC_FORCED);
431                }
432                if(DEBUG) Log.i(TAG, "Waiting for deinit to complete");
433                try {
434                    mDeinitSignal.await();
435                } catch (InterruptedException e) {
436                    Log.e(TAG, "Interrupt received while waitinf for de-init to complete", e);
437                }
438            }
439
440            if(mIntentReceiver != null) {
441                mContext.unregisterReceiver(mIntentReceiver);
442                mIntentReceiver = null;
443            }
444            stopDisconnectTimer();
445            clearNotification();
446
447            if(mHandlerThread != null) try {
448                mHandlerThread.quit();
449                mHandlerThread.join();
450                mHandlerThread = null;
451            } catch (InterruptedException e) {}
452            if(mRilBtReceiverThread != null) try {
453                if(mRilBtReceiver != null) {
454                    mRilBtReceiver.shutdown();
455                    mRilBtReceiver = null;
456                }
457                mRilBtReceiverThread.join();
458                mRilBtReceiverThread = null;
459            } catch (InterruptedException e) {}
460
461            if(mRfcommIn != null) try {
462                if(VERBOSE) Log.i(TAG, "Closing mRfcommIn...");
463                mRfcommIn.close();
464                mRfcommIn = null;
465            } catch (IOException e) {}
466
467            if(mRfcommOut != null) try {
468                if(VERBOSE) Log.i(TAG, "Closing mRfcommOut...");
469                mRfcommOut.close();
470                mRfcommOut = null;
471            } catch (IOException e) {}
472
473            if (mSapServiceHandler != null) {
474                Message msg = Message.obtain(mSapServiceHandler);
475                msg.what = SapService.MSG_SERVERSESSION_CLOSE;
476                msg.sendToTarget();
477                if (DEBUG) Log.d(TAG, "MSG_SERVERSESSION_CLOSE sent out.");
478            }
479            Log.i(TAG, "All done exiting thread...");
480        }
481    }
482
483
484    /**
485     * This function needs to determine:
486     *  - if the maxMsgSize is acceptable - else reply CON_STATUS_ERROR_MAX_MSG_SIZE_UNSUPPORTED
487     *      + new maxMsgSize if too big
488     *  - connect to the RIL-BT socket
489     *  - if a call is ongoing reply CON_STATUS_OK_ONGOING_CALL.
490     *  - if all ok, just respond CON_STATUS_OK.
491     *
492     * @param msg the incoming SapMessage
493     */
494    private void onConnectRequest(SapMessage msg) {
495        SapMessage reply = new SapMessage(SapMessage.ID_CONNECT_RESP);
496
497        if(mState == SAP_STATE.CONNECTING) {
498            /* A connect request might have been rejected because of maxMessageSize negotiation, and
499             * this is a new connect request. Simply forward to RIL, and stay in connecting state.
500             * */
501            reply = null;
502            sendRilMessage(msg);
503            stopDisconnectTimer();
504
505        } else if(mState != SAP_STATE.DISCONNECTED && mState != SAP_STATE.CONNECTING_CALL_ONGOING) {
506            reply.setConnectionStatus(SapMessage.CON_STATUS_ERROR_CONNECTION);
507        } else {
508            // Store the MaxMsgSize for future use
509            mMaxMsgSize = msg.getMaxMsgSize();
510            // All parameters OK, examine if a call is ongoing and start the RIL-BT listener thread
511            if (isCallOngoing() == true) {
512                /* If a call is ongoing we set the state, inform the SAP client and wait for a state
513                 * change intent from the TelephonyManager with state IDLE. */
514                reply.setConnectionStatus(SapMessage.CON_STATUS_OK_ONGOING_CALL);
515            } else {
516                /* no call is ongoing, initiate the connect sequence:
517                 *  1) Start the SapRilReceiver thread (open the rild-bt socket)
518                 *  2) Send a RIL_SIM_SAP_CONNECT request to RILD
519                 *  3) Send a RIL_SIM_RESET request and a connect confirm to the SAP client */
520                changeState(SAP_STATE.CONNECTING);
521                if(mRilBtReceiverThread != null) {
522                     // Open the RIL socket, and wait for the complete message: SAP_MSG_RIL_CONNECT
523                    mRilBtReceiverThread.start();
524                    // Don't send reply yet
525                    reply = null;
526                } else {
527                    reply = new SapMessage(SapMessage.ID_CONNECT_RESP);
528                    reply.setConnectionStatus(SapMessage.CON_STATUS_ERROR_CONNECTION);
529                    sendClientMessage(reply);
530                }
531            }
532        }
533        if(reply != null)
534            sendClientMessage(reply);
535    }
536
537    private void clearPendingRilResponses(SapMessage msg) {
538        if(mState == SAP_STATE.CONNECTED_BUSY) {
539            msg.setClearRilQueue(true);
540        }
541    }
542    /**
543     * Send RFCOMM message to the Sap Server Handler Thread
544     * @param sapMsg The message to send
545     */
546    private void sendClientMessage(SapMessage sapMsg) {
547        Message newMsg = mSapHandler.obtainMessage(SAP_MSG_RFC_REPLY, sapMsg);
548        mSapHandler.sendMessage(newMsg);
549    }
550
551    /**
552     * Send a RIL message to the SapServer message handler thread
553     * @param sapMsg
554     */
555    private void sendRilThreadMessage(SapMessage sapMsg) {
556        Message newMsg = mSapHandler.obtainMessage(SAP_MSG_RIL_REQ, sapMsg);
557        mSapHandler.sendMessage(newMsg);
558    }
559
560    /**
561     * Examine if a call is ongoing, by asking the telephony manager
562     * @return false if the phone is IDLE (can be used for SAP), true otherwise.
563     */
564    private boolean isCallOngoing() {
565        TelephonyManager tManager =
566                (TelephonyManager)mContext.getSystemService(Context.TELEPHONY_SERVICE);
567        if(tManager.getCallState() == TelephonyManager.CALL_STATE_IDLE) {
568            return false;
569        }
570        return true;
571    }
572
573    /**
574     * Change the SAP Server state.
575     * We add thread protection, as we access the state from two threads.
576     * @param newState
577     */
578    private void changeState(SAP_STATE newState) {
579        if(DEBUG) Log.i(TAG_HANDLER,"Changing state from " + mState.name() +
580                                        " to " + newState.name());
581        synchronized (this) {
582            mState = newState;
583        }
584    }
585
586    /*************************************************************************
587     * SAP Server Message Handler Thread Functions
588     *************************************************************************/
589
590    /**
591     * The SapServer message handler thread implements the SAP state machine.
592     *  - Handle all outgoing communication to the out-socket. Either replies from the RIL or direct
593     *    messages send from the SapServe (e.g. connect_resp).
594     *  - Handle all outgoing communication to the RIL-BT socket.
595     *  - Handle all replies from the RIL
596     */
597    @Override
598    public boolean handleMessage(Message msg) {
599        if(VERBOSE) Log.i(TAG_HANDLER,"Handling message (ID: " + msg.what + "): "
600                + getMessageName(msg.what));
601
602        SapMessage sapMsg = null;
603
604        switch(msg.what) {
605        case SAP_MSG_RFC_REPLY:
606            sapMsg = (SapMessage) msg.obj;
607            handleRfcommReply(sapMsg);
608            break;
609        case SAP_MSG_RIL_CONNECT:
610            /* The connection to rild-bt have been established. Store the outStream handle
611             * and send the connect request. */
612            if(mTestMode != SapMessage.INVALID_VALUE) {
613                SapMessage rilTestModeReq = new SapMessage(SapMessage.ID_RIL_SIM_ACCESS_TEST_REQ);
614                rilTestModeReq.setTestMode(mTestMode);
615                sendRilMessage(rilTestModeReq);
616                mTestMode = SapMessage.INVALID_VALUE;
617            }
618            SapMessage rilSapConnect = new SapMessage(SapMessage.ID_CONNECT_REQ);
619            rilSapConnect.setMaxMsgSize(mMaxMsgSize);
620            sendRilMessage(rilSapConnect);
621            break;
622        case SAP_MSG_RIL_REQ:
623            sapMsg = (SapMessage) msg.obj;
624            if(sapMsg != null) {
625                sendRilMessage(sapMsg);
626            }
627            break;
628        case SAP_MSG_RIL_IND:
629            sapMsg = (SapMessage) msg.obj;
630            handleRilInd(sapMsg);
631            break;
632        case SAP_RIL_SOCK_CLOSED:
633            /* The RIL socket was closed unexpectedly, send immediate disconnect indication
634               - close RFCOMM after timeout if no response. */
635            sendDisconnectInd(SapMessage.DISC_IMMEDIATE);
636            startDisconnectTimer(SapMessage.DISC_RFCOMM, DISCONNECT_TIMEOUT_RFCOMM);
637            break;
638        default:
639            /* Message not handled */
640            return false;
641        }
642        return true; // Message handles
643    }
644
645    /**
646     * Close the in/out rfcomm streams, to trigger a shutdown of the SapServer main thread.
647     * Use this after completing the deinit sequence.
648     */
649    private void shutdown() {
650
651        if(DEBUG) Log.i(TAG_HANDLER, "in Shutdown()");
652        try {
653            if (mRfcommOut != null)
654                mRfcommOut.close();
655        } catch (IOException e) {}
656        try {
657            if (mRfcommIn != null)
658                mRfcommIn.close();
659        } catch (IOException e) {}
660        mRfcommIn = null;
661        mRfcommOut = null;
662        stopDisconnectTimer();
663        clearNotification();
664    }
665
666    private void startDisconnectTimer(int discType, int timeMs) {
667
668        stopDisconnectTimer();
669        synchronized (this) {
670            Intent sapDisconnectIntent = new Intent(SapServer.SAP_DISCONNECT_ACTION);
671            sapDisconnectIntent.putExtra(SAP_DISCONNECT_TYPE_EXTRA, discType);
672            AlarmManager alarmManager =
673                    (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
674            pDiscIntent = PendingIntent.getBroadcast(mContext,
675                                                    discType,
676                                                    sapDisconnectIntent,
677                                                    PendingIntent.FLAG_CANCEL_CURRENT);
678            alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
679                    SystemClock.elapsedRealtime() + timeMs, pDiscIntent);
680
681            if(VERBOSE) Log.d(TAG_HANDLER, "Setting alarm for " + timeMs +
682                    " ms to activate disconnect type " + discType);
683        }
684    }
685
686    private void stopDisconnectTimer() {
687        synchronized (this) {
688            if(pDiscIntent != null)
689            {
690                AlarmManager alarmManager =
691                        (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
692                alarmManager.cancel(pDiscIntent);
693                pDiscIntent.cancel();
694                if(VERBOSE) {
695                    Log.d(TAG_HANDLER, "Canceling disconnect alarm");
696                }
697                pDiscIntent = null;
698            }
699        }
700    }
701
702    /**
703     * Here we handle the replies to the SAP client, normally forwarded directly from the RIL.
704     * We do need to handle some of the messages in the SAP profile, hence we look at the messages
705     * here before they go to the client
706     * @param sapMsg the message to send to the SAP client
707     */
708    private void handleRfcommReply(SapMessage sapMsg) {
709        if(sapMsg != null) {
710
711            if(DEBUG) Log.i(TAG_HANDLER, "handleRfcommReply() handling "
712                    + SapMessage.getMsgTypeName(sapMsg.getMsgType()));
713
714            switch(sapMsg.getMsgType()) {
715
716                case SapMessage.ID_CONNECT_RESP:
717                    if(mState == SAP_STATE.CONNECTING_CALL_ONGOING) {
718                        /* Hold back the connect resp if a call was ongoing when the connect req
719                         * was received.
720                         * A response with status call-ongoing was sent, and the connect response
721                         * received from the RIL when call ends must be discarded.
722                         */
723                        if (sapMsg.getConnectionStatus() == SapMessage.CON_STATUS_OK) {
724                            // This is successful connect response from RIL/modem.
725                            changeState(SAP_STATE.CONNECTED);
726                        }
727                        if(VERBOSE) Log.i(TAG, "Hold back the connect resp, as a call was ongoing" +
728                                " when the initial response were sent.");
729                        sapMsg = null;
730                    } else if (sapMsg.getConnectionStatus() == SapMessage.CON_STATUS_OK) {
731                        // This is successful connect response from RIL/modem.
732                        changeState(SAP_STATE.CONNECTED);
733                    } else if(sapMsg.getConnectionStatus() ==
734                            SapMessage.CON_STATUS_OK_ONGOING_CALL) {
735                        changeState(SAP_STATE.CONNECTING_CALL_ONGOING);
736                    } else if(sapMsg.getConnectionStatus() != SapMessage.CON_STATUS_OK) {
737                        /* Most likely the peer will try to connect again, hence we keep the
738                         * connection to RIL open and stay in connecting state.
739                         *
740                         * Start timer to do shutdown if a new connect request is not received in
741                         * time. */
742                        startDisconnectTimer(SapMessage.DISC_FORCED, DISCONNECT_TIMEOUT_RFCOMM);
743                    }
744                    break;
745                case SapMessage.ID_DISCONNECT_RESP:
746                    if(mState == SAP_STATE.DISCONNECTING) {
747                        /* Close the RIL-BT output Stream and signal to SapRilReceiver to close
748                         * down the input stream. */
749                        if(DEBUG) Log.i(TAG, "ID_DISCONNECT_RESP received in SAP_STATE." +
750                                "DISCONNECTING.");
751
752                        /* Send the disconnect resp, and wait for the client to close the Rfcomm,
753                         * but start a timeout timer, just to be sure. Use alarm, to ensure we wake
754                         * the host to close the connection to minimize power consumption. */
755                        SapMessage disconnectResp = new SapMessage(SapMessage.ID_DISCONNECT_RESP);
756                        changeState(SAP_STATE.DISCONNECTED);
757                        sapMsg = disconnectResp;
758                        startDisconnectTimer(SapMessage.DISC_RFCOMM, DISCONNECT_TIMEOUT_RFCOMM);
759                        mDeinitSignal.countDown(); /* Signal deinit complete */
760                    } else { /* DISCONNECTED */
761                        mDeinitSignal.countDown(); /* Signal deinit complete */
762                        if(mIsLocalInitDisconnect == true) {
763                            if(VERBOSE) Log.i(TAG_HANDLER, "This is a FORCED disconnect.");
764                            /* We needed to force the disconnect, hence no hope for the client to
765                             * close the RFCOMM connection, hence we do it here. */
766                            shutdown();
767                            sapMsg = null;
768                        } else {
769                            /* The client must disconnect the RFCOMM, but in case it does not, we
770                             * need to do it.
771                             * We start an alarm, and if it triggers, we must send the
772                             * MSG_SERVERSESSION_CLOSE */
773                            if(VERBOSE) Log.i(TAG_HANDLER, "This is a NORMAL disconnect.");
774                            startDisconnectTimer(SapMessage.DISC_RFCOMM, DISCONNECT_TIMEOUT_RFCOMM);
775                        }
776                    }
777                    break;
778                case SapMessage.ID_STATUS_IND:
779                    /* Some car-kits only "likes" status indication when connected, hence discard
780                     * any arriving outside this state */
781                    if(mState == SAP_STATE.DISCONNECTED ||
782                            mState == SAP_STATE.CONNECTING ||
783                            mState == SAP_STATE.DISCONNECTING) {
784                        sapMsg = null;
785                    }
786                    if (mSapServiceHandler != null && mState == SAP_STATE.CONNECTED) {
787                        Message msg = Message.obtain(mSapServiceHandler);
788                        msg.what = SapService.MSG_CHANGE_STATE;
789                        msg.arg1 = BluetoothSap.STATE_CONNECTED;
790                        msg.sendToTarget();
791                        setNotification(SapMessage.DISC_GRACEFULL, 0);
792                        if (DEBUG) Log.d(TAG, "MSG_CHANGE_STATE sent out.");
793                    }
794                    break;
795                default:
796                // Nothing special, just send the message
797            }
798        }
799
800        /* Update state variable based on the number of pending commands. We are only able to
801         * handle one request at the time, except from disconnect, sim off and sim reset.
802         * Hence if one of these are received while in busy state, we might have a crossing
803         * response, hence we must stay in BUSY state if we have an ongoing RIL request. */
804        if(mState == SAP_STATE.CONNECTED_BUSY) {
805            if(SapMessage.getNumPendingRilMessages() == 0) {
806                changeState(SAP_STATE.CONNECTED);
807            }
808        }
809
810        // This is the default case - just send the message to the SAP client.
811        if(sapMsg != null)
812            sendReply(sapMsg);
813    }
814
815    private void handleRilInd(SapMessage sapMsg) {
816        if(sapMsg == null)
817            return;
818
819        switch(sapMsg.getMsgType()) {
820        case SapMessage.ID_DISCONNECT_IND:
821        {
822            if(mState != SAP_STATE.DISCONNECTED && mState != SAP_STATE.DISCONNECTING){
823                /* we only send disconnect indication to the client if we are actually connected*/
824                SapMessage reply = new SapMessage(SapMessage.ID_DISCONNECT_IND);
825                reply.setDisconnectionType(sapMsg.getDisconnectionType()) ;
826                sendClientMessage(reply);
827            } else {
828                /* TODO: This was introduced to handle disconnect indication from RIL */
829                sendDisconnectInd(sapMsg.getDisconnectionType());
830            }
831            break;
832        }
833
834        default:
835            if(DEBUG) Log.w(TAG_HANDLER,"Unhandled message - type: "
836                    + SapMessage.getMsgTypeName(sapMsg.getMsgType()));
837        }
838    }
839
840    /**
841     * This is only to be called from the handlerThread, else use sendRilThreadMessage();
842     * @param sapMsg
843     */
844    private void sendRilMessage(SapMessage sapMsg) {
845        if(VERBOSE) Log.i(TAG_HANDLER, "sendRilMessage() - "
846                + SapMessage.getMsgTypeName(sapMsg.getMsgType()));
847
848        Log.d(TAG_HANDLER, "sendRilMessage: calling getSapProxy");
849        ISap sapProxy = mRilBtReceiver.getSapProxy();
850        if (sapProxy == null) {
851            Log.e(TAG_HANDLER, "sendRilMessage: Unable to send message to RIL; sapProxy is null");
852            SapMessage errorReply = new SapMessage(SapMessage.ID_ERROR_RESP);
853            sendClientMessage(errorReply);
854            return;
855        }
856
857        try {
858            sapMsg.send(sapProxy);
859            if (VERBOSE) {
860                Log.d(TAG_HANDLER, "sendRilMessage: sapMsg.callISapReq called "
861                                + "successfully");
862            }
863        } catch (Exception e) {
864            Log.e(TAG_HANDLER, "sendRilMessage: Unable to send message to RIL", e);
865            SapMessage errorReply = new SapMessage(SapMessage.ID_ERROR_RESP);
866            sendClientMessage(errorReply);
867        }
868    }
869
870    /**
871     * Only call this from the sapHandler thread.
872     */
873    private void sendReply(SapMessage msg) {
874        if(VERBOSE) Log.i(TAG_HANDLER, "sendReply() RFCOMM - "
875                + SapMessage.getMsgTypeName(msg.getMsgType()));
876        if(mRfcommOut != null) { // Needed to handle brutal shutdown from car-kit and out of range
877            try {
878                msg.write(mRfcommOut);
879                mRfcommOut.flush();
880            } catch (IOException e) {
881                Log.w(TAG_HANDLER, e);
882                /* As we cannot write to the rfcomm channel we are disconnected.
883                   Shutdown and prepare for a new connect. */
884            }
885        }
886    }
887
888    private static String getMessageName(int messageId) {
889        switch (messageId) {
890        case SAP_MSG_RFC_REPLY:
891            return "SAP_MSG_REPLY";
892        case SAP_MSG_RIL_CONNECT:
893            return "SAP_MSG_RIL_CONNECT";
894        case SAP_MSG_RIL_REQ:
895            return "SAP_MSG_RIL_REQ";
896        case SAP_MSG_RIL_IND:
897            return "SAP_MSG_RIL_IND";
898        default:
899            return "Unknown message ID";
900        }
901    }
902
903}
904