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.BluetoothAdapter; 22 import android.bluetooth.BluetoothCsipSetCoordinator; 23 import android.bluetooth.BluetoothDevice; 24 import android.bluetooth.BluetoothHapClient; 25 import android.bluetooth.BluetoothHeadset; 26 import android.bluetooth.BluetoothHeadsetClient; 27 import android.bluetooth.BluetoothHearingAid; 28 import android.bluetooth.BluetoothHidDevice; 29 import android.bluetooth.BluetoothHidHost; 30 import android.bluetooth.BluetoothLeAudio; 31 import android.bluetooth.BluetoothLeBroadcastAssistant; 32 import android.bluetooth.BluetoothMap; 33 import android.bluetooth.BluetoothMapClient; 34 import android.bluetooth.BluetoothPan; 35 import android.bluetooth.BluetoothPbap; 36 import android.bluetooth.BluetoothPbapClient; 37 import android.bluetooth.BluetoothProfile; 38 import android.bluetooth.BluetoothSap; 39 import android.bluetooth.BluetoothUuid; 40 import android.bluetooth.BluetoothVolumeControl; 41 import android.content.Context; 42 import android.content.Intent; 43 import android.os.ParcelUuid; 44 import android.util.Log; 45 46 import androidx.annotation.VisibleForTesting; 47 48 import com.android.internal.util.ArrayUtils; 49 import com.android.internal.util.CollectionUtils; 50 51 import java.util.ArrayList; 52 import java.util.Collection; 53 import java.util.HashMap; 54 import java.util.List; 55 import java.util.Map; 56 import java.util.concurrent.CopyOnWriteArrayList; 57 58 59 /** 60 * LocalBluetoothProfileManager provides access to the LocalBluetoothProfile 61 * objects for the available Bluetooth profiles. 62 */ 63 public class LocalBluetoothProfileManager { 64 private static final String TAG = "LocalBluetoothProfileManager"; 65 private static final boolean DEBUG = BluetoothUtils.D; 66 67 /** 68 * An interface for notifying BluetoothHeadset IPC clients when they have 69 * been connected to the BluetoothHeadset service. 70 * Only used by com.android.settings.bluetooth.DockService. 71 */ 72 public interface ServiceListener { 73 /** 74 * Called to notify the client when this proxy object has been 75 * connected to the BluetoothHeadset service. Clients must wait for 76 * this callback before making IPC calls on the BluetoothHeadset 77 * service. 78 */ onServiceConnected()79 void onServiceConnected(); 80 81 /** 82 * Called to notify the client that this proxy object has been 83 * disconnected from the BluetoothHeadset service. Clients must not 84 * make IPC calls on the BluetoothHeadset service after this callback. 85 * This callback will currently only occur if the application hosting 86 * the BluetoothHeadset service, but may be called more often in future. 87 */ onServiceDisconnected()88 void onServiceDisconnected(); 89 } 90 91 private final Context mContext; 92 private final CachedBluetoothDeviceManager mDeviceManager; 93 private final BluetoothEventManager mEventManager; 94 95 private A2dpProfile mA2dpProfile; 96 private A2dpSinkProfile mA2dpSinkProfile; 97 private HeadsetProfile mHeadsetProfile; 98 private HfpClientProfile mHfpClientProfile; 99 private MapProfile mMapProfile; 100 private MapClientProfile mMapClientProfile; 101 private HidProfile mHidProfile; 102 private HidDeviceProfile mHidDeviceProfile; 103 private OppProfile mOppProfile; 104 private PanProfile mPanProfile; 105 private PbapClientProfile mPbapClientProfile; 106 private PbapServerProfile mPbapProfile; 107 private HearingAidProfile mHearingAidProfile; 108 private HapClientProfile mHapClientProfile; 109 private CsipSetCoordinatorProfile mCsipSetCoordinatorProfile; 110 private LeAudioProfile mLeAudioProfile; 111 private LocalBluetoothLeBroadcast mLeAudioBroadcast; 112 private LocalBluetoothLeBroadcastAssistant mLeAudioBroadcastAssistant; 113 private SapProfile mSapProfile; 114 private VolumeControlProfile mVolumeControlProfile; 115 116 /** 117 * Mapping from profile name, e.g. "HEADSET" to profile object. 118 */ 119 private final Map<String, LocalBluetoothProfile> 120 mProfileNameMap = new HashMap<String, LocalBluetoothProfile>(); 121 LocalBluetoothProfileManager(Context context, LocalBluetoothAdapter adapter, CachedBluetoothDeviceManager deviceManager, BluetoothEventManager eventManager)122 LocalBluetoothProfileManager(Context context, 123 LocalBluetoothAdapter adapter, 124 CachedBluetoothDeviceManager deviceManager, 125 BluetoothEventManager eventManager) { 126 mContext = context; 127 128 mDeviceManager = deviceManager; 129 mEventManager = eventManager; 130 // pass this reference to adapter and event manager (circular dependency) 131 adapter.setProfileManager(this); 132 133 if (DEBUG) Log.d(TAG, "LocalBluetoothProfileManager construction complete"); 134 } 135 136 /** 137 * create profile instance according to bluetooth supported profile list 138 */ updateLocalProfiles()139 synchronized void updateLocalProfiles() { 140 List<Integer> supportedList = BluetoothAdapter.getDefaultAdapter().getSupportedProfiles(); 141 if (CollectionUtils.isEmpty(supportedList)) { 142 if (DEBUG) Log.d(TAG, "supportedList is null"); 143 return; 144 } 145 if (mA2dpProfile == null && supportedList.contains(BluetoothProfile.A2DP)) { 146 if (DEBUG) Log.d(TAG, "Adding local A2DP profile"); 147 mA2dpProfile = new A2dpProfile(mContext, mDeviceManager, this); 148 addProfile(mA2dpProfile, A2dpProfile.NAME, 149 BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED); 150 } 151 if (mA2dpSinkProfile == null && supportedList.contains(BluetoothProfile.A2DP_SINK)) { 152 if (DEBUG) Log.d(TAG, "Adding local A2DP SINK profile"); 153 mA2dpSinkProfile = new A2dpSinkProfile(mContext, mDeviceManager, this); 154 addProfile(mA2dpSinkProfile, A2dpSinkProfile.NAME, 155 BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED); 156 } 157 if (mHeadsetProfile == null && supportedList.contains(BluetoothProfile.HEADSET)) { 158 if (DEBUG) Log.d(TAG, "Adding local HEADSET profile"); 159 mHeadsetProfile = new HeadsetProfile(mContext, mDeviceManager, this); 160 addHeadsetProfile(mHeadsetProfile, HeadsetProfile.NAME, 161 BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED, 162 BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED, 163 BluetoothHeadset.STATE_AUDIO_DISCONNECTED); 164 } 165 if (mHfpClientProfile == null && supportedList.contains(BluetoothProfile.HEADSET_CLIENT)) { 166 if (DEBUG) Log.d(TAG, "Adding local HfpClient profile"); 167 mHfpClientProfile = new HfpClientProfile(mContext, mDeviceManager, this); 168 addProfile(mHfpClientProfile, HfpClientProfile.NAME, 169 BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED); 170 } 171 if (mMapClientProfile == null && supportedList.contains(BluetoothProfile.MAP_CLIENT)) { 172 if (DEBUG) Log.d(TAG, "Adding local MAP CLIENT profile"); 173 mMapClientProfile = new MapClientProfile(mContext, mDeviceManager,this); 174 addProfile(mMapClientProfile, MapClientProfile.NAME, 175 BluetoothMapClient.ACTION_CONNECTION_STATE_CHANGED); 176 } 177 if (mMapProfile == null && supportedList.contains(BluetoothProfile.MAP)) { 178 if (DEBUG) Log.d(TAG, "Adding local MAP profile"); 179 mMapProfile = new MapProfile(mContext, mDeviceManager, this); 180 addProfile(mMapProfile, MapProfile.NAME, BluetoothMap.ACTION_CONNECTION_STATE_CHANGED); 181 } 182 if (mOppProfile == null && supportedList.contains(BluetoothProfile.OPP)) { 183 if (DEBUG) Log.d(TAG, "Adding local OPP profile"); 184 mOppProfile = new OppProfile(); 185 // Note: no event handler for OPP, only name map. 186 mProfileNameMap.put(OppProfile.NAME, mOppProfile); 187 } 188 if (mHearingAidProfile == null && supportedList.contains(BluetoothProfile.HEARING_AID)) { 189 if (DEBUG) Log.d(TAG, "Adding local Hearing Aid profile"); 190 mHearingAidProfile = new HearingAidProfile(mContext, mDeviceManager, 191 this); 192 addProfile(mHearingAidProfile, HearingAidProfile.NAME, 193 BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED); 194 } 195 if (mHapClientProfile == null && supportedList.contains(BluetoothProfile.HAP_CLIENT)) { 196 if (DEBUG) Log.d(TAG, "Adding local HAP_CLIENT profile"); 197 mHapClientProfile = new HapClientProfile(mContext, mDeviceManager, this); 198 addProfile(mHapClientProfile, HapClientProfile.NAME, 199 BluetoothHapClient.ACTION_HAP_CONNECTION_STATE_CHANGED); 200 } 201 if (mHidProfile == null && supportedList.contains(BluetoothProfile.HID_HOST)) { 202 if (DEBUG) Log.d(TAG, "Adding local HID_HOST profile"); 203 mHidProfile = new HidProfile(mContext, mDeviceManager, this); 204 addProfile(mHidProfile, HidProfile.NAME, 205 BluetoothHidHost.ACTION_CONNECTION_STATE_CHANGED); 206 } 207 if (mHidDeviceProfile == null && supportedList.contains(BluetoothProfile.HID_DEVICE)) { 208 if (DEBUG) Log.d(TAG, "Adding local HID_DEVICE profile"); 209 mHidDeviceProfile = new HidDeviceProfile(mContext, mDeviceManager, this); 210 addProfile(mHidDeviceProfile, HidDeviceProfile.NAME, 211 BluetoothHidDevice.ACTION_CONNECTION_STATE_CHANGED); 212 } 213 if (mPanProfile == null && supportedList.contains(BluetoothProfile.PAN)) { 214 if (DEBUG) Log.d(TAG, "Adding local PAN profile"); 215 mPanProfile = new PanProfile(mContext); 216 addPanProfile(mPanProfile, PanProfile.NAME, 217 BluetoothPan.ACTION_CONNECTION_STATE_CHANGED); 218 } 219 if (mPbapProfile == null && supportedList.contains(BluetoothProfile.PBAP)) { 220 if (DEBUG) Log.d(TAG, "Adding local PBAP profile"); 221 mPbapProfile = new PbapServerProfile(mContext); 222 addProfile(mPbapProfile, PbapServerProfile.NAME, 223 BluetoothPbap.ACTION_CONNECTION_STATE_CHANGED); 224 } 225 if (mPbapClientProfile == null && supportedList.contains(BluetoothProfile.PBAP_CLIENT)) { 226 if (DEBUG) Log.d(TAG, "Adding local PBAP Client profile"); 227 mPbapClientProfile = new PbapClientProfile(mContext, mDeviceManager,this); 228 addProfile(mPbapClientProfile, PbapClientProfile.NAME, 229 BluetoothPbapClient.ACTION_CONNECTION_STATE_CHANGED); 230 } 231 if (mSapProfile == null && supportedList.contains(BluetoothProfile.SAP)) { 232 if (DEBUG) { 233 Log.d(TAG, "Adding local SAP profile"); 234 } 235 mSapProfile = new SapProfile(mContext, mDeviceManager, this); 236 addProfile(mSapProfile, SapProfile.NAME, BluetoothSap.ACTION_CONNECTION_STATE_CHANGED); 237 } 238 if (mVolumeControlProfile == null 239 && supportedList.contains(BluetoothProfile.VOLUME_CONTROL)) { 240 if (DEBUG) { 241 Log.d(TAG, "Adding local Volume Control profile"); 242 } 243 mVolumeControlProfile = new VolumeControlProfile(mContext, mDeviceManager, this); 244 addProfile(mVolumeControlProfile, VolumeControlProfile.NAME, 245 BluetoothVolumeControl.ACTION_CONNECTION_STATE_CHANGED); 246 } 247 if (mLeAudioProfile == null && supportedList.contains(BluetoothProfile.LE_AUDIO)) { 248 if (DEBUG) { 249 Log.d(TAG, "Adding local LE_AUDIO profile"); 250 } 251 mLeAudioProfile = new LeAudioProfile(mContext, mDeviceManager, this); 252 addProfile(mLeAudioProfile, LeAudioProfile.NAME, 253 BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED); 254 } 255 if (mLeAudioBroadcast == null 256 && supportedList.contains(BluetoothProfile.LE_AUDIO_BROADCAST)) { 257 if (DEBUG) { 258 Log.d(TAG, "Adding local LE_AUDIO_BROADCAST profile"); 259 } 260 mLeAudioBroadcast = new LocalBluetoothLeBroadcast(mContext, mDeviceManager); 261 // no event handler for the LE boradcast. 262 mProfileNameMap.put(LocalBluetoothLeBroadcast.NAME, mLeAudioBroadcast); 263 } 264 if (mLeAudioBroadcastAssistant == null 265 && supportedList.contains(BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT)) { 266 if (DEBUG) { 267 Log.d(TAG, "Adding local LE_AUDIO_BROADCAST_ASSISTANT profile"); 268 } 269 mLeAudioBroadcastAssistant = new LocalBluetoothLeBroadcastAssistant(mContext, 270 mDeviceManager, this); 271 addProfile(mLeAudioBroadcastAssistant, LocalBluetoothLeBroadcast.NAME, 272 BluetoothLeBroadcastAssistant.ACTION_CONNECTION_STATE_CHANGED); 273 } 274 if (mCsipSetCoordinatorProfile == null 275 && supportedList.contains(BluetoothProfile.CSIP_SET_COORDINATOR)) { 276 if (DEBUG) { 277 Log.d(TAG, "Adding local CSIP set coordinator profile"); 278 } 279 mCsipSetCoordinatorProfile = 280 new CsipSetCoordinatorProfile(mContext, mDeviceManager, this); 281 addProfile(mCsipSetCoordinatorProfile, mCsipSetCoordinatorProfile.NAME, 282 BluetoothCsipSetCoordinator.ACTION_CSIS_CONNECTION_STATE_CHANGED); 283 } 284 mEventManager.registerProfileIntentReceiver(); 285 } 286 addHeadsetProfile(LocalBluetoothProfile profile, String profileName, String stateChangedAction, String audioStateChangedAction, int audioDisconnectedState)287 private void addHeadsetProfile(LocalBluetoothProfile profile, String profileName, 288 String stateChangedAction, String audioStateChangedAction, int audioDisconnectedState) { 289 BluetoothEventManager.Handler handler = new HeadsetStateChangeHandler( 290 profile, audioStateChangedAction, audioDisconnectedState); 291 mEventManager.addProfileHandler(stateChangedAction, handler); 292 mEventManager.addProfileHandler(audioStateChangedAction, handler); 293 mProfileNameMap.put(profileName, profile); 294 } 295 296 private final Collection<ServiceListener> mServiceListeners = 297 new CopyOnWriteArrayList<ServiceListener>(); 298 addProfile(LocalBluetoothProfile profile, String profileName, String stateChangedAction)299 private void addProfile(LocalBluetoothProfile profile, 300 String profileName, String stateChangedAction) { 301 mEventManager.addProfileHandler(stateChangedAction, new StateChangedHandler(profile)); 302 mProfileNameMap.put(profileName, profile); 303 } 304 addPanProfile(LocalBluetoothProfile profile, String profileName, String stateChangedAction)305 private void addPanProfile(LocalBluetoothProfile profile, 306 String profileName, String stateChangedAction) { 307 mEventManager.addProfileHandler(stateChangedAction, 308 new PanStateChangedHandler(profile)); 309 mProfileNameMap.put(profileName, profile); 310 } 311 getProfileByName(String name)312 public LocalBluetoothProfile getProfileByName(String name) { 313 return mProfileNameMap.get(name); 314 } 315 316 // Called from LocalBluetoothAdapter when state changes to ON setBluetoothStateOn()317 void setBluetoothStateOn() { 318 updateLocalProfiles(); 319 mEventManager.readPairedDevices(); 320 } 321 322 /** 323 * Generic handler for connection state change events for the specified profile. 324 */ 325 private class StateChangedHandler implements BluetoothEventManager.Handler { 326 final LocalBluetoothProfile mProfile; 327 StateChangedHandler(LocalBluetoothProfile profile)328 StateChangedHandler(LocalBluetoothProfile profile) { 329 mProfile = profile; 330 } 331 onReceive(Context context, Intent intent, BluetoothDevice device)332 public void onReceive(Context context, Intent intent, BluetoothDevice device) { 333 CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device); 334 if (cachedDevice == null) { 335 Log.w(TAG, "StateChangedHandler found new device: " + device); 336 cachedDevice = mDeviceManager.addDevice(device); 337 } 338 onReceiveInternal(intent, cachedDevice); 339 } 340 onReceiveInternal(Intent intent, CachedBluetoothDevice cachedDevice)341 protected void onReceiveInternal(Intent intent, CachedBluetoothDevice cachedDevice) { 342 int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0); 343 int oldState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, 0); 344 if (newState == BluetoothProfile.STATE_DISCONNECTED && 345 oldState == BluetoothProfile.STATE_CONNECTING) { 346 Log.i(TAG, "Failed to connect " + mProfile + " device"); 347 } 348 349 if (getHearingAidProfile() != null 350 && mProfile instanceof HearingAidProfile 351 && (newState == BluetoothProfile.STATE_CONNECTED)) { 352 353 // Check if the HiSyncID has being initialized 354 if (cachedDevice.getHiSyncId() == BluetoothHearingAid.HI_SYNC_ID_INVALID) { 355 long newHiSyncId = getHearingAidProfile().getHiSyncId(cachedDevice.getDevice()); 356 if (newHiSyncId != BluetoothHearingAid.HI_SYNC_ID_INVALID) { 357 final BluetoothDevice device = cachedDevice.getDevice(); 358 final HearingAidInfo.Builder infoBuilder = new HearingAidInfo.Builder() 359 .setAshaDeviceSide(getHearingAidProfile().getDeviceSide(device)) 360 .setAshaDeviceMode(getHearingAidProfile().getDeviceMode(device)) 361 .setHiSyncId(newHiSyncId); 362 cachedDevice.setHearingAidInfo(infoBuilder.build()); 363 } 364 } 365 366 HearingAidStatsLogUtils.logHearingAidInfo(cachedDevice); 367 } 368 369 final boolean isHapClientProfile = getHapClientProfile() != null 370 && mProfile instanceof HapClientProfile; 371 final boolean isLeAudioProfile = getLeAudioProfile() != null 372 && mProfile instanceof LeAudioProfile; 373 final boolean isHapClientOrLeAudioProfile = isHapClientProfile || isLeAudioProfile; 374 if (isHapClientOrLeAudioProfile && newState == BluetoothProfile.STATE_CONNECTED) { 375 376 // Checks if both profiles are connected to the device. Hearing aid info need 377 // to be retrieved from these profiles separately. 378 if (cachedDevice.isConnectedLeAudioHearingAidDevice()) { 379 final BluetoothDevice device = cachedDevice.getDevice(); 380 final HearingAidInfo.Builder infoBuilder = new HearingAidInfo.Builder() 381 .setLeAudioLocation(getLeAudioProfile().getAudioLocation(device)) 382 .setHapDeviceType(getHapClientProfile().getHearingAidType(device)); 383 cachedDevice.setHearingAidInfo(infoBuilder.build()); 384 HearingAidStatsLogUtils.logHearingAidInfo(cachedDevice); 385 } 386 } 387 388 if (getCsipSetCoordinatorProfile() != null 389 && mProfile instanceof CsipSetCoordinatorProfile 390 && newState == BluetoothProfile.STATE_CONNECTED) { 391 // Check if the GroupID has being initialized 392 if (cachedDevice.getGroupId() == BluetoothCsipSetCoordinator.GROUP_ID_INVALID) { 393 final Map<Integer, ParcelUuid> groupIdMap = getCsipSetCoordinatorProfile() 394 .getGroupUuidMapByDevice(cachedDevice.getDevice()); 395 if (groupIdMap != null) { 396 for (Map.Entry<Integer, ParcelUuid> entry: groupIdMap.entrySet()) { 397 if (entry.getValue().equals(BluetoothUuid.CAP)) { 398 cachedDevice.setGroupId(entry.getKey()); 399 break; 400 } 401 } 402 } 403 } 404 } 405 406 cachedDevice.onProfileStateChanged(mProfile, newState); 407 // Dispatch profile changed after device update 408 boolean needDispatchProfileConnectionState = true; 409 if (cachedDevice.getHiSyncId() != BluetoothHearingAid.HI_SYNC_ID_INVALID 410 || cachedDevice.getGroupId() != BluetoothCsipSetCoordinator.GROUP_ID_INVALID) { 411 mDeviceManager.syncDeviceWithinHearingAidSetIfNeeded(cachedDevice, newState, 412 mProfile.getProfileId()); 413 needDispatchProfileConnectionState = !mDeviceManager 414 .onProfileConnectionStateChangedIfProcessed(cachedDevice, newState, 415 mProfile.getProfileId()); 416 } 417 if (needDispatchProfileConnectionState) { 418 cachedDevice.refresh(); 419 mEventManager.dispatchProfileConnectionStateChanged(cachedDevice, newState, 420 mProfile.getProfileId()); 421 } 422 } 423 } 424 425 /** Connectivity and audio state change handler for headset profiles. */ 426 private class HeadsetStateChangeHandler extends StateChangedHandler { 427 private final String mAudioChangeAction; 428 private final int mAudioDisconnectedState; 429 HeadsetStateChangeHandler(LocalBluetoothProfile profile, String audioChangeAction, int audioDisconnectedState)430 HeadsetStateChangeHandler(LocalBluetoothProfile profile, String audioChangeAction, 431 int audioDisconnectedState) { 432 super(profile); 433 mAudioChangeAction = audioChangeAction; 434 mAudioDisconnectedState = audioDisconnectedState; 435 } 436 437 @Override onReceiveInternal(Intent intent, CachedBluetoothDevice cachedDevice)438 public void onReceiveInternal(Intent intent, CachedBluetoothDevice cachedDevice) { 439 if (mAudioChangeAction.equals(intent.getAction())) { 440 int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0); 441 if (newState != mAudioDisconnectedState) { 442 cachedDevice.onProfileStateChanged(mProfile, BluetoothProfile.STATE_CONNECTED); 443 } 444 cachedDevice.refresh(); 445 } else { 446 super.onReceiveInternal(intent, cachedDevice); 447 } 448 } 449 } 450 451 /** State change handler for NAP and PANU profiles. */ 452 private class PanStateChangedHandler extends StateChangedHandler { 453 PanStateChangedHandler(LocalBluetoothProfile profile)454 PanStateChangedHandler(LocalBluetoothProfile profile) { 455 super(profile); 456 } 457 458 @Override onReceive(Context context, Intent intent, BluetoothDevice device)459 public void onReceive(Context context, Intent intent, BluetoothDevice device) { 460 PanProfile panProfile = (PanProfile) mProfile; 461 int role = intent.getIntExtra(BluetoothPan.EXTRA_LOCAL_ROLE, 0); 462 panProfile.setLocalRole(device, role); 463 super.onReceive(context, intent, device); 464 } 465 } 466 467 // called from DockService addServiceListener(ServiceListener l)468 public void addServiceListener(ServiceListener l) { 469 mServiceListeners.add(l); 470 } 471 472 // called from DockService removeServiceListener(ServiceListener l)473 public void removeServiceListener(ServiceListener l) { 474 mServiceListeners.remove(l); 475 } 476 477 // not synchronized: use only from UI thread! (TODO: verify) callServiceConnectedListeners()478 void callServiceConnectedListeners() { 479 final Collection<ServiceListener> listeners = new ArrayList<>(mServiceListeners); 480 481 for (ServiceListener l : listeners) { 482 l.onServiceConnected(); 483 } 484 } 485 486 // not synchronized: use only from UI thread! (TODO: verify) callServiceDisconnectedListeners()487 void callServiceDisconnectedListeners() { 488 final Collection<ServiceListener> listeners = new ArrayList<>(mServiceListeners); 489 490 for (ServiceListener listener : listeners) { 491 listener.onServiceDisconnected(); 492 } 493 } 494 495 // This is called by DockService, so check Headset and A2DP. isManagerReady()496 public synchronized boolean isManagerReady() { 497 // Getting just the headset profile is fine for now. Will need to deal with A2DP 498 // and others if they aren't always in a ready state. 499 LocalBluetoothProfile profile = mHeadsetProfile; 500 if (profile != null) { 501 return profile.isProfileReady(); 502 } 503 profile = mA2dpProfile; 504 if (profile != null) { 505 return profile.isProfileReady(); 506 } 507 profile = mA2dpSinkProfile; 508 if (profile != null) { 509 return profile.isProfileReady(); 510 } 511 return false; 512 } 513 getA2dpProfile()514 public A2dpProfile getA2dpProfile() { 515 return mA2dpProfile; 516 } 517 getA2dpSinkProfile()518 public A2dpSinkProfile getA2dpSinkProfile() { 519 if ((mA2dpSinkProfile != null) && (mA2dpSinkProfile.isProfileReady())) { 520 return mA2dpSinkProfile; 521 } else { 522 return null; 523 } 524 } 525 getHeadsetProfile()526 public HeadsetProfile getHeadsetProfile() { 527 return mHeadsetProfile; 528 } 529 getHfpClientProfile()530 public HfpClientProfile getHfpClientProfile() { 531 if ((mHfpClientProfile != null) && (mHfpClientProfile.isProfileReady())) { 532 return mHfpClientProfile; 533 } else { 534 return null; 535 } 536 } 537 getPbapClientProfile()538 public PbapClientProfile getPbapClientProfile() { 539 return mPbapClientProfile; 540 } 541 getPbapProfile()542 public PbapServerProfile getPbapProfile(){ 543 return mPbapProfile; 544 } 545 getMapProfile()546 public MapProfile getMapProfile(){ 547 return mMapProfile; 548 } 549 getMapClientProfile()550 public MapClientProfile getMapClientProfile() { 551 return mMapClientProfile; 552 } 553 getHearingAidProfile()554 public HearingAidProfile getHearingAidProfile() { 555 return mHearingAidProfile; 556 } 557 getHapClientProfile()558 public HapClientProfile getHapClientProfile() { 559 return mHapClientProfile; 560 } 561 getLeAudioProfile()562 public LeAudioProfile getLeAudioProfile() { 563 return mLeAudioProfile; 564 } 565 getLeAudioBroadcastProfile()566 public LocalBluetoothLeBroadcast getLeAudioBroadcastProfile() { 567 return mLeAudioBroadcast; 568 } getLeAudioBroadcastAssistantProfile()569 public LocalBluetoothLeBroadcastAssistant getLeAudioBroadcastAssistantProfile() { 570 return mLeAudioBroadcastAssistant; 571 } 572 getSapProfile()573 SapProfile getSapProfile() { 574 return mSapProfile; 575 } 576 getHidProfile()577 public HidProfile getHidProfile() { 578 return mHidProfile; 579 } 580 581 @VisibleForTesting getHidDeviceProfile()582 HidDeviceProfile getHidDeviceProfile() { 583 return mHidDeviceProfile; 584 } 585 getCsipSetCoordinatorProfile()586 public CsipSetCoordinatorProfile getCsipSetCoordinatorProfile() { 587 return mCsipSetCoordinatorProfile; 588 } 589 getVolumeControlProfile()590 public VolumeControlProfile getVolumeControlProfile() { 591 return mVolumeControlProfile; 592 } 593 594 /** 595 * Fill in a list of LocalBluetoothProfile objects that are supported by 596 * the local device and the remote device. 597 * 598 * @param uuids of the remote device 599 * @param localUuids UUIDs of the local device 600 * @param profiles The list of profiles to fill 601 * @param removedProfiles list of profiles that were removed 602 */ updateProfiles(ParcelUuid[] uuids, ParcelUuid[] localUuids, Collection<LocalBluetoothProfile> profiles, Collection<LocalBluetoothProfile> removedProfiles, boolean isPanNapConnected, BluetoothDevice device)603 synchronized void updateProfiles(ParcelUuid[] uuids, ParcelUuid[] localUuids, 604 Collection<LocalBluetoothProfile> profiles, 605 Collection<LocalBluetoothProfile> removedProfiles, 606 boolean isPanNapConnected, BluetoothDevice device) { 607 // Copy previous profile list into removedProfiles 608 removedProfiles.clear(); 609 removedProfiles.addAll(profiles); 610 if (DEBUG) { 611 Log.d(TAG,"Current Profiles" + profiles.toString()); 612 } 613 profiles.clear(); 614 615 if (uuids == null) { 616 return; 617 } 618 619 // The profiles list's sequence will affect the bluetooth icon at 620 // BluetoothUtils.getBtClassDrawableWithDescription(Context,CachedBluetoothDevice). 621 622 // Moving the LE audio profile to be the first priority if the device supports LE audio. 623 if (ArrayUtils.contains(uuids, BluetoothUuid.LE_AUDIO) && mLeAudioProfile != null) { 624 profiles.add(mLeAudioProfile); 625 removedProfiles.remove(mLeAudioProfile); 626 } 627 628 if (mHeadsetProfile != null) { 629 if ((ArrayUtils.contains(localUuids, BluetoothUuid.HSP_AG) 630 && ArrayUtils.contains(uuids, BluetoothUuid.HSP)) 631 || (ArrayUtils.contains(localUuids, BluetoothUuid.HFP_AG) 632 && ArrayUtils.contains(uuids, BluetoothUuid.HFP))) { 633 profiles.add(mHeadsetProfile); 634 removedProfiles.remove(mHeadsetProfile); 635 } 636 } 637 638 if ((mHfpClientProfile != null) && 639 ArrayUtils.contains(uuids, BluetoothUuid.HFP_AG) 640 && ArrayUtils.contains(localUuids, BluetoothUuid.HFP)) { 641 profiles.add(mHfpClientProfile); 642 removedProfiles.remove(mHfpClientProfile); 643 } 644 645 if (BluetoothUuid.containsAnyUuid(uuids, A2dpProfile.SINK_UUIDS) && mA2dpProfile != null) { 646 profiles.add(mA2dpProfile); 647 removedProfiles.remove(mA2dpProfile); 648 } 649 650 if (BluetoothUuid.containsAnyUuid(uuids, A2dpSinkProfile.SRC_UUIDS) 651 && mA2dpSinkProfile != null) { 652 profiles.add(mA2dpSinkProfile); 653 removedProfiles.remove(mA2dpSinkProfile); 654 } 655 656 if (ArrayUtils.contains(uuids, BluetoothUuid.OBEX_OBJECT_PUSH) && mOppProfile != null) { 657 profiles.add(mOppProfile); 658 removedProfiles.remove(mOppProfile); 659 } 660 661 if ((ArrayUtils.contains(uuids, BluetoothUuid.HID) 662 || ArrayUtils.contains(uuids, BluetoothUuid.HOGP)) && mHidProfile != null) { 663 profiles.add(mHidProfile); 664 removedProfiles.remove(mHidProfile); 665 } 666 667 if (mHidDeviceProfile != null && mHidDeviceProfile.getConnectionStatus(device) 668 != BluetoothProfile.STATE_DISCONNECTED) { 669 profiles.add(mHidDeviceProfile); 670 removedProfiles.remove(mHidDeviceProfile); 671 } 672 673 if(isPanNapConnected) 674 if(DEBUG) Log.d(TAG, "Valid PAN-NAP connection exists."); 675 if ((ArrayUtils.contains(uuids, BluetoothUuid.NAP) && mPanProfile != null) 676 || isPanNapConnected) { 677 profiles.add(mPanProfile); 678 removedProfiles.remove(mPanProfile); 679 } 680 681 if ((mMapProfile != null) && 682 (mMapProfile.getConnectionStatus(device) == BluetoothProfile.STATE_CONNECTED)) { 683 profiles.add(mMapProfile); 684 removedProfiles.remove(mMapProfile); 685 mMapProfile.setEnabled(device, true); 686 } 687 688 if ((mPbapProfile != null) && 689 (mPbapProfile.getConnectionStatus(device) == BluetoothProfile.STATE_CONNECTED)) { 690 profiles.add(mPbapProfile); 691 removedProfiles.remove(mPbapProfile); 692 mPbapProfile.setEnabled(device, true); 693 } 694 695 if ((mMapClientProfile != null) 696 && BluetoothUuid.containsAnyUuid(uuids, MapClientProfile.UUIDS)) { 697 profiles.add(mMapClientProfile); 698 removedProfiles.remove(mMapClientProfile); 699 } 700 701 if ((mPbapClientProfile != null) 702 && BluetoothUuid.containsAnyUuid(uuids, PbapClientProfile.SRC_UUIDS)) { 703 profiles.add(mPbapClientProfile); 704 removedProfiles.remove(mPbapClientProfile); 705 } 706 707 if (ArrayUtils.contains(uuids, BluetoothUuid.HEARING_AID) && mHearingAidProfile != null) { 708 profiles.add(mHearingAidProfile); 709 removedProfiles.remove(mHearingAidProfile); 710 } 711 712 if (mHapClientProfile != null && ArrayUtils.contains(uuids, BluetoothUuid.HAS)) { 713 profiles.add(mHapClientProfile); 714 removedProfiles.remove(mHapClientProfile); 715 } 716 717 if (mSapProfile != null && ArrayUtils.contains(uuids, BluetoothUuid.SAP)) { 718 profiles.add(mSapProfile); 719 removedProfiles.remove(mSapProfile); 720 } 721 722 if (mVolumeControlProfile != null 723 && ArrayUtils.contains(uuids, BluetoothUuid.VOLUME_CONTROL)) { 724 profiles.add(mVolumeControlProfile); 725 removedProfiles.remove(mVolumeControlProfile); 726 } 727 728 if (mCsipSetCoordinatorProfile != null 729 && ArrayUtils.contains(uuids, BluetoothUuid.COORDINATED_SET)) { 730 profiles.add(mCsipSetCoordinatorProfile); 731 removedProfiles.remove(mCsipSetCoordinatorProfile); 732 } 733 734 if (DEBUG) { 735 Log.d(TAG,"New Profiles" + profiles.toString()); 736 } 737 } 738 } 739