• 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 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