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.DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE; 21 import static com.android.server.telecom.AudioRoute.TYPE_INVALID; 22 import static com.android.server.telecom.AudioRoute.TYPE_SPEAKER; 23 24 import android.bluetooth.BluetoothAdapter; 25 import android.bluetooth.BluetoothDevice; 26 import android.bluetooth.BluetoothLeAudio; 27 import android.bluetooth.BluetoothProfile; 28 import android.content.BroadcastReceiver; 29 import android.content.Context; 30 import android.content.Intent; 31 import android.content.IntentFilter; 32 import android.media.AudioAttributes; 33 import android.media.AudioDeviceAttributes; 34 import android.media.AudioDeviceInfo; 35 import android.media.AudioManager; 36 import android.media.IAudioService; 37 import android.media.audiopolicy.AudioProductStrategy; 38 import android.os.Handler; 39 import android.os.HandlerThread; 40 import android.os.Looper; 41 import android.os.Message; 42 import android.os.RemoteException; 43 import android.telecom.CallAudioState; 44 import android.telecom.Log; 45 import android.telecom.Logging.Session; 46 import android.telecom.VideoProfile; 47 import android.util.ArrayMap; 48 import android.util.Pair; 49 50 import androidx.annotation.NonNull; 51 52 import com.android.internal.annotations.VisibleForTesting; 53 import com.android.internal.os.SomeArgs; 54 import com.android.internal.util.IndentingPrintWriter; 55 import com.android.server.telecom.bluetooth.BluetoothRouteManager; 56 import com.android.server.telecom.flags.FeatureFlags; 57 import com.android.server.telecom.metrics.ErrorStats; 58 import com.android.server.telecom.metrics.TelecomMetricsController; 59 60 import java.util.ArrayList; 61 import java.util.Collections; 62 import java.util.HashMap; 63 import java.util.HashSet; 64 import java.util.LinkedHashMap; 65 import java.util.List; 66 import java.util.Map; 67 import java.util.Objects; 68 import java.util.Set; 69 import java.util.concurrent.CountDownLatch; 70 import java.util.concurrent.ExecutorService; 71 import java.util.concurrent.Executors; 72 73 public class CallAudioRouteController implements CallAudioRouteAdapter { 74 private static final AudioRoute DUMMY_ROUTE = new AudioRoute(TYPE_INVALID, null, null); 75 private static final Map<Integer, Integer> ROUTE_MAP; 76 static { 77 ROUTE_MAP = new ArrayMap<>(); ROUTE_MAP.put(TYPE_INVALID, 0)78 ROUTE_MAP.put(TYPE_INVALID, 0); ROUTE_MAP.put(AudioRoute.TYPE_EARPIECE, CallAudioState.ROUTE_EARPIECE)79 ROUTE_MAP.put(AudioRoute.TYPE_EARPIECE, CallAudioState.ROUTE_EARPIECE); ROUTE_MAP.put(AudioRoute.TYPE_WIRED, CallAudioState.ROUTE_WIRED_HEADSET)80 ROUTE_MAP.put(AudioRoute.TYPE_WIRED, CallAudioState.ROUTE_WIRED_HEADSET); ROUTE_MAP.put(AudioRoute.TYPE_SPEAKER, CallAudioState.ROUTE_SPEAKER)81 ROUTE_MAP.put(AudioRoute.TYPE_SPEAKER, CallAudioState.ROUTE_SPEAKER); ROUTE_MAP.put(AudioRoute.TYPE_DOCK, CallAudioState.ROUTE_SPEAKER)82 ROUTE_MAP.put(AudioRoute.TYPE_DOCK, CallAudioState.ROUTE_SPEAKER); ROUTE_MAP.put(AudioRoute.TYPE_BUS, CallAudioState.ROUTE_SPEAKER)83 ROUTE_MAP.put(AudioRoute.TYPE_BUS, CallAudioState.ROUTE_SPEAKER); ROUTE_MAP.put(AudioRoute.TYPE_BLUETOOTH_SCO, CallAudioState.ROUTE_BLUETOOTH)84 ROUTE_MAP.put(AudioRoute.TYPE_BLUETOOTH_SCO, CallAudioState.ROUTE_BLUETOOTH); ROUTE_MAP.put(AudioRoute.TYPE_BLUETOOTH_HA, CallAudioState.ROUTE_BLUETOOTH)85 ROUTE_MAP.put(AudioRoute.TYPE_BLUETOOTH_HA, CallAudioState.ROUTE_BLUETOOTH); ROUTE_MAP.put(AudioRoute.TYPE_BLUETOOTH_LE, CallAudioState.ROUTE_BLUETOOTH)86 ROUTE_MAP.put(AudioRoute.TYPE_BLUETOOTH_LE, CallAudioState.ROUTE_BLUETOOTH); ROUTE_MAP.put(AudioRoute.TYPE_STREAMING, CallAudioState.ROUTE_STREAMING)87 ROUTE_MAP.put(AudioRoute.TYPE_STREAMING, CallAudioState.ROUTE_STREAMING); 88 } 89 90 /** Valid values for the first argument for SWITCH_BASELINE_ROUTE */ 91 public static final int INCLUDE_BLUETOOTH_IN_BASELINE = 1; 92 93 private final CallsManager mCallsManager; 94 private final Context mContext; 95 private AudioManager mAudioManager; 96 private CallAudioManager mCallAudioManager; 97 private final BluetoothRouteManager mBluetoothRouteManager; 98 private final CallAudioManager.AudioServiceFactory mAudioServiceFactory; 99 private final Handler mHandler; 100 private final WiredHeadsetManager mWiredHeadsetManager; 101 private Set<AudioRoute> mAvailableRoutes; 102 private Set<AudioRoute> mCallSupportedRoutes; 103 private AudioRoute mCurrentRoute; 104 private AudioRoute mEarpieceWiredRoute; 105 private AudioRoute mSpeakerDockRoute; 106 private AudioRoute mStreamingRoute; 107 private Set<AudioRoute> mStreamingRoutes; 108 private Map<AudioRoute, BluetoothDevice> mBluetoothRoutes; 109 private Pair<Integer, String> mActiveBluetoothDevice; 110 private Map<Integer, String> mActiveDeviceCache; 111 private String mBluetoothAddressForRinging; 112 private Map<Integer, AudioRoute> mTypeRoutes; 113 private PendingAudioRoute mPendingAudioRoute; 114 private AudioRoute.Factory mAudioRouteFactory; 115 private StatusBarNotifier mStatusBarNotifier; 116 private AudioManager.OnCommunicationDeviceChangedListener mCommunicationDeviceListener; 117 private ExecutorService mCommunicationDeviceChangedExecutor; 118 private FeatureFlags mFeatureFlags; 119 private int mFocusType; 120 private int mCallSupportedRouteMask = -1; 121 private BluetoothDevice mScoAudioConnectedDevice; 122 private boolean mAvailableRoutesUpdated; 123 private boolean mUsePreferredDeviceStrategy; 124 private AudioDeviceInfo mCurrentCommunicationDevice; 125 private final Object mLock = new Object(); 126 private final TelecomSystem.SyncRoot mTelecomLock; 127 private CountDownLatch mAudioOperationsCompleteLatch; 128 private CountDownLatch mAudioActiveCompleteLatch; 129 private final BroadcastReceiver mSpeakerPhoneChangeReceiver = new BroadcastReceiver() { 130 @Override 131 public void onReceive(Context context, Intent intent) { 132 Log.startSession("CARC.mSPCR"); 133 try { 134 if (AudioManager.ACTION_SPEAKERPHONE_STATE_CHANGED.equals(intent.getAction())) { 135 if (mAudioManager != null) { 136 AudioDeviceInfo info = mFeatureFlags.updatePreferredAudioDeviceLogic() 137 ? getCurrentCommunicationDevice() 138 : mAudioManager.getCommunicationDevice(); 139 if ((info != null) && 140 (info.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER)) { 141 if (mCurrentRoute.getType() != AudioRoute.TYPE_SPEAKER) { 142 sendMessageWithSessionInfo(SPEAKER_ON); 143 } 144 } else { 145 sendMessageWithSessionInfo(SPEAKER_OFF); 146 } 147 } 148 } else { 149 Log.w(this, "Received non-speakerphone-change intent"); 150 } 151 } finally { 152 Log.endSession(); 153 } 154 } 155 }; 156 private final BroadcastReceiver mMuteChangeReceiver = new BroadcastReceiver() { 157 @Override 158 public void onReceive(Context context, Intent intent) { 159 Log.startSession("CARC.mCR"); 160 try { 161 if (AudioManager.ACTION_MICROPHONE_MUTE_CHANGED.equals(intent.getAction())) { 162 if (mCallsManager.isInEmergencyCall()) { 163 Log.i(this, "Mute was externally changed when there's an emergency call. " 164 + "Forcing mute back off."); 165 sendMessageWithSessionInfo(MUTE_OFF); 166 } else { 167 sendMessageWithSessionInfo(MUTE_EXTERNALLY_CHANGED); 168 } 169 } else if (AudioManager.STREAM_MUTE_CHANGED_ACTION.equals(intent.getAction())) { 170 int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1); 171 boolean isStreamMuted = intent.getBooleanExtra( 172 AudioManager.EXTRA_STREAM_VOLUME_MUTED, false); 173 174 if (streamType == AudioManager.STREAM_RING && !isStreamMuted 175 && mCallAudioManager != null) { 176 Log.i(this, "Ring stream was un-muted."); 177 mCallAudioManager.onRingerModeChange(); 178 } 179 } else { 180 Log.w(this, "Received non-mute-change intent"); 181 } 182 } finally { 183 Log.endSession(); 184 } 185 } 186 }; 187 private CallAudioState mCallAudioState; 188 private boolean mIsMute; 189 private boolean mIsPending; 190 private boolean mIsActive; 191 private boolean mWasOnSpeaker; 192 private final TelecomMetricsController mMetricsController; 193 CallAudioRouteController( Context context, CallsManager callsManager, CallAudioManager.AudioServiceFactory audioServiceFactory, AudioRoute.Factory audioRouteFactory, WiredHeadsetManager wiredHeadsetManager, BluetoothRouteManager bluetoothRouteManager, StatusBarNotifier statusBarNotifier, FeatureFlags featureFlags, TelecomMetricsController metricsController)194 public CallAudioRouteController( 195 Context context, CallsManager callsManager, 196 CallAudioManager.AudioServiceFactory audioServiceFactory, 197 AudioRoute.Factory audioRouteFactory, WiredHeadsetManager wiredHeadsetManager, 198 BluetoothRouteManager bluetoothRouteManager, StatusBarNotifier statusBarNotifier, 199 FeatureFlags featureFlags, TelecomMetricsController metricsController) { 200 mContext = context; 201 mCallsManager = callsManager; 202 mAudioManager = context.getSystemService(AudioManager.class); 203 mAudioServiceFactory = audioServiceFactory; 204 mAudioRouteFactory = audioRouteFactory; 205 mWiredHeadsetManager = wiredHeadsetManager; 206 mIsMute = false; 207 mBluetoothRouteManager = bluetoothRouteManager; 208 mStatusBarNotifier = statusBarNotifier; 209 mFeatureFlags = featureFlags; 210 mMetricsController = metricsController; 211 mFocusType = NO_FOCUS; 212 mScoAudioConnectedDevice = null; 213 mUsePreferredDeviceStrategy = true; 214 mWasOnSpeaker = false; 215 setCurrentCommunicationDevice(null); 216 217 mTelecomLock = callsManager.getLock(); 218 HandlerThread handlerThread = new HandlerThread(this.getClass().getSimpleName()); 219 if (!mFeatureFlags.callAudioRoutingPerformanceImprovemenent()) { 220 handlerThread.start(); 221 } 222 223 // Register broadcast receivers 224 if (!mFeatureFlags.newAudioPathSpeakerBroadcastAndUnfocusedRouting()) { 225 IntentFilter speakerChangedFilter = new IntentFilter( 226 AudioManager.ACTION_SPEAKERPHONE_STATE_CHANGED); 227 speakerChangedFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); 228 context.registerReceiver(mSpeakerPhoneChangeReceiver, speakerChangedFilter); 229 } 230 231 IntentFilter micMuteChangedFilter = new IntentFilter( 232 AudioManager.ACTION_MICROPHONE_MUTE_CHANGED); 233 micMuteChangedFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); 234 context.registerReceiver(mMuteChangeReceiver, micMuteChangedFilter); 235 236 IntentFilter muteChangedFilter = new IntentFilter(AudioManager.STREAM_MUTE_CHANGED_ACTION); 237 muteChangedFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); 238 context.registerReceiver(mMuteChangeReceiver, muteChangedFilter); 239 240 // Register AudioManager#onCommunicationDeviceChangedListener listener to receive updates 241 // to communication device (via AudioManager#setCommunicationDevice). This is a replacement 242 // to using broadcasts in the hopes of improving performance. 243 mCommunicationDeviceChangedExecutor = Executors.newSingleThreadExecutor(); 244 mCommunicationDeviceListener = new AudioManager.OnCommunicationDeviceChangedListener() { 245 @Override 246 public void onCommunicationDeviceChanged(AudioDeviceInfo device) { 247 @AudioRoute.AudioRouteType int audioType = getAudioType(device); 248 setCurrentCommunicationDevice(device); 249 Log.i(this, "onCommunicationDeviceChanged: device (%s), audioType (%d)", 250 device, audioType); 251 if (audioType == TYPE_SPEAKER) { 252 if (mCurrentRoute.getType() != TYPE_SPEAKER) { 253 sendMessageWithSessionInfo(SPEAKER_ON); 254 } 255 } else { 256 sendMessageWithSessionInfo(SPEAKER_OFF); 257 } 258 } 259 }; 260 261 Looper looper = mFeatureFlags.callAudioRoutingPerformanceImprovemenent() 262 ? Looper.getMainLooper() 263 : handlerThread.getLooper(); 264 // Create handler 265 mHandler = new Handler(looper) { 266 @Override 267 public void handleMessage(@NonNull Message msg) { 268 synchronized (this) { 269 preHandleMessage(msg); 270 String address; 271 BluetoothDevice bluetoothDevice; 272 int focus; 273 int handleEndTone; 274 @AudioRoute.AudioRouteType int type; 275 switch (msg.what) { 276 case CONNECT_WIRED_HEADSET: 277 handleWiredHeadsetConnected(); 278 break; 279 case DISCONNECT_WIRED_HEADSET: 280 handleWiredHeadsetDisconnected(); 281 break; 282 case CONNECT_DOCK: 283 handleDockConnected(); 284 break; 285 case DISCONNECT_DOCK: 286 handleDockDisconnected(); 287 break; 288 case BLUETOOTH_DEVICE_LIST_CHANGED: 289 break; 290 case BT_ACTIVE_DEVICE_PRESENT: 291 type = msg.arg1; 292 address = (String) ((SomeArgs) msg.obj).arg2; 293 handleBtActiveDevicePresent(type, address); 294 break; 295 case BT_ACTIVE_DEVICE_GONE: 296 type = msg.arg1; 297 handleBtActiveDeviceGone(type); 298 break; 299 case BT_DEVICE_ADDED: 300 type = msg.arg1; 301 bluetoothDevice = (BluetoothDevice) ((SomeArgs) msg.obj).arg2; 302 handleBtConnected(type, bluetoothDevice); 303 break; 304 case BT_DEVICE_REMOVED: 305 type = msg.arg1; 306 bluetoothDevice = (BluetoothDevice) ((SomeArgs) msg.obj).arg2; 307 handleBtDisconnected(type, bluetoothDevice); 308 break; 309 case SWITCH_EARPIECE: 310 case USER_SWITCH_EARPIECE: 311 handleSwitchEarpiece(msg.what == USER_SWITCH_EARPIECE); 312 break; 313 case SWITCH_BLUETOOTH: 314 case USER_SWITCH_BLUETOOTH: 315 address = (String) ((SomeArgs) msg.obj).arg2; 316 handleSwitchBluetooth(address, msg.what == USER_SWITCH_BLUETOOTH); 317 break; 318 case SWITCH_HEADSET: 319 case USER_SWITCH_HEADSET: 320 handleSwitchHeadset(msg.what == USER_SWITCH_HEADSET); 321 break; 322 case SWITCH_SPEAKER: 323 case USER_SWITCH_SPEAKER: 324 handleSwitchSpeaker(); 325 break; 326 case SWITCH_BASELINE_ROUTE: 327 address = (String) ((SomeArgs) msg.obj).arg2; 328 handleSwitchBaselineRoute(false, 329 msg.arg1 == INCLUDE_BLUETOOTH_IN_BASELINE, address); 330 break; 331 case USER_SWITCH_BASELINE_ROUTE: 332 handleSwitchBaselineRoute(true, 333 msg.arg1 == INCLUDE_BLUETOOTH_IN_BASELINE, null); 334 break; 335 case SPEAKER_ON: 336 handleSpeakerOn(); 337 break; 338 case SPEAKER_OFF: 339 handleSpeakerOff(); 340 break; 341 case STREAMING_FORCE_ENABLED: 342 handleStreamingEnabled(); 343 break; 344 case STREAMING_FORCE_DISABLED: 345 handleStreamingDisabled(); 346 break; 347 case BT_AUDIO_CONNECTED: 348 bluetoothDevice = (BluetoothDevice) ((SomeArgs) msg.obj).arg2; 349 handleBtAudioActive(bluetoothDevice); 350 break; 351 case BT_AUDIO_DISCONNECTED: 352 bluetoothDevice = (BluetoothDevice) ((SomeArgs) msg.obj).arg2; 353 handleBtAudioInactive(bluetoothDevice); 354 break; 355 case MUTE_ON: 356 handleMuteChanged(true); 357 break; 358 case MUTE_OFF: 359 handleMuteChanged(false); 360 break; 361 case MUTE_EXTERNALLY_CHANGED: 362 handleMuteChanged(mAudioManager.isMicrophoneMute()); 363 break; 364 case TOGGLE_MUTE: 365 handleMuteChanged(!mIsMute); 366 break; 367 case SWITCH_FOCUS: 368 focus = msg.arg1; 369 handleEndTone = (int) ((SomeArgs) msg.obj).arg2; 370 handleSwitchFocus(focus, handleEndTone); 371 break; 372 case EXIT_PENDING_ROUTE: 373 handleExitPendingRoute(); 374 break; 375 case UPDATE_SYSTEM_AUDIO_ROUTE: 376 // Based on the available routes for foreground call, adjust routing. 377 updateRouteForForeground(); 378 // Force update to notify all ICS/CS. 379 updateCallAudioState(new CallAudioState(mIsMute, 380 mCallAudioState.getRoute(), 381 mCallAudioState.getSupportedRouteMask(), 382 mCallAudioState.getActiveBluetoothDevice(), 383 mCallAudioState.getSupportedBluetoothDevices())); 384 default: 385 break; 386 } 387 postHandleMessage(msg); 388 } 389 } 390 }; 391 } 392 @Override initialize()393 public void initialize() { 394 mAvailableRoutes = new HashSet<>(); 395 mCallSupportedRoutes = new HashSet<>(); 396 mBluetoothRoutes = Collections.synchronizedMap(new LinkedHashMap<>()); 397 mActiveDeviceCache = new HashMap<>(); 398 mActiveDeviceCache.put(AudioRoute.TYPE_BLUETOOTH_SCO, null); 399 mActiveDeviceCache.put(AudioRoute.TYPE_BLUETOOTH_HA, null); 400 mActiveDeviceCache.put(AudioRoute.TYPE_BLUETOOTH_LE, null); 401 mActiveBluetoothDevice = null; 402 mTypeRoutes = new ArrayMap<>(); 403 mStreamingRoutes = new HashSet<>(); 404 mPendingAudioRoute = new PendingAudioRoute(this, mAudioManager, mBluetoothRouteManager, 405 mFeatureFlags); 406 mStreamingRoute = new AudioRoute(AudioRoute.TYPE_STREAMING, null, null); 407 mStreamingRoutes.add(mStreamingRoute); 408 409 int supportMask = calculateSupportedRouteMaskInit(); 410 if ((supportMask & CallAudioState.ROUTE_SPEAKER) != 0) { 411 int audioRouteType = AudioRoute.TYPE_SPEAKER; 412 // Create speaker routes 413 mSpeakerDockRoute = mAudioRouteFactory.create(AudioRoute.TYPE_SPEAKER, null, 414 mAudioManager); 415 if (mSpeakerDockRoute == null){ 416 Log.i(this, "Can't find available audio device info for route TYPE_SPEAKER, trying" 417 + " for TYPE_BUS"); 418 mSpeakerDockRoute = mAudioRouteFactory.create(AudioRoute.TYPE_BUS, null, 419 mAudioManager); 420 audioRouteType = AudioRoute.TYPE_BUS; 421 } 422 if (mSpeakerDockRoute != null) { 423 mTypeRoutes.put(audioRouteType, mSpeakerDockRoute); 424 updateAvailableRoutes(mSpeakerDockRoute, true); 425 } else { 426 Log.w(this, "Can't find available audio device info for route TYPE_SPEAKER " 427 + "or TYPE_BUS."); 428 } 429 } 430 431 if ((supportMask & CallAudioState.ROUTE_WIRED_HEADSET) != 0) { 432 // Create wired headset routes 433 mEarpieceWiredRoute = mAudioRouteFactory.create(AudioRoute.TYPE_WIRED, null, 434 mAudioManager); 435 if (mEarpieceWiredRoute == null) { 436 Log.w(this, "Can't find available audio device info for route TYPE_WIRED_HEADSET"); 437 } else { 438 mTypeRoutes.put(AudioRoute.TYPE_WIRED, mEarpieceWiredRoute); 439 updateAvailableRoutes(mEarpieceWiredRoute, true); 440 } 441 } else if ((supportMask & CallAudioState.ROUTE_EARPIECE) != 0) { 442 // Create earpiece routes 443 mEarpieceWiredRoute = mAudioRouteFactory.create(AudioRoute.TYPE_EARPIECE, null, 444 mAudioManager); 445 if (mEarpieceWiredRoute == null) { 446 Log.w(this, "Can't find available audio device info for route TYPE_EARPIECE"); 447 } else { 448 mTypeRoutes.put(AudioRoute.TYPE_EARPIECE, mEarpieceWiredRoute); 449 updateAvailableRoutes(mEarpieceWiredRoute, true); 450 } 451 } 452 453 // set current route 454 if (mEarpieceWiredRoute != null) { 455 mCurrentRoute = mEarpieceWiredRoute; 456 } else if (mSpeakerDockRoute != null) { 457 mCurrentRoute = mSpeakerDockRoute; 458 } else { 459 mCurrentRoute = DUMMY_ROUTE; 460 } 461 // Audio ops will only ever be completed if there's a call placed and it gains 462 // ACTIVE/RINGING focus, hence why the initial value is 0. 463 mAudioOperationsCompleteLatch = new CountDownLatch(0); 464 // This latch will be count down when ACTIVE/RINGING focus is gained. This is determined 465 // when the routing goes active. 466 mAudioActiveCompleteLatch = new CountDownLatch(1); 467 mIsActive = false; 468 mCallAudioState = new CallAudioState(mIsMute, ROUTE_MAP.get(mCurrentRoute.getType()), 469 supportMask, null, new HashSet<>()); 470 if (mFeatureFlags.newAudioPathSpeakerBroadcastAndUnfocusedRouting()) { 471 mAudioManager.addOnCommunicationDeviceChangedListener( 472 mCommunicationDeviceChangedExecutor, 473 mCommunicationDeviceListener); 474 } 475 } 476 477 @Override sendMessageWithSessionInfo(int message)478 public void sendMessageWithSessionInfo(int message) { 479 sendMessageWithSessionInfo(message, 0, (String) null); 480 } 481 482 @Override sendMessageWithSessionInfo(int message, int arg)483 public void sendMessageWithSessionInfo(int message, int arg) { 484 sendMessageWithSessionInfo(message, arg, (String) null); 485 } 486 487 @Override sendMessageWithSessionInfo(int message, int arg, String data)488 public void sendMessageWithSessionInfo(int message, int arg, String data) { 489 SomeArgs args = SomeArgs.obtain(); 490 args.arg1 = Log.createSubsession(); 491 args.arg2 = data; 492 sendMessage(message, arg, 0, args); 493 } 494 495 @Override sendMessageWithSessionInfo(int message, int arg, int data)496 public void sendMessageWithSessionInfo(int message, int arg, int data) { 497 SomeArgs args = SomeArgs.obtain(); 498 args.arg1 = Log.createSubsession(); 499 args.arg2 = data; 500 sendMessage(message, arg, 0, args); 501 } 502 503 @Override sendMessageWithSessionInfo(int message, int arg, BluetoothDevice bluetoothDevice)504 public void sendMessageWithSessionInfo(int message, int arg, BluetoothDevice bluetoothDevice) { 505 SomeArgs args = SomeArgs.obtain(); 506 args.arg1 = Log.createSubsession(); 507 args.arg2 = bluetoothDevice; 508 sendMessage(message, arg, 0, args); 509 } 510 511 @Override sendMessage(int message, Runnable r)512 public void sendMessage(int message, Runnable r) { 513 r.run(); 514 } 515 sendMessage(int what, int arg1, int arg2, Object obj)516 private void sendMessage(int what, int arg1, int arg2, Object obj) { 517 mHandler.sendMessage(Message.obtain(mHandler, what, arg1, arg2, obj)); 518 } 519 520 @Override setCallAudioManager(CallAudioManager callAudioManager)521 public void setCallAudioManager(CallAudioManager callAudioManager) { 522 mCallAudioManager = callAudioManager; 523 } 524 525 @Override getCurrentCallAudioState()526 public CallAudioState getCurrentCallAudioState() { 527 return mCallAudioState; 528 } 529 530 @Override isHfpDeviceAvailable()531 public boolean isHfpDeviceAvailable() { 532 return !mBluetoothRoutes.isEmpty(); 533 } 534 535 @Override getAdapterHandler()536 public Handler getAdapterHandler() { 537 return mHandler; 538 } 539 540 @Override getPendingAudioRoute()541 public PendingAudioRoute getPendingAudioRoute() { 542 return mPendingAudioRoute; 543 } 544 545 @Override dump(IndentingPrintWriter pw)546 public void dump(IndentingPrintWriter pw) { 547 } 548 preHandleMessage(Message msg)549 private void preHandleMessage(Message msg) { 550 if (msg.obj instanceof SomeArgs) { 551 Session session = (Session) ((SomeArgs) msg.obj).arg1; 552 String messageCodeName = MESSAGE_CODE_TO_NAME.get(msg.what, "unknown"); 553 Log.continueSession(session, "CARC.pM_" + messageCodeName); 554 Log.i(this, "Message received: %s=%d, arg1=%d", messageCodeName, msg.what, msg.arg1); 555 } 556 } 557 postHandleMessage(Message msg)558 private void postHandleMessage(Message msg) { 559 Log.endSession(); 560 if (msg.obj instanceof SomeArgs) { 561 ((SomeArgs) msg.obj).recycle(); 562 } 563 } 564 isActive()565 public boolean isActive() { 566 return mIsActive; 567 } 568 isPending()569 public boolean isPending() { 570 return mIsPending; 571 } 572 routeTo(boolean isDestRouteActive, AudioRoute destRoute)573 private void routeTo(boolean isDestRouteActive, AudioRoute destRoute) { 574 if (destRoute == null || (!destRoute.equals(mStreamingRoute) 575 && !getCallSupportedRoutes().contains(destRoute))) { 576 Log.i(this, "Ignore routing to unavailable route: %s", destRoute); 577 if (mFeatureFlags.telecomMetricsSupport()) { 578 mMetricsController.getErrorStats().log(ErrorStats.SUB_CALL_AUDIO, 579 ErrorStats.ERROR_AUDIO_ROUTE_UNAVAILABLE); 580 } 581 return; 582 } 583 // If another BT device connects during RINGING_FOCUS, in-band ringing will be disabled by 584 // default. In this case, we should adjust the active routing value so that we don't try 585 // to connect to the BT device as it will fail. 586 isDestRouteActive = maybeAdjustActiveRouting(destRoute, isDestRouteActive); 587 // It's possible that there are multiple HFP devices connected and if we receive SCO audio 588 // connected for the destination route's BT device, then we shouldn't disconnect SCO when 589 // clearing the communication device for the original route if it was also a HFP device. 590 // This does not apply to the route deactivation scenario. 591 boolean isScoDeviceAlreadyConnected = mScoAudioConnectedDevice != null && isDestRouteActive 592 && Objects.equals(mScoAudioConnectedDevice, mBluetoothRoutes.get(destRoute)); 593 if (mIsPending) { 594 if (destRoute.equals(mPendingAudioRoute.getDestRoute()) 595 && (mIsActive == isDestRouteActive)) { 596 return; 597 } 598 Log.i(this, "Override current pending route destination from %s(active=%b) to " 599 + "%s(active=%b)", 600 mPendingAudioRoute.getDestRoute(), mIsActive, destRoute, isDestRouteActive); 601 // Ensure we don't keep waiting for SPEAKER_ON if dest route gets overridden. 602 if (!mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue() && isDestRouteActive 603 && mPendingAudioRoute.getDestRoute().getType() == TYPE_SPEAKER) { 604 mPendingAudioRoute.clearPendingMessage(new Pair<>(SPEAKER_ON, null)); 605 } 606 // override pending route while keep waiting for still pending messages for the 607 // previous pending route 608 mPendingAudioRoute.setOrigRoute(mIsActive /* origin */, 609 mPendingAudioRoute.getDestRoute(), isDestRouteActive /* dest */, 610 isScoDeviceAlreadyConnected); 611 } else { 612 if (mCurrentRoute.equals(destRoute) && (mIsActive == isDestRouteActive)) { 613 return; 614 } 615 Log.i(this, "Enter pending route, orig%s(active=%b), dest%s(active=%b)", mCurrentRoute, 616 mIsActive, destRoute, isDestRouteActive); 617 // route to pending route 618 if (getCallSupportedRoutes().contains(mCurrentRoute)) { 619 mPendingAudioRoute.setOrigRoute(mIsActive /* origin */, mCurrentRoute, 620 isDestRouteActive /* dest */, isScoDeviceAlreadyConnected); 621 } else { 622 // Avoid waiting for pending messages for an unavailable route 623 mPendingAudioRoute.setOrigRoute(mIsActive /* origin */, DUMMY_ROUTE, 624 isDestRouteActive /* dest */, isScoDeviceAlreadyConnected); 625 } 626 mIsPending = true; 627 } 628 mPendingAudioRoute.setDestRoute(isDestRouteActive, destRoute, 629 mBluetoothRoutes.get(destRoute), isScoDeviceAlreadyConnected); 630 mIsActive = isDestRouteActive; 631 mPendingAudioRoute.evaluatePendingState(); 632 if (mFeatureFlags.telecomMetricsSupport()) { 633 mMetricsController.getAudioRouteStats().onRouteEnter(mPendingAudioRoute); 634 } 635 } 636 handleWiredHeadsetConnected()637 private void handleWiredHeadsetConnected() { 638 AudioRoute wiredHeadsetRoute = null; 639 try { 640 wiredHeadsetRoute = mAudioRouteFactory.create(AudioRoute.TYPE_WIRED, null, 641 mAudioManager); 642 } catch (IllegalArgumentException e) { 643 if (mFeatureFlags.telecomMetricsSupport()) { 644 mMetricsController.getErrorStats().log(ErrorStats.SUB_CALL_AUDIO, 645 ErrorStats.ERROR_EXTERNAL_EXCEPTION); 646 } 647 Log.e(this, e, "Can't find available audio device info for route type:" 648 + AudioRoute.DEVICE_TYPE_STRINGS.get(AudioRoute.TYPE_WIRED)); 649 } 650 651 if (wiredHeadsetRoute != null) { 652 updateAvailableRoutes(wiredHeadsetRoute, true); 653 updateAvailableRoutes(mEarpieceWiredRoute, false); 654 mTypeRoutes.put(AudioRoute.TYPE_WIRED, wiredHeadsetRoute); 655 mEarpieceWiredRoute = wiredHeadsetRoute; 656 routeTo(mIsActive, wiredHeadsetRoute); 657 onAvailableRoutesChanged(); 658 } 659 } 660 handleWiredHeadsetDisconnected()661 public void handleWiredHeadsetDisconnected() { 662 // Update audio route states 663 AudioRoute wiredHeadsetRoute = mTypeRoutes.remove(AudioRoute.TYPE_WIRED); 664 if (wiredHeadsetRoute != null) { 665 updateAvailableRoutes(wiredHeadsetRoute, false); 666 mEarpieceWiredRoute = null; 667 } 668 AudioRoute earpieceRoute = null; 669 try { 670 earpieceRoute = mTypeRoutes.get(AudioRoute.TYPE_EARPIECE) == null 671 ? mAudioRouteFactory.create(AudioRoute.TYPE_EARPIECE, null, 672 mAudioManager) 673 : mTypeRoutes.get(AudioRoute.TYPE_EARPIECE); 674 } catch (IllegalArgumentException e) { 675 if (mFeatureFlags.telecomMetricsSupport()) { 676 mMetricsController.getErrorStats().log(ErrorStats.SUB_CALL_AUDIO, 677 ErrorStats.ERROR_EXTERNAL_EXCEPTION); 678 } 679 Log.e(this, e, "Can't find available audio device info for route type:" 680 + AudioRoute.DEVICE_TYPE_STRINGS.get(AudioRoute.TYPE_EARPIECE)); 681 } 682 if (earpieceRoute != null) { 683 updateAvailableRoutes(earpieceRoute, true); 684 mEarpieceWiredRoute = earpieceRoute; 685 // In the case that the route was never created, ensure that we update the map. 686 mTypeRoutes.putIfAbsent(AudioRoute.TYPE_EARPIECE, mEarpieceWiredRoute); 687 } 688 onAvailableRoutesChanged(); 689 690 // Route to expected state 691 if (mCurrentRoute.equals(wiredHeadsetRoute)) { 692 // Preserve speaker routing if it was the last audio routing path when the wired headset 693 // disconnects. Ignore this special cased routing when the route isn't active 694 // (in other words, when we're not in a call). 695 AudioRoute route = mWasOnSpeaker && mIsActive && mSpeakerDockRoute != null 696 && mSpeakerDockRoute.getType() == AudioRoute.TYPE_SPEAKER 697 ? mSpeakerDockRoute : getBaseRoute(true, null); 698 routeTo(mIsActive, route); 699 } 700 } 701 handleDockConnected()702 private void handleDockConnected() { 703 AudioRoute dockRoute = null; 704 try { 705 dockRoute = mAudioRouteFactory.create(AudioRoute.TYPE_DOCK, null, mAudioManager); 706 } catch (IllegalArgumentException e) { 707 if (mFeatureFlags.telecomMetricsSupport()) { 708 mMetricsController.getErrorStats().log(ErrorStats.SUB_CALL_AUDIO, 709 ErrorStats.ERROR_EXTERNAL_EXCEPTION); 710 } 711 Log.e(this, e, "Can't find available audio device info for route type:" 712 + AudioRoute.DEVICE_TYPE_STRINGS.get(AudioRoute.TYPE_WIRED)); 713 } 714 715 if (dockRoute != null) { 716 updateAvailableRoutes(dockRoute, true); 717 updateAvailableRoutes(mSpeakerDockRoute, false); 718 mTypeRoutes.put(AudioRoute.TYPE_DOCK, dockRoute); 719 mSpeakerDockRoute = dockRoute; 720 routeTo(mIsActive, dockRoute); 721 onAvailableRoutesChanged(); 722 } 723 } 724 handleDockDisconnected()725 public void handleDockDisconnected() { 726 // Update audio route states 727 AudioRoute dockRoute = mTypeRoutes.get(AudioRoute.TYPE_DOCK); 728 if (dockRoute != null) { 729 updateAvailableRoutes(dockRoute, false); 730 mSpeakerDockRoute = null; 731 } 732 AudioRoute speakerRoute = mTypeRoutes.get(AudioRoute.TYPE_SPEAKER); 733 if (speakerRoute != null) { 734 updateAvailableRoutes(speakerRoute, true); 735 mSpeakerDockRoute = speakerRoute; 736 } 737 onAvailableRoutesChanged(); 738 739 // Route to expected state 740 if (mCurrentRoute.equals(dockRoute)) { 741 routeTo(mIsActive, getBaseRoute(true, null)); 742 } 743 } 744 handleStreamingEnabled()745 private void handleStreamingEnabled() { 746 if (!mCurrentRoute.equals(mStreamingRoute)) { 747 routeTo(mIsActive, mStreamingRoute); 748 } else { 749 Log.i(this, "ignore enable streaming, already in streaming"); 750 } 751 } 752 handleStreamingDisabled()753 private void handleStreamingDisabled() { 754 if (mCurrentRoute.equals(mStreamingRoute)) { 755 mCurrentRoute = DUMMY_ROUTE; 756 onAvailableRoutesChanged(); 757 routeTo(mIsActive, getBaseRoute(true, null)); 758 } else { 759 Log.i(this, "ignore disable streaming, not in streaming"); 760 } 761 } 762 763 /** 764 * Handles the case when SCO audio is connected for the BT headset. This follows shortly after 765 * the BT device has been established as an active device (BT_ACTIVE_DEVICE_PRESENT) and doesn't 766 * apply to other BT device types. In this case, the pending audio route will process the 767 * BT_AUDIO_CONNECTED message that will trigger routing to the pending destination audio route; 768 * otherwise, routing will be ignored if there aren't pending routes to be processed. 769 * 770 * Message being handled: BT_AUDIO_CONNECTED 771 */ handleBtAudioActive(BluetoothDevice bluetoothDevice)772 private void handleBtAudioActive(BluetoothDevice bluetoothDevice) { 773 if (mIsPending) { 774 Log.i(this, "handleBtAudioActive: is pending path"); 775 if (Objects.equals(mPendingAudioRoute.getDestRoute().getBluetoothAddress(), 776 bluetoothDevice.getAddress())) { 777 mPendingAudioRoute.onMessageReceived(new Pair<>(BT_AUDIO_CONNECTED, 778 bluetoothDevice.getAddress()), null); 779 } 780 } 781 } 782 783 /** 784 * Handles the case when SCO audio is disconnected for the BT headset. In this case, the pending 785 * audio route will process the BT_AUDIO_DISCONNECTED message which will trigger routing to the 786 * pending destination audio route; otherwise, routing will be ignored if there aren't any 787 * pending routes to be processed. 788 * 789 * Message being handled: BT_AUDIO_DISCONNECTED 790 */ handleBtAudioInactive(BluetoothDevice bluetoothDevice)791 private void handleBtAudioInactive(BluetoothDevice bluetoothDevice) { 792 if (mIsPending) { 793 Log.i(this, "handleBtAudioInactive: is pending path"); 794 if (Objects.equals(mPendingAudioRoute.getOrigRoute().getBluetoothAddress(), 795 bluetoothDevice.getAddress())) { 796 mPendingAudioRoute.onMessageReceived(new Pair<>(BT_AUDIO_DISCONNECTED, 797 bluetoothDevice.getAddress()), null); 798 } 799 } 800 } 801 802 /** 803 * This particular routing occurs when the BT device is trying to establish itself as a 804 * connected device (refer to BluetoothStateReceiver#handleConnectionStateChanged). The device 805 * is included as an available route and cached into the current BT routes. 806 * 807 * Message being handled: BT_DEVICE_ADDED 808 */ handleBtConnected(@udioRoute.AudioRouteType int type, BluetoothDevice bluetoothDevice)809 private void handleBtConnected(@AudioRoute.AudioRouteType int type, 810 BluetoothDevice bluetoothDevice) { 811 if (containsHearingAidPair(type, bluetoothDevice)) { 812 return; 813 } 814 815 AudioRoute bluetoothRoute = mAudioRouteFactory.create(type, bluetoothDevice.getAddress(), 816 mAudioManager); 817 if (bluetoothRoute == null) { 818 Log.w(this, "Can't find available audio device info for route type:" 819 + AudioRoute.DEVICE_TYPE_STRINGS.get(type)); 820 } else { 821 Log.i(this, "bluetooth route added: " + bluetoothRoute); 822 updateAvailableRoutes(bluetoothRoute, true); 823 mBluetoothRoutes.put(bluetoothRoute, bluetoothDevice); 824 onAvailableRoutesChanged(); 825 } 826 } 827 828 /** 829 * Handles the case when the BT device is in a disconnecting/disconnected state. In this case, 830 * the audio route for the specified device is removed from the available BT routes and the 831 * audio is routed to an available route if the current route is pointing to the device which 832 * got disconnected. 833 * 834 * Message being handled: BT_DEVICE_REMOVED 835 */ handleBtDisconnected(@udioRoute.AudioRouteType int type, BluetoothDevice bluetoothDevice)836 private void handleBtDisconnected(@AudioRoute.AudioRouteType int type, 837 BluetoothDevice bluetoothDevice) { 838 // Clean up unavailable routes 839 AudioRoute bluetoothRoute = getBluetoothRoute(type, bluetoothDevice.getAddress()); 840 if (bluetoothRoute != null) { 841 Log.i(this, "bluetooth route removed: " + bluetoothRoute); 842 mBluetoothRoutes.remove(bluetoothRoute); 843 updateAvailableRoutes(bluetoothRoute, false); 844 onAvailableRoutesChanged(); 845 } 846 847 // Fallback to an available route 848 if (Objects.equals(mCurrentRoute, bluetoothRoute)) { 849 routeTo(mIsActive, getBaseRoute(true, null)); 850 } 851 } 852 853 /** 854 * This particular routing occurs when the specified bluetooth device is marked as the active 855 * device (refer to BluetoothStateReceiver#handleActiveDeviceChanged). This takes care of 856 * moving the call audio route to the bluetooth route. 857 * 858 * Message being handled: BT_ACTIVE_DEVICE_PRESENT 859 */ handleBtActiveDevicePresent(@udioRoute.AudioRouteType int type, String deviceAddress)860 private void handleBtActiveDevicePresent(@AudioRoute.AudioRouteType int type, 861 String deviceAddress) { 862 AudioRoute bluetoothRoute = getBluetoothRoute(type, deviceAddress); 863 boolean isBtDeviceCurrentActive = Objects.equals(bluetoothRoute, 864 getArbitraryBluetoothDevice()); 865 if (bluetoothRoute != null && isBtDeviceCurrentActive) { 866 Log.i(this, "request to route to bluetooth route: %s (active=%b)", bluetoothRoute, 867 mIsActive); 868 routeTo(mIsActive, bluetoothRoute); 869 } else { 870 Log.i(this, "request to route to unavailable bluetooth route or the route isn't the " 871 + "currently active device - type (%s), address (%s)", type, deviceAddress); 872 } 873 } 874 875 /** 876 * Handles routing for when the active BT device is removed for a given audio route type. In 877 * this case, the audio is routed to another available route if the current route hasn't been 878 * adjusted yet or there is a pending destination route associated with the device type that 879 * went inactive. Note that BT_DEVICE_REMOVED will be processed first in this case, which will 880 * handle removing the BT route for the device that went inactive as well as falling back to 881 * an available route. 882 * 883 * Message being handled: BT_ACTIVE_DEVICE_GONE 884 */ handleBtActiveDeviceGone(@udioRoute.AudioRouteType int type)885 private void handleBtActiveDeviceGone(@AudioRoute.AudioRouteType int type) { 886 // Determine what the active device for the BT audio type was so that we can exclude this 887 // device from being used when calculating the base route. 888 String previouslyActiveDeviceAddress = mFeatureFlags 889 .resolveActiveBtRoutingAndBtTimingIssue() 890 ? mActiveDeviceCache.get(type) 891 : null; 892 // It's possible that the dest route hasn't been set yet when the controller is first 893 // initialized. 894 boolean pendingRouteNeedsUpdate = mPendingAudioRoute.getDestRoute() != null 895 && mPendingAudioRoute.getDestRoute().getType() == type; 896 boolean currentRouteNeedsUpdate = mCurrentRoute.getType() == type; 897 if (mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()) { 898 if (pendingRouteNeedsUpdate) { 899 pendingRouteNeedsUpdate = mPendingAudioRoute.getDestRoute().getBluetoothAddress() 900 .equals(previouslyActiveDeviceAddress); 901 } 902 if (currentRouteNeedsUpdate) { 903 currentRouteNeedsUpdate = mCurrentRoute.getBluetoothAddress() 904 .equals(previouslyActiveDeviceAddress); 905 } 906 } 907 if ((mIsPending && pendingRouteNeedsUpdate) || (!mIsPending && currentRouteNeedsUpdate)) { 908 maybeDisableWasOnSpeaker(true); 909 // Fallback to an available route excluding the previously active device. 910 routeTo(mIsActive, getBaseRoute(true, previouslyActiveDeviceAddress)); 911 } 912 } 913 handleMuteChanged(boolean mute)914 private void handleMuteChanged(boolean mute) { 915 mIsMute = mute; 916 if (mIsMute != mAudioManager.isMicrophoneMute() && mIsActive) { 917 IAudioService audioService = mAudioServiceFactory.getAudioService(); 918 Log.i(this, "changing microphone mute state to: %b [serviceIsNull=%b]", mute, 919 audioService == null); 920 if (audioService != null) { 921 try { 922 audioService.setMicrophoneMute(mute, mContext.getOpPackageName(), 923 mCallsManager.getCurrentUserHandle().getIdentifier(), 924 mContext.getAttributionTag()); 925 } catch (RemoteException e) { 926 if (mFeatureFlags.telecomMetricsSupport()) { 927 mMetricsController.getErrorStats().log(ErrorStats.SUB_CALL_AUDIO, 928 ErrorStats.ERROR_EXTERNAL_EXCEPTION); 929 } 930 Log.e(this, e, "Remote exception while toggling mute."); 931 return; 932 } 933 } 934 } 935 onMuteStateChanged(mIsMute); 936 } 937 handleSwitchFocus(int focus, int handleEndTone)938 private void handleSwitchFocus(int focus, int handleEndTone) { 939 Log.i(this, "handleSwitchFocus: focus (%s)", focus); 940 mFocusType = focus; 941 switch (focus) { 942 case NO_FOCUS -> { 943 mWasOnSpeaker = false; 944 // Notify the CallAudioModeStateMachine that audio operations are complete so 945 // that we can relinquish audio focus. 946 mCallAudioManager.notifyAudioOperationsComplete(); 947 // Reset mute state after call ends. This should remain unaffected if audio routing 948 // never went active. 949 handleMuteChanged(false); 950 // Ensure we reset call audio state at the end of the call (i.e. if we're on 951 // speaker, route back to earpiece). If we're on BT, remain on BT if it's still 952 // connected. 953 AudioRoute route = mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue() 954 ? calculateBaselineRoute(false, true, null) 955 : mCurrentRoute; 956 routeTo(false, route); 957 // Clear pending messages 958 mPendingAudioRoute.clearPendingMessages(); 959 clearRingingBluetoothAddress(); 960 mUsePreferredDeviceStrategy = true; 961 } 962 case ACTIVE_FOCUS -> { 963 // Route to active baseline route (we may need to change audio route in the case 964 // when a video call is put on hold). Ignore route changes if we're handling playing 965 // the end tone. Otherwise, it's possible that we'll override the route a client has 966 // previously requested. 967 if (handleEndTone == 0) { 968 // Cache BT device switch in the case that inband ringing is disabled and audio 969 // was routed to a watch. When active focus is received, this selection will be 970 // honored provided that the current route is associated. 971 Log.i(this, "handleSwitchFocus (ACTIVE_FOCUS): mBluetoothAddressForRinging = " 972 + "%s, mCurrentRoute = %s", mBluetoothAddressForRinging, mCurrentRoute); 973 AudioRoute audioRoute = mBluetoothAddressForRinging != null 974 && mBluetoothAddressForRinging.equals( 975 mCurrentRoute.getBluetoothAddress()) 976 ? mCurrentRoute 977 : getBaseRoute(true, null); 978 // Once we have processed active focus once during the call, we can ignore using 979 // the preferred device strategy. 980 mUsePreferredDeviceStrategy = false; 981 routeTo(true, audioRoute); 982 clearRingingBluetoothAddress(); 983 } 984 } 985 case RINGING_FOCUS -> { 986 if (!mIsActive) { 987 AudioRoute route = getBaseRoute(true, null); 988 BluetoothDevice device = mBluetoothRoutes.get(route); 989 // Check if in-band ringtone is enabled for the device; if it isn't, move to 990 // inactive route. 991 if (device != null && !mBluetoothRouteManager 992 .isInbandRingEnabled(route.getType(), device)) { 993 routeTo(false, route); 994 } else { 995 routeTo(true, route); 996 } 997 } else { 998 // Route is already active. 999 BluetoothDevice device = mBluetoothRoutes.get(mCurrentRoute); 1000 if (device != null && !mBluetoothRouteManager 1001 .isInbandRingEnabled(mCurrentRoute.getType(), device)) { 1002 routeTo(false, mCurrentRoute); 1003 } 1004 } 1005 } 1006 } 1007 } 1008 handleSwitchEarpiece(boolean isUserRequest)1009 public void handleSwitchEarpiece(boolean isUserRequest) { 1010 AudioRoute earpieceRoute = mTypeRoutes.get(AudioRoute.TYPE_EARPIECE); 1011 if (earpieceRoute != null && getCallSupportedRoutes().contains(earpieceRoute)) { 1012 maybeDisableWasOnSpeaker(isUserRequest); 1013 routeTo(mIsActive, earpieceRoute); 1014 } else { 1015 Log.i(this, "ignore switch earpiece request"); 1016 } 1017 } 1018 handleSwitchBluetooth(String address, boolean isUserRequest)1019 private void handleSwitchBluetooth(String address, boolean isUserRequest) { 1020 Log.i(this, "handle switch to bluetooth with address %s", address); 1021 AudioRoute bluetoothRoute = null; 1022 BluetoothDevice bluetoothDevice = null; 1023 if (address == null) { 1024 bluetoothRoute = getArbitraryBluetoothDevice(); 1025 bluetoothDevice = mBluetoothRoutes.get(bluetoothRoute); 1026 } else { 1027 for (AudioRoute route : getCallSupportedRoutes()) { 1028 if (Objects.equals(address, route.getBluetoothAddress())) { 1029 bluetoothRoute = route; 1030 bluetoothDevice = mBluetoothRoutes.get(route); 1031 break; 1032 } 1033 } 1034 } 1035 1036 if (bluetoothRoute != null && bluetoothDevice != null) { 1037 maybeDisableWasOnSpeaker(isUserRequest); 1038 if (mFocusType == RINGING_FOCUS) { 1039 routeTo(mBluetoothRouteManager 1040 .isInbandRingEnabled(bluetoothRoute.getType(), bluetoothDevice) 1041 && mIsActive, bluetoothRoute); 1042 mBluetoothAddressForRinging = bluetoothDevice.getAddress(); 1043 } else { 1044 routeTo(mIsActive, bluetoothRoute); 1045 } 1046 } else { 1047 Log.i(this, "ignore switch bluetooth request to unavailable address"); 1048 } 1049 } 1050 1051 /** 1052 * Retrieve the active BT device, if available, otherwise return the most recently tracked 1053 * active device, or null if none are available. 1054 * @return {@link AudioRoute} of the BT device. 1055 */ getArbitraryBluetoothDevice()1056 private AudioRoute getArbitraryBluetoothDevice() { 1057 synchronized (mLock) { 1058 if (mActiveBluetoothDevice != null) { 1059 return getBluetoothRoute( 1060 mActiveBluetoothDevice.first, mActiveBluetoothDevice.second); 1061 } else if (!mBluetoothRoutes.isEmpty()) { 1062 return mBluetoothRoutes.keySet().stream().toList() 1063 .get(mBluetoothRoutes.size() - 1); 1064 } 1065 return null; 1066 } 1067 } 1068 handleSwitchHeadset(boolean isUserRequest)1069 private void handleSwitchHeadset(boolean isUserRequest) { 1070 AudioRoute headsetRoute = mTypeRoutes.get(AudioRoute.TYPE_WIRED); 1071 if (headsetRoute != null && getCallSupportedRoutes().contains(headsetRoute)) { 1072 maybeDisableWasOnSpeaker(isUserRequest); 1073 routeTo(mIsActive, headsetRoute); 1074 } else { 1075 Log.i(this, "ignore switch headset request"); 1076 } 1077 } 1078 handleSwitchSpeaker()1079 private void handleSwitchSpeaker() { 1080 if (mSpeakerDockRoute != null && getCallSupportedRoutes().contains(mSpeakerDockRoute) 1081 && mSpeakerDockRoute.getType() == AudioRoute.TYPE_SPEAKER) { 1082 routeTo(mIsActive, mSpeakerDockRoute); 1083 } else { 1084 Log.i(this, "ignore switch speaker request"); 1085 } 1086 } 1087 handleSwitchBaselineRoute(boolean isExplicitUserRequest, boolean includeBluetooth, String btAddressToExclude)1088 private void handleSwitchBaselineRoute(boolean isExplicitUserRequest, boolean includeBluetooth, 1089 String btAddressToExclude) { 1090 Log.i(this, "handleSwitchBaselineRoute: includeBluetooth: %b, " 1091 + "btAddressToExclude: %s", includeBluetooth, btAddressToExclude); 1092 AudioRoute pendingDestRoute = mPendingAudioRoute.getDestRoute(); 1093 boolean areExcludedBtAndDestBtSame = btAddressToExclude != null 1094 && pendingDestRoute != null 1095 && Objects.equals(btAddressToExclude, pendingDestRoute.getBluetoothAddress()); 1096 Pair<Integer, String> btDevicePendingMsg = 1097 new Pair<>(BT_AUDIO_CONNECTED, btAddressToExclude); 1098 1099 // If SCO is once again connected or there's a pending message for BT_AUDIO_CONNECTED, then 1100 // we know that the device has reconnected or is in the middle of connecting. Ignore routing 1101 // out of this BT device. 1102 boolean isExcludedDeviceConnectingOrConnected = areExcludedBtAndDestBtSame 1103 && (Objects.equals(mBluetoothRoutes.get(pendingDestRoute), mScoAudioConnectedDevice) 1104 || mPendingAudioRoute.getPendingMessages().contains(btDevicePendingMsg)); 1105 // Check if the pending audio route or current route is already different from the route 1106 // including the BT device that should be excluded from route selection. 1107 boolean isCurrentOrDestRouteDifferent = btAddressToExclude != null 1108 && ((mIsPending && !btAddressToExclude.equals(mPendingAudioRoute.getDestRoute() 1109 .getBluetoothAddress())) || (!mIsPending && !btAddressToExclude.equals( 1110 mCurrentRoute.getBluetoothAddress()))); 1111 if (mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()) { 1112 if (isExcludedDeviceConnectingOrConnected) { 1113 Log.i(this, "BT device with address (%s) is currently connecting/connected. " 1114 + "Ignoring route switch.", btAddressToExclude); 1115 return; 1116 } else if (isCurrentOrDestRouteDifferent) { 1117 Log.i(this, "Current or pending audio route isn't routed to device with address " 1118 + "(%s). Ignoring route switch.", btAddressToExclude); 1119 return; 1120 } 1121 } 1122 maybeDisableWasOnSpeaker(isExplicitUserRequest); 1123 routeTo(mIsActive, calculateBaselineRoute(isExplicitUserRequest, includeBluetooth, 1124 btAddressToExclude)); 1125 } 1126 handleSpeakerOn()1127 private void handleSpeakerOn() { 1128 if (isPending()) { 1129 Log.i(this, "handleSpeakerOn: sending SPEAKER_ON to pending audio route"); 1130 mPendingAudioRoute.onMessageReceived(new Pair<>(SPEAKER_ON, null), null); 1131 // Update status bar notification if we are in a call. 1132 mStatusBarNotifier.notifySpeakerphone(mCallsManager.hasAnyCalls()); 1133 } else { 1134 if (mSpeakerDockRoute != null && getCallSupportedRoutes().contains(mSpeakerDockRoute) 1135 && mSpeakerDockRoute.getType() == AudioRoute.TYPE_SPEAKER 1136 && mCurrentRoute.getType() != AudioRoute.TYPE_SPEAKER) { 1137 routeTo(mIsActive, mSpeakerDockRoute); 1138 // Since the route switching triggered by this message, we need to manually send it 1139 // again so that we won't stuck in the pending route 1140 if (mIsActive) { 1141 sendMessageWithSessionInfo(SPEAKER_ON); 1142 } 1143 } 1144 } 1145 } 1146 handleSpeakerOff()1147 private void handleSpeakerOff() { 1148 if (isPending()) { 1149 Log.i(this, "handleSpeakerOff - sending SPEAKER_OFF to pending audio route"); 1150 mPendingAudioRoute.onMessageReceived(new Pair<>(SPEAKER_OFF, null), null); 1151 // Update status bar notification 1152 mStatusBarNotifier.notifySpeakerphone(false); 1153 } else if (mCurrentRoute.getType() == AudioRoute.TYPE_SPEAKER) { 1154 routeTo(mIsActive, getBaseRoute(true, null)); 1155 // Since the route switching triggered by this message, we need to manually send it 1156 // again so that we won't stuck in the pending route 1157 if (mIsActive) { 1158 sendMessageWithSessionInfo(SPEAKER_OFF); 1159 } 1160 onAvailableRoutesChanged(); 1161 } 1162 } 1163 1164 /** 1165 * This is invoked when there are no more pending audio routes to be processed, which signals 1166 * a change for the current audio route and the call audio state to be updated accordingly. 1167 */ handleExitPendingRoute()1168 public void handleExitPendingRoute() { 1169 if (mIsPending) { 1170 mCurrentRoute = mPendingAudioRoute.getDestRoute(); 1171 Log.addEvent(mCallsManager.getForegroundCall(), LogUtils.Events.AUDIO_ROUTE, 1172 "Entering audio route: " + mCurrentRoute + " (active=" + mIsActive + ")"); 1173 mIsPending = false; 1174 mPendingAudioRoute.clearPendingMessages(); 1175 onCurrentRouteChanged(); 1176 if (mIsActive) { 1177 // Only set mWasOnSpeaker if the routing was active. We don't want to consider this 1178 // selection outside of a call. 1179 if (mCurrentRoute.getType() == TYPE_SPEAKER) { 1180 mWasOnSpeaker = true; 1181 } 1182 // Reinitialize the audio ops complete latch since the routing went active. We 1183 // should always expect operations to complete after this point. 1184 if (mAudioOperationsCompleteLatch.getCount() == 0) { 1185 mAudioOperationsCompleteLatch = new CountDownLatch(1); 1186 } 1187 mAudioActiveCompleteLatch.countDown(); 1188 } else { 1189 // Reinitialize the active routing latch when audio ops are complete so that it can 1190 // once again be processed when a new call is placed/received. 1191 if (mAudioActiveCompleteLatch.getCount() == 0) { 1192 mAudioActiveCompleteLatch = new CountDownLatch(1); 1193 } 1194 mAudioOperationsCompleteLatch.countDown(); 1195 } 1196 if (mFeatureFlags.telecomMetricsSupport()) { 1197 mMetricsController.getAudioRouteStats().onRouteExit(mPendingAudioRoute, true); 1198 } 1199 } 1200 } 1201 onCurrentRouteChanged()1202 private void onCurrentRouteChanged() { 1203 synchronized (mLock) { 1204 BluetoothDevice activeBluetoothDevice = null; 1205 int route = ROUTE_MAP.get(mCurrentRoute.getType()); 1206 if (route == CallAudioState.ROUTE_STREAMING) { 1207 updateCallAudioState(new CallAudioState(mIsMute, route, route)); 1208 return; 1209 } 1210 if (route == CallAudioState.ROUTE_BLUETOOTH) { 1211 activeBluetoothDevice = mBluetoothRoutes.get(mCurrentRoute); 1212 } 1213 updateCallAudioState(new CallAudioState(mIsMute, route, 1214 mCallAudioState.getRawSupportedRouteMask(), activeBluetoothDevice, 1215 mCallAudioState.getSupportedBluetoothDevices())); 1216 } 1217 } 1218 onAvailableRoutesChanged()1219 private void onAvailableRoutesChanged() { 1220 synchronized (mLock) { 1221 int routeMask = 0; 1222 Set<BluetoothDevice> availableBluetoothDevices = new HashSet<>(); 1223 for (AudioRoute route : getCallSupportedRoutes()) { 1224 routeMask |= ROUTE_MAP.get(route.getType()); 1225 if (BT_AUDIO_ROUTE_TYPES.contains(route.getType())) { 1226 BluetoothDevice deviceToAdd = mBluetoothRoutes.get(route); 1227 // Only include the lead device for LE audio (otherwise, the routes will show 1228 // two separate devices in the UI). 1229 if (deviceToAdd != null && route.getType() == AudioRoute.TYPE_BLUETOOTH_LE 1230 && getLeAudioService() != null) { 1231 int groupId = getLeAudioService().getGroupId(deviceToAdd); 1232 if (groupId != BluetoothLeAudio.GROUP_ID_INVALID) { 1233 deviceToAdd = getLeAudioService().getConnectedGroupLeadDevice(groupId); 1234 } 1235 } 1236 // This will only ever be null when the lead device (LE) is disconnected and 1237 // try to obtain the lead device for the 2nd bud. 1238 if (deviceToAdd != null) { 1239 availableBluetoothDevices.add(deviceToAdd); 1240 } 1241 } 1242 } 1243 1244 updateCallAudioState(new CallAudioState(mIsMute, mCallAudioState.getRoute(), routeMask, 1245 mCallAudioState.getActiveBluetoothDevice(), availableBluetoothDevices)); 1246 } 1247 } 1248 onMuteStateChanged(boolean mute)1249 private void onMuteStateChanged(boolean mute) { 1250 updateCallAudioState(new CallAudioState(mute, mCallAudioState.getRoute(), 1251 mCallAudioState.getSupportedRouteMask(), mCallAudioState.getActiveBluetoothDevice(), 1252 mCallAudioState.getSupportedBluetoothDevices())); 1253 } 1254 1255 /** 1256 * Retrieves the current call's supported audio route and adjusts the audio routing if the 1257 * current route isn't supported. 1258 */ updateRouteForForeground()1259 private void updateRouteForForeground() { 1260 boolean updatedRouteForCall = updateCallSupportedAudioRoutes(); 1261 // Ensure that current call audio state has updated routes for current call. 1262 if (updatedRouteForCall) { 1263 mCallAudioState = new CallAudioState(mIsMute, mCallAudioState.getRoute(), 1264 mCallSupportedRouteMask, mCallAudioState.getActiveBluetoothDevice(), 1265 mCallAudioState.getSupportedBluetoothDevices()); 1266 // Update audio route if foreground call doesn't support the current route. 1267 if ((mCallSupportedRouteMask & mCallAudioState.getRoute()) == 0) { 1268 routeTo(mIsActive, getBaseRoute(true, null)); 1269 } 1270 } 1271 } 1272 1273 /** 1274 * Update supported audio routes for the foreground call if present. 1275 */ updateCallSupportedAudioRoutes()1276 private boolean updateCallSupportedAudioRoutes() { 1277 int availableRouteMask = 0; 1278 Call foregroundCall = mCallsManager.getForegroundCall(); 1279 mCallSupportedRoutes.clear(); 1280 if (foregroundCall != null) { 1281 int foregroundCallSupportedRouteMask = foregroundCall.getSupportedAudioRoutes(); 1282 for (AudioRoute route : getAvailableRoutes()) { 1283 int routeType = ROUTE_MAP.get(route.getType()); 1284 availableRouteMask |= routeType; 1285 if ((routeType & foregroundCallSupportedRouteMask) == routeType) { 1286 mCallSupportedRoutes.add(route); 1287 } 1288 } 1289 mCallSupportedRouteMask = availableRouteMask & foregroundCallSupportedRouteMask; 1290 return true; 1291 } else { 1292 mCallSupportedRouteMask = -1; 1293 return false; 1294 } 1295 } 1296 updateCallAudioState(CallAudioState newCallAudioState)1297 private void updateCallAudioState(CallAudioState newCallAudioState) { 1298 synchronized (mTelecomLock) { 1299 Log.i(this, "updateCallAudioState: updating call audio state to %s", newCallAudioState); 1300 CallAudioState oldState = mCallAudioState; 1301 mCallAudioState = newCallAudioState; 1302 // Update status bar notification 1303 mStatusBarNotifier.notifyMute(newCallAudioState.isMuted()); 1304 mCallsManager.onCallAudioStateChanged(oldState, mCallAudioState); 1305 updateAudioStateForTrackedCalls(mCallAudioState); 1306 } 1307 } 1308 updateAudioStateForTrackedCalls(CallAudioState newCallAudioState)1309 private void updateAudioStateForTrackedCalls(CallAudioState newCallAudioState) { 1310 List<Call> calls = new ArrayList<>(mCallsManager.getTrackedCalls()); 1311 for (Call call : calls) { 1312 if (call != null && call.getConnectionService() != null) { 1313 call.getConnectionService().onCallAudioStateChanged(call, newCallAudioState); 1314 } 1315 } 1316 } 1317 getPreferredAudioRouteFromStrategy()1318 private AudioRoute getPreferredAudioRouteFromStrategy() { 1319 // Get preferred device 1320 AudioDeviceAttributes deviceAttr = getPreferredDeviceForStrategy(); 1321 Log.i(this, "getPreferredAudioRouteFromStrategy: preferred device is %s", deviceAttr); 1322 if (deviceAttr == null) { 1323 return null; 1324 } 1325 1326 // Get corresponding audio route 1327 @AudioRoute.AudioRouteType int type = DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.get( 1328 deviceAttr.getType()); 1329 AudioDeviceInfo currentCommunicationDevice = null; 1330 if (mFeatureFlags.updatePreferredAudioDeviceLogic()) { 1331 currentCommunicationDevice = getCurrentCommunicationDevice(); 1332 } 1333 // We will default to TYPE_INVALID if the currentCommunicationDevice is null or the type 1334 // cannot be resolved from the given audio device info. 1335 int communicationDeviceAudioType = getAudioType(currentCommunicationDevice); 1336 // Sync the preferred device strategy with the current communication device if there's a 1337 // valid audio device output set as the preferred device strategy. This will address timing 1338 // issues between updates made to the preferred device strategy. From the audio fwk 1339 // standpoint, updates to the communication device take precedent to changes in the 1340 // preferred device strategy so the former should be used as the source of truth. 1341 if (type != TYPE_INVALID && communicationDeviceAudioType != TYPE_INVALID 1342 && communicationDeviceAudioType != type) { 1343 type = communicationDeviceAudioType; 1344 } 1345 if (BT_AUDIO_ROUTE_TYPES.contains(type)) { 1346 return getBluetoothRoute(type, deviceAttr.getAddress()); 1347 } else { 1348 return mTypeRoutes.get(type); 1349 } 1350 } 1351 getPreferredDeviceForStrategy()1352 private AudioDeviceAttributes getPreferredDeviceForStrategy() { 1353 // Get audio produce strategy 1354 AudioProductStrategy strategy = null; 1355 final AudioAttributes attr = new AudioAttributes.Builder() 1356 .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION) 1357 .build(); 1358 List<AudioProductStrategy> strategies = AudioManager.getAudioProductStrategies(); 1359 for (AudioProductStrategy s : strategies) { 1360 if (s.supportsAudioAttributes(attr)) { 1361 strategy = s; 1362 } 1363 } 1364 if (strategy == null) { 1365 return null; 1366 } 1367 1368 return mAudioManager.getPreferredDeviceForStrategy(strategy); 1369 } 1370 getPreferredAudioRouteFromDefault(boolean isExplicitUserRequest, boolean includeBluetooth, String btAddressToExclude)1371 private AudioRoute getPreferredAudioRouteFromDefault(boolean isExplicitUserRequest, 1372 boolean includeBluetooth, String btAddressToExclude) { 1373 boolean skipEarpiece = false; 1374 Call foregroundCall = mCallAudioManager.getForegroundCall(); 1375 if (!mFeatureFlags.fixUserRequestBaselineRouteVideoCall()) { 1376 isExplicitUserRequest = false; 1377 } 1378 if (!isExplicitUserRequest) { 1379 synchronized (mTelecomLock) { 1380 skipEarpiece = foregroundCall != null 1381 && VideoProfile.isVideo(foregroundCall.getVideoState()); 1382 } 1383 } 1384 // Route to earpiece, wired, or speaker route if there are not bluetooth routes or if there 1385 // are only wearables available. 1386 AudioRoute activeWatchOrNonWatchDeviceRoute = 1387 getActiveWatchOrNonWatchDeviceRoute(btAddressToExclude); 1388 if ((!mCallSupportedRoutes.isEmpty() && (mCallSupportedRouteMask 1389 & CallAudioState.ROUTE_BLUETOOTH) == 0) || mBluetoothRoutes.isEmpty() 1390 || !includeBluetooth || activeWatchOrNonWatchDeviceRoute == null) { 1391 Log.i(this, "getPreferredAudioRouteFromDefault: Audio routing defaulting to " 1392 + "available non-BT route."); 1393 boolean callSupportsEarpieceWiredRoute = mCallSupportedRoutes.isEmpty() 1394 || mCallSupportedRoutes.contains(mEarpieceWiredRoute); 1395 // If call supported route doesn't contain earpiece/wired/BT, it should have speaker 1396 // enabled. Otherwise, no routes would be supported for the call which should never be 1397 // the case. 1398 AudioRoute defaultRoute = mEarpieceWiredRoute != null && callSupportsEarpieceWiredRoute 1399 ? mEarpieceWiredRoute 1400 : mSpeakerDockRoute; 1401 // Ensure that we default to speaker route if we're in a video call, but disregard it if 1402 // a wired headset is plugged in. Also consider the case when we're holding/unholding a 1403 // call. If the route was on speaker mode, ensure that we preserve the route selection. 1404 boolean shouldDefaultSpeaker = mFeatureFlags.maybeDefaultSpeakerAfterUnhold() 1405 && mWasOnSpeaker; 1406 if ((skipEarpiece || shouldDefaultSpeaker) && defaultRoute != null 1407 && defaultRoute.getType() == AudioRoute.TYPE_EARPIECE) { 1408 Log.i(this, "getPreferredAudioRouteFromDefault: Audio routing defaulting to " 1409 + "speaker route for (video) call."); 1410 defaultRoute = mSpeakerDockRoute; 1411 } 1412 return defaultRoute; 1413 } else { 1414 // Most recent active route will always be the last in the array (ensure that we don't 1415 // auto route to a wearable device unless it's already active). 1416 String autoRoutingToWatchExcerpt = mFeatureFlags.ignoreAutoRouteToWatchDevice() 1417 ? " (except watch)" 1418 : ""; 1419 Log.i(this, "getPreferredAudioRouteFromDefault: Audio routing defaulting to " 1420 + "most recently active BT route" + autoRoutingToWatchExcerpt + "."); 1421 return activeWatchOrNonWatchDeviceRoute; 1422 } 1423 } 1424 calculateSupportedRouteMaskInit()1425 private int calculateSupportedRouteMaskInit() { 1426 Log.i(this, "calculateSupportedRouteMaskInit: is wired headset plugged in - %s", 1427 mWiredHeadsetManager.isPluggedIn()); 1428 int routeMask = CallAudioState.ROUTE_SPEAKER; 1429 1430 if (mWiredHeadsetManager.isPluggedIn()) { 1431 routeMask |= CallAudioState.ROUTE_WIRED_HEADSET; 1432 } else { 1433 AudioDeviceInfo[] deviceList = mAudioManager.getDevices( 1434 AudioManager.GET_DEVICES_OUTPUTS); 1435 for (AudioDeviceInfo device: deviceList) { 1436 if (device.getType() == AudioDeviceInfo.TYPE_BUILTIN_EARPIECE) { 1437 routeMask |= CallAudioState.ROUTE_EARPIECE; 1438 break; 1439 } 1440 } 1441 } 1442 return routeMask; 1443 } 1444 1445 @VisibleForTesting getAvailableRoutes()1446 public Set<AudioRoute> getAvailableRoutes() { 1447 if (mCurrentRoute.equals(mStreamingRoute)) { 1448 return mStreamingRoutes; 1449 } else { 1450 return mAvailableRoutes; 1451 } 1452 } 1453 getCallSupportedRoutes()1454 public Set<AudioRoute> getCallSupportedRoutes() { 1455 if (mCurrentRoute.equals(mStreamingRoute)) { 1456 return mStreamingRoutes; 1457 } else { 1458 if (mAvailableRoutesUpdated) { 1459 updateCallSupportedAudioRoutes(); 1460 mAvailableRoutesUpdated = false; 1461 } 1462 return mCallSupportedRoutes.isEmpty() ? mAvailableRoutes : mCallSupportedRoutes; 1463 } 1464 } 1465 getCurrentRoute()1466 public AudioRoute getCurrentRoute() { 1467 return mCurrentRoute; 1468 } 1469 getBluetoothRoute(@udioRoute.AudioRouteType int audioRouteType, String address)1470 public AudioRoute getBluetoothRoute(@AudioRoute.AudioRouteType int audioRouteType, 1471 String address) { 1472 for (AudioRoute route : mBluetoothRoutes.keySet()) { 1473 if (route.getType() == audioRouteType && route.getBluetoothAddress().equals(address)) { 1474 return route; 1475 } 1476 } 1477 return null; 1478 } 1479 getBaseRoute(boolean includeBluetooth, String btAddressToExclude)1480 public AudioRoute getBaseRoute(boolean includeBluetooth, String btAddressToExclude) { 1481 // Catch-all case for all invocations to this method where we shouldn't be using 1482 // getPreferredAudioRouteFromStrategy 1483 if (mFeatureFlags.updatePreferredAudioDeviceLogic() && !mUsePreferredDeviceStrategy) { 1484 return calculateBaselineRoute(false, includeBluetooth, btAddressToExclude); 1485 } 1486 AudioRoute destRoute = getPreferredAudioRouteFromStrategy(); 1487 Log.i(this, "getBaseRoute: preferred audio route is %s", destRoute); 1488 if (destRoute == null || (destRoute.getBluetoothAddress() != null && (!includeBluetooth 1489 || destRoute.getBluetoothAddress().equals(btAddressToExclude)))) { 1490 destRoute = getPreferredAudioRouteFromDefault(false, includeBluetooth, btAddressToExclude); 1491 } 1492 if (destRoute != null && !getCallSupportedRoutes().contains(destRoute)) { 1493 destRoute = null; 1494 } 1495 Log.i(this, "getBaseRoute - audio routing to %s", destRoute); 1496 return destRoute; 1497 } 1498 calculateBaselineRoute(boolean isExplicitUserRequest, boolean includeBluetooth, String btAddressToExclude)1499 private AudioRoute calculateBaselineRoute(boolean isExplicitUserRequest, 1500 boolean includeBluetooth, String btAddressToExclude) { 1501 AudioRoute destRoute = getPreferredAudioRouteFromDefault(isExplicitUserRequest, 1502 includeBluetooth, btAddressToExclude); 1503 if (destRoute != null && !getCallSupportedRoutes().contains(destRoute)) { 1504 destRoute = null; 1505 } 1506 Log.i(this, "getBaseRoute - audio routing to %s", destRoute); 1507 return destRoute; 1508 } 1509 1510 /** 1511 * Don't add additional AudioRoute when a hearing aid pair is detected. The devices have 1512 * separate addresses, so we need to perform explicit handling to ensure we don't 1513 * treat them as two separate devices. 1514 */ containsHearingAidPair(@udioRoute.AudioRouteType int type, BluetoothDevice bluetoothDevice)1515 private boolean containsHearingAidPair(@AudioRoute.AudioRouteType int type, 1516 BluetoothDevice bluetoothDevice) { 1517 // Check if it is a hearing aid pair and skip connecting to the other device in this case. 1518 // Traverse mBluetoothRoutes backwards as the most recently active device will be inserted 1519 // last. 1520 String existingHearingAidAddress = null; 1521 List<AudioRoute> bluetoothRoutes = mBluetoothRoutes.keySet().stream().toList(); 1522 for (int i = bluetoothRoutes.size() - 1; i >= 0; i--) { 1523 AudioRoute audioRoute = bluetoothRoutes.get(i); 1524 if (audioRoute.getType() == AudioRoute.TYPE_BLUETOOTH_HA) { 1525 existingHearingAidAddress = audioRoute.getBluetoothAddress(); 1526 break; 1527 } 1528 } 1529 1530 // Check that route is for hearing aid and that there exists another hearing aid route 1531 // created for the first device (of the pair) that was connected. 1532 if (type == AudioRoute.TYPE_BLUETOOTH_HA && existingHearingAidAddress != null) { 1533 BluetoothAdapter bluetoothAdapter = mBluetoothRouteManager.getDeviceManager() 1534 .getBluetoothAdapter(); 1535 if (bluetoothAdapter != null) { 1536 List<BluetoothDevice> activeHearingAids = 1537 bluetoothAdapter.getActiveDevices(BluetoothProfile.HEARING_AID); 1538 for (BluetoothDevice hearingAid : activeHearingAids) { 1539 if (hearingAid != null && hearingAid.getAddress() != null) { 1540 String address = hearingAid.getAddress(); 1541 if (address.equals(bluetoothDevice.getAddress()) 1542 || address.equals(existingHearingAidAddress)) { 1543 Log.i(this, "containsHearingAidPair: Detected a hearing aid " 1544 + "pair, ignoring creating a new AudioRoute"); 1545 return true; 1546 } 1547 } 1548 } 1549 } 1550 } 1551 return false; 1552 } 1553 1554 /** 1555 * Prevent auto routing to a wearable device when calculating the default bluetooth audio route 1556 * to move to. This function ensures that the most recently active non-wearable device is 1557 * selected for routing unless a wearable device has already been identified as an active 1558 * device. 1559 */ getActiveWatchOrNonWatchDeviceRoute(String btAddressToExclude)1560 private AudioRoute getActiveWatchOrNonWatchDeviceRoute(String btAddressToExclude) { 1561 if (!mFeatureFlags.ignoreAutoRouteToWatchDevice()) { 1562 Log.i(this, "getActiveWatchOrNonWatchDeviceRoute: ignore_auto_route_to_watch_device " 1563 + "flag is disabled. Routing to most recently reported active device."); 1564 return getMostRecentlyActiveBtRoute(btAddressToExclude); 1565 } 1566 1567 List<AudioRoute> bluetoothRoutes = getAvailableBluetoothDevicesForRouting(); 1568 // Traverse the routes from the most recently active recorded devices first. 1569 AudioRoute nonWatchDeviceRoute = null; 1570 for (int i = bluetoothRoutes.size() - 1; i >= 0; i--) { 1571 AudioRoute route = bluetoothRoutes.get(i); 1572 BluetoothDevice device = mBluetoothRoutes.get(route); 1573 // Skip excluded BT address and LE audio if it's not the lead device. 1574 if (route.getBluetoothAddress().equals(btAddressToExclude) 1575 || isLeAudioNonLeadDeviceOrServiceUnavailable(route.getType(), device)) { 1576 continue; 1577 } 1578 // Check if the most recently active device is a watch device. 1579 boolean isActiveDevice; 1580 synchronized (mLock) { 1581 isActiveDevice = mActiveBluetoothDevice != null 1582 && device.getAddress().equals(mActiveBluetoothDevice.second); 1583 } 1584 if (i == (bluetoothRoutes.size() - 1) && mBluetoothRouteManager.isWatch(device) 1585 && (device.equals(mCallAudioState.getActiveBluetoothDevice()) 1586 || isActiveDevice)) { 1587 Log.i(this, "getActiveWatchOrNonWatchDeviceRoute: Routing to active watch - %s", 1588 bluetoothRoutes.get(0)); 1589 return bluetoothRoutes.get(0); 1590 } 1591 // Record the first occurrence of a non-watch device route if found. 1592 if (!mBluetoothRouteManager.isWatch(device)) { 1593 nonWatchDeviceRoute = route; 1594 break; 1595 } 1596 } 1597 1598 Log.i(this, "Routing to a non-watch device - %s", nonWatchDeviceRoute); 1599 return nonWatchDeviceRoute; 1600 } 1601 getAvailableBluetoothDevicesForRouting()1602 private List<AudioRoute> getAvailableBluetoothDevicesForRouting() { 1603 List<AudioRoute> bluetoothRoutes = new ArrayList<>(mBluetoothRoutes.keySet()); 1604 if (!mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()) { 1605 return bluetoothRoutes; 1606 } 1607 // Consider the active device (BT_ACTIVE_DEVICE_PRESENT) if it exists first. 1608 AudioRoute activeDeviceRoute = getArbitraryBluetoothDevice(); 1609 if (activeDeviceRoute != null && (bluetoothRoutes.isEmpty() 1610 || !bluetoothRoutes.get(bluetoothRoutes.size() - 1).equals(activeDeviceRoute))) { 1611 Log.i(this, "getActiveWatchOrNonWatchDeviceRoute: active BT device (%s) present." 1612 + "Considering this device for selection first.", activeDeviceRoute); 1613 bluetoothRoutes.add(activeDeviceRoute); 1614 } 1615 return bluetoothRoutes; 1616 } 1617 1618 /** 1619 * Returns the most actively reported bluetooth route excluding the passed in route. 1620 */ getMostRecentlyActiveBtRoute(String btAddressToExclude)1621 private AudioRoute getMostRecentlyActiveBtRoute(String btAddressToExclude) { 1622 List<AudioRoute> bluetoothRoutes = mBluetoothRoutes.keySet().stream().toList(); 1623 for (int i = bluetoothRoutes.size() - 1; i >= 0; i--) { 1624 AudioRoute route = bluetoothRoutes.get(i); 1625 // Skip LE route if it's not the lead device. 1626 if (isLeAudioNonLeadDeviceOrServiceUnavailable( 1627 route.getType(), mBluetoothRoutes.get(route))) { 1628 continue; 1629 } 1630 if (!route.getBluetoothAddress().equals(btAddressToExclude)) { 1631 return route; 1632 } 1633 } 1634 return null; 1635 } 1636 isLeAudioNonLeadDeviceOrServiceUnavailable(@udioRoute.AudioRouteType int type, BluetoothDevice device)1637 private boolean isLeAudioNonLeadDeviceOrServiceUnavailable(@AudioRoute.AudioRouteType int type, 1638 BluetoothDevice device) { 1639 BluetoothLeAudio leAudioService = getLeAudioService(); 1640 if (type != AudioRoute.TYPE_BLUETOOTH_LE) { 1641 return false; 1642 } else if (leAudioService == null) { 1643 return true; 1644 } 1645 1646 int groupId = leAudioService.getGroupId(device); 1647 if (groupId != BluetoothLeAudio.GROUP_ID_INVALID) { 1648 BluetoothDevice leadDevice = leAudioService.getConnectedGroupLeadDevice(groupId); 1649 Log.i(this, "Lead device for device (%s) is %s.", device, leadDevice); 1650 return leadDevice == null || !device.getAddress().equals(leadDevice.getAddress()); 1651 } 1652 return false; 1653 } 1654 getLeAudioService()1655 private BluetoothLeAudio getLeAudioService() { 1656 return mBluetoothRouteManager.getDeviceManager().getLeAudioService(); 1657 } 1658 1659 @VisibleForTesting setAudioManager(AudioManager audioManager)1660 public void setAudioManager(AudioManager audioManager) { 1661 mAudioManager = audioManager; 1662 } 1663 1664 @VisibleForTesting setAudioRouteFactory(AudioRoute.Factory audioRouteFactory)1665 public void setAudioRouteFactory(AudioRoute.Factory audioRouteFactory) { 1666 mAudioRouteFactory = audioRouteFactory; 1667 } 1668 getBluetoothRoutes()1669 public Map<AudioRoute, BluetoothDevice> getBluetoothRoutes() { 1670 return mBluetoothRoutes; 1671 } 1672 overrideIsPending(boolean isPending)1673 public void overrideIsPending(boolean isPending) { 1674 mIsPending = isPending; 1675 } 1676 1677 @VisibleForTesting setScoAudioConnectedDevice(BluetoothDevice device)1678 public void setScoAudioConnectedDevice(BluetoothDevice device) { 1679 mScoAudioConnectedDevice = device; 1680 } 1681 clearRingingBluetoothAddress()1682 private void clearRingingBluetoothAddress() { 1683 mBluetoothAddressForRinging = null; 1684 } 1685 1686 /** 1687 * Update the active bluetooth device being tracked (as well as for individual profiles). 1688 * We need to keep track of active devices for individual profiles because of potential 1689 * inconsistencies found in BluetoothStateReceiver#handleActiveDeviceChanged. When multiple 1690 * profiles are paired, we could have a scenario where an active device A is replaced 1691 * with an active device B (from a different profile), which is then removed as an active 1692 * device shortly after, causing device A to be reactive. It's possible that the active device 1693 * changed intent is never received again for device A so an active device cache is necessary 1694 * to track these devices at a profile level. 1695 * @param device {@link Pair} containing the BT audio route type (i.e. SCO/HA/LE) and the 1696 * address of the device. 1697 */ updateActiveBluetoothDevice(Pair<Integer, String> device)1698 public void updateActiveBluetoothDevice(Pair<Integer, String> device) { 1699 synchronized (mLock) { 1700 mActiveDeviceCache.put(device.first, device.second); 1701 // Update most recently active device if address isn't null (meaning 1702 // some device is active). 1703 if (device.second != null) { 1704 mActiveBluetoothDevice = device; 1705 } else { 1706 // If a device was removed, check to ensure that no other device is 1707 //still considered active. 1708 boolean hasActiveDevice = false; 1709 List<Map.Entry<Integer, String>> activeBtDevices = 1710 new ArrayList<>(mActiveDeviceCache.entrySet()); 1711 for (Map.Entry<Integer, String> activeDevice : activeBtDevices) { 1712 Integer btAudioType = activeDevice.getKey(); 1713 String address = activeDevice.getValue(); 1714 if (address != null) { 1715 hasActiveDevice = true; 1716 if (mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()) { 1717 mActiveBluetoothDevice = new Pair<>(btAudioType, address); 1718 } 1719 break; 1720 } 1721 } 1722 if (!hasActiveDevice) { 1723 mActiveBluetoothDevice = null; 1724 } 1725 } 1726 } 1727 } 1728 updateAvailableRoutes(AudioRoute route, boolean includeRoute)1729 private void updateAvailableRoutes(AudioRoute route, boolean includeRoute) { 1730 if (includeRoute) { 1731 mAvailableRoutes.add(route); 1732 } else { 1733 mAvailableRoutes.remove(route); 1734 } 1735 mAvailableRoutesUpdated = true; 1736 } 1737 1738 @VisibleForTesting setActive(boolean active)1739 public void setActive(boolean active) { 1740 if (active) { 1741 mFocusType = ACTIVE_FOCUS; 1742 } else { 1743 mFocusType = NO_FOCUS; 1744 } 1745 mIsActive = active; 1746 } 1747 fallBack(String btAddressToExclude)1748 void fallBack(String btAddressToExclude) { 1749 mMetricsController.getAudioRouteStats().onRouteExit(mPendingAudioRoute, false); 1750 sendMessageWithSessionInfo(SWITCH_BASELINE_ROUTE, INCLUDE_BLUETOOTH_IN_BASELINE, 1751 btAddressToExclude); 1752 } 1753 getAudioOperationsCompleteLatch()1754 public CountDownLatch getAudioOperationsCompleteLatch() { 1755 return mAudioOperationsCompleteLatch; 1756 } 1757 getAudioActiveCompleteLatch()1758 public CountDownLatch getAudioActiveCompleteLatch() { 1759 return mAudioActiveCompleteLatch; 1760 } 1761 getAudioType(AudioDeviceInfo device)1762 private @AudioRoute.AudioRouteType int getAudioType(AudioDeviceInfo device) { 1763 return device != null 1764 ? DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.getOrDefault( 1765 device.getType(), TYPE_INVALID) 1766 : TYPE_INVALID; 1767 } 1768 1769 @VisibleForTesting getUsePreferredDeviceStrategy()1770 public boolean getUsePreferredDeviceStrategy() { 1771 return mUsePreferredDeviceStrategy; 1772 } 1773 1774 @VisibleForTesting setCurrentCommunicationDevice(AudioDeviceInfo device)1775 public void setCurrentCommunicationDevice(AudioDeviceInfo device) { 1776 synchronized (mLock) { 1777 mCurrentCommunicationDevice = device; 1778 } 1779 } 1780 getCurrentCommunicationDevice()1781 public AudioDeviceInfo getCurrentCommunicationDevice() { 1782 synchronized (mLock) { 1783 return mCurrentCommunicationDevice; 1784 } 1785 } 1786 maybeDisableWasOnSpeaker(boolean isUserRequest)1787 private void maybeDisableWasOnSpeaker(boolean isUserRequest) { 1788 if (isUserRequest) { 1789 mWasOnSpeaker = false; 1790 } 1791 } 1792 1793 /* 1794 * Adjusts routing to go inactive if we're active in the case that we're processing 1795 * RINGING_FOCUS and another BT headset is connected which causes in-band ringing to get 1796 * disabled. If we stay in active routing, Telecom will send requests to connect to these BT 1797 * devices while the call is ringing and each of these requests will fail at the BT stack side. 1798 * By default, in-band ringtone is disabled when more than one BT device is paired. Instead, 1799 * ringtone is played using the headset's default ringtone. 1800 */ maybeAdjustActiveRouting(AudioRoute destRoute, boolean isDestRouteActive)1801 private boolean maybeAdjustActiveRouting(AudioRoute destRoute, boolean isDestRouteActive) { 1802 BluetoothDevice device = mBluetoothRoutes.get(destRoute); 1803 // If routing is active and in-band ringing is disabled while the call is ringing, move to 1804 // inactive routing. 1805 if (isDestRouteActive && mFocusType == RINGING_FOCUS && device != null 1806 && !mBluetoothRouteManager.isInbandRingEnabled(destRoute.getType(), device)) { 1807 return false; 1808 } 1809 else if (!isDestRouteActive && mFocusType == RINGING_FOCUS && (device == null 1810 || mBluetoothRouteManager.isInbandRingEnabled(destRoute.getType(), device))) { 1811 // If the routing is inactive while the call is ringing and we re-evaluate this to find 1812 // that we're routing to a non-BT device or a BT device that does support in-band 1813 // ringing, then re-enable active routing (i.e. second HFP headset is disconnected 1814 // while call is ringing). 1815 return true; 1816 } 1817 return isDestRouteActive; 1818 } 1819 } 1820