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