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