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