1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.android.auto.mapservice; 18 19 import android.bluetooth.BluetoothDevice; 20 import android.bluetooth.BluetoothAdapter; 21 import android.content.BroadcastReceiver; 22 import android.content.ComponentName; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.content.IntentFilter; 26 import android.content.ServiceConnection; 27 import android.os.Handler; 28 import android.os.IBinder; 29 import android.os.Looper; 30 import android.os.RemoteException; 31 import android.util.Log; 32 33 import java.lang.ref.WeakReference; 34 import java.util.List; 35 36 public final class BluetoothMapManager { 37 public static final String CALLBACK_MISMATCH = "CALLBACK_MISMATCH"; 38 39 /** 40 * Connection State(s) for the service bound to this Manager. 41 * The connection state manage if the Manager is connected to Service: 42 * DISCONNECTED: Manager cannot execute calls on service currently because the client either 43 * never called connect() on the manager OR the device is disconnected. In the later case the 44 * client will have to eventually call connect() again. 45 * CONNECTING: This state persists from calling onBind on the service, until we have 46 * successfully received onConnect from the service. 47 * CONNECTED: The service is successfully connected and ready to receive commands. 48 */ 49 private static final int DISCONNECTED = 0; 50 private static final int SUSPENDED = 1; 51 private static final int CONNECTING = 2; 52 private static final int CONNECTED = 3; 53 54 // Error codes returned via the onError call. 55 56 // On connection suspended the Manager will callback with onConnect when the service is 57 // restarted and connected by android binder. 58 public static final int ERROR_CONNECT_SUSPENDED = 0; 59 // Connection between the service (backed by this Manager) and the remote device has failed. 60 // It can be due to variety of reasons such as obex transport failure or the device going out 61 // of range. The client will need to either call connect() again. In cases where the device 62 // goes out of range, calling connect agian will lead to this error being throw again but if 63 // it was a transient failure due to obex transport or other binder issue, then this call will 64 // succeed. 65 public static final int ERROR_CONNECT_FAILED = 1; 66 67 // String representation of operations. 68 private static final String OP_NONE = ""; 69 private static final String OP_PUSH_MESSAGE = "pushMessage"; 70 private static final String OP_GET_MESSAGE = "getMessage"; 71 private static final String OP_GET_MESSAGES_LISTING = "getMessagesListing"; 72 private static final String OP_ENABLE_NOTIFICATIONS = "enableNotifications"; 73 74 private static final boolean DBG = true; 75 private static final String TAG = "BluetoothMapManager"; 76 private final Context mContext; 77 private final ConnectionCallbacks mCallbacks; 78 private final BluetoothDevice mDevice; 79 // We have a handler to make sure that all code modifying/accessing the final non-final 80 // objects are serialized. This is done by ensuring the following: 81 // a) All calls done by the client (using this manager) should be on the main thread. 82 // b) All calls done by the manager not-on main thread (binder threads) should be posted back 83 // to main thread using this handler. 84 private final Handler mHandler = new Handler(); 85 86 private IBluetoothMapService mServiceBinder; 87 private IBluetoothMapServiceCallbacks mServiceCallbacks; 88 private BluetoothMapServiceConnection mServiceConnection; 89 private int mConnectionState = DISCONNECTED; 90 private String mOpInflight = OP_NONE; 91 BluetoothMapManager( Context context, BluetoothDevice device, ConnectionCallbacks callbacks)92 public BluetoothMapManager( 93 Context context, BluetoothDevice device, ConnectionCallbacks callbacks) { 94 if (device == null) { 95 throw new IllegalArgumentException("Device cannot be null."); 96 } 97 if (callbacks == null) { 98 throw new IllegalArgumentException(TAG + ": Callbacks cannot be null!"); 99 } 100 101 if (Looper.getMainLooper().getThread() != Thread.currentThread()) { 102 throw new IllegalStateException( 103 "Client needs to call the manager from the main UI thread."); 104 } 105 106 mDevice = device; 107 mContext = context; 108 mCallbacks = callbacks; 109 } 110 111 /** 112 * Defines the callback interface that clients using the Manager should implement in order to 113 * receive callbacks and notification for changes happening to either the binder connection 114 * between the Manager and Service or MAP profile changes. 115 */ 116 public static abstract class ConnectionCallbacks { 117 /** 118 * Called when connection has been established successfully with the service and the 119 * service itself is successfully connected to MAP profile. 120 * See connect(). 121 */ onConnected()122 public abstract void onConnected(); 123 124 /** 125 * Called when the Manager is no longer able to execute commands on the service. 126 * Client who holds this manager should consider all in-flight commands sent till now as 127 * cancelled. For permanent failures such as device going out of range and not coming back 128 * the client should call connect() again too continue working otherwise 129 * when the Manager does connect back it will call onConnected() (see above). 130 */ onError(int errorCode)131 public abstract void onError(int errorCode); 132 133 /** 134 * Callen when notification status has been adjusted. 135 * See enableNotifications(). 136 */ onEnableNotifications()137 public abstract void onEnableNotifications(); 138 139 /** 140 * Called when the message has been queued for sending. 141 * The argument always contains a "valid" handle. 142 * See pushMessage(). 143 */ onPushMessage(String handle)144 public abstract void onPushMessage(String handle); 145 146 /** 147 * Called when the message is fetched. 148 * See getMessage(). 149 */ onGetMessage(BluetoothMapMessage msg)150 public abstract void onGetMessage(BluetoothMapMessage msg); 151 152 /** 153 * Called when the messages listing is retrieved. 154 * See getMessagesListing(). 155 */ onGetMessagesListing(List<BluetoothMapMessagesListing> msgsListing)156 public abstract void onGetMessagesListing(List<BluetoothMapMessagesListing> msgsListing); 157 158 /** 159 * Called when an event has occured. 160 * See BluetoothMapEventReport for a description of what an event may look like. 161 */ onEvent(BluetoothMapEventReport eventReport)162 public abstract void onEvent(BluetoothMapEventReport eventReport); 163 164 } 165 166 /** 167 * Bind to the service and register the callback passed in the constructor. 168 */ connect()169 public boolean connect() { 170 checkMainThread(); 171 172 if (mConnectionState != DISCONNECTED && mConnectionState != SUSPENDED) { 173 Log.w(TAG, "Not in disconnected state, connection will eventually resume: " + 174 mConnectionState); 175 return true; 176 } 177 mConnectionState = CONNECTING; 178 mServiceConnection = new BluetoothMapServiceConnection(); 179 180 boolean bound = false; 181 ComponentName cName = 182 new ComponentName( 183 "com.google.android.auto.mapservice", 184 "com.google.android.auto.mapservice.BluetoothMapService"); 185 final Intent intent = new Intent(); 186 intent.setComponent(cName); 187 try { 188 bound = mContext.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE); 189 } catch (Exception ex) { 190 Log.e(TAG, "Failed binding to service." + ex); 191 dumpState(); 192 return false; 193 } 194 195 if (!bound) { 196 forceCloseConnection(); 197 dumpState(); 198 return false; 199 } 200 201 dumpState(); 202 return true; 203 } 204 205 /** 206 * Unregister the callbacks and unbind from the service. 207 */ disconnect()208 public void disconnect() { 209 checkMainThread(); 210 211 if (DBG) { 212 Log.d(TAG, "Calling IBluetoothMapService.disconnect ..."); 213 } 214 215 // In case the manager is already disconnected, we don't need to do anything more here. 216 if (mServiceBinder != null) { 217 try { 218 mServiceBinder.disconnect(mServiceCallbacks); 219 } catch (RemoteException ex) { 220 Log.w(TAG, "RemoteException during disconnect for " + mServiceConnection); 221 } catch (IllegalStateException ex) { 222 sendError(ex); 223 } 224 } 225 forceCloseConnection(); 226 dumpState(); 227 } 228 229 /** 230 * Enable notifications. 231 */ enableNotifications(boolean status)232 public void enableNotifications(boolean status) { 233 checkMainThread(); 234 235 if (DBG) { 236 Log.d(TAG, "Calling IBluetoothMapService.enableNotifications ..." + status); 237 } 238 239 if (mConnectionState != CONNECTED) { 240 if (DBG) { 241 Log.d(TAG, "Not connected to service."); 242 } 243 throw new IllegalStateException( 244 "Service is not connected, either connect() is not called or a disconnect " + 245 "event is not handled correctly."); 246 } 247 248 if (!mOpInflight.equals(OP_NONE)) { 249 throw new IllegalStateException( 250 TAG + "Operation already in flight: " + mOpInflight + 251 ". Please wait for an appropriate callback from your previous operation."); 252 } 253 254 try { 255 mServiceBinder.enableNotifications(mServiceCallbacks, status); 256 } catch (RemoteException ex) { 257 // If we have disconnected then the client will get a ERROR_CONNECT_SUSPENDED. 258 Log.e(TAG, "", ex); 259 return; 260 } catch (IllegalStateException ex) { 261 sendError(ex); 262 } 263 mOpInflight = OP_ENABLE_NOTIFICATIONS; 264 } 265 266 /** 267 * Push a message. 268 */ pushMessage(BluetoothMapMessage msg)269 public void pushMessage(BluetoothMapMessage msg) { 270 checkMainThread(); 271 272 if (mConnectionState != CONNECTED) { 273 throw new IllegalStateException( 274 "Service is not connected, either connect() is not called or a disconnect " + 275 "event is not handled correctly."); 276 } 277 278 if (!mOpInflight.equals(OP_NONE)) { 279 throw new IllegalStateException( 280 TAG + "Operation already in flight: " + mOpInflight + 281 ". Please wait for an appropriate callback from your previous operation."); 282 } 283 284 try { 285 mServiceBinder.pushMessage(mServiceCallbacks, msg); 286 } catch (RemoteException ex) { 287 // If we have disconnected then the client will get a ERROR_CONNECT_SUSPENDED. 288 Log.e(TAG, "", ex); 289 return; 290 } catch (IllegalStateException ex) { 291 sendError(ex); 292 } 293 mOpInflight = OP_PUSH_MESSAGE; 294 } 295 296 /** 297 * Get a message by its handle. 298 */ getMessage(String handle)299 public void getMessage(String handle) { 300 checkMainThread(); 301 302 if (mConnectionState != CONNECTED) { 303 throw new IllegalStateException( 304 "Service is not connected, either connect() is not called or a disconnect " + 305 "event is not handled correctly."); 306 } 307 308 if (!mOpInflight.equals(OP_NONE)) { 309 throw new IllegalStateException( 310 TAG + "Operation already in flight: " + mOpInflight + 311 ". Please wait for an appropriate callback from your previous operation."); 312 } 313 314 try { 315 mServiceBinder.getMessage(mServiceCallbacks, handle); 316 } catch (RemoteException ex) { 317 // If we have disconnected then the client will get a ERROR_CONNECT_SUSPENDED. 318 Log.e(TAG, "", ex); 319 return; 320 } catch (IllegalStateException ex) { 321 sendError(ex); 322 } 323 mOpInflight = OP_GET_MESSAGE; 324 } 325 getMessagesListing(String folder)326 public void getMessagesListing(String folder) { 327 // If count is not specified the cap is put by the Bluetooth spec, we pass a large number 328 // to use the cap provided by spec implementation on MAS server. 329 getMessagesListing(folder, 65535, 0); 330 } getMessagesListing(String folder, int count)331 public void getMessagesListing(String folder, int count) { 332 getMessagesListing(folder, count, 0); 333 } getMessagesListing(String folder, int count, int offset)334 public void getMessagesListing(String folder, int count, int offset) { 335 checkMainThread(); 336 337 if (mConnectionState != CONNECTED) { 338 throw new IllegalStateException( 339 "Service is not connected, either connect() is not called or a disconnect " + 340 "event is not handled correctly."); 341 } 342 343 if (!mOpInflight.equals(OP_NONE)) { 344 throw new IllegalStateException( 345 TAG + "Operation already in flight: " + mOpInflight + 346 ". Please wait for an appropriate callback from your previous operation."); 347 } 348 349 try { 350 mServiceBinder.getMessagesListing(mServiceCallbacks, folder, count, offset); 351 } catch (RemoteException ex) { 352 // If we have disconnected then the client will get a ERROR_CONNECT_SUSPENDED. 353 Log.e(TAG, "", ex); 354 return; 355 } catch (IllegalStateException ex) { 356 sendError(ex); 357 } 358 mOpInflight = OP_GET_MESSAGES_LISTING; 359 } 360 361 /** 362 * Checks if the current thread is main thread. 363 * 364 * Throws an IllegalStateException otherwise. 365 */ checkMainThread()366 void checkMainThread() { 367 if (Looper.getMainLooper().getThread() != Thread.currentThread()) { 368 throw new IllegalStateException( 369 "Manager APIs should be called only from main thread."); 370 } 371 } 372 373 /** 374 * Implements the callbacks when changes in the binder connection of Manager (this) and the 375 * BluetoothMapService occur. 376 * For a list of possible states that the binder connection can exist, see DISCONNECTED, 377 * CONNECTING and CONNECTED states above. 378 */ 379 private class BluetoothMapServiceConnection implements ServiceConnection { 380 /** 381 * Called when the Manager connects to service via the binder */ 382 @Override onServiceConnected(ComponentName name, IBinder binder)383 public void onServiceConnected(ComponentName name, IBinder binder) { 384 // onServiceConnected can be called either because we called onBind and in which case 385 // connection state should already be CONNECTING, or it could be called because the 386 // service came back up in which case we need to set it to CONNECTING (from 387 // DISCONNECTED). 388 if (mConnectionState == CONNECTED) { 389 throw new IllegalStateException( 390 "Cannot be in connected state while (re)connecting."); 391 } 392 // We may either be in CONNECTING or DISCONNECTED state here. Its safe to set to 393 // CONNECTING in any scenario. 394 mConnectionState = CONNECTING; 395 396 if (DBG) { 397 Log.d(TAG, "BluetoothMapServiceConnection.onServiceConnected name=" + 398 name + " binder=" + binder); 399 } 400 401 // Save the binder for future calls to service. 402 mServiceBinder = IBluetoothMapService.Stub.asInterface(binder); 403 404 // Register the callbacks to the service. 405 mServiceCallbacks = new ServiceCallbacks(BluetoothMapManager.this); 406 407 try { 408 if (DBG) { 409 Log.d(TAG, "ServiceCallbacks.connect ..."); 410 } 411 boolean status = mServiceBinder.connect(mServiceCallbacks, mDevice); 412 if (DBG && !status) { 413 Log.d(TAG, "Failed to connect to service after binding."); 414 } 415 } catch (RemoteException ex) { 416 Log.d(TAG, "connect failed with RemoteException."); 417 } 418 } 419 420 /** 421 * Called when the service is disconnected from the manager due to binder failure. 422 */ 423 @Override onServiceDisconnected(ComponentName name)424 public void onServiceDisconnected(ComponentName name) { 425 if (DBG) { 426 Log.d(TAG, "BluetoothMapServiceConnection.onServiceDisconnected name=" + name 427 + " this=" + this + "mServiceConnection=" + mServiceConnection); 428 } 429 430 mConnectionState = SUSPENDED; 431 432 mServiceBinder = null; 433 mServiceCallbacks = null; 434 mOpInflight = OP_NONE; 435 mCallbacks.onError(ERROR_CONNECT_SUSPENDED); 436 } 437 } 438 439 /** 440 * Implements the AIDL interface which is called by BluetoothMapService either in reply to any 441 * of the commands issued to it via the service binder or when there's a new notification that 442 * service has to push to Manager. 443 */ 444 private static class ServiceCallbacks extends IBluetoothMapServiceCallbacks.Stub { 445 private WeakReference<BluetoothMapManager> mMngr; 446 ServiceCallbacks(BluetoothMapManager manager)447 public ServiceCallbacks(BluetoothMapManager manager) { 448 mMngr = new WeakReference<BluetoothMapManager>(manager); 449 } 450 451 /** 452 * Called when the service is successfully connected to a remote device and is capable to 453 * execute the MAP profile. 454 */ 455 @Override onConnect()456 public void onConnect() { 457 if (DBG) { 458 Log.d(TAG, "IBluetoothMapServiceCallbacks.onConnect() called."); 459 } 460 461 // Client may clean up the manager before the service responds, we may have a race 462 // if the manager instance has disappeared, in which case we can return silently. 463 BluetoothMapManager mgr = mMngr.get(); 464 if (mgr == null) return; 465 466 mgr.onServiceConnected(); 467 } 468 469 /** 470 * Called when the service is not connected to a remote device and cannot execute the MAP 471 * profile. 472 */ 473 @Override onConnectFailed()474 public void onConnectFailed() { 475 if (DBG) { 476 Log.d(TAG, "IBluetoothMapServiceCallbacks.onConnectionFailed() called."); 477 } 478 479 // Client may clean up the manager before the service responds, we may have a race if 480 // the manager instance has disappeared, in which case we can return silently. 481 BluetoothMapManager mgr = mMngr.get(); 482 if (mgr == null) return; 483 484 mgr.onServiceConnectionFailed(); 485 } 486 487 @Override onEnableNotifications()488 public void onEnableNotifications() { 489 if (DBG) { 490 Log.d(TAG, "IBluetoothMapServiceCallbacks.onEnableNotifications() called."); 491 } 492 493 // Client may clean up the manager before the service responds, we may have a race if 494 // the manager instance has disappeared, in which case we can return silently. 495 BluetoothMapManager mgr = mMngr.get(); 496 if (mgr == null) return; 497 498 mgr.onEnableNotifications(); 499 } 500 501 @Override onPushMessage(String handle)502 public void onPushMessage(String handle) { 503 if (DBG) { 504 Log.d(TAG, "IBluetoothMapServiceCallbacks.onPushMessage() called with " + handle); 505 } 506 507 // Client may clean up the manager before the service responds, we may have a race if 508 // the manager instance has disappeared, in which case we can return silently. 509 BluetoothMapManager mgr = mMngr.get(); 510 if (mgr == null) return; 511 512 mgr.onPushMessage(handle); 513 } 514 515 @Override onGetMessage(BluetoothMapMessage msg)516 public void onGetMessage(BluetoothMapMessage msg) { 517 if (DBG) { 518 Log.d(TAG, "IBluetoothMapServiceCallbacks.onGetMessage() called with " + msg); 519 } 520 521 // Client may clean up the manager before the service responds, we may have a race if 522 // the manager instance has disappeared, in which case we can return silently. 523 BluetoothMapManager mgr = mMngr.get(); 524 if (mgr == null) return; 525 526 mgr.onGetMessage(msg); 527 } 528 529 @Override onGetMessagesListing(List<BluetoothMapMessagesListing> msgsListing)530 public void onGetMessagesListing(List<BluetoothMapMessagesListing> msgsListing) { 531 if (DBG) { 532 Log.d(TAG, "IBluetoothMapServiceCallbacks.onGetMessagesListing() called with " + 533 msgsListing); 534 } 535 536 // Client may clean up the manager before the service responds, we may have a race if 537 // the manager instance has disappeared, in which case we can return silently. 538 BluetoothMapManager mgr = mMngr.get(); 539 if (mgr == null) return; 540 541 mgr.onGetMessagesListing(msgsListing); 542 } 543 544 @Override onEvent(BluetoothMapEventReport eventReport)545 public void onEvent(BluetoothMapEventReport eventReport) { 546 if (DBG) { 547 Log.d(TAG, "IBluetoothMapServiceCallbacks.onEvent() called with " + eventReport); 548 } 549 550 BluetoothMapManager mngr = mMngr.get(); 551 // Client may clean up the manager before the service responds, we may have a race if 552 // the manager instance has disappeared, in which case we can return silently. 553 BluetoothMapManager mgr = mMngr.get(); 554 if (mgr == null) return; 555 556 mgr.onEvent(eventReport); 557 } 558 }; 559 onServiceConnected()560 private void onServiceConnected() { 561 mHandler.post(new Runnable() { 562 @Override 563 public void run() { 564 mConnectionState = CONNECTED; 565 mCallbacks.onConnected(); 566 dumpState(); 567 } 568 }); 569 } 570 onServiceConnectionFailed()571 private void onServiceConnectionFailed() { 572 mHandler.post(new Runnable() { 573 @Override 574 public void run() { 575 forceCloseConnection(); 576 mCallbacks.onError(ERROR_CONNECT_FAILED); 577 dumpState(); 578 } 579 }); 580 } 581 onEnableNotifications()582 private void onEnableNotifications() { 583 mHandler.post(new Runnable() { 584 @Override 585 public void run() { 586 if (!mOpInflight.equals(OP_ENABLE_NOTIFICATIONS)) { 587 throw new IllegalStateException( 588 TAG + " Expected Inflight op: " + OP_ENABLE_NOTIFICATIONS + 589 " actual op: " + mOpInflight); 590 } 591 mOpInflight = OP_NONE; 592 mCallbacks.onEnableNotifications(); 593 } 594 }); 595 } 596 onPushMessage(final String handle)597 private void onPushMessage(final String handle) { 598 mHandler.post(new Runnable() { 599 @Override 600 public void run() { 601 if (!mOpInflight.equals(OP_PUSH_MESSAGE)) { 602 throw new IllegalStateException( 603 TAG + " Expected Inflight op: " + OP_PUSH_MESSAGE + 604 " actual op: " + mOpInflight); 605 } 606 607 if (handle == null || handle.equals("")) { 608 Log.e(TAG, "Empty handle, the service may have been disconnected."); 609 } 610 mOpInflight = OP_NONE; 611 mCallbacks.onPushMessage(handle); 612 } 613 }); 614 } 615 onGetMessage(final BluetoothMapMessage msg)616 private void onGetMessage(final BluetoothMapMessage msg) { 617 mHandler.post(new Runnable() { 618 @Override 619 public void run() { 620 if (!mOpInflight.equals(OP_GET_MESSAGE)) { 621 throw new IllegalStateException( 622 TAG + " Expected inflight op: " + OP_GET_MESSAGE + 623 " actual op: " + mOpInflight); 624 } 625 mOpInflight = OP_NONE; 626 mCallbacks.onGetMessage(msg); 627 } 628 }); 629 } 630 onGetMessagesListing(final List<BluetoothMapMessagesListing> msgsListing)631 private void onGetMessagesListing(final List<BluetoothMapMessagesListing> msgsListing) { 632 mHandler.post(new Runnable() { 633 @Override 634 public void run() { 635 if (!mOpInflight.equals(OP_GET_MESSAGES_LISTING)) { 636 throw new IllegalStateException( 637 TAG + " Expected inflight op: " + OP_GET_MESSAGES_LISTING + 638 " actual op: " + mOpInflight); 639 } 640 mOpInflight = OP_NONE; 641 mCallbacks.onGetMessagesListing(msgsListing); 642 } 643 }); 644 } 645 onEvent(final BluetoothMapEventReport eventReport)646 private void onEvent(final BluetoothMapEventReport eventReport) { 647 mHandler.post(new Runnable() { 648 @Override 649 public void run() { 650 mCallbacks.onEvent(eventReport); 651 } 652 }); 653 } 654 forceCloseConnection()655 private void forceCloseConnection() { 656 if (mConnectionState == DISCONNECTED) { 657 Log.e(TAG, "Connection already closed."); 658 return; 659 } 660 mConnectionState = DISCONNECTED; 661 662 if (mServiceConnection != null) { 663 mContext.unbindService(mServiceConnection); 664 } 665 mServiceConnection = null; 666 mServiceCallbacks = null; 667 mServiceBinder = null; 668 // Even if there is an inflight message, we will never hear from the callback now. 669 mOpInflight = OP_NONE; 670 } 671 sendError(IllegalStateException ex)672 private void sendError(IllegalStateException ex) { 673 if (ex.getMessage().equals(CALLBACK_MISMATCH)) { 674 throw new IllegalStateException( 675 "Client tried to call with an unregistered callback. This can happen if either " + 676 "client never called connect() or if it got disconnected and GCed by service but " + 677 "forgot to call connect(). Check your connection state and reconnect."); 678 } else { 679 throw new IllegalArgumentException(TAG + " unknown exception: " + ex.toString()); 680 } 681 } 682 683 // Log the state of Manager. Useful for debugging. dumpState()684 private void dumpState() { 685 if (!DBG) { 686 return; 687 } 688 Log.d(TAG, "dumpState(). Connection State: " + mConnectionState); 689 } 690 } 691 692