Home | History | Annotate | Download | only in telecom
      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 
     17 package com.android.server.telecom;
     18 
     19 import android.content.Context;
     20 import android.media.AudioManager;
     21 import android.os.Handler;
     22 import android.os.Looper;
     23 import android.os.Message;
     24 import android.telecom.CallAudioState;
     25 
     26 import com.android.internal.util.IndentingPrintWriter;
     27 import com.android.internal.util.Preconditions;
     28 
     29 import java.util.Objects;
     30 
     31 /**
     32  * This class manages audio modes, streams and other properties.
     33  */
     34 final class CallAudioManager extends CallsManagerListenerBase
     35         implements WiredHeadsetManager.Listener, DockManager.Listener {
     36     private static final int STREAM_NONE = -1;
     37 
     38     private static final String STREAM_DESCRIPTION_NONE = "STEAM_NONE";
     39     private static final String STREAM_DESCRIPTION_ALARM = "STEAM_ALARM";
     40     private static final String STREAM_DESCRIPTION_BLUETOOTH_SCO = "STREAM_BLUETOOTH_SCO";
     41     private static final String STREAM_DESCRIPTION_DTMF = "STREAM_DTMF";
     42     private static final String STREAM_DESCRIPTION_MUSIC = "STREAM_MUSIC";
     43     private static final String STREAM_DESCRIPTION_NOTIFICATION = "STREAM_NOTIFICATION";
     44     private static final String STREAM_DESCRIPTION_RING = "STREAM_RING";
     45     private static final String STREAM_DESCRIPTION_SYSTEM = "STREAM_SYSTEM";
     46     private static final String STREAM_DESCRIPTION_VOICE_CALL = "STREAM_VOICE_CALL";
     47 
     48     private static final String MODE_DESCRIPTION_INVALID = "MODE_INVALID";
     49     private static final String MODE_DESCRIPTION_CURRENT = "MODE_CURRENT";
     50     private static final String MODE_DESCRIPTION_NORMAL = "MODE_NORMAL";
     51     private static final String MODE_DESCRIPTION_RINGTONE = "MODE_RINGTONE";
     52     private static final String MODE_DESCRIPTION_IN_CALL = "MODE_IN_CALL";
     53     private static final String MODE_DESCRIPTION_IN_COMMUNICATION = "MODE_IN_COMMUNICATION";
     54 
     55     private static final int MSG_AUDIO_MANAGER_INITIALIZE = 0;
     56     private static final int MSG_AUDIO_MANAGER_TURN_ON_SPEAKER = 1;
     57     private static final int MSG_AUDIO_MANAGER_ABANDON_AUDIO_FOCUS_FOR_CALL = 2;
     58     private static final int MSG_AUDIO_MANAGER_SET_MICROPHONE_MUTE = 3;
     59     private static final int MSG_AUDIO_MANAGER_REQUEST_AUDIO_FOCUS_FOR_CALL = 4;
     60     private static final int MSG_AUDIO_MANAGER_SET_MODE = 5;
     61 
     62     private final Handler mAudioManagerHandler = new Handler(Looper.getMainLooper()) {
     63 
     64         private AudioManager mAudioManager;
     65 
     66         @Override
     67         public void handleMessage(Message msg) {
     68             switch (msg.what) {
     69                 case MSG_AUDIO_MANAGER_INITIALIZE: {
     70                     mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
     71                     break;
     72                 }
     73                 case MSG_AUDIO_MANAGER_TURN_ON_SPEAKER: {
     74                     boolean on = (msg.arg1 != 0);
     75                     // Wired headset and earpiece work the same way
     76                     if (mAudioManager.isSpeakerphoneOn() != on) {
     77                         Log.i(this, "turning speaker phone %s", on);
     78                         mAudioManager.setSpeakerphoneOn(on);
     79                     }
     80                     break;
     81                 }
     82                 case MSG_AUDIO_MANAGER_ABANDON_AUDIO_FOCUS_FOR_CALL: {
     83                     mAudioManager.abandonAudioFocusForCall();
     84                     break;
     85                 }
     86                 case MSG_AUDIO_MANAGER_SET_MICROPHONE_MUTE: {
     87                     boolean mute = (msg.arg1 != 0);
     88                     if (mute != mAudioManager.isMicrophoneMute()) {
     89                         Log.i(this, "changing microphone mute state to: %b", mute);
     90                         mAudioManager.setMicrophoneMute(mute);
     91                     }
     92                     break;
     93                 }
     94                 case MSG_AUDIO_MANAGER_REQUEST_AUDIO_FOCUS_FOR_CALL: {
     95                     int stream = msg.arg1;
     96                     mAudioManager.requestAudioFocusForCall(
     97                             stream,
     98                             AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
     99                     break;
    100                 }
    101                 case MSG_AUDIO_MANAGER_SET_MODE: {
    102                     int newMode = msg.arg1;
    103                     int oldMode = mAudioManager.getMode();
    104                     Log.v(this, "Request to change audio mode from %s to %s", modeToString(oldMode),
    105                             modeToString(newMode));
    106 
    107                     if (oldMode != newMode) {
    108                         if (oldMode == AudioManager.MODE_IN_CALL &&
    109                                 newMode == AudioManager.MODE_RINGTONE) {
    110                             Log.i(this, "Transition from IN_CALL -> RINGTONE."
    111                                     + "  Resetting to NORMAL first.");
    112                             mAudioManager.setMode(AudioManager.MODE_NORMAL);
    113                         }
    114                         mAudioManager.setMode(newMode);
    115                         synchronized (mLock) {
    116                             mMostRecentlyUsedMode = newMode;
    117                         }
    118                     }
    119                     break;
    120                 }
    121                 default:
    122                     break;
    123             }
    124         }
    125     };
    126 
    127     private final Context mContext;
    128     private final TelecomSystem.SyncRoot mLock;
    129     private final StatusBarNotifier mStatusBarNotifier;
    130     private final BluetoothManager mBluetoothManager;
    131     private final WiredHeadsetManager mWiredHeadsetManager;
    132     private final DockManager mDockManager;
    133     private final CallsManager mCallsManager;
    134 
    135     private CallAudioState mCallAudioState;
    136     private int mAudioFocusStreamType;
    137     private boolean mIsRinging;
    138     private boolean mIsTonePlaying;
    139     private boolean mWasSpeakerOn;
    140     private int mMostRecentlyUsedMode = AudioManager.MODE_IN_CALL;
    141     private Call mCallToSpeedUpMTAudio = null;
    142 
    143     CallAudioManager(
    144             Context context,
    145             TelecomSystem.SyncRoot lock,
    146             StatusBarNotifier statusBarNotifier,
    147             WiredHeadsetManager wiredHeadsetManager,
    148             DockManager dockManager,
    149             CallsManager callsManager) {
    150         mContext = context;
    151         mLock = lock;
    152         mAudioManagerHandler.obtainMessage(MSG_AUDIO_MANAGER_INITIALIZE, 0, 0).sendToTarget();
    153         mStatusBarNotifier = statusBarNotifier;
    154         mBluetoothManager = new BluetoothManager(context, this);
    155         mWiredHeadsetManager = wiredHeadsetManager;
    156         mCallsManager = callsManager;
    157 
    158         mWiredHeadsetManager.addListener(this);
    159         mDockManager = dockManager;
    160         mDockManager.addListener(this);
    161 
    162         saveAudioState(getInitialAudioState(null));
    163         mAudioFocusStreamType = STREAM_NONE;
    164     }
    165 
    166     CallAudioState getCallAudioState() {
    167         return mCallAudioState;
    168     }
    169 
    170     @Override
    171     public void onCallAdded(Call call) {
    172         Log.v(this, "onCallAdded");
    173         onCallUpdated(call);
    174 
    175         if (hasFocus() && getForegroundCall() == call) {
    176             if (!call.isIncoming()) {
    177                 // Unmute new outgoing call.
    178                 setSystemAudioState(false, mCallAudioState.getRoute(),
    179                         mCallAudioState.getSupportedRouteMask());
    180             }
    181         }
    182     }
    183 
    184     @Override
    185     public void onCallRemoved(Call call) {
    186         Log.v(this, "onCallRemoved");
    187         // If we didn't already have focus, there's nothing to do.
    188         if (hasFocus()) {
    189             if (mCallsManager.getCalls().isEmpty()) {
    190                 Log.v(this, "all calls removed, resetting system audio to default state");
    191                 setInitialAudioState(null, false /* force */);
    192                 mWasSpeakerOn = false;
    193             }
    194             updateAudioStreamAndMode(call);
    195         }
    196     }
    197 
    198     @Override
    199     public void onCallStateChanged(Call call, int oldState, int newState) {
    200         Log.v(this, "onCallStateChanged : oldState = %d, newState = %d", oldState, newState);
    201         onCallUpdated(call);
    202     }
    203 
    204     @Override
    205     public void onIncomingCallAnswered(Call call) {
    206         Log.v(this, "onIncomingCallAnswered");
    207         int route = mCallAudioState.getRoute();
    208 
    209         // We do two things:
    210         // (1) If this is the first call, then we can to turn on bluetooth if available.
    211         // (2) Unmute the audio for the new incoming call.
    212         boolean isOnlyCall = mCallsManager.getCalls().size() == 1;
    213         if (isOnlyCall && mBluetoothManager.isBluetoothAvailable()) {
    214             mBluetoothManager.connectBluetoothAudio();
    215             route = CallAudioState.ROUTE_BLUETOOTH;
    216         }
    217 
    218         setSystemAudioState(false /* isMute */, route, mCallAudioState.getSupportedRouteMask());
    219 
    220         if (call.can(android.telecom.Call.Details.CAPABILITY_SPEED_UP_MT_AUDIO)) {
    221             Log.v(this, "Speed up audio setup for IMS MT call.");
    222             mCallToSpeedUpMTAudio = call;
    223             updateAudioStreamAndMode(call);
    224         }
    225     }
    226 
    227     @Override
    228     public void onForegroundCallChanged(Call oldForegroundCall, Call newForegroundCall) {
    229         onCallUpdated(newForegroundCall);
    230         // Ensure that the foreground call knows about the latest audio state.
    231         updateAudioForForegroundCall();
    232     }
    233 
    234     @Override
    235     public void onIsVoipAudioModeChanged(Call call) {
    236         updateAudioStreamAndMode(call);
    237     }
    238 
    239     /**
    240       * Updates the audio route when the headset plugged in state changes. For example, if audio is
    241       * being routed over speakerphone and a headset is plugged in then switch to wired headset.
    242       */
    243     @Override
    244     public void onWiredHeadsetPluggedInChanged(boolean oldIsPluggedIn, boolean newIsPluggedIn) {
    245         // This can happen even when there are no calls and we don't have focus.
    246         if (!hasFocus()) {
    247             return;
    248         }
    249 
    250         boolean isCurrentlyWiredHeadset = mCallAudioState.getRoute()
    251                 == CallAudioState.ROUTE_WIRED_HEADSET;
    252 
    253         int newRoute = mCallAudioState.getRoute();  // start out with existing route
    254         if (newIsPluggedIn) {
    255             newRoute = CallAudioState.ROUTE_WIRED_HEADSET;
    256         } else if (isCurrentlyWiredHeadset) {
    257             Call call = getForegroundCall();
    258             boolean hasLiveCall = call != null && call.isAlive();
    259 
    260             if (hasLiveCall) {
    261                 // In order of preference when a wireless headset is unplugged.
    262                 if (mWasSpeakerOn) {
    263                     newRoute = CallAudioState.ROUTE_SPEAKER;
    264                 } else {
    265                     newRoute = CallAudioState.ROUTE_EARPIECE;
    266                 }
    267 
    268                 // We don't automatically connect to bluetooth when user unplugs their wired headset
    269                 // and they were previously using the wired. Wired and earpiece are effectively the
    270                 // same choice in that they replace each other as an option when wired headsets
    271                 // are plugged in and out. This means that keeping it earpiece is a bit more
    272                 // consistent with the status quo.  Bluetooth also has more danger associated with
    273                 // choosing it in the wrong curcumstance because bluetooth devices can be
    274                 // semi-public (like in a very-occupied car) where earpiece doesn't carry that risk.
    275             }
    276         }
    277 
    278         // We need to call this every time even if we do not change the route because the supported
    279         // routes changed either to include or not include WIRED_HEADSET.
    280         setSystemAudioState(mCallAudioState.isMuted(), newRoute, calculateSupportedRoutes());
    281     }
    282 
    283     @Override
    284     public void onDockChanged(boolean isDocked) {
    285         // This can happen even when there are no calls and we don't have focus.
    286         if (!hasFocus()) {
    287             return;
    288         }
    289 
    290         if (isDocked) {
    291             // Device just docked, turn to speakerphone. Only do so if the route is currently
    292             // earpiece so that we dont switch out of a BT headset or a wired headset.
    293             if (mCallAudioState.getRoute() == CallAudioState.ROUTE_EARPIECE) {
    294                 setAudioRoute(CallAudioState.ROUTE_SPEAKER);
    295             }
    296         } else {
    297             // Device just undocked, remove from speakerphone if possible.
    298             if (mCallAudioState.getRoute() == CallAudioState.ROUTE_SPEAKER) {
    299                 setAudioRoute(CallAudioState.ROUTE_WIRED_OR_EARPIECE);
    300             }
    301         }
    302     }
    303 
    304     void toggleMute() {
    305         mute(!mCallAudioState.isMuted());
    306     }
    307 
    308     void mute(boolean shouldMute) {
    309         if (!hasFocus()) {
    310             return;
    311         }
    312 
    313         Log.v(this, "mute, shouldMute: %b", shouldMute);
    314 
    315         // Don't mute if there are any emergency calls.
    316         if (mCallsManager.hasEmergencyCall()) {
    317             shouldMute = false;
    318             Log.v(this, "ignoring mute for emergency call");
    319         }
    320 
    321         if (mCallAudioState.isMuted() != shouldMute) {
    322             // We user CallsManager's foreground call so that we dont ignore ringing calls
    323             // for logging purposes
    324             Log.event(mCallsManager.getForegroundCall(), Log.Events.MUTE,
    325                     shouldMute ? "on" : "off");
    326 
    327             setSystemAudioState(shouldMute, mCallAudioState.getRoute(),
    328                     mCallAudioState.getSupportedRouteMask());
    329         }
    330     }
    331 
    332     /**
    333      * Changed the audio route, for example from earpiece to speaker phone.
    334      *
    335      * @param route The new audio route to use. See {@link CallAudioState}.
    336      */
    337     void setAudioRoute(int route) {
    338         // This can happen even when there are no calls and we don't have focus.
    339         if (!hasFocus()) {
    340             return;
    341         }
    342 
    343         Log.v(this, "setAudioRoute, route: %s", CallAudioState.audioRouteToString(route));
    344 
    345         // Change ROUTE_WIRED_OR_EARPIECE to a single entry.
    346         int newRoute = selectWiredOrEarpiece(route, mCallAudioState.getSupportedRouteMask());
    347 
    348         // If route is unsupported, do nothing.
    349         if ((mCallAudioState.getSupportedRouteMask() | newRoute) == 0) {
    350             Log.wtf(this, "Asking to set to a route that is unsupported: %d", newRoute);
    351             return;
    352         }
    353 
    354         if (mCallAudioState.getRoute() != newRoute) {
    355             // Remember the new speaker state so it can be restored when the user plugs and unplugs
    356             // a headset.
    357             mWasSpeakerOn = newRoute == CallAudioState.ROUTE_SPEAKER;
    358             setSystemAudioState(mCallAudioState.isMuted(), newRoute,
    359                     mCallAudioState.getSupportedRouteMask());
    360         }
    361     }
    362 
    363     /**
    364      * Sets the audio stream and mode based on whether a call is ringing.
    365      *
    366      * @param call The call which changed ringing state.
    367      * @param isRinging {@code true} if the call is ringing, {@code false} otherwise.
    368      */
    369     void setIsRinging(Call call, boolean isRinging) {
    370         if (mIsRinging != isRinging) {
    371             Log.i(this, "setIsRinging %b -> %b (call = %s)", mIsRinging, isRinging, call);
    372             mIsRinging = isRinging;
    373             updateAudioStreamAndMode(call);
    374         }
    375     }
    376 
    377     /**
    378      * Sets the tone playing status. Some tones can play even when there are no live calls and this
    379      * status indicates that we should keep audio focus even for tones that play beyond the life of
    380      * calls.
    381      *
    382      * @param isPlayingNew The status to set.
    383      */
    384     void setIsTonePlaying(boolean isPlayingNew) {
    385         if (mIsTonePlaying != isPlayingNew) {
    386             Log.v(this, "mIsTonePlaying %b -> %b.", mIsTonePlaying, isPlayingNew);
    387             mIsTonePlaying = isPlayingNew;
    388             updateAudioStreamAndMode();
    389         }
    390     }
    391 
    392     /**
    393      * Updates the audio routing according to the bluetooth state.
    394      */
    395     void onBluetoothStateChange(BluetoothManager bluetoothManager) {
    396         // This can happen even when there are no calls and we don't have focus.
    397         if (!hasFocus()) {
    398             return;
    399         }
    400 
    401         int supportedRoutes = calculateSupportedRoutes();
    402         int newRoute = mCallAudioState.getRoute();
    403         if (bluetoothManager.isBluetoothAudioConnectedOrPending()) {
    404             newRoute = CallAudioState.ROUTE_BLUETOOTH;
    405         } else if (mCallAudioState.getRoute() == CallAudioState.ROUTE_BLUETOOTH) {
    406             newRoute = selectWiredOrEarpiece(CallAudioState.ROUTE_WIRED_OR_EARPIECE,
    407                     supportedRoutes);
    408             // Do not switch to speaker when bluetooth disconnects.
    409             mWasSpeakerOn = false;
    410         }
    411 
    412         setSystemAudioState(mCallAudioState.isMuted(), newRoute, supportedRoutes);
    413     }
    414 
    415     boolean isBluetoothAudioOn() {
    416         return mBluetoothManager.isBluetoothAudioConnected();
    417     }
    418 
    419     boolean isBluetoothDeviceAvailable() {
    420         return mBluetoothManager.isBluetoothAvailable();
    421     }
    422 
    423     private void saveAudioState(CallAudioState callAudioState) {
    424         mCallAudioState = callAudioState;
    425         mStatusBarNotifier.notifyMute(mCallAudioState.isMuted());
    426         mStatusBarNotifier.notifySpeakerphone(mCallAudioState.getRoute()
    427                 == CallAudioState.ROUTE_SPEAKER);
    428     }
    429 
    430     private void onCallUpdated(Call call) {
    431         updateAudioStreamAndMode(call);
    432         if (call != null && call.getState() == CallState.ACTIVE &&
    433                             call == mCallToSpeedUpMTAudio) {
    434             mCallToSpeedUpMTAudio = null;
    435         }
    436     }
    437 
    438     private void setSystemAudioState(boolean isMuted, int route, int supportedRouteMask) {
    439         setSystemAudioState(false /* force */, isMuted, route, supportedRouteMask);
    440     }
    441 
    442     private void setSystemAudioState(
    443             boolean force, boolean isMuted, int route, int supportedRouteMask) {
    444         if (!hasFocus()) {
    445             return;
    446         }
    447 
    448         CallAudioState oldAudioState = mCallAudioState;
    449         saveAudioState(new CallAudioState(isMuted, route, supportedRouteMask));
    450         if (!force && Objects.equals(oldAudioState, mCallAudioState)) {
    451             return;
    452         }
    453 
    454         Log.i(this, "setSystemAudioState: changing from %s to %s", oldAudioState, mCallAudioState);
    455         Log.event(mCallsManager.getForegroundCall(), Log.Events.AUDIO_ROUTE,
    456                 CallAudioState.audioRouteToString(mCallAudioState.getRoute()));
    457 
    458         mAudioManagerHandler.obtainMessage(
    459                 MSG_AUDIO_MANAGER_SET_MICROPHONE_MUTE,
    460                 mCallAudioState.isMuted() ? 1 : 0,
    461                 0)
    462                 .sendToTarget();
    463 
    464         // Audio route.
    465         if (mCallAudioState.getRoute() == CallAudioState.ROUTE_BLUETOOTH) {
    466             turnOnSpeaker(false);
    467             turnOnBluetooth(true);
    468         } else if (mCallAudioState.getRoute() == CallAudioState.ROUTE_SPEAKER) {
    469             turnOnBluetooth(false);
    470             turnOnSpeaker(true);
    471         } else if (mCallAudioState.getRoute() == CallAudioState.ROUTE_EARPIECE ||
    472                 mCallAudioState.getRoute() == CallAudioState.ROUTE_WIRED_HEADSET) {
    473             turnOnBluetooth(false);
    474             turnOnSpeaker(false);
    475         }
    476 
    477         if (!oldAudioState.equals(mCallAudioState)) {
    478             mCallsManager.onCallAudioStateChanged(oldAudioState, mCallAudioState);
    479             updateAudioForForegroundCall();
    480         }
    481     }
    482 
    483     private void turnOnSpeaker(boolean on) {
    484         mAudioManagerHandler.obtainMessage(MSG_AUDIO_MANAGER_TURN_ON_SPEAKER, on ? 1 : 0, 0)
    485                 .sendToTarget();
    486     }
    487 
    488     private void turnOnBluetooth(boolean on) {
    489         if (mBluetoothManager.isBluetoothAvailable()) {
    490             boolean isAlreadyOn = mBluetoothManager.isBluetoothAudioConnectedOrPending();
    491             if (on != isAlreadyOn) {
    492                 Log.i(this, "connecting bluetooth %s", on);
    493                 if (on) {
    494                     mBluetoothManager.connectBluetoothAudio();
    495                 } else {
    496                     mBluetoothManager.disconnectBluetoothAudio();
    497                 }
    498             }
    499         }
    500     }
    501 
    502     private void updateAudioStreamAndMode() {
    503         updateAudioStreamAndMode(null /* call */);
    504     }
    505 
    506     private void updateAudioStreamAndMode(Call callToUpdate) {
    507         Log.i(this, "updateAudioStreamAndMode :  mIsRinging: %b, mIsTonePlaying: %b, call: %s",
    508                 mIsRinging, mIsTonePlaying, callToUpdate);
    509 
    510         boolean wasVoiceCall = mAudioFocusStreamType == AudioManager.STREAM_VOICE_CALL;
    511         if (mIsRinging) {
    512             Log.i(this, "updateAudioStreamAndMode : ringing");
    513             requestAudioFocusAndSetMode(AudioManager.STREAM_RING, AudioManager.MODE_RINGTONE);
    514         } else {
    515             Call foregroundCall = getForegroundCall();
    516             Call waitingForAccountSelectionCall = mCallsManager
    517                     .getFirstCallWithState(CallState.SELECT_PHONE_ACCOUNT);
    518             Call call = mCallsManager.getForegroundCall();
    519             if (foregroundCall == null && call != null && call == mCallToSpeedUpMTAudio) {
    520                 Log.v(this, "updateAudioStreamAndMode : no foreground, speeding up MT audio.");
    521                 requestAudioFocusAndSetMode(AudioManager.STREAM_VOICE_CALL,
    522                                                          AudioManager.MODE_IN_CALL);
    523             } else if (foregroundCall != null && waitingForAccountSelectionCall == null) {
    524                 // In the case where there is a call that is waiting for account selection,
    525                 // this will fall back to abandonAudioFocus() below, which temporarily exits
    526                 // the in-call audio mode. This is to allow TalkBack to speak the "Call with"
    527                 // dialog information at media volume as opposed to through the earpiece.
    528                 // Once exiting the "Call with" dialog, the audio focus will return to an in-call
    529                 // audio mode when this method (updateAudioStreamAndMode) is called again.
    530                 int mode = foregroundCall.getIsVoipAudioMode() ?
    531                         AudioManager.MODE_IN_COMMUNICATION : AudioManager.MODE_IN_CALL;
    532                 Log.v(this, "updateAudioStreamAndMode : foreground");
    533                 requestAudioFocusAndSetMode(AudioManager.STREAM_VOICE_CALL, mode);
    534             } else if (mIsTonePlaying) {
    535                 // There is no call, however, we are still playing a tone, so keep focus.
    536                 // Since there is no call from which to determine the mode, use the most
    537                 // recently used mode instead.
    538                 Log.v(this, "updateAudioStreamAndMode : tone playing");
    539                 requestAudioFocusAndSetMode(
    540                         AudioManager.STREAM_VOICE_CALL, mMostRecentlyUsedMode);
    541             } else if (!hasRingingForegroundCall()) {
    542                 Log.v(this, "updateAudioStreamAndMode : no ringing call");
    543                 abandonAudioFocus();
    544             } else {
    545                 // mIsRinging is false, but there is a foreground ringing call present. Don't
    546                 // abandon audio focus immediately to prevent audio focus from getting lost between
    547                 // the time it takes for the foreground call to transition from RINGING to ACTIVE/
    548                 // DISCONNECTED. When the call eventually transitions to the next state, audio
    549                 // focus will be correctly abandoned by the if clause above.
    550             }
    551         }
    552 
    553         boolean isVoiceCall = mAudioFocusStreamType == AudioManager.STREAM_VOICE_CALL;
    554 
    555         // If we transition from not a voice call to a voice call, we need to set an initial audio
    556         // state for the call.
    557         if (!wasVoiceCall && isVoiceCall) {
    558             setInitialAudioState(callToUpdate, true /* force */);
    559         }
    560     }
    561 
    562     private void requestAudioFocusAndSetMode(int stream, int mode) {
    563         Log.v(this, "requestAudioFocusAndSetMode : stream: %s -> %s, mode: %s",
    564                 streamTypeToString(mAudioFocusStreamType), streamTypeToString(stream),
    565                 modeToString(mode));
    566         Preconditions.checkState(stream != STREAM_NONE);
    567 
    568         // Even if we already have focus, if the stream is different we update audio manager to give
    569         // it a hint about the purpose of our focus.
    570         if (mAudioFocusStreamType != stream) {
    571             Log.i(this, "requestAudioFocusAndSetMode : requesting stream: %s -> %s",
    572                     streamTypeToString(mAudioFocusStreamType), streamTypeToString(stream));
    573             mAudioManagerHandler.obtainMessage(
    574                     MSG_AUDIO_MANAGER_REQUEST_AUDIO_FOCUS_FOR_CALL,
    575                     stream,
    576                     0)
    577                     .sendToTarget();
    578         }
    579         mAudioFocusStreamType = stream;
    580 
    581         setMode(mode);
    582     }
    583 
    584     private void abandonAudioFocus() {
    585         if (hasFocus()) {
    586             setMode(AudioManager.MODE_NORMAL);
    587             Log.v(this, "abandoning audio focus");
    588             mAudioManagerHandler.obtainMessage(MSG_AUDIO_MANAGER_ABANDON_AUDIO_FOCUS_FOR_CALL, 0, 0)
    589                     .sendToTarget();
    590             mAudioFocusStreamType = STREAM_NONE;
    591             mCallToSpeedUpMTAudio = null;
    592         }
    593     }
    594 
    595     /**
    596      * Sets the audio mode.
    597      *
    598      * @param newMode Mode constant from AudioManager.MODE_*.
    599      */
    600     private void setMode(int newMode) {
    601         Preconditions.checkState(hasFocus());
    602         mAudioManagerHandler.obtainMessage(MSG_AUDIO_MANAGER_SET_MODE, newMode, 0).sendToTarget();
    603     }
    604 
    605     private int selectWiredOrEarpiece(int route, int supportedRouteMask) {
    606         // Since they are mutually exclusive and one is ALWAYS valid, we allow a special input of
    607         // ROUTE_WIRED_OR_EARPIECE so that callers dont have to make a call to check which is
    608         // supported before calling setAudioRoute.
    609         if (route == CallAudioState.ROUTE_WIRED_OR_EARPIECE) {
    610             route = CallAudioState.ROUTE_WIRED_OR_EARPIECE & supportedRouteMask;
    611             if (route == 0) {
    612                 Log.wtf(this, "One of wired headset or earpiece should always be valid.");
    613                 // assume earpiece in this case.
    614                 route = CallAudioState.ROUTE_EARPIECE;
    615             }
    616         }
    617         return route;
    618     }
    619 
    620     private int calculateSupportedRoutes() {
    621         int routeMask = CallAudioState.ROUTE_SPEAKER;
    622 
    623         if (mWiredHeadsetManager.isPluggedIn()) {
    624             routeMask |= CallAudioState.ROUTE_WIRED_HEADSET;
    625         } else {
    626             routeMask |= CallAudioState.ROUTE_EARPIECE;
    627         }
    628 
    629         if (mBluetoothManager.isBluetoothAvailable()) {
    630             routeMask |=  CallAudioState.ROUTE_BLUETOOTH;
    631         }
    632 
    633         return routeMask;
    634     }
    635 
    636     private CallAudioState getInitialAudioState(Call call) {
    637         int supportedRouteMask = calculateSupportedRoutes();
    638         int route = selectWiredOrEarpiece(
    639                 CallAudioState.ROUTE_WIRED_OR_EARPIECE, supportedRouteMask);
    640 
    641         // We want the UI to indicate that "bluetooth is in use" in two slightly different cases:
    642         // (a) The obvious case: if a bluetooth headset is currently in use for an ongoing call.
    643         // (b) The not-so-obvious case: if an incoming call is ringing, and we expect that audio
    644         //     *will* be routed to a bluetooth headset once the call is answered. In this case, just
    645         //     check if the headset is available. Note this only applies when we are dealing with
    646         //     the first call.
    647         if (call != null && mBluetoothManager.isBluetoothAvailable()) {
    648             switch(call.getState()) {
    649                 case CallState.ACTIVE:
    650                 case CallState.ON_HOLD:
    651                 case CallState.DIALING:
    652                 case CallState.CONNECTING:
    653                 case CallState.RINGING:
    654                     route = CallAudioState.ROUTE_BLUETOOTH;
    655                     break;
    656                 default:
    657                     break;
    658             }
    659         }
    660 
    661         return new CallAudioState(false, route, supportedRouteMask);
    662     }
    663 
    664     private void setInitialAudioState(Call call, boolean force) {
    665         CallAudioState audioState = getInitialAudioState(call);
    666         Log.i(this, "setInitialAudioState : audioState = %s, call = %s", audioState, call);
    667         setSystemAudioState(
    668                 force, audioState.isMuted(), audioState.getRoute(),
    669                 audioState.getSupportedRouteMask());
    670     }
    671 
    672     private void updateAudioForForegroundCall() {
    673         Call call = mCallsManager.getForegroundCall();
    674         if (call != null && call.getConnectionService() != null) {
    675             call.getConnectionService().onCallAudioStateChanged(call, mCallAudioState);
    676         }
    677     }
    678 
    679     /**
    680      * Returns the current foreground call in order to properly set the audio mode.
    681      */
    682     private Call getForegroundCall() {
    683         Call call = mCallsManager.getForegroundCall();
    684 
    685         // We ignore any foreground call that is in the ringing state because we deal with ringing
    686         // calls exclusively through the mIsRinging variable set by {@link Ringer}.
    687         if (call != null && call.getState() == CallState.RINGING) {
    688             return null;
    689         }
    690 
    691         return call;
    692     }
    693 
    694     private boolean hasRingingForegroundCall() {
    695         Call call = mCallsManager.getForegroundCall();
    696         return call != null && call.getState() == CallState.RINGING;
    697     }
    698 
    699     private boolean hasFocus() {
    700         return mAudioFocusStreamType != STREAM_NONE;
    701     }
    702 
    703     /**
    704      * Translates an {@link AudioManager} stream type to a human-readable string description.
    705      *
    706      * @param streamType The stream type.
    707      * @return Human readable description.
    708      */
    709     private String streamTypeToString(int streamType) {
    710         switch (streamType) {
    711             case STREAM_NONE:
    712                 return STREAM_DESCRIPTION_NONE;
    713             case AudioManager.STREAM_ALARM:
    714                 return STREAM_DESCRIPTION_ALARM;
    715             case AudioManager.STREAM_BLUETOOTH_SCO:
    716                 return STREAM_DESCRIPTION_BLUETOOTH_SCO;
    717             case AudioManager.STREAM_DTMF:
    718                 return STREAM_DESCRIPTION_DTMF;
    719             case AudioManager.STREAM_MUSIC:
    720                 return STREAM_DESCRIPTION_MUSIC;
    721             case AudioManager.STREAM_NOTIFICATION:
    722                 return STREAM_DESCRIPTION_NOTIFICATION;
    723             case AudioManager.STREAM_RING:
    724                 return STREAM_DESCRIPTION_RING;
    725             case AudioManager.STREAM_SYSTEM:
    726                 return STREAM_DESCRIPTION_SYSTEM;
    727             case AudioManager.STREAM_VOICE_CALL:
    728                 return STREAM_DESCRIPTION_VOICE_CALL;
    729             default:
    730                 return "STEAM_OTHER_" + streamType;
    731         }
    732     }
    733 
    734     /**
    735      * Translates an {@link AudioManager} mode into a human readable string.
    736      *
    737      * @param mode The mode.
    738      * @return The string.
    739      */
    740     private String modeToString(int mode) {
    741         switch (mode) {
    742             case AudioManager.MODE_INVALID:
    743                 return MODE_DESCRIPTION_INVALID;
    744             case AudioManager.MODE_CURRENT:
    745                 return MODE_DESCRIPTION_CURRENT;
    746             case AudioManager.MODE_NORMAL:
    747                 return MODE_DESCRIPTION_NORMAL;
    748             case AudioManager.MODE_RINGTONE:
    749                 return MODE_DESCRIPTION_RINGTONE;
    750             case AudioManager.MODE_IN_CALL:
    751                 return MODE_DESCRIPTION_IN_CALL;
    752             case AudioManager.MODE_IN_COMMUNICATION:
    753                 return MODE_DESCRIPTION_IN_COMMUNICATION;
    754             default:
    755                 return "MODE_OTHER_" + mode;
    756         }
    757     }
    758 
    759     /**
    760      * Dumps the state of the {@link CallAudioManager}.
    761      *
    762      * @param pw The {@code IndentingPrintWriter} to write the state to.
    763      */
    764     public void dump(IndentingPrintWriter pw) {
    765         pw.println("mAudioState: " + mCallAudioState);
    766         pw.println("mBluetoothManager:");
    767         pw.increaseIndent();
    768         mBluetoothManager.dump(pw);
    769         pw.decreaseIndent();
    770         if (mWiredHeadsetManager != null) {
    771             pw.println("mWiredHeadsetManager:");
    772             pw.increaseIndent();
    773             mWiredHeadsetManager.dump(pw);
    774             pw.decreaseIndent();
    775         } else {
    776             pw.println("mWiredHeadsetManager: null");
    777         }
    778         pw.println("mAudioFocusStreamType: " + streamTypeToString(mAudioFocusStreamType));
    779         pw.println("mIsRinging: " + mIsRinging);
    780         pw.println("mIsTonePlaying: " + mIsTonePlaying);
    781         pw.println("mWasSpeakerOn: " + mWasSpeakerOn);
    782         pw.println("mMostRecentlyUsedMode: " + modeToString(mMostRecentlyUsedMode));
    783     }
    784 }
    785