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 static com.android.server.telecom.AudioRoute.TYPE_BLUETOOTH_HA;
20 import static com.android.server.telecom.AudioRoute.TYPE_BLUETOOTH_SCO;
21 import static com.android.server.telecom.CallAudioRouteAdapter.BT_DEVICE_REMOVED;
22 import static com.android.server.telecom.CallAudioRouteAdapter.SWITCH_BASELINE_ROUTE;
23 import static com.android.server.telecom.CallAudioRouteController.INCLUDE_BLUETOOTH_IN_BASELINE;
24 
25 import android.bluetooth.BluetoothAdapter;
26 import android.bluetooth.BluetoothDevice;
27 import android.bluetooth.BluetoothHeadset;
28 import android.bluetooth.BluetoothHearingAid;
29 import android.bluetooth.BluetoothLeAudio;
30 import android.bluetooth.BluetoothLeAudioCodecStatus;
31 import android.bluetooth.BluetoothProfile;
32 import android.bluetooth.BluetoothStatusCodes;
33 import android.content.Context;
34 import android.media.AudioDeviceInfo;
35 import android.media.AudioManager;
36 import android.os.Bundle;
37 import android.telecom.Log;
38 import android.util.ArraySet;
39 import android.util.LocalLog;
40 import android.util.Pair;
41 
42 import com.android.internal.annotations.VisibleForTesting;
43 import com.android.internal.util.IndentingPrintWriter;
44 import com.android.server.telecom.AudioRoute;
45 import com.android.server.telecom.CallAudioCommunicationDeviceTracker;
46 import com.android.server.telecom.CallAudioRouteAdapter;
47 import com.android.server.telecom.CallAudioRouteController;
48 import com.android.server.telecom.flags.FeatureFlags;
49 
50 import java.util.ArrayList;
51 import java.util.Collection;
52 import java.util.Collections;
53 import java.util.HashMap;
54 import java.util.LinkedHashMap;
55 import java.util.LinkedHashSet;
56 import java.util.LinkedList;
57 import java.util.List;
58 import java.util.Map;
59 import java.util.Objects;
60 import java.util.Set;
61 import java.util.concurrent.CompletableFuture;
62 import java.util.concurrent.ExecutionException;
63 import java.util.concurrent.Executor;
64 import java.util.concurrent.TimeUnit;
65 import java.util.concurrent.TimeoutException;
66 
67 public class BluetoothDeviceManager {
68 
69     public static final int DEVICE_TYPE_HEADSET = 0;
70     public static final int DEVICE_TYPE_HEARING_AID = 1;
71     public static final int DEVICE_TYPE_LE_AUDIO = 2;
72 
73     private static final Map<Integer, Integer> PROFILE_TO_AUDIO_ROUTE_MAP = new HashMap<>();
74     static {
PROFILE_TO_AUDIO_ROUTE_MAP.put(BluetoothProfile.HEADSET, AudioRoute.TYPE_BLUETOOTH_SCO)75         PROFILE_TO_AUDIO_ROUTE_MAP.put(BluetoothProfile.HEADSET,
76                 AudioRoute.TYPE_BLUETOOTH_SCO);
PROFILE_TO_AUDIO_ROUTE_MAP.put(BluetoothProfile.LE_AUDIO, AudioRoute.TYPE_BLUETOOTH_LE)77         PROFILE_TO_AUDIO_ROUTE_MAP.put(BluetoothProfile.LE_AUDIO,
78                 AudioRoute.TYPE_BLUETOOTH_LE);
PROFILE_TO_AUDIO_ROUTE_MAP.put(BluetoothProfile.HEARING_AID, TYPE_BLUETOOTH_HA)79         PROFILE_TO_AUDIO_ROUTE_MAP.put(BluetoothProfile.HEARING_AID,
80                 TYPE_BLUETOOTH_HA);
81     }
82 
83     private BluetoothLeAudio.Callback mLeAudioCallbacks =
84         new BluetoothLeAudio.Callback() {
85             @Override
86             public void onCodecConfigChanged(int groupId, BluetoothLeAudioCodecStatus status) {}
87             @Override
88             public void onGroupStatusChanged(int groupId, int groupStatus) {}
89             @Override
90             public void onGroupNodeAdded(BluetoothDevice device, int groupId) {
91                 Log.i(this, (device == null ? "device is null" : device.getAddress())
92                         + " group added " + groupId);
93                 if (device == null || groupId == BluetoothLeAudio.GROUP_ID_INVALID) {
94                     Log.w(this, "invalid parameter");
95                     return;
96                 }
97 
98                 synchronized (mLock) {
99                     mGroupsByDevice.put(device, groupId);
100                 }
101             }
102             @Override
103             public void onGroupNodeRemoved(BluetoothDevice device, int groupId) {
104                 Log.i(this, (device == null ? "device is null" : device.getAddress())
105                         + " group removed " + groupId);
106                 if (device == null || groupId == BluetoothLeAudio.GROUP_ID_INVALID) {
107                     Log.w(this, "invalid parameter");
108                     return;
109                 }
110 
111                 synchronized (mLock) {
112                     mGroupsByDevice.remove(device);
113                 }
114             }
115         };
116 
117     private final BluetoothProfile.ServiceListener mBluetoothProfileServiceListener =
118             new BluetoothProfile.ServiceListener() {
119                 @Override
120                 public void onServiceConnected(int profile, BluetoothProfile proxy) {
121                     Log.startSession("BPSL.oSC");
122                     try {
123                         synchronized (mLock) {
124                             String logString;
125                             if (profile == BluetoothProfile.HEADSET) {
126                                 if (mFeatureFlags.useRefactoredAudioRouteSwitching()) {
127                                     mBluetoothHeadsetFuture.complete((BluetoothHeadset) proxy);
128                                 }
129                                 mBluetoothHeadset = (BluetoothHeadset) proxy;
130                                 logString = "Got BluetoothHeadset: " + mBluetoothHeadset;
131                             } else if (profile == BluetoothProfile.HEARING_AID) {
132                                 mBluetoothHearingAid = (BluetoothHearingAid) proxy;
133                                 logString = "Got BluetoothHearingAid: "
134                                         + mBluetoothHearingAid;
135                             } else if (profile == BluetoothProfile.LE_AUDIO) {
136                                 mBluetoothLeAudioService = (BluetoothLeAudio) proxy;
137                                 logString = ("Got BluetoothLeAudio: " + mBluetoothLeAudioService )
138                                         + (", mLeAudioCallbackRegistered: "
139                                         + mLeAudioCallbackRegistered);
140                                 if (!mLeAudioCallbackRegistered) {
141                                     if (mFeatureFlags.postponeRegisterToLeaudio()) {
142                                         mExecutor.execute(this::registerToLeAudio);
143                                     } else {
144                                         registerToLeAudio();
145                                     }
146                                 }
147                             } else {
148                                 logString = "Connected to non-requested bluetooth service." +
149                                         " Not changing bluetooth headset.";
150                             }
151                             Log.i(BluetoothDeviceManager.this, logString);
152                             mLocalLog.log(logString);
153                         }
154                     } finally {
155                         Log.endSession();
156                     }
157                 }
158 
159                 private void registerToLeAudio() {
160                     synchronized (mLock) {
161                         String logString = "Register to leAudio";
162 
163                         if (mLeAudioCallbackRegistered) {
164                             logString +=  ", but already registered";
165                             Log.i(BluetoothDeviceManager.this, logString);
166                             mLocalLog.log(logString);
167                             return;
168                         }
169                         try {
170                             mLeAudioCallbackRegistered = true;
171                             mBluetoothLeAudioService.registerCallback(
172                                             mExecutor, mLeAudioCallbacks);
173                         } catch (IllegalStateException e) {
174                             mLeAudioCallbackRegistered = false;
175                             logString += ", but failed: " + e;
176                         }
177                         Log.i(BluetoothDeviceManager.this, logString);
178                         mLocalLog.log(logString);
179                     }
180                 }
181 
182                 @Override
183                 public void onServiceDisconnected(int profile) {
184                     Log.startSession("BPSL.oSD");
185                     try {
186                         synchronized (mLock) {
187                             LinkedHashMap<String, BluetoothDevice> lostServiceDevices;
188                             String logString;
189                             if (profile == BluetoothProfile.HEADSET) {
190                                 if (mFeatureFlags.useRefactoredAudioRouteSwitching()) {
191                                     mBluetoothHeadsetFuture.complete(null);
192                                 }
193                                 mBluetoothHeadset = null;
194                                 lostServiceDevices = mHfpDevicesByAddress;
195                                 mBluetoothRouteManager.onActiveDeviceChanged(null,
196                                         DEVICE_TYPE_HEADSET);
197                                 logString = "Lost BluetoothHeadset service. " +
198                                         "Removing all tracked devices";
199                             } else if (profile == BluetoothProfile.HEARING_AID) {
200                                 mBluetoothHearingAid = null;
201                                 logString = "Lost BluetoothHearingAid service. " +
202                                         "Removing all tracked devices.";
203                                 lostServiceDevices = mHearingAidDevicesByAddress;
204                                 mBluetoothRouteManager.onActiveDeviceChanged(null,
205                                         DEVICE_TYPE_HEARING_AID);
206                             } else if (profile == BluetoothProfile.LE_AUDIO) {
207                                 mBluetoothLeAudioService = null;
208                                 logString = "Lost BluetoothLeAudio service. " +
209                                         "Removing all tracked devices.";
210                                 lostServiceDevices = mLeAudioDevicesByAddress;
211                                 mBluetoothRouteManager.onActiveDeviceChanged(null,
212                                         DEVICE_TYPE_LE_AUDIO);
213                             } else {
214                                 return;
215                             }
216                             Log.i(BluetoothDeviceManager.this, logString);
217                             mLocalLog.log(logString);
218 
219                             if (mFeatureFlags.useRefactoredAudioRouteSwitching()) {
220                                 handleAudioRefactoringServiceDisconnected(profile);
221                             } else {
222                                 List<BluetoothDevice> devicesToRemove = new LinkedList<>(
223                                         lostServiceDevices.values());
224                                 lostServiceDevices.clear();
225                                 for (BluetoothDevice device : devicesToRemove) {
226                                     mBluetoothRouteManager.onDeviceLost(device.getAddress());
227                                 }
228                             }
229                         }
230                     } finally {
231                         Log.endSession();
232                     }
233                 }
234            };
235 
handleAudioRefactoringServiceDisconnected(int profile)236     private void handleAudioRefactoringServiceDisconnected(int profile) {
237         CallAudioRouteController controller = (CallAudioRouteController)
238                 mCallAudioRouteAdapter;
239         Map<AudioRoute, BluetoothDevice> btRoutes = controller
240                 .getBluetoothRoutes();
241         List<Pair<AudioRoute, BluetoothDevice>> btRoutesToRemove =
242                 new ArrayList<>();
243         for (AudioRoute route: btRoutes.keySet()) {
244             if (route.getType() != PROFILE_TO_AUDIO_ROUTE_MAP.get(profile)) {
245                 continue;
246             }
247             BluetoothDevice device = btRoutes.get(route);
248             // Prevent concurrent modification exception by just iterating through keys instead of
249             // simultaneously removing them.
250             btRoutesToRemove.add(new Pair<>(route, device));
251         }
252 
253         for (Pair<AudioRoute, BluetoothDevice> routeToRemove:
254                 btRoutesToRemove) {
255             AudioRoute route = routeToRemove.first;
256             BluetoothDevice device = routeToRemove.second;
257             mCallAudioRouteAdapter.sendMessageWithSessionInfo(
258                     BT_DEVICE_REMOVED, route.getType(), device);
259         }
260         mCallAudioRouteAdapter.sendMessageWithSessionInfo(
261                 SWITCH_BASELINE_ROUTE, INCLUDE_BLUETOOTH_IN_BASELINE, (String) null);
262     }
263 
264     private final LinkedHashMap<String, BluetoothDevice> mHfpDevicesByAddress =
265             new LinkedHashMap<>();
266     private final LinkedHashMap<String, BluetoothDevice> mHearingAidDevicesByAddress =
267             new LinkedHashMap<>();
268     private final LinkedHashMap<BluetoothDevice, Long> mHearingAidDeviceSyncIds =
269             new LinkedHashMap<>();
270     private final LinkedHashMap<String, BluetoothDevice> mLeAudioDevicesByAddress =
271             new LinkedHashMap<>();
272     private final LinkedHashMap<BluetoothDevice, Integer> mGroupsByDevice =
273             new LinkedHashMap<>();
274     private final ArrayList<LinkedHashMap<String, BluetoothDevice>>
275             mDevicesByAddressMaps = new ArrayList<LinkedHashMap<String, BluetoothDevice>>(); {
276         mDevicesByAddressMaps.add(mHfpDevicesByAddress);
277         mDevicesByAddressMaps.add(mHearingAidDevicesByAddress);
278         mDevicesByAddressMaps.add(mLeAudioDevicesByAddress);
279     }
280     private int mGroupIdActive = BluetoothLeAudio.GROUP_ID_INVALID;
281     private int mGroupIdPending = BluetoothLeAudio.GROUP_ID_INVALID;
282     private final LocalLog mLocalLog = new LocalLog(20);
283 
284     // This lock only protects internal state -- it doesn't lock on anything going into Telecom.
285     private final Object mLock = new Object();
286 
287     private BluetoothRouteManager mBluetoothRouteManager;
288     private BluetoothHeadset mBluetoothHeadset;
289     private CompletableFuture<BluetoothHeadset> mBluetoothHeadsetFuture;
290     private BluetoothHearingAid mBluetoothHearingAid;
291     private boolean mLeAudioCallbackRegistered = false;
292     private BluetoothLeAudio mBluetoothLeAudioService;
293     private boolean mLeAudioSetAsCommunicationDevice = false;
294     private String mLeAudioDevice;
295     private String mHearingAidDevice;
296     private boolean mHearingAidSetAsCommunicationDevice = false;
297     private BluetoothDevice mBluetoothHearingAidActiveDeviceCache;
298     private BluetoothAdapter mBluetoothAdapter;
299     private AudioManager mAudioManager;
300     private Executor mExecutor;
301     private CallAudioCommunicationDeviceTracker mCommunicationDeviceTracker;
302     private CallAudioRouteAdapter mCallAudioRouteAdapter;
303     private FeatureFlags mFeatureFlags;
304 
BluetoothDeviceManager(Context context, BluetoothAdapter bluetoothAdapter, CallAudioCommunicationDeviceTracker communicationDeviceTracker, FeatureFlags featureFlags)305     public BluetoothDeviceManager(Context context, BluetoothAdapter bluetoothAdapter,
306             CallAudioCommunicationDeviceTracker communicationDeviceTracker,
307             FeatureFlags featureFlags) {
308         mFeatureFlags = featureFlags;
309         if (bluetoothAdapter != null) {
310             mBluetoothAdapter = bluetoothAdapter;
311             if (mFeatureFlags.useRefactoredAudioRouteSwitching()) {
312                 mBluetoothHeadsetFuture = new CompletableFuture<>();
313             }
314             bluetoothAdapter.getProfileProxy(context, mBluetoothProfileServiceListener,
315                     BluetoothProfile.HEADSET);
316             bluetoothAdapter.getProfileProxy(context, mBluetoothProfileServiceListener,
317                     BluetoothProfile.HEARING_AID);
318             bluetoothAdapter.getProfileProxy(context, mBluetoothProfileServiceListener,
319                     BluetoothProfile.LE_AUDIO);
320             mAudioManager = context.getSystemService(AudioManager.class);
321             mExecutor = context.getMainExecutor();
322             mCommunicationDeviceTracker = communicationDeviceTracker;
323         }
324     }
325 
setBluetoothRouteManager(BluetoothRouteManager brm)326     public void setBluetoothRouteManager(BluetoothRouteManager brm) {
327         mBluetoothRouteManager = brm;
328     }
329 
getLeAudioConnectedDevices()330     private List<BluetoothDevice> getLeAudioConnectedDevices() {
331         synchronized (mLock) {
332             // Let's get devices which are a group leaders
333             ArrayList<BluetoothDevice> devices = new ArrayList<>();
334 
335             if (mGroupsByDevice.isEmpty() || mBluetoothLeAudioService == null) {
336                 return devices;
337             }
338 
339             for (LinkedHashMap.Entry<BluetoothDevice, Integer> entry : mGroupsByDevice.entrySet()) {
340                 if (Objects.equals(entry.getKey(),
341                         mBluetoothLeAudioService.getConnectedGroupLeadDevice(entry.getValue()))) {
342                     devices.add(entry.getKey());
343                 }
344             }
345             devices.removeIf(device -> !mLeAudioDevicesByAddress.containsValue(device));
346             return devices;
347         }
348     }
349 
getNumConnectedDevices()350     public int getNumConnectedDevices() {
351         return getConnectedDevices().size();
352     }
353 
getConnectedDevices()354     public Collection<BluetoothDevice> getConnectedDevices() {
355         synchronized (mLock) {
356             ArraySet<BluetoothDevice> result = new ArraySet<>();
357 
358             // Set storing the group ids of all dual mode audio devices to de-dupe them
359             Set<Integer> dualModeGroupIds = new ArraySet<>();
360             for (BluetoothDevice hfpDevice: mHfpDevicesByAddress.values()) {
361                 result.add(hfpDevice);
362                 if (mBluetoothLeAudioService == null) {
363                     continue;
364                 }
365                 int groupId = mBluetoothLeAudioService.getGroupId(hfpDevice);
366                 if (groupId != BluetoothLeAudio.GROUP_ID_INVALID) {
367                     dualModeGroupIds.add(groupId);
368                 }
369             }
370 
371             result.addAll(mHearingAidDevicesByAddress.values());
372             if (mBluetoothLeAudioService == null) {
373                 return Collections.unmodifiableCollection(result);
374             }
375             for (BluetoothDevice leAudioDevice: getLeAudioConnectedDevices()) {
376                 // Exclude dual mode audio devices included from the HFP devices list
377                 int groupId = mBluetoothLeAudioService.getGroupId(leAudioDevice);
378                 if (groupId != BluetoothLeAudio.GROUP_ID_INVALID
379                         && !dualModeGroupIds.contains(groupId)) {
380                     result.add(leAudioDevice);
381                 }
382             }
383             return Collections.unmodifiableCollection(result);
384         }
385     }
386 
387     // Same as getConnectedDevices except it filters out the hearing aid devices that are linked
388     // together by their hiSyncId.
getUniqueConnectedDevices()389     public Collection<BluetoothDevice> getUniqueConnectedDevices() {
390         ArraySet<BluetoothDevice> result;
391         synchronized (mLock) {
392             result = new ArraySet<>(mHfpDevicesByAddress.values());
393         }
394         Set<Long> seenHiSyncIds = new LinkedHashSet<>();
395         // Add the left-most active device to the seen list so that we match up with the list
396         // generated in BluetoothRouteManager.
397         if (mBluetoothAdapter != null) {
398             for (BluetoothDevice device : mBluetoothAdapter.getActiveDevices(
399                         BluetoothProfile.HEARING_AID)) {
400                 if (device != null) {
401                     result.add(device);
402                     seenHiSyncIds.add(mHearingAidDeviceSyncIds.getOrDefault(device, -1L));
403                     break;
404                 }
405             }
406         }
407         synchronized (mLock) {
408             for (BluetoothDevice d : mHearingAidDevicesByAddress.values()) {
409                 long hiSyncId = mHearingAidDeviceSyncIds.getOrDefault(d, -1L);
410                 if (seenHiSyncIds.contains(hiSyncId)) {
411                     continue;
412                 }
413                 result.add(d);
414                 seenHiSyncIds.add(hiSyncId);
415             }
416         }
417 
418         if (mBluetoothLeAudioService != null) {
419             result.addAll(getLeAudioConnectedDevices());
420         }
421 
422         return Collections.unmodifiableCollection(result);
423     }
424 
getBluetoothHeadset()425     public BluetoothHeadset getBluetoothHeadset() {
426         if (mFeatureFlags.useRefactoredAudioRouteSwitching()) {
427             try {
428                 mBluetoothHeadset = mBluetoothHeadsetFuture.get(500L,
429                         TimeUnit.MILLISECONDS);
430                 return mBluetoothHeadset;
431             } catch (TimeoutException | InterruptedException | ExecutionException e) {
432                 // ignore
433                 Log.w(this, "Acquire BluetoothHeadset service failed due to: " + e);
434                 return null;
435             }
436         } else {
437             return mBluetoothHeadset;
438         }
439     }
440 
getBluetoothAdapter()441     public BluetoothAdapter getBluetoothAdapter() {
442         return mBluetoothAdapter;
443     }
444 
getBluetoothHearingAid()445     public BluetoothHearingAid getBluetoothHearingAid() {
446         return mBluetoothHearingAid;
447     }
448 
getLeAudioService()449     public BluetoothLeAudio getLeAudioService() {
450         return mBluetoothLeAudioService;
451     }
452 
setHeadsetServiceForTesting(BluetoothHeadset bluetoothHeadset)453     public void setHeadsetServiceForTesting(BluetoothHeadset bluetoothHeadset) {
454         mBluetoothHeadset = bluetoothHeadset;
455     }
456 
setHearingAidServiceForTesting(BluetoothHearingAid bluetoothHearingAid)457     public void setHearingAidServiceForTesting(BluetoothHearingAid bluetoothHearingAid) {
458         mBluetoothHearingAid = bluetoothHearingAid;
459     }
460 
setLeAudioServiceForTesting(BluetoothLeAudio bluetoothLeAudio)461     public void setLeAudioServiceForTesting(BluetoothLeAudio bluetoothLeAudio) {
462         mBluetoothLeAudioService = bluetoothLeAudio;
463         mBluetoothLeAudioService.registerCallback(mExecutor, mLeAudioCallbacks);
464     }
465 
getDeviceTypeString(int deviceType)466     public static String getDeviceTypeString(int deviceType) {
467         switch (deviceType) {
468             case DEVICE_TYPE_LE_AUDIO:
469                 return "LeAudio";
470             case DEVICE_TYPE_HEARING_AID:
471                 return "HearingAid";
472             case DEVICE_TYPE_HEADSET:
473                 return "HFP";
474             default:
475                 return "unknown type";
476         }
477     }
478 
479     @VisibleForTesting
onDeviceConnected(BluetoothDevice device, int deviceType)480     public void onDeviceConnected(BluetoothDevice device, int deviceType) {
481         synchronized (mLock) {
482             clearDeviceFromDeviceMaps(device.getAddress());
483             LinkedHashMap<String, BluetoothDevice> targetDeviceMap;
484             if (deviceType == DEVICE_TYPE_LE_AUDIO) {
485                 if (mBluetoothLeAudioService == null) {
486                     Log.w(this, "LE audio service null when receiving device added broadcast");
487                     return;
488                 }
489                 /* Check if group is known. */
490                 if (!mGroupsByDevice.containsKey(device)) {
491                     int groupId = mBluetoothLeAudioService.getGroupId(device);
492                     /* If it is not yet assigned, then it will be provided in the callback */
493                     if (groupId != BluetoothLeAudio.GROUP_ID_INVALID) {
494                         mGroupsByDevice.put(device, groupId);
495                     }
496                 }
497                 targetDeviceMap = mLeAudioDevicesByAddress;
498             } else if (deviceType == DEVICE_TYPE_HEARING_AID) {
499                 if (mBluetoothHearingAid == null) {
500                     Log.w(this, "Hearing aid service null when receiving device added broadcast");
501                     return;
502                 }
503                 long hiSyncId = mBluetoothHearingAid.getHiSyncId(device);
504                 mHearingAidDeviceSyncIds.put(device, hiSyncId);
505                 targetDeviceMap = mHearingAidDevicesByAddress;
506             } else if (deviceType == DEVICE_TYPE_HEADSET) {
507                 if (getBluetoothHeadset() == null) {
508                     Log.w(this, "Headset service null when receiving device added broadcast");
509                     return;
510                 }
511                 targetDeviceMap = mHfpDevicesByAddress;
512             } else {
513                 Log.w(this, "Device: " + device.getAddress() + " with invalid type: "
514                             + getDeviceTypeString(deviceType));
515                 return;
516             }
517             if (!targetDeviceMap.containsKey(device.getAddress())) {
518                 Log.i(this, "Adding device with address: " + device + " and devicetype="
519                         + getDeviceTypeString(deviceType));
520                 targetDeviceMap.put(device.getAddress(), device);
521                 mBluetoothRouteManager.onDeviceAdded(device.getAddress());
522             }
523         }
524     }
525 
clearDeviceFromDeviceMaps(String deviceAddress)526     void clearDeviceFromDeviceMaps(String deviceAddress) {
527         for (LinkedHashMap<String, BluetoothDevice> deviceMap : mDevicesByAddressMaps) {
528             deviceMap.remove(deviceAddress);
529         }
530     }
531 
onDeviceDisconnected(BluetoothDevice device, int deviceType)532     void onDeviceDisconnected(BluetoothDevice device, int deviceType) {
533         mLocalLog.log("Device disconnected -- address: " + device.getAddress() + " deviceType: "
534                 + deviceType);
535         synchronized (mLock) {
536             LinkedHashMap<String, BluetoothDevice> targetDeviceMap;
537             if (deviceType == DEVICE_TYPE_LE_AUDIO) {
538                 targetDeviceMap = mLeAudioDevicesByAddress;
539             } else if (deviceType == DEVICE_TYPE_HEARING_AID) {
540                 mHearingAidDeviceSyncIds.remove(device);
541                 targetDeviceMap = mHearingAidDevicesByAddress;
542             } else if (deviceType == DEVICE_TYPE_HEADSET) {
543                 targetDeviceMap = mHfpDevicesByAddress;
544             } else {
545                 Log.w(this, "Device: " + device.getAddress() + " with invalid type: "
546                             + getDeviceTypeString(deviceType));
547                 return;
548             }
549             if (targetDeviceMap.containsKey(device.getAddress())) {
550                 Log.i(this, "Removing device with address: " + device + " and devicetype="
551                         + getDeviceTypeString(deviceType));
552                 targetDeviceMap.remove(device.getAddress());
553                 mBluetoothRouteManager.onDeviceLost(device.getAddress());
554             }
555         }
556     }
557 
disconnectAudio()558     public void disconnectAudio() {
559         if (mFeatureFlags.callAudioCommunicationDeviceRefactor()) {
560             mCommunicationDeviceTracker.clearBtCommunicationDevice();
561             disconnectSco();
562         } else {
563             disconnectSco();
564             clearLeAudioCommunicationDevice();
565             clearHearingAidCommunicationDevice();
566         }
567     }
568 
disconnectSco()569     public int disconnectSco() {
570         int result = BluetoothStatusCodes.ERROR_UNKNOWN;
571         if (getBluetoothHeadset() == null) {
572             Log.w(this, "Trying to disconnect audio but no headset service exists.");
573         } else {
574             result = mBluetoothHeadset.disconnectAudio();
575         }
576         return result;
577     }
578 
isLeAudioCommunicationDevice()579     public boolean isLeAudioCommunicationDevice() {
580         return mLeAudioSetAsCommunicationDevice;
581     }
582 
isHearingAidSetAsCommunicationDevice()583     public boolean isHearingAidSetAsCommunicationDevice() {
584         return mHearingAidSetAsCommunicationDevice;
585     }
586 
clearLeAudioCommunicationDevice()587     public void clearLeAudioCommunicationDevice() {
588         Log.i(this, "clearLeAudioCommunicationDevice: mLeAudioSetAsCommunicationDevice = " +
589                 mLeAudioSetAsCommunicationDevice + " device = " + mLeAudioDevice);
590         if (!mLeAudioSetAsCommunicationDevice) {
591             return;
592         }
593         mLeAudioSetAsCommunicationDevice = false;
594         if (mLeAudioDevice != null) {
595             mBluetoothRouteManager.onAudioLost(mLeAudioDevice);
596             mLeAudioDevice = null;
597         }
598 
599         if (mAudioManager == null) {
600             Log.i(this, "clearLeAudioCommunicationDevice: mAudioManager is null");
601             return;
602         }
603 
604         AudioDeviceInfo audioDeviceInfo = mAudioManager.getCommunicationDevice();
605         if (audioDeviceInfo != null && audioDeviceInfo.getType()
606                 == AudioDeviceInfo.TYPE_BLE_HEADSET) {
607             mBluetoothRouteManager.onAudioLost(audioDeviceInfo.getAddress());
608             mAudioManager.clearCommunicationDevice();
609         }
610     }
611 
clearHearingAidCommunicationDevice()612     public void clearHearingAidCommunicationDevice() {
613         Log.i(this, "clearHearingAidCommunicationDevice: mHearingAidSetAsCommunicationDevice = "
614                 + mHearingAidSetAsCommunicationDevice);
615         if (!mHearingAidSetAsCommunicationDevice) {
616             return;
617         }
618         mHearingAidSetAsCommunicationDevice = false;
619         if (mHearingAidDevice != null) {
620             mBluetoothRouteManager.onAudioLost(mHearingAidDevice);
621             mHearingAidDevice = null;
622         }
623 
624         if (mAudioManager == null) {
625             Log.i(this, "clearHearingAidCommunicationDevice: mAudioManager is null");
626             return;
627         }
628 
629         AudioDeviceInfo audioDeviceInfo = mAudioManager.getCommunicationDevice();
630         if (audioDeviceInfo != null && audioDeviceInfo.getType()
631                 == AudioDeviceInfo.TYPE_HEARING_AID) {
632             mAudioManager.clearCommunicationDevice();
633         }
634     }
635 
setLeAudioCommunicationDevice()636     public boolean setLeAudioCommunicationDevice() {
637         Log.i(this, "setLeAudioCommunicationDevice");
638 
639         if (mLeAudioSetAsCommunicationDevice) {
640             Log.i(this, "setLeAudioCommunicationDevice already set");
641             return true;
642         }
643 
644         if (mAudioManager == null) {
645             Log.w(this, " mAudioManager is null");
646             return false;
647         }
648 
649         AudioDeviceInfo bleHeadset = null;
650         List<AudioDeviceInfo> devices = mAudioManager.getAvailableCommunicationDevices();
651         if (devices.size() == 0) {
652             Log.w(this, " No communication devices available.");
653             return false;
654         }
655 
656         for (AudioDeviceInfo device : devices) {
657             Log.i(this, " Available device type:  " + device.getType());
658             if (device.getType() == AudioDeviceInfo.TYPE_BLE_HEADSET) {
659                 bleHeadset = device;
660                 break;
661             }
662         }
663 
664         if (bleHeadset == null) {
665             Log.w(this, " No bleHeadset device available");
666             return false;
667         }
668 
669         // clear hearing aid communication device if set
670         clearHearingAidCommunicationDevice();
671 
672         // Turn BLE_OUT_HEADSET ON.
673         boolean result = mAudioManager.setCommunicationDevice(bleHeadset);
674         if (!result) {
675             Log.w(this, " Could not set bleHeadset device");
676         } else {
677             Log.i(this, " bleHeadset device set");
678             mBluetoothRouteManager.onAudioOn(bleHeadset.getAddress());
679             mLeAudioSetAsCommunicationDevice = true;
680             mLeAudioDevice = bleHeadset.getAddress();
681         }
682         return result;
683     }
684 
setHearingAidCommunicationDevice()685     public boolean setHearingAidCommunicationDevice() {
686         Log.i(this, "setHearingAidCommunicationDevice");
687 
688         if (mHearingAidSetAsCommunicationDevice) {
689             Log.i(this, "mHearingAidSetAsCommunicationDevice already set");
690             return true;
691         }
692 
693         if (mAudioManager == null) {
694             Log.w(this, " mAudioManager is null");
695             return false;
696         }
697 
698         AudioDeviceInfo hearingAid = null;
699         List<AudioDeviceInfo> devices = mAudioManager.getAvailableCommunicationDevices();
700         if (devices.size() == 0) {
701             Log.w(this, " No communication devices available.");
702             return false;
703         }
704 
705         for (AudioDeviceInfo device : devices) {
706             Log.i(this, " Available device type:  " + device.getType());
707             if (device.getType() == AudioDeviceInfo.TYPE_HEARING_AID) {
708                 hearingAid = device;
709                 break;
710             }
711         }
712 
713         if (hearingAid == null) {
714             Log.w(this, " No hearingAid device available");
715             return false;
716         }
717 
718         // clear LE audio communication device if set
719         clearLeAudioCommunicationDevice();
720 
721         // Turn hearing aid ON.
722         boolean result = mAudioManager.setCommunicationDevice(hearingAid);
723         if (!result) {
724             Log.w(this, " Could not set hearingAid device");
725         } else {
726             Log.i(this, " hearingAid device set");
727             mHearingAidDevice = hearingAid.getAddress();
728             mHearingAidSetAsCommunicationDevice = true;
729         }
730         return result;
731     }
732 
setCommunicationDeviceForAddress(String address)733     public boolean setCommunicationDeviceForAddress(String address) {
734         AudioDeviceInfo deviceInfo = null;
735         List<AudioDeviceInfo> devices = mAudioManager.getAvailableCommunicationDevices();
736         if (devices.size() == 0) {
737             Log.w(this, " No communication devices available.");
738             return false;
739         }
740 
741         for (AudioDeviceInfo device : devices) {
742             Log.i(this, " Available device type:  " + device.getType());
743             if (device.getAddress().equals(address)) {
744                 deviceInfo = device;
745                 break;
746             }
747         }
748 
749         if (!mAudioManager.getCommunicationDevice().equals(deviceInfo)) {
750             return mAudioManager.setCommunicationDevice(deviceInfo);
751         }
752         return true;
753     }
754 
755     // Connect audio to the bluetooth device at address, checking to see whether it's
756     // le audio, hearing aid or a HFP device, and using the proper BT API.
connectAudio(String address, boolean switchingBtDevices)757     public boolean connectAudio(String address, boolean switchingBtDevices) {
758         int callProfile = BluetoothProfile.LE_AUDIO;
759         Log.i(this, "Telecomm connecting audio to device: " + address);
760         BluetoothDevice device = null;
761         if (mLeAudioDevicesByAddress.containsKey(address)) {
762             Log.i(this, "Telecomm found LE Audio device for address: " + address);
763             if (mBluetoothLeAudioService == null) {
764                 Log.w(this, "Attempting to turn on audio when the le audio service is null");
765                 return false;
766             }
767             device = mLeAudioDevicesByAddress.get(address);
768             callProfile = BluetoothProfile.LE_AUDIO;
769         } else if (mHearingAidDevicesByAddress.containsKey(address)) {
770             Log.i(this, "Telecomm found hearing aid device for address: " + address);
771             if (mBluetoothHearingAid == null) {
772                 Log.w(this, "Attempting to turn on audio when the hearing aid service is null");
773                 return false;
774             }
775             device = mHearingAidDevicesByAddress.get(address);
776             callProfile = BluetoothProfile.HEARING_AID;
777         } else if (mHfpDevicesByAddress.containsKey(address)) {
778             Log.i(this, "Telecomm found HFP device for address: " + address);
779             if (getBluetoothHeadset() == null) {
780                 Log.w(this, "Attempting to turn on audio when the headset service is null");
781                 return false;
782             }
783             device = mHfpDevicesByAddress.get(address);
784             callProfile = BluetoothProfile.HEADSET;
785         }
786 
787         if (device == null) {
788             Log.w(this, "No active profiles for Bluetooth address=" + address);
789             return false;
790         }
791 
792         Bundle preferredAudioProfiles = mBluetoothAdapter.getPreferredAudioProfiles(device);
793         if (preferredAudioProfiles != null && !preferredAudioProfiles.isEmpty()
794             && preferredAudioProfiles.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX) != 0) {
795             Log.i(this, "Preferred duplex profile for device=" + address + " is "
796                 + preferredAudioProfiles.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX));
797             callProfile = preferredAudioProfiles.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX);
798         }
799 
800         if (callProfile == BluetoothProfile.LE_AUDIO) {
801             if (mBluetoothAdapter.setActiveDevice(
802                     device, BluetoothAdapter.ACTIVE_DEVICE_ALL)) {
803                 /* ACTION_ACTIVE_DEVICE_CHANGED intent will trigger setting communication device.
804                  * Only after receiving ACTION_ACTIVE_DEVICE_CHANGED it is known that device that
805                  * will be audio switched to is available to be choose as communication device */
806                 if (!switchingBtDevices) {
807                     return mFeatureFlags.callAudioCommunicationDeviceRefactor() ?
808                             mCommunicationDeviceTracker.setCommunicationDevice(
809                                     AudioDeviceInfo.TYPE_BLE_HEADSET, device)
810                             : setLeAudioCommunicationDevice();
811                 }
812                 return true;
813             }
814             return false;
815         } else if (callProfile == BluetoothProfile.HEARING_AID) {
816             if (mBluetoothAdapter.setActiveDevice(device, BluetoothAdapter.ACTIVE_DEVICE_ALL)) {
817                 /* ACTION_ACTIVE_DEVICE_CHANGED intent will trigger setting communication device.
818                  * Only after receiving ACTION_ACTIVE_DEVICE_CHANGED it is known that device that
819                  * will be audio switched to is available to be choose as communication device */
820                 if (!switchingBtDevices) {
821                     return mFeatureFlags.callAudioCommunicationDeviceRefactor() ?
822                             mCommunicationDeviceTracker.setCommunicationDevice(
823                                     AudioDeviceInfo.TYPE_HEARING_AID, null)
824                             : setHearingAidCommunicationDevice();
825                 }
826                 return true;
827             }
828             return false;
829         } else if (callProfile == BluetoothProfile.HEADSET) {
830             boolean success = mBluetoothAdapter.setActiveDevice(device,
831                 BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL);
832             if (!success) {
833                 Log.w(this, "Couldn't set active device to %s", address);
834                 return false;
835             }
836             if (getBluetoothHeadset() != null) {
837                 int scoConnectionRequest = mBluetoothHeadset.connectAudio();
838                 return scoConnectionRequest == BluetoothStatusCodes.SUCCESS ||
839                         scoConnectionRequest
840                                 == BluetoothStatusCodes.ERROR_AUDIO_DEVICE_ALREADY_CONNECTED;
841             } else {
842                 Log.w(this, "Couldn't find bluetooth headset service");
843                 return false;
844             }
845         } else {
846             Log.w(this, "Attempting to turn on audio for a disconnected device");
847             return false;
848         }
849     }
850 
851     /**
852      * Used by CallAudioRouteController in order to connect the BT device.
853      * @param device {@link BluetoothDevice} to connect to.
854      * @param type {@link AudioRoute.AudioRouteType} associated with the device.
855      * @return {@code true} if device was successfully connected, {@code false} otherwise.
856      */
connectAudio(BluetoothDevice device, @AudioRoute.AudioRouteType int type)857     public boolean connectAudio(BluetoothDevice device, @AudioRoute.AudioRouteType int type) {
858         String address = device.getAddress();
859         int callProfile = BluetoothProfile.LE_AUDIO;
860         if (type == TYPE_BLUETOOTH_SCO) {
861             callProfile = BluetoothProfile.HEADSET;
862         } else if (type == TYPE_BLUETOOTH_HA) {
863             callProfile = BluetoothProfile.HEARING_AID;
864         }
865 
866         Bundle preferredAudioProfiles = mBluetoothAdapter.getPreferredAudioProfiles(device);
867         if (preferredAudioProfiles != null && !preferredAudioProfiles.isEmpty()
868                 && preferredAudioProfiles.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX) != 0) {
869             Log.i(this, "Preferred duplex profile for device=" + address + " is "
870                     + preferredAudioProfiles.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX));
871             callProfile = preferredAudioProfiles.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX);
872         }
873 
874         if (callProfile == BluetoothProfile.LE_AUDIO
875                 || callProfile == BluetoothProfile.HEARING_AID) {
876             return mBluetoothAdapter.setActiveDevice(device, BluetoothAdapter.ACTIVE_DEVICE_ALL);
877         } else if (callProfile == BluetoothProfile.HEADSET) {
878             boolean success = mBluetoothAdapter.setActiveDevice(device,
879                     BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL);
880             if (!success) {
881                 Log.w(this, "Couldn't set active device to %s", address);
882                 return false;
883             }
884             if (getBluetoothHeadset() != null) {
885                 int scoConnectionRequest = mBluetoothHeadset.connectAudio();
886                 return scoConnectionRequest == BluetoothStatusCodes.SUCCESS ||
887                         scoConnectionRequest
888                                 == BluetoothStatusCodes.ERROR_AUDIO_DEVICE_ALREADY_CONNECTED;
889             } else {
890                 Log.w(this, "Couldn't find bluetooth headset service");
891                 return false;
892             }
893         } else {
894             Log.w(this, "Attempting to turn on audio for a disconnected device");
895             return false;
896         }
897     }
898 
cacheHearingAidDevice()899     public void cacheHearingAidDevice() {
900         if (mBluetoothAdapter != null) {
901             for (BluetoothDevice device : mBluetoothAdapter.getActiveDevices(
902                         BluetoothProfile.HEARING_AID)) {
903                 if (device != null) {
904                     mBluetoothHearingAidActiveDeviceCache = device;
905                 }
906             }
907         }
908     }
909 
restoreHearingAidDevice()910     public void restoreHearingAidDevice() {
911         if (mBluetoothHearingAidActiveDeviceCache != null) {
912             mBluetoothAdapter.setActiveDevice(mBluetoothHearingAidActiveDeviceCache,
913                     BluetoothAdapter.ACTIVE_DEVICE_ALL);
914             mBluetoothHearingAidActiveDeviceCache = null;
915         }
916     }
917 
isInbandRingingEnabled()918     public boolean isInbandRingingEnabled() {
919         // Get the inband ringing enabled status of expected BT device to route call audio instead
920         // of using the address of currently connected device.
921         BluetoothDevice activeDevice = mBluetoothRouteManager.getMostRecentlyReportedActiveDevice();
922         return isInbandRingEnabled(activeDevice);
923     }
924 
isInbandRingEnabled(BluetoothDevice bluetoothDevice)925     public boolean isInbandRingEnabled(BluetoothDevice bluetoothDevice) {
926         Log.i(this, "isInbandRingEnabled: device: " + bluetoothDevice);
927         if (mBluetoothRouteManager.isCachedLeAudioDevice(bluetoothDevice)) {
928             if (mBluetoothLeAudioService == null) {
929                 Log.i(this, "isInbandRingingEnabled: no leaudio service available.");
930                 return false;
931             }
932             int groupId = mBluetoothLeAudioService.getGroupId(bluetoothDevice);
933             return mBluetoothLeAudioService.isInbandRingtoneEnabled(groupId);
934         } else {
935             if (getBluetoothHeadset() == null) {
936                 Log.i(this, "isInbandRingingEnabled: no headset service available.");
937                 return false;
938             }
939             return mBluetoothHeadset.isInbandRingingEnabled();
940         }
941     }
942 
setCallAudioRouteAdapter(CallAudioRouteAdapter adapter)943     public void setCallAudioRouteAdapter(CallAudioRouteAdapter adapter) {
944         mCallAudioRouteAdapter = adapter;
945     }
946 
dump(IndentingPrintWriter pw)947     public void dump(IndentingPrintWriter pw) {
948         mLocalLog.dump(pw);
949     }
950 }
951