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