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