LocalBluetoothProfileManager.java revision 7ce96b9e610de2782ec5f2af806e7bc0f90c8578
1/*
2 * Copyright (C) 2011 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.settingslib.bluetooth;
18
19import android.bluetooth.BluetoothA2dp;
20import android.bluetooth.BluetoothDevice;
21import android.bluetooth.BluetoothHeadset;
22import android.bluetooth.BluetoothMap;
23import android.bluetooth.BluetoothInputDevice;
24import android.bluetooth.BluetoothPan;
25import android.bluetooth.BluetoothPbap;
26import android.bluetooth.BluetoothProfile;
27import android.bluetooth.BluetoothUuid;
28import android.content.Context;
29import android.content.Intent;
30import android.os.ParcelUuid;
31import android.util.Log;
32import android.os.Handler;
33import android.os.Message;
34
35import java.util.ArrayList;
36import java.util.Collection;
37import java.util.HashMap;
38import java.util.Map;
39import java.util.Set;
40import java.util.List;
41
42/**
43 * LocalBluetoothProfileManager provides access to the LocalBluetoothProfile
44 * objects for the available Bluetooth profiles.
45 */
46public final class LocalBluetoothProfileManager {
47    private static final String TAG = "LocalBluetoothProfileManager";
48    private static final boolean DEBUG = Utils.D;
49    /** Singleton instance. */
50    private static LocalBluetoothProfileManager sInstance;
51
52    /**
53     * An interface for notifying BluetoothHeadset IPC clients when they have
54     * been connected to the BluetoothHeadset service.
55     * Only used by com.android.settings.bluetooth.DockService.
56     */
57    public interface ServiceListener {
58        /**
59         * Called to notify the client when this proxy object has been
60         * connected to the BluetoothHeadset service. Clients must wait for
61         * this callback before making IPC calls on the BluetoothHeadset
62         * service.
63         */
64        void onServiceConnected();
65
66        /**
67         * Called to notify the client that this proxy object has been
68         * disconnected from the BluetoothHeadset service. Clients must not
69         * make IPC calls on the BluetoothHeadset service after this callback.
70         * This callback will currently only occur if the application hosting
71         * the BluetoothHeadset service, but may be called more often in future.
72         */
73        void onServiceDisconnected();
74    }
75
76    private final Context mContext;
77    private final LocalBluetoothAdapter mLocalAdapter;
78    private final CachedBluetoothDeviceManager mDeviceManager;
79    private final BluetoothEventManager mEventManager;
80
81    private A2dpProfile mA2dpProfile;
82    private HeadsetProfile mHeadsetProfile;
83    private MapProfile mMapProfile;
84    private final HidProfile mHidProfile;
85    private OppProfile mOppProfile;
86    private final PanProfile mPanProfile;
87    private final PbapServerProfile mPbapProfile;
88
89    /**
90     * Mapping from profile name, e.g. "HEADSET" to profile object.
91     */
92    private final Map<String, LocalBluetoothProfile>
93            mProfileNameMap = new HashMap<String, LocalBluetoothProfile>();
94
95    LocalBluetoothProfileManager(Context context,
96            LocalBluetoothAdapter adapter,
97            CachedBluetoothDeviceManager deviceManager,
98            BluetoothEventManager eventManager) {
99        mContext = context;
100
101        mLocalAdapter = adapter;
102        mDeviceManager = deviceManager;
103        mEventManager = eventManager;
104        // pass this reference to adapter and event manager (circular dependency)
105        mLocalAdapter.setProfileManager(this);
106        mEventManager.setProfileManager(this);
107
108        ParcelUuid[] uuids = adapter.getUuids();
109
110        // uuids may be null if Bluetooth is turned off
111        if (uuids != null) {
112            updateLocalProfiles(uuids);
113        }
114
115        // Always add HID and PAN profiles
116        mHidProfile = new HidProfile(context, mLocalAdapter, mDeviceManager, this);
117        addProfile(mHidProfile, HidProfile.NAME,
118                BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED);
119
120        mPanProfile = new PanProfile(context);
121        addPanProfile(mPanProfile, PanProfile.NAME,
122                BluetoothPan.ACTION_CONNECTION_STATE_CHANGED);
123
124        if(DEBUG) Log.d(TAG, "Adding local MAP profile");
125        mMapProfile = new MapProfile(mContext, mLocalAdapter,
126                mDeviceManager, this);
127        addProfile(mMapProfile, MapProfile.NAME,
128                BluetoothMap.ACTION_CONNECTION_STATE_CHANGED);
129
130       //Create PBAP server profile, but do not add it to list of profiles
131       // as we do not need to monitor the profile as part of profile list
132        mPbapProfile = new PbapServerProfile(context);
133
134        if (DEBUG) Log.d(TAG, "LocalBluetoothProfileManager construction complete");
135    }
136
137    /**
138     * Initialize or update the local profile objects. If a UUID was previously
139     * present but has been removed, we print a warning but don't remove the
140     * profile object as it might be referenced elsewhere, or the UUID might
141     * come back and we don't want multiple copies of the profile objects.
142     * @param uuids
143     */
144    void updateLocalProfiles(ParcelUuid[] uuids) {
145        // A2DP
146        if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.AudioSource)) {
147            if (mA2dpProfile == null) {
148                if(DEBUG) Log.d(TAG, "Adding local A2DP profile");
149                mA2dpProfile = new A2dpProfile(mContext, mLocalAdapter, mDeviceManager, this);
150                addProfile(mA2dpProfile, A2dpProfile.NAME,
151                        BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
152            }
153        } else if (mA2dpProfile != null) {
154            Log.w(TAG, "Warning: A2DP profile was previously added but the UUID is now missing.");
155        }
156
157        // Headset / Handsfree
158        if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Handsfree_AG) ||
159            BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.HSP_AG)) {
160            if (mHeadsetProfile == null) {
161                if (DEBUG) Log.d(TAG, "Adding local HEADSET profile");
162                mHeadsetProfile = new HeadsetProfile(mContext, mLocalAdapter,
163                        mDeviceManager, this);
164                addProfile(mHeadsetProfile, HeadsetProfile.NAME,
165                        BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
166            }
167        } else if (mHeadsetProfile != null) {
168            Log.w(TAG, "Warning: HEADSET profile was previously added but the UUID is now missing.");
169        }
170
171        // OPP
172        if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.ObexObjectPush)) {
173            if (mOppProfile == null) {
174                if(DEBUG) Log.d(TAG, "Adding local OPP profile");
175                mOppProfile = new OppProfile();
176                // Note: no event handler for OPP, only name map.
177                mProfileNameMap.put(OppProfile.NAME, mOppProfile);
178            }
179        } else if (mOppProfile != null) {
180            Log.w(TAG, "Warning: OPP profile was previously added but the UUID is now missing.");
181        }
182        mEventManager.registerProfileIntentReceiver();
183
184        // There is no local SDP record for HID and Settings app doesn't control PBAP
185    }
186
187    private final Collection<ServiceListener> mServiceListeners =
188            new ArrayList<ServiceListener>();
189
190    private void addProfile(LocalBluetoothProfile profile,
191            String profileName, String stateChangedAction) {
192        mEventManager.addProfileHandler(stateChangedAction, new StateChangedHandler(profile));
193        mProfileNameMap.put(profileName, profile);
194    }
195
196    private void addPanProfile(LocalBluetoothProfile profile,
197            String profileName, String stateChangedAction) {
198        mEventManager.addProfileHandler(stateChangedAction,
199                new PanStateChangedHandler(profile));
200        mProfileNameMap.put(profileName, profile);
201    }
202
203    public LocalBluetoothProfile getProfileByName(String name) {
204        return mProfileNameMap.get(name);
205    }
206
207    // Called from LocalBluetoothAdapter when state changes to ON
208    void setBluetoothStateOn() {
209        ParcelUuid[] uuids = mLocalAdapter.getUuids();
210        if (uuids != null) {
211            updateLocalProfiles(uuids);
212        }
213        mEventManager.readPairedDevices();
214    }
215
216    /**
217     * Generic handler for connection state change events for the specified profile.
218     */
219    private class StateChangedHandler implements BluetoothEventManager.Handler {
220        final LocalBluetoothProfile mProfile;
221
222        StateChangedHandler(LocalBluetoothProfile profile) {
223            mProfile = profile;
224        }
225
226        public void onReceive(Context context, Intent intent, BluetoothDevice device) {
227            CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
228            if (cachedDevice == null) {
229                Log.w(TAG, "StateChangedHandler found new device: " + device);
230                cachedDevice = mDeviceManager.addDevice(mLocalAdapter,
231                        LocalBluetoothProfileManager.this, device);
232            }
233            int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0);
234            int oldState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, 0);
235            if (newState == BluetoothProfile.STATE_DISCONNECTED &&
236                    oldState == BluetoothProfile.STATE_CONNECTING) {
237                Log.i(TAG, "Failed to connect " + mProfile + " device");
238            }
239
240            cachedDevice.onProfileStateChanged(mProfile, newState);
241            cachedDevice.refresh();
242        }
243    }
244
245    /** State change handler for NAP and PANU profiles. */
246    private class PanStateChangedHandler extends StateChangedHandler {
247
248        PanStateChangedHandler(LocalBluetoothProfile profile) {
249            super(profile);
250        }
251
252        @Override
253        public void onReceive(Context context, Intent intent, BluetoothDevice device) {
254            PanProfile panProfile = (PanProfile) mProfile;
255            int role = intent.getIntExtra(BluetoothPan.EXTRA_LOCAL_ROLE, 0);
256            panProfile.setLocalRole(device, role);
257            super.onReceive(context, intent, device);
258        }
259    }
260
261    // called from DockService
262    public void addServiceListener(ServiceListener l) {
263        mServiceListeners.add(l);
264    }
265
266    // called from DockService
267    public void removeServiceListener(ServiceListener l) {
268        mServiceListeners.remove(l);
269    }
270
271    // not synchronized: use only from UI thread! (TODO: verify)
272    void callServiceConnectedListeners() {
273        for (ServiceListener l : mServiceListeners) {
274            l.onServiceConnected();
275        }
276    }
277
278    // not synchronized: use only from UI thread! (TODO: verify)
279    void callServiceDisconnectedListeners() {
280        for (ServiceListener listener : mServiceListeners) {
281            listener.onServiceDisconnected();
282        }
283    }
284
285    // This is called by DockService, so check Headset and A2DP.
286    public synchronized boolean isManagerReady() {
287        // Getting just the headset profile is fine for now. Will need to deal with A2DP
288        // and others if they aren't always in a ready state.
289        LocalBluetoothProfile profile = mHeadsetProfile;
290        if (profile != null) {
291            return profile.isProfileReady();
292        }
293        profile = mA2dpProfile;
294        if (profile != null) {
295            return profile.isProfileReady();
296        }
297        return false;
298    }
299
300    public A2dpProfile getA2dpProfile() {
301        return mA2dpProfile;
302    }
303
304    public HeadsetProfile getHeadsetProfile() {
305        return mHeadsetProfile;
306    }
307
308    public PbapServerProfile getPbapProfile(){
309        return mPbapProfile;
310    }
311
312    public MapProfile getMapProfile(){
313        return mMapProfile;
314    }
315
316    /**
317     * Fill in a list of LocalBluetoothProfile objects that are supported by
318     * the local device and the remote device.
319     *
320     * @param uuids of the remote device
321     * @param localUuids UUIDs of the local device
322     * @param profiles The list of profiles to fill
323     * @param removedProfiles list of profiles that were removed
324     */
325    synchronized void updateProfiles(ParcelUuid[] uuids, ParcelUuid[] localUuids,
326            Collection<LocalBluetoothProfile> profiles,
327            Collection<LocalBluetoothProfile> removedProfiles,
328            boolean isPanNapConnected, BluetoothDevice device) {
329        // Copy previous profile list into removedProfiles
330        removedProfiles.clear();
331        removedProfiles.addAll(profiles);
332        profiles.clear();
333
334        if (uuids == null) {
335            return;
336        }
337
338        if (mHeadsetProfile != null) {
339            if ((BluetoothUuid.isUuidPresent(localUuids, BluetoothUuid.HSP_AG) &&
340                    BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.HSP)) ||
341                    (BluetoothUuid.isUuidPresent(localUuids, BluetoothUuid.Handsfree_AG) &&
342                            BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Handsfree))) {
343                profiles.add(mHeadsetProfile);
344                removedProfiles.remove(mHeadsetProfile);
345            }
346        }
347
348        if (BluetoothUuid.containsAnyUuid(uuids, A2dpProfile.SINK_UUIDS) &&
349            mA2dpProfile != null) {
350            profiles.add(mA2dpProfile);
351            removedProfiles.remove(mA2dpProfile);
352        }
353
354        if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.ObexObjectPush) &&
355            mOppProfile != null) {
356            profiles.add(mOppProfile);
357            removedProfiles.remove(mOppProfile);
358        }
359
360        if ((BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Hid) ||
361             BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Hogp)) &&
362            mHidProfile != null) {
363            profiles.add(mHidProfile);
364            removedProfiles.remove(mHidProfile);
365        }
366
367        if(isPanNapConnected)
368            if(DEBUG) Log.d(TAG, "Valid PAN-NAP connection exists.");
369        if ((BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.NAP) &&
370            mPanProfile != null) || isPanNapConnected) {
371            profiles.add(mPanProfile);
372            removedProfiles.remove(mPanProfile);
373        }
374
375        if ((mMapProfile != null) &&
376            (mMapProfile.getConnectionStatus(device) == BluetoothProfile.STATE_CONNECTED)) {
377            profiles.add(mMapProfile);
378            removedProfiles.remove(mMapProfile);
379            mMapProfile.setPreferred(device, true);
380        }
381    }
382
383}
384