blob: 5eb2f9762e96368a1b1920f2bd057d26416210f9 [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 Nepalce704b92014-03-17 18:31:43 -070019import android.net.Uri;
Sailesh Nepal84fa5f82014-04-02 11:01:11 -070020import android.os.Bundle;
Santos Cordonfd71f4a2014-05-28 13:59:49 -070021import android.os.Handler;
Santos Cordon0b03b4b2014-01-29 18:01:59 -080022import android.telecomm.CallInfo;
Sailesh Nepal84fa5f82014-04-02 11:01:11 -070023import android.telecomm.CallServiceDescriptor;
Santos Cordon0b03b4b2014-01-29 18:01:59 -080024import android.telecomm.CallState;
Yorke Lee33501632014-03-17 19:24:12 -070025import android.telecomm.GatewayInfo;
Santos Cordon766d04f2014-05-06 10:28:25 -070026import android.telecomm.TelecommConstants;
Santos Cordon79ff2bc2014-03-27 15:31:27 -070027import android.telephony.DisconnectCause;
Sailesh Nepal6aca10a2014-03-24 16:11:02 -070028import android.telephony.PhoneNumberUtils;
Santos Cordonfd71f4a2014-05-28 13:59:49 -070029import android.text.TextUtils;
Santos Cordon0b03b4b2014-01-29 18:01:59 -080030
Santos Cordonfd71f4a2014-05-28 13:59:49 -070031import com.android.internal.telephony.CallerInfo;
32import com.android.internal.telephony.CallerInfoAsyncQuery;
33import com.android.internal.telephony.CallerInfoAsyncQuery.OnQueryCompleteListener;
Ben Gilad61925612014-03-11 19:06:36 -070034import com.google.android.collect.Sets;
Santos Cordon61d0f702014-02-19 02:52:23 -080035import com.google.common.base.Preconditions;
36
Sailesh Nepal91990782014-03-08 17:45:52 -080037import java.util.Locale;
Ben Gilad61925612014-03-11 19:06:36 -070038import java.util.Set;
Ben Gilad0407fb22014-01-09 16:18:41 -080039
Ben Gilad2495d572014-01-09 17:26:19 -080040/**
41 * Encapsulates all aspects of a given phone call throughout its lifecycle, starting
42 * from the time the call intent was received by Telecomm (vs. the time the call was
43 * connected etc).
44 */
Ben Gilad0407fb22014-01-09 16:18:41 -080045final class Call {
Santos Cordon766d04f2014-05-06 10:28:25 -070046
47 /**
48 * Listener for events on the call.
49 */
50 interface Listener {
51 void onSuccessfulOutgoingCall(Call call);
52 void onFailedOutgoingCall(Call call, boolean isAborted);
53 void onSuccessfulIncomingCall(Call call, CallInfo callInfo);
54 void onFailedIncomingCall(Call call);
55 }
56
Santos Cordonfd71f4a2014-05-28 13:59:49 -070057 private static final OnQueryCompleteListener sCallerInfoQueryListener =
58 new OnQueryCompleteListener() {
59 /** ${inheritDoc} */
60 @Override
61 public void onQueryComplete(int token, Object cookie, CallerInfo callerInfo) {
62 if (cookie != null) {
63 ((Call) cookie).setCallerInfo(callerInfo, token);
64 }
65 }
66 };
Ben Gilad0407fb22014-01-09 16:18:41 -080067
Sailesh Nepal810735e2014-03-18 18:15:46 -070068 /** True if this is an incoming call. */
69 private final boolean mIsIncoming;
70
Ben Gilad0407fb22014-01-09 16:18:41 -080071 /**
72 * The time this call was created, typically also the time this call was added to the set
73 * of pending outgoing calls (mPendingOutgoingCalls) that's maintained by the switchboard.
74 * Beyond logging and such, may also be used for bookkeeping and specifically for marking
75 * certain call attempts as failed attempts.
76 */
Sailesh Nepal8c85dee2014-04-07 22:21:40 -070077 private final long mCreationTimeMillis = System.currentTimeMillis();
78
Santos Cordonfd71f4a2014-05-28 13:59:49 -070079 /** The gateway information associated with this call. This stores the original call handle
80 * that the user is attempting to connect to via the gateway, the actual handle to dial in
81 * order to connect the call via the gateway, as well as the package name of the gateway
82 * service. */
83 private final GatewayInfo mGatewayInfo;
84
85 private final Handler mHandler = new Handler();
86
Sailesh Nepal8c85dee2014-04-07 22:21:40 -070087 private long mConnectTimeMillis;
Ben Gilad0407fb22014-01-09 16:18:41 -080088
Santos Cordon61d0f702014-02-19 02:52:23 -080089 /** The state of the call. */
90 private CallState mState;
91
92 /** The handle with which to establish this call. */
Sailesh Nepalce704b92014-03-17 18:31:43 -070093 private Uri mHandle;
Santos Cordon61d0f702014-02-19 02:52:23 -080094
Ben Gilad0407fb22014-01-09 16:18:41 -080095 /**
Ben Gilad8e55d1d2014-02-26 16:25:56 -080096 * The call service which is attempted or already connecting this call.
Santos Cordon681663d2014-01-30 04:32:15 -080097 */
Santos Cordonc195e362014-02-11 17:05:31 -080098 private CallServiceWrapper mCallService;
Santos Cordon681663d2014-01-30 04:32:15 -080099
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800100 /**
101 * The call-service selector for this call.
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800102 */
Sailesh Nepal18386a82014-03-19 10:22:40 -0700103 private CallServiceSelectorWrapper mCallServiceSelector;
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800104
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800105 /**
Ben Gilad61925612014-03-11 19:06:36 -0700106 * The set of call services that were attempted in the process of placing/switching this call
107 * but turned out unsuitable. Only used in the context of call switching.
108 */
109 private Set<CallServiceWrapper> mIncompatibleCallServices;
110
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700111 private boolean mIsEmergencyCall;
112
Ben Gilad61925612014-03-11 19:06:36 -0700113 /**
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700114 * Disconnect cause for the call. Only valid if the state of the call is DISCONNECTED.
115 * See {@link android.telephony.DisconnectCause}.
116 */
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700117 private int mDisconnectCause = DisconnectCause.NOT_VALID;
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700118
119 /**
120 * Additional disconnect information provided by the call service.
121 */
122 private String mDisconnectMessage;
123
Sailesh Nepal84fa5f82014-04-02 11:01:11 -0700124 /** Info used by the call services. */
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700125 private Bundle mExtras = Bundle.EMPTY;
Sailesh Nepal84fa5f82014-04-02 11:01:11 -0700126
127 /** The Uri to dial to perform the handoff. If this is null then handoff is not supported. */
128 private Uri mHandoffHandle;
129
130 /**
131 * References the call that is being handed off. This value is non-null for untracked calls
132 * that are being used to perform a handoff.
133 */
134 private Call mOriginalCall;
135
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700136 /**
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700137 * The descriptor for the call service that this call is being switched to, null if handoff is
138 * not in progress.
139 */
140 private CallServiceDescriptor mHandoffCallServiceDescriptor;
141
Santos Cordon766d04f2014-05-06 10:28:25 -0700142 /** Set of listeners on this call. */
143 private Set<Listener> mListeners = Sets.newHashSet();
144
Santos Cordon682fe6b2014-05-20 08:56:39 -0700145 private OutgoingCallProcessor mOutgoingCallProcessor;
146
147 // TODO(santoscordon): The repositories should be changed into singleton types.
148 private CallServiceRepository mCallServiceRepository;
149 private CallServiceSelectorRepository mSelectorRepository;
150
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700151 /** Caller information retrieved from the latest contact query. */
152 private CallerInfo mCallerInfo;
153
154 /** The latest token used with a contact info query. */
155 private int mQueryToken = 0;
156
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700157 /**
Sailesh Nepale59bb192014-04-01 18:33:59 -0700158 * Creates an empty call object.
Sailesh Nepal810735e2014-03-18 18:15:46 -0700159 *
160 * @param isIncoming True if this is an incoming call.
Santos Cordon493e8f22014-02-19 03:15:12 -0800161 */
Sailesh Nepal810735e2014-03-18 18:15:46 -0700162 Call(boolean isIncoming) {
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700163 this(null, null, isIncoming);
Santos Cordon493e8f22014-02-19 03:15:12 -0800164 }
165
166 /**
Ben Gilad0407fb22014-01-09 16:18:41 -0800167 * Persists the specified parameters and initializes the new instance.
168 *
169 * @param handle The handle to dial.
Yorke Lee33501632014-03-17 19:24:12 -0700170 * @param gatewayInfo Gateway information to use for the call.
Sailesh Nepal810735e2014-03-18 18:15:46 -0700171 * @param isIncoming True if this is an incoming call.
Ben Gilad0407fb22014-01-09 16:18:41 -0800172 */
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700173 Call(Uri handle, GatewayInfo gatewayInfo, boolean isIncoming) {
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800174 mState = CallState.NEW;
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700175 setHandle(handle);
Yorke Lee33501632014-03-17 19:24:12 -0700176 mGatewayInfo = gatewayInfo;
Sailesh Nepal810735e2014-03-18 18:15:46 -0700177 mIsIncoming = isIncoming;
Ben Gilad0407fb22014-01-09 16:18:41 -0800178 }
179
Santos Cordon766d04f2014-05-06 10:28:25 -0700180 void addListener(Listener listener) {
181 mListeners.add(listener);
182 }
183
184 void removeListener(Listener listener) {
185 mListeners.remove(listener);
186 }
187
Santos Cordon61d0f702014-02-19 02:52:23 -0800188 /** {@inheritDoc} */
189 @Override public String toString() {
Sailesh Nepal4538f012014-04-15 11:40:33 -0700190 String component = null;
191 if (mCallService != null && mCallService.getComponentName() != null) {
192 component = mCallService.getComponentName().flattenToShortString();
193 }
194 return String.format(Locale.US, "[%s, %s, %s]", mState, component, Log.piiHandle(mHandle));
Santos Cordon61d0f702014-02-19 02:52:23 -0800195 }
196
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800197 CallState getState() {
198 return mState;
199 }
200
201 /**
202 * Sets the call state. Although there exists the notion of appropriate state transitions
203 * (see {@link CallState}), in practice those expectations break down when cellular systems
204 * misbehave and they do this very often. The result is that we do not enforce state transitions
205 * and instead keep the code resilient to unexpected state changes.
206 */
Sailesh Nepal810735e2014-03-18 18:15:46 -0700207 void setState(CallState newState) {
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700208 Preconditions.checkState(newState != CallState.DISCONNECTED ||
209 mDisconnectCause != DisconnectCause.NOT_VALID);
Sailesh Nepal810735e2014-03-18 18:15:46 -0700210 if (mState != newState) {
211 Log.v(this, "setState %s -> %s", mState, newState);
212 mState = newState;
Sailesh Nepal810735e2014-03-18 18:15:46 -0700213 }
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800214 }
215
Sailesh Nepalce704b92014-03-17 18:31:43 -0700216 Uri getHandle() {
Ben Gilad0bf5b912014-01-28 17:55:57 -0800217 return mHandle;
218 }
219
Sailesh Nepalce704b92014-03-17 18:31:43 -0700220 void setHandle(Uri handle) {
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700221 if ((mHandle == null && handle != null) || (mHandle != null && !mHandle.equals(handle))) {
222 mHandle = handle;
223 mIsEmergencyCall = mHandle != null && PhoneNumberUtils.isLocalEmergencyNumber(
224 mHandle.getSchemeSpecificPart(), TelecommApp.getInstance());
225 startCallerInfoLookup();
226 }
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700227 }
228
Santos Cordon79ff2bc2014-03-27 15:31:27 -0700229 /**
230 * @param disconnectCause The reason for the disconnection, any of
231 * {@link android.telephony.DisconnectCause}.
232 * @param disconnectMessage Optional call-service-provided message about the disconnect.
233 */
234 void setDisconnectCause(int disconnectCause, String disconnectMessage) {
235 // TODO: Consider combining this method with a setDisconnected() method that is totally
236 // separate from setState.
237 mDisconnectCause = disconnectCause;
238 mDisconnectMessage = disconnectMessage;
239 }
240
241 int getDisconnectCause() {
242 return mDisconnectCause;
243 }
244
245 String getDisconnectMessage() {
246 return mDisconnectMessage;
247 }
248
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700249 boolean isEmergencyCall() {
250 return mIsEmergencyCall;
Santos Cordon61d0f702014-02-19 02:52:23 -0800251 }
252
Yorke Lee33501632014-03-17 19:24:12 -0700253 /**
254 * @return The original handle this call is associated with. In-call services should use this
255 * handle when indicating in their UI the handle that is being called.
256 */
257 public Uri getOriginalHandle() {
258 if (mGatewayInfo != null && !mGatewayInfo.isEmpty()) {
259 return mGatewayInfo.getOriginalHandle();
260 }
261 return getHandle();
262 }
263
264 GatewayInfo getGatewayInfo() {
265 return mGatewayInfo;
266 }
267
Sailesh Nepal810735e2014-03-18 18:15:46 -0700268 boolean isIncoming() {
269 return mIsIncoming;
270 }
271
Ben Gilad0407fb22014-01-09 16:18:41 -0800272 /**
273 * @return The "age" of this call object in milliseconds, which typically also represents the
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700274 * period since this call was added to the set pending outgoing calls, see
275 * mCreationTimeMillis.
Ben Gilad0407fb22014-01-09 16:18:41 -0800276 */
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700277 long getAgeMillis() {
278 return System.currentTimeMillis() - mCreationTimeMillis;
Ben Gilad0407fb22014-01-09 16:18:41 -0800279 }
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800280
Yorke Leef98fb572014-03-05 10:56:55 -0800281 /**
282 * @return The time when this call object was created and added to the set of pending outgoing
283 * calls.
284 */
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700285 long getCreationTimeMillis() {
286 return mCreationTimeMillis;
287 }
288
289 long getConnectTimeMillis() {
290 return mConnectTimeMillis;
291 }
292
293 void setConnectTimeMillis(long connectTimeMillis) {
294 mConnectTimeMillis = connectTimeMillis;
Yorke Leef98fb572014-03-05 10:56:55 -0800295 }
296
Santos Cordonc195e362014-02-11 17:05:31 -0800297 CallServiceWrapper getCallService() {
Santos Cordon681663d2014-01-30 04:32:15 -0800298 return mCallService;
299 }
300
Santos Cordonc195e362014-02-11 17:05:31 -0800301 void setCallService(CallServiceWrapper callService) {
Sailesh Nepal0e5410a2014-04-04 01:20:58 -0700302 setCallService(callService, null);
303 }
304
305 /**
306 * Changes the call service this call is associated with. If callToReplace is non-null then this
307 * call takes its place within the call service.
308 */
309 void setCallService(CallServiceWrapper callService, Call callToReplace) {
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800310 Preconditions.checkNotNull(callService);
311
Yorke Leeadee12d2014-03-13 12:08:30 -0700312 clearCallService();
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800313
314 callService.incrementAssociatedCallCount();
Santos Cordon681663d2014-01-30 04:32:15 -0800315 mCallService = callService;
Sailesh Nepal0e5410a2014-04-04 01:20:58 -0700316 if (callToReplace == null) {
317 mCallService.addCall(this);
318 } else {
319 mCallService.replaceCall(this, callToReplace);
320 }
Santos Cordon681663d2014-01-30 04:32:15 -0800321 }
322
323 /**
324 * Clears the associated call service.
325 */
326 void clearCallService() {
Yorke Leeadee12d2014-03-13 12:08:30 -0700327 if (mCallService != null) {
Santos Cordonc499c1c2014-04-14 17:13:14 -0700328 CallServiceWrapper callServiceTemp = mCallService;
Yorke Leeadee12d2014-03-13 12:08:30 -0700329 mCallService = null;
Santos Cordonc499c1c2014-04-14 17:13:14 -0700330 callServiceTemp.removeCall(this);
331
332 // Decrementing the count can cause the service to unbind, which itself can trigger the
333 // service-death code. Since the service death code tries to clean up any associated
334 // calls, we need to make sure to remove that information (e.g., removeCall()) before
335 // we decrement. Technically, invoking removeCall() prior to decrementing is all that is
336 // necessary, but cleaning up mCallService prior to triggering an unbind is good to do.
337 // If you change this, make sure to update {@link clearCallServiceSelector} as well.
338 decrementAssociatedCallCount(callServiceTemp);
Yorke Leeadee12d2014-03-13 12:08:30 -0700339 }
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800340 }
341
Sailesh Nepal84fa5f82014-04-02 11:01:11 -0700342 CallServiceSelectorWrapper getCallServiceSelector() {
343 return mCallServiceSelector;
344 }
345
Sailesh Nepal18386a82014-03-19 10:22:40 -0700346 void setCallServiceSelector(CallServiceSelectorWrapper selector) {
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800347 Preconditions.checkNotNull(selector);
Sailesh Nepale59bb192014-04-01 18:33:59 -0700348
349 clearCallServiceSelector();
350
Santos Cordonc499c1c2014-04-14 17:13:14 -0700351 selector.incrementAssociatedCallCount();
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800352 mCallServiceSelector = selector;
Sailesh Nepale59bb192014-04-01 18:33:59 -0700353 mCallServiceSelector.addCall(this);
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800354 }
355
356 void clearCallServiceSelector() {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700357 if (mCallServiceSelector != null) {
Santos Cordonc499c1c2014-04-14 17:13:14 -0700358 CallServiceSelectorWrapper selectorTemp = mCallServiceSelector;
Sailesh Nepale59bb192014-04-01 18:33:59 -0700359 mCallServiceSelector = null;
Santos Cordonc499c1c2014-04-14 17:13:14 -0700360 selectorTemp.removeCall(this);
361
362 // See comment on {@link #clearCallService}.
363 decrementAssociatedCallCount(selectorTemp);
Sailesh Nepale59bb192014-04-01 18:33:59 -0700364 }
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800365 }
366
367 /**
Santos Cordon766d04f2014-05-06 10:28:25 -0700368 * Starts the incoming call flow through the switchboard. When switchboard completes, it will
369 * invoke handle[Un]SuccessfulIncomingCall.
370 *
371 * @param descriptor The relevant call-service descriptor.
372 * @param extras The optional extras passed via
373 * {@link TelecommConstants#EXTRA_INCOMING_CALL_EXTRAS}.
374 */
375 void startIncoming(CallServiceDescriptor descriptor, Bundle extras) {
376 Switchboard.getInstance().retrieveIncomingCall(this, descriptor, extras);
377 }
378
379 void handleSuccessfulIncoming(CallInfo callInfo) {
380 Preconditions.checkState(callInfo.getState() == CallState.RINGING);
381 setHandle(callInfo.getHandle());
382
383 // TODO(santoscordon): Make this class (not CallsManager) responsible for changing the call
384 // state to RINGING.
385
386 // TODO(santoscordon): Replace this with state transitions related to "connecting".
387 for (Listener l : mListeners) {
388 l.onSuccessfulIncomingCall(this, callInfo);
389 }
390 }
391
392 void handleFailedIncoming() {
393 clearCallService();
394
395 // TODO: Needs more specific disconnect error for this case.
396 setDisconnectCause(DisconnectCause.ERROR_UNSPECIFIED, null);
397 setState(CallState.DISCONNECTED);
398
399 // TODO(santoscordon): Replace this with state transitions related to "connecting".
400 for (Listener l : mListeners) {
401 l.onFailedIncomingCall(this);
402 }
403 }
404
405 /**
Santos Cordon682fe6b2014-05-20 08:56:39 -0700406 * Starts the outgoing call sequence. Upon completion, there should exist an active connection
407 * through a call service (or the call will have failed).
Santos Cordon766d04f2014-05-06 10:28:25 -0700408 */
409 void startOutgoing() {
Santos Cordon682fe6b2014-05-20 08:56:39 -0700410 Preconditions.checkState(mOutgoingCallProcessor == null);
411
412 mOutgoingCallProcessor = new OutgoingCallProcessor(
413 this,
414 Switchboard.getInstance().getCallServiceRepository(),
415 Switchboard.getInstance().getSelectorRepository(),
416 new AsyncResultCallback<Boolean>() {
417 @Override
418 public void onResult(Boolean wasCallPlaced) {
419 if (wasCallPlaced) {
420 handleSuccessfulOutgoing();
421 } else {
422 handleFailedOutgoing(mOutgoingCallProcessor.isAborted());
423 }
424 mOutgoingCallProcessor = null;
425 }
426 });
427 mOutgoingCallProcessor.process();
Santos Cordon766d04f2014-05-06 10:28:25 -0700428 }
429
430 void handleSuccessfulOutgoing() {
431 // TODO(santoscordon): Replace this with state transitions related to "connecting".
432 for (Listener l : mListeners) {
433 l.onSuccessfulOutgoingCall(this);
434 }
435 }
436
437 void handleFailedOutgoing(boolean isAborted) {
438 // TODO(santoscordon): Replace this with state transitions related to "connecting".
439 for (Listener l : mListeners) {
440 l.onFailedOutgoingCall(this, isAborted);
441 }
Santos Cordon682fe6b2014-05-20 08:56:39 -0700442
443 clearCallService();
444 clearCallServiceSelector();
Santos Cordon766d04f2014-05-06 10:28:25 -0700445 }
446
447 /**
Ben Gilad61925612014-03-11 19:06:36 -0700448 * Adds the specified call service to the list of incompatible services. The set is used when
449 * attempting to switch a phone call between call services such that incompatible services can
450 * be avoided.
451 *
452 * @param callService The incompatible call service.
453 */
454 void addIncompatibleCallService(CallServiceWrapper callService) {
455 if (mIncompatibleCallServices == null) {
456 mIncompatibleCallServices = Sets.newHashSet();
457 }
458 mIncompatibleCallServices.add(callService);
459 }
460
461 /**
462 * Checks whether or not the specified callService was identified as incompatible in the
463 * context of this call.
464 *
465 * @param callService The call service to evaluate.
466 * @return True upon incompatible call services and false otherwise.
467 */
468 boolean isIncompatibleCallService(CallServiceWrapper callService) {
469 return mIncompatibleCallServices != null &&
470 mIncompatibleCallServices.contains(callService);
471 }
472
473 /**
Ihab Awad74549ec2014-03-10 15:33:25 -0700474 * Plays the specified DTMF tone.
475 */
476 void playDtmfTone(char digit) {
477 if (mCallService == null) {
478 Log.w(this, "playDtmfTone() request on a call without a call service.");
479 } else {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700480 Log.i(this, "Send playDtmfTone to call service for call %s", this);
481 mCallService.playDtmfTone(this, digit);
Ihab Awad74549ec2014-03-10 15:33:25 -0700482 }
483 }
484
485 /**
486 * Stops playing any currently playing DTMF tone.
487 */
488 void stopDtmfTone() {
489 if (mCallService == null) {
490 Log.w(this, "stopDtmfTone() request on a call without a call service.");
491 } else {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700492 Log.i(this, "Send stopDtmfTone to call service for call %s", this);
493 mCallService.stopDtmfTone(this);
Ihab Awad74549ec2014-03-10 15:33:25 -0700494 }
495 }
496
497 /**
Santos Cordon049b7b62014-01-30 05:34:26 -0800498 * Attempts to disconnect the call through the call service.
499 */
500 void disconnect() {
Santos Cordon766d04f2014-05-06 10:28:25 -0700501 if (mState == CallState.NEW) {
Santos Cordon682fe6b2014-05-20 08:56:39 -0700502 Log.v(this, "Aborting call %s", this);
503 abort();
Santos Cordon74d420b2014-05-07 14:38:47 -0700504 } else if (mState != CallState.ABORTED && mState != CallState.DISCONNECTED) {
Santos Cordon766d04f2014-05-06 10:28:25 -0700505 Preconditions.checkNotNull(mCallService);
506
Sailesh Nepale59bb192014-04-01 18:33:59 -0700507 Log.i(this, "Send disconnect to call service for call: %s", this);
Santos Cordonc195e362014-02-11 17:05:31 -0800508 // The call isn't officially disconnected until the call service confirms that the call
509 // was actually disconnected. Only then is the association between call and call service
510 // severed, see {@link CallsManager#markCallAsDisconnected}.
Sailesh Nepale59bb192014-04-01 18:33:59 -0700511 mCallService.disconnect(this);
Santos Cordon049b7b62014-01-30 05:34:26 -0800512 }
513 }
514
Santos Cordon682fe6b2014-05-20 08:56:39 -0700515 void abort() {
516 if (mOutgoingCallProcessor != null) {
517 mOutgoingCallProcessor.abort();
518 }
519 }
520
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800521 /**
Santos Cordon61d0f702014-02-19 02:52:23 -0800522 * Answers the call if it is ringing.
523 */
524 void answer() {
525 Preconditions.checkNotNull(mCallService);
526
527 // Check to verify that the call is still in the ringing state. A call can change states
528 // between the time the user hits 'answer' and Telecomm receives the command.
529 if (isRinging("answer")) {
530 // At this point, we are asking the call service to answer but we don't assume that
531 // it will work. Instead, we wait until confirmation from the call service that the
532 // call is in a non-RINGING state before changing the UI. See
533 // {@link CallServiceAdapter#setActive} and other set* methods.
Sailesh Nepale59bb192014-04-01 18:33:59 -0700534 mCallService.answer(this);
Santos Cordon61d0f702014-02-19 02:52:23 -0800535 }
536 }
537
538 /**
539 * Rejects the call if it is ringing.
540 */
541 void reject() {
542 Preconditions.checkNotNull(mCallService);
543
544 // Check to verify that the call is still in the ringing state. A call can change states
545 // between the time the user hits 'reject' and Telecomm receives the command.
546 if (isRinging("reject")) {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700547 mCallService.reject(this);
Santos Cordon61d0f702014-02-19 02:52:23 -0800548 }
549 }
550
551 /**
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700552 * Puts the call on hold if it is currently active.
553 */
554 void hold() {
555 Preconditions.checkNotNull(mCallService);
556
557 if (mState == CallState.ACTIVE) {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700558 mCallService.hold(this);
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700559 }
560 }
561
562 /**
563 * Releases the call from hold if it is currently active.
564 */
565 void unhold() {
566 Preconditions.checkNotNull(mCallService);
567
568 if (mState == CallState.ON_HOLD) {
Sailesh Nepale59bb192014-04-01 18:33:59 -0700569 mCallService.unhold(this);
Yorke Leecdf3ebd2014-03-12 18:31:41 -0700570 }
571 }
572
573 /**
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800574 * @return An object containing read-only information about this call.
575 */
Sailesh Nepale59bb192014-04-01 18:33:59 -0700576 CallInfo toCallInfo(String callId) {
Sailesh Nepal84fa5f82014-04-02 11:01:11 -0700577 CallServiceDescriptor descriptor = null;
578 if (mCallService != null) {
579 descriptor = mCallService.getDescriptor();
580 } else if (mOriginalCall != null && mOriginalCall.mCallService != null) {
581 descriptor = mOriginalCall.mCallService.getDescriptor();
582 }
583 return new CallInfo(callId, mState, mHandle, mGatewayInfo, mExtras, descriptor);
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800584 }
585
Sailesh Nepal6aca10a2014-03-24 16:11:02 -0700586 /** Checks if this is a live call or not. */
587 boolean isAlive() {
588 switch (mState) {
589 case NEW:
590 case RINGING:
591 case DISCONNECTED:
592 case ABORTED:
593 return false;
594 default:
595 return true;
596 }
597 }
598
Santos Cordon40f78c22014-04-07 02:11:42 -0700599 boolean isActive() {
600 switch (mState) {
601 case ACTIVE:
602 case POST_DIAL:
603 case POST_DIAL_WAIT:
604 return true;
605 default:
606 return false;
607 }
608 }
609
Sailesh Nepal84fa5f82014-04-02 11:01:11 -0700610 Bundle getExtras() {
611 return mExtras;
612 }
613
614 void setExtras(Bundle extras) {
615 mExtras = extras;
616 }
617
618 Uri getHandoffHandle() {
619 return mHandoffHandle;
620 }
621
622 void setHandoffHandle(Uri handoffHandle) {
623 mHandoffHandle = handoffHandle;
624 }
625
626 Call getOriginalCall() {
627 return mOriginalCall;
628 }
629
630 void setOriginalCall(Call originalCall) {
631 mOriginalCall = originalCall;
632 }
633
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700634 CallServiceDescriptor getHandoffCallServiceDescriptor() {
635 return mHandoffCallServiceDescriptor;
636 }
637
638 void setHandoffCallServiceDescriptor(CallServiceDescriptor descriptor) {
639 mHandoffCallServiceDescriptor = descriptor;
640 }
641
Santos Cordon0b03b4b2014-01-29 18:01:59 -0800642 /**
Santos Cordon61d0f702014-02-19 02:52:23 -0800643 * @return True if the call is ringing, else logs the action name.
644 */
645 private boolean isRinging(String actionName) {
646 if (mState == CallState.RINGING) {
647 return true;
648 }
649
Sailesh Nepalf1c191d2014-03-07 18:17:39 -0800650 Log.i(this, "Request to %s a non-ringing call %s", actionName, this);
Santos Cordon61d0f702014-02-19 02:52:23 -0800651 return false;
652 }
653
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800654 @SuppressWarnings("rawtypes")
655 private void decrementAssociatedCallCount(ServiceBinder binder) {
656 if (binder != null) {
657 binder.decrementAssociatedCallCount();
658 }
659 }
Santos Cordonfd71f4a2014-05-28 13:59:49 -0700660
661 /**
662 * Looks up contact information based on the current handle.
663 */
664 private void startCallerInfoLookup() {
665 String number = mHandle == null ? null : mHandle.getSchemeSpecificPart();
666
667 mQueryToken++; // Updated so that previous queries can no longer set the information.
668 mCallerInfo = null;
669 if (!TextUtils.isEmpty(number)) {
670 CallerInfoAsyncQuery.startQuery(
671 mQueryToken,
672 TelecommApp.getInstance(),
673 number,
674 sCallerInfoQueryListener,
675 this);
676 }
677 }
678
679 /**
680 * Saved the specified caller info if the specified token matches that of the last query
681 * that was made.
682 *
683 * @param callerInfo The new caller information to set.
684 * @param token The token used with this query.
685 */
686 private void setCallerInfo(CallerInfo callerInfo, int token) {
687 if (mQueryToken == token) {
688 mCallerInfo = callerInfo;
689 }
690 }
Ben Gilad9f2bed32013-12-12 17:43:26 -0800691}