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