• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 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.server.telecom.bluetooth;
18 
19 import android.bluetooth.BluetoothAdapter;
20 import android.bluetooth.BluetoothDevice;
21 import android.bluetooth.BluetoothHeadset;
22 import android.bluetooth.BluetoothHearingAid;
23 import android.bluetooth.BluetoothLeAudio;
24 import android.bluetooth.BluetoothLeAudioCodecStatus;
25 import android.bluetooth.BluetoothProfile;
26 import android.bluetooth.BluetoothStatusCodes;
27 import android.content.Context;
28 import android.media.AudioManager;
29 import android.media.AudioDeviceInfo;
30 import android.media.audio.common.AudioDevice;
31 import android.os.Bundle;
32 import android.telecom.Log;
33 import android.util.ArraySet;
34 import android.util.LocalLog;
35 
36 import com.android.internal.util.IndentingPrintWriter;
37 
38 import java.util.ArrayList;
39 import java.util.Collection;
40 import java.util.Collections;
41 import java.util.concurrent.Executor;
42 import java.util.LinkedHashMap;
43 import java.util.LinkedHashSet;
44 import java.util.LinkedList;
45 import java.util.List;
46 import java.util.Objects;
47 import java.util.Set;
48 
49 public class BluetoothDeviceManager {
50 
51     public static final int DEVICE_TYPE_HEADSET = 0;
52     public static final int DEVICE_TYPE_HEARING_AID = 1;
53     public static final int DEVICE_TYPE_LE_AUDIO = 2;
54 
55     private BluetoothLeAudio.Callback mLeAudioCallbacks =
56         new BluetoothLeAudio.Callback() {
57             @Override
58             public void onCodecConfigChanged(int groupId, BluetoothLeAudioCodecStatus status) {}
59             @Override
60             public void onGroupStatusChanged(int groupId, int groupStatus) {}
61             @Override
62             public void onGroupNodeAdded(BluetoothDevice device, int groupId) {
63                 Log.i(this, device.getAddress() + " group added " + groupId);
64                 if (device == null || groupId == BluetoothLeAudio.GROUP_ID_INVALID) {
65                     Log.w(this, "invalid parameter");
66                     return;
67                 }
68 
69                 synchronized (mLock) {
70                     mGroupsByDevice.put(device, groupId);
71                 }
72             }
73             @Override
74             public void onGroupNodeRemoved(BluetoothDevice device, int groupId) {
75                 if (device == null || groupId == BluetoothLeAudio.GROUP_ID_INVALID) {
76                     Log.w(this, "invalid parameter");
77                     return;
78                 }
79 
80                 synchronized (mLock) {
81                     mGroupsByDevice.remove(device);
82                 }
83             }
84         };
85 
86     private final BluetoothProfile.ServiceListener mBluetoothProfileServiceListener =
87             new BluetoothProfile.ServiceListener() {
88                 @Override
89                 public void onServiceConnected(int profile, BluetoothProfile proxy) {
90                     Log.startSession("BMSL.oSC");
91                     try {
92                         synchronized (mLock) {
93                             String logString;
94                             if (profile == BluetoothProfile.HEADSET) {
95                                 mBluetoothHeadset = (BluetoothHeadset) proxy;
96                                 logString = "Got BluetoothHeadset: " + mBluetoothHeadset;
97                             } else if (profile == BluetoothProfile.HEARING_AID) {
98                                 mBluetoothHearingAid = (BluetoothHearingAid) proxy;
99                                 logString = "Got BluetoothHearingAid: "
100                                         + mBluetoothHearingAid;
101                             } else if (profile == BluetoothProfile.LE_AUDIO) {
102                                 mBluetoothLeAudioService = (BluetoothLeAudio) proxy;
103                                 logString = "Got BluetoothLeAudio: "
104                                         + mBluetoothLeAudioService;
105                                 if (!mLeAudioCallbackRegistered) {
106                                     try {
107                                         mBluetoothLeAudioService.registerCallback(
108                                                     mExecutor, mLeAudioCallbacks);
109                                         mLeAudioCallbackRegistered = true;
110                                     } catch (IllegalStateException e) {
111                                         logString += ", but Bluetooth is down";
112                                     }
113                                 }
114                             } else {
115                                 logString = "Connected to non-requested bluetooth service." +
116                                         " Not changing bluetooth headset.";
117                             }
118                             Log.i(BluetoothDeviceManager.this, logString);
119                             mLocalLog.log(logString);
120                         }
121                     } finally {
122                         Log.endSession();
123                     }
124                 }
125 
126                 @Override
127                 public void onServiceDisconnected(int profile) {
128                     Log.startSession("BMSL.oSD");
129                     try {
130                         synchronized (mLock) {
131                             LinkedHashMap<String, BluetoothDevice> lostServiceDevices;
132                             String logString;
133                             if (profile == BluetoothProfile.HEADSET) {
134                                 mBluetoothHeadset = null;
135                                 lostServiceDevices = mHfpDevicesByAddress;
136                                 mBluetoothRouteManager.onActiveDeviceChanged(null,
137                                         DEVICE_TYPE_HEADSET);
138                                 logString = "Lost BluetoothHeadset service. " +
139                                         "Removing all tracked devices";
140                             } else if (profile == BluetoothProfile.HEARING_AID) {
141                                 mBluetoothHearingAid = null;
142                                 logString = "Lost BluetoothHearingAid service. " +
143                                         "Removing all tracked devices.";
144                                 lostServiceDevices = mHearingAidDevicesByAddress;
145                                 mBluetoothRouteManager.onActiveDeviceChanged(null,
146                                         DEVICE_TYPE_HEARING_AID);
147                             } else if (profile == BluetoothProfile.LE_AUDIO) {
148                                 mBluetoothLeAudioService = null;
149                                 logString = "Lost BluetoothLeAudio service. " +
150                                         "Removing all tracked devices.";
151                                 lostServiceDevices = mLeAudioDevicesByAddress;
152                                 mBluetoothRouteManager.onActiveDeviceChanged(null,
153                                         DEVICE_TYPE_LE_AUDIO);
154                             } else {
155                                 return;
156                             }
157                             Log.i(BluetoothDeviceManager.this, logString);
158                             mLocalLog.log(logString);
159 
160                             List<BluetoothDevice> devicesToRemove = new LinkedList<>(
161                                     lostServiceDevices.values());
162                             lostServiceDevices.clear();
163                             for (BluetoothDevice device : devicesToRemove) {
164                                 mBluetoothRouteManager.onDeviceLost(device.getAddress());
165                             }
166                         }
167                     } finally {
168                         Log.endSession();
169                     }
170                 }
171            };
172 
173     private final LinkedHashMap<String, BluetoothDevice> mHfpDevicesByAddress =
174             new LinkedHashMap<>();
175     private final LinkedHashMap<String, BluetoothDevice> mHearingAidDevicesByAddress =
176             new LinkedHashMap<>();
177     private final LinkedHashMap<BluetoothDevice, Long> mHearingAidDeviceSyncIds =
178             new LinkedHashMap<>();
179     private final LinkedHashMap<String, BluetoothDevice> mLeAudioDevicesByAddress =
180             new LinkedHashMap<>();
181     private final LinkedHashMap<BluetoothDevice, Integer> mGroupsByDevice =
182             new LinkedHashMap<>();
183     private int mGroupIdActive = BluetoothLeAudio.GROUP_ID_INVALID;
184     private int mGroupIdPending = BluetoothLeAudio.GROUP_ID_INVALID;
185     private final LocalLog mLocalLog = new LocalLog(20);
186 
187     // This lock only protects internal state -- it doesn't lock on anything going into Telecom.
188     private final Object mLock = new Object();
189 
190     private BluetoothRouteManager mBluetoothRouteManager;
191     private BluetoothHeadset mBluetoothHeadset;
192     private BluetoothHearingAid mBluetoothHearingAid;
193     private boolean mLeAudioCallbackRegistered = false;
194     private BluetoothLeAudio mBluetoothLeAudioService;
195     private boolean mLeAudioSetAsCommunicationDevice = false;
196     private String mLeAudioDevice;
197     private String mHearingAidDevice;
198     private boolean mHearingAidSetAsCommunicationDevice = false;
199     private BluetoothDevice mBluetoothHearingAidActiveDeviceCache;
200     private BluetoothAdapter mBluetoothAdapter;
201     private AudioManager mAudioManager;
202     private Executor mExecutor;
203 
BluetoothDeviceManager(Context context, BluetoothAdapter bluetoothAdapter)204     public BluetoothDeviceManager(Context context, BluetoothAdapter bluetoothAdapter) {
205         if (bluetoothAdapter != null) {
206             mBluetoothAdapter = bluetoothAdapter;
207             bluetoothAdapter.getProfileProxy(context, mBluetoothProfileServiceListener,
208                     BluetoothProfile.HEADSET);
209             bluetoothAdapter.getProfileProxy(context, mBluetoothProfileServiceListener,
210                     BluetoothProfile.HEARING_AID);
211             bluetoothAdapter.getProfileProxy(context, mBluetoothProfileServiceListener,
212                     BluetoothProfile.LE_AUDIO);
213             mAudioManager = context.getSystemService(AudioManager.class);
214             mExecutor = context.getMainExecutor();
215         }
216     }
217 
setBluetoothRouteManager(BluetoothRouteManager brm)218     public void setBluetoothRouteManager(BluetoothRouteManager brm) {
219         mBluetoothRouteManager = brm;
220     }
221 
getLeAudioConnectedDevices()222     private List<BluetoothDevice> getLeAudioConnectedDevices() {
223         synchronized (mLock) {
224             // Let's get devices which are a group leaders
225             ArrayList<BluetoothDevice> devices = new ArrayList<>();
226 
227             if (mGroupsByDevice.isEmpty() || mBluetoothLeAudioService == null) {
228                 return devices;
229             }
230 
231             for (LinkedHashMap.Entry<BluetoothDevice, Integer> entry : mGroupsByDevice.entrySet()) {
232                 if (Objects.equals(entry.getKey(),
233                         mBluetoothLeAudioService.getConnectedGroupLeadDevice(entry.getValue()))) {
234                     devices.add(entry.getKey());
235                 }
236             }
237             devices.removeIf(device -> !mLeAudioDevicesByAddress.containsValue(device));
238             return devices;
239         }
240     }
241 
getNumConnectedDevices()242     public int getNumConnectedDevices() {
243         return getConnectedDevices().size();
244     }
245 
getConnectedDevices()246     public Collection<BluetoothDevice> getConnectedDevices() {
247         synchronized (mLock) {
248             ArraySet<BluetoothDevice> result = new ArraySet<>();
249 
250             // Set storing the group ids of all dual mode audio devices to de-dupe them
251             Set<Integer> dualModeGroupIds = new ArraySet<>();
252             for (BluetoothDevice hfpDevice: mHfpDevicesByAddress.values()) {
253                 result.add(hfpDevice);
254                 if (mBluetoothLeAudioService == null) {
255                     continue;
256                 }
257                 int groupId = mBluetoothLeAudioService.getGroupId(hfpDevice);
258                 if (groupId != BluetoothLeAudio.GROUP_ID_INVALID) {
259                     dualModeGroupIds.add(groupId);
260                 }
261             }
262 
263             result.addAll(mHearingAidDevicesByAddress.values());
264             if (mBluetoothLeAudioService == null) {
265                 return Collections.unmodifiableCollection(result);
266             }
267             for (BluetoothDevice leAudioDevice: getLeAudioConnectedDevices()) {
268                 // Exclude dual mode audio devices included from the HFP devices list
269                 int groupId = mBluetoothLeAudioService.getGroupId(leAudioDevice);
270                 if (groupId != BluetoothLeAudio.GROUP_ID_INVALID
271                         && !dualModeGroupIds.contains(groupId)) {
272                     result.add(leAudioDevice);
273                 }
274             }
275             return Collections.unmodifiableCollection(result);
276         }
277     }
278 
279     // Same as getConnectedDevices except it filters out the hearing aid devices that are linked
280     // together by their hiSyncId.
getUniqueConnectedDevices()281     public Collection<BluetoothDevice> getUniqueConnectedDevices() {
282         ArraySet<BluetoothDevice> result;
283         synchronized (mLock) {
284             result = new ArraySet<>(mHfpDevicesByAddress.values());
285         }
286         Set<Long> seenHiSyncIds = new LinkedHashSet<>();
287         // Add the left-most active device to the seen list so that we match up with the list
288         // generated in BluetoothRouteManager.
289         if (mBluetoothAdapter != null) {
290             for (BluetoothDevice device : mBluetoothAdapter.getActiveDevices(
291                         BluetoothProfile.HEARING_AID)) {
292                 if (device != null) {
293                     result.add(device);
294                     seenHiSyncIds.add(mHearingAidDeviceSyncIds.getOrDefault(device, -1L));
295                     break;
296                 }
297             }
298         }
299         synchronized (mLock) {
300             for (BluetoothDevice d : mHearingAidDevicesByAddress.values()) {
301                 long hiSyncId = mHearingAidDeviceSyncIds.getOrDefault(d, -1L);
302                 if (seenHiSyncIds.contains(hiSyncId)) {
303                     continue;
304                 }
305                 result.add(d);
306                 seenHiSyncIds.add(hiSyncId);
307             }
308         }
309 
310         if (mBluetoothLeAudioService != null) {
311             result.addAll(getLeAudioConnectedDevices());
312         }
313 
314         return Collections.unmodifiableCollection(result);
315     }
316 
getBluetoothHeadset()317     public BluetoothHeadset getBluetoothHeadset() {
318         return mBluetoothHeadset;
319     }
320 
getBluetoothAdapter()321     public BluetoothAdapter getBluetoothAdapter() {
322         return mBluetoothAdapter;
323     }
324 
getBluetoothHearingAid()325     public BluetoothHearingAid getBluetoothHearingAid() {
326         return mBluetoothHearingAid;
327     }
328 
getLeAudioService()329     public BluetoothLeAudio getLeAudioService() {
330         return mBluetoothLeAudioService;
331     }
332 
setHeadsetServiceForTesting(BluetoothHeadset bluetoothHeadset)333     public void setHeadsetServiceForTesting(BluetoothHeadset bluetoothHeadset) {
334         mBluetoothHeadset = bluetoothHeadset;
335     }
336 
setHearingAidServiceForTesting(BluetoothHearingAid bluetoothHearingAid)337     public void setHearingAidServiceForTesting(BluetoothHearingAid bluetoothHearingAid) {
338         mBluetoothHearingAid = bluetoothHearingAid;
339     }
340 
setLeAudioServiceForTesting(BluetoothLeAudio bluetoothLeAudio)341     public void setLeAudioServiceForTesting(BluetoothLeAudio bluetoothLeAudio) {
342         mBluetoothLeAudioService = bluetoothLeAudio;
343         mBluetoothLeAudioService.registerCallback(mExecutor, mLeAudioCallbacks);
344     }
345 
getDeviceTypeString(int deviceType)346     public static String getDeviceTypeString(int deviceType) {
347         switch (deviceType) {
348             case DEVICE_TYPE_LE_AUDIO:
349                 return "LeAudio";
350             case DEVICE_TYPE_HEARING_AID:
351                 return "HearingAid";
352             case DEVICE_TYPE_HEADSET:
353                 return "HFP";
354             default:
355                 return "unknown type";
356         }
357     }
358 
onDeviceConnected(BluetoothDevice device, int deviceType)359     void onDeviceConnected(BluetoothDevice device, int deviceType) {
360         synchronized (mLock) {
361             LinkedHashMap<String, BluetoothDevice> targetDeviceMap;
362             if (deviceType == DEVICE_TYPE_LE_AUDIO) {
363                 if (mBluetoothLeAudioService == null) {
364                     Log.w(this, "LE audio service null when receiving device added broadcast");
365                     return;
366                 }
367                 /* Check if group is known. */
368                 if (!mGroupsByDevice.containsKey(device)) {
369                     int groupId = mBluetoothLeAudioService.getGroupId(device);
370                     /* If it is not yet assigned, then it will be provided in the callback */
371                     if (groupId != BluetoothLeAudio.GROUP_ID_INVALID) {
372                         mGroupsByDevice.put(device, groupId);
373                     }
374                 }
375                 targetDeviceMap = mLeAudioDevicesByAddress;
376             } else if (deviceType == DEVICE_TYPE_HEARING_AID) {
377                 if (mBluetoothHearingAid == null) {
378                     Log.w(this, "Hearing aid service null when receiving device added broadcast");
379                     return;
380                 }
381                 long hiSyncId = mBluetoothHearingAid.getHiSyncId(device);
382                 mHearingAidDeviceSyncIds.put(device, hiSyncId);
383                 targetDeviceMap = mHearingAidDevicesByAddress;
384             } else if (deviceType == DEVICE_TYPE_HEADSET) {
385                 if (mBluetoothHeadset == null) {
386                     Log.w(this, "Headset service null when receiving device added broadcast");
387                     return;
388                 }
389                 targetDeviceMap = mHfpDevicesByAddress;
390             } else {
391                 Log.w(this, "Device: " + device.getAddress() + " with invalid type: "
392                             + getDeviceTypeString(deviceType));
393                 return;
394             }
395             if (!targetDeviceMap.containsKey(device.getAddress())) {
396                 Log.i(this, "Adding device with address: " + device + " and devicetype="
397                         + getDeviceTypeString(deviceType));
398                 targetDeviceMap.put(device.getAddress(), device);
399                 mBluetoothRouteManager.onDeviceAdded(device.getAddress());
400             }
401         }
402     }
403 
onDeviceDisconnected(BluetoothDevice device, int deviceType)404     void onDeviceDisconnected(BluetoothDevice device, int deviceType) {
405         mLocalLog.log("Device disconnected -- address: " + device.getAddress() + " deviceType: "
406                 + deviceType);
407         synchronized (mLock) {
408             LinkedHashMap<String, BluetoothDevice> targetDeviceMap;
409             if (deviceType == DEVICE_TYPE_LE_AUDIO) {
410                 targetDeviceMap = mLeAudioDevicesByAddress;
411             } else if (deviceType == DEVICE_TYPE_HEARING_AID) {
412                 mHearingAidDeviceSyncIds.remove(device);
413                 targetDeviceMap = mHearingAidDevicesByAddress;
414             } else if (deviceType == DEVICE_TYPE_HEADSET) {
415                 targetDeviceMap = mHfpDevicesByAddress;
416             } else {
417                 Log.w(this, "Device: " + device.getAddress() + " with invalid type: "
418                             + getDeviceTypeString(deviceType));
419                 return;
420             }
421             if (targetDeviceMap.containsKey(device.getAddress())) {
422                 Log.i(this, "Removing device with address: " + device + " and devicetype="
423                         + getDeviceTypeString(deviceType));
424                 targetDeviceMap.remove(device.getAddress());
425                 mBluetoothRouteManager.onDeviceLost(device.getAddress());
426             }
427         }
428     }
429 
disconnectAudio()430     public void disconnectAudio() {
431         disconnectSco();
432         clearLeAudioCommunicationDevice();
433         clearHearingAidCommunicationDevice();
434     }
435 
disconnectSco()436     public void disconnectSco() {
437         if (mBluetoothHeadset == null) {
438             Log.w(this, "Trying to disconnect audio but no headset service exists.");
439         } else {
440             mBluetoothHeadset.disconnectAudio();
441         }
442     }
443 
isLeAudioCommunicationDevice()444     public boolean isLeAudioCommunicationDevice() {
445         return mLeAudioSetAsCommunicationDevice;
446     }
447 
isHearingAidSetAsCommunicationDevice()448     public boolean isHearingAidSetAsCommunicationDevice() {
449         return mHearingAidSetAsCommunicationDevice;
450     }
451 
clearLeAudioCommunicationDevice()452     public void clearLeAudioCommunicationDevice() {
453         Log.i(this, "clearLeAudioCommunicationDevice: mLeAudioSetAsCommunicationDevice = " +
454                 mLeAudioSetAsCommunicationDevice + " device = " + mLeAudioDevice);
455         if (!mLeAudioSetAsCommunicationDevice) {
456             return;
457         }
458         mLeAudioSetAsCommunicationDevice = false;
459         if (mLeAudioDevice != null) {
460             mBluetoothRouteManager.onAudioLost(mLeAudioDevice);
461             mLeAudioDevice = null;
462         }
463 
464         if (mAudioManager == null) {
465             Log.i(this, "clearLeAudioCommunicationDevice: mAudioManager is null");
466             return;
467         }
468 
469         AudioDeviceInfo audioDeviceInfo = mAudioManager.getCommunicationDevice();
470         if (audioDeviceInfo != null && audioDeviceInfo.getType()
471                 == AudioDeviceInfo.TYPE_BLE_HEADSET) {
472             mBluetoothRouteManager.onAudioLost(audioDeviceInfo.getAddress());
473             mAudioManager.clearCommunicationDevice();
474         }
475     }
476 
clearHearingAidCommunicationDevice()477     public void clearHearingAidCommunicationDevice() {
478         Log.i(this, "clearHearingAidCommunicationDevice: mHearingAidSetAsCommunicationDevice = "
479                 + mHearingAidSetAsCommunicationDevice);
480         if (!mHearingAidSetAsCommunicationDevice) {
481             return;
482         }
483         mHearingAidSetAsCommunicationDevice = false;
484         if (mHearingAidDevice != null) {
485             mBluetoothRouteManager.onAudioLost(mHearingAidDevice);
486             mHearingAidDevice = null;
487         }
488 
489         if (mAudioManager == null) {
490             Log.i(this, "clearHearingAidCommunicationDevice: mAudioManager is null");
491             return;
492         }
493 
494         AudioDeviceInfo audioDeviceInfo = mAudioManager.getCommunicationDevice();
495         if (audioDeviceInfo != null && audioDeviceInfo.getType()
496                 == AudioDeviceInfo.TYPE_HEARING_AID) {
497             mAudioManager.clearCommunicationDevice();
498         }
499     }
500 
setLeAudioCommunicationDevice()501     public boolean setLeAudioCommunicationDevice() {
502         Log.i(this, "setLeAudioCommunicationDevice");
503 
504         if (mLeAudioSetAsCommunicationDevice) {
505             Log.i(this, "setLeAudioCommunicationDevice already set");
506             return true;
507         }
508 
509         if (mAudioManager == null) {
510             Log.w(this, " mAudioManager is null");
511             return false;
512         }
513 
514         AudioDeviceInfo bleHeadset = null;
515         List<AudioDeviceInfo> devices = mAudioManager.getAvailableCommunicationDevices();
516         if (devices.size() == 0) {
517             Log.w(this, " No communication devices available.");
518             return false;
519         }
520 
521         for (AudioDeviceInfo device : devices) {
522             Log.i(this, " Available device type:  " + device.getType());
523             if (device.getType() == AudioDeviceInfo.TYPE_BLE_HEADSET) {
524                 bleHeadset = device;
525                 break;
526             }
527         }
528 
529         if (bleHeadset == null) {
530             Log.w(this, " No bleHeadset device available");
531             return false;
532         }
533 
534         // clear hearing aid communication device if set
535         clearHearingAidCommunicationDevice();
536 
537         // Turn BLE_OUT_HEADSET ON.
538         boolean result = mAudioManager.setCommunicationDevice(bleHeadset);
539         if (!result) {
540             Log.w(this, " Could not set bleHeadset device");
541         } else {
542             Log.i(this, " bleHeadset device set");
543             mBluetoothRouteManager.onAudioOn(bleHeadset.getAddress());
544             mLeAudioSetAsCommunicationDevice = true;
545             mLeAudioDevice = bleHeadset.getAddress();
546         }
547         return result;
548     }
549 
setHearingAidCommunicationDevice()550     public boolean setHearingAidCommunicationDevice() {
551         Log.i(this, "setHearingAidCommunicationDevice");
552 
553         if (mHearingAidSetAsCommunicationDevice) {
554             Log.i(this, "mHearingAidSetAsCommunicationDevice already set");
555             return true;
556         }
557 
558         if (mAudioManager == null) {
559             Log.w(this, " mAudioManager is null");
560             return false;
561         }
562 
563         AudioDeviceInfo hearingAid = null;
564         List<AudioDeviceInfo> devices = mAudioManager.getAvailableCommunicationDevices();
565         if (devices.size() == 0) {
566             Log.w(this, " No communication devices available.");
567             return false;
568         }
569 
570         for (AudioDeviceInfo device : devices) {
571             Log.i(this, " Available device type:  " + device.getType());
572             if (device.getType() == AudioDeviceInfo.TYPE_HEARING_AID) {
573                 hearingAid = device;
574                 break;
575             }
576         }
577 
578         if (hearingAid == null) {
579             Log.w(this, " No hearingAid device available");
580             return false;
581         }
582 
583         // clear LE audio communication device if set
584         clearLeAudioCommunicationDevice();
585 
586         // Turn hearing aid ON.
587         boolean result = mAudioManager.setCommunicationDevice(hearingAid);
588         if (!result) {
589             Log.w(this, " Could not set hearingAid device");
590         } else {
591             Log.i(this, " hearingAid device set");
592             mHearingAidDevice = hearingAid.getAddress();
593             mHearingAidSetAsCommunicationDevice = true;
594         }
595         return result;
596     }
597 
598     // Connect audio to the bluetooth device at address, checking to see whether it's
599     // le audio, hearing aid or a HFP device, and using the proper BT API.
connectAudio(String address, boolean switchingBtDevices)600     public boolean connectAudio(String address, boolean switchingBtDevices) {
601         int callProfile = BluetoothProfile.LE_AUDIO;
602         Log.i(this, "Telecomm connecting audio to device: " + address);
603         BluetoothDevice device = null;
604         if (mLeAudioDevicesByAddress.containsKey(address)) {
605             Log.i(this, "Telecomm found LE Audio device for address: " + address);
606             if (mBluetoothLeAudioService == null) {
607                 Log.w(this, "Attempting to turn on audio when the le audio service is null");
608                 return false;
609             }
610             device = mLeAudioDevicesByAddress.get(address);
611             callProfile = BluetoothProfile.LE_AUDIO;
612         } else if (mHearingAidDevicesByAddress.containsKey(address)) {
613             Log.i(this, "Telecomm found hearing aid device for address: " + address);
614             if (mBluetoothHearingAid == null) {
615                 Log.w(this, "Attempting to turn on audio when the hearing aid service is null");
616                 return false;
617             }
618             device = mHearingAidDevicesByAddress.get(address);
619             callProfile = BluetoothProfile.HEARING_AID;
620         } else if (mHfpDevicesByAddress.containsKey(address)) {
621             Log.i(this, "Telecomm found HFP device for address: " + address);
622             if (mBluetoothHeadset == null) {
623                 Log.w(this, "Attempting to turn on audio when the headset service is null");
624                 return false;
625             }
626             device = mHfpDevicesByAddress.get(address);
627             callProfile = BluetoothProfile.HEADSET;
628         }
629 
630         if (device == null) {
631             Log.w(this, "No active profiles for Bluetooth address=" + address);
632             return false;
633         }
634 
635         Bundle preferredAudioProfiles = mBluetoothAdapter.getPreferredAudioProfiles(device);
636         if (preferredAudioProfiles != null && !preferredAudioProfiles.isEmpty()
637             && preferredAudioProfiles.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX) != 0) {
638             Log.i(this, "Preferred duplex profile for device=" + address + " is "
639                 + preferredAudioProfiles.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX));
640             callProfile = preferredAudioProfiles.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX);
641         }
642 
643         if (callProfile == BluetoothProfile.LE_AUDIO) {
644             if (mBluetoothAdapter.setActiveDevice(
645                     device, BluetoothAdapter.ACTIVE_DEVICE_ALL)) {
646                 /* ACTION_ACTIVE_DEVICE_CHANGED intent will trigger setting communication device.
647                  * Only after receiving ACTION_ACTIVE_DEVICE_CHANGED it is known that device that
648                  * will be audio switched to is available to be choose as communication device */
649                 if (!switchingBtDevices) {
650                     return setLeAudioCommunicationDevice();
651                 }
652                 return true;
653             }
654             return false;
655         } else if (callProfile == BluetoothProfile.HEARING_AID) {
656             if (mBluetoothAdapter.setActiveDevice(device, BluetoothAdapter.ACTIVE_DEVICE_ALL)) {
657                 /* ACTION_ACTIVE_DEVICE_CHANGED intent will trigger setting communication device.
658                  * Only after receiving ACTION_ACTIVE_DEVICE_CHANGED it is known that device that
659                  * will be audio switched to is available to be choose as communication device */
660                 if (!switchingBtDevices) {
661                     return setHearingAidCommunicationDevice();
662                 }
663                 return true;
664             }
665             return false;
666         } else if (callProfile == BluetoothProfile.HEADSET) {
667             boolean success = mBluetoothAdapter.setActiveDevice(device,
668                 BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL);
669             if (!success) {
670                 Log.w(this, "Couldn't set active device to %s", address);
671                 return false;
672             }
673             int scoConnectionRequest = mBluetoothHeadset.connectAudio();
674             return scoConnectionRequest == BluetoothStatusCodes.SUCCESS ||
675                 scoConnectionRequest == BluetoothStatusCodes.ERROR_AUDIO_DEVICE_ALREADY_CONNECTED;
676         } else {
677             Log.w(this, "Attempting to turn on audio for a disconnected device");
678             return false;
679         }
680     }
681 
cacheHearingAidDevice()682     public void cacheHearingAidDevice() {
683         if (mBluetoothAdapter != null) {
684             for (BluetoothDevice device : mBluetoothAdapter.getActiveDevices(
685                         BluetoothProfile.HEARING_AID)) {
686                 if (device != null) {
687                     mBluetoothHearingAidActiveDeviceCache = device;
688                 }
689             }
690         }
691     }
692 
restoreHearingAidDevice()693     public void restoreHearingAidDevice() {
694         if (mBluetoothHearingAidActiveDeviceCache != null) {
695             mBluetoothAdapter.setActiveDevice(mBluetoothHearingAidActiveDeviceCache,
696                     BluetoothAdapter.ACTIVE_DEVICE_ALL);
697             mBluetoothHearingAidActiveDeviceCache = null;
698         }
699     }
700 
isInbandRingingEnabled()701     public boolean isInbandRingingEnabled() {
702         BluetoothDevice activeDevice = mBluetoothRouteManager.getBluetoothAudioConnectedDevice();
703         Log.i(this, "isInbandRingingEnabled: activeDevice: " + activeDevice);
704         if (mBluetoothRouteManager.isCachedLeAudioDevice(activeDevice)) {
705             if (mBluetoothLeAudioService == null) {
706                 Log.i(this, "isInbandRingingEnabled: no leaudio service available.");
707                 return false;
708             }
709             int groupId = mBluetoothLeAudioService.getGroupId(activeDevice);
710             return mBluetoothLeAudioService.isInbandRingtoneEnabled(groupId);
711         } else {
712             if (mBluetoothHeadset == null) {
713                 Log.i(this, "isInbandRingingEnabled: no headset service available.");
714                 return false;
715             }
716             return mBluetoothHeadset.isInbandRingingEnabled();
717         }
718     }
719 
dump(IndentingPrintWriter pw)720     public void dump(IndentingPrintWriter pw) {
721         mLocalLog.dump(pw);
722     }
723 }
724