blob: 79e78796f121a35962fed16055d296121333dcda [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 */
Sailesh Nepal5a73b032014-06-25 15:53:21 -070056final class Call implements OutgoingCallResponse {
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);
Sailesh Nepal5a73b032014-06-25 15:53:21 -070063 void onFailedOutgoingCall(Call call, int errorCode, String errorMsg);
64 void onCancelledOutgoingCall(Call call);
Santos Cordon766d04f2014-05-06 10:28:25 -070065 void onSuccessfulIncomingCall(Call call, CallInfo callInfo);
66 void onFailedIncomingCall(Call call);
Ihab Awadcb387ac2014-05-28 16:49:38 -070067 void onRequestingRingback(Call call, boolean requestingRingback);
Evan Charlton352105c2014-06-03 14:10:54 -070068 void onPostDialWait(Call call, String remaining);
Santos Cordona1610702014-06-04 20:22:56 -070069 void onIsConferenceCapableChanged(Call call, boolean isConferenceCapable);
70 void onExpiredConferenceCall(Call call);
71 void onConfirmedConferenceCall(Call call);
72 void onParentChanged(Call call);
73 void onChildrenChanged(Call call);
Ihab Awadff7493a2014-06-10 13:47:44 -070074 void onCannedSmsResponsesLoaded(Call call);
Santos Cordon766d04f2014-05-06 10:28:25 -070075 }
76
Santos Cordonfd71f4a2014-05-28 13:59:49 -070077 private static final OnQueryCompleteListener sCallerInfoQueryListener =
Santos Cordon99c8a6f2014-05-28 18:28:47 -070078 new OnQueryCompleteListener() {
79 /** ${inheritDoc} */
80 @Override
81 public void onQueryComplete(int token, Object cookie, CallerInfo callerInfo) {
82 if (cookie != null) {
83 ((Call) cookie).setCallerInfo(callerInfo, token);
84 }
Santos Cordonfd71f4a2014-05-28 13:59:49 -070085 }
Santos Cordon99c8a6f2014-05-28 18:28:47 -070086 };
87
88 private static final OnImageLoadCompleteListener sPhotoLoadListener =
89 new OnImageLoadCompleteListener() {
90 /** ${inheritDoc} */
91 @Override
92 public void onImageLoadComplete(
93 int token, Drawable photo, Bitmap photoIcon, Object cookie) {
94 if (cookie != null) {
95 ((Call) cookie).setPhoto(photo, photoIcon, token);
96 }
97 }
98 };
Ben Gilad0407fb22014-01-09 16:18:41 -080099
Sailesh Nepal810735e2014-03-18 18:15:46 -0700100 /** True if this is an incoming call. */
101 private final boolean mIsIncoming;
102
Ben Gilad0407fb22014-01-09 16:18:41 -0800103 /**
104 * The time this call was created, typically also the time this call was added to the set
105 * of pending outgoing calls (mPendingOutgoingCalls) that's maintained by the switchboard.
106 * Beyond logging and such, may also be used for bookkeeping and specifically for marking
107 * certain call attempts as failed attempts.
108 */
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700109 private final long mCreationTimeMillis = System.currentTimeMillis();
110
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700111 /** The gateway information associated with this call. This stores the original call handle
112 * that the user is attempting to connect to via the gateway, the actual handle to dial in
113 * order to connect the call via the gateway, as well as the package name of the gateway
114 * service. */
115 private final GatewayInfo mGatewayInfo;
116
Santos Cordon2174fb52014-05-29 08:22:56 -0700117 private final Handler mHandler = new Handler();
118
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700119 private long mConnectTimeMillis;
Ben Gilad0407fb22014-01-09 16:18:41 -0800120
Santos Cordon61d0f702014-02-19 02:52:23 -0800121 /** The state of the call. */
122 private CallState mState;
123
124 /** The handle with which to establish this call. */
Sailesh Nepalce704b92014-03-17 18:31:43 -0700125 private Uri mHandle;
Santos Cordon61d0f702014-02-19 02:52:23 -0800126
Ben Gilad0407fb22014-01-09 16:18:41 -0800127 /**
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800128 * The call service which is attempted or already connecting this call.
Santos Cordon681663d2014-01-30 04:32:15 -0800129 */
Santos Cordonc195e362014-02-11 17:05:31 -0800130 private CallServiceWrapper mCallService;
Santos Cordon681663d2014-01-30 04:32:15 -0800131
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800132 /**
Ben Gilad61925612014-03-11 19:06:36 -0700133 * The set of call services that were attempted in the process of placing/switching this call
134 * but turned out unsuitable. Only used in the context of call switching.
135 */
136 private Set<CallServiceWrapper> mIncompatibleCallServices;
137
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700138 private boolean mIsEmergencyCall;
139
Sai Cheemalapatib7157e92014-06-11 17:51:55 -0700140 private boolean mSpeakerphoneOn;
141
Ben Gilad61925612014-03-11 19:06:36 -0700142 /**
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700143 * Disconnect cause for the call. Only valid if the state of the call is DISCONNECTED.
144 * See {@link android.telephony.DisconnectCause}.
145 */
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700146 private int mDisconnectCause = DisconnectCause.NOT_VALID;
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700147
148 /**
149 * Additional disconnect information provided by the call service.
150 */
151 private String mDisconnectMessage;
152
Sailesh Nepal84fa5f82014-04-02 11:01:11 -0700153 /** Info used by the call services. */
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700154 private Bundle mExtras = Bundle.EMPTY;
Sailesh Nepal84fa5f82014-04-02 11:01:11 -0700155
156 /** The Uri to dial to perform the handoff. If this is null then handoff is not supported. */
157 private Uri mHandoffHandle;
158
159 /**
160 * References the call that is being handed off. This value is non-null for untracked calls
161 * that are being used to perform a handoff.
162 */
163 private Call mOriginalCall;
164
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700165 /**
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700166 * The descriptor for the call service that this call is being switched to, null if handoff is
167 * not in progress.
168 */
169 private CallServiceDescriptor mHandoffCallServiceDescriptor;
170
Santos Cordon766d04f2014-05-06 10:28:25 -0700171 /** Set of listeners on this call. */
172 private Set<Listener> mListeners = Sets.newHashSet();
173
Santos Cordon682fe6b2014-05-20 08:56:39 -0700174 private OutgoingCallProcessor mOutgoingCallProcessor;
175
176 // TODO(santoscordon): The repositories should be changed into singleton types.
177 private CallServiceRepository mCallServiceRepository;
Santos Cordon682fe6b2014-05-20 08:56:39 -0700178
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700179 /** Caller information retrieved from the latest contact query. */
180 private CallerInfo mCallerInfo;
181
182 /** The latest token used with a contact info query. */
183 private int mQueryToken = 0;
184
Ihab Awadcb387ac2014-05-28 16:49:38 -0700185 /** Whether this call is requesting that Telecomm play the ringback tone on its behalf. */
186 private boolean mRequestingRingback = false;
187
Santos Cordon2174fb52014-05-29 08:22:56 -0700188 /** Incoming call-info to use when direct-to-voicemail query finishes. */
189 private CallInfo mPendingDirectToVoicemailCallInfo;
190
Santos Cordona1610702014-06-04 20:22:56 -0700191 private boolean mIsConferenceCapable = false;
192
193 private boolean mIsConference = false;
194
195 private Call mParentCall = null;
196
197 private List<Call> mChildCalls = new LinkedList<>();
198
Ihab Awadff7493a2014-06-10 13:47:44 -0700199 /** Set of text message responses allowed for this call, if applicable. */
200 private List<String> mCannedSmsResponses = Collections.EMPTY_LIST;
201
202 /** Whether an attempt has been made to load the text message responses. */
203 private boolean mCannedSmsResponsesLoadingStarted = false;
204
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700205 /**
Sailesh Nepale59bb192014-04-01 18:33:59 -0700206 * Creates an empty call object.
Sailesh Nepal810735e2014-03-18 18:15:46 -0700207 *
208 * @param isIncoming True if this is an incoming call.
Santos Cordon493e8f22014-02-19 03:15:12 -0800209 */
Santos Cordona1610702014-06-04 20:22:56 -0700210 Call(boolean isIncoming, boolean isConference) {
211 this(null, null, isIncoming, isConference);
Santos Cordon493e8f22014-02-19 03:15:12 -0800212 }
213
214 /**
Ben Gilad0407fb22014-01-09 16:18:41 -0800215 * Persists the specified parameters and initializes the new instance.
216 *
217 * @param handle The handle to dial.
Yorke Lee33501632014-03-17 19:24:12 -0700218 * @param gatewayInfo Gateway information to use for the call.
Sailesh Nepal810735e2014-03-18 18:15:46 -0700219 * @param isIncoming True if this is an incoming call.
Ben Gilad0407fb22014-01-09 16:18:41 -0800220 */
Santos Cordona1610702014-06-04 20:22:56 -0700221 Call(Uri handle, GatewayInfo gatewayInfo, boolean isIncoming, boolean isConference) {
222 mState = isConference ? CallState.ACTIVE : CallState.NEW;
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700223 setHandle(handle);
Yorke Lee33501632014-03-17 19:24:12 -0700224 mGatewayInfo = gatewayInfo;
Sailesh Nepal810735e2014-03-18 18:15:46 -0700225 mIsIncoming = isIncoming;
Santos Cordona1610702014-06-04 20:22:56 -0700226 mIsConference = isConference;
Ihab Awadff7493a2014-06-10 13:47:44 -0700227 maybeLoadCannedSmsResponses();
Ben Gilad0407fb22014-01-09 16:18:41 -0800228 }
229
Santos Cordon766d04f2014-05-06 10:28:25 -0700230 void addListener(Listener listener) {
231 mListeners.add(listener);
232 }
233
234 void removeListener(Listener listener) {
235 mListeners.remove(listener);
236 }
237
Santos Cordon61d0f702014-02-19 02:52:23 -0800238 /** {@inheritDoc} */
239 @Override public String toString() {
Sailesh Nepal4538f012014-04-15 11:40:33 -0700240 String component = null;
241 if (mCallService != null && mCallService.getComponentName() != null) {
242 component = mCallService.getComponentName().flattenToShortString();
243 }
244 return String.format(Locale.US, "[%s, %s, %s]", mState, component, Log.piiHandle(mHandle));
Santos Cordon61d0f702014-02-19 02:52:23 -0800245 }
246
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800247 CallState getState() {
Santos Cordona1610702014-06-04 20:22:56 -0700248 if (mIsConference) {
249 if (!mChildCalls.isEmpty()) {
250 // If we have child calls, just return the child call.
251 return mChildCalls.get(0).getState();
252 }
253 return CallState.ACTIVE;
254 } else {
255 return mState;
256 }
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800257 }
258
259 /**
260 * Sets the call state. Although there exists the notion of appropriate state transitions
261 * (see {@link CallState}), in practice those expectations break down when cellular systems
262 * misbehave and they do this very often. The result is that we do not enforce state transitions
263 * and instead keep the code resilient to unexpected state changes.
264 */
Sailesh Nepal810735e2014-03-18 18:15:46 -0700265 void setState(CallState newState) {
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700266 Preconditions.checkState(newState != CallState.DISCONNECTED ||
267 mDisconnectCause != DisconnectCause.NOT_VALID);
Sailesh Nepal810735e2014-03-18 18:15:46 -0700268 if (mState != newState) {
269 Log.v(this, "setState %s -> %s", mState, newState);
270 mState = newState;
Ihab Awadff7493a2014-06-10 13:47:44 -0700271 maybeLoadCannedSmsResponses();
Sailesh Nepal810735e2014-03-18 18:15:46 -0700272 }
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800273 }
274
Ihab Awadcb387ac2014-05-28 16:49:38 -0700275 void setRequestingRingback(boolean requestingRingback) {
276 mRequestingRingback = requestingRingback;
277 for (Listener l : mListeners) {
278 l.onRequestingRingback(this, mRequestingRingback);
279 }
280 }
281
282 boolean isRequestingRingback() {
283 return mRequestingRingback;
284 }
285
Sailesh Nepalce704b92014-03-17 18:31:43 -0700286 Uri getHandle() {
Ben Gilad0bf5b912014-01-28 17:55:57 -0800287 return mHandle;
288 }
289
Sailesh Nepalce704b92014-03-17 18:31:43 -0700290 void setHandle(Uri handle) {
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700291 if ((mHandle == null && handle != null) || (mHandle != null && !mHandle.equals(handle))) {
292 mHandle = handle;
293 mIsEmergencyCall = mHandle != null && PhoneNumberUtils.isLocalEmergencyNumber(
Yorke Lee66255452014-06-05 08:09:24 -0700294 TelecommApp.getInstance(), mHandle.getSchemeSpecificPart());
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700295 startCallerInfoLookup();
296 }
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700297 }
298
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700299 String getName() {
300 return mCallerInfo == null ? null : mCallerInfo.name;
301 }
302
303 Bitmap getPhotoIcon() {
304 return mCallerInfo == null ? null : mCallerInfo.cachedPhotoIcon;
305 }
306
307 Drawable getPhoto() {
308 return mCallerInfo == null ? null : mCallerInfo.cachedPhoto;
309 }
310
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700311 /**
312 * @param disconnectCause The reason for the disconnection, any of
313 * {@link android.telephony.DisconnectCause}.
314 * @param disconnectMessage Optional call-service-provided message about the disconnect.
315 */
316 void setDisconnectCause(int disconnectCause, String disconnectMessage) {
317 // TODO: Consider combining this method with a setDisconnected() method that is totally
318 // separate from setState.
319 mDisconnectCause = disconnectCause;
320 mDisconnectMessage = disconnectMessage;
321 }
322
323 int getDisconnectCause() {
324 return mDisconnectCause;
325 }
326
327 String getDisconnectMessage() {
328 return mDisconnectMessage;
329 }
330
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700331 boolean isEmergencyCall() {
332 return mIsEmergencyCall;
Santos Cordon61d0f702014-02-19 02:52:23 -0800333 }
334
Yorke Lee33501632014-03-17 19:24:12 -0700335 /**
336 * @return The original handle this call is associated with. In-call services should use this
337 * handle when indicating in their UI the handle that is being called.
338 */
339 public Uri getOriginalHandle() {
340 if (mGatewayInfo != null && !mGatewayInfo.isEmpty()) {
341 return mGatewayInfo.getOriginalHandle();
342 }
343 return getHandle();
344 }
345
346 GatewayInfo getGatewayInfo() {
347 return mGatewayInfo;
348 }
349
Sailesh Nepal810735e2014-03-18 18:15:46 -0700350 boolean isIncoming() {
351 return mIsIncoming;
352 }
353
Ben Gilad0407fb22014-01-09 16:18:41 -0800354 /**
355 * @return The "age" of this call object in milliseconds, which typically also represents the
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700356 * period since this call was added to the set pending outgoing calls, see
357 * mCreationTimeMillis.
Ben Gilad0407fb22014-01-09 16:18:41 -0800358 */
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700359 long getAgeMillis() {
360 return System.currentTimeMillis() - mCreationTimeMillis;
Ben Gilad0407fb22014-01-09 16:18:41 -0800361 }
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800362
Yorke Leef98fb572014-03-05 10:56:55 -0800363 /**
364 * @return The time when this call object was created and added to the set of pending outgoing
365 * calls.
366 */
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700367 long getCreationTimeMillis() {
368 return mCreationTimeMillis;
369 }
370
371 long getConnectTimeMillis() {
372 return mConnectTimeMillis;
373 }
374
375 void setConnectTimeMillis(long connectTimeMillis) {
376 mConnectTimeMillis = connectTimeMillis;
Yorke Leef98fb572014-03-05 10:56:55 -0800377 }
378
Santos Cordona1610702014-06-04 20:22:56 -0700379 boolean isConferenceCapable() {
380 return mIsConferenceCapable;
381 }
382
383 void setIsConferenceCapable(boolean isConferenceCapable) {
384 if (mIsConferenceCapable != isConferenceCapable) {
385 mIsConferenceCapable = isConferenceCapable;
386 for (Listener l : mListeners) {
387 l.onIsConferenceCapableChanged(this, mIsConferenceCapable);
388 }
389 }
390 }
391
392 Call getParentCall() {
393 return mParentCall;
394 }
395
396 List<Call> getChildCalls() {
397 return mChildCalls;
398 }
399
Santos Cordonc195e362014-02-11 17:05:31 -0800400 CallServiceWrapper getCallService() {
Santos Cordon681663d2014-01-30 04:32:15 -0800401 return mCallService;
402 }
403
Santos Cordonc195e362014-02-11 17:05:31 -0800404 void setCallService(CallServiceWrapper callService) {
Sailesh Nepal0e5410a2014-04-04 01:20:58 -0700405 setCallService(callService, null);
406 }
407
408 /**
409 * Changes the call service this call is associated with. If callToReplace is non-null then this
410 * call takes its place within the call service.
411 */
412 void setCallService(CallServiceWrapper callService, Call callToReplace) {
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800413 Preconditions.checkNotNull(callService);
414
Yorke Leeadee12d2014-03-13 12:08:30 -0700415 clearCallService();
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800416
417 callService.incrementAssociatedCallCount();
Santos Cordon681663d2014-01-30 04:32:15 -0800418 mCallService = callService;
Sailesh Nepal0e5410a2014-04-04 01:20:58 -0700419 if (callToReplace == null) {
420 mCallService.addCall(this);
421 } else {
422 mCallService.replaceCall(this, callToReplace);
423 }
Santos Cordon681663d2014-01-30 04:32:15 -0800424 }
425
426 /**
427 * Clears the associated call service.
428 */
429 void clearCallService() {
Yorke Leeadee12d2014-03-13 12:08:30 -0700430 if (mCallService != null) {
Santos Cordonc499c1c2014-04-14 17:13:14 -0700431 CallServiceWrapper callServiceTemp = mCallService;
Yorke Leeadee12d2014-03-13 12:08:30 -0700432 mCallService = null;
Santos Cordonc499c1c2014-04-14 17:13:14 -0700433 callServiceTemp.removeCall(this);
434
435 // Decrementing the count can cause the service to unbind, which itself can trigger the
436 // service-death code. Since the service death code tries to clean up any associated
437 // calls, we need to make sure to remove that information (e.g., removeCall()) before
438 // we decrement. Technically, invoking removeCall() prior to decrementing is all that is
439 // necessary, but cleaning up mCallService prior to triggering an unbind is good to do.
440 // If you change this, make sure to update {@link clearCallServiceSelector} as well.
441 decrementAssociatedCallCount(callServiceTemp);
Yorke Leeadee12d2014-03-13 12:08:30 -0700442 }
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800443 }
444
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800445 /**
Santos Cordon766d04f2014-05-06 10:28:25 -0700446 * Starts the incoming call flow through the switchboard. When switchboard completes, it will
447 * invoke handle[Un]SuccessfulIncomingCall.
448 *
449 * @param descriptor The relevant call-service descriptor.
450 * @param extras The optional extras passed via
451 * {@link TelecommConstants#EXTRA_INCOMING_CALL_EXTRAS}.
452 */
453 void startIncoming(CallServiceDescriptor descriptor, Bundle extras) {
454 Switchboard.getInstance().retrieveIncomingCall(this, descriptor, extras);
455 }
456
Santos Cordon2174fb52014-05-29 08:22:56 -0700457 /**
458 * Takes a verified incoming call and uses the handle to lookup direct-to-voicemail property
459 * from the contacts provider. The call is not yet exposed to the user at this point and
460 * the result of the query will determine if the call is rejected or passed through to the
461 * in-call UI.
462 */
463 void handleVerifiedIncoming(CallInfo callInfo) {
Santos Cordon766d04f2014-05-06 10:28:25 -0700464 Preconditions.checkState(callInfo.getState() == CallState.RINGING);
Santos Cordon2174fb52014-05-29 08:22:56 -0700465
466 // We do not handle incoming calls immediately when they are verified by the call service.
467 // We allow the caller-info-query code to execute first so that we can read the
468 // direct-to-voicemail property before deciding if we want to show the incoming call to the
469 // user or if we want to reject the call.
470 mPendingDirectToVoicemailCallInfo = callInfo;
471
472 // Setting the handle triggers the caller info lookup code.
Santos Cordon766d04f2014-05-06 10:28:25 -0700473 setHandle(callInfo.getHandle());
474
Santos Cordon2174fb52014-05-29 08:22:56 -0700475 // Timeout the direct-to-voicemail lookup execution so that we dont wait too long before
476 // showing the user the incoming call screen.
477 mHandler.postDelayed(new Runnable() {
478 @Override
479 public void run() {
480 processDirectToVoicemail();
481 }
Santos Cordona1610702014-06-04 20:22:56 -0700482 }, Timeouts.getDirectToVoicemailMillis());
Santos Cordon2174fb52014-05-29 08:22:56 -0700483 }
Santos Cordon766d04f2014-05-06 10:28:25 -0700484
Santos Cordon2174fb52014-05-29 08:22:56 -0700485 void processDirectToVoicemail() {
486 if (mPendingDirectToVoicemailCallInfo != null) {
487 if (mCallerInfo != null && mCallerInfo.shouldSendToVoicemail) {
488 Log.i(this, "Directing call to voicemail: %s.", this);
489 // TODO(santoscordon): Once we move State handling from CallsManager to Call, we
490 // will not need to set RINGING state prior to calling reject.
491 setState(CallState.RINGING);
Ihab Awadff7493a2014-06-10 13:47:44 -0700492 reject(false, null);
Santos Cordon2174fb52014-05-29 08:22:56 -0700493 } else {
494 // TODO(santoscordon): Make this class (not CallsManager) responsible for changing
495 // the call state to RINGING.
496
497 // TODO(santoscordon): Replace this with state transition to RINGING.
498 for (Listener l : mListeners) {
499 l.onSuccessfulIncomingCall(this, mPendingDirectToVoicemailCallInfo);
500 }
501 }
502
503 mPendingDirectToVoicemailCallInfo = null;
Santos Cordon766d04f2014-05-06 10:28:25 -0700504 }
505 }
506
507 void handleFailedIncoming() {
508 clearCallService();
509
510 // TODO: Needs more specific disconnect error for this case.
511 setDisconnectCause(DisconnectCause.ERROR_UNSPECIFIED, null);
512 setState(CallState.DISCONNECTED);
513
514 // TODO(santoscordon): Replace this with state transitions related to "connecting".
515 for (Listener l : mListeners) {
516 l.onFailedIncomingCall(this);
517 }
518 }
519
520 /**
Santos Cordon682fe6b2014-05-20 08:56:39 -0700521 * Starts the outgoing call sequence. Upon completion, there should exist an active connection
522 * through a call service (or the call will have failed).
Santos Cordon766d04f2014-05-06 10:28:25 -0700523 */
524 void startOutgoing() {
Santos Cordon682fe6b2014-05-20 08:56:39 -0700525 Preconditions.checkState(mOutgoingCallProcessor == null);
526
527 mOutgoingCallProcessor = new OutgoingCallProcessor(
Sailesh Nepal5a73b032014-06-25 15:53:21 -0700528 this, Switchboard.getInstance().getCallServiceRepository(), this);
Santos Cordon682fe6b2014-05-20 08:56:39 -0700529 mOutgoingCallProcessor.process();
Santos Cordon766d04f2014-05-06 10:28:25 -0700530 }
531
Sailesh Nepal5a73b032014-06-25 15:53:21 -0700532 @Override
533 public void onOutgoingCallSuccess() {
Santos Cordon766d04f2014-05-06 10:28:25 -0700534 // TODO(santoscordon): Replace this with state transitions related to "connecting".
535 for (Listener l : mListeners) {
536 l.onSuccessfulOutgoingCall(this);
537 }
Sailesh Nepal5a73b032014-06-25 15:53:21 -0700538 mOutgoingCallProcessor = null;
Santos Cordon766d04f2014-05-06 10:28:25 -0700539 }
540
Sailesh Nepal5a73b032014-06-25 15:53:21 -0700541 @Override
542 public void onOutgoingCallFailure(int code, String msg) {
Santos Cordon766d04f2014-05-06 10:28:25 -0700543 // TODO(santoscordon): Replace this with state transitions related to "connecting".
544 for (Listener l : mListeners) {
Sailesh Nepal5a73b032014-06-25 15:53:21 -0700545 l.onFailedOutgoingCall(this, code, msg);
Santos Cordon766d04f2014-05-06 10:28:25 -0700546 }
Santos Cordon682fe6b2014-05-20 08:56:39 -0700547
548 clearCallService();
Sailesh Nepal5a73b032014-06-25 15:53:21 -0700549 mOutgoingCallProcessor = null;
550 }
551
552 @Override
553 public void onOutgoingCallCancel() {
554 // TODO(santoscordon): Replace this with state transitions related to "connecting".
555 for (Listener l : mListeners) {
556 l.onCancelledOutgoingCall(this);
557 }
558
559 clearCallService();
560 mOutgoingCallProcessor = null;
Santos Cordon766d04f2014-05-06 10:28:25 -0700561 }
562
563 /**
Ben Gilad61925612014-03-11 19:06:36 -0700564 * Adds the specified call service to the list of incompatible services. The set is used when
565 * attempting to switch a phone call between call services such that incompatible services can
566 * be avoided.
567 *
568 * @param callService The incompatible call service.
569 */
570 void addIncompatibleCallService(CallServiceWrapper callService) {
571 if (mIncompatibleCallServices == null) {
572 mIncompatibleCallServices = Sets.newHashSet();
573 }
574 mIncompatibleCallServices.add(callService);
575 }
576
577 /**
578 * Checks whether or not the specified callService was identified as incompatible in the
579 * context of this call.
580 *
581 * @param callService The call service to evaluate.
582 * @return True upon incompatible call services and false otherwise.
583 */
584 boolean isIncompatibleCallService(CallServiceWrapper callService) {
585 return mIncompatibleCallServices != null &&
586 mIncompatibleCallServices.contains(callService);
587 }
588
589 /**
Ihab Awad74549ec2014-03-10 15:33:25 -0700590 * Plays the specified DTMF tone.
591 */
592 void playDtmfTone(char digit) {
593 if (mCallService == null) {
594 Log.w(this, "playDtmfTone() request on a call without a call service.");
595 } else {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700596 Log.i(this, "Send playDtmfTone to call service for call %s", this);
597 mCallService.playDtmfTone(this, digit);
Ihab Awad74549ec2014-03-10 15:33:25 -0700598 }
599 }
600
601 /**
602 * Stops playing any currently playing DTMF tone.
603 */
604 void stopDtmfTone() {
605 if (mCallService == null) {
606 Log.w(this, "stopDtmfTone() request on a call without a call service.");
607 } else {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700608 Log.i(this, "Send stopDtmfTone to call service for call %s", this);
609 mCallService.stopDtmfTone(this);
Ihab Awad74549ec2014-03-10 15:33:25 -0700610 }
611 }
612
613 /**
Santos Cordon049b7b62014-01-30 05:34:26 -0800614 * Attempts to disconnect the call through the call service.
615 */
616 void disconnect() {
Santos Cordon766d04f2014-05-06 10:28:25 -0700617 if (mState == CallState.NEW) {
Santos Cordon682fe6b2014-05-20 08:56:39 -0700618 Log.v(this, "Aborting call %s", this);
619 abort();
Santos Cordon74d420b2014-05-07 14:38:47 -0700620 } else if (mState != CallState.ABORTED && mState != CallState.DISCONNECTED) {
Santos Cordon766d04f2014-05-06 10:28:25 -0700621 Preconditions.checkNotNull(mCallService);
622
Sailesh Nepale59bb192014-04-01 18:33:59 -0700623 Log.i(this, "Send disconnect to call service for call: %s", this);
Santos Cordonc195e362014-02-11 17:05:31 -0800624 // The call isn't officially disconnected until the call service confirms that the call
625 // was actually disconnected. Only then is the association between call and call service
626 // severed, see {@link CallsManager#markCallAsDisconnected}.
Sailesh Nepale59bb192014-04-01 18:33:59 -0700627 mCallService.disconnect(this);
Santos Cordon049b7b62014-01-30 05:34:26 -0800628 }
629 }
630
Santos Cordon682fe6b2014-05-20 08:56:39 -0700631 void abort() {
632 if (mOutgoingCallProcessor != null) {
633 mOutgoingCallProcessor.abort();
634 }
635 }
636
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800637 /**
Santos Cordon61d0f702014-02-19 02:52:23 -0800638 * Answers the call if it is ringing.
639 */
640 void answer() {
641 Preconditions.checkNotNull(mCallService);
642
643 // Check to verify that the call is still in the ringing state. A call can change states
644 // between the time the user hits 'answer' and Telecomm receives the command.
645 if (isRinging("answer")) {
646 // At this point, we are asking the call service to answer but we don't assume that
647 // it will work. Instead, we wait until confirmation from the call service that the
648 // call is in a non-RINGING state before changing the UI. See
649 // {@link CallServiceAdapter#setActive} and other set* methods.
Sailesh Nepale59bb192014-04-01 18:33:59 -0700650 mCallService.answer(this);
Santos Cordon61d0f702014-02-19 02:52:23 -0800651 }
652 }
653
654 /**
655 * Rejects the call if it is ringing.
Ihab Awadff7493a2014-06-10 13:47:44 -0700656 *
657 * @param rejectWithMessage Whether to send a text message as part of the call rejection.
658 * @param textMessage An optional text message to send as part of the rejection.
Santos Cordon61d0f702014-02-19 02:52:23 -0800659 */
Ihab Awadff7493a2014-06-10 13:47:44 -0700660 void reject(boolean rejectWithMessage, String textMessage) {
Santos Cordon61d0f702014-02-19 02:52:23 -0800661 Preconditions.checkNotNull(mCallService);
662
663 // Check to verify that the call is still in the ringing state. A call can change states
664 // between the time the user hits 'reject' and Telecomm receives the command.
665 if (isRinging("reject")) {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700666 mCallService.reject(this);
Santos Cordon61d0f702014-02-19 02:52:23 -0800667 }
668 }
669
670 /**
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700671 * Puts the call on hold if it is currently active.
672 */
673 void hold() {
674 Preconditions.checkNotNull(mCallService);
675
676 if (mState == CallState.ACTIVE) {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700677 mCallService.hold(this);
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700678 }
679 }
680
681 /**
682 * Releases the call from hold if it is currently active.
683 */
684 void unhold() {
685 Preconditions.checkNotNull(mCallService);
686
687 if (mState == CallState.ON_HOLD) {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700688 mCallService.unhold(this);
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700689 }
690 }
691
692 /**
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800693 * @return An object containing read-only information about this call.
694 */
Sailesh Nepale59bb192014-04-01 18:33:59 -0700695 CallInfo toCallInfo(String callId) {
Sailesh Nepal84fa5f82014-04-02 11:01:11 -0700696 CallServiceDescriptor descriptor = null;
697 if (mCallService != null) {
698 descriptor = mCallService.getDescriptor();
699 } else if (mOriginalCall != null && mOriginalCall.mCallService != null) {
700 descriptor = mOriginalCall.mCallService.getDescriptor();
701 }
Santos Cordon571f0732014-06-25 18:13:15 -0700702 Bundle extras = mExtras;
703 if (mGatewayInfo != null && mGatewayInfo.getGatewayProviderPackageName() != null &&
704 mGatewayInfo.getOriginalHandle() != null) {
705 extras = (Bundle) mExtras.clone();
706 extras.putString(
707 NewOutgoingCallIntentBroadcaster.EXTRA_GATEWAY_PROVIDER_PACKAGE,
708 mGatewayInfo.getGatewayProviderPackageName());
709 extras.putParcelable(
710 NewOutgoingCallIntentBroadcaster.EXTRA_GATEWAY_ORIGINAL_URI,
711 mGatewayInfo.getOriginalHandle());
712
713 }
714 return new CallInfo(callId, mState, mHandle, mGatewayInfo, extras, descriptor);
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800715 }
716
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700717 /** Checks if this is a live call or not. */
718 boolean isAlive() {
719 switch (mState) {
720 case NEW:
721 case RINGING:
722 case DISCONNECTED:
723 case ABORTED:
724 return false;
725 default:
726 return true;
727 }
728 }
729
Santos Cordon40f78c22014-04-07 02:11:42 -0700730 boolean isActive() {
731 switch (mState) {
732 case ACTIVE:
733 case POST_DIAL:
734 case POST_DIAL_WAIT:
735 return true;
736 default:
737 return false;
738 }
739 }
740
Sailesh Nepal84fa5f82014-04-02 11:01:11 -0700741 Bundle getExtras() {
742 return mExtras;
743 }
744
745 void setExtras(Bundle extras) {
746 mExtras = extras;
747 }
748
749 Uri getHandoffHandle() {
750 return mHandoffHandle;
751 }
752
753 void setHandoffHandle(Uri handoffHandle) {
754 mHandoffHandle = handoffHandle;
755 }
756
757 Call getOriginalCall() {
758 return mOriginalCall;
759 }
760
761 void setOriginalCall(Call originalCall) {
762 mOriginalCall = originalCall;
763 }
764
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700765 CallServiceDescriptor getHandoffCallServiceDescriptor() {
766 return mHandoffCallServiceDescriptor;
767 }
768
769 void setHandoffCallServiceDescriptor(CallServiceDescriptor descriptor) {
770 mHandoffCallServiceDescriptor = descriptor;
771 }
772
Santos Cordon5ba7f272014-05-28 13:59:49 -0700773 Uri getRingtone() {
774 return mCallerInfo == null ? null : mCallerInfo.contactRingtoneUri;
775 }
776
Evan Charlton352105c2014-06-03 14:10:54 -0700777 void onPostDialWait(String remaining) {
778 for (Listener l : mListeners) {
779 l.onPostDialWait(this, remaining);
780 }
781 }
782
783 void postDialContinue(boolean proceed) {
784 getCallService().onPostDialContinue(this, proceed);
785 }
786
Santos Cordona1610702014-06-04 20:22:56 -0700787 void conferenceInto(Call conferenceCall) {
788 if (mCallService == null) {
789 Log.w(this, "conference requested on a call without a call service.");
790 } else {
791 mCallService.conference(conferenceCall, this);
792 }
793 }
794
795 void expireConference() {
796 // The conference call expired before we got a confirmation of the conference from the
797 // call service...so start shutting down.
798 clearCallService();
799 for (Listener l : mListeners) {
800 l.onExpiredConferenceCall(this);
801 }
802 }
803
804 void confirmConference() {
805 Log.v(this, "confirming Conf call %s", mListeners);
806 for (Listener l : mListeners) {
807 l.onConfirmedConferenceCall(this);
808 }
809 }
810
811 void splitFromConference() {
812 // TODO(santoscordon): todo
813 }
814
815 void setParentCall(Call parentCall) {
816 if (parentCall == this) {
817 Log.e(this, new Exception(), "setting the parent to self");
818 return;
819 }
820 Preconditions.checkState(parentCall == null || mParentCall == null);
821
822 Call oldParent = mParentCall;
823 if (mParentCall != null) {
824 mParentCall.removeChildCall(this);
825 }
826 mParentCall = parentCall;
827 if (mParentCall != null) {
828 mParentCall.addChildCall(this);
829 }
830
831 for (Listener l : mListeners) {
832 l.onParentChanged(this);
833 }
834 }
835
836 private void addChildCall(Call call) {
837 if (!mChildCalls.contains(call)) {
838 mChildCalls.add(call);
839
840 for (Listener l : mListeners) {
841 l.onChildrenChanged(this);
842 }
843 }
844 }
845
846 private void removeChildCall(Call call) {
847 if (mChildCalls.remove(call)) {
848 for (Listener l : mListeners) {
849 l.onChildrenChanged(this);
850 }
851 }
852 }
853
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800854 /**
Ihab Awadff7493a2014-06-10 13:47:44 -0700855 * Return whether the user can respond to this {@code Call} via an SMS message.
856 *
857 * @return true if the "Respond via SMS" feature should be enabled
858 * for this incoming call.
859 *
860 * The general rule is that we *do* allow "Respond via SMS" except for
861 * the few (relatively rare) cases where we know for sure it won't
862 * work, namely:
863 * - a bogus or blank incoming number
864 * - a call from a SIP address
865 * - a "call presentation" that doesn't allow the number to be revealed
866 *
867 * In all other cases, we allow the user to respond via SMS.
868 *
869 * Note that this behavior isn't perfect; for example we have no way
870 * to detect whether the incoming call is from a landline (with most
871 * networks at least), so we still enable this feature even though
872 * SMSes to that number will silently fail.
873 */
874 boolean isRespondViaSmsCapable() {
875 if (mState != CallState.RINGING) {
876 return false;
877 }
878
879 if (getHandle() == null) {
880 // No incoming number known or call presentation is "PRESENTATION_RESTRICTED", in
881 // other words, the user should not be able to see the incoming phone number.
882 return false;
883 }
884
885 if (PhoneNumberUtils.isUriNumber(getHandle().toString())) {
886 // The incoming number is actually a URI (i.e. a SIP address),
887 // not a regular PSTN phone number, and we can't send SMSes to
888 // SIP addresses.
889 // (TODO: That might still be possible eventually, though. Is
890 // there some SIP-specific equivalent to sending a text message?)
891 return false;
892 }
893
894 // Is there a valid SMS application on the phone?
895 if (SmsApplication.getDefaultRespondViaMessageApplication(TelecommApp.getInstance(),
896 true /*updateIfNeeded*/) == null) {
897 return false;
898 }
899
900 // TODO: with some carriers (in certain countries) you *can* actually
901 // tell whether a given number is a mobile phone or not. So in that
902 // case we could potentially return false here if the incoming call is
903 // from a land line.
904
905 // If none of the above special cases apply, it's OK to enable the
906 // "Respond via SMS" feature.
907 return true;
908 }
909
910 List<String> getCannedSmsResponses() {
911 return mCannedSmsResponses;
912 }
913
914 /**
Santos Cordon61d0f702014-02-19 02:52:23 -0800915 * @return True if the call is ringing, else logs the action name.
916 */
917 private boolean isRinging(String actionName) {
918 if (mState == CallState.RINGING) {
919 return true;
920 }
921
Sailesh Nepalf1c191d2014-03-07 18:17:39 -0800922 Log.i(this, "Request to %s a non-ringing call %s", actionName, this);
Santos Cordon61d0f702014-02-19 02:52:23 -0800923 return false;
924 }
925
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800926 @SuppressWarnings("rawtypes")
927 private void decrementAssociatedCallCount(ServiceBinder binder) {
928 if (binder != null) {
929 binder.decrementAssociatedCallCount();
930 }
931 }
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700932
933 /**
934 * Looks up contact information based on the current handle.
935 */
936 private void startCallerInfoLookup() {
937 String number = mHandle == null ? null : mHandle.getSchemeSpecificPart();
938
939 mQueryToken++; // Updated so that previous queries can no longer set the information.
940 mCallerInfo = null;
941 if (!TextUtils.isEmpty(number)) {
Santos Cordon5ba7f272014-05-28 13:59:49 -0700942 Log.v(this, "Looking up information for: %s.", Log.piiHandle(number));
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700943 CallerInfoAsyncQuery.startQuery(
944 mQueryToken,
945 TelecommApp.getInstance(),
946 number,
947 sCallerInfoQueryListener,
948 this);
949 }
950 }
951
952 /**
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700953 * Saves the specified caller info if the specified token matches that of the last query
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700954 * that was made.
955 *
956 * @param callerInfo The new caller information to set.
957 * @param token The token used with this query.
958 */
959 private void setCallerInfo(CallerInfo callerInfo, int token) {
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700960 Preconditions.checkNotNull(callerInfo);
961
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700962 if (mQueryToken == token) {
963 mCallerInfo = callerInfo;
Santos Cordon5ba7f272014-05-28 13:59:49 -0700964 Log.i(this, "CallerInfo received for %s: %s", Log.piiHandle(mHandle), callerInfo);
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700965
966 if (mCallerInfo.person_id != 0) {
967 Uri personUri =
968 ContentUris.withAppendedId(Contacts.CONTENT_URI, mCallerInfo.person_id);
969 Log.d(this, "Searching person uri %s for call %s", personUri, this);
970 ContactsAsyncHelper.startObtainPhotoAsync(
971 token,
972 TelecommApp.getInstance(),
973 personUri,
974 sPhotoLoadListener,
975 this);
976 }
Santos Cordon2174fb52014-05-29 08:22:56 -0700977
978 processDirectToVoicemail();
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700979 }
980 }
981
982 /**
983 * Saves the specified photo information if the specified token matches that of the last query.
984 *
985 * @param photo The photo as a drawable.
986 * @param photoIcon The photo as a small icon.
987 * @param token The token used with this query.
988 */
989 private void setPhoto(Drawable photo, Bitmap photoIcon, int token) {
990 if (mQueryToken == token) {
991 mCallerInfo.cachedPhoto = photo;
992 mCallerInfo.cachedPhotoIcon = photoIcon;
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700993 }
994 }
Ihab Awadff7493a2014-06-10 13:47:44 -0700995
996 private void maybeLoadCannedSmsResponses() {
997 if (mIsIncoming && isRespondViaSmsCapable() && !mCannedSmsResponsesLoadingStarted) {
998 Log.d(this, "maybeLoadCannedSmsResponses: starting task to load messages");
999 mCannedSmsResponsesLoadingStarted = true;
1000 RespondViaSmsManager.getInstance().loadCannedTextMessages(
1001 new Response<Void, List<String>>() {
1002 @Override
1003 public void onResult(Void request, List<String>... result) {
1004 if (result.length > 0) {
1005 Log.d(this, "maybeLoadCannedSmsResponses: got %s", result[0]);
1006 mCannedSmsResponses = result[0];
1007 for (Listener l : mListeners) {
1008 l.onCannedSmsResponsesLoaded(Call.this);
1009 }
1010 }
1011 }
1012
1013 @Override
1014 public void onError(Void request, int code, String msg) {
1015 Log.w(Call.this, "Error obtaining canned SMS responses: %d %s", code,
1016 msg);
1017 }
1018 }
1019 );
1020 } else {
1021 Log.d(this, "maybeLoadCannedSmsResponses: doing nothing");
1022 }
1023 }
Sai Cheemalapatib7157e92014-06-11 17:51:55 -07001024
1025 /**
1026 * Sets speakerphone option on when call begins.
1027 */
1028 public void setStartWithSpeakerphoneOn(boolean startWithSpeakerphone) {
1029 mSpeakerphoneOn = startWithSpeakerphone;
1030 }
1031
1032 /**
1033 * Returns speakerphone option.
1034 *
1035 * @return Whether or not speakerphone should be set automatically when call begins.
1036 */
1037 public boolean getStartWithSpeakerphoneOn() {
1038 return mSpeakerphoneOn;
1039 }
Ben Gilad9f2bed32013-12-12 17:43:26 -08001040}