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