blob: 104ff4ec81dda8cf380fd36cd9cf9bfabccf3575 [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;
Ihab Awadff7493a2014-06-10 13:47:44 -070030import android.telecomm.Response;
Santos Cordon766d04f2014-05-06 10:28:25 -070031import android.telecomm.TelecommConstants;
Santos Cordon79ff2bc2014-03-27 15:31:27 -070032import android.telephony.DisconnectCause;
Sailesh Nepal6aca10a2014-03-24 16:11:02 -070033import android.telephony.PhoneNumberUtils;
Santos Cordonfd71f4a2014-05-28 13:59:49 -070034import android.text.TextUtils;
Santos Cordon0b03b4b2014-01-29 18:01:59 -080035
Santos Cordonfd71f4a2014-05-28 13:59:49 -070036import com.android.internal.telephony.CallerInfo;
37import com.android.internal.telephony.CallerInfoAsyncQuery;
38import com.android.internal.telephony.CallerInfoAsyncQuery.OnQueryCompleteListener;
Santos Cordon99c8a6f2014-05-28 18:28:47 -070039
Ihab Awadff7493a2014-06-10 13:47:44 -070040import com.android.internal.telephony.SmsApplication;
Santos Cordon99c8a6f2014-05-28 18:28:47 -070041import com.android.telecomm.ContactsAsyncHelper.OnImageLoadCompleteListener;
Santos Cordon61d0f702014-02-19 02:52:23 -080042import com.google.common.base.Preconditions;
Ihab Awadff7493a2014-06-10 13:47:44 -070043import com.google.common.collect.Sets;
Santos Cordon61d0f702014-02-19 02:52:23 -080044
Ihab Awadff7493a2014-06-10 13:47:44 -070045import java.util.Collections;
Santos Cordona1610702014-06-04 20:22:56 -070046import java.util.LinkedList;
47import java.util.List;
Sailesh Nepal91990782014-03-08 17:45:52 -080048import java.util.Locale;
Ben Gilad61925612014-03-11 19:06:36 -070049import java.util.Set;
Ben Gilad0407fb22014-01-09 16:18:41 -080050
Ben Gilad2495d572014-01-09 17:26:19 -080051/**
52 * Encapsulates all aspects of a given phone call throughout its lifecycle, starting
53 * from the time the call intent was received by Telecomm (vs. the time the call was
54 * connected etc).
55 */
Ben Gilad0407fb22014-01-09 16:18:41 -080056final class Call {
Santos Cordon766d04f2014-05-06 10:28:25 -070057
58 /**
59 * Listener for events on the call.
60 */
61 interface Listener {
62 void onSuccessfulOutgoingCall(Call call);
Ihab Awad0fea3f22014-06-03 18:45:05 -070063 void onFailedOutgoingCall(Call call, boolean isAborted, int errorCode, String errorMsg);
Santos Cordon766d04f2014-05-06 10:28:25 -070064 void onSuccessfulIncomingCall(Call call, CallInfo callInfo);
65 void onFailedIncomingCall(Call call);
Ihab Awadcb387ac2014-05-28 16:49:38 -070066 void onRequestingRingback(Call call, boolean requestingRingback);
Evan Charlton352105c2014-06-03 14:10:54 -070067 void onPostDialWait(Call call, String remaining);
Santos Cordona1610702014-06-04 20:22:56 -070068 void onIsConferenceCapableChanged(Call call, boolean isConferenceCapable);
69 void onExpiredConferenceCall(Call call);
70 void onConfirmedConferenceCall(Call call);
71 void onParentChanged(Call call);
72 void onChildrenChanged(Call call);
Ihab Awadff7493a2014-06-10 13:47:44 -070073 void onCannedSmsResponsesLoaded(Call call);
Santos Cordon766d04f2014-05-06 10:28:25 -070074 }
75
Santos Cordonfd71f4a2014-05-28 13:59:49 -070076 private static final OnQueryCompleteListener sCallerInfoQueryListener =
Santos Cordon99c8a6f2014-05-28 18:28:47 -070077 new OnQueryCompleteListener() {
78 /** ${inheritDoc} */
79 @Override
80 public void onQueryComplete(int token, Object cookie, CallerInfo callerInfo) {
81 if (cookie != null) {
82 ((Call) cookie).setCallerInfo(callerInfo, token);
83 }
Santos Cordonfd71f4a2014-05-28 13:59:49 -070084 }
Santos Cordon99c8a6f2014-05-28 18:28:47 -070085 };
86
87 private static final OnImageLoadCompleteListener sPhotoLoadListener =
88 new OnImageLoadCompleteListener() {
89 /** ${inheritDoc} */
90 @Override
91 public void onImageLoadComplete(
92 int token, Drawable photo, Bitmap photoIcon, Object cookie) {
93 if (cookie != null) {
94 ((Call) cookie).setPhoto(photo, photoIcon, token);
95 }
96 }
97 };
Ben Gilad0407fb22014-01-09 16:18:41 -080098
Sailesh Nepal810735e2014-03-18 18:15:46 -070099 /** True if this is an incoming call. */
100 private final boolean mIsIncoming;
101
Ben Gilad0407fb22014-01-09 16:18:41 -0800102 /**
103 * The time this call was created, typically also the time this call was added to the set
104 * of pending outgoing calls (mPendingOutgoingCalls) that's maintained by the switchboard.
105 * Beyond logging and such, may also be used for bookkeeping and specifically for marking
106 * certain call attempts as failed attempts.
107 */
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700108 private final long mCreationTimeMillis = System.currentTimeMillis();
109
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700110 /** The gateway information associated with this call. This stores the original call handle
111 * that the user is attempting to connect to via the gateway, the actual handle to dial in
112 * order to connect the call via the gateway, as well as the package name of the gateway
113 * service. */
114 private final GatewayInfo mGatewayInfo;
115
Santos Cordon2174fb52014-05-29 08:22:56 -0700116 private final Handler mHandler = new Handler();
117
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700118 private long mConnectTimeMillis;
Ben Gilad0407fb22014-01-09 16:18:41 -0800119
Santos Cordon61d0f702014-02-19 02:52:23 -0800120 /** The state of the call. */
121 private CallState mState;
122
123 /** The handle with which to establish this call. */
Sailesh Nepalce704b92014-03-17 18:31:43 -0700124 private Uri mHandle;
Santos Cordon61d0f702014-02-19 02:52:23 -0800125
Ben Gilad0407fb22014-01-09 16:18:41 -0800126 /**
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800127 * The call service which is attempted or already connecting this call.
Santos Cordon681663d2014-01-30 04:32:15 -0800128 */
Santos Cordonc195e362014-02-11 17:05:31 -0800129 private CallServiceWrapper mCallService;
Santos Cordon681663d2014-01-30 04:32:15 -0800130
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800131 /**
132 * The call-service selector for this call.
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800133 */
Sailesh Nepal18386a82014-03-19 10:22:40 -0700134 private CallServiceSelectorWrapper mCallServiceSelector;
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800135
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800136 /**
Ben Gilad61925612014-03-11 19:06:36 -0700137 * The set of call services that were attempted in the process of placing/switching this call
138 * but turned out unsuitable. Only used in the context of call switching.
139 */
140 private Set<CallServiceWrapper> mIncompatibleCallServices;
141
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700142 private boolean mIsEmergencyCall;
143
Ben Gilad61925612014-03-11 19:06:36 -0700144 /**
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700145 * Disconnect cause for the call. Only valid if the state of the call is DISCONNECTED.
146 * See {@link android.telephony.DisconnectCause}.
147 */
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700148 private int mDisconnectCause = DisconnectCause.NOT_VALID;
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700149
150 /**
151 * Additional disconnect information provided by the call service.
152 */
153 private String mDisconnectMessage;
154
Sailesh Nepal84fa5f82014-04-02 11:01:11 -0700155 /** Info used by the call services. */
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700156 private Bundle mExtras = Bundle.EMPTY;
Sailesh Nepal84fa5f82014-04-02 11:01:11 -0700157
158 /** The Uri to dial to perform the handoff. If this is null then handoff is not supported. */
159 private Uri mHandoffHandle;
160
161 /**
162 * References the call that is being handed off. This value is non-null for untracked calls
163 * that are being used to perform a handoff.
164 */
165 private Call mOriginalCall;
166
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700167 /**
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700168 * The descriptor for the call service that this call is being switched to, null if handoff is
169 * not in progress.
170 */
171 private CallServiceDescriptor mHandoffCallServiceDescriptor;
172
Santos Cordon766d04f2014-05-06 10:28:25 -0700173 /** Set of listeners on this call. */
174 private Set<Listener> mListeners = Sets.newHashSet();
175
Santos Cordon682fe6b2014-05-20 08:56:39 -0700176 private OutgoingCallProcessor mOutgoingCallProcessor;
177
178 // TODO(santoscordon): The repositories should be changed into singleton types.
179 private CallServiceRepository mCallServiceRepository;
180 private CallServiceSelectorRepository mSelectorRepository;
181
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700182 /** Caller information retrieved from the latest contact query. */
183 private CallerInfo mCallerInfo;
184
185 /** The latest token used with a contact info query. */
186 private int mQueryToken = 0;
187
Ihab Awadcb387ac2014-05-28 16:49:38 -0700188 /** Whether this call is requesting that Telecomm play the ringback tone on its behalf. */
189 private boolean mRequestingRingback = false;
190
Santos Cordon2174fb52014-05-29 08:22:56 -0700191 /** Incoming call-info to use when direct-to-voicemail query finishes. */
192 private CallInfo mPendingDirectToVoicemailCallInfo;
193
Santos Cordona1610702014-06-04 20:22:56 -0700194 private boolean mIsConferenceCapable = false;
195
196 private boolean mIsConference = false;
197
198 private Call mParentCall = null;
199
200 private List<Call> mChildCalls = new LinkedList<>();
201
Ihab Awadff7493a2014-06-10 13:47:44 -0700202 /** Set of text message responses allowed for this call, if applicable. */
203 private List<String> mCannedSmsResponses = Collections.EMPTY_LIST;
204
205 /** Whether an attempt has been made to load the text message responses. */
206 private boolean mCannedSmsResponsesLoadingStarted = false;
207
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700208 /**
Sailesh Nepale59bb192014-04-01 18:33:59 -0700209 * Creates an empty call object.
Sailesh Nepal810735e2014-03-18 18:15:46 -0700210 *
211 * @param isIncoming True if this is an incoming call.
Santos Cordon493e8f22014-02-19 03:15:12 -0800212 */
Santos Cordona1610702014-06-04 20:22:56 -0700213 Call(boolean isIncoming, boolean isConference) {
214 this(null, null, isIncoming, isConference);
Santos Cordon493e8f22014-02-19 03:15:12 -0800215 }
216
217 /**
Ben Gilad0407fb22014-01-09 16:18:41 -0800218 * Persists the specified parameters and initializes the new instance.
219 *
220 * @param handle The handle to dial.
Yorke Lee33501632014-03-17 19:24:12 -0700221 * @param gatewayInfo Gateway information to use for the call.
Sailesh Nepal810735e2014-03-18 18:15:46 -0700222 * @param isIncoming True if this is an incoming call.
Ben Gilad0407fb22014-01-09 16:18:41 -0800223 */
Santos Cordona1610702014-06-04 20:22:56 -0700224 Call(Uri handle, GatewayInfo gatewayInfo, boolean isIncoming, boolean isConference) {
225 mState = isConference ? CallState.ACTIVE : CallState.NEW;
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700226 setHandle(handle);
Yorke Lee33501632014-03-17 19:24:12 -0700227 mGatewayInfo = gatewayInfo;
Sailesh Nepal810735e2014-03-18 18:15:46 -0700228 mIsIncoming = isIncoming;
Santos Cordona1610702014-06-04 20:22:56 -0700229 mIsConference = isConference;
Ihab Awadff7493a2014-06-10 13:47:44 -0700230 maybeLoadCannedSmsResponses();
Ben Gilad0407fb22014-01-09 16:18:41 -0800231 }
232
Santos Cordon766d04f2014-05-06 10:28:25 -0700233 void addListener(Listener listener) {
234 mListeners.add(listener);
235 }
236
237 void removeListener(Listener listener) {
238 mListeners.remove(listener);
239 }
240
Santos Cordon61d0f702014-02-19 02:52:23 -0800241 /** {@inheritDoc} */
242 @Override public String toString() {
Sailesh Nepal4538f012014-04-15 11:40:33 -0700243 String component = null;
244 if (mCallService != null && mCallService.getComponentName() != null) {
245 component = mCallService.getComponentName().flattenToShortString();
246 }
247 return String.format(Locale.US, "[%s, %s, %s]", mState, component, Log.piiHandle(mHandle));
Santos Cordon61d0f702014-02-19 02:52:23 -0800248 }
249
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800250 CallState getState() {
Santos Cordona1610702014-06-04 20:22:56 -0700251 if (mIsConference) {
252 if (!mChildCalls.isEmpty()) {
253 // If we have child calls, just return the child call.
254 return mChildCalls.get(0).getState();
255 }
256 return CallState.ACTIVE;
257 } else {
258 return mState;
259 }
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800260 }
261
262 /**
263 * Sets the call state. Although there exists the notion of appropriate state transitions
264 * (see {@link CallState}), in practice those expectations break down when cellular systems
265 * misbehave and they do this very often. The result is that we do not enforce state transitions
266 * and instead keep the code resilient to unexpected state changes.
267 */
Sailesh Nepal810735e2014-03-18 18:15:46 -0700268 void setState(CallState newState) {
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700269 Preconditions.checkState(newState != CallState.DISCONNECTED ||
270 mDisconnectCause != DisconnectCause.NOT_VALID);
Sailesh Nepal810735e2014-03-18 18:15:46 -0700271 if (mState != newState) {
272 Log.v(this, "setState %s -> %s", mState, newState);
273 mState = newState;
Ihab Awadff7493a2014-06-10 13:47:44 -0700274 maybeLoadCannedSmsResponses();
Sailesh Nepal810735e2014-03-18 18:15:46 -0700275 }
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800276 }
277
Ihab Awadcb387ac2014-05-28 16:49:38 -0700278 void setRequestingRingback(boolean requestingRingback) {
279 mRequestingRingback = requestingRingback;
280 for (Listener l : mListeners) {
281 l.onRequestingRingback(this, mRequestingRingback);
282 }
283 }
284
285 boolean isRequestingRingback() {
286 return mRequestingRingback;
287 }
288
Sailesh Nepalce704b92014-03-17 18:31:43 -0700289 Uri getHandle() {
Ben Gilad0bf5b912014-01-28 17:55:57 -0800290 return mHandle;
291 }
292
Sailesh Nepalce704b92014-03-17 18:31:43 -0700293 void setHandle(Uri handle) {
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700294 if ((mHandle == null && handle != null) || (mHandle != null && !mHandle.equals(handle))) {
295 mHandle = handle;
296 mIsEmergencyCall = mHandle != null && PhoneNumberUtils.isLocalEmergencyNumber(
Yorke Lee66255452014-06-05 08:09:24 -0700297 TelecommApp.getInstance(), mHandle.getSchemeSpecificPart());
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700298 startCallerInfoLookup();
299 }
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700300 }
301
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700302 String getName() {
303 return mCallerInfo == null ? null : mCallerInfo.name;
304 }
305
306 Bitmap getPhotoIcon() {
307 return mCallerInfo == null ? null : mCallerInfo.cachedPhotoIcon;
308 }
309
310 Drawable getPhoto() {
311 return mCallerInfo == null ? null : mCallerInfo.cachedPhoto;
312 }
313
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700314 /**
315 * @param disconnectCause The reason for the disconnection, any of
316 * {@link android.telephony.DisconnectCause}.
317 * @param disconnectMessage Optional call-service-provided message about the disconnect.
318 */
319 void setDisconnectCause(int disconnectCause, String disconnectMessage) {
320 // TODO: Consider combining this method with a setDisconnected() method that is totally
321 // separate from setState.
322 mDisconnectCause = disconnectCause;
323 mDisconnectMessage = disconnectMessage;
324 }
325
326 int getDisconnectCause() {
327 return mDisconnectCause;
328 }
329
330 String getDisconnectMessage() {
331 return mDisconnectMessage;
332 }
333
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700334 boolean isEmergencyCall() {
335 return mIsEmergencyCall;
Santos Cordon61d0f702014-02-19 02:52:23 -0800336 }
337
Yorke Lee33501632014-03-17 19:24:12 -0700338 /**
339 * @return The original handle this call is associated with. In-call services should use this
340 * handle when indicating in their UI the handle that is being called.
341 */
342 public Uri getOriginalHandle() {
343 if (mGatewayInfo != null && !mGatewayInfo.isEmpty()) {
344 return mGatewayInfo.getOriginalHandle();
345 }
346 return getHandle();
347 }
348
349 GatewayInfo getGatewayInfo() {
350 return mGatewayInfo;
351 }
352
Sailesh Nepal810735e2014-03-18 18:15:46 -0700353 boolean isIncoming() {
354 return mIsIncoming;
355 }
356
Ben Gilad0407fb22014-01-09 16:18:41 -0800357 /**
358 * @return The "age" of this call object in milliseconds, which typically also represents the
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700359 * period since this call was added to the set pending outgoing calls, see
360 * mCreationTimeMillis.
Ben Gilad0407fb22014-01-09 16:18:41 -0800361 */
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700362 long getAgeMillis() {
363 return System.currentTimeMillis() - mCreationTimeMillis;
Ben Gilad0407fb22014-01-09 16:18:41 -0800364 }
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800365
Yorke Leef98fb572014-03-05 10:56:55 -0800366 /**
367 * @return The time when this call object was created and added to the set of pending outgoing
368 * calls.
369 */
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700370 long getCreationTimeMillis() {
371 return mCreationTimeMillis;
372 }
373
374 long getConnectTimeMillis() {
375 return mConnectTimeMillis;
376 }
377
378 void setConnectTimeMillis(long connectTimeMillis) {
379 mConnectTimeMillis = connectTimeMillis;
Yorke Leef98fb572014-03-05 10:56:55 -0800380 }
381
Santos Cordona1610702014-06-04 20:22:56 -0700382 boolean isConferenceCapable() {
383 return mIsConferenceCapable;
384 }
385
386 void setIsConferenceCapable(boolean isConferenceCapable) {
387 if (mIsConferenceCapable != isConferenceCapable) {
388 mIsConferenceCapable = isConferenceCapable;
389 for (Listener l : mListeners) {
390 l.onIsConferenceCapableChanged(this, mIsConferenceCapable);
391 }
392 }
393 }
394
395 Call getParentCall() {
396 return mParentCall;
397 }
398
399 List<Call> getChildCalls() {
400 return mChildCalls;
401 }
402
Santos Cordonc195e362014-02-11 17:05:31 -0800403 CallServiceWrapper getCallService() {
Santos Cordon681663d2014-01-30 04:32:15 -0800404 return mCallService;
405 }
406
Santos Cordonc195e362014-02-11 17:05:31 -0800407 void setCallService(CallServiceWrapper callService) {
Sailesh Nepal0e5410a2014-04-04 01:20:58 -0700408 setCallService(callService, null);
409 }
410
411 /**
412 * Changes the call service this call is associated with. If callToReplace is non-null then this
413 * call takes its place within the call service.
414 */
415 void setCallService(CallServiceWrapper callService, Call callToReplace) {
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800416 Preconditions.checkNotNull(callService);
417
Yorke Leeadee12d2014-03-13 12:08:30 -0700418 clearCallService();
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800419
420 callService.incrementAssociatedCallCount();
Santos Cordon681663d2014-01-30 04:32:15 -0800421 mCallService = callService;
Sailesh Nepal0e5410a2014-04-04 01:20:58 -0700422 if (callToReplace == null) {
423 mCallService.addCall(this);
424 } else {
425 mCallService.replaceCall(this, callToReplace);
426 }
Santos Cordon681663d2014-01-30 04:32:15 -0800427 }
428
429 /**
430 * Clears the associated call service.
431 */
432 void clearCallService() {
Yorke Leeadee12d2014-03-13 12:08:30 -0700433 if (mCallService != null) {
Santos Cordonc499c1c2014-04-14 17:13:14 -0700434 CallServiceWrapper callServiceTemp = mCallService;
Yorke Leeadee12d2014-03-13 12:08:30 -0700435 mCallService = null;
Santos Cordonc499c1c2014-04-14 17:13:14 -0700436 callServiceTemp.removeCall(this);
437
438 // Decrementing the count can cause the service to unbind, which itself can trigger the
439 // service-death code. Since the service death code tries to clean up any associated
440 // calls, we need to make sure to remove that information (e.g., removeCall()) before
441 // we decrement. Technically, invoking removeCall() prior to decrementing is all that is
442 // necessary, but cleaning up mCallService prior to triggering an unbind is good to do.
443 // If you change this, make sure to update {@link clearCallServiceSelector} as well.
444 decrementAssociatedCallCount(callServiceTemp);
Yorke Leeadee12d2014-03-13 12:08:30 -0700445 }
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800446 }
447
Sailesh Nepal84fa5f82014-04-02 11:01:11 -0700448 CallServiceSelectorWrapper getCallServiceSelector() {
449 return mCallServiceSelector;
450 }
451
Sailesh Nepal18386a82014-03-19 10:22:40 -0700452 void setCallServiceSelector(CallServiceSelectorWrapper selector) {
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800453 Preconditions.checkNotNull(selector);
Sailesh Nepale59bb192014-04-01 18:33:59 -0700454
455 clearCallServiceSelector();
456
Santos Cordonc499c1c2014-04-14 17:13:14 -0700457 selector.incrementAssociatedCallCount();
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800458 mCallServiceSelector = selector;
Sailesh Nepale59bb192014-04-01 18:33:59 -0700459 mCallServiceSelector.addCall(this);
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800460 }
461
462 void clearCallServiceSelector() {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700463 if (mCallServiceSelector != null) {
Santos Cordonc499c1c2014-04-14 17:13:14 -0700464 CallServiceSelectorWrapper selectorTemp = mCallServiceSelector;
Sailesh Nepale59bb192014-04-01 18:33:59 -0700465 mCallServiceSelector = null;
Santos Cordonc499c1c2014-04-14 17:13:14 -0700466 selectorTemp.removeCall(this);
467
468 // See comment on {@link #clearCallService}.
469 decrementAssociatedCallCount(selectorTemp);
Sailesh Nepale59bb192014-04-01 18:33:59 -0700470 }
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800471 }
472
473 /**
Santos Cordon766d04f2014-05-06 10:28:25 -0700474 * Starts the incoming call flow through the switchboard. When switchboard completes, it will
475 * invoke handle[Un]SuccessfulIncomingCall.
476 *
477 * @param descriptor The relevant call-service descriptor.
478 * @param extras The optional extras passed via
479 * {@link TelecommConstants#EXTRA_INCOMING_CALL_EXTRAS}.
480 */
481 void startIncoming(CallServiceDescriptor descriptor, Bundle extras) {
482 Switchboard.getInstance().retrieveIncomingCall(this, descriptor, extras);
483 }
484
Santos Cordon2174fb52014-05-29 08:22:56 -0700485 /**
486 * Takes a verified incoming call and uses the handle to lookup direct-to-voicemail property
487 * from the contacts provider. The call is not yet exposed to the user at this point and
488 * the result of the query will determine if the call is rejected or passed through to the
489 * in-call UI.
490 */
491 void handleVerifiedIncoming(CallInfo callInfo) {
Santos Cordon766d04f2014-05-06 10:28:25 -0700492 Preconditions.checkState(callInfo.getState() == CallState.RINGING);
Santos Cordon2174fb52014-05-29 08:22:56 -0700493
494 // We do not handle incoming calls immediately when they are verified by the call service.
495 // We allow the caller-info-query code to execute first so that we can read the
496 // direct-to-voicemail property before deciding if we want to show the incoming call to the
497 // user or if we want to reject the call.
498 mPendingDirectToVoicemailCallInfo = callInfo;
499
500 // Setting the handle triggers the caller info lookup code.
Santos Cordon766d04f2014-05-06 10:28:25 -0700501 setHandle(callInfo.getHandle());
502
Santos Cordon2174fb52014-05-29 08:22:56 -0700503 // Timeout the direct-to-voicemail lookup execution so that we dont wait too long before
504 // showing the user the incoming call screen.
505 mHandler.postDelayed(new Runnable() {
506 @Override
507 public void run() {
508 processDirectToVoicemail();
509 }
Santos Cordona1610702014-06-04 20:22:56 -0700510 }, Timeouts.getDirectToVoicemailMillis());
Santos Cordon2174fb52014-05-29 08:22:56 -0700511 }
Santos Cordon766d04f2014-05-06 10:28:25 -0700512
Santos Cordon2174fb52014-05-29 08:22:56 -0700513 void processDirectToVoicemail() {
514 if (mPendingDirectToVoicemailCallInfo != null) {
515 if (mCallerInfo != null && mCallerInfo.shouldSendToVoicemail) {
516 Log.i(this, "Directing call to voicemail: %s.", this);
517 // TODO(santoscordon): Once we move State handling from CallsManager to Call, we
518 // will not need to set RINGING state prior to calling reject.
519 setState(CallState.RINGING);
Ihab Awadff7493a2014-06-10 13:47:44 -0700520 reject(false, null);
Santos Cordon2174fb52014-05-29 08:22:56 -0700521 } else {
522 // TODO(santoscordon): Make this class (not CallsManager) responsible for changing
523 // the call state to RINGING.
524
525 // TODO(santoscordon): Replace this with state transition to RINGING.
526 for (Listener l : mListeners) {
527 l.onSuccessfulIncomingCall(this, mPendingDirectToVoicemailCallInfo);
528 }
529 }
530
531 mPendingDirectToVoicemailCallInfo = null;
Santos Cordon766d04f2014-05-06 10:28:25 -0700532 }
533 }
534
535 void handleFailedIncoming() {
536 clearCallService();
537
538 // TODO: Needs more specific disconnect error for this case.
539 setDisconnectCause(DisconnectCause.ERROR_UNSPECIFIED, null);
540 setState(CallState.DISCONNECTED);
541
542 // TODO(santoscordon): Replace this with state transitions related to "connecting".
543 for (Listener l : mListeners) {
544 l.onFailedIncomingCall(this);
545 }
546 }
547
548 /**
Santos Cordon682fe6b2014-05-20 08:56:39 -0700549 * Starts the outgoing call sequence. Upon completion, there should exist an active connection
550 * through a call service (or the call will have failed).
Santos Cordon766d04f2014-05-06 10:28:25 -0700551 */
552 void startOutgoing() {
Santos Cordon682fe6b2014-05-20 08:56:39 -0700553 Preconditions.checkState(mOutgoingCallProcessor == null);
554
555 mOutgoingCallProcessor = new OutgoingCallProcessor(
556 this,
557 Switchboard.getInstance().getCallServiceRepository(),
558 Switchboard.getInstance().getSelectorRepository(),
559 new AsyncResultCallback<Boolean>() {
560 @Override
Ihab Awad0fea3f22014-06-03 18:45:05 -0700561 public void onResult(Boolean wasCallPlaced, int errorCode, String errorMsg) {
Santos Cordon682fe6b2014-05-20 08:56:39 -0700562 if (wasCallPlaced) {
563 handleSuccessfulOutgoing();
564 } else {
Ihab Awad0fea3f22014-06-03 18:45:05 -0700565 handleFailedOutgoing(
566 mOutgoingCallProcessor.isAborted(), errorCode, errorMsg);
Santos Cordon682fe6b2014-05-20 08:56:39 -0700567 }
568 mOutgoingCallProcessor = null;
569 }
570 });
571 mOutgoingCallProcessor.process();
Santos Cordon766d04f2014-05-06 10:28:25 -0700572 }
573
574 void handleSuccessfulOutgoing() {
575 // TODO(santoscordon): Replace this with state transitions related to "connecting".
576 for (Listener l : mListeners) {
577 l.onSuccessfulOutgoingCall(this);
578 }
579 }
580
Ihab Awad0fea3f22014-06-03 18:45:05 -0700581 void handleFailedOutgoing(boolean isAborted, int errorCode, String errorMsg) {
Santos Cordon766d04f2014-05-06 10:28:25 -0700582 // TODO(santoscordon): Replace this with state transitions related to "connecting".
583 for (Listener l : mListeners) {
Ihab Awad0fea3f22014-06-03 18:45:05 -0700584 l.onFailedOutgoingCall(this, isAborted, errorCode, errorMsg);
Santos Cordon766d04f2014-05-06 10:28:25 -0700585 }
Santos Cordon682fe6b2014-05-20 08:56:39 -0700586
587 clearCallService();
588 clearCallServiceSelector();
Santos Cordon766d04f2014-05-06 10:28:25 -0700589 }
590
591 /**
Ben Gilad61925612014-03-11 19:06:36 -0700592 * Adds the specified call service to the list of incompatible services. The set is used when
593 * attempting to switch a phone call between call services such that incompatible services can
594 * be avoided.
595 *
596 * @param callService The incompatible call service.
597 */
598 void addIncompatibleCallService(CallServiceWrapper callService) {
599 if (mIncompatibleCallServices == null) {
600 mIncompatibleCallServices = Sets.newHashSet();
601 }
602 mIncompatibleCallServices.add(callService);
603 }
604
605 /**
606 * Checks whether or not the specified callService was identified as incompatible in the
607 * context of this call.
608 *
609 * @param callService The call service to evaluate.
610 * @return True upon incompatible call services and false otherwise.
611 */
612 boolean isIncompatibleCallService(CallServiceWrapper callService) {
613 return mIncompatibleCallServices != null &&
614 mIncompatibleCallServices.contains(callService);
615 }
616
617 /**
Ihab Awad74549ec2014-03-10 15:33:25 -0700618 * Plays the specified DTMF tone.
619 */
620 void playDtmfTone(char digit) {
621 if (mCallService == null) {
622 Log.w(this, "playDtmfTone() request on a call without a call service.");
623 } else {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700624 Log.i(this, "Send playDtmfTone to call service for call %s", this);
625 mCallService.playDtmfTone(this, digit);
Ihab Awad74549ec2014-03-10 15:33:25 -0700626 }
627 }
628
629 /**
630 * Stops playing any currently playing DTMF tone.
631 */
632 void stopDtmfTone() {
633 if (mCallService == null) {
634 Log.w(this, "stopDtmfTone() request on a call without a call service.");
635 } else {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700636 Log.i(this, "Send stopDtmfTone to call service for call %s", this);
637 mCallService.stopDtmfTone(this);
Ihab Awad74549ec2014-03-10 15:33:25 -0700638 }
639 }
640
641 /**
Santos Cordon049b7b62014-01-30 05:34:26 -0800642 * Attempts to disconnect the call through the call service.
643 */
644 void disconnect() {
Santos Cordon766d04f2014-05-06 10:28:25 -0700645 if (mState == CallState.NEW) {
Santos Cordon682fe6b2014-05-20 08:56:39 -0700646 Log.v(this, "Aborting call %s", this);
647 abort();
Santos Cordon74d420b2014-05-07 14:38:47 -0700648 } else if (mState != CallState.ABORTED && mState != CallState.DISCONNECTED) {
Santos Cordon766d04f2014-05-06 10:28:25 -0700649 Preconditions.checkNotNull(mCallService);
650
Sailesh Nepale59bb192014-04-01 18:33:59 -0700651 Log.i(this, "Send disconnect to call service for call: %s", this);
Santos Cordonc195e362014-02-11 17:05:31 -0800652 // The call isn't officially disconnected until the call service confirms that the call
653 // was actually disconnected. Only then is the association between call and call service
654 // severed, see {@link CallsManager#markCallAsDisconnected}.
Sailesh Nepale59bb192014-04-01 18:33:59 -0700655 mCallService.disconnect(this);
Santos Cordon049b7b62014-01-30 05:34:26 -0800656 }
657 }
658
Santos Cordon682fe6b2014-05-20 08:56:39 -0700659 void abort() {
660 if (mOutgoingCallProcessor != null) {
661 mOutgoingCallProcessor.abort();
662 }
663 }
664
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800665 /**
Santos Cordon61d0f702014-02-19 02:52:23 -0800666 * Answers the call if it is ringing.
667 */
668 void answer() {
669 Preconditions.checkNotNull(mCallService);
670
671 // Check to verify that the call is still in the ringing state. A call can change states
672 // between the time the user hits 'answer' and Telecomm receives the command.
673 if (isRinging("answer")) {
674 // At this point, we are asking the call service to answer but we don't assume that
675 // it will work. Instead, we wait until confirmation from the call service that the
676 // call is in a non-RINGING state before changing the UI. See
677 // {@link CallServiceAdapter#setActive} and other set* methods.
Sailesh Nepale59bb192014-04-01 18:33:59 -0700678 mCallService.answer(this);
Santos Cordon61d0f702014-02-19 02:52:23 -0800679 }
680 }
681
682 /**
683 * Rejects the call if it is ringing.
Ihab Awadff7493a2014-06-10 13:47:44 -0700684 *
685 * @param rejectWithMessage Whether to send a text message as part of the call rejection.
686 * @param textMessage An optional text message to send as part of the rejection.
Santos Cordon61d0f702014-02-19 02:52:23 -0800687 */
Ihab Awadff7493a2014-06-10 13:47:44 -0700688 void reject(boolean rejectWithMessage, String textMessage) {
Santos Cordon61d0f702014-02-19 02:52:23 -0800689 Preconditions.checkNotNull(mCallService);
690
691 // Check to verify that the call is still in the ringing state. A call can change states
692 // between the time the user hits 'reject' and Telecomm receives the command.
693 if (isRinging("reject")) {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700694 mCallService.reject(this);
Santos Cordon61d0f702014-02-19 02:52:23 -0800695 }
696 }
697
698 /**
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700699 * Puts the call on hold if it is currently active.
700 */
701 void hold() {
702 Preconditions.checkNotNull(mCallService);
703
704 if (mState == CallState.ACTIVE) {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700705 mCallService.hold(this);
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700706 }
707 }
708
709 /**
710 * Releases the call from hold if it is currently active.
711 */
712 void unhold() {
713 Preconditions.checkNotNull(mCallService);
714
715 if (mState == CallState.ON_HOLD) {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700716 mCallService.unhold(this);
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700717 }
718 }
719
720 /**
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800721 * @return An object containing read-only information about this call.
722 */
Sailesh Nepale59bb192014-04-01 18:33:59 -0700723 CallInfo toCallInfo(String callId) {
Sailesh Nepal84fa5f82014-04-02 11:01:11 -0700724 CallServiceDescriptor descriptor = null;
725 if (mCallService != null) {
726 descriptor = mCallService.getDescriptor();
727 } else if (mOriginalCall != null && mOriginalCall.mCallService != null) {
728 descriptor = mOriginalCall.mCallService.getDescriptor();
729 }
730 return new CallInfo(callId, mState, mHandle, mGatewayInfo, mExtras, descriptor);
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800731 }
732
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700733 /** Checks if this is a live call or not. */
734 boolean isAlive() {
735 switch (mState) {
736 case NEW:
737 case RINGING:
738 case DISCONNECTED:
739 case ABORTED:
740 return false;
741 default:
742 return true;
743 }
744 }
745
Santos Cordon40f78c22014-04-07 02:11:42 -0700746 boolean isActive() {
747 switch (mState) {
748 case ACTIVE:
749 case POST_DIAL:
750 case POST_DIAL_WAIT:
751 return true;
752 default:
753 return false;
754 }
755 }
756
Sailesh Nepal84fa5f82014-04-02 11:01:11 -0700757 Bundle getExtras() {
758 return mExtras;
759 }
760
761 void setExtras(Bundle extras) {
762 mExtras = extras;
763 }
764
765 Uri getHandoffHandle() {
766 return mHandoffHandle;
767 }
768
769 void setHandoffHandle(Uri handoffHandle) {
770 mHandoffHandle = handoffHandle;
771 }
772
773 Call getOriginalCall() {
774 return mOriginalCall;
775 }
776
777 void setOriginalCall(Call originalCall) {
778 mOriginalCall = originalCall;
779 }
780
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700781 CallServiceDescriptor getHandoffCallServiceDescriptor() {
782 return mHandoffCallServiceDescriptor;
783 }
784
785 void setHandoffCallServiceDescriptor(CallServiceDescriptor descriptor) {
786 mHandoffCallServiceDescriptor = descriptor;
787 }
788
Santos Cordon5ba7f272014-05-28 13:59:49 -0700789 Uri getRingtone() {
790 return mCallerInfo == null ? null : mCallerInfo.contactRingtoneUri;
791 }
792
Evan Charlton352105c2014-06-03 14:10:54 -0700793 void onPostDialWait(String remaining) {
794 for (Listener l : mListeners) {
795 l.onPostDialWait(this, remaining);
796 }
797 }
798
799 void postDialContinue(boolean proceed) {
800 getCallService().onPostDialContinue(this, proceed);
801 }
802
Santos Cordona1610702014-06-04 20:22:56 -0700803 void conferenceInto(Call conferenceCall) {
804 if (mCallService == null) {
805 Log.w(this, "conference requested on a call without a call service.");
806 } else {
807 mCallService.conference(conferenceCall, this);
808 }
809 }
810
811 void expireConference() {
812 // The conference call expired before we got a confirmation of the conference from the
813 // call service...so start shutting down.
814 clearCallService();
815 for (Listener l : mListeners) {
816 l.onExpiredConferenceCall(this);
817 }
818 }
819
820 void confirmConference() {
821 Log.v(this, "confirming Conf call %s", mListeners);
822 for (Listener l : mListeners) {
823 l.onConfirmedConferenceCall(this);
824 }
825 }
826
827 void splitFromConference() {
828 // TODO(santoscordon): todo
829 }
830
831 void setParentCall(Call parentCall) {
832 if (parentCall == this) {
833 Log.e(this, new Exception(), "setting the parent to self");
834 return;
835 }
836 Preconditions.checkState(parentCall == null || mParentCall == null);
837
838 Call oldParent = mParentCall;
839 if (mParentCall != null) {
840 mParentCall.removeChildCall(this);
841 }
842 mParentCall = parentCall;
843 if (mParentCall != null) {
844 mParentCall.addChildCall(this);
845 }
846
847 for (Listener l : mListeners) {
848 l.onParentChanged(this);
849 }
850 }
851
852 private void addChildCall(Call call) {
853 if (!mChildCalls.contains(call)) {
854 mChildCalls.add(call);
855
856 for (Listener l : mListeners) {
857 l.onChildrenChanged(this);
858 }
859 }
860 }
861
862 private void removeChildCall(Call call) {
863 if (mChildCalls.remove(call)) {
864 for (Listener l : mListeners) {
865 l.onChildrenChanged(this);
866 }
867 }
868 }
869
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800870 /**
Ihab Awadff7493a2014-06-10 13:47:44 -0700871 * Return whether the user can respond to this {@code Call} via an SMS message.
872 *
873 * @return true if the "Respond via SMS" feature should be enabled
874 * for this incoming call.
875 *
876 * The general rule is that we *do* allow "Respond via SMS" except for
877 * the few (relatively rare) cases where we know for sure it won't
878 * work, namely:
879 * - a bogus or blank incoming number
880 * - a call from a SIP address
881 * - a "call presentation" that doesn't allow the number to be revealed
882 *
883 * In all other cases, we allow the user to respond via SMS.
884 *
885 * Note that this behavior isn't perfect; for example we have no way
886 * to detect whether the incoming call is from a landline (with most
887 * networks at least), so we still enable this feature even though
888 * SMSes to that number will silently fail.
889 */
890 boolean isRespondViaSmsCapable() {
891 if (mState != CallState.RINGING) {
892 return false;
893 }
894
895 if (getHandle() == null) {
896 // No incoming number known or call presentation is "PRESENTATION_RESTRICTED", in
897 // other words, the user should not be able to see the incoming phone number.
898 return false;
899 }
900
901 if (PhoneNumberUtils.isUriNumber(getHandle().toString())) {
902 // The incoming number is actually a URI (i.e. a SIP address),
903 // not a regular PSTN phone number, and we can't send SMSes to
904 // SIP addresses.
905 // (TODO: That might still be possible eventually, though. Is
906 // there some SIP-specific equivalent to sending a text message?)
907 return false;
908 }
909
910 // Is there a valid SMS application on the phone?
911 if (SmsApplication.getDefaultRespondViaMessageApplication(TelecommApp.getInstance(),
912 true /*updateIfNeeded*/) == null) {
913 return false;
914 }
915
916 // TODO: with some carriers (in certain countries) you *can* actually
917 // tell whether a given number is a mobile phone or not. So in that
918 // case we could potentially return false here if the incoming call is
919 // from a land line.
920
921 // If none of the above special cases apply, it's OK to enable the
922 // "Respond via SMS" feature.
923 return true;
924 }
925
926 List<String> getCannedSmsResponses() {
927 return mCannedSmsResponses;
928 }
929
930 /**
Santos Cordon61d0f702014-02-19 02:52:23 -0800931 * @return True if the call is ringing, else logs the action name.
932 */
933 private boolean isRinging(String actionName) {
934 if (mState == CallState.RINGING) {
935 return true;
936 }
937
Sailesh Nepalf1c191d2014-03-07 18:17:39 -0800938 Log.i(this, "Request to %s a non-ringing call %s", actionName, this);
Santos Cordon61d0f702014-02-19 02:52:23 -0800939 return false;
940 }
941
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800942 @SuppressWarnings("rawtypes")
943 private void decrementAssociatedCallCount(ServiceBinder binder) {
944 if (binder != null) {
945 binder.decrementAssociatedCallCount();
946 }
947 }
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700948
949 /**
950 * Looks up contact information based on the current handle.
951 */
952 private void startCallerInfoLookup() {
953 String number = mHandle == null ? null : mHandle.getSchemeSpecificPart();
954
955 mQueryToken++; // Updated so that previous queries can no longer set the information.
956 mCallerInfo = null;
957 if (!TextUtils.isEmpty(number)) {
Santos Cordon5ba7f272014-05-28 13:59:49 -0700958 Log.v(this, "Looking up information for: %s.", Log.piiHandle(number));
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700959 CallerInfoAsyncQuery.startQuery(
960 mQueryToken,
961 TelecommApp.getInstance(),
962 number,
963 sCallerInfoQueryListener,
964 this);
965 }
966 }
967
968 /**
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700969 * Saves the specified caller info if the specified token matches that of the last query
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700970 * that was made.
971 *
972 * @param callerInfo The new caller information to set.
973 * @param token The token used with this query.
974 */
975 private void setCallerInfo(CallerInfo callerInfo, int token) {
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700976 Preconditions.checkNotNull(callerInfo);
977
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700978 if (mQueryToken == token) {
979 mCallerInfo = callerInfo;
Santos Cordon5ba7f272014-05-28 13:59:49 -0700980 Log.i(this, "CallerInfo received for %s: %s", Log.piiHandle(mHandle), callerInfo);
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700981
982 if (mCallerInfo.person_id != 0) {
983 Uri personUri =
984 ContentUris.withAppendedId(Contacts.CONTENT_URI, mCallerInfo.person_id);
985 Log.d(this, "Searching person uri %s for call %s", personUri, this);
986 ContactsAsyncHelper.startObtainPhotoAsync(
987 token,
988 TelecommApp.getInstance(),
989 personUri,
990 sPhotoLoadListener,
991 this);
992 }
Santos Cordon2174fb52014-05-29 08:22:56 -0700993
994 processDirectToVoicemail();
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700995 }
996 }
997
998 /**
999 * Saves the specified photo information if the specified token matches that of the last query.
1000 *
1001 * @param photo The photo as a drawable.
1002 * @param photoIcon The photo as a small icon.
1003 * @param token The token used with this query.
1004 */
1005 private void setPhoto(Drawable photo, Bitmap photoIcon, int token) {
1006 if (mQueryToken == token) {
1007 mCallerInfo.cachedPhoto = photo;
1008 mCallerInfo.cachedPhotoIcon = photoIcon;
Santos Cordonfd71f4a2014-05-28 13:59:49 -07001009 }
1010 }
Ihab Awadff7493a2014-06-10 13:47:44 -07001011
1012 private void maybeLoadCannedSmsResponses() {
1013 if (mIsIncoming && isRespondViaSmsCapable() && !mCannedSmsResponsesLoadingStarted) {
1014 Log.d(this, "maybeLoadCannedSmsResponses: starting task to load messages");
1015 mCannedSmsResponsesLoadingStarted = true;
1016 RespondViaSmsManager.getInstance().loadCannedTextMessages(
1017 new Response<Void, List<String>>() {
1018 @Override
1019 public void onResult(Void request, List<String>... result) {
1020 if (result.length > 0) {
1021 Log.d(this, "maybeLoadCannedSmsResponses: got %s", result[0]);
1022 mCannedSmsResponses = result[0];
1023 for (Listener l : mListeners) {
1024 l.onCannedSmsResponsesLoaded(Call.this);
1025 }
1026 }
1027 }
1028
1029 @Override
1030 public void onError(Void request, int code, String msg) {
1031 Log.w(Call.this, "Error obtaining canned SMS responses: %d %s", code,
1032 msg);
1033 }
1034 }
1035 );
1036 } else {
1037 Log.d(this, "maybeLoadCannedSmsResponses: doing nothing");
1038 }
1039 }
Ben Gilad9f2bed32013-12-12 17:43:26 -08001040}