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