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