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