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