blob: 66f936689bf452abaf7dd08d74c06cdefb2bbbb3 [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);
Ihab Awad0fea3f22014-06-03 18:45:05 -070058 void onFailedOutgoingCall(Call call, boolean isAborted, int errorCode, String errorMsg);
Santos Cordon766d04f2014-05-06 10:28:25 -070059 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);
Evan Charlton352105c2014-06-03 14:10:54 -070062 void onPostDialWait(Call call, String remaining);
Santos Cordon766d04f2014-05-06 10:28:25 -070063 }
64
Santos Cordonfd71f4a2014-05-28 13:59:49 -070065 private static final OnQueryCompleteListener sCallerInfoQueryListener =
Santos Cordon99c8a6f2014-05-28 18:28:47 -070066 new OnQueryCompleteListener() {
67 /** ${inheritDoc} */
68 @Override
69 public void onQueryComplete(int token, Object cookie, CallerInfo callerInfo) {
70 if (cookie != null) {
71 ((Call) cookie).setCallerInfo(callerInfo, token);
72 }
Santos Cordonfd71f4a2014-05-28 13:59:49 -070073 }
Santos Cordon99c8a6f2014-05-28 18:28:47 -070074 };
75
76 private static final OnImageLoadCompleteListener sPhotoLoadListener =
77 new OnImageLoadCompleteListener() {
78 /** ${inheritDoc} */
79 @Override
80 public void onImageLoadComplete(
81 int token, Drawable photo, Bitmap photoIcon, Object cookie) {
82 if (cookie != null) {
83 ((Call) cookie).setPhoto(photo, photoIcon, token);
84 }
85 }
86 };
Ben Gilad0407fb22014-01-09 16:18:41 -080087
Sailesh Nepal810735e2014-03-18 18:15:46 -070088 /** True if this is an incoming call. */
89 private final boolean mIsIncoming;
90
Ben Gilad0407fb22014-01-09 16:18:41 -080091 /**
92 * The time this call was created, typically also the time this call was added to the set
93 * of pending outgoing calls (mPendingOutgoingCalls) that's maintained by the switchboard.
94 * Beyond logging and such, may also be used for bookkeeping and specifically for marking
95 * certain call attempts as failed attempts.
96 */
Sailesh Nepal8c85dee2014-04-07 22:21:40 -070097 private final long mCreationTimeMillis = System.currentTimeMillis();
98
Santos Cordonfd71f4a2014-05-28 13:59:49 -070099 /** The gateway information associated with this call. This stores the original call handle
100 * that the user is attempting to connect to via the gateway, the actual handle to dial in
101 * order to connect the call via the gateway, as well as the package name of the gateway
102 * service. */
103 private final GatewayInfo mGatewayInfo;
104
Santos Cordon2174fb52014-05-29 08:22:56 -0700105 private final Handler mHandler = new Handler();
106
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700107 private long mConnectTimeMillis;
Ben Gilad0407fb22014-01-09 16:18:41 -0800108
Santos Cordon61d0f702014-02-19 02:52:23 -0800109 /** The state of the call. */
110 private CallState mState;
111
112 /** The handle with which to establish this call. */
Sailesh Nepalce704b92014-03-17 18:31:43 -0700113 private Uri mHandle;
Santos Cordon61d0f702014-02-19 02:52:23 -0800114
Ben Gilad0407fb22014-01-09 16:18:41 -0800115 /**
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800116 * The call service which is attempted or already connecting this call.
Santos Cordon681663d2014-01-30 04:32:15 -0800117 */
Santos Cordonc195e362014-02-11 17:05:31 -0800118 private CallServiceWrapper mCallService;
Santos Cordon681663d2014-01-30 04:32:15 -0800119
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800120 /**
121 * The call-service selector for this call.
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800122 */
Sailesh Nepal18386a82014-03-19 10:22:40 -0700123 private CallServiceSelectorWrapper mCallServiceSelector;
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800124
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800125 /**
Ben Gilad61925612014-03-11 19:06:36 -0700126 * The set of call services that were attempted in the process of placing/switching this call
127 * but turned out unsuitable. Only used in the context of call switching.
128 */
129 private Set<CallServiceWrapper> mIncompatibleCallServices;
130
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700131 private boolean mIsEmergencyCall;
132
Ben Gilad61925612014-03-11 19:06:36 -0700133 /**
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700134 * Disconnect cause for the call. Only valid if the state of the call is DISCONNECTED.
135 * See {@link android.telephony.DisconnectCause}.
136 */
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700137 private int mDisconnectCause = DisconnectCause.NOT_VALID;
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700138
139 /**
140 * Additional disconnect information provided by the call service.
141 */
142 private String mDisconnectMessage;
143
Sailesh Nepal84fa5f82014-04-02 11:01:11 -0700144 /** Info used by the call services. */
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700145 private Bundle mExtras = Bundle.EMPTY;
Sailesh Nepal84fa5f82014-04-02 11:01:11 -0700146
147 /** The Uri to dial to perform the handoff. If this is null then handoff is not supported. */
148 private Uri mHandoffHandle;
149
150 /**
151 * References the call that is being handed off. This value is non-null for untracked calls
152 * that are being used to perform a handoff.
153 */
154 private Call mOriginalCall;
155
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700156 /**
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700157 * The descriptor for the call service that this call is being switched to, null if handoff is
158 * not in progress.
159 */
160 private CallServiceDescriptor mHandoffCallServiceDescriptor;
161
Santos Cordon766d04f2014-05-06 10:28:25 -0700162 /** Set of listeners on this call. */
163 private Set<Listener> mListeners = Sets.newHashSet();
164
Santos Cordon682fe6b2014-05-20 08:56:39 -0700165 private OutgoingCallProcessor mOutgoingCallProcessor;
166
167 // TODO(santoscordon): The repositories should be changed into singleton types.
168 private CallServiceRepository mCallServiceRepository;
169 private CallServiceSelectorRepository mSelectorRepository;
170
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700171 /** Caller information retrieved from the latest contact query. */
172 private CallerInfo mCallerInfo;
173
174 /** The latest token used with a contact info query. */
175 private int mQueryToken = 0;
176
Ihab Awadcb387ac2014-05-28 16:49:38 -0700177 /** Whether this call is requesting that Telecomm play the ringback tone on its behalf. */
178 private boolean mRequestingRingback = false;
179
Santos Cordon2174fb52014-05-29 08:22:56 -0700180 /** Incoming call-info to use when direct-to-voicemail query finishes. */
181 private CallInfo mPendingDirectToVoicemailCallInfo;
182
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700183 /**
Sailesh Nepale59bb192014-04-01 18:33:59 -0700184 * Creates an empty call object.
Sailesh Nepal810735e2014-03-18 18:15:46 -0700185 *
186 * @param isIncoming True if this is an incoming call.
Santos Cordon493e8f22014-02-19 03:15:12 -0800187 */
Sailesh Nepal810735e2014-03-18 18:15:46 -0700188 Call(boolean isIncoming) {
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700189 this(null, null, isIncoming);
Santos Cordon493e8f22014-02-19 03:15:12 -0800190 }
191
192 /**
Ben Gilad0407fb22014-01-09 16:18:41 -0800193 * Persists the specified parameters and initializes the new instance.
194 *
195 * @param handle The handle to dial.
Yorke Lee33501632014-03-17 19:24:12 -0700196 * @param gatewayInfo Gateway information to use for the call.
Sailesh Nepal810735e2014-03-18 18:15:46 -0700197 * @param isIncoming True if this is an incoming call.
Ben Gilad0407fb22014-01-09 16:18:41 -0800198 */
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700199 Call(Uri handle, GatewayInfo gatewayInfo, boolean isIncoming) {
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800200 mState = CallState.NEW;
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700201 setHandle(handle);
Yorke Lee33501632014-03-17 19:24:12 -0700202 mGatewayInfo = gatewayInfo;
Sailesh Nepal810735e2014-03-18 18:15:46 -0700203 mIsIncoming = isIncoming;
Ben Gilad0407fb22014-01-09 16:18:41 -0800204 }
205
Santos Cordon766d04f2014-05-06 10:28:25 -0700206 void addListener(Listener listener) {
207 mListeners.add(listener);
208 }
209
210 void removeListener(Listener listener) {
211 mListeners.remove(listener);
212 }
213
Santos Cordon61d0f702014-02-19 02:52:23 -0800214 /** {@inheritDoc} */
215 @Override public String toString() {
Sailesh Nepal4538f012014-04-15 11:40:33 -0700216 String component = null;
217 if (mCallService != null && mCallService.getComponentName() != null) {
218 component = mCallService.getComponentName().flattenToShortString();
219 }
220 return String.format(Locale.US, "[%s, %s, %s]", mState, component, Log.piiHandle(mHandle));
Santos Cordon61d0f702014-02-19 02:52:23 -0800221 }
222
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800223 CallState getState() {
224 return mState;
225 }
226
227 /**
228 * Sets the call state. Although there exists the notion of appropriate state transitions
229 * (see {@link CallState}), in practice those expectations break down when cellular systems
230 * misbehave and they do this very often. The result is that we do not enforce state transitions
231 * and instead keep the code resilient to unexpected state changes.
232 */
Sailesh Nepal810735e2014-03-18 18:15:46 -0700233 void setState(CallState newState) {
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700234 Preconditions.checkState(newState != CallState.DISCONNECTED ||
235 mDisconnectCause != DisconnectCause.NOT_VALID);
Sailesh Nepal810735e2014-03-18 18:15:46 -0700236 if (mState != newState) {
237 Log.v(this, "setState %s -> %s", mState, newState);
238 mState = newState;
Sailesh Nepal810735e2014-03-18 18:15:46 -0700239 }
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800240 }
241
Ihab Awadcb387ac2014-05-28 16:49:38 -0700242 void setRequestingRingback(boolean requestingRingback) {
243 mRequestingRingback = requestingRingback;
244 for (Listener l : mListeners) {
245 l.onRequestingRingback(this, mRequestingRingback);
246 }
247 }
248
249 boolean isRequestingRingback() {
250 return mRequestingRingback;
251 }
252
Sailesh Nepalce704b92014-03-17 18:31:43 -0700253 Uri getHandle() {
Ben Gilad0bf5b912014-01-28 17:55:57 -0800254 return mHandle;
255 }
256
Sailesh Nepalce704b92014-03-17 18:31:43 -0700257 void setHandle(Uri handle) {
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700258 if ((mHandle == null && handle != null) || (mHandle != null && !mHandle.equals(handle))) {
259 mHandle = handle;
260 mIsEmergencyCall = mHandle != null && PhoneNumberUtils.isLocalEmergencyNumber(
Yorke Lee66255452014-06-05 08:09:24 -0700261 TelecommApp.getInstance(), mHandle.getSchemeSpecificPart());
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700262 startCallerInfoLookup();
263 }
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700264 }
265
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700266 String getName() {
267 return mCallerInfo == null ? null : mCallerInfo.name;
268 }
269
270 Bitmap getPhotoIcon() {
271 return mCallerInfo == null ? null : mCallerInfo.cachedPhotoIcon;
272 }
273
274 Drawable getPhoto() {
275 return mCallerInfo == null ? null : mCallerInfo.cachedPhoto;
276 }
277
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700278 /**
279 * @param disconnectCause The reason for the disconnection, any of
280 * {@link android.telephony.DisconnectCause}.
281 * @param disconnectMessage Optional call-service-provided message about the disconnect.
282 */
283 void setDisconnectCause(int disconnectCause, String disconnectMessage) {
284 // TODO: Consider combining this method with a setDisconnected() method that is totally
285 // separate from setState.
286 mDisconnectCause = disconnectCause;
287 mDisconnectMessage = disconnectMessage;
288 }
289
290 int getDisconnectCause() {
291 return mDisconnectCause;
292 }
293
294 String getDisconnectMessage() {
295 return mDisconnectMessage;
296 }
297
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700298 boolean isEmergencyCall() {
299 return mIsEmergencyCall;
Santos Cordon61d0f702014-02-19 02:52:23 -0800300 }
301
Yorke Lee33501632014-03-17 19:24:12 -0700302 /**
303 * @return The original handle this call is associated with. In-call services should use this
304 * handle when indicating in their UI the handle that is being called.
305 */
306 public Uri getOriginalHandle() {
307 if (mGatewayInfo != null && !mGatewayInfo.isEmpty()) {
308 return mGatewayInfo.getOriginalHandle();
309 }
310 return getHandle();
311 }
312
313 GatewayInfo getGatewayInfo() {
314 return mGatewayInfo;
315 }
316
Sailesh Nepal810735e2014-03-18 18:15:46 -0700317 boolean isIncoming() {
318 return mIsIncoming;
319 }
320
Ben Gilad0407fb22014-01-09 16:18:41 -0800321 /**
322 * @return The "age" of this call object in milliseconds, which typically also represents the
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700323 * period since this call was added to the set pending outgoing calls, see
324 * mCreationTimeMillis.
Ben Gilad0407fb22014-01-09 16:18:41 -0800325 */
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700326 long getAgeMillis() {
327 return System.currentTimeMillis() - mCreationTimeMillis;
Ben Gilad0407fb22014-01-09 16:18:41 -0800328 }
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800329
Yorke Leef98fb572014-03-05 10:56:55 -0800330 /**
331 * @return The time when this call object was created and added to the set of pending outgoing
332 * calls.
333 */
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700334 long getCreationTimeMillis() {
335 return mCreationTimeMillis;
336 }
337
338 long getConnectTimeMillis() {
339 return mConnectTimeMillis;
340 }
341
342 void setConnectTimeMillis(long connectTimeMillis) {
343 mConnectTimeMillis = connectTimeMillis;
Yorke Leef98fb572014-03-05 10:56:55 -0800344 }
345
Santos Cordonc195e362014-02-11 17:05:31 -0800346 CallServiceWrapper getCallService() {
Santos Cordon681663d2014-01-30 04:32:15 -0800347 return mCallService;
348 }
349
Santos Cordonc195e362014-02-11 17:05:31 -0800350 void setCallService(CallServiceWrapper callService) {
Sailesh Nepal0e5410a2014-04-04 01:20:58 -0700351 setCallService(callService, null);
352 }
353
354 /**
355 * Changes the call service this call is associated with. If callToReplace is non-null then this
356 * call takes its place within the call service.
357 */
358 void setCallService(CallServiceWrapper callService, Call callToReplace) {
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800359 Preconditions.checkNotNull(callService);
360
Yorke Leeadee12d2014-03-13 12:08:30 -0700361 clearCallService();
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800362
363 callService.incrementAssociatedCallCount();
Santos Cordon681663d2014-01-30 04:32:15 -0800364 mCallService = callService;
Sailesh Nepal0e5410a2014-04-04 01:20:58 -0700365 if (callToReplace == null) {
366 mCallService.addCall(this);
367 } else {
368 mCallService.replaceCall(this, callToReplace);
369 }
Santos Cordon681663d2014-01-30 04:32:15 -0800370 }
371
372 /**
373 * Clears the associated call service.
374 */
375 void clearCallService() {
Yorke Leeadee12d2014-03-13 12:08:30 -0700376 if (mCallService != null) {
Santos Cordonc499c1c2014-04-14 17:13:14 -0700377 CallServiceWrapper callServiceTemp = mCallService;
Yorke Leeadee12d2014-03-13 12:08:30 -0700378 mCallService = null;
Santos Cordonc499c1c2014-04-14 17:13:14 -0700379 callServiceTemp.removeCall(this);
380
381 // Decrementing the count can cause the service to unbind, which itself can trigger the
382 // service-death code. Since the service death code tries to clean up any associated
383 // calls, we need to make sure to remove that information (e.g., removeCall()) before
384 // we decrement. Technically, invoking removeCall() prior to decrementing is all that is
385 // necessary, but cleaning up mCallService prior to triggering an unbind is good to do.
386 // If you change this, make sure to update {@link clearCallServiceSelector} as well.
387 decrementAssociatedCallCount(callServiceTemp);
Yorke Leeadee12d2014-03-13 12:08:30 -0700388 }
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800389 }
390
Sailesh Nepal84fa5f82014-04-02 11:01:11 -0700391 CallServiceSelectorWrapper getCallServiceSelector() {
392 return mCallServiceSelector;
393 }
394
Sailesh Nepal18386a82014-03-19 10:22:40 -0700395 void setCallServiceSelector(CallServiceSelectorWrapper selector) {
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800396 Preconditions.checkNotNull(selector);
Sailesh Nepale59bb192014-04-01 18:33:59 -0700397
398 clearCallServiceSelector();
399
Santos Cordonc499c1c2014-04-14 17:13:14 -0700400 selector.incrementAssociatedCallCount();
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800401 mCallServiceSelector = selector;
Sailesh Nepale59bb192014-04-01 18:33:59 -0700402 mCallServiceSelector.addCall(this);
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800403 }
404
405 void clearCallServiceSelector() {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700406 if (mCallServiceSelector != null) {
Santos Cordonc499c1c2014-04-14 17:13:14 -0700407 CallServiceSelectorWrapper selectorTemp = mCallServiceSelector;
Sailesh Nepale59bb192014-04-01 18:33:59 -0700408 mCallServiceSelector = null;
Santos Cordonc499c1c2014-04-14 17:13:14 -0700409 selectorTemp.removeCall(this);
410
411 // See comment on {@link #clearCallService}.
412 decrementAssociatedCallCount(selectorTemp);
Sailesh Nepale59bb192014-04-01 18:33:59 -0700413 }
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800414 }
415
416 /**
Santos Cordon766d04f2014-05-06 10:28:25 -0700417 * Starts the incoming call flow through the switchboard. When switchboard completes, it will
418 * invoke handle[Un]SuccessfulIncomingCall.
419 *
420 * @param descriptor The relevant call-service descriptor.
421 * @param extras The optional extras passed via
422 * {@link TelecommConstants#EXTRA_INCOMING_CALL_EXTRAS}.
423 */
424 void startIncoming(CallServiceDescriptor descriptor, Bundle extras) {
425 Switchboard.getInstance().retrieveIncomingCall(this, descriptor, extras);
426 }
427
Santos Cordon2174fb52014-05-29 08:22:56 -0700428 /**
429 * Takes a verified incoming call and uses the handle to lookup direct-to-voicemail property
430 * from the contacts provider. The call is not yet exposed to the user at this point and
431 * the result of the query will determine if the call is rejected or passed through to the
432 * in-call UI.
433 */
434 void handleVerifiedIncoming(CallInfo callInfo) {
Santos Cordon766d04f2014-05-06 10:28:25 -0700435 Preconditions.checkState(callInfo.getState() == CallState.RINGING);
Santos Cordon2174fb52014-05-29 08:22:56 -0700436
437 // We do not handle incoming calls immediately when they are verified by the call service.
438 // We allow the caller-info-query code to execute first so that we can read the
439 // direct-to-voicemail property before deciding if we want to show the incoming call to the
440 // user or if we want to reject the call.
441 mPendingDirectToVoicemailCallInfo = callInfo;
442
443 // Setting the handle triggers the caller info lookup code.
Santos Cordon766d04f2014-05-06 10:28:25 -0700444 setHandle(callInfo.getHandle());
445
Santos Cordon2174fb52014-05-29 08:22:56 -0700446 // Timeout the direct-to-voicemail lookup execution so that we dont wait too long before
447 // showing the user the incoming call screen.
448 mHandler.postDelayed(new Runnable() {
449 @Override
450 public void run() {
451 processDirectToVoicemail();
452 }
453 }, Timeouts.getDirectToVoicemail());
454 }
Santos Cordon766d04f2014-05-06 10:28:25 -0700455
Santos Cordon2174fb52014-05-29 08:22:56 -0700456 void processDirectToVoicemail() {
457 if (mPendingDirectToVoicemailCallInfo != null) {
458 if (mCallerInfo != null && mCallerInfo.shouldSendToVoicemail) {
459 Log.i(this, "Directing call to voicemail: %s.", this);
460 // TODO(santoscordon): Once we move State handling from CallsManager to Call, we
461 // will not need to set RINGING state prior to calling reject.
462 setState(CallState.RINGING);
463 reject();
464 } else {
465 // TODO(santoscordon): Make this class (not CallsManager) responsible for changing
466 // the call state to RINGING.
467
468 // TODO(santoscordon): Replace this with state transition to RINGING.
469 for (Listener l : mListeners) {
470 l.onSuccessfulIncomingCall(this, mPendingDirectToVoicemailCallInfo);
471 }
472 }
473
474 mPendingDirectToVoicemailCallInfo = null;
Santos Cordon766d04f2014-05-06 10:28:25 -0700475 }
476 }
477
478 void handleFailedIncoming() {
479 clearCallService();
480
481 // TODO: Needs more specific disconnect error for this case.
482 setDisconnectCause(DisconnectCause.ERROR_UNSPECIFIED, null);
483 setState(CallState.DISCONNECTED);
484
485 // TODO(santoscordon): Replace this with state transitions related to "connecting".
486 for (Listener l : mListeners) {
487 l.onFailedIncomingCall(this);
488 }
489 }
490
491 /**
Santos Cordon682fe6b2014-05-20 08:56:39 -0700492 * Starts the outgoing call sequence. Upon completion, there should exist an active connection
493 * through a call service (or the call will have failed).
Santos Cordon766d04f2014-05-06 10:28:25 -0700494 */
495 void startOutgoing() {
Santos Cordon682fe6b2014-05-20 08:56:39 -0700496 Preconditions.checkState(mOutgoingCallProcessor == null);
497
498 mOutgoingCallProcessor = new OutgoingCallProcessor(
499 this,
500 Switchboard.getInstance().getCallServiceRepository(),
501 Switchboard.getInstance().getSelectorRepository(),
502 new AsyncResultCallback<Boolean>() {
503 @Override
Ihab Awad0fea3f22014-06-03 18:45:05 -0700504 public void onResult(Boolean wasCallPlaced, int errorCode, String errorMsg) {
Santos Cordon682fe6b2014-05-20 08:56:39 -0700505 if (wasCallPlaced) {
506 handleSuccessfulOutgoing();
507 } else {
Ihab Awad0fea3f22014-06-03 18:45:05 -0700508 handleFailedOutgoing(
509 mOutgoingCallProcessor.isAborted(), errorCode, errorMsg);
Santos Cordon682fe6b2014-05-20 08:56:39 -0700510 }
511 mOutgoingCallProcessor = null;
512 }
513 });
514 mOutgoingCallProcessor.process();
Santos Cordon766d04f2014-05-06 10:28:25 -0700515 }
516
517 void handleSuccessfulOutgoing() {
518 // TODO(santoscordon): Replace this with state transitions related to "connecting".
519 for (Listener l : mListeners) {
520 l.onSuccessfulOutgoingCall(this);
521 }
522 }
523
Ihab Awad0fea3f22014-06-03 18:45:05 -0700524 void handleFailedOutgoing(boolean isAborted, int errorCode, String errorMsg) {
Santos Cordon766d04f2014-05-06 10:28:25 -0700525 // TODO(santoscordon): Replace this with state transitions related to "connecting".
526 for (Listener l : mListeners) {
Ihab Awad0fea3f22014-06-03 18:45:05 -0700527 l.onFailedOutgoingCall(this, isAborted, errorCode, errorMsg);
Santos Cordon766d04f2014-05-06 10:28:25 -0700528 }
Santos Cordon682fe6b2014-05-20 08:56:39 -0700529
530 clearCallService();
531 clearCallServiceSelector();
Santos Cordon766d04f2014-05-06 10:28:25 -0700532 }
533
534 /**
Ben Gilad61925612014-03-11 19:06:36 -0700535 * Adds the specified call service to the list of incompatible services. The set is used when
536 * attempting to switch a phone call between call services such that incompatible services can
537 * be avoided.
538 *
539 * @param callService The incompatible call service.
540 */
541 void addIncompatibleCallService(CallServiceWrapper callService) {
542 if (mIncompatibleCallServices == null) {
543 mIncompatibleCallServices = Sets.newHashSet();
544 }
545 mIncompatibleCallServices.add(callService);
546 }
547
548 /**
549 * Checks whether or not the specified callService was identified as incompatible in the
550 * context of this call.
551 *
552 * @param callService The call service to evaluate.
553 * @return True upon incompatible call services and false otherwise.
554 */
555 boolean isIncompatibleCallService(CallServiceWrapper callService) {
556 return mIncompatibleCallServices != null &&
557 mIncompatibleCallServices.contains(callService);
558 }
559
560 /**
Ihab Awad74549ec2014-03-10 15:33:25 -0700561 * Plays the specified DTMF tone.
562 */
563 void playDtmfTone(char digit) {
564 if (mCallService == null) {
565 Log.w(this, "playDtmfTone() request on a call without a call service.");
566 } else {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700567 Log.i(this, "Send playDtmfTone to call service for call %s", this);
568 mCallService.playDtmfTone(this, digit);
Ihab Awad74549ec2014-03-10 15:33:25 -0700569 }
570 }
571
572 /**
573 * Stops playing any currently playing DTMF tone.
574 */
575 void stopDtmfTone() {
576 if (mCallService == null) {
577 Log.w(this, "stopDtmfTone() request on a call without a call service.");
578 } else {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700579 Log.i(this, "Send stopDtmfTone to call service for call %s", this);
580 mCallService.stopDtmfTone(this);
Ihab Awad74549ec2014-03-10 15:33:25 -0700581 }
582 }
583
584 /**
Santos Cordon049b7b62014-01-30 05:34:26 -0800585 * Attempts to disconnect the call through the call service.
586 */
587 void disconnect() {
Santos Cordon766d04f2014-05-06 10:28:25 -0700588 if (mState == CallState.NEW) {
Santos Cordon682fe6b2014-05-20 08:56:39 -0700589 Log.v(this, "Aborting call %s", this);
590 abort();
Santos Cordon74d420b2014-05-07 14:38:47 -0700591 } else if (mState != CallState.ABORTED && mState != CallState.DISCONNECTED) {
Santos Cordon766d04f2014-05-06 10:28:25 -0700592 Preconditions.checkNotNull(mCallService);
593
Sailesh Nepale59bb192014-04-01 18:33:59 -0700594 Log.i(this, "Send disconnect to call service for call: %s", this);
Santos Cordonc195e362014-02-11 17:05:31 -0800595 // The call isn't officially disconnected until the call service confirms that the call
596 // was actually disconnected. Only then is the association between call and call service
597 // severed, see {@link CallsManager#markCallAsDisconnected}.
Sailesh Nepale59bb192014-04-01 18:33:59 -0700598 mCallService.disconnect(this);
Santos Cordon049b7b62014-01-30 05:34:26 -0800599 }
600 }
601
Santos Cordon682fe6b2014-05-20 08:56:39 -0700602 void abort() {
603 if (mOutgoingCallProcessor != null) {
604 mOutgoingCallProcessor.abort();
605 }
606 }
607
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800608 /**
Santos Cordon61d0f702014-02-19 02:52:23 -0800609 * Answers the call if it is ringing.
610 */
611 void answer() {
612 Preconditions.checkNotNull(mCallService);
613
614 // Check to verify that the call is still in the ringing state. A call can change states
615 // between the time the user hits 'answer' and Telecomm receives the command.
616 if (isRinging("answer")) {
617 // At this point, we are asking the call service to answer but we don't assume that
618 // it will work. Instead, we wait until confirmation from the call service that the
619 // call is in a non-RINGING state before changing the UI. See
620 // {@link CallServiceAdapter#setActive} and other set* methods.
Sailesh Nepale59bb192014-04-01 18:33:59 -0700621 mCallService.answer(this);
Santos Cordon61d0f702014-02-19 02:52:23 -0800622 }
623 }
624
625 /**
626 * Rejects the call if it is ringing.
627 */
628 void reject() {
629 Preconditions.checkNotNull(mCallService);
630
631 // Check to verify that the call is still in the ringing state. A call can change states
632 // between the time the user hits 'reject' and Telecomm receives the command.
633 if (isRinging("reject")) {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700634 mCallService.reject(this);
Santos Cordon61d0f702014-02-19 02:52:23 -0800635 }
636 }
637
638 /**
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700639 * Puts the call on hold if it is currently active.
640 */
641 void hold() {
642 Preconditions.checkNotNull(mCallService);
643
644 if (mState == CallState.ACTIVE) {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700645 mCallService.hold(this);
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700646 }
647 }
648
649 /**
650 * Releases the call from hold if it is currently active.
651 */
652 void unhold() {
653 Preconditions.checkNotNull(mCallService);
654
655 if (mState == CallState.ON_HOLD) {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700656 mCallService.unhold(this);
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700657 }
658 }
659
660 /**
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800661 * @return An object containing read-only information about this call.
662 */
Sailesh Nepale59bb192014-04-01 18:33:59 -0700663 CallInfo toCallInfo(String callId) {
Sailesh Nepal84fa5f82014-04-02 11:01:11 -0700664 CallServiceDescriptor descriptor = null;
665 if (mCallService != null) {
666 descriptor = mCallService.getDescriptor();
667 } else if (mOriginalCall != null && mOriginalCall.mCallService != null) {
668 descriptor = mOriginalCall.mCallService.getDescriptor();
669 }
670 return new CallInfo(callId, mState, mHandle, mGatewayInfo, mExtras, descriptor);
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800671 }
672
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700673 /** Checks if this is a live call or not. */
674 boolean isAlive() {
675 switch (mState) {
676 case NEW:
677 case RINGING:
678 case DISCONNECTED:
679 case ABORTED:
680 return false;
681 default:
682 return true;
683 }
684 }
685
Santos Cordon40f78c22014-04-07 02:11:42 -0700686 boolean isActive() {
687 switch (mState) {
688 case ACTIVE:
689 case POST_DIAL:
690 case POST_DIAL_WAIT:
691 return true;
692 default:
693 return false;
694 }
695 }
696
Sailesh Nepal84fa5f82014-04-02 11:01:11 -0700697 Bundle getExtras() {
698 return mExtras;
699 }
700
701 void setExtras(Bundle extras) {
702 mExtras = extras;
703 }
704
705 Uri getHandoffHandle() {
706 return mHandoffHandle;
707 }
708
709 void setHandoffHandle(Uri handoffHandle) {
710 mHandoffHandle = handoffHandle;
711 }
712
713 Call getOriginalCall() {
714 return mOriginalCall;
715 }
716
717 void setOriginalCall(Call originalCall) {
718 mOriginalCall = originalCall;
719 }
720
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700721 CallServiceDescriptor getHandoffCallServiceDescriptor() {
722 return mHandoffCallServiceDescriptor;
723 }
724
725 void setHandoffCallServiceDescriptor(CallServiceDescriptor descriptor) {
726 mHandoffCallServiceDescriptor = descriptor;
727 }
728
Santos Cordon5ba7f272014-05-28 13:59:49 -0700729 Uri getRingtone() {
730 return mCallerInfo == null ? null : mCallerInfo.contactRingtoneUri;
731 }
732
Evan Charlton352105c2014-06-03 14:10:54 -0700733 void onPostDialWait(String remaining) {
734 for (Listener l : mListeners) {
735 l.onPostDialWait(this, remaining);
736 }
737 }
738
739 void postDialContinue(boolean proceed) {
740 getCallService().onPostDialContinue(this, proceed);
741 }
742
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800743 /**
Santos Cordon61d0f702014-02-19 02:52:23 -0800744 * @return True if the call is ringing, else logs the action name.
745 */
746 private boolean isRinging(String actionName) {
747 if (mState == CallState.RINGING) {
748 return true;
749 }
750
Sailesh Nepalf1c191d2014-03-07 18:17:39 -0800751 Log.i(this, "Request to %s a non-ringing call %s", actionName, this);
Santos Cordon61d0f702014-02-19 02:52:23 -0800752 return false;
753 }
754
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800755 @SuppressWarnings("rawtypes")
756 private void decrementAssociatedCallCount(ServiceBinder binder) {
757 if (binder != null) {
758 binder.decrementAssociatedCallCount();
759 }
760 }
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700761
762 /**
763 * Looks up contact information based on the current handle.
764 */
765 private void startCallerInfoLookup() {
766 String number = mHandle == null ? null : mHandle.getSchemeSpecificPart();
767
768 mQueryToken++; // Updated so that previous queries can no longer set the information.
769 mCallerInfo = null;
770 if (!TextUtils.isEmpty(number)) {
Santos Cordon5ba7f272014-05-28 13:59:49 -0700771 Log.v(this, "Looking up information for: %s.", Log.piiHandle(number));
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700772 CallerInfoAsyncQuery.startQuery(
773 mQueryToken,
774 TelecommApp.getInstance(),
775 number,
776 sCallerInfoQueryListener,
777 this);
778 }
779 }
780
781 /**
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700782 * Saves the specified caller info if the specified token matches that of the last query
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700783 * that was made.
784 *
785 * @param callerInfo The new caller information to set.
786 * @param token The token used with this query.
787 */
788 private void setCallerInfo(CallerInfo callerInfo, int token) {
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700789 Preconditions.checkNotNull(callerInfo);
790
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700791 if (mQueryToken == token) {
792 mCallerInfo = callerInfo;
Santos Cordon5ba7f272014-05-28 13:59:49 -0700793 Log.i(this, "CallerInfo received for %s: %s", Log.piiHandle(mHandle), callerInfo);
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700794
795 if (mCallerInfo.person_id != 0) {
796 Uri personUri =
797 ContentUris.withAppendedId(Contacts.CONTENT_URI, mCallerInfo.person_id);
798 Log.d(this, "Searching person uri %s for call %s", personUri, this);
799 ContactsAsyncHelper.startObtainPhotoAsync(
800 token,
801 TelecommApp.getInstance(),
802 personUri,
803 sPhotoLoadListener,
804 this);
805 }
Santos Cordon2174fb52014-05-29 08:22:56 -0700806
807 processDirectToVoicemail();
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700808 }
809 }
810
811 /**
812 * Saves the specified photo information if the specified token matches that of the last query.
813 *
814 * @param photo The photo as a drawable.
815 * @param photoIcon The photo as a small icon.
816 * @param token The token used with this query.
817 */
818 private void setPhoto(Drawable photo, Bitmap photoIcon, int token) {
819 if (mQueryToken == token) {
820 mCallerInfo.cachedPhoto = photo;
821 mCallerInfo.cachedPhotoIcon = photoIcon;
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700822 }
823 }
Ben Gilad9f2bed32013-12-12 17:43:26 -0800824}