1 /* 2 * Copyright (C) 2014 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.android.tv.settings.accessories; 18 19 import android.bluetooth.BluetoothAdapter; 20 import android.bluetooth.BluetoothClass; 21 import android.bluetooth.BluetoothDevice; 22 import android.content.BroadcastReceiver; 23 import android.content.ComponentName; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.IntentFilter; 27 import android.content.pm.PackageManager; 28 import android.hardware.input.InputManager; 29 import android.os.Handler; 30 import android.os.Message; 31 import android.os.SystemClock; 32 import android.util.Log; 33 import android.view.InputDevice; 34 35 import com.android.tv.settings.util.bluetooth.BluetoothDeviceCriteria; 36 import com.android.tv.settings.util.bluetooth.BluetoothScanner; 37 38 import java.time.Duration; 39 import java.util.ArrayList; 40 import java.util.List; 41 import com.android.tv.settings.R; 42 43 /** 44 * Monitors available Bluetooth devices and manages process of pairing 45 * and connecting to the device. 46 */ 47 public class BluetoothDevicePairer { 48 49 /** 50 * This class operates in two modes, automatic and manual. 51 * 52 * AUTO MODE 53 * In auto mode we listen for an input device that looks like it can 54 * generate DPAD events. When one is found we wait 55 * {@link #DELAY_AUTO_PAIRING} milliseconds before starting the process of 56 * connecting to the device. The idea is that a UI making use of this class 57 * would give the user a chance to cancel pairing during this window. Once 58 * the connection process starts, it is considered uninterruptible. 59 * 60 * Connection is accomplished in two phases, bonding and socket connection. 61 * First we try to create a bond to the device and listen for bond status 62 * change broadcasts. Once the bond is made, we connect to the device. 63 * Connecting to the device actually opens a socket and hooks the device up 64 * to the input system. 65 * 66 * In auto mode if we see more than one compatible input device before 67 * bonding with a candidate device, we stop the process. We don't want to 68 * connect to the wrong device and it is up to the user of this class to 69 * tell us what to connect to. 70 * 71 * MANUAL MODE 72 * Manual mode is where a user of this class explicitly tells us which 73 * device to connect to. To switch to manual mode you can call 74 * {@link #cancelPairing()}. It is safe to call this method even if no 75 * device connection process is underway. You would then call 76 * {@link #start()} to resume scanning for devices. Once one is found 77 * that you want to connect to, call {@link #startPairing(BluetoothDevice)} 78 * to start the connection process. At this point the same process is 79 * followed as when we start connection in auto mode. 80 * 81 * Even in manual mode there is a timeout before we actually start 82 * connecting, but it is {@link #DELAY_MANUAL_PAIRING}. 83 */ 84 85 public static final String TAG = "BluetoothDevicePairer"; 86 public static final int STATUS_ERROR = -1; 87 public static final int STATUS_NONE = 0; 88 public static final int STATUS_SCANNING = 1; 89 /** 90 * A device to pair with has been identified, we're currently in the 91 * timeout period where the process can be cancelled. 92 */ 93 public static final int STATUS_WAITING_TO_PAIR = 2; 94 /** 95 * Pairing is in progress. 96 */ 97 public static final int STATUS_PAIRING = 3; 98 /** 99 * Device has been paired with, we are opening a connection to the device. 100 */ 101 public static final int STATUS_CONNECTING = 4; 102 /** 103 * BR/EDR mice need to be handled separately because of the unique 104 * connection establishment sequence. 105 */ 106 public static final int STATUS_SUCCEED_BREDRMOUSE = 5; 107 108 109 public interface EventListener { 110 /** 111 * The status of the {@link BluetoothDevicePairer} changed. 112 */ statusChanged()113 void statusChanged(); 114 } 115 116 public interface BluetoothConnector { openConnection(BluetoothAdapter adapter)117 void openConnection(BluetoothAdapter adapter); dispose()118 void dispose(); 119 } 120 121 public interface OpenConnectionCallback { 122 /** 123 * Call back when BT device connection is completed. 124 */ succeeded()125 void succeeded(); failed()126 void failed(); 127 } 128 129 /** 130 * Time between when a single input device is found and pairing begins. If 131 * one or more other input devices are found before this timeout or 132 * {@link #cancelPairing()} is called then pairing will not proceed. 133 */ 134 public static final int DELAY_AUTO_PAIRING = 15 * 1000; 135 /** 136 * Time between when the call to {@link #startPairing(BluetoothDevice)} is 137 * called and when we actually start pairing. This gives the caller a 138 * chance to change their mind. 139 */ 140 public static final int DELAY_MANUAL_PAIRING = 5 * 1000; 141 /** 142 * If there was an error in pairing, we will wait this long before trying 143 * again. 144 */ 145 public static final int DELAY_RETRY = 5 * 1000; 146 147 private static final int MSG_PAIR = 1; 148 private static final int MSG_START = 2; 149 150 private static final boolean DEBUG = true; 151 152 private static final String[] INVALID_INPUT_KEYBOARD_DEVICE_NAMES = { 153 "gpio-keypad", "cec_keyboard", "Virtual", "athome_remote" 154 }; 155 156 private static final int SCAN_MODE_NOT_SET = 0; 157 158 private final BluetoothScanner.Listener mBtListener = new BluetoothScanner.Listener() { 159 @Override 160 public void onDeviceAdded(BluetoothScanner.Device device) { 161 if (DEBUG) { 162 Log.d(TAG, "Adding device: " + device.btDevice.getAddress()); 163 } 164 onDeviceFound(device.btDevice); 165 } 166 167 @Override 168 public void onDeviceRemoved(BluetoothScanner.Device device) { 169 if (DEBUG) { 170 Log.d(TAG, "Device lost: " + device.btDevice.getAddress()); 171 } 172 onDeviceLost(device.btDevice); 173 } 174 }; 175 hasValidInputDevice(Context context, int[] deviceIds)176 public static boolean hasValidInputDevice(Context context, int[] deviceIds) { 177 InputManager inMan = (InputManager) context.getSystemService(Context.INPUT_SERVICE); 178 179 for (int ptr = deviceIds.length - 1; ptr > -1; ptr--) { 180 InputDevice device = inMan.getInputDevice(deviceIds[ptr]); 181 int sources = device.getSources(); 182 183 boolean isCompatible = false; 184 185 if ((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD) { 186 isCompatible = true; 187 } 188 189 if ((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) { 190 isCompatible = true; 191 } 192 193 if ((sources & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE) { 194 isCompatible = true; 195 } 196 197 if ((sources & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK) { 198 isCompatible = true; 199 } 200 201 if ((sources & InputDevice.SOURCE_KEYBOARD) == InputDevice.SOURCE_KEYBOARD) { 202 boolean isValidKeyboard = true; 203 String keyboardName = device.getName(); 204 for (int index = 0; index < INVALID_INPUT_KEYBOARD_DEVICE_NAMES.length; ++index) { 205 if (keyboardName.equals(INVALID_INPUT_KEYBOARD_DEVICE_NAMES[index])) { 206 isValidKeyboard = false; 207 break; 208 } 209 } 210 211 if (isValidKeyboard) { 212 isCompatible = true; 213 } 214 } 215 216 if (!device.isVirtual() && isCompatible) { 217 return true; 218 } 219 } 220 return false; 221 } 222 hasValidInputDevice(Context context)223 public static boolean hasValidInputDevice(Context context) { 224 InputManager inMan = (InputManager) context.getSystemService(Context.INPUT_SERVICE); 225 int[] inputDevices = inMan.getInputDeviceIds(); 226 227 return hasValidInputDevice(context, inputDevices); 228 } 229 230 private final BroadcastReceiver mLinkStatusReceiver = new BroadcastReceiver() { 231 @Override 232 public void onReceive(Context context, Intent intent) { 233 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 234 if (DEBUG) { 235 Log.d(TAG, "There was a link status change for: " + device.getAddress()); 236 } 237 238 if (device.equals(mTarget)) { 239 int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, 240 BluetoothDevice.BOND_NONE); 241 int previousBondState = intent.getIntExtra( 242 BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, BluetoothDevice.BOND_NONE); 243 244 if (DEBUG) { 245 Log.d(TAG, "Bond states: old = " + previousBondState + ", new = " + 246 bondState); 247 } 248 249 if (bondState == BluetoothDevice.BOND_NONE && 250 previousBondState == BluetoothDevice.BOND_BONDING) { 251 // we seem to have reverted, this is an error 252 // TODO inform user, start scanning again 253 unregisterLinkStatusReceiver(); 254 onBondFailed(); 255 } else if (bondState == BluetoothDevice.BOND_BONDED) { 256 unregisterLinkStatusReceiver(); 257 onBonded(); 258 } 259 } 260 } 261 }; 262 263 private BroadcastReceiver mBluetoothStateReceiver; 264 265 private final OpenConnectionCallback mOpenConnectionCallback = new OpenConnectionCallback() { 266 public void succeeded() { 267 setStatus(STATUS_NONE); 268 } 269 public void failed() { 270 setStatus(STATUS_ERROR); 271 } 272 }; 273 274 private final Context mContext; 275 private EventListener mListener; 276 private int mStatus = STATUS_NONE; 277 /** 278 * Set to {@code false} when {@link #cancelPairing()} or 279 * {@link #startPairing(BluetoothDevice)}. This instance 280 * will now no longer automatically start pairing. 281 */ 282 private boolean mAutoMode = true; 283 private final ArrayList<BluetoothDevice> mVisibleDevices = new ArrayList<>(); 284 private BluetoothDevice mTarget; 285 private final Handler mHandler; 286 private long mNextStageTimestamp = -1; 287 private boolean mLinkReceiverRegistered = false; 288 private final ArrayList<BluetoothDeviceCriteria> mBluetoothDeviceCriteria = new ArrayList<>(); 289 private InputDeviceCriteria mInputDeviceCriteria; 290 private int mDefaultScanMode = SCAN_MODE_NOT_SET; 291 private BluetoothConnector mBTConnector = null; 292 293 /** 294 * Should be instantiated on a thread with a Looper, perhaps the main thread! 295 */ BluetoothDevicePairer(Context context, EventListener listener)296 public BluetoothDevicePairer(Context context, EventListener listener) { 297 mContext = context.getApplicationContext(); 298 mListener = listener; 299 300 addBluetoothDeviceCriteria(); 301 302 mHandler = new Handler() { 303 @Override 304 public void handleMessage(Message msg) { 305 switch (msg.what) { 306 case MSG_PAIR: 307 startBonding(); 308 break; 309 case MSG_START: 310 start(); 311 break; 312 default: 313 Log.d(TAG, "No handler case available for message: " + msg.what); 314 } 315 } 316 }; 317 } 318 addBluetoothDeviceCriteria()319 private void addBluetoothDeviceCriteria() { 320 // Input is supported by all devices. 321 mInputDeviceCriteria = new InputDeviceCriteria(); 322 mBluetoothDeviceCriteria.add(mInputDeviceCriteria); 323 324 // Add Bluetooth a2dp on if the service is running and the 325 // setting profile_supported_a2dp is set to true. 326 Intent intent = new Intent("android.bluetooth.IBluetoothA2dp"); 327 ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0); 328 if (comp != null) { 329 int enabledState = mContext.getPackageManager().getComponentEnabledSetting(comp); 330 if (enabledState != PackageManager.COMPONENT_ENABLED_STATE_DISABLED) { 331 Log.d(TAG, "Adding A2dp device criteria for pairing"); 332 mBluetoothDeviceCriteria.add(new A2dpDeviceCriteria()); 333 } 334 } 335 } 336 337 /** 338 * Start listening for devices and begin the pairing process when 339 * criteria is met. 340 */ start()341 public void start() { 342 final BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 343 if (!bluetoothAdapter.isEnabled()) { 344 Log.d(TAG, "Bluetooth not enabled, delaying startup."); 345 if (mBluetoothStateReceiver == null) { 346 mBluetoothStateReceiver = new BroadcastReceiver() { 347 @Override 348 public void onReceive(Context context, Intent intent) { 349 if (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, 350 BluetoothAdapter.STATE_OFF) == BluetoothAdapter.STATE_ON) { 351 Log.d(TAG, "Bluetooth now enabled, starting."); 352 start(); 353 } else { 354 Log.d(TAG, "Bluetooth not yet started, got broadcast: " + intent); 355 } 356 } 357 }; 358 mContext.registerReceiver(mBluetoothStateReceiver, 359 new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)); 360 } 361 362 bluetoothAdapter.enable(); 363 return; 364 } else { 365 if (mBluetoothStateReceiver != null) { 366 mContext.unregisterReceiver(mBluetoothStateReceiver); 367 mBluetoothStateReceiver = null; 368 } 369 } 370 371 // Another device may initiate pairing. To accommodate this, turn on discoverability 372 // if it isn't already. 373 final int scanMode = bluetoothAdapter.getScanMode(); 374 if (scanMode != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) { 375 Log.d(TAG, "Turning on discoverability, default scan mode: " + scanMode); 376 mDefaultScanMode = scanMode; 377 // Remove discoverable timeout. 378 bluetoothAdapter.setDiscoverableTimeout(Duration.ZERO); 379 bluetoothAdapter.setScanMode( 380 BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE); 381 } 382 383 // set status to scanning before we start listening since 384 // startListening may result in a transition to STATUS_WAITING_TO_PAIR 385 // which might seem odd from a client perspective 386 setStatus(STATUS_SCANNING); 387 388 BluetoothScanner.startListening(mContext, mBtListener, mBluetoothDeviceCriteria); 389 } 390 clearDeviceList()391 public void clearDeviceList() { 392 doCancel(); 393 mVisibleDevices.clear(); 394 } 395 396 /** 397 * Stop any pairing request that is in progress. 398 */ cancelPairing()399 public void cancelPairing() { 400 mAutoMode = false; 401 doCancel(); 402 } 403 404 405 /** 406 * Switch to manual pairing mode. 407 */ disableAutoPairing()408 public void disableAutoPairing() { 409 mAutoMode = false; 410 } 411 412 /** 413 * Stop doing anything we're doing, release any resources. 414 */ dispose()415 public void dispose() { 416 final BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 417 if (mDefaultScanMode != SCAN_MODE_NOT_SET 418 && mDefaultScanMode != bluetoothAdapter.getScanMode()) { 419 Log.d(TAG, "Resetting discoverability to: " + mDefaultScanMode); 420 bluetoothAdapter.setScanMode(mDefaultScanMode); 421 } 422 423 mHandler.removeCallbacksAndMessages(null); 424 if (mLinkReceiverRegistered) { 425 unregisterLinkStatusReceiver(); 426 } 427 if (mBluetoothStateReceiver != null) { 428 mContext.unregisterReceiver(mBluetoothStateReceiver); 429 } 430 stopScanning(); 431 if (mBTConnector != null) { 432 mBTConnector.dispose(); 433 } 434 435 } 436 437 /** 438 * Start pairing and connection to the specified device. 439 * @param device device 440 */ startPairing(BluetoothDevice device)441 public void startPairing(BluetoothDevice device) { 442 startPairing(device, true); 443 } 444 445 /** 446 * Return our state 447 * @return One of the STATE_ constants. 448 */ getStatus()449 public int getStatus() { 450 return mStatus; 451 } 452 453 /** 454 * Get the device that we're currently targeting. This will be null if 455 * there is no device that is in the process of being connected to. 456 */ getTargetDevice()457 public BluetoothDevice getTargetDevice() { 458 return mTarget; 459 } 460 461 /** 462 * When the timer to start the next stage will expire, in {@link SystemClock#elapsedRealtime()}. 463 * Will only be valid while waiting to pair and after an error from which we are restarting. 464 */ getNextStageTime()465 public long getNextStageTime() { 466 return mNextStageTimestamp; 467 } 468 getAvailableDevices()469 public List<BluetoothDevice> getAvailableDevices() { 470 ArrayList<BluetoothDevice> copy = new ArrayList<>(mVisibleDevices.size()); 471 copy.addAll(mVisibleDevices); 472 return copy; 473 } 474 setListener(EventListener listener)475 public void setListener(EventListener listener) { 476 mListener = listener; 477 } 478 invalidateDevice(BluetoothDevice device)479 public void invalidateDevice(BluetoothDevice device) { 480 onDeviceLost(device); 481 } 482 startPairing(BluetoothDevice device, boolean isManual)483 private void startPairing(BluetoothDevice device, boolean isManual) { 484 // TODO check if we're already paired/bonded to this device 485 486 // cancel auto-mode if applicable 487 mAutoMode = !isManual; 488 489 mTarget = device; 490 491 if (isInProgress()) { 492 throw new RuntimeException("Pairing already in progress, you must cancel the " + 493 "previous request first"); 494 } 495 496 mHandler.removeCallbacksAndMessages(null); 497 498 int delay = DELAY_AUTO_PAIRING; 499 if (!mAutoMode) { 500 delay = mContext.getResources().getInteger(R.integer.config_delay_manual_pairing); 501 if (delay < 0) { 502 delay = DELAY_MANUAL_PAIRING; 503 } 504 } 505 506 mNextStageTimestamp = SystemClock.elapsedRealtime() + delay; 507 mHandler.sendEmptyMessageDelayed(MSG_PAIR, delay); 508 509 setStatus(STATUS_WAITING_TO_PAIR); 510 } 511 512 /** 513 * Pairing is in progress and is no longer cancelable. 514 */ isInProgress()515 public boolean isInProgress() { 516 return mStatus != STATUS_NONE && mStatus != STATUS_ERROR && mStatus != STATUS_SCANNING && 517 mStatus != STATUS_WAITING_TO_PAIR; 518 } 519 updateListener()520 private void updateListener() { 521 if (mListener != null) { 522 mListener.statusChanged(); 523 } 524 } 525 onDeviceFound(BluetoothDevice device)526 private void onDeviceFound(BluetoothDevice device) { 527 if (!mVisibleDevices.contains(device)) { 528 mVisibleDevices.add(device); 529 Log.d(TAG, "Added device to visible list. Name = " + device.getName() + " , class = " + 530 device.getBluetoothClass().getDeviceClass()); 531 } else { 532 return; 533 } 534 535 updatePairingState(); 536 // update the listener because a new device is visible 537 updateListener(); 538 } 539 onDeviceLost(BluetoothDevice device)540 private void onDeviceLost(BluetoothDevice device) { 541 // TODO validate removal works as expected 542 if (mVisibleDevices.remove(device)) { 543 updatePairingState(); 544 // update the listener because a device disappeared 545 updateListener(); 546 } 547 } 548 updatePairingState()549 private void updatePairingState() { 550 if (mAutoMode) { 551 BluetoothDevice candidate = getAutoPairDevice(); 552 if (null != candidate) { 553 mTarget = candidate; 554 startPairing(mTarget, false); 555 } else { 556 doCancel(); 557 } 558 } 559 } 560 561 /** 562 * @return returns the only visible input device if there is only one 563 */ getAutoPairDevice()564 private BluetoothDevice getAutoPairDevice() { 565 List<BluetoothDevice> inputDevices = new ArrayList<>(); 566 for (BluetoothDevice device : mVisibleDevices) { 567 if (mInputDeviceCriteria.isInputDevice(device.getBluetoothClass())) { 568 inputDevices.add(device); 569 } 570 } 571 if (inputDevices.size() == 1) { 572 return inputDevices.get(0); 573 } 574 return null; 575 } 576 doCancel()577 private void doCancel() { 578 // TODO allow cancel to be called from any state 579 if (isInProgress()) { 580 Log.d(TAG, "Pairing process has already begun, it can not be canceled."); 581 return; 582 } 583 584 // stop scanning, just in case we are 585 final boolean wasListening = BluetoothScanner.stopListening(mBtListener); 586 BluetoothScanner.stopNow(); 587 588 mHandler.removeCallbacksAndMessages(null); 589 590 // remove bond, if existing 591 unpairDevice(mTarget); 592 593 mTarget = null; 594 595 setStatus(STATUS_NONE); 596 597 // resume scanning 598 if (wasListening) { 599 start(); 600 } 601 } 602 603 /** 604 * Set the status and update any listener. 605 */ setStatus(int status)606 private void setStatus(int status) { 607 mStatus = status; 608 updateListener(); 609 } 610 startBonding()611 private void startBonding() { 612 stopScanning(); 613 setStatus(STATUS_PAIRING); 614 if (mTarget.getBondState() != BluetoothDevice.BOND_BONDED) { 615 registerLinkStatusReceiver(); 616 617 // create bond (pair) to the device 618 mTarget.createBond(); 619 } else { 620 onBonded(); 621 } 622 } 623 onBonded()624 private void onBonded() { 625 BluetoothDevice target = getTargetDevice(); 626 if (!(target.getBluetoothClass().getDeviceClass() 627 == BluetoothClass.Device.PERIPHERAL_POINTING) 628 || !(target.getType() == BluetoothDevice.DEVICE_TYPE_CLASSIC)) { 629 openConnection(); 630 } else if (target.isConnected()) { 631 setStatus(STATUS_SUCCEED_BREDRMOUSE); 632 } else { 633 Log.w(TAG, "There was an error connect by BR/EDR Mouse."); 634 setStatus(STATUS_ERROR); 635 } 636 } 637 openConnection()638 private void openConnection() { 639 BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); 640 mBTConnector = getBluetoothConnector(); 641 if (mBTConnector != null) { 642 setStatus(STATUS_CONNECTING); 643 mBTConnector.openConnection(adapter); 644 } else { 645 Log.w(TAG, "There was an error getting the BluetoothConnector."); 646 setStatus(STATUS_ERROR); 647 if (mLinkReceiverRegistered) { 648 unregisterLinkStatusReceiver(); 649 } 650 unpairDevice(mTarget); 651 } 652 } 653 onBondFailed()654 private void onBondFailed() { 655 Log.w(TAG, "There was an error bonding with the device."); 656 setStatus(STATUS_ERROR); 657 658 // remove bond, if existing 659 unpairDevice(mTarget); 660 661 // TODO do we need to check Bluetooth for the device and possible delete it? 662 mNextStageTimestamp = SystemClock.elapsedRealtime() + DELAY_RETRY; 663 mHandler.sendEmptyMessageDelayed(MSG_START, DELAY_RETRY); 664 } 665 registerLinkStatusReceiver()666 private void registerLinkStatusReceiver() { 667 mLinkReceiverRegistered = true; 668 IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED); 669 mContext.registerReceiver(mLinkStatusReceiver, filter); 670 } 671 unregisterLinkStatusReceiver()672 private void unregisterLinkStatusReceiver() { 673 mLinkReceiverRegistered = false; 674 mContext.unregisterReceiver(mLinkStatusReceiver); 675 } 676 stopScanning()677 private void stopScanning() { 678 BluetoothScanner.stopListening(mBtListener); 679 BluetoothScanner.stopNow(); 680 } 681 unpairDevice(BluetoothDevice device)682 public boolean unpairDevice(BluetoothDevice device) { 683 if (device != null) { 684 int state = device.getBondState(); 685 686 if (state == BluetoothDevice.BOND_BONDING) { 687 device.cancelBondProcess(); 688 } 689 690 if (state != BluetoothDevice.BOND_NONE) { 691 final boolean successful = device.removeBond(); 692 if (successful) { 693 if (DEBUG) { 694 Log.d(TAG, "Bluetooth device successfully unpaired: " + device.getName()); 695 } 696 return true; 697 } else { 698 Log.e(TAG, "Failed to unpair Bluetooth Device: " + device.getName()); 699 } 700 } 701 } 702 return false; 703 } 704 getBluetoothConnector()705 private BluetoothConnector getBluetoothConnector() { 706 int majorDeviceClass = mTarget.getBluetoothClass().getMajorDeviceClass(); 707 switch (majorDeviceClass) { 708 case BluetoothClass.Device.Major.PERIPHERAL: 709 return new BluetoothInputDeviceConnector( 710 mContext, mTarget, mHandler, mOpenConnectionCallback); 711 case BluetoothClass.Device.Major.AUDIO_VIDEO: 712 return new BluetoothA2dpConnector(mContext, mTarget, mOpenConnectionCallback); 713 default: 714 Log.d(TAG, "Unhandle device class: " + majorDeviceClass); 715 break; 716 } 717 return null; 718 } 719 } 720