blob: b8724e34deaf4ceab49d05ae8477415d873a3b9f [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 Cordona0e5f1a2014-03-31 21:43:00 -070095 TelecommApp app = TelecommApp.getInstance();
Santos Cordona56f2762014-03-24 15:55:53 -070096
Santos Cordona0e5f1a2014-03-31 21:43:00 -070097 mSwitchboard = new Switchboard(this);
Sailesh Nepal810735e2014-03-18 18:15:46 -070098 mCallAudioManager = new CallAudioManager();
Santos Cordona56f2762014-03-24 15:55:53 -070099
Santos Cordonc7b8eba2014-04-01 15:26:28 -0700100 InCallTonePlayer.Factory playerFactory = new InCallTonePlayer.Factory(mCallAudioManager);
Santos Cordona0e5f1a2014-03-31 21:43:00 -0700101 mListeners.add(new CallLogManager(app));
Santos Cordona56f2762014-03-24 15:55:53 -0700102 mListeners.add(new PhoneStateBroadcaster());
103 mListeners.add(new InCallController());
104 mListeners.add(new Ringer(mCallAudioManager));
Santos Cordonc7b8eba2014-04-01 15:26:28 -0700105 mListeners.add(new RingbackPlayer(this, playerFactory));
106 mListeners.add(new InCallToneMonitor(playerFactory, this));
Santos Cordona56f2762014-03-24 15:55:53 -0700107 mListeners.add(mCallAudioManager);
Santos Cordona0e5f1a2014-03-31 21:43:00 -0700108 mListeners.add(app.getMissedCallNotifier());
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800109 }
110
Ben Gilada0d9f752014-02-26 11:49:03 -0800111 static CallsManager getInstance() {
112 return INSTANCE;
113 }
114
Sailesh Nepal810735e2014-03-18 18:15:46 -0700115 ImmutableCollection<Call> getCalls() {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700116 return ImmutableList.copyOf(mCalls);
Sailesh Nepal810735e2014-03-18 18:15:46 -0700117 }
118
119 Call getForegroundCall() {
120 return mForegroundCall;
121 }
122
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700123 boolean hasEmergencyCall() {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700124 for (Call call : mCalls) {
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700125 if (call.isEmergencyCall()) {
126 return true;
127 }
128 }
129 return false;
130 }
131
132 CallAudioState getAudioState() {
133 return mCallAudioManager.getAudioState();
134 }
135
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800136 /**
Santos Cordon493e8f22014-02-19 03:15:12 -0800137 * Starts the incoming call sequence by having switchboard gather more information about the
138 * specified call; using the specified call service descriptor. Upon success, execution returns
139 * to {@link #handleSuccessfulIncomingCall} to start the in-call UI.
Santos Cordon80d9bdc2014-02-13 18:28:46 -0800140 *
Ben Giladc5b22692014-02-18 20:03:22 -0800141 * @param descriptor The descriptor of the call service to use for this incoming call.
Evan Charltona05805b2014-03-05 08:21:46 -0800142 * @param extras The optional extras Bundle passed with the intent used for the incoming call.
Santos Cordon80d9bdc2014-02-13 18:28:46 -0800143 */
Evan Charltona05805b2014-03-05 08:21:46 -0800144 void processIncomingCallIntent(CallServiceDescriptor descriptor, Bundle extras) {
Sailesh Nepalf1c191d2014-03-07 18:17:39 -0800145 Log.d(this, "processIncomingCallIntent");
Santos Cordon80d9bdc2014-02-13 18:28:46 -0800146 // Create a call with no handle. Eventually, switchboard will update the call with
Sailesh Nepale59bb192014-04-01 18:33:59 -0700147 // additional information from the call service, but for now we just need one to pass
148 // around.
Sailesh Nepal810735e2014-03-18 18:15:46 -0700149 Call call = new Call(true /* isIncoming */);
Santos Cordon80d9bdc2014-02-13 18:28:46 -0800150
Evan Charltona05805b2014-03-05 08:21:46 -0800151 mSwitchboard.retrieveIncomingCall(call, descriptor, extras);
Santos Cordon80d9bdc2014-02-13 18:28:46 -0800152 }
153
154 /**
Ben Gilada0d9f752014-02-26 11:49:03 -0800155 * Validates the specified call and, upon no objection to connect it, adds the new call to the
156 * list of live calls. Also notifies the in-call app so the user can answer or reject the call.
157 *
158 * @param call The new incoming call.
Sailesh Nepal810735e2014-03-18 18:15:46 -0700159 * @param callInfo The details of the call.
Ben Gilada0d9f752014-02-26 11:49:03 -0800160 */
Sailesh Nepal810735e2014-03-18 18:15:46 -0700161 void handleSuccessfulIncomingCall(Call call, CallInfo callInfo) {
Sailesh Nepalf1c191d2014-03-07 18:17:39 -0800162 Log.d(this, "handleSuccessfulIncomingCall");
Sailesh Nepal810735e2014-03-18 18:15:46 -0700163 Preconditions.checkState(callInfo.getState() == CallState.RINGING);
Ben Gilada0d9f752014-02-26 11:49:03 -0800164
Sailesh Nepalce704b92014-03-17 18:31:43 -0700165 Uri handle = call.getHandle();
Ben Gilada0d9f752014-02-26 11:49:03 -0800166 ContactInfo contactInfo = call.getContactInfo();
167 for (IncomingCallValidator validator : mIncomingCallValidators) {
168 if (!validator.isValid(handle, contactInfo)) {
169 // TODO(gilad): Consider displaying an error message.
Sailesh Nepalf1c191d2014-03-07 18:17:39 -0800170 Log.i(this, "Dropping restricted incoming call");
Ben Gilada0d9f752014-02-26 11:49:03 -0800171 return;
172 }
173 }
174
175 // No objection to accept the incoming call, proceed with potentially connecting it (based
176 // on the user's action, or lack thereof).
Sailesh Nepal810735e2014-03-18 18:15:46 -0700177 call.setHandle(callInfo.getHandle());
178 setCallState(call, callInfo.getState());
Ben Gilada0d9f752014-02-26 11:49:03 -0800179 addCall(call);
Sailesh Nepal810735e2014-03-18 18:15:46 -0700180 }
Santos Cordon4e9fffe2014-03-04 18:13:41 -0800181
Sailesh Nepal810735e2014-03-18 18:15:46 -0700182 /**
183 * Called when an incoming call was not connected.
184 *
185 * @param call The incoming call.
186 */
187 void handleUnsuccessfulIncomingCall(Call call) {
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700188 // Incoming calls are not added unless they are successful. We set the state and disconnect
189 // cause just as a matter of good bookkeeping. We do not use the specific methods for
190 // setting those values so that we do not trigger CallsManagerListener events.
191 // TODO: Needs more specific disconnect error for this case.
192 call.setDisconnectCause(DisconnectCause.ERROR_UNSPECIFIED, null);
193 call.setState(CallState.DISCONNECTED);
Ben Gilada0d9f752014-02-26 11:49:03 -0800194 }
195
196 /**
Yorke Lee33501632014-03-17 19:24:12 -0700197 * Attempts to issue/connect the specified call.
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800198 *
Yorke Lee33501632014-03-17 19:24:12 -0700199 * @param handle Handle to connect the call with.
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800200 * @param contactInfo Information about the entity being called.
Yorke Lee33501632014-03-17 19:24:12 -0700201 * @param gatewayInfo Optional gateway information that can be used to route the call to the
Santos Cordon5b7b9b32014-03-26 14:00:22 -0700202 * actual dialed handle via a gateway provider. May be null.
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800203 */
Yorke Lee33501632014-03-17 19:24:12 -0700204 void placeOutgoingCall(Uri handle, ContactInfo contactInfo, GatewayInfo gatewayInfo) {
Ben Gilad8bdaa462014-02-05 12:53:19 -0800205 for (OutgoingCallValidator validator : mOutgoingCallValidators) {
Ben Gilada0d9f752014-02-26 11:49:03 -0800206 if (!validator.isValid(handle, contactInfo)) {
207 // TODO(gilad): Display an error message.
Sailesh Nepalf1c191d2014-03-07 18:17:39 -0800208 Log.i(this, "Dropping restricted outgoing call.");
Ben Gilada0d9f752014-02-26 11:49:03 -0800209 return;
210 }
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800211 }
212
Yorke Lee33501632014-03-17 19:24:12 -0700213 final Uri uriHandle = (gatewayInfo == null) ? handle : gatewayInfo.getGatewayHandle();
214
215 if (gatewayInfo == null) {
216 Log.i(this, "Creating a new outgoing call with handle: %s", Log.pii(uriHandle));
217 } else {
218 Log.i(this, "Creating a new outgoing call with gateway handle: %s, original handle: %s",
219 Log.pii(uriHandle), Log.pii(handle));
220 }
Santos Cordon5b7b9b32014-03-26 14:00:22 -0700221 Call call = new Call(uriHandle, contactInfo, gatewayInfo, false /* isIncoming */);
Sailesh Nepal810735e2014-03-18 18:15:46 -0700222 setCallState(call, CallState.DIALING);
223 addCall(call);
Santos Cordonc195e362014-02-11 17:05:31 -0800224 mSwitchboard.placeOutgoingCall(call);
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800225 }
Santos Cordone3d76ab2014-01-28 17:25:20 -0800226
227 /**
Sailesh Nepal810735e2014-03-18 18:15:46 -0700228 * Called when a call service acknowledges that it can place a call.
Santos Cordon681663d2014-01-30 04:32:15 -0800229 *
230 * @param call The new outgoing call.
231 */
232 void handleSuccessfulOutgoingCall(Call call) {
Sailesh Nepal810735e2014-03-18 18:15:46 -0700233 Log.v(this, "handleSuccessfulOutgoingCall, %s", call);
Santos Cordon681663d2014-01-30 04:32:15 -0800234 }
235
236 /**
Sailesh Nepal810735e2014-03-18 18:15:46 -0700237 * Called when an outgoing call was not placed.
Yorke Leef98fb572014-03-05 10:56:55 -0800238 *
Sailesh Nepal810735e2014-03-18 18:15:46 -0700239 * @param call The outgoing call.
240 * @param isAborted True if the call was unsuccessful because it was aborted.
Yorke Leef98fb572014-03-05 10:56:55 -0800241 */
Sailesh Nepal810735e2014-03-18 18:15:46 -0700242 void handleUnsuccessfulOutgoingCall(Call call, boolean isAborted) {
243 Log.v(this, "handleAbortedOutgoingCall, call: %s, isAborted: %b", call, isAborted);
244 if (isAborted) {
245 call.abort();
246 setCallState(call, CallState.ABORTED);
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700247 removeCall(call);
Sailesh Nepal810735e2014-03-18 18:15:46 -0700248 } else {
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700249 // TODO: Replace disconnect cause with more specific disconnect causes.
Sailesh Nepale59bb192014-04-01 18:33:59 -0700250 markCallAsDisconnected(call, DisconnectCause.ERROR_UNSPECIFIED, null);
Sailesh Nepal810735e2014-03-18 18:15:46 -0700251 }
Yorke Leef98fb572014-03-05 10:56:55 -0800252 }
253
254 /**
Santos Cordone3d76ab2014-01-28 17:25:20 -0800255 * Instructs Telecomm to answer the specified call. Intended to be invoked by the in-call
256 * app through {@link InCallAdapter} after Telecomm notifies it of an incoming call followed by
257 * the user opting to answer said call.
Santos Cordone3d76ab2014-01-28 17:25:20 -0800258 */
Sailesh Nepale59bb192014-04-01 18:33:59 -0700259 void answerCall(Call call) {
260 if (!mCalls.contains(call)) {
261 Log.i(this, "Request to answer a non-existent call %s", call);
Santos Cordon61d0f702014-02-19 02:52:23 -0800262 } else {
Santos Cordona56f2762014-03-24 15:55:53 -0700263 for (CallsManagerListener listener : mListeners) {
264 listener.onIncomingCallAnswered(call);
265 }
Santos Cordon4e9fffe2014-03-04 18:13:41 -0800266
Santos Cordon61d0f702014-02-19 02:52:23 -0800267 // We do not update the UI until we get confirmation of the answer() through
Sailesh Nepale59bb192014-04-01 18:33:59 -0700268 // {@link #markCallAsActive}.
Santos Cordon61d0f702014-02-19 02:52:23 -0800269 call.answer();
270 }
Santos Cordone3d76ab2014-01-28 17:25:20 -0800271 }
272
273 /**
274 * Instructs Telecomm to reject the specified call. Intended to be invoked by the in-call
275 * app through {@link InCallAdapter} after Telecomm notifies it of an incoming call followed by
276 * the user opting to reject said call.
Santos Cordone3d76ab2014-01-28 17:25:20 -0800277 */
Sailesh Nepale59bb192014-04-01 18:33:59 -0700278 void rejectCall(Call call) {
279 if (!mCalls.contains(call)) {
280 Log.i(this, "Request to reject a non-existent call %s", call);
Santos Cordon61d0f702014-02-19 02:52:23 -0800281 } else {
Santos Cordona56f2762014-03-24 15:55:53 -0700282 for (CallsManagerListener listener : mListeners) {
283 listener.onIncomingCallRejected(call);
284 }
Santos Cordon61d0f702014-02-19 02:52:23 -0800285 call.reject();
286 }
Santos Cordone3d76ab2014-01-28 17:25:20 -0800287 }
288
289 /**
Ihab Awad74549ec2014-03-10 15:33:25 -0700290 * Instructs Telecomm to play the specified DTMF tone within the specified call.
291 *
Ihab Awad74549ec2014-03-10 15:33:25 -0700292 * @param digit The DTMF digit to play.
293 */
Sailesh Nepale59bb192014-04-01 18:33:59 -0700294 void playDtmfTone(Call call, char digit) {
295 if (!mCalls.contains(call)) {
296 Log.i(this, "Request to play DTMF in a non-existent call %s", call);
Ihab Awad74549ec2014-03-10 15:33:25 -0700297 } else {
298 call.playDtmfTone(digit);
299 }
300 }
301
302 /**
303 * Instructs Telecomm to stop the currently playing DTMF tone, if any.
Ihab Awad74549ec2014-03-10 15:33:25 -0700304 */
Sailesh Nepale59bb192014-04-01 18:33:59 -0700305 void stopDtmfTone(Call call) {
306 if (!mCalls.contains(call)) {
307 Log.i(this, "Request to stop DTMF in a non-existent call %s", call);
Ihab Awad74549ec2014-03-10 15:33:25 -0700308 } else {
309 call.stopDtmfTone();
310 }
311 }
312
313 /**
314 * Instructs Telecomm to continue the current post-dial DTMF string, if any.
Ihab Awad74549ec2014-03-10 15:33:25 -0700315 */
Sailesh Nepale59bb192014-04-01 18:33:59 -0700316 void postDialContinue(Call call) {
317 if (!mCalls.contains(call)) {
318 Log.i(this, "Request to continue post-dial string in a non-existent call %s", call);
Ihab Awad74549ec2014-03-10 15:33:25 -0700319 } else {
320 // TODO(ihab): Implement this from this level on downwards
321 // call.postDialContinue();
322 // Must play tones locally -- see DTMFTonePlayer.java in Telephony
323 }
324 }
325
326 /**
Santos Cordone3d76ab2014-01-28 17:25:20 -0800327 * Instructs Telecomm to disconnect the specified call. Intended to be invoked by the
328 * in-call app through {@link InCallAdapter} for an ongoing call. This is usually triggered by
329 * the user hitting the end-call button.
Santos Cordone3d76ab2014-01-28 17:25:20 -0800330 */
Sailesh Nepale59bb192014-04-01 18:33:59 -0700331 void disconnectCall(Call call) {
332 if (!mCalls.contains(call)) {
333 Log.w(this, "Unknown call (%s) asked to disconnect", call);
Santos Cordon049b7b62014-01-30 05:34:26 -0800334 } else {
335 call.disconnect();
336 }
Santos Cordone3d76ab2014-01-28 17:25:20 -0800337 }
Santos Cordon681663d2014-01-30 04:32:15 -0800338
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700339 /**
340 * Instructs Telecomm to put the specified call on hold. Intended to be invoked by the
341 * in-call app through {@link InCallAdapter} for an ongoing call. This is usually triggered by
342 * the user hitting the hold button during an active call.
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700343 */
Sailesh Nepale59bb192014-04-01 18:33:59 -0700344 void holdCall(Call call) {
345 if (!mCalls.contains(call)) {
346 Log.w(this, "Unknown call (%s) asked to be put on hold", call);
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700347 } else {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700348 Log.d(this, "Putting call on hold: (%s)", call);
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700349 call.hold();
350 }
351 }
352
353 /**
354 * Instructs Telecomm to release the specified call from hold. Intended to be invoked by
355 * the in-call app through {@link InCallAdapter} for an ongoing call. This is usually triggered
356 * by the user hitting the hold button during a held call.
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700357 */
Sailesh Nepale59bb192014-04-01 18:33:59 -0700358 void unholdCall(Call call) {
359 if (!mCalls.contains(call)) {
360 Log.w(this, "Unknown call (%s) asked to be removed from hold", call);
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700361 } else {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700362 Log.d(this, "Removing call from hold: (%s)", call);
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700363 call.unhold();
364 }
365 }
366
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700367 /** Called by the in-call UI to change the mute state. */
368 void mute(boolean shouldMute) {
369 mCallAudioManager.mute(shouldMute);
370 }
371
372 /**
373 * Called by the in-call UI to change the audio route, for example to change from earpiece to
374 * speaker phone.
375 */
376 void setAudioRoute(int route) {
377 mCallAudioManager.setAudioRoute(route);
378 }
379
Sailesh Nepal84fa5f82014-04-02 11:01:11 -0700380 void startHandoffForCall(Call originalCall) {
381 if (!mCalls.contains(originalCall)) {
382 Log.w(this, "Unknown call %s asked to be handed off", originalCall);
383 return;
384 }
385
386 for (Call handoffCall : mPendingHandoffCalls) {
387 if (handoffCall.getOriginalCall() == originalCall) {
388 Log.w(this, "Call %s is already being handed off, skipping", originalCall);
389 return;
390 }
391 }
392
393 // Create a new call to be placed in the background. If handoff is successful then the
394 // original call will live on but its state will be updated to the new call's state. In
395 // particular the original call's call service will be updated to the new call's call
396 // service.
397 Call tempCall = new Call(originalCall.getHandoffHandle(), originalCall.getContactInfo(),
398 originalCall.getGatewayInfo(), false);
399 tempCall.setOriginalCall(originalCall);
400 tempCall.setExtras(originalCall.getExtras());
401 tempCall.setCallServiceSelector(originalCall.getCallServiceSelector());
402 mPendingHandoffCalls.add(tempCall);
403 Log.d(this, "Placing handoff call");
404 mSwitchboard.placeOutgoingCall(tempCall);
405 }
406
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700407 /** Called when the audio state changes. */
408 void onAudioStateChanged(CallAudioState oldAudioState, CallAudioState newAudioState) {
409 Log.v(this, "onAudioStateChanged, audioState: %s -> %s", oldAudioState, newAudioState);
Santos Cordona56f2762014-03-24 15:55:53 -0700410 for (CallsManagerListener listener : mListeners) {
411 listener.onAudioStateChanged(oldAudioState, newAudioState);
412 }
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700413 }
414
Sailesh Nepale59bb192014-04-01 18:33:59 -0700415 void markCallAsRinging(Call call) {
416 setCallState(call, CallState.RINGING);
Santos Cordon681663d2014-01-30 04:32:15 -0800417 }
418
Sailesh Nepale59bb192014-04-01 18:33:59 -0700419 void markCallAsDialing(Call call) {
420 setCallState(call, CallState.DIALING);
Santos Cordon681663d2014-01-30 04:32:15 -0800421 }
422
Sailesh Nepale59bb192014-04-01 18:33:59 -0700423 void markCallAsActive(Call call) {
424 setCallState(call, CallState.ACTIVE);
Sailesh Nepal84fa5f82014-04-02 11:01:11 -0700425
426 if (mPendingHandoffCalls.contains(call)) {
427 completeHandoff(call);
428 }
Santos Cordon681663d2014-01-30 04:32:15 -0800429 }
430
Sailesh Nepale59bb192014-04-01 18:33:59 -0700431 void markCallAsOnHold(Call call) {
432 setCallState(call, CallState.ON_HOLD);
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700433 }
434
Santos Cordon049b7b62014-01-30 05:34:26 -0800435 /**
436 * Marks the specified call as DISCONNECTED and notifies the in-call app. If this was the last
437 * live call, then also disconnect from the in-call controller.
438 *
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700439 * @param disconnectCause The disconnect reason, see {@link android.telephony.DisconnectCause}.
440 * @param disconnectMessage Optional call-service-provided message about the disconnect.
Santos Cordon049b7b62014-01-30 05:34:26 -0800441 */
Sailesh Nepale59bb192014-04-01 18:33:59 -0700442 void markCallAsDisconnected(Call call, int disconnectCause, String disconnectMessage) {
443 call.setDisconnectCause(disconnectCause, disconnectMessage);
444 setCallState(call, CallState.DISCONNECTED);
445 removeCall(call);
Ben Gilada0d9f752014-02-26 11:49:03 -0800446 }
447
Sailesh Nepal6ab6fb72014-04-01 20:03:19 -0700448 void setHandoffInfo(Call call, Uri handle, Bundle extras) {
Sailesh Nepal84fa5f82014-04-02 11:01:11 -0700449 if (!mCalls.contains(call)) {
450 Log.w(this, "Unknown call (%s) asked to set handoff info", call);
451 return;
452 }
453
454 if (extras == null) {
455 call.setExtras(Bundle.EMPTY);
456 } else {
457 call.setExtras(extras);
458 }
459
460 Uri oldHandle = call.getHandoffHandle();
461 Log.v(this, "set handoff handle %s -> %s, for call: %s", oldHandle, handle, call);
462 if (!areUriEqual(oldHandle, handle)) {
463 call.setHandoffHandle(handle);
464 for (CallsManagerListener listener : mListeners) {
465 listener.onCallHandoffHandleChanged(call, oldHandle, handle);
466 }
467 }
Sailesh Nepal6ab6fb72014-04-01 20:03:19 -0700468 }
469
Ben Gilada0d9f752014-02-26 11:49:03 -0800470 /**
Santos Cordon4b2c1192014-03-19 18:15:38 -0700471 * Cleans up any calls currently associated with the specified call service when the
472 * call-service binder disconnects unexpectedly.
473 *
474 * @param callService The call service that disconnected.
475 */
476 void handleCallServiceDeath(CallServiceWrapper callService) {
477 Preconditions.checkNotNull(callService);
Sailesh Nepale59bb192014-04-01 18:33:59 -0700478 for (Call call : ImmutableList.copyOf(mCalls)) {
Santos Cordon4b2c1192014-03-19 18:15:38 -0700479 if (call.getCallService() == callService) {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700480 markCallAsDisconnected(call, DisconnectCause.ERROR_UNSPECIFIED, null);
Santos Cordon4b2c1192014-03-19 18:15:38 -0700481 }
482 }
483 }
484
485 /**
Ben Gilada0d9f752014-02-26 11:49:03 -0800486 * Adds the specified call to the main list of live calls.
487 *
488 * @param call The call to add.
489 */
490 private void addCall(Call call) {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700491 mCalls.add(call);
Santos Cordona56f2762014-03-24 15:55:53 -0700492 for (CallsManagerListener listener : mListeners) {
493 listener.onCallAdded(call);
494 }
Sailesh Nepal810735e2014-03-18 18:15:46 -0700495 updateForegroundCall();
Ben Gilada0d9f752014-02-26 11:49:03 -0800496 }
497
Sailesh Nepal810735e2014-03-18 18:15:46 -0700498 private void removeCall(Call call) {
499 call.clearCallService();
Sailesh Nepale59bb192014-04-01 18:33:59 -0700500 call.clearCallServiceSelector();
501
502 boolean shouldNotify = false;
503 if (mCalls.contains(call)) {
504 mCalls.remove(call);
505 shouldNotify = true;
Sailesh Nepal84fa5f82014-04-02 11:01:11 -0700506 cleanUpHandoffCallsForOriginalCall(call);
507 } else if (mPendingHandoffCalls.contains(call)) {
508 Log.v(this, "silently removing handoff call %s", call);
509 mPendingHandoffCalls.remove(call);
Santos Cordona56f2762014-03-24 15:55:53 -0700510 }
Ben Gilada0d9f752014-02-26 11:49:03 -0800511
Sailesh Nepale59bb192014-04-01 18:33:59 -0700512 // Only broadcast changes for calls that are being tracked.
513 if (shouldNotify) {
514 for (CallsManagerListener listener : mListeners) {
515 listener.onCallRemoved(call);
516 }
517 updateForegroundCall();
Sailesh Nepal810735e2014-03-18 18:15:46 -0700518 }
519 }
Evan Charlton5c670a92014-03-06 14:58:20 -0800520
Sailesh Nepal810735e2014-03-18 18:15:46 -0700521 /**
Santos Cordon1ae2b852014-03-19 03:03:10 -0700522 * Sets the specified state on the specified call.
Sailesh Nepal810735e2014-03-18 18:15:46 -0700523 *
524 * @param call The call.
525 * @param newState The new state of the call.
526 */
527 private void setCallState(Call call, CallState newState) {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700528 Preconditions.checkNotNull(newState);
Sailesh Nepal810735e2014-03-18 18:15:46 -0700529 CallState oldState = call.getState();
530 if (newState != oldState) {
531 // Unfortunately, in the telephony world the radio is king. So if the call notifies
532 // us that the call is in a particular state, we allow it even if it doesn't make
533 // sense (e.g., ACTIVE -> RINGING).
534 // TODO(santoscordon): Consider putting a stop to the above and turning CallState
535 // into a well-defined state machine.
536 // TODO(santoscordon): Define expected state transitions here, and log when an
537 // unexpected transition occurs.
538 call.setState(newState);
539
540 // Only broadcast state change for calls that are being tracked.
Sailesh Nepale59bb192014-04-01 18:33:59 -0700541 if (mCalls.contains(call)) {
Santos Cordona56f2762014-03-24 15:55:53 -0700542 for (CallsManagerListener listener : mListeners) {
543 listener.onCallStateChanged(call, oldState, newState);
544 }
Sailesh Nepal810735e2014-03-18 18:15:46 -0700545 updateForegroundCall();
Santos Cordon4e9fffe2014-03-04 18:13:41 -0800546 }
547 }
548 }
549
550 /**
Sailesh Nepal810735e2014-03-18 18:15:46 -0700551 * Checks which call should be visible to the user and have audio focus.
Evan Charlton5c670a92014-03-06 14:58:20 -0800552 */
Sailesh Nepal810735e2014-03-18 18:15:46 -0700553 private void updateForegroundCall() {
554 Call newForegroundCall = null;
Sailesh Nepale59bb192014-04-01 18:33:59 -0700555 for (Call call : mCalls) {
Sailesh Nepal810735e2014-03-18 18:15:46 -0700556 // Incoming ringing calls have priority.
557 if (call.getState() == CallState.RINGING) {
558 newForegroundCall = call;
Evan Charlton5c670a92014-03-06 14:58:20 -0800559 break;
Sailesh Nepal810735e2014-03-18 18:15:46 -0700560 }
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700561 if (call.isAlive()) {
Sailesh Nepal810735e2014-03-18 18:15:46 -0700562 newForegroundCall = call;
563 // Don't break in case there's a ringing call that has priority.
Santos Cordon4e9fffe2014-03-04 18:13:41 -0800564 }
565 }
Santos Cordon4e9fffe2014-03-04 18:13:41 -0800566
Sailesh Nepal810735e2014-03-18 18:15:46 -0700567 if (newForegroundCall != mForegroundCall) {
568 Call oldForegroundCall = mForegroundCall;
569 mForegroundCall = newForegroundCall;
570
Santos Cordona56f2762014-03-24 15:55:53 -0700571 for (CallsManagerListener listener : mListeners) {
572 listener.onForegroundCallChanged(oldForegroundCall, mForegroundCall);
573 }
Santos Cordon681663d2014-01-30 04:32:15 -0800574 }
575 }
Sailesh Nepal84fa5f82014-04-02 11:01:11 -0700576
577 private void completeHandoff(Call handoffCall) {
578 Call originalCall = handoffCall.getOriginalCall();
579 Log.v(this, "complete handoff, %s -> %s", handoffCall, originalCall);
580
581 // Disconnect.
582 originalCall.disconnect();
583
584 // Synchronize.
585 originalCall.setCallService(handoffCall.getCallService());
586 setCallState(originalCall, handoffCall.getState());
587
588 // Remove the transient handoff call object (don't disconnect because the call is still
589 // live).
590 removeCall(handoffCall);
591
592 // Force the foreground call changed notification to be sent.
593 for (CallsManagerListener listener : mListeners) {
594 listener.onForegroundCallChanged(mForegroundCall, mForegroundCall);
595 }
596 }
597
598 /** Makes sure there are no dangling handoff calls. */
599 private void cleanUpHandoffCallsForOriginalCall(Call originalCall) {
600 for (Call handoffCall : ImmutableList.copyOf((mPendingHandoffCalls))) {
601 if (handoffCall.getOriginalCall() == originalCall) {
602 Log.d(this, "cancelling handoff call %s for originalCall: %s", handoffCall,
603 originalCall);
604 if (handoffCall.getState() == CallState.NEW) {
605 handoffCall.abort();
606 handoffCall.setState(CallState.ABORTED);
607 } else {
608 handoffCall.disconnect();
609 handoffCall.setState(CallState.DISCONNECTED);
610 }
611 removeCall(handoffCall);
612 }
613 }
614 }
615
616 private static boolean areUriEqual(Uri handle1, Uri handle2) {
617 if (handle1 == null) {
618 return handle2 == null;
619 }
620 return handle1.equals(handle2);
621 }
Ben Gilad9f2bed32013-12-12 17:43:26 -0800622}