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.bluetooth.BluetoothStateReceiver; 30 31 import java.util.Collection; 32 import java.util.HashSet; 33 import java.util.Set; 34 import java.util.LinkedHashSet; 35 36 public class CallAudioManager extends CallsManagerListenerBase { 37 38 public interface AudioServiceFactory { getAudioService()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 CallAudioManager(CallAudioRouteStateMachine callAudioRouteStateMachine, CallsManager callsManager, CallAudioModeStateMachine callAudioModeStateMachine, InCallTonePlayer.Factory playerFactory, Ringer ringer, RingbackPlayer ringbackPlayer, BluetoothStateReceiver bluetoothStateReceiver, DtmfLocalTonePlayer dtmfLocalTonePlayer)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 onCallStateChanged(Call call, int oldState, int newState)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 onCallAdded(Call call)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 onCallRemoved(Call call)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 addCall(Call call)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 removeCall(Call call)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 onExternalCallChanged(Call call, boolean isExternalCall)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 */ shouldIgnoreCallForAudio(Call call)219 private boolean shouldIgnoreCallForAudio(Call call) { 220 return call.getParentCall() != null || call.isExternalCall(); 221 } 222 223 @Override onIncomingCallAnswered(Call call)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 onSessionModifyRequestReceived(Call call, VideoProfile videoProfile)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 onHoldToneRequested(Call call)288 public void onHoldToneRequested(Call call) { 289 maybePlayHoldTone(); 290 } 291 292 @Override onIsVoipAudioModeChanged(Call call)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 onRingbackRequested(Call call, boolean shouldRingback)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 onIncomingCallRejected(Call call, boolean rejectWithMessage, String message)312 public void onIncomingCallRejected(Call call, boolean rejectWithMessage, String message) { 313 maybeStopRingingAndCallWaitingForAnsweredOrRejectedCall(call); 314 } 315 316 @Override onIsConferencedChanged(Call call)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 onConnectionServiceChanged(Call call, ConnectionServiceWrapper oldCs, ConnectionServiceWrapper newCs)338 public void onConnectionServiceChanged(Call call, ConnectionServiceWrapper oldCs, 339 ConnectionServiceWrapper newCs) { 340 mCallAudioRouteStateMachine.sendMessageWithSessionInfo( 341 CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE); 342 } 343 344 @Override onVideoStateChanged(Call call, int previousVideoState, int newVideoState)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 getCallAudioState()363 public CallAudioState getCallAudioState() { 364 return mCallAudioRouteStateMachine.getCurrentCallAudioState(); 365 } 366 getPossiblyHeldForegroundCall()367 public Call getPossiblyHeldForegroundCall() { 368 return mForegroundCall; 369 } 370 getForegroundCall()371 public Call getForegroundCall() { 372 if (mForegroundCall != null && mForegroundCall.getState() != CallState.ON_HOLD) { 373 return mForegroundCall; 374 } 375 return null; 376 } 377 378 @VisibleForTesting toggleMute()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) onRingerModeChange()390 public void onRingerModeChange() { 391 mCallAudioModeStateMachine.sendMessageWithArgs( 392 CallAudioModeStateMachine.RINGER_MODE_CHANGE, makeArgsForModeStateMachine()); 393 } 394 395 @VisibleForTesting mute(boolean shouldMute)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 */ setAudioRoute(int route, String bluetoothAddress)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 */ switchBaseline()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 silenceRingers()456 void silenceRingers() { 457 for (Call call : mRingingCalls) { 458 call.silence(); 459 } 460 461 mRinger.stopRinging(); 462 mRinger.stopCallWaiting(); 463 } 464 465 @VisibleForTesting startRinging()466 public boolean startRinging() { 467 return mRinger.startRinging(mForegroundCall, 468 mCallAudioRouteStateMachine.isHfpDeviceAvailable()); 469 } 470 471 @VisibleForTesting startCallWaiting()472 public void startCallWaiting() { 473 if (mRingingCalls.size() == 1) { 474 mRinger.startCallWaiting(mRingingCalls.iterator().next()); 475 } 476 } 477 478 @VisibleForTesting stopRinging()479 public void stopRinging() { 480 mRinger.stopRinging(); 481 } 482 483 @VisibleForTesting stopCallWaiting()484 public void stopCallWaiting() { 485 mRinger.stopCallWaiting(); 486 } 487 488 @VisibleForTesting setCallAudioRouteFocusState(int focusState)489 public void setCallAudioRouteFocusState(int focusState) { 490 mCallAudioRouteStateMachine.sendMessageWithSessionInfo( 491 CallAudioRouteStateMachine.SWITCH_FOCUS, focusState); 492 } 493 494 @VisibleForTesting getCallAudioRouteStateMachine()495 public CallAudioRouteStateMachine getCallAudioRouteStateMachine() { 496 return mCallAudioRouteStateMachine; 497 } 498 499 @VisibleForTesting getCallAudioModeStateMachine()500 public CallAudioModeStateMachine getCallAudioModeStateMachine() { 501 return mCallAudioModeStateMachine; 502 } 503 dump(IndentingPrintWriter pw)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 setIsTonePlaying(boolean isTonePlaying)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 onCallLeavingState(Call call, int state)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 onCallEnteringState(Call call, int state)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 onCallLeavingActiveDialingOrConnecting()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 onCallLeavingRinging()605 private void onCallLeavingRinging() { 606 if (mRingingCalls.size() == 0) { 607 mCallAudioModeStateMachine.sendMessageWithArgs( 608 CallAudioModeStateMachine.NO_MORE_RINGING_CALLS, 609 makeArgsForModeStateMachine()); 610 } 611 } 612 onCallLeavingHold()613 private void onCallLeavingHold() { 614 if (mHoldingCalls.size() == 0) { 615 mCallAudioModeStateMachine.sendMessageWithArgs( 616 CallAudioModeStateMachine.NO_MORE_HOLDING_CALLS, 617 makeArgsForModeStateMachine()); 618 } 619 } 620 onCallEnteringActiveDialingOrConnecting()621 private void onCallEnteringActiveDialingOrConnecting() { 622 if (mActiveDialingOrConnectingCalls.size() == 1) { 623 mCallAudioModeStateMachine.sendMessageWithArgs( 624 CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL, 625 makeArgsForModeStateMachine()); 626 } 627 } 628 onCallEnteringRinging()629 private void onCallEnteringRinging() { 630 if (mRingingCalls.size() == 1) { 631 mCallAudioModeStateMachine.sendMessageWithArgs( 632 CallAudioModeStateMachine.NEW_RINGING_CALL, 633 makeArgsForModeStateMachine()); 634 } 635 } 636 onCallEnteringHold()637 private void onCallEnteringHold() { 638 if (mHoldingCalls.size() == 1) { 639 mCallAudioModeStateMachine.sendMessageWithArgs( 640 CallAudioModeStateMachine.NEW_HOLDING_CALL, 641 makeArgsForModeStateMachine()); 642 } 643 } 644 updateForegroundCall()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 makeArgsForModeStateMachine()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 playToneForDisconnectedCall(Call call)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 playRingbackForCall(Call call)737 private void playRingbackForCall(Call call) { 738 if (call == mForegroundCall && call.isRingbackRequested()) { 739 mRingbackPlayer.startRingbackForCall(call); 740 } 741 } 742 stopRingbackForCall(Call call)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 */ maybePlayHoldTone()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 */ shouldPlayHoldTone()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 dumpCallsInCollection(IndentingPrintWriter pw, Collection<Call> calls)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 maybeStopRingingAndCallWaitingForAnsweredOrRejectedCall(Call call)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 shouldPlayDisconnectTone(int oldState, int newState)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 getTrackedCalls()818 public Set<Call> getTrackedCalls() { 819 return mCalls; 820 } 821 822 @VisibleForTesting getCallStateToCalls()823 public SparseArray<LinkedHashSet<Call>> getCallStateToCalls() { 824 return mCallStateToCalls; 825 } 826 } 827