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 */ 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 */ 172 public void setTestMode(int testMode) { 173 if(SapMessage.TEST) { 174 mTestMode = testMode; 175 } 176 } 177 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 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 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 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 */ 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 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 */ 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 */ 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 */ 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 */ 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 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 */ 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 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 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 */ 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 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 */ 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 */ 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 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