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