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