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 267 /** 268 * Play or stop a call hold tone for a call. Triggered via 269 * {@link Connection#sendConnectionEvent(String)} when the 270 * {@link Connection#EVENT_ON_HOLD_TONE_START} event or 271 * {@link Connection#EVENT_ON_HOLD_TONE_STOP} event is passed through to the 272 * 273 * @param call The call which requested the hold tone. 274 */ 275 @Override onHoldToneRequested(Call call)276 public void onHoldToneRequested(Call call) { 277 maybePlayHoldTone(); 278 } 279 280 @Override onIsVoipAudioModeChanged(Call call)281 public void onIsVoipAudioModeChanged(Call call) { 282 if (call != mForegroundCall) { 283 return; 284 } 285 mCallAudioModeStateMachine.sendMessageWithArgs( 286 CallAudioModeStateMachine.FOREGROUND_VOIP_MODE_CHANGE, 287 makeArgsForModeStateMachine()); 288 } 289 290 @Override onRingbackRequested(Call call, boolean shouldRingback)291 public void onRingbackRequested(Call call, boolean shouldRingback) { 292 if (call == mForegroundCall && shouldRingback) { 293 mRingbackPlayer.startRingbackForCall(call); 294 } else { 295 mRingbackPlayer.stopRingbackForCall(call); 296 } 297 } 298 299 @Override onIncomingCallRejected(Call call, boolean rejectWithMessage, String message)300 public void onIncomingCallRejected(Call call, boolean rejectWithMessage, String message) { 301 maybeStopRingingAndCallWaitingForAnsweredOrRejectedCall(call); 302 } 303 304 @Override onIsConferencedChanged(Call call)305 public void onIsConferencedChanged(Call call) { 306 // This indicates a conferencing change, which shouldn't impact any audio mode stuff. 307 Call parentCall = call.getParentCall(); 308 if (parentCall == null) { 309 // Indicates that the call should be tracked for audio purposes. Treat it as if it were 310 // just added. 311 Log.i(LOG_TAG, "Call TC@" + call.getId() + " left conference and will" + 312 " now be tracked by CallAudioManager."); 313 onCallAdded(call); 314 } else { 315 // The call joined a conference, so stop tracking it. 316 removeCallFromAllBins(call); 317 updateForegroundCall(); 318 mCalls.remove(call); 319 } 320 } 321 322 @Override onConnectionServiceChanged(Call call, ConnectionServiceWrapper oldCs, ConnectionServiceWrapper newCs)323 public void onConnectionServiceChanged(Call call, ConnectionServiceWrapper oldCs, 324 ConnectionServiceWrapper newCs) { 325 mCallAudioRouteStateMachine.sendMessageWithSessionInfo( 326 CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE); 327 } 328 329 @Override onVideoStateChanged(Call call, int previousVideoState, int newVideoState)330 public void onVideoStateChanged(Call call, int previousVideoState, int newVideoState) { 331 if (call != getForegroundCall()) { 332 Log.d(LOG_TAG, "Ignoring video state change from %s to %s for call %s -- not " + 333 "foreground.", VideoProfile.videoStateToString(previousVideoState), 334 VideoProfile.videoStateToString(newVideoState), call.getId()); 335 return; 336 } 337 338 if (!VideoProfile.isVideo(previousVideoState) && 339 mCallsManager.isSpeakerphoneAutoEnabledForVideoCalls(newVideoState)) { 340 Log.d(LOG_TAG, "Switching to speaker because call %s transitioned video state from %s" + 341 " to %s", call.getId(), VideoProfile.videoStateToString(previousVideoState), 342 VideoProfile.videoStateToString(newVideoState)); 343 mCallAudioRouteStateMachine.sendMessageWithSessionInfo( 344 CallAudioRouteStateMachine.SWITCH_SPEAKER); 345 } 346 } 347 getCallAudioState()348 public CallAudioState getCallAudioState() { 349 return mCallAudioRouteStateMachine.getCurrentCallAudioState(); 350 } 351 getPossiblyHeldForegroundCall()352 public Call getPossiblyHeldForegroundCall() { 353 return mForegroundCall; 354 } 355 getForegroundCall()356 public Call getForegroundCall() { 357 if (mForegroundCall != null && mForegroundCall.getState() != CallState.ON_HOLD) { 358 return mForegroundCall; 359 } 360 return null; 361 } 362 363 @VisibleForTesting toggleMute()364 public void toggleMute() { 365 // Don't mute if there are any emergency calls. 366 if (mCallsManager.isInEmergencyCall()) { 367 Log.v(this, "ignoring toggleMute for emergency call"); 368 return; 369 } 370 mCallAudioRouteStateMachine.sendMessageWithSessionInfo( 371 CallAudioRouteStateMachine.TOGGLE_MUTE); 372 } 373 374 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) onRingerModeChange()375 public void onRingerModeChange() { 376 mCallAudioModeStateMachine.sendMessageWithArgs( 377 CallAudioModeStateMachine.RINGER_MODE_CHANGE, makeArgsForModeStateMachine()); 378 } 379 380 @VisibleForTesting mute(boolean shouldMute)381 public void mute(boolean shouldMute) { 382 Log.v(this, "mute, shouldMute: %b", shouldMute); 383 384 // Don't mute if there are any emergency calls. 385 if (mCallsManager.isInEmergencyCall()) { 386 shouldMute = false; 387 Log.v(this, "ignoring mute for emergency call"); 388 } 389 390 mCallAudioRouteStateMachine.sendMessageWithSessionInfo(shouldMute 391 ? CallAudioRouteStateMachine.MUTE_ON : CallAudioRouteStateMachine.MUTE_OFF); 392 } 393 394 /** 395 * Changed the audio route, for example from earpiece to speaker phone. 396 * 397 * @param route The new audio route to use. See {@link CallAudioState}. 398 * @param bluetoothAddress the address of the desired bluetooth device, if route is 399 * {@link CallAudioState#ROUTE_BLUETOOTH}. 400 */ setAudioRoute(int route, String bluetoothAddress)401 void setAudioRoute(int route, String bluetoothAddress) { 402 Log.v(this, "setAudioRoute, route: %s", CallAudioState.audioRouteToString(route)); 403 switch (route) { 404 case CallAudioState.ROUTE_BLUETOOTH: 405 mCallAudioRouteStateMachine.sendMessageWithSessionInfo( 406 CallAudioRouteStateMachine.USER_SWITCH_BLUETOOTH, 0, bluetoothAddress); 407 return; 408 case CallAudioState.ROUTE_SPEAKER: 409 mCallAudioRouteStateMachine.sendMessageWithSessionInfo( 410 CallAudioRouteStateMachine.USER_SWITCH_SPEAKER); 411 return; 412 case CallAudioState.ROUTE_WIRED_HEADSET: 413 mCallAudioRouteStateMachine.sendMessageWithSessionInfo( 414 CallAudioRouteStateMachine.USER_SWITCH_HEADSET); 415 return; 416 case CallAudioState.ROUTE_EARPIECE: 417 mCallAudioRouteStateMachine.sendMessageWithSessionInfo( 418 CallAudioRouteStateMachine.USER_SWITCH_EARPIECE); 419 return; 420 case CallAudioState.ROUTE_WIRED_OR_EARPIECE: 421 mCallAudioRouteStateMachine.sendMessageWithSessionInfo( 422 CallAudioRouteStateMachine.USER_SWITCH_BASELINE_ROUTE, 423 CallAudioRouteStateMachine.NO_INCLUDE_BLUETOOTH_IN_BASELINE); 424 return; 425 default: 426 Log.w(this, "InCallService requested an invalid audio route: %d", route); 427 } 428 } 429 430 /** 431 * Switch call audio routing to the baseline route, including bluetooth headsets if there are 432 * any connected. 433 */ switchBaseline()434 void switchBaseline() { 435 Log.i(this, "switchBaseline"); 436 mCallAudioRouteStateMachine.sendMessageWithSessionInfo( 437 CallAudioRouteStateMachine.USER_SWITCH_BASELINE_ROUTE, 438 CallAudioRouteStateMachine.INCLUDE_BLUETOOTH_IN_BASELINE); 439 } 440 silenceRingers()441 void silenceRingers() { 442 synchronized (mCallsManager.getLock()) { 443 for (Call call : mRingingCalls) { 444 call.silence(); 445 } 446 447 mRinger.stopRinging(); 448 mRinger.stopCallWaiting(); 449 } 450 } 451 isRingtonePlaying()452 public boolean isRingtonePlaying() { 453 return mRinger.isRinging(); 454 } 455 456 @VisibleForTesting startRinging()457 public boolean startRinging() { 458 synchronized (mCallsManager.getLock()) { 459 return mRinger.startRinging(mForegroundCall, 460 mCallAudioRouteStateMachine.isHfpDeviceAvailable()); 461 } 462 } 463 464 @VisibleForTesting startCallWaiting(String reason)465 public void startCallWaiting(String reason) { 466 synchronized (mCallsManager.getLock()) { 467 if (mRingingCalls.size() == 1) { 468 mRinger.startCallWaiting(mRingingCalls.iterator().next(), reason); 469 } 470 } 471 } 472 473 @VisibleForTesting stopRinging()474 public void stopRinging() { 475 synchronized (mCallsManager.getLock()) { 476 mRinger.stopRinging(); 477 } 478 } 479 480 @VisibleForTesting stopCallWaiting()481 public void stopCallWaiting() { 482 synchronized (mCallsManager.getLock()) { 483 mRinger.stopCallWaiting(); 484 } 485 } 486 487 @VisibleForTesting setCallAudioRouteFocusState(int focusState)488 public void setCallAudioRouteFocusState(int focusState) { 489 mCallAudioRouteStateMachine.sendMessageWithSessionInfo( 490 CallAudioRouteStateMachine.SWITCH_FOCUS, focusState); 491 } 492 notifyAudioOperationsComplete()493 public void notifyAudioOperationsComplete() { 494 mCallAudioModeStateMachine.sendMessageWithArgs( 495 CallAudioModeStateMachine.AUDIO_OPERATIONS_COMPLETE, makeArgsForModeStateMachine()); 496 } 497 498 @VisibleForTesting getCallAudioRouteStateMachine()499 public CallAudioRouteStateMachine getCallAudioRouteStateMachine() { 500 return mCallAudioRouteStateMachine; 501 } 502 503 @VisibleForTesting getCallAudioModeStateMachine()504 public CallAudioModeStateMachine getCallAudioModeStateMachine() { 505 return mCallAudioModeStateMachine; 506 } 507 dump(IndentingPrintWriter pw)508 void dump(IndentingPrintWriter pw) { 509 pw.println("All calls:"); 510 pw.increaseIndent(); 511 dumpCallsInCollection(pw, mCalls); 512 pw.decreaseIndent(); 513 514 pw.println("Active dialing, or connecting calls:"); 515 pw.increaseIndent(); 516 dumpCallsInCollection(pw, mActiveDialingOrConnectingCalls); 517 pw.decreaseIndent(); 518 519 pw.println("Ringing calls:"); 520 pw.increaseIndent(); 521 dumpCallsInCollection(pw, mRingingCalls); 522 pw.decreaseIndent(); 523 524 pw.println("Holding calls:"); 525 pw.increaseIndent(); 526 dumpCallsInCollection(pw, mHoldingCalls); 527 pw.decreaseIndent(); 528 529 pw.println("Foreground call:"); 530 pw.println(mForegroundCall); 531 532 pw.println("CallAudioModeStateMachine pending messages:"); 533 pw.increaseIndent(); 534 mCallAudioModeStateMachine.dumpPendingMessages(pw); 535 pw.decreaseIndent(); 536 537 pw.println("CallAudioRouteStateMachine pending messages:"); 538 pw.increaseIndent(); 539 mCallAudioRouteStateMachine.dumpPendingMessages(pw); 540 pw.decreaseIndent(); 541 542 pw.println("BluetoothDeviceManager:"); 543 pw.increaseIndent(); 544 if (mBluetoothStateReceiver.getBluetoothDeviceManager() != null) { 545 mBluetoothStateReceiver.getBluetoothDeviceManager().dump(pw); 546 } 547 pw.decreaseIndent(); 548 } 549 550 @VisibleForTesting setIsTonePlaying(boolean isTonePlaying)551 public void setIsTonePlaying(boolean isTonePlaying) { 552 mIsTonePlaying = isTonePlaying; 553 mCallAudioModeStateMachine.sendMessageWithArgs( 554 isTonePlaying ? CallAudioModeStateMachine.TONE_STARTED_PLAYING 555 : CallAudioModeStateMachine.TONE_STOPPED_PLAYING, 556 makeArgsForModeStateMachine()); 557 558 if (!isTonePlaying && mIsDisconnectedTonePlaying) { 559 mCallsManager.onDisconnectedTonePlaying(false); 560 mIsDisconnectedTonePlaying = false; 561 } 562 } 563 onCallLeavingState(Call call, int state)564 private void onCallLeavingState(Call call, int state) { 565 switch (state) { 566 case CallState.ACTIVE: 567 case CallState.CONNECTING: 568 onCallLeavingActiveDialingOrConnecting(); 569 break; 570 case CallState.RINGING: 571 case CallState.SIMULATED_RINGING: 572 case CallState.ANSWERED: 573 onCallLeavingRinging(); 574 break; 575 case CallState.ON_HOLD: 576 onCallLeavingHold(); 577 break; 578 case CallState.PULLING: 579 onCallLeavingActiveDialingOrConnecting(); 580 break; 581 case CallState.DIALING: 582 stopRingbackForCall(call); 583 onCallLeavingActiveDialingOrConnecting(); 584 break; 585 case CallState.AUDIO_PROCESSING: 586 onCallLeavingAudioProcessing(); 587 break; 588 } 589 } 590 onCallEnteringState(Call call, int state)591 private void onCallEnteringState(Call call, int state) { 592 switch (state) { 593 case CallState.ACTIVE: 594 case CallState.CONNECTING: 595 onCallEnteringActiveDialingOrConnecting(); 596 break; 597 case CallState.RINGING: 598 case CallState.SIMULATED_RINGING: 599 onCallEnteringRinging(); 600 break; 601 case CallState.ON_HOLD: 602 onCallEnteringHold(); 603 break; 604 case CallState.PULLING: 605 onCallEnteringActiveDialingOrConnecting(); 606 break; 607 case CallState.DIALING: 608 onCallEnteringActiveDialingOrConnecting(); 609 playRingbackForCall(call); 610 break; 611 case CallState.ANSWERED: 612 if (call.can(android.telecom.Call.Details.CAPABILITY_SPEED_UP_MT_AUDIO)) { 613 onCallEnteringActiveDialingOrConnecting(); 614 } 615 break; 616 case CallState.AUDIO_PROCESSING: 617 onCallEnteringAudioProcessing(); 618 break; 619 } 620 } 621 onCallLeavingAudioProcessing()622 private void onCallLeavingAudioProcessing() { 623 if (mAudioProcessingCalls.size() == 0) { 624 mCallAudioModeStateMachine.sendMessageWithArgs( 625 CallAudioModeStateMachine.NO_MORE_AUDIO_PROCESSING_CALLS, 626 makeArgsForModeStateMachine()); 627 } 628 } 629 onCallEnteringAudioProcessing()630 private void onCallEnteringAudioProcessing() { 631 if (mAudioProcessingCalls.size() == 1) { 632 mCallAudioModeStateMachine.sendMessageWithArgs( 633 CallAudioModeStateMachine.NEW_AUDIO_PROCESSING_CALL, 634 makeArgsForModeStateMachine()); 635 } 636 } 637 onCallLeavingActiveDialingOrConnecting()638 private void onCallLeavingActiveDialingOrConnecting() { 639 if (mActiveDialingOrConnectingCalls.size() == 0) { 640 mCallAudioModeStateMachine.sendMessageWithArgs( 641 CallAudioModeStateMachine.NO_MORE_ACTIVE_OR_DIALING_CALLS, 642 makeArgsForModeStateMachine()); 643 } 644 } 645 onCallLeavingRinging()646 private void onCallLeavingRinging() { 647 if (mRingingCalls.size() == 0) { 648 mCallAudioModeStateMachine.sendMessageWithArgs( 649 CallAudioModeStateMachine.NO_MORE_RINGING_CALLS, 650 makeArgsForModeStateMachine()); 651 } 652 } 653 onCallLeavingHold()654 private void onCallLeavingHold() { 655 if (mHoldingCalls.size() == 0) { 656 mCallAudioModeStateMachine.sendMessageWithArgs( 657 CallAudioModeStateMachine.NO_MORE_HOLDING_CALLS, 658 makeArgsForModeStateMachine()); 659 } 660 } 661 onCallEnteringActiveDialingOrConnecting()662 private void onCallEnteringActiveDialingOrConnecting() { 663 if (mActiveDialingOrConnectingCalls.size() == 1) { 664 mCallAudioModeStateMachine.sendMessageWithArgs( 665 CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL, 666 makeArgsForModeStateMachine()); 667 } 668 } 669 onCallEnteringRinging()670 private void onCallEnteringRinging() { 671 if (mRingingCalls.size() == 1) { 672 mCallAudioModeStateMachine.sendMessageWithArgs( 673 CallAudioModeStateMachine.NEW_RINGING_CALL, 674 makeArgsForModeStateMachine()); 675 } 676 } 677 onCallEnteringHold()678 private void onCallEnteringHold() { 679 if (mHoldingCalls.size() == 1) { 680 mCallAudioModeStateMachine.sendMessageWithArgs( 681 CallAudioModeStateMachine.NEW_HOLDING_CALL, 682 makeArgsForModeStateMachine()); 683 } 684 } 685 updateForegroundCall()686 private void updateForegroundCall() { 687 Call oldForegroundCall = mForegroundCall; 688 if (mActiveDialingOrConnectingCalls.size() > 0) { 689 // Give preference for connecting calls over active/dialing for foreground-ness. 690 Call possibleConnectingCall = null; 691 for (Call call : mActiveDialingOrConnectingCalls) { 692 if (call.getState() == CallState.CONNECTING) { 693 possibleConnectingCall = call; 694 } 695 } 696 mForegroundCall = possibleConnectingCall == null ? 697 mActiveDialingOrConnectingCalls.iterator().next() : possibleConnectingCall; 698 } else if (mRingingCalls.size() > 0) { 699 mForegroundCall = mRingingCalls.iterator().next(); 700 } else if (mHoldingCalls.size() > 0) { 701 mForegroundCall = mHoldingCalls.iterator().next(); 702 } else { 703 mForegroundCall = null; 704 } 705 706 if (mForegroundCall != oldForegroundCall) { 707 mCallAudioRouteStateMachine.sendMessageWithSessionInfo( 708 CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE); 709 mDtmfLocalTonePlayer.onForegroundCallChanged(oldForegroundCall, mForegroundCall); 710 maybePlayHoldTone(); 711 } 712 } 713 714 @NonNull makeArgsForModeStateMachine()715 private CallAudioModeStateMachine.MessageArgs makeArgsForModeStateMachine() { 716 return new Builder() 717 .setHasActiveOrDialingCalls(mActiveDialingOrConnectingCalls.size() > 0) 718 .setHasRingingCalls(mRingingCalls.size() > 0) 719 .setHasHoldingCalls(mHoldingCalls.size() > 0) 720 .setHasAudioProcessingCalls(mAudioProcessingCalls.size() > 0) 721 .setIsTonePlaying(mIsTonePlaying) 722 .setForegroundCallIsVoip( 723 mForegroundCall != null && mForegroundCall.getIsVoipAudioMode()) 724 .setSession(Log.createSubsession()).build(); 725 } 726 getBinForCall(Call call)727 private HashSet<Call> getBinForCall(Call call) { 728 if (call.getState() == CallState.ANSWERED) { 729 // If the call has the speed-up-mt-audio capability, treat answered state as active 730 // for audio purposes. 731 if (call.can(android.telecom.Call.Details.CAPABILITY_SPEED_UP_MT_AUDIO)) { 732 return mActiveDialingOrConnectingCalls; 733 } 734 return mRingingCalls; 735 } 736 return mCallStateToCalls.get(call.getState()); 737 } 738 removeCallFromAllBins(Call call)739 private void removeCallFromAllBins(Call call) { 740 for (int i = 0; i < mCallStateToCalls.size(); i++) { 741 mCallStateToCalls.valueAt(i).remove(call); 742 } 743 } 744 playToneForDisconnectedCall(Call call)745 private void playToneForDisconnectedCall(Call call) { 746 // If this call is being disconnected as a result of being handed over to another call, 747 // we will not play a disconnect tone. 748 if (call.isHandoverInProgress()) { 749 Log.i(LOG_TAG, "Omitting tone because %s is being handed over.", call); 750 return; 751 } 752 753 if (mForegroundCall != null && call != mForegroundCall && mCalls.size() > 1) { 754 Log.v(LOG_TAG, "Omitting tone because we are not foreground" + 755 " and there is another call."); 756 return; 757 } 758 759 if (call.getDisconnectCause() != null) { 760 int toneToPlay = InCallTonePlayer.TONE_INVALID; 761 762 Log.v(this, "Disconnect cause: %s.", call.getDisconnectCause()); 763 764 switch(call.getDisconnectCause().getTone()) { 765 case ToneGenerator.TONE_SUP_BUSY: 766 toneToPlay = InCallTonePlayer.TONE_BUSY; 767 break; 768 case ToneGenerator.TONE_SUP_CONGESTION: 769 toneToPlay = InCallTonePlayer.TONE_CONGESTION; 770 break; 771 case ToneGenerator.TONE_CDMA_REORDER: 772 toneToPlay = InCallTonePlayer.TONE_REORDER; 773 break; 774 case ToneGenerator.TONE_CDMA_ABBR_INTERCEPT: 775 toneToPlay = InCallTonePlayer.TONE_INTERCEPT; 776 break; 777 case ToneGenerator.TONE_CDMA_CALLDROP_LITE: 778 toneToPlay = InCallTonePlayer.TONE_CDMA_DROP; 779 break; 780 case ToneGenerator.TONE_SUP_ERROR: 781 toneToPlay = InCallTonePlayer.TONE_UNOBTAINABLE_NUMBER; 782 break; 783 case ToneGenerator.TONE_PROP_PROMPT: 784 toneToPlay = InCallTonePlayer.TONE_CALL_ENDED; 785 break; 786 } 787 788 Log.d(this, "Found a disconnected call with tone to play %d.", toneToPlay); 789 790 if (toneToPlay != InCallTonePlayer.TONE_INVALID) { 791 boolean didToneStart = mPlayerFactory.createPlayer(toneToPlay).startTone(); 792 if (didToneStart) { 793 mCallsManager.onDisconnectedTonePlaying(true); 794 mIsDisconnectedTonePlaying = true; 795 } 796 } 797 } 798 } 799 playRingbackForCall(Call call)800 private void playRingbackForCall(Call call) { 801 if (call == mForegroundCall && call.isRingbackRequested()) { 802 mRingbackPlayer.startRingbackForCall(call); 803 } 804 } 805 stopRingbackForCall(Call call)806 private void stopRingbackForCall(Call call) { 807 mRingbackPlayer.stopRingbackForCall(call); 808 } 809 810 /** 811 * Determines if a hold tone should be played and then starts or stops it accordingly. 812 */ maybePlayHoldTone()813 private void maybePlayHoldTone() { 814 if (shouldPlayHoldTone()) { 815 if (mHoldTonePlayer == null) { 816 mHoldTonePlayer = mPlayerFactory.createPlayer(InCallTonePlayer.TONE_CALL_WAITING); 817 mHoldTonePlayer.startTone(); 818 } 819 } else { 820 if (mHoldTonePlayer != null) { 821 mHoldTonePlayer.stopTone(); 822 mHoldTonePlayer = null; 823 } 824 } 825 } 826 827 /** 828 * Determines if a hold tone should be played. 829 * A hold tone should be played only if foreground call is equals with call which is 830 * remotely held. 831 * 832 * @return {@code true} if the the hold tone should be played, {@code false} otherwise. 833 */ shouldPlayHoldTone()834 private boolean shouldPlayHoldTone() { 835 Call foregroundCall = getForegroundCall(); 836 // If there is no foreground call, no hold tone should play. 837 if (foregroundCall == null) { 838 return false; 839 } 840 841 // If another call is ringing, no hold tone should play. 842 if (mCallsManager.hasRingingCall()) { 843 return false; 844 } 845 846 // If the foreground call isn't active, no hold tone should play. This might happen, for 847 // example, if the user puts a remotely held call on hold itself. 848 if (!foregroundCall.isActive()) { 849 return false; 850 } 851 852 return foregroundCall.isRemotelyHeld(); 853 } 854 dumpCallsInCollection(IndentingPrintWriter pw, Collection<Call> calls)855 private void dumpCallsInCollection(IndentingPrintWriter pw, Collection<Call> calls) { 856 for (Call call : calls) { 857 if (call != null) pw.println(call.getId()); 858 } 859 } 860 maybeStopRingingAndCallWaitingForAnsweredOrRejectedCall(Call call)861 private void maybeStopRingingAndCallWaitingForAnsweredOrRejectedCall(Call call) { 862 // Check to see if the call being answered/rejected is the only ringing call, since this 863 // will be called before the connection service acknowledges the state change. 864 synchronized (mCallsManager.getLock()) { 865 if (mRingingCalls.size() == 0 || 866 (mRingingCalls.size() == 1 && call == mRingingCalls.iterator().next())) { 867 mRinger.stopRinging(); 868 mRinger.stopCallWaiting(); 869 } 870 } 871 } 872 shouldPlayDisconnectTone(int oldState, int newState)873 private boolean shouldPlayDisconnectTone(int oldState, int newState) { 874 if (newState != CallState.DISCONNECTED) { 875 return false; 876 } 877 return oldState == CallState.ACTIVE || 878 oldState == CallState.DIALING || 879 oldState == CallState.ON_HOLD; 880 } 881 882 @VisibleForTesting getTrackedCalls()883 public Set<Call> getTrackedCalls() { 884 return mCalls; 885 } 886 887 @VisibleForTesting getCallStateToCalls()888 public SparseArray<LinkedHashSet<Call>> getCallStateToCalls() { 889 return mCallStateToCalls; 890 } 891 } 892