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