blob: 607aeb1f1d4526ae5425d89c5030dfdd17a9d2e1 [file] [log] [blame]
Ben Gilad0407fb22014-01-09 16:18:41 -08001/*
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
Ben Gilad9f2bed32013-12-12 17:43:26 -080017package com.android.telecomm;
18
Santos Cordon99c8a6f2014-05-28 18:28:47 -070019import android.content.ContentUris;
20import android.graphics.Bitmap;
21import android.graphics.drawable.Drawable;
Sailesh Nepalce704b92014-03-17 18:31:43 -070022import android.net.Uri;
Sailesh Nepal84fa5f82014-04-02 11:01:11 -070023import android.os.Bundle;
Santos Cordonfd71f4a2014-05-28 13:59:49 -070024import android.os.Handler;
Santos Cordon99c8a6f2014-05-28 18:28:47 -070025import android.provider.ContactsContract.Contacts;
Santos Cordon0b03b4b2014-01-29 18:01:59 -080026import android.telecomm.CallInfo;
Sailesh Nepal84fa5f82014-04-02 11:01:11 -070027import android.telecomm.CallServiceDescriptor;
Santos Cordon0b03b4b2014-01-29 18:01:59 -080028import android.telecomm.CallState;
Yorke Lee33501632014-03-17 19:24:12 -070029import android.telecomm.GatewayInfo;
Santos Cordon766d04f2014-05-06 10:28:25 -070030import android.telecomm.TelecommConstants;
Santos Cordon79ff2bc2014-03-27 15:31:27 -070031import android.telephony.DisconnectCause;
Sailesh Nepal6aca10a2014-03-24 16:11:02 -070032import android.telephony.PhoneNumberUtils;
Santos Cordonfd71f4a2014-05-28 13:59:49 -070033import android.text.TextUtils;
Santos Cordon0b03b4b2014-01-29 18:01:59 -080034
Santos Cordonfd71f4a2014-05-28 13:59:49 -070035import com.android.internal.telephony.CallerInfo;
36import com.android.internal.telephony.CallerInfoAsyncQuery;
37import com.android.internal.telephony.CallerInfoAsyncQuery.OnQueryCompleteListener;
Santos Cordon99c8a6f2014-05-28 18:28:47 -070038
39import com.android.telecomm.ContactsAsyncHelper.OnImageLoadCompleteListener;
Ben Gilad61925612014-03-11 19:06:36 -070040import com.google.android.collect.Sets;
Santos Cordon61d0f702014-02-19 02:52:23 -080041import com.google.common.base.Preconditions;
42
Santos Cordona1610702014-06-04 20:22:56 -070043import java.util.HashSet;
44import java.util.LinkedList;
45import java.util.List;
Sailesh Nepal91990782014-03-08 17:45:52 -080046import java.util.Locale;
Ben Gilad61925612014-03-11 19:06:36 -070047import java.util.Set;
Ben Gilad0407fb22014-01-09 16:18:41 -080048
Ben Gilad2495d572014-01-09 17:26:19 -080049/**
50 * Encapsulates all aspects of a given phone call throughout its lifecycle, starting
51 * from the time the call intent was received by Telecomm (vs. the time the call was
52 * connected etc).
53 */
Ben Gilad0407fb22014-01-09 16:18:41 -080054final class Call {
Santos Cordon766d04f2014-05-06 10:28:25 -070055
56 /**
57 * Listener for events on the call.
58 */
59 interface Listener {
60 void onSuccessfulOutgoingCall(Call call);
Ihab Awad0fea3f22014-06-03 18:45:05 -070061 void onFailedOutgoingCall(Call call, boolean isAborted, int errorCode, String errorMsg);
Santos Cordon766d04f2014-05-06 10:28:25 -070062 void onSuccessfulIncomingCall(Call call, CallInfo callInfo);
63 void onFailedIncomingCall(Call call);
Ihab Awadcb387ac2014-05-28 16:49:38 -070064 void onRequestingRingback(Call call, boolean requestingRingback);
Evan Charlton352105c2014-06-03 14:10:54 -070065 void onPostDialWait(Call call, String remaining);
Santos Cordona1610702014-06-04 20:22:56 -070066 void onIsConferenceCapableChanged(Call call, boolean isConferenceCapable);
67 void onExpiredConferenceCall(Call call);
68 void onConfirmedConferenceCall(Call call);
69 void onParentChanged(Call call);
70 void onChildrenChanged(Call call);
Santos Cordon766d04f2014-05-06 10:28:25 -070071 }
72
Santos Cordonfd71f4a2014-05-28 13:59:49 -070073 private static final OnQueryCompleteListener sCallerInfoQueryListener =
Santos Cordon99c8a6f2014-05-28 18:28:47 -070074 new OnQueryCompleteListener() {
75 /** ${inheritDoc} */
76 @Override
77 public void onQueryComplete(int token, Object cookie, CallerInfo callerInfo) {
78 if (cookie != null) {
79 ((Call) cookie).setCallerInfo(callerInfo, token);
80 }
Santos Cordonfd71f4a2014-05-28 13:59:49 -070081 }
Santos Cordon99c8a6f2014-05-28 18:28:47 -070082 };
83
84 private static final OnImageLoadCompleteListener sPhotoLoadListener =
85 new OnImageLoadCompleteListener() {
86 /** ${inheritDoc} */
87 @Override
88 public void onImageLoadComplete(
89 int token, Drawable photo, Bitmap photoIcon, Object cookie) {
90 if (cookie != null) {
91 ((Call) cookie).setPhoto(photo, photoIcon, token);
92 }
93 }
94 };
Ben Gilad0407fb22014-01-09 16:18:41 -080095
Sailesh Nepal810735e2014-03-18 18:15:46 -070096 /** True if this is an incoming call. */
97 private final boolean mIsIncoming;
98
Ben Gilad0407fb22014-01-09 16:18:41 -080099 /**
100 * The time this call was created, typically also the time this call was added to the set
101 * of pending outgoing calls (mPendingOutgoingCalls) that's maintained by the switchboard.
102 * Beyond logging and such, may also be used for bookkeeping and specifically for marking
103 * certain call attempts as failed attempts.
104 */
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700105 private final long mCreationTimeMillis = System.currentTimeMillis();
106
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700107 /** The gateway information associated with this call. This stores the original call handle
108 * that the user is attempting to connect to via the gateway, the actual handle to dial in
109 * order to connect the call via the gateway, as well as the package name of the gateway
110 * service. */
111 private final GatewayInfo mGatewayInfo;
112
Santos Cordon2174fb52014-05-29 08:22:56 -0700113 private final Handler mHandler = new Handler();
114
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700115 private long mConnectTimeMillis;
Ben Gilad0407fb22014-01-09 16:18:41 -0800116
Santos Cordon61d0f702014-02-19 02:52:23 -0800117 /** The state of the call. */
118 private CallState mState;
119
120 /** The handle with which to establish this call. */
Sailesh Nepalce704b92014-03-17 18:31:43 -0700121 private Uri mHandle;
Santos Cordon61d0f702014-02-19 02:52:23 -0800122
Ben Gilad0407fb22014-01-09 16:18:41 -0800123 /**
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800124 * The call service which is attempted or already connecting this call.
Santos Cordon681663d2014-01-30 04:32:15 -0800125 */
Santos Cordonc195e362014-02-11 17:05:31 -0800126 private CallServiceWrapper mCallService;
Santos Cordon681663d2014-01-30 04:32:15 -0800127
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800128 /**
129 * The call-service selector for this call.
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800130 */
Sailesh Nepal18386a82014-03-19 10:22:40 -0700131 private CallServiceSelectorWrapper mCallServiceSelector;
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800132
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800133 /**
Ben Gilad61925612014-03-11 19:06:36 -0700134 * The set of call services that were attempted in the process of placing/switching this call
135 * but turned out unsuitable. Only used in the context of call switching.
136 */
137 private Set<CallServiceWrapper> mIncompatibleCallServices;
138
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700139 private boolean mIsEmergencyCall;
140
Ben Gilad61925612014-03-11 19:06:36 -0700141 /**
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700142 * Disconnect cause for the call. Only valid if the state of the call is DISCONNECTED.
143 * See {@link android.telephony.DisconnectCause}.
144 */
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700145 private int mDisconnectCause = DisconnectCause.NOT_VALID;
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700146
147 /**
148 * Additional disconnect information provided by the call service.
149 */
150 private String mDisconnectMessage;
151
Sailesh Nepal84fa5f82014-04-02 11:01:11 -0700152 /** Info used by the call services. */
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700153 private Bundle mExtras = Bundle.EMPTY;
Sailesh Nepal84fa5f82014-04-02 11:01:11 -0700154
155 /** The Uri to dial to perform the handoff. If this is null then handoff is not supported. */
156 private Uri mHandoffHandle;
157
158 /**
159 * References the call that is being handed off. This value is non-null for untracked calls
160 * that are being used to perform a handoff.
161 */
162 private Call mOriginalCall;
163
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700164 /**
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700165 * The descriptor for the call service that this call is being switched to, null if handoff is
166 * not in progress.
167 */
168 private CallServiceDescriptor mHandoffCallServiceDescriptor;
169
Santos Cordon766d04f2014-05-06 10:28:25 -0700170 /** Set of listeners on this call. */
171 private Set<Listener> mListeners = Sets.newHashSet();
172
Santos Cordon682fe6b2014-05-20 08:56:39 -0700173 private OutgoingCallProcessor mOutgoingCallProcessor;
174
175 // TODO(santoscordon): The repositories should be changed into singleton types.
176 private CallServiceRepository mCallServiceRepository;
177 private CallServiceSelectorRepository mSelectorRepository;
178
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700179 /** Caller information retrieved from the latest contact query. */
180 private CallerInfo mCallerInfo;
181
182 /** The latest token used with a contact info query. */
183 private int mQueryToken = 0;
184
Ihab Awadcb387ac2014-05-28 16:49:38 -0700185 /** Whether this call is requesting that Telecomm play the ringback tone on its behalf. */
186 private boolean mRequestingRingback = false;
187
Santos Cordon2174fb52014-05-29 08:22:56 -0700188 /** Incoming call-info to use when direct-to-voicemail query finishes. */
189 private CallInfo mPendingDirectToVoicemailCallInfo;
190
Santos Cordona1610702014-06-04 20:22:56 -0700191 private boolean mIsConferenceCapable = false;
192
193 private boolean mIsConference = false;
194
195 private Call mParentCall = null;
196
197 private List<Call> mChildCalls = new LinkedList<>();
198
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700199 /**
Sailesh Nepale59bb192014-04-01 18:33:59 -0700200 * Creates an empty call object.
Sailesh Nepal810735e2014-03-18 18:15:46 -0700201 *
202 * @param isIncoming True if this is an incoming call.
Santos Cordon493e8f22014-02-19 03:15:12 -0800203 */
Santos Cordona1610702014-06-04 20:22:56 -0700204 Call(boolean isIncoming, boolean isConference) {
205 this(null, null, isIncoming, isConference);
Santos Cordon493e8f22014-02-19 03:15:12 -0800206 }
207
208 /**
Ben Gilad0407fb22014-01-09 16:18:41 -0800209 * Persists the specified parameters and initializes the new instance.
210 *
211 * @param handle The handle to dial.
Yorke Lee33501632014-03-17 19:24:12 -0700212 * @param gatewayInfo Gateway information to use for the call.
Sailesh Nepal810735e2014-03-18 18:15:46 -0700213 * @param isIncoming True if this is an incoming call.
Ben Gilad0407fb22014-01-09 16:18:41 -0800214 */
Santos Cordona1610702014-06-04 20:22:56 -0700215 Call(Uri handle, GatewayInfo gatewayInfo, boolean isIncoming, boolean isConference) {
216 mState = isConference ? CallState.ACTIVE : CallState.NEW;
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700217 setHandle(handle);
Yorke Lee33501632014-03-17 19:24:12 -0700218 mGatewayInfo = gatewayInfo;
Sailesh Nepal810735e2014-03-18 18:15:46 -0700219 mIsIncoming = isIncoming;
Santos Cordona1610702014-06-04 20:22:56 -0700220 mIsConference = isConference;
Ben Gilad0407fb22014-01-09 16:18:41 -0800221 }
222
Santos Cordon766d04f2014-05-06 10:28:25 -0700223 void addListener(Listener listener) {
224 mListeners.add(listener);
225 }
226
227 void removeListener(Listener listener) {
228 mListeners.remove(listener);
229 }
230
Santos Cordon61d0f702014-02-19 02:52:23 -0800231 /** {@inheritDoc} */
232 @Override public String toString() {
Sailesh Nepal4538f012014-04-15 11:40:33 -0700233 String component = null;
234 if (mCallService != null && mCallService.getComponentName() != null) {
235 component = mCallService.getComponentName().flattenToShortString();
236 }
237 return String.format(Locale.US, "[%s, %s, %s]", mState, component, Log.piiHandle(mHandle));
Santos Cordon61d0f702014-02-19 02:52:23 -0800238 }
239
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800240 CallState getState() {
Santos Cordona1610702014-06-04 20:22:56 -0700241 if (mIsConference) {
242 if (!mChildCalls.isEmpty()) {
243 // If we have child calls, just return the child call.
244 return mChildCalls.get(0).getState();
245 }
246 return CallState.ACTIVE;
247 } else {
248 return mState;
249 }
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800250 }
251
252 /**
253 * Sets the call state. Although there exists the notion of appropriate state transitions
254 * (see {@link CallState}), in practice those expectations break down when cellular systems
255 * misbehave and they do this very often. The result is that we do not enforce state transitions
256 * and instead keep the code resilient to unexpected state changes.
257 */
Sailesh Nepal810735e2014-03-18 18:15:46 -0700258 void setState(CallState newState) {
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700259 Preconditions.checkState(newState != CallState.DISCONNECTED ||
260 mDisconnectCause != DisconnectCause.NOT_VALID);
Sailesh Nepal810735e2014-03-18 18:15:46 -0700261 if (mState != newState) {
262 Log.v(this, "setState %s -> %s", mState, newState);
263 mState = newState;
Sailesh Nepal810735e2014-03-18 18:15:46 -0700264 }
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800265 }
266
Ihab Awadcb387ac2014-05-28 16:49:38 -0700267 void setRequestingRingback(boolean requestingRingback) {
268 mRequestingRingback = requestingRingback;
269 for (Listener l : mListeners) {
270 l.onRequestingRingback(this, mRequestingRingback);
271 }
272 }
273
274 boolean isRequestingRingback() {
275 return mRequestingRingback;
276 }
277
Sailesh Nepalce704b92014-03-17 18:31:43 -0700278 Uri getHandle() {
Ben Gilad0bf5b912014-01-28 17:55:57 -0800279 return mHandle;
280 }
281
Sailesh Nepalce704b92014-03-17 18:31:43 -0700282 void setHandle(Uri handle) {
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700283 if ((mHandle == null && handle != null) || (mHandle != null && !mHandle.equals(handle))) {
284 mHandle = handle;
285 mIsEmergencyCall = mHandle != null && PhoneNumberUtils.isLocalEmergencyNumber(
Yorke Lee66255452014-06-05 08:09:24 -0700286 TelecommApp.getInstance(), mHandle.getSchemeSpecificPart());
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700287 startCallerInfoLookup();
288 }
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700289 }
290
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700291 String getName() {
292 return mCallerInfo == null ? null : mCallerInfo.name;
293 }
294
295 Bitmap getPhotoIcon() {
296 return mCallerInfo == null ? null : mCallerInfo.cachedPhotoIcon;
297 }
298
299 Drawable getPhoto() {
300 return mCallerInfo == null ? null : mCallerInfo.cachedPhoto;
301 }
302
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700303 /**
304 * @param disconnectCause The reason for the disconnection, any of
305 * {@link android.telephony.DisconnectCause}.
306 * @param disconnectMessage Optional call-service-provided message about the disconnect.
307 */
308 void setDisconnectCause(int disconnectCause, String disconnectMessage) {
309 // TODO: Consider combining this method with a setDisconnected() method that is totally
310 // separate from setState.
311 mDisconnectCause = disconnectCause;
312 mDisconnectMessage = disconnectMessage;
313 }
314
315 int getDisconnectCause() {
316 return mDisconnectCause;
317 }
318
319 String getDisconnectMessage() {
320 return mDisconnectMessage;
321 }
322
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700323 boolean isEmergencyCall() {
324 return mIsEmergencyCall;
Santos Cordon61d0f702014-02-19 02:52:23 -0800325 }
326
Yorke Lee33501632014-03-17 19:24:12 -0700327 /**
328 * @return The original handle this call is associated with. In-call services should use this
329 * handle when indicating in their UI the handle that is being called.
330 */
331 public Uri getOriginalHandle() {
332 if (mGatewayInfo != null && !mGatewayInfo.isEmpty()) {
333 return mGatewayInfo.getOriginalHandle();
334 }
335 return getHandle();
336 }
337
338 GatewayInfo getGatewayInfo() {
339 return mGatewayInfo;
340 }
341
Sailesh Nepal810735e2014-03-18 18:15:46 -0700342 boolean isIncoming() {
343 return mIsIncoming;
344 }
345
Ben Gilad0407fb22014-01-09 16:18:41 -0800346 /**
347 * @return The "age" of this call object in milliseconds, which typically also represents the
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700348 * period since this call was added to the set pending outgoing calls, see
349 * mCreationTimeMillis.
Ben Gilad0407fb22014-01-09 16:18:41 -0800350 */
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700351 long getAgeMillis() {
352 return System.currentTimeMillis() - mCreationTimeMillis;
Ben Gilad0407fb22014-01-09 16:18:41 -0800353 }
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800354
Yorke Leef98fb572014-03-05 10:56:55 -0800355 /**
356 * @return The time when this call object was created and added to the set of pending outgoing
357 * calls.
358 */
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700359 long getCreationTimeMillis() {
360 return mCreationTimeMillis;
361 }
362
363 long getConnectTimeMillis() {
364 return mConnectTimeMillis;
365 }
366
367 void setConnectTimeMillis(long connectTimeMillis) {
368 mConnectTimeMillis = connectTimeMillis;
Yorke Leef98fb572014-03-05 10:56:55 -0800369 }
370
Santos Cordona1610702014-06-04 20:22:56 -0700371 boolean isConferenceCapable() {
372 return mIsConferenceCapable;
373 }
374
375 void setIsConferenceCapable(boolean isConferenceCapable) {
376 if (mIsConferenceCapable != isConferenceCapable) {
377 mIsConferenceCapable = isConferenceCapable;
378 for (Listener l : mListeners) {
379 l.onIsConferenceCapableChanged(this, mIsConferenceCapable);
380 }
381 }
382 }
383
384 Call getParentCall() {
385 return mParentCall;
386 }
387
388 List<Call> getChildCalls() {
389 return mChildCalls;
390 }
391
Santos Cordonc195e362014-02-11 17:05:31 -0800392 CallServiceWrapper getCallService() {
Santos Cordon681663d2014-01-30 04:32:15 -0800393 return mCallService;
394 }
395
Santos Cordonc195e362014-02-11 17:05:31 -0800396 void setCallService(CallServiceWrapper callService) {
Sailesh Nepal0e5410a2014-04-04 01:20:58 -0700397 setCallService(callService, null);
398 }
399
400 /**
401 * Changes the call service this call is associated with. If callToReplace is non-null then this
402 * call takes its place within the call service.
403 */
404 void setCallService(CallServiceWrapper callService, Call callToReplace) {
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800405 Preconditions.checkNotNull(callService);
406
Yorke Leeadee12d2014-03-13 12:08:30 -0700407 clearCallService();
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800408
409 callService.incrementAssociatedCallCount();
Santos Cordon681663d2014-01-30 04:32:15 -0800410 mCallService = callService;
Sailesh Nepal0e5410a2014-04-04 01:20:58 -0700411 if (callToReplace == null) {
412 mCallService.addCall(this);
413 } else {
414 mCallService.replaceCall(this, callToReplace);
415 }
Santos Cordon681663d2014-01-30 04:32:15 -0800416 }
417
418 /**
419 * Clears the associated call service.
420 */
421 void clearCallService() {
Yorke Leeadee12d2014-03-13 12:08:30 -0700422 if (mCallService != null) {
Santos Cordonc499c1c2014-04-14 17:13:14 -0700423 CallServiceWrapper callServiceTemp = mCallService;
Yorke Leeadee12d2014-03-13 12:08:30 -0700424 mCallService = null;
Santos Cordonc499c1c2014-04-14 17:13:14 -0700425 callServiceTemp.removeCall(this);
426
427 // Decrementing the count can cause the service to unbind, which itself can trigger the
428 // service-death code. Since the service death code tries to clean up any associated
429 // calls, we need to make sure to remove that information (e.g., removeCall()) before
430 // we decrement. Technically, invoking removeCall() prior to decrementing is all that is
431 // necessary, but cleaning up mCallService prior to triggering an unbind is good to do.
432 // If you change this, make sure to update {@link clearCallServiceSelector} as well.
433 decrementAssociatedCallCount(callServiceTemp);
Yorke Leeadee12d2014-03-13 12:08:30 -0700434 }
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800435 }
436
Sailesh Nepal84fa5f82014-04-02 11:01:11 -0700437 CallServiceSelectorWrapper getCallServiceSelector() {
438 return mCallServiceSelector;
439 }
440
Sailesh Nepal18386a82014-03-19 10:22:40 -0700441 void setCallServiceSelector(CallServiceSelectorWrapper selector) {
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800442 Preconditions.checkNotNull(selector);
Sailesh Nepale59bb192014-04-01 18:33:59 -0700443
444 clearCallServiceSelector();
445
Santos Cordonc499c1c2014-04-14 17:13:14 -0700446 selector.incrementAssociatedCallCount();
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800447 mCallServiceSelector = selector;
Sailesh Nepale59bb192014-04-01 18:33:59 -0700448 mCallServiceSelector.addCall(this);
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800449 }
450
451 void clearCallServiceSelector() {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700452 if (mCallServiceSelector != null) {
Santos Cordonc499c1c2014-04-14 17:13:14 -0700453 CallServiceSelectorWrapper selectorTemp = mCallServiceSelector;
Sailesh Nepale59bb192014-04-01 18:33:59 -0700454 mCallServiceSelector = null;
Santos Cordonc499c1c2014-04-14 17:13:14 -0700455 selectorTemp.removeCall(this);
456
457 // See comment on {@link #clearCallService}.
458 decrementAssociatedCallCount(selectorTemp);
Sailesh Nepale59bb192014-04-01 18:33:59 -0700459 }
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800460 }
461
462 /**
Santos Cordon766d04f2014-05-06 10:28:25 -0700463 * Starts the incoming call flow through the switchboard. When switchboard completes, it will
464 * invoke handle[Un]SuccessfulIncomingCall.
465 *
466 * @param descriptor The relevant call-service descriptor.
467 * @param extras The optional extras passed via
468 * {@link TelecommConstants#EXTRA_INCOMING_CALL_EXTRAS}.
469 */
470 void startIncoming(CallServiceDescriptor descriptor, Bundle extras) {
471 Switchboard.getInstance().retrieveIncomingCall(this, descriptor, extras);
472 }
473
Santos Cordon2174fb52014-05-29 08:22:56 -0700474 /**
475 * Takes a verified incoming call and uses the handle to lookup direct-to-voicemail property
476 * from the contacts provider. The call is not yet exposed to the user at this point and
477 * the result of the query will determine if the call is rejected or passed through to the
478 * in-call UI.
479 */
480 void handleVerifiedIncoming(CallInfo callInfo) {
Santos Cordon766d04f2014-05-06 10:28:25 -0700481 Preconditions.checkState(callInfo.getState() == CallState.RINGING);
Santos Cordon2174fb52014-05-29 08:22:56 -0700482
483 // We do not handle incoming calls immediately when they are verified by the call service.
484 // We allow the caller-info-query code to execute first so that we can read the
485 // direct-to-voicemail property before deciding if we want to show the incoming call to the
486 // user or if we want to reject the call.
487 mPendingDirectToVoicemailCallInfo = callInfo;
488
489 // Setting the handle triggers the caller info lookup code.
Santos Cordon766d04f2014-05-06 10:28:25 -0700490 setHandle(callInfo.getHandle());
491
Santos Cordon2174fb52014-05-29 08:22:56 -0700492 // Timeout the direct-to-voicemail lookup execution so that we dont wait too long before
493 // showing the user the incoming call screen.
494 mHandler.postDelayed(new Runnable() {
495 @Override
496 public void run() {
497 processDirectToVoicemail();
498 }
Santos Cordona1610702014-06-04 20:22:56 -0700499 }, Timeouts.getDirectToVoicemailMillis());
Santos Cordon2174fb52014-05-29 08:22:56 -0700500 }
Santos Cordon766d04f2014-05-06 10:28:25 -0700501
Santos Cordon2174fb52014-05-29 08:22:56 -0700502 void processDirectToVoicemail() {
503 if (mPendingDirectToVoicemailCallInfo != null) {
504 if (mCallerInfo != null && mCallerInfo.shouldSendToVoicemail) {
505 Log.i(this, "Directing call to voicemail: %s.", this);
506 // TODO(santoscordon): Once we move State handling from CallsManager to Call, we
507 // will not need to set RINGING state prior to calling reject.
508 setState(CallState.RINGING);
509 reject();
510 } else {
511 // TODO(santoscordon): Make this class (not CallsManager) responsible for changing
512 // the call state to RINGING.
513
514 // TODO(santoscordon): Replace this with state transition to RINGING.
515 for (Listener l : mListeners) {
516 l.onSuccessfulIncomingCall(this, mPendingDirectToVoicemailCallInfo);
517 }
518 }
519
520 mPendingDirectToVoicemailCallInfo = null;
Santos Cordon766d04f2014-05-06 10:28:25 -0700521 }
522 }
523
524 void handleFailedIncoming() {
525 clearCallService();
526
527 // TODO: Needs more specific disconnect error for this case.
528 setDisconnectCause(DisconnectCause.ERROR_UNSPECIFIED, null);
529 setState(CallState.DISCONNECTED);
530
531 // TODO(santoscordon): Replace this with state transitions related to "connecting".
532 for (Listener l : mListeners) {
533 l.onFailedIncomingCall(this);
534 }
535 }
536
537 /**
Santos Cordon682fe6b2014-05-20 08:56:39 -0700538 * Starts the outgoing call sequence. Upon completion, there should exist an active connection
539 * through a call service (or the call will have failed).
Santos Cordon766d04f2014-05-06 10:28:25 -0700540 */
541 void startOutgoing() {
Santos Cordon682fe6b2014-05-20 08:56:39 -0700542 Preconditions.checkState(mOutgoingCallProcessor == null);
543
544 mOutgoingCallProcessor = new OutgoingCallProcessor(
545 this,
546 Switchboard.getInstance().getCallServiceRepository(),
547 Switchboard.getInstance().getSelectorRepository(),
548 new AsyncResultCallback<Boolean>() {
549 @Override
Ihab Awad0fea3f22014-06-03 18:45:05 -0700550 public void onResult(Boolean wasCallPlaced, int errorCode, String errorMsg) {
Santos Cordon682fe6b2014-05-20 08:56:39 -0700551 if (wasCallPlaced) {
552 handleSuccessfulOutgoing();
553 } else {
Ihab Awad0fea3f22014-06-03 18:45:05 -0700554 handleFailedOutgoing(
555 mOutgoingCallProcessor.isAborted(), errorCode, errorMsg);
Santos Cordon682fe6b2014-05-20 08:56:39 -0700556 }
557 mOutgoingCallProcessor = null;
558 }
559 });
560 mOutgoingCallProcessor.process();
Santos Cordon766d04f2014-05-06 10:28:25 -0700561 }
562
563 void handleSuccessfulOutgoing() {
564 // TODO(santoscordon): Replace this with state transitions related to "connecting".
565 for (Listener l : mListeners) {
566 l.onSuccessfulOutgoingCall(this);
567 }
568 }
569
Ihab Awad0fea3f22014-06-03 18:45:05 -0700570 void handleFailedOutgoing(boolean isAborted, int errorCode, String errorMsg) {
Santos Cordon766d04f2014-05-06 10:28:25 -0700571 // TODO(santoscordon): Replace this with state transitions related to "connecting".
572 for (Listener l : mListeners) {
Ihab Awad0fea3f22014-06-03 18:45:05 -0700573 l.onFailedOutgoingCall(this, isAborted, errorCode, errorMsg);
Santos Cordon766d04f2014-05-06 10:28:25 -0700574 }
Santos Cordon682fe6b2014-05-20 08:56:39 -0700575
576 clearCallService();
577 clearCallServiceSelector();
Santos Cordon766d04f2014-05-06 10:28:25 -0700578 }
579
580 /**
Ben Gilad61925612014-03-11 19:06:36 -0700581 * Adds the specified call service to the list of incompatible services. The set is used when
582 * attempting to switch a phone call between call services such that incompatible services can
583 * be avoided.
584 *
585 * @param callService The incompatible call service.
586 */
587 void addIncompatibleCallService(CallServiceWrapper callService) {
588 if (mIncompatibleCallServices == null) {
589 mIncompatibleCallServices = Sets.newHashSet();
590 }
591 mIncompatibleCallServices.add(callService);
592 }
593
594 /**
595 * Checks whether or not the specified callService was identified as incompatible in the
596 * context of this call.
597 *
598 * @param callService The call service to evaluate.
599 * @return True upon incompatible call services and false otherwise.
600 */
601 boolean isIncompatibleCallService(CallServiceWrapper callService) {
602 return mIncompatibleCallServices != null &&
603 mIncompatibleCallServices.contains(callService);
604 }
605
606 /**
Ihab Awad74549ec2014-03-10 15:33:25 -0700607 * Plays the specified DTMF tone.
608 */
609 void playDtmfTone(char digit) {
610 if (mCallService == null) {
611 Log.w(this, "playDtmfTone() request on a call without a call service.");
612 } else {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700613 Log.i(this, "Send playDtmfTone to call service for call %s", this);
614 mCallService.playDtmfTone(this, digit);
Ihab Awad74549ec2014-03-10 15:33:25 -0700615 }
616 }
617
618 /**
619 * Stops playing any currently playing DTMF tone.
620 */
621 void stopDtmfTone() {
622 if (mCallService == null) {
623 Log.w(this, "stopDtmfTone() request on a call without a call service.");
624 } else {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700625 Log.i(this, "Send stopDtmfTone to call service for call %s", this);
626 mCallService.stopDtmfTone(this);
Ihab Awad74549ec2014-03-10 15:33:25 -0700627 }
628 }
629
630 /**
Santos Cordon049b7b62014-01-30 05:34:26 -0800631 * Attempts to disconnect the call through the call service.
632 */
633 void disconnect() {
Santos Cordon766d04f2014-05-06 10:28:25 -0700634 if (mState == CallState.NEW) {
Santos Cordon682fe6b2014-05-20 08:56:39 -0700635 Log.v(this, "Aborting call %s", this);
636 abort();
Santos Cordon74d420b2014-05-07 14:38:47 -0700637 } else if (mState != CallState.ABORTED && mState != CallState.DISCONNECTED) {
Santos Cordon766d04f2014-05-06 10:28:25 -0700638 Preconditions.checkNotNull(mCallService);
639
Sailesh Nepale59bb192014-04-01 18:33:59 -0700640 Log.i(this, "Send disconnect to call service for call: %s", this);
Santos Cordonc195e362014-02-11 17:05:31 -0800641 // The call isn't officially disconnected until the call service confirms that the call
642 // was actually disconnected. Only then is the association between call and call service
643 // severed, see {@link CallsManager#markCallAsDisconnected}.
Sailesh Nepale59bb192014-04-01 18:33:59 -0700644 mCallService.disconnect(this);
Santos Cordon049b7b62014-01-30 05:34:26 -0800645 }
646 }
647
Santos Cordon682fe6b2014-05-20 08:56:39 -0700648 void abort() {
649 if (mOutgoingCallProcessor != null) {
650 mOutgoingCallProcessor.abort();
651 }
652 }
653
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800654 /**
Santos Cordon61d0f702014-02-19 02:52:23 -0800655 * Answers the call if it is ringing.
656 */
657 void answer() {
658 Preconditions.checkNotNull(mCallService);
659
660 // Check to verify that the call is still in the ringing state. A call can change states
661 // between the time the user hits 'answer' and Telecomm receives the command.
662 if (isRinging("answer")) {
663 // At this point, we are asking the call service to answer but we don't assume that
664 // it will work. Instead, we wait until confirmation from the call service that the
665 // call is in a non-RINGING state before changing the UI. See
666 // {@link CallServiceAdapter#setActive} and other set* methods.
Sailesh Nepale59bb192014-04-01 18:33:59 -0700667 mCallService.answer(this);
Santos Cordon61d0f702014-02-19 02:52:23 -0800668 }
669 }
670
671 /**
672 * Rejects the call if it is ringing.
673 */
674 void reject() {
675 Preconditions.checkNotNull(mCallService);
676
677 // Check to verify that the call is still in the ringing state. A call can change states
678 // between the time the user hits 'reject' and Telecomm receives the command.
679 if (isRinging("reject")) {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700680 mCallService.reject(this);
Santos Cordon61d0f702014-02-19 02:52:23 -0800681 }
682 }
683
684 /**
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700685 * Puts the call on hold if it is currently active.
686 */
687 void hold() {
688 Preconditions.checkNotNull(mCallService);
689
690 if (mState == CallState.ACTIVE) {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700691 mCallService.hold(this);
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700692 }
693 }
694
695 /**
696 * Releases the call from hold if it is currently active.
697 */
698 void unhold() {
699 Preconditions.checkNotNull(mCallService);
700
701 if (mState == CallState.ON_HOLD) {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700702 mCallService.unhold(this);
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700703 }
704 }
705
706 /**
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800707 * @return An object containing read-only information about this call.
708 */
Sailesh Nepale59bb192014-04-01 18:33:59 -0700709 CallInfo toCallInfo(String callId) {
Sailesh Nepal84fa5f82014-04-02 11:01:11 -0700710 CallServiceDescriptor descriptor = null;
711 if (mCallService != null) {
712 descriptor = mCallService.getDescriptor();
713 } else if (mOriginalCall != null && mOriginalCall.mCallService != null) {
714 descriptor = mOriginalCall.mCallService.getDescriptor();
715 }
716 return new CallInfo(callId, mState, mHandle, mGatewayInfo, mExtras, descriptor);
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800717 }
718
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700719 /** Checks if this is a live call or not. */
720 boolean isAlive() {
721 switch (mState) {
722 case NEW:
723 case RINGING:
724 case DISCONNECTED:
725 case ABORTED:
726 return false;
727 default:
728 return true;
729 }
730 }
731
Santos Cordon40f78c22014-04-07 02:11:42 -0700732 boolean isActive() {
733 switch (mState) {
734 case ACTIVE:
735 case POST_DIAL:
736 case POST_DIAL_WAIT:
737 return true;
738 default:
739 return false;
740 }
741 }
742
Sailesh Nepal84fa5f82014-04-02 11:01:11 -0700743 Bundle getExtras() {
744 return mExtras;
745 }
746
747 void setExtras(Bundle extras) {
748 mExtras = extras;
749 }
750
751 Uri getHandoffHandle() {
752 return mHandoffHandle;
753 }
754
755 void setHandoffHandle(Uri handoffHandle) {
756 mHandoffHandle = handoffHandle;
757 }
758
759 Call getOriginalCall() {
760 return mOriginalCall;
761 }
762
763 void setOriginalCall(Call originalCall) {
764 mOriginalCall = originalCall;
765 }
766
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700767 CallServiceDescriptor getHandoffCallServiceDescriptor() {
768 return mHandoffCallServiceDescriptor;
769 }
770
771 void setHandoffCallServiceDescriptor(CallServiceDescriptor descriptor) {
772 mHandoffCallServiceDescriptor = descriptor;
773 }
774
Santos Cordon5ba7f272014-05-28 13:59:49 -0700775 Uri getRingtone() {
776 return mCallerInfo == null ? null : mCallerInfo.contactRingtoneUri;
777 }
778
Evan Charlton352105c2014-06-03 14:10:54 -0700779 void onPostDialWait(String remaining) {
780 for (Listener l : mListeners) {
781 l.onPostDialWait(this, remaining);
782 }
783 }
784
785 void postDialContinue(boolean proceed) {
786 getCallService().onPostDialContinue(this, proceed);
787 }
788
Santos Cordona1610702014-06-04 20:22:56 -0700789 void conferenceInto(Call conferenceCall) {
790 if (mCallService == null) {
791 Log.w(this, "conference requested on a call without a call service.");
792 } else {
793 mCallService.conference(conferenceCall, this);
794 }
795 }
796
797 void expireConference() {
798 // The conference call expired before we got a confirmation of the conference from the
799 // call service...so start shutting down.
800 clearCallService();
801 for (Listener l : mListeners) {
802 l.onExpiredConferenceCall(this);
803 }
804 }
805
806 void confirmConference() {
807 Log.v(this, "confirming Conf call %s", mListeners);
808 for (Listener l : mListeners) {
809 l.onConfirmedConferenceCall(this);
810 }
811 }
812
813 void splitFromConference() {
814 // TODO(santoscordon): todo
815 }
816
817 void setParentCall(Call parentCall) {
818 if (parentCall == this) {
819 Log.e(this, new Exception(), "setting the parent to self");
820 return;
821 }
822 Preconditions.checkState(parentCall == null || mParentCall == null);
823
824 Call oldParent = mParentCall;
825 if (mParentCall != null) {
826 mParentCall.removeChildCall(this);
827 }
828 mParentCall = parentCall;
829 if (mParentCall != null) {
830 mParentCall.addChildCall(this);
831 }
832
833 for (Listener l : mListeners) {
834 l.onParentChanged(this);
835 }
836 }
837
838 private void addChildCall(Call call) {
839 if (!mChildCalls.contains(call)) {
840 mChildCalls.add(call);
841
842 for (Listener l : mListeners) {
843 l.onChildrenChanged(this);
844 }
845 }
846 }
847
848 private void removeChildCall(Call call) {
849 if (mChildCalls.remove(call)) {
850 for (Listener l : mListeners) {
851 l.onChildrenChanged(this);
852 }
853 }
854 }
855
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800856 /**
Santos Cordon61d0f702014-02-19 02:52:23 -0800857 * @return True if the call is ringing, else logs the action name.
858 */
859 private boolean isRinging(String actionName) {
860 if (mState == CallState.RINGING) {
861 return true;
862 }
863
Sailesh Nepalf1c191d2014-03-07 18:17:39 -0800864 Log.i(this, "Request to %s a non-ringing call %s", actionName, this);
Santos Cordon61d0f702014-02-19 02:52:23 -0800865 return false;
866 }
867
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800868 @SuppressWarnings("rawtypes")
869 private void decrementAssociatedCallCount(ServiceBinder binder) {
870 if (binder != null) {
871 binder.decrementAssociatedCallCount();
872 }
873 }
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700874
875 /**
876 * Looks up contact information based on the current handle.
877 */
878 private void startCallerInfoLookup() {
879 String number = mHandle == null ? null : mHandle.getSchemeSpecificPart();
880
881 mQueryToken++; // Updated so that previous queries can no longer set the information.
882 mCallerInfo = null;
883 if (!TextUtils.isEmpty(number)) {
Santos Cordon5ba7f272014-05-28 13:59:49 -0700884 Log.v(this, "Looking up information for: %s.", Log.piiHandle(number));
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700885 CallerInfoAsyncQuery.startQuery(
886 mQueryToken,
887 TelecommApp.getInstance(),
888 number,
889 sCallerInfoQueryListener,
890 this);
891 }
892 }
893
894 /**
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700895 * Saves the specified caller info if the specified token matches that of the last query
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700896 * that was made.
897 *
898 * @param callerInfo The new caller information to set.
899 * @param token The token used with this query.
900 */
901 private void setCallerInfo(CallerInfo callerInfo, int token) {
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700902 Preconditions.checkNotNull(callerInfo);
903
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700904 if (mQueryToken == token) {
905 mCallerInfo = callerInfo;
Santos Cordon5ba7f272014-05-28 13:59:49 -0700906 Log.i(this, "CallerInfo received for %s: %s", Log.piiHandle(mHandle), callerInfo);
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700907
908 if (mCallerInfo.person_id != 0) {
909 Uri personUri =
910 ContentUris.withAppendedId(Contacts.CONTENT_URI, mCallerInfo.person_id);
911 Log.d(this, "Searching person uri %s for call %s", personUri, this);
912 ContactsAsyncHelper.startObtainPhotoAsync(
913 token,
914 TelecommApp.getInstance(),
915 personUri,
916 sPhotoLoadListener,
917 this);
918 }
Santos Cordon2174fb52014-05-29 08:22:56 -0700919
920 processDirectToVoicemail();
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700921 }
922 }
923
924 /**
925 * Saves the specified photo information if the specified token matches that of the last query.
926 *
927 * @param photo The photo as a drawable.
928 * @param photoIcon The photo as a small icon.
929 * @param token The token used with this query.
930 */
931 private void setPhoto(Drawable photo, Bitmap photoIcon, int token) {
932 if (mQueryToken == token) {
933 mCallerInfo.cachedPhoto = photo;
934 mCallerInfo.cachedPhotoIcon = photoIcon;
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700935 }
936 }
Ben Gilad9f2bed32013-12-12 17:43:26 -0800937}