blob: 760e5e814fe7783db5ee788b96bc83311c8e672f [file] [log] [blame]
Wink Savilleef36ef62014-06-11 08:39:38 -07001/*
2 * Copyright (c) 2013 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
17package com.android.ims;
18
Uma Maheswari Ramalingamca45f582014-05-19 12:32:20 -070019import com.android.internal.R;
20
Tyler Gunn1c467602014-11-04 14:51:52 -080021import java.util.ArrayList;
Wink Savilleef36ef62014-06-11 08:39:38 -070022import java.util.Iterator;
Tyler Gunn1c467602014-11-04 14:51:52 -080023import java.util.List;
Wink Savilleef36ef62014-06-11 08:39:38 -070024import java.util.Map.Entry;
25import java.util.Set;
26
27import android.content.Context;
Tyler Gunn59656142014-10-28 13:52:11 -070028import android.net.Uri;
Wink Savilleef36ef62014-06-11 08:39:38 -070029import android.os.Bundle;
30import android.os.Message;
Hall Liubd3f3772017-03-22 15:38:23 -070031import android.os.Parcel;
Tyler Gunn59656142014-10-28 13:52:11 -070032import android.telecom.ConferenceParticipant;
Rekha Kumar5aec2e92015-03-24 11:00:34 -070033import android.telecom.Connection;
Shunta Sakaifb688ca2016-12-06 20:32:07 +090034import android.telephony.Rlog;
Uma Maheswari Ramalingam95be7c62015-05-06 23:08:17 -070035import java.util.Objects;
Jack Yub42ff552016-06-22 13:20:52 -070036import java.util.concurrent.atomic.AtomicInteger;
Tyler Gunn1ac24852016-06-22 10:04:23 -070037
38import android.telephony.ServiceState;
Brad Ebingered690772018-01-23 13:41:32 -080039import android.telephony.ims.ImsCallProfile;
40import android.telephony.ims.ImsConferenceState;
41import android.telephony.ims.ImsReasonInfo;
42import android.telephony.ims.ImsStreamMediaProfile;
43import android.telephony.ims.ImsSuppServiceNotification;
Tyler Gunn9bd5ca52014-12-02 09:21:01 -080044import android.util.Log;
Wink Savilleef36ef62014-06-11 08:39:38 -070045
Wink Savilleef36ef62014-06-11 08:39:38 -070046import com.android.ims.internal.ICall;
Brad Ebingered690772018-01-23 13:41:32 -080047import android.telephony.ims.ImsCallSession;
Wink Savilleef36ef62014-06-11 08:39:38 -070048import com.android.ims.internal.ImsStreamMediaSession;
Tyler Gunn938116f2014-10-27 09:15:23 -070049import com.android.internal.annotations.VisibleForTesting;
Wink Savilleef36ef62014-06-11 08:39:38 -070050
51/**
52 * Handles an IMS voice / video call over LTE. You can instantiate this class with
53 * {@link ImsManager}.
54 *
55 * @hide
56 */
57public class ImsCall implements ICall {
Wink Savilleef36ef62014-06-11 08:39:38 -070058 // Mode of USSD message
59 public static final int USSD_MODE_NOTIFY = 0;
60 public static final int USSD_MODE_REQUEST = 1;
61
62 private static final String TAG = "ImsCall";
Anthony Lee68048512015-03-18 15:04:18 -070063
64 // This flag is meant to be used as a debugging tool to quickly see all logs
65 // regardless of the actual log level set on this component.
Tyler Gunn6cb99be2014-12-02 14:16:30 -080066 private static final boolean FORCE_DEBUG = false; /* STOPSHIP if true */
Anthony Lee68048512015-03-18 15:04:18 -070067
68 // We will log messages guarded by these flags at the info level. If logging is required
69 // to occur at (and only at) a particular log level, please use the logd, logv and loge
70 // functions as those will not be affected by the value of FORCE_DEBUG at all.
71 // Otherwise, anything guarded by these flags will be logged at the info level since that
72 // level allows those statements ot be logged by default which supports the workflow of
73 // setting FORCE_DEBUG and knowing these logs will show up regardless of the actual log
74 // level of this component.
75 private static final boolean DBG = FORCE_DEBUG || Log.isLoggable(TAG, Log.DEBUG);
76 private static final boolean VDBG = FORCE_DEBUG || Log.isLoggable(TAG, Log.VERBOSE);
77 // This is a special flag that is used only to highlight specific log around bringing
78 // up and tearing down conference calls. At times, these errors are transient and hard to
79 // reproduce so we need to capture this information the first time.
80 // TODO: Set this flag to FORCE_DEBUG once the new conference call logic gets more mileage
81 // across different IMS implementations.
82 private static final boolean CONF_DBG = true;
Wink Savilleef36ef62014-06-11 08:39:38 -070083
Uma Maheswari Ramalingama1ed7b02015-05-20 14:26:42 -070084 private List<ConferenceParticipant> mConferenceParticipants;
Wink Savilleef36ef62014-06-11 08:39:38 -070085 /**
86 * Listener for events relating to an IMS call, such as when a call is being
Anthony Leec479f662015-02-11 17:04:35 -080087 * received ("on ringing") or a call is outgoing ("on calling").
Wink Savilleef36ef62014-06-11 08:39:38 -070088 * <p>Many of these events are also received by {@link ImsCallSession.Listener}.</p>
89 */
90 public static class Listener {
91 /**
92 * Called when a request is sent out to initiate a new call
93 * and 1xx response is received from the network.
94 * The default implementation calls {@link #onCallStateChanged}.
95 *
96 * @param call the call object that carries out the IMS call
97 */
98 public void onCallProgressing(ImsCall call) {
99 onCallStateChanged(call);
100 }
101
102 /**
103 * Called when the call is established.
104 * The default implementation calls {@link #onCallStateChanged}.
105 *
106 * @param call the call object that carries out the IMS call
107 */
108 public void onCallStarted(ImsCall call) {
109 onCallStateChanged(call);
110 }
111
112 /**
113 * Called when the call setup is failed.
114 * The default implementation calls {@link #onCallError}.
115 *
116 * @param call the call object that carries out the IMS call
117 * @param reasonInfo detailed reason of the call setup failure
118 */
119 public void onCallStartFailed(ImsCall call, ImsReasonInfo reasonInfo) {
120 onCallError(call, reasonInfo);
121 }
122
123 /**
124 * Called when the call is terminated.
125 * The default implementation calls {@link #onCallStateChanged}.
126 *
127 * @param call the call object that carries out the IMS call
128 * @param reasonInfo detailed reason of the call termination
129 */
130 public void onCallTerminated(ImsCall call, ImsReasonInfo reasonInfo) {
131 // Store the call termination reason
132
133 onCallStateChanged(call);
134 }
135
136 /**
137 * Called when the call is in hold.
138 * The default implementation calls {@link #onCallStateChanged}.
139 *
140 * @param call the call object that carries out the IMS call
141 */
142 public void onCallHeld(ImsCall call) {
143 onCallStateChanged(call);
144 }
145
146 /**
147 * Called when the call hold is failed.
148 * The default implementation calls {@link #onCallError}.
149 *
150 * @param call the call object that carries out the IMS call
151 * @param reasonInfo detailed reason of the call hold failure
152 */
153 public void onCallHoldFailed(ImsCall call, ImsReasonInfo reasonInfo) {
154 onCallError(call, reasonInfo);
155 }
156
157 /**
158 * Called when the call hold is received from the remote user.
159 * The default implementation calls {@link #onCallStateChanged}.
160 *
161 * @param call the call object that carries out the IMS call
162 */
163 public void onCallHoldReceived(ImsCall call) {
164 onCallStateChanged(call);
165 }
166
167 /**
168 * Called when the call is in call.
169 * The default implementation calls {@link #onCallStateChanged}.
170 *
171 * @param call the call object that carries out the IMS call
172 */
173 public void onCallResumed(ImsCall call) {
174 onCallStateChanged(call);
175 }
176
177 /**
178 * Called when the call resume is failed.
179 * The default implementation calls {@link #onCallError}.
180 *
181 * @param call the call object that carries out the IMS call
182 * @param reasonInfo detailed reason of the call resume failure
183 */
184 public void onCallResumeFailed(ImsCall call, ImsReasonInfo reasonInfo) {
185 onCallError(call, reasonInfo);
186 }
187
188 /**
189 * Called when the call resume is received from the remote user.
190 * The default implementation calls {@link #onCallStateChanged}.
191 *
192 * @param call the call object that carries out the IMS call
193 */
194 public void onCallResumeReceived(ImsCall call) {
195 onCallStateChanged(call);
196 }
197
198 /**
199 * Called when the call is in call.
200 * The default implementation calls {@link #onCallStateChanged}.
201 *
Uma Maheswari Ramalingam95be7c62015-05-06 23:08:17 -0700202 * @param call the call object that carries out the active IMS call
203 * @param peerCall the call object that carries out the held IMS call
Tyler Gunn047d8102015-01-30 15:21:11 -0800204 * @param swapCalls {@code true} if the foreground and background calls should be swapped
205 * now that the merge has completed.
Wink Savilleef36ef62014-06-11 08:39:38 -0700206 */
Uma Maheswari Ramalingam95be7c62015-05-06 23:08:17 -0700207 public void onCallMerged(ImsCall call, ImsCall peerCall, boolean swapCalls) {
Anthony Lee71382692014-10-30 10:50:10 -0700208 onCallStateChanged(call);
Wink Savilleef36ef62014-06-11 08:39:38 -0700209 }
210
211 /**
212 * Called when the call merge is failed.
213 * The default implementation calls {@link #onCallError}.
214 *
215 * @param call the call object that carries out the IMS call
216 * @param reasonInfo detailed reason of the call merge failure
217 */
218 public void onCallMergeFailed(ImsCall call, ImsReasonInfo reasonInfo) {
219 onCallError(call, reasonInfo);
220 }
221
222 /**
223 * Called when the call is updated (except for hold/unhold).
224 * The default implementation calls {@link #onCallStateChanged}.
225 *
226 * @param call the call object that carries out the IMS call
227 */
228 public void onCallUpdated(ImsCall call) {
229 onCallStateChanged(call);
230 }
231
232 /**
233 * Called when the call update is failed.
234 * The default implementation calls {@link #onCallError}.
235 *
236 * @param call the call object that carries out the IMS call
237 * @param reasonInfo detailed reason of the call update failure
238 */
239 public void onCallUpdateFailed(ImsCall call, ImsReasonInfo reasonInfo) {
240 onCallError(call, reasonInfo);
241 }
242
243 /**
244 * Called when the call update is received from the remote user.
245 *
246 * @param call the call object that carries out the IMS call
247 */
248 public void onCallUpdateReceived(ImsCall call) {
249 // no-op
250 }
251
252 /**
253 * Called when the call is extended to the conference call.
254 * The default implementation calls {@link #onCallStateChanged}.
255 *
256 * @param call the call object that carries out the IMS call
257 * @param newCall the call object that is extended to the conference from the active call
258 */
259 public void onCallConferenceExtended(ImsCall call, ImsCall newCall) {
Anthony Lee71382692014-10-30 10:50:10 -0700260 onCallStateChanged(call);
Wink Savilleef36ef62014-06-11 08:39:38 -0700261 }
262
263 /**
264 * Called when the conference extension is failed.
265 * The default implementation calls {@link #onCallError}.
266 *
267 * @param call the call object that carries out the IMS call
268 * @param reasonInfo detailed reason of the conference extension failure
269 */
270 public void onCallConferenceExtendFailed(ImsCall call,
271 ImsReasonInfo reasonInfo) {
272 onCallError(call, reasonInfo);
273 }
274
275 /**
276 * Called when the conference extension is received from the remote user.
277 *
278 * @param call the call object that carries out the IMS call
279 * @param newCall the call object that is extended to the conference from the active call
280 */
281 public void onCallConferenceExtendReceived(ImsCall call, ImsCall newCall) {
Anthony Lee71382692014-10-30 10:50:10 -0700282 onCallStateChanged(call);
Wink Savilleef36ef62014-06-11 08:39:38 -0700283 }
284
285 /**
286 * Called when the invitation request of the participants is delivered to
287 * the conference server.
288 *
289 * @param call the call object that carries out the IMS call
290 */
291 public void onCallInviteParticipantsRequestDelivered(ImsCall call) {
292 // no-op
293 }
294
295 /**
296 * Called when the invitation request of the participants is failed.
297 *
298 * @param call the call object that carries out the IMS call
299 * @param reasonInfo detailed reason of the conference invitation failure
300 */
301 public void onCallInviteParticipantsRequestFailed(ImsCall call,
302 ImsReasonInfo reasonInfo) {
303 // no-op
304 }
305
306 /**
307 * Called when the removal request of the participants is delivered to
308 * the conference server.
309 *
310 * @param call the call object that carries out the IMS call
311 */
312 public void onCallRemoveParticipantsRequestDelivered(ImsCall call) {
313 // no-op
314 }
315
316 /**
317 * Called when the removal request of the participants is failed.
318 *
319 * @param call the call object that carries out the IMS call
320 * @param reasonInfo detailed reason of the conference removal failure
321 */
322 public void onCallRemoveParticipantsRequestFailed(ImsCall call,
323 ImsReasonInfo reasonInfo) {
324 // no-op
325 }
326
327 /**
328 * Called when the conference state is updated.
329 *
330 * @param call the call object that carries out the IMS call
331 * @param state state of the participant who is participated in the conference call
332 */
333 public void onCallConferenceStateUpdated(ImsCall call, ImsConferenceState state) {
334 // no-op
335 }
336
337 /**
Tyler Gunn1c467602014-11-04 14:51:52 -0800338 * Called when the state of IMS conference participant(s) has changed.
Tyler Gunn59656142014-10-28 13:52:11 -0700339 *
340 * @param call the call object that carries out the IMS call.
Tyler Gunn1c467602014-11-04 14:51:52 -0800341 * @param participants the participant(s) and their new state information.
Tyler Gunn59656142014-10-28 13:52:11 -0700342 */
Tyler Gunn1c467602014-11-04 14:51:52 -0800343 public void onConferenceParticipantsStateChanged(ImsCall call,
344 List<ConferenceParticipant> participants) {
Tyler Gunn59656142014-10-28 13:52:11 -0700345 // no-op
346 }
347
348 /**
Wink Savilleef36ef62014-06-11 08:39:38 -0700349 * Called when the USSD message is received from the network.
350 *
351 * @param mode mode of the USSD message (REQUEST / NOTIFY)
352 * @param ussdMessage USSD message
353 */
354 public void onCallUssdMessageReceived(ImsCall call,
355 int mode, String ussdMessage) {
356 // no-op
357 }
358
359 /**
360 * Called when an error occurs. The default implementation is no op.
361 * overridden. The default implementation is no op. Error events are
362 * not re-directed to this callback and are handled in {@link #onCallError}.
363 *
364 * @param call the call object that carries out the IMS call
365 * @param reasonInfo detailed reason of this error
366 * @see ImsReasonInfo
367 */
368 public void onCallError(ImsCall call, ImsReasonInfo reasonInfo) {
369 // no-op
370 }
371
372 /**
373 * Called when an event occurs and the corresponding callback is not
374 * overridden. The default implementation is no op. Error events are
375 * not re-directed to this callback and are handled in {@link #onCallError}.
376 *
377 * @param call the call object that carries out the IMS call
378 */
379 public void onCallStateChanged(ImsCall call) {
380 // no-op
381 }
382
383 /**
Wink Savilleef36ef62014-06-11 08:39:38 -0700384 * Called when the call moves the hold state to the conversation state.
385 * For example, when merging the active & hold call, the state of all the hold call
386 * will be changed from hold state to conversation state.
387 * This callback method can be invoked even though the application does not trigger
388 * any operations.
389 *
390 * @param call the call object that carries out the IMS call
391 * @param state the detailed state of call state changes;
392 * Refer to CALL_STATE_* in {@link ImsCall}
393 */
394 public void onCallStateChanged(ImsCall call, int state) {
395 // no-op
396 }
Pavel Zhamaitsiaka6cae362014-12-10 17:31:33 -0800397
398 /**
Shriram Ganeshd3adfad2015-05-31 10:06:15 -0700399 * Called when the call supp service is received
400 * The default implementation calls {@link #onCallStateChanged}.
401 *
402 * @param call the call object that carries out the IMS call
403 */
404 public void onCallSuppServiceReceived(ImsCall call,
405 ImsSuppServiceNotification suppServiceInfo) {
406 }
407
408 /**
Pavel Zhamaitsiaka6cae362014-12-10 17:31:33 -0800409 * Called when TTY mode of remote party changed
410 *
411 * @param call the call object that carries out the IMS call
412 * @param mode TTY mode of remote party
413 */
414 public void onCallSessionTtyModeReceived(ImsCall call, int mode) {
415 // no-op
416 }
Rekha Kumar14631742015-02-04 10:47:00 -0800417
418 /**
419 * Called when handover occurs from one access technology to another.
420 *
Anthony Lee68048512015-03-18 15:04:18 -0700421 * @param imsCall ImsCall object
Rekha Kumar14631742015-02-04 10:47:00 -0800422 * @param srcAccessTech original access technology
423 * @param targetAccessTech new access technology
424 * @param reasonInfo
425 */
426 public void onCallHandover(ImsCall imsCall, int srcAccessTech, int targetAccessTech,
427 ImsReasonInfo reasonInfo) {
428 }
429
430 /**
Hall Liubd3f3772017-03-22 15:38:23 -0700431 * Called when the remote party issues an RTT modify request
432 *
433 * @param imsCall ImsCall object
434 */
435 public void onRttModifyRequestReceived(ImsCall imsCall) {
436 }
437
438 /**
439 * Called when the remote party responds to a locally-issued RTT request.
440 *
441 * @param imsCall ImsCall object
442 * @param status The status of the request. See
443 * {@link Connection.RttModifyStatus} for possible values.
444 */
445 public void onRttModifyResponseReceived(ImsCall imsCall, int status) {
446 }
447
448 /**
449 * Called when the remote party has sent some characters via RTT
450 *
451 * @param imsCall ImsCall object
452 * @param message A string containing the transmitted characters.
453 */
454 public void onRttMessageReceived(ImsCall imsCall, String message) {
455 }
456
457 /**
Rekha Kumar14631742015-02-04 10:47:00 -0800458 * Called when handover from one access technology to another fails.
459 *
Anthony Lee68048512015-03-18 15:04:18 -0700460 * @param imsCall call that failed the handover.
Rekha Kumar14631742015-02-04 10:47:00 -0800461 * @param srcAccessTech original access technology
462 * @param targetAccessTech new access technology
463 * @param reasonInfo
464 */
465 public void onCallHandoverFailed(ImsCall imsCall, int srcAccessTech, int targetAccessTech,
466 ImsReasonInfo reasonInfo) {
467 }
Tyler Gunn25394092015-04-01 09:40:02 -0700468
469 /**
470 * Notifies of a change to the multiparty state for this {@code ImsCall}.
471 *
472 * @param imsCall The IMS call.
473 * @param isMultiParty {@code true} if the call became multiparty, {@code false}
474 * otherwise.
475 */
476 public void onMultipartyStateChanged(ImsCall imsCall, boolean isMultiParty) {
477 }
Wink Savilleef36ef62014-06-11 08:39:38 -0700478 }
479
Wink Savilleef36ef62014-06-11 08:39:38 -0700480 // List of update operation for IMS call control
481 private static final int UPDATE_NONE = 0;
482 private static final int UPDATE_HOLD = 1;
483 private static final int UPDATE_HOLD_MERGE = 2;
484 private static final int UPDATE_RESUME = 3;
485 private static final int UPDATE_MERGE = 4;
486 private static final int UPDATE_EXTEND_TO_CONFERENCE = 5;
487 private static final int UPDATE_UNSPECIFIED = 6;
488
489 // For synchronization of private variables
490 private Object mLockObj = new Object();
491 private Context mContext;
492
493 // true if the call is established & in the conversation state
494 private boolean mInCall = false;
495 // true if the call is on hold
496 // If it is triggered by the local, mute the call. Otherwise, play local hold tone
497 // or network generated media.
498 private boolean mHold = false;
499 // true if the call is on mute
500 private boolean mMute = false;
501 // It contains the exclusive call update request. Refer to UPDATE_*.
502 private int mUpdateRequest = UPDATE_NONE;
503
504 private ImsCall.Listener mListener = null;
Tyler Gunn9bd5ca52014-12-02 09:21:01 -0800505
506 // When merging two calls together, the "peer" call that will merge into this call.
507 private ImsCall mMergePeer = null;
508 // When merging two calls together, the "host" call we are merging into.
509 private ImsCall mMergeHost = null;
Wink Savilleef36ef62014-06-11 08:39:38 -0700510
Uma Maheswari Ramalingam95be7c62015-05-06 23:08:17 -0700511 // True if Conference request was initiated by
512 // Foreground Conference call else it will be false
513 private boolean mMergeRequestedByConference = false;
Wink Savilleef36ef62014-06-11 08:39:38 -0700514 // Wrapper call session to interworking the IMS service (server).
515 private ImsCallSession mSession = null;
516 // Call profile of the current session.
517 // It can be changed at anytime when the call is updated.
518 private ImsCallProfile mCallProfile = null;
519 // Call profile to be updated after the application's action (accept/reject)
520 // to the call update. After the application's action (accept/reject) is done,
521 // it will be set to null.
522 private ImsCallProfile mProposedCallProfile = null;
523 private ImsReasonInfo mLastReasonInfo = null;
524
525 // Media session to control media (audio/video) operations for an IMS call
526 private ImsStreamMediaSession mMediaSession = null;
527
Anthony Leeb8799fe2014-11-03 15:13:47 -0800528 // The temporary ImsCallSession that could represent the merged call once
529 // we receive notification that the merge was successful.
Anthony Lee71382692014-10-30 10:50:10 -0700530 private ImsCallSession mTransientConferenceSession = null;
Anthony Leeb8799fe2014-11-03 15:13:47 -0800531 // While a merge is progressing, we bury any session termination requests
532 // made on the original ImsCallSession until we have closure on the merge request
533 // If the request ultimately fails, we need to act on the termination request
534 // that we buried temporarily. We do this because we feel that timing issues could
535 // cause the termination request to occur just because the merge is succeeding.
536 private boolean mSessionEndDuringMerge = false;
537 // Just like mSessionEndDuringMerge, we need to keep track of the reason why the
538 // termination request was made on the original session in case we need to act
539 // on it in the case of a merge failure.
540 private ImsReasonInfo mSessionEndDuringMergeReasonInfo = null;
Anthony Leec479f662015-02-11 17:04:35 -0800541 // This flag is used to indicate if this ImsCall was merged into a conference
542 // or not. It is used primarily to determine if a disconnect sound should
543 // be heard when the call is terminated.
Andrew Lee8ae59492014-11-17 17:03:02 -0800544 private boolean mIsMerged = false;
Anthony Leec479f662015-02-11 17:04:35 -0800545 // If true, this flag means that this ImsCall is in the process of merging
546 // into a conference but it does not yet have closure on if it was
547 // actually added to the conference or not. false implies that it either
548 // is not part of a merging conference or already knows if it was
549 // successfully added.
Tyler Gunn047d8102015-01-30 15:21:11 -0800550 private boolean mCallSessionMergePending = false;
551
Wink Savilleef36ef62014-06-11 08:39:38 -0700552 /**
Tyler Gunn95d563e2015-07-23 09:28:58 -0700553 * If {@code true}, this flag indicates that a request to terminate the call was made by
554 * Telephony (could be from the user or some internal telephony logic)
555 * and that when we receive a {@link #processCallTerminated(ImsReasonInfo)} callback from the
556 * radio indicating that the call was terminated, we should override any burying of the
557 * termination due to an ongoing conference merge.
558 */
559 private boolean mTerminationRequestPending = false;
560
561 /**
Tyler Gunn25394092015-04-01 09:40:02 -0700562 * For multi-party IMS calls (e.g. conferences), determines if this {@link ImsCall} is the one
563 * hosting the call. This is used to distinguish between a situation where an {@link ImsCall}
564 * is {@link #isMultiparty()} because calls were merged on the device, and a situation where
565 * an {@link ImsCall} is {@link #isMultiparty()} because it is a member of a conference started
566 * on another device.
567 * <p>
568 * When {@code true}, this {@link ImsCall} is is the origin of the conference call.
569 * When {@code false}, this {@link ImsCall} is a member of a conference started on another
570 * device.
571 */
572 private boolean mIsConferenceHost = false;
573
574 /**
Tyler Gunn1ac24852016-06-22 10:04:23 -0700575 * Tracks whether this {@link ImsCall} has been a video call at any point in its lifetime.
576 * Some examples of calls which are/were video calls:
577 * 1. A call which has been a video call for its duration.
578 * 2. An audio call upgraded to video (and potentially downgraded to audio later).
579 * 3. A call answered as video which was downgraded to audio.
580 */
581 private boolean mWasVideoCall = false;
582
583 /**
Jack Yub42ff552016-06-22 13:20:52 -0700584 * Unique id generator used to generate call id.
585 */
586 private static final AtomicInteger sUniqueIdGenerator = new AtomicInteger();
587
588 /**
589 * Unique identifier.
590 */
591 public final int uniqueId;
592
593 /**
Tyler Gunnafb53c82016-08-05 14:38:13 -0700594 * The current ImsCallSessionListenerProxy.
595 */
596 private ImsCallSessionListenerProxy mImsCallSessionListenerProxy;
597
598 /**
Tyler Gunn25a72fc2016-08-11 13:16:54 -0700599 * When calling {@link #terminate(int, int)}, an override for the termination reason which the
600 * modem returns.
601 *
602 * Necessary because passing in an unexpected {@link ImsReasonInfo} reason code to
603 * {@link #terminate(int)} will cause the modem to ignore the terminate request.
604 */
605 private int mOverrideReason = ImsReasonInfo.CODE_UNSPECIFIED;
606
607 /**
Wink Savilleef36ef62014-06-11 08:39:38 -0700608 * Create an IMS call object.
609 *
610 * @param context the context for accessing system services
611 * @param profile the call profile to make/take a call
612 */
613 public ImsCall(Context context, ImsCallProfile profile) {
614 mContext = context;
Tyler Gunn1ac24852016-06-22 10:04:23 -0700615 setCallProfile(profile);
Jack Yub42ff552016-06-22 13:20:52 -0700616 uniqueId = sUniqueIdGenerator.getAndIncrement();
Wink Savilleef36ef62014-06-11 08:39:38 -0700617 }
618
619 /**
620 * Closes this object. This object is not usable after being closed.
621 */
622 @Override
623 public void close() {
624 synchronized(mLockObj) {
Wink Savilleef36ef62014-06-11 08:39:38 -0700625 if (mSession != null) {
626 mSession.close();
627 mSession = null;
Uma Maheswari Ramalingam95be7c62015-05-06 23:08:17 -0700628 } else {
629 logi("close :: Cannot close Null call session!");
Wink Savilleef36ef62014-06-11 08:39:38 -0700630 }
631
632 mCallProfile = null;
633 mProposedCallProfile = null;
634 mLastReasonInfo = null;
635 mMediaSession = null;
636 }
637 }
638
639 /**
640 * Checks if the call has a same remote user identity or not.
641 *
642 * @param userId the remote user identity
643 * @return true if the remote user identity is equal; otherwise, false
644 */
645 @Override
646 public boolean checkIfRemoteUserIsSame(String userId) {
647 if (userId == null) {
648 return false;
649 }
650
651 return userId.equals(mCallProfile.getCallExtra(ImsCallProfile.EXTRA_REMOTE_URI, ""));
652 }
653
654 /**
655 * Checks if the call is equal or not.
656 *
657 * @param call the call to be compared
658 * @return true if the call is equal; otherwise, false
659 */
660 @Override
661 public boolean equalsTo(ICall call) {
662 if (call == null) {
663 return false;
664 }
665
666 if (call instanceof ImsCall) {
Etan Cohen16b3b362014-10-24 11:10:58 -0700667 return this.equals(call);
Wink Savilleef36ef62014-06-11 08:39:38 -0700668 }
669
670 return false;
671 }
672
Anthony Leec479f662015-02-11 17:04:35 -0800673 public static boolean isSessionAlive(ImsCallSession session) {
674 return session != null && session.isAlive();
675 }
676
Wink Savilleef36ef62014-06-11 08:39:38 -0700677 /**
678 * Gets the negotiated (local & remote) call profile.
679 *
680 * @return a {@link ImsCallProfile} object that has the negotiated call profile
681 */
682 public ImsCallProfile getCallProfile() {
683 synchronized(mLockObj) {
684 return mCallProfile;
685 }
686 }
687
688 /**
Tyler Gunn1ac24852016-06-22 10:04:23 -0700689 * Replaces the current call profile with a new one, tracking whethere this was previously a
690 * video call or not.
691 *
692 * @param profile The new call profile.
693 */
Tyler Gunncc0f78f2017-12-04 13:01:55 -0800694 @VisibleForTesting
695 public void setCallProfile(ImsCallProfile profile) {
Tyler Gunn1ac24852016-06-22 10:04:23 -0700696 synchronized(mLockObj) {
697 mCallProfile = profile;
698 trackVideoStateHistory(mCallProfile);
699 }
700 }
701
702 /**
Wink Savilleef36ef62014-06-11 08:39:38 -0700703 * Gets the local call profile (local capabilities).
704 *
705 * @return a {@link ImsCallProfile} object that has the local call profile
706 */
707 public ImsCallProfile getLocalCallProfile() throws ImsException {
708 synchronized(mLockObj) {
709 if (mSession == null) {
710 throw new ImsException("No call session",
711 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
712 }
713
714 try {
715 return mSession.getLocalCallProfile();
716 } catch (Throwable t) {
717 loge("getLocalCallProfile :: ", t);
718 throw new ImsException("getLocalCallProfile()", t, 0);
719 }
720 }
721 }
722
723 /**
Shriram Ganeshe8719892014-10-13 18:28:34 -0700724 * Gets the remote call profile (remote capabilities).
725 *
726 * @return a {@link ImsCallProfile} object that has the remote call profile
727 */
728 public ImsCallProfile getRemoteCallProfile() throws ImsException {
729 synchronized(mLockObj) {
730 if (mSession == null) {
731 throw new ImsException("No call session",
732 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
733 }
734
735 try {
736 return mSession.getRemoteCallProfile();
737 } catch (Throwable t) {
738 loge("getRemoteCallProfile :: ", t);
739 throw new ImsException("getRemoteCallProfile()", t, 0);
740 }
741 }
742 }
743
744 /**
Wink Savilleef36ef62014-06-11 08:39:38 -0700745 * Gets the call profile proposed by the local/remote user.
746 *
747 * @return a {@link ImsCallProfile} object that has the proposed call profile
748 */
749 public ImsCallProfile getProposedCallProfile() {
750 synchronized(mLockObj) {
751 if (!isInCall()) {
752 return null;
753 }
754
755 return mProposedCallProfile;
756 }
757 }
758
759 /**
Uma Maheswari Ramalingama1ed7b02015-05-20 14:26:42 -0700760 * Gets the list of conference participants currently
761 * associated with this call.
762 *
Tyler Gunn3a3d8eb2016-08-18 12:58:26 -0700763 * @return Copy of the list of conference participants.
Uma Maheswari Ramalingama1ed7b02015-05-20 14:26:42 -0700764 */
765 public List<ConferenceParticipant> getConferenceParticipants() {
766 synchronized(mLockObj) {
767 logi("getConferenceParticipants :: mConferenceParticipants"
768 + mConferenceParticipants);
Tyler Gunn108a6b72016-08-23 21:05:25 -0700769 if (mConferenceParticipants == null) {
770 return null;
771 }
772 if (mConferenceParticipants.isEmpty()) {
773 return new ArrayList<ConferenceParticipant>(0);
774 }
Tyler Gunn3a3d8eb2016-08-18 12:58:26 -0700775 return new ArrayList<ConferenceParticipant>(mConferenceParticipants);
Uma Maheswari Ramalingama1ed7b02015-05-20 14:26:42 -0700776 }
777 }
778
779 /**
Wink Savilleef36ef62014-06-11 08:39:38 -0700780 * Gets the state of the {@link ImsCallSession} that carries this call.
781 * The value returned must be one of the states in {@link ImsCallSession#State}.
782 *
783 * @return the session state
784 */
785 public int getState() {
786 synchronized(mLockObj) {
787 if (mSession == null) {
788 return ImsCallSession.State.IDLE;
789 }
790
791 return mSession.getState();
792 }
793 }
794
795 /**
796 * Gets the {@link ImsCallSession} that carries this call.
797 *
798 * @return the session object that carries this call
799 * @hide
800 */
801 public ImsCallSession getCallSession() {
802 synchronized(mLockObj) {
803 return mSession;
804 }
805 }
806
807 /**
808 * Gets the {@link ImsStreamMediaSession} that handles the media operation of this call.
809 * Almost interface APIs are for the VT (Video Telephony).
810 *
811 * @return the media session object that handles the media operation of this call
812 * @hide
813 */
814 public ImsStreamMediaSession getMediaSession() {
815 synchronized(mLockObj) {
816 return mMediaSession;
817 }
818 }
819
820 /**
821 * Gets the specified property of this call.
822 *
823 * @param name key to get the extra call information defined in {@link ImsCallProfile}
824 * @return the extra call information as string
825 */
826 public String getCallExtra(String name) throws ImsException {
827 // Lookup the cache
828
829 synchronized(mLockObj) {
830 // If not found, try to get the property from the remote
831 if (mSession == null) {
832 throw new ImsException("No call session",
833 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
834 }
835
836 try {
837 return mSession.getProperty(name);
838 } catch (Throwable t) {
839 loge("getCallExtra :: ", t);
840 throw new ImsException("getCallExtra()", t, 0);
841 }
842 }
843 }
844
845 /**
846 * Gets the last reason information when the call is not established, cancelled or terminated.
847 *
848 * @return the last reason information
849 */
850 public ImsReasonInfo getLastReasonInfo() {
851 synchronized(mLockObj) {
852 return mLastReasonInfo;
853 }
854 }
855
856 /**
857 * Checks if the call has a pending update operation.
858 *
859 * @return true if the call has a pending update operation
860 */
861 public boolean hasPendingUpdate() {
862 synchronized(mLockObj) {
863 return (mUpdateRequest != UPDATE_NONE);
864 }
865 }
866
867 /**
Tyler Gunn6c0b0d02015-07-01 16:39:43 -0700868 * Checks if the call is pending a hold operation.
869 *
870 * @return true if the call is pending a hold operation.
871 */
872 public boolean isPendingHold() {
873 synchronized(mLockObj) {
874 return (mUpdateRequest == UPDATE_HOLD);
875 }
876 }
877
878 /**
Wink Savilleef36ef62014-06-11 08:39:38 -0700879 * Checks if the call is established.
880 *
881 * @return true if the call is established
882 */
883 public boolean isInCall() {
884 synchronized(mLockObj) {
885 return mInCall;
886 }
887 }
888
889 /**
890 * Checks if the call is muted.
891 *
892 * @return true if the call is muted
893 */
894 public boolean isMuted() {
895 synchronized(mLockObj) {
896 return mMute;
897 }
898 }
899
900 /**
901 * Checks if the call is on hold.
902 *
903 * @return true if the call is on hold
904 */
905 public boolean isOnHold() {
906 synchronized(mLockObj) {
907 return mHold;
908 }
909 }
910
911 /**
Tyler Gunn725ad372014-10-22 11:28:47 -0700912 * Determines if the call is a multiparty call.
913 *
914 * @return {@code True} if the call is a multiparty call.
915 */
916 public boolean isMultiparty() {
Etan Cohen16b3b362014-10-24 11:10:58 -0700917 synchronized(mLockObj) {
918 if (mSession == null) {
919 return false;
920 }
921
Anthony Lee71382692014-10-30 10:50:10 -0700922 return mSession.isMultiparty();
Etan Cohen16b3b362014-10-24 11:10:58 -0700923 }
Tyler Gunn725ad372014-10-22 11:28:47 -0700924 }
925
926 /**
Tyler Gunn25394092015-04-01 09:40:02 -0700927 * Where {@link #isMultiparty()} is {@code true}, determines if this {@link ImsCall} is the
928 * origin of the conference call (i.e. {@code #isConferenceHost()} is {@code true}), or if this
929 * {@link ImsCall} is a member of a conference hosted on another device.
930 *
931 * @return {@code true} if this call is the origin of the conference call it is a member of,
932 * {@code false} otherwise.
933 */
934 public boolean isConferenceHost() {
935 synchronized(mLockObj) {
936 return isMultiparty() && mIsConferenceHost;
937 }
938 }
939
940 /**
Tyler Gunn9bd5ca52014-12-02 09:21:01 -0800941 * Marks whether an IMS call is merged. This should be set {@code true} when the call merges
942 * into a conference.
Andrew Lee8ae59492014-11-17 17:03:02 -0800943 *
944 * @param isMerged Whether the call is merged.
945 */
946 public void setIsMerged(boolean isMerged) {
947 mIsMerged = isMerged;
948 }
949
950 /**
Tyler Gunn9bd5ca52014-12-02 09:21:01 -0800951 * @return {@code true} if the call recently merged into a conference call.
Andrew Lee8ae59492014-11-17 17:03:02 -0800952 */
953 public boolean isMerged() {
954 return mIsMerged;
955 }
956
957 /**
Wink Savilleef36ef62014-06-11 08:39:38 -0700958 * Sets the listener to listen to the IMS call events.
959 * The method calls {@link #setListener setListener(listener, false)}.
960 *
961 * @param listener to listen to the IMS call events of this object; null to remove listener
962 * @see #setListener(Listener, boolean)
963 */
964 public void setListener(ImsCall.Listener listener) {
965 setListener(listener, false);
966 }
967
968 /**
969 * Sets the listener to listen to the IMS call events.
970 * A {@link ImsCall} can only hold one listener at a time. Subsequent calls
971 * to this method override the previous listener.
972 *
973 * @param listener to listen to the IMS call events of this object; null to remove listener
974 * @param callbackImmediately set to true if the caller wants to be called
975 * back immediately on the current state
976 */
977 public void setListener(ImsCall.Listener listener, boolean callbackImmediately) {
978 boolean inCall;
979 boolean onHold;
980 int state;
981 ImsReasonInfo lastReasonInfo;
982
983 synchronized(mLockObj) {
984 mListener = listener;
985
986 if ((listener == null) || !callbackImmediately) {
987 return;
988 }
989
990 inCall = mInCall;
991 onHold = mHold;
992 state = getState();
993 lastReasonInfo = mLastReasonInfo;
994 }
995
996 try {
997 if (lastReasonInfo != null) {
998 listener.onCallError(this, lastReasonInfo);
999 } else if (inCall) {
1000 if (onHold) {
1001 listener.onCallHeld(this);
1002 } else {
1003 listener.onCallStarted(this);
1004 }
1005 } else {
1006 switch (state) {
1007 case ImsCallSession.State.ESTABLISHING:
1008 listener.onCallProgressing(this);
1009 break;
1010 case ImsCallSession.State.TERMINATED:
1011 listener.onCallTerminated(this, lastReasonInfo);
1012 break;
1013 default:
1014 // Ignore it. There is no action in the other state.
1015 break;
1016 }
1017 }
1018 } catch (Throwable t) {
Anthony Lee68048512015-03-18 15:04:18 -07001019 loge("setListener() :: ", t);
Wink Savilleef36ef62014-06-11 08:39:38 -07001020 }
1021 }
1022
1023 /**
1024 * Mutes or unmutes the mic for the active call.
1025 *
1026 * @param muted true if the call is muted, false otherwise
1027 */
1028 public void setMute(boolean muted) throws ImsException {
1029 synchronized(mLockObj) {
1030 if (mMute != muted) {
Anthony Lee68048512015-03-18 15:04:18 -07001031 logi("setMute :: turning mute " + (muted ? "on" : "off"));
Wink Savilleef36ef62014-06-11 08:39:38 -07001032 mMute = muted;
1033
1034 try {
1035 mSession.setMute(muted);
1036 } catch (Throwable t) {
1037 loge("setMute :: ", t);
1038 throwImsException(t, 0);
1039 }
1040 }
1041 }
1042 }
1043
1044 /**
1045 * Attaches an incoming call to this call object.
1046 *
1047 * @param session the session that receives the incoming call
1048 * @throws ImsException if the IMS service fails to attach this object to the session
1049 */
1050 public void attachSession(ImsCallSession session) throws ImsException {
Anthony Lee68048512015-03-18 15:04:18 -07001051 logi("attachSession :: session=" + session);
Wink Savilleef36ef62014-06-11 08:39:38 -07001052
1053 synchronized(mLockObj) {
1054 mSession = session;
1055
1056 try {
1057 mSession.setListener(createCallSessionListener());
1058 } catch (Throwable t) {
1059 loge("attachSession :: ", t);
1060 throwImsException(t, 0);
1061 }
1062 }
1063 }
1064
1065 /**
1066 * Initiates an IMS call with the call profile which is provided
1067 * when creating a {@link ImsCall}.
1068 *
1069 * @param session the {@link ImsCallSession} for carrying out the call
1070 * @param callee callee information to initiate an IMS call
1071 * @throws ImsException if the IMS service fails to initiate the call
1072 */
1073 public void start(ImsCallSession session, String callee)
1074 throws ImsException {
fionaxu7b3107c2016-07-06 14:04:06 -07001075 logi("start(1) :: session=" + session);
Wink Savilleef36ef62014-06-11 08:39:38 -07001076
1077 synchronized(mLockObj) {
1078 mSession = session;
1079
1080 try {
1081 session.setListener(createCallSessionListener());
1082 session.start(callee, mCallProfile);
1083 } catch (Throwable t) {
1084 loge("start(1) :: ", t);
1085 throw new ImsException("start(1)", t, 0);
1086 }
1087 }
1088 }
1089
1090 /**
1091 * Initiates an IMS conferenca call with the call profile which is provided
1092 * when creating a {@link ImsCall}.
1093 *
1094 * @param session the {@link ImsCallSession} for carrying out the call
1095 * @param participants participant list to initiate an IMS conference call
1096 * @throws ImsException if the IMS service fails to initiate the call
1097 */
1098 public void start(ImsCallSession session, String[] participants)
1099 throws ImsException {
fionaxu7b3107c2016-07-06 14:04:06 -07001100 logi("start(n) :: session=" + session);
Wink Savilleef36ef62014-06-11 08:39:38 -07001101
1102 synchronized(mLockObj) {
1103 mSession = session;
1104
1105 try {
1106 session.setListener(createCallSessionListener());
1107 session.start(participants, mCallProfile);
1108 } catch (Throwable t) {
1109 loge("start(n) :: ", t);
1110 throw new ImsException("start(n)", t, 0);
1111 }
1112 }
1113 }
1114
1115 /**
1116 * Accepts a call.
1117 *
1118 * @see Listener#onCallStarted
Tyler Gunnd1edfd82014-07-18 13:44:18 -07001119 *
1120 * @param callType The call type the user agreed to for accepting the call.
Wink Savilleef36ef62014-06-11 08:39:38 -07001121 * @throws ImsException if the IMS service fails to accept the call
1122 */
Tyler Gunnd1edfd82014-07-18 13:44:18 -07001123 public void accept(int callType) throws ImsException {
Tyler Gunnd1edfd82014-07-18 13:44:18 -07001124 accept(callType, new ImsStreamMediaProfile());
Wink Savilleef36ef62014-06-11 08:39:38 -07001125 }
1126
1127 /**
1128 * Accepts a call.
1129 *
1130 * @param callType call type to be answered in {@link ImsCallProfile}
1131 * @param profile a media profile to be answered (audio/audio & video, direction, ...)
1132 * @see Listener#onCallStarted
1133 * @throws ImsException if the IMS service fails to accept the call
1134 */
1135 public void accept(int callType, ImsStreamMediaProfile profile) throws ImsException {
Anthony Lee68048512015-03-18 15:04:18 -07001136 logi("accept :: callType=" + callType + ", profile=" + profile);
Wink Savilleef36ef62014-06-11 08:39:38 -07001137
1138 synchronized(mLockObj) {
1139 if (mSession == null) {
1140 throw new ImsException("No call to answer",
1141 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1142 }
1143
1144 try {
1145 mSession.accept(callType, profile);
1146 } catch (Throwable t) {
1147 loge("accept :: ", t);
1148 throw new ImsException("accept()", t, 0);
1149 }
1150
1151 if (mInCall && (mProposedCallProfile != null)) {
1152 if (DBG) {
Anthony Lee68048512015-03-18 15:04:18 -07001153 logi("accept :: call profile will be updated");
Wink Savilleef36ef62014-06-11 08:39:38 -07001154 }
1155
1156 mCallProfile = mProposedCallProfile;
Tyler Gunn1ac24852016-06-22 10:04:23 -07001157 trackVideoStateHistory(mCallProfile);
Wink Savilleef36ef62014-06-11 08:39:38 -07001158 mProposedCallProfile = null;
1159 }
1160
1161 // Other call update received
1162 if (mInCall && (mUpdateRequest == UPDATE_UNSPECIFIED)) {
1163 mUpdateRequest = UPDATE_NONE;
1164 }
1165 }
1166 }
1167
1168 /**
1169 * Rejects a call.
1170 *
1171 * @param reason reason code to reject an incoming call
1172 * @see Listener#onCallStartFailed
Anthony Lee68048512015-03-18 15:04:18 -07001173 * @throws ImsException if the IMS service fails to reject the call
Wink Savilleef36ef62014-06-11 08:39:38 -07001174 */
1175 public void reject(int reason) throws ImsException {
Anthony Lee68048512015-03-18 15:04:18 -07001176 logi("reject :: reason=" + reason);
Wink Savilleef36ef62014-06-11 08:39:38 -07001177
1178 synchronized(mLockObj) {
1179 if (mSession != null) {
1180 mSession.reject(reason);
1181 }
1182
1183 if (mInCall && (mProposedCallProfile != null)) {
1184 if (DBG) {
Anthony Lee68048512015-03-18 15:04:18 -07001185 logi("reject :: call profile is not updated; destroy it...");
Wink Savilleef36ef62014-06-11 08:39:38 -07001186 }
1187
1188 mProposedCallProfile = null;
1189 }
1190
1191 // Other call update received
1192 if (mInCall && (mUpdateRequest == UPDATE_UNSPECIFIED)) {
1193 mUpdateRequest = UPDATE_NONE;
1194 }
1195 }
1196 }
1197
Tyler Gunn25a72fc2016-08-11 13:16:54 -07001198 public void terminate(int reason, int overrideReason) throws ImsException {
1199 logi("terminate :: reason=" + reason + " ; overrideReadon=" + overrideReason);
1200 mOverrideReason = overrideReason;
1201 terminate(reason);
1202 }
1203
Wink Savilleef36ef62014-06-11 08:39:38 -07001204 /**
Tyler Gunn95d563e2015-07-23 09:28:58 -07001205 * Terminates an IMS call (e.g. user initiated).
Wink Savilleef36ef62014-06-11 08:39:38 -07001206 *
1207 * @param reason reason code to terminate a call
1208 * @throws ImsException if the IMS service fails to terminate the call
1209 */
1210 public void terminate(int reason) throws ImsException {
Anthony Lee68048512015-03-18 15:04:18 -07001211 logi("terminate :: reason=" + reason);
Wink Savilleef36ef62014-06-11 08:39:38 -07001212
1213 synchronized(mLockObj) {
1214 mHold = false;
1215 mInCall = false;
Tyler Gunn95d563e2015-07-23 09:28:58 -07001216 mTerminationRequestPending = true;
Wink Savilleef36ef62014-06-11 08:39:38 -07001217
1218 if (mSession != null) {
Anthony Leec479f662015-02-11 17:04:35 -08001219 // TODO: Fix the fact that user invoked call terminations during
1220 // the process of establishing a conference call needs to be handled
1221 // as a special case.
1222 // Currently, any terminations (both invoked by the user or
1223 // by the network results in a callSessionTerminated() callback
1224 // from the network. When establishing a conference call we bury
1225 // these callbacks until we get closure on all participants of the
1226 // conference. In some situations, we will throw away the callback
1227 // (when the underlying session of the host of the new conference
1228 // is terminated) or will will unbury it when the conference has been
1229 // established, like when the peer of the new conference goes away
1230 // after the conference has been created. The UI relies on the callback
1231 // to reflect the fact that the call is gone.
1232 // So if a user decides to terminated a call while it is merging, it
1233 // could take a long time to reflect in the UI due to the conference
1234 // processing but we should probably cancel that and just terminate
1235 // the call immediately and clean up. This is not a huge issue right
1236 // now because we have not seen instances where establishing a
1237 // conference takes a long time (more than a second or two).
Wink Savilleef36ef62014-06-11 08:39:38 -07001238 mSession.terminate(reason);
1239 }
1240 }
1241 }
1242
Uma Maheswari Ramalingamd43b5302014-08-29 14:13:17 -07001243
Wink Savilleef36ef62014-06-11 08:39:38 -07001244 /**
1245 * Puts a call on hold. When succeeds, {@link Listener#onCallHeld} is called.
1246 *
1247 * @see Listener#onCallHeld, Listener#onCallHoldFailed
1248 * @throws ImsException if the IMS service fails to hold the call
1249 */
1250 public void hold() throws ImsException {
Anthony Lee68048512015-03-18 15:04:18 -07001251 logi("hold :: ");
Etan Cohen111eecc2014-09-10 17:18:12 -07001252
Wink Savilleef36ef62014-06-11 08:39:38 -07001253 if (isOnHold()) {
1254 if (DBG) {
Anthony Lee68048512015-03-18 15:04:18 -07001255 logi("hold :: call is already on hold");
Wink Savilleef36ef62014-06-11 08:39:38 -07001256 }
1257 return;
1258 }
1259
1260 synchronized(mLockObj) {
1261 if (mUpdateRequest != UPDATE_NONE) {
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08001262 loge("hold :: update is in progress; request=" +
1263 updateRequestToString(mUpdateRequest));
Wink Savilleef36ef62014-06-11 08:39:38 -07001264 throw new ImsException("Call update is in progress",
1265 ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
1266 }
1267
1268 if (mSession == null) {
Wink Savilleef36ef62014-06-11 08:39:38 -07001269 throw new ImsException("No call session",
1270 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1271 }
1272
1273 mSession.hold(createHoldMediaProfile());
Anthony Lee68048512015-03-18 15:04:18 -07001274 // FIXME: We should update the state on the callback because that is where
1275 // we can confirm that the hold request was successful or not.
Wink Savilleef36ef62014-06-11 08:39:38 -07001276 mHold = true;
1277 mUpdateRequest = UPDATE_HOLD;
1278 }
1279 }
1280
1281 /**
1282 * Continues a call that's on hold. When succeeds, {@link Listener#onCallResumed} is called.
1283 *
1284 * @see Listener#onCallResumed, Listener#onCallResumeFailed
1285 * @throws ImsException if the IMS service fails to resume the call
1286 */
1287 public void resume() throws ImsException {
Anthony Lee68048512015-03-18 15:04:18 -07001288 logi("resume :: ");
Etan Cohen111eecc2014-09-10 17:18:12 -07001289
Wink Savilleef36ef62014-06-11 08:39:38 -07001290 if (!isOnHold()) {
1291 if (DBG) {
Anthony Lee68048512015-03-18 15:04:18 -07001292 logi("resume :: call is not being held");
Wink Savilleef36ef62014-06-11 08:39:38 -07001293 }
1294 return;
1295 }
1296
1297 synchronized(mLockObj) {
1298 if (mUpdateRequest != UPDATE_NONE) {
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08001299 loge("resume :: update is in progress; request=" +
1300 updateRequestToString(mUpdateRequest));
Wink Savilleef36ef62014-06-11 08:39:38 -07001301 throw new ImsException("Call update is in progress",
1302 ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
1303 }
1304
1305 if (mSession == null) {
1306 loge("resume :: ");
1307 throw new ImsException("No call session",
1308 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1309 }
1310
Anthony Leec479f662015-02-11 17:04:35 -08001311 // mHold is set to false in confirmation callback that the
1312 // ImsCall was resumed.
Wink Savilleef36ef62014-06-11 08:39:38 -07001313 mUpdateRequest = UPDATE_RESUME;
Anthony Leec479f662015-02-11 17:04:35 -08001314 mSession.resume(createResumeMediaProfile());
Wink Savilleef36ef62014-06-11 08:39:38 -07001315 }
1316 }
1317
1318 /**
1319 * Merges the active & hold call.
1320 *
1321 * @see Listener#onCallMerged, Listener#onCallMergeFailed
1322 * @throws ImsException if the IMS service fails to merge the call
1323 */
Tyler Gunn047d8102015-01-30 15:21:11 -08001324 private void merge() throws ImsException {
Anthony Lee68048512015-03-18 15:04:18 -07001325 logi("merge :: ");
Wink Savilleef36ef62014-06-11 08:39:38 -07001326
1327 synchronized(mLockObj) {
Tyler Gunnc7ff7ee2017-09-06 14:38:37 -07001328 // If the host of the merge is in the midst of some other operation, we cannot merge.
Wink Savilleef36ef62014-06-11 08:39:38 -07001329 if (mUpdateRequest != UPDATE_NONE) {
Tyler Gunnc7ff7ee2017-09-06 14:38:37 -07001330 setCallSessionMergePending(false);
1331 if (mMergePeer != null) {
1332 mMergePeer.setCallSessionMergePending(false);
1333 }
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08001334 loge("merge :: update is in progress; request=" +
1335 updateRequestToString(mUpdateRequest));
Wink Savilleef36ef62014-06-11 08:39:38 -07001336 throw new ImsException("Call update is in progress",
1337 ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
1338 }
1339
Tyler Gunnc7ff7ee2017-09-06 14:38:37 -07001340 // The peer of the merge is in the midst of some other operation, we cannot merge.
1341 if (mMergePeer != null && mMergePeer.mUpdateRequest != UPDATE_NONE) {
1342 setCallSessionMergePending(false);
1343 mMergePeer.setCallSessionMergePending(false);
1344 loge("merge :: peer call update is in progress; request=" +
1345 updateRequestToString(mMergePeer.mUpdateRequest));
1346 throw new ImsException("Peer call update is in progress",
1347 ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
1348 }
1349
Wink Savilleef36ef62014-06-11 08:39:38 -07001350 if (mSession == null) {
Anthony Leec479f662015-02-11 17:04:35 -08001351 loge("merge :: no call session");
Wink Savilleef36ef62014-06-11 08:39:38 -07001352 throw new ImsException("No call session",
1353 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1354 }
1355
Uma Maheswari Ramalingamca45f582014-05-19 12:32:20 -07001356 // if skipHoldBeforeMerge = true, IMS service implementation will
1357 // merge without explicitly holding the call.
1358 if (mHold || (mContext.getResources().getBoolean(
1359 com.android.internal.R.bool.skipHoldBeforeMerge))) {
Anthony Leeb8799fe2014-11-03 15:13:47 -08001360
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08001361 if (mMergePeer != null && !mMergePeer.isMultiparty() && !isMultiparty()) {
1362 // We only set UPDATE_MERGE when we are adding the first
1363 // calls to the Conference. If there is already a conference
Tyler Gunn047d8102015-01-30 15:21:11 -08001364 // no special handling is needed. The existing conference
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08001365 // session will just go active and any other sessions will be terminated
1366 // if needed. There will be no merge failed callback.
Tyler Gunn047d8102015-01-30 15:21:11 -08001367 // Mark both the host and peer UPDATE_MERGE to ensure both are aware that a
1368 // merge is pending.
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08001369 mUpdateRequest = UPDATE_MERGE;
Tyler Gunn047d8102015-01-30 15:21:11 -08001370 mMergePeer.mUpdateRequest = UPDATE_MERGE;
Anthony Leeb8799fe2014-11-03 15:13:47 -08001371 }
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08001372
1373 mSession.merge();
Wink Savilleef36ef62014-06-11 08:39:38 -07001374 } else {
Anthony Lee71382692014-10-30 10:50:10 -07001375 // This code basically says, we need to explicitly hold before requesting a merge
1376 // when we get the callback that the hold was successful (or failed), we should
1377 // automatically request a merge.
Wink Savilleef36ef62014-06-11 08:39:38 -07001378 mSession.hold(createHoldMediaProfile());
Wink Savilleef36ef62014-06-11 08:39:38 -07001379 mHold = true;
1380 mUpdateRequest = UPDATE_HOLD_MERGE;
1381 }
1382 }
1383 }
1384
1385 /**
1386 * Merges the active & hold call.
1387 *
1388 * @param bgCall the background (holding) call
1389 * @see Listener#onCallMerged, Listener#onCallMergeFailed
1390 * @throws ImsException if the IMS service fails to merge the call
1391 */
1392 public void merge(ImsCall bgCall) throws ImsException {
Anthony Lee68048512015-03-18 15:04:18 -07001393 logi("merge(1) :: bgImsCall=" + bgCall);
Wink Savilleef36ef62014-06-11 08:39:38 -07001394
1395 if (bgCall == null) {
1396 throw new ImsException("No background call",
1397 ImsReasonInfo.CODE_LOCAL_ILLEGAL_ARGUMENT);
1398 }
1399
1400 synchronized(mLockObj) {
Tyler Gunn047d8102015-01-30 15:21:11 -08001401 // Mark both sessions as pending merge.
1402 this.setCallSessionMergePending(true);
1403 bgCall.setCallSessionMergePending(true);
1404
Tyler Gunn87466c52014-12-08 09:56:17 -08001405 if ((!isMultiparty() && !bgCall.isMultiparty()) || isMultiparty()) {
1406 // If neither call is multiparty, the current call is the merge host and the bg call
1407 // is the merge peer (ie we're starting a new conference).
1408 // OR
1409 // If this call is multiparty, it is the merge host and the other call is the merge
1410 // peer.
1411 setMergePeer(bgCall);
1412 } else {
1413 // If the bg call is multiparty, it is the merge host.
1414 setMergeHost(bgCall);
1415 }
Wink Savilleef36ef62014-06-11 08:39:38 -07001416 }
Uma Maheswari Ramalingama1ed7b02015-05-20 14:26:42 -07001417
Uma Maheswari Ramalingam95be7c62015-05-06 23:08:17 -07001418 if (isMultiparty()) {
1419 mMergeRequestedByConference = true;
1420 } else {
1421 logi("merge : mMergeRequestedByConference not set");
1422 }
Wink Savilleef36ef62014-06-11 08:39:38 -07001423 merge();
1424 }
1425
1426 /**
1427 * Updates the current call's properties (ex. call mode change: video upgrade / downgrade).
1428 */
1429 public void update(int callType, ImsStreamMediaProfile mediaProfile) throws ImsException {
Anthony Lee68048512015-03-18 15:04:18 -07001430 logi("update :: callType=" + callType + ", mediaProfile=" + mediaProfile);
Wink Savilleef36ef62014-06-11 08:39:38 -07001431
1432 if (isOnHold()) {
1433 if (DBG) {
Anthony Lee68048512015-03-18 15:04:18 -07001434 logi("update :: call is on hold");
Wink Savilleef36ef62014-06-11 08:39:38 -07001435 }
1436 throw new ImsException("Not in a call to update call",
1437 ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
1438 }
1439
1440 synchronized(mLockObj) {
1441 if (mUpdateRequest != UPDATE_NONE) {
1442 if (DBG) {
Anthony Lee68048512015-03-18 15:04:18 -07001443 logi("update :: update is in progress; request=" +
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08001444 updateRequestToString(mUpdateRequest));
Wink Savilleef36ef62014-06-11 08:39:38 -07001445 }
1446 throw new ImsException("Call update is in progress",
1447 ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
1448 }
1449
1450 if (mSession == null) {
1451 loge("update :: ");
1452 throw new ImsException("No call session",
1453 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1454 }
1455
1456 mSession.update(callType, mediaProfile);
1457 mUpdateRequest = UPDATE_UNSPECIFIED;
1458 }
1459 }
1460
1461 /**
1462 * Extends this call (1-to-1 call) to the conference call
1463 * inviting the specified participants to.
1464 *
1465 */
1466 public void extendToConference(String[] participants) throws ImsException {
Anthony Lee68048512015-03-18 15:04:18 -07001467 logi("extendToConference ::");
Wink Savilleef36ef62014-06-11 08:39:38 -07001468
1469 if (isOnHold()) {
1470 if (DBG) {
Anthony Lee68048512015-03-18 15:04:18 -07001471 logi("extendToConference :: call is on hold");
Wink Savilleef36ef62014-06-11 08:39:38 -07001472 }
1473 throw new ImsException("Not in a call to extend a call to conference",
1474 ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
1475 }
1476
1477 synchronized(mLockObj) {
1478 if (mUpdateRequest != UPDATE_NONE) {
Anthony Lee68048512015-03-18 15:04:18 -07001479 if (CONF_DBG) {
1480 logi("extendToConference :: update is in progress; request=" +
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08001481 updateRequestToString(mUpdateRequest));
Wink Savilleef36ef62014-06-11 08:39:38 -07001482 }
1483 throw new ImsException("Call update is in progress",
1484 ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
1485 }
1486
1487 if (mSession == null) {
1488 loge("extendToConference :: ");
1489 throw new ImsException("No call session",
1490 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1491 }
1492
1493 mSession.extendToConference(participants);
1494 mUpdateRequest = UPDATE_EXTEND_TO_CONFERENCE;
1495 }
1496 }
1497
1498 /**
1499 * Requests the conference server to invite an additional participants to the conference.
1500 *
1501 */
1502 public void inviteParticipants(String[] participants) throws ImsException {
Anthony Lee68048512015-03-18 15:04:18 -07001503 logi("inviteParticipants ::");
Wink Savilleef36ef62014-06-11 08:39:38 -07001504
1505 synchronized(mLockObj) {
1506 if (mSession == null) {
1507 loge("inviteParticipants :: ");
1508 throw new ImsException("No call session",
1509 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1510 }
1511
1512 mSession.inviteParticipants(participants);
1513 }
1514 }
1515
1516 /**
1517 * Requests the conference server to remove the specified participants from the conference.
1518 *
1519 */
1520 public void removeParticipants(String[] participants) throws ImsException {
Uma Maheswari Ramalingama1ed7b02015-05-20 14:26:42 -07001521 logi("removeParticipants :: session=" + mSession);
Wink Savilleef36ef62014-06-11 08:39:38 -07001522 synchronized(mLockObj) {
1523 if (mSession == null) {
1524 loge("removeParticipants :: ");
1525 throw new ImsException("No call session",
1526 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1527 }
1528
1529 mSession.removeParticipants(participants);
Uma Maheswari Ramalingama1ed7b02015-05-20 14:26:42 -07001530
Wink Savilleef36ef62014-06-11 08:39:38 -07001531 }
1532 }
1533
Wink Savilleef36ef62014-06-11 08:39:38 -07001534 /**
1535 * Sends a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2833</a>,
1536 * event 0 ~ 9 maps to decimal value 0 ~ 9, '*' to 10, '#' to 11, event 'A' ~ 'D' to 12 ~ 15,
1537 * and event flash to 16. Currently, event flash is not supported.
1538 *
Libin.Tang@motorola.com2f92daf2014-08-23 18:08:31 -05001539 * @param c that represents the DTMF to send. '0' ~ '9', 'A' ~ 'D', '*', '#' are valid inputs.
1540 * @param result the result message to send when done.
Wink Savilleef36ef62014-06-11 08:39:38 -07001541 */
Libin.Tang@motorola.com2f92daf2014-08-23 18:08:31 -05001542 public void sendDtmf(char c, Message result) {
Anthony Lee68048512015-03-18 15:04:18 -07001543 logi("sendDtmf :: code=" + c);
Wink Savilleef36ef62014-06-11 08:39:38 -07001544
1545 synchronized(mLockObj) {
1546 if (mSession != null) {
Andrew Leea4710d52014-12-09 14:51:53 -08001547 mSession.sendDtmf(c, result);
Wink Savilleef36ef62014-06-11 08:39:38 -07001548 }
1549 }
Wink Savilleef36ef62014-06-11 08:39:38 -07001550 }
1551
1552 /**
Uma Maheswari Ramalingama6fbae92014-12-05 16:40:46 -08001553 * Start a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2833</a>,
1554 * event 0 ~ 9 maps to decimal value 0 ~ 9, '*' to 10, '#' to 11, event 'A' ~ 'D' to 12 ~ 15,
1555 * and event flash to 16. Currently, event flash is not supported.
1556 *
1557 * @param c that represents the DTMF to send. '0' ~ '9', 'A' ~ 'D', '*', '#' are valid inputs.
1558 */
1559 public void startDtmf(char c) {
Anthony Lee68048512015-03-18 15:04:18 -07001560 logi("startDtmf :: code=" + c);
Uma Maheswari Ramalingama6fbae92014-12-05 16:40:46 -08001561
1562 synchronized(mLockObj) {
1563 if (mSession != null) {
1564 mSession.startDtmf(c);
1565 }
1566 }
1567 }
1568
1569 /**
1570 * Stop a DTMF code.
1571 */
1572 public void stopDtmf() {
Anthony Lee68048512015-03-18 15:04:18 -07001573 logi("stopDtmf :: ");
Uma Maheswari Ramalingama6fbae92014-12-05 16:40:46 -08001574
1575 synchronized(mLockObj) {
1576 if (mSession != null) {
1577 mSession.stopDtmf();
1578 }
1579 }
1580 }
1581
1582 /**
Wink Savilleef36ef62014-06-11 08:39:38 -07001583 * Sends an USSD message.
1584 *
1585 * @param ussdMessage USSD message to send
1586 */
1587 public void sendUssd(String ussdMessage) throws ImsException {
Anthony Lee68048512015-03-18 15:04:18 -07001588 logi("sendUssd :: ussdMessage=" + ussdMessage);
Wink Savilleef36ef62014-06-11 08:39:38 -07001589
1590 synchronized(mLockObj) {
1591 if (mSession == null) {
1592 loge("sendUssd :: ");
1593 throw new ImsException("No call session",
1594 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1595 }
1596
1597 mSession.sendUssd(ussdMessage);
1598 }
1599 }
1600
Hall Liubd3f3772017-03-22 15:38:23 -07001601 public void sendRttMessage(String rttMessage) {
1602 synchronized(mLockObj) {
1603 if (mSession == null) {
1604 loge("sendRttMessage::no session");
1605 }
1606 if (!mCallProfile.mMediaProfile.isRttCall()) {
1607 logi("sendRttMessage::Not an rtt call, ignoring");
1608 return;
1609 }
1610 mSession.sendRttMessage(rttMessage);
1611 }
1612 }
1613
1614 /**
1615 * Sends a user-requested RTT upgrade request.
1616 */
1617 public void sendRttModifyRequest() {
1618 logi("sendRttModifyRequest");
1619
1620 synchronized(mLockObj) {
1621 if (mSession == null) {
1622 loge("sendRttModifyRequest::no session");
1623 }
1624 if (mCallProfile.mMediaProfile.isRttCall()) {
1625 logi("sendRttModifyRequest::Already RTT call, ignoring.");
1626 return;
1627 }
1628 // Make a copy of the current ImsCallProfile and modify it to enable RTT
1629 Parcel p = Parcel.obtain();
1630 mCallProfile.writeToParcel(p, 0);
1631 ImsCallProfile requestedProfile = new ImsCallProfile(p);
1632 requestedProfile.mMediaProfile.setRttMode(ImsStreamMediaProfile.RTT_MODE_FULL);
1633
1634 mSession.sendRttModifyRequest(requestedProfile);
1635 }
1636 }
1637
1638 /**
1639 * Sends the user's response to a remotely-issued RTT upgrade request
1640 *
1641 * @param textStream A valid {@link Connection.RttTextStream} if the user
1642 * accepts, {@code null} if not.
1643 */
1644 public void sendRttModifyResponse(boolean status) {
1645 logi("sendRttModifyResponse");
1646
1647 synchronized(mLockObj) {
1648 if (mSession == null) {
1649 loge("sendRttModifyResponse::no session");
1650 }
1651 if (mCallProfile.mMediaProfile.isRttCall()) {
1652 logi("sendRttModifyResponse::Already RTT call, ignoring.");
1653 return;
1654 }
1655 mSession.sendRttModifyResponse(status);
1656 }
1657 }
1658
Wink Savilleef36ef62014-06-11 08:39:38 -07001659 private void clear(ImsReasonInfo lastReasonInfo) {
1660 mInCall = false;
1661 mHold = false;
1662 mUpdateRequest = UPDATE_NONE;
1663 mLastReasonInfo = lastReasonInfo;
Wink Savilleef36ef62014-06-11 08:39:38 -07001664 }
1665
1666 /**
1667 * Creates an IMS call session listener.
1668 */
1669 private ImsCallSession.Listener createCallSessionListener() {
Tyler Gunnafb53c82016-08-05 14:38:13 -07001670 mImsCallSessionListenerProxy = new ImsCallSessionListenerProxy();
1671 return mImsCallSessionListenerProxy;
1672 }
1673
1674 /**
1675 * @return the current ImsCallSessionListenerProxy. NOTE: ONLY FOR USE WITH TESTING.
1676 */
1677 @VisibleForTesting
1678 public ImsCallSessionListenerProxy getImsCallSessionListenerProxy() {
1679 return mImsCallSessionListenerProxy;
Wink Savilleef36ef62014-06-11 08:39:38 -07001680 }
1681
1682 private ImsCall createNewCall(ImsCallSession session, ImsCallProfile profile) {
1683 ImsCall call = new ImsCall(mContext, profile);
1684
1685 try {
1686 call.attachSession(session);
1687 } catch (ImsException e) {
1688 if (call != null) {
1689 call.close();
1690 call = null;
1691 }
1692 }
1693
1694 // Do additional operations...
1695
1696 return call;
1697 }
1698
1699 private ImsStreamMediaProfile createHoldMediaProfile() {
1700 ImsStreamMediaProfile mediaProfile = new ImsStreamMediaProfile();
1701
1702 if (mCallProfile == null) {
1703 return mediaProfile;
1704 }
1705
1706 mediaProfile.mAudioQuality = mCallProfile.mMediaProfile.mAudioQuality;
1707 mediaProfile.mVideoQuality = mCallProfile.mMediaProfile.mVideoQuality;
1708 mediaProfile.mAudioDirection = ImsStreamMediaProfile.DIRECTION_SEND;
1709
1710 if (mediaProfile.mVideoQuality != ImsStreamMediaProfile.VIDEO_QUALITY_NONE) {
1711 mediaProfile.mVideoDirection = ImsStreamMediaProfile.DIRECTION_SEND;
1712 }
1713
1714 return mediaProfile;
1715 }
1716
1717 private ImsStreamMediaProfile createResumeMediaProfile() {
1718 ImsStreamMediaProfile mediaProfile = new ImsStreamMediaProfile();
1719
1720 if (mCallProfile == null) {
1721 return mediaProfile;
1722 }
1723
1724 mediaProfile.mAudioQuality = mCallProfile.mMediaProfile.mAudioQuality;
1725 mediaProfile.mVideoQuality = mCallProfile.mMediaProfile.mVideoQuality;
1726 mediaProfile.mAudioDirection = ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE;
1727
1728 if (mediaProfile.mVideoQuality != ImsStreamMediaProfile.VIDEO_QUALITY_NONE) {
1729 mediaProfile.mVideoDirection = ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE;
1730 }
1731
1732 return mediaProfile;
1733 }
1734
1735 private void enforceConversationMode() {
1736 if (mInCall) {
1737 mHold = false;
1738 mUpdateRequest = UPDATE_NONE;
1739 }
1740 }
1741
1742 private void mergeInternal() {
Anthony Lee68048512015-03-18 15:04:18 -07001743 if (CONF_DBG) {
1744 logi("mergeInternal :: ");
Wink Savilleef36ef62014-06-11 08:39:38 -07001745 }
1746
1747 mSession.merge();
1748 mUpdateRequest = UPDATE_MERGE;
1749 }
1750
Wink Savilleef36ef62014-06-11 08:39:38 -07001751 private void notifyConferenceSessionTerminated(ImsReasonInfo reasonInfo) {
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08001752 ImsCall.Listener listener = mListener;
Wink Savilleef36ef62014-06-11 08:39:38 -07001753 clear(reasonInfo);
1754
1755 if (listener != null) {
1756 try {
1757 listener.onCallTerminated(this, reasonInfo);
1758 } catch (Throwable t) {
1759 loge("notifyConferenceSessionTerminated :: ", t);
1760 }
1761 }
1762 }
1763
1764 private void notifyConferenceStateUpdated(ImsConferenceState state) {
Tyler Gunn9e3452a2015-09-08 13:10:14 -07001765 if (state == null || state.mParticipants == null) {
1766 return;
1767 }
1768
Tyler Gunn1c467602014-11-04 14:51:52 -08001769 Set<Entry<String, Bundle>> participants = state.mParticipants.entrySet();
Wink Savilleef36ef62014-06-11 08:39:38 -07001770
Tyler Gunn1c467602014-11-04 14:51:52 -08001771 if (participants == null) {
Wink Savilleef36ef62014-06-11 08:39:38 -07001772 return;
1773 }
1774
Tyler Gunn1c467602014-11-04 14:51:52 -08001775 Iterator<Entry<String, Bundle>> iterator = participants.iterator();
Uma Maheswari Ramalingama1ed7b02015-05-20 14:26:42 -07001776 mConferenceParticipants = new ArrayList<>(participants.size());
Wink Savilleef36ef62014-06-11 08:39:38 -07001777 while (iterator.hasNext()) {
1778 Entry<String, Bundle> entry = iterator.next();
1779
1780 String key = entry.getKey();
1781 Bundle confInfo = entry.getValue();
1782 String status = confInfo.getString(ImsConferenceState.STATUS);
1783 String user = confInfo.getString(ImsConferenceState.USER);
Tyler Gunn59656142014-10-28 13:52:11 -07001784 String displayName = confInfo.getString(ImsConferenceState.DISPLAY_TEXT);
Wink Savilleef36ef62014-06-11 08:39:38 -07001785 String endpoint = confInfo.getString(ImsConferenceState.ENDPOINT);
1786
Anthony Lee68048512015-03-18 15:04:18 -07001787 if (CONF_DBG) {
Shunta Sakaifb688ca2016-12-06 20:32:07 +09001788 logi("notifyConferenceStateUpdated :: key=" + Rlog.pii(TAG, key) +
Wink Savilleef36ef62014-06-11 08:39:38 -07001789 ", status=" + status +
Shunta Sakaifb688ca2016-12-06 20:32:07 +09001790 ", user=" + Rlog.pii(TAG, user) +
1791 ", displayName= " + Rlog.pii(TAG, displayName) +
Wink Savilleef36ef62014-06-11 08:39:38 -07001792 ", endpoint=" + endpoint);
1793 }
1794
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08001795 Uri handle = Uri.parse(user);
Uma Maheswari Ramalingama1ed7b02015-05-20 14:26:42 -07001796 if (endpoint == null) {
1797 endpoint = "";
1798 }
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08001799 Uri endpointUri = Uri.parse(endpoint);
1800 int connectionState = ImsConferenceState.getConnectionStateForStatus(status);
Wink Savilleef36ef62014-06-11 08:39:38 -07001801
Uma Maheswari Ramalingama1ed7b02015-05-20 14:26:42 -07001802 if (connectionState != Connection.STATE_DISCONNECTED) {
1803 ConferenceParticipant conferenceParticipant = new ConferenceParticipant(handle,
1804 displayName, endpointUri, connectionState);
1805 mConferenceParticipants.add(conferenceParticipant);
1806 }
Wink Savilleef36ef62014-06-11 08:39:38 -07001807 }
Tyler Gunn1c467602014-11-04 14:51:52 -08001808
Anju Mathapati0f108032015-10-09 15:34:28 -07001809 if (mConferenceParticipants != null && mListener != null) {
Tyler Gunn1c467602014-11-04 14:51:52 -08001810 try {
Uma Maheswari Ramalingama1ed7b02015-05-20 14:26:42 -07001811 mListener.onConferenceParticipantsStateChanged(this, mConferenceParticipants);
Tyler Gunn1c467602014-11-04 14:51:52 -08001812 } catch (Throwable t) {
1813 loge("notifyConferenceStateUpdated :: ", t);
1814 }
1815 }
Wink Savilleef36ef62014-06-11 08:39:38 -07001816 }
1817
Anthony Leeb8799fe2014-11-03 15:13:47 -08001818 /**
1819 * Perform all cleanup and notification around the termination of a session.
1820 * Note that there are 2 distinct modes of operation. The first is when
1821 * we receive a session termination on the primary session when we are
1822 * in the processing of merging. The second is when we are not merging anything
1823 * and the call is terminated.
1824 *
1825 * @param reasonInfo The reason for the session termination
1826 */
1827 private void processCallTerminated(ImsReasonInfo reasonInfo) {
Tyler Gunn95d563e2015-07-23 09:28:58 -07001828 logi("processCallTerminated :: reason=" + reasonInfo + " userInitiated = " +
1829 mTerminationRequestPending);
Anthony Leeb8799fe2014-11-03 15:13:47 -08001830
1831 ImsCall.Listener listener = null;
Anthony Leeb8799fe2014-11-03 15:13:47 -08001832 synchronized(ImsCall.this) {
Tyler Gunn047d8102015-01-30 15:21:11 -08001833 // If we are in the midst of establishing a conference, we will bury the termination
Tyler Gunn95d563e2015-07-23 09:28:58 -07001834 // until the merge has completed. If necessary we can surface the termination at
1835 // this point.
1836 // We will also NOT bury the termination if a termination was initiated locally.
1837 if (isCallSessionMergePending() && !mTerminationRequestPending) {
Anthony Leeb8799fe2014-11-03 15:13:47 -08001838 // Since we are in the process of a merge, this trigger means something
1839 // else because it is probably due to the merge happening vs. the
1840 // session is really terminated. Let's flag this and revisit if
1841 // the merge() ends up failing because we will need to take action on the
1842 // mSession in that case since the termination was not due to the merge
1843 // succeeding.
Anthony Lee68048512015-03-18 15:04:18 -07001844 if (CONF_DBG) {
1845 logi("processCallTerminated :: burying termination during ongoing merge.");
Anthony Leeb8799fe2014-11-03 15:13:47 -08001846 }
1847 mSessionEndDuringMerge = true;
1848 mSessionEndDuringMergeReasonInfo = reasonInfo;
1849 return;
1850 }
1851
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08001852 // If we are terminating the conference call, notify using conference listeners.
1853 if (isMultiparty()) {
Anthony Leeb8799fe2014-11-03 15:13:47 -08001854 notifyConferenceSessionTerminated(reasonInfo);
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08001855 return;
Anthony Leeb8799fe2014-11-03 15:13:47 -08001856 } else {
1857 listener = mListener;
1858 clear(reasonInfo);
1859 }
1860 }
1861
1862 if (listener != null) {
1863 try {
1864 listener.onCallTerminated(ImsCall.this, reasonInfo);
1865 } catch (Throwable t) {
Anthony Leec479f662015-02-11 17:04:35 -08001866 loge("processCallTerminated :: ", t);
Anthony Leeb8799fe2014-11-03 15:13:47 -08001867 }
1868 }
1869 }
Anthony Lee71382692014-10-30 10:50:10 -07001870
1871 /**
1872 * This function determines if the ImsCallSession is our actual ImsCallSession or if is
1873 * the transient session used in the process of creating a conference. This function should only
1874 * be called within callbacks that are not directly related to conference merging but might
1875 * potentially still be called on the transient ImsCallSession sent to us from
1876 * callSessionMergeStarted() when we don't really care. In those situations, we probably don't
1877 * want to take any action so we need to know that we can return early.
1878 *
1879 * @param session - The {@link ImsCallSession} that the function needs to analyze
1880 * @return true if this is the transient {@link ImsCallSession}, false otherwise.
1881 */
1882 private boolean isTransientConferenceSession(ImsCallSession session) {
1883 if (session != null && session != mSession && session == mTransientConferenceSession) {
1884 return true;
1885 }
1886 return false;
1887 }
1888
Anthony Leec479f662015-02-11 17:04:35 -08001889 private void setTransientSessionAsPrimary(ImsCallSession transientSession) {
1890 synchronized (ImsCall.this) {
1891 mSession.setListener(null);
1892 mSession = transientSession;
1893 mSession.setListener(createCallSessionListener());
1894 }
Tyler Gunn047d8102015-01-30 15:21:11 -08001895 }
1896
Anju Mathapati818c09d2015-08-31 14:30:47 -07001897 private void markCallAsMerged(boolean playDisconnectTone) {
1898 if (!isSessionAlive(mSession)) {
Uma Maheswari Ramalingam95be7c62015-05-06 23:08:17 -07001899 // If the peer is dead, let's not play a disconnect sound for it when we
1900 // unbury the termination callback.
Anju Mathapati818c09d2015-08-31 14:30:47 -07001901 logi("markCallAsMerged");
1902 setIsMerged(playDisconnectTone);
1903 mSessionEndDuringMerge = true;
1904 String reasonInfo;
Jayachandran C96e2d852017-05-11 11:43:52 -07001905 int reasonCode = ImsReasonInfo.CODE_UNSPECIFIED;
Anju Mathapati818c09d2015-08-31 14:30:47 -07001906 if (playDisconnectTone) {
Jayachandran C96e2d852017-05-11 11:43:52 -07001907 reasonCode = ImsReasonInfo.CODE_USER_TERMINATED_BY_REMOTE;
Anju Mathapati818c09d2015-08-31 14:30:47 -07001908 reasonInfo = "Call ended by network";
1909 } else {
Jayachandran C96e2d852017-05-11 11:43:52 -07001910 reasonCode = ImsReasonInfo.CODE_LOCAL_ENDED_BY_CONFERENCE_MERGE;
Anju Mathapati818c09d2015-08-31 14:30:47 -07001911 reasonInfo = "Call ended during conference merge process.";
1912 }
1913 mSessionEndDuringMergeReasonInfo = new ImsReasonInfo(
Jayachandran C96e2d852017-05-11 11:43:52 -07001914 reasonCode, 0, reasonInfo);
Uma Maheswari Ramalingam95be7c62015-05-06 23:08:17 -07001915 }
1916 }
1917
Tyler Gunn047d8102015-01-30 15:21:11 -08001918 /**
Uma Maheswari Ramalingam95be7c62015-05-06 23:08:17 -07001919 * Checks if the merge was requested by foreground conference call
1920 *
1921 * @return true if the merge was requested by foreground conference call
Anthony Leec479f662015-02-11 17:04:35 -08001922 */
Uma Maheswari Ramalingam95be7c62015-05-06 23:08:17 -07001923 public boolean isMergeRequestedByConf() {
1924 synchronized(mLockObj) {
1925 return mMergeRequestedByConference;
1926 }
1927 }
1928
1929 /**
1930 * Resets the flag which indicates merge request was sent by
1931 * foreground conference call
1932 */
1933 public void resetIsMergeRequestedByConf(boolean value) {
1934 synchronized(mLockObj) {
1935 mMergeRequestedByConference = value;
1936 }
1937 }
1938
1939 /**
1940 * Returns current ImsCallSession
1941 *
1942 * @return current session
1943 */
1944 public ImsCallSession getSession() {
1945 synchronized(mLockObj) {
1946 return mSession;
Anthony Leec479f662015-02-11 17:04:35 -08001947 }
1948 }
1949
1950 /**
1951 * We have detected that a initial conference call has been fully configured. The internal
1952 * state of both {@code ImsCall} objects need to be cleaned up to reflect the new state.
1953 * This function should only be called in the context of the merge host to simplify logic
Anthony Lee71382692014-10-30 10:50:10 -07001954 *
Anthony Lee71382692014-10-30 10:50:10 -07001955 */
Anthony Leeb8799fe2014-11-03 15:13:47 -08001956 private void processMergeComplete() {
Anthony Lee68048512015-03-18 15:04:18 -07001957 logi("processMergeComplete :: ");
Anthony Leec479f662015-02-11 17:04:35 -08001958
1959 // The logic simplifies if we can assume that this function is only called on
1960 // the merge host.
1961 if (!isMergeHost()) {
1962 loge("processMergeComplete :: We are not the merge host!");
1963 return;
Anthony Leeb8799fe2014-11-03 15:13:47 -08001964 }
1965
Anthony Lee71382692014-10-30 10:50:10 -07001966 ImsCall.Listener listener;
Tyler Gunn047d8102015-01-30 15:21:11 -08001967 boolean swapRequired = false;
Tyler Gunn047d8102015-01-30 15:21:11 -08001968
Uma Maheswari Ramalingam95be7c62015-05-06 23:08:17 -07001969 ImsCall finalHostCall;
1970 ImsCall finalPeerCall;
1971
1972 synchronized(ImsCall.this) {
Anthony Leec479f662015-02-11 17:04:35 -08001973 if (isMultiparty()) {
Anthony Leec479f662015-02-11 17:04:35 -08001974 setIsMerged(false);
Uma Maheswari Ramalingam95be7c62015-05-06 23:08:17 -07001975 // if case handles Case 4 explained in callSessionMergeComplete
1976 // otherwise it is case 5
1977 if (!mMergeRequestedByConference) {
1978 // single call in fg, conference call in bg.
1979 // Finally conf call becomes active after conference
1980 this.mHold = false;
1981 swapRequired = true;
Tyler Gunn047d8102015-01-30 15:21:11 -08001982 }
Anju Mathapati818c09d2015-08-31 14:30:47 -07001983 mMergePeer.markCallAsMerged(false);
Uma Maheswari Ramalingam95be7c62015-05-06 23:08:17 -07001984 finalHostCall = this;
1985 finalPeerCall = mMergePeer;
Tyler Gunn047d8102015-01-30 15:21:11 -08001986 } else {
Anthony Leec479f662015-02-11 17:04:35 -08001987 // If we are here, we are not trying to merge a new call into an existing
1988 // conference. That means that there is a transient session on the merge
1989 // host that represents the future conference once all the parties
1990 // have been added to it. So make sure that it exists or else something
1991 // very wrong is going on.
1992 if (mTransientConferenceSession == null) {
1993 loge("processMergeComplete :: No transient session!");
1994 return;
Tyler Gunn047d8102015-01-30 15:21:11 -08001995 }
Anthony Leec479f662015-02-11 17:04:35 -08001996 if (mMergePeer == null) {
1997 loge("processMergeComplete :: No merge peer!");
1998 return;
Tyler Gunn047d8102015-01-30 15:21:11 -08001999 }
Tyler Gunn047d8102015-01-30 15:21:11 -08002000
Anthony Leec479f662015-02-11 17:04:35 -08002001 // Since we are the host, we have the transient session attached to us. Let's detach
2002 // it and figure out where we need to set it for the final conference configuration.
2003 ImsCallSession transientConferenceSession = mTransientConferenceSession;
2004 mTransientConferenceSession = null;
2005
2006 // Clear the listener for this transient session, we'll create a new listener
2007 // when it is attached to the final ImsCall that it should live on.
2008 transientConferenceSession.setListener(null);
2009
2010 // Determine which call the transient session should be moved to. If the current
2011 // call session is still alive and the merge peer's session is not, we have a
2012 // situation where the current call failed to merge into the conference but the
2013 // merge peer did merge in to the conference. In this type of scenario the current
2014 // call will continue as a single party call, yet the background call will become
2015 // the conference.
2016
Uma Maheswari Ramalingam95be7c62015-05-06 23:08:17 -07002017 // handles Case 3 explained in callSessionMergeComplete
Anthony Leec479f662015-02-11 17:04:35 -08002018 if (isSessionAlive(mSession) && !isSessionAlive(mMergePeer.getCallSession())) {
2019 // I'm the host but we are moving the transient session to the peer since its
2020 // session was disconnected and my session is still alive. This signifies that
2021 // their session was properly added to the conference but mine was not because
2022 // it is probably in the held state as opposed to part of the final conference.
2023 // In this case, we need to set isMerged to false on both calls so the
2024 // disconnect sound is called when either call disconnects.
2025 // Note that this case is only valid if this is an initial conference being
2026 // brought up.
Uma Maheswari Ramalingam95be7c62015-05-06 23:08:17 -07002027 mMergePeer.mHold = false;
2028 this.mHold = true;
Anju Mathapatid0fb6642015-07-22 13:55:57 -07002029 if (mConferenceParticipants != null && !mConferenceParticipants.isEmpty()) {
2030 mMergePeer.mConferenceParticipants = mConferenceParticipants;
2031 }
2032 // At this point both host & peer will have participant information.
2033 // Peer will transition to host & the participant information
2034 // from that will be used
2035 // HostCall that failed to merge will remain as a single call with
2036 // mConferenceParticipants, which should not be used.
2037 // Expectation is that if this call becomes part of a conference call in future,
2038 // mConferenceParticipants will be overriten with new CEP that is received.
Anthony Leec479f662015-02-11 17:04:35 -08002039 finalHostCall = mMergePeer;
2040 finalPeerCall = this;
2041 swapRequired = true;
2042 setIsMerged(false);
2043 mMergePeer.setIsMerged(false);
Anthony Lee68048512015-03-18 15:04:18 -07002044 if (CONF_DBG) {
2045 logi("processMergeComplete :: transient will transfer to merge peer");
Tyler Gunn047d8102015-01-30 15:21:11 -08002046 }
Anthony Lee68048512015-03-18 15:04:18 -07002047 } else if (!isSessionAlive(mSession) &&
2048 isSessionAlive(mMergePeer.getCallSession())) {
Uma Maheswari Ramalingam95be7c62015-05-06 23:08:17 -07002049 // Handles case 2 explained in callSessionMergeComplete
Anthony Leec479f662015-02-11 17:04:35 -08002050 // The transient session stays with us and the disconnect sound should be played
2051 // when the merge peer eventually disconnects since it was not actually added to
2052 // the conference and is probably sitting in the held state.
2053 finalHostCall = this;
2054 finalPeerCall = mMergePeer;
2055 swapRequired = false;
2056 setIsMerged(false);
2057 mMergePeer.setIsMerged(false); // Play the disconnect sound
Anthony Lee68048512015-03-18 15:04:18 -07002058 if (CONF_DBG) {
2059 logi("processMergeComplete :: transient will stay with the merge host");
Tyler Gunn047d8102015-01-30 15:21:11 -08002060 }
Anthony Leec479f662015-02-11 17:04:35 -08002061 } else {
Uma Maheswari Ramalingam95be7c62015-05-06 23:08:17 -07002062 // Handles case 1 explained in callSessionMergeComplete
Anthony Leec479f662015-02-11 17:04:35 -08002063 // The transient session stays with us and the disconnect sound should not be
2064 // played when we ripple up the disconnect for the merge peer because it was
2065 // only disconnected to be added to the conference.
2066 finalHostCall = this;
2067 finalPeerCall = mMergePeer;
Anju Mathapati818c09d2015-08-31 14:30:47 -07002068 mMergePeer.markCallAsMerged(false);
Anthony Leec479f662015-02-11 17:04:35 -08002069 swapRequired = false;
2070 setIsMerged(false);
2071 mMergePeer.setIsMerged(true);
Anthony Lee68048512015-03-18 15:04:18 -07002072 if (CONF_DBG) {
2073 logi("processMergeComplete :: transient will stay with us (I'm the host).");
Anthony Leec479f662015-02-11 17:04:35 -08002074 }
Tyler Gunn047d8102015-01-30 15:21:11 -08002075 }
Anthony Leec479f662015-02-11 17:04:35 -08002076
Anthony Lee68048512015-03-18 15:04:18 -07002077 if (CONF_DBG) {
2078 logi("processMergeComplete :: call=" + finalHostCall + " is the final host");
Anthony Leec479f662015-02-11 17:04:35 -08002079 }
2080
2081 // Add the transient session to the ImsCall that ended up being the host for the
2082 // conference.
2083 finalHostCall.setTransientSessionAsPrimary(transientConferenceSession);
Tyler Gunn047d8102015-01-30 15:21:11 -08002084 }
2085
Anthony Leec479f662015-02-11 17:04:35 -08002086 listener = finalHostCall.mListener;
Tyler Gunn047d8102015-01-30 15:21:11 -08002087
Anju Mathapati1c108882015-11-17 18:59:24 +05302088 updateCallProfile(finalPeerCall);
2089 updateCallProfile(finalHostCall);
2090
Anthony Leec479f662015-02-11 17:04:35 -08002091 // Clear all the merge related flags.
2092 clearMergeInfo();
2093
2094 // For the final peer...let's bubble up any possible disconnects that we had
2095 // during the merge process
2096 finalPeerCall.notifySessionTerminatedDuringMerge();
2097 // For the final host, let's just bury the disconnects that we my have received
2098 // during the merge process since we are now the host of the conference call.
2099 finalHostCall.clearSessionTerminationFlags();
Tyler Gunn25394092015-04-01 09:40:02 -07002100
2101 // Keep track of the fact that merge host is the origin of a conference call in
2102 // progress. This is important so that we can later determine if a multiparty ImsCall
2103 // is multiparty because it was the origin of a conference call, or because it is a
2104 // member of a conference on another device.
2105 finalHostCall.mIsConferenceHost = true;
Anthony Lee71382692014-10-30 10:50:10 -07002106 }
2107 if (listener != null) {
2108 try {
Uma Maheswari Ramalingam95be7c62015-05-06 23:08:17 -07002109 // finalPeerCall will have the participant that was not merged and
2110 // it will be held state
2111 // if peer was merged successfully, finalPeerCall will be null
2112 listener.onCallMerged(finalHostCall, finalPeerCall, swapRequired);
Anthony Lee71382692014-10-30 10:50:10 -07002113 } catch (Throwable t) {
Anthony Leeb8799fe2014-11-03 15:13:47 -08002114 loge("processMergeComplete :: ", t);
Anthony Lee71382692014-10-30 10:50:10 -07002115 }
Tyler Gunn6c0b0d02015-07-01 16:39:43 -07002116 if (mConferenceParticipants != null && !mConferenceParticipants.isEmpty()) {
Uma Maheswari Ramalingama1ed7b02015-05-20 14:26:42 -07002117 try {
Anju Mathapatid0fb6642015-07-22 13:55:57 -07002118 listener.onConferenceParticipantsStateChanged(finalHostCall,
2119 mConferenceParticipants);
Uma Maheswari Ramalingama1ed7b02015-05-20 14:26:42 -07002120 } catch (Throwable t) {
2121 loge("processMergeComplete :: ", t);
2122 }
2123 }
Anthony Lee71382692014-10-30 10:50:10 -07002124 }
Anthony Lee71382692014-10-30 10:50:10 -07002125 return;
2126 }
2127
Anju Mathapati1c108882015-11-17 18:59:24 +05302128 private static void updateCallProfile(ImsCall call) {
2129 if (call != null) {
2130 call.updateCallProfile();
2131 }
2132 }
2133
2134 private void updateCallProfile() {
2135 synchronized (mLockObj) {
2136 if (mSession != null) {
Tyler Gunn1ac24852016-06-22 10:04:23 -07002137 setCallProfile(mSession.getCallProfile());
Anju Mathapati1c108882015-11-17 18:59:24 +05302138 }
2139 }
2140 }
2141
Anthony Lee71382692014-10-30 10:50:10 -07002142 /**
Tyler Gunn047d8102015-01-30 15:21:11 -08002143 * Handles the case where the session has ended during a merge by reporting the termination
2144 * reason to listeners.
2145 */
2146 private void notifySessionTerminatedDuringMerge() {
2147 ImsCall.Listener listener;
2148 boolean notifyFailure = false;
2149 ImsReasonInfo notifyFailureReasonInfo = null;
2150
2151 synchronized(ImsCall.this) {
2152 listener = mListener;
2153 if (mSessionEndDuringMerge) {
2154 // Set some local variables that will send out a notification about a
2155 // previously buried termination callback for our primary session now that
2156 // we know that this is not due to the conference call merging successfully.
Anthony Lee68048512015-03-18 15:04:18 -07002157 if (CONF_DBG) {
2158 logi("notifySessionTerminatedDuringMerge ::reporting terminate during merge");
Tyler Gunn047d8102015-01-30 15:21:11 -08002159 }
2160 notifyFailure = true;
2161 notifyFailureReasonInfo = mSessionEndDuringMergeReasonInfo;
2162 }
Anthony Leec479f662015-02-11 17:04:35 -08002163 clearSessionTerminationFlags();
Tyler Gunn047d8102015-01-30 15:21:11 -08002164 }
2165
2166 if (listener != null && notifyFailure) {
2167 try {
2168 processCallTerminated(notifyFailureReasonInfo);
2169 } catch (Throwable t) {
2170 loge("notifySessionTerminatedDuringMerge :: ", t);
2171 }
2172 }
2173 }
2174
Anthony Leec479f662015-02-11 17:04:35 -08002175 private void clearSessionTerminationFlags() {
2176 mSessionEndDuringMerge = false;
2177 mSessionEndDuringMergeReasonInfo = null;
2178 }
2179
Uma Maheswari Ramalingama1ed7b02015-05-20 14:26:42 -07002180 /**
Anthony Lee71382692014-10-30 10:50:10 -07002181 * We received a callback from ImsCallSession that a merge failed. Clean up all
Anthony Leec479f662015-02-11 17:04:35 -08002182 * internal state to represent this state change. The calling function is a callback
2183 * and should have been called on the session that was in the foreground
2184 * when merge() was originally called. It is assumed that this function will be called
2185 * on the merge host.
Anthony Lee71382692014-10-30 10:50:10 -07002186 *
Anthony Lee71382692014-10-30 10:50:10 -07002187 * @param reasonInfo The {@link ImsReasonInfo} why the merge failed.
2188 */
Anthony Leeb8799fe2014-11-03 15:13:47 -08002189 private void processMergeFailed(ImsReasonInfo reasonInfo) {
Anthony Lee68048512015-03-18 15:04:18 -07002190 logi("processMergeFailed :: reason=" + reasonInfo);
Tyler Gunn047d8102015-01-30 15:21:11 -08002191
2192 ImsCall.Listener listener;
Anthony Lee71382692014-10-30 10:50:10 -07002193 synchronized(ImsCall.this) {
Anthony Leec479f662015-02-11 17:04:35 -08002194 // The logic simplifies if we can assume that this function is only called on
2195 // the merge host.
2196 if (!isMergeHost()) {
2197 loge("processMergeFailed :: We are not the merge host!");
2198 return;
2199 }
2200
Anthony Lee61d41c12015-04-02 09:40:01 -07002201 // Try to clean up the transient session if it exists.
2202 if (mTransientConferenceSession != null) {
Anthony Lee71382692014-10-30 10:50:10 -07002203 mTransientConferenceSession.setListener(null);
2204 mTransientConferenceSession = null;
2205 }
Tyler Gunn047d8102015-01-30 15:21:11 -08002206
Anthony Leec479f662015-02-11 17:04:35 -08002207 listener = mListener;
2208
Anthony Lee61d41c12015-04-02 09:40:01 -07002209 // Ensure the calls being conferenced into the conference has isMerged = false.
Anthony Leec479f662015-02-11 17:04:35 -08002210 // Ensure any terminations are surfaced from this session.
Anju Mathapati818c09d2015-08-31 14:30:47 -07002211 markCallAsMerged(true);
2212 setCallSessionMergePending(false);
Anthony Leec479f662015-02-11 17:04:35 -08002213 notifySessionTerminatedDuringMerge();
Anthony Lee61d41c12015-04-02 09:40:01 -07002214
Anju Mathapati818c09d2015-08-31 14:30:47 -07002215 // Perform the same cleanup on the merge peer if it exists.
Anthony Lee61d41c12015-04-02 09:40:01 -07002216 if (mMergePeer != null) {
Anju Mathapati818c09d2015-08-31 14:30:47 -07002217 mMergePeer.markCallAsMerged(true);
2218 mMergePeer.setCallSessionMergePending(false);
Anthony Lee61d41c12015-04-02 09:40:01 -07002219 mMergePeer.notifySessionTerminatedDuringMerge();
2220 } else {
2221 loge("processMergeFailed :: No merge peer!");
2222 }
Anthony Leec479f662015-02-11 17:04:35 -08002223
2224 // Clear all the various flags around coordinating this merge.
2225 clearMergeInfo();
Anthony Lee71382692014-10-30 10:50:10 -07002226 }
2227 if (listener != null) {
2228 try {
2229 listener.onCallMergeFailed(ImsCall.this, reasonInfo);
2230 } catch (Throwable t) {
Anthony Leeb8799fe2014-11-03 15:13:47 -08002231 loge("processMergeFailed :: ", t);
Anthony Lee71382692014-10-30 10:50:10 -07002232 }
2233 }
Anthony Leec479f662015-02-11 17:04:35 -08002234
Anthony Lee71382692014-10-30 10:50:10 -07002235 return;
2236 }
2237
Tyler Gunnafb53c82016-08-05 14:38:13 -07002238 @VisibleForTesting
2239 public class ImsCallSessionListenerProxy extends ImsCallSession.Listener {
Wink Savilleef36ef62014-06-11 08:39:38 -07002240 @Override
Anthony Lee71382692014-10-30 10:50:10 -07002241 public void callSessionProgressing(ImsCallSession session, ImsStreamMediaProfile profile) {
Anthony Lee68048512015-03-18 15:04:18 -07002242 logi("callSessionProgressing :: session=" + session + " profile=" + profile);
2243
Anthony Lee71382692014-10-30 10:50:10 -07002244 if (isTransientConferenceSession(session)) {
Anthony Leec479f662015-02-11 17:04:35 -08002245 // If it is a transient (conference) session, there is no action for this signal.
Anthony Lee68048512015-03-18 15:04:18 -07002246 logi("callSessionProgressing :: not supported for transient conference session=" +
Anthony Leeb8799fe2014-11-03 15:13:47 -08002247 session);
Anthony Lee71382692014-10-30 10:50:10 -07002248 return;
2249 }
2250
Wink Savilleef36ef62014-06-11 08:39:38 -07002251 ImsCall.Listener listener;
2252
2253 synchronized(ImsCall.this) {
2254 listener = mListener;
2255 mCallProfile.mMediaProfile.copyFrom(profile);
2256 }
2257
2258 if (listener != null) {
2259 try {
2260 listener.onCallProgressing(ImsCall.this);
2261 } catch (Throwable t) {
2262 loge("callSessionProgressing :: ", t);
2263 }
2264 }
2265 }
2266
2267 @Override
Anthony Lee71382692014-10-30 10:50:10 -07002268 public void callSessionStarted(ImsCallSession session, ImsCallProfile profile) {
Anthony Lee68048512015-03-18 15:04:18 -07002269 logi("callSessionStarted :: session=" + session + " profile=" + profile);
Anthony Leec479f662015-02-11 17:04:35 -08002270
2271 if (!isTransientConferenceSession(session)) {
2272 // In the case that we are in the middle of a merge (either host or peer), we have
2273 // closure as far as this call's primary session is concerned. If we are not
2274 // merging...its a NOOP.
2275 setCallSessionMergePending(false);
2276 } else {
Anthony Lee68048512015-03-18 15:04:18 -07002277 logi("callSessionStarted :: on transient session=" + session);
Anthony Leec479f662015-02-11 17:04:35 -08002278 return;
Wink Savilleef36ef62014-06-11 08:39:38 -07002279 }
2280
Anthony Leec479f662015-02-11 17:04:35 -08002281 if (isTransientConferenceSession(session)) {
2282 // No further processing is needed if this is the transient session.
Anthony Leeb8799fe2014-11-03 15:13:47 -08002283 return;
2284 }
2285
Wink Savilleef36ef62014-06-11 08:39:38 -07002286 ImsCall.Listener listener;
2287
2288 synchronized(ImsCall.this) {
2289 listener = mListener;
Tyler Gunn1ac24852016-06-22 10:04:23 -07002290 setCallProfile(profile);
Wink Savilleef36ef62014-06-11 08:39:38 -07002291 }
2292
2293 if (listener != null) {
2294 try {
2295 listener.onCallStarted(ImsCall.this);
2296 } catch (Throwable t) {
2297 loge("callSessionStarted :: ", t);
2298 }
2299 }
2300 }
2301
2302 @Override
Anthony Lee71382692014-10-30 10:50:10 -07002303 public void callSessionStartFailed(ImsCallSession session, ImsReasonInfo reasonInfo) {
Anthony Lee68048512015-03-18 15:04:18 -07002304 loge("callSessionStartFailed :: session=" + session + " reasonInfo=" + reasonInfo);
2305
Anthony Lee71382692014-10-30 10:50:10 -07002306 if (isTransientConferenceSession(session)) {
Anthony Leec479f662015-02-11 17:04:35 -08002307 // We should not get this callback for a transient session.
Anthony Lee68048512015-03-18 15:04:18 -07002308 logi("callSessionStartFailed :: not supported for transient conference session=" +
Anthony Leeb8799fe2014-11-03 15:13:47 -08002309 session);
Anthony Lee71382692014-10-30 10:50:10 -07002310 return;
2311 }
2312
Wink Savilleef36ef62014-06-11 08:39:38 -07002313 ImsCall.Listener listener;
2314
2315 synchronized(ImsCall.this) {
2316 listener = mListener;
2317 mLastReasonInfo = reasonInfo;
2318 }
2319
2320 if (listener != null) {
2321 try {
2322 listener.onCallStartFailed(ImsCall.this, reasonInfo);
2323 } catch (Throwable t) {
2324 loge("callSessionStarted :: ", t);
2325 }
2326 }
2327 }
2328
2329 @Override
Anthony Lee71382692014-10-30 10:50:10 -07002330 public void callSessionTerminated(ImsCallSession session, ImsReasonInfo reasonInfo) {
Anthony Lee68048512015-03-18 15:04:18 -07002331 logi("callSessionTerminated :: session=" + session + " reasonInfo=" + reasonInfo);
2332
Anthony Leec479f662015-02-11 17:04:35 -08002333 if (isTransientConferenceSession(session)) {
Anthony Lee68048512015-03-18 15:04:18 -07002334 logi("callSessionTerminated :: on transient session=" + session);
Anthony Leec479f662015-02-11 17:04:35 -08002335 // This is bad, it should be treated much a callSessionMergeFailed since the
2336 // transient session only exists when in the process of a merge and the
2337 // termination of this session is effectively the end of the merge.
2338 processMergeFailed(reasonInfo);
Anthony Lee71382692014-10-30 10:50:10 -07002339 return;
2340 }
2341
Tyler Gunn25a72fc2016-08-11 13:16:54 -07002342 if (mOverrideReason != ImsReasonInfo.CODE_UNSPECIFIED) {
2343 logi("callSessionTerminated :: overrideReasonInfo=" + mOverrideReason);
2344 reasonInfo = new ImsReasonInfo(mOverrideReason, reasonInfo.getExtraCode(),
2345 reasonInfo.getExtraMessage());
2346 }
2347
Anthony Leec479f662015-02-11 17:04:35 -08002348 // Process the termination first. If we are in the midst of establishing a conference
2349 // call, we may bury this callback until we are done. If there so no conference
2350 // call, the code after this function will be a NOOP.
2351 processCallTerminated(reasonInfo);
2352
Tyler Gunn047d8102015-01-30 15:21:11 -08002353 // If session has terminated, it is no longer pending merge.
2354 setCallSessionMergePending(false);
2355
Wink Savilleef36ef62014-06-11 08:39:38 -07002356 }
2357
2358 @Override
Anthony Lee71382692014-10-30 10:50:10 -07002359 public void callSessionHeld(ImsCallSession session, ImsCallProfile profile) {
Anthony Lee68048512015-03-18 15:04:18 -07002360 logi("callSessionHeld :: session=" + session + "profile=" + profile);
Wink Savilleef36ef62014-06-11 08:39:38 -07002361 ImsCall.Listener listener;
2362
2363 synchronized(ImsCall.this) {
Tyler Gunn047d8102015-01-30 15:21:11 -08002364 // If the session was held, it is no longer pending a merge -- this means it could
2365 // not be merged into the conference and was held instead.
2366 setCallSessionMergePending(false);
2367
Tyler Gunn1ac24852016-06-22 10:04:23 -07002368 setCallProfile(profile);
Wink Savilleef36ef62014-06-11 08:39:38 -07002369
2370 if (mUpdateRequest == UPDATE_HOLD_MERGE) {
Anthony Leec479f662015-02-11 17:04:35 -08002371 // This hold request was made to set the stage for a merge.
Wink Savilleef36ef62014-06-11 08:39:38 -07002372 mergeInternal();
2373 return;
2374 }
2375
2376 mUpdateRequest = UPDATE_NONE;
2377 listener = mListener;
2378 }
2379
2380 if (listener != null) {
2381 try {
2382 listener.onCallHeld(ImsCall.this);
2383 } catch (Throwable t) {
2384 loge("callSessionHeld :: ", t);
2385 }
2386 }
2387 }
2388
2389 @Override
Anthony Lee71382692014-10-30 10:50:10 -07002390 public void callSessionHoldFailed(ImsCallSession session, ImsReasonInfo reasonInfo) {
Anthony Lee68048512015-03-18 15:04:18 -07002391 loge("callSessionHoldFailed :: session" + session + "reasonInfo=" + reasonInfo);
2392
Anthony Lee71382692014-10-30 10:50:10 -07002393 if (isTransientConferenceSession(session)) {
Anthony Leec479f662015-02-11 17:04:35 -08002394 // We should not get this callback for a transient session.
Anthony Lee68048512015-03-18 15:04:18 -07002395 logi("callSessionHoldFailed :: not supported for transient conference session=" +
Anthony Leeb8799fe2014-11-03 15:13:47 -08002396 session);
Anthony Lee71382692014-10-30 10:50:10 -07002397 return;
2398 }
2399
Shriram Ganeshd3adfad2015-05-31 10:06:15 -07002400 logi("callSessionHoldFailed :: session=" + session +
2401 ", reasonInfo=" + reasonInfo);
2402
2403 synchronized (mLockObj) {
Uma Maheswari Ramalingama1ed7b02015-05-20 14:26:42 -07002404 mHold = false;
2405 }
2406
Wink Savilleef36ef62014-06-11 08:39:38 -07002407 boolean isHoldForMerge = false;
2408 ImsCall.Listener listener;
2409
2410 synchronized(ImsCall.this) {
2411 if (mUpdateRequest == UPDATE_HOLD_MERGE) {
2412 isHoldForMerge = true;
2413 }
2414
2415 mUpdateRequest = UPDATE_NONE;
2416 listener = mListener;
2417 }
2418
Wink Savilleef36ef62014-06-11 08:39:38 -07002419 if (listener != null) {
2420 try {
2421 listener.onCallHoldFailed(ImsCall.this, reasonInfo);
2422 } catch (Throwable t) {
2423 loge("callSessionHoldFailed :: ", t);
2424 }
2425 }
2426 }
2427
Tyler Gunncc0f78f2017-12-04 13:01:55 -08002428 /**
2429 * Indicates that an {@link ImsCallSession} has been remotely held. This can be due to the
2430 * remote party holding the current call, or swapping between calls.
2431 * @param session the session which was held.
2432 * @param profile the profile for the held call.
2433 */
Wink Savilleef36ef62014-06-11 08:39:38 -07002434 @Override
Anthony Lee71382692014-10-30 10:50:10 -07002435 public void callSessionHoldReceived(ImsCallSession session, ImsCallProfile profile) {
Anthony Lee68048512015-03-18 15:04:18 -07002436 logi("callSessionHoldReceived :: session=" + session + "profile=" + profile);
2437
Anthony Lee71382692014-10-30 10:50:10 -07002438 if (isTransientConferenceSession(session)) {
Anthony Leec479f662015-02-11 17:04:35 -08002439 // We should not get this callback for a transient session.
Anthony Lee68048512015-03-18 15:04:18 -07002440 logi("callSessionHoldReceived :: not supported for transient conference session=" +
Anthony Leeb8799fe2014-11-03 15:13:47 -08002441 session);
Anthony Lee71382692014-10-30 10:50:10 -07002442 return;
2443 }
2444
Wink Savilleef36ef62014-06-11 08:39:38 -07002445 ImsCall.Listener listener;
2446
2447 synchronized(ImsCall.this) {
2448 listener = mListener;
Tyler Gunn1ac24852016-06-22 10:04:23 -07002449 setCallProfile(profile);
Wink Savilleef36ef62014-06-11 08:39:38 -07002450 }
2451
2452 if (listener != null) {
2453 try {
2454 listener.onCallHoldReceived(ImsCall.this);
2455 } catch (Throwable t) {
2456 loge("callSessionHoldReceived :: ", t);
2457 }
2458 }
2459 }
2460
Tyler Gunncc0f78f2017-12-04 13:01:55 -08002461 /**
2462 * Indicates that an {@link ImsCallSession} has been remotely resumed. This can be due to
2463 * the remote party un-holding the current call, or swapping back to this call.
2464 * @param session the session which was resumed.
2465 * @param profile the profile for the held call.
2466 */
Wink Savilleef36ef62014-06-11 08:39:38 -07002467 @Override
Anthony Lee71382692014-10-30 10:50:10 -07002468 public void callSessionResumed(ImsCallSession session, ImsCallProfile profile) {
Anthony Lee68048512015-03-18 15:04:18 -07002469 logi("callSessionResumed :: session=" + session + "profile=" + profile);
2470
Anthony Lee71382692014-10-30 10:50:10 -07002471 if (isTransientConferenceSession(session)) {
Anthony Lee68048512015-03-18 15:04:18 -07002472 logi("callSessionResumed :: not supported for transient conference session=" +
Anthony Leeb8799fe2014-11-03 15:13:47 -08002473 session);
Anthony Lee71382692014-10-30 10:50:10 -07002474 return;
2475 }
2476
Anthony Leec479f662015-02-11 17:04:35 -08002477 // If this call was pending a merge, it is not anymore. This is the case when we
2478 // are merging in a new call into an existing conference.
2479 setCallSessionMergePending(false);
Wink Savilleef36ef62014-06-11 08:39:38 -07002480
Anthony Leec479f662015-02-11 17:04:35 -08002481 // TOOD: When we are merging a new call into an existing conference we are waiting
2482 // for 2 triggers to let us know that the conference has been established, the first
2483 // is a termination for the new calls (since it is added to the conference) the second
2484 // would be a resume on the existing conference. If the resume comes first, then
2485 // we will make the onCallResumed() callback and its unclear how this will behave if
2486 // the termination has not come yet.
2487
2488 ImsCall.Listener listener;
Wink Savilleef36ef62014-06-11 08:39:38 -07002489 synchronized(ImsCall.this) {
2490 listener = mListener;
Tyler Gunn1ac24852016-06-22 10:04:23 -07002491 setCallProfile(profile);
Wink Savilleef36ef62014-06-11 08:39:38 -07002492 mUpdateRequest = UPDATE_NONE;
Uma Maheswari Ramalingam095839a2014-12-03 14:47:15 -08002493 mHold = false;
Wink Savilleef36ef62014-06-11 08:39:38 -07002494 }
2495
2496 if (listener != null) {
2497 try {
2498 listener.onCallResumed(ImsCall.this);
2499 } catch (Throwable t) {
2500 loge("callSessionResumed :: ", t);
2501 }
2502 }
2503 }
2504
2505 @Override
Anthony Lee71382692014-10-30 10:50:10 -07002506 public void callSessionResumeFailed(ImsCallSession session, ImsReasonInfo reasonInfo) {
Anthony Lee68048512015-03-18 15:04:18 -07002507 loge("callSessionResumeFailed :: session=" + session + "reasonInfo=" + reasonInfo);
2508
Anthony Lee71382692014-10-30 10:50:10 -07002509 if (isTransientConferenceSession(session)) {
Anthony Lee68048512015-03-18 15:04:18 -07002510 logi("callSessionResumeFailed :: not supported for transient conference session=" +
Anthony Leeb8799fe2014-11-03 15:13:47 -08002511 session);
Anthony Lee71382692014-10-30 10:50:10 -07002512 return;
2513 }
2514
Uma Maheswari Ramalingama1ed7b02015-05-20 14:26:42 -07002515 synchronized(mLockObj) {
2516 mHold = true;
2517 }
2518
Wink Savilleef36ef62014-06-11 08:39:38 -07002519 ImsCall.Listener listener;
2520
2521 synchronized(ImsCall.this) {
2522 listener = mListener;
2523 mUpdateRequest = UPDATE_NONE;
2524 }
2525
2526 if (listener != null) {
2527 try {
2528 listener.onCallResumeFailed(ImsCall.this, reasonInfo);
2529 } catch (Throwable t) {
2530 loge("callSessionResumeFailed :: ", t);
2531 }
2532 }
2533 }
2534
2535 @Override
Anthony Lee71382692014-10-30 10:50:10 -07002536 public void callSessionResumeReceived(ImsCallSession session, ImsCallProfile profile) {
Anthony Lee68048512015-03-18 15:04:18 -07002537 logi("callSessionResumeReceived :: session=" + session + "profile=" + profile);
2538
Anthony Lee71382692014-10-30 10:50:10 -07002539 if (isTransientConferenceSession(session)) {
Anthony Lee68048512015-03-18 15:04:18 -07002540 logi("callSessionResumeReceived :: not supported for transient conference session=" +
Anthony Leeb8799fe2014-11-03 15:13:47 -08002541 session);
Anthony Lee71382692014-10-30 10:50:10 -07002542 return;
2543 }
2544
Wink Savilleef36ef62014-06-11 08:39:38 -07002545 ImsCall.Listener listener;
2546
2547 synchronized(ImsCall.this) {
2548 listener = mListener;
Tyler Gunn1ac24852016-06-22 10:04:23 -07002549 setCallProfile(profile);
Wink Savilleef36ef62014-06-11 08:39:38 -07002550 }
2551
2552 if (listener != null) {
2553 try {
2554 listener.onCallResumeReceived(ImsCall.this);
2555 } catch (Throwable t) {
2556 loge("callSessionResumeReceived :: ", t);
2557 }
2558 }
2559 }
2560
2561 @Override
Tyler Gunn3f2b0aa2014-10-24 10:02:19 -07002562 public void callSessionMergeStarted(ImsCallSession session,
Wink Savilleef36ef62014-06-11 08:39:38 -07002563 ImsCallSession newSession, ImsCallProfile profile) {
Anthony Lee68048512015-03-18 15:04:18 -07002564 logi("callSessionMergeStarted :: session=" + session + " newSession=" + newSession +
2565 ", profile=" + profile);
Wink Savilleef36ef62014-06-11 08:39:38 -07002566
Anthony Lee71382692014-10-30 10:50:10 -07002567 return;
Wink Savilleef36ef62014-06-11 08:39:38 -07002568 }
2569
Uma Maheswari Ramalingam95be7c62015-05-06 23:08:17 -07002570 /*
2571 * This method check if session exists as a session on the current
2572 * ImsCall or its counterpart if it is in the process of a conference
2573 */
2574 private boolean doesCallSessionExistsInMerge(ImsCallSession cs) {
2575 String callId = cs.getCallId();
2576 return ((isMergeHost() && Objects.equals(mMergePeer.mSession.getCallId(), callId)) ||
2577 (isMergePeer() && Objects.equals(mMergeHost.mSession.getCallId(), callId)) ||
2578 Objects.equals(mSession.getCallId(), callId));
2579 }
2580
2581 /**
2582 * We received a callback from ImsCallSession that merge completed.
Anju Mathapatid9ed8742015-07-17 14:54:10 -07002583 * @param newSession - this session can have 2 values based on the below scenarios
Uma Maheswari Ramalingam95be7c62015-05-06 23:08:17 -07002584 *
2585 * Conference Scenarios :
2586 * Case 1 - 3 way success case
2587 * Case 2 - 3 way success case but held call fails to merge
2588 * Case 3 - 3 way success case but active call fails to merge
2589 * case 4 - 4 way success case, where merge is initiated on the foreground single-party
2590 * call and the conference (mergeHost) is the background call.
2591 * case 5 - 4 way success case, where merge is initiated on the foreground conference
2592 * call (mergeHost) and the single party call is in the background.
2593 *
2594 * Conference Result:
Anju Mathapatid9ed8742015-07-17 14:54:10 -07002595 * session : new session after conference
2596 * newSession = new session for case 1, 2, 3.
2597 * Should be considered as mTransientConferencession
2598 * newSession = Active conference session for case 5 will be null
2599 * mergehost was foreground call
2600 * mTransientConferencession will be null
2601 * newSession = Active conference session for case 4 will be null
2602 * mergeHost was background call
2603 * mTransientConferencession will be null
Uma Maheswari Ramalingam95be7c62015-05-06 23:08:17 -07002604 */
Wink Savilleef36ef62014-06-11 08:39:38 -07002605 @Override
Anju Mathapatid9ed8742015-07-17 14:54:10 -07002606 public void callSessionMergeComplete(ImsCallSession newSession) {
2607 logi("callSessionMergeComplete :: newSession =" + newSession);
Uma Maheswari Ramalingam95be7c62015-05-06 23:08:17 -07002608 if (!isMergeHost()) {
2609 // Handles case 4
2610 mMergeHost.processMergeComplete();
2611 } else {
Anju Mathapatid9ed8742015-07-17 14:54:10 -07002612 // Handles case 1, 2, 3
2613 if (newSession != null) {
2614 mTransientConferenceSession = doesCallSessionExistsInMerge(newSession) ?
2615 null: newSession;
2616 }
2617 // Handles case 5
Uma Maheswari Ramalingam95be7c62015-05-06 23:08:17 -07002618 processMergeComplete();
2619 }
Wink Savilleef36ef62014-06-11 08:39:38 -07002620 }
2621
2622 @Override
Anthony Lee71382692014-10-30 10:50:10 -07002623 public void callSessionMergeFailed(ImsCallSession session, ImsReasonInfo reasonInfo) {
Anthony Lee68048512015-03-18 15:04:18 -07002624 loge("callSessionMergeFailed :: session=" + session + "reasonInfo=" + reasonInfo);
Anthony Leec479f662015-02-11 17:04:35 -08002625
2626 // Its possible that there could be threading issues with the other thread handling
2627 // the other call. This could affect our state.
2628 synchronized (ImsCall.this) {
Anthony Leec479f662015-02-11 17:04:35 -08002629 // Let's tell our parent ImsCall that the merge has failed and we need to clean
2630 // up any temporary, transient state. Note this only gets called for an initial
2631 // conference. If a merge into an existing conference fails, the two sessions will
2632 // just go back to their original state (ACTIVE or HELD).
2633 if (isMergeHost()) {
2634 processMergeFailed(reasonInfo);
2635 } else if (mMergeHost != null) {
2636 mMergeHost.processMergeFailed(reasonInfo);
2637 } else {
2638 loge("callSessionMergeFailed :: No merge host for this conference!");
2639 }
Anthony Lee71382692014-10-30 10:50:10 -07002640 }
Anthony Lee71382692014-10-30 10:50:10 -07002641 }
2642
2643 @Override
2644 public void callSessionUpdated(ImsCallSession session, ImsCallProfile profile) {
Anthony Lee68048512015-03-18 15:04:18 -07002645 logi("callSessionUpdated :: session=" + session + " profile=" + profile);
2646
Anthony Lee71382692014-10-30 10:50:10 -07002647 if (isTransientConferenceSession(session)) {
Anthony Lee68048512015-03-18 15:04:18 -07002648 logi("callSessionUpdated :: not supported for transient conference session=" +
Anthony Leeb8799fe2014-11-03 15:13:47 -08002649 session);
Anthony Lee71382692014-10-30 10:50:10 -07002650 return;
2651 }
2652
Wink Savilleef36ef62014-06-11 08:39:38 -07002653 ImsCall.Listener listener;
2654
2655 synchronized(ImsCall.this) {
2656 listener = mListener;
Tyler Gunn1ac24852016-06-22 10:04:23 -07002657 setCallProfile(profile);
Wink Savilleef36ef62014-06-11 08:39:38 -07002658 }
2659
2660 if (listener != null) {
2661 try {
2662 listener.onCallUpdated(ImsCall.this);
2663 } catch (Throwable t) {
2664 loge("callSessionUpdated :: ", t);
2665 }
2666 }
2667 }
2668
2669 @Override
Anthony Lee71382692014-10-30 10:50:10 -07002670 public void callSessionUpdateFailed(ImsCallSession session, ImsReasonInfo reasonInfo) {
Anthony Lee68048512015-03-18 15:04:18 -07002671 loge("callSessionUpdateFailed :: session=" + session + " reasonInfo=" + reasonInfo);
2672
Anthony Lee71382692014-10-30 10:50:10 -07002673 if (isTransientConferenceSession(session)) {
Anthony Lee68048512015-03-18 15:04:18 -07002674 logi("callSessionUpdateFailed :: not supported for transient conference session=" +
Anthony Leeb8799fe2014-11-03 15:13:47 -08002675 session);
Anthony Lee71382692014-10-30 10:50:10 -07002676 return;
2677 }
2678
Wink Savilleef36ef62014-06-11 08:39:38 -07002679 ImsCall.Listener listener;
2680
2681 synchronized(ImsCall.this) {
2682 listener = mListener;
2683 mUpdateRequest = UPDATE_NONE;
2684 }
2685
2686 if (listener != null) {
2687 try {
2688 listener.onCallUpdateFailed(ImsCall.this, reasonInfo);
2689 } catch (Throwable t) {
2690 loge("callSessionUpdateFailed :: ", t);
2691 }
2692 }
2693 }
2694
2695 @Override
Anthony Lee71382692014-10-30 10:50:10 -07002696 public void callSessionUpdateReceived(ImsCallSession session, ImsCallProfile profile) {
Anthony Lee68048512015-03-18 15:04:18 -07002697 logi("callSessionUpdateReceived :: session=" + session + " profile=" + profile);
2698
Anthony Lee71382692014-10-30 10:50:10 -07002699 if (isTransientConferenceSession(session)) {
Anthony Lee68048512015-03-18 15:04:18 -07002700 logi("callSessionUpdateReceived :: not supported for transient conference " +
Anthony Leeb8799fe2014-11-03 15:13:47 -08002701 "session=" + session);
Anthony Lee71382692014-10-30 10:50:10 -07002702 return;
2703 }
2704
Wink Savilleef36ef62014-06-11 08:39:38 -07002705 ImsCall.Listener listener;
2706
2707 synchronized(ImsCall.this) {
2708 listener = mListener;
2709 mProposedCallProfile = profile;
2710 mUpdateRequest = UPDATE_UNSPECIFIED;
2711 }
2712
2713 if (listener != null) {
2714 try {
2715 listener.onCallUpdateReceived(ImsCall.this);
2716 } catch (Throwable t) {
2717 loge("callSessionUpdateReceived :: ", t);
2718 }
2719 }
2720 }
2721
2722 @Override
Anthony Lee71382692014-10-30 10:50:10 -07002723 public void callSessionConferenceExtended(ImsCallSession session, ImsCallSession newSession,
2724 ImsCallProfile profile) {
Anthony Lee68048512015-03-18 15:04:18 -07002725 logi("callSessionConferenceExtended :: session=" + session + " newSession=" +
2726 newSession + ", profile=" + profile);
2727
Anthony Lee71382692014-10-30 10:50:10 -07002728 if (isTransientConferenceSession(session)) {
Anthony Lee68048512015-03-18 15:04:18 -07002729 logi("callSessionConferenceExtended :: not supported for transient conference " +
Anthony Leeb8799fe2014-11-03 15:13:47 -08002730 "session=" + session);
Anthony Lee71382692014-10-30 10:50:10 -07002731 return;
2732 }
2733
Wink Savilleef36ef62014-06-11 08:39:38 -07002734 ImsCall newCall = createNewCall(newSession, profile);
2735
2736 if (newCall == null) {
2737 callSessionConferenceExtendFailed(session, new ImsReasonInfo());
2738 return;
2739 }
2740
2741 ImsCall.Listener listener;
2742
2743 synchronized(ImsCall.this) {
2744 listener = mListener;
2745 mUpdateRequest = UPDATE_NONE;
2746 }
2747
2748 if (listener != null) {
2749 try {
2750 listener.onCallConferenceExtended(ImsCall.this, newCall);
2751 } catch (Throwable t) {
2752 loge("callSessionConferenceExtended :: ", t);
2753 }
2754 }
2755 }
2756
2757 @Override
2758 public void callSessionConferenceExtendFailed(ImsCallSession session,
2759 ImsReasonInfo reasonInfo) {
Anthony Lee68048512015-03-18 15:04:18 -07002760 loge("callSessionConferenceExtendFailed :: reasonInfo=" + reasonInfo);
2761
Anthony Lee71382692014-10-30 10:50:10 -07002762 if (isTransientConferenceSession(session)) {
Anthony Lee68048512015-03-18 15:04:18 -07002763 logi("callSessionConferenceExtendFailed :: not supported for transient " +
Anthony Leeb8799fe2014-11-03 15:13:47 -08002764 "conference session=" + session);
Anthony Lee71382692014-10-30 10:50:10 -07002765 return;
2766 }
2767
Wink Savilleef36ef62014-06-11 08:39:38 -07002768 ImsCall.Listener listener;
2769
2770 synchronized(ImsCall.this) {
2771 listener = mListener;
2772 mUpdateRequest = UPDATE_NONE;
2773 }
2774
2775 if (listener != null) {
2776 try {
2777 listener.onCallConferenceExtendFailed(ImsCall.this, reasonInfo);
2778 } catch (Throwable t) {
2779 loge("callSessionConferenceExtendFailed :: ", t);
2780 }
2781 }
2782 }
2783
2784 @Override
2785 public void callSessionConferenceExtendReceived(ImsCallSession session,
2786 ImsCallSession newSession, ImsCallProfile profile) {
Anthony Lee68048512015-03-18 15:04:18 -07002787 logi("callSessionConferenceExtendReceived :: newSession=" + newSession +
2788 ", profile=" + profile);
2789
Anthony Lee71382692014-10-30 10:50:10 -07002790 if (isTransientConferenceSession(session)) {
Anthony Lee68048512015-03-18 15:04:18 -07002791 logi("callSessionConferenceExtendReceived :: not supported for transient " +
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08002792 "conference session" + session);
Anthony Lee71382692014-10-30 10:50:10 -07002793 return;
2794 }
2795
Wink Savilleef36ef62014-06-11 08:39:38 -07002796 ImsCall newCall = createNewCall(newSession, profile);
2797
2798 if (newCall == null) {
2799 // Should all the calls be terminated...???
2800 return;
2801 }
2802
2803 ImsCall.Listener listener;
2804
2805 synchronized(ImsCall.this) {
2806 listener = mListener;
2807 }
2808
2809 if (listener != null) {
2810 try {
2811 listener.onCallConferenceExtendReceived(ImsCall.this, newCall);
2812 } catch (Throwable t) {
2813 loge("callSessionConferenceExtendReceived :: ", t);
2814 }
2815 }
2816 }
2817
2818 @Override
2819 public void callSessionInviteParticipantsRequestDelivered(ImsCallSession session) {
Anthony Lee68048512015-03-18 15:04:18 -07002820 logi("callSessionInviteParticipantsRequestDelivered ::");
2821
Anthony Lee71382692014-10-30 10:50:10 -07002822 if (isTransientConferenceSession(session)) {
Anthony Lee68048512015-03-18 15:04:18 -07002823 logi("callSessionInviteParticipantsRequestDelivered :: not supported for " +
Anthony Lee71382692014-10-30 10:50:10 -07002824 "conference session=" + session);
2825 return;
2826 }
2827
Wink Savilleef36ef62014-06-11 08:39:38 -07002828 ImsCall.Listener listener;
2829
2830 synchronized(ImsCall.this) {
2831 listener = mListener;
2832 }
2833
2834 if (listener != null) {
2835 try {
2836 listener.onCallInviteParticipantsRequestDelivered(ImsCall.this);
2837 } catch (Throwable t) {
2838 loge("callSessionInviteParticipantsRequestDelivered :: ", t);
2839 }
2840 }
2841 }
2842
2843 @Override
2844 public void callSessionInviteParticipantsRequestFailed(ImsCallSession session,
2845 ImsReasonInfo reasonInfo) {
Anthony Lee68048512015-03-18 15:04:18 -07002846 loge("callSessionInviteParticipantsRequestFailed :: reasonInfo=" + reasonInfo);
2847
Anthony Lee71382692014-10-30 10:50:10 -07002848 if (isTransientConferenceSession(session)) {
Anthony Lee68048512015-03-18 15:04:18 -07002849 logi("callSessionInviteParticipantsRequestFailed :: not supported for " +
Anthony Lee71382692014-10-30 10:50:10 -07002850 "conference session=" + session);
2851 return;
2852 }
2853
Wink Savilleef36ef62014-06-11 08:39:38 -07002854 ImsCall.Listener listener;
2855
2856 synchronized(ImsCall.this) {
2857 listener = mListener;
2858 }
2859
2860 if (listener != null) {
2861 try {
2862 listener.onCallInviteParticipantsRequestFailed(ImsCall.this, reasonInfo);
2863 } catch (Throwable t) {
2864 loge("callSessionInviteParticipantsRequestFailed :: ", t);
2865 }
2866 }
2867 }
2868
2869 @Override
2870 public void callSessionRemoveParticipantsRequestDelivered(ImsCallSession session) {
Anthony Lee68048512015-03-18 15:04:18 -07002871 logi("callSessionRemoveParticipantsRequestDelivered ::");
2872
Anthony Lee71382692014-10-30 10:50:10 -07002873 if (isTransientConferenceSession(session)) {
Anthony Lee68048512015-03-18 15:04:18 -07002874 logi("callSessionRemoveParticipantsRequestDelivered :: not supported for " +
Anthony Lee71382692014-10-30 10:50:10 -07002875 "conference session=" + session);
2876 return;
2877 }
2878
Wink Savilleef36ef62014-06-11 08:39:38 -07002879 ImsCall.Listener listener;
2880
2881 synchronized(ImsCall.this) {
2882 listener = mListener;
2883 }
2884
2885 if (listener != null) {
2886 try {
2887 listener.onCallRemoveParticipantsRequestDelivered(ImsCall.this);
2888 } catch (Throwable t) {
2889 loge("callSessionRemoveParticipantsRequestDelivered :: ", t);
2890 }
2891 }
2892 }
2893
2894 @Override
2895 public void callSessionRemoveParticipantsRequestFailed(ImsCallSession session,
2896 ImsReasonInfo reasonInfo) {
Anthony Lee68048512015-03-18 15:04:18 -07002897 loge("callSessionRemoveParticipantsRequestFailed :: reasonInfo=" + reasonInfo);
2898
Anthony Lee71382692014-10-30 10:50:10 -07002899 if (isTransientConferenceSession(session)) {
Anthony Lee68048512015-03-18 15:04:18 -07002900 logi("callSessionRemoveParticipantsRequestFailed :: not supported for " +
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08002901 "conference session=" + session);
Anthony Lee71382692014-10-30 10:50:10 -07002902 return;
2903 }
2904
Wink Savilleef36ef62014-06-11 08:39:38 -07002905 ImsCall.Listener listener;
2906
2907 synchronized(ImsCall.this) {
2908 listener = mListener;
2909 }
2910
2911 if (listener != null) {
2912 try {
2913 listener.onCallRemoveParticipantsRequestFailed(ImsCall.this, reasonInfo);
2914 } catch (Throwable t) {
2915 loge("callSessionRemoveParticipantsRequestFailed :: ", t);
2916 }
2917 }
2918 }
2919
2920 @Override
2921 public void callSessionConferenceStateUpdated(ImsCallSession session,
2922 ImsConferenceState state) {
Anthony Lee68048512015-03-18 15:04:18 -07002923 logi("callSessionConferenceStateUpdated :: state=" + state);
2924
Tyler Gunn938116f2014-10-27 09:15:23 -07002925 conferenceStateUpdated(state);
Wink Savilleef36ef62014-06-11 08:39:38 -07002926 }
2927
2928 @Override
Anthony Lee71382692014-10-30 10:50:10 -07002929 public void callSessionUssdMessageReceived(ImsCallSession session, int mode,
2930 String ussdMessage) {
Anthony Lee68048512015-03-18 15:04:18 -07002931 logi("callSessionUssdMessageReceived :: mode=" + mode + ", ussdMessage=" +
2932 ussdMessage);
2933
Anthony Lee71382692014-10-30 10:50:10 -07002934 if (isTransientConferenceSession(session)) {
Anthony Lee68048512015-03-18 15:04:18 -07002935 logi("callSessionUssdMessageReceived :: not supported for transient " +
Anthony Leeb8799fe2014-11-03 15:13:47 -08002936 "conference session=" + session);
Anthony Lee71382692014-10-30 10:50:10 -07002937 return;
2938 }
2939
Wink Savilleef36ef62014-06-11 08:39:38 -07002940 ImsCall.Listener listener;
2941
2942 synchronized(ImsCall.this) {
2943 listener = mListener;
2944 }
2945
2946 if (listener != null) {
2947 try {
2948 listener.onCallUssdMessageReceived(ImsCall.this, mode, ussdMessage);
2949 } catch (Throwable t) {
2950 loge("callSessionUssdMessageReceived :: ", t);
2951 }
2952 }
2953 }
Pavel Zhamaitsiak987bab82014-11-16 15:29:09 -08002954
2955 @Override
2956 public void callSessionTtyModeReceived(ImsCallSession session, int mode) {
Anthony Lee68048512015-03-18 15:04:18 -07002957 logi("callSessionTtyModeReceived :: mode=" + mode);
Pavel Zhamaitsiak987bab82014-11-16 15:29:09 -08002958
Pavel Zhamaitsiaka6cae362014-12-10 17:31:33 -08002959 ImsCall.Listener listener;
2960
2961 synchronized(ImsCall.this) {
2962 listener = mListener;
2963 }
2964
2965 if (listener != null) {
2966 try {
2967 listener.onCallSessionTtyModeReceived(ImsCall.this, mode);
2968 } catch (Throwable t) {
2969 loge("callSessionTtyModeReceived :: ", t);
Pavel Zhamaitsiak987bab82014-11-16 15:29:09 -08002970 }
2971 }
2972 }
Rekha Kumar14631742015-02-04 10:47:00 -08002973
Tyler Gunn25394092015-04-01 09:40:02 -07002974 /**
2975 * Notifies of a change to the multiparty state for this {@code ImsCallSession}.
2976 *
2977 * @param session The call session.
2978 * @param isMultiParty {@code true} if the session became multiparty, {@code false}
2979 * otherwise.
2980 */
2981 @Override
2982 public void callSessionMultipartyStateChanged(ImsCallSession session,
2983 boolean isMultiParty) {
2984 if (VDBG) {
Pavel Zhamaitsiak691a1cc2015-04-09 10:14:55 -07002985 logi("callSessionMultipartyStateChanged isMultiParty: " + (isMultiParty ? "Y"
Tyler Gunn25394092015-04-01 09:40:02 -07002986 : "N"));
2987 }
2988
2989 ImsCall.Listener listener;
2990
2991 synchronized(ImsCall.this) {
2992 listener = mListener;
2993 }
2994
2995 if (listener != null) {
2996 try {
2997 listener.onMultipartyStateChanged(ImsCall.this, isMultiParty);
2998 } catch (Throwable t) {
2999 loge("callSessionMultipartyStateChanged :: ", t);
3000 }
3001 }
3002 }
3003
Rekha Kumar14631742015-02-04 10:47:00 -08003004 public void callSessionHandover(ImsCallSession session, int srcAccessTech,
3005 int targetAccessTech, ImsReasonInfo reasonInfo) {
Anthony Lee68048512015-03-18 15:04:18 -07003006 logi("callSessionHandover :: session=" + session + ", srcAccessTech=" +
3007 srcAccessTech + ", targetAccessTech=" + targetAccessTech + ", reasonInfo=" +
3008 reasonInfo);
Rekha Kumar14631742015-02-04 10:47:00 -08003009
3010 ImsCall.Listener listener;
3011
3012 synchronized(ImsCall.this) {
3013 listener = mListener;
3014 }
3015
3016 if (listener != null) {
3017 try {
3018 listener.onCallHandover(ImsCall.this, srcAccessTech, targetAccessTech,
3019 reasonInfo);
3020 } catch (Throwable t) {
3021 loge("callSessionHandover :: ", t);
3022 }
3023 }
3024 }
3025
3026 @Override
3027 public void callSessionHandoverFailed(ImsCallSession session, int srcAccessTech,
3028 int targetAccessTech, ImsReasonInfo reasonInfo) {
Anthony Lee68048512015-03-18 15:04:18 -07003029 loge("callSessionHandoverFailed :: session=" + session + ", srcAccessTech=" +
3030 srcAccessTech + ", targetAccessTech=" + targetAccessTech + ", reasonInfo=" +
3031 reasonInfo);
Rekha Kumar14631742015-02-04 10:47:00 -08003032
3033 ImsCall.Listener listener;
3034
3035 synchronized(ImsCall.this) {
3036 listener = mListener;
3037 }
3038
3039 if (listener != null) {
3040 try {
3041 listener.onCallHandoverFailed(ImsCall.this, srcAccessTech, targetAccessTech,
3042 reasonInfo);
3043 } catch (Throwable t) {
3044 loge("callSessionHandoverFailed :: ", t);
3045 }
3046 }
3047 }
Shriram Ganeshd3adfad2015-05-31 10:06:15 -07003048
3049 @Override
3050 public void callSessionSuppServiceReceived(ImsCallSession session,
3051 ImsSuppServiceNotification suppServiceInfo ) {
3052 if (isTransientConferenceSession(session)) {
3053 logi("callSessionSuppServiceReceived :: not supported for transient conference"
3054 + " session=" + session);
3055 return;
3056 }
3057
3058 logi("callSessionSuppServiceReceived :: session=" + session +
3059 ", suppServiceInfo" + suppServiceInfo);
3060
3061 ImsCall.Listener listener;
3062
3063 synchronized(ImsCall.this) {
3064 listener = mListener;
3065 }
3066
3067 if (listener != null) {
3068 try {
3069 listener.onCallSuppServiceReceived(ImsCall.this, suppServiceInfo);
3070 } catch (Throwable t) {
3071 loge("callSessionSuppServiceReceived :: ", t);
3072 }
3073 }
3074 }
Hall Liubd3f3772017-03-22 15:38:23 -07003075
3076 @Override
3077 public void callSessionRttModifyRequestReceived(ImsCallSession session,
3078 ImsCallProfile callProfile) {
3079 ImsCall.Listener listener;
3080
3081 synchronized(ImsCall.this) {
3082 listener = mListener;
3083 }
3084
3085 if (!callProfile.mMediaProfile.isRttCall()) {
3086 logi("callSessionRttModifyRequestReceived:: ignoring request, requested profile " +
3087 "is not RTT.");
3088 return;
3089 }
3090
3091 if (listener != null) {
3092 try {
3093 listener.onRttModifyRequestReceived(ImsCall.this);
3094 } catch (Throwable t) {
3095 loge("callSessionRttModifyRequestReceived:: ", t);
3096 }
3097 }
3098 }
3099
3100 @Override
3101 public void callSessionRttModifyResponseReceived(int status) {
3102 ImsCall.Listener listener;
3103
3104 synchronized(ImsCall.this) {
3105 listener = mListener;
3106 }
3107
3108 if (listener != null) {
3109 try {
3110 listener.onRttModifyResponseReceived(ImsCall.this, status);
3111 } catch (Throwable t) {
3112 loge("callSessionRttModifyResponseReceived:: ", t);
3113 }
3114 }
3115 }
3116
3117 @Override
3118 public void callSessionRttMessageReceived(String rttMessage) {
3119 ImsCall.Listener listener;
3120
3121 synchronized(ImsCall.this) {
3122 listener = mListener;
3123 }
3124
3125 if (listener != null) {
3126 try {
3127 listener.onRttMessageReceived(ImsCall.this, rttMessage);
3128 } catch (Throwable t) {
3129 loge("callSessionRttModifyResponseReceived:: ", t);
3130 }
3131 }
3132 }
Wink Savilleef36ef62014-06-11 08:39:38 -07003133 }
Tyler Gunn938116f2014-10-27 09:15:23 -07003134
3135 /**
3136 * Report a new conference state to the current {@link ImsCall} and inform listeners of the
3137 * change. Marked as {@code VisibleForTesting} so that the
3138 * {@code com.android.internal.telephony.TelephonyTester} class can inject a test conference
3139 * event package into a regular ongoing IMS call.
3140 *
3141 * @param state The {@link ImsConferenceState}.
3142 */
3143 @VisibleForTesting
3144 public void conferenceStateUpdated(ImsConferenceState state) {
3145 Listener listener;
3146
3147 synchronized(this) {
3148 notifyConferenceStateUpdated(state);
3149 listener = mListener;
3150 }
3151
3152 if (listener != null) {
3153 try {
3154 listener.onCallConferenceStateUpdated(this, state);
3155 } catch (Throwable t) {
3156 loge("callSessionConferenceStateUpdated :: ", t);
3157 }
3158 }
3159 }
Tyler Gunn168c6342014-11-18 08:40:49 -08003160
3161 /**
3162 * Provides a human-readable string representation of an update request.
3163 *
3164 * @param updateRequest The update request.
3165 * @return The string representation.
3166 */
3167 private String updateRequestToString(int updateRequest) {
3168 switch (updateRequest) {
3169 case UPDATE_NONE:
3170 return "NONE";
3171 case UPDATE_HOLD:
3172 return "HOLD";
3173 case UPDATE_HOLD_MERGE:
3174 return "HOLD_MERGE";
3175 case UPDATE_RESUME:
3176 return "RESUME";
3177 case UPDATE_MERGE:
3178 return "MERGE";
3179 case UPDATE_EXTEND_TO_CONFERENCE:
3180 return "EXTEND_TO_CONFERENCE";
3181 case UPDATE_UNSPECIFIED:
3182 return "UNSPECIFIED";
3183 default:
3184 return "UNKNOWN";
3185 }
3186 }
3187
3188 /**
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08003189 * Clears the merge peer for this call, ensuring that the peer's connection to this call is also
3190 * severed at the same time.
3191 */
Anthony Leec479f662015-02-11 17:04:35 -08003192 private void clearMergeInfo() {
Anthony Lee68048512015-03-18 15:04:18 -07003193 if (CONF_DBG) {
3194 logi("clearMergeInfo :: clearing all merge info");
Anthony Leec479f662015-02-11 17:04:35 -08003195 }
Tyler Gunn047d8102015-01-30 15:21:11 -08003196
Anthony Leec479f662015-02-11 17:04:35 -08003197 // First clear out the merge partner then clear ourselves out.
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08003198 if (mMergeHost != null) {
3199 mMergeHost.mMergePeer = null;
Libin.Tang@motorola.com165aed52015-02-05 22:12:09 -06003200 mMergeHost.mUpdateRequest = UPDATE_NONE;
Anthony Leec479f662015-02-11 17:04:35 -08003201 mMergeHost.mCallSessionMergePending = false;
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08003202 }
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08003203 if (mMergePeer != null) {
3204 mMergePeer.mMergeHost = null;
Libin.Tang@motorola.com165aed52015-02-05 22:12:09 -06003205 mMergePeer.mUpdateRequest = UPDATE_NONE;
Anthony Leec479f662015-02-11 17:04:35 -08003206 mMergePeer.mCallSessionMergePending = false;
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08003207 }
Anthony Leec479f662015-02-11 17:04:35 -08003208 mMergeHost = null;
3209 mMergePeer = null;
3210 mUpdateRequest = UPDATE_NONE;
3211 mCallSessionMergePending = false;
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08003212 }
3213
3214 /**
3215 * Sets the merge peer for the current call. The merge peer is the background call that will be
3216 * merged into this call. On the merge peer, sets the merge host to be this call.
3217 *
3218 * @param mergePeer The peer call to be merged into this one.
3219 */
3220 private void setMergePeer(ImsCall mergePeer) {
3221 mMergePeer = mergePeer;
3222 mMergeHost = null;
3223
3224 mergePeer.mMergeHost = ImsCall.this;
3225 mergePeer.mMergePeer = null;
3226 }
3227
3228 /**
3229 * Sets the merge hody for the current call. The merge host is the foreground call this call
3230 * will be merged into. On the merge host, sets the merge peer to be this call.
3231 *
3232 * @param mergeHost The merge host this call will be merged into.
3233 */
3234 public void setMergeHost(ImsCall mergeHost) {
3235 mMergeHost = mergeHost;
3236 mMergePeer = null;
3237
3238 mergeHost.mMergeHost = null;
3239 mergeHost.mMergePeer = ImsCall.this;
3240 }
3241
3242 /**
3243 * Determines if the current call is in the process of merging with another call or conference.
3244 *
3245 * @return {@code true} if in the process of merging.
3246 */
3247 private boolean isMerging() {
3248 return mMergePeer != null || mMergeHost != null;
3249 }
3250
3251 /**
3252 * Determines if the current call is the host of the merge.
3253 *
3254 * @return {@code true} if the call is the merge host.
3255 */
3256 private boolean isMergeHost() {
3257 return mMergePeer != null && mMergeHost == null;
3258 }
3259
3260 /**
3261 * Determines if the current call is the peer of the merge.
3262 *
3263 * @return {@code true} if the call is the merge peer.
3264 */
3265 private boolean isMergePeer() {
3266 return mMergePeer == null && mMergeHost != null;
3267 }
3268
3269 /**
Tyler Gunn047d8102015-01-30 15:21:11 -08003270 * Determines if the call session is pending merge into a conference or not.
3271 *
3272 * @return {@code true} if a merge into a conference is pending, {@code false} otherwise.
3273 */
Tyler Gunn083efaa2017-06-20 15:50:32 -07003274 public boolean isCallSessionMergePending() {
Tyler Gunn047d8102015-01-30 15:21:11 -08003275 return mCallSessionMergePending;
3276 }
3277
3278 /**
3279 * Sets flag indicating whether the call session is pending merge into a conference or not.
3280 *
3281 * @param callSessionMergePending {@code true} if a merge into the conference is pending,
3282 * {@code false} otherwise.
3283 */
3284 private void setCallSessionMergePending(boolean callSessionMergePending) {
3285 mCallSessionMergePending = callSessionMergePending;
3286 }
3287
3288 /**
3289 * Determines if there is a conference merge in process. If there is a merge in process,
3290 * determines if both the merge host and peer sessions have completed the merge process. This
3291 * means that we have received terminate or hold signals for the sessions, indicating that they
3292 * are no longer in the process of being merged into the conference.
3293 * <p>
Anthony Leec479f662015-02-11 17:04:35 -08003294 * The sessions are considered to have merged if: both calls still have merge peer/host
3295 * relationships configured, both sessions are not waiting to be merged into the conference,
3296 * and the transient conference session is alive in the case of an initial conference.
Tyler Gunn047d8102015-01-30 15:21:11 -08003297 *
3298 * @return {@code true} where the host and peer sessions have finished merging into the
3299 * conference, {@code false} if the merge has not yet completed, and {@code false} if there
3300 * is no conference merge in progress.
3301 */
Anthony Leec479f662015-02-11 17:04:35 -08003302 private boolean shouldProcessConferenceResult() {
3303 boolean areMergeTriggersDone = false;
Tyler Gunn047d8102015-01-30 15:21:11 -08003304
Anthony Leec479f662015-02-11 17:04:35 -08003305 synchronized (ImsCall.this) {
3306 // if there is a merge going on, then the merge host/peer relationships should have been
3307 // set up. This works for both the initial conference or merging a call into an
3308 // existing conference.
3309 if (!isMergeHost() && !isMergePeer()) {
Anthony Lee68048512015-03-18 15:04:18 -07003310 if (CONF_DBG) {
3311 loge("shouldProcessConferenceResult :: no merge in progress");
Anthony Leec479f662015-02-11 17:04:35 -08003312 }
3313 return false;
3314 }
3315
3316 // There is a merge in progress, so check the sessions to ensure:
3317 // 1. Both calls have completed being merged (or failing to merge) into the conference.
3318 // 2. The transient conference session is alive.
3319 if (isMergeHost()) {
Anthony Lee68048512015-03-18 15:04:18 -07003320 if (CONF_DBG) {
3321 logi("shouldProcessConferenceResult :: We are a merge host");
3322 logi("shouldProcessConferenceResult :: Here is the merge peer=" + mMergePeer);
Anthony Leec479f662015-02-11 17:04:35 -08003323 }
3324 areMergeTriggersDone = !isCallSessionMergePending() &&
3325 !mMergePeer.isCallSessionMergePending();
3326 if (!isMultiparty()) {
3327 // Only check the transient session when there is no existing conference
3328 areMergeTriggersDone &= isSessionAlive(mTransientConferenceSession);
3329 }
3330 } else if (isMergePeer()) {
Anthony Lee68048512015-03-18 15:04:18 -07003331 if (CONF_DBG) {
3332 logi("shouldProcessConferenceResult :: We are a merge peer");
3333 logi("shouldProcessConferenceResult :: Here is the merge host=" + mMergeHost);
Anthony Leec479f662015-02-11 17:04:35 -08003334 }
3335 areMergeTriggersDone = !isCallSessionMergePending() &&
3336 !mMergeHost.isCallSessionMergePending();
3337 if (!mMergeHost.isMultiparty()) {
3338 // Only check the transient session when there is no existing conference
3339 areMergeTriggersDone &= isSessionAlive(mMergeHost.mTransientConferenceSession);
3340 } else {
3341 // This else block is a special case for Verizon to handle these steps
3342 // 1. Establish a conference call.
3343 // 2. Add a new call (conference in in BG)
3344 // 3. Swap (conference active on FG)
3345 // 4. Merge
3346 // What happens here is that the BG call gets a terminated callback
3347 // because it was added to the conference. I've seen where
3348 // the FG gets no callback at all because its already active.
3349 // So if we continue to wait for it to set its isCallSessionMerging
3350 // flag to false...we'll be waiting forever.
3351 areMergeTriggersDone = !isCallSessionMergePending();
3352 }
3353 } else {
3354 // Realistically this shouldn't happen, but best to be safe.
3355 loge("shouldProcessConferenceResult : merge in progress but call is neither" +
Anthony Lee68048512015-03-18 15:04:18 -07003356 " host nor peer.");
Anthony Leec479f662015-02-11 17:04:35 -08003357 }
Anthony Lee68048512015-03-18 15:04:18 -07003358 if (CONF_DBG) {
3359 logi("shouldProcessConferenceResult :: returning:" +
Anthony Leec479f662015-02-11 17:04:35 -08003360 (areMergeTriggersDone ? "true" : "false"));
3361 }
Tyler Gunn047d8102015-01-30 15:21:11 -08003362 }
Anthony Leec479f662015-02-11 17:04:35 -08003363 return areMergeTriggersDone;
Tyler Gunn047d8102015-01-30 15:21:11 -08003364 }
3365
3366 /**
Tyler Gunn168c6342014-11-18 08:40:49 -08003367 * Provides a string representation of the {@link ImsCall}. Primarily intended for use in log
3368 * statements.
3369 *
3370 * @return String representation of call.
3371 */
3372 @Override
3373 public String toString() {
3374 StringBuilder sb = new StringBuilder();
3375 sb.append("[ImsCall objId:");
3376 sb.append(System.identityHashCode(this));
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08003377 sb.append(" onHold:");
3378 sb.append(isOnHold() ? "Y" : "N");
3379 sb.append(" mute:");
3380 sb.append(isMuted() ? "Y" : "N");
Pavel Zhamaitsiak50b03e92016-02-29 14:47:39 -08003381 if (mCallProfile != null) {
Jack Yu9f71a242016-08-08 11:59:18 -07003382 sb.append(" mCallProfile:" + mCallProfile);
Pavel Zhamaitsiak50b03e92016-02-29 14:47:39 -08003383 sb.append(" tech:");
3384 sb.append(mCallProfile.getCallExtra(ImsCallProfile.EXTRA_CALL_RAT_TYPE));
3385 }
Tyler Gunn168c6342014-11-18 08:40:49 -08003386 sb.append(" updateRequest:");
3387 sb.append(updateRequestToString(mUpdateRequest));
Tyler Gunn87466c52014-12-08 09:56:17 -08003388 sb.append(" merging:");
3389 sb.append(isMerging() ? "Y" : "N");
3390 if (isMerging()) {
3391 if (isMergePeer()) {
3392 sb.append("P");
3393 } else {
3394 sb.append("H");
3395 }
3396 }
Anthony Leec479f662015-02-11 17:04:35 -08003397 sb.append(" merge action pending:");
3398 sb.append(isCallSessionMergePending() ? "Y" : "N");
Tyler Gunn87466c52014-12-08 09:56:17 -08003399 sb.append(" merged:");
3400 sb.append(isMerged() ? "Y" : "N");
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08003401 sb.append(" multiParty:");
3402 sb.append(isMultiparty() ? "Y" : "N");
Tyler Gunn25394092015-04-01 09:40:02 -07003403 sb.append(" confHost:");
3404 sb.append(isConferenceHost() ? "Y" : "N");
Anthony Leec479f662015-02-11 17:04:35 -08003405 sb.append(" buried term:");
3406 sb.append(mSessionEndDuringMerge ? "Y" : "N");
Jack Yu9f71a242016-08-08 11:59:18 -07003407 sb.append(" isVideo: ");
3408 sb.append(isVideoCall() ? "Y" : "N");
Tyler Gunn1ac24852016-06-22 10:04:23 -07003409 sb.append(" wasVideo: ");
3410 sb.append(mWasVideoCall ? "Y" : "N");
Jack Yu9f71a242016-08-08 11:59:18 -07003411 sb.append(" isWifi: ");
3412 sb.append(isWifiCall() ? "Y" : "N");
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08003413 sb.append(" session:");
3414 sb.append(mSession);
Tyler Gunn168c6342014-11-18 08:40:49 -08003415 sb.append(" transientSession:");
3416 sb.append(mTransientConferenceSession);
3417 sb.append("]");
3418 return sb.toString();
3419 }
Anthony Lee68048512015-03-18 15:04:18 -07003420
3421 private void throwImsException(Throwable t, int code) throws ImsException {
3422 if (t instanceof ImsException) {
3423 throw (ImsException) t;
3424 } else {
3425 throw new ImsException(String.valueOf(code), t, code);
3426 }
3427 }
3428
3429 /**
3430 * Append the ImsCall information to the provided string. Usefull for as a logging helper.
3431 * @param s The original string
3432 * @return The original string with {@code ImsCall} information appended to it.
3433 */
3434 private String appendImsCallInfoToString(String s) {
3435 StringBuilder sb = new StringBuilder();
3436 sb.append(s);
3437 sb.append(" ImsCall=");
3438 sb.append(ImsCall.this);
3439 return sb.toString();
3440 }
3441
3442 /**
Tyler Gunn1ac24852016-06-22 10:04:23 -07003443 * Updates {@link #mWasVideoCall} based on the current {@link ImsCallProfile} for the call.
3444 *
3445 * @param profile The current {@link ImsCallProfile} for the call.
3446 */
3447 private void trackVideoStateHistory(ImsCallProfile profile) {
3448 mWasVideoCall = mWasVideoCall || profile.isVideoCall();
3449 }
3450
3451 /**
3452 * @return {@code true} if this call was a video call at some point in its life span,
3453 * {@code false} otherwise.
3454 */
3455 public boolean wasVideoCall() {
3456 return mWasVideoCall;
3457 }
3458
3459 /**
3460 * @return {@code true} if this call is a video call, {@code false} otherwise.
3461 */
3462 public boolean isVideoCall() {
3463 synchronized(mLockObj) {
3464 return mCallProfile != null && mCallProfile.isVideoCall();
3465 }
3466 }
3467
3468 /**
3469 * Determines if the current call radio access technology is over WIFI.
3470 * Note: This depends on the RIL exposing the {@link ImsCallProfile#EXTRA_CALL_RAT_TYPE} extra.
3471 * This method is primarily intended to be used when checking if answering an incoming audio
3472 * call should cause a wifi video call to drop (e.g.
3473 * {@link android.telephony.CarrierConfigManager#
3474 * KEY_DROP_VIDEO_CALL_WHEN_ANSWERING_AUDIO_CALL_BOOL} is set).
3475 *
3476 * @return {@code true} if the call is over WIFI, {@code false} otherwise.
3477 */
3478 public boolean isWifiCall() {
3479 synchronized(mLockObj) {
3480 if (mCallProfile == null) {
3481 return false;
3482 }
Tyler Gunn730752a2017-08-17 15:55:53 -07003483 int radioTechnology = getRadioTechnology();
3484 return radioTechnology == ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN;
3485 }
3486 }
3487
3488 /**
3489 * Determines the radio access technology for the {@link ImsCall}.
3490 * @return The {@link ServiceState} {@code RIL_RADIO_TECHNOLOGY_*} code in use.
3491 */
3492 public int getRadioTechnology() {
3493 synchronized(mLockObj) {
3494 if (mCallProfile == null) {
3495 return ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN;
3496 }
Tyler Gunn1ac24852016-06-22 10:04:23 -07003497 String callType = mCallProfile.getCallExtra(ImsCallProfile.EXTRA_CALL_RAT_TYPE);
Tyler Gunn8ae5fbb2016-07-13 17:24:04 -07003498 if (callType == null || callType.isEmpty()) {
3499 callType = mCallProfile.getCallExtra(ImsCallProfile.EXTRA_CALL_RAT_TYPE_ALT);
3500 }
Tyler Gunn1ac24852016-06-22 10:04:23 -07003501
3502 // The RIL (sadly) sends us the EXTRA_CALL_RAT_TYPE as a string extra, rather than an
3503 // integer extra, so we need to parse it.
3504 int radioTechnology = ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN;
3505 try {
3506 radioTechnology = Integer.parseInt(callType);
3507 } catch (NumberFormatException nfe) {
3508 radioTechnology = ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN;
3509 }
3510
Tyler Gunn730752a2017-08-17 15:55:53 -07003511 return radioTechnology;
Tyler Gunn1ac24852016-06-22 10:04:23 -07003512 }
3513 }
3514
3515 /**
Anthony Lee68048512015-03-18 15:04:18 -07003516 * Log a string to the radio buffer at the info level.
3517 * @param s The message to log
3518 */
3519 private void logi(String s) {
3520 Log.i(TAG, appendImsCallInfoToString(s));
3521 }
3522
3523 /**
3524 * Log a string to the radio buffer at the debug level.
3525 * @param s The message to log
3526 */
3527 private void logd(String s) {
3528 Log.d(TAG, appendImsCallInfoToString(s));
3529 }
3530
3531 /**
3532 * Log a string to the radio buffer at the verbose level.
3533 * @param s The message to log
3534 */
3535 private void logv(String s) {
3536 Log.v(TAG, appendImsCallInfoToString(s));
3537 }
3538
3539 /**
3540 * Log a string to the radio buffer at the error level.
3541 * @param s The message to log
3542 */
3543 private void loge(String s) {
3544 Log.e(TAG, appendImsCallInfoToString(s));
3545 }
3546
3547 /**
3548 * Log a string to the radio buffer at the error level with a throwable
3549 * @param s The message to log
3550 * @param t The associated throwable
3551 */
3552 private void loge(String s, Throwable t) {
3553 Log.e(TAG, appendImsCallInfoToString(s), t);
3554 }
Wink Savilleef36ef62014-06-11 08:39:38 -07003555}