CallAudioManager.java revision 59c77e691e566373cbb6d06b3909c1126035d982
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 17package com.android.server.telecom; 18 19import android.annotation.NonNull; 20import android.media.IAudioService; 21import android.media.ToneGenerator; 22import android.telecom.CallAudioState; 23import android.telecom.VideoProfile; 24import android.util.SparseArray; 25 26import com.android.internal.annotations.VisibleForTesting; 27import com.android.internal.util.IndentingPrintWriter; 28 29import java.util.Collection; 30import java.util.HashSet; 31import java.util.Set; 32import java.util.LinkedHashSet; 33 34public class CallAudioManager extends CallsManagerListenerBase { 35 36 public interface AudioServiceFactory { 37 IAudioService getAudioService(); 38 } 39 40 private final String LOG_TAG = CallAudioManager.class.getSimpleName(); 41 42 private final LinkedHashSet<Call> mActiveDialingOrConnectingCalls; 43 private final LinkedHashSet<Call> mRingingCalls; 44 private final LinkedHashSet<Call> mHoldingCalls; 45 private final Set<Call> mCalls; 46 private final SparseArray<LinkedHashSet<Call>> mCallStateToCalls; 47 48 private final CallAudioRouteStateMachine mCallAudioRouteStateMachine; 49 private final CallAudioModeStateMachine mCallAudioModeStateMachine; 50 private final CallsManager mCallsManager; 51 private final InCallTonePlayer.Factory mPlayerFactory; 52 private final Ringer mRinger; 53 private final RingbackPlayer mRingbackPlayer; 54 private final DtmfLocalTonePlayer mDtmfLocalTonePlayer; 55 56 private Call mForegroundCall; 57 private boolean mIsTonePlaying = false; 58 private InCallTonePlayer mHoldTonePlayer; 59 60 public CallAudioManager(CallAudioRouteStateMachine callAudioRouteStateMachine, 61 CallsManager callsManager, 62 CallAudioModeStateMachine callAudioModeStateMachine, 63 InCallTonePlayer.Factory playerFactory, 64 Ringer ringer, 65 RingbackPlayer ringbackPlayer, 66 DtmfLocalTonePlayer dtmfLocalTonePlayer) { 67 mActiveDialingOrConnectingCalls = new LinkedHashSet<>(); 68 mRingingCalls = new LinkedHashSet<>(); 69 mHoldingCalls = new LinkedHashSet<>(); 70 mCalls = new HashSet<>(); 71 mCallStateToCalls = new SparseArray<LinkedHashSet<Call>>() {{ 72 put(CallState.CONNECTING, mActiveDialingOrConnectingCalls); 73 put(CallState.ACTIVE, mActiveDialingOrConnectingCalls); 74 put(CallState.DIALING, mActiveDialingOrConnectingCalls); 75 put(CallState.RINGING, mRingingCalls); 76 put(CallState.ON_HOLD, mHoldingCalls); 77 }}; 78 79 mCallAudioRouteStateMachine = callAudioRouteStateMachine; 80 mCallAudioModeStateMachine = callAudioModeStateMachine; 81 mCallsManager = callsManager; 82 mPlayerFactory = playerFactory; 83 mRinger = ringer; 84 mRingbackPlayer = ringbackPlayer; 85 mDtmfLocalTonePlayer = dtmfLocalTonePlayer; 86 87 mPlayerFactory.setCallAudioManager(this); 88 mCallAudioModeStateMachine.setCallAudioManager(this); 89 } 90 91 @Override 92 public void onCallStateChanged(Call call, int oldState, int newState) { 93 if (call.getParentCall() != null) { 94 // No audio management for calls in a conference. 95 return; 96 } 97 Log.d(LOG_TAG, "Call state changed for TC@%s: %s -> %s", call.getId(), 98 CallState.toString(oldState), CallState.toString(newState)); 99 100 if (mCallStateToCalls.get(oldState) != null) { 101 mCallStateToCalls.get(oldState).remove(call); 102 } 103 if (mCallStateToCalls.get(newState) != null) { 104 mCallStateToCalls.get(newState).add(call); 105 } 106 107 updateForegroundCall(); 108 if (newState == CallState.DISCONNECTED) { 109 playToneForDisconnectedCall(call); 110 } 111 112 onCallLeavingState(call, oldState); 113 onCallEnteringState(call, newState); 114 } 115 116 @Override 117 public void onCallAdded(Call call) { 118 if (call.getParentCall() != null) { 119 return; // Don't do audio handling for calls in a conference. 120 } 121 122 if (mCalls.contains(call)) { 123 Log.w(LOG_TAG, "Call TC@%s is being added twice.", call.getId()); 124 return; // No guarantees that the same call won't get added twice. 125 } 126 127 Log.d(LOG_TAG, "Call added with id TC@%s in state %s", call.getId(), 128 CallState.toString(call.getState())); 129 130 if (mCallStateToCalls.get(call.getState()) != null) { 131 mCallStateToCalls.get(call.getState()).add(call); 132 } 133 updateForegroundCall(); 134 mCalls.add(call); 135 136 onCallEnteringState(call, call.getState()); 137 } 138 139 @Override 140 public void onCallRemoved(Call call) { 141 if (call.getParentCall() != null) { 142 return; // Don't do audio handling for calls in a conference. 143 } 144 145 if (!mCalls.contains(call)) { 146 return; // No guarantees that the same call won't get removed twice. 147 } 148 149 Log.d(LOG_TAG, "Call removed with id TC@%s in state %s", call.getId(), 150 CallState.toString(call.getState())); 151 152 if (mCallStateToCalls.get(call.getState()) != null) { 153 mCallStateToCalls.get(call.getState()).remove(call); 154 } 155 156 updateForegroundCall(); 157 mCalls.remove(call); 158 159 onCallLeavingState(call, call.getState()); 160 161 if (mCallsManager.getCalls().isEmpty()) { 162 Log.v(this, "all calls removed, resetting system audio to default state"); 163 mCallAudioRouteStateMachine.sendMessageWithSessionInfo( 164 CallAudioRouteStateMachine.REINITIALIZE); 165 } 166 } 167 168 @Override 169 public void onIncomingCallAnswered(Call call) { 170 // This is called after the UI answers the call, but before the connection service 171 // sets the call to active. Only thing to handle for mode here is the audio speedup thing. 172 173 if (call.can(android.telecom.Call.Details.CAPABILITY_SPEED_UP_MT_AUDIO)) { 174 if (mForegroundCall == call) { 175 Log.i(LOG_TAG, "Invoking the MT_AUDIO_SPEEDUP mechanism. Transitioning into " + 176 "an active in-call audio state before connection service has " + 177 "connected the call."); 178 if (mCallStateToCalls.get(call.getState()) != null) { 179 mCallStateToCalls.get(call.getState()).remove(call); 180 } 181 mActiveDialingOrConnectingCalls.add(call); 182 mCallAudioModeStateMachine.sendMessage( 183 CallAudioModeStateMachine.MT_AUDIO_SPEEDUP_FOR_RINGING_CALL, 184 makeArgsForModeStateMachine()); 185 } 186 } 187 188 if (mRingingCalls.size() == 0) { 189 mRinger.stopRinging(); 190 mRinger.stopCallWaiting(); 191 } 192 } 193 194 @Override 195 public void onSessionModifyRequestReceived(Call call, VideoProfile videoProfile) { 196 if (videoProfile == null) { 197 return; 198 } 199 200 if (call != mForegroundCall) { 201 // We only play tones for foreground calls. 202 return; 203 } 204 205 int previousVideoState = call.getVideoState(); 206 int newVideoState = videoProfile.getVideoState(); 207 Log.v(this, "onSessionModifyRequestReceived : videoProfile = " + VideoProfile 208 .videoStateToString(newVideoState)); 209 210 boolean isUpgradeRequest = !VideoProfile.isReceptionEnabled(previousVideoState) && 211 VideoProfile.isReceptionEnabled(newVideoState); 212 213 if (isUpgradeRequest) { 214 mPlayerFactory.createPlayer(InCallTonePlayer.TONE_VIDEO_UPGRADE).startTone(); 215 } 216 } 217 218 /** 219 * Play or stop a call hold tone for a call. Triggered via 220 * {@link Connection#sendConnectionEvent(String)} when the 221 * {@link Connection#EVENT_ON_HOLD_TONE_START} event or 222 * {@link Connection#EVENT_ON_HOLD_TONE_STOP} event is passed through to the 223 * 224 * @param call The call which requested the hold tone. 225 */ 226 @Override 227 public void onHoldToneRequested(Call call) { 228 maybePlayHoldTone(); 229 } 230 231 @Override 232 public void onIsVoipAudioModeChanged(Call call) { 233 if (call != mForegroundCall) { 234 return; 235 } 236 mCallAudioModeStateMachine.sendMessage( 237 CallAudioModeStateMachine.FOREGROUND_VOIP_MODE_CHANGE, 238 makeArgsForModeStateMachine()); 239 } 240 241 @Override 242 public void onRingbackRequested(Call call, boolean shouldRingback) { 243 if (call == mForegroundCall && shouldRingback) { 244 mRingbackPlayer.startRingbackForCall(call); 245 } else { 246 mRingbackPlayer.stopRingbackForCall(call); 247 } 248 } 249 250 @Override 251 public void onIncomingCallRejected(Call call, boolean rejectWithMessage, String message) { 252 // This gets called after the UI rejects a call but before the CS processes the rejection. 253 // Will get called before the state change from ringing to not ringing. 254 255 if (mRingingCalls.size() == 0 || call == mRingingCalls.iterator().next()) { 256 mRinger.stopRinging(); 257 mRinger.stopCallWaiting(); 258 } 259 } 260 261 @Override 262 public void onIsConferencedChanged(Call call) { 263 // This indicates a conferencing change, which shouldn't impact any audio mode stuff. 264 Call parentCall = call.getParentCall(); 265 if (parentCall == null) { 266 // Indicates that the call should be tracked for audio purposes. Treat it as if it were 267 // just added. 268 Log.i(LOG_TAG, "Call TC@" + call.getId() + " left conference and will" + 269 " now be tracked by CallAudioManager."); 270 onCallAdded(call); 271 } else { 272 // The call joined a conference, so stop tracking it. 273 if (mCallStateToCalls.get(call.getState()) != null) { 274 mCallStateToCalls.get(call.getState()).remove(call); 275 } 276 277 updateForegroundCall(); 278 mCalls.remove(call); 279 } 280 } 281 282 public CallAudioState getCallAudioState() { 283 return mCallAudioRouteStateMachine.getCurrentCallAudioState(); 284 } 285 286 public Call getPossiblyHeldForegroundCall() { 287 return mForegroundCall; 288 } 289 290 public Call getForegroundCall() { 291 if (mForegroundCall != null && mForegroundCall.getState() != CallState.ON_HOLD) { 292 return mForegroundCall; 293 } 294 return null; 295 } 296 297 void toggleMute() { 298 mCallAudioRouteStateMachine.sendMessageWithSessionInfo( 299 CallAudioRouteStateMachine.TOGGLE_MUTE); 300 } 301 302 void mute(boolean shouldMute) { 303 Log.v(this, "mute, shouldMute: %b", shouldMute); 304 305 // Don't mute if there are any emergency calls. 306 if (mCallsManager.hasEmergencyCall()) { 307 shouldMute = false; 308 Log.v(this, "ignoring mute for emergency call"); 309 } 310 311 mCallAudioRouteStateMachine.sendMessageWithSessionInfo(shouldMute 312 ? CallAudioRouteStateMachine.MUTE_ON : CallAudioRouteStateMachine.MUTE_OFF); 313 } 314 315 /** 316 * Changed the audio route, for example from earpiece to speaker phone. 317 * 318 * @param route The new audio route to use. See {@link CallAudioState}. 319 */ 320 void setAudioRoute(int route) { 321 Log.v(this, "setAudioRoute, route: %s", CallAudioState.audioRouteToString(route)); 322 switch (route) { 323 case CallAudioState.ROUTE_BLUETOOTH: 324 mCallAudioRouteStateMachine.sendMessageWithSessionInfo( 325 CallAudioRouteStateMachine.USER_SWITCH_BLUETOOTH); 326 return; 327 case CallAudioState.ROUTE_SPEAKER: 328 mCallAudioRouteStateMachine.sendMessageWithSessionInfo( 329 CallAudioRouteStateMachine.USER_SWITCH_SPEAKER); 330 return; 331 case CallAudioState.ROUTE_WIRED_HEADSET: 332 mCallAudioRouteStateMachine.sendMessageWithSessionInfo( 333 CallAudioRouteStateMachine.USER_SWITCH_HEADSET); 334 return; 335 case CallAudioState.ROUTE_EARPIECE: 336 mCallAudioRouteStateMachine.sendMessageWithSessionInfo( 337 CallAudioRouteStateMachine.USER_SWITCH_EARPIECE); 338 return; 339 case CallAudioState.ROUTE_WIRED_OR_EARPIECE: 340 mCallAudioRouteStateMachine.sendMessageWithSessionInfo( 341 CallAudioRouteStateMachine.USER_SWITCH_BASELINE_ROUTE); 342 return; 343 default: 344 Log.wtf(this, "Invalid route specified: %d", route); 345 } 346 } 347 348 void silenceRingers() { 349 for (Call call : mRingingCalls) { 350 call.silence(); 351 } 352 353 mRingingCalls.clear(); 354 mRinger.stopRinging(); 355 mRinger.stopCallWaiting(); 356 mCallAudioModeStateMachine.sendMessage(CallAudioModeStateMachine.NO_MORE_RINGING_CALLS, 357 makeArgsForModeStateMachine()); 358 } 359 360 @VisibleForTesting 361 public void startRinging() { 362 mRinger.startRinging(mForegroundCall); 363 } 364 365 @VisibleForTesting 366 public void startCallWaiting() { 367 mRinger.startCallWaiting(mRingingCalls.iterator().next()); 368 } 369 370 @VisibleForTesting 371 public void stopRinging() { 372 mRinger.stopRinging(); 373 } 374 375 @VisibleForTesting 376 public void stopCallWaiting() { 377 mRinger.stopCallWaiting(); 378 } 379 380 @VisibleForTesting 381 public void setCallAudioRouteFocusState(int focusState) { 382 mCallAudioRouteStateMachine.sendMessageWithSessionInfo( 383 CallAudioRouteStateMachine.SWITCH_FOCUS, focusState); 384 } 385 386 void dump(IndentingPrintWriter pw) { 387 pw.println("Active dialing, or connecting calls:"); 388 pw.increaseIndent(); 389 dumpCallsInCollection(pw, mActiveDialingOrConnectingCalls); 390 pw.decreaseIndent(); 391 392 pw.println("Ringing calls:"); 393 pw.increaseIndent(); 394 dumpCallsInCollection(pw, mRingingCalls); 395 pw.decreaseIndent(); 396 397 pw.println("Holding calls:"); 398 pw.increaseIndent(); 399 dumpCallsInCollection(pw, mHoldingCalls); 400 pw.decreaseIndent(); 401 402 pw.println("Foreground call:"); 403 pw.println(mForegroundCall); 404 } 405 406 @VisibleForTesting 407 public void setIsTonePlaying(boolean isTonePlaying) { 408 mIsTonePlaying = isTonePlaying; 409 mCallAudioModeStateMachine.sendMessage( 410 isTonePlaying ? CallAudioModeStateMachine.TONE_STARTED_PLAYING 411 : CallAudioModeStateMachine.TONE_STOPPED_PLAYING, 412 makeArgsForModeStateMachine()); 413 } 414 415 private void onCallLeavingState(Call call, int state) { 416 switch (state) { 417 case CallState.ACTIVE: 418 case CallState.CONNECTING: 419 onCallLeavingActiveDialingOrConnecting(); 420 break; 421 case CallState.RINGING: 422 onCallLeavingRinging(); 423 break; 424 case CallState.ON_HOLD: 425 onCallLeavingHold(); 426 break; 427 case CallState.DIALING: 428 stopRingbackForCall(call); 429 onCallLeavingActiveDialingOrConnecting(); 430 } 431 } 432 433 private void onCallEnteringState(Call call, int state) { 434 switch (state) { 435 case CallState.ACTIVE: 436 case CallState.CONNECTING: 437 onCallEnteringActiveDialingOrConnecting(); 438 break; 439 case CallState.RINGING: 440 onCallEnteringRinging(); 441 break; 442 case CallState.ON_HOLD: 443 onCallEnteringHold(); 444 break; 445 case CallState.DIALING: 446 onCallEnteringActiveDialingOrConnecting(); 447 playRingbackForCall(call); 448 break; 449 } 450 } 451 452 private void onCallLeavingActiveDialingOrConnecting() { 453 if (mActiveDialingOrConnectingCalls.size() == 0) { 454 mCallAudioModeStateMachine.sendMessage( 455 CallAudioModeStateMachine.NO_MORE_ACTIVE_OR_DIALING_CALLS, 456 makeArgsForModeStateMachine()); 457 } 458 } 459 460 private void onCallLeavingRinging() { 461 if (mRingingCalls.size() == 0) { 462 mCallAudioModeStateMachine.sendMessage(CallAudioModeStateMachine.NO_MORE_RINGING_CALLS, 463 makeArgsForModeStateMachine()); 464 } 465 } 466 467 private void onCallLeavingHold() { 468 if (mHoldingCalls.size() == 0) { 469 mCallAudioModeStateMachine.sendMessage(CallAudioModeStateMachine.NO_MORE_HOLDING_CALLS, 470 makeArgsForModeStateMachine()); 471 } 472 } 473 474 private void onCallEnteringActiveDialingOrConnecting() { 475 if (mActiveDialingOrConnectingCalls.size() == 1) { 476 mCallAudioModeStateMachine.sendMessage( 477 CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL, 478 makeArgsForModeStateMachine()); 479 } 480 } 481 482 private void onCallEnteringRinging() { 483 if (mRingingCalls.size() == 1) { 484 mCallAudioModeStateMachine.sendMessage(CallAudioModeStateMachine.NEW_RINGING_CALL, 485 makeArgsForModeStateMachine()); 486 } 487 } 488 489 private void onCallEnteringHold() { 490 if (mHoldingCalls.size() == 1) { 491 mCallAudioModeStateMachine.sendMessage(CallAudioModeStateMachine.NEW_HOLDING_CALL, 492 makeArgsForModeStateMachine()); 493 } 494 } 495 496 private void updateForegroundCall() { 497 Call oldForegroundCall = mForegroundCall; 498 if (mActiveDialingOrConnectingCalls.size() > 0) { 499 // Give preference for connecting calls over active/dialing for foreground-ness. 500 Call possibleConnectingCall = null; 501 for (Call call : mActiveDialingOrConnectingCalls) { 502 if (call.getState() == CallState.CONNECTING) { 503 possibleConnectingCall = call; 504 } 505 } 506 mForegroundCall = possibleConnectingCall == null ? 507 mActiveDialingOrConnectingCalls.iterator().next() : possibleConnectingCall; 508 } else if (mRingingCalls.size() > 0) { 509 mForegroundCall = mRingingCalls.iterator().next(); 510 } else if (mHoldingCalls.size() > 0) { 511 mForegroundCall = mHoldingCalls.iterator().next(); 512 } else { 513 mForegroundCall = null; 514 } 515 516 if (mForegroundCall != oldForegroundCall) { 517 mDtmfLocalTonePlayer.onForegroundCallChanged(oldForegroundCall, mForegroundCall); 518 maybePlayHoldTone(); 519 } 520 } 521 522 @NonNull 523 private CallAudioModeStateMachine.MessageArgs makeArgsForModeStateMachine() { 524 return new CallAudioModeStateMachine.MessageArgs( 525 mActiveDialingOrConnectingCalls.size() > 0, 526 mRingingCalls.size() > 0, 527 mHoldingCalls.size() > 0, 528 mIsTonePlaying, 529 mForegroundCall != null && mForegroundCall.getIsVoipAudioMode(), 530 Log.createSubsession()); 531 } 532 533 private void playToneForDisconnectedCall(Call call) { 534 if (mForegroundCall != null && call != mForegroundCall && mCalls.size() > 1) { 535 Log.v(LOG_TAG, "Omitting tone because we are not foreground" + 536 " and there is another call."); 537 return; 538 } 539 540 if (call.getDisconnectCause() != null) { 541 int toneToPlay = InCallTonePlayer.TONE_INVALID; 542 543 Log.v(this, "Disconnect cause: %s.", call.getDisconnectCause()); 544 545 switch(call.getDisconnectCause().getTone()) { 546 case ToneGenerator.TONE_SUP_BUSY: 547 toneToPlay = InCallTonePlayer.TONE_BUSY; 548 break; 549 case ToneGenerator.TONE_SUP_CONGESTION: 550 toneToPlay = InCallTonePlayer.TONE_CONGESTION; 551 break; 552 case ToneGenerator.TONE_CDMA_REORDER: 553 toneToPlay = InCallTonePlayer.TONE_REORDER; 554 break; 555 case ToneGenerator.TONE_CDMA_ABBR_INTERCEPT: 556 toneToPlay = InCallTonePlayer.TONE_INTERCEPT; 557 break; 558 case ToneGenerator.TONE_CDMA_CALLDROP_LITE: 559 toneToPlay = InCallTonePlayer.TONE_CDMA_DROP; 560 break; 561 case ToneGenerator.TONE_SUP_ERROR: 562 toneToPlay = InCallTonePlayer.TONE_UNOBTAINABLE_NUMBER; 563 break; 564 case ToneGenerator.TONE_PROP_PROMPT: 565 toneToPlay = InCallTonePlayer.TONE_CALL_ENDED; 566 break; 567 } 568 569 Log.d(this, "Found a disconnected call with tone to play %d.", toneToPlay); 570 571 if (toneToPlay != InCallTonePlayer.TONE_INVALID) { 572 mPlayerFactory.createPlayer(toneToPlay).startTone(); 573 } 574 } 575 } 576 577 private void playRingbackForCall(Call call) { 578 if (call == mForegroundCall && call.isRingbackRequested()) { 579 mRingbackPlayer.startRingbackForCall(call); 580 } 581 } 582 583 private void stopRingbackForCall(Call call) { 584 mRingbackPlayer.stopRingbackForCall(call); 585 } 586 587 /** 588 * Determines if a hold tone should be played and then starts or stops it accordingly. 589 */ 590 private void maybePlayHoldTone() { 591 if (shouldPlayHoldTone()) { 592 if (mHoldTonePlayer == null) { 593 mHoldTonePlayer = mPlayerFactory.createPlayer(InCallTonePlayer.TONE_CALL_WAITING); 594 mHoldTonePlayer.start(); 595 } 596 } else { 597 if (mHoldTonePlayer != null) { 598 mHoldTonePlayer.stopTone(); 599 mHoldTonePlayer = null; 600 } 601 } 602 } 603 604 /** 605 * Determines if a hold tone should be played. 606 * A hold tone should be played only if foreground call is equals with call which is 607 * remotely held. 608 * 609 * @return {@code true} if the the hold tone should be played, {@code false} otherwise. 610 */ 611 private boolean shouldPlayHoldTone() { 612 Call foregroundCall = getForegroundCall(); 613 // If there is no foreground call, no hold tone should play. 614 if (foregroundCall == null) { 615 return false; 616 } 617 618 // If another call is ringing, no hold tone should play. 619 if (mCallsManager.hasRingingCall()) { 620 return false; 621 } 622 623 // If the foreground call isn't active, no hold tone should play. This might happen, for 624 // example, if the user puts a remotely held call on hold itself. 625 if (!foregroundCall.isActive()) { 626 return false; 627 } 628 629 return foregroundCall.isRemotelyHeld(); 630 } 631 632 private void dumpCallsInCollection(IndentingPrintWriter pw, Collection<Call> calls) { 633 for (Call call : calls) { 634 if (call != null) pw.println(call.getId()); 635 } 636 } 637}