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