blob: dc3fa077075ba995ff54087e6d9150a18f4cf92d [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;
Santos Cordon72890ce2014-07-21 01:32:04 -070025import android.telecomm.Connection;
Ihab Awad6fb37c82014-08-07 19:48:57 -070026import android.telecomm.PhoneCapabilities;
27import android.telecomm.PropertyPresentation;
28import android.telecomm.CallState;
Yorke Lee33501632014-03-17 19:24:12 -070029import android.telecomm.GatewayInfo;
Santos Cordon72890ce2014-07-21 01:32:04 -070030import android.telecomm.ParcelableConnection;
Ihab Awadb78b2762014-07-25 15:16:23 -070031import android.telecomm.PhoneAccount;
Evan Charlton89176372014-07-19 18:23:09 -070032import android.telecomm.PhoneAccountHandle;
Ihab Awadff7493a2014-06-10 13:47:44 -070033import android.telecomm.Response;
Sailesh Nepal35faf8c2014-07-08 22:02:34 -070034import android.telecomm.StatusHints;
Ihab Awad6fb37c82014-08-07 19:48:57 -070035import android.telecomm.VideoProfile;
Santos Cordon79ff2bc2014-03-27 15:31:27 -070036import android.telephony.DisconnectCause;
Sailesh Nepal6aca10a2014-03-24 16:11:02 -070037import android.telephony.PhoneNumberUtils;
Santos Cordonfd71f4a2014-05-28 13:59:49 -070038import android.text.TextUtils;
Santos Cordon0b03b4b2014-01-29 18:01:59 -080039
Ihab Awad6fb37c82014-08-07 19:48:57 -070040import com.android.internal.telecomm.IVideoProvider;
Santos Cordonfd71f4a2014-05-28 13:59:49 -070041import com.android.internal.telephony.CallerInfo;
42import com.android.internal.telephony.CallerInfoAsyncQuery;
43import com.android.internal.telephony.CallerInfoAsyncQuery.OnQueryCompleteListener;
Ihab Awadff7493a2014-06-10 13:47:44 -070044import com.android.internal.telephony.SmsApplication;
Santos Cordon99c8a6f2014-05-28 18:28:47 -070045import com.android.telecomm.ContactsAsyncHelper.OnImageLoadCompleteListener;
Santos Cordon61d0f702014-02-19 02:52:23 -080046import com.google.common.base.Preconditions;
47
Santos Cordon12d61822014-07-29 16:02:20 -070048import java.util.ArrayList;
Ihab Awadff7493a2014-06-10 13:47:44 -070049import java.util.Collections;
Santos Cordona1610702014-06-04 20:22:56 -070050import java.util.LinkedList;
51import java.util.List;
Sailesh Nepal91990782014-03-08 17:45:52 -080052import java.util.Locale;
Sailesh Nepale8ecb982014-07-11 17:19:42 -070053import java.util.Objects;
Ben Gilad61925612014-03-11 19:06:36 -070054import java.util.Set;
Jay Shraunera82c8f72014-08-14 15:49:16 -070055import java.util.concurrent.ConcurrentHashMap;
Ben Gilad0407fb22014-01-09 16:18:41 -080056
Ben Gilad2495d572014-01-09 17:26:19 -080057/**
58 * Encapsulates all aspects of a given phone call throughout its lifecycle, starting
59 * from the time the call intent was received by Telecomm (vs. the time the call was
60 * connected etc).
61 */
Sailesh Nepal664837f2014-07-14 16:31:51 -070062final class Call implements CreateConnectionResponse {
Santos Cordon766d04f2014-05-06 10:28:25 -070063 /**
64 * Listener for events on the call.
65 */
66 interface Listener {
Ihab Awad6fb37c82014-08-07 19:48:57 -070067 void onSuccessfulOutgoingCall(Call call, int callState);
Sailesh Nepal5a73b032014-06-25 15:53:21 -070068 void onFailedOutgoingCall(Call call, int errorCode, String errorMsg);
Sailesh Nepalc92c4362014-07-04 18:33:21 -070069 void onSuccessfulIncomingCall(Call call);
Santos Cordon766d04f2014-05-06 10:28:25 -070070 void onFailedIncomingCall(Call call);
Ihab Awadcb387ac2014-05-28 16:49:38 -070071 void onRequestingRingback(Call call, boolean requestingRingback);
Evan Charlton352105c2014-06-03 14:10:54 -070072 void onPostDialWait(Call call, String remaining);
Sailesh Nepale20bc972014-07-09 21:22:36 -070073 void onCallCapabilitiesChanged(Call call);
Santos Cordona1610702014-06-04 20:22:56 -070074 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 Awadb78b2762014-07-25 15:16:23 -070085 void onTargetPhoneAccountChanged(Call call);
86 void onConnectionManagerPhoneAccountChanged(Call call);
Santos Cordon12d61822014-07-29 16:02:20 -070087 void onPhoneAccountChanged(Call call);
88 void onConferenceableCallsChanged(Call call);
Santos Cordon64c7e962014-07-02 15:15:27 -070089 }
90
91 abstract static class ListenerBase implements Listener {
92 @Override
Ihab Awad6fb37c82014-08-07 19:48:57 -070093 public void onSuccessfulOutgoingCall(Call call, int callState) {}
Santos Cordon64c7e962014-07-02 15:15:27 -070094 @Override
95 public void onFailedOutgoingCall(Call call, int errorCode, String errorMsg) {}
96 @Override
Sailesh Nepalc92c4362014-07-04 18:33:21 -070097 public void onSuccessfulIncomingCall(Call call) {}
Santos Cordon64c7e962014-07-02 15:15:27 -070098 @Override
99 public void onFailedIncomingCall(Call call) {}
100 @Override
101 public void onRequestingRingback(Call call, boolean requestingRingback) {}
102 @Override
103 public void onPostDialWait(Call call, String remaining) {}
104 @Override
Sailesh Nepale20bc972014-07-09 21:22:36 -0700105 public void onCallCapabilitiesChanged(Call call) {}
Santos Cordon64c7e962014-07-02 15:15:27 -0700106 @Override
Santos Cordon64c7e962014-07-02 15:15:27 -0700107 public void onParentChanged(Call call) {}
108 @Override
109 public void onChildrenChanged(Call call) {}
110 @Override
111 public void onCannedSmsResponsesLoaded(Call call) {}
112 @Override
Andrew Lee3bcf9352014-07-23 12:36:05 -0700113 public void onVideoCallProviderChanged(Call call) {}
Santos Cordon64c7e962014-07-02 15:15:27 -0700114 @Override
Santos Cordon64c7e962014-07-02 15:15:27 -0700115 public void onCallerInfoChanged(Call call) {}
Sailesh Nepal7e669572014-07-08 21:29:12 -0700116 @Override
117 public void onAudioModeIsVoipChanged(Call call) {}
Sailesh Nepal35faf8c2014-07-08 22:02:34 -0700118 @Override
119 public void onStatusHintsChanged(Call call) {}
Sailesh Nepale8ecb982014-07-11 17:19:42 -0700120 @Override
121 public void onHandleChanged(Call call) {}
122 @Override
123 public void onCallerDisplayNameChanged(Call call) {}
Andrew Lee4a796602014-07-11 17:23:03 -0700124 @Override
125 public void onVideoStateChanged(Call call) {}
Sailesh Nepal9d58de52014-07-18 14:53:19 -0700126 @Override
127 public void onStartActivityFromInCall(Call call, PendingIntent intent) {}
Ihab Awad69eb0f52014-07-18 11:20:37 -0700128 @Override
Ihab Awadb78b2762014-07-25 15:16:23 -0700129 public void onTargetPhoneAccountChanged(Call call) {}
130 @Override
131 public void onConnectionManagerPhoneAccountChanged(Call call) {}
Santos Cordon12d61822014-07-29 16:02:20 -0700132 @Override
133 public void onPhoneAccountChanged(Call call) {}
134 @Override
135 public void onConferenceableCallsChanged(Call call) {}
Santos Cordon766d04f2014-05-06 10:28:25 -0700136 }
137
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700138 private static final OnQueryCompleteListener sCallerInfoQueryListener =
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700139 new OnQueryCompleteListener() {
140 /** ${inheritDoc} */
141 @Override
142 public void onQueryComplete(int token, Object cookie, CallerInfo callerInfo) {
143 if (cookie != null) {
144 ((Call) cookie).setCallerInfo(callerInfo, token);
145 }
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700146 }
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700147 };
148
149 private static final OnImageLoadCompleteListener sPhotoLoadListener =
150 new OnImageLoadCompleteListener() {
151 /** ${inheritDoc} */
152 @Override
153 public void onImageLoadComplete(
154 int token, Drawable photo, Bitmap photoIcon, Object cookie) {
155 if (cookie != null) {
156 ((Call) cookie).setPhoto(photo, photoIcon, token);
157 }
158 }
159 };
Ben Gilad0407fb22014-01-09 16:18:41 -0800160
Sailesh Nepal664837f2014-07-14 16:31:51 -0700161 private final Runnable mDirectToVoicemailRunnable = new Runnable() {
162 @Override
163 public void run() {
164 processDirectToVoicemail();
165 }
166 };
167
Sailesh Nepal810735e2014-03-18 18:15:46 -0700168 /** True if this is an incoming call. */
169 private final boolean mIsIncoming;
170
Ben Gilad0407fb22014-01-09 16:18:41 -0800171 /**
Sailesh Nepal664837f2014-07-14 16:31:51 -0700172 * The time this call was created. Beyond logging and such, may also be used for bookkeeping
173 * and specifically for marking certain call attempts as failed attempts.
Ben Gilad0407fb22014-01-09 16:18:41 -0800174 */
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700175 private final long mCreationTimeMillis = System.currentTimeMillis();
176
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700177 /** The gateway information associated with this call. This stores the original call handle
178 * that the user is attempting to connect to via the gateway, the actual handle to dial in
179 * order to connect the call via the gateway, as well as the package name of the gateway
180 * service. */
Nancy Chen0d3076c2014-07-30 14:45:44 -0700181 private GatewayInfo mGatewayInfo;
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700182
Ihab Awadb78b2762014-07-25 15:16:23 -0700183 private PhoneAccountHandle mConnectionManagerPhoneAccountHandle;
184
185 private PhoneAccountHandle mTargetPhoneAccountHandle;
Nancy Chen77d2d0e2014-06-24 12:06:03 -0700186
Santos Cordon2174fb52014-05-29 08:22:56 -0700187 private final Handler mHandler = new Handler();
188
Santos Cordon12d61822014-07-29 16:02:20 -0700189 private final List<Call> mConferenceableCalls = new ArrayList<>();
190
191 private PhoneAccountHandle mPhoneAccountHandle;
192
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700193 private long mConnectTimeMillis;
Ben Gilad0407fb22014-01-09 16:18:41 -0800194
Santos Cordon61d0f702014-02-19 02:52:23 -0800195 /** The state of the call. */
Ihab Awad6fb37c82014-08-07 19:48:57 -0700196 private int mState;
Santos Cordon61d0f702014-02-19 02:52:23 -0800197
198 /** The handle with which to establish this call. */
Sailesh Nepalce704b92014-03-17 18:31:43 -0700199 private Uri mHandle;
Santos Cordon61d0f702014-02-19 02:52:23 -0800200
Ihab Awad6fb37c82014-08-07 19:48:57 -0700201 /** The {@link PropertyPresentation} that controls how the handle is shown. */
Sailesh Nepale8ecb982014-07-11 17:19:42 -0700202 private int mHandlePresentation;
203
204 /** The caller display name (CNAP) set by the connection service. */
205 private String mCallerDisplayName;
206
Ihab Awad6fb37c82014-08-07 19:48:57 -0700207 /** The {@link PropertyPresentation} that controls how the caller display name is shown. */
Sailesh Nepale8ecb982014-07-11 17:19:42 -0700208 private int mCallerDisplayNamePresentation;
209
Ben Gilad0407fb22014-01-09 16:18:41 -0800210 /**
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700211 * The connection service which is attempted or already connecting this call.
Santos Cordon681663d2014-01-30 04:32:15 -0800212 */
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700213 private ConnectionServiceWrapper mConnectionService;
Ben Gilad61925612014-03-11 19:06:36 -0700214
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700215 private boolean mIsEmergencyCall;
216
Sai Cheemalapatib7157e92014-06-11 17:51:55 -0700217 private boolean mSpeakerphoneOn;
218
Tyler Gunn0a388fc2014-07-17 12:21:17 -0700219 /**
220 * Tracks the video states which were applicable over the duration of a call.
Ihab Awad6fb37c82014-08-07 19:48:57 -0700221 * See {@link VideoProfile} for a list of valid video states.
Tyler Gunn0a388fc2014-07-17 12:21:17 -0700222 */
223 private int mVideoStateHistory;
224
Tyler Gunnc4abd912014-07-08 14:22:10 -0700225 private int mVideoState;
226
Ben Gilad61925612014-03-11 19:06:36 -0700227 /**
Ihab Awad6fb37c82014-08-07 19:48:57 -0700228 * Disconnect cause for the call. Only valid if the state of the call is STATE_DISCONNECTED.
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700229 * See {@link android.telephony.DisconnectCause}.
230 */
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700231 private int mDisconnectCause = DisconnectCause.NOT_VALID;
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700232
233 /**
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700234 * Additional disconnect information provided by the connection service.
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700235 */
236 private String mDisconnectMessage;
237
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700238 /** Info used by the connection services. */
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700239 private Bundle mExtras = Bundle.EMPTY;
Sailesh Nepal84fa5f82014-04-02 11:01:11 -0700240
Jay Shraunera82c8f72014-08-14 15:49:16 -0700241 /** Set of listeners on this call.
242 *
243 * ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is
244 * load factor before resizing, 1 means we only expect a single thread to
245 * access the map so make only a single shard
246 */
247 private final Set<Listener> mListeners = Collections.newSetFromMap(
248 new ConcurrentHashMap<Listener, Boolean>(8, 0.9f, 1));
Santos Cordon766d04f2014-05-06 10:28:25 -0700249
Sailesh Nepal664837f2014-07-14 16:31:51 -0700250 private CreateConnectionProcessor mCreateConnectionProcessor;
Santos Cordon682fe6b2014-05-20 08:56:39 -0700251
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700252 /** Caller information retrieved from the latest contact query. */
253 private CallerInfo mCallerInfo;
254
255 /** The latest token used with a contact info query. */
256 private int mQueryToken = 0;
257
Ihab Awadcb387ac2014-05-28 16:49:38 -0700258 /** Whether this call is requesting that Telecomm play the ringback tone on its behalf. */
259 private boolean mRequestingRingback = false;
260
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700261 /** Whether direct-to-voicemail query is pending. */
262 private boolean mDirectToVoicemailQueryPending;
Santos Cordon2174fb52014-05-29 08:22:56 -0700263
Sailesh Nepale20bc972014-07-09 21:22:36 -0700264 private int mCallCapabilities;
Santos Cordona1610702014-06-04 20:22:56 -0700265
266 private boolean mIsConference = false;
267
268 private Call mParentCall = null;
269
270 private List<Call> mChildCalls = new LinkedList<>();
271
Ihab Awadff7493a2014-06-10 13:47:44 -0700272 /** Set of text message responses allowed for this call, if applicable. */
273 private List<String> mCannedSmsResponses = Collections.EMPTY_LIST;
274
275 /** Whether an attempt has been made to load the text message responses. */
276 private boolean mCannedSmsResponsesLoadingStarted = false;
277
Ihab Awad6fb37c82014-08-07 19:48:57 -0700278 private IVideoProvider mVideoProvider;
Nancy Chena65d41f2014-06-24 12:06:03 -0700279
Sailesh Nepal7e669572014-07-08 21:29:12 -0700280 private boolean mAudioModeIsVoip;
Sailesh Nepal35faf8c2014-07-08 22:02:34 -0700281 private StatusHints mStatusHints;
Sailesh Nepal664837f2014-07-14 16:31:51 -0700282 private final ConnectionServiceRepository mRepository;
Sailesh Nepal7e669572014-07-08 21:29:12 -0700283
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700284 /**
Ben Gilad0407fb22014-01-09 16:18:41 -0800285 * Persists the specified parameters and initializes the new instance.
286 *
287 * @param handle The handle to dial.
Yorke Lee33501632014-03-17 19:24:12 -0700288 * @param gatewayInfo Gateway information to use for the call.
Ihab Awadb78b2762014-07-25 15:16:23 -0700289 * @param connectionManagerPhoneAccountHandle Account to use for the service managing the call.
290 * This account must be one that was registered with the
291 * {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER} flag.
292 * @param targetPhoneAccountHandle Account information to use for the call. This account must be
293 * one that was registered with the {@link PhoneAccount#CAPABILITY_CALL_PROVIDER} flag.
Sailesh Nepal810735e2014-03-18 18:15:46 -0700294 * @param isIncoming True if this is an incoming call.
Ben Gilad0407fb22014-01-09 16:18:41 -0800295 */
Sailesh Nepal664837f2014-07-14 16:31:51 -0700296 Call(
297 ConnectionServiceRepository repository,
298 Uri handle,
299 GatewayInfo gatewayInfo,
Ihab Awadb78b2762014-07-25 15:16:23 -0700300 PhoneAccountHandle connectionManagerPhoneAccountHandle,
301 PhoneAccountHandle targetPhoneAccountHandle,
Sailesh Nepal664837f2014-07-14 16:31:51 -0700302 boolean isIncoming,
303 boolean isConference) {
Santos Cordona1610702014-06-04 20:22:56 -0700304 mState = isConference ? CallState.ACTIVE : CallState.NEW;
Sailesh Nepal664837f2014-07-14 16:31:51 -0700305 mRepository = repository;
Nancy Chen0d3076c2014-07-30 14:45:44 -0700306 setHandle(handle);
Ihab Awad6fb37c82014-08-07 19:48:57 -0700307 setHandle(handle, PropertyPresentation.ALLOWED);
Yorke Lee33501632014-03-17 19:24:12 -0700308 mGatewayInfo = gatewayInfo;
Ihab Awadb78b2762014-07-25 15:16:23 -0700309 mConnectionManagerPhoneAccountHandle = connectionManagerPhoneAccountHandle;
310 mTargetPhoneAccountHandle = targetPhoneAccountHandle;
Sailesh Nepal810735e2014-03-18 18:15:46 -0700311 mIsIncoming = isIncoming;
Santos Cordona1610702014-06-04 20:22:56 -0700312 mIsConference = isConference;
Ihab Awadff7493a2014-06-10 13:47:44 -0700313 maybeLoadCannedSmsResponses();
Ben Gilad0407fb22014-01-09 16:18:41 -0800314 }
315
Santos Cordon766d04f2014-05-06 10:28:25 -0700316 void addListener(Listener listener) {
317 mListeners.add(listener);
318 }
319
320 void removeListener(Listener listener) {
Jay Shraunera82c8f72014-08-14 15:49:16 -0700321 if (listener != null) {
322 mListeners.remove(listener);
323 }
Santos Cordon766d04f2014-05-06 10:28:25 -0700324 }
325
Santos Cordon61d0f702014-02-19 02:52:23 -0800326 /** {@inheritDoc} */
Santos Cordon12d61822014-07-29 16:02:20 -0700327 @Override
328 public String toString() {
Sailesh Nepal4538f012014-04-15 11:40:33 -0700329 String component = null;
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700330 if (mConnectionService != null && mConnectionService.getComponentName() != null) {
331 component = mConnectionService.getComponentName().flattenToShortString();
Sailesh Nepal4538f012014-04-15 11:40:33 -0700332 }
Tyler Gunn0a388fc2014-07-17 12:21:17 -0700333
334 return String.format(Locale.US, "[%s, %s, %s, %d]", mState, component,
335 Log.piiHandle(mHandle), getVideoState());
Santos Cordon61d0f702014-02-19 02:52:23 -0800336 }
337
Ihab Awad6fb37c82014-08-07 19:48:57 -0700338 int getState() {
Santos Cordon0fbe6322014-08-14 04:04:25 -0700339 return mState;
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800340 }
341
342 /**
343 * Sets the call state. Although there exists the notion of appropriate state transitions
344 * (see {@link CallState}), in practice those expectations break down when cellular systems
345 * misbehave and they do this very often. The result is that we do not enforce state transitions
346 * and instead keep the code resilient to unexpected state changes.
347 */
Ihab Awad6fb37c82014-08-07 19:48:57 -0700348 void setState(int newState) {
Sailesh Nepal810735e2014-03-18 18:15:46 -0700349 if (mState != newState) {
350 Log.v(this, "setState %s -> %s", mState, newState);
351 mState = newState;
Ihab Awadff7493a2014-06-10 13:47:44 -0700352 maybeLoadCannedSmsResponses();
Santos Cordon672321e2014-08-21 14:38:58 -0700353
354 if (mState == CallState.DISCONNECTED) {
355 fixParentAfterDisconnect();
356 }
Sailesh Nepal810735e2014-03-18 18:15:46 -0700357 }
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800358 }
359
Ihab Awadcb387ac2014-05-28 16:49:38 -0700360 void setRequestingRingback(boolean requestingRingback) {
361 mRequestingRingback = requestingRingback;
362 for (Listener l : mListeners) {
363 l.onRequestingRingback(this, mRequestingRingback);
364 }
365 }
366
367 boolean isRequestingRingback() {
368 return mRequestingRingback;
369 }
370
Santos Cordon672321e2014-08-21 14:38:58 -0700371 boolean isConference() {
372 return mIsConference;
373 }
374
Sailesh Nepalce704b92014-03-17 18:31:43 -0700375 Uri getHandle() {
Ben Gilad0bf5b912014-01-28 17:55:57 -0800376 return mHandle;
377 }
378
Sailesh Nepale8ecb982014-07-11 17:19:42 -0700379 int getHandlePresentation() {
380 return mHandlePresentation;
381 }
382
Nancy Chen0d3076c2014-07-30 14:45:44 -0700383
384 void setHandle(Uri handle) {
Ihab Awad6fb37c82014-08-07 19:48:57 -0700385 setHandle(handle, PropertyPresentation.ALLOWED);
Nancy Chen0d3076c2014-07-30 14:45:44 -0700386 }
387
Sailesh Nepale8ecb982014-07-11 17:19:42 -0700388 void setHandle(Uri handle, int presentation) {
389 if (!Objects.equals(handle, mHandle) || presentation != mHandlePresentation) {
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700390 mHandle = handle;
Sailesh Nepale8ecb982014-07-11 17:19:42 -0700391 mHandlePresentation = presentation;
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700392 mIsEmergencyCall = mHandle != null && PhoneNumberUtils.isLocalEmergencyNumber(
Yorke Lee66255452014-06-05 08:09:24 -0700393 TelecommApp.getInstance(), mHandle.getSchemeSpecificPart());
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700394 startCallerInfoLookup();
Sailesh Nepale8ecb982014-07-11 17:19:42 -0700395 for (Listener l : mListeners) {
396 l.onHandleChanged(this);
397 }
398 }
399 }
400
401 String getCallerDisplayName() {
402 return mCallerDisplayName;
403 }
404
405 int getCallerDisplayNamePresentation() {
406 return mCallerDisplayNamePresentation;
407 }
408
409 void setCallerDisplayName(String callerDisplayName, int presentation) {
410 if (!TextUtils.equals(callerDisplayName, mCallerDisplayName) ||
411 presentation != mCallerDisplayNamePresentation) {
412 mCallerDisplayName = callerDisplayName;
413 mCallerDisplayNamePresentation = presentation;
414 for (Listener l : mListeners) {
415 l.onCallerDisplayNameChanged(this);
416 }
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700417 }
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700418 }
419
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700420 String getName() {
421 return mCallerInfo == null ? null : mCallerInfo.name;
422 }
423
424 Bitmap getPhotoIcon() {
425 return mCallerInfo == null ? null : mCallerInfo.cachedPhotoIcon;
426 }
427
428 Drawable getPhoto() {
429 return mCallerInfo == null ? null : mCallerInfo.cachedPhoto;
430 }
431
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700432 /**
433 * @param disconnectCause The reason for the disconnection, any of
434 * {@link android.telephony.DisconnectCause}.
Sailesh Nepal905dfba2014-07-14 08:20:41 -0700435 * @param disconnectMessage Optional message about the disconnect.
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700436 */
437 void setDisconnectCause(int disconnectCause, String disconnectMessage) {
438 // TODO: Consider combining this method with a setDisconnected() method that is totally
439 // separate from setState.
440 mDisconnectCause = disconnectCause;
441 mDisconnectMessage = disconnectMessage;
442 }
443
444 int getDisconnectCause() {
445 return mDisconnectCause;
446 }
447
448 String getDisconnectMessage() {
449 return mDisconnectMessage;
450 }
451
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700452 boolean isEmergencyCall() {
453 return mIsEmergencyCall;
Santos Cordon61d0f702014-02-19 02:52:23 -0800454 }
455
Yorke Lee33501632014-03-17 19:24:12 -0700456 /**
457 * @return The original handle this call is associated with. In-call services should use this
458 * handle when indicating in their UI the handle that is being called.
459 */
460 public Uri getOriginalHandle() {
461 if (mGatewayInfo != null && !mGatewayInfo.isEmpty()) {
462 return mGatewayInfo.getOriginalHandle();
463 }
464 return getHandle();
465 }
466
467 GatewayInfo getGatewayInfo() {
468 return mGatewayInfo;
469 }
470
Nancy Chen0d3076c2014-07-30 14:45:44 -0700471 void setGatewayInfo(GatewayInfo gatewayInfo) {
472 mGatewayInfo = gatewayInfo;
473 }
474
Ihab Awadb78b2762014-07-25 15:16:23 -0700475 PhoneAccountHandle getConnectionManagerPhoneAccount() {
476 return mConnectionManagerPhoneAccountHandle;
Nancy Chen77d2d0e2014-06-24 12:06:03 -0700477 }
478
Ihab Awadb78b2762014-07-25 15:16:23 -0700479 void setConnectionManagerPhoneAccount(PhoneAccountHandle accountHandle) {
480 if (!Objects.equals(mConnectionManagerPhoneAccountHandle, accountHandle)) {
481 mConnectionManagerPhoneAccountHandle = accountHandle;
Ihab Awad69eb0f52014-07-18 11:20:37 -0700482 for (Listener l : mListeners) {
Ihab Awadb78b2762014-07-25 15:16:23 -0700483 l.onConnectionManagerPhoneAccountChanged(this);
484 }
485 }
486
487 }
488
489 PhoneAccountHandle getTargetPhoneAccount() {
490 return mTargetPhoneAccountHandle;
491 }
492
493 void setTargetPhoneAccount(PhoneAccountHandle accountHandle) {
494 if (!Objects.equals(mTargetPhoneAccountHandle, accountHandle)) {
495 mTargetPhoneAccountHandle = accountHandle;
496 for (Listener l : mListeners) {
497 l.onTargetPhoneAccountChanged(this);
Ihab Awad69eb0f52014-07-18 11:20:37 -0700498 }
499 }
Nancy Chen53ceedc2014-07-08 18:56:51 -0700500 }
501
Sailesh Nepal810735e2014-03-18 18:15:46 -0700502 boolean isIncoming() {
503 return mIsIncoming;
504 }
505
Ben Gilad0407fb22014-01-09 16:18:41 -0800506 /**
507 * @return The "age" of this call object in milliseconds, which typically also represents the
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700508 * period since this call was added to the set pending outgoing calls, see
509 * mCreationTimeMillis.
Ben Gilad0407fb22014-01-09 16:18:41 -0800510 */
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700511 long getAgeMillis() {
512 return System.currentTimeMillis() - mCreationTimeMillis;
Ben Gilad0407fb22014-01-09 16:18:41 -0800513 }
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800514
Yorke Leef98fb572014-03-05 10:56:55 -0800515 /**
516 * @return The time when this call object was created and added to the set of pending outgoing
517 * calls.
518 */
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700519 long getCreationTimeMillis() {
520 return mCreationTimeMillis;
521 }
522
523 long getConnectTimeMillis() {
524 return mConnectTimeMillis;
525 }
526
527 void setConnectTimeMillis(long connectTimeMillis) {
528 mConnectTimeMillis = connectTimeMillis;
Yorke Leef98fb572014-03-05 10:56:55 -0800529 }
530
Sailesh Nepale20bc972014-07-09 21:22:36 -0700531 int getCallCapabilities() {
532 return mCallCapabilities;
Santos Cordona1610702014-06-04 20:22:56 -0700533 }
534
Sailesh Nepale20bc972014-07-09 21:22:36 -0700535 void setCallCapabilities(int callCapabilities) {
Ihab Awad6fb37c82014-08-07 19:48:57 -0700536 Log.v(this, "setCallCapabilities: %s", PhoneCapabilities.toString(callCapabilities));
Sailesh Nepale20bc972014-07-09 21:22:36 -0700537 if (mCallCapabilities != callCapabilities) {
Santos Cordon12d61822014-07-29 16:02:20 -0700538 mCallCapabilities = callCapabilities;
Santos Cordona1610702014-06-04 20:22:56 -0700539 for (Listener l : mListeners) {
Sailesh Nepale20bc972014-07-09 21:22:36 -0700540 l.onCallCapabilitiesChanged(this);
Santos Cordona1610702014-06-04 20:22:56 -0700541 }
542 }
543 }
544
545 Call getParentCall() {
546 return mParentCall;
547 }
548
549 List<Call> getChildCalls() {
550 return mChildCalls;
551 }
552
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700553 ConnectionServiceWrapper getConnectionService() {
554 return mConnectionService;
Santos Cordon681663d2014-01-30 04:32:15 -0800555 }
556
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700557 void setConnectionService(ConnectionServiceWrapper service) {
558 Preconditions.checkNotNull(service);
559
560 clearConnectionService();
561
562 service.incrementAssociatedCallCount();
563 mConnectionService = service;
564 mConnectionService.addCall(this);
Sailesh Nepal0e5410a2014-04-04 01:20:58 -0700565 }
566
567 /**
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700568 * Clears the associated connection service.
Sailesh Nepal0e5410a2014-04-04 01:20:58 -0700569 */
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700570 void clearConnectionService() {
571 if (mConnectionService != null) {
572 ConnectionServiceWrapper serviceTemp = mConnectionService;
573 mConnectionService = null;
574 serviceTemp.removeCall(this);
Santos Cordonc499c1c2014-04-14 17:13:14 -0700575
576 // Decrementing the count can cause the service to unbind, which itself can trigger the
577 // service-death code. Since the service death code tries to clean up any associated
578 // calls, we need to make sure to remove that information (e.g., removeCall()) before
579 // we decrement. Technically, invoking removeCall() prior to decrementing is all that is
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700580 // necessary, but cleaning up mConnectionService prior to triggering an unbind is good
581 // to do.
582 decrementAssociatedCallCount(serviceTemp);
Yorke Leeadee12d2014-03-13 12:08:30 -0700583 }
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800584 }
585
Sailesh Nepal664837f2014-07-14 16:31:51 -0700586 private void processDirectToVoicemail() {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700587 if (mDirectToVoicemailQueryPending) {
Santos Cordon2174fb52014-05-29 08:22:56 -0700588 if (mCallerInfo != null && mCallerInfo.shouldSendToVoicemail) {
589 Log.i(this, "Directing call to voicemail: %s.", this);
Santos Cordondf399862014-08-06 04:39:15 -0700590 // TODO: Once we move State handling from CallsManager to Call, we
Ihab Awad6fb37c82014-08-07 19:48:57 -0700591 // will not need to set STATE_RINGING state prior to calling reject.
Santos Cordon2174fb52014-05-29 08:22:56 -0700592 setState(CallState.RINGING);
Ihab Awadff7493a2014-06-10 13:47:44 -0700593 reject(false, null);
Santos Cordon2174fb52014-05-29 08:22:56 -0700594 } else {
Santos Cordondf399862014-08-06 04:39:15 -0700595 // TODO: Make this class (not CallsManager) responsible for changing
Ihab Awad6fb37c82014-08-07 19:48:57 -0700596 // the call state to STATE_RINGING.
Santos Cordon2174fb52014-05-29 08:22:56 -0700597
Ihab Awad6fb37c82014-08-07 19:48:57 -0700598 // TODO: Replace this with state transition to STATE_RINGING.
Santos Cordon2174fb52014-05-29 08:22:56 -0700599 for (Listener l : mListeners) {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700600 l.onSuccessfulIncomingCall(this);
Santos Cordon2174fb52014-05-29 08:22:56 -0700601 }
602 }
603
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700604 mDirectToVoicemailQueryPending = false;
Santos Cordon766d04f2014-05-06 10:28:25 -0700605 }
606 }
607
Santos Cordon766d04f2014-05-06 10:28:25 -0700608 /**
Sailesh Nepal664837f2014-07-14 16:31:51 -0700609 * Starts the create connection sequence. Upon completion, there should exist an active
610 * connection through a connection service (or the call will have failed).
Santos Cordon766d04f2014-05-06 10:28:25 -0700611 */
Sailesh Nepal664837f2014-07-14 16:31:51 -0700612 void startCreateConnection() {
613 Preconditions.checkState(mCreateConnectionProcessor == null);
614 mCreateConnectionProcessor = new CreateConnectionProcessor(this, mRepository, this);
615 mCreateConnectionProcessor.process();
Santos Cordon766d04f2014-05-06 10:28:25 -0700616 }
617
Sailesh Nepal5a73b032014-06-25 15:53:21 -0700618 @Override
Ihab Awad80008452014-08-23 20:35:44 -0700619 public void handleCreateConnectionSuccess(
620 CallIdMapper idMapper,
621 ParcelableConnection connection) {
Santos Cordon12d61822014-07-29 16:02:20 -0700622 Log.v(this, "handleCreateConnectionSuccessful %s", connection);
Sailesh Nepal664837f2014-07-14 16:31:51 -0700623 mCreateConnectionProcessor = null;
Ihab Awadb78b2762014-07-25 15:16:23 -0700624 setTargetPhoneAccount(connection.getPhoneAccount());
Santos Cordon72890ce2014-07-21 01:32:04 -0700625 setHandle(connection.getHandle(), connection.getHandlePresentation());
626 setCallerDisplayName(
627 connection.getCallerDisplayName(), connection.getCallerDisplayNamePresentation());
Yorke Leeb2920b32014-08-20 13:30:08 -0700628 setCallCapabilities(connection.getCapabilities());
Ihab Awad6fb37c82014-08-07 19:48:57 -0700629 setVideoProvider(connection.getVideoProvider());
Tyler Gunnb1a95a72014-07-23 14:08:19 -0700630 setVideoState(connection.getVideoState());
Sailesh Nepal604a4fe2014-08-14 18:03:43 -0700631 setRequestingRingback(connection.isRequestingRingback());
632 setAudioModeIsVoip(connection.getAudioModeIsVoip());
633 setStatusHints(connection.getStatusHints());
Sailesh Nepal664837f2014-07-14 16:31:51 -0700634
Ihab Awad80008452014-08-23 20:35:44 -0700635 mConferenceableCalls.clear();
636 for (String id : connection.getConferenceableConnectionIds()) {
637 mConferenceableCalls.add(idMapper.getCall(id));
638 }
639
Sailesh Nepal664837f2014-07-14 16:31:51 -0700640 if (mIsIncoming) {
641 // We do not handle incoming calls immediately when they are verified by the connection
642 // service. We allow the caller-info-query code to execute first so that we can read the
643 // direct-to-voicemail property before deciding if we want to show the incoming call to
644 // the user or if we want to reject the call.
645 mDirectToVoicemailQueryPending = true;
646
Sailesh Nepal664837f2014-07-14 16:31:51 -0700647 // Timeout the direct-to-voicemail lookup execution so that we dont wait too long before
648 // showing the user the incoming call screen.
649 mHandler.postDelayed(mDirectToVoicemailRunnable, Timeouts.getDirectToVoicemailMillis());
650 } else {
651 for (Listener l : mListeners) {
Tyler Gunncde2e502014-08-12 14:35:58 -0700652 l.onSuccessfulOutgoingCall(this,
653 getStateFromConnectionState(connection.getState()));
Sailesh Nepal664837f2014-07-14 16:31:51 -0700654 }
Santos Cordon766d04f2014-05-06 10:28:25 -0700655 }
656 }
657
Sailesh Nepal5a73b032014-06-25 15:53:21 -0700658 @Override
Ihab Awadfb5560d2014-08-18 09:32:51 -0700659 public void handleCreateConnectionFailure(int code, String msg) {
Sailesh Nepal664837f2014-07-14 16:31:51 -0700660 mCreateConnectionProcessor = null;
Ihab Awadfb5560d2014-08-18 09:32:51 -0700661 clearConnectionService();
662 setDisconnectCause(code, msg);
663 CallsManager.getInstance().markCallAsDisconnected(this, code, msg);
Santos Cordon682fe6b2014-05-20 08:56:39 -0700664
Ihab Awadfb5560d2014-08-18 09:32:51 -0700665 if (mIsIncoming) {
Jay Shraunera82c8f72014-08-14 15:49:16 -0700666 for (Listener listener : mListeners) {
667 listener.onFailedIncomingCall(this);
Sailesh Nepal664837f2014-07-14 16:31:51 -0700668 }
669 } else {
Jay Shraunera82c8f72014-08-14 15:49:16 -0700670 for (Listener listener : mListeners) {
671 listener.onFailedOutgoingCall(this, code, msg);
Sailesh Nepal664837f2014-07-14 16:31:51 -0700672 }
Sailesh Nepal664837f2014-07-14 16:31:51 -0700673 }
Santos Cordon766d04f2014-05-06 10:28:25 -0700674 }
675
676 /**
Ihab Awad74549ec2014-03-10 15:33:25 -0700677 * Plays the specified DTMF tone.
678 */
679 void playDtmfTone(char digit) {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700680 if (mConnectionService == null) {
681 Log.w(this, "playDtmfTone() request on a call without a connection service.");
Ihab Awad74549ec2014-03-10 15:33:25 -0700682 } else {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700683 Log.i(this, "Send playDtmfTone to connection service for call %s", this);
684 mConnectionService.playDtmfTone(this, digit);
Ihab Awad74549ec2014-03-10 15:33:25 -0700685 }
686 }
687
688 /**
689 * Stops playing any currently playing DTMF tone.
690 */
691 void stopDtmfTone() {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700692 if (mConnectionService == null) {
Jay Shraunerfd4d7b52014-08-14 14:14:43 -0700693 Log.w(this, "stopDtmfTone() request on a call without a connection service.");
Ihab Awad74549ec2014-03-10 15:33:25 -0700694 } else {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700695 Log.i(this, "Send stopDtmfTone to connection service for call %s", this);
696 mConnectionService.stopDtmfTone(this);
Ihab Awad74549ec2014-03-10 15:33:25 -0700697 }
698 }
699
700 /**
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700701 * Attempts to disconnect the call through the connection service.
Santos Cordon049b7b62014-01-30 05:34:26 -0800702 */
703 void disconnect() {
Jay Shraunerfd4d7b52014-08-14 14:14:43 -0700704 if (mState == CallState.NEW || mState == CallState.PRE_DIAL_WAIT ||
705 mState == CallState.CONNECTING) {
Santos Cordon682fe6b2014-05-20 08:56:39 -0700706 Log.v(this, "Aborting call %s", this);
707 abort();
Santos Cordon74d420b2014-05-07 14:38:47 -0700708 } else if (mState != CallState.ABORTED && mState != CallState.DISCONNECTED) {
Jay Shraunerfd4d7b52014-08-14 14:14:43 -0700709 if (mConnectionService == null) {
710 Log.e(this, new Exception(), "disconnect() request on a call without a"
711 + " connection service.");
712 } else {
713 Log.i(this, "Send disconnect to connection service for call: %s", this);
714 // The call isn't officially disconnected until the connection service
715 // confirms that the call was actually disconnected. Only then is the
716 // association between call and connection service severed, see
717 // {@link CallsManager#markCallAsDisconnected}.
718 mConnectionService.disconnect(this);
719 }
Santos Cordon049b7b62014-01-30 05:34:26 -0800720 }
721 }
722
Santos Cordon682fe6b2014-05-20 08:56:39 -0700723 void abort() {
Sailesh Nepal664837f2014-07-14 16:31:51 -0700724 if (mCreateConnectionProcessor != null) {
725 mCreateConnectionProcessor.abort();
Yorke Lee43df90f2014-08-20 10:56:09 -0700726 } else if (mState == CallState.NEW || mState == CallState.PRE_DIAL_WAIT
727 || mState == CallState.CONNECTING) {
728 handleCreateConnectionFailure(DisconnectCause.OUTGOING_CANCELED, null);
Santos Cordon2d0b3312014-08-15 15:04:17 -0700729 } else {
730 Log.v(this, "Cannot abort a call which isn't either PRE_DIAL_WAIT or CONNECTING");
Santos Cordon682fe6b2014-05-20 08:56:39 -0700731 }
732 }
733
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800734 /**
Santos Cordon61d0f702014-02-19 02:52:23 -0800735 * Answers the call if it is ringing.
Andrew Lee38931d02014-07-16 10:17:36 -0700736 *
737 * @param videoState The video state in which to answer the call.
Santos Cordon61d0f702014-02-19 02:52:23 -0800738 */
Andrew Lee38931d02014-07-16 10:17:36 -0700739 void answer(int videoState) {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700740 Preconditions.checkNotNull(mConnectionService);
Santos Cordon61d0f702014-02-19 02:52:23 -0800741
742 // Check to verify that the call is still in the ringing state. A call can change states
743 // between the time the user hits 'answer' and Telecomm receives the command.
744 if (isRinging("answer")) {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700745 // At this point, we are asking the connection service to answer but we don't assume
746 // that it will work. Instead, we wait until confirmation from the connectino service
Ihab Awad6fb37c82014-08-07 19:48:57 -0700747 // that the call is in a non-STATE_RINGING state before changing the UI. See
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700748 // {@link ConnectionServiceAdapter#setActive} and other set* methods.
Andrew Lee38931d02014-07-16 10:17:36 -0700749 mConnectionService.answer(this, videoState);
Santos Cordon61d0f702014-02-19 02:52:23 -0800750 }
751 }
752
753 /**
754 * Rejects the call if it is ringing.
Ihab Awadff7493a2014-06-10 13:47:44 -0700755 *
756 * @param rejectWithMessage Whether to send a text message as part of the call rejection.
757 * @param textMessage An optional text message to send as part of the rejection.
Santos Cordon61d0f702014-02-19 02:52:23 -0800758 */
Ihab Awadff7493a2014-06-10 13:47:44 -0700759 void reject(boolean rejectWithMessage, String textMessage) {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700760 Preconditions.checkNotNull(mConnectionService);
Santos Cordon61d0f702014-02-19 02:52:23 -0800761
762 // Check to verify that the call is still in the ringing state. A call can change states
763 // between the time the user hits 'reject' and Telecomm receives the command.
764 if (isRinging("reject")) {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700765 mConnectionService.reject(this);
Santos Cordon61d0f702014-02-19 02:52:23 -0800766 }
767 }
768
769 /**
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700770 * Puts the call on hold if it is currently active.
771 */
772 void hold() {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700773 Preconditions.checkNotNull(mConnectionService);
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700774
775 if (mState == CallState.ACTIVE) {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700776 mConnectionService.hold(this);
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700777 }
778 }
779
780 /**
781 * Releases the call from hold if it is currently active.
782 */
783 void unhold() {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700784 Preconditions.checkNotNull(mConnectionService);
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700785
786 if (mState == CallState.ON_HOLD) {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700787 mConnectionService.unhold(this);
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700788 }
789 }
790
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700791 /** Checks if this is a live call or not. */
792 boolean isAlive() {
793 switch (mState) {
Ihab Awad6fb37c82014-08-07 19:48:57 -0700794 case CallState.NEW:
795 case CallState.RINGING:
796 case CallState.DISCONNECTED:
797 case CallState.ABORTED:
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700798 return false;
799 default:
800 return true;
801 }
802 }
803
Santos Cordon40f78c22014-04-07 02:11:42 -0700804 boolean isActive() {
Ihab Awad84bfe472014-07-13 17:11:57 -0700805 return mState == CallState.ACTIVE;
Santos Cordon40f78c22014-04-07 02:11:42 -0700806 }
807
Sailesh Nepal84fa5f82014-04-02 11:01:11 -0700808 Bundle getExtras() {
809 return mExtras;
810 }
811
812 void setExtras(Bundle extras) {
813 mExtras = extras;
814 }
815
Santos Cordon5ba7f272014-05-28 13:59:49 -0700816 Uri getRingtone() {
817 return mCallerInfo == null ? null : mCallerInfo.contactRingtoneUri;
818 }
819
Evan Charlton352105c2014-06-03 14:10:54 -0700820 void onPostDialWait(String remaining) {
821 for (Listener l : mListeners) {
822 l.onPostDialWait(this, remaining);
823 }
824 }
825
826 void postDialContinue(boolean proceed) {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700827 mConnectionService.onPostDialContinue(this, proceed);
Evan Charlton352105c2014-06-03 14:10:54 -0700828 }
829
Sailesh Nepal77da19e2014-07-02 21:31:16 -0700830 void phoneAccountClicked() {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700831 mConnectionService.onPhoneAccountClicked(this);
Sailesh Nepal77da19e2014-07-02 21:31:16 -0700832 }
833
Santos Cordon0fbe6322014-08-14 04:04:25 -0700834 void conferenceWith(Call otherCall) {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700835 if (mConnectionService == null) {
836 Log.w(this, "conference requested on a call without a connection service.");
Santos Cordona1610702014-06-04 20:22:56 -0700837 } else {
Santos Cordon0fbe6322014-08-14 04:04:25 -0700838 mConnectionService.conference(this, otherCall);
Santos Cordona1610702014-06-04 20:22:56 -0700839 }
840 }
841
842 void splitFromConference() {
Santos Cordon672321e2014-08-21 14:38:58 -0700843 if (mConnectionService == null) {
844 Log.w(this, "splitting from conference call without a connection service");
845 } else {
846 mConnectionService.splitFromConference(this);
847 }
Santos Cordona1610702014-06-04 20:22:56 -0700848 }
849
850 void setParentCall(Call parentCall) {
851 if (parentCall == this) {
852 Log.e(this, new Exception(), "setting the parent to self");
853 return;
854 }
Santos Cordon0fbe6322014-08-14 04:04:25 -0700855 if (parentCall == mParentCall) {
856 // nothing to do
857 return;
858 }
Santos Cordona1610702014-06-04 20:22:56 -0700859 Preconditions.checkState(parentCall == null || mParentCall == null);
860
861 Call oldParent = mParentCall;
862 if (mParentCall != null) {
863 mParentCall.removeChildCall(this);
864 }
865 mParentCall = parentCall;
866 if (mParentCall != null) {
867 mParentCall.addChildCall(this);
868 }
869
870 for (Listener l : mListeners) {
871 l.onParentChanged(this);
872 }
873 }
874
Santos Cordon12d61822014-07-29 16:02:20 -0700875 void setConferenceableCalls(List<Call> conferenceableCalls) {
876 mConferenceableCalls.clear();
877 mConferenceableCalls.addAll(conferenceableCalls);
878 }
879
880 List<Call> getConferenceableCalls() {
881 return mConferenceableCalls;
882 }
883
Santos Cordona1610702014-06-04 20:22:56 -0700884 private void addChildCall(Call call) {
885 if (!mChildCalls.contains(call)) {
886 mChildCalls.add(call);
887
888 for (Listener l : mListeners) {
889 l.onChildrenChanged(this);
890 }
891 }
892 }
893
894 private void removeChildCall(Call call) {
895 if (mChildCalls.remove(call)) {
896 for (Listener l : mListeners) {
897 l.onChildrenChanged(this);
898 }
899 }
900 }
901
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800902 /**
Ihab Awadff7493a2014-06-10 13:47:44 -0700903 * Return whether the user can respond to this {@code Call} via an SMS message.
904 *
905 * @return true if the "Respond via SMS" feature should be enabled
906 * for this incoming call.
907 *
908 * The general rule is that we *do* allow "Respond via SMS" except for
909 * the few (relatively rare) cases where we know for sure it won't
910 * work, namely:
911 * - a bogus or blank incoming number
912 * - a call from a SIP address
913 * - a "call presentation" that doesn't allow the number to be revealed
914 *
915 * In all other cases, we allow the user to respond via SMS.
916 *
917 * Note that this behavior isn't perfect; for example we have no way
918 * to detect whether the incoming call is from a landline (with most
919 * networks at least), so we still enable this feature even though
920 * SMSes to that number will silently fail.
921 */
922 boolean isRespondViaSmsCapable() {
923 if (mState != CallState.RINGING) {
924 return false;
925 }
926
927 if (getHandle() == null) {
928 // No incoming number known or call presentation is "PRESENTATION_RESTRICTED", in
929 // other words, the user should not be able to see the incoming phone number.
930 return false;
931 }
932
933 if (PhoneNumberUtils.isUriNumber(getHandle().toString())) {
934 // The incoming number is actually a URI (i.e. a SIP address),
935 // not a regular PSTN phone number, and we can't send SMSes to
936 // SIP addresses.
937 // (TODO: That might still be possible eventually, though. Is
938 // there some SIP-specific equivalent to sending a text message?)
939 return false;
940 }
941
942 // Is there a valid SMS application on the phone?
943 if (SmsApplication.getDefaultRespondViaMessageApplication(TelecommApp.getInstance(),
944 true /*updateIfNeeded*/) == null) {
945 return false;
946 }
947
948 // TODO: with some carriers (in certain countries) you *can* actually
949 // tell whether a given number is a mobile phone or not. So in that
950 // case we could potentially return false here if the incoming call is
951 // from a land line.
952
953 // If none of the above special cases apply, it's OK to enable the
954 // "Respond via SMS" feature.
955 return true;
956 }
957
958 List<String> getCannedSmsResponses() {
959 return mCannedSmsResponses;
960 }
961
962 /**
Santos Cordon672321e2014-08-21 14:38:58 -0700963 * We need to make sure that before we move a call to the disconnected state, it no
964 * longer has any parent/child relationships. We want to do this to ensure that the InCall
965 * Service always has the right data in the right order. We also want to do it in telecomm so
966 * that the insurance policy lives in the framework side of things.
967 */
968 private void fixParentAfterDisconnect() {
969 setParentCall(null);
970 }
971
972 /**
Santos Cordon61d0f702014-02-19 02:52:23 -0800973 * @return True if the call is ringing, else logs the action name.
974 */
975 private boolean isRinging(String actionName) {
976 if (mState == CallState.RINGING) {
977 return true;
978 }
979
Sailesh Nepalf1c191d2014-03-07 18:17:39 -0800980 Log.i(this, "Request to %s a non-ringing call %s", actionName, this);
Santos Cordon61d0f702014-02-19 02:52:23 -0800981 return false;
982 }
983
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800984 @SuppressWarnings("rawtypes")
985 private void decrementAssociatedCallCount(ServiceBinder binder) {
986 if (binder != null) {
987 binder.decrementAssociatedCallCount();
988 }
989 }
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700990
991 /**
992 * Looks up contact information based on the current handle.
993 */
994 private void startCallerInfoLookup() {
995 String number = mHandle == null ? null : mHandle.getSchemeSpecificPart();
996
997 mQueryToken++; // Updated so that previous queries can no longer set the information.
998 mCallerInfo = null;
999 if (!TextUtils.isEmpty(number)) {
Santos Cordon5ba7f272014-05-28 13:59:49 -07001000 Log.v(this, "Looking up information for: %s.", Log.piiHandle(number));
Santos Cordonfd71f4a2014-05-28 13:59:49 -07001001 CallerInfoAsyncQuery.startQuery(
1002 mQueryToken,
1003 TelecommApp.getInstance(),
1004 number,
1005 sCallerInfoQueryListener,
1006 this);
1007 }
1008 }
1009
1010 /**
Santos Cordon99c8a6f2014-05-28 18:28:47 -07001011 * Saves the specified caller info if the specified token matches that of the last query
Santos Cordonfd71f4a2014-05-28 13:59:49 -07001012 * that was made.
1013 *
1014 * @param callerInfo The new caller information to set.
1015 * @param token The token used with this query.
1016 */
1017 private void setCallerInfo(CallerInfo callerInfo, int token) {
Santos Cordon99c8a6f2014-05-28 18:28:47 -07001018 Preconditions.checkNotNull(callerInfo);
1019
Santos Cordonfd71f4a2014-05-28 13:59:49 -07001020 if (mQueryToken == token) {
1021 mCallerInfo = callerInfo;
Santos Cordon5ba7f272014-05-28 13:59:49 -07001022 Log.i(this, "CallerInfo received for %s: %s", Log.piiHandle(mHandle), callerInfo);
Santos Cordon99c8a6f2014-05-28 18:28:47 -07001023
Makoto Onukia1662d02014-07-10 15:31:59 -07001024 if (mCallerInfo.contactDisplayPhotoUri != null) {
1025 Log.d(this, "Searching person uri %s for call %s",
1026 mCallerInfo.contactDisplayPhotoUri, this);
Santos Cordon99c8a6f2014-05-28 18:28:47 -07001027 ContactsAsyncHelper.startObtainPhotoAsync(
1028 token,
1029 TelecommApp.getInstance(),
Makoto Onukia1662d02014-07-10 15:31:59 -07001030 mCallerInfo.contactDisplayPhotoUri,
Santos Cordon99c8a6f2014-05-28 18:28:47 -07001031 sPhotoLoadListener,
1032 this);
Makoto Onukia1662d02014-07-10 15:31:59 -07001033 // Do not call onCallerInfoChanged yet in this case. We call it in setPhoto().
Santos Cordon64c7e962014-07-02 15:15:27 -07001034 } else {
1035 for (Listener l : mListeners) {
1036 l.onCallerInfoChanged(this);
1037 }
Santos Cordon99c8a6f2014-05-28 18:28:47 -07001038 }
Santos Cordon2174fb52014-05-29 08:22:56 -07001039
1040 processDirectToVoicemail();
Santos Cordon99c8a6f2014-05-28 18:28:47 -07001041 }
1042 }
1043
Yorke Lee6f3f7af2014-07-11 10:59:46 -07001044 CallerInfo getCallerInfo() {
1045 return mCallerInfo;
1046 }
1047
Santos Cordon99c8a6f2014-05-28 18:28:47 -07001048 /**
1049 * Saves the specified photo information if the specified token matches that of the last query.
1050 *
1051 * @param photo The photo as a drawable.
1052 * @param photoIcon The photo as a small icon.
1053 * @param token The token used with this query.
1054 */
1055 private void setPhoto(Drawable photo, Bitmap photoIcon, int token) {
1056 if (mQueryToken == token) {
1057 mCallerInfo.cachedPhoto = photo;
1058 mCallerInfo.cachedPhotoIcon = photoIcon;
Santos Cordon64c7e962014-07-02 15:15:27 -07001059
1060 for (Listener l : mListeners) {
1061 l.onCallerInfoChanged(this);
1062 }
Santos Cordonfd71f4a2014-05-28 13:59:49 -07001063 }
1064 }
Ihab Awadff7493a2014-06-10 13:47:44 -07001065
1066 private void maybeLoadCannedSmsResponses() {
1067 if (mIsIncoming && isRespondViaSmsCapable() && !mCannedSmsResponsesLoadingStarted) {
1068 Log.d(this, "maybeLoadCannedSmsResponses: starting task to load messages");
1069 mCannedSmsResponsesLoadingStarted = true;
1070 RespondViaSmsManager.getInstance().loadCannedTextMessages(
1071 new Response<Void, List<String>>() {
1072 @Override
1073 public void onResult(Void request, List<String>... result) {
1074 if (result.length > 0) {
1075 Log.d(this, "maybeLoadCannedSmsResponses: got %s", result[0]);
1076 mCannedSmsResponses = result[0];
1077 for (Listener l : mListeners) {
1078 l.onCannedSmsResponsesLoaded(Call.this);
1079 }
1080 }
1081 }
1082
1083 @Override
1084 public void onError(Void request, int code, String msg) {
1085 Log.w(Call.this, "Error obtaining canned SMS responses: %d %s", code,
1086 msg);
1087 }
1088 }
1089 );
1090 } else {
1091 Log.d(this, "maybeLoadCannedSmsResponses: doing nothing");
1092 }
1093 }
Sai Cheemalapatib7157e92014-06-11 17:51:55 -07001094
1095 /**
1096 * Sets speakerphone option on when call begins.
1097 */
1098 public void setStartWithSpeakerphoneOn(boolean startWithSpeakerphone) {
1099 mSpeakerphoneOn = startWithSpeakerphone;
1100 }
1101
1102 /**
1103 * Returns speakerphone option.
1104 *
1105 * @return Whether or not speakerphone should be set automatically when call begins.
1106 */
1107 public boolean getStartWithSpeakerphoneOn() {
1108 return mSpeakerphoneOn;
1109 }
Andrew Leee9a77652014-06-26 13:07:57 -07001110
1111 /**
Andrew Lee3bcf9352014-07-23 12:36:05 -07001112 * Sets a video call provider for the call.
Andrew Leee9a77652014-06-26 13:07:57 -07001113 */
Ihab Awad6fb37c82014-08-07 19:48:57 -07001114 public void setVideoProvider(IVideoProvider videoProvider) {
1115 mVideoProvider = videoProvider;
Nancy Chena65d41f2014-06-24 12:06:03 -07001116 for (Listener l : mListeners) {
Andrew Lee3bcf9352014-07-23 12:36:05 -07001117 l.onVideoCallProviderChanged(Call.this);
Nancy Chena65d41f2014-06-24 12:06:03 -07001118 }
1119 }
1120
1121 /**
Ihab Awad6fb37c82014-08-07 19:48:57 -07001122 * @return Return the {@link Connection.VideoProvider} binder.
Nancy Chena65d41f2014-06-24 12:06:03 -07001123 */
Ihab Awad6fb37c82014-08-07 19:48:57 -07001124 public IVideoProvider getVideoProvider() {
1125 return mVideoProvider;
Andrew Leee9a77652014-06-26 13:07:57 -07001126 }
Tyler Gunne19cc002014-07-01 11:32:53 -07001127
1128 /**
Tyler Gunnc4abd912014-07-08 14:22:10 -07001129 * The current video state for the call.
Ihab Awad6fb37c82014-08-07 19:48:57 -07001130 * Valid values: see {@link VideoProfile.VideoState}.
Tyler Gunnc4abd912014-07-08 14:22:10 -07001131 */
1132 public int getVideoState() {
1133 return mVideoState;
1134 }
1135
1136 /**
Tyler Gunn0a388fc2014-07-17 12:21:17 -07001137 * Returns the video states which were applicable over the duration of a call.
Ihab Awad6fb37c82014-08-07 19:48:57 -07001138 * See {@link VideoProfile} for a list of valid video states.
Tyler Gunn0a388fc2014-07-17 12:21:17 -07001139 *
1140 * @return The video states applicable over the duration of the call.
1141 */
1142 public int getVideoStateHistory() {
1143 return mVideoStateHistory;
1144 }
1145
1146 /**
1147 * Determines the current video state for the call.
1148 * For an outgoing call determines the desired video state for the call.
Ihab Awad6fb37c82014-08-07 19:48:57 -07001149 * Valid values: see {@link VideoProfile.VideoState}
Tyler Gunnc4abd912014-07-08 14:22:10 -07001150 *
Tyler Gunn0a388fc2014-07-17 12:21:17 -07001151 * @param videoState The video state for the call.
Tyler Gunnc4abd912014-07-08 14:22:10 -07001152 */
1153 public void setVideoState(int videoState) {
Tyler Gunn0a388fc2014-07-17 12:21:17 -07001154 // Track which video states were applicable over the duration of the call.
1155 mVideoStateHistory = mVideoStateHistory | videoState;
1156
Tyler Gunnc4abd912014-07-08 14:22:10 -07001157 mVideoState = videoState;
Andrew Lee4a796602014-07-11 17:23:03 -07001158 for (Listener l : mListeners) {
1159 l.onVideoStateChanged(this);
1160 }
Tyler Gunnc4abd912014-07-08 14:22:10 -07001161 }
Sailesh Nepal7e669572014-07-08 21:29:12 -07001162
1163 public boolean getAudioModeIsVoip() {
1164 return mAudioModeIsVoip;
1165 }
1166
1167 public void setAudioModeIsVoip(boolean audioModeIsVoip) {
1168 mAudioModeIsVoip = audioModeIsVoip;
1169 for (Listener l : mListeners) {
Sailesh Nepal35faf8c2014-07-08 22:02:34 -07001170 l.onAudioModeIsVoipChanged(this);
1171 }
1172 }
1173
1174 public StatusHints getStatusHints() {
1175 return mStatusHints;
1176 }
1177
1178 public void setStatusHints(StatusHints statusHints) {
1179 mStatusHints = statusHints;
1180 for (Listener l : mListeners) {
1181 l.onStatusHintsChanged(this);
Sailesh Nepal7e669572014-07-08 21:29:12 -07001182 }
1183 }
Sailesh Nepal9d58de52014-07-18 14:53:19 -07001184
1185 public void startActivityFromInCall(PendingIntent intent) {
1186 if (intent.isActivity()) {
1187 for (Listener l : mListeners) {
1188 l.onStartActivityFromInCall(this, intent);
1189 }
1190 } else {
1191 Log.w(this, "startActivityFromInCall, activity intent required");
1192 }
1193 }
Santos Cordon72890ce2014-07-21 01:32:04 -07001194
Santos Cordon0fbe6322014-08-14 04:04:25 -07001195 static int getStateFromConnectionState(int state) {
Santos Cordon72890ce2014-07-21 01:32:04 -07001196 switch (state) {
Ihab Awada02bef52014-08-13 18:18:42 -07001197 case Connection.STATE_INITIALIZING:
1198 return CallState.CONNECTING;
Ihab Awad6fb37c82014-08-07 19:48:57 -07001199 case Connection.STATE_ACTIVE:
Santos Cordon72890ce2014-07-21 01:32:04 -07001200 return CallState.ACTIVE;
Ihab Awad6fb37c82014-08-07 19:48:57 -07001201 case Connection.STATE_DIALING:
Santos Cordon72890ce2014-07-21 01:32:04 -07001202 return CallState.DIALING;
Ihab Awad6fb37c82014-08-07 19:48:57 -07001203 case Connection.STATE_DISCONNECTED:
Santos Cordon72890ce2014-07-21 01:32:04 -07001204 return CallState.DISCONNECTED;
Ihab Awad6fb37c82014-08-07 19:48:57 -07001205 case Connection.STATE_HOLDING:
Santos Cordon72890ce2014-07-21 01:32:04 -07001206 return CallState.ON_HOLD;
Ihab Awad6fb37c82014-08-07 19:48:57 -07001207 case Connection.STATE_NEW:
Santos Cordon72890ce2014-07-21 01:32:04 -07001208 return CallState.NEW;
Ihab Awad6fb37c82014-08-07 19:48:57 -07001209 case Connection.STATE_RINGING:
Santos Cordon72890ce2014-07-21 01:32:04 -07001210 return CallState.RINGING;
1211 }
1212 return CallState.DISCONNECTED;
1213 }
Ben Gilad9f2bed32013-12-12 17:43:26 -08001214}