1/* 2 * Copyright (C) 2008 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 17package com.android.settings.bluetooth; 18 19import android.app.AlertDialog; 20import android.bluetooth.BluetoothAdapter; 21import android.bluetooth.BluetoothClass; 22import android.bluetooth.BluetoothDevice; 23import android.content.Context; 24import android.content.DialogInterface; 25import android.content.Intent; 26import android.content.res.Resources; 27import android.os.ParcelUuid; 28import android.os.SystemClock; 29import android.text.TextUtils; 30import android.util.Log; 31import android.view.ContextMenu; 32import android.view.Menu; 33import android.view.MenuItem; 34 35import com.android.settings.R; 36import com.android.settings.bluetooth.LocalBluetoothProfileManager.Profile; 37 38import java.util.ArrayList; 39import java.util.List; 40import java.util.Set; 41 42/** 43 * CachedBluetoothDevice represents a remote Bluetooth device. It contains 44 * attributes of the device (such as the address, name, RSSI, etc.) and 45 * functionality that can be performed on the device (connect, pair, disconnect, 46 * etc.). 47 */ 48public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> { 49 private static final String TAG = "CachedBluetoothDevice"; 50 private static final boolean D = LocalBluetoothManager.D; 51 private static final boolean V = LocalBluetoothManager.V; 52 private static final boolean DEBUG = false; 53 54 private static final int CONTEXT_ITEM_CONNECT = Menu.FIRST + 1; 55 private static final int CONTEXT_ITEM_DISCONNECT = Menu.FIRST + 2; 56 private static final int CONTEXT_ITEM_UNPAIR = Menu.FIRST + 3; 57 private static final int CONTEXT_ITEM_CONNECT_ADVANCED = Menu.FIRST + 4; 58 59 private final BluetoothDevice mDevice; 60 private String mName; 61 private short mRssi; 62 private BluetoothClass mBtClass; 63 64 private List<Profile> mProfiles = new ArrayList<Profile>(); 65 66 private boolean mVisible; 67 68 private final LocalBluetoothManager mLocalManager; 69 70 private List<Callback> mCallbacks = new ArrayList<Callback>(); 71 72 /** 73 * When we connect to multiple profiles, we only want to display a single 74 * error even if they all fail. This tracks that state. 75 */ 76 private boolean mIsConnectingErrorPossible; 77 78 /** 79 * Last time a bt profile auto-connect was attempted. 80 * If an ACTION_UUID intent comes in within 81 * MAX_UUID_DELAY_FOR_AUTO_CONNECT milliseconds, we will try auto-connect 82 * again with the new UUIDs 83 */ 84 private long mConnectAttempted; 85 86 // See mConnectAttempted 87 private static final long MAX_UUID_DELAY_FOR_AUTO_CONNECT = 5000; 88 89 /** Auto-connect after pairing only if locally initiated. */ 90 private boolean mConnectAfterPairing; 91 92 /** 93 * Describes the current device and profile for logging. 94 * 95 * @param profile Profile to describe 96 * @return Description of the device and profile 97 */ 98 private String describe(CachedBluetoothDevice cachedDevice, Profile profile) { 99 StringBuilder sb = new StringBuilder(); 100 sb.append("Address:").append(cachedDevice.mDevice); 101 if (profile != null) { 102 sb.append(" Profile:").append(profile.name()); 103 } 104 105 return sb.toString(); 106 } 107 108 private String describe(Profile profile) { 109 return describe(this, profile); 110 } 111 112 public void onProfileStateChanged(Profile profile, int newProfileState) { 113 if (D) { 114 Log.d(TAG, "onProfileStateChanged: profile " + profile.toString() + 115 " newProfileState " + newProfileState); 116 } 117 118 int newState = LocalBluetoothProfileManager.getProfileManager(mLocalManager, 119 profile).convertState(newProfileState); 120 121 if (newState == SettingsBtStatus.CONNECTION_STATUS_CONNECTED) { 122 if (!mProfiles.contains(profile)) { 123 mProfiles.add(profile); 124 } 125 } 126 } 127 128 CachedBluetoothDevice(Context context, BluetoothDevice device) { 129 mLocalManager = LocalBluetoothManager.getInstance(context); 130 if (mLocalManager == null) { 131 throw new IllegalStateException( 132 "Cannot use CachedBluetoothDevice without Bluetooth hardware"); 133 } 134 135 mDevice = device; 136 137 fillData(); 138 } 139 140 public void onClicked() { 141 int bondState = getBondState(); 142 143 if (isConnected()) { 144 askDisconnect(); 145 } else if (bondState == BluetoothDevice.BOND_BONDED) { 146 connect(); 147 } else if (bondState == BluetoothDevice.BOND_NONE) { 148 pair(); 149 } 150 } 151 152 public void disconnect() { 153 for (Profile profile : mProfiles) { 154 disconnect(profile); 155 } 156 } 157 158 public void disconnect(Profile profile) { 159 disconnectInt(this, profile); 160 } 161 162 private boolean disconnectInt(CachedBluetoothDevice cachedDevice, Profile profile) { 163 LocalBluetoothProfileManager profileManager = 164 LocalBluetoothProfileManager.getProfileManager(mLocalManager, profile); 165 int status = profileManager.getConnectionStatus(cachedDevice.mDevice); 166 if (profileManager.disconnect(cachedDevice.mDevice)) { 167 if (D) { 168 Log.d(TAG, "Command sent successfully:DISCONNECT " + describe(profile)); 169 } 170 return true; 171 } 172 return false; 173 } 174 175 public void askDisconnect() { 176 Context context = mLocalManager.getForegroundActivity(); 177 if (context == null) { 178 // Cannot ask, since we need an activity context 179 disconnect(); 180 return; 181 } 182 183 Resources res = context.getResources(); 184 185 String name = getName(); 186 if (TextUtils.isEmpty(name)) { 187 name = res.getString(R.string.bluetooth_device); 188 } 189 String message = res.getString(R.string.bluetooth_disconnect_blank, name); 190 191 DialogInterface.OnClickListener disconnectListener = new DialogInterface.OnClickListener() { 192 public void onClick(DialogInterface dialog, int which) { 193 disconnect(); 194 } 195 }; 196 197 new AlertDialog.Builder(context) 198 .setTitle(getName()) 199 .setMessage(message) 200 .setPositiveButton(android.R.string.ok, disconnectListener) 201 .setNegativeButton(android.R.string.cancel, null) 202 .show(); 203 } 204 205 public void connect() { 206 if (!ensurePaired()) return; 207 208 mConnectAttempted = SystemClock.elapsedRealtime(); 209 210 connectWithoutResettingTimer(); 211 } 212 213 /*package*/ void onBondingDockConnect() { 214 // Attempt to connect if UUIDs are available. Otherwise, 215 // we will connect when the ACTION_UUID intent arrives. 216 connect(); 217 } 218 219 private void connectWithoutResettingTimer() { 220 // Try to initialize the profiles if there were not. 221 if (mProfiles.size() == 0) { 222 if (!updateProfiles()) { 223 // If UUIDs are not available yet, connect will be happen 224 // upon arrival of the ACTION_UUID intent. 225 if (DEBUG) Log.d(TAG, "No profiles. Maybe we will connect later"); 226 return; 227 } 228 } 229 230 // Reset the only-show-one-error-dialog tracking variable 231 mIsConnectingErrorPossible = true; 232 233 int preferredProfiles = 0; 234 for (Profile profile : mProfiles) { 235 if (isConnectableProfile(profile)) { 236 LocalBluetoothProfileManager profileManager = LocalBluetoothProfileManager 237 .getProfileManager(mLocalManager, profile); 238 if (profileManager.isPreferred(mDevice)) { 239 ++preferredProfiles; 240 disconnectConnected(this, profile); 241 connectInt(this, profile); 242 } 243 } 244 } 245 if (DEBUG) Log.d(TAG, "Preferred profiles = " + preferredProfiles); 246 247 if (preferredProfiles == 0) { 248 connectAllProfiles(); 249 } 250 } 251 252 private void connectAllProfiles() { 253 if (!ensurePaired()) return; 254 255 // Reset the only-show-one-error-dialog tracking variable 256 mIsConnectingErrorPossible = true; 257 258 for (Profile profile : mProfiles) { 259 if (isConnectableProfile(profile)) { 260 LocalBluetoothProfileManager profileManager = LocalBluetoothProfileManager 261 .getProfileManager(mLocalManager, profile); 262 profileManager.setPreferred(mDevice, false); 263 disconnectConnected(this, profile); 264 connectInt(this, profile); 265 } 266 } 267 } 268 269 public void connect(Profile profile) { 270 mConnectAttempted = SystemClock.elapsedRealtime(); 271 // Reset the only-show-one-error-dialog tracking variable 272 mIsConnectingErrorPossible = true; 273 disconnectConnected(this, profile); 274 connectInt(this, profile); 275 } 276 277 private void disconnectConnected(CachedBluetoothDevice device, Profile profile) { 278 LocalBluetoothProfileManager profileManager = 279 LocalBluetoothProfileManager.getProfileManager(mLocalManager, profile); 280 CachedBluetoothDeviceManager cachedDeviceManager = mLocalManager.getCachedDeviceManager(); 281 Set<BluetoothDevice> devices = profileManager.getConnectedDevices(); 282 if (devices == null) return; 283 for (BluetoothDevice btDevice : devices) { 284 CachedBluetoothDevice cachedDevice = cachedDeviceManager.findDevice(btDevice); 285 286 if (cachedDevice != null && !cachedDevice.equals(device)) { 287 disconnectInt(cachedDevice, profile); 288 } 289 } 290 } 291 292 private boolean connectInt(CachedBluetoothDevice cachedDevice, Profile profile) { 293 if (!cachedDevice.ensurePaired()) return false; 294 295 LocalBluetoothProfileManager profileManager = 296 LocalBluetoothProfileManager.getProfileManager(mLocalManager, profile); 297 int status = profileManager.getConnectionStatus(cachedDevice.mDevice); 298 if (profileManager.connect(cachedDevice.mDevice)) { 299 if (D) { 300 Log.d(TAG, "Command sent successfully:CONNECT " + describe(profile)); 301 } 302 return true; 303 } 304 Log.i(TAG, "Failed to connect " + profile.toString() + " to " + cachedDevice.mName); 305 306 return false; 307 } 308 309 public void showConnectingError() { 310 if (!mIsConnectingErrorPossible) return; 311 mIsConnectingErrorPossible = false; 312 313 mLocalManager.showError(mDevice, R.string.bluetooth_error_title, 314 R.string.bluetooth_connecting_error_message); 315 } 316 317 private boolean ensurePaired() { 318 if (getBondState() == BluetoothDevice.BOND_NONE) { 319 pair(); 320 return false; 321 } else { 322 return true; 323 } 324 } 325 326 public void pair() { 327 BluetoothAdapter adapter = mLocalManager.getBluetoothAdapter(); 328 329 // Pairing is unreliable while scanning, so cancel discovery 330 if (adapter.isDiscovering()) { 331 adapter.cancelDiscovery(); 332 } 333 334 if (!mDevice.createBond()) { 335 mLocalManager.showError(mDevice, R.string.bluetooth_error_title, 336 R.string.bluetooth_pairing_error_message); 337 return; 338 } 339 340 mConnectAfterPairing = true; // auto-connect after pairing 341 } 342 343 public void unpair() { 344 disconnect(); 345 346 int state = getBondState(); 347 348 if (state == BluetoothDevice.BOND_BONDING) { 349 mDevice.cancelBondProcess(); 350 } 351 352 if (state != BluetoothDevice.BOND_NONE) { 353 final BluetoothDevice dev = getDevice(); 354 if (dev != null) { 355 final boolean successful = dev.removeBond(); 356 if (successful) { 357 if (D) { 358 Log.d(TAG, "Command sent successfully:REMOVE_BOND " + describe(null)); 359 } 360 } else if (V) { 361 Log.v(TAG, "Framework rejected command immediately:REMOVE_BOND " + 362 describe(null)); 363 } 364 } 365 } 366 } 367 368 private void fillData() { 369 fetchName(); 370 fetchBtClass(); 371 updateProfiles(); 372 373 mVisible = false; 374 375 dispatchAttributesChanged(); 376 } 377 378 public BluetoothDevice getDevice() { 379 return mDevice; 380 } 381 382 public String getName() { 383 return mName; 384 } 385 386 public void setName(String name) { 387 if (!mName.equals(name)) { 388 if (TextUtils.isEmpty(name)) { 389 mName = mDevice.getAddress(); 390 } else { 391 mName = name; 392 } 393 dispatchAttributesChanged(); 394 } 395 } 396 397 public void refreshName() { 398 fetchName(); 399 dispatchAttributesChanged(); 400 } 401 402 private void fetchName() { 403 mName = mDevice.getName(); 404 405 if (TextUtils.isEmpty(mName)) { 406 mName = mDevice.getAddress(); 407 if (DEBUG) Log.d(TAG, "Default to address. Device has no name (yet) " + mName); 408 } 409 } 410 411 public void refresh() { 412 dispatchAttributesChanged(); 413 } 414 415 public boolean isVisible() { 416 return mVisible; 417 } 418 419 void setVisible(boolean visible) { 420 if (mVisible != visible) { 421 mVisible = visible; 422 dispatchAttributesChanged(); 423 } 424 } 425 426 public int getBondState() { 427 return mDevice.getBondState(); 428 } 429 430 void setRssi(short rssi) { 431 if (mRssi != rssi) { 432 mRssi = rssi; 433 dispatchAttributesChanged(); 434 } 435 } 436 437 /** 438 * Checks whether we are connected to this device (any profile counts). 439 * 440 * @return Whether it is connected. 441 */ 442 public boolean isConnected() { 443 for (Profile profile : mProfiles) { 444 int status = LocalBluetoothProfileManager.getProfileManager(mLocalManager, profile) 445 .getConnectionStatus(mDevice); 446 if (SettingsBtStatus.isConnectionStatusConnected(status)) { 447 return true; 448 } 449 } 450 451 return false; 452 } 453 454 public boolean isBusy() { 455 for (Profile profile : mProfiles) { 456 int status = LocalBluetoothProfileManager.getProfileManager(mLocalManager, profile) 457 .getConnectionStatus(mDevice); 458 if (SettingsBtStatus.isConnectionStatusBusy(status)) { 459 return true; 460 } 461 } 462 463 if (getBondState() == BluetoothDevice.BOND_BONDING) { 464 return true; 465 } 466 467 return false; 468 } 469 470 public int getBtClassDrawable() { 471 if (mBtClass != null) { 472 switch (mBtClass.getMajorDeviceClass()) { 473 case BluetoothClass.Device.Major.COMPUTER: 474 return R.drawable.ic_bt_laptop; 475 476 case BluetoothClass.Device.Major.PHONE: 477 return R.drawable.ic_bt_cellphone; 478 } 479 } else { 480 Log.w(TAG, "mBtClass is null"); 481 } 482 483 if (mProfiles.size() > 0) { 484 if (mProfiles.contains(Profile.A2DP)) { 485 return R.drawable.ic_bt_headphones_a2dp; 486 } else if (mProfiles.contains(Profile.HEADSET)) { 487 return R.drawable.ic_bt_headset_hfp; 488 } 489 } else if (mBtClass != null) { 490 if (mBtClass.doesClassMatch(BluetoothClass.PROFILE_A2DP)) { 491 return R.drawable.ic_bt_headphones_a2dp; 492 493 } 494 if (mBtClass.doesClassMatch(BluetoothClass.PROFILE_HEADSET)) { 495 return R.drawable.ic_bt_headset_hfp; 496 } 497 } 498 return 0; 499 } 500 501 /** 502 * Fetches a new value for the cached BT class. 503 */ 504 private void fetchBtClass() { 505 mBtClass = mDevice.getBluetoothClass(); 506 } 507 508 private boolean updateProfiles() { 509 ParcelUuid[] uuids = mDevice.getUuids(); 510 if (uuids == null) return false; 511 512 LocalBluetoothProfileManager.updateProfiles(uuids, mProfiles); 513 514 if (DEBUG) { 515 Log.e(TAG, "updating profiles for " + mDevice.getName()); 516 517 boolean printUuids = true; 518 BluetoothClass bluetoothClass = mDevice.getBluetoothClass(); 519 520 if (bluetoothClass != null) { 521 if (bluetoothClass.doesClassMatch(BluetoothClass.PROFILE_HEADSET) != 522 mProfiles.contains(Profile.HEADSET)) { 523 Log.v(TAG, "headset classbits != uuid"); 524 printUuids = true; 525 } 526 527 if (bluetoothClass.doesClassMatch(BluetoothClass.PROFILE_A2DP) != 528 mProfiles.contains(Profile.A2DP)) { 529 Log.v(TAG, "a2dp classbits != uuid"); 530 printUuids = true; 531 } 532 533 if (bluetoothClass.doesClassMatch(BluetoothClass.PROFILE_OPP) != 534 mProfiles.contains(Profile.OPP)) { 535 Log.v(TAG, "opp classbits != uuid"); 536 printUuids = true; 537 } 538 } 539 540 if (printUuids) { 541 if (bluetoothClass != null) Log.v(TAG, "Class: " + bluetoothClass.toString()); 542 Log.v(TAG, "UUID:"); 543 for (int i = 0; i < uuids.length; i++) { 544 Log.v(TAG, " " + uuids[i]); 545 } 546 } 547 } 548 return true; 549 } 550 551 /** 552 * Refreshes the UI for the BT class, including fetching the latest value 553 * for the class. 554 */ 555 public void refreshBtClass() { 556 fetchBtClass(); 557 dispatchAttributesChanged(); 558 } 559 560 /** 561 * Refreshes the UI when framework alerts us of a UUID change. 562 */ 563 public void onUuidChanged() { 564 updateProfiles(); 565 566 if (DEBUG) { 567 Log.e(TAG, "onUuidChanged: Time since last connect" 568 + (SystemClock.elapsedRealtime() - mConnectAttempted)); 569 } 570 571 /* 572 * If a connect was attempted earlier without any UUID, we will do the 573 * connect now. 574 */ 575 if (mProfiles.size() > 0 576 && (mConnectAttempted + MAX_UUID_DELAY_FOR_AUTO_CONNECT) > SystemClock 577 .elapsedRealtime()) { 578 connectWithoutResettingTimer(); 579 } 580 dispatchAttributesChanged(); 581 } 582 583 public void onBondingStateChanged(int bondState) { 584 if (bondState == BluetoothDevice.BOND_NONE) { 585 mProfiles.clear(); 586 mConnectAfterPairing = false; // cancel auto-connect 587 } 588 589 refresh(); 590 591 if (bondState == BluetoothDevice.BOND_BONDED) { 592 if (mDevice.isBluetoothDock()) { 593 onBondingDockConnect(); 594 } else if (mConnectAfterPairing) { 595 connect(); 596 } 597 mConnectAfterPairing = false; 598 } 599 } 600 601 public void setBtClass(BluetoothClass btClass) { 602 if (btClass != null && mBtClass != btClass) { 603 mBtClass = btClass; 604 dispatchAttributesChanged(); 605 } 606 } 607 608 public int getSummary() { 609 // TODO: clean up 610 int oneOffSummary = getOneOffSummary(); 611 if (oneOffSummary != 0) { 612 return oneOffSummary; 613 } 614 615 for (Profile profile : mProfiles) { 616 LocalBluetoothProfileManager profileManager = LocalBluetoothProfileManager 617 .getProfileManager(mLocalManager, profile); 618 int connectionStatus = profileManager.getConnectionStatus(mDevice); 619 620 if (SettingsBtStatus.isConnectionStatusConnected(connectionStatus) || 621 connectionStatus == SettingsBtStatus.CONNECTION_STATUS_CONNECTING || 622 connectionStatus == SettingsBtStatus.CONNECTION_STATUS_DISCONNECTING) { 623 return SettingsBtStatus.getConnectionStatusSummary(connectionStatus); 624 } 625 } 626 627 return SettingsBtStatus.getPairingStatusSummary(getBondState()); 628 } 629 630 /** 631 * We have special summaries when particular profiles are connected. This 632 * checks for those states and returns an applicable summary. 633 * 634 * @return A one-off summary that is applicable for the current state, or 0. 635 */ 636 private int getOneOffSummary() { 637 boolean isA2dpConnected = false, isHeadsetConnected = false, isConnecting = false; 638 639 if (mProfiles.contains(Profile.A2DP)) { 640 LocalBluetoothProfileManager profileManager = LocalBluetoothProfileManager 641 .getProfileManager(mLocalManager, Profile.A2DP); 642 isConnecting = profileManager.getConnectionStatus(mDevice) == 643 SettingsBtStatus.CONNECTION_STATUS_CONNECTING; 644 isA2dpConnected = profileManager.isConnected(mDevice); 645 } 646 647 if (mProfiles.contains(Profile.HEADSET)) { 648 LocalBluetoothProfileManager profileManager = LocalBluetoothProfileManager 649 .getProfileManager(mLocalManager, Profile.HEADSET); 650 isConnecting |= profileManager.getConnectionStatus(mDevice) == 651 SettingsBtStatus.CONNECTION_STATUS_CONNECTING; 652 isHeadsetConnected = profileManager.isConnected(mDevice); 653 } 654 655 if (isConnecting) { 656 // If any of these important profiles is connecting, prefer that 657 return SettingsBtStatus.getConnectionStatusSummary( 658 SettingsBtStatus.CONNECTION_STATUS_CONNECTING); 659 } else if (isA2dpConnected && isHeadsetConnected) { 660 return R.string.bluetooth_summary_connected_to_a2dp_headset; 661 } else if (isA2dpConnected) { 662 return R.string.bluetooth_summary_connected_to_a2dp; 663 } else if (isHeadsetConnected) { 664 return R.string.bluetooth_summary_connected_to_headset; 665 } else { 666 return 0; 667 } 668 } 669 670 public List<Profile> getConnectableProfiles() { 671 ArrayList<Profile> connectableProfiles = new ArrayList<Profile>(); 672 for (Profile profile : mProfiles) { 673 if (isConnectableProfile(profile)) { 674 connectableProfiles.add(profile); 675 } 676 } 677 return connectableProfiles; 678 } 679 680 private boolean isConnectableProfile(Profile profile) { 681 return profile.equals(Profile.HEADSET) || profile.equals(Profile.A2DP); 682 } 683 684 public void onCreateContextMenu(ContextMenu menu) { 685 // No context menu if it is busy (none of these items are applicable if busy) 686 if (mLocalManager.getBluetoothState() != BluetoothAdapter.STATE_ON || isBusy()) { 687 return; 688 } 689 690 int bondState = getBondState(); 691 boolean isConnected = isConnected(); 692 boolean hasConnectableProfiles = false; 693 694 for (Profile profile : mProfiles) { 695 if (isConnectableProfile(profile)) { 696 hasConnectableProfiles = true; 697 break; 698 } 699 } 700 701 menu.setHeaderTitle(getName()); 702 703 if (bondState == BluetoothDevice.BOND_NONE) { // Not paired and not connected 704 menu.add(0, CONTEXT_ITEM_CONNECT, 0, R.string.bluetooth_device_context_pair_connect); 705 } else { // Paired 706 if (isConnected) { // Paired and connected 707 menu.add(0, CONTEXT_ITEM_DISCONNECT, 0, 708 R.string.bluetooth_device_context_disconnect); 709 menu.add(0, CONTEXT_ITEM_UNPAIR, 0, 710 R.string.bluetooth_device_context_disconnect_unpair); 711 } else { // Paired but not connected 712 if (hasConnectableProfiles) { 713 menu.add(0, CONTEXT_ITEM_CONNECT, 0, R.string.bluetooth_device_context_connect); 714 } 715 menu.add(0, CONTEXT_ITEM_UNPAIR, 0, R.string.bluetooth_device_context_unpair); 716 } 717 718 // Show the connection options item 719 if (hasConnectableProfiles) { 720 menu.add(0, CONTEXT_ITEM_CONNECT_ADVANCED, 0, 721 R.string.bluetooth_device_context_connect_advanced); 722 } 723 } 724 } 725 726 /** 727 * Called when a context menu item is clicked. 728 * 729 * @param item The item that was clicked. 730 */ 731 public void onContextItemSelected(MenuItem item) { 732 switch (item.getItemId()) { 733 case CONTEXT_ITEM_DISCONNECT: 734 disconnect(); 735 break; 736 737 case CONTEXT_ITEM_CONNECT: 738 connect(); 739 break; 740 741 case CONTEXT_ITEM_UNPAIR: 742 unpair(); 743 break; 744 745 case CONTEXT_ITEM_CONNECT_ADVANCED: 746 Intent intent = new Intent(); 747 // Need an activity context to open this in our task 748 Context context = mLocalManager.getForegroundActivity(); 749 if (context == null) { 750 // Fallback on application context, and open in a new task 751 context = mLocalManager.getContext(); 752 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 753 } 754 intent.setClass(context, ConnectSpecificProfilesActivity.class); 755 intent.putExtra(ConnectSpecificProfilesActivity.EXTRA_DEVICE, mDevice); 756 context.startActivity(intent); 757 break; 758 } 759 } 760 761 public void registerCallback(Callback callback) { 762 synchronized (mCallbacks) { 763 mCallbacks.add(callback); 764 } 765 } 766 767 public void unregisterCallback(Callback callback) { 768 synchronized (mCallbacks) { 769 mCallbacks.remove(callback); 770 } 771 } 772 773 private void dispatchAttributesChanged() { 774 synchronized (mCallbacks) { 775 for (Callback callback : mCallbacks) { 776 callback.onDeviceAttributesChanged(this); 777 } 778 } 779 } 780 781 @Override 782 public String toString() { 783 return mDevice.toString(); 784 } 785 786 @Override 787 public boolean equals(Object o) { 788 if ((o == null) || !(o instanceof CachedBluetoothDevice)) { 789 throw new ClassCastException(); 790 } 791 792 return mDevice.equals(((CachedBluetoothDevice) o).mDevice); 793 } 794 795 @Override 796 public int hashCode() { 797 return mDevice.getAddress().hashCode(); 798 } 799 800 public int compareTo(CachedBluetoothDevice another) { 801 int comparison; 802 803 // Connected above not connected 804 comparison = (another.isConnected() ? 1 : 0) - (isConnected() ? 1 : 0); 805 if (comparison != 0) return comparison; 806 807 // Paired above not paired 808 comparison = (another.getBondState() == BluetoothDevice.BOND_BONDED ? 1 : 0) - 809 (getBondState() == BluetoothDevice.BOND_BONDED ? 1 : 0); 810 if (comparison != 0) return comparison; 811 812 // Visible above not visible 813 comparison = (another.mVisible ? 1 : 0) - (mVisible ? 1 : 0); 814 if (comparison != 0) return comparison; 815 816 // Stronger signal above weaker signal 817 comparison = another.mRssi - mRssi; 818 if (comparison != 0) return comparison; 819 820 // Fallback on name 821 return getName().compareTo(another.getName()); 822 } 823 824 public interface Callback { 825 void onDeviceAttributesChanged(CachedBluetoothDevice cachedDevice); 826 } 827} 828