blob: bf41f707c84eb3b653a0eacf22fde94bdb937e6d [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
17package com.android.telecomm;
18
19import android.content.Context;
20import android.media.AudioManager;
Sailesh Nepal6aca10a2014-03-24 16:11:02 -070021import android.telecomm.CallAudioState;
Sailesh Nepal810735e2014-03-18 18:15:46 -070022import android.telecomm.CallState;
23
Sailesh Nepal6aca10a2014-03-24 16:11:02 -070024import com.google.common.base.Preconditions;
Santos Cordon1ae2b852014-03-19 03:03:10 -070025
Sailesh Nepal810735e2014-03-18 18:15:46 -070026/**
27 * This class manages audio modes, streams and other properties.
28 */
29final class CallAudioManager extends CallsManagerListenerBase {
Sailesh Nepal6aca10a2014-03-24 16:11:02 -070030 private static final int STREAM_NONE = -1;
Santos Cordon1ae2b852014-03-19 03:03:10 -070031
Sailesh Nepal6aca10a2014-03-24 16:11:02 -070032 private final AudioManager mAudioManager;
33 private final WiredHeadsetManager mWiredHeadsetManager;
34 private CallAudioState mAudioState;
35 private int mAudioFocusStreamType;
36 private boolean mIsRinging;
Santos Cordona56f2762014-03-24 15:55:53 -070037 private boolean mIsTonePlaying;
Sailesh Nepal6aca10a2014-03-24 16:11:02 -070038 private boolean mWasSpeakerOn;
Santos Cordon1ae2b852014-03-19 03:03:10 -070039
Sailesh Nepal6aca10a2014-03-24 16:11:02 -070040 CallAudioManager() {
41 Context context = TelecommApp.getInstance();
42 mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
43 mWiredHeadsetManager = new WiredHeadsetManager(this);
44 mAudioState = getInitialAudioState();
45 mAudioFocusStreamType = STREAM_NONE;
46 }
Santos Cordon1ae2b852014-03-19 03:03:10 -070047
Sailesh Nepal6aca10a2014-03-24 16:11:02 -070048 CallAudioState getAudioState() {
49 return mAudioState;
50 }
Santos Cordon1ae2b852014-03-19 03:03:10 -070051
52 @Override
53 public void onCallAdded(Call call) {
Sailesh Nepal6aca10a2014-03-24 16:11:02 -070054 updateAudioStreamAndMode();
55 if (CallsManager.getInstance().getCalls().size() == 1) {
56 Log.v(this, "first call added, reseting system audio to default state");
57 setInitialAudioState();
58 } else if (!call.isIncoming()) {
59 // Unmute new outgoing call.
60 setSystemAudioState(false, mAudioState.route, mAudioState.supportedRouteMask);
Santos Cordon1ae2b852014-03-19 03:03:10 -070061 }
Santos Cordon1ae2b852014-03-19 03:03:10 -070062 }
63
64 @Override
65 public void onCallRemoved(Call call) {
Sailesh Nepal6aca10a2014-03-24 16:11:02 -070066 if (CallsManager.getInstance().getCalls().isEmpty()) {
67 Log.v(this, "all calls removed, reseting system audio to default state");
68 setInitialAudioState();
69 }
70 updateAudioStreamAndMode();
Santos Cordon1ae2b852014-03-19 03:03:10 -070071 }
72
Sailesh Nepal810735e2014-03-18 18:15:46 -070073 @Override
74 public void onCallStateChanged(Call call, CallState oldState, CallState newState) {
Sailesh Nepal6aca10a2014-03-24 16:11:02 -070075 updateAudioStreamAndMode();
Santos Cordon1ae2b852014-03-19 03:03:10 -070076 }
77
78 @Override
79 public void onIncomingCallAnswered(Call call) {
Sailesh Nepal6aca10a2014-03-24 16:11:02 -070080 // Unmute new incoming call.
81 setSystemAudioState(false, mAudioState.route, mAudioState.supportedRouteMask);
Santos Cordon1ae2b852014-03-19 03:03:10 -070082 }
83
84 @Override
Sailesh Nepal6aca10a2014-03-24 16:11:02 -070085 public void onForegroundCallChanged(Call oldForegroundCall, Call newForegroundCall) {
86 updateAudioStreamAndMode();
87 // Ensure that the foreground call knows about the latest audio state.
88 updateAudioForForegroundCall();
Santos Cordon1ae2b852014-03-19 03:03:10 -070089 }
90
Sailesh Nepal6aca10a2014-03-24 16:11:02 -070091 void mute(boolean shouldMute) {
92 Log.v(this, "mute, shouldMute: %b", shouldMute);
Santos Cordon1ae2b852014-03-19 03:03:10 -070093
Sailesh Nepal6aca10a2014-03-24 16:11:02 -070094 // Don't mute if there are any emergency calls.
95 if (CallsManager.getInstance().hasEmergencyCall()) {
96 shouldMute = false;
97 Log.v(this, "ignoring mute for emergency call");
Santos Cordon1ae2b852014-03-19 03:03:10 -070098 }
99
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700100 if (mAudioState.isMuted != shouldMute) {
101 setSystemAudioState(shouldMute, mAudioState.route, mAudioState.supportedRouteMask);
Sailesh Nepal810735e2014-03-18 18:15:46 -0700102 }
103 }
104
Santos Cordon1ae2b852014-03-19 03:03:10 -0700105 /**
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700106 * Changed the audio route, for example from earpiece to speaker phone.
Santos Cordon1ae2b852014-03-19 03:03:10 -0700107 *
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700108 * @param route The new audio route to use. See {@link CallAudioState}.
Santos Cordon1ae2b852014-03-19 03:03:10 -0700109 */
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700110 void setAudioRoute(int route) {
111 Log.v(this, "setAudioRoute, route: %s", CallAudioState.audioRouteToString(route));
Santos Cordon1ae2b852014-03-19 03:03:10 -0700112
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700113 // Change ROUTE_WIRED_OR_EARPIECE to a single entry.
114 int newRoute = selectWiredOrEarpiece(route, mAudioState.supportedRouteMask);
115
116 // If route is unsupported, do nothing.
117 if ((mAudioState.supportedRouteMask | newRoute) == 0) {
118 Log.wtf(this, "Asking to set to a route that is unsupported: %d", newRoute);
119 return;
120 }
121
122 if (mAudioState.route != newRoute) {
123 // Remember the new speaker state so it can be restored when the user plugs and unplugs
124 // a headset.
125 mWasSpeakerOn = newRoute == CallAudioState.ROUTE_SPEAKER;
126 setSystemAudioState(mAudioState.isMuted, newRoute, mAudioState.supportedRouteMask);
127 }
128 }
129
130 void setIsRinging(boolean isRinging) {
131 if (mIsRinging != isRinging) {
132 Log.v(this, "setIsRinging %b -> %b", mIsRinging, isRinging);
133 mIsRinging = isRinging;
134 updateAudioStreamAndMode();
Santos Cordon1ae2b852014-03-19 03:03:10 -0700135 }
Sailesh Nepal810735e2014-03-18 18:15:46 -0700136 }
137
Santos Cordon1ae2b852014-03-19 03:03:10 -0700138 /**
Santos Cordona56f2762014-03-24 15:55:53 -0700139 * Sets the tone playing status. Some tones can play even when there are no live calls and this
140 * status indicates that we should keep audio focus even for tones that play beyond the life of
141 * calls.
142 *
143 * @param isPlayingNew The status to set.
144 */
145 void setIsTonePlaying(boolean isPlayingNew) {
146 ThreadUtil.checkOnMainThread();
147
148 if (mIsTonePlaying != isPlayingNew) {
149 Log.v(this, "mIsTonePlaying %b -> %b.", mIsTonePlaying, isPlayingNew);
150 mIsTonePlaying = isPlayingNew;
151 updateAudioStreamAndMode();
152 }
153 }
154
155 /**
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700156 * Updates the audio route when the headset plugged in state changes. For example, if audio is
157 * being routed over speakerphone and a headset is plugged in then switch to wired headset.
158 */
159 void onHeadsetPluggedInChanged(boolean oldIsPluggedIn, boolean newIsPluggedIn) {
160 int newRoute = CallAudioState.ROUTE_EARPIECE;
161 if (newIsPluggedIn) {
162 newRoute = CallAudioState.ROUTE_WIRED_HEADSET;
163 } else if (mWasSpeakerOn) {
164 Call call = CallsManager.getInstance().getForegroundCall();
165 if (call != null && call.isAlive()) {
166 // Restore the speaker state.
167 newRoute = CallAudioState.ROUTE_SPEAKER;
168 }
169 }
170 setSystemAudioState(mAudioState.isMuted, newRoute, calculateSupportedRoutes());
171 }
Santos Cordon1ae2b852014-03-19 03:03:10 -0700172
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700173 private void setSystemAudioState(boolean isMuted, int route, int supportedRouteMask) {
174 CallAudioState oldAudioState = mAudioState;
175 mAudioState = new CallAudioState(isMuted, route, supportedRouteMask);
176 Log.i(this, "changing audio state from %s to %s", oldAudioState, mAudioState);
Santos Cordon1ae2b852014-03-19 03:03:10 -0700177
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700178 // Mute.
179 if (mAudioState.isMuted != mAudioManager.isMicrophoneMute()) {
180 Log.i(this, "changing microphone mute state to: %b", mAudioState.isMuted);
181 mAudioManager.setMicrophoneMute(mAudioState.isMuted);
Santos Cordon1ae2b852014-03-19 03:03:10 -0700182 }
183
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700184 // Audio route.
185 if (mAudioState.route == CallAudioState.ROUTE_SPEAKER) {
186 if (!mAudioManager.isSpeakerphoneOn()) {
187 Log.i(this, "turning speaker phone on");
188 mAudioManager.setSpeakerphoneOn(true);
189 }
190 } else if (mAudioState.route == CallAudioState.ROUTE_EARPIECE ||
191 mAudioState.route == CallAudioState.ROUTE_WIRED_HEADSET) {
192 // Wired headset and earpiece work the same way
193 if (mAudioManager.isSpeakerphoneOn()) {
194 Log.i(this, "turning speaker phone off");
195 mAudioManager.setSpeakerphoneOn(false);
196 }
197 }
198
199 if (!oldAudioState.equals(mAudioState)) {
200 CallsManager.getInstance().onAudioStateChanged(oldAudioState, mAudioState);
201 updateAudioForForegroundCall();
202 }
203 }
204
205 private void updateAudioStreamAndMode() {
Santos Cordona56f2762014-03-24 15:55:53 -0700206 Log.v(this, "updateAudioStreamAndMode, mIsRinging: %b, mIsTonePlaying: %b", mIsRinging,
207 mIsTonePlaying);
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700208 if (mIsRinging) {
209 requestAudioFocusAndSetMode(AudioManager.STREAM_RING, AudioManager.MODE_RINGTONE);
210 } else {
211 Call call = CallsManager.getInstance().getForegroundCall();
212 if (call != null) {
213 int mode = TelephonyUtil.isCurrentlyPSTNCall(call) ?
214 AudioManager.MODE_IN_CALL : AudioManager.MODE_IN_COMMUNICATION;
215 requestAudioFocusAndSetMode(AudioManager.STREAM_VOICE_CALL, mode);
Santos Cordona56f2762014-03-24 15:55:53 -0700216 } else if (mIsTonePlaying) {
217 // There is no call, however, we are still playing a tone, so keep focus.
218 requestAudioFocusAndSetMode(
219 AudioManager.STREAM_VOICE_CALL, AudioManager.MODE_IN_COMMUNICATION);
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700220 } else {
221 abandonAudioFocus();
222 }
223 }
224 }
225
226 private void requestAudioFocusAndSetMode(int stream, int mode) {
227 Log.v(this, "setSystemAudioStreamAndMode, stream: %d -> %d", mAudioFocusStreamType, stream);
228 Preconditions.checkState(stream != STREAM_NONE);
229
230 // Only request audio focus once. If the stream type changes there's no need to abandon
231 // and re-request audio focus. The system doesn't really care about the stream we requested
232 // focus for so just silently switch.
233 if (mAudioFocusStreamType == STREAM_NONE) {
234 Log.v(this, "requesting audio focus for stream: %d", stream);
235 mAudioManager.requestAudioFocusForCall(stream,
236 AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
237 }
238 mAudioFocusStreamType = stream;
239 setMode(mode);
240 }
241
242 private void abandonAudioFocus() {
243 if (mAudioFocusStreamType != STREAM_NONE) {
244 setMode(AudioManager.MODE_NORMAL);
245 Log.v(this, "abandoning audio focus");
246 mAudioManager.abandonAudioFocusForCall();
247 mAudioFocusStreamType = STREAM_NONE;
248 }
Santos Cordon1ae2b852014-03-19 03:03:10 -0700249 }
250
251 /**
252 * Sets the audio mode.
253 *
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700254 * @param newMode Mode constant from AudioManager.MODE_*.
Santos Cordon1ae2b852014-03-19 03:03:10 -0700255 */
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700256 private void setMode(int newMode) {
257 Preconditions.checkState(mAudioFocusStreamType != STREAM_NONE);
258 int oldMode = mAudioManager.getMode();
259 Log.v(this, "Request to change audio mode from %d to %d", oldMode, newMode);
260 if (oldMode != newMode) {
261 mAudioManager.setMode(newMode);
262 }
263 }
264
265 private int selectWiredOrEarpiece(int route, int supportedRouteMask) {
266 // Since they are mutually exclusive and one is ALWAYS valid, we allow a special input of
267 // ROUTE_WIRED_OR_EARPIECE so that callers dont have to make a call to check which is
268 // supported before calling setAudioRoute.
269 if (route == CallAudioState.ROUTE_WIRED_OR_EARPIECE) {
270 route = CallAudioState.ROUTE_WIRED_OR_EARPIECE & supportedRouteMask;
271 if (route == 0) {
272 Log.wtf(this, "One of wired headset or earpiece should always be valid.");
273 // assume earpiece in this case.
274 route = CallAudioState.ROUTE_EARPIECE;
Santos Cordon1ae2b852014-03-19 03:03:10 -0700275 }
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700276 }
277 return route;
278 }
279
280 private int calculateSupportedRoutes() {
281 int routeMask = CallAudioState.ROUTE_SPEAKER;
282
283 if (mWiredHeadsetManager.isPluggedIn()) {
284 routeMask |= CallAudioState.ROUTE_WIRED_HEADSET;
Santos Cordon1ae2b852014-03-19 03:03:10 -0700285 } else {
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700286 routeMask |= CallAudioState.ROUTE_EARPIECE;
Santos Cordon1ae2b852014-03-19 03:03:10 -0700287 }
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700288
289 return routeMask;
Santos Cordon1ae2b852014-03-19 03:03:10 -0700290 }
291
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700292 private CallAudioState getInitialAudioState() {
293 int supportedRouteMask = calculateSupportedRoutes();
294 return new CallAudioState(false,
295 selectWiredOrEarpiece(CallAudioState.ROUTE_WIRED_OR_EARPIECE, supportedRouteMask),
296 supportedRouteMask);
Santos Cordon1ae2b852014-03-19 03:03:10 -0700297 }
298
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700299 private void setInitialAudioState() {
300 CallAudioState audioState = getInitialAudioState();
301 setSystemAudioState(audioState.isMuted, audioState.route, audioState.supportedRouteMask);
302 }
303
304 private void updateAudioForForegroundCall() {
305 Call call = CallsManager.getInstance().getForegroundCall();
306 if (call != null && call.getCallService() != null) {
307 call.getCallService().onAudioStateChanged(call.getId(), mAudioState);
308 }
Sailesh Nepal810735e2014-03-18 18:15:46 -0700309 }
310}