blob: b8d980bf8067f3371c4c3c9e09e564a329e79d0d [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;
37 private boolean mWasSpeakerOn;
Santos Cordon1ae2b852014-03-19 03:03:10 -070038
Sailesh Nepal6aca10a2014-03-24 16:11:02 -070039 CallAudioManager() {
40 Context context = TelecommApp.getInstance();
41 mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
42 mWiredHeadsetManager = new WiredHeadsetManager(this);
43 mAudioState = getInitialAudioState();
44 mAudioFocusStreamType = STREAM_NONE;
45 }
Santos Cordon1ae2b852014-03-19 03:03:10 -070046
Sailesh Nepal6aca10a2014-03-24 16:11:02 -070047 CallAudioState getAudioState() {
48 return mAudioState;
49 }
Santos Cordon1ae2b852014-03-19 03:03:10 -070050
51 @Override
52 public void onCallAdded(Call call) {
Sailesh Nepal6aca10a2014-03-24 16:11:02 -070053 updateAudioStreamAndMode();
54 if (CallsManager.getInstance().getCalls().size() == 1) {
55 Log.v(this, "first call added, reseting system audio to default state");
56 setInitialAudioState();
57 } else if (!call.isIncoming()) {
58 // Unmute new outgoing call.
59 setSystemAudioState(false, mAudioState.route, mAudioState.supportedRouteMask);
Santos Cordon1ae2b852014-03-19 03:03:10 -070060 }
Santos Cordon1ae2b852014-03-19 03:03:10 -070061 }
62
63 @Override
64 public void onCallRemoved(Call call) {
Sailesh Nepal6aca10a2014-03-24 16:11:02 -070065 if (CallsManager.getInstance().getCalls().isEmpty()) {
66 Log.v(this, "all calls removed, reseting system audio to default state");
67 setInitialAudioState();
68 }
69 updateAudioStreamAndMode();
Santos Cordon1ae2b852014-03-19 03:03:10 -070070 }
71
Sailesh Nepal810735e2014-03-18 18:15:46 -070072 @Override
73 public void onCallStateChanged(Call call, CallState oldState, CallState newState) {
Sailesh Nepal6aca10a2014-03-24 16:11:02 -070074 updateAudioStreamAndMode();
Santos Cordon1ae2b852014-03-19 03:03:10 -070075 }
76
77 @Override
78 public void onIncomingCallAnswered(Call call) {
Sailesh Nepal6aca10a2014-03-24 16:11:02 -070079 // Unmute new incoming call.
80 setSystemAudioState(false, mAudioState.route, mAudioState.supportedRouteMask);
Santos Cordon1ae2b852014-03-19 03:03:10 -070081 }
82
83 @Override
Sailesh Nepal6aca10a2014-03-24 16:11:02 -070084 public void onForegroundCallChanged(Call oldForegroundCall, Call newForegroundCall) {
85 updateAudioStreamAndMode();
86 // Ensure that the foreground call knows about the latest audio state.
87 updateAudioForForegroundCall();
Santos Cordon1ae2b852014-03-19 03:03:10 -070088 }
89
Sailesh Nepal6aca10a2014-03-24 16:11:02 -070090 void mute(boolean shouldMute) {
91 Log.v(this, "mute, shouldMute: %b", shouldMute);
Santos Cordon1ae2b852014-03-19 03:03:10 -070092
Sailesh Nepal6aca10a2014-03-24 16:11:02 -070093 // Don't mute if there are any emergency calls.
94 if (CallsManager.getInstance().hasEmergencyCall()) {
95 shouldMute = false;
96 Log.v(this, "ignoring mute for emergency call");
Santos Cordon1ae2b852014-03-19 03:03:10 -070097 }
98
Sailesh Nepal6aca10a2014-03-24 16:11:02 -070099 if (mAudioState.isMuted != shouldMute) {
100 setSystemAudioState(shouldMute, mAudioState.route, mAudioState.supportedRouteMask);
Sailesh Nepal810735e2014-03-18 18:15:46 -0700101 }
102 }
103
Santos Cordon1ae2b852014-03-19 03:03:10 -0700104 /**
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700105 * Changed the audio route, for example from earpiece to speaker phone.
Santos Cordon1ae2b852014-03-19 03:03:10 -0700106 *
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700107 * @param route The new audio route to use. See {@link CallAudioState}.
Santos Cordon1ae2b852014-03-19 03:03:10 -0700108 */
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700109 void setAudioRoute(int route) {
110 Log.v(this, "setAudioRoute, route: %s", CallAudioState.audioRouteToString(route));
Santos Cordon1ae2b852014-03-19 03:03:10 -0700111
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700112 // Change ROUTE_WIRED_OR_EARPIECE to a single entry.
113 int newRoute = selectWiredOrEarpiece(route, mAudioState.supportedRouteMask);
114
115 // If route is unsupported, do nothing.
116 if ((mAudioState.supportedRouteMask | newRoute) == 0) {
117 Log.wtf(this, "Asking to set to a route that is unsupported: %d", newRoute);
118 return;
119 }
120
121 if (mAudioState.route != newRoute) {
122 // Remember the new speaker state so it can be restored when the user plugs and unplugs
123 // a headset.
124 mWasSpeakerOn = newRoute == CallAudioState.ROUTE_SPEAKER;
125 setSystemAudioState(mAudioState.isMuted, newRoute, mAudioState.supportedRouteMask);
126 }
127 }
128
129 void setIsRinging(boolean isRinging) {
130 if (mIsRinging != isRinging) {
131 Log.v(this, "setIsRinging %b -> %b", mIsRinging, isRinging);
132 mIsRinging = isRinging;
133 updateAudioStreamAndMode();
Santos Cordon1ae2b852014-03-19 03:03:10 -0700134 }
Sailesh Nepal810735e2014-03-18 18:15:46 -0700135 }
136
Santos Cordon1ae2b852014-03-19 03:03:10 -0700137 /**
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700138 * Updates the audio route when the headset plugged in state changes. For example, if audio is
139 * being routed over speakerphone and a headset is plugged in then switch to wired headset.
140 */
141 void onHeadsetPluggedInChanged(boolean oldIsPluggedIn, boolean newIsPluggedIn) {
142 int newRoute = CallAudioState.ROUTE_EARPIECE;
143 if (newIsPluggedIn) {
144 newRoute = CallAudioState.ROUTE_WIRED_HEADSET;
145 } else if (mWasSpeakerOn) {
146 Call call = CallsManager.getInstance().getForegroundCall();
147 if (call != null && call.isAlive()) {
148 // Restore the speaker state.
149 newRoute = CallAudioState.ROUTE_SPEAKER;
150 }
151 }
152 setSystemAudioState(mAudioState.isMuted, newRoute, calculateSupportedRoutes());
153 }
Santos Cordon1ae2b852014-03-19 03:03:10 -0700154
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700155 private void setSystemAudioState(boolean isMuted, int route, int supportedRouteMask) {
156 CallAudioState oldAudioState = mAudioState;
157 mAudioState = new CallAudioState(isMuted, route, supportedRouteMask);
158 Log.i(this, "changing audio state from %s to %s", oldAudioState, mAudioState);
Santos Cordon1ae2b852014-03-19 03:03:10 -0700159
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700160 // Mute.
161 if (mAudioState.isMuted != mAudioManager.isMicrophoneMute()) {
162 Log.i(this, "changing microphone mute state to: %b", mAudioState.isMuted);
163 mAudioManager.setMicrophoneMute(mAudioState.isMuted);
Santos Cordon1ae2b852014-03-19 03:03:10 -0700164 }
165
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700166 // Audio route.
167 if (mAudioState.route == CallAudioState.ROUTE_SPEAKER) {
168 if (!mAudioManager.isSpeakerphoneOn()) {
169 Log.i(this, "turning speaker phone on");
170 mAudioManager.setSpeakerphoneOn(true);
171 }
172 } else if (mAudioState.route == CallAudioState.ROUTE_EARPIECE ||
173 mAudioState.route == CallAudioState.ROUTE_WIRED_HEADSET) {
174 // Wired headset and earpiece work the same way
175 if (mAudioManager.isSpeakerphoneOn()) {
176 Log.i(this, "turning speaker phone off");
177 mAudioManager.setSpeakerphoneOn(false);
178 }
179 }
180
181 if (!oldAudioState.equals(mAudioState)) {
182 CallsManager.getInstance().onAudioStateChanged(oldAudioState, mAudioState);
183 updateAudioForForegroundCall();
184 }
185 }
186
187 private void updateAudioStreamAndMode() {
188 Log.v(this, "updateAudioStreamAndMode, mIsRinging: %b", mIsRinging);
189 if (mIsRinging) {
190 requestAudioFocusAndSetMode(AudioManager.STREAM_RING, AudioManager.MODE_RINGTONE);
191 } else {
192 Call call = CallsManager.getInstance().getForegroundCall();
193 if (call != null) {
194 int mode = TelephonyUtil.isCurrentlyPSTNCall(call) ?
195 AudioManager.MODE_IN_CALL : AudioManager.MODE_IN_COMMUNICATION;
196 requestAudioFocusAndSetMode(AudioManager.STREAM_VOICE_CALL, mode);
197 } else {
198 abandonAudioFocus();
199 }
200 }
201 }
202
203 private void requestAudioFocusAndSetMode(int stream, int mode) {
204 Log.v(this, "setSystemAudioStreamAndMode, stream: %d -> %d", mAudioFocusStreamType, stream);
205 Preconditions.checkState(stream != STREAM_NONE);
206
207 // Only request audio focus once. If the stream type changes there's no need to abandon
208 // and re-request audio focus. The system doesn't really care about the stream we requested
209 // focus for so just silently switch.
210 if (mAudioFocusStreamType == STREAM_NONE) {
211 Log.v(this, "requesting audio focus for stream: %d", stream);
212 mAudioManager.requestAudioFocusForCall(stream,
213 AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
214 }
215 mAudioFocusStreamType = stream;
216 setMode(mode);
217 }
218
219 private void abandonAudioFocus() {
220 if (mAudioFocusStreamType != STREAM_NONE) {
221 setMode(AudioManager.MODE_NORMAL);
222 Log.v(this, "abandoning audio focus");
223 mAudioManager.abandonAudioFocusForCall();
224 mAudioFocusStreamType = STREAM_NONE;
225 }
Santos Cordon1ae2b852014-03-19 03:03:10 -0700226 }
227
228 /**
229 * Sets the audio mode.
230 *
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700231 * @param newMode Mode constant from AudioManager.MODE_*.
Santos Cordon1ae2b852014-03-19 03:03:10 -0700232 */
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700233 private void setMode(int newMode) {
234 Preconditions.checkState(mAudioFocusStreamType != STREAM_NONE);
235 int oldMode = mAudioManager.getMode();
236 Log.v(this, "Request to change audio mode from %d to %d", oldMode, newMode);
237 if (oldMode != newMode) {
238 mAudioManager.setMode(newMode);
239 }
240 }
241
242 private int selectWiredOrEarpiece(int route, int supportedRouteMask) {
243 // Since they are mutually exclusive and one is ALWAYS valid, we allow a special input of
244 // ROUTE_WIRED_OR_EARPIECE so that callers dont have to make a call to check which is
245 // supported before calling setAudioRoute.
246 if (route == CallAudioState.ROUTE_WIRED_OR_EARPIECE) {
247 route = CallAudioState.ROUTE_WIRED_OR_EARPIECE & supportedRouteMask;
248 if (route == 0) {
249 Log.wtf(this, "One of wired headset or earpiece should always be valid.");
250 // assume earpiece in this case.
251 route = CallAudioState.ROUTE_EARPIECE;
Santos Cordon1ae2b852014-03-19 03:03:10 -0700252 }
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700253 }
254 return route;
255 }
256
257 private int calculateSupportedRoutes() {
258 int routeMask = CallAudioState.ROUTE_SPEAKER;
259
260 if (mWiredHeadsetManager.isPluggedIn()) {
261 routeMask |= CallAudioState.ROUTE_WIRED_HEADSET;
Santos Cordon1ae2b852014-03-19 03:03:10 -0700262 } else {
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700263 routeMask |= CallAudioState.ROUTE_EARPIECE;
Santos Cordon1ae2b852014-03-19 03:03:10 -0700264 }
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700265
266 return routeMask;
Santos Cordon1ae2b852014-03-19 03:03:10 -0700267 }
268
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700269 private CallAudioState getInitialAudioState() {
270 int supportedRouteMask = calculateSupportedRoutes();
271 return new CallAudioState(false,
272 selectWiredOrEarpiece(CallAudioState.ROUTE_WIRED_OR_EARPIECE, supportedRouteMask),
273 supportedRouteMask);
Santos Cordon1ae2b852014-03-19 03:03:10 -0700274 }
275
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700276 private void setInitialAudioState() {
277 CallAudioState audioState = getInitialAudioState();
278 setSystemAudioState(audioState.isMuted, audioState.route, audioState.supportedRouteMask);
279 }
280
281 private void updateAudioForForegroundCall() {
282 Call call = CallsManager.getInstance().getForegroundCall();
283 if (call != null && call.getCallService() != null) {
284 call.getCallService().onAudioStateChanged(call.getId(), mAudioState);
285 }
Sailesh Nepal810735e2014-03-18 18:15:46 -0700286 }
287}