2 * Copyright (C) 2011 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.settings.bluetooth;
19 import android.bluetooth.BluetoothA2dp;
20 import android.bluetooth.BluetoothDevice;
21 import android.bluetooth.BluetoothHeadset;
22 import android.bluetooth.BluetoothInputDevice;
23 import android.bluetooth.BluetoothPan;
24 import android.bluetooth.BluetoothProfile;
25 import android.bluetooth.BluetoothUuid;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.os.ParcelUuid;
29 import android.util.Log;
31 import java.util.ArrayList;
32 import java.util.Collection;
33 import java.util.HashMap;
37 * LocalBluetoothProfileManager provides access to the LocalBluetoothProfile
38 * objects for the available Bluetooth profiles.
40 final class LocalBluetoothProfileManager {
41 private static final String TAG = "LocalBluetoothProfileManager";
43 /** Singleton instance. */
44 private static LocalBluetoothProfileManager sInstance;
47 * An interface for notifying BluetoothHeadset IPC clients when they have
48 * been connected to the BluetoothHeadset service.
49 * Only used by {@link DockService}.
51 public interface ServiceListener {
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
58 void onServiceConnected();
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.
67 void onServiceDisconnected();
70 private final Context mContext;
71 private final LocalBluetoothAdapter mLocalAdapter;
72 private final CachedBluetoothDeviceManager mDeviceManager;
73 private final BluetoothEventManager mEventManager;
75 private A2dpProfile mA2dpProfile;
76 private HeadsetProfile mHeadsetProfile;
77 private final HidProfile mHidProfile;
78 private OppProfile mOppProfile;
79 private final PanProfile mPanProfile;
82 * Mapping from profile name, e.g. "HEADSET" to profile object.
84 private final Map<String, LocalBluetoothProfile>
85 mProfileNameMap = new HashMap<String, LocalBluetoothProfile>();
87 LocalBluetoothProfileManager(Context context,
88 LocalBluetoothAdapter adapter,
89 CachedBluetoothDeviceManager deviceManager,
90 BluetoothEventManager eventManager) {
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);
100 ParcelUuid[] uuids = adapter.getUuids();
102 // uuids may be null if Bluetooth is turned off
104 updateLocalProfiles(uuids);
107 // Always add HID and PAN profiles
108 mHidProfile = new HidProfile(context, mLocalAdapter);
109 addProfile(mHidProfile, HidProfile.NAME,
110 BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED);
112 mPanProfile = new PanProfile(context);
113 addProfile(mPanProfile, PanProfile.NAME, BluetoothPan.ACTION_CONNECTION_STATE_CHANGED);
114 Log.d(TAG, "LocalBluetoothProfileManager construction complete");
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.
124 void updateLocalProfiles(ParcelUuid[] uuids) {
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);
133 } else if (mA2dpProfile != null) {
134 Log.w(TAG, "Warning: A2DP profile was previously added but the UUID is now missing.");
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);
147 } else if (mHeadsetProfile != null) {
148 Log.w(TAG, "Warning: HEADSET profile was previously added but the UUID is now missing.");
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);
159 } else if (mOppProfile != null) {
160 Log.w(TAG, "Warning: OPP profile was previously added but the UUID is now missing.");
163 // There is no local SDP record for HID and Settings app doesn't control PBAP
166 private final Collection<ServiceListener> mServiceListeners =
167 new ArrayList<ServiceListener>();
169 private void addProfile(LocalBluetoothProfile profile,
170 String profileName, String stateChangedAction) {
171 mEventManager.addHandler(stateChangedAction, new StateChangedHandler(profile));
172 mProfileNameMap.put(profileName, profile);
175 LocalBluetoothProfile getProfileByName(String name) {
176 return mProfileNameMap.get(name);
179 // Called from LocalBluetoothAdapter when state changes to ON
180 void setBluetoothStateOn() {
181 ParcelUuid[] uuids = mLocalAdapter.getUuids();
183 updateLocalProfiles(uuids);
185 mEventManager.readPairedDevices();
189 * Generic handler for connection state change events for the specified profile.
191 private class StateChangedHandler implements BluetoothEventManager.Handler {
192 private final LocalBluetoothProfile mProfile;
194 StateChangedHandler(LocalBluetoothProfile profile) {
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);
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");
212 cachedDevice.onProfileStateChanged(mProfile, newState);
213 cachedDevice.refresh();
217 // called from DockService
218 void addServiceListener(ServiceListener l) {
219 mServiceListeners.add(l);
222 // called from DockService
223 void removeServiceListener(ServiceListener l) {
224 mServiceListeners.remove(l);
227 // not synchronized: use only from UI thread! (TODO: verify)
228 void callServiceConnectedListeners() {
229 for (ServiceListener l : mServiceListeners) {
230 l.onServiceConnected();
234 // not synchronized: use only from UI thread! (TODO: verify)
235 void callServiceDisconnectedListeners() {
236 for (ServiceListener listener : mServiceListeners) {
237 listener.onServiceDisconnected();
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();
249 profile = mA2dpProfile;
250 if (profile != null) {
251 return profile.isProfileReady();
256 A2dpProfile getA2dpProfile() {
260 HeadsetProfile getHeadsetProfile() {
261 return mHeadsetProfile;
265 * Fill in a list of LocalBluetoothProfile objects that are supported by
266 * the local device and the remote device.
268 * @param uuids of the remote device
269 * @param localUuids UUIDs of the local device
270 * @param profiles The list of profiles to fill
272 synchronized void updateProfiles(ParcelUuid[] uuids, ParcelUuid[] localUuids,
273 Collection<LocalBluetoothProfile> profiles) {
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);
289 if (BluetoothUuid.containsAnyUuid(uuids, A2dpProfile.SINK_UUIDS) &&
290 mA2dpProfile != null) {
291 profiles.add(mA2dpProfile);
294 if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.ObexObjectPush) &&
295 mOppProfile != null) {
296 profiles.add(mOppProfile);
299 if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Hid) &&
300 mHidProfile != null) {
301 profiles.add(mHidProfile);
304 if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.NAP) &&
305 mPanProfile != null) {
306 profiles.add(mPanProfile);