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