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