blob: d76e3141b2dc16a98e925ddc07c7e9578aa32731 [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
Sailesh Nepal9d58de52014-07-18 14:53:19 -070019import android.app.PendingIntent;
Santos Cordon99c8a6f2014-05-28 18:28:47 -070020import 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;
Sailesh Nepale8ecb982014-07-11 17:19:42 -070025import android.telecomm.CallPropertyPresentation;
Santos Cordon0b03b4b2014-01-29 18:01:59 -080026import android.telecomm.CallState;
Santos Cordon72890ce2014-07-21 01:32:04 -070027import android.telecomm.Connection;
Sailesh Nepalc92c4362014-07-04 18:33:21 -070028import android.telecomm.ConnectionRequest;
Yorke Lee33501632014-03-17 19:24:12 -070029import android.telecomm.GatewayInfo;
Santos Cordon72890ce2014-07-21 01:32:04 -070030import android.telecomm.ParcelableConnection;
Evan Charlton89176372014-07-19 18:23:09 -070031import android.telecomm.PhoneAccountHandle;
Ihab Awadff7493a2014-06-10 13:47:44 -070032import android.telecomm.Response;
Sailesh Nepal35faf8c2014-07-08 22:02:34 -070033import android.telecomm.StatusHints;
Santos Cordon79ff2bc2014-03-27 15:31:27 -070034import android.telephony.DisconnectCause;
Sailesh Nepal6aca10a2014-03-24 16:11:02 -070035import android.telephony.PhoneNumberUtils;
Santos Cordonfd71f4a2014-05-28 13:59:49 -070036import android.text.TextUtils;
Santos Cordon0b03b4b2014-01-29 18:01:59 -080037
Andrew Lee3bcf9352014-07-23 12:36:05 -070038import com.android.internal.telecomm.IVideoCallProvider;
Santos Cordonfd71f4a2014-05-28 13:59:49 -070039import com.android.internal.telephony.CallerInfo;
40import com.android.internal.telephony.CallerInfoAsyncQuery;
41import com.android.internal.telephony.CallerInfoAsyncQuery.OnQueryCompleteListener;
Ihab Awadff7493a2014-06-10 13:47:44 -070042import com.android.internal.telephony.SmsApplication;
Santos Cordon99c8a6f2014-05-28 18:28:47 -070043import com.android.telecomm.ContactsAsyncHelper.OnImageLoadCompleteListener;
Santos Cordon61d0f702014-02-19 02:52:23 -080044import com.google.common.base.Preconditions;
45
Ihab Awadff7493a2014-06-10 13:47:44 -070046import java.util.Collections;
Santos Cordona1610702014-06-04 20:22:56 -070047import java.util.LinkedList;
48import java.util.List;
Sailesh Nepal91990782014-03-08 17:45:52 -080049import java.util.Locale;
Sailesh Nepale8ecb982014-07-11 17:19:42 -070050import java.util.Objects;
Ben Gilad61925612014-03-11 19:06:36 -070051import java.util.Set;
Santos Cordondfc66012014-07-15 13:41:40 -070052import java.util.concurrent.CopyOnWriteArraySet;
Ben Gilad0407fb22014-01-09 16:18:41 -080053
Ben Gilad2495d572014-01-09 17:26:19 -080054/**
55 * Encapsulates all aspects of a given phone call throughout its lifecycle, starting
56 * from the time the call intent was received by Telecomm (vs. the time the call was
57 * connected etc).
58 */
Sailesh Nepal664837f2014-07-14 16:31:51 -070059final class Call implements CreateConnectionResponse {
Santos Cordon766d04f2014-05-06 10:28:25 -070060 /**
61 * Listener for events on the call.
62 */
63 interface Listener {
64 void onSuccessfulOutgoingCall(Call call);
Sailesh Nepal5a73b032014-06-25 15:53:21 -070065 void onFailedOutgoingCall(Call call, int errorCode, String errorMsg);
66 void onCancelledOutgoingCall(Call call);
Sailesh Nepalc92c4362014-07-04 18:33:21 -070067 void onSuccessfulIncomingCall(Call call);
Santos Cordon766d04f2014-05-06 10:28:25 -070068 void onFailedIncomingCall(Call call);
Ihab Awadcb387ac2014-05-28 16:49:38 -070069 void onRequestingRingback(Call call, boolean requestingRingback);
Evan Charlton352105c2014-06-03 14:10:54 -070070 void onPostDialWait(Call call, String remaining);
Sailesh Nepale20bc972014-07-09 21:22:36 -070071 void onCallCapabilitiesChanged(Call call);
Santos Cordona1610702014-06-04 20:22:56 -070072 void onExpiredConferenceCall(Call call);
73 void onConfirmedConferenceCall(Call call);
74 void onParentChanged(Call call);
75 void onChildrenChanged(Call call);
Ihab Awadff7493a2014-06-10 13:47:44 -070076 void onCannedSmsResponsesLoaded(Call call);
Andrew Lee3bcf9352014-07-23 12:36:05 -070077 void onVideoCallProviderChanged(Call call);
Santos Cordon64c7e962014-07-02 15:15:27 -070078 void onCallerInfoChanged(Call call);
Sailesh Nepal7e669572014-07-08 21:29:12 -070079 void onAudioModeIsVoipChanged(Call call);
Sailesh Nepal35faf8c2014-07-08 22:02:34 -070080 void onStatusHintsChanged(Call call);
Sailesh Nepale8ecb982014-07-11 17:19:42 -070081 void onHandleChanged(Call call);
82 void onCallerDisplayNameChanged(Call call);
Andrew Lee4a796602014-07-11 17:23:03 -070083 void onVideoStateChanged(Call call);
Sailesh Nepal9d58de52014-07-18 14:53:19 -070084 void onStartActivityFromInCall(Call call, PendingIntent intent);
Ihab Awad69eb0f52014-07-18 11:20:37 -070085 void onPhoneAccountChanged(Call call);
Santos Cordon64c7e962014-07-02 15:15:27 -070086 }
87
88 abstract static class ListenerBase implements Listener {
89 @Override
90 public void onSuccessfulOutgoingCall(Call call) {}
91 @Override
92 public void onFailedOutgoingCall(Call call, int errorCode, String errorMsg) {}
93 @Override
94 public void onCancelledOutgoingCall(Call call) {}
95 @Override
Sailesh Nepalc92c4362014-07-04 18:33:21 -070096 public void onSuccessfulIncomingCall(Call call) {}
Santos Cordon64c7e962014-07-02 15:15:27 -070097 @Override
98 public void onFailedIncomingCall(Call call) {}
99 @Override
100 public void onRequestingRingback(Call call, boolean requestingRingback) {}
101 @Override
102 public void onPostDialWait(Call call, String remaining) {}
103 @Override
Sailesh Nepale20bc972014-07-09 21:22:36 -0700104 public void onCallCapabilitiesChanged(Call call) {}
Santos Cordon64c7e962014-07-02 15:15:27 -0700105 @Override
106 public void onExpiredConferenceCall(Call call) {}
107 @Override
108 public void onConfirmedConferenceCall(Call call) {}
109 @Override
110 public void onParentChanged(Call call) {}
111 @Override
112 public void onChildrenChanged(Call call) {}
113 @Override
114 public void onCannedSmsResponsesLoaded(Call call) {}
115 @Override
Andrew Lee3bcf9352014-07-23 12:36:05 -0700116 public void onVideoCallProviderChanged(Call call) {}
Santos Cordon64c7e962014-07-02 15:15:27 -0700117 @Override
Santos Cordon64c7e962014-07-02 15:15:27 -0700118 public void onCallerInfoChanged(Call call) {}
Sailesh Nepal7e669572014-07-08 21:29:12 -0700119 @Override
120 public void onAudioModeIsVoipChanged(Call call) {}
Sailesh Nepal35faf8c2014-07-08 22:02:34 -0700121 @Override
122 public void onStatusHintsChanged(Call call) {}
Sailesh Nepale8ecb982014-07-11 17:19:42 -0700123 @Override
124 public void onHandleChanged(Call call) {}
125 @Override
126 public void onCallerDisplayNameChanged(Call call) {}
Andrew Lee4a796602014-07-11 17:23:03 -0700127 @Override
128 public void onVideoStateChanged(Call call) {}
Sailesh Nepal9d58de52014-07-18 14:53:19 -0700129 @Override
130 public void onStartActivityFromInCall(Call call, PendingIntent intent) {}
Ihab Awad69eb0f52014-07-18 11:20:37 -0700131 @Override
132 public void onPhoneAccountChanged(Call call) {}
Santos Cordon766d04f2014-05-06 10:28:25 -0700133 }
134
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700135 private static final OnQueryCompleteListener sCallerInfoQueryListener =
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700136 new OnQueryCompleteListener() {
137 /** ${inheritDoc} */
138 @Override
139 public void onQueryComplete(int token, Object cookie, CallerInfo callerInfo) {
140 if (cookie != null) {
141 ((Call) cookie).setCallerInfo(callerInfo, token);
142 }
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700143 }
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700144 };
145
146 private static final OnImageLoadCompleteListener sPhotoLoadListener =
147 new OnImageLoadCompleteListener() {
148 /** ${inheritDoc} */
149 @Override
150 public void onImageLoadComplete(
151 int token, Drawable photo, Bitmap photoIcon, Object cookie) {
152 if (cookie != null) {
153 ((Call) cookie).setPhoto(photo, photoIcon, token);
154 }
155 }
156 };
Ben Gilad0407fb22014-01-09 16:18:41 -0800157
Sailesh Nepal664837f2014-07-14 16:31:51 -0700158 private final Runnable mDirectToVoicemailRunnable = new Runnable() {
159 @Override
160 public void run() {
161 processDirectToVoicemail();
162 }
163 };
164
Sailesh Nepal810735e2014-03-18 18:15:46 -0700165 /** True if this is an incoming call. */
166 private final boolean mIsIncoming;
167
Ben Gilad0407fb22014-01-09 16:18:41 -0800168 /**
Sailesh Nepal664837f2014-07-14 16:31:51 -0700169 * The time this call was created. Beyond logging and such, may also be used for bookkeeping
170 * and specifically for marking certain call attempts as failed attempts.
Ben Gilad0407fb22014-01-09 16:18:41 -0800171 */
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700172 private final long mCreationTimeMillis = System.currentTimeMillis();
173
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700174 /** The gateway information associated with this call. This stores the original call handle
175 * that the user is attempting to connect to via the gateway, the actual handle to dial in
176 * order to connect the call via the gateway, as well as the package name of the gateway
177 * service. */
178 private final GatewayInfo mGatewayInfo;
179
Evan Charlton89176372014-07-19 18:23:09 -0700180 private PhoneAccountHandle mPhoneAccountHandle;
Nancy Chen77d2d0e2014-06-24 12:06:03 -0700181
Santos Cordon2174fb52014-05-29 08:22:56 -0700182 private final Handler mHandler = new Handler();
183
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700184 private long mConnectTimeMillis;
Ben Gilad0407fb22014-01-09 16:18:41 -0800185
Santos Cordon61d0f702014-02-19 02:52:23 -0800186 /** The state of the call. */
187 private CallState mState;
188
189 /** The handle with which to establish this call. */
Sailesh Nepalce704b92014-03-17 18:31:43 -0700190 private Uri mHandle;
Santos Cordon61d0f702014-02-19 02:52:23 -0800191
Sailesh Nepale8ecb982014-07-11 17:19:42 -0700192 /** The {@link CallPropertyPresentation} that controls how the handle is shown. */
193 private int mHandlePresentation;
194
195 /** The caller display name (CNAP) set by the connection service. */
196 private String mCallerDisplayName;
197
198 /** The {@link CallPropertyPresentation} that controls how the caller display name is shown. */
199 private int mCallerDisplayNamePresentation;
200
Ben Gilad0407fb22014-01-09 16:18:41 -0800201 /**
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700202 * The connection service which is attempted or already connecting this call.
Santos Cordon681663d2014-01-30 04:32:15 -0800203 */
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700204 private ConnectionServiceWrapper mConnectionService;
Ben Gilad61925612014-03-11 19:06:36 -0700205
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700206 private boolean mIsEmergencyCall;
207
Sai Cheemalapatib7157e92014-06-11 17:51:55 -0700208 private boolean mSpeakerphoneOn;
209
Tyler Gunn0a388fc2014-07-17 12:21:17 -0700210 /**
211 * Tracks the video states which were applicable over the duration of a call.
212 * See {@link android.telecomm.VideoCallProfile} for a list of valid video states.
213 */
214 private int mVideoStateHistory;
215
Tyler Gunnc4abd912014-07-08 14:22:10 -0700216 private int mVideoState;
217
Ben Gilad61925612014-03-11 19:06:36 -0700218 /**
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700219 * Disconnect cause for the call. Only valid if the state of the call is DISCONNECTED.
220 * See {@link android.telephony.DisconnectCause}.
221 */
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700222 private int mDisconnectCause = DisconnectCause.NOT_VALID;
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700223
224 /**
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700225 * Additional disconnect information provided by the connection service.
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700226 */
227 private String mDisconnectMessage;
228
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700229 /** Info used by the connection services. */
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700230 private Bundle mExtras = Bundle.EMPTY;
Sailesh Nepal84fa5f82014-04-02 11:01:11 -0700231
Santos Cordon766d04f2014-05-06 10:28:25 -0700232 /** Set of listeners on this call. */
Santos Cordondfc66012014-07-15 13:41:40 -0700233 private Set<Listener> mListeners = new CopyOnWriteArraySet<>();
Santos Cordon766d04f2014-05-06 10:28:25 -0700234
Sailesh Nepal664837f2014-07-14 16:31:51 -0700235 private CreateConnectionProcessor mCreateConnectionProcessor;
Santos Cordon682fe6b2014-05-20 08:56:39 -0700236
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700237 /** Caller information retrieved from the latest contact query. */
238 private CallerInfo mCallerInfo;
239
240 /** The latest token used with a contact info query. */
241 private int mQueryToken = 0;
242
Ihab Awadcb387ac2014-05-28 16:49:38 -0700243 /** Whether this call is requesting that Telecomm play the ringback tone on its behalf. */
244 private boolean mRequestingRingback = false;
245
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700246 /** Whether direct-to-voicemail query is pending. */
247 private boolean mDirectToVoicemailQueryPending;
Santos Cordon2174fb52014-05-29 08:22:56 -0700248
Sailesh Nepale20bc972014-07-09 21:22:36 -0700249 private int mCallCapabilities;
Santos Cordona1610702014-06-04 20:22:56 -0700250
251 private boolean mIsConference = false;
252
253 private Call mParentCall = null;
254
255 private List<Call> mChildCalls = new LinkedList<>();
256
Ihab Awadff7493a2014-06-10 13:47:44 -0700257 /** Set of text message responses allowed for this call, if applicable. */
258 private List<String> mCannedSmsResponses = Collections.EMPTY_LIST;
259
260 /** Whether an attempt has been made to load the text message responses. */
261 private boolean mCannedSmsResponsesLoadingStarted = false;
262
Andrew Lee3bcf9352014-07-23 12:36:05 -0700263 private IVideoCallProvider mVideoCallProvider;
Nancy Chena65d41f2014-06-24 12:06:03 -0700264
Sailesh Nepal7e669572014-07-08 21:29:12 -0700265 private boolean mAudioModeIsVoip;
Sailesh Nepal35faf8c2014-07-08 22:02:34 -0700266 private StatusHints mStatusHints;
Sailesh Nepal664837f2014-07-14 16:31:51 -0700267 private final ConnectionServiceRepository mRepository;
Sailesh Nepal7e669572014-07-08 21:29:12 -0700268
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700269 /**
Ben Gilad0407fb22014-01-09 16:18:41 -0800270 * Persists the specified parameters and initializes the new instance.
271 *
272 * @param handle The handle to dial.
Yorke Lee33501632014-03-17 19:24:12 -0700273 * @param gatewayInfo Gateway information to use for the call.
Evan Charlton94d01622014-07-20 12:32:05 -0700274 * @param accountHandle Account information to use for the call.
Sailesh Nepal810735e2014-03-18 18:15:46 -0700275 * @param isIncoming True if this is an incoming call.
Ben Gilad0407fb22014-01-09 16:18:41 -0800276 */
Sailesh Nepal664837f2014-07-14 16:31:51 -0700277 Call(
278 ConnectionServiceRepository repository,
279 Uri handle,
280 GatewayInfo gatewayInfo,
Evan Charlton94d01622014-07-20 12:32:05 -0700281 PhoneAccountHandle accountHandle,
Sailesh Nepal664837f2014-07-14 16:31:51 -0700282 boolean isIncoming,
283 boolean isConference) {
Santos Cordona1610702014-06-04 20:22:56 -0700284 mState = isConference ? CallState.ACTIVE : CallState.NEW;
Sailesh Nepal664837f2014-07-14 16:31:51 -0700285 mRepository = repository;
Sailesh Nepale8ecb982014-07-11 17:19:42 -0700286 setHandle(handle, CallPropertyPresentation.ALLOWED);
Yorke Lee33501632014-03-17 19:24:12 -0700287 mGatewayInfo = gatewayInfo;
Evan Charlton94d01622014-07-20 12:32:05 -0700288 mPhoneAccountHandle = accountHandle;
Sailesh Nepal810735e2014-03-18 18:15:46 -0700289 mIsIncoming = isIncoming;
Santos Cordona1610702014-06-04 20:22:56 -0700290 mIsConference = isConference;
Ihab Awadff7493a2014-06-10 13:47:44 -0700291 maybeLoadCannedSmsResponses();
Ben Gilad0407fb22014-01-09 16:18:41 -0800292 }
293
Santos Cordon766d04f2014-05-06 10:28:25 -0700294 void addListener(Listener listener) {
295 mListeners.add(listener);
296 }
297
298 void removeListener(Listener listener) {
299 mListeners.remove(listener);
300 }
301
Santos Cordon61d0f702014-02-19 02:52:23 -0800302 /** {@inheritDoc} */
303 @Override public String toString() {
Sailesh Nepal4538f012014-04-15 11:40:33 -0700304 String component = null;
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700305 if (mConnectionService != null && mConnectionService.getComponentName() != null) {
306 component = mConnectionService.getComponentName().flattenToShortString();
Sailesh Nepal4538f012014-04-15 11:40:33 -0700307 }
Tyler Gunn0a388fc2014-07-17 12:21:17 -0700308
309 return String.format(Locale.US, "[%s, %s, %s, %d]", mState, component,
310 Log.piiHandle(mHandle), getVideoState());
Santos Cordon61d0f702014-02-19 02:52:23 -0800311 }
312
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800313 CallState getState() {
Santos Cordona1610702014-06-04 20:22:56 -0700314 if (mIsConference) {
315 if (!mChildCalls.isEmpty()) {
316 // If we have child calls, just return the child call.
317 return mChildCalls.get(0).getState();
318 }
319 return CallState.ACTIVE;
320 } else {
321 return mState;
322 }
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800323 }
324
325 /**
326 * Sets the call state. Although there exists the notion of appropriate state transitions
327 * (see {@link CallState}), in practice those expectations break down when cellular systems
328 * misbehave and they do this very often. The result is that we do not enforce state transitions
329 * and instead keep the code resilient to unexpected state changes.
330 */
Sailesh Nepal810735e2014-03-18 18:15:46 -0700331 void setState(CallState newState) {
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700332 Preconditions.checkState(newState != CallState.DISCONNECTED ||
333 mDisconnectCause != DisconnectCause.NOT_VALID);
Sailesh Nepal810735e2014-03-18 18:15:46 -0700334 if (mState != newState) {
335 Log.v(this, "setState %s -> %s", mState, newState);
336 mState = newState;
Ihab Awadff7493a2014-06-10 13:47:44 -0700337 maybeLoadCannedSmsResponses();
Sailesh Nepal810735e2014-03-18 18:15:46 -0700338 }
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800339 }
340
Ihab Awadcb387ac2014-05-28 16:49:38 -0700341 void setRequestingRingback(boolean requestingRingback) {
342 mRequestingRingback = requestingRingback;
343 for (Listener l : mListeners) {
344 l.onRequestingRingback(this, mRequestingRingback);
345 }
346 }
347
348 boolean isRequestingRingback() {
349 return mRequestingRingback;
350 }
351
Sailesh Nepalce704b92014-03-17 18:31:43 -0700352 Uri getHandle() {
Ben Gilad0bf5b912014-01-28 17:55:57 -0800353 return mHandle;
354 }
355
Sailesh Nepale8ecb982014-07-11 17:19:42 -0700356 int getHandlePresentation() {
357 return mHandlePresentation;
358 }
359
360 void setHandle(Uri handle, int presentation) {
361 if (!Objects.equals(handle, mHandle) || presentation != mHandlePresentation) {
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700362 mHandle = handle;
Sailesh Nepale8ecb982014-07-11 17:19:42 -0700363 mHandlePresentation = presentation;
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700364 mIsEmergencyCall = mHandle != null && PhoneNumberUtils.isLocalEmergencyNumber(
Yorke Lee66255452014-06-05 08:09:24 -0700365 TelecommApp.getInstance(), mHandle.getSchemeSpecificPart());
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700366 startCallerInfoLookup();
Sailesh Nepale8ecb982014-07-11 17:19:42 -0700367 for (Listener l : mListeners) {
368 l.onHandleChanged(this);
369 }
370 }
371 }
372
373 String getCallerDisplayName() {
374 return mCallerDisplayName;
375 }
376
377 int getCallerDisplayNamePresentation() {
378 return mCallerDisplayNamePresentation;
379 }
380
381 void setCallerDisplayName(String callerDisplayName, int presentation) {
382 if (!TextUtils.equals(callerDisplayName, mCallerDisplayName) ||
383 presentation != mCallerDisplayNamePresentation) {
384 mCallerDisplayName = callerDisplayName;
385 mCallerDisplayNamePresentation = presentation;
386 for (Listener l : mListeners) {
387 l.onCallerDisplayNameChanged(this);
388 }
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700389 }
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700390 }
391
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700392 String getName() {
393 return mCallerInfo == null ? null : mCallerInfo.name;
394 }
395
396 Bitmap getPhotoIcon() {
397 return mCallerInfo == null ? null : mCallerInfo.cachedPhotoIcon;
398 }
399
400 Drawable getPhoto() {
401 return mCallerInfo == null ? null : mCallerInfo.cachedPhoto;
402 }
403
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700404 /**
405 * @param disconnectCause The reason for the disconnection, any of
406 * {@link android.telephony.DisconnectCause}.
Sailesh Nepal905dfba2014-07-14 08:20:41 -0700407 * @param disconnectMessage Optional message about the disconnect.
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700408 */
409 void setDisconnectCause(int disconnectCause, String disconnectMessage) {
410 // TODO: Consider combining this method with a setDisconnected() method that is totally
411 // separate from setState.
412 mDisconnectCause = disconnectCause;
413 mDisconnectMessage = disconnectMessage;
414 }
415
416 int getDisconnectCause() {
417 return mDisconnectCause;
418 }
419
420 String getDisconnectMessage() {
421 return mDisconnectMessage;
422 }
423
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700424 boolean isEmergencyCall() {
425 return mIsEmergencyCall;
Santos Cordon61d0f702014-02-19 02:52:23 -0800426 }
427
Yorke Lee33501632014-03-17 19:24:12 -0700428 /**
429 * @return The original handle this call is associated with. In-call services should use this
430 * handle when indicating in their UI the handle that is being called.
431 */
432 public Uri getOriginalHandle() {
433 if (mGatewayInfo != null && !mGatewayInfo.isEmpty()) {
434 return mGatewayInfo.getOriginalHandle();
435 }
436 return getHandle();
437 }
438
439 GatewayInfo getGatewayInfo() {
440 return mGatewayInfo;
441 }
442
Evan Charlton89176372014-07-19 18:23:09 -0700443 PhoneAccountHandle getPhoneAccount() {
444 return mPhoneAccountHandle;
Nancy Chen77d2d0e2014-06-24 12:06:03 -0700445 }
446
Evan Charlton89176372014-07-19 18:23:09 -0700447 void setPhoneAccount(PhoneAccountHandle accountHandle) {
448 if (!Objects.equals(mPhoneAccountHandle, accountHandle)) {
449 mPhoneAccountHandle = accountHandle;
Ihab Awad69eb0f52014-07-18 11:20:37 -0700450 for (Listener l : mListeners) {
451 l.onPhoneAccountChanged(this);
452 }
453 }
Nancy Chen53ceedc2014-07-08 18:56:51 -0700454 }
455
Sailesh Nepal810735e2014-03-18 18:15:46 -0700456 boolean isIncoming() {
457 return mIsIncoming;
458 }
459
Ben Gilad0407fb22014-01-09 16:18:41 -0800460 /**
461 * @return The "age" of this call object in milliseconds, which typically also represents the
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700462 * period since this call was added to the set pending outgoing calls, see
463 * mCreationTimeMillis.
Ben Gilad0407fb22014-01-09 16:18:41 -0800464 */
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700465 long getAgeMillis() {
466 return System.currentTimeMillis() - mCreationTimeMillis;
Ben Gilad0407fb22014-01-09 16:18:41 -0800467 }
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800468
Yorke Leef98fb572014-03-05 10:56:55 -0800469 /**
470 * @return The time when this call object was created and added to the set of pending outgoing
471 * calls.
472 */
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700473 long getCreationTimeMillis() {
474 return mCreationTimeMillis;
475 }
476
477 long getConnectTimeMillis() {
478 return mConnectTimeMillis;
479 }
480
481 void setConnectTimeMillis(long connectTimeMillis) {
482 mConnectTimeMillis = connectTimeMillis;
Yorke Leef98fb572014-03-05 10:56:55 -0800483 }
484
Sailesh Nepale20bc972014-07-09 21:22:36 -0700485 int getCallCapabilities() {
486 return mCallCapabilities;
Santos Cordona1610702014-06-04 20:22:56 -0700487 }
488
Sailesh Nepale20bc972014-07-09 21:22:36 -0700489 void setCallCapabilities(int callCapabilities) {
490 if (mCallCapabilities != callCapabilities) {
491 mCallCapabilities = callCapabilities;
Santos Cordona1610702014-06-04 20:22:56 -0700492 for (Listener l : mListeners) {
Sailesh Nepale20bc972014-07-09 21:22:36 -0700493 l.onCallCapabilitiesChanged(this);
Santos Cordona1610702014-06-04 20:22:56 -0700494 }
495 }
496 }
497
498 Call getParentCall() {
499 return mParentCall;
500 }
501
502 List<Call> getChildCalls() {
503 return mChildCalls;
504 }
505
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700506 ConnectionServiceWrapper getConnectionService() {
507 return mConnectionService;
Santos Cordon681663d2014-01-30 04:32:15 -0800508 }
509
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700510 void setConnectionService(ConnectionServiceWrapper service) {
511 Preconditions.checkNotNull(service);
512
513 clearConnectionService();
514
515 service.incrementAssociatedCallCount();
516 mConnectionService = service;
517 mConnectionService.addCall(this);
Sailesh Nepal0e5410a2014-04-04 01:20:58 -0700518 }
519
520 /**
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700521 * Clears the associated connection service.
Sailesh Nepal0e5410a2014-04-04 01:20:58 -0700522 */
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700523 void clearConnectionService() {
524 if (mConnectionService != null) {
525 ConnectionServiceWrapper serviceTemp = mConnectionService;
526 mConnectionService = null;
527 serviceTemp.removeCall(this);
Santos Cordonc499c1c2014-04-14 17:13:14 -0700528
529 // Decrementing the count can cause the service to unbind, which itself can trigger the
530 // service-death code. Since the service death code tries to clean up any associated
531 // calls, we need to make sure to remove that information (e.g., removeCall()) before
532 // we decrement. Technically, invoking removeCall() prior to decrementing is all that is
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700533 // necessary, but cleaning up mConnectionService prior to triggering an unbind is good
534 // to do.
535 decrementAssociatedCallCount(serviceTemp);
Yorke Leeadee12d2014-03-13 12:08:30 -0700536 }
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800537 }
538
Sailesh Nepal664837f2014-07-14 16:31:51 -0700539 private void processDirectToVoicemail() {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700540 if (mDirectToVoicemailQueryPending) {
Santos Cordon2174fb52014-05-29 08:22:56 -0700541 if (mCallerInfo != null && mCallerInfo.shouldSendToVoicemail) {
542 Log.i(this, "Directing call to voicemail: %s.", this);
543 // TODO(santoscordon): Once we move State handling from CallsManager to Call, we
544 // will not need to set RINGING state prior to calling reject.
545 setState(CallState.RINGING);
Ihab Awadff7493a2014-06-10 13:47:44 -0700546 reject(false, null);
Santos Cordon2174fb52014-05-29 08:22:56 -0700547 } else {
548 // TODO(santoscordon): Make this class (not CallsManager) responsible for changing
549 // the call state to RINGING.
550
551 // TODO(santoscordon): Replace this with state transition to RINGING.
552 for (Listener l : mListeners) {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700553 l.onSuccessfulIncomingCall(this);
Santos Cordon2174fb52014-05-29 08:22:56 -0700554 }
555 }
556
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700557 mDirectToVoicemailQueryPending = false;
Santos Cordon766d04f2014-05-06 10:28:25 -0700558 }
559 }
560
Santos Cordon766d04f2014-05-06 10:28:25 -0700561 /**
Sailesh Nepal664837f2014-07-14 16:31:51 -0700562 * Starts the create connection sequence. Upon completion, there should exist an active
563 * connection through a connection service (or the call will have failed).
Santos Cordon766d04f2014-05-06 10:28:25 -0700564 */
Sailesh Nepal664837f2014-07-14 16:31:51 -0700565 void startCreateConnection() {
566 Preconditions.checkState(mCreateConnectionProcessor == null);
567 mCreateConnectionProcessor = new CreateConnectionProcessor(this, mRepository, this);
568 mCreateConnectionProcessor.process();
Santos Cordon766d04f2014-05-06 10:28:25 -0700569 }
570
Sailesh Nepal5a73b032014-06-25 15:53:21 -0700571 @Override
Santos Cordon72890ce2014-07-21 01:32:04 -0700572 public void handleCreateConnectionSuccessful(
573 ConnectionRequest request, ParcelableConnection connection) {
Sailesh Nepal664837f2014-07-14 16:31:51 -0700574 mCreateConnectionProcessor = null;
Santos Cordon72890ce2014-07-21 01:32:04 -0700575 setState(getStateFromConnectionState(connection.getState()));
576 setPhoneAccount(connection.getPhoneAccount());
577 setHandle(connection.getHandle(), connection.getHandlePresentation());
578 setCallerDisplayName(
579 connection.getCallerDisplayName(), connection.getCallerDisplayNamePresentation());
Andrew Lee3bcf9352014-07-23 12:36:05 -0700580
581 setVideoCallProvider(connection.getVideoCallProvider());
Tyler Gunnb1a95a72014-07-23 14:08:19 -0700582 setVideoState(connection.getVideoState());
Sailesh Nepal664837f2014-07-14 16:31:51 -0700583
584 if (mIsIncoming) {
585 // We do not handle incoming calls immediately when they are verified by the connection
586 // service. We allow the caller-info-query code to execute first so that we can read the
587 // direct-to-voicemail property before deciding if we want to show the incoming call to
588 // the user or if we want to reject the call.
589 mDirectToVoicemailQueryPending = true;
590
Sailesh Nepal664837f2014-07-14 16:31:51 -0700591 // Timeout the direct-to-voicemail lookup execution so that we dont wait too long before
592 // showing the user the incoming call screen.
593 mHandler.postDelayed(mDirectToVoicemailRunnable, Timeouts.getDirectToVoicemailMillis());
594 } else {
595 for (Listener l : mListeners) {
596 l.onSuccessfulOutgoingCall(this);
597 }
Santos Cordon766d04f2014-05-06 10:28:25 -0700598 }
599 }
600
Sailesh Nepal5a73b032014-06-25 15:53:21 -0700601 @Override
Sailesh Nepal664837f2014-07-14 16:31:51 -0700602 public void handleCreateConnectionFailed(int code, String msg) {
603 mCreateConnectionProcessor = null;
604 if (mIsIncoming) {
605 clearConnectionService();
606 setDisconnectCause(code, null);
607 setState(CallState.DISCONNECTED);
Santos Cordon682fe6b2014-05-20 08:56:39 -0700608
Sailesh Nepal664837f2014-07-14 16:31:51 -0700609 Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]);
610 for (int i = 0; i < listeners.length; i++) {
611 listeners[i].onFailedIncomingCall(this);
612 }
613 } else {
614 Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]);
615 for (int i = 0; i < listeners.length; i++) {
616 listeners[i].onFailedOutgoingCall(this, code, msg);
617 }
618 clearConnectionService();
619 }
Sailesh Nepal5a73b032014-06-25 15:53:21 -0700620 }
621
622 @Override
Sailesh Nepal664837f2014-07-14 16:31:51 -0700623 public void handleCreateConnectionCancelled() {
624 mCreateConnectionProcessor = null;
625 if (mIsIncoming) {
626 clearConnectionService();
Santos Cordonfd6ca442014-07-24 15:34:01 -0700627 setDisconnectCause(DisconnectCause.OUTGOING_CANCELED, null);
Sailesh Nepal664837f2014-07-14 16:31:51 -0700628 setState(CallState.DISCONNECTED);
Sailesh Nepal5a73b032014-06-25 15:53:21 -0700629
Sailesh Nepal664837f2014-07-14 16:31:51 -0700630 Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]);
631 for (int i = 0; i < listeners.length; i++) {
632 listeners[i].onFailedIncomingCall(this);
633 }
634 } else {
635 Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]);
636 for (int i = 0; i < listeners.length; i++) {
637 listeners[i].onCancelledOutgoingCall(this);
638 }
639 clearConnectionService();
640 }
Santos Cordon766d04f2014-05-06 10:28:25 -0700641 }
642
643 /**
Ihab Awad74549ec2014-03-10 15:33:25 -0700644 * Plays the specified DTMF tone.
645 */
646 void playDtmfTone(char digit) {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700647 if (mConnectionService == null) {
648 Log.w(this, "playDtmfTone() request on a call without a connection service.");
Ihab Awad74549ec2014-03-10 15:33:25 -0700649 } else {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700650 Log.i(this, "Send playDtmfTone to connection service for call %s", this);
651 mConnectionService.playDtmfTone(this, digit);
Ihab Awad74549ec2014-03-10 15:33:25 -0700652 }
653 }
654
655 /**
656 * Stops playing any currently playing DTMF tone.
657 */
658 void stopDtmfTone() {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700659 if (mConnectionService == null) {
660 Log.w(this, "stopDtmfTone() request on a call without a connectino service.");
Ihab Awad74549ec2014-03-10 15:33:25 -0700661 } else {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700662 Log.i(this, "Send stopDtmfTone to connection service for call %s", this);
663 mConnectionService.stopDtmfTone(this);
Ihab Awad74549ec2014-03-10 15:33:25 -0700664 }
665 }
666
667 /**
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700668 * Attempts to disconnect the call through the connection service.
Santos Cordon049b7b62014-01-30 05:34:26 -0800669 */
670 void disconnect() {
Nancy Chen53ceedc2014-07-08 18:56:51 -0700671 if (mState == CallState.NEW || mState == CallState.PRE_DIAL_WAIT) {
Santos Cordon682fe6b2014-05-20 08:56:39 -0700672 Log.v(this, "Aborting call %s", this);
673 abort();
Santos Cordon74d420b2014-05-07 14:38:47 -0700674 } else if (mState != CallState.ABORTED && mState != CallState.DISCONNECTED) {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700675 Preconditions.checkNotNull(mConnectionService);
Santos Cordon766d04f2014-05-06 10:28:25 -0700676
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700677 Log.i(this, "Send disconnect to connection service for call: %s", this);
678 // The call isn't officially disconnected until the connection service confirms that the
679 // call was actually disconnected. Only then is the association between call and
680 // connection service severed, see {@link CallsManager#markCallAsDisconnected}.
681 mConnectionService.disconnect(this);
Santos Cordon049b7b62014-01-30 05:34:26 -0800682 }
683 }
684
Santos Cordon682fe6b2014-05-20 08:56:39 -0700685 void abort() {
Sailesh Nepal664837f2014-07-14 16:31:51 -0700686 if (mCreateConnectionProcessor != null) {
687 mCreateConnectionProcessor.abort();
Nancy Chen53ceedc2014-07-08 18:56:51 -0700688 } else if (mState == CallState.PRE_DIAL_WAIT) {
689 handleCreateConnectionFailed(DisconnectCause.LOCAL, null);
Santos Cordon682fe6b2014-05-20 08:56:39 -0700690 }
691 }
692
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800693 /**
Santos Cordon61d0f702014-02-19 02:52:23 -0800694 * Answers the call if it is ringing.
Andrew Lee38931d02014-07-16 10:17:36 -0700695 *
696 * @param videoState The video state in which to answer the call.
Santos Cordon61d0f702014-02-19 02:52:23 -0800697 */
Andrew Lee38931d02014-07-16 10:17:36 -0700698 void answer(int videoState) {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700699 Preconditions.checkNotNull(mConnectionService);
Santos Cordon61d0f702014-02-19 02:52:23 -0800700
701 // Check to verify that the call is still in the ringing state. A call can change states
702 // between the time the user hits 'answer' and Telecomm receives the command.
703 if (isRinging("answer")) {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700704 // At this point, we are asking the connection service to answer but we don't assume
705 // that it will work. Instead, we wait until confirmation from the connectino service
706 // that the call is in a non-RINGING state before changing the UI. See
707 // {@link ConnectionServiceAdapter#setActive} and other set* methods.
Andrew Lee38931d02014-07-16 10:17:36 -0700708 mConnectionService.answer(this, videoState);
Santos Cordon61d0f702014-02-19 02:52:23 -0800709 }
710 }
711
712 /**
713 * Rejects the call if it is ringing.
Ihab Awadff7493a2014-06-10 13:47:44 -0700714 *
715 * @param rejectWithMessage Whether to send a text message as part of the call rejection.
716 * @param textMessage An optional text message to send as part of the rejection.
Santos Cordon61d0f702014-02-19 02:52:23 -0800717 */
Ihab Awadff7493a2014-06-10 13:47:44 -0700718 void reject(boolean rejectWithMessage, String textMessage) {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700719 Preconditions.checkNotNull(mConnectionService);
Santos Cordon61d0f702014-02-19 02:52:23 -0800720
721 // Check to verify that the call is still in the ringing state. A call can change states
722 // between the time the user hits 'reject' and Telecomm receives the command.
723 if (isRinging("reject")) {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700724 mConnectionService.reject(this);
Santos Cordon61d0f702014-02-19 02:52:23 -0800725 }
726 }
727
728 /**
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700729 * Puts the call on hold if it is currently active.
730 */
731 void hold() {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700732 Preconditions.checkNotNull(mConnectionService);
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700733
734 if (mState == CallState.ACTIVE) {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700735 mConnectionService.hold(this);
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700736 }
737 }
738
739 /**
740 * Releases the call from hold if it is currently active.
741 */
742 void unhold() {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700743 Preconditions.checkNotNull(mConnectionService);
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700744
745 if (mState == CallState.ON_HOLD) {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700746 mConnectionService.unhold(this);
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700747 }
748 }
749
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700750 /** Checks if this is a live call or not. */
751 boolean isAlive() {
752 switch (mState) {
753 case NEW:
754 case RINGING:
755 case DISCONNECTED:
756 case ABORTED:
757 return false;
758 default:
759 return true;
760 }
761 }
762
Santos Cordon40f78c22014-04-07 02:11:42 -0700763 boolean isActive() {
Ihab Awad84bfe472014-07-13 17:11:57 -0700764 return mState == CallState.ACTIVE;
Santos Cordon40f78c22014-04-07 02:11:42 -0700765 }
766
Sailesh Nepal84fa5f82014-04-02 11:01:11 -0700767 Bundle getExtras() {
768 return mExtras;
769 }
770
771 void setExtras(Bundle extras) {
772 mExtras = extras;
773 }
774
Santos Cordon5ba7f272014-05-28 13:59:49 -0700775 Uri getRingtone() {
776 return mCallerInfo == null ? null : mCallerInfo.contactRingtoneUri;
777 }
778
Evan Charlton352105c2014-06-03 14:10:54 -0700779 void onPostDialWait(String remaining) {
780 for (Listener l : mListeners) {
781 l.onPostDialWait(this, remaining);
782 }
783 }
784
785 void postDialContinue(boolean proceed) {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700786 mConnectionService.onPostDialContinue(this, proceed);
Evan Charlton352105c2014-06-03 14:10:54 -0700787 }
788
Sailesh Nepal77da19e2014-07-02 21:31:16 -0700789 void phoneAccountClicked() {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700790 mConnectionService.onPhoneAccountClicked(this);
Sailesh Nepal77da19e2014-07-02 21:31:16 -0700791 }
792
Santos Cordona1610702014-06-04 20:22:56 -0700793 void conferenceInto(Call conferenceCall) {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700794 if (mConnectionService == null) {
795 Log.w(this, "conference requested on a call without a connection service.");
Santos Cordona1610702014-06-04 20:22:56 -0700796 } else {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700797 mConnectionService.conference(conferenceCall, this);
Santos Cordona1610702014-06-04 20:22:56 -0700798 }
799 }
800
801 void expireConference() {
802 // The conference call expired before we got a confirmation of the conference from the
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700803 // connection service...so start shutting down.
804 clearConnectionService();
Santos Cordona1610702014-06-04 20:22:56 -0700805 for (Listener l : mListeners) {
806 l.onExpiredConferenceCall(this);
807 }
808 }
809
810 void confirmConference() {
811 Log.v(this, "confirming Conf call %s", mListeners);
812 for (Listener l : mListeners) {
813 l.onConfirmedConferenceCall(this);
814 }
815 }
816
817 void splitFromConference() {
818 // TODO(santoscordon): todo
819 }
820
Sailesh Nepale8ecb982014-07-11 17:19:42 -0700821 void swapWithBackgroundCall() {
822 mConnectionService.swapWithBackgroundCall(this);
823 }
824
Santos Cordona1610702014-06-04 20:22:56 -0700825 void setParentCall(Call parentCall) {
826 if (parentCall == this) {
827 Log.e(this, new Exception(), "setting the parent to self");
828 return;
829 }
830 Preconditions.checkState(parentCall == null || mParentCall == null);
831
832 Call oldParent = mParentCall;
833 if (mParentCall != null) {
834 mParentCall.removeChildCall(this);
835 }
836 mParentCall = parentCall;
837 if (mParentCall != null) {
838 mParentCall.addChildCall(this);
839 }
840
841 for (Listener l : mListeners) {
842 l.onParentChanged(this);
843 }
844 }
845
846 private void addChildCall(Call call) {
847 if (!mChildCalls.contains(call)) {
848 mChildCalls.add(call);
849
850 for (Listener l : mListeners) {
851 l.onChildrenChanged(this);
852 }
853 }
854 }
855
856 private void removeChildCall(Call call) {
857 if (mChildCalls.remove(call)) {
858 for (Listener l : mListeners) {
859 l.onChildrenChanged(this);
860 }
861 }
862 }
863
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800864 /**
Ihab Awadff7493a2014-06-10 13:47:44 -0700865 * Return whether the user can respond to this {@code Call} via an SMS message.
866 *
867 * @return true if the "Respond via SMS" feature should be enabled
868 * for this incoming call.
869 *
870 * The general rule is that we *do* allow "Respond via SMS" except for
871 * the few (relatively rare) cases where we know for sure it won't
872 * work, namely:
873 * - a bogus or blank incoming number
874 * - a call from a SIP address
875 * - a "call presentation" that doesn't allow the number to be revealed
876 *
877 * In all other cases, we allow the user to respond via SMS.
878 *
879 * Note that this behavior isn't perfect; for example we have no way
880 * to detect whether the incoming call is from a landline (with most
881 * networks at least), so we still enable this feature even though
882 * SMSes to that number will silently fail.
883 */
884 boolean isRespondViaSmsCapable() {
885 if (mState != CallState.RINGING) {
886 return false;
887 }
888
889 if (getHandle() == null) {
890 // No incoming number known or call presentation is "PRESENTATION_RESTRICTED", in
891 // other words, the user should not be able to see the incoming phone number.
892 return false;
893 }
894
895 if (PhoneNumberUtils.isUriNumber(getHandle().toString())) {
896 // The incoming number is actually a URI (i.e. a SIP address),
897 // not a regular PSTN phone number, and we can't send SMSes to
898 // SIP addresses.
899 // (TODO: That might still be possible eventually, though. Is
900 // there some SIP-specific equivalent to sending a text message?)
901 return false;
902 }
903
904 // Is there a valid SMS application on the phone?
905 if (SmsApplication.getDefaultRespondViaMessageApplication(TelecommApp.getInstance(),
906 true /*updateIfNeeded*/) == null) {
907 return false;
908 }
909
910 // TODO: with some carriers (in certain countries) you *can* actually
911 // tell whether a given number is a mobile phone or not. So in that
912 // case we could potentially return false here if the incoming call is
913 // from a land line.
914
915 // If none of the above special cases apply, it's OK to enable the
916 // "Respond via SMS" feature.
917 return true;
918 }
919
920 List<String> getCannedSmsResponses() {
921 return mCannedSmsResponses;
922 }
923
924 /**
Santos Cordon61d0f702014-02-19 02:52:23 -0800925 * @return True if the call is ringing, else logs the action name.
926 */
927 private boolean isRinging(String actionName) {
928 if (mState == CallState.RINGING) {
929 return true;
930 }
931
Sailesh Nepalf1c191d2014-03-07 18:17:39 -0800932 Log.i(this, "Request to %s a non-ringing call %s", actionName, this);
Santos Cordon61d0f702014-02-19 02:52:23 -0800933 return false;
934 }
935
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800936 @SuppressWarnings("rawtypes")
937 private void decrementAssociatedCallCount(ServiceBinder binder) {
938 if (binder != null) {
939 binder.decrementAssociatedCallCount();
940 }
941 }
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700942
943 /**
944 * Looks up contact information based on the current handle.
945 */
946 private void startCallerInfoLookup() {
947 String number = mHandle == null ? null : mHandle.getSchemeSpecificPart();
948
949 mQueryToken++; // Updated so that previous queries can no longer set the information.
950 mCallerInfo = null;
951 if (!TextUtils.isEmpty(number)) {
Santos Cordon5ba7f272014-05-28 13:59:49 -0700952 Log.v(this, "Looking up information for: %s.", Log.piiHandle(number));
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700953 CallerInfoAsyncQuery.startQuery(
954 mQueryToken,
955 TelecommApp.getInstance(),
956 number,
957 sCallerInfoQueryListener,
958 this);
959 }
960 }
961
962 /**
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700963 * Saves the specified caller info if the specified token matches that of the last query
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700964 * that was made.
965 *
966 * @param callerInfo The new caller information to set.
967 * @param token The token used with this query.
968 */
969 private void setCallerInfo(CallerInfo callerInfo, int token) {
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700970 Preconditions.checkNotNull(callerInfo);
971
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700972 if (mQueryToken == token) {
973 mCallerInfo = callerInfo;
Santos Cordon5ba7f272014-05-28 13:59:49 -0700974 Log.i(this, "CallerInfo received for %s: %s", Log.piiHandle(mHandle), callerInfo);
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700975
Makoto Onukia1662d02014-07-10 15:31:59 -0700976 if (mCallerInfo.contactDisplayPhotoUri != null) {
977 Log.d(this, "Searching person uri %s for call %s",
978 mCallerInfo.contactDisplayPhotoUri, this);
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700979 ContactsAsyncHelper.startObtainPhotoAsync(
980 token,
981 TelecommApp.getInstance(),
Makoto Onukia1662d02014-07-10 15:31:59 -0700982 mCallerInfo.contactDisplayPhotoUri,
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700983 sPhotoLoadListener,
984 this);
Makoto Onukia1662d02014-07-10 15:31:59 -0700985 // Do not call onCallerInfoChanged yet in this case. We call it in setPhoto().
Santos Cordon64c7e962014-07-02 15:15:27 -0700986 } else {
987 for (Listener l : mListeners) {
988 l.onCallerInfoChanged(this);
989 }
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700990 }
Santos Cordon2174fb52014-05-29 08:22:56 -0700991
992 processDirectToVoicemail();
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700993 }
994 }
995
Yorke Lee6f3f7af2014-07-11 10:59:46 -0700996 CallerInfo getCallerInfo() {
997 return mCallerInfo;
998 }
999
Santos Cordon99c8a6f2014-05-28 18:28:47 -07001000 /**
1001 * Saves the specified photo information if the specified token matches that of the last query.
1002 *
1003 * @param photo The photo as a drawable.
1004 * @param photoIcon The photo as a small icon.
1005 * @param token The token used with this query.
1006 */
1007 private void setPhoto(Drawable photo, Bitmap photoIcon, int token) {
1008 if (mQueryToken == token) {
1009 mCallerInfo.cachedPhoto = photo;
1010 mCallerInfo.cachedPhotoIcon = photoIcon;
Santos Cordon64c7e962014-07-02 15:15:27 -07001011
1012 for (Listener l : mListeners) {
1013 l.onCallerInfoChanged(this);
1014 }
Santos Cordonfd71f4a2014-05-28 13:59:49 -07001015 }
1016 }
Ihab Awadff7493a2014-06-10 13:47:44 -07001017
1018 private void maybeLoadCannedSmsResponses() {
1019 if (mIsIncoming && isRespondViaSmsCapable() && !mCannedSmsResponsesLoadingStarted) {
1020 Log.d(this, "maybeLoadCannedSmsResponses: starting task to load messages");
1021 mCannedSmsResponsesLoadingStarted = true;
1022 RespondViaSmsManager.getInstance().loadCannedTextMessages(
1023 new Response<Void, List<String>>() {
1024 @Override
1025 public void onResult(Void request, List<String>... result) {
1026 if (result.length > 0) {
1027 Log.d(this, "maybeLoadCannedSmsResponses: got %s", result[0]);
1028 mCannedSmsResponses = result[0];
1029 for (Listener l : mListeners) {
1030 l.onCannedSmsResponsesLoaded(Call.this);
1031 }
1032 }
1033 }
1034
1035 @Override
1036 public void onError(Void request, int code, String msg) {
1037 Log.w(Call.this, "Error obtaining canned SMS responses: %d %s", code,
1038 msg);
1039 }
1040 }
1041 );
1042 } else {
1043 Log.d(this, "maybeLoadCannedSmsResponses: doing nothing");
1044 }
1045 }
Sai Cheemalapatib7157e92014-06-11 17:51:55 -07001046
1047 /**
1048 * Sets speakerphone option on when call begins.
1049 */
1050 public void setStartWithSpeakerphoneOn(boolean startWithSpeakerphone) {
1051 mSpeakerphoneOn = startWithSpeakerphone;
1052 }
1053
1054 /**
1055 * Returns speakerphone option.
1056 *
1057 * @return Whether or not speakerphone should be set automatically when call begins.
1058 */
1059 public boolean getStartWithSpeakerphoneOn() {
1060 return mSpeakerphoneOn;
1061 }
Andrew Leee9a77652014-06-26 13:07:57 -07001062
1063 /**
Andrew Lee3bcf9352014-07-23 12:36:05 -07001064 * Sets a video call provider for the call.
Andrew Leee9a77652014-06-26 13:07:57 -07001065 */
Andrew Lee3bcf9352014-07-23 12:36:05 -07001066 public void setVideoCallProvider(IVideoCallProvider videoCallProvider) {
1067 mVideoCallProvider = videoCallProvider;
Nancy Chena65d41f2014-06-24 12:06:03 -07001068 for (Listener l : mListeners) {
Andrew Lee3bcf9352014-07-23 12:36:05 -07001069 l.onVideoCallProviderChanged(Call.this);
Nancy Chena65d41f2014-06-24 12:06:03 -07001070 }
1071 }
1072
1073 /**
Andrew Lee3bcf9352014-07-23 12:36:05 -07001074 * @return Return the {@link VideoCallProvider} binder.
Nancy Chena65d41f2014-06-24 12:06:03 -07001075 */
Andrew Lee3bcf9352014-07-23 12:36:05 -07001076 public IVideoCallProvider getVideoCallProvider() {
1077 return mVideoCallProvider;
Andrew Leee9a77652014-06-26 13:07:57 -07001078 }
Tyler Gunne19cc002014-07-01 11:32:53 -07001079
1080 /**
Tyler Gunnc4abd912014-07-08 14:22:10 -07001081 * The current video state for the call.
1082 * Valid values: {@link android.telecomm.VideoCallProfile#VIDEO_STATE_AUDIO_ONLY},
1083 * {@link android.telecomm.VideoCallProfile#VIDEO_STATE_BIDIRECTIONAL},
1084 * {@link android.telecomm.VideoCallProfile#VIDEO_STATE_TX_ENABLED},
1085 * {@link android.telecomm.VideoCallProfile#VIDEO_STATE_RX_ENABLED}.
1086 *
1087 * @return True if video is enabled.
1088 */
1089 public int getVideoState() {
1090 return mVideoState;
1091 }
1092
1093 /**
Tyler Gunn0a388fc2014-07-17 12:21:17 -07001094 * Returns the video states which were applicable over the duration of a call.
1095 * See {@link android.telecomm.VideoCallProfile} for a list of valid video states.
1096 *
1097 * @return The video states applicable over the duration of the call.
1098 */
1099 public int getVideoStateHistory() {
1100 return mVideoStateHistory;
1101 }
1102
1103 /**
1104 * Determines the current video state for the call.
1105 * For an outgoing call determines the desired video state for the call.
Tyler Gunnc4abd912014-07-08 14:22:10 -07001106 * Valid values: {@link android.telecomm.VideoCallProfile#VIDEO_STATE_AUDIO_ONLY},
1107 * {@link android.telecomm.VideoCallProfile#VIDEO_STATE_BIDIRECTIONAL},
1108 * {@link android.telecomm.VideoCallProfile#VIDEO_STATE_TX_ENABLED},
1109 * {@link android.telecomm.VideoCallProfile#VIDEO_STATE_RX_ENABLED}.
1110 *
Tyler Gunn0a388fc2014-07-17 12:21:17 -07001111 * @param videoState The video state for the call.
Tyler Gunnc4abd912014-07-08 14:22:10 -07001112 */
1113 public void setVideoState(int videoState) {
Tyler Gunn0a388fc2014-07-17 12:21:17 -07001114 // Track which video states were applicable over the duration of the call.
1115 mVideoStateHistory = mVideoStateHistory | videoState;
1116
Tyler Gunnc4abd912014-07-08 14:22:10 -07001117 mVideoState = videoState;
Andrew Lee4a796602014-07-11 17:23:03 -07001118 for (Listener l : mListeners) {
1119 l.onVideoStateChanged(this);
1120 }
Tyler Gunnc4abd912014-07-08 14:22:10 -07001121 }
Sailesh Nepal7e669572014-07-08 21:29:12 -07001122
1123 public boolean getAudioModeIsVoip() {
1124 return mAudioModeIsVoip;
1125 }
1126
1127 public void setAudioModeIsVoip(boolean audioModeIsVoip) {
1128 mAudioModeIsVoip = audioModeIsVoip;
1129 for (Listener l : mListeners) {
Sailesh Nepal35faf8c2014-07-08 22:02:34 -07001130 l.onAudioModeIsVoipChanged(this);
1131 }
1132 }
1133
1134 public StatusHints getStatusHints() {
1135 return mStatusHints;
1136 }
1137
1138 public void setStatusHints(StatusHints statusHints) {
1139 mStatusHints = statusHints;
1140 for (Listener l : mListeners) {
1141 l.onStatusHintsChanged(this);
Sailesh Nepal7e669572014-07-08 21:29:12 -07001142 }
1143 }
Sailesh Nepal9d58de52014-07-18 14:53:19 -07001144
1145 public void startActivityFromInCall(PendingIntent intent) {
1146 if (intent.isActivity()) {
1147 for (Listener l : mListeners) {
1148 l.onStartActivityFromInCall(this, intent);
1149 }
1150 } else {
1151 Log.w(this, "startActivityFromInCall, activity intent required");
1152 }
1153 }
Santos Cordon72890ce2014-07-21 01:32:04 -07001154
1155 private CallState getStateFromConnectionState(int state) {
1156 switch (state) {
1157 case Connection.State.ACTIVE:
1158 return CallState.ACTIVE;
1159 case Connection.State.DIALING:
1160 return CallState.DIALING;
1161 case Connection.State.DISCONNECTED:
1162 return CallState.DISCONNECTED;
1163 case Connection.State.HOLDING:
1164 return CallState.ON_HOLD;
1165 case Connection.State.NEW:
1166 return CallState.NEW;
1167 case Connection.State.RINGING:
1168 return CallState.RINGING;
1169 }
1170 return CallState.DISCONNECTED;
1171 }
Ben Gilad9f2bed32013-12-12 17:43:26 -08001172}