blob: 3c8e9d34a455435e475848922321a58ea61a2568 [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
Santos Cordon96094942013-10-01 13:49:24 -070044 private static final boolean ON = true;
45 private static final boolean OFF = false;
46
Santos Cordon9b7bac72013-08-06 08:04:52 -070047 private final Context mContext;
48 private final BluetoothManager mBluetoothManager;
Santos Cordon593ab382013-08-06 21:58:23 -070049 private final WiredHeadsetManager mWiredHeadsetManager;
50 private final CallManager mCallManager;
Santos Cordon9b7bac72013-08-06 08:04:52 -070051 private final List<AudioModeListener> mListeners = Lists.newArrayList();
52 private int mAudioMode = AudioMode.EARPIECE;
53 private int mPreviousMode = AudioMode.EARPIECE;
Santos Cordon593ab382013-08-06 21:58:23 -070054 private int mSupportedModes = AudioMode.ALL_MODES;
Santos Cordon9b7bac72013-08-06 08:04:52 -070055
Santos Cordon593ab382013-08-06 21:58:23 -070056 public AudioRouter(Context context, BluetoothManager bluetoothManager,
57 WiredHeadsetManager wiredHeadsetManager, CallManager callManager) {
Santos Cordon9b7bac72013-08-06 08:04:52 -070058 mContext = context;
59 mBluetoothManager = bluetoothManager;
Santos Cordon593ab382013-08-06 21:58:23 -070060 mWiredHeadsetManager = wiredHeadsetManager;
61 mCallManager = callManager;
Santos Cordon9b7bac72013-08-06 08:04:52 -070062
63 init();
64 }
65
66 /**
67 * Return the current audio mode.
68 */
69 public int getAudioMode() {
70 return mAudioMode;
71 }
72
73 /**
Santos Cordon593ab382013-08-06 21:58:23 -070074 * Returns the currently supported audio modes.
75 */
76 public int getSupportedAudioModes() {
77 return mSupportedModes;
78 }
79
80 /**
Santos Cordoncd95f622013-08-29 03:38:52 -070081 * Returns the current mute state.
82 */
83 public boolean getMute() {
84 return PhoneUtils.getMute();
85 }
86
87 /**
Santos Cordon9b7bac72013-08-06 08:04:52 -070088 * Add a listener to audio mode changes.
89 */
90 public void addAudioModeListener(AudioModeListener listener) {
91 if (!mListeners.contains(listener)) {
92 mListeners.add(listener);
Santos Cordon593ab382013-08-06 21:58:23 -070093
94 // For first notification, mPreviousAudioMode doesn't make sense.
Santos Cordoncd95f622013-08-29 03:38:52 -070095 listener.onAudioModeChange(mAudioMode, getMute());
Santos Cordon593ab382013-08-06 21:58:23 -070096 listener.onSupportedAudioModeChange(mSupportedModes);
Santos Cordon9b7bac72013-08-06 08:04:52 -070097 }
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) {
Santos Cordon593ab382013-08-06 21:58:23 -0700113 logD("setAudioMode " + AudioMode.toString(mode));
Santos Cordon96094942013-10-01 13:49:24 -0700114 boolean error = false;
Santos Cordon9b7bac72013-08-06 08:04:52 -0700115
Santos Cordon96094942013-10-01 13:49:24 -0700116 // changes WIRED_OR_EARPIECE to appropriate single entry WIRED_HEADSET or EARPIECE
117 mode = selectWiredOrEarpiece(mode);
Santos Cordon593ab382013-08-06 21:58:23 -0700118
Santos Cordon96094942013-10-01 13:49:24 -0700119 // If mode is unsupported, do nothing.
Santos Cordon593ab382013-08-06 21:58:23 -0700120 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) {
Santos Cordon96094942013-10-01 13:49:24 -0700126 turnOnOffBluetooth(OFF);
127 turnOnOffSpeaker(ON);
Santos Cordon593ab382013-08-06 21:58:23 -0700128
129 } else if (AudioMode.BLUETOOTH == mode) {
Santos Cordon96094942013-10-01 13:49:24 -0700130 if (mBluetoothManager.isBluetoothAvailable()) {
Santos Cordon593ab382013-08-06 21:58:23 -0700131 // 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().)
Santos Cordon96094942013-10-01 13:49:24 -0700138 turnOnOffSpeaker(OFF);
139 if (!turnOnOffBluetooth(ON)) {
140 error = true;
Santos Cordon593ab382013-08-06 21:58:23 -0700141 }
Santos Cordon96094942013-10-01 13:49:24 -0700142 } else {
143 Log.e(LOG_TAG, "Asking to turn on bluetooth when no bluetooth available. " +
144 "supportedModes: " + AudioMode.toString(calculateSupportedModes()));
145 error = true;
Santos Cordon593ab382013-08-06 21:58:23 -0700146 }
147
148 // Wired headset and earpiece work the same way
149 } else if (AudioMode.EARPIECE == mode || AudioMode.WIRED_HEADSET == mode) {
Santos Cordon96094942013-10-01 13:49:24 -0700150 turnOnOffBluetooth(OFF);
151 turnOnOffSpeaker(OFF);
Santos Cordon593ab382013-08-06 21:58:23 -0700152
153 } else {
Santos Cordon96094942013-10-01 13:49:24 -0700154 error = true;
155 }
Santos Cordon593ab382013-08-06 21:58:23 -0700156
Santos Cordon96094942013-10-01 13:49:24 -0700157 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
Santos Cordon593ab382013-08-06 21:58:23 -0700162 }
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);
Santos Cordon9b7bac72013-08-06 08:04:52 -0700177 }
178 }
179
Santos Cordoncd95f622013-08-29 03:38:52 -0700180 public void onMuteChange(boolean muted) {
181 logD("onMuteChange: " + muted);
182 notifyListeners();
183 }
184
Santos Cordon9b7bac72013-08-06 08:04:52 -0700185 /**
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) {
Santos Cordon593ab382013-08-06 21:58:23 -0700191 logD("onBluetoothIndicationChange " + isConnected);
Santos Cordon9b7bac72013-08-06 08:04:52 -0700192
Santos Cordon593ab382013-08-06 21:58:23 -0700193 // this will read the new bluetooth mode appropriately
194 updateAudioModeTo(calculateModeFromCurrentState());
Santos Cordon9b7bac72013-08-06 08:04:52 -0700195 }
196
Santos Cordon593ab382013-08-06 21:58:23 -0700197 /**
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.
Santos Cordon96094942013-10-01 13:49:24 -0700208 final boolean isOffhook = (mCallManager.getState() == PhoneConstants.State.OFFHOOK);
Santos Cordon593ab382013-08-06 21:58:23 -0700209
210 int newMode = mAudioMode;
211
Santos Cordon96094942013-10-01 13:49:24 -0700212 // Change state only if we are not using bluetooth
213 if (!mBluetoothManager.isBluetoothHeadsetAudioOn()) {
Santos Cordon593ab382013-08-06 21:58:23 -0700214
Santos Cordon96094942013-10-01 13:49:24 -0700215 // 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);
Santos Cordon593ab382013-08-06 21:58:23 -0700220
Santos Cordon96094942013-10-01 13:49:24 -0700221 if (PhoneUtils.isSpeakerOn(mContext)) {
222 newMode = AudioMode.SPEAKER;
223 } else {
224 newMode = AudioMode.EARPIECE;
225 }
Santos Cordon593ab382013-08-06 21:58:23 -0700226 } else {
Santos Cordon96094942013-10-01 13:49:24 -0700227 // if the state is "connected", force the speaker off without
228 // storing the state.
229 PhoneUtils.turnOnSpeaker(mContext, false, false);
Santos Cordon593ab382013-08-06 21:58:23 -0700230
Santos Cordon96094942013-10-01 13:49:24 -0700231 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;
Santos Cordon593ab382013-08-06 21:58:23 -0700237 }
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
Santos Cordon96094942013-10-01 13:49:24 -0700243 /**
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
Santos Cordon9b7bac72013-08-06 08:04:52 -0700295 private void init() {
296 mBluetoothManager.addBluetoothIndicatorListener(this);
Santos Cordon593ab382013-08-06 21:58:23 -0700297 mWiredHeadsetManager.addWiredHeadsetListener(this);
Santos Cordon9b7bac72013-08-06 08:04:52 -0700298 }
299
Santos Cordon593ab382013-08-06 21:58:23 -0700300 /**
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
Santos Cordon2c2d3cf2013-08-08 03:53:47 -0700308 if (mBluetoothManager.showBluetoothIndication()) {
Santos Cordon593ab382013-08-06 21:58:23 -0700309 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) {
Santos Cordon96094942013-10-01 13:49:24 -0700334 Log.e(LOG_TAG, "Setting audio mode to an unsupported mode: " +
335 AudioMode.toString(mode) + ", supported (" +
336 AudioMode.toString(mSupportedModes) + ")");
Santos Cordon593ab382013-08-06 21:58:23 -0700337 }
338
339 boolean doNotify = oldSupportedModes != mSupportedModes;
340
341 // only update if it really changed.
Santos Cordon9b7bac72013-08-06 08:04:52 -0700342 if (mAudioMode != mode) {
343 Log.i(LOG_TAG, "Audio mode changing to " + AudioMode.toString(mode));
Santos Cordon593ab382013-08-06 21:58:23 -0700344 doNotify = true;
345 }
346
Santos Cordon2c2d3cf2013-08-08 03:53:47 -0700347 mPreviousMode = mAudioMode;
348 mAudioMode = mode;
349
Santos Cordon593ab382013-08-06 21:58:23 -0700350 if (doNotify) {
Santos Cordon9b7bac72013-08-06 08:04:52 -0700351 notifyListeners();
352 }
353 }
354
Santos Cordon593ab382013-08-06 21:58:23 -0700355 /**
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
Santos Cordon9b7bac72013-08-06 08:04:52 -0700375 private void notifyListeners() {
Santos Cordon593ab382013-08-06 21:58:23 -0700376 logD("AudioMode: " + AudioMode.toString(mAudioMode));
377 logD("Supported AudioMode: " + AudioMode.toString(mSupportedModes));
378
Santos Cordon9b7bac72013-08-06 08:04:52 -0700379 for (int i = 0; i < mListeners.size(); i++) {
Santos Cordoncd95f622013-08-29 03:38:52 -0700380 mListeners.get(i).onAudioModeChange(mAudioMode, getMute());
Santos Cordon593ab382013-08-06 21:58:23 -0700381 mListeners.get(i).onSupportedAudioModeChange(mSupportedModes);
Santos Cordon9b7bac72013-08-06 08:04:52 -0700382 }
383 }
384
385 public interface AudioModeListener {
Santos Cordoncd95f622013-08-29 03:38:52 -0700386 void onAudioModeChange(int newMode, boolean muted);
Santos Cordon593ab382013-08-06 21:58:23 -0700387 void onSupportedAudioModeChange(int modeMask);
388 }
389
390 private void logD(String msg) {
391 if (DBG) {
392 Log.d(LOG_TAG, msg);
393 }
Santos Cordon9b7bac72013-08-06 08:04:52 -0700394 }
395}