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