blob: 812d92c36e900a177ac5390f66b7ab74542c636e [file] [log] [blame]
Santos Cordon9b7bac72013-08-06 08:04:52 -07001/*
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
17package com.android.phone;
18
19import com.google.common.collect.Lists;
20
21import android.content.Context;
Santos Cordon9b7bac72013-08-06 08:04:52 -070022import android.os.SystemProperties;
Santos Cordon593ab382013-08-06 21:58:23 -070023import android.provider.MediaStore.Audio;
Santos Cordon9b7bac72013-08-06 08:04:52 -070024import android.util.Log;
25
Santos Cordon593ab382013-08-06 21:58:23 -070026import com.android.internal.telephony.CallManager;
27import com.android.internal.telephony.PhoneConstants;
Santos Cordon9b7bac72013-08-06 08:04:52 -070028import com.android.phone.BluetoothManager.BluetoothIndicatorListener;
Santos Cordon593ab382013-08-06 21:58:23 -070029import com.android.phone.WiredHeadsetManager.WiredHeadsetListener;
Santos Cordon9b7bac72013-08-06 08:04:52 -070030import com.android.services.telephony.common.AudioMode;
31
32import java.util.List;
33
34/**
35 * Responsible for Routing in-call audio and maintaining routing state.
36 */
Santos Cordon593ab382013-08-06 21:58:23 -070037/* package */ class AudioRouter implements BluetoothIndicatorListener, WiredHeadsetListener {
Santos Cordon9b7bac72013-08-06 08:04:52 -070038
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 final Context mContext;
45 private final BluetoothManager mBluetoothManager;
Santos Cordon593ab382013-08-06 21:58:23 -070046 private final WiredHeadsetManager mWiredHeadsetManager;
47 private final CallManager mCallManager;
Santos Cordon9b7bac72013-08-06 08:04:52 -070048 private final List<AudioModeListener> mListeners = Lists.newArrayList();
49 private int mAudioMode = AudioMode.EARPIECE;
50 private int mPreviousMode = AudioMode.EARPIECE;
Santos Cordon593ab382013-08-06 21:58:23 -070051 private int mSupportedModes = AudioMode.ALL_MODES;
Santos Cordon9b7bac72013-08-06 08:04:52 -070052
Santos Cordon593ab382013-08-06 21:58:23 -070053 public AudioRouter(Context context, BluetoothManager bluetoothManager,
54 WiredHeadsetManager wiredHeadsetManager, CallManager callManager) {
Santos Cordon9b7bac72013-08-06 08:04:52 -070055 mContext = context;
56 mBluetoothManager = bluetoothManager;
Santos Cordon593ab382013-08-06 21:58:23 -070057 mWiredHeadsetManager = wiredHeadsetManager;
58 mCallManager = callManager;
Santos Cordon9b7bac72013-08-06 08:04:52 -070059
60 init();
61 }
62
63 /**
64 * Return the current audio mode.
65 */
66 public int getAudioMode() {
67 return mAudioMode;
68 }
69
70 /**
Santos Cordon593ab382013-08-06 21:58:23 -070071 * Returns the currently supported audio modes.
72 */
73 public int getSupportedAudioModes() {
74 return mSupportedModes;
75 }
76
77 /**
Santos Cordon9b7bac72013-08-06 08:04:52 -070078 * Add a listener to audio mode changes.
79 */
80 public void addAudioModeListener(AudioModeListener listener) {
81 if (!mListeners.contains(listener)) {
82 mListeners.add(listener);
Santos Cordon593ab382013-08-06 21:58:23 -070083
84 // For first notification, mPreviousAudioMode doesn't make sense.
85 listener.onAudioModeChange(mAudioMode, mAudioMode);
86 listener.onSupportedAudioModeChange(mSupportedModes);
Santos Cordon9b7bac72013-08-06 08:04:52 -070087 }
88 }
89
90 /**
91 * Remove listener.
92 */
93 public void removeAudioModeListener(AudioModeListener listener) {
94 if (mListeners.contains(listener)) {
95 mListeners.remove(listener);
96 }
97 }
98
99 /**
100 * Sets the audio mode to the mode that is passed in.
101 */
102 public void setAudioMode(int mode) {
Santos Cordon593ab382013-08-06 21:58:23 -0700103 logD("setAudioMode " + AudioMode.toString(mode));
Santos Cordon9b7bac72013-08-06 08:04:52 -0700104
Santos Cordon593ab382013-08-06 21:58:23 -0700105 // Since they are mutually exclusive and one is ALWAYS valid, we allow a special input of
106 // WIRED_OR_EARPIECE so that callers dont have to make a call to check which is supported
107 // before calling setAudioMode.
108 if (mode == AudioMode.WIRED_OR_EARPIECE) {
109 mode = AudioMode.WIRED_OR_EARPIECE & mSupportedModes;
110
111 if (mode == 0) {
112 Log.wtf(LOG_TAG, "One of wired headset or earpiece should always be valid.");
113 // assume earpiece in this case.
114 mode = AudioMode.EARPIECE;
115 }
116 }
117
118 if ((calculateSupportedModes() | mode) == 0) {
119 Log.wtf(LOG_TAG, "Asking to set to a mode that is unsupported: " + mode);
120 return;
121 }
122
123 if (AudioMode.SPEAKER == mode) {
124
125 if (!PhoneUtils.isSpeakerOn(mContext)) {
126 // Switch away from Bluetooth, if it was active.
127 if (mBluetoothManager.isBluetoothAvailable() &&
128 mBluetoothManager.isBluetoothAudioConnected()) {
129
130 mBluetoothManager.disconnectBluetoothAudio();
131 }
132 PhoneUtils.turnOnSpeaker(mContext, true, true);
133 }
134
135 } else if (AudioMode.BLUETOOTH == mode) {
136
137 // If already connected to BT, there's nothing to do here.
138 if (mBluetoothManager.isBluetoothAvailable() &&
139 !mBluetoothManager.isBluetoothAudioConnected()) {
140 // Manually turn the speaker phone off, instead of allowing the
141 // Bluetooth audio routing to handle it, since there's other
142 // important state-updating that needs to happen in the
143 // PhoneUtils.turnOnSpeaker() method.
144 // (Similarly, whenever the user turns *on* the speaker, we
145 // manually disconnect the active bluetooth headset;
146 // see toggleSpeaker() and/or switchInCallAudio().)
147 if (PhoneUtils.isSpeakerOn(mContext)) {
148 PhoneUtils.turnOnSpeaker(mContext, false, true);
149 }
150 mBluetoothManager.connectBluetoothAudio();
151 }
152
153 // Wired headset and earpiece work the same way
154 } else if (AudioMode.EARPIECE == mode || AudioMode.WIRED_HEADSET == mode) {
155
156 // Switch to either the handset earpiece, or the wired headset (if connected.)
157 // (Do this by simply making sure both speaker and bluetooth are off.)
158 if (mBluetoothManager.isBluetoothAvailable() &&
159 mBluetoothManager.isBluetoothAudioConnected()) {
160 mBluetoothManager.disconnectBluetoothAudio();
161 }
162 if (PhoneUtils.isSpeakerOn(mContext)) {
163 PhoneUtils.turnOnSpeaker(mContext, false, true);
164 }
165
166 } else {
167 Log.wtf(LOG_TAG, "Asking us to set to an unsupported mode " +
168 AudioMode.toString(mode) + " (" + mode + ")");
169
170 // set it to the current audio mode
171 mode = mAudioMode;
172 }
173
174 updateAudioModeTo(mode);
175 }
176
177 /**
178 * Turns on speaker.
179 */
180 public void setSpeaker(boolean on) {
181 logD("setSpeaker " + on);
182
183 if (on) {
184 setAudioMode(AudioMode.SPEAKER);
185 } else {
186 setAudioMode(AudioMode.WIRED_OR_EARPIECE);
Santos Cordon9b7bac72013-08-06 08:04:52 -0700187 }
188 }
189
190 /**
191 * Called when the bluetooth connection changes.
192 * We adjust the audio mode according to the state we receive.
193 */
194 @Override
195 public void onBluetoothIndicationChange(boolean isConnected, BluetoothManager btManager) {
Santos Cordon593ab382013-08-06 21:58:23 -0700196 logD("onBluetoothIndicationChange " + isConnected);
Santos Cordon9b7bac72013-08-06 08:04:52 -0700197
Santos Cordon593ab382013-08-06 21:58:23 -0700198 // this will read the new bluetooth mode appropriately
199 updateAudioModeTo(calculateModeFromCurrentState());
Santos Cordon9b7bac72013-08-06 08:04:52 -0700200 }
201
Santos Cordon593ab382013-08-06 21:58:23 -0700202 /**
203 * Called when the state of the wired headset changes.
204 */
205 @Override
206 public void onWiredHeadsetConnection(boolean pluggedIn) {
207 logD("onWireHeadsetConnection " + pluggedIn);
208
209 // Since the presence of a wired headset or bluetooth affects the
210 // speakerphone, update the "speaker" state. We ONLY want to do
211 // this on the wired headset connect / disconnect events for now
212 // though.
213 PhoneConstants.State phoneState = mCallManager.getState();
214
215 int newMode = mAudioMode;
216
217 // TODO: Consider using our stored states instead
218
219 // Do not change speaker state if phone is not off hook
220 if (phoneState == PhoneConstants.State.OFFHOOK &&
221 !mBluetoothManager.isBluetoothHeadsetAudioOn()) {
222 if (!pluggedIn) {
223 // if the state is "not connected", restore the speaker state.
224 PhoneUtils.restoreSpeakerMode(mContext);
225
226 if (PhoneUtils.isSpeakerOn(mContext)) {
227 newMode = AudioMode.SPEAKER;
228 } else {
229 newMode = AudioMode.EARPIECE;
230 }
231 } else {
232 // if the state is "connected", force the speaker off without
233 // storing the state.
234 PhoneUtils.turnOnSpeaker(mContext, false, false);
235
236 newMode = AudioMode.WIRED_HEADSET;
237 }
Santos Cordon9b7bac72013-08-06 08:04:52 -0700238 }
Santos Cordon593ab382013-08-06 21:58:23 -0700239
240 updateAudioModeTo(newMode);
Santos Cordon9b7bac72013-08-06 08:04:52 -0700241 }
242
243 private void init() {
244 mBluetoothManager.addBluetoothIndicatorListener(this);
Santos Cordon593ab382013-08-06 21:58:23 -0700245 mWiredHeadsetManager.addWiredHeadsetListener(this);
Santos Cordon9b7bac72013-08-06 08:04:52 -0700246 }
247
Santos Cordon593ab382013-08-06 21:58:23 -0700248 /**
249 * Reads the state of the world to determine Audio mode.
250 */
251 private int calculateModeFromCurrentState() {
252
253 int mode = AudioMode.EARPIECE;
254
255 // There is a very specific ordering here
256 if (mBluetoothManager.isBluetoothAudioConnectedOrPending()) {
257 mode = AudioMode.BLUETOOTH;
258 } else if (PhoneUtils.isSpeakerOn(mContext)) {
259 mode = AudioMode.SPEAKER;
260 } else if (mWiredHeadsetManager.isHeadsetPlugged()) {
261 mode = AudioMode.WIRED_HEADSET;
262 }
263
264 logD("calculateModeFromCurrentState " + AudioMode.toString(mode));
265
266 return mode;
267 }
268
269 /**
270 * Changes the audio mode to the mode in the parameter.
271 */
272 private void updateAudioModeTo(int mode) {
273 int oldSupportedModes = mSupportedModes;
274
275 mSupportedModes = calculateSupportedModes();
276
277 // This is a weird state that shouldn't happen, but we get called here
278 // once we've changed the audio layers so lets log the error, but assume
279 // that it went through. If it happens it is likely it is a race condition
280 // that will resolve itself when we get updates on the mode change.
281 if ((mSupportedModes & mode) == 0) {
282 Log.e(LOG_TAG, "Setting audio mode to an unsupported mode!");
283 }
284
285 boolean doNotify = oldSupportedModes != mSupportedModes;
286
287 // only update if it really changed.
Santos Cordon9b7bac72013-08-06 08:04:52 -0700288 if (mAudioMode != mode) {
289 Log.i(LOG_TAG, "Audio mode changing to " + AudioMode.toString(mode));
290
291 mPreviousMode = mAudioMode;
292 mAudioMode = mode;
293
Santos Cordon593ab382013-08-06 21:58:23 -0700294 doNotify = true;
295 }
296
297 if (doNotify) {
Santos Cordon9b7bac72013-08-06 08:04:52 -0700298 notifyListeners();
299 }
300 }
301
Santos Cordon593ab382013-08-06 21:58:23 -0700302 /**
303 * Gets the updates supported modes from the state of the audio systems.
304 */
305 private int calculateSupportedModes() {
306 // speaker phone always a supported state
307 int supportedModes = AudioMode.SPEAKER;
308
309 if (mWiredHeadsetManager.isHeadsetPlugged()) {
310 supportedModes |= AudioMode.WIRED_HEADSET;
311 } else {
312 supportedModes |= AudioMode.EARPIECE;
313 }
314
315 if (mBluetoothManager.isBluetoothAvailable()) {
316 supportedModes |= AudioMode.BLUETOOTH;
317 }
318
319 return supportedModes;
320 }
321
Santos Cordon9b7bac72013-08-06 08:04:52 -0700322 private void notifyListeners() {
Santos Cordon593ab382013-08-06 21:58:23 -0700323 logD("AudioMode: " + AudioMode.toString(mAudioMode));
324 logD("Supported AudioMode: " + AudioMode.toString(mSupportedModes));
325
Santos Cordon9b7bac72013-08-06 08:04:52 -0700326 for (int i = 0; i < mListeners.size(); i++) {
327 mListeners.get(i).onAudioModeChange(mPreviousMode, mAudioMode);
Santos Cordon593ab382013-08-06 21:58:23 -0700328 mListeners.get(i).onSupportedAudioModeChange(mSupportedModes);
Santos Cordon9b7bac72013-08-06 08:04:52 -0700329 }
330 }
331
332 public interface AudioModeListener {
333 void onAudioModeChange(int previousMode, int newMode);
Santos Cordon593ab382013-08-06 21:58:23 -0700334 void onSupportedAudioModeChange(int modeMask);
335 }
336
337 private void logD(String msg) {
338 if (DBG) {
339 Log.d(LOG_TAG, msg);
340 }
Santos Cordon9b7bac72013-08-06 08:04:52 -0700341 }
342}