blob: cc948e47d536d406247f043e8b92fd7c66b89016 [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 Gunn9787e0e2014-10-14 14:36:12 -070024import com.android.internal.util.IndentingPrintWriter;
Tyler Gunn91d43cf2014-09-17 12:19:39 -070025import com.android.internal.util.Preconditions;
Santos Cordon1ae2b852014-03-19 03:03:10 -070026
Santos Cordon14ff8382014-08-05 20:44:27 -070027import java.util.Objects;
28
Sailesh Nepal810735e2014-03-18 18:15:46 -070029/**
30 * This class manages audio modes, streams and other properties.
31 */
Sailesh Nepalb88795a2014-07-15 14:53:27 -070032final class CallAudioManager extends CallsManagerListenerBase
33 implements WiredHeadsetManager.Listener {
Sailesh Nepal6aca10a2014-03-24 16:11:02 -070034 private static final int STREAM_NONE = -1;
Santos Cordon1ae2b852014-03-19 03:03:10 -070035
Santos Cordondeb8c892014-05-30 01:38:03 -070036 private final StatusBarNotifier mStatusBarNotifier;
Sailesh Nepal6aca10a2014-03-24 16:11:02 -070037 private final AudioManager mAudioManager;
Santos Cordonc7e85d42014-05-22 02:51:48 -070038 private final BluetoothManager mBluetoothManager;
Sailesh Nepalb88795a2014-07-15 14:53:27 -070039 private final WiredHeadsetManager mWiredHeadsetManager;
Santos Cordondeb8c892014-05-30 01:38:03 -070040
Ihab Awad6fb37c82014-08-07 19:48:57 -070041 private AudioState mAudioState;
Sailesh Nepal6aca10a2014-03-24 16:11:02 -070042 private int mAudioFocusStreamType;
43 private boolean mIsRinging;
Santos Cordona56f2762014-03-24 15:55:53 -070044 private boolean mIsTonePlaying;
Sailesh Nepal6aca10a2014-03-24 16:11:02 -070045 private boolean mWasSpeakerOn;
Santos Cordon14ff8382014-08-05 20:44:27 -070046 private int mMostRecentlyUsedMode = AudioManager.MODE_IN_CALL;
Santos Cordon1ae2b852014-03-19 03:03:10 -070047
Sailesh Nepalb88795a2014-07-15 14:53:27 -070048 CallAudioManager(Context context, StatusBarNotifier statusBarNotifier,
49 WiredHeadsetManager wiredHeadsetManager) {
Santos Cordondeb8c892014-05-30 01:38:03 -070050 mStatusBarNotifier = statusBarNotifier;
Sailesh Nepal6aca10a2014-03-24 16:11:02 -070051 mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
Santos Cordonc7e85d42014-05-22 02:51:48 -070052 mBluetoothManager = new BluetoothManager(context, this);
Sailesh Nepalb88795a2014-07-15 14:53:27 -070053 mWiredHeadsetManager = wiredHeadsetManager;
Sailesh Nepald0a76aa2014-07-16 22:12:23 -070054 mWiredHeadsetManager.addListener(this);
Sailesh Nepalb88795a2014-07-15 14:53:27 -070055
Santos Cordondeb8c892014-05-30 01:38:03 -070056 saveAudioState(getInitialAudioState(null));
Sailesh Nepal6aca10a2014-03-24 16:11:02 -070057 mAudioFocusStreamType = STREAM_NONE;
58 }
Santos Cordon1ae2b852014-03-19 03:03:10 -070059
Ihab Awad6fb37c82014-08-07 19:48:57 -070060 AudioState getAudioState() {
Sailesh Nepal6aca10a2014-03-24 16:11:02 -070061 return mAudioState;
62 }
Santos Cordon1ae2b852014-03-19 03:03:10 -070063
64 @Override
65 public void onCallAdded(Call call) {
Santos Cordon14ff8382014-08-05 20:44:27 -070066 onCallUpdated(call);
67
68 if (hasFocus() && getForegroundCall() == call) {
69 if (!call.isIncoming()) {
70 // Unmute new outgoing call.
Ihab Awad07bc5ee2014-11-12 13:42:52 -080071 setSystemAudioState(false, mAudioState.getRoute(),
72 mAudioState.getSupportedRouteMask());
Santos Cordon14ff8382014-08-05 20:44:27 -070073 }
Santos Cordon1ae2b852014-03-19 03:03:10 -070074 }
Santos Cordon1ae2b852014-03-19 03:03:10 -070075 }
76
77 @Override
78 public void onCallRemoved(Call call) {
Santos Cordon14ff8382014-08-05 20:44:27 -070079 // If we didn't already have focus, there's nothing to do.
80 if (hasFocus()) {
81 if (CallsManager.getInstance().getCalls().isEmpty()) {
82 Log.v(this, "all calls removed, reseting system audio to default state");
Santos Cordon7c6a5ec2014-09-11 19:47:23 -070083 setInitialAudioState(null, false /* force */);
Santos Cordon14ff8382014-08-05 20:44:27 -070084 mWasSpeakerOn = false;
85 }
86 updateAudioStreamAndMode();
Sailesh Nepal6aca10a2014-03-24 16:11:02 -070087 }
Santos Cordon1ae2b852014-03-19 03:03:10 -070088 }
89
Sailesh Nepal810735e2014-03-18 18:15:46 -070090 @Override
Ihab Awad6fb37c82014-08-07 19:48:57 -070091 public void onCallStateChanged(Call call, int oldState, int newState) {
Santos Cordon14ff8382014-08-05 20:44:27 -070092 onCallUpdated(call);
Santos Cordon1ae2b852014-03-19 03:03:10 -070093 }
94
95 @Override
96 public void onIncomingCallAnswered(Call call) {
Ihab Awad07bc5ee2014-11-12 13:42:52 -080097 int route = mAudioState.getRoute();
Santos Cordonc7e85d42014-05-22 02:51:48 -070098
99 // We do two things:
100 // (1) If this is the first call, then we can to turn on bluetooth if available.
101 // (2) Unmute the audio for the new incoming call.
102 boolean isOnlyCall = CallsManager.getInstance().getCalls().size() == 1;
103 if (isOnlyCall && mBluetoothManager.isBluetoothAvailable()) {
104 mBluetoothManager.connectBluetoothAudio();
Ihab Awad6fb37c82014-08-07 19:48:57 -0700105 route = AudioState.ROUTE_BLUETOOTH;
Santos Cordonc7e85d42014-05-22 02:51:48 -0700106 }
107
Ihab Awad07bc5ee2014-11-12 13:42:52 -0800108 setSystemAudioState(false /* isMute */, route, mAudioState.getSupportedRouteMask());
Santos Cordon1ae2b852014-03-19 03:03:10 -0700109 }
110
111 @Override
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700112 public void onForegroundCallChanged(Call oldForegroundCall, Call newForegroundCall) {
Santos Cordon14ff8382014-08-05 20:44:27 -0700113 onCallUpdated(newForegroundCall);
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700114 // Ensure that the foreground call knows about the latest audio state.
115 updateAudioForForegroundCall();
Santos Cordon1ae2b852014-03-19 03:03:10 -0700116 }
117
Sailesh Nepal7e669572014-07-08 21:29:12 -0700118 @Override
Andrew Lee5be64bc2014-09-08 18:35:15 -0700119 public void onIsVoipAudioModeChanged(Call call) {
Sailesh Nepal7e669572014-07-08 21:29:12 -0700120 updateAudioStreamAndMode();
121 }
122
Sailesh Nepalb88795a2014-07-15 14:53:27 -0700123 /**
124 * Updates the audio route when the headset plugged in state changes. For example, if audio is
125 * being routed over speakerphone and a headset is plugged in then switch to wired headset.
126 */
127 @Override
128 public void onWiredHeadsetPluggedInChanged(boolean oldIsPluggedIn, boolean newIsPluggedIn) {
Santos Cordon14ff8382014-08-05 20:44:27 -0700129 // This can happen even when there are no calls and we don't have focus.
130 if (!hasFocus()) {
131 return;
132 }
133
Ihab Awad6fb37c82014-08-07 19:48:57 -0700134 int newRoute = AudioState.ROUTE_EARPIECE;
Sailesh Nepalb88795a2014-07-15 14:53:27 -0700135 if (newIsPluggedIn) {
Ihab Awad6fb37c82014-08-07 19:48:57 -0700136 newRoute = AudioState.ROUTE_WIRED_HEADSET;
Sailesh Nepalb88795a2014-07-15 14:53:27 -0700137 } else if (mWasSpeakerOn) {
138 Call call = getForegroundCall();
139 if (call != null && call.isAlive()) {
140 // Restore the speaker state.
Ihab Awad6fb37c82014-08-07 19:48:57 -0700141 newRoute = AudioState.ROUTE_SPEAKER;
Sailesh Nepalb88795a2014-07-15 14:53:27 -0700142 }
143 }
Ihab Awad07bc5ee2014-11-12 13:42:52 -0800144 setSystemAudioState(mAudioState.isMuted(), newRoute, calculateSupportedRoutes());
Sailesh Nepalb88795a2014-07-15 14:53:27 -0700145 }
146
Santos Cordondeb8c892014-05-30 01:38:03 -0700147 void toggleMute() {
Ihab Awad07bc5ee2014-11-12 13:42:52 -0800148 mute(!mAudioState.isMuted());
Santos Cordondeb8c892014-05-30 01:38:03 -0700149 }
150
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700151 void mute(boolean shouldMute) {
Santos Cordon14ff8382014-08-05 20:44:27 -0700152 if (!hasFocus()) {
153 return;
154 }
155
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700156 Log.v(this, "mute, shouldMute: %b", shouldMute);
Santos Cordon1ae2b852014-03-19 03:03:10 -0700157
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700158 // Don't mute if there are any emergency calls.
159 if (CallsManager.getInstance().hasEmergencyCall()) {
160 shouldMute = false;
161 Log.v(this, "ignoring mute for emergency call");
Santos Cordon1ae2b852014-03-19 03:03:10 -0700162 }
163
Ihab Awad07bc5ee2014-11-12 13:42:52 -0800164 if (mAudioState.isMuted() != shouldMute) {
165 setSystemAudioState(shouldMute, mAudioState.getRoute(),
166 mAudioState.getSupportedRouteMask());
Sailesh Nepal810735e2014-03-18 18:15:46 -0700167 }
168 }
169
Santos Cordon1ae2b852014-03-19 03:03:10 -0700170 /**
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700171 * Changed the audio route, for example from earpiece to speaker phone.
Santos Cordon1ae2b852014-03-19 03:03:10 -0700172 *
Ihab Awad6fb37c82014-08-07 19:48:57 -0700173 * @param route The new audio route to use. See {@link AudioState}.
Santos Cordon1ae2b852014-03-19 03:03:10 -0700174 */
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700175 void setAudioRoute(int route) {
Santos Cordon14ff8382014-08-05 20:44:27 -0700176 // This can happen even when there are no calls and we don't have focus.
177 if (!hasFocus()) {
178 return;
179 }
180
Ihab Awad6fb37c82014-08-07 19:48:57 -0700181 Log.v(this, "setAudioRoute, route: %s", AudioState.audioRouteToString(route));
Santos Cordon1ae2b852014-03-19 03:03:10 -0700182
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700183 // Change ROUTE_WIRED_OR_EARPIECE to a single entry.
Ihab Awad07bc5ee2014-11-12 13:42:52 -0800184 int newRoute = selectWiredOrEarpiece(route, mAudioState.getSupportedRouteMask());
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700185
186 // If route is unsupported, do nothing.
Ihab Awad07bc5ee2014-11-12 13:42:52 -0800187 if ((mAudioState.getSupportedRouteMask() | newRoute) == 0) {
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700188 Log.wtf(this, "Asking to set to a route that is unsupported: %d", newRoute);
189 return;
190 }
191
Ihab Awad07bc5ee2014-11-12 13:42:52 -0800192 if (mAudioState.getRoute() != newRoute) {
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700193 // Remember the new speaker state so it can be restored when the user plugs and unplugs
194 // a headset.
Ihab Awad6fb37c82014-08-07 19:48:57 -0700195 mWasSpeakerOn = newRoute == AudioState.ROUTE_SPEAKER;
Ihab Awad07bc5ee2014-11-12 13:42:52 -0800196 setSystemAudioState(mAudioState.isMuted(), newRoute,
197 mAudioState.getSupportedRouteMask());
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700198 }
199 }
200
201 void setIsRinging(boolean isRinging) {
202 if (mIsRinging != isRinging) {
203 Log.v(this, "setIsRinging %b -> %b", mIsRinging, isRinging);
204 mIsRinging = isRinging;
205 updateAudioStreamAndMode();
Santos Cordon1ae2b852014-03-19 03:03:10 -0700206 }
Sailesh Nepal810735e2014-03-18 18:15:46 -0700207 }
208
Santos Cordon1ae2b852014-03-19 03:03:10 -0700209 /**
Santos Cordona56f2762014-03-24 15:55:53 -0700210 * Sets the tone playing status. Some tones can play even when there are no live calls and this
211 * status indicates that we should keep audio focus even for tones that play beyond the life of
212 * calls.
213 *
214 * @param isPlayingNew The status to set.
215 */
216 void setIsTonePlaying(boolean isPlayingNew) {
217 ThreadUtil.checkOnMainThread();
218
219 if (mIsTonePlaying != isPlayingNew) {
220 Log.v(this, "mIsTonePlaying %b -> %b.", mIsTonePlaying, isPlayingNew);
221 mIsTonePlaying = isPlayingNew;
222 updateAudioStreamAndMode();
223 }
224 }
225
226 /**
Santos Cordonc7e85d42014-05-22 02:51:48 -0700227 * Updates the audio routing according to the bluetooth state.
228 */
229 void onBluetoothStateChange(BluetoothManager bluetoothManager) {
Santos Cordon14ff8382014-08-05 20:44:27 -0700230 // This can happen even when there are no calls and we don't have focus.
231 if (!hasFocus()) {
232 return;
233 }
234
Santos Cordon31953642014-09-12 03:23:59 -0700235 int supportedRoutes = calculateSupportedRoutes();
Ihab Awad07bc5ee2014-11-12 13:42:52 -0800236 int newRoute = mAudioState.getRoute();
Santos Cordonc7e85d42014-05-22 02:51:48 -0700237 if (bluetoothManager.isBluetoothAudioConnectedOrPending()) {
Ihab Awad6fb37c82014-08-07 19:48:57 -0700238 newRoute = AudioState.ROUTE_BLUETOOTH;
Ihab Awad07bc5ee2014-11-12 13:42:52 -0800239 } else if (mAudioState.getRoute() == AudioState.ROUTE_BLUETOOTH) {
Santos Cordon31953642014-09-12 03:23:59 -0700240 newRoute = selectWiredOrEarpiece(AudioState.ROUTE_WIRED_OR_EARPIECE, supportedRoutes);
Santos Cordonc7e85d42014-05-22 02:51:48 -0700241 // Do not switch to speaker when bluetooth disconnects.
242 mWasSpeakerOn = false;
243 }
244
Ihab Awad07bc5ee2014-11-12 13:42:52 -0800245 setSystemAudioState(mAudioState.isMuted(), newRoute, supportedRoutes);
Santos Cordonc7e85d42014-05-22 02:51:48 -0700246 }
247
248 boolean isBluetoothAudioOn() {
249 return mBluetoothManager.isBluetoothAudioConnected();
250 }
251
252 boolean isBluetoothDeviceAvailable() {
253 return mBluetoothManager.isBluetoothAvailable();
254 }
255
Ihab Awad6fb37c82014-08-07 19:48:57 -0700256 private void saveAudioState(AudioState audioState) {
Santos Cordondeb8c892014-05-30 01:38:03 -0700257 mAudioState = audioState;
Ihab Awad07bc5ee2014-11-12 13:42:52 -0800258 mStatusBarNotifier.notifyMute(mAudioState.isMuted());
259 mStatusBarNotifier.notifySpeakerphone(mAudioState.getRoute() == AudioState.ROUTE_SPEAKER);
Santos Cordondeb8c892014-05-30 01:38:03 -0700260 }
261
Santos Cordon14ff8382014-08-05 20:44:27 -0700262 private void onCallUpdated(Call call) {
263 boolean wasNotVoiceCall = mAudioFocusStreamType != AudioManager.STREAM_VOICE_CALL;
264 updateAudioStreamAndMode();
265
266 // If we transition from not voice call to voice call, we need to set an initial state.
267 if (wasNotVoiceCall && mAudioFocusStreamType == AudioManager.STREAM_VOICE_CALL) {
Santos Cordon7c6a5ec2014-09-11 19:47:23 -0700268 setInitialAudioState(call, true /* force */);
Santos Cordon14ff8382014-08-05 20:44:27 -0700269 }
270 }
271
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700272 private void setSystemAudioState(boolean isMuted, int route, int supportedRouteMask) {
Santos Cordon7c6a5ec2014-09-11 19:47:23 -0700273 setSystemAudioState(false /* force */, isMuted, route, supportedRouteMask);
274 }
275
276 private void setSystemAudioState(
277 boolean force, boolean isMuted, int route, int supportedRouteMask) {
Santos Cordon14ff8382014-08-05 20:44:27 -0700278 if (!hasFocus()) {
279 return;
280 }
281
Ihab Awad6fb37c82014-08-07 19:48:57 -0700282 AudioState oldAudioState = mAudioState;
283 saveAudioState(new AudioState(isMuted, route, supportedRouteMask));
Santos Cordon7c6a5ec2014-09-11 19:47:23 -0700284 if (!force && Objects.equals(oldAudioState, mAudioState)) {
Santos Cordon14ff8382014-08-05 20:44:27 -0700285 return;
286 }
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700287 Log.i(this, "changing audio state from %s to %s", oldAudioState, mAudioState);
Santos Cordon1ae2b852014-03-19 03:03:10 -0700288
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700289 // Mute.
Ihab Awad07bc5ee2014-11-12 13:42:52 -0800290 if (mAudioState.isMuted() != mAudioManager.isMicrophoneMute()) {
291 Log.i(this, "changing microphone mute state to: %b", mAudioState.isMuted());
292 mAudioManager.setMicrophoneMute(mAudioState.isMuted());
Santos Cordon1ae2b852014-03-19 03:03:10 -0700293 }
294
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700295 // Audio route.
Ihab Awad07bc5ee2014-11-12 13:42:52 -0800296 if (mAudioState.getRoute() == AudioState.ROUTE_BLUETOOTH) {
Santos Cordonc7e85d42014-05-22 02:51:48 -0700297 turnOnSpeaker(false);
298 turnOnBluetooth(true);
Ihab Awad07bc5ee2014-11-12 13:42:52 -0800299 } else if (mAudioState.getRoute() == AudioState.ROUTE_SPEAKER) {
Santos Cordonc7e85d42014-05-22 02:51:48 -0700300 turnOnBluetooth(false);
301 turnOnSpeaker(true);
Ihab Awad07bc5ee2014-11-12 13:42:52 -0800302 } else if (mAudioState.getRoute() == AudioState.ROUTE_EARPIECE ||
303 mAudioState.getRoute() == AudioState.ROUTE_WIRED_HEADSET) {
Santos Cordonc7e85d42014-05-22 02:51:48 -0700304 turnOnBluetooth(false);
305 turnOnSpeaker(false);
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700306 }
307
308 if (!oldAudioState.equals(mAudioState)) {
309 CallsManager.getInstance().onAudioStateChanged(oldAudioState, mAudioState);
310 updateAudioForForegroundCall();
311 }
312 }
313
Santos Cordonc7e85d42014-05-22 02:51:48 -0700314 private void turnOnSpeaker(boolean on) {
315 // Wired headset and earpiece work the same way
316 if (mAudioManager.isSpeakerphoneOn() != on) {
Santos Cordon14ff8382014-08-05 20:44:27 -0700317 Log.i(this, "turning speaker phone %s", on);
Santos Cordonc7e85d42014-05-22 02:51:48 -0700318 mAudioManager.setSpeakerphoneOn(on);
319 }
320 }
321
322 private void turnOnBluetooth(boolean on) {
323 if (mBluetoothManager.isBluetoothAvailable()) {
Santos Cordon14ff8382014-08-05 20:44:27 -0700324 boolean isAlreadyOn = mBluetoothManager.isBluetoothAudioConnectedOrPending();
Santos Cordonc7e85d42014-05-22 02:51:48 -0700325 if (on != isAlreadyOn) {
Santos Cordon14ff8382014-08-05 20:44:27 -0700326 Log.i(this, "connecting bluetooth %s", on);
Santos Cordonc7e85d42014-05-22 02:51:48 -0700327 if (on) {
328 mBluetoothManager.connectBluetoothAudio();
329 } else {
330 mBluetoothManager.disconnectBluetoothAudio();
331 }
332 }
333 }
334 }
335
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700336 private void updateAudioStreamAndMode() {
Yorke Leee5a7c922014-10-16 16:50:51 -0700337 Log.i(this, "updateAudioStreamAndMode, mIsRinging: %b, mIsTonePlaying: %b", mIsRinging,
Santos Cordona56f2762014-03-24 15:55:53 -0700338 mIsTonePlaying);
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700339 if (mIsRinging) {
340 requestAudioFocusAndSetMode(AudioManager.STREAM_RING, AudioManager.MODE_RINGTONE);
341 } else {
Nancy Chendda69192014-12-02 15:56:55 -0800342 Call foregroundCall = getForegroundCall();
343 Call waitingForAccountSelectionCall =
344 CallsManager.getInstance().getFirstCallWithState(CallState.PRE_DIAL_WAIT);
345 if (foregroundCall != null && waitingForAccountSelectionCall == null) {
346 // In the case where there is a call that is waiting for account selection,
347 // this will fall back to abandonAudioFocus() below, which temporarily exits
348 // the in-call audio mode. This is to allow TalkBack to speak the "Call with"
349 // dialog information at media volume as opposed to through the earpiece.
350 // Once exiting the "Call with" dialog, the audio focus will return to an in-call
351 // audio mode when this method (updateAudioStreamAndMode) is called again.
352 int mode = foregroundCall.getIsVoipAudioMode() ?
Sailesh Nepal7e669572014-07-08 21:29:12 -0700353 AudioManager.MODE_IN_COMMUNICATION : AudioManager.MODE_IN_CALL;
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700354 requestAudioFocusAndSetMode(AudioManager.STREAM_VOICE_CALL, mode);
Santos Cordona56f2762014-03-24 15:55:53 -0700355 } else if (mIsTonePlaying) {
356 // There is no call, however, we are still playing a tone, so keep focus.
Santos Cordon14ff8382014-08-05 20:44:27 -0700357 // Since there is no call from which to determine the mode, use the most
358 // recently used mode instead.
Santos Cordona56f2762014-03-24 15:55:53 -0700359 requestAudioFocusAndSetMode(
Santos Cordon14ff8382014-08-05 20:44:27 -0700360 AudioManager.STREAM_VOICE_CALL, mMostRecentlyUsedMode);
Yorke Lee42afb972014-09-08 09:47:21 -0700361 } else if (!hasRingingForegroundCall()) {
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700362 abandonAudioFocus();
Yorke Lee42afb972014-09-08 09:47:21 -0700363 } else {
364 // mIsRinging is false, but there is a foreground ringing call present. Don't
365 // abandon audio focus immediately to prevent audio focus from getting lost between
366 // the time it takes for the foreground call to transition from RINGING to ACTIVE/
367 // DISCONNECTED. When the call eventually transitions to the next state, audio
368 // focus will be correctly abandoned by the if clause above.
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700369 }
370 }
371 }
372
373 private void requestAudioFocusAndSetMode(int stream, int mode) {
Nancy Chendda69192014-12-02 15:56:55 -0800374 Log.i(this, "requestAudioFocusAndSetMode, stream: %d -> %d, mode: %d",
375 mAudioFocusStreamType, stream, mode);
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700376 Preconditions.checkState(stream != STREAM_NONE);
377
Santos Cordon5ba7f272014-05-28 13:59:49 -0700378 // Even if we already have focus, if the stream is different we update audio manager to give
379 // it a hint about the purpose of our focus.
380 if (mAudioFocusStreamType != stream) {
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700381 Log.v(this, "requesting audio focus for stream: %d", stream);
382 mAudioManager.requestAudioFocusForCall(stream,
383 AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
384 }
385 mAudioFocusStreamType = stream;
Santos Cordon14ff8382014-08-05 20:44:27 -0700386
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700387 setMode(mode);
388 }
389
390 private void abandonAudioFocus() {
Santos Cordon14ff8382014-08-05 20:44:27 -0700391 if (hasFocus()) {
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700392 setMode(AudioManager.MODE_NORMAL);
393 Log.v(this, "abandoning audio focus");
394 mAudioManager.abandonAudioFocusForCall();
395 mAudioFocusStreamType = STREAM_NONE;
396 }
Santos Cordon1ae2b852014-03-19 03:03:10 -0700397 }
398
399 /**
400 * Sets the audio mode.
401 *
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700402 * @param newMode Mode constant from AudioManager.MODE_*.
Santos Cordon1ae2b852014-03-19 03:03:10 -0700403 */
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700404 private void setMode(int newMode) {
Santos Cordon14ff8382014-08-05 20:44:27 -0700405 Preconditions.checkState(hasFocus());
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700406 int oldMode = mAudioManager.getMode();
407 Log.v(this, "Request to change audio mode from %d to %d", oldMode, newMode);
Yorke Lee60b6e282014-10-20 18:43:31 -0700408
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700409 if (oldMode != newMode) {
Yorke Lee60b6e282014-10-20 18:43:31 -0700410 if (oldMode == AudioManager.MODE_IN_CALL && newMode == AudioManager.MODE_RINGTONE) {
411 Log.i(this, "Transition from IN_CALL -> RINGTONE. Resetting to NORMAL first.");
412 mAudioManager.setMode(AudioManager.MODE_NORMAL);
413 }
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700414 mAudioManager.setMode(newMode);
Santos Cordon14ff8382014-08-05 20:44:27 -0700415 mMostRecentlyUsedMode = newMode;
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700416 }
417 }
418
419 private int selectWiredOrEarpiece(int route, int supportedRouteMask) {
420 // Since they are mutually exclusive and one is ALWAYS valid, we allow a special input of
421 // ROUTE_WIRED_OR_EARPIECE so that callers dont have to make a call to check which is
422 // supported before calling setAudioRoute.
Ihab Awad6fb37c82014-08-07 19:48:57 -0700423 if (route == AudioState.ROUTE_WIRED_OR_EARPIECE) {
424 route = AudioState.ROUTE_WIRED_OR_EARPIECE & supportedRouteMask;
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700425 if (route == 0) {
426 Log.wtf(this, "One of wired headset or earpiece should always be valid.");
427 // assume earpiece in this case.
Ihab Awad6fb37c82014-08-07 19:48:57 -0700428 route = AudioState.ROUTE_EARPIECE;
Santos Cordon1ae2b852014-03-19 03:03:10 -0700429 }
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700430 }
431 return route;
432 }
433
434 private int calculateSupportedRoutes() {
Ihab Awad6fb37c82014-08-07 19:48:57 -0700435 int routeMask = AudioState.ROUTE_SPEAKER;
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700436
437 if (mWiredHeadsetManager.isPluggedIn()) {
Ihab Awad6fb37c82014-08-07 19:48:57 -0700438 routeMask |= AudioState.ROUTE_WIRED_HEADSET;
Santos Cordon1ae2b852014-03-19 03:03:10 -0700439 } else {
Ihab Awad6fb37c82014-08-07 19:48:57 -0700440 routeMask |= AudioState.ROUTE_EARPIECE;
Santos Cordon1ae2b852014-03-19 03:03:10 -0700441 }
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700442
Santos Cordonc7e85d42014-05-22 02:51:48 -0700443 if (mBluetoothManager.isBluetoothAvailable()) {
Ihab Awad6fb37c82014-08-07 19:48:57 -0700444 routeMask |= AudioState.ROUTE_BLUETOOTH;
Santos Cordonc7e85d42014-05-22 02:51:48 -0700445 }
446
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700447 return routeMask;
Santos Cordon1ae2b852014-03-19 03:03:10 -0700448 }
449
Ihab Awad6fb37c82014-08-07 19:48:57 -0700450 private AudioState getInitialAudioState(Call call) {
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700451 int supportedRouteMask = calculateSupportedRoutes();
Santos Cordonc7e85d42014-05-22 02:51:48 -0700452 int route = selectWiredOrEarpiece(
Ihab Awad6fb37c82014-08-07 19:48:57 -0700453 AudioState.ROUTE_WIRED_OR_EARPIECE, supportedRouteMask);
Santos Cordonc7e85d42014-05-22 02:51:48 -0700454
455 // We want the UI to indicate that "bluetooth is in use" in two slightly different cases:
456 // (a) The obvious case: if a bluetooth headset is currently in use for an ongoing call.
457 // (b) The not-so-obvious case: if an incoming call is ringing, and we expect that audio
458 // *will* be routed to a bluetooth headset once the call is answered. In this case, just
459 // check if the headset is available. Note this only applies when we are dealing with
460 // the first call.
461 if (call != null && mBluetoothManager.isBluetoothAvailable()) {
462 switch(call.getState()) {
Ihab Awad6fb37c82014-08-07 19:48:57 -0700463 case CallState.ACTIVE:
464 case CallState.ON_HOLD:
465 case CallState.DIALING:
Santos Cordon5753f302014-09-26 13:56:16 -0700466 case CallState.CONNECTING:
Ihab Awad6fb37c82014-08-07 19:48:57 -0700467 case CallState.RINGING:
468 route = AudioState.ROUTE_BLUETOOTH;
Santos Cordonc7e85d42014-05-22 02:51:48 -0700469 break;
470 default:
471 break;
472 }
473 }
474
Ihab Awad6fb37c82014-08-07 19:48:57 -0700475 return new AudioState(false, route, supportedRouteMask);
Santos Cordon1ae2b852014-03-19 03:03:10 -0700476 }
477
Santos Cordon7c6a5ec2014-09-11 19:47:23 -0700478 private void setInitialAudioState(Call call, boolean force) {
Ihab Awad6fb37c82014-08-07 19:48:57 -0700479 AudioState audioState = getInitialAudioState(call);
Santos Cordon14ff8382014-08-05 20:44:27 -0700480 Log.v(this, "setInitialAudioState %s, %s", audioState, call);
Santos Cordon7c6a5ec2014-09-11 19:47:23 -0700481 setSystemAudioState(
Ihab Awad07bc5ee2014-11-12 13:42:52 -0800482 force, audioState.isMuted(), audioState.getRoute(),
483 audioState.getSupportedRouteMask());
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700484 }
485
486 private void updateAudioForForegroundCall() {
487 Call call = CallsManager.getInstance().getForegroundCall();
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700488 if (call != null && call.getConnectionService() != null) {
489 call.getConnectionService().onAudioStateChanged(call, mAudioState);
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700490 }
Sailesh Nepal810735e2014-03-18 18:15:46 -0700491 }
Santos Cordon5ba7f272014-05-28 13:59:49 -0700492
493 /**
494 * Returns the current foreground call in order to properly set the audio mode.
495 */
496 private Call getForegroundCall() {
497 Call call = CallsManager.getInstance().getForegroundCall();
498
499 // We ignore any foreground call that is in the ringing state because we deal with ringing
500 // calls exclusively through the mIsRinging variable set by {@link Ringer}.
501 if (call != null && call.getState() == CallState.RINGING) {
Nancy Chendda69192014-12-02 15:56:55 -0800502 return null;
Santos Cordon5ba7f272014-05-28 13:59:49 -0700503 }
Nancy Chendda69192014-12-02 15:56:55 -0800504
Santos Cordon5ba7f272014-05-28 13:59:49 -0700505 return call;
506 }
Santos Cordon14ff8382014-08-05 20:44:27 -0700507
Yorke Lee42afb972014-09-08 09:47:21 -0700508 private boolean hasRingingForegroundCall() {
509 Call call = CallsManager.getInstance().getForegroundCall();
510 return call != null && call.getState() == CallState.RINGING;
511 }
512
Santos Cordon14ff8382014-08-05 20:44:27 -0700513 private boolean hasFocus() {
514 return mAudioFocusStreamType != STREAM_NONE;
515 }
Tyler Gunn9787e0e2014-10-14 14:36:12 -0700516
517 /**
518 * Dumps the state of the {@link CallAudioManager}.
519 *
520 * @param pw The {@code IndentingPrintWriter} to write the state to.
521 */
522 public void dump(IndentingPrintWriter pw) {
523 pw.println("mAudioState: " + mAudioState);
524 pw.println("mBluetoothManager:");
525 pw.increaseIndent();
526 mBluetoothManager.dump(pw);
527 pw.decreaseIndent();
528 if (mWiredHeadsetManager != null) {
529 pw.println("mWiredHeadsetManager:");
530 pw.increaseIndent();
531 mWiredHeadsetManager.dump(pw);
532 pw.decreaseIndent();
533 } else {
534 pw.println("mWiredHeadsetManager: null");
535 }
536 pw.println("mAudioFocusStreamType: " + mAudioFocusStreamType);
537 pw.println("mIsRinging: " + mIsRinging);
538 pw.println("mIsTonePlaying: " + mIsTonePlaying);
539 pw.println("mWasSpeakerOn: " + mWasSpeakerOn);
540 pw.println("mMostRecentlyUsedMode: " + mMostRecentlyUsedMode);
541 }
Sailesh Nepal810735e2014-03-18 18:15:46 -0700542}