blob: 00fc13102860819cbb16becd083fb4021a3b72c6 [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 Cordoncd95f622013-08-29 03:38:52 -070078 * Returns the current mute state.
79 */
80 public boolean getMute() {
81 return PhoneUtils.getMute();
82 }
83
84 /**
Santos Cordon9b7bac72013-08-06 08:04:52 -070085 * Add a listener to audio mode changes.
86 */
87 public void addAudioModeListener(AudioModeListener listener) {
88 if (!mListeners.contains(listener)) {
89 mListeners.add(listener);
Santos Cordon593ab382013-08-06 21:58:23 -070090
91 // For first notification, mPreviousAudioMode doesn't make sense.
Santos Cordoncd95f622013-08-29 03:38:52 -070092 listener.onAudioModeChange(mAudioMode, getMute());
Santos Cordon593ab382013-08-06 21:58:23 -070093 listener.onSupportedAudioModeChange(mSupportedModes);
Santos Cordon9b7bac72013-08-06 08:04:52 -070094 }
95 }
96
97 /**
98 * Remove listener.
99 */
100 public void removeAudioModeListener(AudioModeListener listener) {
101 if (mListeners.contains(listener)) {
102 mListeners.remove(listener);
103 }
104 }
105
106 /**
107 * Sets the audio mode to the mode that is passed in.
108 */
109 public void setAudioMode(int mode) {
Santos Cordon593ab382013-08-06 21:58:23 -0700110 logD("setAudioMode " + AudioMode.toString(mode));
Santos Cordon9b7bac72013-08-06 08:04:52 -0700111
Santos Cordon593ab382013-08-06 21:58:23 -0700112 // Since they are mutually exclusive and one is ALWAYS valid, we allow a special input of
113 // WIRED_OR_EARPIECE so that callers dont have to make a call to check which is supported
114 // before calling setAudioMode.
115 if (mode == AudioMode.WIRED_OR_EARPIECE) {
116 mode = AudioMode.WIRED_OR_EARPIECE & mSupportedModes;
117
118 if (mode == 0) {
119 Log.wtf(LOG_TAG, "One of wired headset or earpiece should always be valid.");
120 // assume earpiece in this case.
121 mode = AudioMode.EARPIECE;
122 }
123 }
124
125 if ((calculateSupportedModes() | mode) == 0) {
126 Log.wtf(LOG_TAG, "Asking to set to a mode that is unsupported: " + mode);
127 return;
128 }
129
130 if (AudioMode.SPEAKER == mode) {
131
132 if (!PhoneUtils.isSpeakerOn(mContext)) {
133 // Switch away from Bluetooth, if it was active.
134 if (mBluetoothManager.isBluetoothAvailable() &&
135 mBluetoothManager.isBluetoothAudioConnected()) {
136
137 mBluetoothManager.disconnectBluetoothAudio();
138 }
139 PhoneUtils.turnOnSpeaker(mContext, true, true);
140 }
141
142 } else if (AudioMode.BLUETOOTH == mode) {
143
144 // If already connected to BT, there's nothing to do here.
145 if (mBluetoothManager.isBluetoothAvailable() &&
146 !mBluetoothManager.isBluetoothAudioConnected()) {
147 // Manually turn the speaker phone off, instead of allowing the
148 // Bluetooth audio routing to handle it, since there's other
149 // important state-updating that needs to happen in the
150 // PhoneUtils.turnOnSpeaker() method.
151 // (Similarly, whenever the user turns *on* the speaker, we
152 // manually disconnect the active bluetooth headset;
153 // see toggleSpeaker() and/or switchInCallAudio().)
154 if (PhoneUtils.isSpeakerOn(mContext)) {
155 PhoneUtils.turnOnSpeaker(mContext, false, true);
156 }
157 mBluetoothManager.connectBluetoothAudio();
158 }
159
160 // Wired headset and earpiece work the same way
161 } else if (AudioMode.EARPIECE == mode || AudioMode.WIRED_HEADSET == mode) {
162
163 // Switch to either the handset earpiece, or the wired headset (if connected.)
164 // (Do this by simply making sure both speaker and bluetooth are off.)
165 if (mBluetoothManager.isBluetoothAvailable() &&
166 mBluetoothManager.isBluetoothAudioConnected()) {
167 mBluetoothManager.disconnectBluetoothAudio();
168 }
169 if (PhoneUtils.isSpeakerOn(mContext)) {
170 PhoneUtils.turnOnSpeaker(mContext, false, true);
171 }
172
173 } else {
174 Log.wtf(LOG_TAG, "Asking us to set to an unsupported mode " +
175 AudioMode.toString(mode) + " (" + mode + ")");
176
177 // set it to the current audio mode
178 mode = mAudioMode;
179 }
180
181 updateAudioModeTo(mode);
182 }
183
184 /**
185 * Turns on speaker.
186 */
187 public void setSpeaker(boolean on) {
188 logD("setSpeaker " + on);
189
190 if (on) {
191 setAudioMode(AudioMode.SPEAKER);
192 } else {
193 setAudioMode(AudioMode.WIRED_OR_EARPIECE);
Santos Cordon9b7bac72013-08-06 08:04:52 -0700194 }
195 }
196
Santos Cordoncd95f622013-08-29 03:38:52 -0700197 public void onMuteChange(boolean muted) {
198 logD("onMuteChange: " + muted);
199 notifyListeners();
200 }
201
Santos Cordon9b7bac72013-08-06 08:04:52 -0700202 /**
203 * Called when the bluetooth connection changes.
204 * We adjust the audio mode according to the state we receive.
205 */
206 @Override
207 public void onBluetoothIndicationChange(boolean isConnected, BluetoothManager btManager) {
Santos Cordon593ab382013-08-06 21:58:23 -0700208 logD("onBluetoothIndicationChange " + isConnected);
Santos Cordon9b7bac72013-08-06 08:04:52 -0700209
Santos Cordon593ab382013-08-06 21:58:23 -0700210 // this will read the new bluetooth mode appropriately
211 updateAudioModeTo(calculateModeFromCurrentState());
Santos Cordon9b7bac72013-08-06 08:04:52 -0700212 }
213
Santos Cordon593ab382013-08-06 21:58:23 -0700214 /**
215 * Called when the state of the wired headset changes.
216 */
217 @Override
218 public void onWiredHeadsetConnection(boolean pluggedIn) {
219 logD("onWireHeadsetConnection " + pluggedIn);
220
221 // Since the presence of a wired headset or bluetooth affects the
222 // speakerphone, update the "speaker" state. We ONLY want to do
223 // this on the wired headset connect / disconnect events for now
224 // though.
225 PhoneConstants.State phoneState = mCallManager.getState();
226
227 int newMode = mAudioMode;
228
229 // TODO: Consider using our stored states instead
230
231 // Do not change speaker state if phone is not off hook
232 if (phoneState == PhoneConstants.State.OFFHOOK &&
233 !mBluetoothManager.isBluetoothHeadsetAudioOn()) {
234 if (!pluggedIn) {
235 // if the state is "not connected", restore the speaker state.
236 PhoneUtils.restoreSpeakerMode(mContext);
237
238 if (PhoneUtils.isSpeakerOn(mContext)) {
239 newMode = AudioMode.SPEAKER;
240 } else {
241 newMode = AudioMode.EARPIECE;
242 }
243 } else {
244 // if the state is "connected", force the speaker off without
245 // storing the state.
246 PhoneUtils.turnOnSpeaker(mContext, false, false);
247
248 newMode = AudioMode.WIRED_HEADSET;
249 }
Santos Cordon9b7bac72013-08-06 08:04:52 -0700250 }
Santos Cordon593ab382013-08-06 21:58:23 -0700251
252 updateAudioModeTo(newMode);
Santos Cordon9b7bac72013-08-06 08:04:52 -0700253 }
254
255 private void init() {
256 mBluetoothManager.addBluetoothIndicatorListener(this);
Santos Cordon593ab382013-08-06 21:58:23 -0700257 mWiredHeadsetManager.addWiredHeadsetListener(this);
Santos Cordon9b7bac72013-08-06 08:04:52 -0700258 }
259
Santos Cordon593ab382013-08-06 21:58:23 -0700260 /**
261 * Reads the state of the world to determine Audio mode.
262 */
263 private int calculateModeFromCurrentState() {
264
265 int mode = AudioMode.EARPIECE;
266
267 // There is a very specific ordering here
Santos Cordon2c2d3cf2013-08-08 03:53:47 -0700268 if (mBluetoothManager.showBluetoothIndication()) {
Santos Cordon593ab382013-08-06 21:58:23 -0700269 mode = AudioMode.BLUETOOTH;
270 } else if (PhoneUtils.isSpeakerOn(mContext)) {
271 mode = AudioMode.SPEAKER;
272 } else if (mWiredHeadsetManager.isHeadsetPlugged()) {
273 mode = AudioMode.WIRED_HEADSET;
274 }
275
276 logD("calculateModeFromCurrentState " + AudioMode.toString(mode));
277
278 return mode;
279 }
280
281 /**
282 * Changes the audio mode to the mode in the parameter.
283 */
284 private void updateAudioModeTo(int mode) {
285 int oldSupportedModes = mSupportedModes;
286
287 mSupportedModes = calculateSupportedModes();
288
289 // This is a weird state that shouldn't happen, but we get called here
290 // once we've changed the audio layers so lets log the error, but assume
291 // that it went through. If it happens it is likely it is a race condition
292 // that will resolve itself when we get updates on the mode change.
293 if ((mSupportedModes & mode) == 0) {
294 Log.e(LOG_TAG, "Setting audio mode to an unsupported mode!");
295 }
296
297 boolean doNotify = oldSupportedModes != mSupportedModes;
298
299 // only update if it really changed.
Santos Cordon9b7bac72013-08-06 08:04:52 -0700300 if (mAudioMode != mode) {
301 Log.i(LOG_TAG, "Audio mode changing to " + AudioMode.toString(mode));
Santos Cordon593ab382013-08-06 21:58:23 -0700302 doNotify = true;
303 }
304
Santos Cordon2c2d3cf2013-08-08 03:53:47 -0700305 mPreviousMode = mAudioMode;
306 mAudioMode = mode;
307
Santos Cordon593ab382013-08-06 21:58:23 -0700308 if (doNotify) {
Santos Cordon9b7bac72013-08-06 08:04:52 -0700309 notifyListeners();
310 }
311 }
312
Santos Cordon593ab382013-08-06 21:58:23 -0700313 /**
314 * Gets the updates supported modes from the state of the audio systems.
315 */
316 private int calculateSupportedModes() {
317 // speaker phone always a supported state
318 int supportedModes = AudioMode.SPEAKER;
319
320 if (mWiredHeadsetManager.isHeadsetPlugged()) {
321 supportedModes |= AudioMode.WIRED_HEADSET;
322 } else {
323 supportedModes |= AudioMode.EARPIECE;
324 }
325
326 if (mBluetoothManager.isBluetoothAvailable()) {
327 supportedModes |= AudioMode.BLUETOOTH;
328 }
329
330 return supportedModes;
331 }
332
Santos Cordon9b7bac72013-08-06 08:04:52 -0700333 private void notifyListeners() {
Santos Cordon593ab382013-08-06 21:58:23 -0700334 logD("AudioMode: " + AudioMode.toString(mAudioMode));
335 logD("Supported AudioMode: " + AudioMode.toString(mSupportedModes));
336
Santos Cordon9b7bac72013-08-06 08:04:52 -0700337 for (int i = 0; i < mListeners.size(); i++) {
Santos Cordoncd95f622013-08-29 03:38:52 -0700338 mListeners.get(i).onAudioModeChange(mAudioMode, getMute());
Santos Cordon593ab382013-08-06 21:58:23 -0700339 mListeners.get(i).onSupportedAudioModeChange(mSupportedModes);
Santos Cordon9b7bac72013-08-06 08:04:52 -0700340 }
341 }
342
343 public interface AudioModeListener {
Santos Cordoncd95f622013-08-29 03:38:52 -0700344 void onAudioModeChange(int newMode, boolean muted);
Santos Cordon593ab382013-08-06 21:58:23 -0700345 void onSupportedAudioModeChange(int modeMask);
346 }
347
348 private void logD(String msg) {
349 if (DBG) {
350 Log.d(LOG_TAG, msg);
351 }
Santos Cordon9b7bac72013-08-06 08:04:52 -0700352 }
353}