• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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