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