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