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