blob: 3d4771b28c7566b44f893d62f7ca481a7e0b0332 [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;
Sailesh Nepal84fa5f82014-04-02 11:01:11 -070026import android.telecomm.CallServiceDescriptor;
Santos Cordon0b03b4b2014-01-29 18:01:59 -080027import android.telecomm.CallState;
Sailesh Nepalc92c4362014-07-04 18:33:21 -070028import android.telecomm.ConnectionRequest;
Yorke Lee33501632014-03-17 19:24:12 -070029import android.telecomm.GatewayInfo;
Ihab Awad98a55602014-06-30 21:27:28 -070030import android.telecomm.PhoneAccount;
Ihab Awadff7493a2014-06-10 13:47:44 -070031import android.telecomm.Response;
Santos Cordon766d04f2014-05-06 10:28:25 -070032import android.telecomm.TelecommConstants;
Santos Cordon79ff2bc2014-03-27 15:31:27 -070033import android.telephony.DisconnectCause;
Sailesh Nepal6aca10a2014-03-24 16:11:02 -070034import android.telephony.PhoneNumberUtils;
Santos Cordonfd71f4a2014-05-28 13:59:49 -070035import android.text.TextUtils;
Santos Cordon0b03b4b2014-01-29 18:01:59 -080036
Nancy Chena65d41f2014-06-24 12:06:03 -070037import com.android.internal.telecomm.ICallVideoProvider;
Santos Cordonfd71f4a2014-05-28 13:59:49 -070038import com.android.internal.telephony.CallerInfo;
39import com.android.internal.telephony.CallerInfoAsyncQuery;
40import com.android.internal.telephony.CallerInfoAsyncQuery.OnQueryCompleteListener;
Ihab Awadff7493a2014-06-10 13:47:44 -070041import com.android.internal.telephony.SmsApplication;
Santos Cordon99c8a6f2014-05-28 18:28:47 -070042import com.android.telecomm.ContactsAsyncHelper.OnImageLoadCompleteListener;
Santos Cordon61d0f702014-02-19 02:52:23 -080043import com.google.common.base.Preconditions;
Ihab Awadff7493a2014-06-10 13:47:44 -070044import com.google.common.collect.Sets;
Santos Cordon61d0f702014-02-19 02:52:23 -080045
Ihab Awadff7493a2014-06-10 13:47:44 -070046import java.util.Collections;
Santos Cordona1610702014-06-04 20:22:56 -070047import java.util.LinkedList;
48import java.util.List;
Sailesh Nepal91990782014-03-08 17:45:52 -080049import java.util.Locale;
Ben Gilad61925612014-03-11 19:06:36 -070050import java.util.Set;
Ben Gilad0407fb22014-01-09 16:18:41 -080051
Ben Gilad2495d572014-01-09 17:26:19 -080052/**
53 * Encapsulates all aspects of a given phone call throughout its lifecycle, starting
54 * from the time the call intent was received by Telecomm (vs. the time the call was
55 * connected etc).
56 */
Sailesh Nepal5a73b032014-06-25 15:53:21 -070057final class Call implements OutgoingCallResponse {
Santos Cordon766d04f2014-05-06 10:28:25 -070058
59 /**
60 * Listener for events on the call.
61 */
62 interface Listener {
63 void onSuccessfulOutgoingCall(Call call);
Sailesh Nepal5a73b032014-06-25 15:53:21 -070064 void onFailedOutgoingCall(Call call, int errorCode, String errorMsg);
65 void onCancelledOutgoingCall(Call call);
Sailesh Nepalc92c4362014-07-04 18:33:21 -070066 void onSuccessfulIncomingCall(Call call);
Santos Cordon766d04f2014-05-06 10:28:25 -070067 void onFailedIncomingCall(Call call);
Ihab Awadcb387ac2014-05-28 16:49:38 -070068 void onRequestingRingback(Call call, boolean requestingRingback);
Evan Charlton352105c2014-06-03 14:10:54 -070069 void onPostDialWait(Call call, String remaining);
Santos Cordona1610702014-06-04 20:22:56 -070070 void onIsConferenceCapableChanged(Call call, boolean isConferenceCapable);
71 void onExpiredConferenceCall(Call call);
72 void onConfirmedConferenceCall(Call call);
73 void onParentChanged(Call call);
74 void onChildrenChanged(Call call);
Ihab Awadff7493a2014-06-10 13:47:44 -070075 void onCannedSmsResponsesLoaded(Call call);
Nancy Chena65d41f2014-06-24 12:06:03 -070076 void onCallVideoProviderChanged(Call call);
Tyler Gunne19cc002014-07-01 11:32:53 -070077 void onFeaturesChanged(Call call);
Santos Cordon64c7e962014-07-02 15:15:27 -070078 void onCallerInfoChanged(Call call);
79 }
80
81 abstract static class ListenerBase implements Listener {
82 @Override
83 public void onSuccessfulOutgoingCall(Call call) {}
84 @Override
85 public void onFailedOutgoingCall(Call call, int errorCode, String errorMsg) {}
86 @Override
87 public void onCancelledOutgoingCall(Call call) {}
88 @Override
Sailesh Nepalc92c4362014-07-04 18:33:21 -070089 public void onSuccessfulIncomingCall(Call call) {}
Santos Cordon64c7e962014-07-02 15:15:27 -070090 @Override
91 public void onFailedIncomingCall(Call call) {}
92 @Override
93 public void onRequestingRingback(Call call, boolean requestingRingback) {}
94 @Override
95 public void onPostDialWait(Call call, String remaining) {}
96 @Override
97 public void onIsConferenceCapableChanged(Call call, boolean isConferenceCapable) {}
98 @Override
99 public void onExpiredConferenceCall(Call call) {}
100 @Override
101 public void onConfirmedConferenceCall(Call call) {}
102 @Override
103 public void onParentChanged(Call call) {}
104 @Override
105 public void onChildrenChanged(Call call) {}
106 @Override
107 public void onCannedSmsResponsesLoaded(Call call) {}
108 @Override
109 public void onCallVideoProviderChanged(Call call) {}
110 @Override
111 public void onFeaturesChanged(Call call) {}
112 @Override
113 public void onCallerInfoChanged(Call call) {}
Santos Cordon766d04f2014-05-06 10:28:25 -0700114 }
115
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700116 private static final OnQueryCompleteListener sCallerInfoQueryListener =
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700117 new OnQueryCompleteListener() {
118 /** ${inheritDoc} */
119 @Override
120 public void onQueryComplete(int token, Object cookie, CallerInfo callerInfo) {
121 if (cookie != null) {
122 ((Call) cookie).setCallerInfo(callerInfo, token);
123 }
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700124 }
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700125 };
126
127 private static final OnImageLoadCompleteListener sPhotoLoadListener =
128 new OnImageLoadCompleteListener() {
129 /** ${inheritDoc} */
130 @Override
131 public void onImageLoadComplete(
132 int token, Drawable photo, Bitmap photoIcon, Object cookie) {
133 if (cookie != null) {
134 ((Call) cookie).setPhoto(photo, photoIcon, token);
135 }
136 }
137 };
Ben Gilad0407fb22014-01-09 16:18:41 -0800138
Sailesh Nepal810735e2014-03-18 18:15:46 -0700139 /** True if this is an incoming call. */
140 private final boolean mIsIncoming;
141
Ben Gilad0407fb22014-01-09 16:18:41 -0800142 /**
143 * The time this call was created, typically also the time this call was added to the set
144 * of pending outgoing calls (mPendingOutgoingCalls) that's maintained by the switchboard.
145 * Beyond logging and such, may also be used for bookkeeping and specifically for marking
146 * certain call attempts as failed attempts.
147 */
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700148 private final long mCreationTimeMillis = System.currentTimeMillis();
149
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700150 /** The gateway information associated with this call. This stores the original call handle
151 * that the user is attempting to connect to via the gateway, the actual handle to dial in
152 * order to connect the call via the gateway, as well as the package name of the gateway
153 * service. */
154 private final GatewayInfo mGatewayInfo;
155
Ihab Awad98a55602014-06-30 21:27:28 -0700156 private final PhoneAccount mAccount;
Nancy Chen77d2d0e2014-06-24 12:06:03 -0700157
Santos Cordon2174fb52014-05-29 08:22:56 -0700158 private final Handler mHandler = new Handler();
159
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700160 private long mConnectTimeMillis;
Ben Gilad0407fb22014-01-09 16:18:41 -0800161
Santos Cordon61d0f702014-02-19 02:52:23 -0800162 /** The state of the call. */
163 private CallState mState;
164
165 /** The handle with which to establish this call. */
Sailesh Nepalce704b92014-03-17 18:31:43 -0700166 private Uri mHandle;
Santos Cordon61d0f702014-02-19 02:52:23 -0800167
Ben Gilad0407fb22014-01-09 16:18:41 -0800168 /**
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700169 * The connection service which is attempted or already connecting this call.
Santos Cordon681663d2014-01-30 04:32:15 -0800170 */
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700171 private ConnectionServiceWrapper mConnectionService;
Ben Gilad61925612014-03-11 19:06:36 -0700172
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700173 private boolean mIsEmergencyCall;
174
Sai Cheemalapatib7157e92014-06-11 17:51:55 -0700175 private boolean mSpeakerphoneOn;
176
Ben Gilad61925612014-03-11 19:06:36 -0700177 /**
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700178 * Disconnect cause for the call. Only valid if the state of the call is DISCONNECTED.
179 * See {@link android.telephony.DisconnectCause}.
180 */
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700181 private int mDisconnectCause = DisconnectCause.NOT_VALID;
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700182
183 /**
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700184 * Additional disconnect information provided by the connection service.
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700185 */
186 private String mDisconnectMessage;
187
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700188 /** Info used by the connection services. */
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700189 private Bundle mExtras = Bundle.EMPTY;
Sailesh Nepal84fa5f82014-04-02 11:01:11 -0700190
Santos Cordon766d04f2014-05-06 10:28:25 -0700191 /** Set of listeners on this call. */
192 private Set<Listener> mListeners = Sets.newHashSet();
193
Santos Cordon682fe6b2014-05-20 08:56:39 -0700194 private OutgoingCallProcessor mOutgoingCallProcessor;
195
196 // TODO(santoscordon): The repositories should be changed into singleton types.
197 private CallServiceRepository mCallServiceRepository;
Santos Cordon682fe6b2014-05-20 08:56:39 -0700198
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700199 /** Caller information retrieved from the latest contact query. */
200 private CallerInfo mCallerInfo;
201
202 /** The latest token used with a contact info query. */
203 private int mQueryToken = 0;
204
Ihab Awadcb387ac2014-05-28 16:49:38 -0700205 /** Whether this call is requesting that Telecomm play the ringback tone on its behalf. */
206 private boolean mRequestingRingback = false;
207
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700208 /** Whether direct-to-voicemail query is pending. */
209 private boolean mDirectToVoicemailQueryPending;
Santos Cordon2174fb52014-05-29 08:22:56 -0700210
Santos Cordona1610702014-06-04 20:22:56 -0700211 private boolean mIsConferenceCapable = false;
212
213 private boolean mIsConference = false;
214
215 private Call mParentCall = null;
216
217 private List<Call> mChildCalls = new LinkedList<>();
218
Ihab Awadff7493a2014-06-10 13:47:44 -0700219 /** Set of text message responses allowed for this call, if applicable. */
220 private List<String> mCannedSmsResponses = Collections.EMPTY_LIST;
221
222 /** Whether an attempt has been made to load the text message responses. */
223 private boolean mCannedSmsResponsesLoadingStarted = false;
224
Nancy Chena65d41f2014-06-24 12:06:03 -0700225 private ICallVideoProvider mCallVideoProvider;
226
Tyler Gunne19cc002014-07-01 11:32:53 -0700227 /** Features associated with the call which the InCall UI may wish to show icons for. */
228 private int mFeatures;
229
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700230 /**
Sailesh Nepale59bb192014-04-01 18:33:59 -0700231 * Creates an empty call object.
Sailesh Nepal810735e2014-03-18 18:15:46 -0700232 *
233 * @param isIncoming True if this is an incoming call.
Santos Cordon493e8f22014-02-19 03:15:12 -0800234 */
Santos Cordona1610702014-06-04 20:22:56 -0700235 Call(boolean isIncoming, boolean isConference) {
Nancy Chen77d2d0e2014-06-24 12:06:03 -0700236 this(null, null, null, isIncoming, isConference);
Santos Cordon493e8f22014-02-19 03:15:12 -0800237 }
238
239 /**
Ben Gilad0407fb22014-01-09 16:18:41 -0800240 * Persists the specified parameters and initializes the new instance.
241 *
242 * @param handle The handle to dial.
Yorke Lee33501632014-03-17 19:24:12 -0700243 * @param gatewayInfo Gateway information to use for the call.
Ihab Awad98a55602014-06-30 21:27:28 -0700244 * @param account Account information to use for the call.
Sailesh Nepal810735e2014-03-18 18:15:46 -0700245 * @param isIncoming True if this is an incoming call.
Ben Gilad0407fb22014-01-09 16:18:41 -0800246 */
Ihab Awad98a55602014-06-30 21:27:28 -0700247 Call(Uri handle, GatewayInfo gatewayInfo, PhoneAccount account,
Nancy Chen77d2d0e2014-06-24 12:06:03 -0700248 boolean isIncoming, boolean isConference) {
Santos Cordona1610702014-06-04 20:22:56 -0700249 mState = isConference ? CallState.ACTIVE : CallState.NEW;
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700250 setHandle(handle);
Yorke Lee33501632014-03-17 19:24:12 -0700251 mGatewayInfo = gatewayInfo;
Ihab Awad98a55602014-06-30 21:27:28 -0700252 mAccount = account;
Sailesh Nepal810735e2014-03-18 18:15:46 -0700253 mIsIncoming = isIncoming;
Santos Cordona1610702014-06-04 20:22:56 -0700254 mIsConference = isConference;
Ihab Awadff7493a2014-06-10 13:47:44 -0700255 maybeLoadCannedSmsResponses();
Ben Gilad0407fb22014-01-09 16:18:41 -0800256 }
257
Santos Cordon766d04f2014-05-06 10:28:25 -0700258 void addListener(Listener listener) {
259 mListeners.add(listener);
260 }
261
262 void removeListener(Listener listener) {
263 mListeners.remove(listener);
264 }
265
Santos Cordon61d0f702014-02-19 02:52:23 -0800266 /** {@inheritDoc} */
267 @Override public String toString() {
Sailesh Nepal4538f012014-04-15 11:40:33 -0700268 String component = null;
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700269 if (mConnectionService != null && mConnectionService.getComponentName() != null) {
270 component = mConnectionService.getComponentName().flattenToShortString();
Sailesh Nepal4538f012014-04-15 11:40:33 -0700271 }
272 return String.format(Locale.US, "[%s, %s, %s]", mState, component, Log.piiHandle(mHandle));
Santos Cordon61d0f702014-02-19 02:52:23 -0800273 }
274
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800275 CallState getState() {
Santos Cordona1610702014-06-04 20:22:56 -0700276 if (mIsConference) {
277 if (!mChildCalls.isEmpty()) {
278 // If we have child calls, just return the child call.
279 return mChildCalls.get(0).getState();
280 }
281 return CallState.ACTIVE;
282 } else {
283 return mState;
284 }
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800285 }
286
287 /**
288 * Sets the call state. Although there exists the notion of appropriate state transitions
289 * (see {@link CallState}), in practice those expectations break down when cellular systems
290 * misbehave and they do this very often. The result is that we do not enforce state transitions
291 * and instead keep the code resilient to unexpected state changes.
292 */
Sailesh Nepal810735e2014-03-18 18:15:46 -0700293 void setState(CallState newState) {
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700294 Preconditions.checkState(newState != CallState.DISCONNECTED ||
295 mDisconnectCause != DisconnectCause.NOT_VALID);
Sailesh Nepal810735e2014-03-18 18:15:46 -0700296 if (mState != newState) {
297 Log.v(this, "setState %s -> %s", mState, newState);
298 mState = newState;
Ihab Awadff7493a2014-06-10 13:47:44 -0700299 maybeLoadCannedSmsResponses();
Sailesh Nepal810735e2014-03-18 18:15:46 -0700300 }
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800301 }
302
Ihab Awadcb387ac2014-05-28 16:49:38 -0700303 void setRequestingRingback(boolean requestingRingback) {
304 mRequestingRingback = requestingRingback;
305 for (Listener l : mListeners) {
306 l.onRequestingRingback(this, mRequestingRingback);
307 }
308 }
309
310 boolean isRequestingRingback() {
311 return mRequestingRingback;
312 }
313
Sailesh Nepalce704b92014-03-17 18:31:43 -0700314 Uri getHandle() {
Ben Gilad0bf5b912014-01-28 17:55:57 -0800315 return mHandle;
316 }
317
Sailesh Nepalce704b92014-03-17 18:31:43 -0700318 void setHandle(Uri handle) {
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700319 if ((mHandle == null && handle != null) || (mHandle != null && !mHandle.equals(handle))) {
320 mHandle = handle;
321 mIsEmergencyCall = mHandle != null && PhoneNumberUtils.isLocalEmergencyNumber(
Yorke Lee66255452014-06-05 08:09:24 -0700322 TelecommApp.getInstance(), mHandle.getSchemeSpecificPart());
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700323 startCallerInfoLookup();
324 }
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700325 }
326
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700327 String getName() {
328 return mCallerInfo == null ? null : mCallerInfo.name;
329 }
330
331 Bitmap getPhotoIcon() {
332 return mCallerInfo == null ? null : mCallerInfo.cachedPhotoIcon;
333 }
334
335 Drawable getPhoto() {
336 return mCallerInfo == null ? null : mCallerInfo.cachedPhoto;
337 }
338
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700339 /**
340 * @param disconnectCause The reason for the disconnection, any of
341 * {@link android.telephony.DisconnectCause}.
342 * @param disconnectMessage Optional call-service-provided message about the disconnect.
343 */
344 void setDisconnectCause(int disconnectCause, String disconnectMessage) {
345 // TODO: Consider combining this method with a setDisconnected() method that is totally
346 // separate from setState.
347 mDisconnectCause = disconnectCause;
348 mDisconnectMessage = disconnectMessage;
349 }
350
351 int getDisconnectCause() {
352 return mDisconnectCause;
353 }
354
355 String getDisconnectMessage() {
356 return mDisconnectMessage;
357 }
358
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700359 boolean isEmergencyCall() {
360 return mIsEmergencyCall;
Santos Cordon61d0f702014-02-19 02:52:23 -0800361 }
362
Yorke Lee33501632014-03-17 19:24:12 -0700363 /**
364 * @return The original handle this call is associated with. In-call services should use this
365 * handle when indicating in their UI the handle that is being called.
366 */
367 public Uri getOriginalHandle() {
368 if (mGatewayInfo != null && !mGatewayInfo.isEmpty()) {
369 return mGatewayInfo.getOriginalHandle();
370 }
371 return getHandle();
372 }
373
374 GatewayInfo getGatewayInfo() {
375 return mGatewayInfo;
376 }
377
Ihab Awad98a55602014-06-30 21:27:28 -0700378 PhoneAccount getAccount() {
379 return mAccount;
Nancy Chen77d2d0e2014-06-24 12:06:03 -0700380 }
381
Sailesh Nepal810735e2014-03-18 18:15:46 -0700382 boolean isIncoming() {
383 return mIsIncoming;
384 }
385
Ben Gilad0407fb22014-01-09 16:18:41 -0800386 /**
387 * @return The "age" of this call object in milliseconds, which typically also represents the
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700388 * period since this call was added to the set pending outgoing calls, see
389 * mCreationTimeMillis.
Ben Gilad0407fb22014-01-09 16:18:41 -0800390 */
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700391 long getAgeMillis() {
392 return System.currentTimeMillis() - mCreationTimeMillis;
Ben Gilad0407fb22014-01-09 16:18:41 -0800393 }
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800394
Yorke Leef98fb572014-03-05 10:56:55 -0800395 /**
396 * @return The time when this call object was created and added to the set of pending outgoing
397 * calls.
398 */
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700399 long getCreationTimeMillis() {
400 return mCreationTimeMillis;
401 }
402
403 long getConnectTimeMillis() {
404 return mConnectTimeMillis;
405 }
406
407 void setConnectTimeMillis(long connectTimeMillis) {
408 mConnectTimeMillis = connectTimeMillis;
Yorke Leef98fb572014-03-05 10:56:55 -0800409 }
410
Santos Cordona1610702014-06-04 20:22:56 -0700411 boolean isConferenceCapable() {
412 return mIsConferenceCapable;
413 }
414
415 void setIsConferenceCapable(boolean isConferenceCapable) {
416 if (mIsConferenceCapable != isConferenceCapable) {
417 mIsConferenceCapable = isConferenceCapable;
418 for (Listener l : mListeners) {
419 l.onIsConferenceCapableChanged(this, mIsConferenceCapable);
420 }
421 }
422 }
423
424 Call getParentCall() {
425 return mParentCall;
426 }
427
428 List<Call> getChildCalls() {
429 return mChildCalls;
430 }
431
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700432 ConnectionServiceWrapper getConnectionService() {
433 return mConnectionService;
Santos Cordon681663d2014-01-30 04:32:15 -0800434 }
435
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700436 void setConnectionService(ConnectionServiceWrapper service) {
437 Preconditions.checkNotNull(service);
438
439 clearConnectionService();
440
441 service.incrementAssociatedCallCount();
442 mConnectionService = service;
443 mConnectionService.addCall(this);
Sailesh Nepal0e5410a2014-04-04 01:20:58 -0700444 }
445
446 /**
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700447 * Clears the associated connection service.
Sailesh Nepal0e5410a2014-04-04 01:20:58 -0700448 */
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700449 void clearConnectionService() {
450 if (mConnectionService != null) {
451 ConnectionServiceWrapper serviceTemp = mConnectionService;
452 mConnectionService = null;
453 serviceTemp.removeCall(this);
Santos Cordonc499c1c2014-04-14 17:13:14 -0700454
455 // Decrementing the count can cause the service to unbind, which itself can trigger the
456 // service-death code. Since the service death code tries to clean up any associated
457 // calls, we need to make sure to remove that information (e.g., removeCall()) before
458 // we decrement. Technically, invoking removeCall() prior to decrementing is all that is
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700459 // necessary, but cleaning up mConnectionService prior to triggering an unbind is good
460 // to do.
461 decrementAssociatedCallCount(serviceTemp);
Yorke Leeadee12d2014-03-13 12:08:30 -0700462 }
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800463 }
464
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800465 /**
Santos Cordon766d04f2014-05-06 10:28:25 -0700466 * Starts the incoming call flow through the switchboard. When switchboard completes, it will
467 * invoke handle[Un]SuccessfulIncomingCall.
468 *
469 * @param descriptor The relevant call-service descriptor.
470 * @param extras The optional extras passed via
471 * {@link TelecommConstants#EXTRA_INCOMING_CALL_EXTRAS}.
472 */
473 void startIncoming(CallServiceDescriptor descriptor, Bundle extras) {
474 Switchboard.getInstance().retrieveIncomingCall(this, descriptor, extras);
475 }
476
Santos Cordon2174fb52014-05-29 08:22:56 -0700477 /**
478 * Takes a verified incoming call and uses the handle to lookup direct-to-voicemail property
479 * from the contacts provider. The call is not yet exposed to the user at this point and
480 * the result of the query will determine if the call is rejected or passed through to the
481 * in-call UI.
482 */
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700483 void handleVerifiedIncoming(ConnectionRequest request) {
484 // We do not handle incoming calls immediately when they are verified by the connection
485 // service. We allow the caller-info-query code to execute first so that we can read the
Santos Cordon2174fb52014-05-29 08:22:56 -0700486 // direct-to-voicemail property before deciding if we want to show the incoming call to the
487 // user or if we want to reject the call.
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700488 mDirectToVoicemailQueryPending = true;
Santos Cordon2174fb52014-05-29 08:22:56 -0700489
490 // Setting the handle triggers the caller info lookup code.
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700491 setHandle(request.getHandle());
Santos Cordon766d04f2014-05-06 10:28:25 -0700492
Santos Cordon2174fb52014-05-29 08:22:56 -0700493 // Timeout the direct-to-voicemail lookup execution so that we dont wait too long before
494 // showing the user the incoming call screen.
495 mHandler.postDelayed(new Runnable() {
496 @Override
497 public void run() {
498 processDirectToVoicemail();
499 }
Santos Cordona1610702014-06-04 20:22:56 -0700500 }, Timeouts.getDirectToVoicemailMillis());
Santos Cordon2174fb52014-05-29 08:22:56 -0700501 }
Santos Cordon766d04f2014-05-06 10:28:25 -0700502
Santos Cordon2174fb52014-05-29 08:22:56 -0700503 void processDirectToVoicemail() {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700504 if (mDirectToVoicemailQueryPending) {
Santos Cordon2174fb52014-05-29 08:22:56 -0700505 if (mCallerInfo != null && mCallerInfo.shouldSendToVoicemail) {
506 Log.i(this, "Directing call to voicemail: %s.", this);
507 // TODO(santoscordon): Once we move State handling from CallsManager to Call, we
508 // will not need to set RINGING state prior to calling reject.
509 setState(CallState.RINGING);
Ihab Awadff7493a2014-06-10 13:47:44 -0700510 reject(false, null);
Santos Cordon2174fb52014-05-29 08:22:56 -0700511 } else {
512 // TODO(santoscordon): Make this class (not CallsManager) responsible for changing
513 // the call state to RINGING.
514
515 // TODO(santoscordon): Replace this with state transition to RINGING.
516 for (Listener l : mListeners) {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700517 l.onSuccessfulIncomingCall(this);
Santos Cordon2174fb52014-05-29 08:22:56 -0700518 }
519 }
520
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700521 mDirectToVoicemailQueryPending = false;
Santos Cordon766d04f2014-05-06 10:28:25 -0700522 }
523 }
524
525 void handleFailedIncoming() {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700526 clearConnectionService();
Santos Cordon766d04f2014-05-06 10:28:25 -0700527
528 // TODO: Needs more specific disconnect error for this case.
529 setDisconnectCause(DisconnectCause.ERROR_UNSPECIFIED, null);
530 setState(CallState.DISCONNECTED);
531
532 // TODO(santoscordon): Replace this with state transitions related to "connecting".
533 for (Listener l : mListeners) {
534 l.onFailedIncomingCall(this);
535 }
536 }
537
538 /**
Santos Cordon682fe6b2014-05-20 08:56:39 -0700539 * Starts the outgoing call sequence. Upon completion, there should exist an active connection
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700540 * through a connection service (or the call will have failed).
Santos Cordon766d04f2014-05-06 10:28:25 -0700541 */
542 void startOutgoing() {
Santos Cordon682fe6b2014-05-20 08:56:39 -0700543 Preconditions.checkState(mOutgoingCallProcessor == null);
544
545 mOutgoingCallProcessor = new OutgoingCallProcessor(
Sailesh Nepal5a73b032014-06-25 15:53:21 -0700546 this, Switchboard.getInstance().getCallServiceRepository(), this);
Santos Cordon682fe6b2014-05-20 08:56:39 -0700547 mOutgoingCallProcessor.process();
Santos Cordon766d04f2014-05-06 10:28:25 -0700548 }
549
Sailesh Nepal5a73b032014-06-25 15:53:21 -0700550 @Override
551 public void onOutgoingCallSuccess() {
Santos Cordon766d04f2014-05-06 10:28:25 -0700552 // TODO(santoscordon): Replace this with state transitions related to "connecting".
553 for (Listener l : mListeners) {
554 l.onSuccessfulOutgoingCall(this);
555 }
Sailesh Nepal5a73b032014-06-25 15:53:21 -0700556 mOutgoingCallProcessor = null;
Santos Cordon766d04f2014-05-06 10:28:25 -0700557 }
558
Sailesh Nepal5a73b032014-06-25 15:53:21 -0700559 @Override
560 public void onOutgoingCallFailure(int code, String msg) {
Santos Cordon766d04f2014-05-06 10:28:25 -0700561 // TODO(santoscordon): Replace this with state transitions related to "connecting".
562 for (Listener l : mListeners) {
Sailesh Nepal5a73b032014-06-25 15:53:21 -0700563 l.onFailedOutgoingCall(this, code, msg);
Santos Cordon766d04f2014-05-06 10:28:25 -0700564 }
Santos Cordon682fe6b2014-05-20 08:56:39 -0700565
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700566 clearConnectionService();
Sailesh Nepal5a73b032014-06-25 15:53:21 -0700567 mOutgoingCallProcessor = null;
568 }
569
570 @Override
571 public void onOutgoingCallCancel() {
572 // TODO(santoscordon): Replace this with state transitions related to "connecting".
573 for (Listener l : mListeners) {
574 l.onCancelledOutgoingCall(this);
575 }
576
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700577 clearConnectionService();
Sailesh Nepal5a73b032014-06-25 15:53:21 -0700578 mOutgoingCallProcessor = null;
Santos Cordon766d04f2014-05-06 10:28:25 -0700579 }
580
581 /**
Ihab Awad74549ec2014-03-10 15:33:25 -0700582 * Plays the specified DTMF tone.
583 */
584 void playDtmfTone(char digit) {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700585 if (mConnectionService == null) {
586 Log.w(this, "playDtmfTone() request on a call without a connection service.");
Ihab Awad74549ec2014-03-10 15:33:25 -0700587 } else {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700588 Log.i(this, "Send playDtmfTone to connection service for call %s", this);
589 mConnectionService.playDtmfTone(this, digit);
Ihab Awad74549ec2014-03-10 15:33:25 -0700590 }
591 }
592
593 /**
594 * Stops playing any currently playing DTMF tone.
595 */
596 void stopDtmfTone() {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700597 if (mConnectionService == null) {
598 Log.w(this, "stopDtmfTone() request on a call without a connectino service.");
Ihab Awad74549ec2014-03-10 15:33:25 -0700599 } else {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700600 Log.i(this, "Send stopDtmfTone to connection service for call %s", this);
601 mConnectionService.stopDtmfTone(this);
Ihab Awad74549ec2014-03-10 15:33:25 -0700602 }
603 }
604
605 /**
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700606 * Attempts to disconnect the call through the connection service.
Santos Cordon049b7b62014-01-30 05:34:26 -0800607 */
608 void disconnect() {
Santos Cordon766d04f2014-05-06 10:28:25 -0700609 if (mState == CallState.NEW) {
Santos Cordon682fe6b2014-05-20 08:56:39 -0700610 Log.v(this, "Aborting call %s", this);
611 abort();
Santos Cordon74d420b2014-05-07 14:38:47 -0700612 } else if (mState != CallState.ABORTED && mState != CallState.DISCONNECTED) {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700613 Preconditions.checkNotNull(mConnectionService);
Santos Cordon766d04f2014-05-06 10:28:25 -0700614
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700615 Log.i(this, "Send disconnect to connection service for call: %s", this);
616 // The call isn't officially disconnected until the connection service confirms that the
617 // call was actually disconnected. Only then is the association between call and
618 // connection service severed, see {@link CallsManager#markCallAsDisconnected}.
619 mConnectionService.disconnect(this);
Santos Cordon049b7b62014-01-30 05:34:26 -0800620 }
621 }
622
Santos Cordon682fe6b2014-05-20 08:56:39 -0700623 void abort() {
624 if (mOutgoingCallProcessor != null) {
625 mOutgoingCallProcessor.abort();
626 }
627 }
628
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800629 /**
Santos Cordon61d0f702014-02-19 02:52:23 -0800630 * Answers the call if it is ringing.
631 */
632 void answer() {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700633 Preconditions.checkNotNull(mConnectionService);
Santos Cordon61d0f702014-02-19 02:52:23 -0800634
635 // Check to verify that the call is still in the ringing state. A call can change states
636 // between the time the user hits 'answer' and Telecomm receives the command.
637 if (isRinging("answer")) {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700638 // At this point, we are asking the connection service to answer but we don't assume
639 // that it will work. Instead, we wait until confirmation from the connectino service
640 // that the call is in a non-RINGING state before changing the UI. See
641 // {@link ConnectionServiceAdapter#setActive} and other set* methods.
642 mConnectionService.answer(this);
Santos Cordon61d0f702014-02-19 02:52:23 -0800643 }
644 }
645
646 /**
647 * Rejects the call if it is ringing.
Ihab Awadff7493a2014-06-10 13:47:44 -0700648 *
649 * @param rejectWithMessage Whether to send a text message as part of the call rejection.
650 * @param textMessage An optional text message to send as part of the rejection.
Santos Cordon61d0f702014-02-19 02:52:23 -0800651 */
Ihab Awadff7493a2014-06-10 13:47:44 -0700652 void reject(boolean rejectWithMessage, String textMessage) {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700653 Preconditions.checkNotNull(mConnectionService);
Santos Cordon61d0f702014-02-19 02:52:23 -0800654
655 // Check to verify that the call is still in the ringing state. A call can change states
656 // between the time the user hits 'reject' and Telecomm receives the command.
657 if (isRinging("reject")) {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700658 mConnectionService.reject(this);
Santos Cordon61d0f702014-02-19 02:52:23 -0800659 }
660 }
661
662 /**
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700663 * Puts the call on hold if it is currently active.
664 */
665 void hold() {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700666 Preconditions.checkNotNull(mConnectionService);
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700667
668 if (mState == CallState.ACTIVE) {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700669 mConnectionService.hold(this);
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700670 }
671 }
672
673 /**
674 * Releases the call from hold if it is currently active.
675 */
676 void unhold() {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700677 Preconditions.checkNotNull(mConnectionService);
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700678
679 if (mState == CallState.ON_HOLD) {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700680 mConnectionService.unhold(this);
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700681 }
682 }
683
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700684 /** Checks if this is a live call or not. */
685 boolean isAlive() {
686 switch (mState) {
687 case NEW:
688 case RINGING:
689 case DISCONNECTED:
690 case ABORTED:
691 return false;
692 default:
693 return true;
694 }
695 }
696
Santos Cordon40f78c22014-04-07 02:11:42 -0700697 boolean isActive() {
698 switch (mState) {
699 case ACTIVE:
700 case POST_DIAL:
701 case POST_DIAL_WAIT:
702 return true;
703 default:
704 return false;
705 }
706 }
707
Sailesh Nepal84fa5f82014-04-02 11:01:11 -0700708 Bundle getExtras() {
709 return mExtras;
710 }
711
712 void setExtras(Bundle extras) {
713 mExtras = extras;
714 }
715
Santos Cordon5ba7f272014-05-28 13:59:49 -0700716 Uri getRingtone() {
717 return mCallerInfo == null ? null : mCallerInfo.contactRingtoneUri;
718 }
719
Evan Charlton352105c2014-06-03 14:10:54 -0700720 void onPostDialWait(String remaining) {
721 for (Listener l : mListeners) {
722 l.onPostDialWait(this, remaining);
723 }
724 }
725
726 void postDialContinue(boolean proceed) {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700727 mConnectionService.onPostDialContinue(this, proceed);
Evan Charlton352105c2014-06-03 14:10:54 -0700728 }
729
Sailesh Nepal77da19e2014-07-02 21:31:16 -0700730 void phoneAccountClicked() {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700731 mConnectionService.onPhoneAccountClicked(this);
Sailesh Nepal77da19e2014-07-02 21:31:16 -0700732 }
733
Santos Cordona1610702014-06-04 20:22:56 -0700734 void conferenceInto(Call conferenceCall) {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700735 if (mConnectionService == null) {
736 Log.w(this, "conference requested on a call without a connection service.");
Santos Cordona1610702014-06-04 20:22:56 -0700737 } else {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700738 mConnectionService.conference(conferenceCall, this);
Santos Cordona1610702014-06-04 20:22:56 -0700739 }
740 }
741
742 void expireConference() {
743 // The conference call expired before we got a confirmation of the conference from the
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700744 // connection service...so start shutting down.
745 clearConnectionService();
Santos Cordona1610702014-06-04 20:22:56 -0700746 for (Listener l : mListeners) {
747 l.onExpiredConferenceCall(this);
748 }
749 }
750
751 void confirmConference() {
752 Log.v(this, "confirming Conf call %s", mListeners);
753 for (Listener l : mListeners) {
754 l.onConfirmedConferenceCall(this);
755 }
756 }
757
758 void splitFromConference() {
759 // TODO(santoscordon): todo
760 }
761
762 void setParentCall(Call parentCall) {
763 if (parentCall == this) {
764 Log.e(this, new Exception(), "setting the parent to self");
765 return;
766 }
767 Preconditions.checkState(parentCall == null || mParentCall == null);
768
769 Call oldParent = mParentCall;
770 if (mParentCall != null) {
771 mParentCall.removeChildCall(this);
772 }
773 mParentCall = parentCall;
774 if (mParentCall != null) {
775 mParentCall.addChildCall(this);
776 }
777
778 for (Listener l : mListeners) {
779 l.onParentChanged(this);
780 }
781 }
782
783 private void addChildCall(Call call) {
784 if (!mChildCalls.contains(call)) {
785 mChildCalls.add(call);
786
787 for (Listener l : mListeners) {
788 l.onChildrenChanged(this);
789 }
790 }
791 }
792
793 private void removeChildCall(Call call) {
794 if (mChildCalls.remove(call)) {
795 for (Listener l : mListeners) {
796 l.onChildrenChanged(this);
797 }
798 }
799 }
800
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800801 /**
Ihab Awadff7493a2014-06-10 13:47:44 -0700802 * Return whether the user can respond to this {@code Call} via an SMS message.
803 *
804 * @return true if the "Respond via SMS" feature should be enabled
805 * for this incoming call.
806 *
807 * The general rule is that we *do* allow "Respond via SMS" except for
808 * the few (relatively rare) cases where we know for sure it won't
809 * work, namely:
810 * - a bogus or blank incoming number
811 * - a call from a SIP address
812 * - a "call presentation" that doesn't allow the number to be revealed
813 *
814 * In all other cases, we allow the user to respond via SMS.
815 *
816 * Note that this behavior isn't perfect; for example we have no way
817 * to detect whether the incoming call is from a landline (with most
818 * networks at least), so we still enable this feature even though
819 * SMSes to that number will silently fail.
820 */
821 boolean isRespondViaSmsCapable() {
822 if (mState != CallState.RINGING) {
823 return false;
824 }
825
826 if (getHandle() == null) {
827 // No incoming number known or call presentation is "PRESENTATION_RESTRICTED", in
828 // other words, the user should not be able to see the incoming phone number.
829 return false;
830 }
831
832 if (PhoneNumberUtils.isUriNumber(getHandle().toString())) {
833 // The incoming number is actually a URI (i.e. a SIP address),
834 // not a regular PSTN phone number, and we can't send SMSes to
835 // SIP addresses.
836 // (TODO: That might still be possible eventually, though. Is
837 // there some SIP-specific equivalent to sending a text message?)
838 return false;
839 }
840
841 // Is there a valid SMS application on the phone?
842 if (SmsApplication.getDefaultRespondViaMessageApplication(TelecommApp.getInstance(),
843 true /*updateIfNeeded*/) == null) {
844 return false;
845 }
846
847 // TODO: with some carriers (in certain countries) you *can* actually
848 // tell whether a given number is a mobile phone or not. So in that
849 // case we could potentially return false here if the incoming call is
850 // from a land line.
851
852 // If none of the above special cases apply, it's OK to enable the
853 // "Respond via SMS" feature.
854 return true;
855 }
856
857 List<String> getCannedSmsResponses() {
858 return mCannedSmsResponses;
859 }
860
861 /**
Santos Cordon61d0f702014-02-19 02:52:23 -0800862 * @return True if the call is ringing, else logs the action name.
863 */
864 private boolean isRinging(String actionName) {
865 if (mState == CallState.RINGING) {
866 return true;
867 }
868
Sailesh Nepalf1c191d2014-03-07 18:17:39 -0800869 Log.i(this, "Request to %s a non-ringing call %s", actionName, this);
Santos Cordon61d0f702014-02-19 02:52:23 -0800870 return false;
871 }
872
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800873 @SuppressWarnings("rawtypes")
874 private void decrementAssociatedCallCount(ServiceBinder binder) {
875 if (binder != null) {
876 binder.decrementAssociatedCallCount();
877 }
878 }
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700879
880 /**
881 * Looks up contact information based on the current handle.
882 */
883 private void startCallerInfoLookup() {
884 String number = mHandle == null ? null : mHandle.getSchemeSpecificPart();
885
886 mQueryToken++; // Updated so that previous queries can no longer set the information.
887 mCallerInfo = null;
888 if (!TextUtils.isEmpty(number)) {
Santos Cordon5ba7f272014-05-28 13:59:49 -0700889 Log.v(this, "Looking up information for: %s.", Log.piiHandle(number));
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700890 CallerInfoAsyncQuery.startQuery(
891 mQueryToken,
892 TelecommApp.getInstance(),
893 number,
894 sCallerInfoQueryListener,
895 this);
896 }
897 }
898
899 /**
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700900 * Saves the specified caller info if the specified token matches that of the last query
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700901 * that was made.
902 *
903 * @param callerInfo The new caller information to set.
904 * @param token The token used with this query.
905 */
906 private void setCallerInfo(CallerInfo callerInfo, int token) {
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700907 Preconditions.checkNotNull(callerInfo);
908
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700909 if (mQueryToken == token) {
910 mCallerInfo = callerInfo;
Santos Cordon5ba7f272014-05-28 13:59:49 -0700911 Log.i(this, "CallerInfo received for %s: %s", Log.piiHandle(mHandle), callerInfo);
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700912
913 if (mCallerInfo.person_id != 0) {
914 Uri personUri =
915 ContentUris.withAppendedId(Contacts.CONTENT_URI, mCallerInfo.person_id);
916 Log.d(this, "Searching person uri %s for call %s", personUri, this);
917 ContactsAsyncHelper.startObtainPhotoAsync(
918 token,
919 TelecommApp.getInstance(),
920 personUri,
921 sPhotoLoadListener,
922 this);
Santos Cordon64c7e962014-07-02 15:15:27 -0700923 } else {
924 for (Listener l : mListeners) {
925 l.onCallerInfoChanged(this);
926 }
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700927 }
Santos Cordon2174fb52014-05-29 08:22:56 -0700928
929 processDirectToVoicemail();
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700930 }
931 }
932
933 /**
934 * Saves the specified photo information if the specified token matches that of the last query.
935 *
936 * @param photo The photo as a drawable.
937 * @param photoIcon The photo as a small icon.
938 * @param token The token used with this query.
939 */
940 private void setPhoto(Drawable photo, Bitmap photoIcon, int token) {
941 if (mQueryToken == token) {
942 mCallerInfo.cachedPhoto = photo;
943 mCallerInfo.cachedPhotoIcon = photoIcon;
Santos Cordon64c7e962014-07-02 15:15:27 -0700944
945 for (Listener l : mListeners) {
946 l.onCallerInfoChanged(this);
947 }
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700948 }
949 }
Ihab Awadff7493a2014-06-10 13:47:44 -0700950
951 private void maybeLoadCannedSmsResponses() {
952 if (mIsIncoming && isRespondViaSmsCapable() && !mCannedSmsResponsesLoadingStarted) {
953 Log.d(this, "maybeLoadCannedSmsResponses: starting task to load messages");
954 mCannedSmsResponsesLoadingStarted = true;
955 RespondViaSmsManager.getInstance().loadCannedTextMessages(
956 new Response<Void, List<String>>() {
957 @Override
958 public void onResult(Void request, List<String>... result) {
959 if (result.length > 0) {
960 Log.d(this, "maybeLoadCannedSmsResponses: got %s", result[0]);
961 mCannedSmsResponses = result[0];
962 for (Listener l : mListeners) {
963 l.onCannedSmsResponsesLoaded(Call.this);
964 }
965 }
966 }
967
968 @Override
969 public void onError(Void request, int code, String msg) {
970 Log.w(Call.this, "Error obtaining canned SMS responses: %d %s", code,
971 msg);
972 }
973 }
974 );
975 } else {
976 Log.d(this, "maybeLoadCannedSmsResponses: doing nothing");
977 }
978 }
Sai Cheemalapatib7157e92014-06-11 17:51:55 -0700979
980 /**
981 * Sets speakerphone option on when call begins.
982 */
983 public void setStartWithSpeakerphoneOn(boolean startWithSpeakerphone) {
984 mSpeakerphoneOn = startWithSpeakerphone;
985 }
986
987 /**
988 * Returns speakerphone option.
989 *
990 * @return Whether or not speakerphone should be set automatically when call begins.
991 */
992 public boolean getStartWithSpeakerphoneOn() {
993 return mSpeakerphoneOn;
994 }
Andrew Leee9a77652014-06-26 13:07:57 -0700995
996 /**
997 * Sets a call video provider for the call.
998 */
Nancy Chena65d41f2014-06-24 12:06:03 -0700999 public void setCallVideoProvider(ICallVideoProvider callVideoProvider) {
1000 mCallVideoProvider = callVideoProvider;
1001 for (Listener l : mListeners) {
1002 l.onCallVideoProviderChanged(Call.this);
1003 }
1004 }
1005
1006 /**
1007 * @return Return the call video Provider binder.
1008 */
1009 public ICallVideoProvider getCallVideoProvider() {
1010 return mCallVideoProvider;
Andrew Leee9a77652014-06-26 13:07:57 -07001011 }
Tyler Gunne19cc002014-07-01 11:32:53 -07001012
1013 /**
1014 * Returns the features of this call.
1015 *
1016 * @return The features of this call.
1017 */
1018 public int getFeatures() {
1019 return mFeatures;
1020 }
1021
1022 /**
1023 * Set the features associated with the call and notify any listeners of the change.
1024 *
1025 * @param features The features.
1026 */
1027 public void setFeatures(int features) {
1028 Log.d(this, "setFeatures: %d", features);
1029 mFeatures = features;
1030 for (Listener l : mListeners) {
1031 l.onFeaturesChanged(Call.this);
1032 }
1033 }
Ben Gilad9f2bed32013-12-12 17:43:26 -08001034}