blob: cd64ff8dd829472080e9e4c344de4670294812da [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
Sailesh Nepal91990782014-03-08 17:45:52 -080043import java.util.Locale;
Ben Gilad61925612014-03-11 19:06:36 -070044import java.util.Set;
Ben Gilad0407fb22014-01-09 16:18:41 -080045
Ben Gilad2495d572014-01-09 17:26:19 -080046/**
47 * Encapsulates all aspects of a given phone call throughout its lifecycle, starting
48 * from the time the call intent was received by Telecomm (vs. the time the call was
49 * connected etc).
50 */
Ben Gilad0407fb22014-01-09 16:18:41 -080051final class Call {
Santos Cordon766d04f2014-05-06 10:28:25 -070052
53 /**
54 * Listener for events on the call.
55 */
56 interface Listener {
57 void onSuccessfulOutgoingCall(Call call);
58 void onFailedOutgoingCall(Call call, boolean isAborted);
59 void onSuccessfulIncomingCall(Call call, CallInfo callInfo);
60 void onFailedIncomingCall(Call call);
Ihab Awadcb387ac2014-05-28 16:49:38 -070061 void onRequestingRingback(Call call, boolean requestingRingback);
Santos Cordon766d04f2014-05-06 10:28:25 -070062 }
63
Santos Cordonfd71f4a2014-05-28 13:59:49 -070064 private static final OnQueryCompleteListener sCallerInfoQueryListener =
Santos Cordon99c8a6f2014-05-28 18:28:47 -070065 new OnQueryCompleteListener() {
66 /** ${inheritDoc} */
67 @Override
68 public void onQueryComplete(int token, Object cookie, CallerInfo callerInfo) {
69 if (cookie != null) {
70 ((Call) cookie).setCallerInfo(callerInfo, token);
71 }
Santos Cordonfd71f4a2014-05-28 13:59:49 -070072 }
Santos Cordon99c8a6f2014-05-28 18:28:47 -070073 };
74
75 private static final OnImageLoadCompleteListener sPhotoLoadListener =
76 new OnImageLoadCompleteListener() {
77 /** ${inheritDoc} */
78 @Override
79 public void onImageLoadComplete(
80 int token, Drawable photo, Bitmap photoIcon, Object cookie) {
81 if (cookie != null) {
82 ((Call) cookie).setPhoto(photo, photoIcon, token);
83 }
84 }
85 };
Ben Gilad0407fb22014-01-09 16:18:41 -080086
Sailesh Nepal810735e2014-03-18 18:15:46 -070087 /** True if this is an incoming call. */
88 private final boolean mIsIncoming;
89
Ben Gilad0407fb22014-01-09 16:18:41 -080090 /**
91 * The time this call was created, typically also the time this call was added to the set
92 * of pending outgoing calls (mPendingOutgoingCalls) that's maintained by the switchboard.
93 * Beyond logging and such, may also be used for bookkeeping and specifically for marking
94 * certain call attempts as failed attempts.
95 */
Sailesh Nepal8c85dee2014-04-07 22:21:40 -070096 private final long mCreationTimeMillis = System.currentTimeMillis();
97
Santos Cordonfd71f4a2014-05-28 13:59:49 -070098 /** The gateway information associated with this call. This stores the original call handle
99 * that the user is attempting to connect to via the gateway, the actual handle to dial in
100 * order to connect the call via the gateway, as well as the package name of the gateway
101 * service. */
102 private final GatewayInfo mGatewayInfo;
103
Santos Cordon2174fb52014-05-29 08:22:56 -0700104 private final Handler mHandler = new Handler();
105
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700106 private long mConnectTimeMillis;
Ben Gilad0407fb22014-01-09 16:18:41 -0800107
Santos Cordon61d0f702014-02-19 02:52:23 -0800108 /** The state of the call. */
109 private CallState mState;
110
111 /** The handle with which to establish this call. */
Sailesh Nepalce704b92014-03-17 18:31:43 -0700112 private Uri mHandle;
Santos Cordon61d0f702014-02-19 02:52:23 -0800113
Ben Gilad0407fb22014-01-09 16:18:41 -0800114 /**
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800115 * The call service which is attempted or already connecting this call.
Santos Cordon681663d2014-01-30 04:32:15 -0800116 */
Santos Cordonc195e362014-02-11 17:05:31 -0800117 private CallServiceWrapper mCallService;
Santos Cordon681663d2014-01-30 04:32:15 -0800118
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800119 /**
120 * The call-service selector for this call.
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800121 */
Sailesh Nepal18386a82014-03-19 10:22:40 -0700122 private CallServiceSelectorWrapper mCallServiceSelector;
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800123
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800124 /**
Ben Gilad61925612014-03-11 19:06:36 -0700125 * The set of call services that were attempted in the process of placing/switching this call
126 * but turned out unsuitable. Only used in the context of call switching.
127 */
128 private Set<CallServiceWrapper> mIncompatibleCallServices;
129
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700130 private boolean mIsEmergencyCall;
131
Ben Gilad61925612014-03-11 19:06:36 -0700132 /**
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700133 * Disconnect cause for the call. Only valid if the state of the call is DISCONNECTED.
134 * See {@link android.telephony.DisconnectCause}.
135 */
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700136 private int mDisconnectCause = DisconnectCause.NOT_VALID;
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700137
138 /**
139 * Additional disconnect information provided by the call service.
140 */
141 private String mDisconnectMessage;
142
Sailesh Nepal84fa5f82014-04-02 11:01:11 -0700143 /** Info used by the call services. */
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700144 private Bundle mExtras = Bundle.EMPTY;
Sailesh Nepal84fa5f82014-04-02 11:01:11 -0700145
146 /** The Uri to dial to perform the handoff. If this is null then handoff is not supported. */
147 private Uri mHandoffHandle;
148
149 /**
150 * References the call that is being handed off. This value is non-null for untracked calls
151 * that are being used to perform a handoff.
152 */
153 private Call mOriginalCall;
154
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700155 /**
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700156 * The descriptor for the call service that this call is being switched to, null if handoff is
157 * not in progress.
158 */
159 private CallServiceDescriptor mHandoffCallServiceDescriptor;
160
Santos Cordon766d04f2014-05-06 10:28:25 -0700161 /** Set of listeners on this call. */
162 private Set<Listener> mListeners = Sets.newHashSet();
163
Santos Cordon682fe6b2014-05-20 08:56:39 -0700164 private OutgoingCallProcessor mOutgoingCallProcessor;
165
166 // TODO(santoscordon): The repositories should be changed into singleton types.
167 private CallServiceRepository mCallServiceRepository;
168 private CallServiceSelectorRepository mSelectorRepository;
169
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700170 /** Caller information retrieved from the latest contact query. */
171 private CallerInfo mCallerInfo;
172
173 /** The latest token used with a contact info query. */
174 private int mQueryToken = 0;
175
Ihab Awadcb387ac2014-05-28 16:49:38 -0700176 /** Whether this call is requesting that Telecomm play the ringback tone on its behalf. */
177 private boolean mRequestingRingback = false;
178
Santos Cordon2174fb52014-05-29 08:22:56 -0700179 /** Incoming call-info to use when direct-to-voicemail query finishes. */
180 private CallInfo mPendingDirectToVoicemailCallInfo;
181
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700182 /**
Sailesh Nepale59bb192014-04-01 18:33:59 -0700183 * Creates an empty call object.
Sailesh Nepal810735e2014-03-18 18:15:46 -0700184 *
185 * @param isIncoming True if this is an incoming call.
Santos Cordon493e8f22014-02-19 03:15:12 -0800186 */
Sailesh Nepal810735e2014-03-18 18:15:46 -0700187 Call(boolean isIncoming) {
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700188 this(null, null, isIncoming);
Santos Cordon493e8f22014-02-19 03:15:12 -0800189 }
190
191 /**
Ben Gilad0407fb22014-01-09 16:18:41 -0800192 * Persists the specified parameters and initializes the new instance.
193 *
194 * @param handle The handle to dial.
Yorke Lee33501632014-03-17 19:24:12 -0700195 * @param gatewayInfo Gateway information to use for the call.
Sailesh Nepal810735e2014-03-18 18:15:46 -0700196 * @param isIncoming True if this is an incoming call.
Ben Gilad0407fb22014-01-09 16:18:41 -0800197 */
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700198 Call(Uri handle, GatewayInfo gatewayInfo, boolean isIncoming) {
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800199 mState = CallState.NEW;
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700200 setHandle(handle);
Yorke Lee33501632014-03-17 19:24:12 -0700201 mGatewayInfo = gatewayInfo;
Sailesh Nepal810735e2014-03-18 18:15:46 -0700202 mIsIncoming = isIncoming;
Ben Gilad0407fb22014-01-09 16:18:41 -0800203 }
204
Santos Cordon766d04f2014-05-06 10:28:25 -0700205 void addListener(Listener listener) {
206 mListeners.add(listener);
207 }
208
209 void removeListener(Listener listener) {
210 mListeners.remove(listener);
211 }
212
Santos Cordon61d0f702014-02-19 02:52:23 -0800213 /** {@inheritDoc} */
214 @Override public String toString() {
Sailesh Nepal4538f012014-04-15 11:40:33 -0700215 String component = null;
216 if (mCallService != null && mCallService.getComponentName() != null) {
217 component = mCallService.getComponentName().flattenToShortString();
218 }
219 return String.format(Locale.US, "[%s, %s, %s]", mState, component, Log.piiHandle(mHandle));
Santos Cordon61d0f702014-02-19 02:52:23 -0800220 }
221
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800222 CallState getState() {
223 return mState;
224 }
225
226 /**
227 * Sets the call state. Although there exists the notion of appropriate state transitions
228 * (see {@link CallState}), in practice those expectations break down when cellular systems
229 * misbehave and they do this very often. The result is that we do not enforce state transitions
230 * and instead keep the code resilient to unexpected state changes.
231 */
Sailesh Nepal810735e2014-03-18 18:15:46 -0700232 void setState(CallState newState) {
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700233 Preconditions.checkState(newState != CallState.DISCONNECTED ||
234 mDisconnectCause != DisconnectCause.NOT_VALID);
Sailesh Nepal810735e2014-03-18 18:15:46 -0700235 if (mState != newState) {
236 Log.v(this, "setState %s -> %s", mState, newState);
237 mState = newState;
Sailesh Nepal810735e2014-03-18 18:15:46 -0700238 }
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800239 }
240
Ihab Awadcb387ac2014-05-28 16:49:38 -0700241 void setRequestingRingback(boolean requestingRingback) {
242 mRequestingRingback = requestingRingback;
243 for (Listener l : mListeners) {
244 l.onRequestingRingback(this, mRequestingRingback);
245 }
246 }
247
248 boolean isRequestingRingback() {
249 return mRequestingRingback;
250 }
251
Sailesh Nepalce704b92014-03-17 18:31:43 -0700252 Uri getHandle() {
Ben Gilad0bf5b912014-01-28 17:55:57 -0800253 return mHandle;
254 }
255
Sailesh Nepalce704b92014-03-17 18:31:43 -0700256 void setHandle(Uri handle) {
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700257 if ((mHandle == null && handle != null) || (mHandle != null && !mHandle.equals(handle))) {
258 mHandle = handle;
259 mIsEmergencyCall = mHandle != null && PhoneNumberUtils.isLocalEmergencyNumber(
260 mHandle.getSchemeSpecificPart(), TelecommApp.getInstance());
261 startCallerInfoLookup();
262 }
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700263 }
264
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700265 String getName() {
266 return mCallerInfo == null ? null : mCallerInfo.name;
267 }
268
269 Bitmap getPhotoIcon() {
270 return mCallerInfo == null ? null : mCallerInfo.cachedPhotoIcon;
271 }
272
273 Drawable getPhoto() {
274 return mCallerInfo == null ? null : mCallerInfo.cachedPhoto;
275 }
276
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700277 /**
278 * @param disconnectCause The reason for the disconnection, any of
279 * {@link android.telephony.DisconnectCause}.
280 * @param disconnectMessage Optional call-service-provided message about the disconnect.
281 */
282 void setDisconnectCause(int disconnectCause, String disconnectMessage) {
283 // TODO: Consider combining this method with a setDisconnected() method that is totally
284 // separate from setState.
285 mDisconnectCause = disconnectCause;
286 mDisconnectMessage = disconnectMessage;
287 }
288
289 int getDisconnectCause() {
290 return mDisconnectCause;
291 }
292
293 String getDisconnectMessage() {
294 return mDisconnectMessage;
295 }
296
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700297 boolean isEmergencyCall() {
298 return mIsEmergencyCall;
Santos Cordon61d0f702014-02-19 02:52:23 -0800299 }
300
Yorke Lee33501632014-03-17 19:24:12 -0700301 /**
302 * @return The original handle this call is associated with. In-call services should use this
303 * handle when indicating in their UI the handle that is being called.
304 */
305 public Uri getOriginalHandle() {
306 if (mGatewayInfo != null && !mGatewayInfo.isEmpty()) {
307 return mGatewayInfo.getOriginalHandle();
308 }
309 return getHandle();
310 }
311
312 GatewayInfo getGatewayInfo() {
313 return mGatewayInfo;
314 }
315
Sailesh Nepal810735e2014-03-18 18:15:46 -0700316 boolean isIncoming() {
317 return mIsIncoming;
318 }
319
Ben Gilad0407fb22014-01-09 16:18:41 -0800320 /**
321 * @return The "age" of this call object in milliseconds, which typically also represents the
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700322 * period since this call was added to the set pending outgoing calls, see
323 * mCreationTimeMillis.
Ben Gilad0407fb22014-01-09 16:18:41 -0800324 */
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700325 long getAgeMillis() {
326 return System.currentTimeMillis() - mCreationTimeMillis;
Ben Gilad0407fb22014-01-09 16:18:41 -0800327 }
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800328
Yorke Leef98fb572014-03-05 10:56:55 -0800329 /**
330 * @return The time when this call object was created and added to the set of pending outgoing
331 * calls.
332 */
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700333 long getCreationTimeMillis() {
334 return mCreationTimeMillis;
335 }
336
337 long getConnectTimeMillis() {
338 return mConnectTimeMillis;
339 }
340
341 void setConnectTimeMillis(long connectTimeMillis) {
342 mConnectTimeMillis = connectTimeMillis;
Yorke Leef98fb572014-03-05 10:56:55 -0800343 }
344
Santos Cordonc195e362014-02-11 17:05:31 -0800345 CallServiceWrapper getCallService() {
Santos Cordon681663d2014-01-30 04:32:15 -0800346 return mCallService;
347 }
348
Santos Cordonc195e362014-02-11 17:05:31 -0800349 void setCallService(CallServiceWrapper callService) {
Sailesh Nepal0e5410a2014-04-04 01:20:58 -0700350 setCallService(callService, null);
351 }
352
353 /**
354 * Changes the call service this call is associated with. If callToReplace is non-null then this
355 * call takes its place within the call service.
356 */
357 void setCallService(CallServiceWrapper callService, Call callToReplace) {
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800358 Preconditions.checkNotNull(callService);
359
Yorke Leeadee12d2014-03-13 12:08:30 -0700360 clearCallService();
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800361
362 callService.incrementAssociatedCallCount();
Santos Cordon681663d2014-01-30 04:32:15 -0800363 mCallService = callService;
Sailesh Nepal0e5410a2014-04-04 01:20:58 -0700364 if (callToReplace == null) {
365 mCallService.addCall(this);
366 } else {
367 mCallService.replaceCall(this, callToReplace);
368 }
Santos Cordon681663d2014-01-30 04:32:15 -0800369 }
370
371 /**
372 * Clears the associated call service.
373 */
374 void clearCallService() {
Yorke Leeadee12d2014-03-13 12:08:30 -0700375 if (mCallService != null) {
Santos Cordonc499c1c2014-04-14 17:13:14 -0700376 CallServiceWrapper callServiceTemp = mCallService;
Yorke Leeadee12d2014-03-13 12:08:30 -0700377 mCallService = null;
Santos Cordonc499c1c2014-04-14 17:13:14 -0700378 callServiceTemp.removeCall(this);
379
380 // Decrementing the count can cause the service to unbind, which itself can trigger the
381 // service-death code. Since the service death code tries to clean up any associated
382 // calls, we need to make sure to remove that information (e.g., removeCall()) before
383 // we decrement. Technically, invoking removeCall() prior to decrementing is all that is
384 // necessary, but cleaning up mCallService prior to triggering an unbind is good to do.
385 // If you change this, make sure to update {@link clearCallServiceSelector} as well.
386 decrementAssociatedCallCount(callServiceTemp);
Yorke Leeadee12d2014-03-13 12:08:30 -0700387 }
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800388 }
389
Sailesh Nepal84fa5f82014-04-02 11:01:11 -0700390 CallServiceSelectorWrapper getCallServiceSelector() {
391 return mCallServiceSelector;
392 }
393
Sailesh Nepal18386a82014-03-19 10:22:40 -0700394 void setCallServiceSelector(CallServiceSelectorWrapper selector) {
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800395 Preconditions.checkNotNull(selector);
Sailesh Nepale59bb192014-04-01 18:33:59 -0700396
397 clearCallServiceSelector();
398
Santos Cordonc499c1c2014-04-14 17:13:14 -0700399 selector.incrementAssociatedCallCount();
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800400 mCallServiceSelector = selector;
Sailesh Nepale59bb192014-04-01 18:33:59 -0700401 mCallServiceSelector.addCall(this);
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800402 }
403
404 void clearCallServiceSelector() {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700405 if (mCallServiceSelector != null) {
Santos Cordonc499c1c2014-04-14 17:13:14 -0700406 CallServiceSelectorWrapper selectorTemp = mCallServiceSelector;
Sailesh Nepale59bb192014-04-01 18:33:59 -0700407 mCallServiceSelector = null;
Santos Cordonc499c1c2014-04-14 17:13:14 -0700408 selectorTemp.removeCall(this);
409
410 // See comment on {@link #clearCallService}.
411 decrementAssociatedCallCount(selectorTemp);
Sailesh Nepale59bb192014-04-01 18:33:59 -0700412 }
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800413 }
414
415 /**
Santos Cordon766d04f2014-05-06 10:28:25 -0700416 * Starts the incoming call flow through the switchboard. When switchboard completes, it will
417 * invoke handle[Un]SuccessfulIncomingCall.
418 *
419 * @param descriptor The relevant call-service descriptor.
420 * @param extras The optional extras passed via
421 * {@link TelecommConstants#EXTRA_INCOMING_CALL_EXTRAS}.
422 */
423 void startIncoming(CallServiceDescriptor descriptor, Bundle extras) {
424 Switchboard.getInstance().retrieveIncomingCall(this, descriptor, extras);
425 }
426
Santos Cordon2174fb52014-05-29 08:22:56 -0700427 /**
428 * Takes a verified incoming call and uses the handle to lookup direct-to-voicemail property
429 * from the contacts provider. The call is not yet exposed to the user at this point and
430 * the result of the query will determine if the call is rejected or passed through to the
431 * in-call UI.
432 */
433 void handleVerifiedIncoming(CallInfo callInfo) {
Santos Cordon766d04f2014-05-06 10:28:25 -0700434 Preconditions.checkState(callInfo.getState() == CallState.RINGING);
Santos Cordon2174fb52014-05-29 08:22:56 -0700435
436 // We do not handle incoming calls immediately when they are verified by the call service.
437 // We allow the caller-info-query code to execute first so that we can read the
438 // direct-to-voicemail property before deciding if we want to show the incoming call to the
439 // user or if we want to reject the call.
440 mPendingDirectToVoicemailCallInfo = callInfo;
441
442 // Setting the handle triggers the caller info lookup code.
Santos Cordon766d04f2014-05-06 10:28:25 -0700443 setHandle(callInfo.getHandle());
444
Santos Cordon2174fb52014-05-29 08:22:56 -0700445 // Timeout the direct-to-voicemail lookup execution so that we dont wait too long before
446 // showing the user the incoming call screen.
447 mHandler.postDelayed(new Runnable() {
448 @Override
449 public void run() {
450 processDirectToVoicemail();
451 }
452 }, Timeouts.getDirectToVoicemail());
453 }
Santos Cordon766d04f2014-05-06 10:28:25 -0700454
Santos Cordon2174fb52014-05-29 08:22:56 -0700455 void processDirectToVoicemail() {
456 if (mPendingDirectToVoicemailCallInfo != null) {
457 if (mCallerInfo != null && mCallerInfo.shouldSendToVoicemail) {
458 Log.i(this, "Directing call to voicemail: %s.", this);
459 // TODO(santoscordon): Once we move State handling from CallsManager to Call, we
460 // will not need to set RINGING state prior to calling reject.
461 setState(CallState.RINGING);
462 reject();
463 } else {
464 // TODO(santoscordon): Make this class (not CallsManager) responsible for changing
465 // the call state to RINGING.
466
467 // TODO(santoscordon): Replace this with state transition to RINGING.
468 for (Listener l : mListeners) {
469 l.onSuccessfulIncomingCall(this, mPendingDirectToVoicemailCallInfo);
470 }
471 }
472
473 mPendingDirectToVoicemailCallInfo = null;
Santos Cordon766d04f2014-05-06 10:28:25 -0700474 }
475 }
476
477 void handleFailedIncoming() {
478 clearCallService();
479
480 // TODO: Needs more specific disconnect error for this case.
481 setDisconnectCause(DisconnectCause.ERROR_UNSPECIFIED, null);
482 setState(CallState.DISCONNECTED);
483
484 // TODO(santoscordon): Replace this with state transitions related to "connecting".
485 for (Listener l : mListeners) {
486 l.onFailedIncomingCall(this);
487 }
488 }
489
490 /**
Santos Cordon682fe6b2014-05-20 08:56:39 -0700491 * Starts the outgoing call sequence. Upon completion, there should exist an active connection
492 * through a call service (or the call will have failed).
Santos Cordon766d04f2014-05-06 10:28:25 -0700493 */
494 void startOutgoing() {
Santos Cordon682fe6b2014-05-20 08:56:39 -0700495 Preconditions.checkState(mOutgoingCallProcessor == null);
496
497 mOutgoingCallProcessor = new OutgoingCallProcessor(
498 this,
499 Switchboard.getInstance().getCallServiceRepository(),
500 Switchboard.getInstance().getSelectorRepository(),
501 new AsyncResultCallback<Boolean>() {
502 @Override
503 public void onResult(Boolean wasCallPlaced) {
504 if (wasCallPlaced) {
505 handleSuccessfulOutgoing();
506 } else {
507 handleFailedOutgoing(mOutgoingCallProcessor.isAborted());
508 }
509 mOutgoingCallProcessor = null;
510 }
511 });
512 mOutgoingCallProcessor.process();
Santos Cordon766d04f2014-05-06 10:28:25 -0700513 }
514
515 void handleSuccessfulOutgoing() {
516 // TODO(santoscordon): Replace this with state transitions related to "connecting".
517 for (Listener l : mListeners) {
518 l.onSuccessfulOutgoingCall(this);
519 }
520 }
521
522 void handleFailedOutgoing(boolean isAborted) {
523 // TODO(santoscordon): Replace this with state transitions related to "connecting".
524 for (Listener l : mListeners) {
525 l.onFailedOutgoingCall(this, isAborted);
526 }
Santos Cordon682fe6b2014-05-20 08:56:39 -0700527
528 clearCallService();
529 clearCallServiceSelector();
Santos Cordon766d04f2014-05-06 10:28:25 -0700530 }
531
532 /**
Ben Gilad61925612014-03-11 19:06:36 -0700533 * Adds the specified call service to the list of incompatible services. The set is used when
534 * attempting to switch a phone call between call services such that incompatible services can
535 * be avoided.
536 *
537 * @param callService The incompatible call service.
538 */
539 void addIncompatibleCallService(CallServiceWrapper callService) {
540 if (mIncompatibleCallServices == null) {
541 mIncompatibleCallServices = Sets.newHashSet();
542 }
543 mIncompatibleCallServices.add(callService);
544 }
545
546 /**
547 * Checks whether or not the specified callService was identified as incompatible in the
548 * context of this call.
549 *
550 * @param callService The call service to evaluate.
551 * @return True upon incompatible call services and false otherwise.
552 */
553 boolean isIncompatibleCallService(CallServiceWrapper callService) {
554 return mIncompatibleCallServices != null &&
555 mIncompatibleCallServices.contains(callService);
556 }
557
558 /**
Ihab Awad74549ec2014-03-10 15:33:25 -0700559 * Plays the specified DTMF tone.
560 */
561 void playDtmfTone(char digit) {
562 if (mCallService == null) {
563 Log.w(this, "playDtmfTone() request on a call without a call service.");
564 } else {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700565 Log.i(this, "Send playDtmfTone to call service for call %s", this);
566 mCallService.playDtmfTone(this, digit);
Ihab Awad74549ec2014-03-10 15:33:25 -0700567 }
568 }
569
570 /**
571 * Stops playing any currently playing DTMF tone.
572 */
573 void stopDtmfTone() {
574 if (mCallService == null) {
575 Log.w(this, "stopDtmfTone() request on a call without a call service.");
576 } else {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700577 Log.i(this, "Send stopDtmfTone to call service for call %s", this);
578 mCallService.stopDtmfTone(this);
Ihab Awad74549ec2014-03-10 15:33:25 -0700579 }
580 }
581
582 /**
Santos Cordon049b7b62014-01-30 05:34:26 -0800583 * Attempts to disconnect the call through the call service.
584 */
585 void disconnect() {
Santos Cordon766d04f2014-05-06 10:28:25 -0700586 if (mState == CallState.NEW) {
Santos Cordon682fe6b2014-05-20 08:56:39 -0700587 Log.v(this, "Aborting call %s", this);
588 abort();
Santos Cordon74d420b2014-05-07 14:38:47 -0700589 } else if (mState != CallState.ABORTED && mState != CallState.DISCONNECTED) {
Santos Cordon766d04f2014-05-06 10:28:25 -0700590 Preconditions.checkNotNull(mCallService);
591
Sailesh Nepale59bb192014-04-01 18:33:59 -0700592 Log.i(this, "Send disconnect to call service for call: %s", this);
Santos Cordonc195e362014-02-11 17:05:31 -0800593 // The call isn't officially disconnected until the call service confirms that the call
594 // was actually disconnected. Only then is the association between call and call service
595 // severed, see {@link CallsManager#markCallAsDisconnected}.
Sailesh Nepale59bb192014-04-01 18:33:59 -0700596 mCallService.disconnect(this);
Santos Cordon049b7b62014-01-30 05:34:26 -0800597 }
598 }
599
Santos Cordon682fe6b2014-05-20 08:56:39 -0700600 void abort() {
601 if (mOutgoingCallProcessor != null) {
602 mOutgoingCallProcessor.abort();
603 }
604 }
605
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800606 /**
Santos Cordon61d0f702014-02-19 02:52:23 -0800607 * Answers the call if it is ringing.
608 */
609 void answer() {
610 Preconditions.checkNotNull(mCallService);
611
612 // Check to verify that the call is still in the ringing state. A call can change states
613 // between the time the user hits 'answer' and Telecomm receives the command.
614 if (isRinging("answer")) {
615 // At this point, we are asking the call service to answer but we don't assume that
616 // it will work. Instead, we wait until confirmation from the call service that the
617 // call is in a non-RINGING state before changing the UI. See
618 // {@link CallServiceAdapter#setActive} and other set* methods.
Sailesh Nepale59bb192014-04-01 18:33:59 -0700619 mCallService.answer(this);
Santos Cordon61d0f702014-02-19 02:52:23 -0800620 }
621 }
622
623 /**
624 * Rejects the call if it is ringing.
625 */
626 void reject() {
627 Preconditions.checkNotNull(mCallService);
628
629 // Check to verify that the call is still in the ringing state. A call can change states
630 // between the time the user hits 'reject' and Telecomm receives the command.
631 if (isRinging("reject")) {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700632 mCallService.reject(this);
Santos Cordon61d0f702014-02-19 02:52:23 -0800633 }
634 }
635
636 /**
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700637 * Puts the call on hold if it is currently active.
638 */
639 void hold() {
640 Preconditions.checkNotNull(mCallService);
641
642 if (mState == CallState.ACTIVE) {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700643 mCallService.hold(this);
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700644 }
645 }
646
647 /**
648 * Releases the call from hold if it is currently active.
649 */
650 void unhold() {
651 Preconditions.checkNotNull(mCallService);
652
653 if (mState == CallState.ON_HOLD) {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700654 mCallService.unhold(this);
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700655 }
656 }
657
658 /**
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800659 * @return An object containing read-only information about this call.
660 */
Sailesh Nepale59bb192014-04-01 18:33:59 -0700661 CallInfo toCallInfo(String callId) {
Sailesh Nepal84fa5f82014-04-02 11:01:11 -0700662 CallServiceDescriptor descriptor = null;
663 if (mCallService != null) {
664 descriptor = mCallService.getDescriptor();
665 } else if (mOriginalCall != null && mOriginalCall.mCallService != null) {
666 descriptor = mOriginalCall.mCallService.getDescriptor();
667 }
668 return new CallInfo(callId, mState, mHandle, mGatewayInfo, mExtras, descriptor);
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800669 }
670
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700671 /** Checks if this is a live call or not. */
672 boolean isAlive() {
673 switch (mState) {
674 case NEW:
675 case RINGING:
676 case DISCONNECTED:
677 case ABORTED:
678 return false;
679 default:
680 return true;
681 }
682 }
683
Santos Cordon40f78c22014-04-07 02:11:42 -0700684 boolean isActive() {
685 switch (mState) {
686 case ACTIVE:
687 case POST_DIAL:
688 case POST_DIAL_WAIT:
689 return true;
690 default:
691 return false;
692 }
693 }
694
Sailesh Nepal84fa5f82014-04-02 11:01:11 -0700695 Bundle getExtras() {
696 return mExtras;
697 }
698
699 void setExtras(Bundle extras) {
700 mExtras = extras;
701 }
702
703 Uri getHandoffHandle() {
704 return mHandoffHandle;
705 }
706
707 void setHandoffHandle(Uri handoffHandle) {
708 mHandoffHandle = handoffHandle;
709 }
710
711 Call getOriginalCall() {
712 return mOriginalCall;
713 }
714
715 void setOriginalCall(Call originalCall) {
716 mOriginalCall = originalCall;
717 }
718
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700719 CallServiceDescriptor getHandoffCallServiceDescriptor() {
720 return mHandoffCallServiceDescriptor;
721 }
722
723 void setHandoffCallServiceDescriptor(CallServiceDescriptor descriptor) {
724 mHandoffCallServiceDescriptor = descriptor;
725 }
726
Santos Cordon5ba7f272014-05-28 13:59:49 -0700727 Uri getRingtone() {
728 return mCallerInfo == null ? null : mCallerInfo.contactRingtoneUri;
729 }
730
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800731 /**
Santos Cordon61d0f702014-02-19 02:52:23 -0800732 * @return True if the call is ringing, else logs the action name.
733 */
734 private boolean isRinging(String actionName) {
735 if (mState == CallState.RINGING) {
736 return true;
737 }
738
Sailesh Nepalf1c191d2014-03-07 18:17:39 -0800739 Log.i(this, "Request to %s a non-ringing call %s", actionName, this);
Santos Cordon61d0f702014-02-19 02:52:23 -0800740 return false;
741 }
742
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800743 @SuppressWarnings("rawtypes")
744 private void decrementAssociatedCallCount(ServiceBinder binder) {
745 if (binder != null) {
746 binder.decrementAssociatedCallCount();
747 }
748 }
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700749
750 /**
751 * Looks up contact information based on the current handle.
752 */
753 private void startCallerInfoLookup() {
754 String number = mHandle == null ? null : mHandle.getSchemeSpecificPart();
755
756 mQueryToken++; // Updated so that previous queries can no longer set the information.
757 mCallerInfo = null;
758 if (!TextUtils.isEmpty(number)) {
Santos Cordon5ba7f272014-05-28 13:59:49 -0700759 Log.v(this, "Looking up information for: %s.", Log.piiHandle(number));
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700760 CallerInfoAsyncQuery.startQuery(
761 mQueryToken,
762 TelecommApp.getInstance(),
763 number,
764 sCallerInfoQueryListener,
765 this);
766 }
767 }
768
769 /**
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700770 * Saves the specified caller info if the specified token matches that of the last query
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700771 * that was made.
772 *
773 * @param callerInfo The new caller information to set.
774 * @param token The token used with this query.
775 */
776 private void setCallerInfo(CallerInfo callerInfo, int token) {
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700777 Preconditions.checkNotNull(callerInfo);
778
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700779 if (mQueryToken == token) {
780 mCallerInfo = callerInfo;
Santos Cordon5ba7f272014-05-28 13:59:49 -0700781 Log.i(this, "CallerInfo received for %s: %s", Log.piiHandle(mHandle), callerInfo);
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700782
783 if (mCallerInfo.person_id != 0) {
784 Uri personUri =
785 ContentUris.withAppendedId(Contacts.CONTENT_URI, mCallerInfo.person_id);
786 Log.d(this, "Searching person uri %s for call %s", personUri, this);
787 ContactsAsyncHelper.startObtainPhotoAsync(
788 token,
789 TelecommApp.getInstance(),
790 personUri,
791 sPhotoLoadListener,
792 this);
793 }
Santos Cordon2174fb52014-05-29 08:22:56 -0700794
795 processDirectToVoicemail();
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700796 }
797 }
798
799 /**
800 * Saves the specified photo information if the specified token matches that of the last query.
801 *
802 * @param photo The photo as a drawable.
803 * @param photoIcon The photo as a small icon.
804 * @param token The token used with this query.
805 */
806 private void setPhoto(Drawable photo, Bitmap photoIcon, int token) {
807 if (mQueryToken == token) {
808 mCallerInfo.cachedPhoto = photo;
809 mCallerInfo.cachedPhotoIcon = photoIcon;
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700810 }
811 }
Ben Gilad9f2bed32013-12-12 17:43:26 -0800812}