blob: 2bcc97875ed139d4eb4d8102ec8f46a3e978a4ca [file] [log] [blame]
Ben Gilad0407fb22014-01-09 16:18:41 -08001/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Ben Gilad9f2bed32013-12-12 17:43:26 -080017package com.android.telecomm;
18
Santos Cordon99c8a6f2014-05-28 18:28:47 -070019import android.content.ContentUris;
20import android.graphics.Bitmap;
21import android.graphics.drawable.Drawable;
Sailesh Nepalce704b92014-03-17 18:31:43 -070022import android.net.Uri;
Sailesh Nepal84fa5f82014-04-02 11:01:11 -070023import android.os.Bundle;
Santos Cordonfd71f4a2014-05-28 13:59:49 -070024import android.os.Handler;
Santos Cordon99c8a6f2014-05-28 18:28:47 -070025import android.provider.ContactsContract.Contacts;
Santos Cordon0b03b4b2014-01-29 18:01:59 -080026import android.telecomm.CallInfo;
Sailesh Nepal84fa5f82014-04-02 11:01:11 -070027import android.telecomm.CallServiceDescriptor;
Santos Cordon0b03b4b2014-01-29 18:01:59 -080028import android.telecomm.CallState;
Yorke Lee33501632014-03-17 19:24:12 -070029import android.telecomm.GatewayInfo;
Santos Cordon766d04f2014-05-06 10:28:25 -070030import android.telecomm.TelecommConstants;
Santos Cordon79ff2bc2014-03-27 15:31:27 -070031import android.telephony.DisconnectCause;
Sailesh Nepal6aca10a2014-03-24 16:11:02 -070032import android.telephony.PhoneNumberUtils;
Santos Cordonfd71f4a2014-05-28 13:59:49 -070033import android.text.TextUtils;
Santos Cordon0b03b4b2014-01-29 18:01:59 -080034
Santos Cordonfd71f4a2014-05-28 13:59:49 -070035import com.android.internal.telephony.CallerInfo;
36import com.android.internal.telephony.CallerInfoAsyncQuery;
37import com.android.internal.telephony.CallerInfoAsyncQuery.OnQueryCompleteListener;
Santos Cordon99c8a6f2014-05-28 18:28:47 -070038
39import com.android.telecomm.ContactsAsyncHelper.OnImageLoadCompleteListener;
Ben Gilad61925612014-03-11 19:06:36 -070040import com.google.android.collect.Sets;
Santos Cordon61d0f702014-02-19 02:52:23 -080041import com.google.common.base.Preconditions;
42
Sailesh Nepal91990782014-03-08 17:45:52 -080043import java.util.Locale;
Ben Gilad61925612014-03-11 19:06:36 -070044import java.util.Set;
Ben Gilad0407fb22014-01-09 16:18:41 -080045
Ben Gilad2495d572014-01-09 17:26:19 -080046/**
47 * Encapsulates all aspects of a given phone call throughout its lifecycle, starting
48 * from the time the call intent was received by Telecomm (vs. the time the call was
49 * connected etc).
50 */
Ben Gilad0407fb22014-01-09 16:18:41 -080051final class Call {
Santos Cordon766d04f2014-05-06 10:28:25 -070052
53 /**
54 * Listener for events on the call.
55 */
56 interface Listener {
57 void onSuccessfulOutgoingCall(Call call);
58 void onFailedOutgoingCall(Call call, boolean isAborted);
59 void onSuccessfulIncomingCall(Call call, CallInfo callInfo);
60 void onFailedIncomingCall(Call call);
61 }
62
Santos Cordonfd71f4a2014-05-28 13:59:49 -070063 private static final OnQueryCompleteListener sCallerInfoQueryListener =
Santos Cordon99c8a6f2014-05-28 18:28:47 -070064 new OnQueryCompleteListener() {
65 /** ${inheritDoc} */
66 @Override
67 public void onQueryComplete(int token, Object cookie, CallerInfo callerInfo) {
68 if (cookie != null) {
69 ((Call) cookie).setCallerInfo(callerInfo, token);
70 }
Santos Cordonfd71f4a2014-05-28 13:59:49 -070071 }
Santos Cordon99c8a6f2014-05-28 18:28:47 -070072 };
73
74 private static final OnImageLoadCompleteListener sPhotoLoadListener =
75 new OnImageLoadCompleteListener() {
76 /** ${inheritDoc} */
77 @Override
78 public void onImageLoadComplete(
79 int token, Drawable photo, Bitmap photoIcon, Object cookie) {
80 if (cookie != null) {
81 ((Call) cookie).setPhoto(photo, photoIcon, token);
82 }
83 }
84 };
Ben Gilad0407fb22014-01-09 16:18:41 -080085
Sailesh Nepal810735e2014-03-18 18:15:46 -070086 /** True if this is an incoming call. */
87 private final boolean mIsIncoming;
88
Ben Gilad0407fb22014-01-09 16:18:41 -080089 /**
90 * The time this call was created, typically also the time this call was added to the set
91 * of pending outgoing calls (mPendingOutgoingCalls) that's maintained by the switchboard.
92 * Beyond logging and such, may also be used for bookkeeping and specifically for marking
93 * certain call attempts as failed attempts.
94 */
Sailesh Nepal8c85dee2014-04-07 22:21:40 -070095 private final long mCreationTimeMillis = System.currentTimeMillis();
96
Santos Cordonfd71f4a2014-05-28 13:59:49 -070097 /** The gateway information associated with this call. This stores the original call handle
98 * that the user is attempting to connect to via the gateway, the actual handle to dial in
99 * order to connect the call via the gateway, as well as the package name of the gateway
100 * service. */
101 private final GatewayInfo mGatewayInfo;
102
Santos Cordon2174fb52014-05-29 08:22:56 -0700103 private final Handler mHandler = new Handler();
104
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700105 private long mConnectTimeMillis;
Ben Gilad0407fb22014-01-09 16:18:41 -0800106
Santos Cordon61d0f702014-02-19 02:52:23 -0800107 /** The state of the call. */
108 private CallState mState;
109
110 /** The handle with which to establish this call. */
Sailesh Nepalce704b92014-03-17 18:31:43 -0700111 private Uri mHandle;
Santos Cordon61d0f702014-02-19 02:52:23 -0800112
Ben Gilad0407fb22014-01-09 16:18:41 -0800113 /**
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800114 * The call service which is attempted or already connecting this call.
Santos Cordon681663d2014-01-30 04:32:15 -0800115 */
Santos Cordonc195e362014-02-11 17:05:31 -0800116 private CallServiceWrapper mCallService;
Santos Cordon681663d2014-01-30 04:32:15 -0800117
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800118 /**
119 * The call-service selector for this call.
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800120 */
Sailesh Nepal18386a82014-03-19 10:22:40 -0700121 private CallServiceSelectorWrapper mCallServiceSelector;
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800122
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800123 /**
Ben Gilad61925612014-03-11 19:06:36 -0700124 * The set of call services that were attempted in the process of placing/switching this call
125 * but turned out unsuitable. Only used in the context of call switching.
126 */
127 private Set<CallServiceWrapper> mIncompatibleCallServices;
128
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700129 private boolean mIsEmergencyCall;
130
Ben Gilad61925612014-03-11 19:06:36 -0700131 /**
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700132 * Disconnect cause for the call. Only valid if the state of the call is DISCONNECTED.
133 * See {@link android.telephony.DisconnectCause}.
134 */
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700135 private int mDisconnectCause = DisconnectCause.NOT_VALID;
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700136
137 /**
138 * Additional disconnect information provided by the call service.
139 */
140 private String mDisconnectMessage;
141
Sailesh Nepal84fa5f82014-04-02 11:01:11 -0700142 /** Info used by the call services. */
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700143 private Bundle mExtras = Bundle.EMPTY;
Sailesh Nepal84fa5f82014-04-02 11:01:11 -0700144
145 /** The Uri to dial to perform the handoff. If this is null then handoff is not supported. */
146 private Uri mHandoffHandle;
147
148 /**
149 * References the call that is being handed off. This value is non-null for untracked calls
150 * that are being used to perform a handoff.
151 */
152 private Call mOriginalCall;
153
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700154 /**
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700155 * The descriptor for the call service that this call is being switched to, null if handoff is
156 * not in progress.
157 */
158 private CallServiceDescriptor mHandoffCallServiceDescriptor;
159
Santos Cordon766d04f2014-05-06 10:28:25 -0700160 /** Set of listeners on this call. */
161 private Set<Listener> mListeners = Sets.newHashSet();
162
Santos Cordon682fe6b2014-05-20 08:56:39 -0700163 private OutgoingCallProcessor mOutgoingCallProcessor;
164
165 // TODO(santoscordon): The repositories should be changed into singleton types.
166 private CallServiceRepository mCallServiceRepository;
167 private CallServiceSelectorRepository mSelectorRepository;
168
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700169 /** Caller information retrieved from the latest contact query. */
170 private CallerInfo mCallerInfo;
171
172 /** The latest token used with a contact info query. */
173 private int mQueryToken = 0;
174
Santos Cordon2174fb52014-05-29 08:22:56 -0700175 /** Incoming call-info to use when direct-to-voicemail query finishes. */
176 private CallInfo mPendingDirectToVoicemailCallInfo;
177
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700178 /**
Sailesh Nepale59bb192014-04-01 18:33:59 -0700179 * Creates an empty call object.
Sailesh Nepal810735e2014-03-18 18:15:46 -0700180 *
181 * @param isIncoming True if this is an incoming call.
Santos Cordon493e8f22014-02-19 03:15:12 -0800182 */
Sailesh Nepal810735e2014-03-18 18:15:46 -0700183 Call(boolean isIncoming) {
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700184 this(null, null, isIncoming);
Santos Cordon493e8f22014-02-19 03:15:12 -0800185 }
186
187 /**
Ben Gilad0407fb22014-01-09 16:18:41 -0800188 * Persists the specified parameters and initializes the new instance.
189 *
190 * @param handle The handle to dial.
Yorke Lee33501632014-03-17 19:24:12 -0700191 * @param gatewayInfo Gateway information to use for the call.
Sailesh Nepal810735e2014-03-18 18:15:46 -0700192 * @param isIncoming True if this is an incoming call.
Ben Gilad0407fb22014-01-09 16:18:41 -0800193 */
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700194 Call(Uri handle, GatewayInfo gatewayInfo, boolean isIncoming) {
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800195 mState = CallState.NEW;
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700196 setHandle(handle);
Yorke Lee33501632014-03-17 19:24:12 -0700197 mGatewayInfo = gatewayInfo;
Sailesh Nepal810735e2014-03-18 18:15:46 -0700198 mIsIncoming = isIncoming;
Ben Gilad0407fb22014-01-09 16:18:41 -0800199 }
200
Santos Cordon766d04f2014-05-06 10:28:25 -0700201 void addListener(Listener listener) {
202 mListeners.add(listener);
203 }
204
205 void removeListener(Listener listener) {
206 mListeners.remove(listener);
207 }
208
Santos Cordon61d0f702014-02-19 02:52:23 -0800209 /** {@inheritDoc} */
210 @Override public String toString() {
Sailesh Nepal4538f012014-04-15 11:40:33 -0700211 String component = null;
212 if (mCallService != null && mCallService.getComponentName() != null) {
213 component = mCallService.getComponentName().flattenToShortString();
214 }
215 return String.format(Locale.US, "[%s, %s, %s]", mState, component, Log.piiHandle(mHandle));
Santos Cordon61d0f702014-02-19 02:52:23 -0800216 }
217
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800218 CallState getState() {
219 return mState;
220 }
221
222 /**
223 * Sets the call state. Although there exists the notion of appropriate state transitions
224 * (see {@link CallState}), in practice those expectations break down when cellular systems
225 * misbehave and they do this very often. The result is that we do not enforce state transitions
226 * and instead keep the code resilient to unexpected state changes.
227 */
Sailesh Nepal810735e2014-03-18 18:15:46 -0700228 void setState(CallState newState) {
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700229 Preconditions.checkState(newState != CallState.DISCONNECTED ||
230 mDisconnectCause != DisconnectCause.NOT_VALID);
Sailesh Nepal810735e2014-03-18 18:15:46 -0700231 if (mState != newState) {
232 Log.v(this, "setState %s -> %s", mState, newState);
233 mState = newState;
Sailesh Nepal810735e2014-03-18 18:15:46 -0700234 }
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800235 }
236
Sailesh Nepalce704b92014-03-17 18:31:43 -0700237 Uri getHandle() {
Ben Gilad0bf5b912014-01-28 17:55:57 -0800238 return mHandle;
239 }
240
Sailesh Nepalce704b92014-03-17 18:31:43 -0700241 void setHandle(Uri handle) {
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700242 if ((mHandle == null && handle != null) || (mHandle != null && !mHandle.equals(handle))) {
243 mHandle = handle;
244 mIsEmergencyCall = mHandle != null && PhoneNumberUtils.isLocalEmergencyNumber(
245 mHandle.getSchemeSpecificPart(), TelecommApp.getInstance());
246 startCallerInfoLookup();
247 }
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700248 }
249
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700250 String getName() {
251 return mCallerInfo == null ? null : mCallerInfo.name;
252 }
253
254 Bitmap getPhotoIcon() {
255 return mCallerInfo == null ? null : mCallerInfo.cachedPhotoIcon;
256 }
257
258 Drawable getPhoto() {
259 return mCallerInfo == null ? null : mCallerInfo.cachedPhoto;
260 }
261
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700262 /**
263 * @param disconnectCause The reason for the disconnection, any of
264 * {@link android.telephony.DisconnectCause}.
265 * @param disconnectMessage Optional call-service-provided message about the disconnect.
266 */
267 void setDisconnectCause(int disconnectCause, String disconnectMessage) {
268 // TODO: Consider combining this method with a setDisconnected() method that is totally
269 // separate from setState.
270 mDisconnectCause = disconnectCause;
271 mDisconnectMessage = disconnectMessage;
272 }
273
274 int getDisconnectCause() {
275 return mDisconnectCause;
276 }
277
278 String getDisconnectMessage() {
279 return mDisconnectMessage;
280 }
281
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700282 boolean isEmergencyCall() {
283 return mIsEmergencyCall;
Santos Cordon61d0f702014-02-19 02:52:23 -0800284 }
285
Yorke Lee33501632014-03-17 19:24:12 -0700286 /**
287 * @return The original handle this call is associated with. In-call services should use this
288 * handle when indicating in their UI the handle that is being called.
289 */
290 public Uri getOriginalHandle() {
291 if (mGatewayInfo != null && !mGatewayInfo.isEmpty()) {
292 return mGatewayInfo.getOriginalHandle();
293 }
294 return getHandle();
295 }
296
297 GatewayInfo getGatewayInfo() {
298 return mGatewayInfo;
299 }
300
Sailesh Nepal810735e2014-03-18 18:15:46 -0700301 boolean isIncoming() {
302 return mIsIncoming;
303 }
304
Ben Gilad0407fb22014-01-09 16:18:41 -0800305 /**
306 * @return The "age" of this call object in milliseconds, which typically also represents the
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700307 * period since this call was added to the set pending outgoing calls, see
308 * mCreationTimeMillis.
Ben Gilad0407fb22014-01-09 16:18:41 -0800309 */
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700310 long getAgeMillis() {
311 return System.currentTimeMillis() - mCreationTimeMillis;
Ben Gilad0407fb22014-01-09 16:18:41 -0800312 }
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800313
Yorke Leef98fb572014-03-05 10:56:55 -0800314 /**
315 * @return The time when this call object was created and added to the set of pending outgoing
316 * calls.
317 */
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700318 long getCreationTimeMillis() {
319 return mCreationTimeMillis;
320 }
321
322 long getConnectTimeMillis() {
323 return mConnectTimeMillis;
324 }
325
326 void setConnectTimeMillis(long connectTimeMillis) {
327 mConnectTimeMillis = connectTimeMillis;
Yorke Leef98fb572014-03-05 10:56:55 -0800328 }
329
Santos Cordonc195e362014-02-11 17:05:31 -0800330 CallServiceWrapper getCallService() {
Santos Cordon681663d2014-01-30 04:32:15 -0800331 return mCallService;
332 }
333
Santos Cordonc195e362014-02-11 17:05:31 -0800334 void setCallService(CallServiceWrapper callService) {
Sailesh Nepal0e5410a2014-04-04 01:20:58 -0700335 setCallService(callService, null);
336 }
337
338 /**
339 * Changes the call service this call is associated with. If callToReplace is non-null then this
340 * call takes its place within the call service.
341 */
342 void setCallService(CallServiceWrapper callService, Call callToReplace) {
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800343 Preconditions.checkNotNull(callService);
344
Yorke Leeadee12d2014-03-13 12:08:30 -0700345 clearCallService();
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800346
347 callService.incrementAssociatedCallCount();
Santos Cordon681663d2014-01-30 04:32:15 -0800348 mCallService = callService;
Sailesh Nepal0e5410a2014-04-04 01:20:58 -0700349 if (callToReplace == null) {
350 mCallService.addCall(this);
351 } else {
352 mCallService.replaceCall(this, callToReplace);
353 }
Santos Cordon681663d2014-01-30 04:32:15 -0800354 }
355
356 /**
357 * Clears the associated call service.
358 */
359 void clearCallService() {
Yorke Leeadee12d2014-03-13 12:08:30 -0700360 if (mCallService != null) {
Santos Cordonc499c1c2014-04-14 17:13:14 -0700361 CallServiceWrapper callServiceTemp = mCallService;
Yorke Leeadee12d2014-03-13 12:08:30 -0700362 mCallService = null;
Santos Cordonc499c1c2014-04-14 17:13:14 -0700363 callServiceTemp.removeCall(this);
364
365 // Decrementing the count can cause the service to unbind, which itself can trigger the
366 // service-death code. Since the service death code tries to clean up any associated
367 // calls, we need to make sure to remove that information (e.g., removeCall()) before
368 // we decrement. Technically, invoking removeCall() prior to decrementing is all that is
369 // necessary, but cleaning up mCallService prior to triggering an unbind is good to do.
370 // If you change this, make sure to update {@link clearCallServiceSelector} as well.
371 decrementAssociatedCallCount(callServiceTemp);
Yorke Leeadee12d2014-03-13 12:08:30 -0700372 }
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800373 }
374
Sailesh Nepal84fa5f82014-04-02 11:01:11 -0700375 CallServiceSelectorWrapper getCallServiceSelector() {
376 return mCallServiceSelector;
377 }
378
Sailesh Nepal18386a82014-03-19 10:22:40 -0700379 void setCallServiceSelector(CallServiceSelectorWrapper selector) {
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800380 Preconditions.checkNotNull(selector);
Sailesh Nepale59bb192014-04-01 18:33:59 -0700381
382 clearCallServiceSelector();
383
Santos Cordonc499c1c2014-04-14 17:13:14 -0700384 selector.incrementAssociatedCallCount();
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800385 mCallServiceSelector = selector;
Sailesh Nepale59bb192014-04-01 18:33:59 -0700386 mCallServiceSelector.addCall(this);
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800387 }
388
389 void clearCallServiceSelector() {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700390 if (mCallServiceSelector != null) {
Santos Cordonc499c1c2014-04-14 17:13:14 -0700391 CallServiceSelectorWrapper selectorTemp = mCallServiceSelector;
Sailesh Nepale59bb192014-04-01 18:33:59 -0700392 mCallServiceSelector = null;
Santos Cordonc499c1c2014-04-14 17:13:14 -0700393 selectorTemp.removeCall(this);
394
395 // See comment on {@link #clearCallService}.
396 decrementAssociatedCallCount(selectorTemp);
Sailesh Nepale59bb192014-04-01 18:33:59 -0700397 }
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800398 }
399
400 /**
Santos Cordon766d04f2014-05-06 10:28:25 -0700401 * Starts the incoming call flow through the switchboard. When switchboard completes, it will
402 * invoke handle[Un]SuccessfulIncomingCall.
403 *
404 * @param descriptor The relevant call-service descriptor.
405 * @param extras The optional extras passed via
406 * {@link TelecommConstants#EXTRA_INCOMING_CALL_EXTRAS}.
407 */
408 void startIncoming(CallServiceDescriptor descriptor, Bundle extras) {
409 Switchboard.getInstance().retrieveIncomingCall(this, descriptor, extras);
410 }
411
Santos Cordon2174fb52014-05-29 08:22:56 -0700412 /**
413 * Takes a verified incoming call and uses the handle to lookup direct-to-voicemail property
414 * from the contacts provider. The call is not yet exposed to the user at this point and
415 * the result of the query will determine if the call is rejected or passed through to the
416 * in-call UI.
417 */
418 void handleVerifiedIncoming(CallInfo callInfo) {
Santos Cordon766d04f2014-05-06 10:28:25 -0700419 Preconditions.checkState(callInfo.getState() == CallState.RINGING);
Santos Cordon2174fb52014-05-29 08:22:56 -0700420
421 // We do not handle incoming calls immediately when they are verified by the call service.
422 // We allow the caller-info-query code to execute first so that we can read the
423 // direct-to-voicemail property before deciding if we want to show the incoming call to the
424 // user or if we want to reject the call.
425 mPendingDirectToVoicemailCallInfo = callInfo;
426
427 // Setting the handle triggers the caller info lookup code.
Santos Cordon766d04f2014-05-06 10:28:25 -0700428 setHandle(callInfo.getHandle());
429
Santos Cordon2174fb52014-05-29 08:22:56 -0700430 // Timeout the direct-to-voicemail lookup execution so that we dont wait too long before
431 // showing the user the incoming call screen.
432 mHandler.postDelayed(new Runnable() {
433 @Override
434 public void run() {
435 processDirectToVoicemail();
436 }
437 }, Timeouts.getDirectToVoicemail());
438 }
Santos Cordon766d04f2014-05-06 10:28:25 -0700439
Santos Cordon2174fb52014-05-29 08:22:56 -0700440 void processDirectToVoicemail() {
441 if (mPendingDirectToVoicemailCallInfo != null) {
442 if (mCallerInfo != null && mCallerInfo.shouldSendToVoicemail) {
443 Log.i(this, "Directing call to voicemail: %s.", this);
444 // TODO(santoscordon): Once we move State handling from CallsManager to Call, we
445 // will not need to set RINGING state prior to calling reject.
446 setState(CallState.RINGING);
447 reject();
448 } else {
449 // TODO(santoscordon): Make this class (not CallsManager) responsible for changing
450 // the call state to RINGING.
451
452 // TODO(santoscordon): Replace this with state transition to RINGING.
453 for (Listener l : mListeners) {
454 l.onSuccessfulIncomingCall(this, mPendingDirectToVoicemailCallInfo);
455 }
456 }
457
458 mPendingDirectToVoicemailCallInfo = null;
Santos Cordon766d04f2014-05-06 10:28:25 -0700459 }
460 }
461
462 void handleFailedIncoming() {
463 clearCallService();
464
465 // TODO: Needs more specific disconnect error for this case.
466 setDisconnectCause(DisconnectCause.ERROR_UNSPECIFIED, null);
467 setState(CallState.DISCONNECTED);
468
469 // TODO(santoscordon): Replace this with state transitions related to "connecting".
470 for (Listener l : mListeners) {
471 l.onFailedIncomingCall(this);
472 }
473 }
474
475 /**
Santos Cordon682fe6b2014-05-20 08:56:39 -0700476 * Starts the outgoing call sequence. Upon completion, there should exist an active connection
477 * through a call service (or the call will have failed).
Santos Cordon766d04f2014-05-06 10:28:25 -0700478 */
479 void startOutgoing() {
Santos Cordon682fe6b2014-05-20 08:56:39 -0700480 Preconditions.checkState(mOutgoingCallProcessor == null);
481
482 mOutgoingCallProcessor = new OutgoingCallProcessor(
483 this,
484 Switchboard.getInstance().getCallServiceRepository(),
485 Switchboard.getInstance().getSelectorRepository(),
486 new AsyncResultCallback<Boolean>() {
487 @Override
488 public void onResult(Boolean wasCallPlaced) {
489 if (wasCallPlaced) {
490 handleSuccessfulOutgoing();
491 } else {
492 handleFailedOutgoing(mOutgoingCallProcessor.isAborted());
493 }
494 mOutgoingCallProcessor = null;
495 }
496 });
497 mOutgoingCallProcessor.process();
Santos Cordon766d04f2014-05-06 10:28:25 -0700498 }
499
500 void handleSuccessfulOutgoing() {
501 // TODO(santoscordon): Replace this with state transitions related to "connecting".
502 for (Listener l : mListeners) {
503 l.onSuccessfulOutgoingCall(this);
504 }
505 }
506
507 void handleFailedOutgoing(boolean isAborted) {
508 // TODO(santoscordon): Replace this with state transitions related to "connecting".
509 for (Listener l : mListeners) {
510 l.onFailedOutgoingCall(this, isAborted);
511 }
Santos Cordon682fe6b2014-05-20 08:56:39 -0700512
513 clearCallService();
514 clearCallServiceSelector();
Santos Cordon766d04f2014-05-06 10:28:25 -0700515 }
516
517 /**
Ben Gilad61925612014-03-11 19:06:36 -0700518 * Adds the specified call service to the list of incompatible services. The set is used when
519 * attempting to switch a phone call between call services such that incompatible services can
520 * be avoided.
521 *
522 * @param callService The incompatible call service.
523 */
524 void addIncompatibleCallService(CallServiceWrapper callService) {
525 if (mIncompatibleCallServices == null) {
526 mIncompatibleCallServices = Sets.newHashSet();
527 }
528 mIncompatibleCallServices.add(callService);
529 }
530
531 /**
532 * Checks whether or not the specified callService was identified as incompatible in the
533 * context of this call.
534 *
535 * @param callService The call service to evaluate.
536 * @return True upon incompatible call services and false otherwise.
537 */
538 boolean isIncompatibleCallService(CallServiceWrapper callService) {
539 return mIncompatibleCallServices != null &&
540 mIncompatibleCallServices.contains(callService);
541 }
542
543 /**
Ihab Awad74549ec2014-03-10 15:33:25 -0700544 * Plays the specified DTMF tone.
545 */
546 void playDtmfTone(char digit) {
547 if (mCallService == null) {
548 Log.w(this, "playDtmfTone() request on a call without a call service.");
549 } else {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700550 Log.i(this, "Send playDtmfTone to call service for call %s", this);
551 mCallService.playDtmfTone(this, digit);
Ihab Awad74549ec2014-03-10 15:33:25 -0700552 }
553 }
554
555 /**
556 * Stops playing any currently playing DTMF tone.
557 */
558 void stopDtmfTone() {
559 if (mCallService == null) {
560 Log.w(this, "stopDtmfTone() request on a call without a call service.");
561 } else {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700562 Log.i(this, "Send stopDtmfTone to call service for call %s", this);
563 mCallService.stopDtmfTone(this);
Ihab Awad74549ec2014-03-10 15:33:25 -0700564 }
565 }
566
567 /**
Santos Cordon049b7b62014-01-30 05:34:26 -0800568 * Attempts to disconnect the call through the call service.
569 */
570 void disconnect() {
Santos Cordon766d04f2014-05-06 10:28:25 -0700571 if (mState == CallState.NEW) {
Santos Cordon682fe6b2014-05-20 08:56:39 -0700572 Log.v(this, "Aborting call %s", this);
573 abort();
Santos Cordon74d420b2014-05-07 14:38:47 -0700574 } else if (mState != CallState.ABORTED && mState != CallState.DISCONNECTED) {
Santos Cordon766d04f2014-05-06 10:28:25 -0700575 Preconditions.checkNotNull(mCallService);
576
Sailesh Nepale59bb192014-04-01 18:33:59 -0700577 Log.i(this, "Send disconnect to call service for call: %s", this);
Santos Cordonc195e362014-02-11 17:05:31 -0800578 // The call isn't officially disconnected until the call service confirms that the call
579 // was actually disconnected. Only then is the association between call and call service
580 // severed, see {@link CallsManager#markCallAsDisconnected}.
Sailesh Nepale59bb192014-04-01 18:33:59 -0700581 mCallService.disconnect(this);
Santos Cordon049b7b62014-01-30 05:34:26 -0800582 }
583 }
584
Santos Cordon682fe6b2014-05-20 08:56:39 -0700585 void abort() {
586 if (mOutgoingCallProcessor != null) {
587 mOutgoingCallProcessor.abort();
588 }
589 }
590
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800591 /**
Santos Cordon61d0f702014-02-19 02:52:23 -0800592 * Answers the call if it is ringing.
593 */
594 void answer() {
595 Preconditions.checkNotNull(mCallService);
596
597 // Check to verify that the call is still in the ringing state. A call can change states
598 // between the time the user hits 'answer' and Telecomm receives the command.
599 if (isRinging("answer")) {
600 // At this point, we are asking the call service to answer but we don't assume that
601 // it will work. Instead, we wait until confirmation from the call service that the
602 // call is in a non-RINGING state before changing the UI. See
603 // {@link CallServiceAdapter#setActive} and other set* methods.
Sailesh Nepale59bb192014-04-01 18:33:59 -0700604 mCallService.answer(this);
Santos Cordon61d0f702014-02-19 02:52:23 -0800605 }
606 }
607
608 /**
609 * Rejects the call if it is ringing.
610 */
611 void reject() {
612 Preconditions.checkNotNull(mCallService);
613
614 // Check to verify that the call is still in the ringing state. A call can change states
615 // between the time the user hits 'reject' and Telecomm receives the command.
616 if (isRinging("reject")) {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700617 mCallService.reject(this);
Santos Cordon61d0f702014-02-19 02:52:23 -0800618 }
619 }
620
621 /**
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700622 * Puts the call on hold if it is currently active.
623 */
624 void hold() {
625 Preconditions.checkNotNull(mCallService);
626
627 if (mState == CallState.ACTIVE) {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700628 mCallService.hold(this);
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700629 }
630 }
631
632 /**
633 * Releases the call from hold if it is currently active.
634 */
635 void unhold() {
636 Preconditions.checkNotNull(mCallService);
637
638 if (mState == CallState.ON_HOLD) {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700639 mCallService.unhold(this);
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700640 }
641 }
642
643 /**
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800644 * @return An object containing read-only information about this call.
645 */
Sailesh Nepale59bb192014-04-01 18:33:59 -0700646 CallInfo toCallInfo(String callId) {
Sailesh Nepal84fa5f82014-04-02 11:01:11 -0700647 CallServiceDescriptor descriptor = null;
648 if (mCallService != null) {
649 descriptor = mCallService.getDescriptor();
650 } else if (mOriginalCall != null && mOriginalCall.mCallService != null) {
651 descriptor = mOriginalCall.mCallService.getDescriptor();
652 }
653 return new CallInfo(callId, mState, mHandle, mGatewayInfo, mExtras, descriptor);
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800654 }
655
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700656 /** Checks if this is a live call or not. */
657 boolean isAlive() {
658 switch (mState) {
659 case NEW:
660 case RINGING:
661 case DISCONNECTED:
662 case ABORTED:
663 return false;
664 default:
665 return true;
666 }
667 }
668
Santos Cordon40f78c22014-04-07 02:11:42 -0700669 boolean isActive() {
670 switch (mState) {
671 case ACTIVE:
672 case POST_DIAL:
673 case POST_DIAL_WAIT:
674 return true;
675 default:
676 return false;
677 }
678 }
679
Sailesh Nepal84fa5f82014-04-02 11:01:11 -0700680 Bundle getExtras() {
681 return mExtras;
682 }
683
684 void setExtras(Bundle extras) {
685 mExtras = extras;
686 }
687
688 Uri getHandoffHandle() {
689 return mHandoffHandle;
690 }
691
692 void setHandoffHandle(Uri handoffHandle) {
693 mHandoffHandle = handoffHandle;
694 }
695
696 Call getOriginalCall() {
697 return mOriginalCall;
698 }
699
700 void setOriginalCall(Call originalCall) {
701 mOriginalCall = originalCall;
702 }
703
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700704 CallServiceDescriptor getHandoffCallServiceDescriptor() {
705 return mHandoffCallServiceDescriptor;
706 }
707
708 void setHandoffCallServiceDescriptor(CallServiceDescriptor descriptor) {
709 mHandoffCallServiceDescriptor = descriptor;
710 }
711
Santos Cordon5ba7f272014-05-28 13:59:49 -0700712 Uri getRingtone() {
713 return mCallerInfo == null ? null : mCallerInfo.contactRingtoneUri;
714 }
715
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800716 /**
Santos Cordon61d0f702014-02-19 02:52:23 -0800717 * @return True if the call is ringing, else logs the action name.
718 */
719 private boolean isRinging(String actionName) {
720 if (mState == CallState.RINGING) {
721 return true;
722 }
723
Sailesh Nepalf1c191d2014-03-07 18:17:39 -0800724 Log.i(this, "Request to %s a non-ringing call %s", actionName, this);
Santos Cordon61d0f702014-02-19 02:52:23 -0800725 return false;
726 }
727
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800728 @SuppressWarnings("rawtypes")
729 private void decrementAssociatedCallCount(ServiceBinder binder) {
730 if (binder != null) {
731 binder.decrementAssociatedCallCount();
732 }
733 }
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700734
735 /**
736 * Looks up contact information based on the current handle.
737 */
738 private void startCallerInfoLookup() {
739 String number = mHandle == null ? null : mHandle.getSchemeSpecificPart();
740
741 mQueryToken++; // Updated so that previous queries can no longer set the information.
742 mCallerInfo = null;
743 if (!TextUtils.isEmpty(number)) {
Santos Cordon5ba7f272014-05-28 13:59:49 -0700744 Log.v(this, "Looking up information for: %s.", Log.piiHandle(number));
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700745 CallerInfoAsyncQuery.startQuery(
746 mQueryToken,
747 TelecommApp.getInstance(),
748 number,
749 sCallerInfoQueryListener,
750 this);
751 }
752 }
753
754 /**
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700755 * Saves the specified caller info if the specified token matches that of the last query
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700756 * that was made.
757 *
758 * @param callerInfo The new caller information to set.
759 * @param token The token used with this query.
760 */
761 private void setCallerInfo(CallerInfo callerInfo, int token) {
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700762 Preconditions.checkNotNull(callerInfo);
763
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700764 if (mQueryToken == token) {
765 mCallerInfo = callerInfo;
Santos Cordon5ba7f272014-05-28 13:59:49 -0700766 Log.i(this, "CallerInfo received for %s: %s", Log.piiHandle(mHandle), callerInfo);
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700767
768 if (mCallerInfo.person_id != 0) {
769 Uri personUri =
770 ContentUris.withAppendedId(Contacts.CONTENT_URI, mCallerInfo.person_id);
771 Log.d(this, "Searching person uri %s for call %s", personUri, this);
772 ContactsAsyncHelper.startObtainPhotoAsync(
773 token,
774 TelecommApp.getInstance(),
775 personUri,
776 sPhotoLoadListener,
777 this);
778 }
Santos Cordon2174fb52014-05-29 08:22:56 -0700779
780 processDirectToVoicemail();
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700781 }
782 }
783
784 /**
785 * Saves the specified photo information if the specified token matches that of the last query.
786 *
787 * @param photo The photo as a drawable.
788 * @param photoIcon The photo as a small icon.
789 * @param token The token used with this query.
790 */
791 private void setPhoto(Drawable photo, Bitmap photoIcon, int token) {
792 if (mQueryToken == token) {
793 mCallerInfo.cachedPhoto = photo;
794 mCallerInfo.cachedPhotoIcon = photoIcon;
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700795 }
796 }
Ben Gilad9f2bed32013-12-12 17:43:26 -0800797}