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