LocalBluetoothProfileManager.java revision 659b39abf8580a49bd77f194c9ac30359f610709
1/*
2 * Copyright (C) 2008 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
17package com.android.settings.bluetooth;
18
19import com.android.settings.R;
20
21import android.bluetooth.BluetoothA2dp;
22import android.bluetooth.BluetoothAdapter;
23import android.bluetooth.BluetoothDevice;
24import android.bluetooth.BluetoothHeadset;
25import android.bluetooth.BluetoothInputDevice;
26import android.bluetooth.BluetoothPan;
27import android.bluetooth.BluetoothProfile;
28import android.bluetooth.BluetoothUuid;
29import android.os.Handler;
30import android.os.ParcelUuid;
31import android.util.Log;
32
33import java.util.HashMap;
34import java.util.HashSet;
35import java.util.Iterator;
36import java.util.LinkedList;
37import java.util.List;
38import java.util.Map;
39import java.util.Set;
40
41/**
42 * LocalBluetoothProfileManager is an abstract class defining the basic
43 * functionality related to a profile.
44 */
45public abstract class LocalBluetoothProfileManager {
46    private static final String TAG = "LocalBluetoothProfileManager";
47
48    /* package */ static final ParcelUuid[] HEADSET_PROFILE_UUIDS = new ParcelUuid[] {
49        BluetoothUuid.HSP,
50        BluetoothUuid.Handsfree,
51    };
52
53    /* package */ static final ParcelUuid[] A2DP_PROFILE_UUIDS = new ParcelUuid[] {
54        BluetoothUuid.AudioSink,
55        BluetoothUuid.AdvAudioDist,
56    };
57
58    /* package */ static final ParcelUuid[] OPP_PROFILE_UUIDS = new ParcelUuid[] {
59        BluetoothUuid.ObexObjectPush
60    };
61
62    /* package */ static final ParcelUuid[] HID_PROFILE_UUIDS = new ParcelUuid[] {
63        BluetoothUuid.Hid
64    };
65
66    /* package */ static final ParcelUuid[] PANU_PROFILE_UUIDS = new ParcelUuid[] {
67        BluetoothUuid.PANU
68    };
69
70    /* package */ static final ParcelUuid[] NAP_PROFILE_UUIDS = new ParcelUuid[] {
71        BluetoothUuid.NAP
72    };
73
74    /**
75     * An interface for notifying BluetoothHeadset IPC clients when they have
76     * been connected to the BluetoothHeadset service.
77     */
78    public interface ServiceListener {
79        /**
80         * Called to notify the client when this proxy object has been
81         * connected to the BluetoothHeadset service. Clients must wait for
82         * this callback before making IPC calls on the BluetoothHeadset
83         * service.
84         */
85        public void onServiceConnected();
86
87        /**
88         * Called to notify the client that this proxy object has been
89         * disconnected from the BluetoothHeadset service. Clients must not
90         * make IPC calls on the BluetoothHeadset service after this callback.
91         * This callback will currently only occur if the application hosting
92         * the BluetoothHeadset service, but may be called more often in future.
93         */
94        public void onServiceDisconnected();
95    }
96
97    // TODO: close profiles when we're shutting down
98    private static Map<Profile, LocalBluetoothProfileManager> sProfileMap =
99            new HashMap<Profile, LocalBluetoothProfileManager>();
100
101    protected LocalBluetoothManager mLocalManager;
102
103    public static void init(LocalBluetoothManager localManager) {
104        synchronized (sProfileMap) {
105            if (sProfileMap.size() == 0) {
106                LocalBluetoothProfileManager profileManager;
107
108                profileManager = new A2dpProfileManager(localManager);
109                sProfileMap.put(Profile.A2DP, profileManager);
110
111                profileManager = new HeadsetProfileManager(localManager);
112                sProfileMap.put(Profile.HEADSET, profileManager);
113
114                profileManager = new OppProfileManager(localManager);
115                sProfileMap.put(Profile.OPP, profileManager);
116
117                profileManager = new HidProfileManager(localManager);
118                sProfileMap.put(Profile.HID, profileManager);
119
120                profileManager = new PanProfileManager(localManager);
121                sProfileMap.put(Profile.PAN, profileManager);
122            }
123        }
124    }
125
126    private static LinkedList<ServiceListener> mServiceListeners = new LinkedList<ServiceListener>();
127
128    public static void addServiceListener(ServiceListener l) {
129        mServiceListeners.add(l);
130    }
131
132    public static void removeServiceListener(ServiceListener l) {
133        mServiceListeners.remove(l);
134    }
135
136    public static boolean isManagerReady() {
137        // Getting just the headset profile is fine for now. Will need to deal with A2DP
138        // and others if they aren't always in a ready state.
139        LocalBluetoothProfileManager profileManager = sProfileMap.get(Profile.HEADSET);
140        if (profileManager == null) {
141            return sProfileMap.size() > 0;
142        }
143        return profileManager.isProfileReady();
144    }
145
146    public static LocalBluetoothProfileManager getProfileManager(LocalBluetoothManager localManager,
147            Profile profile) {
148        // Note: This code assumes that "localManager" is same as the
149        // LocalBluetoothManager that was used to initialize the sProfileMap.
150        // If that every changes, we can't just keep one copy of sProfileMap.
151        synchronized (sProfileMap) {
152            LocalBluetoothProfileManager profileManager = sProfileMap.get(profile);
153            if (profileManager == null) {
154                Log.e(TAG, "profileManager can't be found for " + profile.toString());
155            }
156            return profileManager;
157        }
158    }
159
160    /**
161     * Temporary method to fill profiles based on a device's class.
162     *
163     * NOTE: This list happens to define the connection order. We should put this logic in a more
164     * well known place when this method is no longer temporary.
165     * @param uuids of the remote device
166     * @param profiles The list of profiles to fill
167     */
168    public static void updateProfiles(ParcelUuid[] uuids, List<Profile> profiles) {
169        profiles.clear();
170
171        if (uuids == null) {
172            return;
173        }
174
175        if (BluetoothUuid.containsAnyUuid(uuids, HEADSET_PROFILE_UUIDS)) {
176            profiles.add(Profile.HEADSET);
177        }
178
179        if (BluetoothUuid.containsAnyUuid(uuids, A2DP_PROFILE_UUIDS)) {
180            profiles.add(Profile.A2DP);
181        }
182
183        if (BluetoothUuid.containsAnyUuid(uuids, OPP_PROFILE_UUIDS)) {
184            profiles.add(Profile.OPP);
185        }
186
187        if (BluetoothUuid.containsAnyUuid(uuids, HID_PROFILE_UUIDS)) {
188            profiles.add(Profile.HID);
189        }
190
191        if (BluetoothUuid.containsAnyUuid(uuids, PANU_PROFILE_UUIDS)) {
192            profiles.add(Profile.PAN);
193        }
194    }
195
196    protected LocalBluetoothProfileManager(LocalBluetoothManager localManager) {
197        mLocalManager = localManager;
198    }
199
200    public abstract List<BluetoothDevice> getConnectedDevices();
201
202    public abstract boolean connect(BluetoothDevice device);
203
204    public abstract boolean disconnect(BluetoothDevice device);
205
206    public abstract int getConnectionStatus(BluetoothDevice device);
207
208    public abstract int getSummary(BluetoothDevice device);
209
210    public abstract int convertState(int a2dpState);
211
212    public abstract boolean isPreferred(BluetoothDevice device);
213
214    public abstract int getPreferred(BluetoothDevice device);
215
216    public abstract void setPreferred(BluetoothDevice device, boolean preferred);
217
218    public boolean isConnected(BluetoothDevice device) {
219        return SettingsBtStatus.isConnectionStatusConnected(getConnectionStatus(device));
220    }
221
222    public abstract boolean isProfileReady();
223
224    // TODO: int instead of enum
225    public enum Profile {
226        HEADSET(R.string.bluetooth_profile_headset),
227        A2DP(R.string.bluetooth_profile_a2dp),
228        OPP(R.string.bluetooth_profile_opp),
229        HID(R.string.bluetooth_profile_hid),
230        PAN(R.string.bluetooth_profile_pan);
231
232        public final int localizedString;
233
234        private Profile(int localizedString) {
235            this.localizedString = localizedString;
236        }
237    }
238
239    /**
240     * A2dpProfileManager is an abstraction for the {@link BluetoothA2dp} service.
241     */
242    private static class A2dpProfileManager extends LocalBluetoothProfileManager
243          implements BluetoothProfile.ServiceListener {
244        private BluetoothA2dp mService;
245
246        // TODO(): The calls must wait for mService. Its not null just
247        // because it runs in the system server.
248        public A2dpProfileManager(LocalBluetoothManager localManager) {
249            super(localManager);
250            BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
251            adapter.getProfileProxy(localManager.getContext(), this, BluetoothProfile.A2DP);
252
253        }
254
255        public void onServiceConnected(int profile, BluetoothProfile proxy) {
256            mService = (BluetoothA2dp) proxy;
257        }
258
259        public void onServiceDisconnected(int profile) {
260            mService = null;
261        }
262
263        @Override
264        public List<BluetoothDevice> getConnectedDevices() {
265            return mService.getDevicesMatchingConnectionStates(
266                  new int[] {BluetoothProfile.STATE_CONNECTED,
267                             BluetoothProfile.STATE_CONNECTING,
268                             BluetoothProfile.STATE_DISCONNECTING});
269        }
270
271        @Override
272        public boolean connect(BluetoothDevice device) {
273            List<BluetoothDevice> sinks = getConnectedDevices();
274            if (sinks != null) {
275                for (BluetoothDevice sink : sinks) {
276                    mService.disconnect(sink);
277                }
278            }
279            return mService.connect(device);
280        }
281
282        @Override
283        public boolean disconnect(BluetoothDevice device) {
284            // Downgrade priority as user is disconnecting the sink.
285            if (mService.getPriority(device) > BluetoothProfile.PRIORITY_ON) {
286                mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
287            }
288            return mService.disconnect(device);
289        }
290
291        @Override
292        public int getConnectionStatus(BluetoothDevice device) {
293            return convertState(mService.getConnectionState(device));
294        }
295
296        @Override
297        public int getSummary(BluetoothDevice device) {
298            int connectionStatus = getConnectionStatus(device);
299
300            if (SettingsBtStatus.isConnectionStatusConnected(connectionStatus)) {
301                return R.string.bluetooth_a2dp_profile_summary_connected;
302            } else {
303                return SettingsBtStatus.getConnectionStatusSummary(connectionStatus);
304            }
305        }
306
307        @Override
308        public boolean isPreferred(BluetoothDevice device) {
309            return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF;
310        }
311
312        @Override
313        public int getPreferred(BluetoothDevice device) {
314            return mService.getPriority(device);
315        }
316
317        @Override
318        public void setPreferred(BluetoothDevice device, boolean preferred) {
319            if (preferred) {
320                if (mService.getPriority(device) < BluetoothProfile.PRIORITY_ON) {
321                    mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
322                }
323            } else {
324                mService.setPriority(device, BluetoothProfile.PRIORITY_OFF);
325            }
326        }
327
328        @Override
329        public int convertState(int a2dpState) {
330            switch (a2dpState) {
331            case BluetoothProfile.STATE_CONNECTED:
332                return SettingsBtStatus.CONNECTION_STATUS_CONNECTED;
333            case BluetoothProfile.STATE_CONNECTING:
334                return SettingsBtStatus.CONNECTION_STATUS_CONNECTING;
335            case BluetoothProfile.STATE_DISCONNECTED:
336                return SettingsBtStatus.CONNECTION_STATUS_DISCONNECTED;
337            case BluetoothProfile.STATE_DISCONNECTING:
338                return SettingsBtStatus.CONNECTION_STATUS_DISCONNECTING;
339            case BluetoothA2dp.STATE_PLAYING:
340                return SettingsBtStatus.CONNECTION_STATUS_ACTIVE;
341            default:
342                return SettingsBtStatus.CONNECTION_STATUS_UNKNOWN;
343            }
344        }
345
346        @Override
347        public boolean isProfileReady() {
348            return true;
349        }
350    }
351
352    /**
353     * HeadsetProfileManager is an abstraction for the {@link BluetoothHeadset} service.
354     */
355    private static class HeadsetProfileManager extends LocalBluetoothProfileManager
356            implements BluetoothProfile.ServiceListener {
357        private BluetoothHeadset mService;
358        private Handler mUiHandler = new Handler();
359        private boolean profileReady = false;
360
361        // TODO(): The calls must get queued if mService becomes null.
362        // It can happen  when phone app crashes for some reason.
363        // All callers should have service listeners. Dock Service is the only
364        // one right now.
365        public HeadsetProfileManager(LocalBluetoothManager localManager) {
366            super(localManager);
367            BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
368            adapter.getProfileProxy(localManager.getContext(), this, BluetoothProfile.HEADSET);
369        }
370
371        public void onServiceConnected(int profile, BluetoothProfile proxy) {
372            mService = (BluetoothHeadset) proxy;
373            profileReady = true;
374            // This could be called on a non-UI thread, funnel to UI thread.
375            mUiHandler.post(new Runnable() {
376                public void run() {
377                    /*
378                     * We just bound to the service, so refresh the UI of the
379                     * headset device.
380                     */
381                    List<BluetoothDevice> deviceList = mService.getConnectedDevices();
382                    if (deviceList.size() == 0) return;
383
384                    mLocalManager.getCachedDeviceManager()
385                            .onProfileStateChanged(deviceList.get(0), Profile.HEADSET,
386                                                   BluetoothProfile.STATE_CONNECTED);
387                }
388            });
389
390            if (mServiceListeners.size() > 0) {
391                Iterator<ServiceListener> it = mServiceListeners.iterator();
392                while(it.hasNext()) {
393                    it.next().onServiceConnected();
394                }
395            }
396        }
397
398        public void onServiceDisconnected(int profile) {
399            mService = null;
400            profileReady = false;
401            if (mServiceListeners.size() > 0) {
402                Iterator<ServiceListener> it = mServiceListeners.iterator();
403                while(it.hasNext()) {
404                    it.next().onServiceDisconnected();
405                }
406            }
407        }
408
409        @Override
410        public boolean isProfileReady() {
411            return profileReady;
412        }
413
414        @Override
415        public List<BluetoothDevice> getConnectedDevices() {
416            return mService.getConnectedDevices();
417        }
418
419        @Override
420        public boolean connect(BluetoothDevice device) {
421            return mService.connect(device);
422        }
423
424        @Override
425        public boolean disconnect(BluetoothDevice device) {
426            List<BluetoothDevice> deviceList = getConnectedDevices();
427            if (deviceList.size() != 0 && deviceList.get(0).equals(device)) {
428                // Downgrade prority as user is disconnecting the headset.
429                if (mService.getPriority(device) > BluetoothProfile.PRIORITY_ON) {
430                    mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
431                }
432                return mService.disconnect(device);
433            } else {
434                return false;
435            }
436        }
437
438        @Override
439        public int getConnectionStatus(BluetoothDevice device) {
440            List<BluetoothDevice> deviceList = getConnectedDevices();
441
442            return deviceList.size() > 0 && deviceList.get(0).equals(device)
443                    ? convertState(mService.getConnectionState(device))
444                    : SettingsBtStatus.CONNECTION_STATUS_DISCONNECTED;
445        }
446
447        @Override
448        public int getSummary(BluetoothDevice device) {
449            int connectionStatus = getConnectionStatus(device);
450
451            if (SettingsBtStatus.isConnectionStatusConnected(connectionStatus)) {
452                return R.string.bluetooth_headset_profile_summary_connected;
453            } else {
454                return SettingsBtStatus.getConnectionStatusSummary(connectionStatus);
455            }
456        }
457
458        @Override
459        public boolean isPreferred(BluetoothDevice device) {
460            return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF;
461        }
462
463        @Override
464        public int getPreferred(BluetoothDevice device) {
465            return mService.getPriority(device);
466        }
467
468        @Override
469        public void setPreferred(BluetoothDevice device, boolean preferred) {
470            if (preferred) {
471                if (mService.getPriority(device) < BluetoothProfile.PRIORITY_ON) {
472                    mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
473                }
474            } else {
475                mService.setPriority(device, BluetoothProfile.PRIORITY_OFF);
476            }
477        }
478
479        @Override
480        public int convertState(int headsetState) {
481            switch (headsetState) {
482            case BluetoothProfile.STATE_CONNECTED:
483                return SettingsBtStatus.CONNECTION_STATUS_CONNECTED;
484            case BluetoothProfile.STATE_CONNECTING:
485                return SettingsBtStatus.CONNECTION_STATUS_CONNECTING;
486            case BluetoothProfile.STATE_DISCONNECTED:
487                return SettingsBtStatus.CONNECTION_STATUS_DISCONNECTED;
488            default:
489                return SettingsBtStatus.CONNECTION_STATUS_UNKNOWN;
490            }
491        }
492    }
493
494    /**
495     * OppProfileManager
496     */
497    private static class OppProfileManager extends LocalBluetoothProfileManager {
498
499        public OppProfileManager(LocalBluetoothManager localManager) {
500            super(localManager);
501        }
502
503        @Override
504        public List<BluetoothDevice> getConnectedDevices() {
505            return null;
506        }
507
508        @Override
509        public boolean connect(BluetoothDevice device) {
510            return false;
511        }
512
513        @Override
514        public boolean disconnect(BluetoothDevice device) {
515            return false;
516        }
517
518        @Override
519        public int getConnectionStatus(BluetoothDevice device) {
520            return -1;
521        }
522
523        @Override
524        public int getSummary(BluetoothDevice device) {
525            int connectionStatus = getConnectionStatus(device);
526
527            if (SettingsBtStatus.isConnectionStatusConnected(connectionStatus)) {
528                return R.string.bluetooth_opp_profile_summary_connected;
529            } else {
530                return R.string.bluetooth_opp_profile_summary_not_connected;
531            }
532        }
533
534        @Override
535        public boolean isPreferred(BluetoothDevice device) {
536            return false;
537        }
538
539        @Override
540        public int getPreferred(BluetoothDevice device) {
541            return -1;
542        }
543
544        @Override
545        public void setPreferred(BluetoothDevice device, boolean preferred) {
546        }
547
548        @Override
549        public boolean isProfileReady() {
550            return true;
551        }
552
553        @Override
554        public int convertState(int oppState) {
555            switch (oppState) {
556            case 0:
557                return SettingsBtStatus.CONNECTION_STATUS_CONNECTED;
558            case 1:
559                return SettingsBtStatus.CONNECTION_STATUS_CONNECTING;
560            case 2:
561                return SettingsBtStatus.CONNECTION_STATUS_DISCONNECTED;
562            default:
563                return SettingsBtStatus.CONNECTION_STATUS_UNKNOWN;
564            }
565        }
566    }
567
568    private static class HidProfileManager extends LocalBluetoothProfileManager {
569        private BluetoothInputDevice mService;
570
571        public HidProfileManager(LocalBluetoothManager localManager) {
572            super(localManager);
573            mService = new BluetoothInputDevice(localManager.getContext());
574        }
575
576        @Override
577        public boolean connect(BluetoothDevice device) {
578            return mService.connectInputDevice(device);
579        }
580
581        @Override
582        public int convertState(int hidState) {
583            switch (hidState) {
584            case BluetoothInputDevice.STATE_CONNECTED:
585                return SettingsBtStatus.CONNECTION_STATUS_CONNECTED;
586            case BluetoothInputDevice.STATE_CONNECTING:
587                return SettingsBtStatus.CONNECTION_STATUS_CONNECTING;
588            case BluetoothInputDevice.STATE_DISCONNECTED:
589                return SettingsBtStatus.CONNECTION_STATUS_DISCONNECTED;
590            case BluetoothInputDevice.STATE_DISCONNECTING:
591                return SettingsBtStatus.CONNECTION_STATUS_DISCONNECTING;
592            default:
593                return SettingsBtStatus.CONNECTION_STATUS_UNKNOWN;
594            }
595        }
596
597        @Override
598        public boolean disconnect(BluetoothDevice device) {
599            return mService.disconnectInputDevice(device);
600        }
601
602        @Override
603        public List<BluetoothDevice> getConnectedDevices() {
604            return mService.getConnectedInputDevices();
605        }
606
607        @Override
608        public int getConnectionStatus(BluetoothDevice device) {
609            return convertState(mService.getInputDeviceState(device));
610        }
611
612        @Override
613        public int getPreferred(BluetoothDevice device) {
614            return mService.getInputDevicePriority(device);
615        }
616
617        @Override
618        public int getSummary(BluetoothDevice device) {
619            final int connectionStatus = getConnectionStatus(device);
620
621            if (SettingsBtStatus.isConnectionStatusConnected(connectionStatus)) {
622                return R.string.bluetooth_hid_profile_summary_connected;
623            } else {
624                return SettingsBtStatus.getConnectionStatusSummary(connectionStatus);
625            }
626        }
627
628        @Override
629        public boolean isPreferred(BluetoothDevice device) {
630            return mService.getInputDevicePriority(device) > BluetoothInputDevice.PRIORITY_OFF;
631        }
632
633        @Override
634        public boolean isProfileReady() {
635            return true;
636        }
637
638        @Override
639        public void setPreferred(BluetoothDevice device, boolean preferred) {
640            if (preferred) {
641                if (mService.getInputDevicePriority(device) < BluetoothInputDevice.PRIORITY_ON) {
642                    mService.setInputDevicePriority(device, BluetoothInputDevice.PRIORITY_ON);
643                }
644            } else {
645                mService.setInputDevicePriority(device, BluetoothInputDevice.PRIORITY_OFF);
646            }
647        }
648    }
649
650    private static class PanProfileManager extends LocalBluetoothProfileManager {
651        private BluetoothPan mService;
652
653        public PanProfileManager(LocalBluetoothManager localManager) {
654            super(localManager);
655            mService = new BluetoothPan(localManager.getContext());
656        }
657
658        @Override
659        public boolean connect(BluetoothDevice device) {
660            return mService.connect(device);
661        }
662
663        @Override
664        public int convertState(int panState) {
665            switch (panState) {
666            case BluetoothPan.STATE_CONNECTED:
667                return SettingsBtStatus.CONNECTION_STATUS_CONNECTED;
668            case BluetoothPan.STATE_CONNECTING:
669                return SettingsBtStatus.CONNECTION_STATUS_CONNECTING;
670            case BluetoothPan.STATE_DISCONNECTED:
671                return SettingsBtStatus.CONNECTION_STATUS_DISCONNECTED;
672            case BluetoothPan.STATE_DISCONNECTING:
673                return SettingsBtStatus.CONNECTION_STATUS_DISCONNECTING;
674            default:
675                return SettingsBtStatus.CONNECTION_STATUS_UNKNOWN;
676            }
677        }
678
679        @Override
680        public boolean disconnect(BluetoothDevice device) {
681            return mService.disconnect(device);
682        }
683
684        @Override
685        public int getSummary(BluetoothDevice device) {
686            final int connectionStatus = getConnectionStatus(device);
687
688            if (SettingsBtStatus.isConnectionStatusConnected(connectionStatus)) {
689                return R.string.bluetooth_pan_profile_summary_connected;
690            } else {
691                return SettingsBtStatus.getConnectionStatusSummary(connectionStatus);
692            }
693        }
694
695        @Override
696        public boolean isProfileReady() {
697            return true;
698        }
699
700        @Override
701        public List<BluetoothDevice> getConnectedDevices() {
702            return mService.getConnectedDevices();
703        }
704
705        @Override
706        public int getConnectionStatus(BluetoothDevice device) {
707            return convertState(mService.getPanDeviceState(device));
708        }
709
710        @Override
711        public int getPreferred(BluetoothDevice device) {
712            return -1;
713        }
714
715        @Override
716        public boolean isPreferred(BluetoothDevice device) {
717            return false;
718        }
719
720        @Override
721        public void setPreferred(BluetoothDevice device, boolean preferred) {
722            return;
723        }
724    }
725}
726