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.BluetoothDevice; 21 import android.bluetooth.BluetoothHeadset; 22 import android.bluetooth.BluetoothMap; 23 import android.bluetooth.BluetoothInputDevice; 24 import android.bluetooth.BluetoothPan; 25 import android.bluetooth.BluetoothProfile; 26 import android.bluetooth.BluetoothUuid; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.os.ParcelUuid; 30 import android.util.Log; 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 public final class LocalBluetoothProfileManager { 41 private static final String TAG = "LocalBluetoothProfileManager"; 42 private static final boolean DEBUG = Utils.D; 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 com.android.settings.bluetooth.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 MapProfile mMapProfile; 78 private final HidProfile mHidProfile; 79 private OppProfile mOppProfile; 80 private final PanProfile mPanProfile; 81 private final PbapServerProfile mPbapProfile; 82 83 /** 84 * Mapping from profile name, e.g. "HEADSET" to profile object. 85 */ 86 private final Map<String, LocalBluetoothProfile> 87 mProfileNameMap = new HashMap<String, LocalBluetoothProfile>(); 88 89 LocalBluetoothProfileManager(Context context, 90 LocalBluetoothAdapter adapter, 91 CachedBluetoothDeviceManager deviceManager, 92 BluetoothEventManager eventManager) { 93 mContext = context; 94 95 mLocalAdapter = adapter; 96 mDeviceManager = deviceManager; 97 mEventManager = eventManager; 98 // pass this reference to adapter and event manager (circular dependency) 99 mLocalAdapter.setProfileManager(this); 100 mEventManager.setProfileManager(this); 101 102 ParcelUuid[] uuids = adapter.getUuids(); 103 104 // uuids may be null if Bluetooth is turned off 105 if (uuids != null) { 106 updateLocalProfiles(uuids); 107 } 108 109 // Always add HID and PAN profiles 110 mHidProfile = new HidProfile(context, mLocalAdapter, mDeviceManager, this); 111 addProfile(mHidProfile, HidProfile.NAME, 112 BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED); 113 114 mPanProfile = new PanProfile(context); 115 addPanProfile(mPanProfile, PanProfile.NAME, 116 BluetoothPan.ACTION_CONNECTION_STATE_CHANGED); 117 118 if(DEBUG) Log.d(TAG, "Adding local MAP profile"); 119 mMapProfile = new MapProfile(mContext, mLocalAdapter, 120 mDeviceManager, this); 121 addProfile(mMapProfile, MapProfile.NAME, 122 BluetoothMap.ACTION_CONNECTION_STATE_CHANGED); 123 124 //Create PBAP server profile, but do not add it to list of profiles 125 // as we do not need to monitor the profile as part of profile list 126 mPbapProfile = new PbapServerProfile(context); 127 128 if (DEBUG) Log.d(TAG, "LocalBluetoothProfileManager construction complete"); 129 } 130 131 /** 132 * Initialize or update the local profile objects. If a UUID was previously 133 * present but has been removed, we print a warning but don't remove the 134 * profile object as it might be referenced elsewhere, or the UUID might 135 * come back and we don't want multiple copies of the profile objects. 136 * @param uuids 137 */ 138 void updateLocalProfiles(ParcelUuid[] uuids) { 139 // A2DP 140 if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.AudioSource)) { 141 if (mA2dpProfile == null) { 142 if(DEBUG) Log.d(TAG, "Adding local A2DP profile"); 143 mA2dpProfile = new A2dpProfile(mContext, mLocalAdapter, mDeviceManager, this); 144 addProfile(mA2dpProfile, A2dpProfile.NAME, 145 BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED); 146 } 147 } else if (mA2dpProfile != null) { 148 Log.w(TAG, "Warning: A2DP profile was previously added but the UUID is now missing."); 149 } 150 151 // Headset / Handsfree 152 if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Handsfree_AG) || 153 BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.HSP_AG)) { 154 if (mHeadsetProfile == null) { 155 if (DEBUG) Log.d(TAG, "Adding local HEADSET profile"); 156 mHeadsetProfile = new HeadsetProfile(mContext, mLocalAdapter, 157 mDeviceManager, this); 158 addProfile(mHeadsetProfile, HeadsetProfile.NAME, 159 BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); 160 } 161 } else if (mHeadsetProfile != null) { 162 Log.w(TAG, "Warning: HEADSET profile was previously added but the UUID is now missing."); 163 } 164 165 // OPP 166 if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.ObexObjectPush)) { 167 if (mOppProfile == null) { 168 if(DEBUG) Log.d(TAG, "Adding local OPP profile"); 169 mOppProfile = new OppProfile(); 170 // Note: no event handler for OPP, only name map. 171 mProfileNameMap.put(OppProfile.NAME, mOppProfile); 172 } 173 } else if (mOppProfile != null) { 174 Log.w(TAG, "Warning: OPP profile was previously added but the UUID is now missing."); 175 } 176 mEventManager.registerProfileIntentReceiver(); 177 178 // There is no local SDP record for HID and Settings app doesn't control PBAP 179 } 180 181 private final Collection<ServiceListener> mServiceListeners = 182 new ArrayList<ServiceListener>(); 183 184 private void addProfile(LocalBluetoothProfile profile, 185 String profileName, String stateChangedAction) { 186 mEventManager.addProfileHandler(stateChangedAction, new StateChangedHandler(profile)); 187 mProfileNameMap.put(profileName, profile); 188 } 189 190 private void addPanProfile(LocalBluetoothProfile profile, 191 String profileName, String stateChangedAction) { 192 mEventManager.addProfileHandler(stateChangedAction, 193 new PanStateChangedHandler(profile)); 194 mProfileNameMap.put(profileName, profile); 195 } 196 197 public LocalBluetoothProfile getProfileByName(String name) { 198 return mProfileNameMap.get(name); 199 } 200 201 // Called from LocalBluetoothAdapter when state changes to ON 202 void setBluetoothStateOn() { 203 ParcelUuid[] uuids = mLocalAdapter.getUuids(); 204 if (uuids != null) { 205 updateLocalProfiles(uuids); 206 } 207 mEventManager.readPairedDevices(); 208 } 209 210 /** 211 * Generic handler for connection state change events for the specified profile. 212 */ 213 private class StateChangedHandler implements BluetoothEventManager.Handler { 214 final LocalBluetoothProfile mProfile; 215 216 StateChangedHandler(LocalBluetoothProfile profile) { 217 mProfile = profile; 218 } 219 220 public void onReceive(Context context, Intent intent, BluetoothDevice device) { 221 CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device); 222 if (cachedDevice == null) { 223 Log.w(TAG, "StateChangedHandler found new device: " + device); 224 cachedDevice = mDeviceManager.addDevice(mLocalAdapter, 225 LocalBluetoothProfileManager.this, device); 226 } 227 int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0); 228 int oldState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, 0); 229 if (newState == BluetoothProfile.STATE_DISCONNECTED && 230 oldState == BluetoothProfile.STATE_CONNECTING) { 231 Log.i(TAG, "Failed to connect " + mProfile + " device"); 232 } 233 234 cachedDevice.onProfileStateChanged(mProfile, newState); 235 cachedDevice.refresh(); 236 } 237 } 238 239 /** State change handler for NAP and PANU profiles. */ 240 private class PanStateChangedHandler extends StateChangedHandler { 241 242 PanStateChangedHandler(LocalBluetoothProfile profile) { 243 super(profile); 244 } 245 246 @Override 247 public void onReceive(Context context, Intent intent, BluetoothDevice device) { 248 PanProfile panProfile = (PanProfile) mProfile; 249 int role = intent.getIntExtra(BluetoothPan.EXTRA_LOCAL_ROLE, 0); 250 panProfile.setLocalRole(device, role); 251 super.onReceive(context, intent, device); 252 } 253 } 254 255 // called from DockService 256 public void addServiceListener(ServiceListener l) { 257 mServiceListeners.add(l); 258 } 259 260 // called from DockService 261 public void removeServiceListener(ServiceListener l) { 262 mServiceListeners.remove(l); 263 } 264 265 // not synchronized: use only from UI thread! (TODO: verify) 266 void callServiceConnectedListeners() { 267 for (ServiceListener l : mServiceListeners) { 268 l.onServiceConnected(); 269 } 270 } 271 272 // not synchronized: use only from UI thread! (TODO: verify) 273 void callServiceDisconnectedListeners() { 274 for (ServiceListener listener : mServiceListeners) { 275 listener.onServiceDisconnected(); 276 } 277 } 278 279 // This is called by DockService, so check Headset and A2DP. 280 public synchronized boolean isManagerReady() { 281 // Getting just the headset profile is fine for now. Will need to deal with A2DP 282 // and others if they aren't always in a ready state. 283 LocalBluetoothProfile profile = mHeadsetProfile; 284 if (profile != null) { 285 return profile.isProfileReady(); 286 } 287 profile = mA2dpProfile; 288 if (profile != null) { 289 return profile.isProfileReady(); 290 } 291 return false; 292 } 293 294 public A2dpProfile getA2dpProfile() { 295 return mA2dpProfile; 296 } 297 298 public HeadsetProfile getHeadsetProfile() { 299 return mHeadsetProfile; 300 } 301 302 public PbapServerProfile getPbapProfile(){ 303 return mPbapProfile; 304 } 305 306 public MapProfile getMapProfile(){ 307 return mMapProfile; 308 } 309 310 /** 311 * Fill in a list of LocalBluetoothProfile objects that are supported by 312 * the local device and the remote device. 313 * 314 * @param uuids of the remote device 315 * @param localUuids UUIDs of the local device 316 * @param profiles The list of profiles to fill 317 * @param removedProfiles list of profiles that were removed 318 */ 319 synchronized void updateProfiles(ParcelUuid[] uuids, ParcelUuid[] localUuids, 320 Collection<LocalBluetoothProfile> profiles, 321 Collection<LocalBluetoothProfile> removedProfiles, 322 boolean isPanNapConnected, BluetoothDevice device) { 323 // Copy previous profile list into removedProfiles 324 removedProfiles.clear(); 325 removedProfiles.addAll(profiles); 326 profiles.clear(); 327 328 if (uuids == null) { 329 return; 330 } 331 332 if (mHeadsetProfile != null) { 333 if ((BluetoothUuid.isUuidPresent(localUuids, BluetoothUuid.HSP_AG) && 334 BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.HSP)) || 335 (BluetoothUuid.isUuidPresent(localUuids, BluetoothUuid.Handsfree_AG) && 336 BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Handsfree))) { 337 profiles.add(mHeadsetProfile); 338 removedProfiles.remove(mHeadsetProfile); 339 } 340 } 341 342 if (BluetoothUuid.containsAnyUuid(uuids, A2dpProfile.SINK_UUIDS) && 343 mA2dpProfile != null) { 344 profiles.add(mA2dpProfile); 345 removedProfiles.remove(mA2dpProfile); 346 } 347 348 if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.ObexObjectPush) && 349 mOppProfile != null) { 350 profiles.add(mOppProfile); 351 removedProfiles.remove(mOppProfile); 352 } 353 354 if ((BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Hid) || 355 BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Hogp)) && 356 mHidProfile != null) { 357 profiles.add(mHidProfile); 358 removedProfiles.remove(mHidProfile); 359 } 360 361 if(isPanNapConnected) 362 if(DEBUG) Log.d(TAG, "Valid PAN-NAP connection exists."); 363 if ((BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.NAP) && 364 mPanProfile != null) || isPanNapConnected) { 365 profiles.add(mPanProfile); 366 removedProfiles.remove(mPanProfile); 367 } 368 369 if ((mMapProfile != null) && 370 (mMapProfile.getConnectionStatus(device) == BluetoothProfile.STATE_CONNECTED)) { 371 profiles.add(mMapProfile); 372 removedProfiles.remove(mMapProfile); 373 mMapProfile.setPreferred(device, true); 374 } 375 } 376 377 } 378