| /* |
| * Copyright (C) 2011 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.settingslib.bluetooth; |
| |
| import android.bluetooth.BluetoothA2dp; |
| import android.bluetooth.BluetoothA2dpSink; |
| import android.bluetooth.BluetoothAdapter; |
| import android.bluetooth.BluetoothCsipSetCoordinator; |
| import android.bluetooth.BluetoothDevice; |
| import android.bluetooth.BluetoothHapClient; |
| import android.bluetooth.BluetoothHeadset; |
| import android.bluetooth.BluetoothHeadsetClient; |
| import android.bluetooth.BluetoothHearingAid; |
| import android.bluetooth.BluetoothHidDevice; |
| import android.bluetooth.BluetoothHidHost; |
| import android.bluetooth.BluetoothLeAudio; |
| import android.bluetooth.BluetoothLeBroadcastAssistant; |
| import android.bluetooth.BluetoothMap; |
| import android.bluetooth.BluetoothMapClient; |
| import android.bluetooth.BluetoothPan; |
| import android.bluetooth.BluetoothPbap; |
| import android.bluetooth.BluetoothPbapClient; |
| import android.bluetooth.BluetoothProfile; |
| import android.bluetooth.BluetoothSap; |
| import android.bluetooth.BluetoothUuid; |
| import android.bluetooth.BluetoothVolumeControl; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.os.ParcelUuid; |
| import android.util.Log; |
| |
| import androidx.annotation.VisibleForTesting; |
| |
| import com.android.internal.util.ArrayUtils; |
| import com.android.internal.util.CollectionUtils; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.concurrent.CopyOnWriteArrayList; |
| |
| |
| /** |
| * LocalBluetoothProfileManager provides access to the LocalBluetoothProfile |
| * objects for the available Bluetooth profiles. |
| */ |
| public class LocalBluetoothProfileManager { |
| private static final String TAG = "LocalBluetoothProfileManager"; |
| private static final boolean DEBUG = BluetoothUtils.D; |
| |
| /** |
| * An interface for notifying BluetoothHeadset IPC clients when they have |
| * been connected to the BluetoothHeadset service. |
| * Only used by com.android.settings.bluetooth.DockService. |
| */ |
| public interface ServiceListener { |
| /** |
| * Called to notify the client when this proxy object has been |
| * connected to the BluetoothHeadset service. Clients must wait for |
| * this callback before making IPC calls on the BluetoothHeadset |
| * service. |
| */ |
| void onServiceConnected(); |
| |
| /** |
| * Called to notify the client that this proxy object has been |
| * disconnected from the BluetoothHeadset service. Clients must not |
| * make IPC calls on the BluetoothHeadset service after this callback. |
| * This callback will currently only occur if the application hosting |
| * the BluetoothHeadset service, but may be called more often in future. |
| */ |
| void onServiceDisconnected(); |
| } |
| |
| private final Context mContext; |
| private final CachedBluetoothDeviceManager mDeviceManager; |
| private final BluetoothEventManager mEventManager; |
| |
| private A2dpProfile mA2dpProfile; |
| private A2dpSinkProfile mA2dpSinkProfile; |
| private HeadsetProfile mHeadsetProfile; |
| private HfpClientProfile mHfpClientProfile; |
| private MapProfile mMapProfile; |
| private MapClientProfile mMapClientProfile; |
| private HidProfile mHidProfile; |
| private HidDeviceProfile mHidDeviceProfile; |
| private OppProfile mOppProfile; |
| private PanProfile mPanProfile; |
| private PbapClientProfile mPbapClientProfile; |
| private PbapServerProfile mPbapProfile; |
| private HearingAidProfile mHearingAidProfile; |
| private HapClientProfile mHapClientProfile; |
| private CsipSetCoordinatorProfile mCsipSetCoordinatorProfile; |
| private LeAudioProfile mLeAudioProfile; |
| private LocalBluetoothLeBroadcast mLeAudioBroadcast; |
| private LocalBluetoothLeBroadcastAssistant mLeAudioBroadcastAssistant; |
| private SapProfile mSapProfile; |
| private VolumeControlProfile mVolumeControlProfile; |
| |
| /** |
| * Mapping from profile name, e.g. "HEADSET" to profile object. |
| */ |
| private final Map<String, LocalBluetoothProfile> |
| mProfileNameMap = new HashMap<String, LocalBluetoothProfile>(); |
| |
| LocalBluetoothProfileManager(Context context, |
| LocalBluetoothAdapter adapter, |
| CachedBluetoothDeviceManager deviceManager, |
| BluetoothEventManager eventManager) { |
| mContext = context; |
| |
| mDeviceManager = deviceManager; |
| mEventManager = eventManager; |
| // pass this reference to adapter and event manager (circular dependency) |
| adapter.setProfileManager(this); |
| |
| if (DEBUG) Log.d(TAG, "LocalBluetoothProfileManager construction complete"); |
| } |
| |
| /** |
| * create profile instance according to bluetooth supported profile list |
| */ |
| void updateLocalProfiles() { |
| List<Integer> supportedList = BluetoothAdapter.getDefaultAdapter().getSupportedProfiles(); |
| if (CollectionUtils.isEmpty(supportedList)) { |
| if (DEBUG) Log.d(TAG, "supportedList is null"); |
| return; |
| } |
| if (mA2dpProfile == null && supportedList.contains(BluetoothProfile.A2DP)) { |
| if (DEBUG) Log.d(TAG, "Adding local A2DP profile"); |
| mA2dpProfile = new A2dpProfile(mContext, mDeviceManager, this); |
| addProfile(mA2dpProfile, A2dpProfile.NAME, |
| BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED); |
| } |
| if (mA2dpSinkProfile == null && supportedList.contains(BluetoothProfile.A2DP_SINK)) { |
| if (DEBUG) Log.d(TAG, "Adding local A2DP SINK profile"); |
| mA2dpSinkProfile = new A2dpSinkProfile(mContext, mDeviceManager, this); |
| addProfile(mA2dpSinkProfile, A2dpSinkProfile.NAME, |
| BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED); |
| } |
| if (mHeadsetProfile == null && supportedList.contains(BluetoothProfile.HEADSET)) { |
| if (DEBUG) Log.d(TAG, "Adding local HEADSET profile"); |
| mHeadsetProfile = new HeadsetProfile(mContext, mDeviceManager, this); |
| addHeadsetProfile(mHeadsetProfile, HeadsetProfile.NAME, |
| BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED, |
| BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED, |
| BluetoothHeadset.STATE_AUDIO_DISCONNECTED); |
| } |
| if (mHfpClientProfile == null && supportedList.contains(BluetoothProfile.HEADSET_CLIENT)) { |
| if (DEBUG) Log.d(TAG, "Adding local HfpClient profile"); |
| mHfpClientProfile = new HfpClientProfile(mContext, mDeviceManager, this); |
| addProfile(mHfpClientProfile, HfpClientProfile.NAME, |
| BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED); |
| } |
| if (mMapClientProfile == null && supportedList.contains(BluetoothProfile.MAP_CLIENT)) { |
| if (DEBUG) Log.d(TAG, "Adding local MAP CLIENT profile"); |
| mMapClientProfile = new MapClientProfile(mContext, mDeviceManager,this); |
| addProfile(mMapClientProfile, MapClientProfile.NAME, |
| BluetoothMapClient.ACTION_CONNECTION_STATE_CHANGED); |
| } |
| if (mMapProfile == null && supportedList.contains(BluetoothProfile.MAP)) { |
| if (DEBUG) Log.d(TAG, "Adding local MAP profile"); |
| mMapProfile = new MapProfile(mContext, mDeviceManager, this); |
| addProfile(mMapProfile, MapProfile.NAME, BluetoothMap.ACTION_CONNECTION_STATE_CHANGED); |
| } |
| if (mOppProfile == null && supportedList.contains(BluetoothProfile.OPP)) { |
| if (DEBUG) Log.d(TAG, "Adding local OPP profile"); |
| mOppProfile = new OppProfile(); |
| // Note: no event handler for OPP, only name map. |
| mProfileNameMap.put(OppProfile.NAME, mOppProfile); |
| } |
| if (mHearingAidProfile == null && supportedList.contains(BluetoothProfile.HEARING_AID)) { |
| if (DEBUG) Log.d(TAG, "Adding local Hearing Aid profile"); |
| mHearingAidProfile = new HearingAidProfile(mContext, mDeviceManager, |
| this); |
| addProfile(mHearingAidProfile, HearingAidProfile.NAME, |
| BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED); |
| } |
| if (mHapClientProfile == null && supportedList.contains(BluetoothProfile.HAP_CLIENT)) { |
| if (DEBUG) Log.d(TAG, "Adding local HAP_CLIENT profile"); |
| mHapClientProfile = new HapClientProfile(mContext, mDeviceManager, this); |
| addProfile(mHapClientProfile, HapClientProfile.NAME, |
| BluetoothHapClient.ACTION_HAP_CONNECTION_STATE_CHANGED); |
| } |
| if (mHidProfile == null && supportedList.contains(BluetoothProfile.HID_HOST)) { |
| if (DEBUG) Log.d(TAG, "Adding local HID_HOST profile"); |
| mHidProfile = new HidProfile(mContext, mDeviceManager, this); |
| addProfile(mHidProfile, HidProfile.NAME, |
| BluetoothHidHost.ACTION_CONNECTION_STATE_CHANGED); |
| } |
| if (mHidDeviceProfile == null && supportedList.contains(BluetoothProfile.HID_DEVICE)) { |
| if (DEBUG) Log.d(TAG, "Adding local HID_DEVICE profile"); |
| mHidDeviceProfile = new HidDeviceProfile(mContext, mDeviceManager, this); |
| addProfile(mHidDeviceProfile, HidDeviceProfile.NAME, |
| BluetoothHidDevice.ACTION_CONNECTION_STATE_CHANGED); |
| } |
| if (mPanProfile == null && supportedList.contains(BluetoothProfile.PAN)) { |
| if (DEBUG) Log.d(TAG, "Adding local PAN profile"); |
| mPanProfile = new PanProfile(mContext); |
| addPanProfile(mPanProfile, PanProfile.NAME, |
| BluetoothPan.ACTION_CONNECTION_STATE_CHANGED); |
| } |
| if (mPbapProfile == null && supportedList.contains(BluetoothProfile.PBAP)) { |
| if (DEBUG) Log.d(TAG, "Adding local PBAP profile"); |
| mPbapProfile = new PbapServerProfile(mContext); |
| addProfile(mPbapProfile, PbapServerProfile.NAME, |
| BluetoothPbap.ACTION_CONNECTION_STATE_CHANGED); |
| } |
| if (mPbapClientProfile == null && supportedList.contains(BluetoothProfile.PBAP_CLIENT)) { |
| if (DEBUG) Log.d(TAG, "Adding local PBAP Client profile"); |
| mPbapClientProfile = new PbapClientProfile(mContext, mDeviceManager,this); |
| addProfile(mPbapClientProfile, PbapClientProfile.NAME, |
| BluetoothPbapClient.ACTION_CONNECTION_STATE_CHANGED); |
| } |
| if (mSapProfile == null && supportedList.contains(BluetoothProfile.SAP)) { |
| if (DEBUG) { |
| Log.d(TAG, "Adding local SAP profile"); |
| } |
| mSapProfile = new SapProfile(mContext, mDeviceManager, this); |
| addProfile(mSapProfile, SapProfile.NAME, BluetoothSap.ACTION_CONNECTION_STATE_CHANGED); |
| } |
| if (mVolumeControlProfile == null |
| && supportedList.contains(BluetoothProfile.VOLUME_CONTROL)) { |
| if (DEBUG) { |
| Log.d(TAG, "Adding local Volume Control profile"); |
| } |
| mVolumeControlProfile = new VolumeControlProfile(mContext, mDeviceManager, this); |
| addProfile(mVolumeControlProfile, VolumeControlProfile.NAME, |
| BluetoothVolumeControl.ACTION_CONNECTION_STATE_CHANGED); |
| } |
| if (mLeAudioProfile == null && supportedList.contains(BluetoothProfile.LE_AUDIO)) { |
| if (DEBUG) { |
| Log.d(TAG, "Adding local LE_AUDIO profile"); |
| } |
| mLeAudioProfile = new LeAudioProfile(mContext, mDeviceManager, this); |
| addProfile(mLeAudioProfile, LeAudioProfile.NAME, |
| BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED); |
| } |
| if (mLeAudioBroadcast == null |
| && supportedList.contains(BluetoothProfile.LE_AUDIO_BROADCAST)) { |
| if (DEBUG) { |
| Log.d(TAG, "Adding local LE_AUDIO_BROADCAST profile"); |
| } |
| mLeAudioBroadcast = new LocalBluetoothLeBroadcast(mContext); |
| // no event handler for the LE boradcast. |
| mProfileNameMap.put(LocalBluetoothLeBroadcast.NAME, mLeAudioBroadcast); |
| } |
| if (mLeAudioBroadcastAssistant == null |
| && supportedList.contains(BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT)) { |
| if (DEBUG) { |
| Log.d(TAG, "Adding local LE_AUDIO_BROADCAST_ASSISTANT profile"); |
| } |
| mLeAudioBroadcastAssistant = new LocalBluetoothLeBroadcastAssistant(mContext, |
| mDeviceManager, this); |
| addProfile(mLeAudioBroadcastAssistant, LocalBluetoothLeBroadcast.NAME, |
| BluetoothLeBroadcastAssistant.ACTION_CONNECTION_STATE_CHANGED); |
| } |
| if (mCsipSetCoordinatorProfile == null |
| && supportedList.contains(BluetoothProfile.CSIP_SET_COORDINATOR)) { |
| if (DEBUG) { |
| Log.d(TAG, "Adding local CSIP set coordinator profile"); |
| } |
| mCsipSetCoordinatorProfile = |
| new CsipSetCoordinatorProfile(mContext, mDeviceManager, this); |
| addProfile(mCsipSetCoordinatorProfile, mCsipSetCoordinatorProfile.NAME, |
| BluetoothCsipSetCoordinator.ACTION_CSIS_CONNECTION_STATE_CHANGED); |
| } |
| mEventManager.registerProfileIntentReceiver(); |
| } |
| |
| private void addHeadsetProfile(LocalBluetoothProfile profile, String profileName, |
| String stateChangedAction, String audioStateChangedAction, int audioDisconnectedState) { |
| BluetoothEventManager.Handler handler = new HeadsetStateChangeHandler( |
| profile, audioStateChangedAction, audioDisconnectedState); |
| mEventManager.addProfileHandler(stateChangedAction, handler); |
| mEventManager.addProfileHandler(audioStateChangedAction, handler); |
| mProfileNameMap.put(profileName, profile); |
| } |
| |
| private final Collection<ServiceListener> mServiceListeners = |
| new CopyOnWriteArrayList<ServiceListener>(); |
| |
| private void addProfile(LocalBluetoothProfile profile, |
| String profileName, String stateChangedAction) { |
| mEventManager.addProfileHandler(stateChangedAction, new StateChangedHandler(profile)); |
| mProfileNameMap.put(profileName, profile); |
| } |
| |
| private void addPanProfile(LocalBluetoothProfile profile, |
| String profileName, String stateChangedAction) { |
| mEventManager.addProfileHandler(stateChangedAction, |
| new PanStateChangedHandler(profile)); |
| mProfileNameMap.put(profileName, profile); |
| } |
| |
| public LocalBluetoothProfile getProfileByName(String name) { |
| return mProfileNameMap.get(name); |
| } |
| |
| // Called from LocalBluetoothAdapter when state changes to ON |
| void setBluetoothStateOn() { |
| updateLocalProfiles(); |
| mEventManager.readPairedDevices(); |
| } |
| |
| /** |
| * Generic handler for connection state change events for the specified profile. |
| */ |
| private class StateChangedHandler implements BluetoothEventManager.Handler { |
| final LocalBluetoothProfile mProfile; |
| |
| StateChangedHandler(LocalBluetoothProfile profile) { |
| mProfile = profile; |
| } |
| |
| public void onReceive(Context context, Intent intent, BluetoothDevice device) { |
| CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device); |
| if (cachedDevice == null) { |
| Log.w(TAG, "StateChangedHandler found new device: " + device); |
| cachedDevice = mDeviceManager.addDevice(device); |
| } |
| onReceiveInternal(intent, cachedDevice); |
| } |
| |
| protected void onReceiveInternal(Intent intent, CachedBluetoothDevice cachedDevice) { |
| int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0); |
| int oldState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, 0); |
| if (newState == BluetoothProfile.STATE_DISCONNECTED && |
| oldState == BluetoothProfile.STATE_CONNECTING) { |
| Log.i(TAG, "Failed to connect " + mProfile + " device"); |
| } |
| |
| if (getHearingAidProfile() != null |
| && mProfile instanceof HearingAidProfile |
| && (newState == BluetoothProfile.STATE_CONNECTED)) { |
| |
| // Check if the HiSyncID has being initialized |
| if (cachedDevice.getHiSyncId() == BluetoothHearingAid.HI_SYNC_ID_INVALID) { |
| long newHiSyncId = getHearingAidProfile().getHiSyncId(cachedDevice.getDevice()); |
| if (newHiSyncId != BluetoothHearingAid.HI_SYNC_ID_INVALID) { |
| final BluetoothDevice device = cachedDevice.getDevice(); |
| final HearingAidInfo.Builder infoBuilder = new HearingAidInfo.Builder() |
| .setAshaDeviceSide(getHearingAidProfile().getDeviceSide(device)) |
| .setAshaDeviceMode(getHearingAidProfile().getDeviceMode(device)) |
| .setHiSyncId(newHiSyncId); |
| cachedDevice.setHearingAidInfo(infoBuilder.build()); |
| } |
| } |
| |
| HearingAidStatsLogUtils.logHearingAidInfo(cachedDevice); |
| } |
| |
| final boolean isHapClientProfile = getHapClientProfile() != null |
| && mProfile instanceof HapClientProfile; |
| final boolean isLeAudioProfile = getLeAudioProfile() != null |
| && mProfile instanceof LeAudioProfile; |
| final boolean isHapClientOrLeAudioProfile = isHapClientProfile || isLeAudioProfile; |
| if (isHapClientOrLeAudioProfile && newState == BluetoothProfile.STATE_CONNECTED) { |
| |
| // Checks if both profiles are connected to the device. Hearing aid info need |
| // to be retrieved from these profiles separately. |
| if (cachedDevice.isConnectedLeAudioHearingAidDevice()) { |
| final BluetoothDevice device = cachedDevice.getDevice(); |
| final HearingAidInfo.Builder infoBuilder = new HearingAidInfo.Builder() |
| .setLeAudioLocation(getLeAudioProfile().getAudioLocation(device)) |
| .setHapDeviceType(getHapClientProfile().getHearingAidType(device)); |
| cachedDevice.setHearingAidInfo(infoBuilder.build()); |
| HearingAidStatsLogUtils.logHearingAidInfo(cachedDevice); |
| } |
| } |
| |
| if (getCsipSetCoordinatorProfile() != null |
| && mProfile instanceof CsipSetCoordinatorProfile |
| && newState == BluetoothProfile.STATE_CONNECTED) { |
| // Check if the GroupID has being initialized |
| if (cachedDevice.getGroupId() == BluetoothCsipSetCoordinator.GROUP_ID_INVALID) { |
| final Map<Integer, ParcelUuid> groupIdMap = getCsipSetCoordinatorProfile() |
| .getGroupUuidMapByDevice(cachedDevice.getDevice()); |
| if (groupIdMap != null) { |
| for (Map.Entry<Integer, ParcelUuid> entry: groupIdMap.entrySet()) { |
| if (entry.getValue().equals(BluetoothUuid.CAP)) { |
| cachedDevice.setGroupId(entry.getKey()); |
| break; |
| } |
| } |
| } |
| } |
| } |
| |
| cachedDevice.onProfileStateChanged(mProfile, newState); |
| // Dispatch profile changed after device update |
| boolean needDispatchProfileConnectionState = true; |
| if (cachedDevice.getHiSyncId() != BluetoothHearingAid.HI_SYNC_ID_INVALID |
| || cachedDevice.getGroupId() != BluetoothCsipSetCoordinator.GROUP_ID_INVALID) { |
| needDispatchProfileConnectionState = !mDeviceManager |
| .onProfileConnectionStateChangedIfProcessed(cachedDevice, newState, |
| mProfile.getProfileId()); |
| } |
| if (needDispatchProfileConnectionState) { |
| cachedDevice.refresh(); |
| mEventManager.dispatchProfileConnectionStateChanged(cachedDevice, newState, |
| mProfile.getProfileId()); |
| } |
| } |
| } |
| |
| /** Connectivity and audio state change handler for headset profiles. */ |
| private class HeadsetStateChangeHandler extends StateChangedHandler { |
| private final String mAudioChangeAction; |
| private final int mAudioDisconnectedState; |
| |
| HeadsetStateChangeHandler(LocalBluetoothProfile profile, String audioChangeAction, |
| int audioDisconnectedState) { |
| super(profile); |
| mAudioChangeAction = audioChangeAction; |
| mAudioDisconnectedState = audioDisconnectedState; |
| } |
| |
| @Override |
| public void onReceiveInternal(Intent intent, CachedBluetoothDevice cachedDevice) { |
| if (mAudioChangeAction.equals(intent.getAction())) { |
| int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0); |
| if (newState != mAudioDisconnectedState) { |
| cachedDevice.onProfileStateChanged(mProfile, BluetoothProfile.STATE_CONNECTED); |
| } |
| cachedDevice.refresh(); |
| } else { |
| super.onReceiveInternal(intent, cachedDevice); |
| } |
| } |
| } |
| |
| /** State change handler for NAP and PANU profiles. */ |
| private class PanStateChangedHandler extends StateChangedHandler { |
| |
| PanStateChangedHandler(LocalBluetoothProfile profile) { |
| super(profile); |
| } |
| |
| @Override |
| public void onReceive(Context context, Intent intent, BluetoothDevice device) { |
| PanProfile panProfile = (PanProfile) mProfile; |
| int role = intent.getIntExtra(BluetoothPan.EXTRA_LOCAL_ROLE, 0); |
| panProfile.setLocalRole(device, role); |
| super.onReceive(context, intent, device); |
| } |
| } |
| |
| // called from DockService |
| public void addServiceListener(ServiceListener l) { |
| mServiceListeners.add(l); |
| } |
| |
| // called from DockService |
| public void removeServiceListener(ServiceListener l) { |
| mServiceListeners.remove(l); |
| } |
| |
| // not synchronized: use only from UI thread! (TODO: verify) |
| void callServiceConnectedListeners() { |
| final Collection<ServiceListener> listeners = new ArrayList<>(mServiceListeners); |
| |
| for (ServiceListener l : listeners) { |
| l.onServiceConnected(); |
| } |
| } |
| |
| // not synchronized: use only from UI thread! (TODO: verify) |
| void callServiceDisconnectedListeners() { |
| final Collection<ServiceListener> listeners = new ArrayList<>(mServiceListeners); |
| |
| for (ServiceListener listener : listeners) { |
| listener.onServiceDisconnected(); |
| } |
| } |
| |
| // This is called by DockService, so check Headset and A2DP. |
| public synchronized boolean isManagerReady() { |
| // Getting just the headset profile is fine for now. Will need to deal with A2DP |
| // and others if they aren't always in a ready state. |
| LocalBluetoothProfile profile = mHeadsetProfile; |
| if (profile != null) { |
| return profile.isProfileReady(); |
| } |
| profile = mA2dpProfile; |
| if (profile != null) { |
| return profile.isProfileReady(); |
| } |
| profile = mA2dpSinkProfile; |
| if (profile != null) { |
| return profile.isProfileReady(); |
| } |
| return false; |
| } |
| |
| public A2dpProfile getA2dpProfile() { |
| return mA2dpProfile; |
| } |
| |
| public A2dpSinkProfile getA2dpSinkProfile() { |
| if ((mA2dpSinkProfile != null) && (mA2dpSinkProfile.isProfileReady())) { |
| return mA2dpSinkProfile; |
| } else { |
| return null; |
| } |
| } |
| |
| public HeadsetProfile getHeadsetProfile() { |
| return mHeadsetProfile; |
| } |
| |
| public HfpClientProfile getHfpClientProfile() { |
| if ((mHfpClientProfile != null) && (mHfpClientProfile.isProfileReady())) { |
| return mHfpClientProfile; |
| } else { |
| return null; |
| } |
| } |
| |
| public PbapClientProfile getPbapClientProfile() { |
| return mPbapClientProfile; |
| } |
| |
| public PbapServerProfile getPbapProfile(){ |
| return mPbapProfile; |
| } |
| |
| public MapProfile getMapProfile(){ |
| return mMapProfile; |
| } |
| |
| public MapClientProfile getMapClientProfile() { |
| return mMapClientProfile; |
| } |
| |
| public HearingAidProfile getHearingAidProfile() { |
| return mHearingAidProfile; |
| } |
| |
| public HapClientProfile getHapClientProfile() { |
| return mHapClientProfile; |
| } |
| |
| public LeAudioProfile getLeAudioProfile() { |
| return mLeAudioProfile; |
| } |
| |
| public LocalBluetoothLeBroadcast getLeAudioBroadcastProfile() { |
| return mLeAudioBroadcast; |
| } |
| public LocalBluetoothLeBroadcastAssistant getLeAudioBroadcastAssistantProfile() { |
| return mLeAudioBroadcastAssistant; |
| } |
| |
| SapProfile getSapProfile() { |
| return mSapProfile; |
| } |
| |
| @VisibleForTesting |
| HidProfile getHidProfile() { |
| return mHidProfile; |
| } |
| |
| @VisibleForTesting |
| HidDeviceProfile getHidDeviceProfile() { |
| return mHidDeviceProfile; |
| } |
| |
| public CsipSetCoordinatorProfile getCsipSetCoordinatorProfile() { |
| return mCsipSetCoordinatorProfile; |
| } |
| |
| public VolumeControlProfile getVolumeControlProfile() { |
| return mVolumeControlProfile; |
| } |
| |
| /** |
| * Fill in a list of LocalBluetoothProfile objects that are supported by |
| * the local device and the remote device. |
| * |
| * @param uuids of the remote device |
| * @param localUuids UUIDs of the local device |
| * @param profiles The list of profiles to fill |
| * @param removedProfiles list of profiles that were removed |
| */ |
| synchronized void updateProfiles(ParcelUuid[] uuids, ParcelUuid[] localUuids, |
| Collection<LocalBluetoothProfile> profiles, |
| Collection<LocalBluetoothProfile> removedProfiles, |
| boolean isPanNapConnected, BluetoothDevice device) { |
| // Copy previous profile list into removedProfiles |
| removedProfiles.clear(); |
| removedProfiles.addAll(profiles); |
| if (DEBUG) { |
| Log.d(TAG,"Current Profiles" + profiles.toString()); |
| } |
| profiles.clear(); |
| |
| if (uuids == null) { |
| return; |
| } |
| |
| // The profiles list's sequence will affect the bluetooth icon at |
| // BluetoothUtils.getBtClassDrawableWithDescription(Context,CachedBluetoothDevice). |
| |
| // Moving the LE audio profile to be the first priority if the device supports LE audio. |
| if (ArrayUtils.contains(uuids, BluetoothUuid.LE_AUDIO) && mLeAudioProfile != null) { |
| profiles.add(mLeAudioProfile); |
| removedProfiles.remove(mLeAudioProfile); |
| } |
| |
| if (mHeadsetProfile != null) { |
| if ((ArrayUtils.contains(localUuids, BluetoothUuid.HSP_AG) |
| && ArrayUtils.contains(uuids, BluetoothUuid.HSP)) |
| || (ArrayUtils.contains(localUuids, BluetoothUuid.HFP_AG) |
| && ArrayUtils.contains(uuids, BluetoothUuid.HFP))) { |
| profiles.add(mHeadsetProfile); |
| removedProfiles.remove(mHeadsetProfile); |
| } |
| } |
| |
| if ((mHfpClientProfile != null) && |
| ArrayUtils.contains(uuids, BluetoothUuid.HFP_AG) |
| && ArrayUtils.contains(localUuids, BluetoothUuid.HFP)) { |
| profiles.add(mHfpClientProfile); |
| removedProfiles.remove(mHfpClientProfile); |
| } |
| |
| if (BluetoothUuid.containsAnyUuid(uuids, A2dpProfile.SINK_UUIDS) && mA2dpProfile != null) { |
| profiles.add(mA2dpProfile); |
| removedProfiles.remove(mA2dpProfile); |
| } |
| |
| if (BluetoothUuid.containsAnyUuid(uuids, A2dpSinkProfile.SRC_UUIDS) |
| && mA2dpSinkProfile != null) { |
| profiles.add(mA2dpSinkProfile); |
| removedProfiles.remove(mA2dpSinkProfile); |
| } |
| |
| if (ArrayUtils.contains(uuids, BluetoothUuid.OBEX_OBJECT_PUSH) && mOppProfile != null) { |
| profiles.add(mOppProfile); |
| removedProfiles.remove(mOppProfile); |
| } |
| |
| if ((ArrayUtils.contains(uuids, BluetoothUuid.HID) |
| || ArrayUtils.contains(uuids, BluetoothUuid.HOGP)) && mHidProfile != null) { |
| profiles.add(mHidProfile); |
| removedProfiles.remove(mHidProfile); |
| } |
| |
| if (mHidDeviceProfile != null && mHidDeviceProfile.getConnectionStatus(device) |
| != BluetoothProfile.STATE_DISCONNECTED) { |
| profiles.add(mHidDeviceProfile); |
| removedProfiles.remove(mHidDeviceProfile); |
| } |
| |
| if(isPanNapConnected) |
| if(DEBUG) Log.d(TAG, "Valid PAN-NAP connection exists."); |
| if ((ArrayUtils.contains(uuids, BluetoothUuid.NAP) && mPanProfile != null) |
| || isPanNapConnected) { |
| profiles.add(mPanProfile); |
| removedProfiles.remove(mPanProfile); |
| } |
| |
| if ((mMapProfile != null) && |
| (mMapProfile.getConnectionStatus(device) == BluetoothProfile.STATE_CONNECTED)) { |
| profiles.add(mMapProfile); |
| removedProfiles.remove(mMapProfile); |
| mMapProfile.setEnabled(device, true); |
| } |
| |
| if ((mPbapProfile != null) && |
| (mPbapProfile.getConnectionStatus(device) == BluetoothProfile.STATE_CONNECTED)) { |
| profiles.add(mPbapProfile); |
| removedProfiles.remove(mPbapProfile); |
| mPbapProfile.setEnabled(device, true); |
| } |
| |
| if ((mMapClientProfile != null) |
| && BluetoothUuid.containsAnyUuid(uuids, MapClientProfile.UUIDS)) { |
| profiles.add(mMapClientProfile); |
| removedProfiles.remove(mMapClientProfile); |
| } |
| |
| if ((mPbapClientProfile != null) |
| && BluetoothUuid.containsAnyUuid(uuids, PbapClientProfile.SRC_UUIDS)) { |
| profiles.add(mPbapClientProfile); |
| removedProfiles.remove(mPbapClientProfile); |
| } |
| |
| if (ArrayUtils.contains(uuids, BluetoothUuid.HEARING_AID) && mHearingAidProfile != null) { |
| profiles.add(mHearingAidProfile); |
| removedProfiles.remove(mHearingAidProfile); |
| } |
| |
| if (mHapClientProfile != null && ArrayUtils.contains(uuids, BluetoothUuid.HAS)) { |
| profiles.add(mHapClientProfile); |
| removedProfiles.remove(mHapClientProfile); |
| } |
| |
| if (mSapProfile != null && ArrayUtils.contains(uuids, BluetoothUuid.SAP)) { |
| profiles.add(mSapProfile); |
| removedProfiles.remove(mSapProfile); |
| } |
| |
| if (mVolumeControlProfile != null |
| && ArrayUtils.contains(uuids, BluetoothUuid.VOLUME_CONTROL)) { |
| profiles.add(mVolumeControlProfile); |
| removedProfiles.remove(mVolumeControlProfile); |
| } |
| |
| if (mCsipSetCoordinatorProfile != null |
| && ArrayUtils.contains(uuids, BluetoothUuid.COORDINATED_SET)) { |
| profiles.add(mCsipSetCoordinatorProfile); |
| removedProfiles.remove(mCsipSetCoordinatorProfile); |
| } |
| |
| if (DEBUG) { |
| Log.d(TAG,"New Profiles" + profiles.toString()); |
| } |
| } |
| } |