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