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