Home | History | Annotate | Download | only in phone
      1 /*
      2  * Copyright (C) 2013 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.phone;
     18 
     19 import com.google.common.collect.Lists;
     20 
     21 import android.content.Context;
     22 import android.os.SystemProperties;
     23 import android.provider.MediaStore.Audio;
     24 import android.util.Log;
     25 
     26 import com.android.internal.telephony.CallManager;
     27 import com.android.internal.telephony.PhoneConstants;
     28 import com.android.phone.BluetoothManager.BluetoothIndicatorListener;
     29 import com.android.phone.WiredHeadsetManager.WiredHeadsetListener;
     30 import com.android.services.telephony.common.AudioMode;
     31 
     32 import java.util.List;
     33 
     34 /**
     35  * Responsible for Routing in-call audio and maintaining routing state.
     36  */
     37 /* package */ class AudioRouter implements BluetoothIndicatorListener, WiredHeadsetListener {
     38 
     39     private static String LOG_TAG = AudioRouter.class.getSimpleName();
     40     private static final boolean DBG =
     41             (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
     42     private static final boolean VDBG = (PhoneGlobals.DBG_LEVEL >= 2);
     43 
     44     private static final boolean ON = true;
     45     private static final boolean OFF = false;
     46 
     47     private final Context mContext;
     48     private final BluetoothManager mBluetoothManager;
     49     private final WiredHeadsetManager mWiredHeadsetManager;
     50     private final CallManager mCallManager;
     51     private final List<AudioModeListener> mListeners = Lists.newArrayList();
     52     private int mAudioMode = AudioMode.EARPIECE;
     53     private int mPreviousMode = AudioMode.EARPIECE;
     54     private int mSupportedModes = AudioMode.ALL_MODES;
     55 
     56     public AudioRouter(Context context, BluetoothManager bluetoothManager,
     57             WiredHeadsetManager wiredHeadsetManager, CallManager callManager) {
     58         mContext = context;
     59         mBluetoothManager = bluetoothManager;
     60         mWiredHeadsetManager = wiredHeadsetManager;
     61         mCallManager = callManager;
     62 
     63         init();
     64     }
     65 
     66     /**
     67      * Return the current audio mode.
     68      */
     69     public int getAudioMode() {
     70         return mAudioMode;
     71     }
     72 
     73     /**
     74      * Returns the currently supported audio modes.
     75      */
     76     public int getSupportedAudioModes() {
     77         return mSupportedModes;
     78     }
     79 
     80     /**
     81      * Returns the current mute state.
     82      */
     83     public boolean getMute() {
     84         return PhoneUtils.getMute();
     85     }
     86 
     87     /**
     88      * Add a listener to audio mode changes.
     89      */
     90     public void addAudioModeListener(AudioModeListener listener) {
     91         if (!mListeners.contains(listener)) {
     92             mListeners.add(listener);
     93 
     94             // For first notification, mPreviousAudioMode doesn't make sense.
     95             listener.onAudioModeChange(mAudioMode, getMute());
     96             listener.onSupportedAudioModeChange(mSupportedModes);
     97         }
     98     }
     99 
    100     /**
    101      * Remove  listener.
    102      */
    103     public void removeAudioModeListener(AudioModeListener listener) {
    104         if (mListeners.contains(listener)) {
    105             mListeners.remove(listener);
    106         }
    107     }
    108 
    109     /**
    110      * Sets the audio mode to the mode that is passed in.
    111      */
    112     public void setAudioMode(int mode) {
    113         logD("setAudioMode " + AudioMode.toString(mode));
    114         boolean error = false;
    115 
    116         // changes WIRED_OR_EARPIECE to appropriate single entry WIRED_HEADSET or EARPIECE
    117         mode = selectWiredOrEarpiece(mode);
    118 
    119         // If mode is unsupported, do nothing.
    120         if ((calculateSupportedModes() | mode) == 0) {
    121             Log.wtf(LOG_TAG, "Asking to set to a mode that is unsupported: " + mode);
    122             return;
    123         }
    124 
    125         if (AudioMode.SPEAKER == mode) {
    126             turnOnOffBluetooth(OFF);
    127             turnOnOffSpeaker(ON);
    128 
    129         } else if (AudioMode.BLUETOOTH == mode) {
    130             if (mBluetoothManager.isBluetoothAvailable()) {
    131                 // Manually turn the speaker phone off, instead of allowing the
    132                 // Bluetooth audio routing to handle it, since there's other
    133                 // important state-updating that needs to happen in the
    134                 // PhoneUtils.turnOnSpeaker() method.
    135                 // (Similarly, whenever the user turns *on* the speaker, we
    136                 // manually disconnect the active bluetooth headset;
    137                 // see toggleSpeaker() and/or switchInCallAudio().)
    138                 turnOnOffSpeaker(OFF);
    139                 if (!turnOnOffBluetooth(ON)) {
    140                     error = true;
    141                 }
    142             } else {
    143                 Log.e(LOG_TAG, "Asking to turn on bluetooth when no bluetooth available. " +
    144                         "supportedModes: " + AudioMode.toString(calculateSupportedModes()));
    145                 error = true;
    146             }
    147 
    148         // Wired headset and earpiece work the same way
    149         } else if (AudioMode.EARPIECE == mode || AudioMode.WIRED_HEADSET == mode) {
    150             turnOnOffBluetooth(OFF);
    151             turnOnOffSpeaker(OFF);
    152 
    153         } else {
    154             error = true;
    155         }
    156 
    157         if (error) {
    158             mode = calculateModeFromCurrentState();
    159             Log.e(LOG_TAG, "There was an error in setting new audio mode. " +
    160                     "Resetting mode to " + AudioMode.toString(mode) + ".");
    161 
    162         }
    163 
    164         updateAudioModeTo(mode);
    165     }
    166 
    167     /**
    168      * Turns on speaker.
    169      */
    170     public void setSpeaker(boolean on) {
    171         logD("setSpeaker " + on);
    172 
    173         if (on) {
    174             setAudioMode(AudioMode.SPEAKER);
    175         } else {
    176             setAudioMode(AudioMode.WIRED_OR_EARPIECE);
    177         }
    178     }
    179 
    180     public void onMuteChange(boolean muted) {
    181         logD("onMuteChange: " + muted);
    182         notifyListeners();
    183     }
    184 
    185     /**
    186      * Called when the bluetooth connection changes.
    187      * We adjust the audio mode according to the state we receive.
    188      */
    189     @Override
    190     public void onBluetoothIndicationChange(boolean isConnected, BluetoothManager btManager) {
    191         logD("onBluetoothIndicationChange " + isConnected);
    192 
    193         // this will read the new bluetooth mode appropriately
    194         updateAudioModeTo(calculateModeFromCurrentState());
    195     }
    196 
    197     /**
    198      * Called when the state of the wired headset changes.
    199      */
    200     @Override
    201     public void onWiredHeadsetConnection(boolean pluggedIn) {
    202         logD("onWireHeadsetConnection " + pluggedIn);
    203 
    204         // Since the presence of a wired headset or bluetooth affects the
    205         // speakerphone, update the "speaker" state.  We ONLY want to do
    206         // this on the wired headset connect / disconnect events for now
    207         // though.
    208         final boolean isOffhook = (mCallManager.getState() == PhoneConstants.State.OFFHOOK);
    209 
    210         int newMode = mAudioMode;
    211 
    212         // Change state only if we are not using bluetooth
    213         if (!mBluetoothManager.isBluetoothHeadsetAudioOn()) {
    214 
    215             // Do special logic with speakerphone if we have an ongoing (offhook) call.
    216             if (isOffhook) {
    217                 if (!pluggedIn) {
    218                     // if the state is "not connected", restore the speaker state.
    219                     PhoneUtils.restoreSpeakerMode(mContext);
    220 
    221                     if (PhoneUtils.isSpeakerOn(mContext)) {
    222                         newMode = AudioMode.SPEAKER;
    223                     } else {
    224                         newMode = AudioMode.EARPIECE;
    225                     }
    226                 } else {
    227                     // if the state is "connected", force the speaker off without
    228                     // storing the state.
    229                     PhoneUtils.turnOnSpeaker(mContext, false, false);
    230 
    231                     newMode = AudioMode.WIRED_HEADSET;
    232                 }
    233 
    234             // if we are outside of a phone call, the logic is simpler
    235             } else {
    236                 newMode = pluggedIn ? AudioMode.WIRED_HEADSET : AudioMode.EARPIECE;
    237             }
    238         }
    239 
    240         updateAudioModeTo(newMode);
    241     }
    242 
    243     /**
    244      * Changes WIRED_OR_EARPIECE to appropriate single entry WIRED_HEADSET or EARPIECE.
    245      * If mode passed it is not WIRED_OR_EARPIECE, this is a no-op and simply returns
    246      * the unchanged mode parameter.
    247      */
    248     private int selectWiredOrEarpiece(int mode) {
    249         // Since they are mutually exclusive and one is ALWAYS valid, we allow a special input of
    250         // WIRED_OR_EARPIECE so that callers dont have to make a call to check which is supported
    251         // before calling setAudioMode.
    252         if (mode == AudioMode.WIRED_OR_EARPIECE) {
    253             mode = AudioMode.WIRED_OR_EARPIECE & mSupportedModes;
    254 
    255             if (mode == 0) {
    256                 Log.wtf(LOG_TAG, "One of wired headset or earpiece should always be valid.");
    257                 // assume earpiece in this case.
    258                 mode = AudioMode.EARPIECE;
    259             }
    260         }
    261 
    262         return mode;
    263     }
    264 
    265     /**
    266      * Turns on/off bluetooth.  If bluetooth is already in the correct mode, this does
    267      * nothing.
    268      */
    269     private boolean turnOnOffBluetooth(boolean onOff) {
    270         if (mBluetoothManager.isBluetoothAvailable()) {
    271             final boolean isAlreadyOn = mBluetoothManager.isBluetoothAudioConnected();
    272 
    273             if (onOff && !isAlreadyOn) {
    274                 mBluetoothManager.connectBluetoothAudio();
    275             } else if (!onOff && isAlreadyOn) {
    276                 mBluetoothManager.disconnectBluetoothAudio();
    277             }
    278         } else if (onOff) {
    279             Log.e(LOG_TAG, "Asking to turn on bluetooth, but there is no bluetooth availabled.");
    280             return false;
    281         }
    282 
    283         return true;
    284     }
    285 
    286     /**
    287      * Turn on/off speaker.
    288      */
    289     private void turnOnOffSpeaker(boolean onOff) {
    290         if (PhoneUtils.isSpeakerOn(mContext) != onOff) {
    291             PhoneUtils.turnOnSpeaker(mContext, onOff, true /* storeState */);
    292         }
    293     }
    294 
    295     private void init() {
    296         mBluetoothManager.addBluetoothIndicatorListener(this);
    297         mWiredHeadsetManager.addWiredHeadsetListener(this);
    298     }
    299 
    300     /**
    301      * Reads the state of the world to determine Audio mode.
    302      */
    303     private int calculateModeFromCurrentState() {
    304 
    305         int mode = AudioMode.EARPIECE;
    306 
    307         // There is a very specific ordering here
    308         if (mBluetoothManager.showBluetoothIndication()) {
    309             mode = AudioMode.BLUETOOTH;
    310         } else if (PhoneUtils.isSpeakerOn(mContext)) {
    311             mode = AudioMode.SPEAKER;
    312         } else if (mWiredHeadsetManager.isHeadsetPlugged()) {
    313             mode = AudioMode.WIRED_HEADSET;
    314         }
    315 
    316         logD("calculateModeFromCurrentState " + AudioMode.toString(mode));
    317 
    318         return mode;
    319     }
    320 
    321     /**
    322      * Changes the audio mode to the mode in the parameter.
    323      */
    324     private void updateAudioModeTo(int mode) {
    325         int oldSupportedModes = mSupportedModes;
    326 
    327         mSupportedModes = calculateSupportedModes();
    328 
    329         // This is a weird state that shouldn't happen, but we get called here
    330         // once we've changed the audio layers so lets log the error, but assume
    331         // that it went through. If it happens it is likely it is a race condition
    332         // that will resolve itself when we get updates on the mode change.
    333         if ((mSupportedModes & mode) == 0) {
    334             Log.e(LOG_TAG, "Setting audio mode to an unsupported mode: " +
    335                     AudioMode.toString(mode) + ", supported (" +
    336                     AudioMode.toString(mSupportedModes) + ")");
    337         }
    338 
    339         boolean doNotify = oldSupportedModes != mSupportedModes;
    340 
    341         // only update if it really changed.
    342         if (mAudioMode != mode) {
    343             Log.i(LOG_TAG, "Audio mode changing to " + AudioMode.toString(mode));
    344             doNotify = true;
    345         }
    346 
    347         mPreviousMode = mAudioMode;
    348         mAudioMode = mode;
    349 
    350         if (doNotify) {
    351             notifyListeners();
    352         }
    353     }
    354 
    355     /**
    356      * Gets the updates supported modes from the state of the audio systems.
    357      */
    358     private int calculateSupportedModes() {
    359         // speaker phone always a supported state
    360         int supportedModes = AudioMode.SPEAKER;
    361 
    362         if (mWiredHeadsetManager.isHeadsetPlugged()) {
    363             supportedModes |= AudioMode.WIRED_HEADSET;
    364         } else {
    365             supportedModes |= AudioMode.EARPIECE;
    366         }
    367 
    368         if (mBluetoothManager.isBluetoothAvailable()) {
    369             supportedModes |= AudioMode.BLUETOOTH;
    370         }
    371 
    372         return supportedModes;
    373     }
    374 
    375     private void notifyListeners() {
    376         logD("AudioMode: " + AudioMode.toString(mAudioMode));
    377         logD("Supported AudioMode: " + AudioMode.toString(mSupportedModes));
    378 
    379         for (int i = 0; i < mListeners.size(); i++) {
    380             mListeners.get(i).onAudioModeChange(mAudioMode, getMute());
    381             mListeners.get(i).onSupportedAudioModeChange(mSupportedModes);
    382         }
    383     }
    384 
    385     public interface AudioModeListener {
    386         void onAudioModeChange(int newMode, boolean muted);
    387         void onSupportedAudioModeChange(int modeMask);
    388     }
    389 
    390     private void logD(String msg) {
    391         if (DBG) {
    392             Log.d(LOG_TAG, msg);
    393         }
    394     }
    395 }
    396