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