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