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