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