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