blob: 9b4bf804d97a7189c87deb9d37265e7f6f0f5fad [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;
Andrew Leee9a77652014-06-26 13:07:57 -070030import android.telecomm.InCallService;
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
Santos Cordonfd71f4a2014-05-28 13:59:49 -070037import com.android.internal.telephony.CallerInfo;
38import com.android.internal.telephony.CallerInfoAsyncQuery;
39import com.android.internal.telephony.CallerInfoAsyncQuery.OnQueryCompleteListener;
Santos Cordon99c8a6f2014-05-28 18:28:47 -070040
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);
Santos Cordon766d04f2014-05-06 10:28:25 -070066 void onSuccessfulIncomingCall(Call call, CallInfo callInfo);
67 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);
Santos Cordon766d04f2014-05-06 10:28:25 -070076 }
77
Santos Cordonfd71f4a2014-05-28 13:59:49 -070078 private static final OnQueryCompleteListener sCallerInfoQueryListener =
Santos Cordon99c8a6f2014-05-28 18:28:47 -070079 new OnQueryCompleteListener() {
80 /** ${inheritDoc} */
81 @Override
82 public void onQueryComplete(int token, Object cookie, CallerInfo callerInfo) {
83 if (cookie != null) {
84 ((Call) cookie).setCallerInfo(callerInfo, token);
85 }
Santos Cordonfd71f4a2014-05-28 13:59:49 -070086 }
Santos Cordon99c8a6f2014-05-28 18:28:47 -070087 };
88
89 private static final OnImageLoadCompleteListener sPhotoLoadListener =
90 new OnImageLoadCompleteListener() {
91 /** ${inheritDoc} */
92 @Override
93 public void onImageLoadComplete(
94 int token, Drawable photo, Bitmap photoIcon, Object cookie) {
95 if (cookie != null) {
96 ((Call) cookie).setPhoto(photo, photoIcon, token);
97 }
98 }
99 };
Ben Gilad0407fb22014-01-09 16:18:41 -0800100
Sailesh Nepal810735e2014-03-18 18:15:46 -0700101 /** True if this is an incoming call. */
102 private final boolean mIsIncoming;
103
Ben Gilad0407fb22014-01-09 16:18:41 -0800104 /**
105 * The time this call was created, typically also the time this call was added to the set
106 * of pending outgoing calls (mPendingOutgoingCalls) that's maintained by the switchboard.
107 * Beyond logging and such, may also be used for bookkeeping and specifically for marking
108 * certain call attempts as failed attempts.
109 */
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700110 private final long mCreationTimeMillis = System.currentTimeMillis();
111
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700112 /** The gateway information associated with this call. This stores the original call handle
113 * that the user is attempting to connect to via the gateway, the actual handle to dial in
114 * order to connect the call via the gateway, as well as the package name of the gateway
115 * service. */
116 private final GatewayInfo mGatewayInfo;
117
Santos Cordon2174fb52014-05-29 08:22:56 -0700118 private final Handler mHandler = new Handler();
119
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700120 private long mConnectTimeMillis;
Ben Gilad0407fb22014-01-09 16:18:41 -0800121
Santos Cordon61d0f702014-02-19 02:52:23 -0800122 /** The state of the call. */
123 private CallState mState;
124
125 /** The handle with which to establish this call. */
Sailesh Nepalce704b92014-03-17 18:31:43 -0700126 private Uri mHandle;
Santos Cordon61d0f702014-02-19 02:52:23 -0800127
Ben Gilad0407fb22014-01-09 16:18:41 -0800128 /**
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800129 * The call service which is attempted or already connecting this call.
Santos Cordon681663d2014-01-30 04:32:15 -0800130 */
Santos Cordonc195e362014-02-11 17:05:31 -0800131 private CallServiceWrapper mCallService;
Santos Cordon681663d2014-01-30 04:32:15 -0800132
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800133 /**
Ben Gilad61925612014-03-11 19:06:36 -0700134 * The set of call services that were attempted in the process of placing/switching this call
135 * but turned out unsuitable. Only used in the context of call switching.
136 */
137 private Set<CallServiceWrapper> mIncompatibleCallServices;
138
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700139 private boolean mIsEmergencyCall;
140
Sai Cheemalapatib7157e92014-06-11 17:51:55 -0700141 private boolean mSpeakerphoneOn;
142
Ben Gilad61925612014-03-11 19:06:36 -0700143 /**
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700144 * Disconnect cause for the call. Only valid if the state of the call is DISCONNECTED.
145 * See {@link android.telephony.DisconnectCause}.
146 */
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700147 private int mDisconnectCause = DisconnectCause.NOT_VALID;
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700148
149 /**
150 * Additional disconnect information provided by the call service.
151 */
152 private String mDisconnectMessage;
153
Sailesh Nepal84fa5f82014-04-02 11:01:11 -0700154 /** Info used by the call services. */
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700155 private Bundle mExtras = Bundle.EMPTY;
Sailesh Nepal84fa5f82014-04-02 11:01:11 -0700156
157 /** The Uri to dial to perform the handoff. If this is null then handoff is not supported. */
158 private Uri mHandoffHandle;
159
160 /**
161 * References the call that is being handed off. This value is non-null for untracked calls
162 * that are being used to perform a handoff.
163 */
164 private Call mOriginalCall;
165
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700166 /**
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700167 * The descriptor for the call service that this call is being switched to, null if handoff is
168 * not in progress.
169 */
170 private CallServiceDescriptor mHandoffCallServiceDescriptor;
171
Santos Cordon766d04f2014-05-06 10:28:25 -0700172 /** Set of listeners on this call. */
173 private Set<Listener> mListeners = Sets.newHashSet();
174
Santos Cordon682fe6b2014-05-20 08:56:39 -0700175 private OutgoingCallProcessor mOutgoingCallProcessor;
176
177 // TODO(santoscordon): The repositories should be changed into singleton types.
178 private CallServiceRepository mCallServiceRepository;
Santos Cordon682fe6b2014-05-20 08:56:39 -0700179
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700180 /** Caller information retrieved from the latest contact query. */
181 private CallerInfo mCallerInfo;
182
183 /** The latest token used with a contact info query. */
184 private int mQueryToken = 0;
185
Ihab Awadcb387ac2014-05-28 16:49:38 -0700186 /** Whether this call is requesting that Telecomm play the ringback tone on its behalf. */
187 private boolean mRequestingRingback = false;
188
Santos Cordon2174fb52014-05-29 08:22:56 -0700189 /** Incoming call-info to use when direct-to-voicemail query finishes. */
190 private CallInfo mPendingDirectToVoicemailCallInfo;
191
Santos Cordona1610702014-06-04 20:22:56 -0700192 private boolean mIsConferenceCapable = false;
193
194 private boolean mIsConference = false;
195
196 private Call mParentCall = null;
197
198 private List<Call> mChildCalls = new LinkedList<>();
199
Ihab Awadff7493a2014-06-10 13:47:44 -0700200 /** Set of text message responses allowed for this call, if applicable. */
201 private List<String> mCannedSmsResponses = Collections.EMPTY_LIST;
202
203 /** Whether an attempt has been made to load the text message responses. */
204 private boolean mCannedSmsResponsesLoadingStarted = false;
205
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700206 /**
Sailesh Nepale59bb192014-04-01 18:33:59 -0700207 * Creates an empty call object.
Sailesh Nepal810735e2014-03-18 18:15:46 -0700208 *
209 * @param isIncoming True if this is an incoming call.
Santos Cordon493e8f22014-02-19 03:15:12 -0800210 */
Santos Cordona1610702014-06-04 20:22:56 -0700211 Call(boolean isIncoming, boolean isConference) {
212 this(null, null, isIncoming, isConference);
Santos Cordon493e8f22014-02-19 03:15:12 -0800213 }
214
215 /**
Ben Gilad0407fb22014-01-09 16:18:41 -0800216 * Persists the specified parameters and initializes the new instance.
217 *
218 * @param handle The handle to dial.
Yorke Lee33501632014-03-17 19:24:12 -0700219 * @param gatewayInfo Gateway information to use for the call.
Sailesh Nepal810735e2014-03-18 18:15:46 -0700220 * @param isIncoming True if this is an incoming call.
Ben Gilad0407fb22014-01-09 16:18:41 -0800221 */
Santos Cordona1610702014-06-04 20:22:56 -0700222 Call(Uri handle, GatewayInfo gatewayInfo, boolean isIncoming, boolean isConference) {
223 mState = isConference ? CallState.ACTIVE : CallState.NEW;
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700224 setHandle(handle);
Yorke Lee33501632014-03-17 19:24:12 -0700225 mGatewayInfo = gatewayInfo;
Sailesh Nepal810735e2014-03-18 18:15:46 -0700226 mIsIncoming = isIncoming;
Santos Cordona1610702014-06-04 20:22:56 -0700227 mIsConference = isConference;
Ihab Awadff7493a2014-06-10 13:47:44 -0700228 maybeLoadCannedSmsResponses();
Ben Gilad0407fb22014-01-09 16:18:41 -0800229 }
230
Santos Cordon766d04f2014-05-06 10:28:25 -0700231 void addListener(Listener listener) {
232 mListeners.add(listener);
233 }
234
235 void removeListener(Listener listener) {
236 mListeners.remove(listener);
237 }
238
Santos Cordon61d0f702014-02-19 02:52:23 -0800239 /** {@inheritDoc} */
240 @Override public String toString() {
Sailesh Nepal4538f012014-04-15 11:40:33 -0700241 String component = null;
242 if (mCallService != null && mCallService.getComponentName() != null) {
243 component = mCallService.getComponentName().flattenToShortString();
244 }
245 return String.format(Locale.US, "[%s, %s, %s]", mState, component, Log.piiHandle(mHandle));
Santos Cordon61d0f702014-02-19 02:52:23 -0800246 }
247
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800248 CallState getState() {
Santos Cordona1610702014-06-04 20:22:56 -0700249 if (mIsConference) {
250 if (!mChildCalls.isEmpty()) {
251 // If we have child calls, just return the child call.
252 return mChildCalls.get(0).getState();
253 }
254 return CallState.ACTIVE;
255 } else {
256 return mState;
257 }
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800258 }
259
260 /**
261 * Sets the call state. Although there exists the notion of appropriate state transitions
262 * (see {@link CallState}), in practice those expectations break down when cellular systems
263 * misbehave and they do this very often. The result is that we do not enforce state transitions
264 * and instead keep the code resilient to unexpected state changes.
265 */
Sailesh Nepal810735e2014-03-18 18:15:46 -0700266 void setState(CallState newState) {
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700267 Preconditions.checkState(newState != CallState.DISCONNECTED ||
268 mDisconnectCause != DisconnectCause.NOT_VALID);
Sailesh Nepal810735e2014-03-18 18:15:46 -0700269 if (mState != newState) {
270 Log.v(this, "setState %s -> %s", mState, newState);
271 mState = newState;
Ihab Awadff7493a2014-06-10 13:47:44 -0700272 maybeLoadCannedSmsResponses();
Sailesh Nepal810735e2014-03-18 18:15:46 -0700273 }
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800274 }
275
Ihab Awadcb387ac2014-05-28 16:49:38 -0700276 void setRequestingRingback(boolean requestingRingback) {
277 mRequestingRingback = requestingRingback;
278 for (Listener l : mListeners) {
279 l.onRequestingRingback(this, mRequestingRingback);
280 }
281 }
282
283 boolean isRequestingRingback() {
284 return mRequestingRingback;
285 }
286
Sailesh Nepalce704b92014-03-17 18:31:43 -0700287 Uri getHandle() {
Ben Gilad0bf5b912014-01-28 17:55:57 -0800288 return mHandle;
289 }
290
Sailesh Nepalce704b92014-03-17 18:31:43 -0700291 void setHandle(Uri handle) {
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700292 if ((mHandle == null && handle != null) || (mHandle != null && !mHandle.equals(handle))) {
293 mHandle = handle;
294 mIsEmergencyCall = mHandle != null && PhoneNumberUtils.isLocalEmergencyNumber(
Yorke Lee66255452014-06-05 08:09:24 -0700295 TelecommApp.getInstance(), mHandle.getSchemeSpecificPart());
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700296 startCallerInfoLookup();
297 }
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700298 }
299
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700300 String getName() {
301 return mCallerInfo == null ? null : mCallerInfo.name;
302 }
303
304 Bitmap getPhotoIcon() {
305 return mCallerInfo == null ? null : mCallerInfo.cachedPhotoIcon;
306 }
307
308 Drawable getPhoto() {
309 return mCallerInfo == null ? null : mCallerInfo.cachedPhoto;
310 }
311
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700312 /**
313 * @param disconnectCause The reason for the disconnection, any of
314 * {@link android.telephony.DisconnectCause}.
315 * @param disconnectMessage Optional call-service-provided message about the disconnect.
316 */
317 void setDisconnectCause(int disconnectCause, String disconnectMessage) {
318 // TODO: Consider combining this method with a setDisconnected() method that is totally
319 // separate from setState.
320 mDisconnectCause = disconnectCause;
321 mDisconnectMessage = disconnectMessage;
322 }
323
324 int getDisconnectCause() {
325 return mDisconnectCause;
326 }
327
328 String getDisconnectMessage() {
329 return mDisconnectMessage;
330 }
331
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700332 boolean isEmergencyCall() {
333 return mIsEmergencyCall;
Santos Cordon61d0f702014-02-19 02:52:23 -0800334 }
335
Yorke Lee33501632014-03-17 19:24:12 -0700336 /**
337 * @return The original handle this call is associated with. In-call services should use this
338 * handle when indicating in their UI the handle that is being called.
339 */
340 public Uri getOriginalHandle() {
341 if (mGatewayInfo != null && !mGatewayInfo.isEmpty()) {
342 return mGatewayInfo.getOriginalHandle();
343 }
344 return getHandle();
345 }
346
347 GatewayInfo getGatewayInfo() {
348 return mGatewayInfo;
349 }
350
Sailesh Nepal810735e2014-03-18 18:15:46 -0700351 boolean isIncoming() {
352 return mIsIncoming;
353 }
354
Ben Gilad0407fb22014-01-09 16:18:41 -0800355 /**
356 * @return The "age" of this call object in milliseconds, which typically also represents the
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700357 * period since this call was added to the set pending outgoing calls, see
358 * mCreationTimeMillis.
Ben Gilad0407fb22014-01-09 16:18:41 -0800359 */
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700360 long getAgeMillis() {
361 return System.currentTimeMillis() - mCreationTimeMillis;
Ben Gilad0407fb22014-01-09 16:18:41 -0800362 }
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800363
Yorke Leef98fb572014-03-05 10:56:55 -0800364 /**
365 * @return The time when this call object was created and added to the set of pending outgoing
366 * calls.
367 */
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700368 long getCreationTimeMillis() {
369 return mCreationTimeMillis;
370 }
371
372 long getConnectTimeMillis() {
373 return mConnectTimeMillis;
374 }
375
376 void setConnectTimeMillis(long connectTimeMillis) {
377 mConnectTimeMillis = connectTimeMillis;
Yorke Leef98fb572014-03-05 10:56:55 -0800378 }
379
Santos Cordona1610702014-06-04 20:22:56 -0700380 boolean isConferenceCapable() {
381 return mIsConferenceCapable;
382 }
383
384 void setIsConferenceCapable(boolean isConferenceCapable) {
385 if (mIsConferenceCapable != isConferenceCapable) {
386 mIsConferenceCapable = isConferenceCapable;
387 for (Listener l : mListeners) {
388 l.onIsConferenceCapableChanged(this, mIsConferenceCapable);
389 }
390 }
391 }
392
393 Call getParentCall() {
394 return mParentCall;
395 }
396
397 List<Call> getChildCalls() {
398 return mChildCalls;
399 }
400
Santos Cordonc195e362014-02-11 17:05:31 -0800401 CallServiceWrapper getCallService() {
Santos Cordon681663d2014-01-30 04:32:15 -0800402 return mCallService;
403 }
404
Santos Cordonc195e362014-02-11 17:05:31 -0800405 void setCallService(CallServiceWrapper callService) {
Sailesh Nepal0e5410a2014-04-04 01:20:58 -0700406 setCallService(callService, null);
407 }
408
409 /**
410 * Changes the call service this call is associated with. If callToReplace is non-null then this
411 * call takes its place within the call service.
412 */
413 void setCallService(CallServiceWrapper callService, Call callToReplace) {
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800414 Preconditions.checkNotNull(callService);
415
Yorke Leeadee12d2014-03-13 12:08:30 -0700416 clearCallService();
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800417
418 callService.incrementAssociatedCallCount();
Santos Cordon681663d2014-01-30 04:32:15 -0800419 mCallService = callService;
Sailesh Nepal0e5410a2014-04-04 01:20:58 -0700420 if (callToReplace == null) {
421 mCallService.addCall(this);
422 } else {
423 mCallService.replaceCall(this, callToReplace);
424 }
Santos Cordon681663d2014-01-30 04:32:15 -0800425 }
426
427 /**
428 * Clears the associated call service.
429 */
430 void clearCallService() {
Yorke Leeadee12d2014-03-13 12:08:30 -0700431 if (mCallService != null) {
Santos Cordonc499c1c2014-04-14 17:13:14 -0700432 CallServiceWrapper callServiceTemp = mCallService;
Yorke Leeadee12d2014-03-13 12:08:30 -0700433 mCallService = null;
Santos Cordonc499c1c2014-04-14 17:13:14 -0700434 callServiceTemp.removeCall(this);
435
436 // Decrementing the count can cause the service to unbind, which itself can trigger the
437 // service-death code. Since the service death code tries to clean up any associated
438 // calls, we need to make sure to remove that information (e.g., removeCall()) before
439 // we decrement. Technically, invoking removeCall() prior to decrementing is all that is
440 // necessary, but cleaning up mCallService prior to triggering an unbind is good to do.
441 // If you change this, make sure to update {@link clearCallServiceSelector} as well.
442 decrementAssociatedCallCount(callServiceTemp);
Yorke Leeadee12d2014-03-13 12:08:30 -0700443 }
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800444 }
445
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800446 /**
Santos Cordon766d04f2014-05-06 10:28:25 -0700447 * Starts the incoming call flow through the switchboard. When switchboard completes, it will
448 * invoke handle[Un]SuccessfulIncomingCall.
449 *
450 * @param descriptor The relevant call-service descriptor.
451 * @param extras The optional extras passed via
452 * {@link TelecommConstants#EXTRA_INCOMING_CALL_EXTRAS}.
453 */
454 void startIncoming(CallServiceDescriptor descriptor, Bundle extras) {
455 Switchboard.getInstance().retrieveIncomingCall(this, descriptor, extras);
456 }
457
Santos Cordon2174fb52014-05-29 08:22:56 -0700458 /**
459 * Takes a verified incoming call and uses the handle to lookup direct-to-voicemail property
460 * from the contacts provider. The call is not yet exposed to the user at this point and
461 * the result of the query will determine if the call is rejected or passed through to the
462 * in-call UI.
463 */
464 void handleVerifiedIncoming(CallInfo callInfo) {
Santos Cordon766d04f2014-05-06 10:28:25 -0700465 Preconditions.checkState(callInfo.getState() == CallState.RINGING);
Santos Cordon2174fb52014-05-29 08:22:56 -0700466
467 // We do not handle incoming calls immediately when they are verified by the call service.
468 // We allow the caller-info-query code to execute first so that we can read the
469 // direct-to-voicemail property before deciding if we want to show the incoming call to the
470 // user or if we want to reject the call.
471 mPendingDirectToVoicemailCallInfo = callInfo;
472
473 // Setting the handle triggers the caller info lookup code.
Santos Cordon766d04f2014-05-06 10:28:25 -0700474 setHandle(callInfo.getHandle());
475
Santos Cordon2174fb52014-05-29 08:22:56 -0700476 // Timeout the direct-to-voicemail lookup execution so that we dont wait too long before
477 // showing the user the incoming call screen.
478 mHandler.postDelayed(new Runnable() {
479 @Override
480 public void run() {
481 processDirectToVoicemail();
482 }
Santos Cordona1610702014-06-04 20:22:56 -0700483 }, Timeouts.getDirectToVoicemailMillis());
Santos Cordon2174fb52014-05-29 08:22:56 -0700484 }
Santos Cordon766d04f2014-05-06 10:28:25 -0700485
Santos Cordon2174fb52014-05-29 08:22:56 -0700486 void processDirectToVoicemail() {
487 if (mPendingDirectToVoicemailCallInfo != null) {
488 if (mCallerInfo != null && mCallerInfo.shouldSendToVoicemail) {
489 Log.i(this, "Directing call to voicemail: %s.", this);
490 // TODO(santoscordon): Once we move State handling from CallsManager to Call, we
491 // will not need to set RINGING state prior to calling reject.
492 setState(CallState.RINGING);
Ihab Awadff7493a2014-06-10 13:47:44 -0700493 reject(false, null);
Santos Cordon2174fb52014-05-29 08:22:56 -0700494 } else {
495 // TODO(santoscordon): Make this class (not CallsManager) responsible for changing
496 // the call state to RINGING.
497
498 // TODO(santoscordon): Replace this with state transition to RINGING.
499 for (Listener l : mListeners) {
500 l.onSuccessfulIncomingCall(this, mPendingDirectToVoicemailCallInfo);
501 }
502 }
503
504 mPendingDirectToVoicemailCallInfo = null;
Santos Cordon766d04f2014-05-06 10:28:25 -0700505 }
506 }
507
508 void handleFailedIncoming() {
509 clearCallService();
510
511 // TODO: Needs more specific disconnect error for this case.
512 setDisconnectCause(DisconnectCause.ERROR_UNSPECIFIED, null);
513 setState(CallState.DISCONNECTED);
514
515 // TODO(santoscordon): Replace this with state transitions related to "connecting".
516 for (Listener l : mListeners) {
517 l.onFailedIncomingCall(this);
518 }
519 }
520
521 /**
Santos Cordon682fe6b2014-05-20 08:56:39 -0700522 * Starts the outgoing call sequence. Upon completion, there should exist an active connection
523 * through a call service (or the call will have failed).
Santos Cordon766d04f2014-05-06 10:28:25 -0700524 */
525 void startOutgoing() {
Santos Cordon682fe6b2014-05-20 08:56:39 -0700526 Preconditions.checkState(mOutgoingCallProcessor == null);
527
528 mOutgoingCallProcessor = new OutgoingCallProcessor(
Sailesh Nepal5a73b032014-06-25 15:53:21 -0700529 this, Switchboard.getInstance().getCallServiceRepository(), this);
Santos Cordon682fe6b2014-05-20 08:56:39 -0700530 mOutgoingCallProcessor.process();
Santos Cordon766d04f2014-05-06 10:28:25 -0700531 }
532
Sailesh Nepal5a73b032014-06-25 15:53:21 -0700533 @Override
534 public void onOutgoingCallSuccess() {
Santos Cordon766d04f2014-05-06 10:28:25 -0700535 // TODO(santoscordon): Replace this with state transitions related to "connecting".
536 for (Listener l : mListeners) {
537 l.onSuccessfulOutgoingCall(this);
538 }
Sailesh Nepal5a73b032014-06-25 15:53:21 -0700539 mOutgoingCallProcessor = null;
Santos Cordon766d04f2014-05-06 10:28:25 -0700540 }
541
Sailesh Nepal5a73b032014-06-25 15:53:21 -0700542 @Override
543 public void onOutgoingCallFailure(int code, String msg) {
Santos Cordon766d04f2014-05-06 10:28:25 -0700544 // TODO(santoscordon): Replace this with state transitions related to "connecting".
545 for (Listener l : mListeners) {
Sailesh Nepal5a73b032014-06-25 15:53:21 -0700546 l.onFailedOutgoingCall(this, code, msg);
Santos Cordon766d04f2014-05-06 10:28:25 -0700547 }
Santos Cordon682fe6b2014-05-20 08:56:39 -0700548
549 clearCallService();
Sailesh Nepal5a73b032014-06-25 15:53:21 -0700550 mOutgoingCallProcessor = null;
551 }
552
553 @Override
554 public void onOutgoingCallCancel() {
555 // TODO(santoscordon): Replace this with state transitions related to "connecting".
556 for (Listener l : mListeners) {
557 l.onCancelledOutgoingCall(this);
558 }
559
560 clearCallService();
561 mOutgoingCallProcessor = null;
Santos Cordon766d04f2014-05-06 10:28:25 -0700562 }
563
564 /**
Ben Gilad61925612014-03-11 19:06:36 -0700565 * Adds the specified call service to the list of incompatible services. The set is used when
566 * attempting to switch a phone call between call services such that incompatible services can
567 * be avoided.
568 *
569 * @param callService The incompatible call service.
570 */
571 void addIncompatibleCallService(CallServiceWrapper callService) {
572 if (mIncompatibleCallServices == null) {
573 mIncompatibleCallServices = Sets.newHashSet();
574 }
575 mIncompatibleCallServices.add(callService);
576 }
577
578 /**
579 * Checks whether or not the specified callService was identified as incompatible in the
580 * context of this call.
581 *
582 * @param callService The call service to evaluate.
583 * @return True upon incompatible call services and false otherwise.
584 */
585 boolean isIncompatibleCallService(CallServiceWrapper callService) {
586 return mIncompatibleCallServices != null &&
587 mIncompatibleCallServices.contains(callService);
588 }
589
590 /**
Ihab Awad74549ec2014-03-10 15:33:25 -0700591 * Plays the specified DTMF tone.
592 */
593 void playDtmfTone(char digit) {
594 if (mCallService == null) {
595 Log.w(this, "playDtmfTone() request on a call without a call service.");
596 } else {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700597 Log.i(this, "Send playDtmfTone to call service for call %s", this);
598 mCallService.playDtmfTone(this, digit);
Ihab Awad74549ec2014-03-10 15:33:25 -0700599 }
600 }
601
602 /**
603 * Stops playing any currently playing DTMF tone.
604 */
605 void stopDtmfTone() {
606 if (mCallService == null) {
607 Log.w(this, "stopDtmfTone() request on a call without a call service.");
608 } else {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700609 Log.i(this, "Send stopDtmfTone to call service for call %s", this);
610 mCallService.stopDtmfTone(this);
Ihab Awad74549ec2014-03-10 15:33:25 -0700611 }
612 }
613
614 /**
Santos Cordon049b7b62014-01-30 05:34:26 -0800615 * Attempts to disconnect the call through the call service.
616 */
617 void disconnect() {
Santos Cordon766d04f2014-05-06 10:28:25 -0700618 if (mState == CallState.NEW) {
Santos Cordon682fe6b2014-05-20 08:56:39 -0700619 Log.v(this, "Aborting call %s", this);
620 abort();
Santos Cordon74d420b2014-05-07 14:38:47 -0700621 } else if (mState != CallState.ABORTED && mState != CallState.DISCONNECTED) {
Santos Cordon766d04f2014-05-06 10:28:25 -0700622 Preconditions.checkNotNull(mCallService);
623
Sailesh Nepale59bb192014-04-01 18:33:59 -0700624 Log.i(this, "Send disconnect to call service for call: %s", this);
Santos Cordonc195e362014-02-11 17:05:31 -0800625 // The call isn't officially disconnected until the call service confirms that the call
626 // was actually disconnected. Only then is the association between call and call service
627 // severed, see {@link CallsManager#markCallAsDisconnected}.
Sailesh Nepale59bb192014-04-01 18:33:59 -0700628 mCallService.disconnect(this);
Santos Cordon049b7b62014-01-30 05:34:26 -0800629 }
630 }
631
Santos Cordon682fe6b2014-05-20 08:56:39 -0700632 void abort() {
633 if (mOutgoingCallProcessor != null) {
634 mOutgoingCallProcessor.abort();
635 }
636 }
637
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800638 /**
Santos Cordon61d0f702014-02-19 02:52:23 -0800639 * Answers the call if it is ringing.
640 */
641 void answer() {
642 Preconditions.checkNotNull(mCallService);
643
644 // Check to verify that the call is still in the ringing state. A call can change states
645 // between the time the user hits 'answer' and Telecomm receives the command.
646 if (isRinging("answer")) {
647 // At this point, we are asking the call service to answer but we don't assume that
648 // it will work. Instead, we wait until confirmation from the call service that the
649 // call is in a non-RINGING state before changing the UI. See
650 // {@link CallServiceAdapter#setActive} and other set* methods.
Sailesh Nepale59bb192014-04-01 18:33:59 -0700651 mCallService.answer(this);
Santos Cordon61d0f702014-02-19 02:52:23 -0800652 }
653 }
654
655 /**
656 * Rejects the call if it is ringing.
Ihab Awadff7493a2014-06-10 13:47:44 -0700657 *
658 * @param rejectWithMessage Whether to send a text message as part of the call rejection.
659 * @param textMessage An optional text message to send as part of the rejection.
Santos Cordon61d0f702014-02-19 02:52:23 -0800660 */
Ihab Awadff7493a2014-06-10 13:47:44 -0700661 void reject(boolean rejectWithMessage, String textMessage) {
Santos Cordon61d0f702014-02-19 02:52:23 -0800662 Preconditions.checkNotNull(mCallService);
663
664 // Check to verify that the call is still in the ringing state. A call can change states
665 // between the time the user hits 'reject' and Telecomm receives the command.
666 if (isRinging("reject")) {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700667 mCallService.reject(this);
Santos Cordon61d0f702014-02-19 02:52:23 -0800668 }
669 }
670
671 /**
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700672 * Puts the call on hold if it is currently active.
673 */
674 void hold() {
675 Preconditions.checkNotNull(mCallService);
676
677 if (mState == CallState.ACTIVE) {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700678 mCallService.hold(this);
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700679 }
680 }
681
682 /**
683 * Releases the call from hold if it is currently active.
684 */
685 void unhold() {
686 Preconditions.checkNotNull(mCallService);
687
688 if (mState == CallState.ON_HOLD) {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700689 mCallService.unhold(this);
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700690 }
691 }
692
693 /**
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800694 * @return An object containing read-only information about this call.
695 */
Sailesh Nepale59bb192014-04-01 18:33:59 -0700696 CallInfo toCallInfo(String callId) {
Sailesh Nepal84fa5f82014-04-02 11:01:11 -0700697 CallServiceDescriptor descriptor = null;
698 if (mCallService != null) {
699 descriptor = mCallService.getDescriptor();
700 } else if (mOriginalCall != null && mOriginalCall.mCallService != null) {
701 descriptor = mOriginalCall.mCallService.getDescriptor();
702 }
Santos Cordon571f0732014-06-25 18:13:15 -0700703 Bundle extras = mExtras;
704 if (mGatewayInfo != null && mGatewayInfo.getGatewayProviderPackageName() != null &&
705 mGatewayInfo.getOriginalHandle() != null) {
706 extras = (Bundle) mExtras.clone();
707 extras.putString(
708 NewOutgoingCallIntentBroadcaster.EXTRA_GATEWAY_PROVIDER_PACKAGE,
709 mGatewayInfo.getGatewayProviderPackageName());
710 extras.putParcelable(
711 NewOutgoingCallIntentBroadcaster.EXTRA_GATEWAY_ORIGINAL_URI,
712 mGatewayInfo.getOriginalHandle());
713
714 }
715 return new CallInfo(callId, mState, mHandle, mGatewayInfo, extras, descriptor);
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800716 }
717
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700718 /** Checks if this is a live call or not. */
719 boolean isAlive() {
720 switch (mState) {
721 case NEW:
722 case RINGING:
723 case DISCONNECTED:
724 case ABORTED:
725 return false;
726 default:
727 return true;
728 }
729 }
730
Santos Cordon40f78c22014-04-07 02:11:42 -0700731 boolean isActive() {
732 switch (mState) {
733 case ACTIVE:
734 case POST_DIAL:
735 case POST_DIAL_WAIT:
736 return true;
737 default:
738 return false;
739 }
740 }
741
Sailesh Nepal84fa5f82014-04-02 11:01:11 -0700742 Bundle getExtras() {
743 return mExtras;
744 }
745
746 void setExtras(Bundle extras) {
747 mExtras = extras;
748 }
749
750 Uri getHandoffHandle() {
751 return mHandoffHandle;
752 }
753
754 void setHandoffHandle(Uri handoffHandle) {
755 mHandoffHandle = handoffHandle;
756 }
757
758 Call getOriginalCall() {
759 return mOriginalCall;
760 }
761
762 void setOriginalCall(Call originalCall) {
763 mOriginalCall = originalCall;
764 }
765
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700766 CallServiceDescriptor getHandoffCallServiceDescriptor() {
767 return mHandoffCallServiceDescriptor;
768 }
769
770 void setHandoffCallServiceDescriptor(CallServiceDescriptor descriptor) {
771 mHandoffCallServiceDescriptor = descriptor;
772 }
773
Santos Cordon5ba7f272014-05-28 13:59:49 -0700774 Uri getRingtone() {
775 return mCallerInfo == null ? null : mCallerInfo.contactRingtoneUri;
776 }
777
Evan Charlton352105c2014-06-03 14:10:54 -0700778 void onPostDialWait(String remaining) {
779 for (Listener l : mListeners) {
780 l.onPostDialWait(this, remaining);
781 }
782 }
783
784 void postDialContinue(boolean proceed) {
785 getCallService().onPostDialContinue(this, proceed);
786 }
787
Santos Cordona1610702014-06-04 20:22:56 -0700788 void conferenceInto(Call conferenceCall) {
789 if (mCallService == null) {
790 Log.w(this, "conference requested on a call without a call service.");
791 } else {
792 mCallService.conference(conferenceCall, this);
793 }
794 }
795
796 void expireConference() {
797 // The conference call expired before we got a confirmation of the conference from the
798 // call service...so start shutting down.
799 clearCallService();
800 for (Listener l : mListeners) {
801 l.onExpiredConferenceCall(this);
802 }
803 }
804
805 void confirmConference() {
806 Log.v(this, "confirming Conf call %s", mListeners);
807 for (Listener l : mListeners) {
808 l.onConfirmedConferenceCall(this);
809 }
810 }
811
812 void splitFromConference() {
813 // TODO(santoscordon): todo
814 }
815
816 void setParentCall(Call parentCall) {
817 if (parentCall == this) {
818 Log.e(this, new Exception(), "setting the parent to self");
819 return;
820 }
821 Preconditions.checkState(parentCall == null || mParentCall == null);
822
823 Call oldParent = mParentCall;
824 if (mParentCall != null) {
825 mParentCall.removeChildCall(this);
826 }
827 mParentCall = parentCall;
828 if (mParentCall != null) {
829 mParentCall.addChildCall(this);
830 }
831
832 for (Listener l : mListeners) {
833 l.onParentChanged(this);
834 }
835 }
836
837 private void addChildCall(Call call) {
838 if (!mChildCalls.contains(call)) {
839 mChildCalls.add(call);
840
841 for (Listener l : mListeners) {
842 l.onChildrenChanged(this);
843 }
844 }
845 }
846
847 private void removeChildCall(Call call) {
848 if (mChildCalls.remove(call)) {
849 for (Listener l : mListeners) {
850 l.onChildrenChanged(this);
851 }
852 }
853 }
854
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800855 /**
Ihab Awadff7493a2014-06-10 13:47:44 -0700856 * Return whether the user can respond to this {@code Call} via an SMS message.
857 *
858 * @return true if the "Respond via SMS" feature should be enabled
859 * for this incoming call.
860 *
861 * The general rule is that we *do* allow "Respond via SMS" except for
862 * the few (relatively rare) cases where we know for sure it won't
863 * work, namely:
864 * - a bogus or blank incoming number
865 * - a call from a SIP address
866 * - a "call presentation" that doesn't allow the number to be revealed
867 *
868 * In all other cases, we allow the user to respond via SMS.
869 *
870 * Note that this behavior isn't perfect; for example we have no way
871 * to detect whether the incoming call is from a landline (with most
872 * networks at least), so we still enable this feature even though
873 * SMSes to that number will silently fail.
874 */
875 boolean isRespondViaSmsCapable() {
876 if (mState != CallState.RINGING) {
877 return false;
878 }
879
880 if (getHandle() == null) {
881 // No incoming number known or call presentation is "PRESENTATION_RESTRICTED", in
882 // other words, the user should not be able to see the incoming phone number.
883 return false;
884 }
885
886 if (PhoneNumberUtils.isUriNumber(getHandle().toString())) {
887 // The incoming number is actually a URI (i.e. a SIP address),
888 // not a regular PSTN phone number, and we can't send SMSes to
889 // SIP addresses.
890 // (TODO: That might still be possible eventually, though. Is
891 // there some SIP-specific equivalent to sending a text message?)
892 return false;
893 }
894
895 // Is there a valid SMS application on the phone?
896 if (SmsApplication.getDefaultRespondViaMessageApplication(TelecommApp.getInstance(),
897 true /*updateIfNeeded*/) == null) {
898 return false;
899 }
900
901 // TODO: with some carriers (in certain countries) you *can* actually
902 // tell whether a given number is a mobile phone or not. So in that
903 // case we could potentially return false here if the incoming call is
904 // from a land line.
905
906 // If none of the above special cases apply, it's OK to enable the
907 // "Respond via SMS" feature.
908 return true;
909 }
910
911 List<String> getCannedSmsResponses() {
912 return mCannedSmsResponses;
913 }
914
915 /**
Santos Cordon61d0f702014-02-19 02:52:23 -0800916 * @return True if the call is ringing, else logs the action name.
917 */
918 private boolean isRinging(String actionName) {
919 if (mState == CallState.RINGING) {
920 return true;
921 }
922
Sailesh Nepalf1c191d2014-03-07 18:17:39 -0800923 Log.i(this, "Request to %s a non-ringing call %s", actionName, this);
Santos Cordon61d0f702014-02-19 02:52:23 -0800924 return false;
925 }
926
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800927 @SuppressWarnings("rawtypes")
928 private void decrementAssociatedCallCount(ServiceBinder binder) {
929 if (binder != null) {
930 binder.decrementAssociatedCallCount();
931 }
932 }
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700933
934 /**
935 * Looks up contact information based on the current handle.
936 */
937 private void startCallerInfoLookup() {
938 String number = mHandle == null ? null : mHandle.getSchemeSpecificPart();
939
940 mQueryToken++; // Updated so that previous queries can no longer set the information.
941 mCallerInfo = null;
942 if (!TextUtils.isEmpty(number)) {
Santos Cordon5ba7f272014-05-28 13:59:49 -0700943 Log.v(this, "Looking up information for: %s.", Log.piiHandle(number));
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700944 CallerInfoAsyncQuery.startQuery(
945 mQueryToken,
946 TelecommApp.getInstance(),
947 number,
948 sCallerInfoQueryListener,
949 this);
950 }
951 }
952
953 /**
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700954 * Saves the specified caller info if the specified token matches that of the last query
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700955 * that was made.
956 *
957 * @param callerInfo The new caller information to set.
958 * @param token The token used with this query.
959 */
960 private void setCallerInfo(CallerInfo callerInfo, int token) {
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700961 Preconditions.checkNotNull(callerInfo);
962
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700963 if (mQueryToken == token) {
964 mCallerInfo = callerInfo;
Santos Cordon5ba7f272014-05-28 13:59:49 -0700965 Log.i(this, "CallerInfo received for %s: %s", Log.piiHandle(mHandle), callerInfo);
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700966
967 if (mCallerInfo.person_id != 0) {
968 Uri personUri =
969 ContentUris.withAppendedId(Contacts.CONTENT_URI, mCallerInfo.person_id);
970 Log.d(this, "Searching person uri %s for call %s", personUri, this);
971 ContactsAsyncHelper.startObtainPhotoAsync(
972 token,
973 TelecommApp.getInstance(),
974 personUri,
975 sPhotoLoadListener,
976 this);
977 }
Santos Cordon2174fb52014-05-29 08:22:56 -0700978
979 processDirectToVoicemail();
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700980 }
981 }
982
983 /**
984 * Saves the specified photo information if the specified token matches that of the last query.
985 *
986 * @param photo The photo as a drawable.
987 * @param photoIcon The photo as a small icon.
988 * @param token The token used with this query.
989 */
990 private void setPhoto(Drawable photo, Bitmap photoIcon, int token) {
991 if (mQueryToken == token) {
992 mCallerInfo.cachedPhoto = photo;
993 mCallerInfo.cachedPhotoIcon = photoIcon;
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700994 }
995 }
Ihab Awadff7493a2014-06-10 13:47:44 -0700996
997 private void maybeLoadCannedSmsResponses() {
998 if (mIsIncoming && isRespondViaSmsCapable() && !mCannedSmsResponsesLoadingStarted) {
999 Log.d(this, "maybeLoadCannedSmsResponses: starting task to load messages");
1000 mCannedSmsResponsesLoadingStarted = true;
1001 RespondViaSmsManager.getInstance().loadCannedTextMessages(
1002 new Response<Void, List<String>>() {
1003 @Override
1004 public void onResult(Void request, List<String>... result) {
1005 if (result.length > 0) {
1006 Log.d(this, "maybeLoadCannedSmsResponses: got %s", result[0]);
1007 mCannedSmsResponses = result[0];
1008 for (Listener l : mListeners) {
1009 l.onCannedSmsResponsesLoaded(Call.this);
1010 }
1011 }
1012 }
1013
1014 @Override
1015 public void onError(Void request, int code, String msg) {
1016 Log.w(Call.this, "Error obtaining canned SMS responses: %d %s", code,
1017 msg);
1018 }
1019 }
1020 );
1021 } else {
1022 Log.d(this, "maybeLoadCannedSmsResponses: doing nothing");
1023 }
1024 }
Sai Cheemalapatib7157e92014-06-11 17:51:55 -07001025
1026 /**
1027 * Sets speakerphone option on when call begins.
1028 */
1029 public void setStartWithSpeakerphoneOn(boolean startWithSpeakerphone) {
1030 mSpeakerphoneOn = startWithSpeakerphone;
1031 }
1032
1033 /**
1034 * Returns speakerphone option.
1035 *
1036 * @return Whether or not speakerphone should be set automatically when call begins.
1037 */
1038 public boolean getStartWithSpeakerphoneOn() {
1039 return mSpeakerphoneOn;
1040 }
Andrew Leee9a77652014-06-26 13:07:57 -07001041
1042 /**
1043 * Sets a call video provider for the call.
1044 */
1045 public void setCallVideoProvider() {
1046 //TODO: Implement this method. For now, it's just an empty stub.
1047 }
Ben Gilad9f2bed32013-12-12 17:43:26 -08001048}