1810735e3f0a4fe924a805981d32b6916ec834b38Sailesh Nepal/*
2810735e3f0a4fe924a805981d32b6916ec834b38Sailesh Nepal * Copyright (C) 2014 The Android Open Source Project
3810735e3f0a4fe924a805981d32b6916ec834b38Sailesh Nepal *
4810735e3f0a4fe924a805981d32b6916ec834b38Sailesh Nepal * Licensed under the Apache License, Version 2.0 (the "License");
5810735e3f0a4fe924a805981d32b6916ec834b38Sailesh Nepal * you may not use this file except in compliance with the License.
6810735e3f0a4fe924a805981d32b6916ec834b38Sailesh Nepal * You may obtain a copy of the License at
7810735e3f0a4fe924a805981d32b6916ec834b38Sailesh Nepal *
8810735e3f0a4fe924a805981d32b6916ec834b38Sailesh Nepal *      http://www.apache.org/licenses/LICENSE-2.0
9810735e3f0a4fe924a805981d32b6916ec834b38Sailesh Nepal *
10810735e3f0a4fe924a805981d32b6916ec834b38Sailesh Nepal * Unless required by applicable law or agreed to in writing, software
11810735e3f0a4fe924a805981d32b6916ec834b38Sailesh Nepal * distributed under the License is distributed on an "AS IS" BASIS,
12810735e3f0a4fe924a805981d32b6916ec834b38Sailesh Nepal * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13810735e3f0a4fe924a805981d32b6916ec834b38Sailesh Nepal * See the License for the specific language governing permissions and
14810735e3f0a4fe924a805981d32b6916ec834b38Sailesh Nepal * limitations under the License.
15810735e3f0a4fe924a805981d32b6916ec834b38Sailesh Nepal */
16810735e3f0a4fe924a805981d32b6916ec834b38Sailesh Nepal
177cc70b4f0ad1064a4a0dce6056ad82b205887160Tyler Gunnpackage com.android.server.telecom;
18810735e3f0a4fe924a805981d32b6916ec834b38Sailesh Nepal
19810735e3f0a4fe924a805981d32b6916ec834b38Sailesh Nepalimport android.content.Context;
20810735e3f0a4fe924a805981d32b6916ec834b38Sailesh Nepalimport android.media.AudioManager;
217cc70b4f0ad1064a4a0dce6056ad82b205887160Tyler Gunnimport android.telecom.AudioState;
227cc70b4f0ad1064a4a0dce6056ad82b205887160Tyler Gunnimport android.telecom.CallState;
23810735e3f0a4fe924a805981d32b6916ec834b38Sailesh Nepal
2491d43cf9c985cc5a83795f256ef5c46ebb8fbdc1Tyler Gunnimport com.android.internal.util.Preconditions;
251ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon
2614ff8384ea44488ed6b203ceaac56a6c5294130aSantos Cordonimport java.util.Objects;
2714ff8384ea44488ed6b203ceaac56a6c5294130aSantos Cordon
28810735e3f0a4fe924a805981d32b6916ec834b38Sailesh Nepal/**
29810735e3f0a4fe924a805981d32b6916ec834b38Sailesh Nepal * This class manages audio modes, streams and other properties.
30810735e3f0a4fe924a805981d32b6916ec834b38Sailesh Nepal */
31b88795aadc636d14156791c2fc93af051d7e0d49Sailesh Nepalfinal class CallAudioManager extends CallsManagerListenerBase
32b88795aadc636d14156791c2fc93af051d7e0d49Sailesh Nepal        implements WiredHeadsetManager.Listener {
336aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal    private static final int STREAM_NONE = -1;
341ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon
35deb8c89707c604d4f9f32e476a58bd10a68293ffSantos Cordon    private final StatusBarNotifier mStatusBarNotifier;
366aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal    private final AudioManager mAudioManager;
37c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon    private final BluetoothManager mBluetoothManager;
38b88795aadc636d14156791c2fc93af051d7e0d49Sailesh Nepal    private final WiredHeadsetManager mWiredHeadsetManager;
39deb8c89707c604d4f9f32e476a58bd10a68293ffSantos Cordon
406fb37c87836b5245046bd3b14320823ab839a10cIhab Awad    private AudioState mAudioState;
416aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal    private int mAudioFocusStreamType;
426aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal    private boolean mIsRinging;
43a56f276cafcad80ab3a28996b6a0d72cffa4b2bcSantos Cordon    private boolean mIsTonePlaying;
446aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal    private boolean mWasSpeakerOn;
4514ff8384ea44488ed6b203ceaac56a6c5294130aSantos Cordon    private int mMostRecentlyUsedMode = AudioManager.MODE_IN_CALL;
461ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon
47b88795aadc636d14156791c2fc93af051d7e0d49Sailesh Nepal    CallAudioManager(Context context, StatusBarNotifier statusBarNotifier,
48b88795aadc636d14156791c2fc93af051d7e0d49Sailesh Nepal            WiredHeadsetManager wiredHeadsetManager) {
49deb8c89707c604d4f9f32e476a58bd10a68293ffSantos Cordon        mStatusBarNotifier = statusBarNotifier;
506aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal        mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
51c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon        mBluetoothManager = new BluetoothManager(context, this);
52b88795aadc636d14156791c2fc93af051d7e0d49Sailesh Nepal        mWiredHeadsetManager = wiredHeadsetManager;
53d0a76aa8789effa81bc7ce640e607718e4b722fdSailesh Nepal        mWiredHeadsetManager.addListener(this);
54b88795aadc636d14156791c2fc93af051d7e0d49Sailesh Nepal
55deb8c89707c604d4f9f32e476a58bd10a68293ffSantos Cordon        saveAudioState(getInitialAudioState(null));
566aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal        mAudioFocusStreamType = STREAM_NONE;
576aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal    }
581ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon
596fb37c87836b5245046bd3b14320823ab839a10cIhab Awad    AudioState getAudioState() {
606aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal        return mAudioState;
616aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal    }
621ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon
631ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon    @Override
641ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon    public void onCallAdded(Call call) {
6514ff8384ea44488ed6b203ceaac56a6c5294130aSantos Cordon        onCallUpdated(call);
6614ff8384ea44488ed6b203ceaac56a6c5294130aSantos Cordon
6714ff8384ea44488ed6b203ceaac56a6c5294130aSantos Cordon        if (hasFocus() && getForegroundCall() == call) {
6814ff8384ea44488ed6b203ceaac56a6c5294130aSantos Cordon            if (!call.isIncoming()) {
6914ff8384ea44488ed6b203ceaac56a6c5294130aSantos Cordon                // Unmute new outgoing call.
7014ff8384ea44488ed6b203ceaac56a6c5294130aSantos Cordon                setSystemAudioState(false, mAudioState.route, mAudioState.supportedRouteMask);
7114ff8384ea44488ed6b203ceaac56a6c5294130aSantos Cordon            }
721ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon        }
731ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon    }
741ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon
751ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon    @Override
761ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon    public void onCallRemoved(Call call) {
7714ff8384ea44488ed6b203ceaac56a6c5294130aSantos Cordon        // If we didn't already have focus, there's nothing to do.
7814ff8384ea44488ed6b203ceaac56a6c5294130aSantos Cordon        if (hasFocus()) {
7914ff8384ea44488ed6b203ceaac56a6c5294130aSantos Cordon            if (CallsManager.getInstance().getCalls().isEmpty()) {
8014ff8384ea44488ed6b203ceaac56a6c5294130aSantos Cordon                Log.v(this, "all calls removed, reseting system audio to default state");
817c6a5ec5c039cb3cc2e19024910fbcba0ce6b4e4Santos Cordon                setInitialAudioState(null, false /* force */);
8214ff8384ea44488ed6b203ceaac56a6c5294130aSantos Cordon                mWasSpeakerOn = false;
8314ff8384ea44488ed6b203ceaac56a6c5294130aSantos Cordon            }
8414ff8384ea44488ed6b203ceaac56a6c5294130aSantos Cordon            updateAudioStreamAndMode();
856aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal        }
861ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon    }
871ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon
88810735e3f0a4fe924a805981d32b6916ec834b38Sailesh Nepal    @Override
896fb37c87836b5245046bd3b14320823ab839a10cIhab Awad    public void onCallStateChanged(Call call, int oldState, int newState) {
9014ff8384ea44488ed6b203ceaac56a6c5294130aSantos Cordon        onCallUpdated(call);
911ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon    }
921ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon
931ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon    @Override
941ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon    public void onIncomingCallAnswered(Call call) {
95c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon        int route = mAudioState.route;
96c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon
97c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon        // We do two things:
98c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon        // (1) If this is the first call, then we can to turn on bluetooth if available.
99c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon        // (2) Unmute the audio for the new incoming call.
100c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon        boolean isOnlyCall = CallsManager.getInstance().getCalls().size() == 1;
101c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon        if (isOnlyCall && mBluetoothManager.isBluetoothAvailable()) {
102c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon            mBluetoothManager.connectBluetoothAudio();
1036fb37c87836b5245046bd3b14320823ab839a10cIhab Awad            route = AudioState.ROUTE_BLUETOOTH;
104c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon        }
105c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon
106c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon        setSystemAudioState(false /* isMute */, route, mAudioState.supportedRouteMask);
1071ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon    }
1081ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon
1091ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon    @Override
1106aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal    public void onForegroundCallChanged(Call oldForegroundCall, Call newForegroundCall) {
11114ff8384ea44488ed6b203ceaac56a6c5294130aSantos Cordon        onCallUpdated(newForegroundCall);
1126aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal        // Ensure that the foreground call knows about the latest audio state.
1136aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal        updateAudioForForegroundCall();
1141ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon    }
1151ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon
1167e66957928c5c23a1028c8e2a2d7cf359cbfa44eSailesh Nepal    @Override
1175be64bc46c23b614d5452ca398a6bb7a512f1887Andrew Lee    public void onIsVoipAudioModeChanged(Call call) {
1187e66957928c5c23a1028c8e2a2d7cf359cbfa44eSailesh Nepal        updateAudioStreamAndMode();
1197e66957928c5c23a1028c8e2a2d7cf359cbfa44eSailesh Nepal    }
1207e66957928c5c23a1028c8e2a2d7cf359cbfa44eSailesh Nepal
121b88795aadc636d14156791c2fc93af051d7e0d49Sailesh Nepal    /**
122b88795aadc636d14156791c2fc93af051d7e0d49Sailesh Nepal      * Updates the audio route when the headset plugged in state changes. For example, if audio is
123b88795aadc636d14156791c2fc93af051d7e0d49Sailesh Nepal      * being routed over speakerphone and a headset is plugged in then switch to wired headset.
124b88795aadc636d14156791c2fc93af051d7e0d49Sailesh Nepal      */
125b88795aadc636d14156791c2fc93af051d7e0d49Sailesh Nepal    @Override
126b88795aadc636d14156791c2fc93af051d7e0d49Sailesh Nepal    public void onWiredHeadsetPluggedInChanged(boolean oldIsPluggedIn, boolean newIsPluggedIn) {
12714ff8384ea44488ed6b203ceaac56a6c5294130aSantos Cordon        // This can happen even when there are no calls and we don't have focus.
12814ff8384ea44488ed6b203ceaac56a6c5294130aSantos Cordon        if (!hasFocus()) {
12914ff8384ea44488ed6b203ceaac56a6c5294130aSantos Cordon            return;
13014ff8384ea44488ed6b203ceaac56a6c5294130aSantos Cordon        }
13114ff8384ea44488ed6b203ceaac56a6c5294130aSantos Cordon
1326fb37c87836b5245046bd3b14320823ab839a10cIhab Awad        int newRoute = AudioState.ROUTE_EARPIECE;
133b88795aadc636d14156791c2fc93af051d7e0d49Sailesh Nepal        if (newIsPluggedIn) {
1346fb37c87836b5245046bd3b14320823ab839a10cIhab Awad            newRoute = AudioState.ROUTE_WIRED_HEADSET;
135b88795aadc636d14156791c2fc93af051d7e0d49Sailesh Nepal        } else if (mWasSpeakerOn) {
136b88795aadc636d14156791c2fc93af051d7e0d49Sailesh Nepal            Call call = getForegroundCall();
137b88795aadc636d14156791c2fc93af051d7e0d49Sailesh Nepal            if (call != null && call.isAlive()) {
138b88795aadc636d14156791c2fc93af051d7e0d49Sailesh Nepal                // Restore the speaker state.
1396fb37c87836b5245046bd3b14320823ab839a10cIhab Awad                newRoute = AudioState.ROUTE_SPEAKER;
140b88795aadc636d14156791c2fc93af051d7e0d49Sailesh Nepal            }
141b88795aadc636d14156791c2fc93af051d7e0d49Sailesh Nepal        }
142b88795aadc636d14156791c2fc93af051d7e0d49Sailesh Nepal        setSystemAudioState(mAudioState.isMuted, newRoute, calculateSupportedRoutes());
143b88795aadc636d14156791c2fc93af051d7e0d49Sailesh Nepal    }
144b88795aadc636d14156791c2fc93af051d7e0d49Sailesh Nepal
145deb8c89707c604d4f9f32e476a58bd10a68293ffSantos Cordon    void toggleMute() {
146deb8c89707c604d4f9f32e476a58bd10a68293ffSantos Cordon        mute(!mAudioState.isMuted);
147deb8c89707c604d4f9f32e476a58bd10a68293ffSantos Cordon    }
148deb8c89707c604d4f9f32e476a58bd10a68293ffSantos Cordon
1496aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal    void mute(boolean shouldMute) {
15014ff8384ea44488ed6b203ceaac56a6c5294130aSantos Cordon        if (!hasFocus()) {
15114ff8384ea44488ed6b203ceaac56a6c5294130aSantos Cordon            return;
15214ff8384ea44488ed6b203ceaac56a6c5294130aSantos Cordon        }
15314ff8384ea44488ed6b203ceaac56a6c5294130aSantos Cordon
1546aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal        Log.v(this, "mute, shouldMute: %b", shouldMute);
1551ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon
1566aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal        // Don't mute if there are any emergency calls.
1576aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal        if (CallsManager.getInstance().hasEmergencyCall()) {
1586aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal            shouldMute = false;
1596aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal            Log.v(this, "ignoring mute for emergency call");
1606aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal        }
1616aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal
1626aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal        if (mAudioState.isMuted != shouldMute) {
1636aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal            setSystemAudioState(shouldMute, mAudioState.route, mAudioState.supportedRouteMask);
1646aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal        }
1656aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal    }
1661ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon
1676aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal    /**
1686aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal     * Changed the audio route, for example from earpiece to speaker phone.
1696aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal     *
1706fb37c87836b5245046bd3b14320823ab839a10cIhab Awad     * @param route The new audio route to use. See {@link AudioState}.
1716aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal     */
1726aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal    void setAudioRoute(int route) {
17314ff8384ea44488ed6b203ceaac56a6c5294130aSantos Cordon        // This can happen even when there are no calls and we don't have focus.
17414ff8384ea44488ed6b203ceaac56a6c5294130aSantos Cordon        if (!hasFocus()) {
17514ff8384ea44488ed6b203ceaac56a6c5294130aSantos Cordon            return;
17614ff8384ea44488ed6b203ceaac56a6c5294130aSantos Cordon        }
17714ff8384ea44488ed6b203ceaac56a6c5294130aSantos Cordon
1786fb37c87836b5245046bd3b14320823ab839a10cIhab Awad        Log.v(this, "setAudioRoute, route: %s", AudioState.audioRouteToString(route));
1791ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon
1806aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal        // Change ROUTE_WIRED_OR_EARPIECE to a single entry.
1816aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal        int newRoute = selectWiredOrEarpiece(route, mAudioState.supportedRouteMask);
1821ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon
1836aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal        // If route is unsupported, do nothing.
1846aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal        if ((mAudioState.supportedRouteMask | newRoute) == 0) {
1856aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal            Log.wtf(this, "Asking to set to a route that is unsupported: %d", newRoute);
1866aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal            return;
1871ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon        }
1881ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon
1896aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal        if (mAudioState.route != newRoute) {
1906aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal            // Remember the new speaker state so it can be restored when the user plugs and unplugs
1916aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal            // a headset.
1926fb37c87836b5245046bd3b14320823ab839a10cIhab Awad            mWasSpeakerOn = newRoute == AudioState.ROUTE_SPEAKER;
1936aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal            setSystemAudioState(mAudioState.isMuted, newRoute, mAudioState.supportedRouteMask);
1941ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon        }
1956aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal    }
1961ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon
1976aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal    void setIsRinging(boolean isRinging) {
1986aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal        if (mIsRinging != isRinging) {
1996aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal            Log.v(this, "setIsRinging %b -> %b", mIsRinging, isRinging);
2006aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal            mIsRinging = isRinging;
2016aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal            updateAudioStreamAndMode();
2021ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon        }
2031ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon    }
2041ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon
2051ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon    /**
206a56f276cafcad80ab3a28996b6a0d72cffa4b2bcSantos Cordon     * Sets the tone playing status. Some tones can play even when there are no live calls and this
207a56f276cafcad80ab3a28996b6a0d72cffa4b2bcSantos Cordon     * status indicates that we should keep audio focus even for tones that play beyond the life of
208a56f276cafcad80ab3a28996b6a0d72cffa4b2bcSantos Cordon     * calls.
209a56f276cafcad80ab3a28996b6a0d72cffa4b2bcSantos Cordon     *
210a56f276cafcad80ab3a28996b6a0d72cffa4b2bcSantos Cordon     * @param isPlayingNew The status to set.
211a56f276cafcad80ab3a28996b6a0d72cffa4b2bcSantos Cordon     */
212a56f276cafcad80ab3a28996b6a0d72cffa4b2bcSantos Cordon    void setIsTonePlaying(boolean isPlayingNew) {
213a56f276cafcad80ab3a28996b6a0d72cffa4b2bcSantos Cordon        ThreadUtil.checkOnMainThread();
214a56f276cafcad80ab3a28996b6a0d72cffa4b2bcSantos Cordon
215a56f276cafcad80ab3a28996b6a0d72cffa4b2bcSantos Cordon        if (mIsTonePlaying != isPlayingNew) {
216a56f276cafcad80ab3a28996b6a0d72cffa4b2bcSantos Cordon            Log.v(this, "mIsTonePlaying %b -> %b.", mIsTonePlaying, isPlayingNew);
217a56f276cafcad80ab3a28996b6a0d72cffa4b2bcSantos Cordon            mIsTonePlaying = isPlayingNew;
218a56f276cafcad80ab3a28996b6a0d72cffa4b2bcSantos Cordon            updateAudioStreamAndMode();
219a56f276cafcad80ab3a28996b6a0d72cffa4b2bcSantos Cordon        }
220a56f276cafcad80ab3a28996b6a0d72cffa4b2bcSantos Cordon    }
221a56f276cafcad80ab3a28996b6a0d72cffa4b2bcSantos Cordon
222a56f276cafcad80ab3a28996b6a0d72cffa4b2bcSantos Cordon    /**
223c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon     * Updates the audio routing according to the bluetooth state.
224c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon     */
225c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon    void onBluetoothStateChange(BluetoothManager bluetoothManager) {
22614ff8384ea44488ed6b203ceaac56a6c5294130aSantos Cordon        // This can happen even when there are no calls and we don't have focus.
22714ff8384ea44488ed6b203ceaac56a6c5294130aSantos Cordon        if (!hasFocus()) {
22814ff8384ea44488ed6b203ceaac56a6c5294130aSantos Cordon            return;
22914ff8384ea44488ed6b203ceaac56a6c5294130aSantos Cordon        }
23014ff8384ea44488ed6b203ceaac56a6c5294130aSantos Cordon
23131953644b056deb3da675a6305cd8d371a8d7d5aSantos Cordon        int supportedRoutes = calculateSupportedRoutes();
232c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon        int newRoute = mAudioState.route;
233c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon        if (bluetoothManager.isBluetoothAudioConnectedOrPending()) {
2346fb37c87836b5245046bd3b14320823ab839a10cIhab Awad            newRoute = AudioState.ROUTE_BLUETOOTH;
2356fb37c87836b5245046bd3b14320823ab839a10cIhab Awad        } else if (mAudioState.route == AudioState.ROUTE_BLUETOOTH) {
23631953644b056deb3da675a6305cd8d371a8d7d5aSantos Cordon            newRoute = selectWiredOrEarpiece(AudioState.ROUTE_WIRED_OR_EARPIECE, supportedRoutes);
237c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon            // Do not switch to speaker when bluetooth disconnects.
238c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon            mWasSpeakerOn = false;
239c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon        }
240c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon
24131953644b056deb3da675a6305cd8d371a8d7d5aSantos Cordon        setSystemAudioState(mAudioState.isMuted, newRoute, supportedRoutes);
242c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon    }
243c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon
244c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon    boolean isBluetoothAudioOn() {
245c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon        return mBluetoothManager.isBluetoothAudioConnected();
246c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon    }
247c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon
248c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon    boolean isBluetoothDeviceAvailable() {
249c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon        return mBluetoothManager.isBluetoothAvailable();
250c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon    }
251c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon
2526fb37c87836b5245046bd3b14320823ab839a10cIhab Awad    private void saveAudioState(AudioState audioState) {
253deb8c89707c604d4f9f32e476a58bd10a68293ffSantos Cordon        mAudioState = audioState;
254deb8c89707c604d4f9f32e476a58bd10a68293ffSantos Cordon        mStatusBarNotifier.notifyMute(mAudioState.isMuted);
2556fb37c87836b5245046bd3b14320823ab839a10cIhab Awad        mStatusBarNotifier.notifySpeakerphone(mAudioState.route == AudioState.ROUTE_SPEAKER);
256deb8c89707c604d4f9f32e476a58bd10a68293ffSantos Cordon    }
257deb8c89707c604d4f9f32e476a58bd10a68293ffSantos Cordon
25814ff8384ea44488ed6b203ceaac56a6c5294130aSantos Cordon    private void onCallUpdated(Call call) {
25914ff8384ea44488ed6b203ceaac56a6c5294130aSantos Cordon        boolean wasNotVoiceCall = mAudioFocusStreamType != AudioManager.STREAM_VOICE_CALL;
26014ff8384ea44488ed6b203ceaac56a6c5294130aSantos Cordon        updateAudioStreamAndMode();
26114ff8384ea44488ed6b203ceaac56a6c5294130aSantos Cordon
26214ff8384ea44488ed6b203ceaac56a6c5294130aSantos Cordon        // If we transition from not voice call to voice call, we need to set an initial state.
26314ff8384ea44488ed6b203ceaac56a6c5294130aSantos Cordon        if (wasNotVoiceCall && mAudioFocusStreamType == AudioManager.STREAM_VOICE_CALL) {
2647c6a5ec5c039cb3cc2e19024910fbcba0ce6b4e4Santos Cordon            setInitialAudioState(call, true /* force */);
26514ff8384ea44488ed6b203ceaac56a6c5294130aSantos Cordon        }
26614ff8384ea44488ed6b203ceaac56a6c5294130aSantos Cordon    }
26714ff8384ea44488ed6b203ceaac56a6c5294130aSantos Cordon
2686aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal    private void setSystemAudioState(boolean isMuted, int route, int supportedRouteMask) {
2697c6a5ec5c039cb3cc2e19024910fbcba0ce6b4e4Santos Cordon        setSystemAudioState(false /* force */, isMuted, route, supportedRouteMask);
2707c6a5ec5c039cb3cc2e19024910fbcba0ce6b4e4Santos Cordon    }
2717c6a5ec5c039cb3cc2e19024910fbcba0ce6b4e4Santos Cordon
2727c6a5ec5c039cb3cc2e19024910fbcba0ce6b4e4Santos Cordon    private void setSystemAudioState(
2737c6a5ec5c039cb3cc2e19024910fbcba0ce6b4e4Santos Cordon            boolean force, boolean isMuted, int route, int supportedRouteMask) {
27414ff8384ea44488ed6b203ceaac56a6c5294130aSantos Cordon        if (!hasFocus()) {
27514ff8384ea44488ed6b203ceaac56a6c5294130aSantos Cordon            return;
27614ff8384ea44488ed6b203ceaac56a6c5294130aSantos Cordon        }
27714ff8384ea44488ed6b203ceaac56a6c5294130aSantos Cordon
2786fb37c87836b5245046bd3b14320823ab839a10cIhab Awad        AudioState oldAudioState = mAudioState;
2796fb37c87836b5245046bd3b14320823ab839a10cIhab Awad        saveAudioState(new AudioState(isMuted, route, supportedRouteMask));
2807c6a5ec5c039cb3cc2e19024910fbcba0ce6b4e4Santos Cordon        if (!force && Objects.equals(oldAudioState, mAudioState)) {
28114ff8384ea44488ed6b203ceaac56a6c5294130aSantos Cordon            return;
28214ff8384ea44488ed6b203ceaac56a6c5294130aSantos Cordon        }
2836aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal        Log.i(this, "changing audio state from %s to %s", oldAudioState, mAudioState);
2846aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal
2856aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal        // Mute.
2866aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal        if (mAudioState.isMuted != mAudioManager.isMicrophoneMute()) {
2876aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal            Log.i(this, "changing microphone mute state to: %b", mAudioState.isMuted);
2886aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal            mAudioManager.setMicrophoneMute(mAudioState.isMuted);
2896aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal        }
2901ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon
2916aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal        // Audio route.
2926fb37c87836b5245046bd3b14320823ab839a10cIhab Awad        if (mAudioState.route == AudioState.ROUTE_BLUETOOTH) {
293c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon            turnOnSpeaker(false);
294c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon            turnOnBluetooth(true);
2956fb37c87836b5245046bd3b14320823ab839a10cIhab Awad        } else if (mAudioState.route == AudioState.ROUTE_SPEAKER) {
296c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon            turnOnBluetooth(false);
297c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon            turnOnSpeaker(true);
2986fb37c87836b5245046bd3b14320823ab839a10cIhab Awad        } else if (mAudioState.route == AudioState.ROUTE_EARPIECE ||
2996fb37c87836b5245046bd3b14320823ab839a10cIhab Awad                mAudioState.route == AudioState.ROUTE_WIRED_HEADSET) {
300c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon            turnOnBluetooth(false);
301c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon            turnOnSpeaker(false);
3026aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal        }
3036aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal
3046aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal        if (!oldAudioState.equals(mAudioState)) {
3056aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal            CallsManager.getInstance().onAudioStateChanged(oldAudioState, mAudioState);
3066aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal            updateAudioForForegroundCall();
3071ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon        }
3081ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon    }
3091ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon
310c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon    private void turnOnSpeaker(boolean on) {
311c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon        // Wired headset and earpiece work the same way
312c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon        if (mAudioManager.isSpeakerphoneOn() != on) {
31314ff8384ea44488ed6b203ceaac56a6c5294130aSantos Cordon            Log.i(this, "turning speaker phone %s", on);
314c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon            mAudioManager.setSpeakerphoneOn(on);
315c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon        }
316c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon    }
317c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon
318c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon    private void turnOnBluetooth(boolean on) {
319c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon        if (mBluetoothManager.isBluetoothAvailable()) {
32014ff8384ea44488ed6b203ceaac56a6c5294130aSantos Cordon            boolean isAlreadyOn = mBluetoothManager.isBluetoothAudioConnectedOrPending();
321c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon            if (on != isAlreadyOn) {
32214ff8384ea44488ed6b203ceaac56a6c5294130aSantos Cordon                Log.i(this, "connecting bluetooth %s", on);
323c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon                if (on) {
324c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon                    mBluetoothManager.connectBluetoothAudio();
325c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon                } else {
326c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon                    mBluetoothManager.disconnectBluetoothAudio();
327c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon                }
328c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon            }
329c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon        }
330c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon    }
331c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon
3326aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal    private void updateAudioStreamAndMode() {
333e5a7c921546825810996bd51414f251f005e0766Yorke Lee        Log.i(this, "updateAudioStreamAndMode, mIsRinging: %b, mIsTonePlaying: %b", mIsRinging,
334a56f276cafcad80ab3a28996b6a0d72cffa4b2bcSantos Cordon                mIsTonePlaying);
3356aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal        if (mIsRinging) {
3366aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal            requestAudioFocusAndSetMode(AudioManager.STREAM_RING, AudioManager.MODE_RINGTONE);
3376aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal        } else {
3385ba7f27491e287f39a999ddd3d1ed6a7bad78272Santos Cordon            Call call = getForegroundCall();
3396aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal            if (call != null) {
3405be64bc46c23b614d5452ca398a6bb7a512f1887Andrew Lee                int mode = call.getIsVoipAudioMode() ?
3417e66957928c5c23a1028c8e2a2d7cf359cbfa44eSailesh Nepal                        AudioManager.MODE_IN_COMMUNICATION : AudioManager.MODE_IN_CALL;
3426aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal                requestAudioFocusAndSetMode(AudioManager.STREAM_VOICE_CALL, mode);
343a56f276cafcad80ab3a28996b6a0d72cffa4b2bcSantos Cordon            } else if (mIsTonePlaying) {
344a56f276cafcad80ab3a28996b6a0d72cffa4b2bcSantos Cordon                // There is no call, however, we are still playing a tone, so keep focus.
34514ff8384ea44488ed6b203ceaac56a6c5294130aSantos Cordon                // Since there is no call from which to determine the mode, use the most
34614ff8384ea44488ed6b203ceaac56a6c5294130aSantos Cordon                // recently used mode instead.
347a56f276cafcad80ab3a28996b6a0d72cffa4b2bcSantos Cordon                requestAudioFocusAndSetMode(
34814ff8384ea44488ed6b203ceaac56a6c5294130aSantos Cordon                        AudioManager.STREAM_VOICE_CALL, mMostRecentlyUsedMode);
34942afb97b25495a0ed2153738b1e4a0c3b4c17276Yorke Lee            } else if (!hasRingingForegroundCall()) {
3506aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal                abandonAudioFocus();
35142afb97b25495a0ed2153738b1e4a0c3b4c17276Yorke Lee            } else {
35242afb97b25495a0ed2153738b1e4a0c3b4c17276Yorke Lee                // mIsRinging is false, but there is a foreground ringing call present. Don't
35342afb97b25495a0ed2153738b1e4a0c3b4c17276Yorke Lee                // abandon audio focus immediately to prevent audio focus from getting lost between
35442afb97b25495a0ed2153738b1e4a0c3b4c17276Yorke Lee                // the time it takes for the foreground call to transition from RINGING to ACTIVE/
35542afb97b25495a0ed2153738b1e4a0c3b4c17276Yorke Lee                // DISCONNECTED. When the call eventually transitions to the next state, audio
35642afb97b25495a0ed2153738b1e4a0c3b4c17276Yorke Lee                // focus will be correctly abandoned by the if clause above.
3576aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal            }
3586aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal        }
3596aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal    }
3601ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon
3616aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal    private void requestAudioFocusAndSetMode(int stream, int mode) {
362e5a7c921546825810996bd51414f251f005e0766Yorke Lee        Log.i(this, "requestAudioFocusAndSetMode, stream: %d -> %d", mAudioFocusStreamType, stream);
3636aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal        Preconditions.checkState(stream != STREAM_NONE);
3641ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon
3655ba7f27491e287f39a999ddd3d1ed6a7bad78272Santos Cordon        // Even if we already have focus, if the stream is different we update audio manager to give
3665ba7f27491e287f39a999ddd3d1ed6a7bad78272Santos Cordon        // it a hint about the purpose of our focus.
3675ba7f27491e287f39a999ddd3d1ed6a7bad78272Santos Cordon        if (mAudioFocusStreamType != stream) {
3686aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal            Log.v(this, "requesting audio focus for stream: %d", stream);
3696aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal            mAudioManager.requestAudioFocusForCall(stream,
3706aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal                    AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
3711ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon        }
3726aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal        mAudioFocusStreamType = stream;
37314ff8384ea44488ed6b203ceaac56a6c5294130aSantos Cordon
3746aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal        setMode(mode);
3756aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal    }
3761ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon
3776aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal    private void abandonAudioFocus() {
37814ff8384ea44488ed6b203ceaac56a6c5294130aSantos Cordon        if (hasFocus()) {
3796aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal            setMode(AudioManager.MODE_NORMAL);
3806aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal            Log.v(this, "abandoning audio focus");
3816aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal            mAudioManager.abandonAudioFocusForCall();
3826aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal            mAudioFocusStreamType = STREAM_NONE;
3836aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal        }
3841ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon    }
3851ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon
3861ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon    /**
3871ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon     * Sets the audio mode.
3881ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon     *
3896aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal     * @param newMode Mode constant from AudioManager.MODE_*.
3901ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon     */
3916aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal    private void setMode(int newMode) {
39214ff8384ea44488ed6b203ceaac56a6c5294130aSantos Cordon        Preconditions.checkState(hasFocus());
3936aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal        int oldMode = mAudioManager.getMode();
3946aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal        Log.v(this, "Request to change audio mode from %d to %d", oldMode, newMode);
39560b6e2830ee5c7d11c0fb8018595bf174d94a086Yorke Lee
3966aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal        if (oldMode != newMode) {
39760b6e2830ee5c7d11c0fb8018595bf174d94a086Yorke Lee            if (oldMode == AudioManager.MODE_IN_CALL && newMode == AudioManager.MODE_RINGTONE) {
39860b6e2830ee5c7d11c0fb8018595bf174d94a086Yorke Lee                Log.i(this, "Transition from IN_CALL -> RINGTONE. Resetting to NORMAL first.");
39960b6e2830ee5c7d11c0fb8018595bf174d94a086Yorke Lee                mAudioManager.setMode(AudioManager.MODE_NORMAL);
40060b6e2830ee5c7d11c0fb8018595bf174d94a086Yorke Lee            }
4016aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal            mAudioManager.setMode(newMode);
40214ff8384ea44488ed6b203ceaac56a6c5294130aSantos Cordon            mMostRecentlyUsedMode = newMode;
4031ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon        }
4041ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon    }
4051ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon
4066aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal    private int selectWiredOrEarpiece(int route, int supportedRouteMask) {
4076aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal        // Since they are mutually exclusive and one is ALWAYS valid, we allow a special input of
4086aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal        // ROUTE_WIRED_OR_EARPIECE so that callers dont have to make a call to check which is
4096aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal        // supported before calling setAudioRoute.
4106fb37c87836b5245046bd3b14320823ab839a10cIhab Awad        if (route == AudioState.ROUTE_WIRED_OR_EARPIECE) {
4116fb37c87836b5245046bd3b14320823ab839a10cIhab Awad            route = AudioState.ROUTE_WIRED_OR_EARPIECE & supportedRouteMask;
4126aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal            if (route == 0) {
4136aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal                Log.wtf(this, "One of wired headset or earpiece should always be valid.");
4146aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal                // assume earpiece in this case.
4156fb37c87836b5245046bd3b14320823ab839a10cIhab Awad                route = AudioState.ROUTE_EARPIECE;
4161ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon            }
4176aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal        }
4186aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal        return route;
4196aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal    }
4206aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal
4216aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal    private int calculateSupportedRoutes() {
4226fb37c87836b5245046bd3b14320823ab839a10cIhab Awad        int routeMask = AudioState.ROUTE_SPEAKER;
4231ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon
4246aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal        if (mWiredHeadsetManager.isPluggedIn()) {
4256fb37c87836b5245046bd3b14320823ab839a10cIhab Awad            routeMask |= AudioState.ROUTE_WIRED_HEADSET;
4266aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal        } else {
4276fb37c87836b5245046bd3b14320823ab839a10cIhab Awad            routeMask |= AudioState.ROUTE_EARPIECE;
4281ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon        }
4296aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal
430c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon        if (mBluetoothManager.isBluetoothAvailable()) {
4316fb37c87836b5245046bd3b14320823ab839a10cIhab Awad            routeMask |=  AudioState.ROUTE_BLUETOOTH;
432c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon        }
433c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon
4346aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal        return routeMask;
4351ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon    }
4361ae2b855daecb6829cf50690b1dfa2d55f62cf6dSantos Cordon
4376fb37c87836b5245046bd3b14320823ab839a10cIhab Awad    private AudioState getInitialAudioState(Call call) {
4386aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal        int supportedRouteMask = calculateSupportedRoutes();
439c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon        int route = selectWiredOrEarpiece(
4406fb37c87836b5245046bd3b14320823ab839a10cIhab Awad                AudioState.ROUTE_WIRED_OR_EARPIECE, supportedRouteMask);
441c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon
442c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon        // We want the UI to indicate that "bluetooth is in use" in two slightly different cases:
443c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon        // (a) The obvious case: if a bluetooth headset is currently in use for an ongoing call.
444c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon        // (b) The not-so-obvious case: if an incoming call is ringing, and we expect that audio
445c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon        //     *will* be routed to a bluetooth headset once the call is answered. In this case, just
446c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon        //     check if the headset is available. Note this only applies when we are dealing with
447c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon        //     the first call.
448c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon        if (call != null && mBluetoothManager.isBluetoothAvailable()) {
449c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon            switch(call.getState()) {
4506fb37c87836b5245046bd3b14320823ab839a10cIhab Awad                case CallState.ACTIVE:
4516fb37c87836b5245046bd3b14320823ab839a10cIhab Awad                case CallState.ON_HOLD:
4526fb37c87836b5245046bd3b14320823ab839a10cIhab Awad                case CallState.DIALING:
4535753f309976589302e1e319b44d0bf2777bbb2a0Santos Cordon                case CallState.CONNECTING:
4546fb37c87836b5245046bd3b14320823ab839a10cIhab Awad                case CallState.RINGING:
4556fb37c87836b5245046bd3b14320823ab839a10cIhab Awad                    route = AudioState.ROUTE_BLUETOOTH;
456c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon                    break;
457c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon                default:
458c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon                    break;
459c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon            }
460c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon        }
461c7e85d4fa0bb3325133a79d4c89f3149e0af430eSantos Cordon
4626fb37c87836b5245046bd3b14320823ab839a10cIhab Awad        return new AudioState(false, route, supportedRouteMask);
4636aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal    }
4646aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal
4657c6a5ec5c039cb3cc2e19024910fbcba0ce6b4e4Santos Cordon    private void setInitialAudioState(Call call, boolean force) {
4666fb37c87836b5245046bd3b14320823ab839a10cIhab Awad        AudioState audioState = getInitialAudioState(call);
46714ff8384ea44488ed6b203ceaac56a6c5294130aSantos Cordon        Log.v(this, "setInitialAudioState %s, %s", audioState, call);
4687c6a5ec5c039cb3cc2e19024910fbcba0ce6b4e4Santos Cordon        setSystemAudioState(
4697c6a5ec5c039cb3cc2e19024910fbcba0ce6b4e4Santos Cordon                force, audioState.isMuted, audioState.route, audioState.supportedRouteMask);
4706aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal    }
4716aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal
4726aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal    private void updateAudioForForegroundCall() {
4736aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal        Call call = CallsManager.getInstance().getForegroundCall();
474c92c436d84de46bb85100df9138378d9ffe0f2f2Sailesh Nepal        if (call != null && call.getConnectionService() != null) {
475c92c436d84de46bb85100df9138378d9ffe0f2f2Sailesh Nepal            call.getConnectionService().onAudioStateChanged(call, mAudioState);
4766aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal        }
477810735e3f0a4fe924a805981d32b6916ec834b38Sailesh Nepal    }
4785ba7f27491e287f39a999ddd3d1ed6a7bad78272Santos Cordon
4795ba7f27491e287f39a999ddd3d1ed6a7bad78272Santos Cordon    /**
4805ba7f27491e287f39a999ddd3d1ed6a7bad78272Santos Cordon     * Returns the current foreground call in order to properly set the audio mode.
4815ba7f27491e287f39a999ddd3d1ed6a7bad78272Santos Cordon     */
4825ba7f27491e287f39a999ddd3d1ed6a7bad78272Santos Cordon    private Call getForegroundCall() {
4835ba7f27491e287f39a999ddd3d1ed6a7bad78272Santos Cordon        Call call = CallsManager.getInstance().getForegroundCall();
4845ba7f27491e287f39a999ddd3d1ed6a7bad78272Santos Cordon
4855ba7f27491e287f39a999ddd3d1ed6a7bad78272Santos Cordon        // We ignore any foreground call that is in the ringing state because we deal with ringing
4865ba7f27491e287f39a999ddd3d1ed6a7bad78272Santos Cordon        // calls exclusively through the mIsRinging variable set by {@link Ringer}.
4875ba7f27491e287f39a999ddd3d1ed6a7bad78272Santos Cordon        if (call != null && call.getState() == CallState.RINGING) {
4885ba7f27491e287f39a999ddd3d1ed6a7bad78272Santos Cordon            call = null;
4895ba7f27491e287f39a999ddd3d1ed6a7bad78272Santos Cordon        }
4905ba7f27491e287f39a999ddd3d1ed6a7bad78272Santos Cordon        return call;
4915ba7f27491e287f39a999ddd3d1ed6a7bad78272Santos Cordon    }
49214ff8384ea44488ed6b203ceaac56a6c5294130aSantos Cordon
49342afb97b25495a0ed2153738b1e4a0c3b4c17276Yorke Lee    private boolean hasRingingForegroundCall() {
49442afb97b25495a0ed2153738b1e4a0c3b4c17276Yorke Lee        Call call = CallsManager.getInstance().getForegroundCall();
49542afb97b25495a0ed2153738b1e4a0c3b4c17276Yorke Lee        return call != null && call.getState() == CallState.RINGING;
49642afb97b25495a0ed2153738b1e4a0c3b4c17276Yorke Lee    }
49742afb97b25495a0ed2153738b1e4a0c3b4c17276Yorke Lee
49814ff8384ea44488ed6b203ceaac56a6c5294130aSantos Cordon    private boolean hasFocus() {
49914ff8384ea44488ed6b203ceaac56a6c5294130aSantos Cordon        return mAudioFocusStreamType != STREAM_NONE;
50014ff8384ea44488ed6b203ceaac56a6c5294130aSantos Cordon    }
501810735e3f0a4fe924a805981d32b6916ec834b38Sailesh Nepal}
502