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.BluetoothStatusCodes;
28 import android.media.AudioDeviceInfo;
29 import android.media.AudioManager;
30 import android.telecom.Log;
31 import android.util.Pair;
32 
33 import com.android.internal.annotations.VisibleForTesting;
34 import com.android.server.telecom.bluetooth.BluetoothRouteManager;
35 
36 import java.lang.annotation.Retention;
37 import java.lang.annotation.RetentionPolicy;
38 import java.util.ArrayList;
39 import java.util.HashMap;
40 import java.util.List;
41 import java.util.Objects;
42 import java.util.Set;
43 import java.util.concurrent.CompletableFuture;
44 import java.util.concurrent.ExecutionException;
45 import java.util.concurrent.RejectedExecutionException;
46 import java.util.concurrent.ScheduledExecutorService;
47 import java.util.concurrent.ScheduledThreadPoolExecutor;
48 import java.util.concurrent.TimeUnit;
49 
50 public class AudioRoute {
51     public static class Factory {
52         private final ScheduledExecutorService mScheduledExecutorService =
53                 new ScheduledThreadPoolExecutor(1);
54         private CompletableFuture<AudioRoute> mAudioRouteFuture;
create(@udioRouteType int type, String bluetoothAddress, AudioManager audioManager)55         public AudioRoute create(@AudioRouteType int type, String bluetoothAddress,
56                                  AudioManager audioManager) throws RuntimeException {
57             mAudioRouteFuture = new CompletableFuture();
58             createRetry(type, bluetoothAddress, audioManager, MAX_CONNECTION_RETRIES);
59             try {
60                 return mAudioRouteFuture.get();
61             } catch (InterruptedException | ExecutionException e) {
62                 throw new RuntimeException("Error when creating requested audio route");
63             }
64         }
createRetry(@udioRouteType int type, String bluetoothAddress, AudioManager audioManager, int retryCount)65         private void createRetry(@AudioRouteType int type, String bluetoothAddress,
66                                        AudioManager audioManager, int retryCount) {
67             // Early exit if exceeded max number of retries (and complete the future).
68             if (retryCount == 0) {
69                 mAudioRouteFuture.complete(null);
70                 return;
71             }
72 
73             Log.i(this, "creating AudioRoute with type %s and address %s, retry count %d",
74                     DEVICE_TYPE_STRINGS.get(type), bluetoothAddress, retryCount);
75             AudioDeviceInfo routeInfo = null;
76             List<AudioDeviceInfo> infos = audioManager.getAvailableCommunicationDevices();
77             List<Integer> possibleInfoTypes = AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE.get(type);
78             for (AudioDeviceInfo info : infos) {
79                 Log.i(this, "type: " + info.getType());
80                 if (possibleInfoTypes != null && possibleInfoTypes.contains(info.getType())) {
81                     if (BT_AUDIO_ROUTE_TYPES.contains(type)) {
82                         if (bluetoothAddress.equals(info.getAddress())) {
83                             routeInfo = info;
84                             break;
85                         }
86                     } else {
87                         routeInfo = info;
88                         break;
89                     }
90                 }
91             }
92             // Try connecting BT device anyway (to handle wearables not showing as available
93             // communication device or LE device not showing up since it may not be the lead
94             // device).
95             if (routeInfo == null && bluetoothAddress == null) {
96                 try {
97                     mScheduledExecutorService.schedule(
98                             () -> createRetry(type, bluetoothAddress, audioManager, retryCount - 1),
99                             RETRY_TIME_DELAY, TimeUnit.MILLISECONDS);
100                 } catch (RejectedExecutionException e) {
101                     Log.e(this, e, "Could not schedule retry for audio routing.");
102                 }
103             } else {
104                 mAudioRouteFuture.complete(new AudioRoute(type, bluetoothAddress, routeInfo));
105             }
106         }
107     }
108 
109     private static final long RETRY_TIME_DELAY = 500L;
110     private static final int MAX_CONNECTION_RETRIES = 2;
111     public static final int TYPE_INVALID = 0;
112     public static final int TYPE_EARPIECE = 1;
113     public static final int TYPE_WIRED = 2;
114     public static final int TYPE_SPEAKER = 3;
115     public static final int TYPE_DOCK = 4;
116     public static final int TYPE_BLUETOOTH_SCO = 5;
117     public static final int TYPE_BLUETOOTH_HA = 6;
118     public static final int TYPE_BLUETOOTH_LE = 7;
119     public static final int TYPE_STREAMING = 8;
120     @IntDef(prefix = "TYPE", value = {
121             TYPE_INVALID,
122             TYPE_EARPIECE,
123             TYPE_WIRED,
124             TYPE_SPEAKER,
125             TYPE_DOCK,
126             TYPE_BLUETOOTH_SCO,
127             TYPE_BLUETOOTH_HA,
128             TYPE_BLUETOOTH_LE,
129             TYPE_STREAMING
130     })
131     @Retention(RetentionPolicy.SOURCE)
132     public @interface AudioRouteType {}
133 
134     private @AudioRouteType int mAudioRouteType;
135     private String mBluetoothAddress;
136     private AudioDeviceInfo mInfo;
137     public static final Set<Integer> BT_AUDIO_DEVICE_INFO_TYPES = Set.of(
138             AudioDeviceInfo.TYPE_BLE_HEADSET,
139             AudioDeviceInfo.TYPE_BLE_SPEAKER,
140             AudioDeviceInfo.TYPE_BLE_BROADCAST,
141             AudioDeviceInfo.TYPE_HEARING_AID,
142             AudioDeviceInfo.TYPE_BLUETOOTH_SCO
143     );
144 
145     public static final Set<Integer> BT_AUDIO_ROUTE_TYPES = Set.of(
146             AudioRoute.TYPE_BLUETOOTH_SCO,
147             AudioRoute.TYPE_BLUETOOTH_HA,
148             AudioRoute.TYPE_BLUETOOTH_LE
149     );
150 
151     public static final HashMap<Integer, String> DEVICE_TYPE_STRINGS;
152     static {
153         DEVICE_TYPE_STRINGS = new HashMap<>();
DEVICE_TYPE_STRINGS.put(TYPE_EARPIECE, "TYPE_EARPIECE")154         DEVICE_TYPE_STRINGS.put(TYPE_EARPIECE, "TYPE_EARPIECE");
DEVICE_TYPE_STRINGS.put(TYPE_WIRED, "TYPE_WIRED_HEADSET")155         DEVICE_TYPE_STRINGS.put(TYPE_WIRED, "TYPE_WIRED_HEADSET");
DEVICE_TYPE_STRINGS.put(TYPE_SPEAKER, "TYPE_SPEAKER")156         DEVICE_TYPE_STRINGS.put(TYPE_SPEAKER, "TYPE_SPEAKER");
DEVICE_TYPE_STRINGS.put(TYPE_DOCK, "TYPE_DOCK")157         DEVICE_TYPE_STRINGS.put(TYPE_DOCK, "TYPE_DOCK");
DEVICE_TYPE_STRINGS.put(TYPE_BLUETOOTH_SCO, "TYPE_BLUETOOTH_SCO")158         DEVICE_TYPE_STRINGS.put(TYPE_BLUETOOTH_SCO, "TYPE_BLUETOOTH_SCO");
DEVICE_TYPE_STRINGS.put(TYPE_BLUETOOTH_HA, "TYPE_BLUETOOTH_HA")159         DEVICE_TYPE_STRINGS.put(TYPE_BLUETOOTH_HA, "TYPE_BLUETOOTH_HA");
DEVICE_TYPE_STRINGS.put(TYPE_BLUETOOTH_LE, "TYPE_BLUETOOTH_LE")160         DEVICE_TYPE_STRINGS.put(TYPE_BLUETOOTH_LE, "TYPE_BLUETOOTH_LE");
DEVICE_TYPE_STRINGS.put(TYPE_STREAMING, "TYPE_STREAMING")161         DEVICE_TYPE_STRINGS.put(TYPE_STREAMING, "TYPE_STREAMING");
162     }
163 
164     public static final HashMap<Integer, Integer> DEVICE_INFO_TYPETO_AUDIO_ROUTE_TYPE;
165     static {
166         DEVICE_INFO_TYPETO_AUDIO_ROUTE_TYPE = new HashMap<>();
DEVICE_INFO_TYPETO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_BUILTIN_EARPIECE, TYPE_EARPIECE)167         DEVICE_INFO_TYPETO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_BUILTIN_EARPIECE,
168                 TYPE_EARPIECE);
DEVICE_INFO_TYPETO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, TYPE_SPEAKER)169         DEVICE_INFO_TYPETO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, TYPE_SPEAKER);
DEVICE_INFO_TYPETO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_WIRED_HEADSET, TYPE_WIRED)170         DEVICE_INFO_TYPETO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_WIRED_HEADSET, TYPE_WIRED);
DEVICE_INFO_TYPETO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_WIRED_HEADPHONES, TYPE_WIRED)171         DEVICE_INFO_TYPETO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_WIRED_HEADPHONES, TYPE_WIRED);
DEVICE_INFO_TYPETO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_BLUETOOTH_SCO, TYPE_BLUETOOTH_SCO)172         DEVICE_INFO_TYPETO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_BLUETOOTH_SCO,
173                 TYPE_BLUETOOTH_SCO);
DEVICE_INFO_TYPETO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_USB_DEVICE, TYPE_WIRED)174         DEVICE_INFO_TYPETO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_USB_DEVICE, TYPE_WIRED);
DEVICE_INFO_TYPETO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_USB_ACCESSORY, TYPE_WIRED)175         DEVICE_INFO_TYPETO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_USB_ACCESSORY, TYPE_WIRED);
DEVICE_INFO_TYPETO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_DOCK, TYPE_DOCK)176         DEVICE_INFO_TYPETO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_DOCK, TYPE_DOCK);
DEVICE_INFO_TYPETO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_USB_HEADSET, TYPE_WIRED)177         DEVICE_INFO_TYPETO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_USB_HEADSET, TYPE_WIRED);
DEVICE_INFO_TYPETO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_HEARING_AID, TYPE_BLUETOOTH_HA)178         DEVICE_INFO_TYPETO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_HEARING_AID,
179                 TYPE_BLUETOOTH_HA);
DEVICE_INFO_TYPETO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_BLE_HEADSET, TYPE_BLUETOOTH_LE)180         DEVICE_INFO_TYPETO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_BLE_HEADSET,
181                 TYPE_BLUETOOTH_LE);
DEVICE_INFO_TYPETO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_BLE_SPEAKER, TYPE_BLUETOOTH_LE)182         DEVICE_INFO_TYPETO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_BLE_SPEAKER,
183                 TYPE_BLUETOOTH_LE);
DEVICE_INFO_TYPETO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_BLE_BROADCAST, TYPE_BLUETOOTH_LE)184         DEVICE_INFO_TYPETO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_BLE_BROADCAST,
185                 TYPE_BLUETOOTH_LE);
DEVICE_INFO_TYPETO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_DOCK_ANALOG, TYPE_DOCK)186         DEVICE_INFO_TYPETO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_DOCK_ANALOG, TYPE_DOCK);
187     }
188 
189     private static final HashMap<Integer, List<Integer>> AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE;
190     static {
191         AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE = new HashMap<>();
192         List<Integer> earpieceDeviceInfoTypes = new ArrayList<>();
193         earpieceDeviceInfoTypes.add(AudioDeviceInfo.TYPE_BUILTIN_EARPIECE);
AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE.put(TYPE_EARPIECE, earpieceDeviceInfoTypes)194         AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE.put(TYPE_EARPIECE, earpieceDeviceInfoTypes);
195 
196         List<Integer> wiredDeviceInfoTypes = new ArrayList<>();
197         wiredDeviceInfoTypes.add(AudioDeviceInfo.TYPE_WIRED_HEADSET);
198         wiredDeviceInfoTypes.add(AudioDeviceInfo.TYPE_WIRED_HEADPHONES);
199         wiredDeviceInfoTypes.add(AudioDeviceInfo.TYPE_USB_DEVICE);
200         wiredDeviceInfoTypes.add(AudioDeviceInfo.TYPE_USB_ACCESSORY);
201         wiredDeviceInfoTypes.add(AudioDeviceInfo.TYPE_USB_HEADSET);
AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE.put(TYPE_WIRED, wiredDeviceInfoTypes)202         AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE.put(TYPE_WIRED, wiredDeviceInfoTypes);
203 
204         List<Integer> speakerDeviceInfoTypes = new ArrayList<>();
205         speakerDeviceInfoTypes.add(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER);
AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE.put(TYPE_SPEAKER, speakerDeviceInfoTypes)206         AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE.put(TYPE_SPEAKER, speakerDeviceInfoTypes);
207 
208         List<Integer> dockDeviceInfoTypes = new ArrayList<>();
209         dockDeviceInfoTypes.add(AudioDeviceInfo.TYPE_DOCK);
210         dockDeviceInfoTypes.add(AudioDeviceInfo.TYPE_DOCK_ANALOG);
AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE.put(TYPE_DOCK, dockDeviceInfoTypes)211         AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE.put(TYPE_DOCK, dockDeviceInfoTypes);
212 
213         List<Integer> bluetoothScoDeviceInfoTypes = new ArrayList<>();
214         bluetoothScoDeviceInfoTypes.add(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP);
215         bluetoothScoDeviceInfoTypes.add(AudioDeviceInfo.TYPE_BLUETOOTH_SCO);
AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE.put(TYPE_BLUETOOTH_SCO, bluetoothScoDeviceInfoTypes)216         AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE.put(TYPE_BLUETOOTH_SCO, bluetoothScoDeviceInfoTypes);
217 
218         List<Integer> bluetoothHearingAidDeviceInfoTypes = new ArrayList<>();
219         bluetoothHearingAidDeviceInfoTypes.add(AudioDeviceInfo.TYPE_HEARING_AID);
AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE.put(TYPE_BLUETOOTH_HA, bluetoothHearingAidDeviceInfoTypes)220         AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE.put(TYPE_BLUETOOTH_HA,
221                 bluetoothHearingAidDeviceInfoTypes);
222 
223         List<Integer> bluetoothLeDeviceInfoTypes = new ArrayList<>();
224         bluetoothLeDeviceInfoTypes.add(AudioDeviceInfo.TYPE_BLE_HEADSET);
225         bluetoothLeDeviceInfoTypes.add(AudioDeviceInfo.TYPE_BLE_SPEAKER);
226         bluetoothLeDeviceInfoTypes.add(AudioDeviceInfo.TYPE_BLE_BROADCAST);
AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE.put(TYPE_BLUETOOTH_LE, bluetoothLeDeviceInfoTypes)227         AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE.put(TYPE_BLUETOOTH_LE, bluetoothLeDeviceInfoTypes);
228     }
229 
getType()230     public int getType() {
231         return mAudioRouteType;
232     }
233 
getBluetoothAddress()234     String getBluetoothAddress() {
235         return mBluetoothAddress;
236     }
237 
238     // Invoked when entered pending route whose dest route is this route
onDestRouteAsPendingRoute(boolean active, PendingAudioRoute pendingAudioRoute, BluetoothDevice device, AudioManager audioManager, BluetoothRouteManager bluetoothRouteManager, boolean isScoAudioConnected)239     void onDestRouteAsPendingRoute(boolean active, PendingAudioRoute pendingAudioRoute,
240             BluetoothDevice device, AudioManager audioManager,
241             BluetoothRouteManager bluetoothRouteManager, boolean isScoAudioConnected) {
242         Log.i(this, "onDestRouteAsPendingRoute: active (%b), type (%d)", active, mAudioRouteType);
243         if (pendingAudioRoute.isActive() && !active) {
244             clearCommunicationDevice(pendingAudioRoute, bluetoothRouteManager, audioManager);
245         } else if (active) {
246             // Handle BT routing case.
247             if (BT_AUDIO_ROUTE_TYPES.contains(mAudioRouteType)) {
248                 boolean connectedBtAudio = connectBtAudio(pendingAudioRoute, device,
249                         audioManager, bluetoothRouteManager);
250                 // Special handling for SCO case.
251                 if (mAudioRouteType == TYPE_BLUETOOTH_SCO) {
252                     // Check if the communication device was set for the device, even if
253                     // BluetoothHeadset#connectAudio reports that the SCO connection wasn't
254                     // successfully established.
255                     if (connectedBtAudio || isScoAudioConnected) {
256                         pendingAudioRoute.setCommunicationDeviceType(mAudioRouteType);
257                         if (!isScoAudioConnected) {
258                             pendingAudioRoute.addMessage(BT_AUDIO_CONNECTED, mBluetoothAddress);
259                         }
260                     } else {
261                         pendingAudioRoute.onMessageReceived(new Pair<>(PENDING_ROUTE_FAILED,
262                                 mBluetoothAddress), mBluetoothAddress);
263                     }
264                     return;
265                 }
266             } else if (mAudioRouteType == TYPE_SPEAKER) {
267                 pendingAudioRoute.addMessage(SPEAKER_ON, null);
268             }
269 
270             boolean result = false;
271             List<AudioDeviceInfo> devices = audioManager.getAvailableCommunicationDevices();
272             for (AudioDeviceInfo deviceInfo : devices) {
273                 // It's possible for the AudioDeviceInfo to be updated for the BT device so adjust
274                 // mInfo accordingly.
275                 if (BT_AUDIO_ROUTE_TYPES.contains(mAudioRouteType) && mBluetoothAddress
276                         .equals(deviceInfo.getAddress())) {
277                     mInfo = deviceInfo;
278                 }
279                 if (deviceInfo.equals(mInfo)) {
280                     result = audioManager.setCommunicationDevice(mInfo);
281                     if (result) {
282                         pendingAudioRoute.setCommunicationDeviceType(mAudioRouteType);
283                     }
284                     Log.i(this, "Result of setting communication device for audio "
285                             + "route (%s) - %b", this, result);
286                     break;
287                 }
288             }
289 
290             // It's possible that BluetoothStateReceiver needs to report that the device is active
291             // before being able to successfully set the communication device. Refrain from sending
292             // pending route failed message for BT route until the second attempt fails.
293             if (!result && !BT_AUDIO_ROUTE_TYPES.contains(mAudioRouteType)) {
294                 pendingAudioRoute.onMessageReceived(new Pair<>(PENDING_ROUTE_FAILED, null), null);
295             }
296         }
297     }
298 
299     // Takes care of cleaning up original audio route (i.e. clearCommunicationDevice,
300     // sending SPEAKER_OFF, or disconnecting SCO).
onOrigRouteAsPendingRoute(boolean active, PendingAudioRoute pendingAudioRoute, AudioManager audioManager, BluetoothRouteManager bluetoothRouteManager)301     void onOrigRouteAsPendingRoute(boolean active, PendingAudioRoute pendingAudioRoute,
302             AudioManager audioManager, BluetoothRouteManager bluetoothRouteManager) {
303         Log.i(this, "onOrigRouteAsPendingRoute: active (%b), type (%d)", active, mAudioRouteType);
304         if (active) {
305             if (mAudioRouteType == TYPE_SPEAKER) {
306                 pendingAudioRoute.addMessage(SPEAKER_OFF, null);
307             }
308             int result = clearCommunicationDevice(pendingAudioRoute, bluetoothRouteManager,
309                     audioManager);
310             // Only send BT_AUDIO_DISCONNECTED for SCO if disconnect was successful.
311             if (mAudioRouteType == TYPE_BLUETOOTH_SCO && result == BluetoothStatusCodes.SUCCESS) {
312                 pendingAudioRoute.addMessage(BT_AUDIO_DISCONNECTED, mBluetoothAddress);
313             }
314         }
315     }
316 
317     @VisibleForTesting
AudioRoute(@udioRouteType int type, String bluetoothAddress, AudioDeviceInfo info)318     public AudioRoute(@AudioRouteType int type, String bluetoothAddress, AudioDeviceInfo info) {
319         mAudioRouteType = type;
320         mBluetoothAddress = bluetoothAddress;
321         mInfo = info;
322     }
323 
324     @Override
equals(Object obj)325     public boolean equals(Object obj) {
326         if (obj == null) {
327             return false;
328         }
329         if (!(obj instanceof AudioRoute otherRoute)) {
330             return false;
331         }
332         if (mAudioRouteType != otherRoute.getType()) {
333             return false;
334         }
335         return !BT_AUDIO_ROUTE_TYPES.contains(mAudioRouteType) || mBluetoothAddress.equals(
336                 otherRoute.getBluetoothAddress());
337     }
338 
339     @Override
hashCode()340     public int hashCode() {
341         return Objects.hash(mAudioRouteType, mBluetoothAddress);
342     }
343 
344     @Override
toString()345     public String toString() {
346         return getClass().getSimpleName() + "[Type=" + DEVICE_TYPE_STRINGS.get(mAudioRouteType)
347                 + ", Address=" + ((mBluetoothAddress != null) ? mBluetoothAddress : "invalid")
348                 + "]";
349     }
350 
connectBtAudio(PendingAudioRoute pendingAudioRoute, BluetoothDevice device, AudioManager audioManager, BluetoothRouteManager bluetoothRouteManager)351     private boolean connectBtAudio(PendingAudioRoute pendingAudioRoute, BluetoothDevice device,
352             AudioManager audioManager, BluetoothRouteManager bluetoothRouteManager) {
353         // Ensure that if another BT device was set, it is disconnected before connecting
354         // the new one.
355         AudioRoute currentRoute = pendingAudioRoute.getOrigRoute();
356         if (currentRoute.getBluetoothAddress() != null &&
357                 !currentRoute.getBluetoothAddress().equals(device.getAddress())) {
358             clearCommunicationDevice(pendingAudioRoute, bluetoothRouteManager, audioManager);
359         }
360 
361         // Connect to the device (explicit handling for HFP devices).
362         boolean success = false;
363         if (device != null) {
364             success = bluetoothRouteManager.getDeviceManager()
365                     .connectAudio(device, mAudioRouteType);
366         }
367 
368         Log.i(this, "connectBtAudio: routeToConnectTo = %s, successful = %b",
369                 this, success);
370         return success;
371     }
372 
clearCommunicationDevice(PendingAudioRoute pendingAudioRoute, BluetoothRouteManager bluetoothRouteManager, AudioManager audioManager)373     int clearCommunicationDevice(PendingAudioRoute pendingAudioRoute,
374             BluetoothRouteManager bluetoothRouteManager, AudioManager audioManager) {
375         // Try to see if there's a previously set device for communication that should be cleared.
376         // This only serves to help in the SCO case to ensure that we disconnect the headset.
377         if (pendingAudioRoute.getCommunicationDeviceType() == AudioRoute.TYPE_INVALID) {
378             return -1;
379         }
380 
381         int result = BluetoothStatusCodes.SUCCESS;
382         if (pendingAudioRoute.getCommunicationDeviceType() == TYPE_BLUETOOTH_SCO) {
383             Log.i(this, "Disconnecting SCO device.");
384             result = bluetoothRouteManager.getDeviceManager().disconnectSco();
385         } else {
386             Log.i(this, "Clearing communication device for audio type %d.",
387                     pendingAudioRoute.getCommunicationDeviceType());
388             audioManager.clearCommunicationDevice();
389         }
390 
391         if (result == BluetoothStatusCodes.SUCCESS) {
392             pendingAudioRoute.setCommunicationDeviceType(AudioRoute.TYPE_INVALID);
393         }
394         return result;
395     }
396 }
397