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