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