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