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