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