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