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