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