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