LocalBluetoothProfileManager.java revision 3327f7887328c55ef2d837a8e2a07cea2070c744
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 void setPreferred(BluetoothDevice device, boolean preferred);
140
141    public boolean isConnected(BluetoothDevice device) {
142        return SettingsBtStatus.isConnectionStatusConnected(getConnectionStatus(device));
143    }
144
145    // TODO: int instead of enum
146    public enum Profile {
147        HEADSET(R.string.bluetooth_profile_headset),
148        A2DP(R.string.bluetooth_profile_a2dp),
149        OPP(R.string.bluetooth_profile_opp);
150
151        public final int localizedString;
152
153        private Profile(int localizedString) {
154            this.localizedString = localizedString;
155        }
156    }
157
158    /**
159     * A2dpProfileManager is an abstraction for the {@link BluetoothA2dp} service.
160     */
161    private static class A2dpProfileManager extends LocalBluetoothProfileManager {
162        private BluetoothA2dp mService;
163
164        public A2dpProfileManager(LocalBluetoothManager localManager) {
165            super(localManager);
166            mService = new BluetoothA2dp(localManager.getContext());
167        }
168
169        @Override
170        public Set<BluetoothDevice> getConnectedDevices() {
171            return mService.getConnectedSinks();
172        }
173
174        @Override
175        public boolean connect(BluetoothDevice device) {
176            Set<BluetoothDevice> sinks = mService.getConnectedSinks();
177            if (sinks != null) {
178                for (BluetoothDevice sink : sinks) {
179                    mService.disconnectSink(sink);
180                }
181            }
182            return mService.connectSink(device);
183        }
184
185        @Override
186        public boolean disconnect(BluetoothDevice device) {
187            // Downgrade priority as user is disconnecting the sink.
188            mService.setSinkPriority(device, BluetoothA2dp.PRIORITY_ON);
189            return mService.disconnectSink(device);
190        }
191
192        @Override
193        public int getConnectionStatus(BluetoothDevice device) {
194            return convertState(mService.getSinkState(device));
195        }
196
197        @Override
198        public int getSummary(BluetoothDevice device) {
199            int connectionStatus = getConnectionStatus(device);
200
201            if (SettingsBtStatus.isConnectionStatusConnected(connectionStatus)) {
202                return R.string.bluetooth_a2dp_profile_summary_connected;
203            } else {
204                return SettingsBtStatus.getConnectionStatusSummary(connectionStatus);
205            }
206        }
207
208        @Override
209        public boolean isPreferred(BluetoothDevice device) {
210            return mService.getSinkPriority(device) > BluetoothA2dp.PRIORITY_OFF;
211        }
212
213        @Override
214        public void setPreferred(BluetoothDevice device, boolean preferred) {
215            mService.setSinkPriority(device,
216                    preferred ? BluetoothA2dp.PRIORITY_AUTO_CONNECT : BluetoothA2dp.PRIORITY_OFF);
217        }
218
219        @Override
220        public int convertState(int a2dpState) {
221            switch (a2dpState) {
222            case BluetoothA2dp.STATE_CONNECTED:
223                return SettingsBtStatus.CONNECTION_STATUS_CONNECTED;
224            case BluetoothA2dp.STATE_CONNECTING:
225                return SettingsBtStatus.CONNECTION_STATUS_CONNECTING;
226            case BluetoothA2dp.STATE_DISCONNECTED:
227                return SettingsBtStatus.CONNECTION_STATUS_DISCONNECTED;
228            case BluetoothA2dp.STATE_DISCONNECTING:
229                return SettingsBtStatus.CONNECTION_STATUS_DISCONNECTING;
230            case BluetoothA2dp.STATE_PLAYING:
231                return SettingsBtStatus.CONNECTION_STATUS_ACTIVE;
232            default:
233                return SettingsBtStatus.CONNECTION_STATUS_UNKNOWN;
234            }
235        }
236    }
237
238    /**
239     * HeadsetProfileManager is an abstraction for the {@link BluetoothHeadset} service.
240     */
241    private static class HeadsetProfileManager extends LocalBluetoothProfileManager
242            implements BluetoothHeadset.ServiceListener {
243        private BluetoothHeadset mService;
244        private Handler mUiHandler = new Handler();
245
246        public HeadsetProfileManager(LocalBluetoothManager localManager) {
247            super(localManager);
248            mService = new BluetoothHeadset(localManager.getContext(), this);
249        }
250
251        public void onServiceConnected() {
252            // This could be called on a non-UI thread, funnel to UI thread.
253            mUiHandler.post(new Runnable() {
254                public void run() {
255                    /*
256                     * We just bound to the service, so refresh the UI of the
257                     * headset device.
258                     */
259                    BluetoothDevice device = mService.getCurrentHeadset();
260                    if (device == null) return;
261                    mLocalManager.getCachedDeviceManager()
262                            .onProfileStateChanged(device, Profile.HEADSET,
263                                                   BluetoothHeadset.STATE_CONNECTED);
264                }
265            });
266        }
267
268        public void onServiceDisconnected() {
269        }
270
271        @Override
272        public Set<BluetoothDevice> getConnectedDevices() {
273            Set<BluetoothDevice> devices = null;
274            BluetoothDevice device = mService.getCurrentHeadset();
275            if (device != null) {
276                devices = new HashSet<BluetoothDevice>();
277                devices.add(device);
278            }
279            return devices;
280        }
281
282        @Override
283        public boolean connect(BluetoothDevice device) {
284            // Since connectHeadset fails if already connected to a headset, we
285            // disconnect from any headset first
286            mService.disconnectHeadset();
287            return mService.connectHeadset(device);
288        }
289
290        @Override
291        public boolean disconnect(BluetoothDevice device) {
292            if (mService.getCurrentHeadset().equals(device)) {
293                // Downgrade prority as user is disconnecting the headset.
294                mService.setPriority(device, BluetoothHeadset.PRIORITY_ON);
295                return mService.disconnectHeadset();
296            } else {
297                return false;
298            }
299        }
300
301        @Override
302        public int getConnectionStatus(BluetoothDevice device) {
303            BluetoothDevice currentDevice = mService.getCurrentHeadset();
304            return currentDevice != null && currentDevice.equals(device)
305                    ? convertState(mService.getState())
306                    : SettingsBtStatus.CONNECTION_STATUS_DISCONNECTED;
307        }
308
309        @Override
310        public int getSummary(BluetoothDevice device) {
311            int connectionStatus = getConnectionStatus(device);
312
313            if (SettingsBtStatus.isConnectionStatusConnected(connectionStatus)) {
314                return R.string.bluetooth_headset_profile_summary_connected;
315            } else {
316                return SettingsBtStatus.getConnectionStatusSummary(connectionStatus);
317            }
318        }
319
320        @Override
321        public boolean isPreferred(BluetoothDevice device) {
322            return mService.getPriority(device) > BluetoothHeadset.PRIORITY_OFF;
323        }
324
325        @Override
326        public void setPreferred(BluetoothDevice device, boolean preferred) {
327            mService.setPriority(device,
328                    preferred ? BluetoothHeadset.PRIORITY_AUTO_CONNECT : BluetoothHeadset.PRIORITY_OFF);
329        }
330
331        @Override
332        public int convertState(int headsetState) {
333            switch (headsetState) {
334            case BluetoothHeadset.STATE_CONNECTED:
335                return SettingsBtStatus.CONNECTION_STATUS_CONNECTED;
336            case BluetoothHeadset.STATE_CONNECTING:
337                return SettingsBtStatus.CONNECTION_STATUS_CONNECTING;
338            case BluetoothHeadset.STATE_DISCONNECTED:
339                return SettingsBtStatus.CONNECTION_STATUS_DISCONNECTED;
340            default:
341                return SettingsBtStatus.CONNECTION_STATUS_UNKNOWN;
342            }
343        }
344    }
345
346    /**
347     * OppProfileManager
348     */
349    private static class OppProfileManager extends LocalBluetoothProfileManager {
350
351        public OppProfileManager(LocalBluetoothManager localManager) {
352            super(localManager);
353        }
354
355        @Override
356        public Set<BluetoothDevice> getConnectedDevices() {
357            return null;
358        }
359
360        @Override
361        public boolean connect(BluetoothDevice device) {
362            return false;
363        }
364
365        @Override
366        public boolean disconnect(BluetoothDevice device) {
367            return false;
368        }
369
370        @Override
371        public int getConnectionStatus(BluetoothDevice device) {
372            return -1;
373        }
374
375        @Override
376        public int getSummary(BluetoothDevice device) {
377            int connectionStatus = getConnectionStatus(device);
378
379            if (SettingsBtStatus.isConnectionStatusConnected(connectionStatus)) {
380                return R.string.bluetooth_opp_profile_summary_connected;
381            } else {
382                return R.string.bluetooth_opp_profile_summary_not_connected;
383            }
384        }
385
386        @Override
387        public boolean isPreferred(BluetoothDevice device) {
388            return false;
389        }
390
391        @Override
392        public void setPreferred(BluetoothDevice device, boolean preferred) {
393        }
394
395        @Override
396        public int convertState(int oppState) {
397            switch (oppState) {
398            case 0:
399                return SettingsBtStatus.CONNECTION_STATUS_CONNECTED;
400            case 1:
401                return SettingsBtStatus.CONNECTION_STATUS_CONNECTING;
402            case 2:
403                return SettingsBtStatus.CONNECTION_STATUS_DISCONNECTED;
404            default:
405                return SettingsBtStatus.CONNECTION_STATUS_UNKNOWN;
406            }
407        }
408    }
409}
410