blob: 699a3e2cba4445e711dc94a41e882db9dcd6fdc2 [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
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700103 private long mConnectTimeMillis;
Ben Gilad0407fb22014-01-09 16:18:41 -0800104
Santos Cordon61d0f702014-02-19 02:52:23 -0800105 /** The state of the call. */
106 private CallState mState;
107
108 /** The handle with which to establish this call. */
Sailesh Nepalce704b92014-03-17 18:31:43 -0700109 private Uri mHandle;
Santos Cordon61d0f702014-02-19 02:52:23 -0800110
Ben Gilad0407fb22014-01-09 16:18:41 -0800111 /**
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800112 * The call service which is attempted or already connecting this call.
Santos Cordon681663d2014-01-30 04:32:15 -0800113 */
Santos Cordonc195e362014-02-11 17:05:31 -0800114 private CallServiceWrapper mCallService;
Santos Cordon681663d2014-01-30 04:32:15 -0800115
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800116 /**
117 * The call-service selector for this call.
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800118 */
Sailesh Nepal18386a82014-03-19 10:22:40 -0700119 private CallServiceSelectorWrapper mCallServiceSelector;
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800120
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800121 /**
Ben Gilad61925612014-03-11 19:06:36 -0700122 * The set of call services that were attempted in the process of placing/switching this call
123 * but turned out unsuitable. Only used in the context of call switching.
124 */
125 private Set<CallServiceWrapper> mIncompatibleCallServices;
126
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700127 private boolean mIsEmergencyCall;
128
Ben Gilad61925612014-03-11 19:06:36 -0700129 /**
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700130 * Disconnect cause for the call. Only valid if the state of the call is DISCONNECTED.
131 * See {@link android.telephony.DisconnectCause}.
132 */
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700133 private int mDisconnectCause = DisconnectCause.NOT_VALID;
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700134
135 /**
136 * Additional disconnect information provided by the call service.
137 */
138 private String mDisconnectMessage;
139
Sailesh Nepal84fa5f82014-04-02 11:01:11 -0700140 /** Info used by the call services. */
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700141 private Bundle mExtras = Bundle.EMPTY;
Sailesh Nepal84fa5f82014-04-02 11:01:11 -0700142
143 /** The Uri to dial to perform the handoff. If this is null then handoff is not supported. */
144 private Uri mHandoffHandle;
145
146 /**
147 * References the call that is being handed off. This value is non-null for untracked calls
148 * that are being used to perform a handoff.
149 */
150 private Call mOriginalCall;
151
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700152 /**
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700153 * The descriptor for the call service that this call is being switched to, null if handoff is
154 * not in progress.
155 */
156 private CallServiceDescriptor mHandoffCallServiceDescriptor;
157
Santos Cordon766d04f2014-05-06 10:28:25 -0700158 /** Set of listeners on this call. */
159 private Set<Listener> mListeners = Sets.newHashSet();
160
Santos Cordon682fe6b2014-05-20 08:56:39 -0700161 private OutgoingCallProcessor mOutgoingCallProcessor;
162
163 // TODO(santoscordon): The repositories should be changed into singleton types.
164 private CallServiceRepository mCallServiceRepository;
165 private CallServiceSelectorRepository mSelectorRepository;
166
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700167 /** Caller information retrieved from the latest contact query. */
168 private CallerInfo mCallerInfo;
169
170 /** The latest token used with a contact info query. */
171 private int mQueryToken = 0;
172
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700173 /**
Sailesh Nepale59bb192014-04-01 18:33:59 -0700174 * Creates an empty call object.
Sailesh Nepal810735e2014-03-18 18:15:46 -0700175 *
176 * @param isIncoming True if this is an incoming call.
Santos Cordon493e8f22014-02-19 03:15:12 -0800177 */
Sailesh Nepal810735e2014-03-18 18:15:46 -0700178 Call(boolean isIncoming) {
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700179 this(null, null, isIncoming);
Santos Cordon493e8f22014-02-19 03:15:12 -0800180 }
181
182 /**
Ben Gilad0407fb22014-01-09 16:18:41 -0800183 * Persists the specified parameters and initializes the new instance.
184 *
185 * @param handle The handle to dial.
Yorke Lee33501632014-03-17 19:24:12 -0700186 * @param gatewayInfo Gateway information to use for the call.
Sailesh Nepal810735e2014-03-18 18:15:46 -0700187 * @param isIncoming True if this is an incoming call.
Ben Gilad0407fb22014-01-09 16:18:41 -0800188 */
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700189 Call(Uri handle, GatewayInfo gatewayInfo, boolean isIncoming) {
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800190 mState = CallState.NEW;
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700191 setHandle(handle);
Yorke Lee33501632014-03-17 19:24:12 -0700192 mGatewayInfo = gatewayInfo;
Sailesh Nepal810735e2014-03-18 18:15:46 -0700193 mIsIncoming = isIncoming;
Ben Gilad0407fb22014-01-09 16:18:41 -0800194 }
195
Santos Cordon766d04f2014-05-06 10:28:25 -0700196 void addListener(Listener listener) {
197 mListeners.add(listener);
198 }
199
200 void removeListener(Listener listener) {
201 mListeners.remove(listener);
202 }
203
Santos Cordon61d0f702014-02-19 02:52:23 -0800204 /** {@inheritDoc} */
205 @Override public String toString() {
Sailesh Nepal4538f012014-04-15 11:40:33 -0700206 String component = null;
207 if (mCallService != null && mCallService.getComponentName() != null) {
208 component = mCallService.getComponentName().flattenToShortString();
209 }
210 return String.format(Locale.US, "[%s, %s, %s]", mState, component, Log.piiHandle(mHandle));
Santos Cordon61d0f702014-02-19 02:52:23 -0800211 }
212
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800213 CallState getState() {
214 return mState;
215 }
216
217 /**
218 * Sets the call state. Although there exists the notion of appropriate state transitions
219 * (see {@link CallState}), in practice those expectations break down when cellular systems
220 * misbehave and they do this very often. The result is that we do not enforce state transitions
221 * and instead keep the code resilient to unexpected state changes.
222 */
Sailesh Nepal810735e2014-03-18 18:15:46 -0700223 void setState(CallState newState) {
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700224 Preconditions.checkState(newState != CallState.DISCONNECTED ||
225 mDisconnectCause != DisconnectCause.NOT_VALID);
Sailesh Nepal810735e2014-03-18 18:15:46 -0700226 if (mState != newState) {
227 Log.v(this, "setState %s -> %s", mState, newState);
228 mState = newState;
Sailesh Nepal810735e2014-03-18 18:15:46 -0700229 }
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800230 }
231
Sailesh Nepalce704b92014-03-17 18:31:43 -0700232 Uri getHandle() {
Ben Gilad0bf5b912014-01-28 17:55:57 -0800233 return mHandle;
234 }
235
Sailesh Nepalce704b92014-03-17 18:31:43 -0700236 void setHandle(Uri handle) {
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700237 if ((mHandle == null && handle != null) || (mHandle != null && !mHandle.equals(handle))) {
238 mHandle = handle;
239 mIsEmergencyCall = mHandle != null && PhoneNumberUtils.isLocalEmergencyNumber(
240 mHandle.getSchemeSpecificPart(), TelecommApp.getInstance());
241 startCallerInfoLookup();
242 }
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700243 }
244
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700245 String getName() {
246 return mCallerInfo == null ? null : mCallerInfo.name;
247 }
248
249 Bitmap getPhotoIcon() {
250 return mCallerInfo == null ? null : mCallerInfo.cachedPhotoIcon;
251 }
252
253 Drawable getPhoto() {
254 return mCallerInfo == null ? null : mCallerInfo.cachedPhoto;
255 }
256
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700257 /**
258 * @param disconnectCause The reason for the disconnection, any of
259 * {@link android.telephony.DisconnectCause}.
260 * @param disconnectMessage Optional call-service-provided message about the disconnect.
261 */
262 void setDisconnectCause(int disconnectCause, String disconnectMessage) {
263 // TODO: Consider combining this method with a setDisconnected() method that is totally
264 // separate from setState.
265 mDisconnectCause = disconnectCause;
266 mDisconnectMessage = disconnectMessage;
267 }
268
269 int getDisconnectCause() {
270 return mDisconnectCause;
271 }
272
273 String getDisconnectMessage() {
274 return mDisconnectMessage;
275 }
276
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700277 boolean isEmergencyCall() {
278 return mIsEmergencyCall;
Santos Cordon61d0f702014-02-19 02:52:23 -0800279 }
280
Yorke Lee33501632014-03-17 19:24:12 -0700281 /**
282 * @return The original handle this call is associated with. In-call services should use this
283 * handle when indicating in their UI the handle that is being called.
284 */
285 public Uri getOriginalHandle() {
286 if (mGatewayInfo != null && !mGatewayInfo.isEmpty()) {
287 return mGatewayInfo.getOriginalHandle();
288 }
289 return getHandle();
290 }
291
292 GatewayInfo getGatewayInfo() {
293 return mGatewayInfo;
294 }
295
Sailesh Nepal810735e2014-03-18 18:15:46 -0700296 boolean isIncoming() {
297 return mIsIncoming;
298 }
299
Ben Gilad0407fb22014-01-09 16:18:41 -0800300 /**
301 * @return The "age" of this call object in milliseconds, which typically also represents the
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700302 * period since this call was added to the set pending outgoing calls, see
303 * mCreationTimeMillis.
Ben Gilad0407fb22014-01-09 16:18:41 -0800304 */
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700305 long getAgeMillis() {
306 return System.currentTimeMillis() - mCreationTimeMillis;
Ben Gilad0407fb22014-01-09 16:18:41 -0800307 }
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800308
Yorke Leef98fb572014-03-05 10:56:55 -0800309 /**
310 * @return The time when this call object was created and added to the set of pending outgoing
311 * calls.
312 */
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700313 long getCreationTimeMillis() {
314 return mCreationTimeMillis;
315 }
316
317 long getConnectTimeMillis() {
318 return mConnectTimeMillis;
319 }
320
321 void setConnectTimeMillis(long connectTimeMillis) {
322 mConnectTimeMillis = connectTimeMillis;
Yorke Leef98fb572014-03-05 10:56:55 -0800323 }
324
Santos Cordonc195e362014-02-11 17:05:31 -0800325 CallServiceWrapper getCallService() {
Santos Cordon681663d2014-01-30 04:32:15 -0800326 return mCallService;
327 }
328
Santos Cordonc195e362014-02-11 17:05:31 -0800329 void setCallService(CallServiceWrapper callService) {
Sailesh Nepal0e5410a2014-04-04 01:20:58 -0700330 setCallService(callService, null);
331 }
332
333 /**
334 * Changes the call service this call is associated with. If callToReplace is non-null then this
335 * call takes its place within the call service.
336 */
337 void setCallService(CallServiceWrapper callService, Call callToReplace) {
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800338 Preconditions.checkNotNull(callService);
339
Yorke Leeadee12d2014-03-13 12:08:30 -0700340 clearCallService();
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800341
342 callService.incrementAssociatedCallCount();
Santos Cordon681663d2014-01-30 04:32:15 -0800343 mCallService = callService;
Sailesh Nepal0e5410a2014-04-04 01:20:58 -0700344 if (callToReplace == null) {
345 mCallService.addCall(this);
346 } else {
347 mCallService.replaceCall(this, callToReplace);
348 }
Santos Cordon681663d2014-01-30 04:32:15 -0800349 }
350
351 /**
352 * Clears the associated call service.
353 */
354 void clearCallService() {
Yorke Leeadee12d2014-03-13 12:08:30 -0700355 if (mCallService != null) {
Santos Cordonc499c1c2014-04-14 17:13:14 -0700356 CallServiceWrapper callServiceTemp = mCallService;
Yorke Leeadee12d2014-03-13 12:08:30 -0700357 mCallService = null;
Santos Cordonc499c1c2014-04-14 17:13:14 -0700358 callServiceTemp.removeCall(this);
359
360 // Decrementing the count can cause the service to unbind, which itself can trigger the
361 // service-death code. Since the service death code tries to clean up any associated
362 // calls, we need to make sure to remove that information (e.g., removeCall()) before
363 // we decrement. Technically, invoking removeCall() prior to decrementing is all that is
364 // necessary, but cleaning up mCallService prior to triggering an unbind is good to do.
365 // If you change this, make sure to update {@link clearCallServiceSelector} as well.
366 decrementAssociatedCallCount(callServiceTemp);
Yorke Leeadee12d2014-03-13 12:08:30 -0700367 }
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800368 }
369
Sailesh Nepal84fa5f82014-04-02 11:01:11 -0700370 CallServiceSelectorWrapper getCallServiceSelector() {
371 return mCallServiceSelector;
372 }
373
Sailesh Nepal18386a82014-03-19 10:22:40 -0700374 void setCallServiceSelector(CallServiceSelectorWrapper selector) {
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800375 Preconditions.checkNotNull(selector);
Sailesh Nepale59bb192014-04-01 18:33:59 -0700376
377 clearCallServiceSelector();
378
Santos Cordonc499c1c2014-04-14 17:13:14 -0700379 selector.incrementAssociatedCallCount();
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800380 mCallServiceSelector = selector;
Sailesh Nepale59bb192014-04-01 18:33:59 -0700381 mCallServiceSelector.addCall(this);
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800382 }
383
384 void clearCallServiceSelector() {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700385 if (mCallServiceSelector != null) {
Santos Cordonc499c1c2014-04-14 17:13:14 -0700386 CallServiceSelectorWrapper selectorTemp = mCallServiceSelector;
Sailesh Nepale59bb192014-04-01 18:33:59 -0700387 mCallServiceSelector = null;
Santos Cordonc499c1c2014-04-14 17:13:14 -0700388 selectorTemp.removeCall(this);
389
390 // See comment on {@link #clearCallService}.
391 decrementAssociatedCallCount(selectorTemp);
Sailesh Nepale59bb192014-04-01 18:33:59 -0700392 }
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800393 }
394
395 /**
Santos Cordon766d04f2014-05-06 10:28:25 -0700396 * Starts the incoming call flow through the switchboard. When switchboard completes, it will
397 * invoke handle[Un]SuccessfulIncomingCall.
398 *
399 * @param descriptor The relevant call-service descriptor.
400 * @param extras The optional extras passed via
401 * {@link TelecommConstants#EXTRA_INCOMING_CALL_EXTRAS}.
402 */
403 void startIncoming(CallServiceDescriptor descriptor, Bundle extras) {
404 Switchboard.getInstance().retrieveIncomingCall(this, descriptor, extras);
405 }
406
407 void handleSuccessfulIncoming(CallInfo callInfo) {
408 Preconditions.checkState(callInfo.getState() == CallState.RINGING);
409 setHandle(callInfo.getHandle());
410
411 // TODO(santoscordon): Make this class (not CallsManager) responsible for changing the call
412 // state to RINGING.
413
414 // TODO(santoscordon): Replace this with state transitions related to "connecting".
415 for (Listener l : mListeners) {
416 l.onSuccessfulIncomingCall(this, callInfo);
417 }
418 }
419
420 void handleFailedIncoming() {
421 clearCallService();
422
423 // TODO: Needs more specific disconnect error for this case.
424 setDisconnectCause(DisconnectCause.ERROR_UNSPECIFIED, null);
425 setState(CallState.DISCONNECTED);
426
427 // TODO(santoscordon): Replace this with state transitions related to "connecting".
428 for (Listener l : mListeners) {
429 l.onFailedIncomingCall(this);
430 }
431 }
432
433 /**
Santos Cordon682fe6b2014-05-20 08:56:39 -0700434 * Starts the outgoing call sequence. Upon completion, there should exist an active connection
435 * through a call service (or the call will have failed).
Santos Cordon766d04f2014-05-06 10:28:25 -0700436 */
437 void startOutgoing() {
Santos Cordon682fe6b2014-05-20 08:56:39 -0700438 Preconditions.checkState(mOutgoingCallProcessor == null);
439
440 mOutgoingCallProcessor = new OutgoingCallProcessor(
441 this,
442 Switchboard.getInstance().getCallServiceRepository(),
443 Switchboard.getInstance().getSelectorRepository(),
444 new AsyncResultCallback<Boolean>() {
445 @Override
446 public void onResult(Boolean wasCallPlaced) {
447 if (wasCallPlaced) {
448 handleSuccessfulOutgoing();
449 } else {
450 handleFailedOutgoing(mOutgoingCallProcessor.isAborted());
451 }
452 mOutgoingCallProcessor = null;
453 }
454 });
455 mOutgoingCallProcessor.process();
Santos Cordon766d04f2014-05-06 10:28:25 -0700456 }
457
458 void handleSuccessfulOutgoing() {
459 // TODO(santoscordon): Replace this with state transitions related to "connecting".
460 for (Listener l : mListeners) {
461 l.onSuccessfulOutgoingCall(this);
462 }
463 }
464
465 void handleFailedOutgoing(boolean isAborted) {
466 // TODO(santoscordon): Replace this with state transitions related to "connecting".
467 for (Listener l : mListeners) {
468 l.onFailedOutgoingCall(this, isAborted);
469 }
Santos Cordon682fe6b2014-05-20 08:56:39 -0700470
471 clearCallService();
472 clearCallServiceSelector();
Santos Cordon766d04f2014-05-06 10:28:25 -0700473 }
474
475 /**
Ben Gilad61925612014-03-11 19:06:36 -0700476 * Adds the specified call service to the list of incompatible services. The set is used when
477 * attempting to switch a phone call between call services such that incompatible services can
478 * be avoided.
479 *
480 * @param callService The incompatible call service.
481 */
482 void addIncompatibleCallService(CallServiceWrapper callService) {
483 if (mIncompatibleCallServices == null) {
484 mIncompatibleCallServices = Sets.newHashSet();
485 }
486 mIncompatibleCallServices.add(callService);
487 }
488
489 /**
490 * Checks whether or not the specified callService was identified as incompatible in the
491 * context of this call.
492 *
493 * @param callService The call service to evaluate.
494 * @return True upon incompatible call services and false otherwise.
495 */
496 boolean isIncompatibleCallService(CallServiceWrapper callService) {
497 return mIncompatibleCallServices != null &&
498 mIncompatibleCallServices.contains(callService);
499 }
500
501 /**
Ihab Awad74549ec2014-03-10 15:33:25 -0700502 * Plays the specified DTMF tone.
503 */
504 void playDtmfTone(char digit) {
505 if (mCallService == null) {
506 Log.w(this, "playDtmfTone() request on a call without a call service.");
507 } else {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700508 Log.i(this, "Send playDtmfTone to call service for call %s", this);
509 mCallService.playDtmfTone(this, digit);
Ihab Awad74549ec2014-03-10 15:33:25 -0700510 }
511 }
512
513 /**
514 * Stops playing any currently playing DTMF tone.
515 */
516 void stopDtmfTone() {
517 if (mCallService == null) {
518 Log.w(this, "stopDtmfTone() request on a call without a call service.");
519 } else {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700520 Log.i(this, "Send stopDtmfTone to call service for call %s", this);
521 mCallService.stopDtmfTone(this);
Ihab Awad74549ec2014-03-10 15:33:25 -0700522 }
523 }
524
525 /**
Santos Cordon049b7b62014-01-30 05:34:26 -0800526 * Attempts to disconnect the call through the call service.
527 */
528 void disconnect() {
Santos Cordon766d04f2014-05-06 10:28:25 -0700529 if (mState == CallState.NEW) {
Santos Cordon682fe6b2014-05-20 08:56:39 -0700530 Log.v(this, "Aborting call %s", this);
531 abort();
Santos Cordon74d420b2014-05-07 14:38:47 -0700532 } else if (mState != CallState.ABORTED && mState != CallState.DISCONNECTED) {
Santos Cordon766d04f2014-05-06 10:28:25 -0700533 Preconditions.checkNotNull(mCallService);
534
Sailesh Nepale59bb192014-04-01 18:33:59 -0700535 Log.i(this, "Send disconnect to call service for call: %s", this);
Santos Cordonc195e362014-02-11 17:05:31 -0800536 // The call isn't officially disconnected until the call service confirms that the call
537 // was actually disconnected. Only then is the association between call and call service
538 // severed, see {@link CallsManager#markCallAsDisconnected}.
Sailesh Nepale59bb192014-04-01 18:33:59 -0700539 mCallService.disconnect(this);
Santos Cordon049b7b62014-01-30 05:34:26 -0800540 }
541 }
542
Santos Cordon682fe6b2014-05-20 08:56:39 -0700543 void abort() {
544 if (mOutgoingCallProcessor != null) {
545 mOutgoingCallProcessor.abort();
546 }
547 }
548
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800549 /**
Santos Cordon61d0f702014-02-19 02:52:23 -0800550 * Answers the call if it is ringing.
551 */
552 void answer() {
553 Preconditions.checkNotNull(mCallService);
554
555 // Check to verify that the call is still in the ringing state. A call can change states
556 // between the time the user hits 'answer' and Telecomm receives the command.
557 if (isRinging("answer")) {
558 // At this point, we are asking the call service to answer but we don't assume that
559 // it will work. Instead, we wait until confirmation from the call service that the
560 // call is in a non-RINGING state before changing the UI. See
561 // {@link CallServiceAdapter#setActive} and other set* methods.
Sailesh Nepale59bb192014-04-01 18:33:59 -0700562 mCallService.answer(this);
Santos Cordon61d0f702014-02-19 02:52:23 -0800563 }
564 }
565
566 /**
567 * Rejects the call if it is ringing.
568 */
569 void reject() {
570 Preconditions.checkNotNull(mCallService);
571
572 // Check to verify that the call is still in the ringing state. A call can change states
573 // between the time the user hits 'reject' and Telecomm receives the command.
574 if (isRinging("reject")) {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700575 mCallService.reject(this);
Santos Cordon61d0f702014-02-19 02:52:23 -0800576 }
577 }
578
579 /**
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700580 * Puts the call on hold if it is currently active.
581 */
582 void hold() {
583 Preconditions.checkNotNull(mCallService);
584
585 if (mState == CallState.ACTIVE) {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700586 mCallService.hold(this);
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700587 }
588 }
589
590 /**
591 * Releases the call from hold if it is currently active.
592 */
593 void unhold() {
594 Preconditions.checkNotNull(mCallService);
595
596 if (mState == CallState.ON_HOLD) {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700597 mCallService.unhold(this);
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700598 }
599 }
600
601 /**
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800602 * @return An object containing read-only information about this call.
603 */
Sailesh Nepale59bb192014-04-01 18:33:59 -0700604 CallInfo toCallInfo(String callId) {
Sailesh Nepal84fa5f82014-04-02 11:01:11 -0700605 CallServiceDescriptor descriptor = null;
606 if (mCallService != null) {
607 descriptor = mCallService.getDescriptor();
608 } else if (mOriginalCall != null && mOriginalCall.mCallService != null) {
609 descriptor = mOriginalCall.mCallService.getDescriptor();
610 }
611 return new CallInfo(callId, mState, mHandle, mGatewayInfo, mExtras, descriptor);
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800612 }
613
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700614 /** Checks if this is a live call or not. */
615 boolean isAlive() {
616 switch (mState) {
617 case NEW:
618 case RINGING:
619 case DISCONNECTED:
620 case ABORTED:
621 return false;
622 default:
623 return true;
624 }
625 }
626
Santos Cordon40f78c22014-04-07 02:11:42 -0700627 boolean isActive() {
628 switch (mState) {
629 case ACTIVE:
630 case POST_DIAL:
631 case POST_DIAL_WAIT:
632 return true;
633 default:
634 return false;
635 }
636 }
637
Sailesh Nepal84fa5f82014-04-02 11:01:11 -0700638 Bundle getExtras() {
639 return mExtras;
640 }
641
642 void setExtras(Bundle extras) {
643 mExtras = extras;
644 }
645
646 Uri getHandoffHandle() {
647 return mHandoffHandle;
648 }
649
650 void setHandoffHandle(Uri handoffHandle) {
651 mHandoffHandle = handoffHandle;
652 }
653
654 Call getOriginalCall() {
655 return mOriginalCall;
656 }
657
658 void setOriginalCall(Call originalCall) {
659 mOriginalCall = originalCall;
660 }
661
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700662 CallServiceDescriptor getHandoffCallServiceDescriptor() {
663 return mHandoffCallServiceDescriptor;
664 }
665
666 void setHandoffCallServiceDescriptor(CallServiceDescriptor descriptor) {
667 mHandoffCallServiceDescriptor = descriptor;
668 }
669
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800670 /**
Santos Cordon61d0f702014-02-19 02:52:23 -0800671 * @return True if the call is ringing, else logs the action name.
672 */
673 private boolean isRinging(String actionName) {
674 if (mState == CallState.RINGING) {
675 return true;
676 }
677
Sailesh Nepalf1c191d2014-03-07 18:17:39 -0800678 Log.i(this, "Request to %s a non-ringing call %s", actionName, this);
Santos Cordon61d0f702014-02-19 02:52:23 -0800679 return false;
680 }
681
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800682 @SuppressWarnings("rawtypes")
683 private void decrementAssociatedCallCount(ServiceBinder binder) {
684 if (binder != null) {
685 binder.decrementAssociatedCallCount();
686 }
687 }
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700688
689 /**
690 * Looks up contact information based on the current handle.
691 */
692 private void startCallerInfoLookup() {
693 String number = mHandle == null ? null : mHandle.getSchemeSpecificPart();
694
695 mQueryToken++; // Updated so that previous queries can no longer set the information.
696 mCallerInfo = null;
697 if (!TextUtils.isEmpty(number)) {
698 CallerInfoAsyncQuery.startQuery(
699 mQueryToken,
700 TelecommApp.getInstance(),
701 number,
702 sCallerInfoQueryListener,
703 this);
704 }
705 }
706
707 /**
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700708 * Saves the specified caller info if the specified token matches that of the last query
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700709 * that was made.
710 *
711 * @param callerInfo The new caller information to set.
712 * @param token The token used with this query.
713 */
714 private void setCallerInfo(CallerInfo callerInfo, int token) {
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700715 Preconditions.checkNotNull(callerInfo);
716
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700717 if (mQueryToken == token) {
718 mCallerInfo = callerInfo;
Santos Cordon99c8a6f2014-05-28 18:28:47 -0700719
720 if (mCallerInfo.person_id != 0) {
721 Uri personUri =
722 ContentUris.withAppendedId(Contacts.CONTENT_URI, mCallerInfo.person_id);
723 Log.d(this, "Searching person uri %s for call %s", personUri, this);
724 ContactsAsyncHelper.startObtainPhotoAsync(
725 token,
726 TelecommApp.getInstance(),
727 personUri,
728 sPhotoLoadListener,
729 this);
730 }
731 }
732 }
733
734 /**
735 * Saves the specified photo information if the specified token matches that of the last query.
736 *
737 * @param photo The photo as a drawable.
738 * @param photoIcon The photo as a small icon.
739 * @param token The token used with this query.
740 */
741 private void setPhoto(Drawable photo, Bitmap photoIcon, int token) {
742 if (mQueryToken == token) {
743 mCallerInfo.cachedPhoto = photo;
744 mCallerInfo.cachedPhotoIcon = photoIcon;
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700745 }
746 }
Ben Gilad9f2bed32013-12-12 17:43:26 -0800747}