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