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