• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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