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 17 package com.android.settings.bluetooth; 18 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; 30 31 import java.util.ArrayList; 32 import java.util.Collection; 33 import java.util.HashMap; 34 import java.util.Map; 35 36 /** 37 * LocalBluetoothProfileManager provides access to the LocalBluetoothProfile 38 * objects for the available Bluetooth profiles. 39 */ 40 final 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 addPanProfile(mPanProfile, PanProfile.NAME, 114 BluetoothPan.ACTION_CONNECTION_STATE_CHANGED); 115 116 Log.d(TAG, "LocalBluetoothProfileManager construction complete"); 117 } 118 119 /** 120 * Initialize or update the local profile objects. If a UUID was previously 121 * present but has been removed, we print a warning but don't remove the 122 * profile object as it might be referenced elsewhere, or the UUID might 123 * come back and we don't want multiple copies of the profile objects. 124 * @param uuids 125 */ 126 void updateLocalProfiles(ParcelUuid[] uuids) { 127 // A2DP 128 if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.AudioSource)) { 129 if (mA2dpProfile == null) { 130 Log.d(TAG, "Adding local A2DP profile"); 131 mA2dpProfile = new A2dpProfile(mContext); 132 addProfile(mA2dpProfile, A2dpProfile.NAME, 133 BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED); 134 } 135 } else if (mA2dpProfile != null) { 136 Log.w(TAG, "Warning: A2DP profile was previously added but the UUID is now missing."); 137 } 138 139 // Headset / Handsfree 140 if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Handsfree_AG) || 141 BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.HSP_AG)) { 142 if (mHeadsetProfile == null) { 143 Log.d(TAG, "Adding local HEADSET profile"); 144 mHeadsetProfile = new HeadsetProfile(mContext, mLocalAdapter, 145 mDeviceManager, this); 146 addProfile(mHeadsetProfile, HeadsetProfile.NAME, 147 BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); 148 } 149 } else if (mHeadsetProfile != null) { 150 Log.w(TAG, "Warning: HEADSET profile was previously added but the UUID is now missing."); 151 } 152 153 // OPP 154 if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.ObexObjectPush)) { 155 if (mOppProfile == null) { 156 Log.d(TAG, "Adding local OPP profile"); 157 mOppProfile = new OppProfile(); 158 // Note: no event handler for OPP, only name map. 159 mProfileNameMap.put(OppProfile.NAME, mOppProfile); 160 } 161 } else if (mOppProfile != null) { 162 Log.w(TAG, "Warning: OPP profile was previously added but the UUID is now missing."); 163 } 164 mEventManager.registerProfileIntentReceiver(); 165 166 // There is no local SDP record for HID and Settings app doesn't control PBAP 167 } 168 169 private final Collection<ServiceListener> mServiceListeners = 170 new ArrayList<ServiceListener>(); 171 172 private void addProfile(LocalBluetoothProfile profile, 173 String profileName, String stateChangedAction) { 174 mEventManager.addProfileHandler(stateChangedAction, new StateChangedHandler(profile)); 175 mProfileNameMap.put(profileName, profile); 176 } 177 178 private void addPanProfile(LocalBluetoothProfile profile, 179 String profileName, String stateChangedAction) { 180 mEventManager.addProfileHandler(stateChangedAction, 181 new PanStateChangedHandler(profile)); 182 mProfileNameMap.put(profileName, profile); 183 } 184 185 LocalBluetoothProfile getProfileByName(String name) { 186 return mProfileNameMap.get(name); 187 } 188 189 // Called from LocalBluetoothAdapter when state changes to ON 190 void setBluetoothStateOn() { 191 ParcelUuid[] uuids = mLocalAdapter.getUuids(); 192 if (uuids != null) { 193 updateLocalProfiles(uuids); 194 } 195 mEventManager.readPairedDevices(); 196 } 197 198 /** 199 * Generic handler for connection state change events for the specified profile. 200 */ 201 private class StateChangedHandler implements BluetoothEventManager.Handler { 202 final LocalBluetoothProfile mProfile; 203 204 StateChangedHandler(LocalBluetoothProfile profile) { 205 mProfile = profile; 206 } 207 208 public void onReceive(Context context, Intent intent, BluetoothDevice device) { 209 CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device); 210 if (cachedDevice == null) { 211 Log.w(TAG, "StateChangedHandler found new device: " + device); 212 cachedDevice = mDeviceManager.addDevice(mLocalAdapter, 213 LocalBluetoothProfileManager.this, device); 214 } 215 int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0); 216 int oldState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, 0); 217 if (newState == BluetoothProfile.STATE_DISCONNECTED && 218 oldState == BluetoothProfile.STATE_CONNECTING) { 219 Log.i(TAG, "Failed to connect " + mProfile + " device"); 220 } 221 222 cachedDevice.onProfileStateChanged(mProfile, newState); 223 cachedDevice.refresh(); 224 } 225 } 226 227 /** State change handler for NAP and PANU profiles. */ 228 private class PanStateChangedHandler extends StateChangedHandler { 229 230 PanStateChangedHandler(LocalBluetoothProfile profile) { 231 super(profile); 232 } 233 234 @Override 235 public void onReceive(Context context, Intent intent, BluetoothDevice device) { 236 PanProfile panProfile = (PanProfile) mProfile; 237 int role = intent.getIntExtra(BluetoothPan.EXTRA_LOCAL_ROLE, 0); 238 panProfile.setLocalRole(device, role); 239 super.onReceive(context, intent, device); 240 } 241 } 242 243 // called from DockService 244 void addServiceListener(ServiceListener l) { 245 mServiceListeners.add(l); 246 } 247 248 // called from DockService 249 void removeServiceListener(ServiceListener l) { 250 mServiceListeners.remove(l); 251 } 252 253 // not synchronized: use only from UI thread! (TODO: verify) 254 void callServiceConnectedListeners() { 255 for (ServiceListener l : mServiceListeners) { 256 l.onServiceConnected(); 257 } 258 } 259 260 // not synchronized: use only from UI thread! (TODO: verify) 261 void callServiceDisconnectedListeners() { 262 for (ServiceListener listener : mServiceListeners) { 263 listener.onServiceDisconnected(); 264 } 265 } 266 267 // This is called by DockService, so check Headset and A2DP. 268 public synchronized boolean isManagerReady() { 269 // Getting just the headset profile is fine for now. Will need to deal with A2DP 270 // and others if they aren't always in a ready state. 271 LocalBluetoothProfile profile = mHeadsetProfile; 272 if (profile != null) { 273 return profile.isProfileReady(); 274 } 275 profile = mA2dpProfile; 276 if (profile != null) { 277 return profile.isProfileReady(); 278 } 279 return false; 280 } 281 282 A2dpProfile getA2dpProfile() { 283 return mA2dpProfile; 284 } 285 286 HeadsetProfile getHeadsetProfile() { 287 return mHeadsetProfile; 288 } 289 290 /** 291 * Fill in a list of LocalBluetoothProfile objects that are supported by 292 * the local device and the remote device. 293 * 294 * @param uuids of the remote device 295 * @param localUuids UUIDs of the local device 296 * @param profiles The list of profiles to fill 297 * @param removedProfiles list of profiles that were removed 298 */ 299 synchronized void updateProfiles(ParcelUuid[] uuids, ParcelUuid[] localUuids, 300 Collection<LocalBluetoothProfile> profiles, 301 Collection<LocalBluetoothProfile> removedProfiles) { 302 // Copy previous profile list into removedProfiles 303 removedProfiles.clear(); 304 removedProfiles.addAll(profiles); 305 profiles.clear(); 306 307 if (uuids == null) { 308 return; 309 } 310 311 if (mHeadsetProfile != null) { 312 if ((BluetoothUuid.isUuidPresent(localUuids, BluetoothUuid.HSP_AG) && 313 BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.HSP)) || 314 (BluetoothUuid.isUuidPresent(localUuids, BluetoothUuid.Handsfree_AG) && 315 BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Handsfree))) { 316 profiles.add(mHeadsetProfile); 317 removedProfiles.remove(mHeadsetProfile); 318 } 319 } 320 321 if (BluetoothUuid.containsAnyUuid(uuids, A2dpProfile.SINK_UUIDS) && 322 mA2dpProfile != null) { 323 profiles.add(mA2dpProfile); 324 removedProfiles.remove(mA2dpProfile); 325 } 326 327 if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.ObexObjectPush) && 328 mOppProfile != null) { 329 profiles.add(mOppProfile); 330 removedProfiles.remove(mOppProfile); 331 } 332 333 if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Hid) && 334 mHidProfile != null) { 335 profiles.add(mHidProfile); 336 removedProfiles.remove(mHidProfile); 337 } 338 339 if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.NAP) && 340 mPanProfile != null) { 341 profiles.add(mPanProfile); 342 removedProfiles.remove(mPanProfile); 343 } 344 } 345 } 346