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