Home | History | Annotate | Download | only in sap
      1 package com.android.bluetooth.sap;
      2 
      3 import java.io.BufferedInputStream;
      4 import java.io.BufferedOutputStream;
      5 import java.io.IOException;
      6 import java.io.InputStream;
      7 import java.io.OutputStream;
      8 import java.util.concurrent.CountDownLatch;
      9 
     10 import com.android.bluetooth.R;
     11 
     12 import android.app.AlarmManager;
     13 import android.app.Notification;
     14 import android.app.NotificationManager;
     15 import android.app.PendingIntent;
     16 import android.bluetooth.BluetoothDevice;
     17 import android.bluetooth.BluetoothSocket;
     18 import android.content.BroadcastReceiver;
     19 import android.content.Context;
     20 import android.content.Intent;
     21 import android.content.IntentFilter;
     22 import android.content.SyncResult;
     23 import android.os.Handler;
     24 import android.os.Handler.Callback;
     25 import android.os.HandlerThread;
     26 import android.os.Looper;
     27 import android.os.Message;
     28 import android.os.Parcel;
     29 import android.os.SystemClock;
     30 import android.os.SystemProperties;
     31 import android.telephony.TelephonyManager;
     32 import android.util.Log;
     33 import android.bluetooth.BluetoothSap;
     34 
     35 //import com.android.internal.telephony.RIL;
     36 import 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  */
     50 public 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