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