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