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