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