1 /* 2 * Copyright (C) 2014 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.content.Context; 20 import android.media.AudioManager; 21 import android.telecom.AudioState; 22 import android.telecom.CallState; 23 24 import com.android.internal.util.IndentingPrintWriter; 25 import com.android.internal.util.Preconditions; 26 27 import java.util.Objects; 28 29 /** 30 * This class manages audio modes, streams and other properties. 31 */ 32 final class CallAudioManager extends CallsManagerListenerBase 33 implements WiredHeadsetManager.Listener { 34 private static final int STREAM_NONE = -1; 35 36 private final StatusBarNotifier mStatusBarNotifier; 37 private final AudioManager mAudioManager; 38 private final BluetoothManager mBluetoothManager; 39 private final WiredHeadsetManager mWiredHeadsetManager; 40 41 private AudioState mAudioState; 42 private int mAudioFocusStreamType; 43 private boolean mIsRinging; 44 private boolean mIsTonePlaying; 45 private boolean mWasSpeakerOn; 46 private int mMostRecentlyUsedMode = AudioManager.MODE_IN_CALL; 47 private Call mCallToSpeedUpMTAudio = null; 48 49 CallAudioManager(Context context, StatusBarNotifier statusBarNotifier, 50 WiredHeadsetManager wiredHeadsetManager) { 51 mStatusBarNotifier = statusBarNotifier; 52 mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); 53 mBluetoothManager = new BluetoothManager(context, this); 54 mWiredHeadsetManager = wiredHeadsetManager; 55 mWiredHeadsetManager.addListener(this); 56 57 saveAudioState(getInitialAudioState(null)); 58 mAudioFocusStreamType = STREAM_NONE; 59 } 60 61 AudioState getAudioState() { 62 return mAudioState; 63 } 64 65 @Override 66 public void onCallAdded(Call call) { 67 onCallUpdated(call); 68 69 if (hasFocus() && getForegroundCall() == call) { 70 if (!call.isIncoming()) { 71 // Unmute new outgoing call. 72 setSystemAudioState(false, mAudioState.getRoute(), 73 mAudioState.getSupportedRouteMask()); 74 } 75 } 76 } 77 78 @Override 79 public void onCallRemoved(Call call) { 80 // If we didn't already have focus, there's nothing to do. 81 if (hasFocus()) { 82 if (CallsManager.getInstance().getCalls().isEmpty()) { 83 Log.v(this, "all calls removed, reseting system audio to default state"); 84 setInitialAudioState(null, false /* force */); 85 mWasSpeakerOn = false; 86 } 87 updateAudioStreamAndMode(); 88 } 89 } 90 91 @Override 92 public void onCallStateChanged(Call call, int oldState, int newState) { 93 onCallUpdated(call); 94 } 95 96 @Override 97 public void onIncomingCallAnswered(Call call) { 98 int route = mAudioState.getRoute(); 99 100 // We do two things: 101 // (1) If this is the first call, then we can to turn on bluetooth if available. 102 // (2) Unmute the audio for the new incoming call. 103 boolean isOnlyCall = CallsManager.getInstance().getCalls().size() == 1; 104 if (isOnlyCall && mBluetoothManager.isBluetoothAvailable()) { 105 mBluetoothManager.connectBluetoothAudio(); 106 route = AudioState.ROUTE_BLUETOOTH; 107 } 108 109 setSystemAudioState(false /* isMute */, route, mAudioState.getSupportedRouteMask()); 110 111 if (call.can(android.telecom.Call.Details.CAPABILITY_SPEED_UP_MT_AUDIO)) { 112 Log.v(this, "Speed up audio setup for IMS MT call."); 113 mCallToSpeedUpMTAudio = call; 114 updateAudioStreamAndMode(); 115 } 116 } 117 118 @Override 119 public void onForegroundCallChanged(Call oldForegroundCall, Call newForegroundCall) { 120 onCallUpdated(newForegroundCall); 121 // Ensure that the foreground call knows about the latest audio state. 122 updateAudioForForegroundCall(); 123 } 124 125 @Override 126 public void onIsVoipAudioModeChanged(Call call) { 127 updateAudioStreamAndMode(); 128 } 129 130 /** 131 * Updates the audio route when the headset plugged in state changes. For example, if audio is 132 * being routed over speakerphone and a headset is plugged in then switch to wired headset. 133 */ 134 @Override 135 public void onWiredHeadsetPluggedInChanged(boolean oldIsPluggedIn, boolean newIsPluggedIn) { 136 // This can happen even when there are no calls and we don't have focus. 137 if (!hasFocus()) { 138 return; 139 } 140 141 boolean isCurrentlyWiredHeadset = mAudioState.getRoute() == AudioState.ROUTE_WIRED_HEADSET; 142 143 int newRoute = mAudioState.getRoute(); // start out with existing route 144 if (newIsPluggedIn) { 145 newRoute = AudioState.ROUTE_WIRED_HEADSET; 146 } else if (isCurrentlyWiredHeadset) { 147 Call call = getForegroundCall(); 148 boolean hasLiveCall = call != null && call.isAlive(); 149 150 if (hasLiveCall) { 151 // In order of preference when a wireless headset is unplugged. 152 if (mWasSpeakerOn) { 153 newRoute = AudioState.ROUTE_SPEAKER; 154 } else { 155 newRoute = AudioState.ROUTE_EARPIECE; 156 } 157 158 // We don't automatically connect to bluetooth when user unplugs their wired headset 159 // and they were previously using the wired. Wired and earpiece are effectively the 160 // same choice in that they replace each other as an option when wired headsets 161 // are plugged in and out. This means that keeping it earpiece is a bit more 162 // consistent with the status quo. Bluetooth also has more danger associated with 163 // choosing it in the wrong curcumstance because bluetooth devices can be 164 // semi-public (like in a very-occupied car) where earpiece doesn't carry that risk. 165 } 166 } 167 168 // We need to call this every time even if we do not change the route because the supported 169 // routes changed either to include or not include WIRED_HEADSET. 170 setSystemAudioState(mAudioState.isMuted(), newRoute, calculateSupportedRoutes()); 171 } 172 173 void toggleMute() { 174 mute(!mAudioState.isMuted()); 175 } 176 177 void mute(boolean shouldMute) { 178 if (!hasFocus()) { 179 return; 180 } 181 182 Log.v(this, "mute, shouldMute: %b", shouldMute); 183 184 // Don't mute if there are any emergency calls. 185 if (CallsManager.getInstance().hasEmergencyCall()) { 186 shouldMute = false; 187 Log.v(this, "ignoring mute for emergency call"); 188 } 189 190 if (mAudioState.isMuted() != shouldMute) { 191 setSystemAudioState(shouldMute, mAudioState.getRoute(), 192 mAudioState.getSupportedRouteMask()); 193 } 194 } 195 196 /** 197 * Changed the audio route, for example from earpiece to speaker phone. 198 * 199 * @param route The new audio route to use. See {@link AudioState}. 200 */ 201 void setAudioRoute(int route) { 202 // This can happen even when there are no calls and we don't have focus. 203 if (!hasFocus()) { 204 return; 205 } 206 207 Log.v(this, "setAudioRoute, route: %s", AudioState.audioRouteToString(route)); 208 209 // Change ROUTE_WIRED_OR_EARPIECE to a single entry. 210 int newRoute = selectWiredOrEarpiece(route, mAudioState.getSupportedRouteMask()); 211 212 // If route is unsupported, do nothing. 213 if ((mAudioState.getSupportedRouteMask() | newRoute) == 0) { 214 Log.wtf(this, "Asking to set to a route that is unsupported: %d", newRoute); 215 return; 216 } 217 218 if (mAudioState.getRoute() != newRoute) { 219 // Remember the new speaker state so it can be restored when the user plugs and unplugs 220 // a headset. 221 mWasSpeakerOn = newRoute == AudioState.ROUTE_SPEAKER; 222 setSystemAudioState(mAudioState.isMuted(), newRoute, 223 mAudioState.getSupportedRouteMask()); 224 } 225 } 226 227 void setIsRinging(boolean isRinging) { 228 if (mIsRinging != isRinging) { 229 Log.v(this, "setIsRinging %b -> %b", mIsRinging, isRinging); 230 mIsRinging = isRinging; 231 updateAudioStreamAndMode(); 232 } 233 } 234 235 /** 236 * Sets the tone playing status. Some tones can play even when there are no live calls and this 237 * status indicates that we should keep audio focus even for tones that play beyond the life of 238 * calls. 239 * 240 * @param isPlayingNew The status to set. 241 */ 242 void setIsTonePlaying(boolean isPlayingNew) { 243 ThreadUtil.checkOnMainThread(); 244 245 if (mIsTonePlaying != isPlayingNew) { 246 Log.v(this, "mIsTonePlaying %b -> %b.", mIsTonePlaying, isPlayingNew); 247 mIsTonePlaying = isPlayingNew; 248 updateAudioStreamAndMode(); 249 } 250 } 251 252 /** 253 * Updates the audio routing according to the bluetooth state. 254 */ 255 void onBluetoothStateChange(BluetoothManager bluetoothManager) { 256 // This can happen even when there are no calls and we don't have focus. 257 if (!hasFocus()) { 258 return; 259 } 260 261 int supportedRoutes = calculateSupportedRoutes(); 262 int newRoute = mAudioState.getRoute(); 263 if (bluetoothManager.isBluetoothAudioConnectedOrPending()) { 264 newRoute = AudioState.ROUTE_BLUETOOTH; 265 } else if (mAudioState.getRoute() == AudioState.ROUTE_BLUETOOTH) { 266 newRoute = selectWiredOrEarpiece(AudioState.ROUTE_WIRED_OR_EARPIECE, supportedRoutes); 267 // Do not switch to speaker when bluetooth disconnects. 268 mWasSpeakerOn = false; 269 } 270 271 setSystemAudioState(mAudioState.isMuted(), newRoute, supportedRoutes); 272 } 273 274 boolean isBluetoothAudioOn() { 275 return mBluetoothManager.isBluetoothAudioConnected(); 276 } 277 278 boolean isBluetoothDeviceAvailable() { 279 return mBluetoothManager.isBluetoothAvailable(); 280 } 281 282 private void saveAudioState(AudioState audioState) { 283 mAudioState = audioState; 284 mStatusBarNotifier.notifyMute(mAudioState.isMuted()); 285 mStatusBarNotifier.notifySpeakerphone(mAudioState.getRoute() == AudioState.ROUTE_SPEAKER); 286 } 287 288 private void onCallUpdated(Call call) { 289 boolean wasNotVoiceCall = mAudioFocusStreamType != AudioManager.STREAM_VOICE_CALL; 290 updateAudioStreamAndMode(); 291 292 if (call != null && call.getState() == CallState.ACTIVE && 293 call == mCallToSpeedUpMTAudio) { 294 mCallToSpeedUpMTAudio = null; 295 } 296 // If we transition from not voice call to voice call, we need to set an initial state. 297 if (wasNotVoiceCall && mAudioFocusStreamType == AudioManager.STREAM_VOICE_CALL) { 298 setInitialAudioState(call, true /* force */); 299 } 300 } 301 302 private void setSystemAudioState(boolean isMuted, int route, int supportedRouteMask) { 303 setSystemAudioState(false /* force */, isMuted, route, supportedRouteMask); 304 } 305 306 private void setSystemAudioState( 307 boolean force, boolean isMuted, int route, int supportedRouteMask) { 308 if (!hasFocus()) { 309 return; 310 } 311 312 AudioState oldAudioState = mAudioState; 313 saveAudioState(new AudioState(isMuted, route, supportedRouteMask)); 314 if (!force && Objects.equals(oldAudioState, mAudioState)) { 315 return; 316 } 317 Log.i(this, "changing audio state from %s to %s", oldAudioState, mAudioState); 318 319 // Mute. 320 if (mAudioState.isMuted() != mAudioManager.isMicrophoneMute()) { 321 Log.i(this, "changing microphone mute state to: %b", mAudioState.isMuted()); 322 mAudioManager.setMicrophoneMute(mAudioState.isMuted()); 323 } 324 325 // Audio route. 326 if (mAudioState.getRoute() == AudioState.ROUTE_BLUETOOTH) { 327 turnOnSpeaker(false); 328 turnOnBluetooth(true); 329 } else if (mAudioState.getRoute() == AudioState.ROUTE_SPEAKER) { 330 turnOnBluetooth(false); 331 turnOnSpeaker(true); 332 } else if (mAudioState.getRoute() == AudioState.ROUTE_EARPIECE || 333 mAudioState.getRoute() == AudioState.ROUTE_WIRED_HEADSET) { 334 turnOnBluetooth(false); 335 turnOnSpeaker(false); 336 } 337 338 if (!oldAudioState.equals(mAudioState)) { 339 CallsManager.getInstance().onAudioStateChanged(oldAudioState, mAudioState); 340 updateAudioForForegroundCall(); 341 } 342 } 343 344 private void turnOnSpeaker(boolean on) { 345 // Wired headset and earpiece work the same way 346 if (mAudioManager.isSpeakerphoneOn() != on) { 347 Log.i(this, "turning speaker phone %s", on); 348 mAudioManager.setSpeakerphoneOn(on); 349 } 350 } 351 352 private void turnOnBluetooth(boolean on) { 353 if (mBluetoothManager.isBluetoothAvailable()) { 354 boolean isAlreadyOn = mBluetoothManager.isBluetoothAudioConnectedOrPending(); 355 if (on != isAlreadyOn) { 356 Log.i(this, "connecting bluetooth %s", on); 357 if (on) { 358 mBluetoothManager.connectBluetoothAudio(); 359 } else { 360 mBluetoothManager.disconnectBluetoothAudio(); 361 } 362 } 363 } 364 } 365 366 private void updateAudioStreamAndMode() { 367 Log.i(this, "updateAudioStreamAndMode, mIsRinging: %b, mIsTonePlaying: %b", mIsRinging, 368 mIsTonePlaying); 369 if (mIsRinging) { 370 requestAudioFocusAndSetMode(AudioManager.STREAM_RING, AudioManager.MODE_RINGTONE); 371 } else { 372 Call foregroundCall = getForegroundCall(); 373 Call waitingForAccountSelectionCall = 374 CallsManager.getInstance().getFirstCallWithState(CallState.PRE_DIAL_WAIT); 375 Call call = CallsManager.getInstance().getForegroundCall(); 376 if (foregroundCall == null && call != null && call == mCallToSpeedUpMTAudio) { 377 requestAudioFocusAndSetMode(AudioManager.STREAM_VOICE_CALL, 378 AudioManager.MODE_IN_CALL); 379 } else if (foregroundCall != null && waitingForAccountSelectionCall == null) { 380 // In the case where there is a call that is waiting for account selection, 381 // this will fall back to abandonAudioFocus() below, which temporarily exits 382 // the in-call audio mode. This is to allow TalkBack to speak the "Call with" 383 // dialog information at media volume as opposed to through the earpiece. 384 // Once exiting the "Call with" dialog, the audio focus will return to an in-call 385 // audio mode when this method (updateAudioStreamAndMode) is called again. 386 int mode = foregroundCall.getIsVoipAudioMode() ? 387 AudioManager.MODE_IN_COMMUNICATION : AudioManager.MODE_IN_CALL; 388 requestAudioFocusAndSetMode(AudioManager.STREAM_VOICE_CALL, mode); 389 } else if (mIsTonePlaying) { 390 // There is no call, however, we are still playing a tone, so keep focus. 391 // Since there is no call from which to determine the mode, use the most 392 // recently used mode instead. 393 requestAudioFocusAndSetMode( 394 AudioManager.STREAM_VOICE_CALL, mMostRecentlyUsedMode); 395 } else if (!hasRingingForegroundCall()) { 396 abandonAudioFocus(); 397 } else { 398 // mIsRinging is false, but there is a foreground ringing call present. Don't 399 // abandon audio focus immediately to prevent audio focus from getting lost between 400 // the time it takes for the foreground call to transition from RINGING to ACTIVE/ 401 // DISCONNECTED. When the call eventually transitions to the next state, audio 402 // focus will be correctly abandoned by the if clause above. 403 } 404 } 405 } 406 407 private void requestAudioFocusAndSetMode(int stream, int mode) { 408 Log.i(this, "requestAudioFocusAndSetMode, stream: %d -> %d, mode: %d", 409 mAudioFocusStreamType, stream, mode); 410 Preconditions.checkState(stream != STREAM_NONE); 411 412 // Even if we already have focus, if the stream is different we update audio manager to give 413 // it a hint about the purpose of our focus. 414 if (mAudioFocusStreamType != stream) { 415 Log.v(this, "requesting audio focus for stream: %d", stream); 416 mAudioManager.requestAudioFocusForCall(stream, 417 AudioManager.AUDIOFOCUS_GAIN_TRANSIENT); 418 } 419 mAudioFocusStreamType = stream; 420 421 setMode(mode); 422 } 423 424 private void abandonAudioFocus() { 425 if (hasFocus()) { 426 setMode(AudioManager.MODE_NORMAL); 427 Log.v(this, "abandoning audio focus"); 428 mAudioManager.abandonAudioFocusForCall(); 429 mAudioFocusStreamType = STREAM_NONE; 430 mCallToSpeedUpMTAudio = null; 431 } 432 } 433 434 /** 435 * Sets the audio mode. 436 * 437 * @param newMode Mode constant from AudioManager.MODE_*. 438 */ 439 private void setMode(int newMode) { 440 Preconditions.checkState(hasFocus()); 441 int oldMode = mAudioManager.getMode(); 442 Log.v(this, "Request to change audio mode from %d to %d", oldMode, newMode); 443 444 if (oldMode != newMode) { 445 if (oldMode == AudioManager.MODE_IN_CALL && newMode == AudioManager.MODE_RINGTONE) { 446 Log.i(this, "Transition from IN_CALL -> RINGTONE. Resetting to NORMAL first."); 447 mAudioManager.setMode(AudioManager.MODE_NORMAL); 448 } 449 mAudioManager.setMode(newMode); 450 mMostRecentlyUsedMode = newMode; 451 } 452 } 453 454 private int selectWiredOrEarpiece(int route, int supportedRouteMask) { 455 // Since they are mutually exclusive and one is ALWAYS valid, we allow a special input of 456 // ROUTE_WIRED_OR_EARPIECE so that callers dont have to make a call to check which is 457 // supported before calling setAudioRoute. 458 if (route == AudioState.ROUTE_WIRED_OR_EARPIECE) { 459 route = AudioState.ROUTE_WIRED_OR_EARPIECE & supportedRouteMask; 460 if (route == 0) { 461 Log.wtf(this, "One of wired headset or earpiece should always be valid."); 462 // assume earpiece in this case. 463 route = AudioState.ROUTE_EARPIECE; 464 } 465 } 466 return route; 467 } 468 469 private int calculateSupportedRoutes() { 470 int routeMask = AudioState.ROUTE_SPEAKER; 471 472 if (mWiredHeadsetManager.isPluggedIn()) { 473 routeMask |= AudioState.ROUTE_WIRED_HEADSET; 474 } else { 475 routeMask |= AudioState.ROUTE_EARPIECE; 476 } 477 478 if (mBluetoothManager.isBluetoothAvailable()) { 479 routeMask |= AudioState.ROUTE_BLUETOOTH; 480 } 481 482 return routeMask; 483 } 484 485 private AudioState getInitialAudioState(Call call) { 486 int supportedRouteMask = calculateSupportedRoutes(); 487 int route = selectWiredOrEarpiece( 488 AudioState.ROUTE_WIRED_OR_EARPIECE, supportedRouteMask); 489 490 // We want the UI to indicate that "bluetooth is in use" in two slightly different cases: 491 // (a) The obvious case: if a bluetooth headset is currently in use for an ongoing call. 492 // (b) The not-so-obvious case: if an incoming call is ringing, and we expect that audio 493 // *will* be routed to a bluetooth headset once the call is answered. In this case, just 494 // check if the headset is available. Note this only applies when we are dealing with 495 // the first call. 496 if (call != null && mBluetoothManager.isBluetoothAvailable()) { 497 switch(call.getState()) { 498 case CallState.ACTIVE: 499 case CallState.ON_HOLD: 500 case CallState.DIALING: 501 case CallState.CONNECTING: 502 case CallState.RINGING: 503 route = AudioState.ROUTE_BLUETOOTH; 504 break; 505 default: 506 break; 507 } 508 } 509 510 return new AudioState(false, route, supportedRouteMask); 511 } 512 513 private void setInitialAudioState(Call call, boolean force) { 514 AudioState audioState = getInitialAudioState(call); 515 Log.v(this, "setInitialAudioState %s, %s", audioState, call); 516 setSystemAudioState( 517 force, audioState.isMuted(), audioState.getRoute(), 518 audioState.getSupportedRouteMask()); 519 } 520 521 private void updateAudioForForegroundCall() { 522 Call call = CallsManager.getInstance().getForegroundCall(); 523 if (call != null && call.getConnectionService() != null) { 524 call.getConnectionService().onAudioStateChanged(call, mAudioState); 525 } 526 } 527 528 /** 529 * Returns the current foreground call in order to properly set the audio mode. 530 */ 531 private Call getForegroundCall() { 532 Call call = CallsManager.getInstance().getForegroundCall(); 533 534 // We ignore any foreground call that is in the ringing state because we deal with ringing 535 // calls exclusively through the mIsRinging variable set by {@link Ringer}. 536 if (call != null && call.getState() == CallState.RINGING) { 537 return null; 538 } 539 540 return call; 541 } 542 543 private boolean hasRingingForegroundCall() { 544 Call call = CallsManager.getInstance().getForegroundCall(); 545 return call != null && call.getState() == CallState.RINGING; 546 } 547 548 private boolean hasFocus() { 549 return mAudioFocusStreamType != STREAM_NONE; 550 } 551 552 /** 553 * Dumps the state of the {@link CallAudioManager}. 554 * 555 * @param pw The {@code IndentingPrintWriter} to write the state to. 556 */ 557 public void dump(IndentingPrintWriter pw) { 558 pw.println("mAudioState: " + mAudioState); 559 pw.println("mBluetoothManager:"); 560 pw.increaseIndent(); 561 mBluetoothManager.dump(pw); 562 pw.decreaseIndent(); 563 if (mWiredHeadsetManager != null) { 564 pw.println("mWiredHeadsetManager:"); 565 pw.increaseIndent(); 566 mWiredHeadsetManager.dump(pw); 567 pw.decreaseIndent(); 568 } else { 569 pw.println("mWiredHeadsetManager: null"); 570 } 571 pw.println("mAudioFocusStreamType: " + mAudioFocusStreamType); 572 pw.println("mIsRinging: " + mIsRinging); 573 pw.println("mIsTonePlaying: " + mIsTonePlaying); 574 pw.println("mWasSpeakerOn: " + mWasSpeakerOn); 575 pw.println("mMostRecentlyUsedMode: " + mMostRecentlyUsedMode); 576 } 577 } 578