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