• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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;
18 
19 import static com.android.server.telecom.CallAudioRouteAdapter.BT_AUDIO_CONNECTED;
20 import static com.android.server.telecom.CallAudioRouteAdapter.BT_AUDIO_DISCONNECTED;
21 import static com.android.server.telecom.CallAudioRouteAdapter.PENDING_ROUTE_FAILED;
22 import static com.android.server.telecom.CallAudioRouteAdapter.SPEAKER_OFF;
23 import static com.android.server.telecom.CallAudioRouteAdapter.SPEAKER_ON;
24 
25 import android.annotation.IntDef;
26 import android.bluetooth.BluetoothDevice;
27 import android.bluetooth.BluetoothHeadset;
28 import android.bluetooth.BluetoothStatusCodes;
29 import android.media.AudioDeviceInfo;
30 import android.media.AudioManager;
31 import android.sysprop.BluetoothProperties;
32 import android.telecom.Log;
33 import android.util.Pair;
34 
35 import com.android.internal.annotations.VisibleForTesting;
36 import com.android.server.telecom.bluetooth.BluetoothRouteManager;
37 
38 import java.lang.annotation.Retention;
39 import java.lang.annotation.RetentionPolicy;
40 import java.util.ArrayList;
41 import java.util.HashMap;
42 import java.util.List;
43 import java.util.Objects;
44 import java.util.Set;
45 import java.util.concurrent.CompletableFuture;
46 import java.util.concurrent.ExecutionException;
47 import java.util.concurrent.RejectedExecutionException;
48 import java.util.concurrent.ScheduledExecutorService;
49 import java.util.concurrent.ScheduledThreadPoolExecutor;
50 import java.util.concurrent.TimeUnit;
51 
52 public class AudioRoute {
53     public static class Factory {
54         private final ScheduledExecutorService mScheduledExecutorService =
55                 new ScheduledThreadPoolExecutor(1);
56         private CompletableFuture<AudioRoute> mAudioRouteFuture;
create(@udioRouteType int type, String bluetoothAddress, AudioManager audioManager)57         public AudioRoute create(@AudioRouteType int type, String bluetoothAddress,
58                                  AudioManager audioManager) throws RuntimeException {
59             mAudioRouteFuture = new CompletableFuture();
60             createRetry(type, bluetoothAddress, audioManager, MAX_CONNECTION_RETRIES);
61             try {
62                 return mAudioRouteFuture.get();
63             } catch (InterruptedException | ExecutionException e) {
64                 throw new RuntimeException("Error when creating requested audio route");
65             }
66         }
createRetry(@udioRouteType int type, String bluetoothAddress, AudioManager audioManager, int retryCount)67         private void createRetry(@AudioRouteType int type, String bluetoothAddress,
68                                        AudioManager audioManager, int retryCount) {
69             // Early exit if exceeded max number of retries (and complete the future).
70             if (retryCount == 0) {
71                 mAudioRouteFuture.complete(null);
72                 return;
73             }
74 
75             Log.i(this, "createRetry; type=%s, address=%s, retryCount=%d",
76                     DEVICE_TYPE_STRINGS.get(type), bluetoothAddress, retryCount);
77             AudioDeviceInfo routeInfo = null;
78             List<AudioDeviceInfo> infos = audioManager.getAvailableCommunicationDevices();
79             List<Integer> possibleInfoTypes = AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE.get(type);
80             for (AudioDeviceInfo info : infos) {
81                 Log.i(this, "type: " + info.getType());
82                 if (possibleInfoTypes != null && possibleInfoTypes.contains(info.getType())) {
83                     if (BT_AUDIO_ROUTE_TYPES.contains(type)) {
84                         if (bluetoothAddress.equals(info.getAddress())) {
85                             routeInfo = info;
86                             break;
87                         }
88                     } else {
89                         routeInfo = info;
90                         break;
91                     }
92                 }
93             }
94             // Try connecting BT device anyway (to handle wearables not showing as available
95             // communication device or LE device not showing up since it may not be the lead
96             // device).
97             if (routeInfo == null && bluetoothAddress == null) {
98                 try {
99                     mScheduledExecutorService.schedule(
100                             () -> createRetry(type, bluetoothAddress, audioManager, retryCount - 1),
101                             RETRY_TIME_DELAY, TimeUnit.MILLISECONDS);
102                 } catch (RejectedExecutionException e) {
103                     Log.e(this, e, "Could not schedule retry for audio routing.");
104                 }
105             } else {
106                 mAudioRouteFuture.complete(new AudioRoute(type, bluetoothAddress, routeInfo));
107             }
108         }
109     }
110 
111     private static final long RETRY_TIME_DELAY = 500L;
112     private static final int MAX_CONNECTION_RETRIES = 2;
113     public static final int TYPE_INVALID = 0;
114     public static final int TYPE_EARPIECE = 1;
115     public static final int TYPE_WIRED = 2;
116     public static final int TYPE_SPEAKER = 3;
117     public static final int TYPE_DOCK = 4;
118     public static final int TYPE_BLUETOOTH_SCO = 5;
119     public static final int TYPE_BLUETOOTH_HA = 6;
120     public static final int TYPE_BLUETOOTH_LE = 7;
121     public static final int TYPE_STREAMING = 8;
122     // Used by auto
123     public static final int TYPE_BUS = 9;
124     @IntDef(prefix = "TYPE", value = {
125             TYPE_INVALID,
126             TYPE_EARPIECE,
127             TYPE_WIRED,
128             TYPE_SPEAKER,
129             TYPE_DOCK,
130             TYPE_BLUETOOTH_SCO,
131             TYPE_BLUETOOTH_HA,
132             TYPE_BLUETOOTH_LE,
133             TYPE_STREAMING,
134             TYPE_BUS
135     })
136     @Retention(RetentionPolicy.SOURCE)
137     public @interface AudioRouteType {}
138 
139     private @AudioRouteType int mAudioRouteType;
140     private String mBluetoothAddress;
141     private AudioDeviceInfo mInfo;
142     private boolean mIsDestRouteForWatch;
143     private boolean mIsScoManagedByAudio;
144     public static final Set<Integer> BT_AUDIO_DEVICE_INFO_TYPES = Set.of(
145             AudioDeviceInfo.TYPE_BLE_HEADSET,
146             AudioDeviceInfo.TYPE_BLE_SPEAKER,
147             AudioDeviceInfo.TYPE_BLE_BROADCAST,
148             AudioDeviceInfo.TYPE_HEARING_AID,
149             AudioDeviceInfo.TYPE_BLUETOOTH_SCO
150     );
151 
152     public static final Set<Integer> BT_AUDIO_ROUTE_TYPES = Set.of(
153             AudioRoute.TYPE_BLUETOOTH_SCO,
154             AudioRoute.TYPE_BLUETOOTH_HA,
155             AudioRoute.TYPE_BLUETOOTH_LE
156     );
157 
158     public static final HashMap<Integer, String> DEVICE_TYPE_STRINGS;
159     static {
160         DEVICE_TYPE_STRINGS = new HashMap<>();
DEVICE_TYPE_STRINGS.put(TYPE_EARPIECE, "TYPE_EARPIECE")161         DEVICE_TYPE_STRINGS.put(TYPE_EARPIECE, "TYPE_EARPIECE");
DEVICE_TYPE_STRINGS.put(TYPE_WIRED, "TYPE_WIRED_HEADSET")162         DEVICE_TYPE_STRINGS.put(TYPE_WIRED, "TYPE_WIRED_HEADSET");
DEVICE_TYPE_STRINGS.put(TYPE_SPEAKER, "TYPE_SPEAKER")163         DEVICE_TYPE_STRINGS.put(TYPE_SPEAKER, "TYPE_SPEAKER");
DEVICE_TYPE_STRINGS.put(TYPE_DOCK, "TYPE_DOCK")164         DEVICE_TYPE_STRINGS.put(TYPE_DOCK, "TYPE_DOCK");
DEVICE_TYPE_STRINGS.put(TYPE_BUS, "TYPE_BUS")165         DEVICE_TYPE_STRINGS.put(TYPE_BUS, "TYPE_BUS");
DEVICE_TYPE_STRINGS.put(TYPE_BLUETOOTH_SCO, "TYPE_BLUETOOTH_SCO")166         DEVICE_TYPE_STRINGS.put(TYPE_BLUETOOTH_SCO, "TYPE_BLUETOOTH_SCO");
DEVICE_TYPE_STRINGS.put(TYPE_BLUETOOTH_HA, "TYPE_BLUETOOTH_HA")167         DEVICE_TYPE_STRINGS.put(TYPE_BLUETOOTH_HA, "TYPE_BLUETOOTH_HA");
DEVICE_TYPE_STRINGS.put(TYPE_BLUETOOTH_LE, "TYPE_BLUETOOTH_LE")168         DEVICE_TYPE_STRINGS.put(TYPE_BLUETOOTH_LE, "TYPE_BLUETOOTH_LE");
DEVICE_TYPE_STRINGS.put(TYPE_STREAMING, "TYPE_STREAMING")169         DEVICE_TYPE_STRINGS.put(TYPE_STREAMING, "TYPE_STREAMING");
170     }
171 
172     public static final HashMap<Integer, Integer> DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE;
173     static {
174         DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE = new HashMap<>();
DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_BUILTIN_EARPIECE, TYPE_EARPIECE)175         DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_BUILTIN_EARPIECE,
176                 TYPE_EARPIECE);
DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, TYPE_SPEAKER)177         DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER,
178                 TYPE_SPEAKER);
DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_WIRED_HEADSET, TYPE_WIRED)179         DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_WIRED_HEADSET, TYPE_WIRED);
DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_WIRED_HEADPHONES, TYPE_WIRED)180         DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_WIRED_HEADPHONES, TYPE_WIRED);
DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_BLUETOOTH_SCO, TYPE_BLUETOOTH_SCO)181         DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_BLUETOOTH_SCO,
182                 TYPE_BLUETOOTH_SCO);
DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_USB_DEVICE, TYPE_WIRED)183         DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_USB_DEVICE, TYPE_WIRED);
DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_USB_ACCESSORY, TYPE_WIRED)184         DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_USB_ACCESSORY, TYPE_WIRED);
DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_DOCK, TYPE_DOCK)185         DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_DOCK, TYPE_DOCK);
DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_USB_HEADSET, TYPE_WIRED)186         DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_USB_HEADSET, TYPE_WIRED);
DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_HEARING_AID, TYPE_BLUETOOTH_HA)187         DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_HEARING_AID,
188                 TYPE_BLUETOOTH_HA);
DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_BLE_HEADSET, TYPE_BLUETOOTH_LE)189         DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_BLE_HEADSET,
190                 TYPE_BLUETOOTH_LE);
DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_BLE_SPEAKER, TYPE_BLUETOOTH_LE)191         DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_BLE_SPEAKER,
192                 TYPE_BLUETOOTH_LE);
DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_BLE_BROADCAST, TYPE_BLUETOOTH_LE)193         DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_BLE_BROADCAST,
194                 TYPE_BLUETOOTH_LE);
DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_DOCK_ANALOG, TYPE_DOCK)195         DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_DOCK_ANALOG, TYPE_DOCK);
DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_BUS, TYPE_BUS)196         DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_BUS, TYPE_BUS);
197     }
198 
199     private static final HashMap<Integer, List<Integer>> AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE;
200     static {
201         AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE = new HashMap<>();
202         List<Integer> earpieceDeviceInfoTypes = new ArrayList<>();
203         earpieceDeviceInfoTypes.add(AudioDeviceInfo.TYPE_BUILTIN_EARPIECE);
AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE.put(TYPE_EARPIECE, earpieceDeviceInfoTypes)204         AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE.put(TYPE_EARPIECE, earpieceDeviceInfoTypes);
205 
206         List<Integer> wiredDeviceInfoTypes = new ArrayList<>();
207         wiredDeviceInfoTypes.add(AudioDeviceInfo.TYPE_WIRED_HEADSET);
208         wiredDeviceInfoTypes.add(AudioDeviceInfo.TYPE_WIRED_HEADPHONES);
209         wiredDeviceInfoTypes.add(AudioDeviceInfo.TYPE_USB_DEVICE);
210         wiredDeviceInfoTypes.add(AudioDeviceInfo.TYPE_USB_ACCESSORY);
211         wiredDeviceInfoTypes.add(AudioDeviceInfo.TYPE_USB_HEADSET);
AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE.put(TYPE_WIRED, wiredDeviceInfoTypes)212         AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE.put(TYPE_WIRED, wiredDeviceInfoTypes);
213 
214         List<Integer> speakerDeviceInfoTypes = new ArrayList<>();
215         speakerDeviceInfoTypes.add(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER);
AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE.put(TYPE_SPEAKER, speakerDeviceInfoTypes)216         AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE.put(TYPE_SPEAKER, speakerDeviceInfoTypes);
217 
218         List<Integer> dockDeviceInfoTypes = new ArrayList<>();
219         dockDeviceInfoTypes.add(AudioDeviceInfo.TYPE_DOCK);
220         dockDeviceInfoTypes.add(AudioDeviceInfo.TYPE_DOCK_ANALOG);
AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE.put(TYPE_DOCK, dockDeviceInfoTypes)221         AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE.put(TYPE_DOCK, dockDeviceInfoTypes);
222 
223         List<Integer> busDeviceInfoTypes = new ArrayList<>();
224         busDeviceInfoTypes.add(AudioDeviceInfo.TYPE_BUS);
AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE.put(TYPE_BUS, busDeviceInfoTypes)225         AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE.put(TYPE_BUS, busDeviceInfoTypes);
226 
227         List<Integer> bluetoothScoDeviceInfoTypes = new ArrayList<>();
228         bluetoothScoDeviceInfoTypes.add(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP);
229         bluetoothScoDeviceInfoTypes.add(AudioDeviceInfo.TYPE_BLUETOOTH_SCO);
AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE.put(TYPE_BLUETOOTH_SCO, bluetoothScoDeviceInfoTypes)230         AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE.put(TYPE_BLUETOOTH_SCO, bluetoothScoDeviceInfoTypes);
231 
232         List<Integer> bluetoothHearingAidDeviceInfoTypes = new ArrayList<>();
233         bluetoothHearingAidDeviceInfoTypes.add(AudioDeviceInfo.TYPE_HEARING_AID);
AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE.put(TYPE_BLUETOOTH_HA, bluetoothHearingAidDeviceInfoTypes)234         AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE.put(TYPE_BLUETOOTH_HA,
235                 bluetoothHearingAidDeviceInfoTypes);
236 
237         List<Integer> bluetoothLeDeviceInfoTypes = new ArrayList<>();
238         bluetoothLeDeviceInfoTypes.add(AudioDeviceInfo.TYPE_BLE_HEADSET);
239         bluetoothLeDeviceInfoTypes.add(AudioDeviceInfo.TYPE_BLE_SPEAKER);
240         bluetoothLeDeviceInfoTypes.add(AudioDeviceInfo.TYPE_BLE_BROADCAST);
AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE.put(TYPE_BLUETOOTH_LE, bluetoothLeDeviceInfoTypes)241         AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE.put(TYPE_BLUETOOTH_LE, bluetoothLeDeviceInfoTypes);
242     }
243 
getType()244     public int getType() {
245         return mAudioRouteType;
246     }
247 
isWatch()248     public boolean isWatch() {
249         return mIsDestRouteForWatch;
250     }
251 
getBluetoothAddress()252     String getBluetoothAddress() {
253         return mBluetoothAddress;
254     }
255 
256     // Invoked when entered pending route whose dest route is this route
onDestRouteAsPendingRoute(boolean active, PendingAudioRoute pendingAudioRoute, BluetoothDevice device, AudioManager audioManager, BluetoothRouteManager bluetoothRouteManager, boolean isScoAlreadyConnected)257     void onDestRouteAsPendingRoute(boolean active, PendingAudioRoute pendingAudioRoute,
258             BluetoothDevice device, AudioManager audioManager,
259             BluetoothRouteManager bluetoothRouteManager, boolean isScoAlreadyConnected) {
260         Log.i(this, "onDestRouteAsPendingRoute: active (%b), type (%s), isScoAlreadyConnected(%s)",
261                 active, DEVICE_TYPE_STRINGS.get(mAudioRouteType), isScoAlreadyConnected);
262         if (pendingAudioRoute.isActive() && !active) {
263             clearCommunicationDevice(pendingAudioRoute, bluetoothRouteManager, audioManager);
264         } else if (active) {
265             // Handle BT routing case.
266             if (BT_AUDIO_ROUTE_TYPES.contains(mAudioRouteType)) {
267                 // Check if the communication device was set for the device, even if
268                 // BluetoothHeadset#connectAudio reports that the SCO connection wasn't
269                 // successfully established.
270                 boolean connectedBtAudio = connectBtAudio(pendingAudioRoute, device,
271                         audioManager, bluetoothRouteManager, isScoAlreadyConnected);
272                 // Special handling for SCO case.
273                 if (!mIsScoManagedByAudio && mAudioRouteType == TYPE_BLUETOOTH_SCO) {
274                     // Set whether the dest route is for the watch
275                     mIsDestRouteForWatch = bluetoothRouteManager.isWatch(device);
276                     if (connectedBtAudio || isScoAlreadyConnected) {
277                         pendingAudioRoute.setCommunicationDeviceType(mAudioRouteType);
278                         if (!isScoAlreadyConnected) {
279                             pendingAudioRoute.addMessage(BT_AUDIO_CONNECTED, mBluetoothAddress);
280                         }
281                     } else {
282                         pendingAudioRoute.onMessageReceived(new Pair<>(PENDING_ROUTE_FAILED,
283                                 mBluetoothAddress), mBluetoothAddress);
284                     }
285                     return;
286                 }
287             } else if (mAudioRouteType == TYPE_SPEAKER && !this.equals(
288                     pendingAudioRoute.getOrigRoute())) {
289                 pendingAudioRoute.addMessage(SPEAKER_ON, null);
290             }
291 
292             boolean result = false;
293             List<AudioDeviceInfo> devices = audioManager.getAvailableCommunicationDevices();
294             for (AudioDeviceInfo deviceInfo : devices) {
295                 // It's possible for the AudioDeviceInfo to be updated for the BT device so adjust
296                 // mInfo accordingly.
297                 // Note: we need to check the device type as well since a dual mode (LE and HFP) BT
298                 // device can change type during a call if the user toggles LE for the device.
299                 boolean isSameDeviceType =
300                         !pendingAudioRoute.getFeatureFlags().checkDeviceTypeOnRouteChange() ||
301                                 (pendingAudioRoute.getFeatureFlags().checkDeviceTypeOnRouteChange()
302                                         && mAudioRouteType
303                                         == DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.get(
304                                         deviceInfo.getType()));
305                 if (BT_AUDIO_ROUTE_TYPES.contains(mAudioRouteType) && mBluetoothAddress
306                         .equals(deviceInfo.getAddress())
307                         && isSameDeviceType) {
308                     mInfo = deviceInfo;
309                 }
310                 if (deviceInfo.equals(mInfo)) {
311                     result = audioManager.setCommunicationDevice(mInfo);
312                     if (result) {
313                         pendingAudioRoute.setCommunicationDeviceType(mAudioRouteType);
314                         if (mAudioRouteType == TYPE_BLUETOOTH_SCO
315                                 && !isScoAlreadyConnected
316                                 && mIsScoManagedByAudio) {
317                             pendingAudioRoute.addMessage(BT_AUDIO_CONNECTED, mBluetoothAddress);
318                         }
319                     }
320                     Log.i(this, "onDestRouteAsPendingRoute: route=%s, "
321                             + "AudioManager#setCommunicationDevice(%s)=%b", this,
322                             audioDeviceTypeToString(mInfo.getType()), result);
323                     break;
324                 }
325             }
326 
327             // It's possible that BluetoothStateReceiver needs to report that the device is active
328             // before being able to successfully set the communication device. Refrain from sending
329             // pending route failed message for BT route until the second attempt fails.
330             if (!result && !BT_AUDIO_ROUTE_TYPES.contains(mAudioRouteType)) {
331                 pendingAudioRoute.onMessageReceived(new Pair<>(PENDING_ROUTE_FAILED, null), null);
332             }
333         }
334     }
335 
336     /**
337      * Takes care of cleaning up original audio route (i.e. clearCommunicationDevice,
338      * sending SPEAKER_OFF, or disconnecting SCO).
339      * @param wasActive Was the origin route active or not.
340      * @param pendingAudioRoute The pending audio route change we're performing.
341      * @param audioManager Good 'ol audio manager.
342      * @param bluetoothRouteManager The BT route manager.
343      */
onOrigRouteAsPendingRoute(boolean wasActive, PendingAudioRoute pendingAudioRoute, AudioManager audioManager, BluetoothRouteManager bluetoothRouteManager, boolean isScoAlreadyConnected)344     void onOrigRouteAsPendingRoute(boolean wasActive, PendingAudioRoute pendingAudioRoute,
345             AudioManager audioManager, BluetoothRouteManager bluetoothRouteManager,
346             boolean isScoAlreadyConnected) {
347         Log.i(this, "onOrigRouteAsPendingRoute: wasActive (%b), type (%s), pending(%s),"
348                 + "isScoAlreadyConnected(%s)", wasActive, DEVICE_TYPE_STRINGS.get(mAudioRouteType),
349                 pendingAudioRoute, isScoAlreadyConnected);
350         if (wasActive && !isScoAlreadyConnected) {
351             int result = clearCommunicationDevice(pendingAudioRoute, bluetoothRouteManager,
352                     audioManager);
353             if (mAudioRouteType == TYPE_SPEAKER) {
354                 pendingAudioRoute.addMessage(SPEAKER_OFF, null);
355             } else if (mAudioRouteType == TYPE_BLUETOOTH_SCO
356                     && result == BluetoothStatusCodes.SUCCESS) {
357                 // Only send BT_AUDIO_DISCONNECTED for SCO if disconnect was successful.
358                 pendingAudioRoute.addMessage(BT_AUDIO_DISCONNECTED, mBluetoothAddress);
359             }
360         }
361     }
362 
363     @VisibleForTesting
AudioRoute(@udioRouteType int type, String bluetoothAddress, AudioDeviceInfo info)364     public AudioRoute(@AudioRouteType int type, String bluetoothAddress, AudioDeviceInfo info) {
365         mAudioRouteType = type;
366         mBluetoothAddress = bluetoothAddress;
367         mInfo = info;
368         // Indication that SCO is managed by audio (i.e. supports setCommunicationDevice).
369         mIsScoManagedByAudio = android.media.audio.Flags.scoManagedByAudio()
370                 && BluetoothProperties.isScoManagedByAudioEnabled().orElse(false);
371     }
372 
373     @Override
equals(Object obj)374     public boolean equals(Object obj) {
375         if (obj == null) {
376             return false;
377         }
378         if (!(obj instanceof AudioRoute otherRoute)) {
379             return false;
380         }
381         if (mAudioRouteType != otherRoute.getType()) {
382             return false;
383         }
384         return !BT_AUDIO_ROUTE_TYPES.contains(mAudioRouteType) || mBluetoothAddress.equals(
385                 otherRoute.getBluetoothAddress());
386     }
387 
388     @Override
hashCode()389     public int hashCode() {
390         return Objects.hash(mAudioRouteType, mBluetoothAddress);
391     }
392 
393     @Override
toString()394     public String toString() {
395         return getClass().getSimpleName() + "[Type=" + DEVICE_TYPE_STRINGS.get(mAudioRouteType)
396                 + ", Address=" + ((mBluetoothAddress != null) ? mBluetoothAddress : "invalid")
397                 + "]";
398     }
399 
connectBtAudio(PendingAudioRoute pendingAudioRoute, BluetoothDevice device, AudioManager audioManager, BluetoothRouteManager bluetoothRouteManager, boolean isScoAlreadyConnected)400     private boolean connectBtAudio(PendingAudioRoute pendingAudioRoute, BluetoothDevice device,
401             AudioManager audioManager, BluetoothRouteManager bluetoothRouteManager,
402             boolean isScoAlreadyConnected) {
403         // Ensure that if another BT device was set, it is disconnected before connecting
404         // the new one.
405         AudioRoute currentRoute = pendingAudioRoute.getOrigRoute();
406         if (!isScoAlreadyConnected && currentRoute.getBluetoothAddress() != null &&
407                 !currentRoute.getBluetoothAddress().equals(device.getAddress())) {
408             clearCommunicationDevice(pendingAudioRoute, bluetoothRouteManager, audioManager);
409         }
410 
411         // Connect to the device (explicit handling for HFP devices).
412         boolean success = false;
413         if (device != null) {
414             success = bluetoothRouteManager.getDeviceManager()
415                     .connectAudio(device, mAudioRouteType, mIsScoManagedByAudio);
416         }
417 
418         Log.i(this, "connectBtAudio: routeToConnectTo = %s, successful = %b",
419                 this, success);
420         return success;
421     }
422 
423     /**
424      * Clears the communication device; this takes into account the fact that SCO devices require
425      * us to call {@link BluetoothHeadset#disconnectAudio()} rather than
426      * {@link AudioManager#clearCommunicationDevice()}.
427      * As a general rule, if we are transitioning from an active route to another active route, we
428      * do NOT need to call {@link AudioManager#clearCommunicationDevice()}, but if the device is a
429      * legacy SCO device we WILL need to call {@link BluetoothHeadset#disconnectAudio()}.  We rely
430      * on the {@link PendingAudioRoute#isActive()} indicator to tell us if the destination route
431      * is going to be active or not.
432      * @param pendingAudioRoute The pending audio route transition we're implementing.
433      * @param bluetoothRouteManager The BT route manager.
434      * @param audioManager The audio manager.
435      * @return -1 if nothing was done, or the result code from the BT SCO disconnect.
436      */
clearCommunicationDevice(PendingAudioRoute pendingAudioRoute, BluetoothRouteManager bluetoothRouteManager, AudioManager audioManager)437     int clearCommunicationDevice(PendingAudioRoute pendingAudioRoute,
438             BluetoothRouteManager bluetoothRouteManager, AudioManager audioManager) {
439         // Try to see if there's a previously set device for communication that should be cleared.
440         // This only serves to help in the SCO case to ensure that we disconnect the headset.
441         if (pendingAudioRoute.getCommunicationDeviceType() == AudioRoute.TYPE_INVALID) {
442             return -1;
443         }
444 
445         int result = BluetoothStatusCodes.SUCCESS;
446         boolean shouldDisconnectSco = !mIsScoManagedByAudio
447                 && pendingAudioRoute.getCommunicationDeviceType() == TYPE_BLUETOOTH_SCO;
448         if (shouldDisconnectSco) {
449             Log.i(this, "Disconnecting SCO device via BluetoothHeadset.");
450             result = bluetoothRouteManager.getDeviceManager().disconnectSco();
451         }
452         // Only clear communication device if the destination route will be inactive; route to
453         // route transitions do not require clearing the communication device.
454         boolean onlyClearCommunicationDeviceOnInactive =
455                 pendingAudioRoute.getFeatureFlags().onlyClearCommunicationDeviceOnInactive();
456         if ((!onlyClearCommunicationDeviceOnInactive && !shouldDisconnectSco)
457                 || !pendingAudioRoute.isActive()) {
458             Log.i(this,
459                     "clearCommunicationDevice: AudioManager#clearCommunicationDevice, type=%s",
460                     DEVICE_TYPE_STRINGS.get(pendingAudioRoute.getCommunicationDeviceType()));
461             audioManager.clearCommunicationDevice();
462         }
463 
464         if (result == BluetoothStatusCodes.SUCCESS) {
465             if (pendingAudioRoute.getFeatureFlags().resolveActiveBtRoutingAndBtTimingIssue()) {
466                 maybeClearConnectedPendingMessages(pendingAudioRoute);
467             }
468             pendingAudioRoute.setCommunicationDeviceType(AudioRoute.TYPE_INVALID);
469         }
470         return result;
471     }
472 
maybeClearConnectedPendingMessages(PendingAudioRoute pendingAudioRoute)473     private void maybeClearConnectedPendingMessages(PendingAudioRoute pendingAudioRoute) {
474         // If we're still waiting on BT_AUDIO_CONNECTED/SPEAKER_ON but have routed out of it
475         // since and disconnected the device, then remove that message so we aren't waiting for
476         // it in the message queue.
477         if (mAudioRouteType == TYPE_BLUETOOTH_SCO) {
478             Log.i(this, "clearCommunicationDevice: Clearing pending "
479                     + "BT_AUDIO_CONNECTED messages.");
480             pendingAudioRoute.clearPendingMessage(
481                     new Pair<>(BT_AUDIO_CONNECTED, mBluetoothAddress));
482         } else if (mAudioRouteType == TYPE_SPEAKER) {
483             Log.i(this, "clearCommunicationDevice: Clearing pending SPEAKER_ON messages.");
484             pendingAudioRoute.clearPendingMessage(new Pair<>(SPEAKER_ON, null));
485         }
486     }
487 
488     /**
489      * Get a human readable (for logs) version of an an audio device type.
490      * @param type the device type
491      * @return the human readable string
492      */
audioDeviceTypeToString(int type)493     private static String audioDeviceTypeToString(int type) {
494         return switch (type) {
495             case AudioDeviceInfo.TYPE_BUILTIN_EARPIECE -> "earpiece";
496             case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER -> "speaker";
497             case AudioDeviceInfo.TYPE_BUS -> "bus(auto speaker)";
498             case AudioDeviceInfo.TYPE_BLUETOOTH_SCO -> "bt sco";
499             case AudioDeviceInfo.TYPE_BLE_HEADSET -> "bt le";
500             case AudioDeviceInfo.TYPE_HEARING_AID -> "bt hearing aid";
501             case AudioDeviceInfo.TYPE_USB_HEADSET -> "usb headset";
502             case AudioDeviceInfo.TYPE_WIRED_HEADSET -> "wired headset";
503             default -> Integer.toString(type);
504         };
505     }
506 }
507