blob: 27a995ad53586f778758f89a3812e58bb56fb068 [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.graphics.Bitmap;
20import android.graphics.drawable.Drawable;
Sailesh Nepalce704b92014-03-17 18:31:43 -070021import android.net.Uri;
Sailesh Nepal84fa5f82014-04-02 11:01:11 -070022import android.os.Bundle;
Santos Cordonfd71f4a2014-05-28 13:59:49 -070023import android.os.Handler;
Sailesh Nepale8ecb982014-07-11 17:19:42 -070024import android.telecomm.CallPropertyPresentation;
Santos Cordon0b03b4b2014-01-29 18:01:59 -080025import android.telecomm.CallState;
Sailesh Nepalc92c4362014-07-04 18:33:21 -070026import android.telecomm.ConnectionRequest;
Yorke Lee33501632014-03-17 19:24:12 -070027import android.telecomm.GatewayInfo;
Ihab Awad98a55602014-06-30 21:27:28 -070028import android.telecomm.PhoneAccount;
Ihab Awadff7493a2014-06-10 13:47:44 -070029import android.telecomm.Response;
Sailesh Nepal35faf8c2014-07-08 22:02:34 -070030import android.telecomm.StatusHints;
Santos Cordon79ff2bc2014-03-27 15:31:27 -070031import android.telephony.DisconnectCause;
Sailesh Nepal6aca10a2014-03-24 16:11:02 -070032import android.telephony.PhoneNumberUtils;
Santos Cordonfd71f4a2014-05-28 13:59:49 -070033import android.text.TextUtils;
Santos Cordon0b03b4b2014-01-29 18:01:59 -080034
Nancy Chena65d41f2014-06-24 12:06:03 -070035import com.android.internal.telecomm.ICallVideoProvider;
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;
Ihab Awadff7493a2014-06-10 13:47:44 -070039import com.android.internal.telephony.SmsApplication;
Santos Cordon99c8a6f2014-05-28 18:28:47 -070040import com.android.telecomm.ContactsAsyncHelper.OnImageLoadCompleteListener;
Santos Cordon61d0f702014-02-19 02:52:23 -080041import com.google.common.base.Preconditions;
42
Ihab Awadff7493a2014-06-10 13:47:44 -070043import java.util.Collections;
Santos Cordona1610702014-06-04 20:22:56 -070044import java.util.LinkedList;
45import java.util.List;
Sailesh Nepal91990782014-03-08 17:45:52 -080046import java.util.Locale;
Sailesh Nepale8ecb982014-07-11 17:19:42 -070047import java.util.Objects;
Ben Gilad61925612014-03-11 19:06:36 -070048import java.util.Set;
Santos Cordondfc66012014-07-15 13:41:40 -070049import java.util.concurrent.CopyOnWriteArraySet;
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 Nepal664837f2014-07-14 16:31:51 -070056final class Call implements CreateConnectionResponse {
Santos Cordon766d04f2014-05-06 10:28:25 -070057 /**
58 * Listener for events on the call.
59 */
60 interface Listener {
61 void onSuccessfulOutgoingCall(Call call);
Sailesh Nepal5a73b032014-06-25 15:53:21 -070062 void onFailedOutgoingCall(Call call, int errorCode, String errorMsg);
63 void onCancelledOutgoingCall(Call call);
Sailesh Nepalc92c4362014-07-04 18:33:21 -070064 void onSuccessfulIncomingCall(Call call);
Santos Cordon766d04f2014-05-06 10:28:25 -070065 void onFailedIncomingCall(Call call);
Ihab Awadcb387ac2014-05-28 16:49:38 -070066 void onRequestingRingback(Call call, boolean requestingRingback);
Evan Charlton352105c2014-06-03 14:10:54 -070067 void onPostDialWait(Call call, String remaining);
Sailesh Nepale20bc972014-07-09 21:22:36 -070068 void onCallCapabilitiesChanged(Call call);
Santos Cordona1610702014-06-04 20:22:56 -070069 void onExpiredConferenceCall(Call call);
70 void onConfirmedConferenceCall(Call call);
71 void onParentChanged(Call call);
72 void onChildrenChanged(Call call);
Ihab Awadff7493a2014-06-10 13:47:44 -070073 void onCannedSmsResponsesLoaded(Call call);
Nancy Chena65d41f2014-06-24 12:06:03 -070074 void onCallVideoProviderChanged(Call call);
Santos Cordon64c7e962014-07-02 15:15:27 -070075 void onCallerInfoChanged(Call call);
Sailesh Nepal7e669572014-07-08 21:29:12 -070076 void onAudioModeIsVoipChanged(Call call);
Sailesh Nepal35faf8c2014-07-08 22:02:34 -070077 void onStatusHintsChanged(Call call);
Sailesh Nepale8ecb982014-07-11 17:19:42 -070078 void onHandleChanged(Call call);
79 void onCallerDisplayNameChanged(Call call);
Andrew Lee4a796602014-07-11 17:23:03 -070080 void onVideoStateChanged(Call call);
Santos Cordon64c7e962014-07-02 15:15:27 -070081 }
82
83 abstract static class ListenerBase implements Listener {
84 @Override
85 public void onSuccessfulOutgoingCall(Call call) {}
86 @Override
87 public void onFailedOutgoingCall(Call call, int errorCode, String errorMsg) {}
88 @Override
89 public void onCancelledOutgoingCall(Call call) {}
90 @Override
Sailesh Nepalc92c4362014-07-04 18:33:21 -070091 public void onSuccessfulIncomingCall(Call call) {}
Santos Cordon64c7e962014-07-02 15:15:27 -070092 @Override
93 public void onFailedIncomingCall(Call call) {}
94 @Override
95 public void onRequestingRingback(Call call, boolean requestingRingback) {}
96 @Override
97 public void onPostDialWait(Call call, String remaining) {}
98 @Override
Sailesh Nepale20bc972014-07-09 21:22:36 -070099 public void onCallCapabilitiesChanged(Call call) {}
Santos Cordon64c7e962014-07-02 15:15:27 -0700100 @Override
101 public void onExpiredConferenceCall(Call call) {}
102 @Override
103 public void onConfirmedConferenceCall(Call call) {}
104 @Override
105 public void onParentChanged(Call call) {}
106 @Override
107 public void onChildrenChanged(Call call) {}
108 @Override
109 public void onCannedSmsResponsesLoaded(Call call) {}
110 @Override
111 public void onCallVideoProviderChanged(Call call) {}
112 @Override
Santos Cordon64c7e962014-07-02 15:15:27 -0700113 public void onCallerInfoChanged(Call call) {}
Sailesh Nepal7e669572014-07-08 21:29:12 -0700114 @Override
115 public void onAudioModeIsVoipChanged(Call call) {}
Sailesh Nepal35faf8c2014-07-08 22:02:34 -0700116 @Override
117 public void onStatusHintsChanged(Call call) {}
Sailesh Nepale8ecb982014-07-11 17:19:42 -0700118 @Override
119 public void onHandleChanged(Call call) {}
120 @Override
121 public void onCallerDisplayNameChanged(Call call) {}
Andrew Lee4a796602014-07-11 17:23:03 -0700122 @Override
123 public void onVideoStateChanged(Call call) {}
Santos Cordon766d04f2014-05-06 10:28:25 -0700124 }
125
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700126 private static final OnQueryCompleteListener sCallerInfoQueryListener =
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700127 new OnQueryCompleteListener() {
128 /** ${inheritDoc} */
129 @Override
130 public void onQueryComplete(int token, Object cookie, CallerInfo callerInfo) {
131 if (cookie != null) {
132 ((Call) cookie).setCallerInfo(callerInfo, token);
133 }
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700134 }
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700135 };
136
137 private static final OnImageLoadCompleteListener sPhotoLoadListener =
138 new OnImageLoadCompleteListener() {
139 /** ${inheritDoc} */
140 @Override
141 public void onImageLoadComplete(
142 int token, Drawable photo, Bitmap photoIcon, Object cookie) {
143 if (cookie != null) {
144 ((Call) cookie).setPhoto(photo, photoIcon, token);
145 }
146 }
147 };
Ben Gilad0407fb22014-01-09 16:18:41 -0800148
Sailesh Nepal664837f2014-07-14 16:31:51 -0700149 private final Runnable mDirectToVoicemailRunnable = new Runnable() {
150 @Override
151 public void run() {
152 processDirectToVoicemail();
153 }
154 };
155
Sailesh Nepal810735e2014-03-18 18:15:46 -0700156 /** True if this is an incoming call. */
157 private final boolean mIsIncoming;
158
Ben Gilad0407fb22014-01-09 16:18:41 -0800159 /**
Sailesh Nepal664837f2014-07-14 16:31:51 -0700160 * The time this call was created. Beyond logging and such, may also be used for bookkeeping
161 * and specifically for marking certain call attempts as failed attempts.
Ben Gilad0407fb22014-01-09 16:18:41 -0800162 */
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700163 private final long mCreationTimeMillis = System.currentTimeMillis();
164
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700165 /** The gateway information associated with this call. This stores the original call handle
166 * that the user is attempting to connect to via the gateway, the actual handle to dial in
167 * order to connect the call via the gateway, as well as the package name of the gateway
168 * service. */
169 private final GatewayInfo mGatewayInfo;
170
Sailesh Nepalb0ba0872014-07-08 22:09:50 -0700171 private PhoneAccount mPhoneAccount;
Nancy Chen77d2d0e2014-06-24 12:06:03 -0700172
Santos Cordon2174fb52014-05-29 08:22:56 -0700173 private final Handler mHandler = new Handler();
174
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700175 private long mConnectTimeMillis;
Ben Gilad0407fb22014-01-09 16:18:41 -0800176
Santos Cordon61d0f702014-02-19 02:52:23 -0800177 /** The state of the call. */
178 private CallState mState;
179
180 /** The handle with which to establish this call. */
Sailesh Nepalce704b92014-03-17 18:31:43 -0700181 private Uri mHandle;
Santos Cordon61d0f702014-02-19 02:52:23 -0800182
Sailesh Nepale8ecb982014-07-11 17:19:42 -0700183 /** The {@link CallPropertyPresentation} that controls how the handle is shown. */
184 private int mHandlePresentation;
185
186 /** The caller display name (CNAP) set by the connection service. */
187 private String mCallerDisplayName;
188
189 /** The {@link CallPropertyPresentation} that controls how the caller display name is shown. */
190 private int mCallerDisplayNamePresentation;
191
Ben Gilad0407fb22014-01-09 16:18:41 -0800192 /**
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700193 * The connection service which is attempted or already connecting this call.
Santos Cordon681663d2014-01-30 04:32:15 -0800194 */
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700195 private ConnectionServiceWrapper mConnectionService;
Ben Gilad61925612014-03-11 19:06:36 -0700196
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700197 private boolean mIsEmergencyCall;
198
Sai Cheemalapatib7157e92014-06-11 17:51:55 -0700199 private boolean mSpeakerphoneOn;
200
Tyler Gunn0a388fc2014-07-17 12:21:17 -0700201 /**
202 * Tracks the video states which were applicable over the duration of a call.
203 * See {@link android.telecomm.VideoCallProfile} for a list of valid video states.
204 */
205 private int mVideoStateHistory;
206
Tyler Gunnc4abd912014-07-08 14:22:10 -0700207 private int mVideoState;
208
Ben Gilad61925612014-03-11 19:06:36 -0700209 /**
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700210 * Disconnect cause for the call. Only valid if the state of the call is DISCONNECTED.
211 * See {@link android.telephony.DisconnectCause}.
212 */
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700213 private int mDisconnectCause = DisconnectCause.NOT_VALID;
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700214
215 /**
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700216 * Additional disconnect information provided by the connection service.
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700217 */
218 private String mDisconnectMessage;
219
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700220 /** Info used by the connection services. */
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700221 private Bundle mExtras = Bundle.EMPTY;
Sailesh Nepal84fa5f82014-04-02 11:01:11 -0700222
Santos Cordon766d04f2014-05-06 10:28:25 -0700223 /** Set of listeners on this call. */
Santos Cordondfc66012014-07-15 13:41:40 -0700224 private Set<Listener> mListeners = new CopyOnWriteArraySet<>();
Santos Cordon766d04f2014-05-06 10:28:25 -0700225
Sailesh Nepal664837f2014-07-14 16:31:51 -0700226 private CreateConnectionProcessor mCreateConnectionProcessor;
Santos Cordon682fe6b2014-05-20 08:56:39 -0700227
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700228 /** Caller information retrieved from the latest contact query. */
229 private CallerInfo mCallerInfo;
230
231 /** The latest token used with a contact info query. */
232 private int mQueryToken = 0;
233
Ihab Awadcb387ac2014-05-28 16:49:38 -0700234 /** Whether this call is requesting that Telecomm play the ringback tone on its behalf. */
235 private boolean mRequestingRingback = false;
236
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700237 /** Whether direct-to-voicemail query is pending. */
238 private boolean mDirectToVoicemailQueryPending;
Santos Cordon2174fb52014-05-29 08:22:56 -0700239
Sailesh Nepale20bc972014-07-09 21:22:36 -0700240 private int mCallCapabilities;
Santos Cordona1610702014-06-04 20:22:56 -0700241
242 private boolean mIsConference = false;
243
244 private Call mParentCall = null;
245
246 private List<Call> mChildCalls = new LinkedList<>();
247
Ihab Awadff7493a2014-06-10 13:47:44 -0700248 /** Set of text message responses allowed for this call, if applicable. */
249 private List<String> mCannedSmsResponses = Collections.EMPTY_LIST;
250
251 /** Whether an attempt has been made to load the text message responses. */
252 private boolean mCannedSmsResponsesLoadingStarted = false;
253
Nancy Chena65d41f2014-06-24 12:06:03 -0700254 private ICallVideoProvider mCallVideoProvider;
255
Sailesh Nepal7e669572014-07-08 21:29:12 -0700256 private boolean mAudioModeIsVoip;
Sailesh Nepal35faf8c2014-07-08 22:02:34 -0700257 private StatusHints mStatusHints;
Sailesh Nepal664837f2014-07-14 16:31:51 -0700258 private final ConnectionServiceRepository mRepository;
Sailesh Nepal7e669572014-07-08 21:29:12 -0700259
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700260 /**
Ben Gilad0407fb22014-01-09 16:18:41 -0800261 * Persists the specified parameters and initializes the new instance.
262 *
263 * @param handle The handle to dial.
Yorke Lee33501632014-03-17 19:24:12 -0700264 * @param gatewayInfo Gateway information to use for the call.
Ihab Awad98a55602014-06-30 21:27:28 -0700265 * @param account Account information to use for the call.
Sailesh Nepal810735e2014-03-18 18:15:46 -0700266 * @param isIncoming True if this is an incoming call.
Ben Gilad0407fb22014-01-09 16:18:41 -0800267 */
Sailesh Nepal664837f2014-07-14 16:31:51 -0700268 Call(
269 ConnectionServiceRepository repository,
270 Uri handle,
271 GatewayInfo gatewayInfo,
272 PhoneAccount account,
273 boolean isIncoming,
274 boolean isConference) {
Santos Cordona1610702014-06-04 20:22:56 -0700275 mState = isConference ? CallState.ACTIVE : CallState.NEW;
Sailesh Nepal664837f2014-07-14 16:31:51 -0700276 mRepository = repository;
Sailesh Nepale8ecb982014-07-11 17:19:42 -0700277 setHandle(handle, CallPropertyPresentation.ALLOWED);
Yorke Lee33501632014-03-17 19:24:12 -0700278 mGatewayInfo = gatewayInfo;
Sailesh Nepalb0ba0872014-07-08 22:09:50 -0700279 mPhoneAccount = account;
Sailesh Nepal810735e2014-03-18 18:15:46 -0700280 mIsIncoming = isIncoming;
Santos Cordona1610702014-06-04 20:22:56 -0700281 mIsConference = isConference;
Ihab Awadff7493a2014-06-10 13:47:44 -0700282 maybeLoadCannedSmsResponses();
Ben Gilad0407fb22014-01-09 16:18:41 -0800283 }
284
Santos Cordon766d04f2014-05-06 10:28:25 -0700285 void addListener(Listener listener) {
286 mListeners.add(listener);
287 }
288
289 void removeListener(Listener listener) {
290 mListeners.remove(listener);
291 }
292
Santos Cordon61d0f702014-02-19 02:52:23 -0800293 /** {@inheritDoc} */
294 @Override public String toString() {
Sailesh Nepal4538f012014-04-15 11:40:33 -0700295 String component = null;
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700296 if (mConnectionService != null && mConnectionService.getComponentName() != null) {
297 component = mConnectionService.getComponentName().flattenToShortString();
Sailesh Nepal4538f012014-04-15 11:40:33 -0700298 }
Tyler Gunn0a388fc2014-07-17 12:21:17 -0700299
300 return String.format(Locale.US, "[%s, %s, %s, %d]", mState, component,
301 Log.piiHandle(mHandle), getVideoState());
Santos Cordon61d0f702014-02-19 02:52:23 -0800302 }
303
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800304 CallState getState() {
Santos Cordona1610702014-06-04 20:22:56 -0700305 if (mIsConference) {
306 if (!mChildCalls.isEmpty()) {
307 // If we have child calls, just return the child call.
308 return mChildCalls.get(0).getState();
309 }
310 return CallState.ACTIVE;
311 } else {
312 return mState;
313 }
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800314 }
315
316 /**
317 * Sets the call state. Although there exists the notion of appropriate state transitions
318 * (see {@link CallState}), in practice those expectations break down when cellular systems
319 * misbehave and they do this very often. The result is that we do not enforce state transitions
320 * and instead keep the code resilient to unexpected state changes.
321 */
Sailesh Nepal810735e2014-03-18 18:15:46 -0700322 void setState(CallState newState) {
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700323 Preconditions.checkState(newState != CallState.DISCONNECTED ||
324 mDisconnectCause != DisconnectCause.NOT_VALID);
Sailesh Nepal810735e2014-03-18 18:15:46 -0700325 if (mState != newState) {
326 Log.v(this, "setState %s -> %s", mState, newState);
327 mState = newState;
Ihab Awadff7493a2014-06-10 13:47:44 -0700328 maybeLoadCannedSmsResponses();
Sailesh Nepal810735e2014-03-18 18:15:46 -0700329 }
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800330 }
331
Ihab Awadcb387ac2014-05-28 16:49:38 -0700332 void setRequestingRingback(boolean requestingRingback) {
333 mRequestingRingback = requestingRingback;
334 for (Listener l : mListeners) {
335 l.onRequestingRingback(this, mRequestingRingback);
336 }
337 }
338
339 boolean isRequestingRingback() {
340 return mRequestingRingback;
341 }
342
Sailesh Nepalce704b92014-03-17 18:31:43 -0700343 Uri getHandle() {
Ben Gilad0bf5b912014-01-28 17:55:57 -0800344 return mHandle;
345 }
346
Sailesh Nepale8ecb982014-07-11 17:19:42 -0700347 int getHandlePresentation() {
348 return mHandlePresentation;
349 }
350
351 void setHandle(Uri handle, int presentation) {
352 if (!Objects.equals(handle, mHandle) || presentation != mHandlePresentation) {
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700353 mHandle = handle;
Sailesh Nepale8ecb982014-07-11 17:19:42 -0700354 mHandlePresentation = presentation;
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700355 mIsEmergencyCall = mHandle != null && PhoneNumberUtils.isLocalEmergencyNumber(
Yorke Lee66255452014-06-05 08:09:24 -0700356 TelecommApp.getInstance(), mHandle.getSchemeSpecificPart());
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700357 startCallerInfoLookup();
Sailesh Nepale8ecb982014-07-11 17:19:42 -0700358 for (Listener l : mListeners) {
359 l.onHandleChanged(this);
360 }
361 }
362 }
363
364 String getCallerDisplayName() {
365 return mCallerDisplayName;
366 }
367
368 int getCallerDisplayNamePresentation() {
369 return mCallerDisplayNamePresentation;
370 }
371
372 void setCallerDisplayName(String callerDisplayName, int presentation) {
373 if (!TextUtils.equals(callerDisplayName, mCallerDisplayName) ||
374 presentation != mCallerDisplayNamePresentation) {
375 mCallerDisplayName = callerDisplayName;
376 mCallerDisplayNamePresentation = presentation;
377 for (Listener l : mListeners) {
378 l.onCallerDisplayNameChanged(this);
379 }
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700380 }
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700381 }
382
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700383 String getName() {
384 return mCallerInfo == null ? null : mCallerInfo.name;
385 }
386
387 Bitmap getPhotoIcon() {
388 return mCallerInfo == null ? null : mCallerInfo.cachedPhotoIcon;
389 }
390
391 Drawable getPhoto() {
392 return mCallerInfo == null ? null : mCallerInfo.cachedPhoto;
393 }
394
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700395 /**
396 * @param disconnectCause The reason for the disconnection, any of
397 * {@link android.telephony.DisconnectCause}.
Sailesh Nepal905dfba2014-07-14 08:20:41 -0700398 * @param disconnectMessage Optional message about the disconnect.
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700399 */
400 void setDisconnectCause(int disconnectCause, String disconnectMessage) {
401 // TODO: Consider combining this method with a setDisconnected() method that is totally
402 // separate from setState.
403 mDisconnectCause = disconnectCause;
404 mDisconnectMessage = disconnectMessage;
405 }
406
407 int getDisconnectCause() {
408 return mDisconnectCause;
409 }
410
411 String getDisconnectMessage() {
412 return mDisconnectMessage;
413 }
414
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700415 boolean isEmergencyCall() {
416 return mIsEmergencyCall;
Santos Cordon61d0f702014-02-19 02:52:23 -0800417 }
418
Yorke Lee33501632014-03-17 19:24:12 -0700419 /**
420 * @return The original handle this call is associated with. In-call services should use this
421 * handle when indicating in their UI the handle that is being called.
422 */
423 public Uri getOriginalHandle() {
424 if (mGatewayInfo != null && !mGatewayInfo.isEmpty()) {
425 return mGatewayInfo.getOriginalHandle();
426 }
427 return getHandle();
428 }
429
430 GatewayInfo getGatewayInfo() {
431 return mGatewayInfo;
432 }
433
Sailesh Nepalb0ba0872014-07-08 22:09:50 -0700434 PhoneAccount getPhoneAccount() {
435 return mPhoneAccount;
Nancy Chen77d2d0e2014-06-24 12:06:03 -0700436 }
437
Nancy Chen53ceedc2014-07-08 18:56:51 -0700438 void setPhoneAccount(PhoneAccount account) {
439 mPhoneAccount = account;
440 }
441
Sailesh Nepal810735e2014-03-18 18:15:46 -0700442 boolean isIncoming() {
443 return mIsIncoming;
444 }
445
Ben Gilad0407fb22014-01-09 16:18:41 -0800446 /**
447 * @return The "age" of this call object in milliseconds, which typically also represents the
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700448 * period since this call was added to the set pending outgoing calls, see
449 * mCreationTimeMillis.
Ben Gilad0407fb22014-01-09 16:18:41 -0800450 */
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700451 long getAgeMillis() {
452 return System.currentTimeMillis() - mCreationTimeMillis;
Ben Gilad0407fb22014-01-09 16:18:41 -0800453 }
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800454
Yorke Leef98fb572014-03-05 10:56:55 -0800455 /**
456 * @return The time when this call object was created and added to the set of pending outgoing
457 * calls.
458 */
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700459 long getCreationTimeMillis() {
460 return mCreationTimeMillis;
461 }
462
463 long getConnectTimeMillis() {
464 return mConnectTimeMillis;
465 }
466
467 void setConnectTimeMillis(long connectTimeMillis) {
468 mConnectTimeMillis = connectTimeMillis;
Yorke Leef98fb572014-03-05 10:56:55 -0800469 }
470
Sailesh Nepale20bc972014-07-09 21:22:36 -0700471 int getCallCapabilities() {
472 return mCallCapabilities;
Santos Cordona1610702014-06-04 20:22:56 -0700473 }
474
Sailesh Nepale20bc972014-07-09 21:22:36 -0700475 void setCallCapabilities(int callCapabilities) {
476 if (mCallCapabilities != callCapabilities) {
477 mCallCapabilities = callCapabilities;
Santos Cordona1610702014-06-04 20:22:56 -0700478 for (Listener l : mListeners) {
Sailesh Nepale20bc972014-07-09 21:22:36 -0700479 l.onCallCapabilitiesChanged(this);
Santos Cordona1610702014-06-04 20:22:56 -0700480 }
481 }
482 }
483
484 Call getParentCall() {
485 return mParentCall;
486 }
487
488 List<Call> getChildCalls() {
489 return mChildCalls;
490 }
491
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700492 ConnectionServiceWrapper getConnectionService() {
493 return mConnectionService;
Santos Cordon681663d2014-01-30 04:32:15 -0800494 }
495
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700496 void setConnectionService(ConnectionServiceWrapper service) {
497 Preconditions.checkNotNull(service);
498
499 clearConnectionService();
500
501 service.incrementAssociatedCallCount();
502 mConnectionService = service;
503 mConnectionService.addCall(this);
Sailesh Nepal0e5410a2014-04-04 01:20:58 -0700504 }
505
506 /**
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700507 * Clears the associated connection service.
Sailesh Nepal0e5410a2014-04-04 01:20:58 -0700508 */
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700509 void clearConnectionService() {
510 if (mConnectionService != null) {
511 ConnectionServiceWrapper serviceTemp = mConnectionService;
512 mConnectionService = null;
513 serviceTemp.removeCall(this);
Santos Cordonc499c1c2014-04-14 17:13:14 -0700514
515 // Decrementing the count can cause the service to unbind, which itself can trigger the
516 // service-death code. Since the service death code tries to clean up any associated
517 // calls, we need to make sure to remove that information (e.g., removeCall()) before
518 // we decrement. Technically, invoking removeCall() prior to decrementing is all that is
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700519 // necessary, but cleaning up mConnectionService prior to triggering an unbind is good
520 // to do.
521 decrementAssociatedCallCount(serviceTemp);
Yorke Leeadee12d2014-03-13 12:08:30 -0700522 }
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800523 }
524
Sailesh Nepal664837f2014-07-14 16:31:51 -0700525 private void processDirectToVoicemail() {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700526 if (mDirectToVoicemailQueryPending) {
Santos Cordon2174fb52014-05-29 08:22:56 -0700527 if (mCallerInfo != null && mCallerInfo.shouldSendToVoicemail) {
528 Log.i(this, "Directing call to voicemail: %s.", this);
529 // TODO(santoscordon): Once we move State handling from CallsManager to Call, we
530 // will not need to set RINGING state prior to calling reject.
531 setState(CallState.RINGING);
Ihab Awadff7493a2014-06-10 13:47:44 -0700532 reject(false, null);
Santos Cordon2174fb52014-05-29 08:22:56 -0700533 } else {
534 // TODO(santoscordon): Make this class (not CallsManager) responsible for changing
535 // the call state to RINGING.
536
537 // TODO(santoscordon): Replace this with state transition to RINGING.
538 for (Listener l : mListeners) {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700539 l.onSuccessfulIncomingCall(this);
Santos Cordon2174fb52014-05-29 08:22:56 -0700540 }
541 }
542
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700543 mDirectToVoicemailQueryPending = false;
Santos Cordon766d04f2014-05-06 10:28:25 -0700544 }
545 }
546
Santos Cordon766d04f2014-05-06 10:28:25 -0700547 /**
Sailesh Nepal664837f2014-07-14 16:31:51 -0700548 * Starts the create connection sequence. Upon completion, there should exist an active
549 * connection through a connection service (or the call will have failed).
Santos Cordon766d04f2014-05-06 10:28:25 -0700550 */
Sailesh Nepal664837f2014-07-14 16:31:51 -0700551 void startCreateConnection() {
552 Preconditions.checkState(mCreateConnectionProcessor == null);
553 mCreateConnectionProcessor = new CreateConnectionProcessor(this, mRepository, this);
554 mCreateConnectionProcessor.process();
Santos Cordon766d04f2014-05-06 10:28:25 -0700555 }
556
Sailesh Nepal5a73b032014-06-25 15:53:21 -0700557 @Override
Sailesh Nepal664837f2014-07-14 16:31:51 -0700558 public void handleCreateConnectionSuccessful(ConnectionRequest request) {
559 mCreateConnectionProcessor = null;
560 mPhoneAccount = request.getAccount();
561
562 if (mIsIncoming) {
563 // We do not handle incoming calls immediately when they are verified by the connection
564 // service. We allow the caller-info-query code to execute first so that we can read the
565 // direct-to-voicemail property before deciding if we want to show the incoming call to
566 // the user or if we want to reject the call.
567 mDirectToVoicemailQueryPending = true;
568
569 // Setting the handle triggers the caller info lookup code.
570 setHandle(request.getHandle(), request.getHandlePresentation());
571
572 // Timeout the direct-to-voicemail lookup execution so that we dont wait too long before
573 // showing the user the incoming call screen.
574 mHandler.postDelayed(mDirectToVoicemailRunnable, Timeouts.getDirectToVoicemailMillis());
575 } else {
576 for (Listener l : mListeners) {
577 l.onSuccessfulOutgoingCall(this);
578 }
Santos Cordon766d04f2014-05-06 10:28:25 -0700579 }
580 }
581
Sailesh Nepal5a73b032014-06-25 15:53:21 -0700582 @Override
Sailesh Nepal664837f2014-07-14 16:31:51 -0700583 public void handleCreateConnectionFailed(int code, String msg) {
584 mCreateConnectionProcessor = null;
585 if (mIsIncoming) {
586 clearConnectionService();
587 setDisconnectCause(code, null);
588 setState(CallState.DISCONNECTED);
Santos Cordon682fe6b2014-05-20 08:56:39 -0700589
Sailesh Nepal664837f2014-07-14 16:31:51 -0700590 Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]);
591 for (int i = 0; i < listeners.length; i++) {
592 listeners[i].onFailedIncomingCall(this);
593 }
594 } else {
595 Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]);
596 for (int i = 0; i < listeners.length; i++) {
597 listeners[i].onFailedOutgoingCall(this, code, msg);
598 }
599 clearConnectionService();
600 }
Sailesh Nepal5a73b032014-06-25 15:53:21 -0700601 }
602
603 @Override
Sailesh Nepal664837f2014-07-14 16:31:51 -0700604 public void handleCreateConnectionCancelled() {
605 mCreateConnectionProcessor = null;
606 if (mIsIncoming) {
607 clearConnectionService();
608 setDisconnectCause(DisconnectCause.ERROR_UNSPECIFIED, null);
609 setState(CallState.DISCONNECTED);
Sailesh Nepal5a73b032014-06-25 15:53:21 -0700610
Sailesh Nepal664837f2014-07-14 16:31:51 -0700611 Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]);
612 for (int i = 0; i < listeners.length; i++) {
613 listeners[i].onFailedIncomingCall(this);
614 }
615 } else {
616 Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]);
617 for (int i = 0; i < listeners.length; i++) {
618 listeners[i].onCancelledOutgoingCall(this);
619 }
620 clearConnectionService();
621 }
Santos Cordon766d04f2014-05-06 10:28:25 -0700622 }
623
624 /**
Ihab Awad74549ec2014-03-10 15:33:25 -0700625 * Plays the specified DTMF tone.
626 */
627 void playDtmfTone(char digit) {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700628 if (mConnectionService == null) {
629 Log.w(this, "playDtmfTone() request on a call without a connection service.");
Ihab Awad74549ec2014-03-10 15:33:25 -0700630 } else {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700631 Log.i(this, "Send playDtmfTone to connection service for call %s", this);
632 mConnectionService.playDtmfTone(this, digit);
Ihab Awad74549ec2014-03-10 15:33:25 -0700633 }
634 }
635
636 /**
637 * Stops playing any currently playing DTMF tone.
638 */
639 void stopDtmfTone() {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700640 if (mConnectionService == null) {
641 Log.w(this, "stopDtmfTone() request on a call without a connectino service.");
Ihab Awad74549ec2014-03-10 15:33:25 -0700642 } else {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700643 Log.i(this, "Send stopDtmfTone to connection service for call %s", this);
644 mConnectionService.stopDtmfTone(this);
Ihab Awad74549ec2014-03-10 15:33:25 -0700645 }
646 }
647
648 /**
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700649 * Attempts to disconnect the call through the connection service.
Santos Cordon049b7b62014-01-30 05:34:26 -0800650 */
651 void disconnect() {
Nancy Chen53ceedc2014-07-08 18:56:51 -0700652 if (mState == CallState.NEW || mState == CallState.PRE_DIAL_WAIT) {
Santos Cordon682fe6b2014-05-20 08:56:39 -0700653 Log.v(this, "Aborting call %s", this);
654 abort();
Santos Cordon74d420b2014-05-07 14:38:47 -0700655 } else if (mState != CallState.ABORTED && mState != CallState.DISCONNECTED) {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700656 Preconditions.checkNotNull(mConnectionService);
Santos Cordon766d04f2014-05-06 10:28:25 -0700657
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700658 Log.i(this, "Send disconnect to connection service for call: %s", this);
659 // The call isn't officially disconnected until the connection service confirms that the
660 // call was actually disconnected. Only then is the association between call and
661 // connection service severed, see {@link CallsManager#markCallAsDisconnected}.
662 mConnectionService.disconnect(this);
Santos Cordon049b7b62014-01-30 05:34:26 -0800663 }
664 }
665
Santos Cordon682fe6b2014-05-20 08:56:39 -0700666 void abort() {
Sailesh Nepal664837f2014-07-14 16:31:51 -0700667 if (mCreateConnectionProcessor != null) {
668 mCreateConnectionProcessor.abort();
Nancy Chen53ceedc2014-07-08 18:56:51 -0700669 } else if (mState == CallState.PRE_DIAL_WAIT) {
670 handleCreateConnectionFailed(DisconnectCause.LOCAL, null);
Santos Cordon682fe6b2014-05-20 08:56:39 -0700671 }
672 }
673
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800674 /**
Santos Cordon61d0f702014-02-19 02:52:23 -0800675 * Answers the call if it is ringing.
Andrew Lee38931d02014-07-16 10:17:36 -0700676 *
677 * @param videoState The video state in which to answer the call.
Santos Cordon61d0f702014-02-19 02:52:23 -0800678 */
Andrew Lee38931d02014-07-16 10:17:36 -0700679 void answer(int videoState) {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700680 Preconditions.checkNotNull(mConnectionService);
Santos Cordon61d0f702014-02-19 02:52:23 -0800681
682 // Check to verify that the call is still in the ringing state. A call can change states
683 // between the time the user hits 'answer' and Telecomm receives the command.
684 if (isRinging("answer")) {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700685 // At this point, we are asking the connection service to answer but we don't assume
686 // that it will work. Instead, we wait until confirmation from the connectino service
687 // that the call is in a non-RINGING state before changing the UI. See
688 // {@link ConnectionServiceAdapter#setActive} and other set* methods.
Andrew Lee38931d02014-07-16 10:17:36 -0700689 mConnectionService.answer(this, videoState);
Santos Cordon61d0f702014-02-19 02:52:23 -0800690 }
691 }
692
693 /**
694 * Rejects the call if it is ringing.
Ihab Awadff7493a2014-06-10 13:47:44 -0700695 *
696 * @param rejectWithMessage Whether to send a text message as part of the call rejection.
697 * @param textMessage An optional text message to send as part of the rejection.
Santos Cordon61d0f702014-02-19 02:52:23 -0800698 */
Ihab Awadff7493a2014-06-10 13:47:44 -0700699 void reject(boolean rejectWithMessage, String textMessage) {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700700 Preconditions.checkNotNull(mConnectionService);
Santos Cordon61d0f702014-02-19 02:52:23 -0800701
702 // Check to verify that the call is still in the ringing state. A call can change states
703 // between the time the user hits 'reject' and Telecomm receives the command.
704 if (isRinging("reject")) {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700705 mConnectionService.reject(this);
Santos Cordon61d0f702014-02-19 02:52:23 -0800706 }
707 }
708
709 /**
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700710 * Puts the call on hold if it is currently active.
711 */
712 void hold() {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700713 Preconditions.checkNotNull(mConnectionService);
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700714
715 if (mState == CallState.ACTIVE) {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700716 mConnectionService.hold(this);
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700717 }
718 }
719
720 /**
721 * Releases the call from hold if it is currently active.
722 */
723 void unhold() {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700724 Preconditions.checkNotNull(mConnectionService);
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700725
726 if (mState == CallState.ON_HOLD) {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700727 mConnectionService.unhold(this);
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700728 }
729 }
730
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700731 /** Checks if this is a live call or not. */
732 boolean isAlive() {
733 switch (mState) {
734 case NEW:
735 case RINGING:
736 case DISCONNECTED:
737 case ABORTED:
738 return false;
739 default:
740 return true;
741 }
742 }
743
Santos Cordon40f78c22014-04-07 02:11:42 -0700744 boolean isActive() {
Ihab Awad84bfe472014-07-13 17:11:57 -0700745 return mState == CallState.ACTIVE;
Santos Cordon40f78c22014-04-07 02:11:42 -0700746 }
747
Sailesh Nepal84fa5f82014-04-02 11:01:11 -0700748 Bundle getExtras() {
749 return mExtras;
750 }
751
752 void setExtras(Bundle extras) {
753 mExtras = extras;
754 }
755
Santos Cordon5ba7f272014-05-28 13:59:49 -0700756 Uri getRingtone() {
757 return mCallerInfo == null ? null : mCallerInfo.contactRingtoneUri;
758 }
759
Evan Charlton352105c2014-06-03 14:10:54 -0700760 void onPostDialWait(String remaining) {
761 for (Listener l : mListeners) {
762 l.onPostDialWait(this, remaining);
763 }
764 }
765
766 void postDialContinue(boolean proceed) {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700767 mConnectionService.onPostDialContinue(this, proceed);
Evan Charlton352105c2014-06-03 14:10:54 -0700768 }
769
Sailesh Nepal77da19e2014-07-02 21:31:16 -0700770 void phoneAccountClicked() {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700771 mConnectionService.onPhoneAccountClicked(this);
Sailesh Nepal77da19e2014-07-02 21:31:16 -0700772 }
773
Santos Cordona1610702014-06-04 20:22:56 -0700774 void conferenceInto(Call conferenceCall) {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700775 if (mConnectionService == null) {
776 Log.w(this, "conference requested on a call without a connection service.");
Santos Cordona1610702014-06-04 20:22:56 -0700777 } else {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700778 mConnectionService.conference(conferenceCall, this);
Santos Cordona1610702014-06-04 20:22:56 -0700779 }
780 }
781
782 void expireConference() {
783 // The conference call expired before we got a confirmation of the conference from the
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700784 // connection service...so start shutting down.
785 clearConnectionService();
Santos Cordona1610702014-06-04 20:22:56 -0700786 for (Listener l : mListeners) {
787 l.onExpiredConferenceCall(this);
788 }
789 }
790
791 void confirmConference() {
792 Log.v(this, "confirming Conf call %s", mListeners);
793 for (Listener l : mListeners) {
794 l.onConfirmedConferenceCall(this);
795 }
796 }
797
798 void splitFromConference() {
799 // TODO(santoscordon): todo
800 }
801
Sailesh Nepale8ecb982014-07-11 17:19:42 -0700802 void swapWithBackgroundCall() {
803 mConnectionService.swapWithBackgroundCall(this);
804 }
805
Santos Cordona1610702014-06-04 20:22:56 -0700806 void setParentCall(Call parentCall) {
807 if (parentCall == this) {
808 Log.e(this, new Exception(), "setting the parent to self");
809 return;
810 }
811 Preconditions.checkState(parentCall == null || mParentCall == null);
812
813 Call oldParent = mParentCall;
814 if (mParentCall != null) {
815 mParentCall.removeChildCall(this);
816 }
817 mParentCall = parentCall;
818 if (mParentCall != null) {
819 mParentCall.addChildCall(this);
820 }
821
822 for (Listener l : mListeners) {
823 l.onParentChanged(this);
824 }
825 }
826
827 private void addChildCall(Call call) {
828 if (!mChildCalls.contains(call)) {
829 mChildCalls.add(call);
830
831 for (Listener l : mListeners) {
832 l.onChildrenChanged(this);
833 }
834 }
835 }
836
837 private void removeChildCall(Call call) {
838 if (mChildCalls.remove(call)) {
839 for (Listener l : mListeners) {
840 l.onChildrenChanged(this);
841 }
842 }
843 }
844
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800845 /**
Ihab Awadff7493a2014-06-10 13:47:44 -0700846 * Return whether the user can respond to this {@code Call} via an SMS message.
847 *
848 * @return true if the "Respond via SMS" feature should be enabled
849 * for this incoming call.
850 *
851 * The general rule is that we *do* allow "Respond via SMS" except for
852 * the few (relatively rare) cases where we know for sure it won't
853 * work, namely:
854 * - a bogus or blank incoming number
855 * - a call from a SIP address
856 * - a "call presentation" that doesn't allow the number to be revealed
857 *
858 * In all other cases, we allow the user to respond via SMS.
859 *
860 * Note that this behavior isn't perfect; for example we have no way
861 * to detect whether the incoming call is from a landline (with most
862 * networks at least), so we still enable this feature even though
863 * SMSes to that number will silently fail.
864 */
865 boolean isRespondViaSmsCapable() {
866 if (mState != CallState.RINGING) {
867 return false;
868 }
869
870 if (getHandle() == null) {
871 // No incoming number known or call presentation is "PRESENTATION_RESTRICTED", in
872 // other words, the user should not be able to see the incoming phone number.
873 return false;
874 }
875
876 if (PhoneNumberUtils.isUriNumber(getHandle().toString())) {
877 // The incoming number is actually a URI (i.e. a SIP address),
878 // not a regular PSTN phone number, and we can't send SMSes to
879 // SIP addresses.
880 // (TODO: That might still be possible eventually, though. Is
881 // there some SIP-specific equivalent to sending a text message?)
882 return false;
883 }
884
885 // Is there a valid SMS application on the phone?
886 if (SmsApplication.getDefaultRespondViaMessageApplication(TelecommApp.getInstance(),
887 true /*updateIfNeeded*/) == null) {
888 return false;
889 }
890
891 // TODO: with some carriers (in certain countries) you *can* actually
892 // tell whether a given number is a mobile phone or not. So in that
893 // case we could potentially return false here if the incoming call is
894 // from a land line.
895
896 // If none of the above special cases apply, it's OK to enable the
897 // "Respond via SMS" feature.
898 return true;
899 }
900
901 List<String> getCannedSmsResponses() {
902 return mCannedSmsResponses;
903 }
904
905 /**
Santos Cordon61d0f702014-02-19 02:52:23 -0800906 * @return True if the call is ringing, else logs the action name.
907 */
908 private boolean isRinging(String actionName) {
909 if (mState == CallState.RINGING) {
910 return true;
911 }
912
Sailesh Nepalf1c191d2014-03-07 18:17:39 -0800913 Log.i(this, "Request to %s a non-ringing call %s", actionName, this);
Santos Cordon61d0f702014-02-19 02:52:23 -0800914 return false;
915 }
916
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800917 @SuppressWarnings("rawtypes")
918 private void decrementAssociatedCallCount(ServiceBinder binder) {
919 if (binder != null) {
920 binder.decrementAssociatedCallCount();
921 }
922 }
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700923
924 /**
925 * Looks up contact information based on the current handle.
926 */
927 private void startCallerInfoLookup() {
928 String number = mHandle == null ? null : mHandle.getSchemeSpecificPart();
929
930 mQueryToken++; // Updated so that previous queries can no longer set the information.
931 mCallerInfo = null;
932 if (!TextUtils.isEmpty(number)) {
Santos Cordon5ba7f272014-05-28 13:59:49 -0700933 Log.v(this, "Looking up information for: %s.", Log.piiHandle(number));
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700934 CallerInfoAsyncQuery.startQuery(
935 mQueryToken,
936 TelecommApp.getInstance(),
937 number,
938 sCallerInfoQueryListener,
939 this);
940 }
941 }
942
943 /**
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700944 * Saves the specified caller info if the specified token matches that of the last query
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700945 * that was made.
946 *
947 * @param callerInfo The new caller information to set.
948 * @param token The token used with this query.
949 */
950 private void setCallerInfo(CallerInfo callerInfo, int token) {
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700951 Preconditions.checkNotNull(callerInfo);
952
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700953 if (mQueryToken == token) {
954 mCallerInfo = callerInfo;
Santos Cordon5ba7f272014-05-28 13:59:49 -0700955 Log.i(this, "CallerInfo received for %s: %s", Log.piiHandle(mHandle), callerInfo);
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700956
Makoto Onukia1662d02014-07-10 15:31:59 -0700957 if (mCallerInfo.contactDisplayPhotoUri != null) {
958 Log.d(this, "Searching person uri %s for call %s",
959 mCallerInfo.contactDisplayPhotoUri, this);
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700960 ContactsAsyncHelper.startObtainPhotoAsync(
961 token,
962 TelecommApp.getInstance(),
Makoto Onukia1662d02014-07-10 15:31:59 -0700963 mCallerInfo.contactDisplayPhotoUri,
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700964 sPhotoLoadListener,
965 this);
Makoto Onukia1662d02014-07-10 15:31:59 -0700966 // Do not call onCallerInfoChanged yet in this case. We call it in setPhoto().
Santos Cordon64c7e962014-07-02 15:15:27 -0700967 } else {
968 for (Listener l : mListeners) {
969 l.onCallerInfoChanged(this);
970 }
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700971 }
Santos Cordon2174fb52014-05-29 08:22:56 -0700972
973 processDirectToVoicemail();
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700974 }
975 }
976
Yorke Lee6f3f7af2014-07-11 10:59:46 -0700977 CallerInfo getCallerInfo() {
978 return mCallerInfo;
979 }
980
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700981 /**
982 * Saves the specified photo information if the specified token matches that of the last query.
983 *
984 * @param photo The photo as a drawable.
985 * @param photoIcon The photo as a small icon.
986 * @param token The token used with this query.
987 */
988 private void setPhoto(Drawable photo, Bitmap photoIcon, int token) {
989 if (mQueryToken == token) {
990 mCallerInfo.cachedPhoto = photo;
991 mCallerInfo.cachedPhotoIcon = photoIcon;
Santos Cordon64c7e962014-07-02 15:15:27 -0700992
993 for (Listener l : mListeners) {
994 l.onCallerInfoChanged(this);
995 }
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700996 }
997 }
Ihab Awadff7493a2014-06-10 13:47:44 -0700998
999 private void maybeLoadCannedSmsResponses() {
1000 if (mIsIncoming && isRespondViaSmsCapable() && !mCannedSmsResponsesLoadingStarted) {
1001 Log.d(this, "maybeLoadCannedSmsResponses: starting task to load messages");
1002 mCannedSmsResponsesLoadingStarted = true;
1003 RespondViaSmsManager.getInstance().loadCannedTextMessages(
1004 new Response<Void, List<String>>() {
1005 @Override
1006 public void onResult(Void request, List<String>... result) {
1007 if (result.length > 0) {
1008 Log.d(this, "maybeLoadCannedSmsResponses: got %s", result[0]);
1009 mCannedSmsResponses = result[0];
1010 for (Listener l : mListeners) {
1011 l.onCannedSmsResponsesLoaded(Call.this);
1012 }
1013 }
1014 }
1015
1016 @Override
1017 public void onError(Void request, int code, String msg) {
1018 Log.w(Call.this, "Error obtaining canned SMS responses: %d %s", code,
1019 msg);
1020 }
1021 }
1022 );
1023 } else {
1024 Log.d(this, "maybeLoadCannedSmsResponses: doing nothing");
1025 }
1026 }
Sai Cheemalapatib7157e92014-06-11 17:51:55 -07001027
1028 /**
1029 * Sets speakerphone option on when call begins.
1030 */
1031 public void setStartWithSpeakerphoneOn(boolean startWithSpeakerphone) {
1032 mSpeakerphoneOn = startWithSpeakerphone;
1033 }
1034
1035 /**
1036 * Returns speakerphone option.
1037 *
1038 * @return Whether or not speakerphone should be set automatically when call begins.
1039 */
1040 public boolean getStartWithSpeakerphoneOn() {
1041 return mSpeakerphoneOn;
1042 }
Andrew Leee9a77652014-06-26 13:07:57 -07001043
1044 /**
1045 * Sets a call video provider for the call.
1046 */
Nancy Chena65d41f2014-06-24 12:06:03 -07001047 public void setCallVideoProvider(ICallVideoProvider callVideoProvider) {
1048 mCallVideoProvider = callVideoProvider;
1049 for (Listener l : mListeners) {
1050 l.onCallVideoProviderChanged(Call.this);
1051 }
1052 }
1053
1054 /**
1055 * @return Return the call video Provider binder.
1056 */
1057 public ICallVideoProvider getCallVideoProvider() {
1058 return mCallVideoProvider;
Andrew Leee9a77652014-06-26 13:07:57 -07001059 }
Tyler Gunne19cc002014-07-01 11:32:53 -07001060
1061 /**
Tyler Gunnc4abd912014-07-08 14:22:10 -07001062 * The current video state for the call.
1063 * Valid values: {@link android.telecomm.VideoCallProfile#VIDEO_STATE_AUDIO_ONLY},
1064 * {@link android.telecomm.VideoCallProfile#VIDEO_STATE_BIDIRECTIONAL},
1065 * {@link android.telecomm.VideoCallProfile#VIDEO_STATE_TX_ENABLED},
1066 * {@link android.telecomm.VideoCallProfile#VIDEO_STATE_RX_ENABLED}.
1067 *
1068 * @return True if video is enabled.
1069 */
1070 public int getVideoState() {
1071 return mVideoState;
1072 }
1073
1074 /**
Tyler Gunn0a388fc2014-07-17 12:21:17 -07001075 * Returns the video states which were applicable over the duration of a call.
1076 * See {@link android.telecomm.VideoCallProfile} for a list of valid video states.
1077 *
1078 * @return The video states applicable over the duration of the call.
1079 */
1080 public int getVideoStateHistory() {
1081 return mVideoStateHistory;
1082 }
1083
1084 /**
1085 * Determines the current video state for the call.
1086 * For an outgoing call determines the desired video state for the call.
Tyler Gunnc4abd912014-07-08 14:22:10 -07001087 * Valid values: {@link android.telecomm.VideoCallProfile#VIDEO_STATE_AUDIO_ONLY},
1088 * {@link android.telecomm.VideoCallProfile#VIDEO_STATE_BIDIRECTIONAL},
1089 * {@link android.telecomm.VideoCallProfile#VIDEO_STATE_TX_ENABLED},
1090 * {@link android.telecomm.VideoCallProfile#VIDEO_STATE_RX_ENABLED}.
1091 *
Tyler Gunn0a388fc2014-07-17 12:21:17 -07001092 * @param videoState The video state for the call.
Tyler Gunnc4abd912014-07-08 14:22:10 -07001093 */
1094 public void setVideoState(int videoState) {
Tyler Gunn0a388fc2014-07-17 12:21:17 -07001095 // Track which video states were applicable over the duration of the call.
1096 mVideoStateHistory = mVideoStateHistory | videoState;
1097
Tyler Gunnc4abd912014-07-08 14:22:10 -07001098 mVideoState = videoState;
Andrew Lee4a796602014-07-11 17:23:03 -07001099 for (Listener l : mListeners) {
1100 l.onVideoStateChanged(this);
1101 }
Tyler Gunnc4abd912014-07-08 14:22:10 -07001102 }
Sailesh Nepal7e669572014-07-08 21:29:12 -07001103
1104 public boolean getAudioModeIsVoip() {
1105 return mAudioModeIsVoip;
1106 }
1107
1108 public void setAudioModeIsVoip(boolean audioModeIsVoip) {
1109 mAudioModeIsVoip = audioModeIsVoip;
1110 for (Listener l : mListeners) {
Sailesh Nepal35faf8c2014-07-08 22:02:34 -07001111 l.onAudioModeIsVoipChanged(this);
1112 }
1113 }
1114
1115 public StatusHints getStatusHints() {
1116 return mStatusHints;
1117 }
1118
1119 public void setStatusHints(StatusHints statusHints) {
1120 mStatusHints = statusHints;
1121 for (Listener l : mListeners) {
1122 l.onStatusHintsChanged(this);
Sailesh Nepal7e669572014-07-08 21:29:12 -07001123 }
1124 }
Ben Gilad9f2bed32013-12-12 17:43:26 -08001125}