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.AudioRoute.BT_AUDIO_ROUTE_TYPES;
20 import static com.android.server.telecom.AudioRoute.TYPE_INVALID;
21 import static com.android.server.telecom.AudioRoute.TYPE_SPEAKER;
22 
23 import android.bluetooth.BluetoothAdapter;
24 import android.bluetooth.BluetoothDevice;
25 import android.bluetooth.BluetoothLeAudio;
26 import android.bluetooth.BluetoothProfile;
27 import android.content.BroadcastReceiver;
28 import android.content.Context;
29 import android.content.Intent;
30 import android.content.IntentFilter;
31 import android.media.AudioAttributes;
32 import android.media.AudioDeviceAttributes;
33 import android.media.AudioDeviceInfo;
34 import android.media.AudioManager;
35 import android.media.IAudioService;
36 import android.media.audiopolicy.AudioProductStrategy;
37 import android.os.Handler;
38 import android.os.HandlerThread;
39 import android.os.Message;
40 import android.os.RemoteException;
41 import android.telecom.CallAudioState;
42 import android.telecom.Log;
43 import android.telecom.Logging.Session;
44 import android.telecom.VideoProfile;
45 import android.util.ArrayMap;
46 import android.util.Pair;
47 
48 import androidx.annotation.NonNull;
49 
50 import com.android.internal.annotations.VisibleForTesting;
51 import com.android.internal.os.SomeArgs;
52 import com.android.internal.util.IndentingPrintWriter;
53 import com.android.server.telecom.bluetooth.BluetoothRouteManager;
54 import com.android.server.telecom.flags.FeatureFlags;
55 
56 import java.util.HashMap;
57 import java.util.HashSet;
58 import java.util.LinkedHashMap;
59 import java.util.List;
60 import java.util.Map;
61 import java.util.Objects;
62 import java.util.Set;
63 
64 public class CallAudioRouteController implements CallAudioRouteAdapter {
65     private static final long TIMEOUT_LIMIT = 2000L;
66     private static final AudioRoute DUMMY_ROUTE = new AudioRoute(TYPE_INVALID, null, null);
67     private static final Map<Integer, Integer> ROUTE_MAP;
68     static {
69         ROUTE_MAP = new ArrayMap<>();
ROUTE_MAP.put(AudioRoute.TYPE_EARPIECE, CallAudioState.ROUTE_EARPIECE)70         ROUTE_MAP.put(AudioRoute.TYPE_EARPIECE, CallAudioState.ROUTE_EARPIECE);
ROUTE_MAP.put(AudioRoute.TYPE_WIRED, CallAudioState.ROUTE_WIRED_HEADSET)71         ROUTE_MAP.put(AudioRoute.TYPE_WIRED, CallAudioState.ROUTE_WIRED_HEADSET);
ROUTE_MAP.put(AudioRoute.TYPE_SPEAKER, CallAudioState.ROUTE_SPEAKER)72         ROUTE_MAP.put(AudioRoute.TYPE_SPEAKER, CallAudioState.ROUTE_SPEAKER);
ROUTE_MAP.put(AudioRoute.TYPE_DOCK, CallAudioState.ROUTE_SPEAKER)73         ROUTE_MAP.put(AudioRoute.TYPE_DOCK, CallAudioState.ROUTE_SPEAKER);
ROUTE_MAP.put(AudioRoute.TYPE_BLUETOOTH_SCO, CallAudioState.ROUTE_BLUETOOTH)74         ROUTE_MAP.put(AudioRoute.TYPE_BLUETOOTH_SCO, CallAudioState.ROUTE_BLUETOOTH);
ROUTE_MAP.put(AudioRoute.TYPE_BLUETOOTH_HA, CallAudioState.ROUTE_BLUETOOTH)75         ROUTE_MAP.put(AudioRoute.TYPE_BLUETOOTH_HA, CallAudioState.ROUTE_BLUETOOTH);
ROUTE_MAP.put(AudioRoute.TYPE_BLUETOOTH_LE, CallAudioState.ROUTE_BLUETOOTH)76         ROUTE_MAP.put(AudioRoute.TYPE_BLUETOOTH_LE, CallAudioState.ROUTE_BLUETOOTH);
ROUTE_MAP.put(AudioRoute.TYPE_STREAMING, CallAudioState.ROUTE_STREAMING)77         ROUTE_MAP.put(AudioRoute.TYPE_STREAMING, CallAudioState.ROUTE_STREAMING);
78     }
79 
80     /** Valid values for the first argument for SWITCH_BASELINE_ROUTE */
81     public static final int INCLUDE_BLUETOOTH_IN_BASELINE = 1;
82 
83     private final CallsManager mCallsManager;
84     private final Context mContext;
85     private AudioManager mAudioManager;
86     private CallAudioManager mCallAudioManager;
87     private final BluetoothRouteManager mBluetoothRouteManager;
88     private final CallAudioManager.AudioServiceFactory mAudioServiceFactory;
89     private final Handler mHandler;
90     private final WiredHeadsetManager mWiredHeadsetManager;
91     private Set<AudioRoute> mAvailableRoutes;
92     private AudioRoute mCurrentRoute;
93     private AudioRoute mEarpieceWiredRoute;
94     private AudioRoute mSpeakerDockRoute;
95     private AudioRoute mStreamingRoute;
96     private Set<AudioRoute> mStreamingRoutes;
97     private Map<AudioRoute, BluetoothDevice> mBluetoothRoutes;
98     private Pair<Integer, String> mActiveBluetoothDevice;
99     private Map<Integer, String> mActiveDeviceCache;
100     private Map<Integer, AudioRoute> mTypeRoutes;
101     private PendingAudioRoute mPendingAudioRoute;
102     private AudioRoute.Factory mAudioRouteFactory;
103     private StatusBarNotifier mStatusBarNotifier;
104     private FeatureFlags mFeatureFlags;
105     private int mFocusType;
106     private boolean mIsScoAudioConnected;
107     private final Object mLock = new Object();
108     private final TelecomSystem.SyncRoot mTelecomLock;
109     private final BroadcastReceiver mSpeakerPhoneChangeReceiver = new BroadcastReceiver() {
110         @Override
111         public void onReceive(Context context, Intent intent) {
112             Log.startSession("CARC.mSPCR");
113             try {
114                 if (AudioManager.ACTION_SPEAKERPHONE_STATE_CHANGED.equals(intent.getAction())) {
115                     if (mAudioManager != null) {
116                         AudioDeviceInfo info = mAudioManager.getCommunicationDevice();
117                         if ((info != null) &&
118                                 (info.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER)) {
119                             if (mCurrentRoute.getType() != AudioRoute.TYPE_SPEAKER) {
120                                 sendMessageWithSessionInfo(SPEAKER_ON);
121                             }
122                         } else {
123                             sendMessageWithSessionInfo(SPEAKER_OFF);
124                         }
125                     }
126                 } else {
127                     Log.w(this, "Received non-speakerphone-change intent");
128                 }
129             } finally {
130                 Log.endSession();
131             }
132         }
133     };
134     private final BroadcastReceiver mMuteChangeReceiver = new BroadcastReceiver() {
135         @Override
136         public void onReceive(Context context, Intent intent) {
137             Log.startSession("CARC.mCR");
138             try {
139                 if (AudioManager.ACTION_MICROPHONE_MUTE_CHANGED.equals(intent.getAction())) {
140                     if (mCallsManager.isInEmergencyCall()) {
141                         Log.i(this, "Mute was externally changed when there's an emergency call. "
142                                 + "Forcing mute back off.");
143                         sendMessageWithSessionInfo(MUTE_OFF);
144                     } else {
145                         sendMessageWithSessionInfo(MUTE_EXTERNALLY_CHANGED);
146                     }
147                 } else if (AudioManager.STREAM_MUTE_CHANGED_ACTION.equals(intent.getAction())) {
148                     int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
149                     boolean isStreamMuted = intent.getBooleanExtra(
150                             AudioManager.EXTRA_STREAM_VOLUME_MUTED, false);
151 
152                     if (streamType == AudioManager.STREAM_RING && !isStreamMuted
153                             && mCallAudioManager != null) {
154                         Log.i(this, "Ring stream was un-muted.");
155                         mCallAudioManager.onRingerModeChange();
156                     }
157                 } else {
158                     Log.w(this, "Received non-mute-change intent");
159                 }
160             } finally {
161                 Log.endSession();
162             }
163         }
164     };
165     private CallAudioState mCallAudioState;
166     private boolean mIsMute;
167     private boolean mIsPending;
168     private boolean mIsActive;
169 
CallAudioRouteController( Context context, CallsManager callsManager, CallAudioManager.AudioServiceFactory audioServiceFactory, AudioRoute.Factory audioRouteFactory, WiredHeadsetManager wiredHeadsetManager, BluetoothRouteManager bluetoothRouteManager, StatusBarNotifier statusBarNotifier, FeatureFlags featureFlags)170     public CallAudioRouteController(
171             Context context, CallsManager callsManager,
172             CallAudioManager.AudioServiceFactory audioServiceFactory,
173             AudioRoute.Factory audioRouteFactory, WiredHeadsetManager wiredHeadsetManager,
174             BluetoothRouteManager bluetoothRouteManager, StatusBarNotifier statusBarNotifier,
175             FeatureFlags featureFlags) {
176         mContext = context;
177         mCallsManager = callsManager;
178         mAudioManager = context.getSystemService(AudioManager.class);
179         mAudioServiceFactory = audioServiceFactory;
180         mAudioRouteFactory = audioRouteFactory;
181         mWiredHeadsetManager = wiredHeadsetManager;
182         mIsMute = false;
183         mBluetoothRouteManager = bluetoothRouteManager;
184         mStatusBarNotifier = statusBarNotifier;
185         mFeatureFlags = featureFlags;
186         mFocusType = NO_FOCUS;
187         mIsScoAudioConnected = false;
188         mTelecomLock = callsManager.getLock();
189         HandlerThread handlerThread = new HandlerThread(this.getClass().getSimpleName());
190         handlerThread.start();
191 
192         // Register broadcast receivers
193         IntentFilter speakerChangedFilter = new IntentFilter(
194                 AudioManager.ACTION_SPEAKERPHONE_STATE_CHANGED);
195         speakerChangedFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
196         context.registerReceiver(mSpeakerPhoneChangeReceiver, speakerChangedFilter);
197 
198         IntentFilter micMuteChangedFilter = new IntentFilter(
199                 AudioManager.ACTION_MICROPHONE_MUTE_CHANGED);
200         micMuteChangedFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
201         context.registerReceiver(mMuteChangeReceiver, micMuteChangedFilter);
202 
203         IntentFilter muteChangedFilter = new IntentFilter(AudioManager.STREAM_MUTE_CHANGED_ACTION);
204         muteChangedFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
205         context.registerReceiver(mMuteChangeReceiver, muteChangedFilter);
206 
207         // Create handler
208         mHandler = new Handler(handlerThread.getLooper()) {
209             @Override
210             public void handleMessage(@NonNull Message msg) {
211                 synchronized (this) {
212                     preHandleMessage(msg);
213                     String address;
214                     BluetoothDevice bluetoothDevice;
215                     int focus;
216                     @AudioRoute.AudioRouteType int type;
217                     switch (msg.what) {
218                         case CONNECT_WIRED_HEADSET:
219                             handleWiredHeadsetConnected();
220                             break;
221                         case DISCONNECT_WIRED_HEADSET:
222                             handleWiredHeadsetDisconnected();
223                             break;
224                         case CONNECT_DOCK:
225                             handleDockConnected();
226                             break;
227                         case DISCONNECT_DOCK:
228                             handleDockDisconnected();
229                             break;
230                         case BLUETOOTH_DEVICE_LIST_CHANGED:
231                             break;
232                         case BT_ACTIVE_DEVICE_PRESENT:
233                             type = msg.arg1;
234                             address = (String) ((SomeArgs) msg.obj).arg2;
235                             handleBtActiveDevicePresent(type, address);
236                             break;
237                         case BT_ACTIVE_DEVICE_GONE:
238                             type = msg.arg1;
239                             handleBtActiveDeviceGone(type);
240                             break;
241                         case BT_DEVICE_ADDED:
242                             type = msg.arg1;
243                             bluetoothDevice = (BluetoothDevice) ((SomeArgs) msg.obj).arg2;
244                             handleBtConnected(type, bluetoothDevice);
245                             break;
246                         case BT_DEVICE_REMOVED:
247                             type = msg.arg1;
248                             bluetoothDevice = (BluetoothDevice) ((SomeArgs) msg.obj).arg2;
249                             handleBtDisconnected(type, bluetoothDevice);
250                             break;
251                         case SWITCH_EARPIECE:
252                         case USER_SWITCH_EARPIECE:
253                             handleSwitchEarpiece();
254                             break;
255                         case SWITCH_BLUETOOTH:
256                         case USER_SWITCH_BLUETOOTH:
257                             address = (String) ((SomeArgs) msg.obj).arg2;
258                             handleSwitchBluetooth(address);
259                             break;
260                         case SWITCH_HEADSET:
261                         case USER_SWITCH_HEADSET:
262                             handleSwitchHeadset();
263                             break;
264                         case SWITCH_SPEAKER:
265                         case USER_SWITCH_SPEAKER:
266                             handleSwitchSpeaker();
267                             break;
268                         case SWITCH_BASELINE_ROUTE:
269                             address = (String) ((SomeArgs) msg.obj).arg2;
270                             handleSwitchBaselineRoute(msg.arg1 == INCLUDE_BLUETOOTH_IN_BASELINE,
271                                     address);
272                             break;
273                         case USER_SWITCH_BASELINE_ROUTE:
274                             handleSwitchBaselineRoute(msg.arg1 == INCLUDE_BLUETOOTH_IN_BASELINE,
275                                     null);
276                             break;
277                         case SPEAKER_ON:
278                             handleSpeakerOn();
279                             break;
280                         case SPEAKER_OFF:
281                             handleSpeakerOff();
282                             break;
283                         case STREAMING_FORCE_ENABLED:
284                             handleStreamingEnabled();
285                             break;
286                         case STREAMING_FORCE_DISABLED:
287                             handleStreamingDisabled();
288                             break;
289                         case BT_AUDIO_CONNECTED:
290                             bluetoothDevice = (BluetoothDevice) ((SomeArgs) msg.obj).arg2;
291                             handleBtAudioActive(bluetoothDevice);
292                             break;
293                         case BT_AUDIO_DISCONNECTED:
294                             bluetoothDevice = (BluetoothDevice) ((SomeArgs) msg.obj).arg2;
295                             handleBtAudioInactive(bluetoothDevice);
296                             break;
297                         case MUTE_ON:
298                             handleMuteChanged(true);
299                             break;
300                         case MUTE_OFF:
301                             handleMuteChanged(false);
302                             break;
303                         case MUTE_EXTERNALLY_CHANGED:
304                             handleMuteChanged(mAudioManager.isMicrophoneMute());
305                             break;
306                         case SWITCH_FOCUS:
307                             focus = msg.arg1;
308                             handleSwitchFocus(focus);
309                             break;
310                         case EXIT_PENDING_ROUTE:
311                             handleExitPendingRoute();
312                             break;
313                         default:
314                             break;
315                     }
316                     postHandleMessage(msg);
317                 }
318             }
319         };
320     }
321     @Override
initialize()322     public void initialize() {
323         mAvailableRoutes = new HashSet<>();
324         mBluetoothRoutes = new LinkedHashMap<>();
325         mActiveDeviceCache = new HashMap<>();
326         mActiveDeviceCache.put(AudioRoute.TYPE_BLUETOOTH_SCO, null);
327         mActiveDeviceCache.put(AudioRoute.TYPE_BLUETOOTH_HA, null);
328         mActiveDeviceCache.put(AudioRoute.TYPE_BLUETOOTH_LE, null);
329         mActiveBluetoothDevice = null;
330         mTypeRoutes = new ArrayMap<>();
331         mStreamingRoutes = new HashSet<>();
332         mPendingAudioRoute = new PendingAudioRoute(this, mAudioManager, mBluetoothRouteManager);
333         mStreamingRoute = new AudioRoute(AudioRoute.TYPE_STREAMING, null, null);
334         mStreamingRoutes.add(mStreamingRoute);
335 
336         int supportMask = calculateSupportedRouteMaskInit();
337         if ((supportMask & CallAudioState.ROUTE_SPEAKER) != 0) {
338             // Create speaker routes
339             mSpeakerDockRoute = mAudioRouteFactory.create(AudioRoute.TYPE_SPEAKER, null,
340                     mAudioManager);
341             if (mSpeakerDockRoute == null) {
342                 Log.w(this, "Can't find available audio device info for route TYPE_SPEAKER");
343             } else {
344                 mTypeRoutes.put(AudioRoute.TYPE_SPEAKER, mSpeakerDockRoute);
345                 mAvailableRoutes.add(mSpeakerDockRoute);
346             }
347         }
348 
349         if ((supportMask & CallAudioState.ROUTE_WIRED_HEADSET) != 0) {
350             // Create wired headset routes
351             mEarpieceWiredRoute = mAudioRouteFactory.create(AudioRoute.TYPE_WIRED, null,
352                     mAudioManager);
353             if (mEarpieceWiredRoute == null) {
354                 Log.w(this, "Can't find available audio device info for route TYPE_WIRED_HEADSET");
355             } else {
356                 mTypeRoutes.put(AudioRoute.TYPE_WIRED, mEarpieceWiredRoute);
357                 mAvailableRoutes.add(mEarpieceWiredRoute);
358             }
359         } else if ((supportMask & CallAudioState.ROUTE_EARPIECE) != 0) {
360             // Create earpiece routes
361             mEarpieceWiredRoute = mAudioRouteFactory.create(AudioRoute.TYPE_EARPIECE, null,
362                     mAudioManager);
363             if (mEarpieceWiredRoute == null) {
364                 Log.w(this, "Can't find available audio device info for route TYPE_EARPIECE");
365             } else {
366                 mTypeRoutes.put(AudioRoute.TYPE_EARPIECE, mEarpieceWiredRoute);
367                 mAvailableRoutes.add(mEarpieceWiredRoute);
368             }
369         }
370 
371         // set current route
372         if (mEarpieceWiredRoute != null) {
373             mCurrentRoute = mEarpieceWiredRoute;
374         } else {
375             mCurrentRoute = mSpeakerDockRoute;
376         }
377         mIsActive = false;
378         mCallAudioState = new CallAudioState(mIsMute, ROUTE_MAP.get(mCurrentRoute.getType()),
379                 supportMask, null, new HashSet<>());
380     }
381 
382     @Override
sendMessageWithSessionInfo(int message)383     public void sendMessageWithSessionInfo(int message) {
384         sendMessageWithSessionInfo(message, 0, (String) null);
385     }
386 
387     @Override
sendMessageWithSessionInfo(int message, int arg)388     public void sendMessageWithSessionInfo(int message, int arg) {
389         sendMessageWithSessionInfo(message, arg, (String) null);
390     }
391 
392     @Override
sendMessageWithSessionInfo(int message, int arg, String data)393     public void sendMessageWithSessionInfo(int message, int arg, String data) {
394         SomeArgs args = SomeArgs.obtain();
395         args.arg1 = Log.createSubsession();
396         args.arg2 = data;
397         sendMessage(message, arg, 0, args);
398     }
399 
400     @Override
sendMessageWithSessionInfo(int message, int arg, BluetoothDevice bluetoothDevice)401     public void sendMessageWithSessionInfo(int message, int arg, BluetoothDevice bluetoothDevice) {
402         SomeArgs args = SomeArgs.obtain();
403         args.arg1 = Log.createSubsession();
404         args.arg2 = bluetoothDevice;
405         sendMessage(message, arg, 0, args);
406     }
407 
408     @Override
sendMessage(int message, Runnable r)409     public void sendMessage(int message, Runnable r) {
410         r.run();
411     }
412 
sendMessage(int what, int arg1, int arg2, Object obj)413     private void sendMessage(int what, int arg1, int arg2, Object obj) {
414         mHandler.sendMessage(Message.obtain(mHandler, what, arg1, arg2, obj));
415     }
416 
417     @Override
setCallAudioManager(CallAudioManager callAudioManager)418     public void setCallAudioManager(CallAudioManager callAudioManager) {
419         mCallAudioManager = callAudioManager;
420     }
421 
422     @Override
getCurrentCallAudioState()423     public CallAudioState getCurrentCallAudioState() {
424         return mCallAudioState;
425     }
426 
427     @Override
isHfpDeviceAvailable()428     public boolean isHfpDeviceAvailable() {
429         return !mBluetoothRoutes.isEmpty();
430     }
431 
432     @Override
getAdapterHandler()433     public Handler getAdapterHandler() {
434         return mHandler;
435     }
436 
437     @Override
getPendingAudioRoute()438     public PendingAudioRoute getPendingAudioRoute() {
439         return mPendingAudioRoute;
440     }
441 
442     @Override
dump(IndentingPrintWriter pw)443     public void dump(IndentingPrintWriter pw) {
444     }
445 
preHandleMessage(Message msg)446     private void preHandleMessage(Message msg) {
447         if (msg.obj instanceof SomeArgs) {
448             Session session = (Session) ((SomeArgs) msg.obj).arg1;
449             String messageCodeName = MESSAGE_CODE_TO_NAME.get(msg.what, "unknown");
450             Log.continueSession(session, "CARC.pM_" + messageCodeName);
451             Log.i(this, "Message received: %s=%d, arg1=%d", messageCodeName, msg.what, msg.arg1);
452         }
453     }
454 
postHandleMessage(Message msg)455     private void postHandleMessage(Message msg) {
456         Log.endSession();
457         if (msg.obj instanceof SomeArgs) {
458             ((SomeArgs) msg.obj).recycle();
459         }
460     }
461 
isActive()462     public boolean isActive() {
463         return mIsActive;
464     }
465 
isPending()466     public boolean isPending() {
467         return mIsPending;
468     }
469 
routeTo(boolean active, AudioRoute destRoute)470     private void routeTo(boolean active, AudioRoute destRoute) {
471         if (!destRoute.equals(mStreamingRoute) && !getAvailableRoutes().contains(destRoute)) {
472             Log.i(this, "Ignore routing to unavailable route: %s", destRoute);
473             return;
474         }
475         if (mIsPending) {
476             if (destRoute.equals(mPendingAudioRoute.getDestRoute()) && (mIsActive == active)) {
477                 return;
478             }
479             Log.i(this, "Override current pending route destination from %s(active=%b) to "
480                             + "%s(active=%b)",
481                     mPendingAudioRoute.getDestRoute(), mIsActive, destRoute, active);
482             // Ensure we don't keep waiting for SPEAKER_ON if dest route gets overridden.
483             if (active && mPendingAudioRoute.getDestRoute().getType() == TYPE_SPEAKER) {
484                 mPendingAudioRoute.clearPendingMessage(new Pair<>(SPEAKER_ON, null));
485             }
486             // override pending route while keep waiting for still pending messages for the
487             // previous pending route
488             mPendingAudioRoute.setOrigRoute(mIsActive, mPendingAudioRoute.getDestRoute());
489         } else {
490             if (mCurrentRoute.equals(destRoute) && (mIsActive == active)) {
491                 return;
492             }
493             Log.i(this, "Enter pending route, orig%s(active=%b), dest%s(active=%b)", mCurrentRoute,
494                     mIsActive, destRoute, active);
495             // route to pending route
496             if (getAvailableRoutes().contains(mCurrentRoute)) {
497                 mPendingAudioRoute.setOrigRoute(mIsActive, mCurrentRoute);
498             } else {
499                 // Avoid waiting for pending messages for an unavailable route
500                 mPendingAudioRoute.setOrigRoute(mIsActive, DUMMY_ROUTE);
501             }
502             mIsPending = true;
503         }
504         mPendingAudioRoute.setDestRoute(active, destRoute, mBluetoothRoutes.get(destRoute),
505                 mIsScoAudioConnected);
506         mIsActive = active;
507         mPendingAudioRoute.evaluatePendingState();
508         postTimeoutMessage();
509     }
510 
postTimeoutMessage()511     private void postTimeoutMessage() {
512         // reset timeout handler
513         mHandler.removeMessages(PENDING_ROUTE_TIMEOUT);
514         mHandler.postDelayed(() -> mHandler.sendMessage(
515                 Message.obtain(mHandler, PENDING_ROUTE_TIMEOUT)), TIMEOUT_LIMIT);
516     }
517 
handleWiredHeadsetConnected()518     private void handleWiredHeadsetConnected() {
519         AudioRoute wiredHeadsetRoute = null;
520         try {
521             wiredHeadsetRoute = mAudioRouteFactory.create(AudioRoute.TYPE_WIRED, null,
522                     mAudioManager);
523         } catch (IllegalArgumentException e) {
524             Log.e(this, e, "Can't find available audio device info for route type:"
525                     + AudioRoute.DEVICE_TYPE_STRINGS.get(AudioRoute.TYPE_WIRED));
526         }
527 
528         if (wiredHeadsetRoute != null) {
529             mAvailableRoutes.add(wiredHeadsetRoute);
530             mAvailableRoutes.remove(mEarpieceWiredRoute);
531             mTypeRoutes.put(AudioRoute.TYPE_WIRED, wiredHeadsetRoute);
532             mEarpieceWiredRoute = wiredHeadsetRoute;
533             routeTo(mIsActive, wiredHeadsetRoute);
534             onAvailableRoutesChanged();
535         }
536     }
537 
handleWiredHeadsetDisconnected()538     public void handleWiredHeadsetDisconnected() {
539         // Update audio route states
540         AudioRoute wiredHeadsetRoute = mTypeRoutes.remove(AudioRoute.TYPE_WIRED);
541         if (wiredHeadsetRoute != null) {
542             mAvailableRoutes.remove(wiredHeadsetRoute);
543             mEarpieceWiredRoute = null;
544         }
545         AudioRoute earpieceRoute = mTypeRoutes.get(AudioRoute.TYPE_EARPIECE);
546         if (earpieceRoute != null) {
547             mAvailableRoutes.add(earpieceRoute);
548             mEarpieceWiredRoute = earpieceRoute;
549         }
550         onAvailableRoutesChanged();
551 
552         // Route to expected state
553         if (mCurrentRoute.equals(wiredHeadsetRoute)) {
554             routeTo(mIsActive, getBaseRoute(true, null));
555         }
556     }
557 
handleDockConnected()558     private void handleDockConnected() {
559         AudioRoute dockRoute = null;
560         try {
561             dockRoute = mAudioRouteFactory.create(AudioRoute.TYPE_DOCK, null, mAudioManager);
562         } catch (IllegalArgumentException e) {
563             Log.e(this, e, "Can't find available audio device info for route type:"
564                     + AudioRoute.DEVICE_TYPE_STRINGS.get(AudioRoute.TYPE_WIRED));
565         }
566 
567         if (dockRoute != null) {
568             mAvailableRoutes.add(dockRoute);
569             mAvailableRoutes.remove(mSpeakerDockRoute);
570             mTypeRoutes.put(AudioRoute.TYPE_DOCK, dockRoute);
571             mSpeakerDockRoute = dockRoute;
572             routeTo(mIsActive, dockRoute);
573             onAvailableRoutesChanged();
574         }
575     }
576 
handleDockDisconnected()577     public void handleDockDisconnected() {
578         // Update audio route states
579         AudioRoute dockRoute = mTypeRoutes.get(AudioRoute.TYPE_DOCK);
580         if (dockRoute != null) {
581             mAvailableRoutes.remove(dockRoute);
582             mSpeakerDockRoute = null;
583         }
584         AudioRoute speakerRoute = mTypeRoutes.get(AudioRoute.TYPE_SPEAKER);
585         if (speakerRoute != null) {
586             mAvailableRoutes.add(speakerRoute);
587             mSpeakerDockRoute = speakerRoute;
588         }
589         onAvailableRoutesChanged();
590 
591         // Route to expected state
592         if (mCurrentRoute.equals(dockRoute)) {
593             routeTo(mIsActive, getBaseRoute(true, null));
594         }
595     }
596 
handleStreamingEnabled()597     private void handleStreamingEnabled() {
598         if (!mCurrentRoute.equals(mStreamingRoute)) {
599             routeTo(mIsActive, mStreamingRoute);
600         } else {
601             Log.i(this, "ignore enable streaming, already in streaming");
602         }
603     }
604 
handleStreamingDisabled()605     private void handleStreamingDisabled() {
606         if (mCurrentRoute.equals(mStreamingRoute)) {
607             mCurrentRoute = DUMMY_ROUTE;
608             onAvailableRoutesChanged();
609             routeTo(mIsActive, getBaseRoute(true, null));
610         } else {
611             Log.i(this, "ignore disable streaming, not in streaming");
612         }
613     }
614 
615     /**
616      * Handles the case when SCO audio is connected for the BT headset. This follows shortly after
617      * the BT device has been established as an active device (BT_ACTIVE_DEVICE_PRESENT) and doesn't
618      * apply to other BT device types. In this case, the pending audio route will process the
619      * BT_AUDIO_CONNECTED message that will trigger routing to the pending destination audio route;
620      * otherwise, routing will be ignored if there aren't pending routes to be processed.
621      *
622      * Message being handled: BT_AUDIO_CONNECTED
623      */
handleBtAudioActive(BluetoothDevice bluetoothDevice)624     private void handleBtAudioActive(BluetoothDevice bluetoothDevice) {
625         if (mIsPending) {
626             Log.i(this, "handleBtAudioActive: is pending path");
627             if (Objects.equals(mPendingAudioRoute.getDestRoute().getBluetoothAddress(),
628                     bluetoothDevice.getAddress())) {
629                 mPendingAudioRoute.onMessageReceived(new Pair<>(BT_AUDIO_CONNECTED,
630                         bluetoothDevice.getAddress()), null);
631             }
632         } else {
633             // ignore, not triggered by telecom
634             Log.i(this, "handleBtAudioActive: ignoring handling bt audio active.");
635         }
636     }
637 
638     /**
639      * Handles the case when SCO audio is disconnected for the BT headset. In this case, the pending
640      * audio route will process the BT_AUDIO_DISCONNECTED message which will trigger routing to the
641      * pending destination audio route; otherwise, routing will be ignored if there aren't any
642      * pending routes to be processed.
643      *
644      * Message being handled: BT_AUDIO_DISCONNECTED
645      */
handleBtAudioInactive(BluetoothDevice bluetoothDevice)646     private void handleBtAudioInactive(BluetoothDevice bluetoothDevice) {
647         if (mIsPending) {
648             Log.i(this, "handleBtAudioInactive: is pending path");
649             if (Objects.equals(mPendingAudioRoute.getOrigRoute().getBluetoothAddress(),
650                     bluetoothDevice.getAddress())) {
651                 mPendingAudioRoute.onMessageReceived(new Pair<>(BT_AUDIO_DISCONNECTED,
652                         bluetoothDevice.getAddress()), null);
653             }
654         } else {
655             // ignore, not triggered by telecom
656             Log.i(this, "handleBtAudioInactive: ignoring handling bt audio inactive.");
657         }
658     }
659 
660     /**
661      * This particular routing occurs when the BT device is trying to establish itself as a
662      * connected device (refer to BluetoothStateReceiver#handleConnectionStateChanged). The device
663      * is included as an available route and cached into the current BT routes.
664      *
665      * Message being handled: BT_DEVICE_ADDED
666      */
handleBtConnected(@udioRoute.AudioRouteType int type, BluetoothDevice bluetoothDevice)667     private void handleBtConnected(@AudioRoute.AudioRouteType int type,
668                                    BluetoothDevice bluetoothDevice) {
669         if (containsHearingAidPair(type, bluetoothDevice)) {
670             return;
671         }
672 
673         AudioRoute bluetoothRoute = mAudioRouteFactory.create(type, bluetoothDevice.getAddress(),
674                 mAudioManager);
675         if (bluetoothRoute == null) {
676             Log.w(this, "Can't find available audio device info for route type:"
677                     + AudioRoute.DEVICE_TYPE_STRINGS.get(type));
678         } else {
679             Log.i(this, "bluetooth route added: " + bluetoothRoute);
680             mAvailableRoutes.add(bluetoothRoute);
681             mBluetoothRoutes.put(bluetoothRoute, bluetoothDevice);
682             onAvailableRoutesChanged();
683         }
684     }
685 
686     /**
687      * Handles the case when the BT device is in a disconnecting/disconnected state. In this case,
688      * the audio route for the specified device is removed from the available BT routes and the
689      * audio is routed to an available route if the current route is pointing to the device which
690      * got disconnected.
691      *
692      * Message being handled: BT_DEVICE_REMOVED
693      */
handleBtDisconnected(@udioRoute.AudioRouteType int type, BluetoothDevice bluetoothDevice)694     private void handleBtDisconnected(@AudioRoute.AudioRouteType int type,
695                                       BluetoothDevice bluetoothDevice) {
696         // Clean up unavailable routes
697         AudioRoute bluetoothRoute = getBluetoothRoute(type, bluetoothDevice.getAddress());
698         if (bluetoothRoute != null) {
699             Log.i(this, "bluetooth route removed: " + bluetoothRoute);
700             mBluetoothRoutes.remove(bluetoothRoute);
701             mAvailableRoutes.remove(bluetoothRoute);
702             onAvailableRoutesChanged();
703         }
704 
705         // Fallback to an available route
706         if (Objects.equals(mCurrentRoute, bluetoothRoute)) {
707             routeTo(mIsActive, getBaseRoute(true, null));
708         }
709     }
710 
711     /**
712      * This particular routing occurs when the specified bluetooth device is marked as the active
713      * device (refer to BluetoothStateReceiver#handleActiveDeviceChanged). This takes care of
714      * moving the call audio route to the bluetooth route.
715      *
716      * Message being handled: BT_ACTIVE_DEVICE_PRESENT
717      */
handleBtActiveDevicePresent(@udioRoute.AudioRouteType int type, String deviceAddress)718     private void handleBtActiveDevicePresent(@AudioRoute.AudioRouteType int type,
719                                              String deviceAddress) {
720         AudioRoute bluetoothRoute = getBluetoothRoute(type, deviceAddress);
721         if (bluetoothRoute != null) {
722             Log.i(this, "request to route to bluetooth route: %s (active=%b)", bluetoothRoute,
723                     mIsActive);
724             routeTo(mIsActive, bluetoothRoute);
725         } else {
726             Log.i(this, "request to route to unavailable bluetooth route - type (%s), address (%s)",
727                     type, deviceAddress);
728         }
729     }
730 
731     /**
732      * Handles routing for when the active BT device is removed for a given audio route type. In
733      * this case, the audio is routed to another available route if the current route hasn't been
734      * adjusted yet or there is a pending destination route associated with the device type that
735      * went inactive. Note that BT_DEVICE_REMOVED will be processed first in this case, which will
736      * handle removing the BT route for the device that went inactive as well as falling back to
737      * an available route.
738      *
739      * Message being handled: BT_ACTIVE_DEVICE_GONE
740      */
handleBtActiveDeviceGone(@udioRoute.AudioRouteType int type)741     private void handleBtActiveDeviceGone(@AudioRoute.AudioRouteType int type) {
742         if ((mIsPending && mPendingAudioRoute.getDestRoute().getType() == type)
743                 || (!mIsPending && mCurrentRoute.getType() == type)) {
744             // Fallback to an available route
745             routeTo(mIsActive, getBaseRoute(true, null));
746         }
747     }
748 
handleMuteChanged(boolean mute)749     private void handleMuteChanged(boolean mute) {
750         mIsMute = mute;
751         if (mIsMute != mAudioManager.isMicrophoneMute() && mIsActive) {
752             IAudioService audioService = mAudioServiceFactory.getAudioService();
753             Log.i(this, "changing microphone mute state to: %b [serviceIsNull=%b]", mute,
754                     audioService == null);
755             if (audioService != null) {
756                 try {
757                     audioService.setMicrophoneMute(mute, mContext.getOpPackageName(),
758                             mCallsManager.getCurrentUserHandle().getIdentifier(),
759                             mContext.getAttributionTag());
760                 } catch (RemoteException e) {
761                     Log.e(this, e, "Remote exception while toggling mute.");
762                     return;
763                 }
764             }
765         }
766         onMuteStateChanged(mIsMute);
767     }
768 
handleSwitchFocus(int focus)769     private void handleSwitchFocus(int focus) {
770         Log.i(this, "handleSwitchFocus: focus (%s)", focus);
771         mFocusType = focus;
772         switch (focus) {
773             case NO_FOCUS -> {
774                 if (mIsActive) {
775                     // Reset mute state after call ends.
776                     handleMuteChanged(false);
777                     // Route back to inactive route.
778                     routeTo(false, mCurrentRoute);
779                     // Clear pending messages
780                     mPendingAudioRoute.clearPendingMessages();
781                 }
782             }
783             case ACTIVE_FOCUS -> {
784                 // Route to active baseline route (we may need to change audio route in the case
785                 // when a video call is put on hold).
786                 routeTo(true, getBaseRoute(true, null));
787             }
788             case RINGING_FOCUS -> {
789                 if (!mIsActive) {
790                     AudioRoute route = getBaseRoute(true, null);
791                     BluetoothDevice device = mBluetoothRoutes.get(route);
792                     // Check if in-band ringtone is enabled for the device; if it isn't, move to
793                     // inactive route.
794                     if (device != null && !mBluetoothRouteManager.isInbandRingEnabled(device)) {
795                         routeTo(false, route);
796                     } else {
797                         routeTo(true, route);
798                     }
799                 } else {
800                     // Route is already active.
801                     BluetoothDevice device = mBluetoothRoutes.get(mCurrentRoute);
802                     if (device != null && !mBluetoothRouteManager.isInbandRingEnabled(device)) {
803                         routeTo(false, mCurrentRoute);
804                     }
805                 }
806             }
807         }
808     }
809 
handleSwitchEarpiece()810     public void handleSwitchEarpiece() {
811         AudioRoute earpieceRoute = mTypeRoutes.get(AudioRoute.TYPE_EARPIECE);
812         if (earpieceRoute != null && getAvailableRoutes().contains(earpieceRoute)) {
813             routeTo(mIsActive, earpieceRoute);
814         } else {
815             Log.i(this, "ignore switch earpiece request");
816         }
817     }
818 
handleSwitchBluetooth(String address)819     private void handleSwitchBluetooth(String address) {
820         Log.i(this, "handle switch to bluetooth with address %s", address);
821         AudioRoute bluetoothRoute = null;
822         BluetoothDevice bluetoothDevice = null;
823         if (address == null) {
824             bluetoothRoute = getArbitraryBluetoothDevice();
825             bluetoothDevice = mBluetoothRoutes.get(bluetoothRoute);
826         } else {
827             for (AudioRoute route : getAvailableRoutes()) {
828                 if (Objects.equals(address, route.getBluetoothAddress())) {
829                     bluetoothRoute = route;
830                     bluetoothDevice = mBluetoothRoutes.get(route);
831                     break;
832                 }
833             }
834         }
835 
836         if (bluetoothRoute != null && bluetoothDevice != null) {
837             if (mFocusType == RINGING_FOCUS) {
838                 routeTo(mBluetoothRouteManager.isInbandRingEnabled(bluetoothDevice) && mIsActive,
839                         bluetoothRoute);
840             } else {
841                 routeTo(mIsActive, bluetoothRoute);
842             }
843         } else {
844             Log.i(this, "ignore switch bluetooth request to unavailable address");
845         }
846     }
847 
848     /**
849      * Retrieve the active BT device, if available, otherwise return the most recently tracked
850      * active device, or null if none are available.
851      * @return {@link AudioRoute} of the BT device.
852      */
getArbitraryBluetoothDevice()853     private AudioRoute getArbitraryBluetoothDevice() {
854         if (mActiveBluetoothDevice != null) {
855             return getBluetoothRoute(mActiveBluetoothDevice.first, mActiveBluetoothDevice.second);
856         } else if (!mBluetoothRoutes.isEmpty()) {
857             return mBluetoothRoutes.keySet().stream().toList().get(mBluetoothRoutes.size() - 1);
858         }
859         return null;
860     }
861 
handleSwitchHeadset()862     private void handleSwitchHeadset() {
863         AudioRoute headsetRoute = mTypeRoutes.get(AudioRoute.TYPE_WIRED);
864         if (headsetRoute != null && getAvailableRoutes().contains(headsetRoute)) {
865             routeTo(mIsActive, headsetRoute);
866         } else {
867             Log.i(this, "ignore switch headset request");
868         }
869     }
870 
handleSwitchSpeaker()871     private void handleSwitchSpeaker() {
872         if (mSpeakerDockRoute != null && getAvailableRoutes().contains(mSpeakerDockRoute)) {
873             routeTo(mIsActive, mSpeakerDockRoute);
874         } else {
875             Log.i(this, "ignore switch speaker request");
876         }
877     }
878 
handleSwitchBaselineRoute(boolean includeBluetooth, String btAddressToExclude)879     private void handleSwitchBaselineRoute(boolean includeBluetooth, String btAddressToExclude) {
880         routeTo(mIsActive, getBaseRoute(includeBluetooth, btAddressToExclude));
881     }
882 
handleSpeakerOn()883     private void handleSpeakerOn() {
884         if (isPending()) {
885             Log.i(this, "handleSpeakerOn: sending SPEAKER_ON to pending audio route");
886             mPendingAudioRoute.onMessageReceived(new Pair<>(SPEAKER_ON, null), null);
887             // Update status bar notification if we are in a call.
888             mStatusBarNotifier.notifySpeakerphone(mCallsManager.hasAnyCalls());
889         } else {
890             if (mSpeakerDockRoute != null && getAvailableRoutes().contains(mSpeakerDockRoute)) {
891                 routeTo(mIsActive, mSpeakerDockRoute);
892                 // Since the route switching triggered by this message, we need to manually send it
893                 // again so that we won't stuck in the pending route
894                 if (mIsActive) {
895                     sendMessageWithSessionInfo(SPEAKER_ON);
896                 }
897             }
898         }
899     }
900 
handleSpeakerOff()901     private void handleSpeakerOff() {
902         if (isPending()) {
903             Log.i(this, "handleSpeakerOff - sending SPEAKER_OFF to pending audio route");
904             mPendingAudioRoute.onMessageReceived(new Pair<>(SPEAKER_OFF, null), null);
905             // Update status bar notification
906             mStatusBarNotifier.notifySpeakerphone(false);
907         } else if (mCurrentRoute.getType() == AudioRoute.TYPE_SPEAKER) {
908             routeTo(mIsActive, getBaseRoute(true, null));
909             // Since the route switching triggered by this message, we need to manually send it
910             // again so that we won't stuck in the pending route
911             if (mIsActive) {
912                 sendMessageWithSessionInfo(SPEAKER_OFF);
913             }
914             onAvailableRoutesChanged();
915         }
916     }
917 
918     /**
919      * This is invoked when there are no more pending audio routes to be processed, which signals
920      * a change for the current audio route and the call audio state to be updated accordingly.
921      */
handleExitPendingRoute()922     public void handleExitPendingRoute() {
923         if (mIsPending) {
924             mCurrentRoute = mPendingAudioRoute.getDestRoute();
925             Log.addEvent(mCallsManager.getForegroundCall(), LogUtils.Events.AUDIO_ROUTE,
926                     "Entering audio route: " + mCurrentRoute + " (active=" + mIsActive + ")");
927             mIsPending = false;
928             mPendingAudioRoute.clearPendingMessages();
929             onCurrentRouteChanged();
930         }
931     }
932 
onCurrentRouteChanged()933     private void onCurrentRouteChanged() {
934         synchronized (mLock) {
935             BluetoothDevice activeBluetoothDevice = null;
936             int route = ROUTE_MAP.get(mCurrentRoute.getType());
937             if (route == CallAudioState.ROUTE_STREAMING) {
938                 updateCallAudioState(new CallAudioState(mIsMute, route, route));
939                 return;
940             }
941             if (route == CallAudioState.ROUTE_BLUETOOTH) {
942                 activeBluetoothDevice = mBluetoothRoutes.get(mCurrentRoute);
943             }
944             updateCallAudioState(new CallAudioState(mIsMute, route,
945                     mCallAudioState.getRawSupportedRouteMask(), activeBluetoothDevice,
946                     mCallAudioState.getSupportedBluetoothDevices()));
947         }
948     }
949 
onAvailableRoutesChanged()950     private void onAvailableRoutesChanged() {
951         synchronized (mLock) {
952             int routeMask = 0;
953             Set<BluetoothDevice> availableBluetoothDevices = new HashSet<>();
954             for (AudioRoute route : getAvailableRoutes()) {
955                 routeMask |= ROUTE_MAP.get(route.getType());
956                 if (BT_AUDIO_ROUTE_TYPES.contains(route.getType())) {
957                     BluetoothDevice deviceToAdd = mBluetoothRoutes.get(route);
958                     // Only include the lead device for LE audio (otherwise, the routes will show
959                     // two separate devices in the UI).
960                     if (route.getType() == AudioRoute.TYPE_BLUETOOTH_LE
961                             && getLeAudioService() != null) {
962                         int groupId = getLeAudioService().getGroupId(deviceToAdd);
963                         if (groupId != BluetoothLeAudio.GROUP_ID_INVALID) {
964                             deviceToAdd = getLeAudioService().getConnectedGroupLeadDevice(groupId);
965                         }
966                     }
967                     // This will only ever be null when the lead device (LE) is disconnected and
968                     // try to obtain the lead device for the 2nd bud.
969                     if (deviceToAdd != null) {
970                         availableBluetoothDevices.add(deviceToAdd);
971                     }
972                 }
973             }
974             updateCallAudioState(new CallAudioState(mIsMute, mCallAudioState.getRoute(), routeMask,
975                     mCallAudioState.getActiveBluetoothDevice(), availableBluetoothDevices));
976         }
977     }
978 
onMuteStateChanged(boolean mute)979     private void onMuteStateChanged(boolean mute) {
980         updateCallAudioState(new CallAudioState(mute, mCallAudioState.getRoute(),
981                 mCallAudioState.getSupportedRouteMask(), mCallAudioState.getActiveBluetoothDevice(),
982                 mCallAudioState.getSupportedBluetoothDevices()));
983     }
984 
updateCallAudioState(CallAudioState newCallAudioState)985     private void updateCallAudioState(CallAudioState newCallAudioState) {
986         Log.i(this, "updateCallAudioState: updating call audio state to %s", newCallAudioState);
987         CallAudioState oldState = mCallAudioState;
988         mCallAudioState = newCallAudioState;
989         // Update status bar notification
990         mStatusBarNotifier.notifyMute(newCallAudioState.isMuted());
991         mCallsManager.onCallAudioStateChanged(oldState, mCallAudioState);
992         updateAudioStateForTrackedCalls(mCallAudioState);
993     }
994 
updateAudioStateForTrackedCalls(CallAudioState newCallAudioState)995     private void updateAudioStateForTrackedCalls(CallAudioState newCallAudioState) {
996         Set<Call> calls = mCallsManager.getTrackedCalls();
997         for (Call call : calls) {
998             if (call != null && call.getConnectionService() != null) {
999                 call.getConnectionService().onCallAudioStateChanged(call, newCallAudioState);
1000             }
1001         }
1002     }
1003 
getPreferredAudioRouteFromStrategy()1004     private AudioRoute getPreferredAudioRouteFromStrategy() {
1005         // Get audio produce strategy
1006         AudioProductStrategy strategy = null;
1007         final AudioAttributes attr = new AudioAttributes.Builder()
1008                 .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
1009                 .build();
1010         List<AudioProductStrategy> strategies = AudioManager.getAudioProductStrategies();
1011         for (AudioProductStrategy s : strategies) {
1012             if (s.supportsAudioAttributes(attr)) {
1013                 strategy = s;
1014             }
1015         }
1016         if (strategy == null) {
1017             return null;
1018         }
1019 
1020         // Get preferred device
1021         AudioDeviceAttributes deviceAttr = mAudioManager.getPreferredDeviceForStrategy(strategy);
1022         Log.i(this, "getPreferredAudioRouteFromStrategy: preferred device is %s", deviceAttr);
1023         if (deviceAttr == null) {
1024             return null;
1025         }
1026 
1027         // Get corresponding audio route
1028         @AudioRoute.AudioRouteType int type = AudioRoute.DEVICE_INFO_TYPETO_AUDIO_ROUTE_TYPE.get(
1029                 deviceAttr.getType());
1030         if (BT_AUDIO_ROUTE_TYPES.contains(type)) {
1031             return getBluetoothRoute(type, deviceAttr.getAddress());
1032         } else {
1033             return mTypeRoutes.get(deviceAttr.getType());
1034 
1035         }
1036     }
1037 
getPreferredAudioRouteFromDefault(boolean includeBluetooth, String btAddressToExclude)1038     private AudioRoute getPreferredAudioRouteFromDefault(boolean includeBluetooth,
1039             String btAddressToExclude) {
1040         boolean skipEarpiece;
1041         Call foregroundCall = mCallAudioManager.getForegroundCall();
1042         synchronized (mTelecomLock) {
1043             skipEarpiece = foregroundCall != null
1044                     && VideoProfile.isVideo(foregroundCall.getVideoState());
1045         }
1046         // Route to earpiece, wired, or speaker route if there are not bluetooth routes or if there
1047         // are only wearables available.
1048         AudioRoute activeWatchOrNonWatchDeviceRoute =
1049                 getActiveWatchOrNonWatchDeviceRoute(btAddressToExclude);
1050         if (mBluetoothRoutes.isEmpty() || !includeBluetooth
1051                 || activeWatchOrNonWatchDeviceRoute == null) {
1052             Log.i(this, "getPreferredAudioRouteFromDefault: Audio routing defaulting to "
1053                     + "available non-BT route.");
1054             AudioRoute defaultRoute = mEarpieceWiredRoute != null
1055                     ? mEarpieceWiredRoute
1056                     : mSpeakerDockRoute;
1057             // Ensure that we default to speaker route if we're in a video call, but disregard it if
1058             // a wired headset is plugged in.
1059             if (skipEarpiece && defaultRoute.getType() == AudioRoute.TYPE_EARPIECE) {
1060                 Log.i(this, "getPreferredAudioRouteFromDefault: Audio routing defaulting to "
1061                         + "speaker route for video call.");
1062                 defaultRoute = mSpeakerDockRoute;
1063             }
1064             return defaultRoute;
1065         } else {
1066             // Most recent active route will always be the last in the array (ensure that we don't
1067             // auto route to a wearable device unless it's already active).
1068             String autoRoutingToWatchExcerpt = mFeatureFlags.ignoreAutoRouteToWatchDevice()
1069                     ? " (except watch)"
1070                     : "";
1071             Log.i(this, "getPreferredAudioRouteFromDefault: Audio routing defaulting to "
1072                     + "most recently active BT route" + autoRoutingToWatchExcerpt + ".");
1073             return activeWatchOrNonWatchDeviceRoute;
1074         }
1075     }
1076 
calculateSupportedRouteMaskInit()1077     private int calculateSupportedRouteMaskInit() {
1078         Log.i(this, "calculateSupportedRouteMaskInit: is wired headset plugged in - %s",
1079                 mWiredHeadsetManager.isPluggedIn());
1080         int routeMask = CallAudioState.ROUTE_SPEAKER;
1081 
1082         if (mWiredHeadsetManager.isPluggedIn()) {
1083             routeMask |= CallAudioState.ROUTE_WIRED_HEADSET;
1084         } else {
1085             AudioDeviceInfo[] deviceList = mAudioManager.getDevices(
1086                     AudioManager.GET_DEVICES_OUTPUTS);
1087             for (AudioDeviceInfo device: deviceList) {
1088                 if (device.getType() == AudioDeviceInfo.TYPE_BUILTIN_EARPIECE) {
1089                     routeMask |= CallAudioState.ROUTE_EARPIECE;
1090                     break;
1091                 }
1092             }
1093         }
1094         return routeMask;
1095     }
1096 
1097     @VisibleForTesting
getAvailableRoutes()1098     public Set<AudioRoute> getAvailableRoutes() {
1099         if (mCurrentRoute.equals(mStreamingRoute)) {
1100             return mStreamingRoutes;
1101         } else {
1102             return mAvailableRoutes;
1103         }
1104     }
1105 
getCurrentRoute()1106     public AudioRoute getCurrentRoute() {
1107         return mCurrentRoute;
1108     }
1109 
getBluetoothRoute(@udioRoute.AudioRouteType int audioRouteType, String address)1110     public AudioRoute getBluetoothRoute(@AudioRoute.AudioRouteType int audioRouteType,
1111             String address) {
1112         for (AudioRoute route : mBluetoothRoutes.keySet()) {
1113             if (route.getType() == audioRouteType && route.getBluetoothAddress().equals(address)) {
1114                 return route;
1115             }
1116         }
1117         return null;
1118     }
1119 
getBaseRoute(boolean includeBluetooth, String btAddressToExclude)1120     public AudioRoute getBaseRoute(boolean includeBluetooth, String btAddressToExclude) {
1121         AudioRoute destRoute = getPreferredAudioRouteFromStrategy();
1122         if (destRoute == null || (destRoute.getBluetoothAddress() != null && !includeBluetooth)) {
1123             destRoute = getPreferredAudioRouteFromDefault(includeBluetooth, btAddressToExclude);
1124         }
1125         if (destRoute != null && !getAvailableRoutes().contains(destRoute)) {
1126             destRoute = null;
1127         }
1128         Log.i(this, "getBaseRoute - audio routing to %s", destRoute);
1129         return destRoute;
1130     }
1131 
1132     /**
1133      * Don't add additional AudioRoute when a hearing aid pair is detected. The devices have
1134      * separate addresses, so we need to perform explicit handling to ensure we don't
1135      * treat them as two separate devices.
1136      */
containsHearingAidPair(@udioRoute.AudioRouteType int type, BluetoothDevice bluetoothDevice)1137     private boolean containsHearingAidPair(@AudioRoute.AudioRouteType int type,
1138             BluetoothDevice bluetoothDevice) {
1139         // Check if it is a hearing aid pair and skip connecting to the other device in this case.
1140         // Traverse mBluetoothRoutes backwards as the most recently active device will be inserted
1141         // last.
1142         String existingHearingAidAddress = null;
1143         List<AudioRoute> bluetoothRoutes = mBluetoothRoutes.keySet().stream().toList();
1144         for (int i = bluetoothRoutes.size() - 1; i >= 0; i--) {
1145             AudioRoute audioRoute = bluetoothRoutes.get(i);
1146             if (audioRoute.getType() == AudioRoute.TYPE_BLUETOOTH_HA) {
1147                 existingHearingAidAddress = audioRoute.getBluetoothAddress();
1148                 break;
1149             }
1150         }
1151 
1152         // Check that route is for hearing aid and that there exists another hearing aid route
1153         // created for the first device (of the pair) that was connected.
1154         if (type == AudioRoute.TYPE_BLUETOOTH_HA && existingHearingAidAddress != null) {
1155             BluetoothAdapter bluetoothAdapter = mBluetoothRouteManager.getDeviceManager()
1156                     .getBluetoothAdapter();
1157             if (bluetoothAdapter != null) {
1158                 List<BluetoothDevice> activeHearingAids =
1159                         bluetoothAdapter.getActiveDevices(BluetoothProfile.HEARING_AID);
1160                 for (BluetoothDevice hearingAid : activeHearingAids) {
1161                     if (hearingAid != null && hearingAid.getAddress() != null) {
1162                         String address = hearingAid.getAddress();
1163                         if (address.equals(bluetoothDevice.getAddress())
1164                                 || address.equals(existingHearingAidAddress)) {
1165                             Log.i(this, "containsHearingAidPair: Detected a hearing aid "
1166                                     + "pair, ignoring creating a new AudioRoute");
1167                             return true;
1168                         }
1169                     }
1170                 }
1171             }
1172         }
1173         return false;
1174     }
1175 
1176     /**
1177      * Prevent auto routing to a wearable device when calculating the default bluetooth audio route
1178      * to move to. This function ensures that the most recently active non-wearable device is
1179      * selected for routing unless a wearable device has already been identified as an active
1180      * device.
1181      */
getActiveWatchOrNonWatchDeviceRoute(String btAddressToExclude)1182     private AudioRoute getActiveWatchOrNonWatchDeviceRoute(String btAddressToExclude) {
1183         if (!mFeatureFlags.ignoreAutoRouteToWatchDevice()) {
1184             Log.i(this, "getActiveWatchOrNonWatchDeviceRoute: ignore_auto_route_to_watch_device "
1185                     + "flag is disabled. Routing to most recently reported active device.");
1186             return getMostRecentlyActiveBtRoute(btAddressToExclude);
1187         }
1188 
1189         List<AudioRoute> bluetoothRoutes = mBluetoothRoutes.keySet().stream().toList();
1190         // Traverse the routes from the most recently active recorded devices first.
1191         AudioRoute nonWatchDeviceRoute = null;
1192         for (int i = bluetoothRoutes.size() - 1; i >= 0; i--) {
1193             AudioRoute route = bluetoothRoutes.get(i);
1194             BluetoothDevice device = mBluetoothRoutes.get(route);
1195             // Skip excluded BT address and LE audio if it's not the lead device.
1196             if (route.getBluetoothAddress().equals(btAddressToExclude)
1197                     || isLeAudioNonLeadDeviceOrServiceUnavailable(route.getType(), device)) {
1198                 continue;
1199             }
1200             // Check if the most recently active device is a watch device.
1201             if (i == (bluetoothRoutes.size() - 1) && device.equals(mCallAudioState
1202                     .getActiveBluetoothDevice()) && mBluetoothRouteManager.isWatch(device)) {
1203                 Log.i(this, "getActiveWatchOrNonWatchDeviceRoute: Routing to active watch - %s",
1204                         bluetoothRoutes.get(0));
1205                 return bluetoothRoutes.get(0);
1206             }
1207             // Record the first occurrence of a non-watch device route if found.
1208             if (!mBluetoothRouteManager.isWatch(device) && nonWatchDeviceRoute == null) {
1209                 nonWatchDeviceRoute = route;
1210                 break;
1211             }
1212         }
1213 
1214         Log.i(this, "Routing to a non-watch device - %s", nonWatchDeviceRoute);
1215         return nonWatchDeviceRoute;
1216     }
1217 
1218     /**
1219      * Returns the most actively reported bluetooth route excluding the passed in route.
1220      */
getMostRecentlyActiveBtRoute(String btAddressToExclude)1221     private AudioRoute getMostRecentlyActiveBtRoute(String btAddressToExclude) {
1222         List<AudioRoute> bluetoothRoutes = mBluetoothRoutes.keySet().stream().toList();
1223         for (int i = bluetoothRoutes.size() - 1; i >= 0; i--) {
1224             AudioRoute route = bluetoothRoutes.get(i);
1225             // Skip LE route if it's not the lead device.
1226             if (isLeAudioNonLeadDeviceOrServiceUnavailable(
1227                     route.getType(), mBluetoothRoutes.get(route))) {
1228                 continue;
1229             }
1230             if (!route.getBluetoothAddress().equals(btAddressToExclude)) {
1231                 return route;
1232             }
1233         }
1234         return null;
1235     }
1236 
isLeAudioNonLeadDeviceOrServiceUnavailable(@udioRoute.AudioRouteType int type, BluetoothDevice device)1237     private boolean isLeAudioNonLeadDeviceOrServiceUnavailable(@AudioRoute.AudioRouteType int type,
1238             BluetoothDevice device) {
1239         if (type != AudioRoute.TYPE_BLUETOOTH_LE) {
1240             return false;
1241         } else if (getLeAudioService() == null) {
1242             return true;
1243         }
1244 
1245         int groupId = getLeAudioService().getGroupId(device);
1246         if (groupId != BluetoothLeAudio.GROUP_ID_INVALID) {
1247             BluetoothDevice leadDevice = getLeAudioService().getConnectedGroupLeadDevice(groupId);
1248             Log.i(this, "Lead device for device (%s) is %s.", device, leadDevice);
1249             return leadDevice == null || !device.getAddress().equals(leadDevice.getAddress());
1250         }
1251         return false;
1252     }
1253 
getLeAudioService()1254     private BluetoothLeAudio getLeAudioService() {
1255         return mBluetoothRouteManager.getDeviceManager().getLeAudioService();
1256     }
1257 
1258     @VisibleForTesting
setAudioManager(AudioManager audioManager)1259     public void setAudioManager(AudioManager audioManager) {
1260         mAudioManager = audioManager;
1261     }
1262 
1263     @VisibleForTesting
setAudioRouteFactory(AudioRoute.Factory audioRouteFactory)1264     public void setAudioRouteFactory(AudioRoute.Factory audioRouteFactory) {
1265         mAudioRouteFactory = audioRouteFactory;
1266     }
1267 
getBluetoothRoutes()1268     public Map<AudioRoute, BluetoothDevice> getBluetoothRoutes() {
1269         return mBluetoothRoutes;
1270     }
1271 
overrideIsPending(boolean isPending)1272     public void overrideIsPending(boolean isPending) {
1273         mIsPending = isPending;
1274     }
1275 
setIsScoAudioConnected(boolean value)1276     public void setIsScoAudioConnected(boolean value) {
1277         mIsScoAudioConnected = value;
1278     }
1279 
1280     /**
1281      * Update the active bluetooth device being tracked (as well as for individual profiles).
1282      * We need to keep track of active devices for individual profiles because of potential
1283      * inconsistencies found in BluetoothStateReceiver#handleActiveDeviceChanged. When multiple
1284      * profiles are paired, we could have a scenario where an active device A is replaced
1285      * with an active device B (from a different profile), which is then removed as an active
1286      * device shortly after, causing device A to be reactive. It's possible that the active device
1287      * changed intent is never received again for device A so an active device cache is necessary
1288      * to track these devices at a profile level.
1289      * @param device {@link Pair} containing the BT audio route type (i.e. SCO/HA/LE) and the
1290      *                           address of the device.
1291      */
updateActiveBluetoothDevice(Pair<Integer, String> device)1292     public void updateActiveBluetoothDevice(Pair<Integer, String> device) {
1293         mActiveDeviceCache.put(device.first, device.second);
1294         // Update most recently active device if address isn't null (meaning some device is active).
1295         if (device.second != null) {
1296             mActiveBluetoothDevice = device;
1297         } else {
1298             // If a device was removed, check to ensure that no other device is still considered
1299             // active.
1300             boolean hasActiveDevice = false;
1301             for (String address : mActiveDeviceCache.values()) {
1302                 if (address != null) {
1303                     hasActiveDevice = true;
1304                     break;
1305                 }
1306             }
1307             if (!hasActiveDevice) {
1308                 mActiveBluetoothDevice = null;
1309             }
1310         }
1311     }
1312 
1313     @VisibleForTesting
setActive(boolean active)1314     public void setActive(boolean active) {
1315         if (active) {
1316             mFocusType = ACTIVE_FOCUS;
1317         } else {
1318             mFocusType = NO_FOCUS;
1319         }
1320         mIsActive = active;
1321     }
1322 }
1323