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