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