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