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