blob: a302c8dd677e44d6d1a3c3af5ea494dfdef374d3 [file] [log] [blame]
Santos Cordon8e8b8d22013-12-19 14:14:05 -08001/*
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
Ben Gilad9f2bed32013-12-12 17:43:26 -080017package com.android.telecomm;
18
Sailesh Nepalce704b92014-03-17 18:31:43 -070019import android.net.Uri;
Evan Charltona05805b2014-03-05 08:21:46 -080020import android.os.Bundle;
Sailesh Nepal6aca10a2014-03-24 16:11:02 -070021import android.telecomm.CallAudioState;
Sailesh Nepal810735e2014-03-18 18:15:46 -070022import android.telecomm.CallInfo;
Ben Giladc5b22692014-02-18 20:03:22 -080023import android.telecomm.CallServiceDescriptor;
Santos Cordon681663d2014-01-30 04:32:15 -080024import android.telecomm.CallState;
Yorke Lee33501632014-03-17 19:24:12 -070025import android.telecomm.GatewayInfo;
Santos Cordon79ff2bc2014-03-27 15:31:27 -070026import android.telephony.DisconnectCause;
Santos Cordon8e8b8d22013-12-19 14:14:05 -080027
Santos Cordon681663d2014-01-30 04:32:15 -080028import com.google.common.base.Preconditions;
29import com.google.common.base.Strings;
Sailesh Nepal810735e2014-03-18 18:15:46 -070030import com.google.common.collect.ImmutableCollection;
31import com.google.common.collect.ImmutableList;
Santos Cordon8e8b8d22013-12-19 14:14:05 -080032import com.google.common.collect.Lists;
Santos Cordon681663d2014-01-30 04:32:15 -080033import com.google.common.collect.Maps;
Santos Cordona56f2762014-03-24 15:55:53 -070034import com.google.common.collect.Sets;
Ben Gilad9f2bed32013-12-12 17:43:26 -080035
Ben Gilad9f2bed32013-12-12 17:43:26 -080036import java.util.List;
Santos Cordon681663d2014-01-30 04:32:15 -080037import java.util.Map;
Santos Cordona56f2762014-03-24 15:55:53 -070038import java.util.Set;
Ben Gilad9f2bed32013-12-12 17:43:26 -080039
Ben Giladdd8c6082013-12-30 14:44:08 -080040/**
41 * Singleton.
42 *
43 * NOTE(gilad): by design most APIs are package private, use the relevant adapter/s to allow
44 * access from other packages specifically refraining from passing the CallsManager instance
45 * beyond the com.android.telecomm package boundary.
46 */
Santos Cordon8e8b8d22013-12-19 14:14:05 -080047public final class CallsManager {
Ben Gilada0d9f752014-02-26 11:49:03 -080048
Santos Cordona56f2762014-03-24 15:55:53 -070049 // TODO(santoscordon): Consider renaming this CallsManagerPlugin.
Sailesh Nepal810735e2014-03-18 18:15:46 -070050 interface CallsManagerListener {
51 void onCallAdded(Call call);
52 void onCallRemoved(Call call);
53 void onCallStateChanged(Call call, CallState oldState, CallState newState);
54 void onIncomingCallAnswered(Call call);
55 void onIncomingCallRejected(Call call);
Sailesh Nepal84fa5f82014-04-02 11:01:11 -070056 void onCallHandoffHandleChanged(Call call, Uri oldHandle, Uri newHandle);
Sailesh Nepal810735e2014-03-18 18:15:46 -070057 void onForegroundCallChanged(Call oldForegroundCall, Call newForegroundCall);
Sailesh Nepal6aca10a2014-03-24 16:11:02 -070058 void onAudioStateChanged(CallAudioState oldAudioState, CallAudioState newAudioState);
Sailesh Nepal810735e2014-03-18 18:15:46 -070059 }
60
Santos Cordon8e8b8d22013-12-19 14:14:05 -080061 private static final CallsManager INSTANCE = new CallsManager();
Ben Gilad9f2bed32013-12-12 17:43:26 -080062
Santos Cordone3d76ab2014-01-28 17:25:20 -080063 private final Switchboard mSwitchboard;
64
Santos Cordon8e8b8d22013-12-19 14:14:05 -080065 /**
Sailesh Nepale59bb192014-04-01 18:33:59 -070066 * The main call repository. Keeps an instance of all live calls. New incoming and outgoing
67 * calls are added to the map and removed when the calls move to the disconnected state.
Santos Cordon681663d2014-01-30 04:32:15 -080068 */
Sailesh Nepale59bb192014-04-01 18:33:59 -070069 private final Set<Call> mCalls = Sets.newLinkedHashSet();
Santos Cordon681663d2014-01-30 04:32:15 -080070
71 /**
Sailesh Nepal84fa5f82014-04-02 11:01:11 -070072 * Set of new calls created to perform a handoff. The calls are added when handoff is initiated
73 * and removed when hadnoff is complete.
74 */
75 private final Set<Call> mPendingHandoffCalls = Sets.newLinkedHashSet();
76
77 /**
Sailesh Nepal810735e2014-03-18 18:15:46 -070078 * The call the user is currently interacting with. This is the call that should have audio
79 * focus and be visible in the in-call UI.
Santos Cordon4e9fffe2014-03-04 18:13:41 -080080 */
Sailesh Nepal810735e2014-03-18 18:15:46 -070081 private Call mForegroundCall;
Santos Cordon4e9fffe2014-03-04 18:13:41 -080082
Sailesh Nepal6aca10a2014-03-24 16:11:02 -070083 private final CallAudioManager mCallAudioManager;
Sailesh Nepal810735e2014-03-18 18:15:46 -070084
Santos Cordona56f2762014-03-24 15:55:53 -070085 private final Set<CallsManagerListener> mListeners = Sets.newHashSet();
Sailesh Nepal6aca10a2014-03-24 16:11:02 -070086
Evan Charltonf02e9882014-03-06 12:54:52 -080087 private final List<OutgoingCallValidator> mOutgoingCallValidators = Lists.newArrayList();
Ben Gilad9f2bed32013-12-12 17:43:26 -080088
Evan Charltonf02e9882014-03-06 12:54:52 -080089 private final List<IncomingCallValidator> mIncomingCallValidators = Lists.newArrayList();
Ben Gilad9f2bed32013-12-12 17:43:26 -080090
Santos Cordon8e8b8d22013-12-19 14:14:05 -080091 /**
Ben Gilad8bdaa462014-02-05 12:53:19 -080092 * Initializes the required Telecomm components.
Santos Cordon8e8b8d22013-12-19 14:14:05 -080093 */
94 private CallsManager() {
Santos Cordon6242b132014-02-07 16:24:42 -080095 mSwitchboard = new Switchboard(this);
Santos Cordona56f2762014-03-24 15:55:53 -070096
Sailesh Nepal810735e2014-03-18 18:15:46 -070097 mCallAudioManager = new CallAudioManager();
Santos Cordona56f2762014-03-24 15:55:53 -070098
99 mListeners.add(new CallLogManager(TelecommApp.getInstance()));
100 mListeners.add(new PhoneStateBroadcaster());
101 mListeners.add(new InCallController());
102 mListeners.add(new Ringer(mCallAudioManager));
103 mListeners.add(new RingbackPlayer(this, new InCallTonePlayer.Factory(mCallAudioManager)));
104 mListeners.add(mCallAudioManager);
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800105 }
106
Ben Gilada0d9f752014-02-26 11:49:03 -0800107 static CallsManager getInstance() {
108 return INSTANCE;
109 }
110
Sailesh Nepal810735e2014-03-18 18:15:46 -0700111 ImmutableCollection<Call> getCalls() {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700112 return ImmutableList.copyOf(mCalls);
Sailesh Nepal810735e2014-03-18 18:15:46 -0700113 }
114
115 Call getForegroundCall() {
116 return mForegroundCall;
117 }
118
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700119 boolean hasEmergencyCall() {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700120 for (Call call : mCalls) {
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700121 if (call.isEmergencyCall()) {
122 return true;
123 }
124 }
125 return false;
126 }
127
128 CallAudioState getAudioState() {
129 return mCallAudioManager.getAudioState();
130 }
131
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800132 /**
Santos Cordon493e8f22014-02-19 03:15:12 -0800133 * Starts the incoming call sequence by having switchboard gather more information about the
134 * specified call; using the specified call service descriptor. Upon success, execution returns
135 * to {@link #handleSuccessfulIncomingCall} to start the in-call UI.
Santos Cordon80d9bdc2014-02-13 18:28:46 -0800136 *
Ben Giladc5b22692014-02-18 20:03:22 -0800137 * @param descriptor The descriptor of the call service to use for this incoming call.
Evan Charltona05805b2014-03-05 08:21:46 -0800138 * @param extras The optional extras Bundle passed with the intent used for the incoming call.
Santos Cordon80d9bdc2014-02-13 18:28:46 -0800139 */
Evan Charltona05805b2014-03-05 08:21:46 -0800140 void processIncomingCallIntent(CallServiceDescriptor descriptor, Bundle extras) {
Sailesh Nepalf1c191d2014-03-07 18:17:39 -0800141 Log.d(this, "processIncomingCallIntent");
Santos Cordon80d9bdc2014-02-13 18:28:46 -0800142 // Create a call with no handle. Eventually, switchboard will update the call with
Sailesh Nepale59bb192014-04-01 18:33:59 -0700143 // additional information from the call service, but for now we just need one to pass
144 // around.
Sailesh Nepal810735e2014-03-18 18:15:46 -0700145 Call call = new Call(true /* isIncoming */);
Santos Cordon80d9bdc2014-02-13 18:28:46 -0800146
Evan Charltona05805b2014-03-05 08:21:46 -0800147 mSwitchboard.retrieveIncomingCall(call, descriptor, extras);
Santos Cordon80d9bdc2014-02-13 18:28:46 -0800148 }
149
150 /**
Ben Gilada0d9f752014-02-26 11:49:03 -0800151 * Validates the specified call and, upon no objection to connect it, adds the new call to the
152 * list of live calls. Also notifies the in-call app so the user can answer or reject the call.
153 *
154 * @param call The new incoming call.
Sailesh Nepal810735e2014-03-18 18:15:46 -0700155 * @param callInfo The details of the call.
Ben Gilada0d9f752014-02-26 11:49:03 -0800156 */
Sailesh Nepal810735e2014-03-18 18:15:46 -0700157 void handleSuccessfulIncomingCall(Call call, CallInfo callInfo) {
Sailesh Nepalf1c191d2014-03-07 18:17:39 -0800158 Log.d(this, "handleSuccessfulIncomingCall");
Sailesh Nepal810735e2014-03-18 18:15:46 -0700159 Preconditions.checkState(callInfo.getState() == CallState.RINGING);
Ben Gilada0d9f752014-02-26 11:49:03 -0800160
Sailesh Nepalce704b92014-03-17 18:31:43 -0700161 Uri handle = call.getHandle();
Ben Gilada0d9f752014-02-26 11:49:03 -0800162 ContactInfo contactInfo = call.getContactInfo();
163 for (IncomingCallValidator validator : mIncomingCallValidators) {
164 if (!validator.isValid(handle, contactInfo)) {
165 // TODO(gilad): Consider displaying an error message.
Sailesh Nepalf1c191d2014-03-07 18:17:39 -0800166 Log.i(this, "Dropping restricted incoming call");
Ben Gilada0d9f752014-02-26 11:49:03 -0800167 return;
168 }
169 }
170
171 // No objection to accept the incoming call, proceed with potentially connecting it (based
172 // on the user's action, or lack thereof).
Sailesh Nepal810735e2014-03-18 18:15:46 -0700173 call.setHandle(callInfo.getHandle());
174 setCallState(call, callInfo.getState());
Ben Gilada0d9f752014-02-26 11:49:03 -0800175 addCall(call);
Sailesh Nepal810735e2014-03-18 18:15:46 -0700176 }
Santos Cordon4e9fffe2014-03-04 18:13:41 -0800177
Sailesh Nepal810735e2014-03-18 18:15:46 -0700178 /**
179 * Called when an incoming call was not connected.
180 *
181 * @param call The incoming call.
182 */
183 void handleUnsuccessfulIncomingCall(Call call) {
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700184 // Incoming calls are not added unless they are successful. We set the state and disconnect
185 // cause just as a matter of good bookkeeping. We do not use the specific methods for
186 // setting those values so that we do not trigger CallsManagerListener events.
187 // TODO: Needs more specific disconnect error for this case.
188 call.setDisconnectCause(DisconnectCause.ERROR_UNSPECIFIED, null);
189 call.setState(CallState.DISCONNECTED);
Ben Gilada0d9f752014-02-26 11:49:03 -0800190 }
191
192 /**
Yorke Lee33501632014-03-17 19:24:12 -0700193 * Attempts to issue/connect the specified call.
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800194 *
Yorke Lee33501632014-03-17 19:24:12 -0700195 * @param handle Handle to connect the call with.
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800196 * @param contactInfo Information about the entity being called.
Yorke Lee33501632014-03-17 19:24:12 -0700197 * @param gatewayInfo Optional gateway information that can be used to route the call to the
Santos Cordon5b7b9b32014-03-26 14:00:22 -0700198 * actual dialed handle via a gateway provider. May be null.
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800199 */
Yorke Lee33501632014-03-17 19:24:12 -0700200 void placeOutgoingCall(Uri handle, ContactInfo contactInfo, GatewayInfo gatewayInfo) {
Ben Gilad8bdaa462014-02-05 12:53:19 -0800201 for (OutgoingCallValidator validator : mOutgoingCallValidators) {
Ben Gilada0d9f752014-02-26 11:49:03 -0800202 if (!validator.isValid(handle, contactInfo)) {
203 // TODO(gilad): Display an error message.
Sailesh Nepalf1c191d2014-03-07 18:17:39 -0800204 Log.i(this, "Dropping restricted outgoing call.");
Ben Gilada0d9f752014-02-26 11:49:03 -0800205 return;
206 }
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800207 }
208
Yorke Lee33501632014-03-17 19:24:12 -0700209 final Uri uriHandle = (gatewayInfo == null) ? handle : gatewayInfo.getGatewayHandle();
210
211 if (gatewayInfo == null) {
212 Log.i(this, "Creating a new outgoing call with handle: %s", Log.pii(uriHandle));
213 } else {
214 Log.i(this, "Creating a new outgoing call with gateway handle: %s, original handle: %s",
215 Log.pii(uriHandle), Log.pii(handle));
216 }
Santos Cordon5b7b9b32014-03-26 14:00:22 -0700217 Call call = new Call(uriHandle, contactInfo, gatewayInfo, false /* isIncoming */);
Sailesh Nepal810735e2014-03-18 18:15:46 -0700218 setCallState(call, CallState.DIALING);
219 addCall(call);
Santos Cordonc195e362014-02-11 17:05:31 -0800220 mSwitchboard.placeOutgoingCall(call);
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800221 }
Santos Cordone3d76ab2014-01-28 17:25:20 -0800222
223 /**
Sailesh Nepal810735e2014-03-18 18:15:46 -0700224 * Called when a call service acknowledges that it can place a call.
Santos Cordon681663d2014-01-30 04:32:15 -0800225 *
226 * @param call The new outgoing call.
227 */
228 void handleSuccessfulOutgoingCall(Call call) {
Sailesh Nepal810735e2014-03-18 18:15:46 -0700229 Log.v(this, "handleSuccessfulOutgoingCall, %s", call);
Santos Cordon681663d2014-01-30 04:32:15 -0800230 }
231
232 /**
Sailesh Nepal810735e2014-03-18 18:15:46 -0700233 * Called when an outgoing call was not placed.
Yorke Leef98fb572014-03-05 10:56:55 -0800234 *
Sailesh Nepal810735e2014-03-18 18:15:46 -0700235 * @param call The outgoing call.
236 * @param isAborted True if the call was unsuccessful because it was aborted.
Yorke Leef98fb572014-03-05 10:56:55 -0800237 */
Sailesh Nepal810735e2014-03-18 18:15:46 -0700238 void handleUnsuccessfulOutgoingCall(Call call, boolean isAborted) {
239 Log.v(this, "handleAbortedOutgoingCall, call: %s, isAborted: %b", call, isAborted);
240 if (isAborted) {
241 call.abort();
242 setCallState(call, CallState.ABORTED);
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700243 removeCall(call);
Sailesh Nepal810735e2014-03-18 18:15:46 -0700244 } else {
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700245 // TODO: Replace disconnect cause with more specific disconnect causes.
Sailesh Nepale59bb192014-04-01 18:33:59 -0700246 markCallAsDisconnected(call, DisconnectCause.ERROR_UNSPECIFIED, null);
Sailesh Nepal810735e2014-03-18 18:15:46 -0700247 }
Yorke Leef98fb572014-03-05 10:56:55 -0800248 }
249
250 /**
Santos Cordone3d76ab2014-01-28 17:25:20 -0800251 * Instructs Telecomm to answer the specified call. Intended to be invoked by the in-call
252 * app through {@link InCallAdapter} after Telecomm notifies it of an incoming call followed by
253 * the user opting to answer said call.
Santos Cordone3d76ab2014-01-28 17:25:20 -0800254 */
Sailesh Nepale59bb192014-04-01 18:33:59 -0700255 void answerCall(Call call) {
256 if (!mCalls.contains(call)) {
257 Log.i(this, "Request to answer a non-existent call %s", call);
Santos Cordon61d0f702014-02-19 02:52:23 -0800258 } else {
Santos Cordona56f2762014-03-24 15:55:53 -0700259 for (CallsManagerListener listener : mListeners) {
260 listener.onIncomingCallAnswered(call);
261 }
Santos Cordon4e9fffe2014-03-04 18:13:41 -0800262
Santos Cordon61d0f702014-02-19 02:52:23 -0800263 // We do not update the UI until we get confirmation of the answer() through
Sailesh Nepale59bb192014-04-01 18:33:59 -0700264 // {@link #markCallAsActive}.
Santos Cordon61d0f702014-02-19 02:52:23 -0800265 call.answer();
266 }
Santos Cordone3d76ab2014-01-28 17:25:20 -0800267 }
268
269 /**
270 * Instructs Telecomm to reject the specified call. Intended to be invoked by the in-call
271 * app through {@link InCallAdapter} after Telecomm notifies it of an incoming call followed by
272 * the user opting to reject said call.
Santos Cordone3d76ab2014-01-28 17:25:20 -0800273 */
Sailesh Nepale59bb192014-04-01 18:33:59 -0700274 void rejectCall(Call call) {
275 if (!mCalls.contains(call)) {
276 Log.i(this, "Request to reject a non-existent call %s", call);
Santos Cordon61d0f702014-02-19 02:52:23 -0800277 } else {
Santos Cordona56f2762014-03-24 15:55:53 -0700278 for (CallsManagerListener listener : mListeners) {
279 listener.onIncomingCallRejected(call);
280 }
Santos Cordon61d0f702014-02-19 02:52:23 -0800281 call.reject();
282 }
Santos Cordone3d76ab2014-01-28 17:25:20 -0800283 }
284
285 /**
Ihab Awad74549ec2014-03-10 15:33:25 -0700286 * Instructs Telecomm to play the specified DTMF tone within the specified call.
287 *
Ihab Awad74549ec2014-03-10 15:33:25 -0700288 * @param digit The DTMF digit to play.
289 */
Sailesh Nepale59bb192014-04-01 18:33:59 -0700290 void playDtmfTone(Call call, char digit) {
291 if (!mCalls.contains(call)) {
292 Log.i(this, "Request to play DTMF in a non-existent call %s", call);
Ihab Awad74549ec2014-03-10 15:33:25 -0700293 } else {
294 call.playDtmfTone(digit);
295 }
296 }
297
298 /**
299 * Instructs Telecomm to stop the currently playing DTMF tone, if any.
Ihab Awad74549ec2014-03-10 15:33:25 -0700300 */
Sailesh Nepale59bb192014-04-01 18:33:59 -0700301 void stopDtmfTone(Call call) {
302 if (!mCalls.contains(call)) {
303 Log.i(this, "Request to stop DTMF in a non-existent call %s", call);
Ihab Awad74549ec2014-03-10 15:33:25 -0700304 } else {
305 call.stopDtmfTone();
306 }
307 }
308
309 /**
310 * Instructs Telecomm to continue the current post-dial DTMF string, if any.
Ihab Awad74549ec2014-03-10 15:33:25 -0700311 */
Sailesh Nepale59bb192014-04-01 18:33:59 -0700312 void postDialContinue(Call call) {
313 if (!mCalls.contains(call)) {
314 Log.i(this, "Request to continue post-dial string in a non-existent call %s", call);
Ihab Awad74549ec2014-03-10 15:33:25 -0700315 } else {
316 // TODO(ihab): Implement this from this level on downwards
317 // call.postDialContinue();
318 // Must play tones locally -- see DTMFTonePlayer.java in Telephony
319 }
320 }
321
322 /**
Santos Cordone3d76ab2014-01-28 17:25:20 -0800323 * Instructs Telecomm to disconnect the specified call. Intended to be invoked by the
324 * in-call app through {@link InCallAdapter} for an ongoing call. This is usually triggered by
325 * the user hitting the end-call button.
Santos Cordone3d76ab2014-01-28 17:25:20 -0800326 */
Sailesh Nepale59bb192014-04-01 18:33:59 -0700327 void disconnectCall(Call call) {
328 if (!mCalls.contains(call)) {
329 Log.w(this, "Unknown call (%s) asked to disconnect", call);
Santos Cordon049b7b62014-01-30 05:34:26 -0800330 } else {
331 call.disconnect();
332 }
Santos Cordone3d76ab2014-01-28 17:25:20 -0800333 }
Santos Cordon681663d2014-01-30 04:32:15 -0800334
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700335 /**
336 * Instructs Telecomm to put the specified call on hold. Intended to be invoked by the
337 * in-call app through {@link InCallAdapter} for an ongoing call. This is usually triggered by
338 * the user hitting the hold button during an active call.
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700339 */
Sailesh Nepale59bb192014-04-01 18:33:59 -0700340 void holdCall(Call call) {
341 if (!mCalls.contains(call)) {
342 Log.w(this, "Unknown call (%s) asked to be put on hold", call);
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700343 } else {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700344 Log.d(this, "Putting call on hold: (%s)", call);
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700345 call.hold();
346 }
347 }
348
349 /**
350 * Instructs Telecomm to release the specified call from hold. Intended to be invoked by
351 * the in-call app through {@link InCallAdapter} for an ongoing call. This is usually triggered
352 * by the user hitting the hold button during a held call.
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700353 */
Sailesh Nepale59bb192014-04-01 18:33:59 -0700354 void unholdCall(Call call) {
355 if (!mCalls.contains(call)) {
356 Log.w(this, "Unknown call (%s) asked to be removed from hold", call);
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700357 } else {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700358 Log.d(this, "Removing call from hold: (%s)", call);
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700359 call.unhold();
360 }
361 }
362
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700363 /** Called by the in-call UI to change the mute state. */
364 void mute(boolean shouldMute) {
365 mCallAudioManager.mute(shouldMute);
366 }
367
368 /**
369 * Called by the in-call UI to change the audio route, for example to change from earpiece to
370 * speaker phone.
371 */
372 void setAudioRoute(int route) {
373 mCallAudioManager.setAudioRoute(route);
374 }
375
Sailesh Nepal84fa5f82014-04-02 11:01:11 -0700376 void startHandoffForCall(Call originalCall) {
377 if (!mCalls.contains(originalCall)) {
378 Log.w(this, "Unknown call %s asked to be handed off", originalCall);
379 return;
380 }
381
382 for (Call handoffCall : mPendingHandoffCalls) {
383 if (handoffCall.getOriginalCall() == originalCall) {
384 Log.w(this, "Call %s is already being handed off, skipping", originalCall);
385 return;
386 }
387 }
388
389 // Create a new call to be placed in the background. If handoff is successful then the
390 // original call will live on but its state will be updated to the new call's state. In
391 // particular the original call's call service will be updated to the new call's call
392 // service.
393 Call tempCall = new Call(originalCall.getHandoffHandle(), originalCall.getContactInfo(),
394 originalCall.getGatewayInfo(), false);
395 tempCall.setOriginalCall(originalCall);
396 tempCall.setExtras(originalCall.getExtras());
397 tempCall.setCallServiceSelector(originalCall.getCallServiceSelector());
398 mPendingHandoffCalls.add(tempCall);
399 Log.d(this, "Placing handoff call");
400 mSwitchboard.placeOutgoingCall(tempCall);
401 }
402
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700403 /** Called when the audio state changes. */
404 void onAudioStateChanged(CallAudioState oldAudioState, CallAudioState newAudioState) {
405 Log.v(this, "onAudioStateChanged, audioState: %s -> %s", oldAudioState, newAudioState);
Santos Cordona56f2762014-03-24 15:55:53 -0700406 for (CallsManagerListener listener : mListeners) {
407 listener.onAudioStateChanged(oldAudioState, newAudioState);
408 }
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700409 }
410
Sailesh Nepale59bb192014-04-01 18:33:59 -0700411 void markCallAsRinging(Call call) {
412 setCallState(call, CallState.RINGING);
Santos Cordon681663d2014-01-30 04:32:15 -0800413 }
414
Sailesh Nepale59bb192014-04-01 18:33:59 -0700415 void markCallAsDialing(Call call) {
416 setCallState(call, CallState.DIALING);
Santos Cordon681663d2014-01-30 04:32:15 -0800417 }
418
Sailesh Nepale59bb192014-04-01 18:33:59 -0700419 void markCallAsActive(Call call) {
420 setCallState(call, CallState.ACTIVE);
Sailesh Nepal84fa5f82014-04-02 11:01:11 -0700421
422 if (mPendingHandoffCalls.contains(call)) {
423 completeHandoff(call);
424 }
Santos Cordon681663d2014-01-30 04:32:15 -0800425 }
426
Sailesh Nepale59bb192014-04-01 18:33:59 -0700427 void markCallAsOnHold(Call call) {
428 setCallState(call, CallState.ON_HOLD);
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700429 }
430
Santos Cordon049b7b62014-01-30 05:34:26 -0800431 /**
432 * Marks the specified call as DISCONNECTED and notifies the in-call app. If this was the last
433 * live call, then also disconnect from the in-call controller.
434 *
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700435 * @param disconnectCause The disconnect reason, see {@link android.telephony.DisconnectCause}.
436 * @param disconnectMessage Optional call-service-provided message about the disconnect.
Santos Cordon049b7b62014-01-30 05:34:26 -0800437 */
Sailesh Nepale59bb192014-04-01 18:33:59 -0700438 void markCallAsDisconnected(Call call, int disconnectCause, String disconnectMessage) {
439 call.setDisconnectCause(disconnectCause, disconnectMessage);
440 setCallState(call, CallState.DISCONNECTED);
441 removeCall(call);
Ben Gilada0d9f752014-02-26 11:49:03 -0800442 }
443
Sailesh Nepal6ab6fb72014-04-01 20:03:19 -0700444 void setHandoffInfo(Call call, Uri handle, Bundle extras) {
Sailesh Nepal84fa5f82014-04-02 11:01:11 -0700445 if (!mCalls.contains(call)) {
446 Log.w(this, "Unknown call (%s) asked to set handoff info", call);
447 return;
448 }
449
450 if (extras == null) {
451 call.setExtras(Bundle.EMPTY);
452 } else {
453 call.setExtras(extras);
454 }
455
456 Uri oldHandle = call.getHandoffHandle();
457 Log.v(this, "set handoff handle %s -> %s, for call: %s", oldHandle, handle, call);
458 if (!areUriEqual(oldHandle, handle)) {
459 call.setHandoffHandle(handle);
460 for (CallsManagerListener listener : mListeners) {
461 listener.onCallHandoffHandleChanged(call, oldHandle, handle);
462 }
463 }
Sailesh Nepal6ab6fb72014-04-01 20:03:19 -0700464 }
465
Ben Gilada0d9f752014-02-26 11:49:03 -0800466 /**
Santos Cordon4b2c1192014-03-19 18:15:38 -0700467 * Cleans up any calls currently associated with the specified call service when the
468 * call-service binder disconnects unexpectedly.
469 *
470 * @param callService The call service that disconnected.
471 */
472 void handleCallServiceDeath(CallServiceWrapper callService) {
473 Preconditions.checkNotNull(callService);
Sailesh Nepale59bb192014-04-01 18:33:59 -0700474 for (Call call : ImmutableList.copyOf(mCalls)) {
Santos Cordon4b2c1192014-03-19 18:15:38 -0700475 if (call.getCallService() == callService) {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700476 markCallAsDisconnected(call, DisconnectCause.ERROR_UNSPECIFIED, null);
Santos Cordon4b2c1192014-03-19 18:15:38 -0700477 }
478 }
479 }
480
481 /**
Ben Gilada0d9f752014-02-26 11:49:03 -0800482 * Adds the specified call to the main list of live calls.
483 *
484 * @param call The call to add.
485 */
486 private void addCall(Call call) {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700487 mCalls.add(call);
Santos Cordona56f2762014-03-24 15:55:53 -0700488 for (CallsManagerListener listener : mListeners) {
489 listener.onCallAdded(call);
490 }
Sailesh Nepal810735e2014-03-18 18:15:46 -0700491 updateForegroundCall();
Ben Gilada0d9f752014-02-26 11:49:03 -0800492 }
493
Sailesh Nepal810735e2014-03-18 18:15:46 -0700494 private void removeCall(Call call) {
495 call.clearCallService();
Sailesh Nepale59bb192014-04-01 18:33:59 -0700496 call.clearCallServiceSelector();
497
498 boolean shouldNotify = false;
499 if (mCalls.contains(call)) {
500 mCalls.remove(call);
501 shouldNotify = true;
Sailesh Nepal84fa5f82014-04-02 11:01:11 -0700502 cleanUpHandoffCallsForOriginalCall(call);
503 } else if (mPendingHandoffCalls.contains(call)) {
504 Log.v(this, "silently removing handoff call %s", call);
505 mPendingHandoffCalls.remove(call);
Santos Cordona56f2762014-03-24 15:55:53 -0700506 }
Ben Gilada0d9f752014-02-26 11:49:03 -0800507
Sailesh Nepale59bb192014-04-01 18:33:59 -0700508 // Only broadcast changes for calls that are being tracked.
509 if (shouldNotify) {
510 for (CallsManagerListener listener : mListeners) {
511 listener.onCallRemoved(call);
512 }
513 updateForegroundCall();
Sailesh Nepal810735e2014-03-18 18:15:46 -0700514 }
515 }
Evan Charlton5c670a92014-03-06 14:58:20 -0800516
Sailesh Nepal810735e2014-03-18 18:15:46 -0700517 /**
Santos Cordon1ae2b852014-03-19 03:03:10 -0700518 * Sets the specified state on the specified call.
Sailesh Nepal810735e2014-03-18 18:15:46 -0700519 *
520 * @param call The call.
521 * @param newState The new state of the call.
522 */
523 private void setCallState(Call call, CallState newState) {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700524 Preconditions.checkNotNull(newState);
Sailesh Nepal810735e2014-03-18 18:15:46 -0700525 CallState oldState = call.getState();
526 if (newState != oldState) {
527 // Unfortunately, in the telephony world the radio is king. So if the call notifies
528 // us that the call is in a particular state, we allow it even if it doesn't make
529 // sense (e.g., ACTIVE -> RINGING).
530 // TODO(santoscordon): Consider putting a stop to the above and turning CallState
531 // into a well-defined state machine.
532 // TODO(santoscordon): Define expected state transitions here, and log when an
533 // unexpected transition occurs.
534 call.setState(newState);
535
536 // Only broadcast state change for calls that are being tracked.
Sailesh Nepale59bb192014-04-01 18:33:59 -0700537 if (mCalls.contains(call)) {
Santos Cordona56f2762014-03-24 15:55:53 -0700538 for (CallsManagerListener listener : mListeners) {
539 listener.onCallStateChanged(call, oldState, newState);
540 }
Sailesh Nepal810735e2014-03-18 18:15:46 -0700541 updateForegroundCall();
Santos Cordon4e9fffe2014-03-04 18:13:41 -0800542 }
543 }
544 }
545
546 /**
Sailesh Nepal810735e2014-03-18 18:15:46 -0700547 * Checks which call should be visible to the user and have audio focus.
Evan Charlton5c670a92014-03-06 14:58:20 -0800548 */
Sailesh Nepal810735e2014-03-18 18:15:46 -0700549 private void updateForegroundCall() {
550 Call newForegroundCall = null;
Sailesh Nepale59bb192014-04-01 18:33:59 -0700551 for (Call call : mCalls) {
Sailesh Nepal810735e2014-03-18 18:15:46 -0700552 // Incoming ringing calls have priority.
553 if (call.getState() == CallState.RINGING) {
554 newForegroundCall = call;
Evan Charlton5c670a92014-03-06 14:58:20 -0800555 break;
Sailesh Nepal810735e2014-03-18 18:15:46 -0700556 }
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700557 if (call.isAlive()) {
Sailesh Nepal810735e2014-03-18 18:15:46 -0700558 newForegroundCall = call;
559 // Don't break in case there's a ringing call that has priority.
Santos Cordon4e9fffe2014-03-04 18:13:41 -0800560 }
561 }
Santos Cordon4e9fffe2014-03-04 18:13:41 -0800562
Sailesh Nepal810735e2014-03-18 18:15:46 -0700563 if (newForegroundCall != mForegroundCall) {
564 Call oldForegroundCall = mForegroundCall;
565 mForegroundCall = newForegroundCall;
566
Santos Cordona56f2762014-03-24 15:55:53 -0700567 for (CallsManagerListener listener : mListeners) {
568 listener.onForegroundCallChanged(oldForegroundCall, mForegroundCall);
569 }
Santos Cordon681663d2014-01-30 04:32:15 -0800570 }
571 }
Sailesh Nepal84fa5f82014-04-02 11:01:11 -0700572
573 private void completeHandoff(Call handoffCall) {
574 Call originalCall = handoffCall.getOriginalCall();
575 Log.v(this, "complete handoff, %s -> %s", handoffCall, originalCall);
576
577 // Disconnect.
578 originalCall.disconnect();
579
580 // Synchronize.
581 originalCall.setCallService(handoffCall.getCallService());
582 setCallState(originalCall, handoffCall.getState());
583
584 // Remove the transient handoff call object (don't disconnect because the call is still
585 // live).
586 removeCall(handoffCall);
587
588 // Force the foreground call changed notification to be sent.
589 for (CallsManagerListener listener : mListeners) {
590 listener.onForegroundCallChanged(mForegroundCall, mForegroundCall);
591 }
592 }
593
594 /** Makes sure there are no dangling handoff calls. */
595 private void cleanUpHandoffCallsForOriginalCall(Call originalCall) {
596 for (Call handoffCall : ImmutableList.copyOf((mPendingHandoffCalls))) {
597 if (handoffCall.getOriginalCall() == originalCall) {
598 Log.d(this, "cancelling handoff call %s for originalCall: %s", handoffCall,
599 originalCall);
600 if (handoffCall.getState() == CallState.NEW) {
601 handoffCall.abort();
602 handoffCall.setState(CallState.ABORTED);
603 } else {
604 handoffCall.disconnect();
605 handoffCall.setState(CallState.DISCONNECTED);
606 }
607 removeCall(handoffCall);
608 }
609 }
610 }
611
612 private static boolean areUriEqual(Uri handle1, Uri handle2) {
613 if (handle1 == null) {
614 return handle2 == null;
615 }
616 return handle1.equals(handle2);
617 }
Ben Gilad9f2bed32013-12-12 17:43:26 -0800618}