blob: c24a9c0299e9499ff5dc2f285255d8764a8e96e4 [file] [log] [blame]
Sailesh Nepal810735e2014-03-18 18:15:46 -07001/*
2 * Copyright (C) 2014 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
Tyler Gunn7cc70b42014-09-12 22:17:27 -070017package com.android.server.telecom;
Sailesh Nepal810735e2014-03-18 18:15:46 -070018
19import android.content.Context;
20import android.media.AudioManager;
Tyler Gunn7cc70b42014-09-12 22:17:27 -070021import android.telecom.AudioState;
22import android.telecom.CallState;
Sailesh Nepal810735e2014-03-18 18:15:46 -070023
Tyler Gunn91d43cf2014-09-17 12:19:39 -070024import com.android.internal.util.Preconditions;
Santos Cordon1ae2b852014-03-19 03:03:10 -070025
Santos Cordon14ff8382014-08-05 20:44:27 -070026import java.util.Objects;
27
Sailesh Nepal810735e2014-03-18 18:15:46 -070028/**
29 * This class manages audio modes, streams and other properties.
30 */
Sailesh Nepalb88795a2014-07-15 14:53:27 -070031final class CallAudioManager extends CallsManagerListenerBase
32 implements WiredHeadsetManager.Listener {
Sailesh Nepal6aca10a2014-03-24 16:11:02 -070033 private static final int STREAM_NONE = -1;
Santos Cordon1ae2b852014-03-19 03:03:10 -070034
Santos Cordondeb8c892014-05-30 01:38:03 -070035 private final StatusBarNotifier mStatusBarNotifier;
Sailesh Nepal6aca10a2014-03-24 16:11:02 -070036 private final AudioManager mAudioManager;
Santos Cordonc7e85d42014-05-22 02:51:48 -070037 private final BluetoothManager mBluetoothManager;
Sailesh Nepalb88795a2014-07-15 14:53:27 -070038 private final WiredHeadsetManager mWiredHeadsetManager;
Santos Cordondeb8c892014-05-30 01:38:03 -070039
Ihab Awad6fb37c82014-08-07 19:48:57 -070040 private AudioState mAudioState;
Sailesh Nepal6aca10a2014-03-24 16:11:02 -070041 private int mAudioFocusStreamType;
42 private boolean mIsRinging;
Santos Cordona56f2762014-03-24 15:55:53 -070043 private boolean mIsTonePlaying;
Sailesh Nepal6aca10a2014-03-24 16:11:02 -070044 private boolean mWasSpeakerOn;
Santos Cordon14ff8382014-08-05 20:44:27 -070045 private int mMostRecentlyUsedMode = AudioManager.MODE_IN_CALL;
Santos Cordon1ae2b852014-03-19 03:03:10 -070046
Sailesh Nepalb88795a2014-07-15 14:53:27 -070047 CallAudioManager(Context context, StatusBarNotifier statusBarNotifier,
48 WiredHeadsetManager wiredHeadsetManager) {
Santos Cordondeb8c892014-05-30 01:38:03 -070049 mStatusBarNotifier = statusBarNotifier;
Sailesh Nepal6aca10a2014-03-24 16:11:02 -070050 mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
Santos Cordonc7e85d42014-05-22 02:51:48 -070051 mBluetoothManager = new BluetoothManager(context, this);
Sailesh Nepalb88795a2014-07-15 14:53:27 -070052 mWiredHeadsetManager = wiredHeadsetManager;
Sailesh Nepald0a76aa2014-07-16 22:12:23 -070053 mWiredHeadsetManager.addListener(this);
Sailesh Nepalb88795a2014-07-15 14:53:27 -070054
Santos Cordondeb8c892014-05-30 01:38:03 -070055 saveAudioState(getInitialAudioState(null));
Sailesh Nepal6aca10a2014-03-24 16:11:02 -070056 mAudioFocusStreamType = STREAM_NONE;
57 }
Santos Cordon1ae2b852014-03-19 03:03:10 -070058
Ihab Awad6fb37c82014-08-07 19:48:57 -070059 AudioState getAudioState() {
Sailesh Nepal6aca10a2014-03-24 16:11:02 -070060 return mAudioState;
61 }
Santos Cordon1ae2b852014-03-19 03:03:10 -070062
63 @Override
64 public void onCallAdded(Call call) {
Santos Cordon14ff8382014-08-05 20:44:27 -070065 onCallUpdated(call);
66
67 if (hasFocus() && getForegroundCall() == call) {
68 if (!call.isIncoming()) {
69 // Unmute new outgoing call.
70 setSystemAudioState(false, mAudioState.route, mAudioState.supportedRouteMask);
71 }
Santos Cordon1ae2b852014-03-19 03:03:10 -070072 }
Santos Cordon1ae2b852014-03-19 03:03:10 -070073 }
74
75 @Override
76 public void onCallRemoved(Call call) {
Santos Cordon14ff8382014-08-05 20:44:27 -070077 // If we didn't already have focus, there's nothing to do.
78 if (hasFocus()) {
79 if (CallsManager.getInstance().getCalls().isEmpty()) {
80 Log.v(this, "all calls removed, reseting system audio to default state");
Santos Cordon7c6a5ec2014-09-11 19:47:23 -070081 setInitialAudioState(null, false /* force */);
Santos Cordon14ff8382014-08-05 20:44:27 -070082 mWasSpeakerOn = false;
83 }
84 updateAudioStreamAndMode();
Sailesh Nepal6aca10a2014-03-24 16:11:02 -070085 }
Santos Cordon1ae2b852014-03-19 03:03:10 -070086 }
87
Sailesh Nepal810735e2014-03-18 18:15:46 -070088 @Override
Ihab Awad6fb37c82014-08-07 19:48:57 -070089 public void onCallStateChanged(Call call, int oldState, int newState) {
Santos Cordon14ff8382014-08-05 20:44:27 -070090 onCallUpdated(call);
Santos Cordon1ae2b852014-03-19 03:03:10 -070091 }
92
93 @Override
94 public void onIncomingCallAnswered(Call call) {
Santos Cordonc7e85d42014-05-22 02:51:48 -070095 int route = mAudioState.route;
96
97 // We do two things:
98 // (1) If this is the first call, then we can to turn on bluetooth if available.
99 // (2) Unmute the audio for the new incoming call.
100 boolean isOnlyCall = CallsManager.getInstance().getCalls().size() == 1;
101 if (isOnlyCall && mBluetoothManager.isBluetoothAvailable()) {
102 mBluetoothManager.connectBluetoothAudio();
Ihab Awad6fb37c82014-08-07 19:48:57 -0700103 route = AudioState.ROUTE_BLUETOOTH;
Santos Cordonc7e85d42014-05-22 02:51:48 -0700104 }
105
106 setSystemAudioState(false /* isMute */, route, mAudioState.supportedRouteMask);
Santos Cordon1ae2b852014-03-19 03:03:10 -0700107 }
108
109 @Override
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700110 public void onForegroundCallChanged(Call oldForegroundCall, Call newForegroundCall) {
Santos Cordon14ff8382014-08-05 20:44:27 -0700111 onCallUpdated(newForegroundCall);
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700112 // Ensure that the foreground call knows about the latest audio state.
113 updateAudioForForegroundCall();
Santos Cordon1ae2b852014-03-19 03:03:10 -0700114 }
115
Sailesh Nepal7e669572014-07-08 21:29:12 -0700116 @Override
Andrew Lee5be64bc2014-09-08 18:35:15 -0700117 public void onIsVoipAudioModeChanged(Call call) {
Sailesh Nepal7e669572014-07-08 21:29:12 -0700118 updateAudioStreamAndMode();
119 }
120
Sailesh Nepalb88795a2014-07-15 14:53:27 -0700121 /**
122 * Updates the audio route when the headset plugged in state changes. For example, if audio is
123 * being routed over speakerphone and a headset is plugged in then switch to wired headset.
124 */
125 @Override
126 public void onWiredHeadsetPluggedInChanged(boolean oldIsPluggedIn, boolean newIsPluggedIn) {
Santos Cordon14ff8382014-08-05 20:44:27 -0700127 // This can happen even when there are no calls and we don't have focus.
128 if (!hasFocus()) {
129 return;
130 }
131
Ihab Awad6fb37c82014-08-07 19:48:57 -0700132 int newRoute = AudioState.ROUTE_EARPIECE;
Sailesh Nepalb88795a2014-07-15 14:53:27 -0700133 if (newIsPluggedIn) {
Ihab Awad6fb37c82014-08-07 19:48:57 -0700134 newRoute = AudioState.ROUTE_WIRED_HEADSET;
Sailesh Nepalb88795a2014-07-15 14:53:27 -0700135 } else if (mWasSpeakerOn) {
136 Call call = getForegroundCall();
137 if (call != null && call.isAlive()) {
138 // Restore the speaker state.
Ihab Awad6fb37c82014-08-07 19:48:57 -0700139 newRoute = AudioState.ROUTE_SPEAKER;
Sailesh Nepalb88795a2014-07-15 14:53:27 -0700140 }
141 }
142 setSystemAudioState(mAudioState.isMuted, newRoute, calculateSupportedRoutes());
143 }
144
Santos Cordondeb8c892014-05-30 01:38:03 -0700145 void toggleMute() {
146 mute(!mAudioState.isMuted);
147 }
148
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700149 void mute(boolean shouldMute) {
Santos Cordon14ff8382014-08-05 20:44:27 -0700150 if (!hasFocus()) {
151 return;
152 }
153
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700154 Log.v(this, "mute, shouldMute: %b", shouldMute);
Santos Cordon1ae2b852014-03-19 03:03:10 -0700155
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700156 // Don't mute if there are any emergency calls.
157 if (CallsManager.getInstance().hasEmergencyCall()) {
158 shouldMute = false;
159 Log.v(this, "ignoring mute for emergency call");
Santos Cordon1ae2b852014-03-19 03:03:10 -0700160 }
161
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700162 if (mAudioState.isMuted != shouldMute) {
163 setSystemAudioState(shouldMute, mAudioState.route, mAudioState.supportedRouteMask);
Sailesh Nepal810735e2014-03-18 18:15:46 -0700164 }
165 }
166
Santos Cordon1ae2b852014-03-19 03:03:10 -0700167 /**
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700168 * Changed the audio route, for example from earpiece to speaker phone.
Santos Cordon1ae2b852014-03-19 03:03:10 -0700169 *
Ihab Awad6fb37c82014-08-07 19:48:57 -0700170 * @param route The new audio route to use. See {@link AudioState}.
Santos Cordon1ae2b852014-03-19 03:03:10 -0700171 */
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700172 void setAudioRoute(int route) {
Santos Cordon14ff8382014-08-05 20:44:27 -0700173 // This can happen even when there are no calls and we don't have focus.
174 if (!hasFocus()) {
175 return;
176 }
177
Ihab Awad6fb37c82014-08-07 19:48:57 -0700178 Log.v(this, "setAudioRoute, route: %s", AudioState.audioRouteToString(route));
Santos Cordon1ae2b852014-03-19 03:03:10 -0700179
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700180 // Change ROUTE_WIRED_OR_EARPIECE to a single entry.
181 int newRoute = selectWiredOrEarpiece(route, mAudioState.supportedRouteMask);
182
183 // If route is unsupported, do nothing.
184 if ((mAudioState.supportedRouteMask | newRoute) == 0) {
185 Log.wtf(this, "Asking to set to a route that is unsupported: %d", newRoute);
186 return;
187 }
188
189 if (mAudioState.route != newRoute) {
190 // Remember the new speaker state so it can be restored when the user plugs and unplugs
191 // a headset.
Ihab Awad6fb37c82014-08-07 19:48:57 -0700192 mWasSpeakerOn = newRoute == AudioState.ROUTE_SPEAKER;
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700193 setSystemAudioState(mAudioState.isMuted, newRoute, mAudioState.supportedRouteMask);
194 }
195 }
196
197 void setIsRinging(boolean isRinging) {
198 if (mIsRinging != isRinging) {
199 Log.v(this, "setIsRinging %b -> %b", mIsRinging, isRinging);
200 mIsRinging = isRinging;
201 updateAudioStreamAndMode();
Santos Cordon1ae2b852014-03-19 03:03:10 -0700202 }
Sailesh Nepal810735e2014-03-18 18:15:46 -0700203 }
204
Santos Cordon1ae2b852014-03-19 03:03:10 -0700205 /**
Santos Cordona56f2762014-03-24 15:55:53 -0700206 * Sets the tone playing status. Some tones can play even when there are no live calls and this
207 * status indicates that we should keep audio focus even for tones that play beyond the life of
208 * calls.
209 *
210 * @param isPlayingNew The status to set.
211 */
212 void setIsTonePlaying(boolean isPlayingNew) {
213 ThreadUtil.checkOnMainThread();
214
215 if (mIsTonePlaying != isPlayingNew) {
216 Log.v(this, "mIsTonePlaying %b -> %b.", mIsTonePlaying, isPlayingNew);
217 mIsTonePlaying = isPlayingNew;
218 updateAudioStreamAndMode();
219 }
220 }
221
222 /**
Santos Cordonc7e85d42014-05-22 02:51:48 -0700223 * Updates the audio routing according to the bluetooth state.
224 */
225 void onBluetoothStateChange(BluetoothManager bluetoothManager) {
Santos Cordon14ff8382014-08-05 20:44:27 -0700226 // This can happen even when there are no calls and we don't have focus.
227 if (!hasFocus()) {
228 return;
229 }
230
Santos Cordon31953642014-09-12 03:23:59 -0700231 int supportedRoutes = calculateSupportedRoutes();
Santos Cordonc7e85d42014-05-22 02:51:48 -0700232 int newRoute = mAudioState.route;
233 if (bluetoothManager.isBluetoothAudioConnectedOrPending()) {
Ihab Awad6fb37c82014-08-07 19:48:57 -0700234 newRoute = AudioState.ROUTE_BLUETOOTH;
235 } else if (mAudioState.route == AudioState.ROUTE_BLUETOOTH) {
Santos Cordon31953642014-09-12 03:23:59 -0700236 newRoute = selectWiredOrEarpiece(AudioState.ROUTE_WIRED_OR_EARPIECE, supportedRoutes);
Santos Cordonc7e85d42014-05-22 02:51:48 -0700237 // Do not switch to speaker when bluetooth disconnects.
238 mWasSpeakerOn = false;
239 }
240
Santos Cordon31953642014-09-12 03:23:59 -0700241 setSystemAudioState(mAudioState.isMuted, newRoute, supportedRoutes);
Santos Cordonc7e85d42014-05-22 02:51:48 -0700242 }
243
244 boolean isBluetoothAudioOn() {
245 return mBluetoothManager.isBluetoothAudioConnected();
246 }
247
248 boolean isBluetoothDeviceAvailable() {
249 return mBluetoothManager.isBluetoothAvailable();
250 }
251
Ihab Awad6fb37c82014-08-07 19:48:57 -0700252 private void saveAudioState(AudioState audioState) {
Santos Cordondeb8c892014-05-30 01:38:03 -0700253 mAudioState = audioState;
254 mStatusBarNotifier.notifyMute(mAudioState.isMuted);
Ihab Awad6fb37c82014-08-07 19:48:57 -0700255 mStatusBarNotifier.notifySpeakerphone(mAudioState.route == AudioState.ROUTE_SPEAKER);
Santos Cordondeb8c892014-05-30 01:38:03 -0700256 }
257
Santos Cordon14ff8382014-08-05 20:44:27 -0700258 private void onCallUpdated(Call call) {
259 boolean wasNotVoiceCall = mAudioFocusStreamType != AudioManager.STREAM_VOICE_CALL;
260 updateAudioStreamAndMode();
261
262 // If we transition from not voice call to voice call, we need to set an initial state.
263 if (wasNotVoiceCall && mAudioFocusStreamType == AudioManager.STREAM_VOICE_CALL) {
Santos Cordon7c6a5ec2014-09-11 19:47:23 -0700264 setInitialAudioState(call, true /* force */);
Santos Cordon14ff8382014-08-05 20:44:27 -0700265 }
266 }
267
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700268 private void setSystemAudioState(boolean isMuted, int route, int supportedRouteMask) {
Santos Cordon7c6a5ec2014-09-11 19:47:23 -0700269 setSystemAudioState(false /* force */, isMuted, route, supportedRouteMask);
270 }
271
272 private void setSystemAudioState(
273 boolean force, boolean isMuted, int route, int supportedRouteMask) {
Santos Cordon14ff8382014-08-05 20:44:27 -0700274 if (!hasFocus()) {
275 return;
276 }
277
Ihab Awad6fb37c82014-08-07 19:48:57 -0700278 AudioState oldAudioState = mAudioState;
279 saveAudioState(new AudioState(isMuted, route, supportedRouteMask));
Santos Cordon7c6a5ec2014-09-11 19:47:23 -0700280 if (!force && Objects.equals(oldAudioState, mAudioState)) {
Santos Cordon14ff8382014-08-05 20:44:27 -0700281 return;
282 }
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700283 Log.i(this, "changing audio state from %s to %s", oldAudioState, mAudioState);
Santos Cordon1ae2b852014-03-19 03:03:10 -0700284
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700285 // Mute.
286 if (mAudioState.isMuted != mAudioManager.isMicrophoneMute()) {
287 Log.i(this, "changing microphone mute state to: %b", mAudioState.isMuted);
288 mAudioManager.setMicrophoneMute(mAudioState.isMuted);
Santos Cordon1ae2b852014-03-19 03:03:10 -0700289 }
290
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700291 // Audio route.
Ihab Awad6fb37c82014-08-07 19:48:57 -0700292 if (mAudioState.route == AudioState.ROUTE_BLUETOOTH) {
Santos Cordonc7e85d42014-05-22 02:51:48 -0700293 turnOnSpeaker(false);
294 turnOnBluetooth(true);
Ihab Awad6fb37c82014-08-07 19:48:57 -0700295 } else if (mAudioState.route == AudioState.ROUTE_SPEAKER) {
Santos Cordonc7e85d42014-05-22 02:51:48 -0700296 turnOnBluetooth(false);
297 turnOnSpeaker(true);
Ihab Awad6fb37c82014-08-07 19:48:57 -0700298 } else if (mAudioState.route == AudioState.ROUTE_EARPIECE ||
299 mAudioState.route == AudioState.ROUTE_WIRED_HEADSET) {
Santos Cordonc7e85d42014-05-22 02:51:48 -0700300 turnOnBluetooth(false);
301 turnOnSpeaker(false);
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700302 }
303
304 if (!oldAudioState.equals(mAudioState)) {
305 CallsManager.getInstance().onAudioStateChanged(oldAudioState, mAudioState);
306 updateAudioForForegroundCall();
307 }
308 }
309
Santos Cordonc7e85d42014-05-22 02:51:48 -0700310 private void turnOnSpeaker(boolean on) {
311 // Wired headset and earpiece work the same way
312 if (mAudioManager.isSpeakerphoneOn() != on) {
Santos Cordon14ff8382014-08-05 20:44:27 -0700313 Log.i(this, "turning speaker phone %s", on);
Santos Cordonc7e85d42014-05-22 02:51:48 -0700314 mAudioManager.setSpeakerphoneOn(on);
315 }
316 }
317
318 private void turnOnBluetooth(boolean on) {
319 if (mBluetoothManager.isBluetoothAvailable()) {
Santos Cordon14ff8382014-08-05 20:44:27 -0700320 boolean isAlreadyOn = mBluetoothManager.isBluetoothAudioConnectedOrPending();
Santos Cordonc7e85d42014-05-22 02:51:48 -0700321 if (on != isAlreadyOn) {
Santos Cordon14ff8382014-08-05 20:44:27 -0700322 Log.i(this, "connecting bluetooth %s", on);
Santos Cordonc7e85d42014-05-22 02:51:48 -0700323 if (on) {
324 mBluetoothManager.connectBluetoothAudio();
325 } else {
326 mBluetoothManager.disconnectBluetoothAudio();
327 }
328 }
329 }
330 }
331
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700332 private void updateAudioStreamAndMode() {
Yorke Leee5a7c922014-10-16 16:50:51 -0700333 Log.i(this, "updateAudioStreamAndMode, mIsRinging: %b, mIsTonePlaying: %b", mIsRinging,
Santos Cordona56f2762014-03-24 15:55:53 -0700334 mIsTonePlaying);
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700335 if (mIsRinging) {
336 requestAudioFocusAndSetMode(AudioManager.STREAM_RING, AudioManager.MODE_RINGTONE);
337 } else {
Santos Cordon5ba7f272014-05-28 13:59:49 -0700338 Call call = getForegroundCall();
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700339 if (call != null) {
Andrew Lee5be64bc2014-09-08 18:35:15 -0700340 int mode = call.getIsVoipAudioMode() ?
Sailesh Nepal7e669572014-07-08 21:29:12 -0700341 AudioManager.MODE_IN_COMMUNICATION : AudioManager.MODE_IN_CALL;
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700342 requestAudioFocusAndSetMode(AudioManager.STREAM_VOICE_CALL, mode);
Santos Cordona56f2762014-03-24 15:55:53 -0700343 } else if (mIsTonePlaying) {
344 // There is no call, however, we are still playing a tone, so keep focus.
Santos Cordon14ff8382014-08-05 20:44:27 -0700345 // Since there is no call from which to determine the mode, use the most
346 // recently used mode instead.
Santos Cordona56f2762014-03-24 15:55:53 -0700347 requestAudioFocusAndSetMode(
Santos Cordon14ff8382014-08-05 20:44:27 -0700348 AudioManager.STREAM_VOICE_CALL, mMostRecentlyUsedMode);
Yorke Lee42afb972014-09-08 09:47:21 -0700349 } else if (!hasRingingForegroundCall()) {
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700350 abandonAudioFocus();
Yorke Lee42afb972014-09-08 09:47:21 -0700351 } else {
352 // mIsRinging is false, but there is a foreground ringing call present. Don't
353 // abandon audio focus immediately to prevent audio focus from getting lost between
354 // the time it takes for the foreground call to transition from RINGING to ACTIVE/
355 // DISCONNECTED. When the call eventually transitions to the next state, audio
356 // focus will be correctly abandoned by the if clause above.
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700357 }
358 }
359 }
360
361 private void requestAudioFocusAndSetMode(int stream, int mode) {
Yorke Leee5a7c922014-10-16 16:50:51 -0700362 Log.i(this, "requestAudioFocusAndSetMode, stream: %d -> %d", mAudioFocusStreamType, stream);
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700363 Preconditions.checkState(stream != STREAM_NONE);
364
Santos Cordon5ba7f272014-05-28 13:59:49 -0700365 // Even if we already have focus, if the stream is different we update audio manager to give
366 // it a hint about the purpose of our focus.
367 if (mAudioFocusStreamType != stream) {
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700368 Log.v(this, "requesting audio focus for stream: %d", stream);
369 mAudioManager.requestAudioFocusForCall(stream,
370 AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
371 }
372 mAudioFocusStreamType = stream;
Santos Cordon14ff8382014-08-05 20:44:27 -0700373
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700374 setMode(mode);
375 }
376
377 private void abandonAudioFocus() {
Santos Cordon14ff8382014-08-05 20:44:27 -0700378 if (hasFocus()) {
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700379 setMode(AudioManager.MODE_NORMAL);
380 Log.v(this, "abandoning audio focus");
381 mAudioManager.abandonAudioFocusForCall();
382 mAudioFocusStreamType = STREAM_NONE;
383 }
Santos Cordon1ae2b852014-03-19 03:03:10 -0700384 }
385
386 /**
387 * Sets the audio mode.
388 *
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700389 * @param newMode Mode constant from AudioManager.MODE_*.
Santos Cordon1ae2b852014-03-19 03:03:10 -0700390 */
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700391 private void setMode(int newMode) {
Santos Cordon14ff8382014-08-05 20:44:27 -0700392 Preconditions.checkState(hasFocus());
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700393 int oldMode = mAudioManager.getMode();
394 Log.v(this, "Request to change audio mode from %d to %d", oldMode, newMode);
395 if (oldMode != newMode) {
396 mAudioManager.setMode(newMode);
Santos Cordon14ff8382014-08-05 20:44:27 -0700397 mMostRecentlyUsedMode = newMode;
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700398 }
399 }
400
401 private int selectWiredOrEarpiece(int route, int supportedRouteMask) {
402 // Since they are mutually exclusive and one is ALWAYS valid, we allow a special input of
403 // ROUTE_WIRED_OR_EARPIECE so that callers dont have to make a call to check which is
404 // supported before calling setAudioRoute.
Ihab Awad6fb37c82014-08-07 19:48:57 -0700405 if (route == AudioState.ROUTE_WIRED_OR_EARPIECE) {
406 route = AudioState.ROUTE_WIRED_OR_EARPIECE & supportedRouteMask;
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700407 if (route == 0) {
408 Log.wtf(this, "One of wired headset or earpiece should always be valid.");
409 // assume earpiece in this case.
Ihab Awad6fb37c82014-08-07 19:48:57 -0700410 route = AudioState.ROUTE_EARPIECE;
Santos Cordon1ae2b852014-03-19 03:03:10 -0700411 }
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700412 }
413 return route;
414 }
415
416 private int calculateSupportedRoutes() {
Ihab Awad6fb37c82014-08-07 19:48:57 -0700417 int routeMask = AudioState.ROUTE_SPEAKER;
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700418
419 if (mWiredHeadsetManager.isPluggedIn()) {
Ihab Awad6fb37c82014-08-07 19:48:57 -0700420 routeMask |= AudioState.ROUTE_WIRED_HEADSET;
Santos Cordon1ae2b852014-03-19 03:03:10 -0700421 } else {
Ihab Awad6fb37c82014-08-07 19:48:57 -0700422 routeMask |= AudioState.ROUTE_EARPIECE;
Santos Cordon1ae2b852014-03-19 03:03:10 -0700423 }
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700424
Santos Cordonc7e85d42014-05-22 02:51:48 -0700425 if (mBluetoothManager.isBluetoothAvailable()) {
Ihab Awad6fb37c82014-08-07 19:48:57 -0700426 routeMask |= AudioState.ROUTE_BLUETOOTH;
Santos Cordonc7e85d42014-05-22 02:51:48 -0700427 }
428
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700429 return routeMask;
Santos Cordon1ae2b852014-03-19 03:03:10 -0700430 }
431
Ihab Awad6fb37c82014-08-07 19:48:57 -0700432 private AudioState getInitialAudioState(Call call) {
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700433 int supportedRouteMask = calculateSupportedRoutes();
Santos Cordonc7e85d42014-05-22 02:51:48 -0700434 int route = selectWiredOrEarpiece(
Ihab Awad6fb37c82014-08-07 19:48:57 -0700435 AudioState.ROUTE_WIRED_OR_EARPIECE, supportedRouteMask);
Santos Cordonc7e85d42014-05-22 02:51:48 -0700436
437 // We want the UI to indicate that "bluetooth is in use" in two slightly different cases:
438 // (a) The obvious case: if a bluetooth headset is currently in use for an ongoing call.
439 // (b) The not-so-obvious case: if an incoming call is ringing, and we expect that audio
440 // *will* be routed to a bluetooth headset once the call is answered. In this case, just
441 // check if the headset is available. Note this only applies when we are dealing with
442 // the first call.
443 if (call != null && mBluetoothManager.isBluetoothAvailable()) {
444 switch(call.getState()) {
Ihab Awad6fb37c82014-08-07 19:48:57 -0700445 case CallState.ACTIVE:
446 case CallState.ON_HOLD:
447 case CallState.DIALING:
Santos Cordon5753f302014-09-26 13:56:16 -0700448 case CallState.CONNECTING:
Ihab Awad6fb37c82014-08-07 19:48:57 -0700449 case CallState.RINGING:
450 route = AudioState.ROUTE_BLUETOOTH;
Santos Cordonc7e85d42014-05-22 02:51:48 -0700451 break;
452 default:
453 break;
454 }
455 }
456
Ihab Awad6fb37c82014-08-07 19:48:57 -0700457 return new AudioState(false, route, supportedRouteMask);
Santos Cordon1ae2b852014-03-19 03:03:10 -0700458 }
459
Santos Cordon7c6a5ec2014-09-11 19:47:23 -0700460 private void setInitialAudioState(Call call, boolean force) {
Ihab Awad6fb37c82014-08-07 19:48:57 -0700461 AudioState audioState = getInitialAudioState(call);
Santos Cordon14ff8382014-08-05 20:44:27 -0700462 Log.v(this, "setInitialAudioState %s, %s", audioState, call);
Santos Cordon7c6a5ec2014-09-11 19:47:23 -0700463 setSystemAudioState(
464 force, audioState.isMuted, audioState.route, audioState.supportedRouteMask);
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700465 }
466
467 private void updateAudioForForegroundCall() {
468 Call call = CallsManager.getInstance().getForegroundCall();
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700469 if (call != null && call.getConnectionService() != null) {
470 call.getConnectionService().onAudioStateChanged(call, mAudioState);
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700471 }
Sailesh Nepal810735e2014-03-18 18:15:46 -0700472 }
Santos Cordon5ba7f272014-05-28 13:59:49 -0700473
474 /**
475 * Returns the current foreground call in order to properly set the audio mode.
476 */
477 private Call getForegroundCall() {
478 Call call = CallsManager.getInstance().getForegroundCall();
479
480 // We ignore any foreground call that is in the ringing state because we deal with ringing
481 // calls exclusively through the mIsRinging variable set by {@link Ringer}.
482 if (call != null && call.getState() == CallState.RINGING) {
483 call = null;
484 }
485 return call;
486 }
Santos Cordon14ff8382014-08-05 20:44:27 -0700487
Yorke Lee42afb972014-09-08 09:47:21 -0700488 private boolean hasRingingForegroundCall() {
489 Call call = CallsManager.getInstance().getForegroundCall();
490 return call != null && call.getState() == CallState.RINGING;
491 }
492
Santos Cordon14ff8382014-08-05 20:44:27 -0700493 private boolean hasFocus() {
494 return mAudioFocusStreamType != STREAM_NONE;
495 }
Sailesh Nepal810735e2014-03-18 18:15:46 -0700496}