LocalBluetoothProfileManager.java revision 8038834f8e768f6a62b4c7d5498160eb2c8b4895
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            BluetoothDevice currDevice = mService.getCurrentHeadset();
374            if (currDevice != null) {
375                mService.disconnectHeadset(currDevice);
376            }
377            return mService.connectHeadset(device);
378        }
379
380        @Override
381        public boolean disconnect(BluetoothDevice device) {
382            if (mService.getCurrentHeadset().equals(device)) {
383                // Downgrade prority as user is disconnecting the headset.
384                if (mService.getPriority(device) > BluetoothHeadset.PRIORITY_ON) {
385                    mService.setPriority(device, BluetoothHeadset.PRIORITY_ON);
386                }
387                return mService.disconnectHeadset(device);
388            } else {
389                return false;
390            }
391        }
392
393        @Override
394        public int getConnectionStatus(BluetoothDevice device) {
395            BluetoothDevice currentDevice = mService.getCurrentHeadset();
396            return currentDevice != null && currentDevice.equals(device)
397                    ? convertState(mService.getState(device))
398                    : SettingsBtStatus.CONNECTION_STATUS_DISCONNECTED;
399        }
400
401        @Override
402        public int getSummary(BluetoothDevice device) {
403            int connectionStatus = getConnectionStatus(device);
404
405            if (SettingsBtStatus.isConnectionStatusConnected(connectionStatus)) {
406                return R.string.bluetooth_headset_profile_summary_connected;
407            } else {
408                return SettingsBtStatus.getConnectionStatusSummary(connectionStatus);
409            }
410        }
411
412        @Override
413        public boolean isPreferred(BluetoothDevice device) {
414            return mService.getPriority(device) > BluetoothHeadset.PRIORITY_OFF;
415        }
416
417        @Override
418        public int getPreferred(BluetoothDevice device) {
419            return mService.getPriority(device);
420        }
421
422        @Override
423        public void setPreferred(BluetoothDevice device, boolean preferred) {
424            if (preferred) {
425                if (mService.getPriority(device) < BluetoothHeadset.PRIORITY_ON) {
426                    mService.setPriority(device, BluetoothHeadset.PRIORITY_ON);
427                }
428            } else {
429                mService.setPriority(device, BluetoothHeadset.PRIORITY_OFF);
430            }
431        }
432
433        @Override
434        public int convertState(int headsetState) {
435            switch (headsetState) {
436            case BluetoothHeadset.STATE_CONNECTED:
437                return SettingsBtStatus.CONNECTION_STATUS_CONNECTED;
438            case BluetoothHeadset.STATE_CONNECTING:
439                return SettingsBtStatus.CONNECTION_STATUS_CONNECTING;
440            case BluetoothHeadset.STATE_DISCONNECTED:
441                return SettingsBtStatus.CONNECTION_STATUS_DISCONNECTED;
442            default:
443                return SettingsBtStatus.CONNECTION_STATUS_UNKNOWN;
444            }
445        }
446    }
447
448    /**
449     * OppProfileManager
450     */
451    private static class OppProfileManager extends LocalBluetoothProfileManager {
452
453        public OppProfileManager(LocalBluetoothManager localManager) {
454            super(localManager);
455        }
456
457        @Override
458        public Set<BluetoothDevice> getConnectedDevices() {
459            return null;
460        }
461
462        @Override
463        public boolean connect(BluetoothDevice device) {
464            return false;
465        }
466
467        @Override
468        public boolean disconnect(BluetoothDevice device) {
469            return false;
470        }
471
472        @Override
473        public int getConnectionStatus(BluetoothDevice device) {
474            return -1;
475        }
476
477        @Override
478        public int getSummary(BluetoothDevice device) {
479            int connectionStatus = getConnectionStatus(device);
480
481            if (SettingsBtStatus.isConnectionStatusConnected(connectionStatus)) {
482                return R.string.bluetooth_opp_profile_summary_connected;
483            } else {
484                return R.string.bluetooth_opp_profile_summary_not_connected;
485            }
486        }
487
488        @Override
489        public boolean isPreferred(BluetoothDevice device) {
490            return false;
491        }
492
493        @Override
494        public int getPreferred(BluetoothDevice device) {
495            return -1;
496        }
497
498        @Override
499        public void setPreferred(BluetoothDevice device, boolean preferred) {
500        }
501
502        @Override
503        public boolean isProfileReady() {
504            return true;
505        }
506
507        @Override
508        public int convertState(int oppState) {
509            switch (oppState) {
510            case 0:
511                return SettingsBtStatus.CONNECTION_STATUS_CONNECTED;
512            case 1:
513                return SettingsBtStatus.CONNECTION_STATUS_CONNECTING;
514            case 2:
515                return SettingsBtStatus.CONNECTION_STATUS_DISCONNECTED;
516            default:
517                return SettingsBtStatus.CONNECTION_STATUS_UNKNOWN;
518            }
519        }
520    }
521}
522