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.os.Handler; 22 import android.os.Looper; 23 import android.os.Message; 24 import android.telecom.CallAudioState; 25 26 import com.android.internal.util.IndentingPrintWriter; 27 import com.android.internal.util.Preconditions; 28 29 import java.util.Objects; 30 31 /** 32 * This class manages audio modes, streams and other properties. 33 */ 34 final class CallAudioManager extends CallsManagerListenerBase 35 implements WiredHeadsetManager.Listener, DockManager.Listener { 36 private static final int STREAM_NONE = -1; 37 38 private static final String STREAM_DESCRIPTION_NONE = "STEAM_NONE"; 39 private static final String STREAM_DESCRIPTION_ALARM = "STEAM_ALARM"; 40 private static final String STREAM_DESCRIPTION_BLUETOOTH_SCO = "STREAM_BLUETOOTH_SCO"; 41 private static final String STREAM_DESCRIPTION_DTMF = "STREAM_DTMF"; 42 private static final String STREAM_DESCRIPTION_MUSIC = "STREAM_MUSIC"; 43 private static final String STREAM_DESCRIPTION_NOTIFICATION = "STREAM_NOTIFICATION"; 44 private static final String STREAM_DESCRIPTION_RING = "STREAM_RING"; 45 private static final String STREAM_DESCRIPTION_SYSTEM = "STREAM_SYSTEM"; 46 private static final String STREAM_DESCRIPTION_VOICE_CALL = "STREAM_VOICE_CALL"; 47 48 private static final String MODE_DESCRIPTION_INVALID = "MODE_INVALID"; 49 private static final String MODE_DESCRIPTION_CURRENT = "MODE_CURRENT"; 50 private static final String MODE_DESCRIPTION_NORMAL = "MODE_NORMAL"; 51 private static final String MODE_DESCRIPTION_RINGTONE = "MODE_RINGTONE"; 52 private static final String MODE_DESCRIPTION_IN_CALL = "MODE_IN_CALL"; 53 private static final String MODE_DESCRIPTION_IN_COMMUNICATION = "MODE_IN_COMMUNICATION"; 54 55 private static final int MSG_AUDIO_MANAGER_INITIALIZE = 0; 56 private static final int MSG_AUDIO_MANAGER_TURN_ON_SPEAKER = 1; 57 private static final int MSG_AUDIO_MANAGER_ABANDON_AUDIO_FOCUS_FOR_CALL = 2; 58 private static final int MSG_AUDIO_MANAGER_SET_MICROPHONE_MUTE = 3; 59 private static final int MSG_AUDIO_MANAGER_REQUEST_AUDIO_FOCUS_FOR_CALL = 4; 60 private static final int MSG_AUDIO_MANAGER_SET_MODE = 5; 61 62 private final Handler mAudioManagerHandler = new Handler(Looper.getMainLooper()) { 63 64 private AudioManager mAudioManager; 65 66 @Override 67 public void handleMessage(Message msg) { 68 switch (msg.what) { 69 case MSG_AUDIO_MANAGER_INITIALIZE: { 70 mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); 71 break; 72 } 73 case MSG_AUDIO_MANAGER_TURN_ON_SPEAKER: { 74 boolean on = (msg.arg1 != 0); 75 // Wired headset and earpiece work the same way 76 if (mAudioManager.isSpeakerphoneOn() != on) { 77 Log.i(this, "turning speaker phone %s", on); 78 mAudioManager.setSpeakerphoneOn(on); 79 } 80 break; 81 } 82 case MSG_AUDIO_MANAGER_ABANDON_AUDIO_FOCUS_FOR_CALL: { 83 mAudioManager.abandonAudioFocusForCall(); 84 break; 85 } 86 case MSG_AUDIO_MANAGER_SET_MICROPHONE_MUTE: { 87 boolean mute = (msg.arg1 != 0); 88 if (mute != mAudioManager.isMicrophoneMute()) { 89 Log.i(this, "changing microphone mute state to: %b", mute); 90 mAudioManager.setMicrophoneMute(mute); 91 } 92 break; 93 } 94 case MSG_AUDIO_MANAGER_REQUEST_AUDIO_FOCUS_FOR_CALL: { 95 int stream = msg.arg1; 96 mAudioManager.requestAudioFocusForCall( 97 stream, 98 AudioManager.AUDIOFOCUS_GAIN_TRANSIENT); 99 break; 100 } 101 case MSG_AUDIO_MANAGER_SET_MODE: { 102 int newMode = msg.arg1; 103 int oldMode = mAudioManager.getMode(); 104 Log.v(this, "Request to change audio mode from %s to %s", modeToString(oldMode), 105 modeToString(newMode)); 106 107 if (oldMode != newMode) { 108 if (oldMode == AudioManager.MODE_IN_CALL && 109 newMode == AudioManager.MODE_RINGTONE) { 110 Log.i(this, "Transition from IN_CALL -> RINGTONE." 111 + " Resetting to NORMAL first."); 112 mAudioManager.setMode(AudioManager.MODE_NORMAL); 113 } 114 mAudioManager.setMode(newMode); 115 synchronized (mLock) { 116 mMostRecentlyUsedMode = newMode; 117 } 118 } 119 break; 120 } 121 default: 122 break; 123 } 124 } 125 }; 126 127 private final Context mContext; 128 private final TelecomSystem.SyncRoot mLock; 129 private final StatusBarNotifier mStatusBarNotifier; 130 private final BluetoothManager mBluetoothManager; 131 private final WiredHeadsetManager mWiredHeadsetManager; 132 private final DockManager mDockManager; 133 private final CallsManager mCallsManager; 134 135 private CallAudioState mCallAudioState; 136 private int mAudioFocusStreamType; 137 private boolean mIsRinging; 138 private boolean mIsTonePlaying; 139 private boolean mWasSpeakerOn; 140 private int mMostRecentlyUsedMode = AudioManager.MODE_IN_CALL; 141 private Call mCallToSpeedUpMTAudio = null; 142 143 CallAudioManager( 144 Context context, 145 TelecomSystem.SyncRoot lock, 146 StatusBarNotifier statusBarNotifier, 147 WiredHeadsetManager wiredHeadsetManager, 148 DockManager dockManager, 149 CallsManager callsManager) { 150 mContext = context; 151 mLock = lock; 152 mAudioManagerHandler.obtainMessage(MSG_AUDIO_MANAGER_INITIALIZE, 0, 0).sendToTarget(); 153 mStatusBarNotifier = statusBarNotifier; 154 mBluetoothManager = new BluetoothManager(context, this); 155 mWiredHeadsetManager = wiredHeadsetManager; 156 mCallsManager = callsManager; 157 158 mWiredHeadsetManager.addListener(this); 159 mDockManager = dockManager; 160 mDockManager.addListener(this); 161 162 saveAudioState(getInitialAudioState(null)); 163 mAudioFocusStreamType = STREAM_NONE; 164 } 165 166 CallAudioState getCallAudioState() { 167 return mCallAudioState; 168 } 169 170 @Override 171 public void onCallAdded(Call call) { 172 Log.v(this, "onCallAdded"); 173 onCallUpdated(call); 174 175 if (hasFocus() && getForegroundCall() == call) { 176 if (!call.isIncoming()) { 177 // Unmute new outgoing call. 178 setSystemAudioState(false, mCallAudioState.getRoute(), 179 mCallAudioState.getSupportedRouteMask()); 180 } 181 } 182 } 183 184 @Override 185 public void onCallRemoved(Call call) { 186 Log.v(this, "onCallRemoved"); 187 // If we didn't already have focus, there's nothing to do. 188 if (hasFocus()) { 189 if (mCallsManager.getCalls().isEmpty()) { 190 Log.v(this, "all calls removed, resetting system audio to default state"); 191 setInitialAudioState(null, false /* force */); 192 mWasSpeakerOn = false; 193 } 194 updateAudioStreamAndMode(call); 195 } 196 } 197 198 @Override 199 public void onCallStateChanged(Call call, int oldState, int newState) { 200 Log.v(this, "onCallStateChanged : oldState = %d, newState = %d", oldState, newState); 201 onCallUpdated(call); 202 } 203 204 @Override 205 public void onIncomingCallAnswered(Call call) { 206 Log.v(this, "onIncomingCallAnswered"); 207 int route = mCallAudioState.getRoute(); 208 209 // We do two things: 210 // (1) If this is the first call, then we can to turn on bluetooth if available. 211 // (2) Unmute the audio for the new incoming call. 212 boolean isOnlyCall = mCallsManager.getCalls().size() == 1; 213 if (isOnlyCall && mBluetoothManager.isBluetoothAvailable()) { 214 mBluetoothManager.connectBluetoothAudio(); 215 route = CallAudioState.ROUTE_BLUETOOTH; 216 } 217 218 setSystemAudioState(false /* isMute */, route, mCallAudioState.getSupportedRouteMask()); 219 220 if (call.can(android.telecom.Call.Details.CAPABILITY_SPEED_UP_MT_AUDIO)) { 221 Log.v(this, "Speed up audio setup for IMS MT call."); 222 mCallToSpeedUpMTAudio = call; 223 updateAudioStreamAndMode(call); 224 } 225 } 226 227 @Override 228 public void onForegroundCallChanged(Call oldForegroundCall, Call newForegroundCall) { 229 onCallUpdated(newForegroundCall); 230 // Ensure that the foreground call knows about the latest audio state. 231 updateAudioForForegroundCall(); 232 } 233 234 @Override 235 public void onIsVoipAudioModeChanged(Call call) { 236 updateAudioStreamAndMode(call); 237 } 238 239 /** 240 * Updates the audio route when the headset plugged in state changes. For example, if audio is 241 * being routed over speakerphone and a headset is plugged in then switch to wired headset. 242 */ 243 @Override 244 public void onWiredHeadsetPluggedInChanged(boolean oldIsPluggedIn, boolean newIsPluggedIn) { 245 // This can happen even when there are no calls and we don't have focus. 246 if (!hasFocus()) { 247 return; 248 } 249 250 boolean isCurrentlyWiredHeadset = mCallAudioState.getRoute() 251 == CallAudioState.ROUTE_WIRED_HEADSET; 252 253 int newRoute = mCallAudioState.getRoute(); // start out with existing route 254 if (newIsPluggedIn) { 255 newRoute = CallAudioState.ROUTE_WIRED_HEADSET; 256 } else if (isCurrentlyWiredHeadset) { 257 Call call = getForegroundCall(); 258 boolean hasLiveCall = call != null && call.isAlive(); 259 260 if (hasLiveCall) { 261 // In order of preference when a wireless headset is unplugged. 262 if (mWasSpeakerOn) { 263 newRoute = CallAudioState.ROUTE_SPEAKER; 264 } else { 265 newRoute = CallAudioState.ROUTE_EARPIECE; 266 } 267 268 // We don't automatically connect to bluetooth when user unplugs their wired headset 269 // and they were previously using the wired. Wired and earpiece are effectively the 270 // same choice in that they replace each other as an option when wired headsets 271 // are plugged in and out. This means that keeping it earpiece is a bit more 272 // consistent with the status quo. Bluetooth also has more danger associated with 273 // choosing it in the wrong curcumstance because bluetooth devices can be 274 // semi-public (like in a very-occupied car) where earpiece doesn't carry that risk. 275 } 276 } 277 278 // We need to call this every time even if we do not change the route because the supported 279 // routes changed either to include or not include WIRED_HEADSET. 280 setSystemAudioState(mCallAudioState.isMuted(), newRoute, calculateSupportedRoutes()); 281 } 282 283 @Override 284 public void onDockChanged(boolean isDocked) { 285 // This can happen even when there are no calls and we don't have focus. 286 if (!hasFocus()) { 287 return; 288 } 289 290 if (isDocked) { 291 // Device just docked, turn to speakerphone. Only do so if the route is currently 292 // earpiece so that we dont switch out of a BT headset or a wired headset. 293 if (mCallAudioState.getRoute() == CallAudioState.ROUTE_EARPIECE) { 294 setAudioRoute(CallAudioState.ROUTE_SPEAKER); 295 } 296 } else { 297 // Device just undocked, remove from speakerphone if possible. 298 if (mCallAudioState.getRoute() == CallAudioState.ROUTE_SPEAKER) { 299 setAudioRoute(CallAudioState.ROUTE_WIRED_OR_EARPIECE); 300 } 301 } 302 } 303 304 void toggleMute() { 305 mute(!mCallAudioState.isMuted()); 306 } 307 308 void mute(boolean shouldMute) { 309 if (!hasFocus()) { 310 return; 311 } 312 313 Log.v(this, "mute, shouldMute: %b", shouldMute); 314 315 // Don't mute if there are any emergency calls. 316 if (mCallsManager.hasEmergencyCall()) { 317 shouldMute = false; 318 Log.v(this, "ignoring mute for emergency call"); 319 } 320 321 if (mCallAudioState.isMuted() != shouldMute) { 322 // We user CallsManager's foreground call so that we dont ignore ringing calls 323 // for logging purposes 324 Log.event(mCallsManager.getForegroundCall(), Log.Events.MUTE, 325 shouldMute ? "on" : "off"); 326 327 setSystemAudioState(shouldMute, mCallAudioState.getRoute(), 328 mCallAudioState.getSupportedRouteMask()); 329 } 330 } 331 332 /** 333 * Changed the audio route, for example from earpiece to speaker phone. 334 * 335 * @param route The new audio route to use. See {@link CallAudioState}. 336 */ 337 void setAudioRoute(int route) { 338 // This can happen even when there are no calls and we don't have focus. 339 if (!hasFocus()) { 340 return; 341 } 342 343 Log.v(this, "setAudioRoute, route: %s", CallAudioState.audioRouteToString(route)); 344 345 // Change ROUTE_WIRED_OR_EARPIECE to a single entry. 346 int newRoute = selectWiredOrEarpiece(route, mCallAudioState.getSupportedRouteMask()); 347 348 // If route is unsupported, do nothing. 349 if ((mCallAudioState.getSupportedRouteMask() | newRoute) == 0) { 350 Log.wtf(this, "Asking to set to a route that is unsupported: %d", newRoute); 351 return; 352 } 353 354 if (mCallAudioState.getRoute() != newRoute) { 355 // Remember the new speaker state so it can be restored when the user plugs and unplugs 356 // a headset. 357 mWasSpeakerOn = newRoute == CallAudioState.ROUTE_SPEAKER; 358 setSystemAudioState(mCallAudioState.isMuted(), newRoute, 359 mCallAudioState.getSupportedRouteMask()); 360 } 361 } 362 363 /** 364 * Sets the audio stream and mode based on whether a call is ringing. 365 * 366 * @param call The call which changed ringing state. 367 * @param isRinging {@code true} if the call is ringing, {@code false} otherwise. 368 */ 369 void setIsRinging(Call call, boolean isRinging) { 370 if (mIsRinging != isRinging) { 371 Log.i(this, "setIsRinging %b -> %b (call = %s)", mIsRinging, isRinging, call); 372 mIsRinging = isRinging; 373 updateAudioStreamAndMode(call); 374 } 375 } 376 377 /** 378 * Sets the tone playing status. Some tones can play even when there are no live calls and this 379 * status indicates that we should keep audio focus even for tones that play beyond the life of 380 * calls. 381 * 382 * @param isPlayingNew The status to set. 383 */ 384 void setIsTonePlaying(boolean isPlayingNew) { 385 if (mIsTonePlaying != isPlayingNew) { 386 Log.v(this, "mIsTonePlaying %b -> %b.", mIsTonePlaying, isPlayingNew); 387 mIsTonePlaying = isPlayingNew; 388 updateAudioStreamAndMode(); 389 } 390 } 391 392 /** 393 * Updates the audio routing according to the bluetooth state. 394 */ 395 void onBluetoothStateChange(BluetoothManager bluetoothManager) { 396 // This can happen even when there are no calls and we don't have focus. 397 if (!hasFocus()) { 398 return; 399 } 400 401 int supportedRoutes = calculateSupportedRoutes(); 402 int newRoute = mCallAudioState.getRoute(); 403 if (bluetoothManager.isBluetoothAudioConnectedOrPending()) { 404 newRoute = CallAudioState.ROUTE_BLUETOOTH; 405 } else if (mCallAudioState.getRoute() == CallAudioState.ROUTE_BLUETOOTH) { 406 newRoute = selectWiredOrEarpiece(CallAudioState.ROUTE_WIRED_OR_EARPIECE, 407 supportedRoutes); 408 // Do not switch to speaker when bluetooth disconnects. 409 mWasSpeakerOn = false; 410 } 411 412 setSystemAudioState(mCallAudioState.isMuted(), newRoute, supportedRoutes); 413 } 414 415 boolean isBluetoothAudioOn() { 416 return mBluetoothManager.isBluetoothAudioConnected(); 417 } 418 419 boolean isBluetoothDeviceAvailable() { 420 return mBluetoothManager.isBluetoothAvailable(); 421 } 422 423 private void saveAudioState(CallAudioState callAudioState) { 424 mCallAudioState = callAudioState; 425 mStatusBarNotifier.notifyMute(mCallAudioState.isMuted()); 426 mStatusBarNotifier.notifySpeakerphone(mCallAudioState.getRoute() 427 == CallAudioState.ROUTE_SPEAKER); 428 } 429 430 private void onCallUpdated(Call call) { 431 updateAudioStreamAndMode(call); 432 if (call != null && call.getState() == CallState.ACTIVE && 433 call == mCallToSpeedUpMTAudio) { 434 mCallToSpeedUpMTAudio = null; 435 } 436 } 437 438 private void setSystemAudioState(boolean isMuted, int route, int supportedRouteMask) { 439 setSystemAudioState(false /* force */, isMuted, route, supportedRouteMask); 440 } 441 442 private void setSystemAudioState( 443 boolean force, boolean isMuted, int route, int supportedRouteMask) { 444 if (!hasFocus()) { 445 return; 446 } 447 448 CallAudioState oldAudioState = mCallAudioState; 449 saveAudioState(new CallAudioState(isMuted, route, supportedRouteMask)); 450 if (!force && Objects.equals(oldAudioState, mCallAudioState)) { 451 return; 452 } 453 454 Log.i(this, "setSystemAudioState: changing from %s to %s", oldAudioState, mCallAudioState); 455 Log.event(mCallsManager.getForegroundCall(), Log.Events.AUDIO_ROUTE, 456 CallAudioState.audioRouteToString(mCallAudioState.getRoute())); 457 458 mAudioManagerHandler.obtainMessage( 459 MSG_AUDIO_MANAGER_SET_MICROPHONE_MUTE, 460 mCallAudioState.isMuted() ? 1 : 0, 461 0) 462 .sendToTarget(); 463 464 // Audio route. 465 if (mCallAudioState.getRoute() == CallAudioState.ROUTE_BLUETOOTH) { 466 turnOnSpeaker(false); 467 turnOnBluetooth(true); 468 } else if (mCallAudioState.getRoute() == CallAudioState.ROUTE_SPEAKER) { 469 turnOnBluetooth(false); 470 turnOnSpeaker(true); 471 } else if (mCallAudioState.getRoute() == CallAudioState.ROUTE_EARPIECE || 472 mCallAudioState.getRoute() == CallAudioState.ROUTE_WIRED_HEADSET) { 473 turnOnBluetooth(false); 474 turnOnSpeaker(false); 475 } 476 477 if (!oldAudioState.equals(mCallAudioState)) { 478 mCallsManager.onCallAudioStateChanged(oldAudioState, mCallAudioState); 479 updateAudioForForegroundCall(); 480 } 481 } 482 483 private void turnOnSpeaker(boolean on) { 484 mAudioManagerHandler.obtainMessage(MSG_AUDIO_MANAGER_TURN_ON_SPEAKER, on ? 1 : 0, 0) 485 .sendToTarget(); 486 } 487 488 private void turnOnBluetooth(boolean on) { 489 if (mBluetoothManager.isBluetoothAvailable()) { 490 boolean isAlreadyOn = mBluetoothManager.isBluetoothAudioConnectedOrPending(); 491 if (on != isAlreadyOn) { 492 Log.i(this, "connecting bluetooth %s", on); 493 if (on) { 494 mBluetoothManager.connectBluetoothAudio(); 495 } else { 496 mBluetoothManager.disconnectBluetoothAudio(); 497 } 498 } 499 } 500 } 501 502 private void updateAudioStreamAndMode() { 503 updateAudioStreamAndMode(null /* call */); 504 } 505 506 private void updateAudioStreamAndMode(Call callToUpdate) { 507 Log.i(this, "updateAudioStreamAndMode : mIsRinging: %b, mIsTonePlaying: %b, call: %s", 508 mIsRinging, mIsTonePlaying, callToUpdate); 509 510 boolean wasVoiceCall = mAudioFocusStreamType == AudioManager.STREAM_VOICE_CALL; 511 if (mIsRinging) { 512 Log.i(this, "updateAudioStreamAndMode : ringing"); 513 requestAudioFocusAndSetMode(AudioManager.STREAM_RING, AudioManager.MODE_RINGTONE); 514 } else { 515 Call foregroundCall = getForegroundCall(); 516 Call waitingForAccountSelectionCall = mCallsManager 517 .getFirstCallWithState(CallState.SELECT_PHONE_ACCOUNT); 518 Call call = mCallsManager.getForegroundCall(); 519 if (foregroundCall == null && call != null && call == mCallToSpeedUpMTAudio) { 520 Log.v(this, "updateAudioStreamAndMode : no foreground, speeding up MT audio."); 521 requestAudioFocusAndSetMode(AudioManager.STREAM_VOICE_CALL, 522 AudioManager.MODE_IN_CALL); 523 } else if (foregroundCall != null && waitingForAccountSelectionCall == null) { 524 // In the case where there is a call that is waiting for account selection, 525 // this will fall back to abandonAudioFocus() below, which temporarily exits 526 // the in-call audio mode. This is to allow TalkBack to speak the "Call with" 527 // dialog information at media volume as opposed to through the earpiece. 528 // Once exiting the "Call with" dialog, the audio focus will return to an in-call 529 // audio mode when this method (updateAudioStreamAndMode) is called again. 530 int mode = foregroundCall.getIsVoipAudioMode() ? 531 AudioManager.MODE_IN_COMMUNICATION : AudioManager.MODE_IN_CALL; 532 Log.v(this, "updateAudioStreamAndMode : foreground"); 533 requestAudioFocusAndSetMode(AudioManager.STREAM_VOICE_CALL, mode); 534 } else if (mIsTonePlaying) { 535 // There is no call, however, we are still playing a tone, so keep focus. 536 // Since there is no call from which to determine the mode, use the most 537 // recently used mode instead. 538 Log.v(this, "updateAudioStreamAndMode : tone playing"); 539 requestAudioFocusAndSetMode( 540 AudioManager.STREAM_VOICE_CALL, mMostRecentlyUsedMode); 541 } else if (!hasRingingForegroundCall()) { 542 Log.v(this, "updateAudioStreamAndMode : no ringing call"); 543 abandonAudioFocus(); 544 } else { 545 // mIsRinging is false, but there is a foreground ringing call present. Don't 546 // abandon audio focus immediately to prevent audio focus from getting lost between 547 // the time it takes for the foreground call to transition from RINGING to ACTIVE/ 548 // DISCONNECTED. When the call eventually transitions to the next state, audio 549 // focus will be correctly abandoned by the if clause above. 550 } 551 } 552 553 boolean isVoiceCall = mAudioFocusStreamType == AudioManager.STREAM_VOICE_CALL; 554 555 // If we transition from not a voice call to a voice call, we need to set an initial audio 556 // state for the call. 557 if (!wasVoiceCall && isVoiceCall) { 558 setInitialAudioState(callToUpdate, true /* force */); 559 } 560 } 561 562 private void requestAudioFocusAndSetMode(int stream, int mode) { 563 Log.v(this, "requestAudioFocusAndSetMode : stream: %s -> %s, mode: %s", 564 streamTypeToString(mAudioFocusStreamType), streamTypeToString(stream), 565 modeToString(mode)); 566 Preconditions.checkState(stream != STREAM_NONE); 567 568 // Even if we already have focus, if the stream is different we update audio manager to give 569 // it a hint about the purpose of our focus. 570 if (mAudioFocusStreamType != stream) { 571 Log.i(this, "requestAudioFocusAndSetMode : requesting stream: %s -> %s", 572 streamTypeToString(mAudioFocusStreamType), streamTypeToString(stream)); 573 mAudioManagerHandler.obtainMessage( 574 MSG_AUDIO_MANAGER_REQUEST_AUDIO_FOCUS_FOR_CALL, 575 stream, 576 0) 577 .sendToTarget(); 578 } 579 mAudioFocusStreamType = stream; 580 581 setMode(mode); 582 } 583 584 private void abandonAudioFocus() { 585 if (hasFocus()) { 586 setMode(AudioManager.MODE_NORMAL); 587 Log.v(this, "abandoning audio focus"); 588 mAudioManagerHandler.obtainMessage(MSG_AUDIO_MANAGER_ABANDON_AUDIO_FOCUS_FOR_CALL, 0, 0) 589 .sendToTarget(); 590 mAudioFocusStreamType = STREAM_NONE; 591 mCallToSpeedUpMTAudio = null; 592 } 593 } 594 595 /** 596 * Sets the audio mode. 597 * 598 * @param newMode Mode constant from AudioManager.MODE_*. 599 */ 600 private void setMode(int newMode) { 601 Preconditions.checkState(hasFocus()); 602 mAudioManagerHandler.obtainMessage(MSG_AUDIO_MANAGER_SET_MODE, newMode, 0).sendToTarget(); 603 } 604 605 private int selectWiredOrEarpiece(int route, int supportedRouteMask) { 606 // Since they are mutually exclusive and one is ALWAYS valid, we allow a special input of 607 // ROUTE_WIRED_OR_EARPIECE so that callers dont have to make a call to check which is 608 // supported before calling setAudioRoute. 609 if (route == CallAudioState.ROUTE_WIRED_OR_EARPIECE) { 610 route = CallAudioState.ROUTE_WIRED_OR_EARPIECE & supportedRouteMask; 611 if (route == 0) { 612 Log.wtf(this, "One of wired headset or earpiece should always be valid."); 613 // assume earpiece in this case. 614 route = CallAudioState.ROUTE_EARPIECE; 615 } 616 } 617 return route; 618 } 619 620 private int calculateSupportedRoutes() { 621 int routeMask = CallAudioState.ROUTE_SPEAKER; 622 623 if (mWiredHeadsetManager.isPluggedIn()) { 624 routeMask |= CallAudioState.ROUTE_WIRED_HEADSET; 625 } else { 626 routeMask |= CallAudioState.ROUTE_EARPIECE; 627 } 628 629 if (mBluetoothManager.isBluetoothAvailable()) { 630 routeMask |= CallAudioState.ROUTE_BLUETOOTH; 631 } 632 633 return routeMask; 634 } 635 636 private CallAudioState getInitialAudioState(Call call) { 637 int supportedRouteMask = calculateSupportedRoutes(); 638 int route = selectWiredOrEarpiece( 639 CallAudioState.ROUTE_WIRED_OR_EARPIECE, supportedRouteMask); 640 641 // We want the UI to indicate that "bluetooth is in use" in two slightly different cases: 642 // (a) The obvious case: if a bluetooth headset is currently in use for an ongoing call. 643 // (b) The not-so-obvious case: if an incoming call is ringing, and we expect that audio 644 // *will* be routed to a bluetooth headset once the call is answered. In this case, just 645 // check if the headset is available. Note this only applies when we are dealing with 646 // the first call. 647 if (call != null && mBluetoothManager.isBluetoothAvailable()) { 648 switch(call.getState()) { 649 case CallState.ACTIVE: 650 case CallState.ON_HOLD: 651 case CallState.DIALING: 652 case CallState.CONNECTING: 653 case CallState.RINGING: 654 route = CallAudioState.ROUTE_BLUETOOTH; 655 break; 656 default: 657 break; 658 } 659 } 660 661 return new CallAudioState(false, route, supportedRouteMask); 662 } 663 664 private void setInitialAudioState(Call call, boolean force) { 665 CallAudioState audioState = getInitialAudioState(call); 666 Log.i(this, "setInitialAudioState : audioState = %s, call = %s", audioState, call); 667 setSystemAudioState( 668 force, audioState.isMuted(), audioState.getRoute(), 669 audioState.getSupportedRouteMask()); 670 } 671 672 private void updateAudioForForegroundCall() { 673 Call call = mCallsManager.getForegroundCall(); 674 if (call != null && call.getConnectionService() != null) { 675 call.getConnectionService().onCallAudioStateChanged(call, mCallAudioState); 676 } 677 } 678 679 /** 680 * Returns the current foreground call in order to properly set the audio mode. 681 */ 682 private Call getForegroundCall() { 683 Call call = mCallsManager.getForegroundCall(); 684 685 // We ignore any foreground call that is in the ringing state because we deal with ringing 686 // calls exclusively through the mIsRinging variable set by {@link Ringer}. 687 if (call != null && call.getState() == CallState.RINGING) { 688 return null; 689 } 690 691 return call; 692 } 693 694 private boolean hasRingingForegroundCall() { 695 Call call = mCallsManager.getForegroundCall(); 696 return call != null && call.getState() == CallState.RINGING; 697 } 698 699 private boolean hasFocus() { 700 return mAudioFocusStreamType != STREAM_NONE; 701 } 702 703 /** 704 * Translates an {@link AudioManager} stream type to a human-readable string description. 705 * 706 * @param streamType The stream type. 707 * @return Human readable description. 708 */ 709 private String streamTypeToString(int streamType) { 710 switch (streamType) { 711 case STREAM_NONE: 712 return STREAM_DESCRIPTION_NONE; 713 case AudioManager.STREAM_ALARM: 714 return STREAM_DESCRIPTION_ALARM; 715 case AudioManager.STREAM_BLUETOOTH_SCO: 716 return STREAM_DESCRIPTION_BLUETOOTH_SCO; 717 case AudioManager.STREAM_DTMF: 718 return STREAM_DESCRIPTION_DTMF; 719 case AudioManager.STREAM_MUSIC: 720 return STREAM_DESCRIPTION_MUSIC; 721 case AudioManager.STREAM_NOTIFICATION: 722 return STREAM_DESCRIPTION_NOTIFICATION; 723 case AudioManager.STREAM_RING: 724 return STREAM_DESCRIPTION_RING; 725 case AudioManager.STREAM_SYSTEM: 726 return STREAM_DESCRIPTION_SYSTEM; 727 case AudioManager.STREAM_VOICE_CALL: 728 return STREAM_DESCRIPTION_VOICE_CALL; 729 default: 730 return "STEAM_OTHER_" + streamType; 731 } 732 } 733 734 /** 735 * Translates an {@link AudioManager} mode into a human readable string. 736 * 737 * @param mode The mode. 738 * @return The string. 739 */ 740 private String modeToString(int mode) { 741 switch (mode) { 742 case AudioManager.MODE_INVALID: 743 return MODE_DESCRIPTION_INVALID; 744 case AudioManager.MODE_CURRENT: 745 return MODE_DESCRIPTION_CURRENT; 746 case AudioManager.MODE_NORMAL: 747 return MODE_DESCRIPTION_NORMAL; 748 case AudioManager.MODE_RINGTONE: 749 return MODE_DESCRIPTION_RINGTONE; 750 case AudioManager.MODE_IN_CALL: 751 return MODE_DESCRIPTION_IN_CALL; 752 case AudioManager.MODE_IN_COMMUNICATION: 753 return MODE_DESCRIPTION_IN_COMMUNICATION; 754 default: 755 return "MODE_OTHER_" + mode; 756 } 757 } 758 759 /** 760 * Dumps the state of the {@link CallAudioManager}. 761 * 762 * @param pw The {@code IndentingPrintWriter} to write the state to. 763 */ 764 public void dump(IndentingPrintWriter pw) { 765 pw.println("mAudioState: " + mCallAudioState); 766 pw.println("mBluetoothManager:"); 767 pw.increaseIndent(); 768 mBluetoothManager.dump(pw); 769 pw.decreaseIndent(); 770 if (mWiredHeadsetManager != null) { 771 pw.println("mWiredHeadsetManager:"); 772 pw.increaseIndent(); 773 mWiredHeadsetManager.dump(pw); 774 pw.decreaseIndent(); 775 } else { 776 pw.println("mWiredHeadsetManager: null"); 777 } 778 pw.println("mAudioFocusStreamType: " + streamTypeToString(mAudioFocusStreamType)); 779 pw.println("mIsRinging: " + mIsRinging); 780 pw.println("mIsTonePlaying: " + mIsTonePlaying); 781 pw.println("mWasSpeakerOn: " + mWasSpeakerOn); 782 pw.println("mMostRecentlyUsedMode: " + modeToString(mMostRecentlyUsedMode)); 783 } 784 } 785