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