1 /* 2 * Copyright (C) 2015 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 android.annotation.NonNull; 20 import android.media.IAudioService; 21 import android.media.ToneGenerator; 22 import android.telecom.CallAudioState; 23 import android.telecom.Log; 24 import android.telecom.VideoProfile; 25 import android.util.SparseArray; 26 27 import com.android.internal.annotations.VisibleForTesting; 28 import com.android.internal.util.IndentingPrintWriter; 29 import com.android.server.telecom.CallAudioModeStateMachine.MessageArgs.Builder; 30 import com.android.server.telecom.bluetooth.BluetoothStateReceiver; 31 32 import java.util.Collection; 33 import java.util.HashSet; 34 import java.util.Set; 35 import java.util.LinkedHashSet; 36 37 public class CallAudioManager extends CallsManagerListenerBase { 38 39 public interface AudioServiceFactory { 40 IAudioService getAudioService(); 41 } 42 43 private final String LOG_TAG = CallAudioManager.class.getSimpleName(); 44 45 private final LinkedHashSet<Call> mActiveDialingOrConnectingCalls; 46 private final LinkedHashSet<Call> mRingingCalls; 47 private final LinkedHashSet<Call> mHoldingCalls; 48 private final LinkedHashSet<Call> mAudioProcessingCalls; 49 private final Set<Call> mCalls; 50 private final SparseArray<LinkedHashSet<Call>> mCallStateToCalls; 51 52 private final CallAudioRouteStateMachine mCallAudioRouteStateMachine; 53 private final CallAudioModeStateMachine mCallAudioModeStateMachine; 54 private final BluetoothStateReceiver mBluetoothStateReceiver; 55 private final CallsManager mCallsManager; 56 private final InCallTonePlayer.Factory mPlayerFactory; 57 private final Ringer mRinger; 58 private final RingbackPlayer mRingbackPlayer; 59 private final DtmfLocalTonePlayer mDtmfLocalTonePlayer; 60 61 private Call mForegroundCall; 62 private boolean mIsTonePlaying = false; 63 private boolean mIsDisconnectedTonePlaying = false; 64 private InCallTonePlayer mHoldTonePlayer; 65 66 public CallAudioManager(CallAudioRouteStateMachine callAudioRouteStateMachine, 67 CallsManager callsManager, 68 CallAudioModeStateMachine callAudioModeStateMachine, 69 InCallTonePlayer.Factory playerFactory, 70 Ringer ringer, 71 RingbackPlayer ringbackPlayer, 72 BluetoothStateReceiver bluetoothStateReceiver, 73 DtmfLocalTonePlayer dtmfLocalTonePlayer) { 74 mActiveDialingOrConnectingCalls = new LinkedHashSet<>(1); 75 mRingingCalls = new LinkedHashSet<>(1); 76 mHoldingCalls = new LinkedHashSet<>(1); 77 mAudioProcessingCalls = new LinkedHashSet<>(1); 78 mCalls = new HashSet<>(); 79 mCallStateToCalls = new SparseArray<LinkedHashSet<Call>>() {{ 80 put(CallState.CONNECTING, mActiveDialingOrConnectingCalls); 81 put(CallState.ACTIVE, mActiveDialingOrConnectingCalls); 82 put(CallState.DIALING, mActiveDialingOrConnectingCalls); 83 put(CallState.PULLING, mActiveDialingOrConnectingCalls); 84 put(CallState.RINGING, mRingingCalls); 85 put(CallState.ON_HOLD, mHoldingCalls); 86 put(CallState.SIMULATED_RINGING, mRingingCalls); 87 put(CallState.AUDIO_PROCESSING, mAudioProcessingCalls); 88 }}; 89 90 mCallAudioRouteStateMachine = callAudioRouteStateMachine; 91 mCallAudioModeStateMachine = callAudioModeStateMachine; 92 mCallsManager = callsManager; 93 mPlayerFactory = playerFactory; 94 mRinger = ringer; 95 mRingbackPlayer = ringbackPlayer; 96 mBluetoothStateReceiver = bluetoothStateReceiver; 97 mDtmfLocalTonePlayer = dtmfLocalTonePlayer; 98 99 mPlayerFactory.setCallAudioManager(this); 100 mCallAudioModeStateMachine.setCallAudioManager(this); 101 mCallAudioRouteStateMachine.setCallAudioManager(this); 102 } 103 104 @Override 105 public void onCallStateChanged(Call call, int oldState, int newState) { 106 if (shouldIgnoreCallForAudio(call)) { 107 // No audio management for calls in a conference, or external calls. 108 return; 109 } 110 Log.d(LOG_TAG, "Call state changed for TC@%s: %s -> %s", call.getId(), 111 CallState.toString(oldState), CallState.toString(newState)); 112 113 removeCallFromAllBins(call); 114 HashSet<Call> newBinForCall = getBinForCall(call); 115 if (newBinForCall != null) { 116 newBinForCall.add(call); 117 } 118 sendCallStatusToBluetoothStateReceiver(); 119 120 updateForegroundCall(); 121 if (shouldPlayDisconnectTone(oldState, newState)) { 122 playToneForDisconnectedCall(call); 123 } 124 125 onCallLeavingState(call, oldState); 126 onCallEnteringState(call, newState); 127 } 128 129 @Override 130 public void onCallAdded(Call call) { 131 if (shouldIgnoreCallForAudio(call)) { 132 return; // Don't do audio handling for calls in a conference, or external calls. 133 } 134 135 addCall(call); 136 } 137 138 @Override 139 public void onCallRemoved(Call call) { 140 if (shouldIgnoreCallForAudio(call)) { 141 return; // Don't do audio handling for calls in a conference, or external calls. 142 } 143 144 removeCall(call); 145 } 146 147 private void addCall(Call call) { 148 if (mCalls.contains(call)) { 149 Log.w(LOG_TAG, "Call TC@%s is being added twice.", call.getId()); 150 return; // No guarantees that the same call won't get added twice. 151 } 152 153 Log.d(LOG_TAG, "Call added with id TC@%s in state %s", call.getId(), 154 CallState.toString(call.getState())); 155 156 HashSet<Call> newBinForCall = getBinForCall(call); 157 if (newBinForCall != null) { 158 newBinForCall.add(call); 159 } 160 updateForegroundCall(); 161 mCalls.add(call); 162 sendCallStatusToBluetoothStateReceiver(); 163 164 onCallEnteringState(call, call.getState()); 165 } 166 167 private void removeCall(Call call) { 168 if (!mCalls.contains(call)) { 169 return; // No guarantees that the same call won't get removed twice. 170 } 171 172 Log.d(LOG_TAG, "Call removed with id TC@%s in state %s", call.getId(), 173 CallState.toString(call.getState())); 174 175 removeCallFromAllBins(call); 176 177 updateForegroundCall(); 178 mCalls.remove(call); 179 sendCallStatusToBluetoothStateReceiver(); 180 181 onCallLeavingState(call, call.getState()); 182 } 183 184 private void sendCallStatusToBluetoothStateReceiver() { 185 // We're in a call if there are calls in mCalls that are not in mAudioProcessingCalls. 186 boolean isInCall = !mAudioProcessingCalls.containsAll(mCalls); 187 mBluetoothStateReceiver.setIsInCall(isInCall); 188 } 189 190 /** 191 * Handles changes to the external state of a call. External calls which become regular calls 192 * should be tracked, and regular calls which become external should no longer be tracked. 193 * 194 * @param call The call. 195 * @param isExternalCall {@code True} if the call is now external, {@code false} if it is now 196 * a regular call. 197 */ 198 @Override 199 public void onExternalCallChanged(Call call, boolean isExternalCall) { 200 if (isExternalCall) { 201 Log.d(LOG_TAG, "Removing call which became external ID %s", call.getId()); 202 removeCall(call); 203 } else if (!isExternalCall) { 204 Log.d(LOG_TAG, "Adding external call which was pulled with ID %s", call.getId()); 205 addCall(call); 206 207 if (mCallsManager.isSpeakerphoneAutoEnabledForVideoCalls(call.getVideoState())) { 208 // When pulling a video call, automatically enable the speakerphone. 209 Log.d(LOG_TAG, "Switching to speaker because external video call %s was pulled." + 210 call.getId()); 211 mCallAudioRouteStateMachine.sendMessageWithSessionInfo( 212 CallAudioRouteStateMachine.SWITCH_SPEAKER); 213 } 214 } 215 } 216 217 /** 218 * Determines if {@link CallAudioManager} should do any audio routing operations for a call. 219 * We ignore child calls of a conference and external calls for audio routing purposes. 220 * 221 * @param call The call to check. 222 * @return {@code true} if the call should be ignored for audio routing, {@code false} 223 * otherwise 224 */ 225 private boolean shouldIgnoreCallForAudio(Call call) { 226 return call.getParentCall() != null || call.isExternalCall(); 227 } 228 229 @Override 230 public void onIncomingCallAnswered(Call call) { 231 if (!mCalls.contains(call)) { 232 return; 233 } 234 235 // Turn off mute when a new incoming call is answered iff it's not a handover. 236 if (!call.isHandoverInProgress()) { 237 mute(false /* shouldMute */); 238 } 239 240 maybeStopRingingAndCallWaitingForAnsweredOrRejectedCall(call); 241 } 242 243 @Override 244 public void onSessionModifyRequestReceived(Call call, VideoProfile videoProfile) { 245 if (videoProfile == null) { 246 return; 247 } 248 249 if (call != mForegroundCall) { 250 // We only play tones for foreground calls. 251 return; 252 } 253 254 int previousVideoState = call.getVideoState(); 255 int newVideoState = videoProfile.getVideoState(); 256 Log.v(this, "onSessionModifyRequestReceived : videoProfile = " + VideoProfile 257 .videoStateToString(newVideoState)); 258 259 boolean isUpgradeRequest = !VideoProfile.isReceptionEnabled(previousVideoState) && 260 VideoProfile.isReceptionEnabled(newVideoState); 261 262 if (isUpgradeRequest) { 263 mPlayerFactory.createPlayer(InCallTonePlayer.TONE_VIDEO_UPGRADE).startTone(); 264 } 265 } 266 267 public void playRttUpgradeTone(Call call) { 268 if (call != mForegroundCall) { 269 // We only play tones for foreground calls. 270 return; 271 } 272 mPlayerFactory.createPlayer(InCallTonePlayer.TONE_RTT_REQUEST).startTone(); 273 } 274 275 /** 276 * Play or stop a call hold tone for a call. Triggered via 277 * {@link Connection#sendConnectionEvent(String)} when the 278 * {@link Connection#EVENT_ON_HOLD_TONE_START} event or 279 * {@link Connection#EVENT_ON_HOLD_TONE_STOP} event is passed through to the 280 * 281 * @param call The call which requested the hold tone. 282 */ 283 @Override 284 public void onHoldToneRequested(Call call) { 285 maybePlayHoldTone(); 286 } 287 288 @Override 289 public void onIsVoipAudioModeChanged(Call call) { 290 if (call != mForegroundCall) { 291 return; 292 } 293 mCallAudioModeStateMachine.sendMessageWithArgs( 294 CallAudioModeStateMachine.FOREGROUND_VOIP_MODE_CHANGE, 295 makeArgsForModeStateMachine()); 296 } 297 298 @Override 299 public void onRingbackRequested(Call call, boolean shouldRingback) { 300 if (call == mForegroundCall && shouldRingback) { 301 mRingbackPlayer.startRingbackForCall(call); 302 } else { 303 mRingbackPlayer.stopRingbackForCall(call); 304 } 305 } 306 307 @Override 308 public void onIncomingCallRejected(Call call, boolean rejectWithMessage, String message) { 309 maybeStopRingingAndCallWaitingForAnsweredOrRejectedCall(call); 310 } 311 312 @Override 313 public void onIsConferencedChanged(Call call) { 314 // This indicates a conferencing change, which shouldn't impact any audio mode stuff. 315 Call parentCall = call.getParentCall(); 316 if (parentCall == null) { 317 // Indicates that the call should be tracked for audio purposes. Treat it as if it were 318 // just added. 319 Log.i(LOG_TAG, "Call TC@" + call.getId() + " left conference and will" + 320 " now be tracked by CallAudioManager."); 321 onCallAdded(call); 322 } else { 323 // The call joined a conference, so stop tracking it. 324 removeCallFromAllBins(call); 325 updateForegroundCall(); 326 mCalls.remove(call); 327 } 328 } 329 330 @Override 331 public void onConnectionServiceChanged(Call call, ConnectionServiceWrapper oldCs, 332 ConnectionServiceWrapper newCs) { 333 mCallAudioRouteStateMachine.sendMessageWithSessionInfo( 334 CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE); 335 } 336 337 @Override 338 public void onVideoStateChanged(Call call, int previousVideoState, int newVideoState) { 339 if (call != getForegroundCall()) { 340 Log.d(LOG_TAG, "Ignoring video state change from %s to %s for call %s -- not " + 341 "foreground.", VideoProfile.videoStateToString(previousVideoState), 342 VideoProfile.videoStateToString(newVideoState), call.getId()); 343 return; 344 } 345 346 if (!VideoProfile.isVideo(previousVideoState) && 347 mCallsManager.isSpeakerphoneAutoEnabledForVideoCalls(newVideoState)) { 348 Log.d(LOG_TAG, "Switching to speaker because call %s transitioned video state from %s" + 349 " to %s", call.getId(), VideoProfile.videoStateToString(previousVideoState), 350 VideoProfile.videoStateToString(newVideoState)); 351 mCallAudioRouteStateMachine.sendMessageWithSessionInfo( 352 CallAudioRouteStateMachine.SWITCH_SPEAKER); 353 } 354 } 355 356 public CallAudioState getCallAudioState() { 357 return mCallAudioRouteStateMachine.getCurrentCallAudioState(); 358 } 359 360 public Call getPossiblyHeldForegroundCall() { 361 return mForegroundCall; 362 } 363 364 public Call getForegroundCall() { 365 if (mForegroundCall != null && mForegroundCall.getState() != CallState.ON_HOLD) { 366 return mForegroundCall; 367 } 368 return null; 369 } 370 371 @VisibleForTesting 372 public void toggleMute() { 373 // Don't mute if there are any emergency calls. 374 if (mCallsManager.isInEmergencyCall()) { 375 Log.v(this, "ignoring toggleMute for emergency call"); 376 return; 377 } 378 mCallAudioRouteStateMachine.sendMessageWithSessionInfo( 379 CallAudioRouteStateMachine.TOGGLE_MUTE); 380 } 381 382 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) 383 public void onRingerModeChange() { 384 mCallAudioModeStateMachine.sendMessageWithArgs( 385 CallAudioModeStateMachine.RINGER_MODE_CHANGE, makeArgsForModeStateMachine()); 386 } 387 388 @VisibleForTesting 389 public void mute(boolean shouldMute) { 390 Log.v(this, "mute, shouldMute: %b", shouldMute); 391 392 // Don't mute if there are any emergency calls. 393 if (mCallsManager.isInEmergencyCall()) { 394 shouldMute = false; 395 Log.v(this, "ignoring mute for emergency call"); 396 } 397 398 mCallAudioRouteStateMachine.sendMessageWithSessionInfo(shouldMute 399 ? CallAudioRouteStateMachine.MUTE_ON : CallAudioRouteStateMachine.MUTE_OFF); 400 } 401 402 /** 403 * Changed the audio route, for example from earpiece to speaker phone. 404 * 405 * @param route The new audio route to use. See {@link CallAudioState}. 406 * @param bluetoothAddress the address of the desired bluetooth device, if route is 407 * {@link CallAudioState#ROUTE_BLUETOOTH}. 408 */ 409 void setAudioRoute(int route, String bluetoothAddress) { 410 Log.v(this, "setAudioRoute, route: %s", CallAudioState.audioRouteToString(route)); 411 switch (route) { 412 case CallAudioState.ROUTE_BLUETOOTH: 413 mCallAudioRouteStateMachine.sendMessageWithSessionInfo( 414 CallAudioRouteStateMachine.USER_SWITCH_BLUETOOTH, 0, bluetoothAddress); 415 return; 416 case CallAudioState.ROUTE_SPEAKER: 417 mCallAudioRouteStateMachine.sendMessageWithSessionInfo( 418 CallAudioRouteStateMachine.USER_SWITCH_SPEAKER); 419 return; 420 case CallAudioState.ROUTE_WIRED_HEADSET: 421 mCallAudioRouteStateMachine.sendMessageWithSessionInfo( 422 CallAudioRouteStateMachine.USER_SWITCH_HEADSET); 423 return; 424 case CallAudioState.ROUTE_EARPIECE: 425 mCallAudioRouteStateMachine.sendMessageWithSessionInfo( 426 CallAudioRouteStateMachine.USER_SWITCH_EARPIECE); 427 return; 428 case CallAudioState.ROUTE_WIRED_OR_EARPIECE: 429 mCallAudioRouteStateMachine.sendMessageWithSessionInfo( 430 CallAudioRouteStateMachine.USER_SWITCH_BASELINE_ROUTE, 431 CallAudioRouteStateMachine.NO_INCLUDE_BLUETOOTH_IN_BASELINE); 432 return; 433 default: 434 Log.w(this, "InCallService requested an invalid audio route: %d", route); 435 } 436 } 437 438 /** 439 * Switch call audio routing to the baseline route, including bluetooth headsets if there are 440 * any connected. 441 */ 442 void switchBaseline() { 443 Log.i(this, "switchBaseline"); 444 mCallAudioRouteStateMachine.sendMessageWithSessionInfo( 445 CallAudioRouteStateMachine.USER_SWITCH_BASELINE_ROUTE, 446 CallAudioRouteStateMachine.INCLUDE_BLUETOOTH_IN_BASELINE); 447 } 448 449 void silenceRingers() { 450 synchronized (mCallsManager.getLock()) { 451 for (Call call : mRingingCalls) { 452 call.silence(); 453 } 454 455 mRinger.stopRinging(); 456 mRinger.stopCallWaiting(); 457 } 458 } 459 460 public boolean isRingtonePlaying() { 461 return mRinger.isRinging(); 462 } 463 464 @VisibleForTesting 465 public boolean startRinging() { 466 synchronized (mCallsManager.getLock()) { 467 return mRinger.startRinging(mForegroundCall, 468 mCallAudioRouteStateMachine.isHfpDeviceAvailable()); 469 } 470 } 471 472 @VisibleForTesting 473 public void startCallWaiting(String reason) { 474 synchronized (mCallsManager.getLock()) { 475 if (mRingingCalls.size() == 1) { 476 mRinger.startCallWaiting(mRingingCalls.iterator().next(), reason); 477 } 478 } 479 } 480 481 @VisibleForTesting 482 public void stopRinging() { 483 synchronized (mCallsManager.getLock()) { 484 mRinger.stopRinging(); 485 } 486 } 487 488 @VisibleForTesting 489 public void stopCallWaiting() { 490 synchronized (mCallsManager.getLock()) { 491 mRinger.stopCallWaiting(); 492 } 493 } 494 495 @VisibleForTesting 496 public void setCallAudioRouteFocusState(int focusState) { 497 mCallAudioRouteStateMachine.sendMessageWithSessionInfo( 498 CallAudioRouteStateMachine.SWITCH_FOCUS, focusState); 499 } 500 501 public void notifyAudioOperationsComplete() { 502 mCallAudioModeStateMachine.sendMessageWithArgs( 503 CallAudioModeStateMachine.AUDIO_OPERATIONS_COMPLETE, makeArgsForModeStateMachine()); 504 } 505 506 @VisibleForTesting 507 public CallAudioRouteStateMachine getCallAudioRouteStateMachine() { 508 return mCallAudioRouteStateMachine; 509 } 510 511 @VisibleForTesting 512 public CallAudioModeStateMachine getCallAudioModeStateMachine() { 513 return mCallAudioModeStateMachine; 514 } 515 516 void dump(IndentingPrintWriter pw) { 517 pw.println("All calls:"); 518 pw.increaseIndent(); 519 dumpCallsInCollection(pw, mCalls); 520 pw.decreaseIndent(); 521 522 pw.println("Active dialing, or connecting calls:"); 523 pw.increaseIndent(); 524 dumpCallsInCollection(pw, mActiveDialingOrConnectingCalls); 525 pw.decreaseIndent(); 526 527 pw.println("Ringing calls:"); 528 pw.increaseIndent(); 529 dumpCallsInCollection(pw, mRingingCalls); 530 pw.decreaseIndent(); 531 532 pw.println("Holding calls:"); 533 pw.increaseIndent(); 534 dumpCallsInCollection(pw, mHoldingCalls); 535 pw.decreaseIndent(); 536 537 pw.println("Foreground call:"); 538 pw.println(mForegroundCall); 539 540 pw.println("CallAudioModeStateMachine pending messages:"); 541 pw.increaseIndent(); 542 mCallAudioModeStateMachine.dumpPendingMessages(pw); 543 pw.decreaseIndent(); 544 545 pw.println("CallAudioRouteStateMachine pending messages:"); 546 pw.increaseIndent(); 547 mCallAudioRouteStateMachine.dumpPendingMessages(pw); 548 pw.decreaseIndent(); 549 550 pw.println("BluetoothDeviceManager:"); 551 pw.increaseIndent(); 552 if (mBluetoothStateReceiver.getBluetoothDeviceManager() != null) { 553 mBluetoothStateReceiver.getBluetoothDeviceManager().dump(pw); 554 } 555 pw.decreaseIndent(); 556 } 557 558 @VisibleForTesting 559 public void setIsTonePlaying(boolean isTonePlaying) { 560 mIsTonePlaying = isTonePlaying; 561 mCallAudioModeStateMachine.sendMessageWithArgs( 562 isTonePlaying ? CallAudioModeStateMachine.TONE_STARTED_PLAYING 563 : CallAudioModeStateMachine.TONE_STOPPED_PLAYING, 564 makeArgsForModeStateMachine()); 565 566 if (!isTonePlaying && mIsDisconnectedTonePlaying) { 567 mCallsManager.onDisconnectedTonePlaying(false); 568 mIsDisconnectedTonePlaying = false; 569 } 570 } 571 572 private void onCallLeavingState(Call call, int state) { 573 switch (state) { 574 case CallState.ACTIVE: 575 case CallState.CONNECTING: 576 onCallLeavingActiveDialingOrConnecting(); 577 break; 578 case CallState.RINGING: 579 case CallState.SIMULATED_RINGING: 580 case CallState.ANSWERED: 581 onCallLeavingRinging(); 582 break; 583 case CallState.ON_HOLD: 584 onCallLeavingHold(); 585 break; 586 case CallState.PULLING: 587 onCallLeavingActiveDialingOrConnecting(); 588 break; 589 case CallState.DIALING: 590 stopRingbackForCall(call); 591 onCallLeavingActiveDialingOrConnecting(); 592 break; 593 case CallState.AUDIO_PROCESSING: 594 onCallLeavingAudioProcessing(); 595 break; 596 } 597 } 598 599 private void onCallEnteringState(Call call, int state) { 600 switch (state) { 601 case CallState.ACTIVE: 602 case CallState.CONNECTING: 603 onCallEnteringActiveDialingOrConnecting(); 604 break; 605 case CallState.RINGING: 606 case CallState.SIMULATED_RINGING: 607 onCallEnteringRinging(); 608 break; 609 case CallState.ON_HOLD: 610 onCallEnteringHold(); 611 break; 612 case CallState.PULLING: 613 onCallEnteringActiveDialingOrConnecting(); 614 break; 615 case CallState.DIALING: 616 onCallEnteringActiveDialingOrConnecting(); 617 playRingbackForCall(call); 618 break; 619 case CallState.ANSWERED: 620 if (call.can(android.telecom.Call.Details.CAPABILITY_SPEED_UP_MT_AUDIO)) { 621 onCallEnteringActiveDialingOrConnecting(); 622 } 623 break; 624 case CallState.AUDIO_PROCESSING: 625 onCallEnteringAudioProcessing(); 626 break; 627 } 628 } 629 630 private void onCallLeavingAudioProcessing() { 631 if (mAudioProcessingCalls.size() == 0) { 632 mCallAudioModeStateMachine.sendMessageWithArgs( 633 CallAudioModeStateMachine.NO_MORE_AUDIO_PROCESSING_CALLS, 634 makeArgsForModeStateMachine()); 635 } 636 } 637 638 private void onCallEnteringAudioProcessing() { 639 if (mAudioProcessingCalls.size() == 1) { 640 mCallAudioModeStateMachine.sendMessageWithArgs( 641 CallAudioModeStateMachine.NEW_AUDIO_PROCESSING_CALL, 642 makeArgsForModeStateMachine()); 643 } 644 } 645 646 private void onCallLeavingActiveDialingOrConnecting() { 647 if (mActiveDialingOrConnectingCalls.size() == 0) { 648 mCallAudioModeStateMachine.sendMessageWithArgs( 649 CallAudioModeStateMachine.NO_MORE_ACTIVE_OR_DIALING_CALLS, 650 makeArgsForModeStateMachine()); 651 } 652 } 653 654 private void onCallLeavingRinging() { 655 if (mRingingCalls.size() == 0) { 656 mCallAudioModeStateMachine.sendMessageWithArgs( 657 CallAudioModeStateMachine.NO_MORE_RINGING_CALLS, 658 makeArgsForModeStateMachine()); 659 } 660 } 661 662 private void onCallLeavingHold() { 663 if (mHoldingCalls.size() == 0) { 664 mCallAudioModeStateMachine.sendMessageWithArgs( 665 CallAudioModeStateMachine.NO_MORE_HOLDING_CALLS, 666 makeArgsForModeStateMachine()); 667 } 668 } 669 670 private void onCallEnteringActiveDialingOrConnecting() { 671 if (mActiveDialingOrConnectingCalls.size() == 1) { 672 mCallAudioModeStateMachine.sendMessageWithArgs( 673 CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL, 674 makeArgsForModeStateMachine()); 675 } 676 } 677 678 private void onCallEnteringRinging() { 679 if (mRingingCalls.size() == 1) { 680 mCallAudioModeStateMachine.sendMessageWithArgs( 681 CallAudioModeStateMachine.NEW_RINGING_CALL, 682 makeArgsForModeStateMachine()); 683 } 684 } 685 686 private void onCallEnteringHold() { 687 if (mHoldingCalls.size() == 1) { 688 mCallAudioModeStateMachine.sendMessageWithArgs( 689 CallAudioModeStateMachine.NEW_HOLDING_CALL, 690 makeArgsForModeStateMachine()); 691 } 692 } 693 694 private void updateForegroundCall() { 695 Call oldForegroundCall = mForegroundCall; 696 if (mActiveDialingOrConnectingCalls.size() > 0) { 697 // Give preference for connecting calls over active/dialing for foreground-ness. 698 Call possibleConnectingCall = null; 699 for (Call call : mActiveDialingOrConnectingCalls) { 700 if (call.getState() == CallState.CONNECTING) { 701 possibleConnectingCall = call; 702 } 703 } 704 mForegroundCall = possibleConnectingCall == null ? 705 mActiveDialingOrConnectingCalls.iterator().next() : possibleConnectingCall; 706 } else if (mRingingCalls.size() > 0) { 707 mForegroundCall = mRingingCalls.iterator().next(); 708 } else if (mHoldingCalls.size() > 0) { 709 mForegroundCall = mHoldingCalls.iterator().next(); 710 } else { 711 mForegroundCall = null; 712 } 713 714 if (mForegroundCall != oldForegroundCall) { 715 mCallAudioRouteStateMachine.sendMessageWithSessionInfo( 716 CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE); 717 mDtmfLocalTonePlayer.onForegroundCallChanged(oldForegroundCall, mForegroundCall); 718 maybePlayHoldTone(); 719 } 720 } 721 722 @NonNull 723 private CallAudioModeStateMachine.MessageArgs makeArgsForModeStateMachine() { 724 return new Builder() 725 .setHasActiveOrDialingCalls(mActiveDialingOrConnectingCalls.size() > 0) 726 .setHasRingingCalls(mRingingCalls.size() > 0) 727 .setHasHoldingCalls(mHoldingCalls.size() > 0) 728 .setHasAudioProcessingCalls(mAudioProcessingCalls.size() > 0) 729 .setIsTonePlaying(mIsTonePlaying) 730 .setForegroundCallIsVoip( 731 mForegroundCall != null && mForegroundCall.getIsVoipAudioMode()) 732 .setSession(Log.createSubsession()).build(); 733 } 734 735 private HashSet<Call> getBinForCall(Call call) { 736 if (call.getState() == CallState.ANSWERED) { 737 // If the call has the speed-up-mt-audio capability, treat answered state as active 738 // for audio purposes. 739 if (call.can(android.telecom.Call.Details.CAPABILITY_SPEED_UP_MT_AUDIO)) { 740 return mActiveDialingOrConnectingCalls; 741 } 742 return mRingingCalls; 743 } 744 return mCallStateToCalls.get(call.getState()); 745 } 746 747 private void removeCallFromAllBins(Call call) { 748 for (int i = 0; i < mCallStateToCalls.size(); i++) { 749 mCallStateToCalls.valueAt(i).remove(call); 750 } 751 } 752 753 private void playToneForDisconnectedCall(Call call) { 754 // If this call is being disconnected as a result of being handed over to another call, 755 // we will not play a disconnect tone. 756 if (call.isHandoverInProgress()) { 757 Log.i(LOG_TAG, "Omitting tone because %s is being handed over.", call); 758 return; 759 } 760 761 if (mForegroundCall != null && call != mForegroundCall && mCalls.size() > 1) { 762 Log.v(LOG_TAG, "Omitting tone because we are not foreground" + 763 " and there is another call."); 764 return; 765 } 766 767 if (call.getDisconnectCause() != null) { 768 int toneToPlay = InCallTonePlayer.TONE_INVALID; 769 770 Log.v(this, "Disconnect cause: %s.", call.getDisconnectCause()); 771 772 switch(call.getDisconnectCause().getTone()) { 773 case ToneGenerator.TONE_SUP_BUSY: 774 toneToPlay = InCallTonePlayer.TONE_BUSY; 775 break; 776 case ToneGenerator.TONE_SUP_CONGESTION: 777 toneToPlay = InCallTonePlayer.TONE_CONGESTION; 778 break; 779 case ToneGenerator.TONE_CDMA_REORDER: 780 toneToPlay = InCallTonePlayer.TONE_REORDER; 781 break; 782 case ToneGenerator.TONE_CDMA_ABBR_INTERCEPT: 783 toneToPlay = InCallTonePlayer.TONE_INTERCEPT; 784 break; 785 case ToneGenerator.TONE_CDMA_CALLDROP_LITE: 786 toneToPlay = InCallTonePlayer.TONE_CDMA_DROP; 787 break; 788 case ToneGenerator.TONE_SUP_ERROR: 789 toneToPlay = InCallTonePlayer.TONE_UNOBTAINABLE_NUMBER; 790 break; 791 case ToneGenerator.TONE_PROP_PROMPT: 792 toneToPlay = InCallTonePlayer.TONE_CALL_ENDED; 793 break; 794 } 795 796 Log.d(this, "Found a disconnected call with tone to play %d.", toneToPlay); 797 798 if (toneToPlay != InCallTonePlayer.TONE_INVALID) { 799 boolean didToneStart = mPlayerFactory.createPlayer(toneToPlay).startTone(); 800 if (didToneStart) { 801 mCallsManager.onDisconnectedTonePlaying(true); 802 mIsDisconnectedTonePlaying = true; 803 } 804 } 805 } 806 } 807 808 private void playRingbackForCall(Call call) { 809 if (call == mForegroundCall && call.isRingbackRequested()) { 810 mRingbackPlayer.startRingbackForCall(call); 811 } 812 } 813 814 private void stopRingbackForCall(Call call) { 815 mRingbackPlayer.stopRingbackForCall(call); 816 } 817 818 /** 819 * Determines if a hold tone should be played and then starts or stops it accordingly. 820 */ 821 private void maybePlayHoldTone() { 822 if (shouldPlayHoldTone()) { 823 if (mHoldTonePlayer == null) { 824 mHoldTonePlayer = mPlayerFactory.createPlayer(InCallTonePlayer.TONE_CALL_WAITING); 825 mHoldTonePlayer.startTone(); 826 } 827 } else { 828 if (mHoldTonePlayer != null) { 829 mHoldTonePlayer.stopTone(); 830 mHoldTonePlayer = null; 831 } 832 } 833 } 834 835 /** 836 * Determines if a hold tone should be played. 837 * A hold tone should be played only if foreground call is equals with call which is 838 * remotely held. 839 * 840 * @return {@code true} if the the hold tone should be played, {@code false} otherwise. 841 */ 842 private boolean shouldPlayHoldTone() { 843 Call foregroundCall = getForegroundCall(); 844 // If there is no foreground call, no hold tone should play. 845 if (foregroundCall == null) { 846 return false; 847 } 848 849 // If another call is ringing, no hold tone should play. 850 if (mCallsManager.hasRingingCall()) { 851 return false; 852 } 853 854 // If the foreground call isn't active, no hold tone should play. This might happen, for 855 // example, if the user puts a remotely held call on hold itself. 856 if (!foregroundCall.isActive()) { 857 return false; 858 } 859 860 return foregroundCall.isRemotelyHeld(); 861 } 862 863 private void dumpCallsInCollection(IndentingPrintWriter pw, Collection<Call> calls) { 864 for (Call call : calls) { 865 if (call != null) pw.println(call.getId()); 866 } 867 } 868 869 private void maybeStopRingingAndCallWaitingForAnsweredOrRejectedCall(Call call) { 870 // Check to see if the call being answered/rejected is the only ringing call, since this 871 // will be called before the connection service acknowledges the state change. 872 synchronized (mCallsManager.getLock()) { 873 if (mRingingCalls.size() == 0 || 874 (mRingingCalls.size() == 1 && call == mRingingCalls.iterator().next())) { 875 mRinger.stopRinging(); 876 mRinger.stopCallWaiting(); 877 } 878 } 879 } 880 881 private boolean shouldPlayDisconnectTone(int oldState, int newState) { 882 if (newState != CallState.DISCONNECTED) { 883 return false; 884 } 885 return oldState == CallState.ACTIVE || 886 oldState == CallState.DIALING || 887 oldState == CallState.ON_HOLD; 888 } 889 890 @VisibleForTesting 891 public Set<Call> getTrackedCalls() { 892 return mCalls; 893 } 894 895 @VisibleForTesting 896 public SparseArray<LinkedHashSet<Call>> getCallStateToCalls() { 897 return mCallStateToCalls; 898 } 899 } 900