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