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 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); 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 needDispatchProfileConnectionState = !mDeviceManager 412 .onProfileConnectionStateChangedIfProcessed(cachedDevice, newState, 413 mProfile.getProfileId()); 414 } 415 if (needDispatchProfileConnectionState) { 416 cachedDevice.refresh(); 417 mEventManager.dispatchProfileConnectionStateChanged(cachedDevice, newState, 418 mProfile.getProfileId()); 419 } 420 } 421 } 422 423 /** Connectivity and audio state change handler for headset profiles. */ 424 private class HeadsetStateChangeHandler extends StateChangedHandler { 425 private final String mAudioChangeAction; 426 private final int mAudioDisconnectedState; 427 HeadsetStateChangeHandler(LocalBluetoothProfile profile, String audioChangeAction, int audioDisconnectedState)428 HeadsetStateChangeHandler(LocalBluetoothProfile profile, String audioChangeAction, 429 int audioDisconnectedState) { 430 super(profile); 431 mAudioChangeAction = audioChangeAction; 432 mAudioDisconnectedState = audioDisconnectedState; 433 } 434 435 @Override onReceiveInternal(Intent intent, CachedBluetoothDevice cachedDevice)436 public void onReceiveInternal(Intent intent, CachedBluetoothDevice cachedDevice) { 437 if (mAudioChangeAction.equals(intent.getAction())) { 438 int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0); 439 if (newState != mAudioDisconnectedState) { 440 cachedDevice.onProfileStateChanged(mProfile, BluetoothProfile.STATE_CONNECTED); 441 } 442 cachedDevice.refresh(); 443 } else { 444 super.onReceiveInternal(intent, cachedDevice); 445 } 446 } 447 } 448 449 /** State change handler for NAP and PANU profiles. */ 450 private class PanStateChangedHandler extends StateChangedHandler { 451 PanStateChangedHandler(LocalBluetoothProfile profile)452 PanStateChangedHandler(LocalBluetoothProfile profile) { 453 super(profile); 454 } 455 456 @Override onReceive(Context context, Intent intent, BluetoothDevice device)457 public void onReceive(Context context, Intent intent, BluetoothDevice device) { 458 PanProfile panProfile = (PanProfile) mProfile; 459 int role = intent.getIntExtra(BluetoothPan.EXTRA_LOCAL_ROLE, 0); 460 panProfile.setLocalRole(device, role); 461 super.onReceive(context, intent, device); 462 } 463 } 464 465 // called from DockService addServiceListener(ServiceListener l)466 public void addServiceListener(ServiceListener l) { 467 mServiceListeners.add(l); 468 } 469 470 // called from DockService removeServiceListener(ServiceListener l)471 public void removeServiceListener(ServiceListener l) { 472 mServiceListeners.remove(l); 473 } 474 475 // not synchronized: use only from UI thread! (TODO: verify) callServiceConnectedListeners()476 void callServiceConnectedListeners() { 477 final Collection<ServiceListener> listeners = new ArrayList<>(mServiceListeners); 478 479 for (ServiceListener l : listeners) { 480 l.onServiceConnected(); 481 } 482 } 483 484 // not synchronized: use only from UI thread! (TODO: verify) callServiceDisconnectedListeners()485 void callServiceDisconnectedListeners() { 486 final Collection<ServiceListener> listeners = new ArrayList<>(mServiceListeners); 487 488 for (ServiceListener listener : listeners) { 489 listener.onServiceDisconnected(); 490 } 491 } 492 493 // This is called by DockService, so check Headset and A2DP. isManagerReady()494 public synchronized boolean isManagerReady() { 495 // Getting just the headset profile is fine for now. Will need to deal with A2DP 496 // and others if they aren't always in a ready state. 497 LocalBluetoothProfile profile = mHeadsetProfile; 498 if (profile != null) { 499 return profile.isProfileReady(); 500 } 501 profile = mA2dpProfile; 502 if (profile != null) { 503 return profile.isProfileReady(); 504 } 505 profile = mA2dpSinkProfile; 506 if (profile != null) { 507 return profile.isProfileReady(); 508 } 509 return false; 510 } 511 getA2dpProfile()512 public A2dpProfile getA2dpProfile() { 513 return mA2dpProfile; 514 } 515 getA2dpSinkProfile()516 public A2dpSinkProfile getA2dpSinkProfile() { 517 if ((mA2dpSinkProfile != null) && (mA2dpSinkProfile.isProfileReady())) { 518 return mA2dpSinkProfile; 519 } else { 520 return null; 521 } 522 } 523 getHeadsetProfile()524 public HeadsetProfile getHeadsetProfile() { 525 return mHeadsetProfile; 526 } 527 getHfpClientProfile()528 public HfpClientProfile getHfpClientProfile() { 529 if ((mHfpClientProfile != null) && (mHfpClientProfile.isProfileReady())) { 530 return mHfpClientProfile; 531 } else { 532 return null; 533 } 534 } 535 getPbapClientProfile()536 public PbapClientProfile getPbapClientProfile() { 537 return mPbapClientProfile; 538 } 539 getPbapProfile()540 public PbapServerProfile getPbapProfile(){ 541 return mPbapProfile; 542 } 543 getMapProfile()544 public MapProfile getMapProfile(){ 545 return mMapProfile; 546 } 547 getMapClientProfile()548 public MapClientProfile getMapClientProfile() { 549 return mMapClientProfile; 550 } 551 getHearingAidProfile()552 public HearingAidProfile getHearingAidProfile() { 553 return mHearingAidProfile; 554 } 555 getHapClientProfile()556 public HapClientProfile getHapClientProfile() { 557 return mHapClientProfile; 558 } 559 getLeAudioProfile()560 public LeAudioProfile getLeAudioProfile() { 561 return mLeAudioProfile; 562 } 563 getLeAudioBroadcastProfile()564 public LocalBluetoothLeBroadcast getLeAudioBroadcastProfile() { 565 return mLeAudioBroadcast; 566 } getLeAudioBroadcastAssistantProfile()567 public LocalBluetoothLeBroadcastAssistant getLeAudioBroadcastAssistantProfile() { 568 return mLeAudioBroadcastAssistant; 569 } 570 getSapProfile()571 SapProfile getSapProfile() { 572 return mSapProfile; 573 } 574 575 @VisibleForTesting getHidProfile()576 HidProfile getHidProfile() { 577 return mHidProfile; 578 } 579 580 @VisibleForTesting getHidDeviceProfile()581 HidDeviceProfile getHidDeviceProfile() { 582 return mHidDeviceProfile; 583 } 584 getCsipSetCoordinatorProfile()585 public CsipSetCoordinatorProfile getCsipSetCoordinatorProfile() { 586 return mCsipSetCoordinatorProfile; 587 } 588 getVolumeControlProfile()589 public VolumeControlProfile getVolumeControlProfile() { 590 return mVolumeControlProfile; 591 } 592 593 /** 594 * Fill in a list of LocalBluetoothProfile objects that are supported by 595 * the local device and the remote device. 596 * 597 * @param uuids of the remote device 598 * @param localUuids UUIDs of the local device 599 * @param profiles The list of profiles to fill 600 * @param removedProfiles list of profiles that were removed 601 */ updateProfiles(ParcelUuid[] uuids, ParcelUuid[] localUuids, Collection<LocalBluetoothProfile> profiles, Collection<LocalBluetoothProfile> removedProfiles, boolean isPanNapConnected, BluetoothDevice device)602 synchronized void updateProfiles(ParcelUuid[] uuids, ParcelUuid[] localUuids, 603 Collection<LocalBluetoothProfile> profiles, 604 Collection<LocalBluetoothProfile> removedProfiles, 605 boolean isPanNapConnected, BluetoothDevice device) { 606 // Copy previous profile list into removedProfiles 607 removedProfiles.clear(); 608 removedProfiles.addAll(profiles); 609 if (DEBUG) { 610 Log.d(TAG,"Current Profiles" + profiles.toString()); 611 } 612 profiles.clear(); 613 614 if (uuids == null) { 615 return; 616 } 617 618 // The profiles list's sequence will affect the bluetooth icon at 619 // BluetoothUtils.getBtClassDrawableWithDescription(Context,CachedBluetoothDevice). 620 621 // Moving the LE audio profile to be the first priority if the device supports LE audio. 622 if (ArrayUtils.contains(uuids, BluetoothUuid.LE_AUDIO) && mLeAudioProfile != null) { 623 profiles.add(mLeAudioProfile); 624 removedProfiles.remove(mLeAudioProfile); 625 } 626 627 if (mHeadsetProfile != null) { 628 if ((ArrayUtils.contains(localUuids, BluetoothUuid.HSP_AG) 629 && ArrayUtils.contains(uuids, BluetoothUuid.HSP)) 630 || (ArrayUtils.contains(localUuids, BluetoothUuid.HFP_AG) 631 && ArrayUtils.contains(uuids, BluetoothUuid.HFP))) { 632 profiles.add(mHeadsetProfile); 633 removedProfiles.remove(mHeadsetProfile); 634 } 635 } 636 637 if ((mHfpClientProfile != null) && 638 ArrayUtils.contains(uuids, BluetoothUuid.HFP_AG) 639 && ArrayUtils.contains(localUuids, BluetoothUuid.HFP)) { 640 profiles.add(mHfpClientProfile); 641 removedProfiles.remove(mHfpClientProfile); 642 } 643 644 if (BluetoothUuid.containsAnyUuid(uuids, A2dpProfile.SINK_UUIDS) && mA2dpProfile != null) { 645 profiles.add(mA2dpProfile); 646 removedProfiles.remove(mA2dpProfile); 647 } 648 649 if (BluetoothUuid.containsAnyUuid(uuids, A2dpSinkProfile.SRC_UUIDS) 650 && mA2dpSinkProfile != null) { 651 profiles.add(mA2dpSinkProfile); 652 removedProfiles.remove(mA2dpSinkProfile); 653 } 654 655 if (ArrayUtils.contains(uuids, BluetoothUuid.OBEX_OBJECT_PUSH) && mOppProfile != null) { 656 profiles.add(mOppProfile); 657 removedProfiles.remove(mOppProfile); 658 } 659 660 if ((ArrayUtils.contains(uuids, BluetoothUuid.HID) 661 || ArrayUtils.contains(uuids, BluetoothUuid.HOGP)) && mHidProfile != null) { 662 profiles.add(mHidProfile); 663 removedProfiles.remove(mHidProfile); 664 } 665 666 if (mHidDeviceProfile != null && mHidDeviceProfile.getConnectionStatus(device) 667 != BluetoothProfile.STATE_DISCONNECTED) { 668 profiles.add(mHidDeviceProfile); 669 removedProfiles.remove(mHidDeviceProfile); 670 } 671 672 if(isPanNapConnected) 673 if(DEBUG) Log.d(TAG, "Valid PAN-NAP connection exists."); 674 if ((ArrayUtils.contains(uuids, BluetoothUuid.NAP) && mPanProfile != null) 675 || isPanNapConnected) { 676 profiles.add(mPanProfile); 677 removedProfiles.remove(mPanProfile); 678 } 679 680 if ((mMapProfile != null) && 681 (mMapProfile.getConnectionStatus(device) == BluetoothProfile.STATE_CONNECTED)) { 682 profiles.add(mMapProfile); 683 removedProfiles.remove(mMapProfile); 684 mMapProfile.setEnabled(device, true); 685 } 686 687 if ((mPbapProfile != null) && 688 (mPbapProfile.getConnectionStatus(device) == BluetoothProfile.STATE_CONNECTED)) { 689 profiles.add(mPbapProfile); 690 removedProfiles.remove(mPbapProfile); 691 mPbapProfile.setEnabled(device, true); 692 } 693 694 if ((mMapClientProfile != null) 695 && BluetoothUuid.containsAnyUuid(uuids, MapClientProfile.UUIDS)) { 696 profiles.add(mMapClientProfile); 697 removedProfiles.remove(mMapClientProfile); 698 } 699 700 if ((mPbapClientProfile != null) 701 && BluetoothUuid.containsAnyUuid(uuids, PbapClientProfile.SRC_UUIDS)) { 702 profiles.add(mPbapClientProfile); 703 removedProfiles.remove(mPbapClientProfile); 704 } 705 706 if (ArrayUtils.contains(uuids, BluetoothUuid.HEARING_AID) && mHearingAidProfile != null) { 707 profiles.add(mHearingAidProfile); 708 removedProfiles.remove(mHearingAidProfile); 709 } 710 711 if (mHapClientProfile != null && ArrayUtils.contains(uuids, BluetoothUuid.HAS)) { 712 profiles.add(mHapClientProfile); 713 removedProfiles.remove(mHapClientProfile); 714 } 715 716 if (mSapProfile != null && ArrayUtils.contains(uuids, BluetoothUuid.SAP)) { 717 profiles.add(mSapProfile); 718 removedProfiles.remove(mSapProfile); 719 } 720 721 if (mVolumeControlProfile != null 722 && ArrayUtils.contains(uuids, BluetoothUuid.VOLUME_CONTROL)) { 723 profiles.add(mVolumeControlProfile); 724 removedProfiles.remove(mVolumeControlProfile); 725 } 726 727 if (mCsipSetCoordinatorProfile != null 728 && ArrayUtils.contains(uuids, BluetoothUuid.COORDINATED_SET)) { 729 profiles.add(mCsipSetCoordinatorProfile); 730 removedProfiles.remove(mCsipSetCoordinatorProfile); 731 } 732 733 if (DEBUG) { 734 Log.d(TAG,"New Profiles" + profiles.toString()); 735 } 736 } 737 } 738