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