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