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