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