blob: 40d9faa2ac53ba3c4946a5dfc2b07044fcd43442 [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 Ebinger190ed932018-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 Ebinger190ed932018-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 /**
Hall Liu43213e22018-02-09 16:55:05 -0800608 * When true, if this call is incoming, it will be answered with an
609 * {@link ImsStreamMediaProfile} that has RTT enabled.
610 */
611 private boolean mAnswerWithRtt = false;
612
613 /**
Wink Savilleef36ef62014-06-11 08:39:38 -0700614 * Create an IMS call object.
615 *
616 * @param context the context for accessing system services
617 * @param profile the call profile to make/take a call
618 */
619 public ImsCall(Context context, ImsCallProfile profile) {
620 mContext = context;
Tyler Gunn1ac24852016-06-22 10:04:23 -0700621 setCallProfile(profile);
Jack Yub42ff552016-06-22 13:20:52 -0700622 uniqueId = sUniqueIdGenerator.getAndIncrement();
Wink Savilleef36ef62014-06-11 08:39:38 -0700623 }
624
625 /**
626 * Closes this object. This object is not usable after being closed.
627 */
628 @Override
629 public void close() {
630 synchronized(mLockObj) {
Wink Savilleef36ef62014-06-11 08:39:38 -0700631 if (mSession != null) {
632 mSession.close();
633 mSession = null;
Uma Maheswari Ramalingam95be7c62015-05-06 23:08:17 -0700634 } else {
635 logi("close :: Cannot close Null call session!");
Wink Savilleef36ef62014-06-11 08:39:38 -0700636 }
637
638 mCallProfile = null;
639 mProposedCallProfile = null;
640 mLastReasonInfo = null;
641 mMediaSession = null;
642 }
643 }
644
645 /**
646 * Checks if the call has a same remote user identity or not.
647 *
648 * @param userId the remote user identity
649 * @return true if the remote user identity is equal; otherwise, false
650 */
651 @Override
652 public boolean checkIfRemoteUserIsSame(String userId) {
653 if (userId == null) {
654 return false;
655 }
656
657 return userId.equals(mCallProfile.getCallExtra(ImsCallProfile.EXTRA_REMOTE_URI, ""));
658 }
659
660 /**
661 * Checks if the call is equal or not.
662 *
663 * @param call the call to be compared
664 * @return true if the call is equal; otherwise, false
665 */
666 @Override
667 public boolean equalsTo(ICall call) {
668 if (call == null) {
669 return false;
670 }
671
672 if (call instanceof ImsCall) {
Etan Cohen16b3b362014-10-24 11:10:58 -0700673 return this.equals(call);
Wink Savilleef36ef62014-06-11 08:39:38 -0700674 }
675
676 return false;
677 }
678
Anthony Leec479f662015-02-11 17:04:35 -0800679 public static boolean isSessionAlive(ImsCallSession session) {
680 return session != null && session.isAlive();
681 }
682
Wink Savilleef36ef62014-06-11 08:39:38 -0700683 /**
684 * Gets the negotiated (local & remote) call profile.
685 *
686 * @return a {@link ImsCallProfile} object that has the negotiated call profile
687 */
688 public ImsCallProfile getCallProfile() {
689 synchronized(mLockObj) {
690 return mCallProfile;
691 }
692 }
693
694 /**
Tyler Gunn1ac24852016-06-22 10:04:23 -0700695 * Replaces the current call profile with a new one, tracking whethere this was previously a
696 * video call or not.
697 *
698 * @param profile The new call profile.
699 */
Tyler Gunncc0f78f2017-12-04 13:01:55 -0800700 @VisibleForTesting
701 public void setCallProfile(ImsCallProfile profile) {
Tyler Gunn1ac24852016-06-22 10:04:23 -0700702 synchronized(mLockObj) {
703 mCallProfile = profile;
704 trackVideoStateHistory(mCallProfile);
705 }
706 }
707
708 /**
Wink Savilleef36ef62014-06-11 08:39:38 -0700709 * Gets the local call profile (local capabilities).
710 *
711 * @return a {@link ImsCallProfile} object that has the local call profile
712 */
713 public ImsCallProfile getLocalCallProfile() throws ImsException {
714 synchronized(mLockObj) {
715 if (mSession == null) {
716 throw new ImsException("No call session",
717 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
718 }
719
720 try {
721 return mSession.getLocalCallProfile();
722 } catch (Throwable t) {
723 loge("getLocalCallProfile :: ", t);
724 throw new ImsException("getLocalCallProfile()", t, 0);
725 }
726 }
727 }
728
729 /**
Shriram Ganeshe8719892014-10-13 18:28:34 -0700730 * Gets the remote call profile (remote capabilities).
731 *
732 * @return a {@link ImsCallProfile} object that has the remote call profile
733 */
734 public ImsCallProfile getRemoteCallProfile() throws ImsException {
735 synchronized(mLockObj) {
736 if (mSession == null) {
737 throw new ImsException("No call session",
738 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
739 }
740
741 try {
742 return mSession.getRemoteCallProfile();
743 } catch (Throwable t) {
744 loge("getRemoteCallProfile :: ", t);
745 throw new ImsException("getRemoteCallProfile()", t, 0);
746 }
747 }
748 }
749
750 /**
Wink Savilleef36ef62014-06-11 08:39:38 -0700751 * Gets the call profile proposed by the local/remote user.
752 *
753 * @return a {@link ImsCallProfile} object that has the proposed call profile
754 */
755 public ImsCallProfile getProposedCallProfile() {
756 synchronized(mLockObj) {
757 if (!isInCall()) {
758 return null;
759 }
760
761 return mProposedCallProfile;
762 }
763 }
764
765 /**
Uma Maheswari Ramalingama1ed7b02015-05-20 14:26:42 -0700766 * Gets the list of conference participants currently
767 * associated with this call.
768 *
Tyler Gunn3a3d8eb2016-08-18 12:58:26 -0700769 * @return Copy of the list of conference participants.
Uma Maheswari Ramalingama1ed7b02015-05-20 14:26:42 -0700770 */
771 public List<ConferenceParticipant> getConferenceParticipants() {
772 synchronized(mLockObj) {
773 logi("getConferenceParticipants :: mConferenceParticipants"
774 + mConferenceParticipants);
Tyler Gunn108a6b72016-08-23 21:05:25 -0700775 if (mConferenceParticipants == null) {
776 return null;
777 }
778 if (mConferenceParticipants.isEmpty()) {
779 return new ArrayList<ConferenceParticipant>(0);
780 }
Tyler Gunn3a3d8eb2016-08-18 12:58:26 -0700781 return new ArrayList<ConferenceParticipant>(mConferenceParticipants);
Uma Maheswari Ramalingama1ed7b02015-05-20 14:26:42 -0700782 }
783 }
784
785 /**
Wink Savilleef36ef62014-06-11 08:39:38 -0700786 * Gets the state of the {@link ImsCallSession} that carries this call.
787 * The value returned must be one of the states in {@link ImsCallSession#State}.
788 *
789 * @return the session state
790 */
791 public int getState() {
792 synchronized(mLockObj) {
793 if (mSession == null) {
794 return ImsCallSession.State.IDLE;
795 }
796
797 return mSession.getState();
798 }
799 }
800
801 /**
802 * Gets the {@link ImsCallSession} that carries this call.
803 *
804 * @return the session object that carries this call
805 * @hide
806 */
807 public ImsCallSession getCallSession() {
808 synchronized(mLockObj) {
809 return mSession;
810 }
811 }
812
813 /**
814 * Gets the {@link ImsStreamMediaSession} that handles the media operation of this call.
815 * Almost interface APIs are for the VT (Video Telephony).
816 *
817 * @return the media session object that handles the media operation of this call
818 * @hide
819 */
820 public ImsStreamMediaSession getMediaSession() {
821 synchronized(mLockObj) {
822 return mMediaSession;
823 }
824 }
825
826 /**
827 * Gets the specified property of this call.
828 *
829 * @param name key to get the extra call information defined in {@link ImsCallProfile}
830 * @return the extra call information as string
831 */
832 public String getCallExtra(String name) throws ImsException {
833 // Lookup the cache
834
835 synchronized(mLockObj) {
836 // If not found, try to get the property from the remote
837 if (mSession == null) {
838 throw new ImsException("No call session",
839 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
840 }
841
842 try {
843 return mSession.getProperty(name);
844 } catch (Throwable t) {
845 loge("getCallExtra :: ", t);
846 throw new ImsException("getCallExtra()", t, 0);
847 }
848 }
849 }
850
851 /**
852 * Gets the last reason information when the call is not established, cancelled or terminated.
853 *
854 * @return the last reason information
855 */
856 public ImsReasonInfo getLastReasonInfo() {
857 synchronized(mLockObj) {
858 return mLastReasonInfo;
859 }
860 }
861
862 /**
863 * Checks if the call has a pending update operation.
864 *
865 * @return true if the call has a pending update operation
866 */
867 public boolean hasPendingUpdate() {
868 synchronized(mLockObj) {
869 return (mUpdateRequest != UPDATE_NONE);
870 }
871 }
872
873 /**
Tyler Gunn6c0b0d02015-07-01 16:39:43 -0700874 * Checks if the call is pending a hold operation.
875 *
876 * @return true if the call is pending a hold operation.
877 */
878 public boolean isPendingHold() {
879 synchronized(mLockObj) {
880 return (mUpdateRequest == UPDATE_HOLD);
881 }
882 }
883
884 /**
Wink Savilleef36ef62014-06-11 08:39:38 -0700885 * Checks if the call is established.
886 *
887 * @return true if the call is established
888 */
889 public boolean isInCall() {
890 synchronized(mLockObj) {
891 return mInCall;
892 }
893 }
894
895 /**
896 * Checks if the call is muted.
897 *
898 * @return true if the call is muted
899 */
900 public boolean isMuted() {
901 synchronized(mLockObj) {
902 return mMute;
903 }
904 }
905
906 /**
907 * Checks if the call is on hold.
908 *
909 * @return true if the call is on hold
910 */
911 public boolean isOnHold() {
912 synchronized(mLockObj) {
913 return mHold;
914 }
915 }
916
917 /**
Tyler Gunn725ad372014-10-22 11:28:47 -0700918 * Determines if the call is a multiparty call.
919 *
920 * @return {@code True} if the call is a multiparty call.
921 */
922 public boolean isMultiparty() {
Etan Cohen16b3b362014-10-24 11:10:58 -0700923 synchronized(mLockObj) {
924 if (mSession == null) {
925 return false;
926 }
927
Anthony Lee71382692014-10-30 10:50:10 -0700928 return mSession.isMultiparty();
Etan Cohen16b3b362014-10-24 11:10:58 -0700929 }
Tyler Gunn725ad372014-10-22 11:28:47 -0700930 }
931
932 /**
Tyler Gunn25394092015-04-01 09:40:02 -0700933 * Where {@link #isMultiparty()} is {@code true}, determines if this {@link ImsCall} is the
934 * origin of the conference call (i.e. {@code #isConferenceHost()} is {@code true}), or if this
935 * {@link ImsCall} is a member of a conference hosted on another device.
936 *
937 * @return {@code true} if this call is the origin of the conference call it is a member of,
938 * {@code false} otherwise.
939 */
940 public boolean isConferenceHost() {
941 synchronized(mLockObj) {
942 return isMultiparty() && mIsConferenceHost;
943 }
944 }
945
946 /**
Tyler Gunn9bd5ca52014-12-02 09:21:01 -0800947 * Marks whether an IMS call is merged. This should be set {@code true} when the call merges
948 * into a conference.
Andrew Lee8ae59492014-11-17 17:03:02 -0800949 *
950 * @param isMerged Whether the call is merged.
951 */
952 public void setIsMerged(boolean isMerged) {
953 mIsMerged = isMerged;
954 }
955
956 /**
Tyler Gunn9bd5ca52014-12-02 09:21:01 -0800957 * @return {@code true} if the call recently merged into a conference call.
Andrew Lee8ae59492014-11-17 17:03:02 -0800958 */
959 public boolean isMerged() {
960 return mIsMerged;
961 }
962
963 /**
Wink Savilleef36ef62014-06-11 08:39:38 -0700964 * Sets the listener to listen to the IMS call events.
965 * The method calls {@link #setListener setListener(listener, false)}.
966 *
967 * @param listener to listen to the IMS call events of this object; null to remove listener
968 * @see #setListener(Listener, boolean)
969 */
970 public void setListener(ImsCall.Listener listener) {
971 setListener(listener, false);
972 }
973
974 /**
975 * Sets the listener to listen to the IMS call events.
976 * A {@link ImsCall} can only hold one listener at a time. Subsequent calls
977 * to this method override the previous listener.
978 *
979 * @param listener to listen to the IMS call events of this object; null to remove listener
980 * @param callbackImmediately set to true if the caller wants to be called
981 * back immediately on the current state
982 */
983 public void setListener(ImsCall.Listener listener, boolean callbackImmediately) {
984 boolean inCall;
985 boolean onHold;
986 int state;
987 ImsReasonInfo lastReasonInfo;
988
989 synchronized(mLockObj) {
990 mListener = listener;
991
992 if ((listener == null) || !callbackImmediately) {
993 return;
994 }
995
996 inCall = mInCall;
997 onHold = mHold;
998 state = getState();
999 lastReasonInfo = mLastReasonInfo;
1000 }
1001
1002 try {
1003 if (lastReasonInfo != null) {
1004 listener.onCallError(this, lastReasonInfo);
1005 } else if (inCall) {
1006 if (onHold) {
1007 listener.onCallHeld(this);
1008 } else {
1009 listener.onCallStarted(this);
1010 }
1011 } else {
1012 switch (state) {
1013 case ImsCallSession.State.ESTABLISHING:
1014 listener.onCallProgressing(this);
1015 break;
1016 case ImsCallSession.State.TERMINATED:
1017 listener.onCallTerminated(this, lastReasonInfo);
1018 break;
1019 default:
1020 // Ignore it. There is no action in the other state.
1021 break;
1022 }
1023 }
1024 } catch (Throwable t) {
Anthony Lee68048512015-03-18 15:04:18 -07001025 loge("setListener() :: ", t);
Wink Savilleef36ef62014-06-11 08:39:38 -07001026 }
1027 }
1028
1029 /**
1030 * Mutes or unmutes the mic for the active call.
1031 *
1032 * @param muted true if the call is muted, false otherwise
1033 */
1034 public void setMute(boolean muted) throws ImsException {
1035 synchronized(mLockObj) {
1036 if (mMute != muted) {
Anthony Lee68048512015-03-18 15:04:18 -07001037 logi("setMute :: turning mute " + (muted ? "on" : "off"));
Wink Savilleef36ef62014-06-11 08:39:38 -07001038 mMute = muted;
1039
1040 try {
1041 mSession.setMute(muted);
1042 } catch (Throwable t) {
1043 loge("setMute :: ", t);
1044 throwImsException(t, 0);
1045 }
1046 }
1047 }
1048 }
1049
1050 /**
1051 * Attaches an incoming call to this call object.
1052 *
1053 * @param session the session that receives the incoming call
1054 * @throws ImsException if the IMS service fails to attach this object to the session
1055 */
1056 public void attachSession(ImsCallSession session) throws ImsException {
Anthony Lee68048512015-03-18 15:04:18 -07001057 logi("attachSession :: session=" + session);
Wink Savilleef36ef62014-06-11 08:39:38 -07001058
1059 synchronized(mLockObj) {
1060 mSession = session;
1061
1062 try {
1063 mSession.setListener(createCallSessionListener());
1064 } catch (Throwable t) {
1065 loge("attachSession :: ", t);
1066 throwImsException(t, 0);
1067 }
1068 }
1069 }
1070
1071 /**
1072 * Initiates an IMS call with the call profile which is provided
1073 * when creating a {@link ImsCall}.
1074 *
1075 * @param session the {@link ImsCallSession} for carrying out the call
1076 * @param callee callee information to initiate an IMS call
1077 * @throws ImsException if the IMS service fails to initiate the call
1078 */
1079 public void start(ImsCallSession session, String callee)
1080 throws ImsException {
fionaxu7b3107c2016-07-06 14:04:06 -07001081 logi("start(1) :: session=" + session);
Wink Savilleef36ef62014-06-11 08:39:38 -07001082
1083 synchronized(mLockObj) {
1084 mSession = session;
1085
1086 try {
1087 session.setListener(createCallSessionListener());
1088 session.start(callee, mCallProfile);
1089 } catch (Throwable t) {
1090 loge("start(1) :: ", t);
1091 throw new ImsException("start(1)", t, 0);
1092 }
1093 }
1094 }
1095
1096 /**
1097 * Initiates an IMS conferenca call with the call profile which is provided
1098 * when creating a {@link ImsCall}.
1099 *
1100 * @param session the {@link ImsCallSession} for carrying out the call
1101 * @param participants participant list to initiate an IMS conference call
1102 * @throws ImsException if the IMS service fails to initiate the call
1103 */
1104 public void start(ImsCallSession session, String[] participants)
1105 throws ImsException {
fionaxu7b3107c2016-07-06 14:04:06 -07001106 logi("start(n) :: session=" + session);
Wink Savilleef36ef62014-06-11 08:39:38 -07001107
1108 synchronized(mLockObj) {
1109 mSession = session;
1110
1111 try {
1112 session.setListener(createCallSessionListener());
1113 session.start(participants, mCallProfile);
1114 } catch (Throwable t) {
1115 loge("start(n) :: ", t);
1116 throw new ImsException("start(n)", t, 0);
1117 }
1118 }
1119 }
1120
1121 /**
1122 * Accepts a call.
1123 *
1124 * @see Listener#onCallStarted
Tyler Gunnd1edfd82014-07-18 13:44:18 -07001125 *
1126 * @param callType The call type the user agreed to for accepting the call.
Wink Savilleef36ef62014-06-11 08:39:38 -07001127 * @throws ImsException if the IMS service fails to accept the call
1128 */
Tyler Gunnd1edfd82014-07-18 13:44:18 -07001129 public void accept(int callType) throws ImsException {
Tyler Gunnd1edfd82014-07-18 13:44:18 -07001130 accept(callType, new ImsStreamMediaProfile());
Wink Savilleef36ef62014-06-11 08:39:38 -07001131 }
1132
1133 /**
1134 * Accepts a call.
1135 *
1136 * @param callType call type to be answered in {@link ImsCallProfile}
1137 * @param profile a media profile to be answered (audio/audio & video, direction, ...)
1138 * @see Listener#onCallStarted
1139 * @throws ImsException if the IMS service fails to accept the call
1140 */
1141 public void accept(int callType, ImsStreamMediaProfile profile) throws ImsException {
Anthony Lee68048512015-03-18 15:04:18 -07001142 logi("accept :: callType=" + callType + ", profile=" + profile);
Wink Savilleef36ef62014-06-11 08:39:38 -07001143
Hall Liu43213e22018-02-09 16:55:05 -08001144 if (mAnswerWithRtt) {
1145 profile.mRttMode = ImsStreamMediaProfile.RTT_MODE_FULL;
1146 logi("accept :: changing media profile RTT mode to full");
1147 }
1148
Wink Savilleef36ef62014-06-11 08:39:38 -07001149 synchronized(mLockObj) {
1150 if (mSession == null) {
1151 throw new ImsException("No call to answer",
1152 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1153 }
1154
1155 try {
1156 mSession.accept(callType, profile);
1157 } catch (Throwable t) {
1158 loge("accept :: ", t);
1159 throw new ImsException("accept()", t, 0);
1160 }
1161
1162 if (mInCall && (mProposedCallProfile != null)) {
1163 if (DBG) {
Anthony Lee68048512015-03-18 15:04:18 -07001164 logi("accept :: call profile will be updated");
Wink Savilleef36ef62014-06-11 08:39:38 -07001165 }
1166
1167 mCallProfile = mProposedCallProfile;
Tyler Gunn1ac24852016-06-22 10:04:23 -07001168 trackVideoStateHistory(mCallProfile);
Wink Savilleef36ef62014-06-11 08:39:38 -07001169 mProposedCallProfile = null;
1170 }
1171
1172 // Other call update received
1173 if (mInCall && (mUpdateRequest == UPDATE_UNSPECIFIED)) {
1174 mUpdateRequest = UPDATE_NONE;
1175 }
1176 }
1177 }
1178
1179 /**
Pooja Jaina7ba0fb2017-12-28 14:35:18 +05301180 * Deflects a call.
1181 *
1182 * @param number number to be deflected to.
1183 * @throws ImsException if the IMS service fails to deflect the call
1184 */
1185 public void deflect(String number) throws ImsException {
1186 logi("deflect :: session=" + mSession + ", number=" + Rlog.pii(TAG, number));
1187
1188 synchronized(mLockObj) {
1189 if (mSession == null) {
1190 throw new ImsException("No call to deflect",
1191 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1192 }
1193
1194 try {
1195 mSession.deflect(number);
1196 } catch (Throwable t) {
1197 loge("deflect :: ", t);
1198 throw new ImsException("deflect()", t, 0);
1199 }
1200 }
1201 }
1202
1203 /**
Wink Savilleef36ef62014-06-11 08:39:38 -07001204 * Rejects a call.
1205 *
1206 * @param reason reason code to reject an incoming call
1207 * @see Listener#onCallStartFailed
Anthony Lee68048512015-03-18 15:04:18 -07001208 * @throws ImsException if the IMS service fails to reject the call
Wink Savilleef36ef62014-06-11 08:39:38 -07001209 */
1210 public void reject(int reason) throws ImsException {
Anthony Lee68048512015-03-18 15:04:18 -07001211 logi("reject :: reason=" + reason);
Wink Savilleef36ef62014-06-11 08:39:38 -07001212
1213 synchronized(mLockObj) {
1214 if (mSession != null) {
1215 mSession.reject(reason);
1216 }
1217
1218 if (mInCall && (mProposedCallProfile != null)) {
1219 if (DBG) {
Anthony Lee68048512015-03-18 15:04:18 -07001220 logi("reject :: call profile is not updated; destroy it...");
Wink Savilleef36ef62014-06-11 08:39:38 -07001221 }
1222
1223 mProposedCallProfile = null;
1224 }
1225
1226 // Other call update received
1227 if (mInCall && (mUpdateRequest == UPDATE_UNSPECIFIED)) {
1228 mUpdateRequest = UPDATE_NONE;
1229 }
1230 }
1231 }
1232
Tyler Gunn25a72fc2016-08-11 13:16:54 -07001233 public void terminate(int reason, int overrideReason) throws ImsException {
1234 logi("terminate :: reason=" + reason + " ; overrideReadon=" + overrideReason);
1235 mOverrideReason = overrideReason;
1236 terminate(reason);
1237 }
1238
Wink Savilleef36ef62014-06-11 08:39:38 -07001239 /**
Tyler Gunn95d563e2015-07-23 09:28:58 -07001240 * Terminates an IMS call (e.g. user initiated).
Wink Savilleef36ef62014-06-11 08:39:38 -07001241 *
1242 * @param reason reason code to terminate a call
1243 * @throws ImsException if the IMS service fails to terminate the call
1244 */
1245 public void terminate(int reason) throws ImsException {
Anthony Lee68048512015-03-18 15:04:18 -07001246 logi("terminate :: reason=" + reason);
Wink Savilleef36ef62014-06-11 08:39:38 -07001247
1248 synchronized(mLockObj) {
1249 mHold = false;
1250 mInCall = false;
Tyler Gunn95d563e2015-07-23 09:28:58 -07001251 mTerminationRequestPending = true;
Wink Savilleef36ef62014-06-11 08:39:38 -07001252
1253 if (mSession != null) {
Anthony Leec479f662015-02-11 17:04:35 -08001254 // TODO: Fix the fact that user invoked call terminations during
1255 // the process of establishing a conference call needs to be handled
1256 // as a special case.
1257 // Currently, any terminations (both invoked by the user or
1258 // by the network results in a callSessionTerminated() callback
1259 // from the network. When establishing a conference call we bury
1260 // these callbacks until we get closure on all participants of the
1261 // conference. In some situations, we will throw away the callback
1262 // (when the underlying session of the host of the new conference
1263 // is terminated) or will will unbury it when the conference has been
1264 // established, like when the peer of the new conference goes away
1265 // after the conference has been created. The UI relies on the callback
1266 // to reflect the fact that the call is gone.
1267 // So if a user decides to terminated a call while it is merging, it
1268 // could take a long time to reflect in the UI due to the conference
1269 // processing but we should probably cancel that and just terminate
1270 // the call immediately and clean up. This is not a huge issue right
1271 // now because we have not seen instances where establishing a
1272 // conference takes a long time (more than a second or two).
Wink Savilleef36ef62014-06-11 08:39:38 -07001273 mSession.terminate(reason);
1274 }
1275 }
1276 }
1277
Uma Maheswari Ramalingamd43b5302014-08-29 14:13:17 -07001278
Wink Savilleef36ef62014-06-11 08:39:38 -07001279 /**
1280 * Puts a call on hold. When succeeds, {@link Listener#onCallHeld} is called.
1281 *
1282 * @see Listener#onCallHeld, Listener#onCallHoldFailed
1283 * @throws ImsException if the IMS service fails to hold the call
1284 */
1285 public void hold() throws ImsException {
Anthony Lee68048512015-03-18 15:04:18 -07001286 logi("hold :: ");
Etan Cohen111eecc2014-09-10 17:18:12 -07001287
Wink Savilleef36ef62014-06-11 08:39:38 -07001288 if (isOnHold()) {
1289 if (DBG) {
Anthony Lee68048512015-03-18 15:04:18 -07001290 logi("hold :: call is already on hold");
Wink Savilleef36ef62014-06-11 08:39:38 -07001291 }
1292 return;
1293 }
1294
1295 synchronized(mLockObj) {
1296 if (mUpdateRequest != UPDATE_NONE) {
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08001297 loge("hold :: update is in progress; request=" +
1298 updateRequestToString(mUpdateRequest));
Wink Savilleef36ef62014-06-11 08:39:38 -07001299 throw new ImsException("Call update is in progress",
1300 ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
1301 }
1302
1303 if (mSession == null) {
Wink Savilleef36ef62014-06-11 08:39:38 -07001304 throw new ImsException("No call session",
1305 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1306 }
1307
1308 mSession.hold(createHoldMediaProfile());
Anthony Lee68048512015-03-18 15:04:18 -07001309 // FIXME: We should update the state on the callback because that is where
1310 // we can confirm that the hold request was successful or not.
Wink Savilleef36ef62014-06-11 08:39:38 -07001311 mHold = true;
1312 mUpdateRequest = UPDATE_HOLD;
1313 }
1314 }
1315
1316 /**
1317 * Continues a call that's on hold. When succeeds, {@link Listener#onCallResumed} is called.
1318 *
1319 * @see Listener#onCallResumed, Listener#onCallResumeFailed
1320 * @throws ImsException if the IMS service fails to resume the call
1321 */
1322 public void resume() throws ImsException {
Anthony Lee68048512015-03-18 15:04:18 -07001323 logi("resume :: ");
Etan Cohen111eecc2014-09-10 17:18:12 -07001324
Wink Savilleef36ef62014-06-11 08:39:38 -07001325 if (!isOnHold()) {
1326 if (DBG) {
Anthony Lee68048512015-03-18 15:04:18 -07001327 logi("resume :: call is not being held");
Wink Savilleef36ef62014-06-11 08:39:38 -07001328 }
1329 return;
1330 }
1331
1332 synchronized(mLockObj) {
1333 if (mUpdateRequest != UPDATE_NONE) {
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08001334 loge("resume :: 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
1340 if (mSession == null) {
1341 loge("resume :: ");
1342 throw new ImsException("No call session",
1343 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1344 }
1345
Anthony Leec479f662015-02-11 17:04:35 -08001346 // mHold is set to false in confirmation callback that the
1347 // ImsCall was resumed.
Wink Savilleef36ef62014-06-11 08:39:38 -07001348 mUpdateRequest = UPDATE_RESUME;
Anthony Leec479f662015-02-11 17:04:35 -08001349 mSession.resume(createResumeMediaProfile());
Wink Savilleef36ef62014-06-11 08:39:38 -07001350 }
1351 }
1352
1353 /**
1354 * Merges the active & hold call.
1355 *
1356 * @see Listener#onCallMerged, Listener#onCallMergeFailed
1357 * @throws ImsException if the IMS service fails to merge the call
1358 */
Tyler Gunn047d8102015-01-30 15:21:11 -08001359 private void merge() throws ImsException {
Anthony Lee68048512015-03-18 15:04:18 -07001360 logi("merge :: ");
Wink Savilleef36ef62014-06-11 08:39:38 -07001361
1362 synchronized(mLockObj) {
Tyler Gunnc7ff7ee2017-09-06 14:38:37 -07001363 // If the host of the merge is in the midst of some other operation, we cannot merge.
Wink Savilleef36ef62014-06-11 08:39:38 -07001364 if (mUpdateRequest != UPDATE_NONE) {
Tyler Gunnc7ff7ee2017-09-06 14:38:37 -07001365 setCallSessionMergePending(false);
1366 if (mMergePeer != null) {
1367 mMergePeer.setCallSessionMergePending(false);
1368 }
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08001369 loge("merge :: update is in progress; request=" +
1370 updateRequestToString(mUpdateRequest));
Wink Savilleef36ef62014-06-11 08:39:38 -07001371 throw new ImsException("Call update is in progress",
1372 ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
1373 }
1374
Tyler Gunnc7ff7ee2017-09-06 14:38:37 -07001375 // The peer of the merge is in the midst of some other operation, we cannot merge.
1376 if (mMergePeer != null && mMergePeer.mUpdateRequest != UPDATE_NONE) {
1377 setCallSessionMergePending(false);
1378 mMergePeer.setCallSessionMergePending(false);
1379 loge("merge :: peer call update is in progress; request=" +
1380 updateRequestToString(mMergePeer.mUpdateRequest));
1381 throw new ImsException("Peer call update is in progress",
1382 ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
1383 }
1384
Wink Savilleef36ef62014-06-11 08:39:38 -07001385 if (mSession == null) {
Anthony Leec479f662015-02-11 17:04:35 -08001386 loge("merge :: no call session");
Wink Savilleef36ef62014-06-11 08:39:38 -07001387 throw new ImsException("No call session",
1388 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1389 }
1390
Uma Maheswari Ramalingamca45f582014-05-19 12:32:20 -07001391 // if skipHoldBeforeMerge = true, IMS service implementation will
1392 // merge without explicitly holding the call.
1393 if (mHold || (mContext.getResources().getBoolean(
1394 com.android.internal.R.bool.skipHoldBeforeMerge))) {
Anthony Leeb8799fe2014-11-03 15:13:47 -08001395
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08001396 if (mMergePeer != null && !mMergePeer.isMultiparty() && !isMultiparty()) {
1397 // We only set UPDATE_MERGE when we are adding the first
1398 // calls to the Conference. If there is already a conference
Tyler Gunn047d8102015-01-30 15:21:11 -08001399 // no special handling is needed. The existing conference
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08001400 // session will just go active and any other sessions will be terminated
1401 // if needed. There will be no merge failed callback.
Tyler Gunn047d8102015-01-30 15:21:11 -08001402 // Mark both the host and peer UPDATE_MERGE to ensure both are aware that a
1403 // merge is pending.
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08001404 mUpdateRequest = UPDATE_MERGE;
Tyler Gunn047d8102015-01-30 15:21:11 -08001405 mMergePeer.mUpdateRequest = UPDATE_MERGE;
Anthony Leeb8799fe2014-11-03 15:13:47 -08001406 }
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08001407
1408 mSession.merge();
Wink Savilleef36ef62014-06-11 08:39:38 -07001409 } else {
Anthony Lee71382692014-10-30 10:50:10 -07001410 // This code basically says, we need to explicitly hold before requesting a merge
1411 // when we get the callback that the hold was successful (or failed), we should
1412 // automatically request a merge.
Wink Savilleef36ef62014-06-11 08:39:38 -07001413 mSession.hold(createHoldMediaProfile());
Wink Savilleef36ef62014-06-11 08:39:38 -07001414 mHold = true;
1415 mUpdateRequest = UPDATE_HOLD_MERGE;
1416 }
1417 }
1418 }
1419
1420 /**
1421 * Merges the active & hold call.
1422 *
1423 * @param bgCall the background (holding) call
1424 * @see Listener#onCallMerged, Listener#onCallMergeFailed
1425 * @throws ImsException if the IMS service fails to merge the call
1426 */
1427 public void merge(ImsCall bgCall) throws ImsException {
Anthony Lee68048512015-03-18 15:04:18 -07001428 logi("merge(1) :: bgImsCall=" + bgCall);
Wink Savilleef36ef62014-06-11 08:39:38 -07001429
1430 if (bgCall == null) {
1431 throw new ImsException("No background call",
1432 ImsReasonInfo.CODE_LOCAL_ILLEGAL_ARGUMENT);
1433 }
1434
1435 synchronized(mLockObj) {
Tyler Gunn047d8102015-01-30 15:21:11 -08001436 // Mark both sessions as pending merge.
1437 this.setCallSessionMergePending(true);
1438 bgCall.setCallSessionMergePending(true);
1439
Tyler Gunn87466c52014-12-08 09:56:17 -08001440 if ((!isMultiparty() && !bgCall.isMultiparty()) || isMultiparty()) {
1441 // If neither call is multiparty, the current call is the merge host and the bg call
1442 // is the merge peer (ie we're starting a new conference).
1443 // OR
1444 // If this call is multiparty, it is the merge host and the other call is the merge
1445 // peer.
1446 setMergePeer(bgCall);
1447 } else {
1448 // If the bg call is multiparty, it is the merge host.
1449 setMergeHost(bgCall);
1450 }
Wink Savilleef36ef62014-06-11 08:39:38 -07001451 }
Uma Maheswari Ramalingama1ed7b02015-05-20 14:26:42 -07001452
Uma Maheswari Ramalingam95be7c62015-05-06 23:08:17 -07001453 if (isMultiparty()) {
1454 mMergeRequestedByConference = true;
1455 } else {
1456 logi("merge : mMergeRequestedByConference not set");
1457 }
Wink Savilleef36ef62014-06-11 08:39:38 -07001458 merge();
1459 }
1460
1461 /**
1462 * Updates the current call's properties (ex. call mode change: video upgrade / downgrade).
1463 */
1464 public void update(int callType, ImsStreamMediaProfile mediaProfile) throws ImsException {
Anthony Lee68048512015-03-18 15:04:18 -07001465 logi("update :: callType=" + callType + ", mediaProfile=" + mediaProfile);
Wink Savilleef36ef62014-06-11 08:39:38 -07001466
1467 if (isOnHold()) {
1468 if (DBG) {
Anthony Lee68048512015-03-18 15:04:18 -07001469 logi("update :: call is on hold");
Wink Savilleef36ef62014-06-11 08:39:38 -07001470 }
1471 throw new ImsException("Not in a call to update call",
1472 ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
1473 }
1474
1475 synchronized(mLockObj) {
1476 if (mUpdateRequest != UPDATE_NONE) {
1477 if (DBG) {
Anthony Lee68048512015-03-18 15:04:18 -07001478 logi("update :: update is in progress; request=" +
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08001479 updateRequestToString(mUpdateRequest));
Wink Savilleef36ef62014-06-11 08:39:38 -07001480 }
1481 throw new ImsException("Call update is in progress",
1482 ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
1483 }
1484
1485 if (mSession == null) {
1486 loge("update :: ");
1487 throw new ImsException("No call session",
1488 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1489 }
1490
1491 mSession.update(callType, mediaProfile);
1492 mUpdateRequest = UPDATE_UNSPECIFIED;
1493 }
1494 }
1495
1496 /**
1497 * Extends this call (1-to-1 call) to the conference call
1498 * inviting the specified participants to.
1499 *
1500 */
1501 public void extendToConference(String[] participants) throws ImsException {
Anthony Lee68048512015-03-18 15:04:18 -07001502 logi("extendToConference ::");
Wink Savilleef36ef62014-06-11 08:39:38 -07001503
1504 if (isOnHold()) {
1505 if (DBG) {
Anthony Lee68048512015-03-18 15:04:18 -07001506 logi("extendToConference :: call is on hold");
Wink Savilleef36ef62014-06-11 08:39:38 -07001507 }
1508 throw new ImsException("Not in a call to extend a call to conference",
1509 ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
1510 }
1511
1512 synchronized(mLockObj) {
1513 if (mUpdateRequest != UPDATE_NONE) {
Anthony Lee68048512015-03-18 15:04:18 -07001514 if (CONF_DBG) {
1515 logi("extendToConference :: update is in progress; request=" +
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08001516 updateRequestToString(mUpdateRequest));
Wink Savilleef36ef62014-06-11 08:39:38 -07001517 }
1518 throw new ImsException("Call update is in progress",
1519 ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
1520 }
1521
1522 if (mSession == null) {
1523 loge("extendToConference :: ");
1524 throw new ImsException("No call session",
1525 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1526 }
1527
1528 mSession.extendToConference(participants);
1529 mUpdateRequest = UPDATE_EXTEND_TO_CONFERENCE;
1530 }
1531 }
1532
1533 /**
1534 * Requests the conference server to invite an additional participants to the conference.
1535 *
1536 */
1537 public void inviteParticipants(String[] participants) throws ImsException {
Anthony Lee68048512015-03-18 15:04:18 -07001538 logi("inviteParticipants ::");
Wink Savilleef36ef62014-06-11 08:39:38 -07001539
1540 synchronized(mLockObj) {
1541 if (mSession == null) {
1542 loge("inviteParticipants :: ");
1543 throw new ImsException("No call session",
1544 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1545 }
1546
1547 mSession.inviteParticipants(participants);
1548 }
1549 }
1550
1551 /**
1552 * Requests the conference server to remove the specified participants from the conference.
1553 *
1554 */
1555 public void removeParticipants(String[] participants) throws ImsException {
Uma Maheswari Ramalingama1ed7b02015-05-20 14:26:42 -07001556 logi("removeParticipants :: session=" + mSession);
Wink Savilleef36ef62014-06-11 08:39:38 -07001557 synchronized(mLockObj) {
1558 if (mSession == null) {
1559 loge("removeParticipants :: ");
1560 throw new ImsException("No call session",
1561 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1562 }
1563
1564 mSession.removeParticipants(participants);
Uma Maheswari Ramalingama1ed7b02015-05-20 14:26:42 -07001565
Wink Savilleef36ef62014-06-11 08:39:38 -07001566 }
1567 }
1568
Wink Savilleef36ef62014-06-11 08:39:38 -07001569 /**
1570 * Sends a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2833</a>,
1571 * event 0 ~ 9 maps to decimal value 0 ~ 9, '*' to 10, '#' to 11, event 'A' ~ 'D' to 12 ~ 15,
1572 * and event flash to 16. Currently, event flash is not supported.
1573 *
Libin.Tang@motorola.com2f92daf2014-08-23 18:08:31 -05001574 * @param c that represents the DTMF to send. '0' ~ '9', 'A' ~ 'D', '*', '#' are valid inputs.
1575 * @param result the result message to send when done.
Wink Savilleef36ef62014-06-11 08:39:38 -07001576 */
Libin.Tang@motorola.com2f92daf2014-08-23 18:08:31 -05001577 public void sendDtmf(char c, Message result) {
Anthony Lee68048512015-03-18 15:04:18 -07001578 logi("sendDtmf :: code=" + c);
Wink Savilleef36ef62014-06-11 08:39:38 -07001579
1580 synchronized(mLockObj) {
1581 if (mSession != null) {
Andrew Leea4710d52014-12-09 14:51:53 -08001582 mSession.sendDtmf(c, result);
Wink Savilleef36ef62014-06-11 08:39:38 -07001583 }
1584 }
Wink Savilleef36ef62014-06-11 08:39:38 -07001585 }
1586
1587 /**
Uma Maheswari Ramalingama6fbae92014-12-05 16:40:46 -08001588 * Start a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2833</a>,
1589 * event 0 ~ 9 maps to decimal value 0 ~ 9, '*' to 10, '#' to 11, event 'A' ~ 'D' to 12 ~ 15,
1590 * and event flash to 16. Currently, event flash is not supported.
1591 *
1592 * @param c that represents the DTMF to send. '0' ~ '9', 'A' ~ 'D', '*', '#' are valid inputs.
1593 */
1594 public void startDtmf(char c) {
Anthony Lee68048512015-03-18 15:04:18 -07001595 logi("startDtmf :: code=" + c);
Uma Maheswari Ramalingama6fbae92014-12-05 16:40:46 -08001596
1597 synchronized(mLockObj) {
1598 if (mSession != null) {
1599 mSession.startDtmf(c);
1600 }
1601 }
1602 }
1603
1604 /**
1605 * Stop a DTMF code.
1606 */
1607 public void stopDtmf() {
Anthony Lee68048512015-03-18 15:04:18 -07001608 logi("stopDtmf :: ");
Uma Maheswari Ramalingama6fbae92014-12-05 16:40:46 -08001609
1610 synchronized(mLockObj) {
1611 if (mSession != null) {
1612 mSession.stopDtmf();
1613 }
1614 }
1615 }
1616
1617 /**
Wink Savilleef36ef62014-06-11 08:39:38 -07001618 * Sends an USSD message.
1619 *
1620 * @param ussdMessage USSD message to send
1621 */
1622 public void sendUssd(String ussdMessage) throws ImsException {
Anthony Lee68048512015-03-18 15:04:18 -07001623 logi("sendUssd :: ussdMessage=" + ussdMessage);
Wink Savilleef36ef62014-06-11 08:39:38 -07001624
1625 synchronized(mLockObj) {
1626 if (mSession == null) {
1627 loge("sendUssd :: ");
1628 throw new ImsException("No call session",
1629 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1630 }
1631
1632 mSession.sendUssd(ussdMessage);
1633 }
1634 }
1635
Hall Liubd3f3772017-03-22 15:38:23 -07001636 public void sendRttMessage(String rttMessage) {
1637 synchronized(mLockObj) {
1638 if (mSession == null) {
1639 loge("sendRttMessage::no session");
1640 }
1641 if (!mCallProfile.mMediaProfile.isRttCall()) {
1642 logi("sendRttMessage::Not an rtt call, ignoring");
1643 return;
1644 }
1645 mSession.sendRttMessage(rttMessage);
1646 }
1647 }
1648
1649 /**
1650 * Sends a user-requested RTT upgrade request.
1651 */
1652 public void sendRttModifyRequest() {
1653 logi("sendRttModifyRequest");
1654
1655 synchronized(mLockObj) {
1656 if (mSession == null) {
1657 loge("sendRttModifyRequest::no session");
1658 }
1659 if (mCallProfile.mMediaProfile.isRttCall()) {
1660 logi("sendRttModifyRequest::Already RTT call, ignoring.");
1661 return;
1662 }
1663 // Make a copy of the current ImsCallProfile and modify it to enable RTT
1664 Parcel p = Parcel.obtain();
1665 mCallProfile.writeToParcel(p, 0);
Hall Liub77d8972018-01-22 19:15:05 -08001666 p.setDataPosition(0);
Hall Liubd3f3772017-03-22 15:38:23 -07001667 ImsCallProfile requestedProfile = new ImsCallProfile(p);
1668 requestedProfile.mMediaProfile.setRttMode(ImsStreamMediaProfile.RTT_MODE_FULL);
1669
1670 mSession.sendRttModifyRequest(requestedProfile);
1671 }
1672 }
1673
1674 /**
1675 * Sends the user's response to a remotely-issued RTT upgrade request
1676 *
1677 * @param textStream A valid {@link Connection.RttTextStream} if the user
1678 * accepts, {@code null} if not.
1679 */
1680 public void sendRttModifyResponse(boolean status) {
1681 logi("sendRttModifyResponse");
1682
1683 synchronized(mLockObj) {
1684 if (mSession == null) {
1685 loge("sendRttModifyResponse::no session");
1686 }
1687 if (mCallProfile.mMediaProfile.isRttCall()) {
1688 logi("sendRttModifyResponse::Already RTT call, ignoring.");
1689 return;
1690 }
1691 mSession.sendRttModifyResponse(status);
1692 }
1693 }
1694
Hall Liu43213e22018-02-09 16:55:05 -08001695 public void setAnswerWithRtt() {
1696 mAnswerWithRtt = true;
1697 }
1698
Wink Savilleef36ef62014-06-11 08:39:38 -07001699 private void clear(ImsReasonInfo lastReasonInfo) {
1700 mInCall = false;
1701 mHold = false;
1702 mUpdateRequest = UPDATE_NONE;
1703 mLastReasonInfo = lastReasonInfo;
Wink Savilleef36ef62014-06-11 08:39:38 -07001704 }
1705
1706 /**
1707 * Creates an IMS call session listener.
1708 */
1709 private ImsCallSession.Listener createCallSessionListener() {
Tyler Gunnafb53c82016-08-05 14:38:13 -07001710 mImsCallSessionListenerProxy = new ImsCallSessionListenerProxy();
1711 return mImsCallSessionListenerProxy;
1712 }
1713
1714 /**
1715 * @return the current ImsCallSessionListenerProxy. NOTE: ONLY FOR USE WITH TESTING.
1716 */
1717 @VisibleForTesting
1718 public ImsCallSessionListenerProxy getImsCallSessionListenerProxy() {
1719 return mImsCallSessionListenerProxy;
Wink Savilleef36ef62014-06-11 08:39:38 -07001720 }
1721
1722 private ImsCall createNewCall(ImsCallSession session, ImsCallProfile profile) {
1723 ImsCall call = new ImsCall(mContext, profile);
1724
1725 try {
1726 call.attachSession(session);
1727 } catch (ImsException e) {
1728 if (call != null) {
1729 call.close();
1730 call = null;
1731 }
1732 }
1733
1734 // Do additional operations...
1735
1736 return call;
1737 }
1738
1739 private ImsStreamMediaProfile createHoldMediaProfile() {
1740 ImsStreamMediaProfile mediaProfile = new ImsStreamMediaProfile();
1741
1742 if (mCallProfile == null) {
1743 return mediaProfile;
1744 }
1745
1746 mediaProfile.mAudioQuality = mCallProfile.mMediaProfile.mAudioQuality;
1747 mediaProfile.mVideoQuality = mCallProfile.mMediaProfile.mVideoQuality;
1748 mediaProfile.mAudioDirection = ImsStreamMediaProfile.DIRECTION_SEND;
1749
1750 if (mediaProfile.mVideoQuality != ImsStreamMediaProfile.VIDEO_QUALITY_NONE) {
1751 mediaProfile.mVideoDirection = ImsStreamMediaProfile.DIRECTION_SEND;
1752 }
1753
1754 return mediaProfile;
1755 }
1756
1757 private ImsStreamMediaProfile createResumeMediaProfile() {
1758 ImsStreamMediaProfile mediaProfile = new ImsStreamMediaProfile();
1759
1760 if (mCallProfile == null) {
1761 return mediaProfile;
1762 }
1763
1764 mediaProfile.mAudioQuality = mCallProfile.mMediaProfile.mAudioQuality;
1765 mediaProfile.mVideoQuality = mCallProfile.mMediaProfile.mVideoQuality;
1766 mediaProfile.mAudioDirection = ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE;
1767
1768 if (mediaProfile.mVideoQuality != ImsStreamMediaProfile.VIDEO_QUALITY_NONE) {
1769 mediaProfile.mVideoDirection = ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE;
1770 }
1771
1772 return mediaProfile;
1773 }
1774
1775 private void enforceConversationMode() {
1776 if (mInCall) {
1777 mHold = false;
1778 mUpdateRequest = UPDATE_NONE;
1779 }
1780 }
1781
1782 private void mergeInternal() {
Anthony Lee68048512015-03-18 15:04:18 -07001783 if (CONF_DBG) {
1784 logi("mergeInternal :: ");
Wink Savilleef36ef62014-06-11 08:39:38 -07001785 }
1786
1787 mSession.merge();
1788 mUpdateRequest = UPDATE_MERGE;
1789 }
1790
Wink Savilleef36ef62014-06-11 08:39:38 -07001791 private void notifyConferenceSessionTerminated(ImsReasonInfo reasonInfo) {
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08001792 ImsCall.Listener listener = mListener;
Wink Savilleef36ef62014-06-11 08:39:38 -07001793 clear(reasonInfo);
1794
1795 if (listener != null) {
1796 try {
1797 listener.onCallTerminated(this, reasonInfo);
1798 } catch (Throwable t) {
1799 loge("notifyConferenceSessionTerminated :: ", t);
1800 }
1801 }
1802 }
1803
1804 private void notifyConferenceStateUpdated(ImsConferenceState state) {
Tyler Gunn9e3452a2015-09-08 13:10:14 -07001805 if (state == null || state.mParticipants == null) {
1806 return;
1807 }
1808
Tyler Gunn1c467602014-11-04 14:51:52 -08001809 Set<Entry<String, Bundle>> participants = state.mParticipants.entrySet();
Wink Savilleef36ef62014-06-11 08:39:38 -07001810
Tyler Gunn1c467602014-11-04 14:51:52 -08001811 if (participants == null) {
Wink Savilleef36ef62014-06-11 08:39:38 -07001812 return;
1813 }
1814
Tyler Gunn1c467602014-11-04 14:51:52 -08001815 Iterator<Entry<String, Bundle>> iterator = participants.iterator();
Uma Maheswari Ramalingama1ed7b02015-05-20 14:26:42 -07001816 mConferenceParticipants = new ArrayList<>(participants.size());
Wink Savilleef36ef62014-06-11 08:39:38 -07001817 while (iterator.hasNext()) {
1818 Entry<String, Bundle> entry = iterator.next();
1819
1820 String key = entry.getKey();
1821 Bundle confInfo = entry.getValue();
1822 String status = confInfo.getString(ImsConferenceState.STATUS);
1823 String user = confInfo.getString(ImsConferenceState.USER);
Tyler Gunn59656142014-10-28 13:52:11 -07001824 String displayName = confInfo.getString(ImsConferenceState.DISPLAY_TEXT);
Wink Savilleef36ef62014-06-11 08:39:38 -07001825 String endpoint = confInfo.getString(ImsConferenceState.ENDPOINT);
1826
Anthony Lee68048512015-03-18 15:04:18 -07001827 if (CONF_DBG) {
Shunta Sakaifb688ca2016-12-06 20:32:07 +09001828 logi("notifyConferenceStateUpdated :: key=" + Rlog.pii(TAG, key) +
Wink Savilleef36ef62014-06-11 08:39:38 -07001829 ", status=" + status +
Shunta Sakaifb688ca2016-12-06 20:32:07 +09001830 ", user=" + Rlog.pii(TAG, user) +
1831 ", displayName= " + Rlog.pii(TAG, displayName) +
Wink Savilleef36ef62014-06-11 08:39:38 -07001832 ", endpoint=" + endpoint);
1833 }
1834
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08001835 Uri handle = Uri.parse(user);
Uma Maheswari Ramalingama1ed7b02015-05-20 14:26:42 -07001836 if (endpoint == null) {
1837 endpoint = "";
1838 }
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08001839 Uri endpointUri = Uri.parse(endpoint);
1840 int connectionState = ImsConferenceState.getConnectionStateForStatus(status);
Wink Savilleef36ef62014-06-11 08:39:38 -07001841
Uma Maheswari Ramalingama1ed7b02015-05-20 14:26:42 -07001842 if (connectionState != Connection.STATE_DISCONNECTED) {
1843 ConferenceParticipant conferenceParticipant = new ConferenceParticipant(handle,
1844 displayName, endpointUri, connectionState);
1845 mConferenceParticipants.add(conferenceParticipant);
1846 }
Wink Savilleef36ef62014-06-11 08:39:38 -07001847 }
Tyler Gunn1c467602014-11-04 14:51:52 -08001848
Anju Mathapati0f108032015-10-09 15:34:28 -07001849 if (mConferenceParticipants != null && mListener != null) {
Tyler Gunn1c467602014-11-04 14:51:52 -08001850 try {
Uma Maheswari Ramalingama1ed7b02015-05-20 14:26:42 -07001851 mListener.onConferenceParticipantsStateChanged(this, mConferenceParticipants);
Tyler Gunn1c467602014-11-04 14:51:52 -08001852 } catch (Throwable t) {
1853 loge("notifyConferenceStateUpdated :: ", t);
1854 }
1855 }
Wink Savilleef36ef62014-06-11 08:39:38 -07001856 }
1857
Anthony Leeb8799fe2014-11-03 15:13:47 -08001858 /**
1859 * Perform all cleanup and notification around the termination of a session.
1860 * Note that there are 2 distinct modes of operation. The first is when
1861 * we receive a session termination on the primary session when we are
1862 * in the processing of merging. The second is when we are not merging anything
1863 * and the call is terminated.
1864 *
1865 * @param reasonInfo The reason for the session termination
1866 */
1867 private void processCallTerminated(ImsReasonInfo reasonInfo) {
Tyler Gunn95d563e2015-07-23 09:28:58 -07001868 logi("processCallTerminated :: reason=" + reasonInfo + " userInitiated = " +
1869 mTerminationRequestPending);
Anthony Leeb8799fe2014-11-03 15:13:47 -08001870
1871 ImsCall.Listener listener = null;
Anthony Leeb8799fe2014-11-03 15:13:47 -08001872 synchronized(ImsCall.this) {
Tyler Gunn047d8102015-01-30 15:21:11 -08001873 // If we are in the midst of establishing a conference, we will bury the termination
Tyler Gunn95d563e2015-07-23 09:28:58 -07001874 // until the merge has completed. If necessary we can surface the termination at
1875 // this point.
1876 // We will also NOT bury the termination if a termination was initiated locally.
1877 if (isCallSessionMergePending() && !mTerminationRequestPending) {
Anthony Leeb8799fe2014-11-03 15:13:47 -08001878 // Since we are in the process of a merge, this trigger means something
1879 // else because it is probably due to the merge happening vs. the
1880 // session is really terminated. Let's flag this and revisit if
1881 // the merge() ends up failing because we will need to take action on the
1882 // mSession in that case since the termination was not due to the merge
1883 // succeeding.
Anthony Lee68048512015-03-18 15:04:18 -07001884 if (CONF_DBG) {
1885 logi("processCallTerminated :: burying termination during ongoing merge.");
Anthony Leeb8799fe2014-11-03 15:13:47 -08001886 }
1887 mSessionEndDuringMerge = true;
1888 mSessionEndDuringMergeReasonInfo = reasonInfo;
1889 return;
1890 }
1891
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08001892 // If we are terminating the conference call, notify using conference listeners.
1893 if (isMultiparty()) {
Anthony Leeb8799fe2014-11-03 15:13:47 -08001894 notifyConferenceSessionTerminated(reasonInfo);
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08001895 return;
Anthony Leeb8799fe2014-11-03 15:13:47 -08001896 } else {
1897 listener = mListener;
1898 clear(reasonInfo);
1899 }
1900 }
1901
1902 if (listener != null) {
1903 try {
1904 listener.onCallTerminated(ImsCall.this, reasonInfo);
1905 } catch (Throwable t) {
Anthony Leec479f662015-02-11 17:04:35 -08001906 loge("processCallTerminated :: ", t);
Anthony Leeb8799fe2014-11-03 15:13:47 -08001907 }
1908 }
1909 }
Anthony Lee71382692014-10-30 10:50:10 -07001910
1911 /**
1912 * This function determines if the ImsCallSession is our actual ImsCallSession or if is
1913 * the transient session used in the process of creating a conference. This function should only
1914 * be called within callbacks that are not directly related to conference merging but might
1915 * potentially still be called on the transient ImsCallSession sent to us from
1916 * callSessionMergeStarted() when we don't really care. In those situations, we probably don't
1917 * want to take any action so we need to know that we can return early.
1918 *
1919 * @param session - The {@link ImsCallSession} that the function needs to analyze
1920 * @return true if this is the transient {@link ImsCallSession}, false otherwise.
1921 */
1922 private boolean isTransientConferenceSession(ImsCallSession session) {
1923 if (session != null && session != mSession && session == mTransientConferenceSession) {
1924 return true;
1925 }
1926 return false;
1927 }
1928
Anthony Leec479f662015-02-11 17:04:35 -08001929 private void setTransientSessionAsPrimary(ImsCallSession transientSession) {
1930 synchronized (ImsCall.this) {
1931 mSession.setListener(null);
1932 mSession = transientSession;
1933 mSession.setListener(createCallSessionListener());
1934 }
Tyler Gunn047d8102015-01-30 15:21:11 -08001935 }
1936
Anju Mathapati818c09d2015-08-31 14:30:47 -07001937 private void markCallAsMerged(boolean playDisconnectTone) {
1938 if (!isSessionAlive(mSession)) {
Uma Maheswari Ramalingam95be7c62015-05-06 23:08:17 -07001939 // If the peer is dead, let's not play a disconnect sound for it when we
1940 // unbury the termination callback.
Anju Mathapati818c09d2015-08-31 14:30:47 -07001941 logi("markCallAsMerged");
1942 setIsMerged(playDisconnectTone);
1943 mSessionEndDuringMerge = true;
1944 String reasonInfo;
Jayachandran C96e2d852017-05-11 11:43:52 -07001945 int reasonCode = ImsReasonInfo.CODE_UNSPECIFIED;
Anju Mathapati818c09d2015-08-31 14:30:47 -07001946 if (playDisconnectTone) {
Jayachandran C96e2d852017-05-11 11:43:52 -07001947 reasonCode = ImsReasonInfo.CODE_USER_TERMINATED_BY_REMOTE;
Anju Mathapati818c09d2015-08-31 14:30:47 -07001948 reasonInfo = "Call ended by network";
1949 } else {
Jayachandran C96e2d852017-05-11 11:43:52 -07001950 reasonCode = ImsReasonInfo.CODE_LOCAL_ENDED_BY_CONFERENCE_MERGE;
Anju Mathapati818c09d2015-08-31 14:30:47 -07001951 reasonInfo = "Call ended during conference merge process.";
1952 }
1953 mSessionEndDuringMergeReasonInfo = new ImsReasonInfo(
Jayachandran C96e2d852017-05-11 11:43:52 -07001954 reasonCode, 0, reasonInfo);
Uma Maheswari Ramalingam95be7c62015-05-06 23:08:17 -07001955 }
1956 }
1957
Tyler Gunn047d8102015-01-30 15:21:11 -08001958 /**
Uma Maheswari Ramalingam95be7c62015-05-06 23:08:17 -07001959 * Checks if the merge was requested by foreground conference call
1960 *
1961 * @return true if the merge was requested by foreground conference call
Anthony Leec479f662015-02-11 17:04:35 -08001962 */
Uma Maheswari Ramalingam95be7c62015-05-06 23:08:17 -07001963 public boolean isMergeRequestedByConf() {
1964 synchronized(mLockObj) {
1965 return mMergeRequestedByConference;
1966 }
1967 }
1968
1969 /**
1970 * Resets the flag which indicates merge request was sent by
1971 * foreground conference call
1972 */
1973 public void resetIsMergeRequestedByConf(boolean value) {
1974 synchronized(mLockObj) {
1975 mMergeRequestedByConference = value;
1976 }
1977 }
1978
1979 /**
1980 * Returns current ImsCallSession
1981 *
1982 * @return current session
1983 */
1984 public ImsCallSession getSession() {
1985 synchronized(mLockObj) {
1986 return mSession;
Anthony Leec479f662015-02-11 17:04:35 -08001987 }
1988 }
1989
1990 /**
1991 * We have detected that a initial conference call has been fully configured. The internal
1992 * state of both {@code ImsCall} objects need to be cleaned up to reflect the new state.
1993 * This function should only be called in the context of the merge host to simplify logic
Anthony Lee71382692014-10-30 10:50:10 -07001994 *
Anthony Lee71382692014-10-30 10:50:10 -07001995 */
Anthony Leeb8799fe2014-11-03 15:13:47 -08001996 private void processMergeComplete() {
Anthony Lee68048512015-03-18 15:04:18 -07001997 logi("processMergeComplete :: ");
Anthony Leec479f662015-02-11 17:04:35 -08001998
1999 // The logic simplifies if we can assume that this function is only called on
2000 // the merge host.
2001 if (!isMergeHost()) {
2002 loge("processMergeComplete :: We are not the merge host!");
2003 return;
Anthony Leeb8799fe2014-11-03 15:13:47 -08002004 }
2005
Anthony Lee71382692014-10-30 10:50:10 -07002006 ImsCall.Listener listener;
Tyler Gunn047d8102015-01-30 15:21:11 -08002007 boolean swapRequired = false;
Tyler Gunn047d8102015-01-30 15:21:11 -08002008
Uma Maheswari Ramalingam95be7c62015-05-06 23:08:17 -07002009 ImsCall finalHostCall;
2010 ImsCall finalPeerCall;
2011
2012 synchronized(ImsCall.this) {
Anthony Leec479f662015-02-11 17:04:35 -08002013 if (isMultiparty()) {
Anthony Leec479f662015-02-11 17:04:35 -08002014 setIsMerged(false);
Uma Maheswari Ramalingam95be7c62015-05-06 23:08:17 -07002015 // if case handles Case 4 explained in callSessionMergeComplete
2016 // otherwise it is case 5
2017 if (!mMergeRequestedByConference) {
2018 // single call in fg, conference call in bg.
2019 // Finally conf call becomes active after conference
2020 this.mHold = false;
2021 swapRequired = true;
Tyler Gunn047d8102015-01-30 15:21:11 -08002022 }
Anju Mathapati818c09d2015-08-31 14:30:47 -07002023 mMergePeer.markCallAsMerged(false);
Uma Maheswari Ramalingam95be7c62015-05-06 23:08:17 -07002024 finalHostCall = this;
2025 finalPeerCall = mMergePeer;
Tyler Gunn047d8102015-01-30 15:21:11 -08002026 } else {
Anthony Leec479f662015-02-11 17:04:35 -08002027 // If we are here, we are not trying to merge a new call into an existing
2028 // conference. That means that there is a transient session on the merge
2029 // host that represents the future conference once all the parties
2030 // have been added to it. So make sure that it exists or else something
2031 // very wrong is going on.
2032 if (mTransientConferenceSession == null) {
2033 loge("processMergeComplete :: No transient session!");
2034 return;
Tyler Gunn047d8102015-01-30 15:21:11 -08002035 }
Anthony Leec479f662015-02-11 17:04:35 -08002036 if (mMergePeer == null) {
2037 loge("processMergeComplete :: No merge peer!");
2038 return;
Tyler Gunn047d8102015-01-30 15:21:11 -08002039 }
Tyler Gunn047d8102015-01-30 15:21:11 -08002040
Anthony Leec479f662015-02-11 17:04:35 -08002041 // Since we are the host, we have the transient session attached to us. Let's detach
2042 // it and figure out where we need to set it for the final conference configuration.
2043 ImsCallSession transientConferenceSession = mTransientConferenceSession;
2044 mTransientConferenceSession = null;
2045
2046 // Clear the listener for this transient session, we'll create a new listener
2047 // when it is attached to the final ImsCall that it should live on.
2048 transientConferenceSession.setListener(null);
2049
2050 // Determine which call the transient session should be moved to. If the current
2051 // call session is still alive and the merge peer's session is not, we have a
2052 // situation where the current call failed to merge into the conference but the
2053 // merge peer did merge in to the conference. In this type of scenario the current
2054 // call will continue as a single party call, yet the background call will become
2055 // the conference.
2056
Uma Maheswari Ramalingam95be7c62015-05-06 23:08:17 -07002057 // handles Case 3 explained in callSessionMergeComplete
Anthony Leec479f662015-02-11 17:04:35 -08002058 if (isSessionAlive(mSession) && !isSessionAlive(mMergePeer.getCallSession())) {
2059 // I'm the host but we are moving the transient session to the peer since its
2060 // session was disconnected and my session is still alive. This signifies that
2061 // their session was properly added to the conference but mine was not because
2062 // it is probably in the held state as opposed to part of the final conference.
2063 // In this case, we need to set isMerged to false on both calls so the
2064 // disconnect sound is called when either call disconnects.
2065 // Note that this case is only valid if this is an initial conference being
2066 // brought up.
Uma Maheswari Ramalingam95be7c62015-05-06 23:08:17 -07002067 mMergePeer.mHold = false;
2068 this.mHold = true;
Anju Mathapatid0fb6642015-07-22 13:55:57 -07002069 if (mConferenceParticipants != null && !mConferenceParticipants.isEmpty()) {
2070 mMergePeer.mConferenceParticipants = mConferenceParticipants;
2071 }
2072 // At this point both host & peer will have participant information.
2073 // Peer will transition to host & the participant information
2074 // from that will be used
2075 // HostCall that failed to merge will remain as a single call with
2076 // mConferenceParticipants, which should not be used.
2077 // Expectation is that if this call becomes part of a conference call in future,
2078 // mConferenceParticipants will be overriten with new CEP that is received.
Anthony Leec479f662015-02-11 17:04:35 -08002079 finalHostCall = mMergePeer;
2080 finalPeerCall = this;
2081 swapRequired = true;
2082 setIsMerged(false);
2083 mMergePeer.setIsMerged(false);
Anthony Lee68048512015-03-18 15:04:18 -07002084 if (CONF_DBG) {
2085 logi("processMergeComplete :: transient will transfer to merge peer");
Tyler Gunn047d8102015-01-30 15:21:11 -08002086 }
Anthony Lee68048512015-03-18 15:04:18 -07002087 } else if (!isSessionAlive(mSession) &&
2088 isSessionAlive(mMergePeer.getCallSession())) {
Uma Maheswari Ramalingam95be7c62015-05-06 23:08:17 -07002089 // Handles case 2 explained in callSessionMergeComplete
Anthony Leec479f662015-02-11 17:04:35 -08002090 // The transient session stays with us and the disconnect sound should be played
2091 // when the merge peer eventually disconnects since it was not actually added to
2092 // the conference and is probably sitting in the held state.
2093 finalHostCall = this;
2094 finalPeerCall = mMergePeer;
2095 swapRequired = false;
2096 setIsMerged(false);
2097 mMergePeer.setIsMerged(false); // Play the disconnect sound
Anthony Lee68048512015-03-18 15:04:18 -07002098 if (CONF_DBG) {
2099 logi("processMergeComplete :: transient will stay with the merge host");
Tyler Gunn047d8102015-01-30 15:21:11 -08002100 }
Anthony Leec479f662015-02-11 17:04:35 -08002101 } else {
Uma Maheswari Ramalingam95be7c62015-05-06 23:08:17 -07002102 // Handles case 1 explained in callSessionMergeComplete
Anthony Leec479f662015-02-11 17:04:35 -08002103 // The transient session stays with us and the disconnect sound should not be
2104 // played when we ripple up the disconnect for the merge peer because it was
2105 // only disconnected to be added to the conference.
2106 finalHostCall = this;
2107 finalPeerCall = mMergePeer;
Anju Mathapati818c09d2015-08-31 14:30:47 -07002108 mMergePeer.markCallAsMerged(false);
Anthony Leec479f662015-02-11 17:04:35 -08002109 swapRequired = false;
2110 setIsMerged(false);
2111 mMergePeer.setIsMerged(true);
Anthony Lee68048512015-03-18 15:04:18 -07002112 if (CONF_DBG) {
2113 logi("processMergeComplete :: transient will stay with us (I'm the host).");
Anthony Leec479f662015-02-11 17:04:35 -08002114 }
Tyler Gunn047d8102015-01-30 15:21:11 -08002115 }
Anthony Leec479f662015-02-11 17:04:35 -08002116
Anthony Lee68048512015-03-18 15:04:18 -07002117 if (CONF_DBG) {
2118 logi("processMergeComplete :: call=" + finalHostCall + " is the final host");
Anthony Leec479f662015-02-11 17:04:35 -08002119 }
2120
2121 // Add the transient session to the ImsCall that ended up being the host for the
2122 // conference.
2123 finalHostCall.setTransientSessionAsPrimary(transientConferenceSession);
Tyler Gunn047d8102015-01-30 15:21:11 -08002124 }
2125
Anthony Leec479f662015-02-11 17:04:35 -08002126 listener = finalHostCall.mListener;
Tyler Gunn047d8102015-01-30 15:21:11 -08002127
Anju Mathapati1c108882015-11-17 18:59:24 +05302128 updateCallProfile(finalPeerCall);
2129 updateCallProfile(finalHostCall);
2130
Anthony Leec479f662015-02-11 17:04:35 -08002131 // Clear all the merge related flags.
2132 clearMergeInfo();
2133
2134 // For the final peer...let's bubble up any possible disconnects that we had
2135 // during the merge process
2136 finalPeerCall.notifySessionTerminatedDuringMerge();
2137 // For the final host, let's just bury the disconnects that we my have received
2138 // during the merge process since we are now the host of the conference call.
2139 finalHostCall.clearSessionTerminationFlags();
Tyler Gunn25394092015-04-01 09:40:02 -07002140
2141 // Keep track of the fact that merge host is the origin of a conference call in
2142 // progress. This is important so that we can later determine if a multiparty ImsCall
2143 // is multiparty because it was the origin of a conference call, or because it is a
2144 // member of a conference on another device.
2145 finalHostCall.mIsConferenceHost = true;
Anthony Lee71382692014-10-30 10:50:10 -07002146 }
2147 if (listener != null) {
2148 try {
Uma Maheswari Ramalingam95be7c62015-05-06 23:08:17 -07002149 // finalPeerCall will have the participant that was not merged and
2150 // it will be held state
2151 // if peer was merged successfully, finalPeerCall will be null
2152 listener.onCallMerged(finalHostCall, finalPeerCall, swapRequired);
Anthony Lee71382692014-10-30 10:50:10 -07002153 } catch (Throwable t) {
Anthony Leeb8799fe2014-11-03 15:13:47 -08002154 loge("processMergeComplete :: ", t);
Anthony Lee71382692014-10-30 10:50:10 -07002155 }
Tyler Gunn6c0b0d02015-07-01 16:39:43 -07002156 if (mConferenceParticipants != null && !mConferenceParticipants.isEmpty()) {
Uma Maheswari Ramalingama1ed7b02015-05-20 14:26:42 -07002157 try {
Anju Mathapatid0fb6642015-07-22 13:55:57 -07002158 listener.onConferenceParticipantsStateChanged(finalHostCall,
2159 mConferenceParticipants);
Uma Maheswari Ramalingama1ed7b02015-05-20 14:26:42 -07002160 } catch (Throwable t) {
2161 loge("processMergeComplete :: ", t);
2162 }
2163 }
Anthony Lee71382692014-10-30 10:50:10 -07002164 }
Anthony Lee71382692014-10-30 10:50:10 -07002165 return;
2166 }
2167
Anju Mathapati1c108882015-11-17 18:59:24 +05302168 private static void updateCallProfile(ImsCall call) {
2169 if (call != null) {
2170 call.updateCallProfile();
2171 }
2172 }
2173
2174 private void updateCallProfile() {
2175 synchronized (mLockObj) {
2176 if (mSession != null) {
Tyler Gunn1ac24852016-06-22 10:04:23 -07002177 setCallProfile(mSession.getCallProfile());
Anju Mathapati1c108882015-11-17 18:59:24 +05302178 }
2179 }
2180 }
2181
Anthony Lee71382692014-10-30 10:50:10 -07002182 /**
Tyler Gunn047d8102015-01-30 15:21:11 -08002183 * Handles the case where the session has ended during a merge by reporting the termination
2184 * reason to listeners.
2185 */
2186 private void notifySessionTerminatedDuringMerge() {
2187 ImsCall.Listener listener;
2188 boolean notifyFailure = false;
2189 ImsReasonInfo notifyFailureReasonInfo = null;
2190
2191 synchronized(ImsCall.this) {
2192 listener = mListener;
2193 if (mSessionEndDuringMerge) {
2194 // Set some local variables that will send out a notification about a
2195 // previously buried termination callback for our primary session now that
2196 // we know that this is not due to the conference call merging successfully.
Anthony Lee68048512015-03-18 15:04:18 -07002197 if (CONF_DBG) {
2198 logi("notifySessionTerminatedDuringMerge ::reporting terminate during merge");
Tyler Gunn047d8102015-01-30 15:21:11 -08002199 }
2200 notifyFailure = true;
2201 notifyFailureReasonInfo = mSessionEndDuringMergeReasonInfo;
2202 }
Anthony Leec479f662015-02-11 17:04:35 -08002203 clearSessionTerminationFlags();
Tyler Gunn047d8102015-01-30 15:21:11 -08002204 }
2205
2206 if (listener != null && notifyFailure) {
2207 try {
2208 processCallTerminated(notifyFailureReasonInfo);
2209 } catch (Throwable t) {
2210 loge("notifySessionTerminatedDuringMerge :: ", t);
2211 }
2212 }
2213 }
2214
Anthony Leec479f662015-02-11 17:04:35 -08002215 private void clearSessionTerminationFlags() {
2216 mSessionEndDuringMerge = false;
2217 mSessionEndDuringMergeReasonInfo = null;
2218 }
2219
Uma Maheswari Ramalingama1ed7b02015-05-20 14:26:42 -07002220 /**
Anthony Lee71382692014-10-30 10:50:10 -07002221 * We received a callback from ImsCallSession that a merge failed. Clean up all
Anthony Leec479f662015-02-11 17:04:35 -08002222 * internal state to represent this state change. The calling function is a callback
2223 * and should have been called on the session that was in the foreground
2224 * when merge() was originally called. It is assumed that this function will be called
2225 * on the merge host.
Anthony Lee71382692014-10-30 10:50:10 -07002226 *
Anthony Lee71382692014-10-30 10:50:10 -07002227 * @param reasonInfo The {@link ImsReasonInfo} why the merge failed.
2228 */
Anthony Leeb8799fe2014-11-03 15:13:47 -08002229 private void processMergeFailed(ImsReasonInfo reasonInfo) {
Anthony Lee68048512015-03-18 15:04:18 -07002230 logi("processMergeFailed :: reason=" + reasonInfo);
Tyler Gunn047d8102015-01-30 15:21:11 -08002231
2232 ImsCall.Listener listener;
Anthony Lee71382692014-10-30 10:50:10 -07002233 synchronized(ImsCall.this) {
Anthony Leec479f662015-02-11 17:04:35 -08002234 // The logic simplifies if we can assume that this function is only called on
2235 // the merge host.
2236 if (!isMergeHost()) {
2237 loge("processMergeFailed :: We are not the merge host!");
2238 return;
2239 }
2240
Anthony Lee61d41c12015-04-02 09:40:01 -07002241 // Try to clean up the transient session if it exists.
2242 if (mTransientConferenceSession != null) {
Anthony Lee71382692014-10-30 10:50:10 -07002243 mTransientConferenceSession.setListener(null);
2244 mTransientConferenceSession = null;
2245 }
Tyler Gunn047d8102015-01-30 15:21:11 -08002246
Anthony Leec479f662015-02-11 17:04:35 -08002247 listener = mListener;
2248
Anthony Lee61d41c12015-04-02 09:40:01 -07002249 // Ensure the calls being conferenced into the conference has isMerged = false.
Anthony Leec479f662015-02-11 17:04:35 -08002250 // Ensure any terminations are surfaced from this session.
Anju Mathapati818c09d2015-08-31 14:30:47 -07002251 markCallAsMerged(true);
2252 setCallSessionMergePending(false);
Anthony Leec479f662015-02-11 17:04:35 -08002253 notifySessionTerminatedDuringMerge();
Anthony Lee61d41c12015-04-02 09:40:01 -07002254
Anju Mathapati818c09d2015-08-31 14:30:47 -07002255 // Perform the same cleanup on the merge peer if it exists.
Anthony Lee61d41c12015-04-02 09:40:01 -07002256 if (mMergePeer != null) {
Anju Mathapati818c09d2015-08-31 14:30:47 -07002257 mMergePeer.markCallAsMerged(true);
2258 mMergePeer.setCallSessionMergePending(false);
Anthony Lee61d41c12015-04-02 09:40:01 -07002259 mMergePeer.notifySessionTerminatedDuringMerge();
2260 } else {
2261 loge("processMergeFailed :: No merge peer!");
2262 }
Anthony Leec479f662015-02-11 17:04:35 -08002263
2264 // Clear all the various flags around coordinating this merge.
2265 clearMergeInfo();
Anthony Lee71382692014-10-30 10:50:10 -07002266 }
2267 if (listener != null) {
2268 try {
2269 listener.onCallMergeFailed(ImsCall.this, reasonInfo);
2270 } catch (Throwable t) {
Anthony Leeb8799fe2014-11-03 15:13:47 -08002271 loge("processMergeFailed :: ", t);
Anthony Lee71382692014-10-30 10:50:10 -07002272 }
2273 }
Anthony Leec479f662015-02-11 17:04:35 -08002274
Anthony Lee71382692014-10-30 10:50:10 -07002275 return;
2276 }
2277
Tyler Gunnafb53c82016-08-05 14:38:13 -07002278 @VisibleForTesting
2279 public class ImsCallSessionListenerProxy extends ImsCallSession.Listener {
Wink Savilleef36ef62014-06-11 08:39:38 -07002280 @Override
Anthony Lee71382692014-10-30 10:50:10 -07002281 public void callSessionProgressing(ImsCallSession session, ImsStreamMediaProfile profile) {
Anthony Lee68048512015-03-18 15:04:18 -07002282 logi("callSessionProgressing :: session=" + session + " profile=" + profile);
2283
Anthony Lee71382692014-10-30 10:50:10 -07002284 if (isTransientConferenceSession(session)) {
Anthony Leec479f662015-02-11 17:04:35 -08002285 // If it is a transient (conference) session, there is no action for this signal.
Anthony Lee68048512015-03-18 15:04:18 -07002286 logi("callSessionProgressing :: not supported for transient conference session=" +
Anthony Leeb8799fe2014-11-03 15:13:47 -08002287 session);
Anthony Lee71382692014-10-30 10:50:10 -07002288 return;
2289 }
2290
Wink Savilleef36ef62014-06-11 08:39:38 -07002291 ImsCall.Listener listener;
2292
2293 synchronized(ImsCall.this) {
2294 listener = mListener;
2295 mCallProfile.mMediaProfile.copyFrom(profile);
2296 }
2297
2298 if (listener != null) {
2299 try {
2300 listener.onCallProgressing(ImsCall.this);
2301 } catch (Throwable t) {
2302 loge("callSessionProgressing :: ", t);
2303 }
2304 }
2305 }
2306
2307 @Override
Anthony Lee71382692014-10-30 10:50:10 -07002308 public void callSessionStarted(ImsCallSession session, ImsCallProfile profile) {
Anthony Lee68048512015-03-18 15:04:18 -07002309 logi("callSessionStarted :: session=" + session + " profile=" + profile);
Anthony Leec479f662015-02-11 17:04:35 -08002310
2311 if (!isTransientConferenceSession(session)) {
2312 // In the case that we are in the middle of a merge (either host or peer), we have
2313 // closure as far as this call's primary session is concerned. If we are not
2314 // merging...its a NOOP.
2315 setCallSessionMergePending(false);
2316 } else {
Anthony Lee68048512015-03-18 15:04:18 -07002317 logi("callSessionStarted :: on transient session=" + session);
Anthony Leec479f662015-02-11 17:04:35 -08002318 return;
Wink Savilleef36ef62014-06-11 08:39:38 -07002319 }
2320
Anthony Leec479f662015-02-11 17:04:35 -08002321 if (isTransientConferenceSession(session)) {
2322 // No further processing is needed if this is the transient session.
Anthony Leeb8799fe2014-11-03 15:13:47 -08002323 return;
2324 }
2325
Wink Savilleef36ef62014-06-11 08:39:38 -07002326 ImsCall.Listener listener;
2327
2328 synchronized(ImsCall.this) {
2329 listener = mListener;
Tyler Gunn1ac24852016-06-22 10:04:23 -07002330 setCallProfile(profile);
Wink Savilleef36ef62014-06-11 08:39:38 -07002331 }
2332
2333 if (listener != null) {
2334 try {
2335 listener.onCallStarted(ImsCall.this);
2336 } catch (Throwable t) {
2337 loge("callSessionStarted :: ", t);
2338 }
2339 }
2340 }
2341
2342 @Override
Anthony Lee71382692014-10-30 10:50:10 -07002343 public void callSessionStartFailed(ImsCallSession session, ImsReasonInfo reasonInfo) {
Anthony Lee68048512015-03-18 15:04:18 -07002344 loge("callSessionStartFailed :: session=" + session + " reasonInfo=" + reasonInfo);
2345
Anthony Lee71382692014-10-30 10:50:10 -07002346 if (isTransientConferenceSession(session)) {
Anthony Leec479f662015-02-11 17:04:35 -08002347 // We should not get this callback for a transient session.
Anthony Lee68048512015-03-18 15:04:18 -07002348 logi("callSessionStartFailed :: not supported for transient conference session=" +
Anthony Leeb8799fe2014-11-03 15:13:47 -08002349 session);
Anthony Lee71382692014-10-30 10:50:10 -07002350 return;
2351 }
2352
Wink Savilleef36ef62014-06-11 08:39:38 -07002353 ImsCall.Listener listener;
2354
2355 synchronized(ImsCall.this) {
2356 listener = mListener;
2357 mLastReasonInfo = reasonInfo;
2358 }
2359
2360 if (listener != null) {
2361 try {
2362 listener.onCallStartFailed(ImsCall.this, reasonInfo);
2363 } catch (Throwable t) {
2364 loge("callSessionStarted :: ", t);
2365 }
2366 }
2367 }
2368
2369 @Override
Anthony Lee71382692014-10-30 10:50:10 -07002370 public void callSessionTerminated(ImsCallSession session, ImsReasonInfo reasonInfo) {
Anthony Lee68048512015-03-18 15:04:18 -07002371 logi("callSessionTerminated :: session=" + session + " reasonInfo=" + reasonInfo);
2372
Anthony Leec479f662015-02-11 17:04:35 -08002373 if (isTransientConferenceSession(session)) {
Anthony Lee68048512015-03-18 15:04:18 -07002374 logi("callSessionTerminated :: on transient session=" + session);
Anthony Leec479f662015-02-11 17:04:35 -08002375 // This is bad, it should be treated much a callSessionMergeFailed since the
2376 // transient session only exists when in the process of a merge and the
2377 // termination of this session is effectively the end of the merge.
2378 processMergeFailed(reasonInfo);
Anthony Lee71382692014-10-30 10:50:10 -07002379 return;
2380 }
2381
Tyler Gunn25a72fc2016-08-11 13:16:54 -07002382 if (mOverrideReason != ImsReasonInfo.CODE_UNSPECIFIED) {
2383 logi("callSessionTerminated :: overrideReasonInfo=" + mOverrideReason);
2384 reasonInfo = new ImsReasonInfo(mOverrideReason, reasonInfo.getExtraCode(),
2385 reasonInfo.getExtraMessage());
2386 }
2387
Anthony Leec479f662015-02-11 17:04:35 -08002388 // Process the termination first. If we are in the midst of establishing a conference
2389 // call, we may bury this callback until we are done. If there so no conference
2390 // call, the code after this function will be a NOOP.
2391 processCallTerminated(reasonInfo);
2392
Tyler Gunn047d8102015-01-30 15:21:11 -08002393 // If session has terminated, it is no longer pending merge.
2394 setCallSessionMergePending(false);
2395
Wink Savilleef36ef62014-06-11 08:39:38 -07002396 }
2397
2398 @Override
Anthony Lee71382692014-10-30 10:50:10 -07002399 public void callSessionHeld(ImsCallSession session, ImsCallProfile profile) {
Anthony Lee68048512015-03-18 15:04:18 -07002400 logi("callSessionHeld :: session=" + session + "profile=" + profile);
Wink Savilleef36ef62014-06-11 08:39:38 -07002401 ImsCall.Listener listener;
2402
2403 synchronized(ImsCall.this) {
Tyler Gunn047d8102015-01-30 15:21:11 -08002404 // If the session was held, it is no longer pending a merge -- this means it could
2405 // not be merged into the conference and was held instead.
2406 setCallSessionMergePending(false);
2407
Tyler Gunn1ac24852016-06-22 10:04:23 -07002408 setCallProfile(profile);
Wink Savilleef36ef62014-06-11 08:39:38 -07002409
2410 if (mUpdateRequest == UPDATE_HOLD_MERGE) {
Anthony Leec479f662015-02-11 17:04:35 -08002411 // This hold request was made to set the stage for a merge.
Wink Savilleef36ef62014-06-11 08:39:38 -07002412 mergeInternal();
2413 return;
2414 }
2415
2416 mUpdateRequest = UPDATE_NONE;
2417 listener = mListener;
2418 }
2419
2420 if (listener != null) {
2421 try {
2422 listener.onCallHeld(ImsCall.this);
2423 } catch (Throwable t) {
2424 loge("callSessionHeld :: ", t);
2425 }
2426 }
2427 }
2428
2429 @Override
Anthony Lee71382692014-10-30 10:50:10 -07002430 public void callSessionHoldFailed(ImsCallSession session, ImsReasonInfo reasonInfo) {
Anthony Lee68048512015-03-18 15:04:18 -07002431 loge("callSessionHoldFailed :: session" + session + "reasonInfo=" + reasonInfo);
2432
Anthony Lee71382692014-10-30 10:50:10 -07002433 if (isTransientConferenceSession(session)) {
Anthony Leec479f662015-02-11 17:04:35 -08002434 // We should not get this callback for a transient session.
Anthony Lee68048512015-03-18 15:04:18 -07002435 logi("callSessionHoldFailed :: not supported for transient conference session=" +
Anthony Leeb8799fe2014-11-03 15:13:47 -08002436 session);
Anthony Lee71382692014-10-30 10:50:10 -07002437 return;
2438 }
2439
Shriram Ganeshd3adfad2015-05-31 10:06:15 -07002440 logi("callSessionHoldFailed :: session=" + session +
2441 ", reasonInfo=" + reasonInfo);
2442
2443 synchronized (mLockObj) {
Uma Maheswari Ramalingama1ed7b02015-05-20 14:26:42 -07002444 mHold = false;
2445 }
2446
Wink Savilleef36ef62014-06-11 08:39:38 -07002447 boolean isHoldForMerge = false;
2448 ImsCall.Listener listener;
2449
2450 synchronized(ImsCall.this) {
2451 if (mUpdateRequest == UPDATE_HOLD_MERGE) {
2452 isHoldForMerge = true;
2453 }
2454
2455 mUpdateRequest = UPDATE_NONE;
2456 listener = mListener;
2457 }
2458
Wink Savilleef36ef62014-06-11 08:39:38 -07002459 if (listener != null) {
2460 try {
2461 listener.onCallHoldFailed(ImsCall.this, reasonInfo);
2462 } catch (Throwable t) {
2463 loge("callSessionHoldFailed :: ", t);
2464 }
2465 }
2466 }
2467
Tyler Gunncc0f78f2017-12-04 13:01:55 -08002468 /**
2469 * Indicates that an {@link ImsCallSession} has been remotely held. This can be due to the
2470 * remote party holding the current call, or swapping between calls.
2471 * @param session the session which was held.
2472 * @param profile the profile for the held call.
2473 */
Wink Savilleef36ef62014-06-11 08:39:38 -07002474 @Override
Anthony Lee71382692014-10-30 10:50:10 -07002475 public void callSessionHoldReceived(ImsCallSession session, ImsCallProfile profile) {
Anthony Lee68048512015-03-18 15:04:18 -07002476 logi("callSessionHoldReceived :: session=" + session + "profile=" + profile);
2477
Anthony Lee71382692014-10-30 10:50:10 -07002478 if (isTransientConferenceSession(session)) {
Anthony Leec479f662015-02-11 17:04:35 -08002479 // We should not get this callback for a transient session.
Anthony Lee68048512015-03-18 15:04:18 -07002480 logi("callSessionHoldReceived :: not supported for transient conference session=" +
Anthony Leeb8799fe2014-11-03 15:13:47 -08002481 session);
Anthony Lee71382692014-10-30 10:50:10 -07002482 return;
2483 }
2484
Wink Savilleef36ef62014-06-11 08:39:38 -07002485 ImsCall.Listener listener;
2486
2487 synchronized(ImsCall.this) {
2488 listener = mListener;
Tyler Gunn1ac24852016-06-22 10:04:23 -07002489 setCallProfile(profile);
Wink Savilleef36ef62014-06-11 08:39:38 -07002490 }
2491
2492 if (listener != null) {
2493 try {
2494 listener.onCallHoldReceived(ImsCall.this);
2495 } catch (Throwable t) {
2496 loge("callSessionHoldReceived :: ", t);
2497 }
2498 }
2499 }
2500
Tyler Gunncc0f78f2017-12-04 13:01:55 -08002501 /**
2502 * Indicates that an {@link ImsCallSession} has been remotely resumed. This can be due to
2503 * the remote party un-holding the current call, or swapping back to this call.
2504 * @param session the session which was resumed.
2505 * @param profile the profile for the held call.
2506 */
Wink Savilleef36ef62014-06-11 08:39:38 -07002507 @Override
Anthony Lee71382692014-10-30 10:50:10 -07002508 public void callSessionResumed(ImsCallSession session, ImsCallProfile profile) {
Anthony Lee68048512015-03-18 15:04:18 -07002509 logi("callSessionResumed :: session=" + session + "profile=" + profile);
2510
Anthony Lee71382692014-10-30 10:50:10 -07002511 if (isTransientConferenceSession(session)) {
Anthony Lee68048512015-03-18 15:04:18 -07002512 logi("callSessionResumed :: not supported for transient conference session=" +
Anthony Leeb8799fe2014-11-03 15:13:47 -08002513 session);
Anthony Lee71382692014-10-30 10:50:10 -07002514 return;
2515 }
2516
Anthony Leec479f662015-02-11 17:04:35 -08002517 // If this call was pending a merge, it is not anymore. This is the case when we
2518 // are merging in a new call into an existing conference.
2519 setCallSessionMergePending(false);
Wink Savilleef36ef62014-06-11 08:39:38 -07002520
Anthony Leec479f662015-02-11 17:04:35 -08002521 // TOOD: When we are merging a new call into an existing conference we are waiting
2522 // for 2 triggers to let us know that the conference has been established, the first
2523 // is a termination for the new calls (since it is added to the conference) the second
2524 // would be a resume on the existing conference. If the resume comes first, then
2525 // we will make the onCallResumed() callback and its unclear how this will behave if
2526 // the termination has not come yet.
2527
2528 ImsCall.Listener listener;
Wink Savilleef36ef62014-06-11 08:39:38 -07002529 synchronized(ImsCall.this) {
2530 listener = mListener;
Tyler Gunn1ac24852016-06-22 10:04:23 -07002531 setCallProfile(profile);
Wink Savilleef36ef62014-06-11 08:39:38 -07002532 mUpdateRequest = UPDATE_NONE;
Uma Maheswari Ramalingam095839a2014-12-03 14:47:15 -08002533 mHold = false;
Wink Savilleef36ef62014-06-11 08:39:38 -07002534 }
2535
2536 if (listener != null) {
2537 try {
2538 listener.onCallResumed(ImsCall.this);
2539 } catch (Throwable t) {
2540 loge("callSessionResumed :: ", t);
2541 }
2542 }
2543 }
2544
2545 @Override
Anthony Lee71382692014-10-30 10:50:10 -07002546 public void callSessionResumeFailed(ImsCallSession session, ImsReasonInfo reasonInfo) {
Anthony Lee68048512015-03-18 15:04:18 -07002547 loge("callSessionResumeFailed :: session=" + session + "reasonInfo=" + reasonInfo);
2548
Anthony Lee71382692014-10-30 10:50:10 -07002549 if (isTransientConferenceSession(session)) {
Anthony Lee68048512015-03-18 15:04:18 -07002550 logi("callSessionResumeFailed :: not supported for transient conference session=" +
Anthony Leeb8799fe2014-11-03 15:13:47 -08002551 session);
Anthony Lee71382692014-10-30 10:50:10 -07002552 return;
2553 }
2554
Uma Maheswari Ramalingama1ed7b02015-05-20 14:26:42 -07002555 synchronized(mLockObj) {
2556 mHold = true;
2557 }
2558
Wink Savilleef36ef62014-06-11 08:39:38 -07002559 ImsCall.Listener listener;
2560
2561 synchronized(ImsCall.this) {
2562 listener = mListener;
2563 mUpdateRequest = UPDATE_NONE;
2564 }
2565
2566 if (listener != null) {
2567 try {
2568 listener.onCallResumeFailed(ImsCall.this, reasonInfo);
2569 } catch (Throwable t) {
2570 loge("callSessionResumeFailed :: ", t);
2571 }
2572 }
2573 }
2574
2575 @Override
Anthony Lee71382692014-10-30 10:50:10 -07002576 public void callSessionResumeReceived(ImsCallSession session, ImsCallProfile profile) {
Anthony Lee68048512015-03-18 15:04:18 -07002577 logi("callSessionResumeReceived :: session=" + session + "profile=" + profile);
2578
Anthony Lee71382692014-10-30 10:50:10 -07002579 if (isTransientConferenceSession(session)) {
Anthony Lee68048512015-03-18 15:04:18 -07002580 logi("callSessionResumeReceived :: not supported for transient conference session=" +
Anthony Leeb8799fe2014-11-03 15:13:47 -08002581 session);
Anthony Lee71382692014-10-30 10:50:10 -07002582 return;
2583 }
2584
Wink Savilleef36ef62014-06-11 08:39:38 -07002585 ImsCall.Listener listener;
2586
2587 synchronized(ImsCall.this) {
2588 listener = mListener;
Tyler Gunn1ac24852016-06-22 10:04:23 -07002589 setCallProfile(profile);
Wink Savilleef36ef62014-06-11 08:39:38 -07002590 }
2591
2592 if (listener != null) {
2593 try {
2594 listener.onCallResumeReceived(ImsCall.this);
2595 } catch (Throwable t) {
2596 loge("callSessionResumeReceived :: ", t);
2597 }
2598 }
2599 }
2600
2601 @Override
Tyler Gunn3f2b0aa2014-10-24 10:02:19 -07002602 public void callSessionMergeStarted(ImsCallSession session,
Wink Savilleef36ef62014-06-11 08:39:38 -07002603 ImsCallSession newSession, ImsCallProfile profile) {
Anthony Lee68048512015-03-18 15:04:18 -07002604 logi("callSessionMergeStarted :: session=" + session + " newSession=" + newSession +
2605 ", profile=" + profile);
Wink Savilleef36ef62014-06-11 08:39:38 -07002606
Anthony Lee71382692014-10-30 10:50:10 -07002607 return;
Wink Savilleef36ef62014-06-11 08:39:38 -07002608 }
2609
Uma Maheswari Ramalingam95be7c62015-05-06 23:08:17 -07002610 /*
2611 * This method check if session exists as a session on the current
2612 * ImsCall or its counterpart if it is in the process of a conference
2613 */
2614 private boolean doesCallSessionExistsInMerge(ImsCallSession cs) {
2615 String callId = cs.getCallId();
2616 return ((isMergeHost() && Objects.equals(mMergePeer.mSession.getCallId(), callId)) ||
2617 (isMergePeer() && Objects.equals(mMergeHost.mSession.getCallId(), callId)) ||
2618 Objects.equals(mSession.getCallId(), callId));
2619 }
2620
2621 /**
2622 * We received a callback from ImsCallSession that merge completed.
Anju Mathapatid9ed8742015-07-17 14:54:10 -07002623 * @param newSession - this session can have 2 values based on the below scenarios
Uma Maheswari Ramalingam95be7c62015-05-06 23:08:17 -07002624 *
2625 * Conference Scenarios :
2626 * Case 1 - 3 way success case
2627 * Case 2 - 3 way success case but held call fails to merge
2628 * Case 3 - 3 way success case but active call fails to merge
2629 * case 4 - 4 way success case, where merge is initiated on the foreground single-party
2630 * call and the conference (mergeHost) is the background call.
2631 * case 5 - 4 way success case, where merge is initiated on the foreground conference
2632 * call (mergeHost) and the single party call is in the background.
2633 *
2634 * Conference Result:
Anju Mathapatid9ed8742015-07-17 14:54:10 -07002635 * session : new session after conference
2636 * newSession = new session for case 1, 2, 3.
2637 * Should be considered as mTransientConferencession
2638 * newSession = Active conference session for case 5 will be null
2639 * mergehost was foreground call
2640 * mTransientConferencession will be null
2641 * newSession = Active conference session for case 4 will be null
2642 * mergeHost was background call
2643 * mTransientConferencession will be null
Uma Maheswari Ramalingam95be7c62015-05-06 23:08:17 -07002644 */
Wink Savilleef36ef62014-06-11 08:39:38 -07002645 @Override
Anju Mathapatid9ed8742015-07-17 14:54:10 -07002646 public void callSessionMergeComplete(ImsCallSession newSession) {
2647 logi("callSessionMergeComplete :: newSession =" + newSession);
Uma Maheswari Ramalingam95be7c62015-05-06 23:08:17 -07002648 if (!isMergeHost()) {
2649 // Handles case 4
2650 mMergeHost.processMergeComplete();
2651 } else {
Anju Mathapatid9ed8742015-07-17 14:54:10 -07002652 // Handles case 1, 2, 3
2653 if (newSession != null) {
2654 mTransientConferenceSession = doesCallSessionExistsInMerge(newSession) ?
2655 null: newSession;
2656 }
2657 // Handles case 5
Uma Maheswari Ramalingam95be7c62015-05-06 23:08:17 -07002658 processMergeComplete();
2659 }
Wink Savilleef36ef62014-06-11 08:39:38 -07002660 }
2661
2662 @Override
Anthony Lee71382692014-10-30 10:50:10 -07002663 public void callSessionMergeFailed(ImsCallSession session, ImsReasonInfo reasonInfo) {
Anthony Lee68048512015-03-18 15:04:18 -07002664 loge("callSessionMergeFailed :: session=" + session + "reasonInfo=" + reasonInfo);
Anthony Leec479f662015-02-11 17:04:35 -08002665
2666 // Its possible that there could be threading issues with the other thread handling
2667 // the other call. This could affect our state.
2668 synchronized (ImsCall.this) {
Anthony Leec479f662015-02-11 17:04:35 -08002669 // Let's tell our parent ImsCall that the merge has failed and we need to clean
2670 // up any temporary, transient state. Note this only gets called for an initial
2671 // conference. If a merge into an existing conference fails, the two sessions will
2672 // just go back to their original state (ACTIVE or HELD).
2673 if (isMergeHost()) {
2674 processMergeFailed(reasonInfo);
2675 } else if (mMergeHost != null) {
2676 mMergeHost.processMergeFailed(reasonInfo);
2677 } else {
2678 loge("callSessionMergeFailed :: No merge host for this conference!");
2679 }
Anthony Lee71382692014-10-30 10:50:10 -07002680 }
Anthony Lee71382692014-10-30 10:50:10 -07002681 }
2682
2683 @Override
2684 public void callSessionUpdated(ImsCallSession session, ImsCallProfile profile) {
Anthony Lee68048512015-03-18 15:04:18 -07002685 logi("callSessionUpdated :: session=" + session + " profile=" + profile);
2686
Anthony Lee71382692014-10-30 10:50:10 -07002687 if (isTransientConferenceSession(session)) {
Anthony Lee68048512015-03-18 15:04:18 -07002688 logi("callSessionUpdated :: not supported for transient conference session=" +
Anthony Leeb8799fe2014-11-03 15:13:47 -08002689 session);
Anthony Lee71382692014-10-30 10:50:10 -07002690 return;
2691 }
2692
Wink Savilleef36ef62014-06-11 08:39:38 -07002693 ImsCall.Listener listener;
2694
2695 synchronized(ImsCall.this) {
2696 listener = mListener;
Tyler Gunn1ac24852016-06-22 10:04:23 -07002697 setCallProfile(profile);
Wink Savilleef36ef62014-06-11 08:39:38 -07002698 }
2699
2700 if (listener != null) {
2701 try {
2702 listener.onCallUpdated(ImsCall.this);
2703 } catch (Throwable t) {
2704 loge("callSessionUpdated :: ", t);
2705 }
2706 }
2707 }
2708
2709 @Override
Anthony Lee71382692014-10-30 10:50:10 -07002710 public void callSessionUpdateFailed(ImsCallSession session, ImsReasonInfo reasonInfo) {
Anthony Lee68048512015-03-18 15:04:18 -07002711 loge("callSessionUpdateFailed :: session=" + session + " reasonInfo=" + reasonInfo);
2712
Anthony Lee71382692014-10-30 10:50:10 -07002713 if (isTransientConferenceSession(session)) {
Anthony Lee68048512015-03-18 15:04:18 -07002714 logi("callSessionUpdateFailed :: not supported for transient conference session=" +
Anthony Leeb8799fe2014-11-03 15:13:47 -08002715 session);
Anthony Lee71382692014-10-30 10:50:10 -07002716 return;
2717 }
2718
Wink Savilleef36ef62014-06-11 08:39:38 -07002719 ImsCall.Listener listener;
2720
2721 synchronized(ImsCall.this) {
2722 listener = mListener;
2723 mUpdateRequest = UPDATE_NONE;
2724 }
2725
2726 if (listener != null) {
2727 try {
2728 listener.onCallUpdateFailed(ImsCall.this, reasonInfo);
2729 } catch (Throwable t) {
2730 loge("callSessionUpdateFailed :: ", t);
2731 }
2732 }
2733 }
2734
2735 @Override
Anthony Lee71382692014-10-30 10:50:10 -07002736 public void callSessionUpdateReceived(ImsCallSession session, ImsCallProfile profile) {
Anthony Lee68048512015-03-18 15:04:18 -07002737 logi("callSessionUpdateReceived :: session=" + session + " profile=" + profile);
2738
Anthony Lee71382692014-10-30 10:50:10 -07002739 if (isTransientConferenceSession(session)) {
Anthony Lee68048512015-03-18 15:04:18 -07002740 logi("callSessionUpdateReceived :: not supported for transient conference " +
Anthony Leeb8799fe2014-11-03 15:13:47 -08002741 "session=" + session);
Anthony Lee71382692014-10-30 10:50:10 -07002742 return;
2743 }
2744
Wink Savilleef36ef62014-06-11 08:39:38 -07002745 ImsCall.Listener listener;
2746
2747 synchronized(ImsCall.this) {
2748 listener = mListener;
2749 mProposedCallProfile = profile;
2750 mUpdateRequest = UPDATE_UNSPECIFIED;
2751 }
2752
2753 if (listener != null) {
2754 try {
2755 listener.onCallUpdateReceived(ImsCall.this);
2756 } catch (Throwable t) {
2757 loge("callSessionUpdateReceived :: ", t);
2758 }
2759 }
2760 }
2761
2762 @Override
Anthony Lee71382692014-10-30 10:50:10 -07002763 public void callSessionConferenceExtended(ImsCallSession session, ImsCallSession newSession,
2764 ImsCallProfile profile) {
Anthony Lee68048512015-03-18 15:04:18 -07002765 logi("callSessionConferenceExtended :: session=" + session + " newSession=" +
2766 newSession + ", profile=" + profile);
2767
Anthony Lee71382692014-10-30 10:50:10 -07002768 if (isTransientConferenceSession(session)) {
Anthony Lee68048512015-03-18 15:04:18 -07002769 logi("callSessionConferenceExtended :: not supported for transient conference " +
Anthony Leeb8799fe2014-11-03 15:13:47 -08002770 "session=" + session);
Anthony Lee71382692014-10-30 10:50:10 -07002771 return;
2772 }
2773
Wink Savilleef36ef62014-06-11 08:39:38 -07002774 ImsCall newCall = createNewCall(newSession, profile);
2775
2776 if (newCall == null) {
2777 callSessionConferenceExtendFailed(session, new ImsReasonInfo());
2778 return;
2779 }
2780
2781 ImsCall.Listener listener;
2782
2783 synchronized(ImsCall.this) {
2784 listener = mListener;
2785 mUpdateRequest = UPDATE_NONE;
2786 }
2787
2788 if (listener != null) {
2789 try {
2790 listener.onCallConferenceExtended(ImsCall.this, newCall);
2791 } catch (Throwable t) {
2792 loge("callSessionConferenceExtended :: ", t);
2793 }
2794 }
2795 }
2796
2797 @Override
2798 public void callSessionConferenceExtendFailed(ImsCallSession session,
2799 ImsReasonInfo reasonInfo) {
Anthony Lee68048512015-03-18 15:04:18 -07002800 loge("callSessionConferenceExtendFailed :: reasonInfo=" + reasonInfo);
2801
Anthony Lee71382692014-10-30 10:50:10 -07002802 if (isTransientConferenceSession(session)) {
Anthony Lee68048512015-03-18 15:04:18 -07002803 logi("callSessionConferenceExtendFailed :: not supported for transient " +
Anthony Leeb8799fe2014-11-03 15:13:47 -08002804 "conference session=" + session);
Anthony Lee71382692014-10-30 10:50:10 -07002805 return;
2806 }
2807
Wink Savilleef36ef62014-06-11 08:39:38 -07002808 ImsCall.Listener listener;
2809
2810 synchronized(ImsCall.this) {
2811 listener = mListener;
2812 mUpdateRequest = UPDATE_NONE;
2813 }
2814
2815 if (listener != null) {
2816 try {
2817 listener.onCallConferenceExtendFailed(ImsCall.this, reasonInfo);
2818 } catch (Throwable t) {
2819 loge("callSessionConferenceExtendFailed :: ", t);
2820 }
2821 }
2822 }
2823
2824 @Override
2825 public void callSessionConferenceExtendReceived(ImsCallSession session,
2826 ImsCallSession newSession, ImsCallProfile profile) {
Anthony Lee68048512015-03-18 15:04:18 -07002827 logi("callSessionConferenceExtendReceived :: newSession=" + newSession +
2828 ", profile=" + profile);
2829
Anthony Lee71382692014-10-30 10:50:10 -07002830 if (isTransientConferenceSession(session)) {
Anthony Lee68048512015-03-18 15:04:18 -07002831 logi("callSessionConferenceExtendReceived :: not supported for transient " +
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08002832 "conference session" + session);
Anthony Lee71382692014-10-30 10:50:10 -07002833 return;
2834 }
2835
Wink Savilleef36ef62014-06-11 08:39:38 -07002836 ImsCall newCall = createNewCall(newSession, profile);
2837
2838 if (newCall == null) {
2839 // Should all the calls be terminated...???
2840 return;
2841 }
2842
2843 ImsCall.Listener listener;
2844
2845 synchronized(ImsCall.this) {
2846 listener = mListener;
2847 }
2848
2849 if (listener != null) {
2850 try {
2851 listener.onCallConferenceExtendReceived(ImsCall.this, newCall);
2852 } catch (Throwable t) {
2853 loge("callSessionConferenceExtendReceived :: ", t);
2854 }
2855 }
2856 }
2857
2858 @Override
2859 public void callSessionInviteParticipantsRequestDelivered(ImsCallSession session) {
Anthony Lee68048512015-03-18 15:04:18 -07002860 logi("callSessionInviteParticipantsRequestDelivered ::");
2861
Anthony Lee71382692014-10-30 10:50:10 -07002862 if (isTransientConferenceSession(session)) {
Anthony Lee68048512015-03-18 15:04:18 -07002863 logi("callSessionInviteParticipantsRequestDelivered :: not supported for " +
Anthony Lee71382692014-10-30 10:50:10 -07002864 "conference session=" + session);
2865 return;
2866 }
2867
Wink Savilleef36ef62014-06-11 08:39:38 -07002868 ImsCall.Listener listener;
2869
2870 synchronized(ImsCall.this) {
2871 listener = mListener;
2872 }
2873
2874 if (listener != null) {
2875 try {
2876 listener.onCallInviteParticipantsRequestDelivered(ImsCall.this);
2877 } catch (Throwable t) {
2878 loge("callSessionInviteParticipantsRequestDelivered :: ", t);
2879 }
2880 }
2881 }
2882
2883 @Override
2884 public void callSessionInviteParticipantsRequestFailed(ImsCallSession session,
2885 ImsReasonInfo reasonInfo) {
Anthony Lee68048512015-03-18 15:04:18 -07002886 loge("callSessionInviteParticipantsRequestFailed :: reasonInfo=" + reasonInfo);
2887
Anthony Lee71382692014-10-30 10:50:10 -07002888 if (isTransientConferenceSession(session)) {
Anthony Lee68048512015-03-18 15:04:18 -07002889 logi("callSessionInviteParticipantsRequestFailed :: not supported for " +
Anthony Lee71382692014-10-30 10:50:10 -07002890 "conference session=" + session);
2891 return;
2892 }
2893
Wink Savilleef36ef62014-06-11 08:39:38 -07002894 ImsCall.Listener listener;
2895
2896 synchronized(ImsCall.this) {
2897 listener = mListener;
2898 }
2899
2900 if (listener != null) {
2901 try {
2902 listener.onCallInviteParticipantsRequestFailed(ImsCall.this, reasonInfo);
2903 } catch (Throwable t) {
2904 loge("callSessionInviteParticipantsRequestFailed :: ", t);
2905 }
2906 }
2907 }
2908
2909 @Override
2910 public void callSessionRemoveParticipantsRequestDelivered(ImsCallSession session) {
Anthony Lee68048512015-03-18 15:04:18 -07002911 logi("callSessionRemoveParticipantsRequestDelivered ::");
2912
Anthony Lee71382692014-10-30 10:50:10 -07002913 if (isTransientConferenceSession(session)) {
Anthony Lee68048512015-03-18 15:04:18 -07002914 logi("callSessionRemoveParticipantsRequestDelivered :: not supported for " +
Anthony Lee71382692014-10-30 10:50:10 -07002915 "conference session=" + session);
2916 return;
2917 }
2918
Wink Savilleef36ef62014-06-11 08:39:38 -07002919 ImsCall.Listener listener;
2920
2921 synchronized(ImsCall.this) {
2922 listener = mListener;
2923 }
2924
2925 if (listener != null) {
2926 try {
2927 listener.onCallRemoveParticipantsRequestDelivered(ImsCall.this);
2928 } catch (Throwable t) {
2929 loge("callSessionRemoveParticipantsRequestDelivered :: ", t);
2930 }
2931 }
2932 }
2933
2934 @Override
2935 public void callSessionRemoveParticipantsRequestFailed(ImsCallSession session,
2936 ImsReasonInfo reasonInfo) {
Anthony Lee68048512015-03-18 15:04:18 -07002937 loge("callSessionRemoveParticipantsRequestFailed :: reasonInfo=" + reasonInfo);
2938
Anthony Lee71382692014-10-30 10:50:10 -07002939 if (isTransientConferenceSession(session)) {
Anthony Lee68048512015-03-18 15:04:18 -07002940 logi("callSessionRemoveParticipantsRequestFailed :: not supported for " +
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08002941 "conference session=" + session);
Anthony Lee71382692014-10-30 10:50:10 -07002942 return;
2943 }
2944
Wink Savilleef36ef62014-06-11 08:39:38 -07002945 ImsCall.Listener listener;
2946
2947 synchronized(ImsCall.this) {
2948 listener = mListener;
2949 }
2950
2951 if (listener != null) {
2952 try {
2953 listener.onCallRemoveParticipantsRequestFailed(ImsCall.this, reasonInfo);
2954 } catch (Throwable t) {
2955 loge("callSessionRemoveParticipantsRequestFailed :: ", t);
2956 }
2957 }
2958 }
2959
2960 @Override
2961 public void callSessionConferenceStateUpdated(ImsCallSession session,
2962 ImsConferenceState state) {
Anthony Lee68048512015-03-18 15:04:18 -07002963 logi("callSessionConferenceStateUpdated :: state=" + state);
2964
Tyler Gunn938116f2014-10-27 09:15:23 -07002965 conferenceStateUpdated(state);
Wink Savilleef36ef62014-06-11 08:39:38 -07002966 }
2967
2968 @Override
Anthony Lee71382692014-10-30 10:50:10 -07002969 public void callSessionUssdMessageReceived(ImsCallSession session, int mode,
2970 String ussdMessage) {
Anthony Lee68048512015-03-18 15:04:18 -07002971 logi("callSessionUssdMessageReceived :: mode=" + mode + ", ussdMessage=" +
2972 ussdMessage);
2973
Anthony Lee71382692014-10-30 10:50:10 -07002974 if (isTransientConferenceSession(session)) {
Anthony Lee68048512015-03-18 15:04:18 -07002975 logi("callSessionUssdMessageReceived :: not supported for transient " +
Anthony Leeb8799fe2014-11-03 15:13:47 -08002976 "conference session=" + session);
Anthony Lee71382692014-10-30 10:50:10 -07002977 return;
2978 }
2979
Wink Savilleef36ef62014-06-11 08:39:38 -07002980 ImsCall.Listener listener;
2981
2982 synchronized(ImsCall.this) {
2983 listener = mListener;
2984 }
2985
2986 if (listener != null) {
2987 try {
2988 listener.onCallUssdMessageReceived(ImsCall.this, mode, ussdMessage);
2989 } catch (Throwable t) {
2990 loge("callSessionUssdMessageReceived :: ", t);
2991 }
2992 }
2993 }
Pavel Zhamaitsiak987bab82014-11-16 15:29:09 -08002994
2995 @Override
2996 public void callSessionTtyModeReceived(ImsCallSession session, int mode) {
Anthony Lee68048512015-03-18 15:04:18 -07002997 logi("callSessionTtyModeReceived :: mode=" + mode);
Pavel Zhamaitsiak987bab82014-11-16 15:29:09 -08002998
Pavel Zhamaitsiaka6cae362014-12-10 17:31:33 -08002999 ImsCall.Listener listener;
3000
3001 synchronized(ImsCall.this) {
3002 listener = mListener;
3003 }
3004
3005 if (listener != null) {
3006 try {
3007 listener.onCallSessionTtyModeReceived(ImsCall.this, mode);
3008 } catch (Throwable t) {
3009 loge("callSessionTtyModeReceived :: ", t);
Pavel Zhamaitsiak987bab82014-11-16 15:29:09 -08003010 }
3011 }
3012 }
Rekha Kumar14631742015-02-04 10:47:00 -08003013
Tyler Gunn25394092015-04-01 09:40:02 -07003014 /**
3015 * Notifies of a change to the multiparty state for this {@code ImsCallSession}.
3016 *
3017 * @param session The call session.
3018 * @param isMultiParty {@code true} if the session became multiparty, {@code false}
3019 * otherwise.
3020 */
3021 @Override
3022 public void callSessionMultipartyStateChanged(ImsCallSession session,
3023 boolean isMultiParty) {
3024 if (VDBG) {
Pavel Zhamaitsiak691a1cc2015-04-09 10:14:55 -07003025 logi("callSessionMultipartyStateChanged isMultiParty: " + (isMultiParty ? "Y"
Tyler Gunn25394092015-04-01 09:40:02 -07003026 : "N"));
3027 }
3028
3029 ImsCall.Listener listener;
3030
3031 synchronized(ImsCall.this) {
3032 listener = mListener;
3033 }
3034
3035 if (listener != null) {
3036 try {
3037 listener.onMultipartyStateChanged(ImsCall.this, isMultiParty);
3038 } catch (Throwable t) {
3039 loge("callSessionMultipartyStateChanged :: ", t);
3040 }
3041 }
3042 }
3043
Rekha Kumar14631742015-02-04 10:47:00 -08003044 public void callSessionHandover(ImsCallSession session, int srcAccessTech,
3045 int targetAccessTech, ImsReasonInfo reasonInfo) {
Anthony Lee68048512015-03-18 15:04:18 -07003046 logi("callSessionHandover :: session=" + session + ", srcAccessTech=" +
3047 srcAccessTech + ", targetAccessTech=" + targetAccessTech + ", reasonInfo=" +
3048 reasonInfo);
Rekha Kumar14631742015-02-04 10:47:00 -08003049
3050 ImsCall.Listener listener;
3051
3052 synchronized(ImsCall.this) {
3053 listener = mListener;
3054 }
3055
3056 if (listener != null) {
3057 try {
3058 listener.onCallHandover(ImsCall.this, srcAccessTech, targetAccessTech,
3059 reasonInfo);
3060 } catch (Throwable t) {
3061 loge("callSessionHandover :: ", t);
3062 }
3063 }
3064 }
3065
3066 @Override
3067 public void callSessionHandoverFailed(ImsCallSession session, int srcAccessTech,
3068 int targetAccessTech, ImsReasonInfo reasonInfo) {
Anthony Lee68048512015-03-18 15:04:18 -07003069 loge("callSessionHandoverFailed :: session=" + session + ", srcAccessTech=" +
3070 srcAccessTech + ", targetAccessTech=" + targetAccessTech + ", reasonInfo=" +
3071 reasonInfo);
Rekha Kumar14631742015-02-04 10:47:00 -08003072
3073 ImsCall.Listener listener;
3074
3075 synchronized(ImsCall.this) {
3076 listener = mListener;
3077 }
3078
3079 if (listener != null) {
3080 try {
3081 listener.onCallHandoverFailed(ImsCall.this, srcAccessTech, targetAccessTech,
3082 reasonInfo);
3083 } catch (Throwable t) {
3084 loge("callSessionHandoverFailed :: ", t);
3085 }
3086 }
3087 }
Shriram Ganeshd3adfad2015-05-31 10:06:15 -07003088
3089 @Override
3090 public void callSessionSuppServiceReceived(ImsCallSession session,
3091 ImsSuppServiceNotification suppServiceInfo ) {
3092 if (isTransientConferenceSession(session)) {
3093 logi("callSessionSuppServiceReceived :: not supported for transient conference"
3094 + " session=" + session);
3095 return;
3096 }
3097
3098 logi("callSessionSuppServiceReceived :: session=" + session +
3099 ", suppServiceInfo" + suppServiceInfo);
3100
3101 ImsCall.Listener listener;
3102
3103 synchronized(ImsCall.this) {
3104 listener = mListener;
3105 }
3106
3107 if (listener != null) {
3108 try {
3109 listener.onCallSuppServiceReceived(ImsCall.this, suppServiceInfo);
3110 } catch (Throwable t) {
3111 loge("callSessionSuppServiceReceived :: ", t);
3112 }
3113 }
3114 }
Hall Liubd3f3772017-03-22 15:38:23 -07003115
3116 @Override
3117 public void callSessionRttModifyRequestReceived(ImsCallSession session,
3118 ImsCallProfile callProfile) {
3119 ImsCall.Listener listener;
Hall Liub77d8972018-01-22 19:15:05 -08003120 logi("callSessionRttModifyRequestReceived");
Hall Liubd3f3772017-03-22 15:38:23 -07003121
3122 synchronized(ImsCall.this) {
3123 listener = mListener;
3124 }
3125
3126 if (!callProfile.mMediaProfile.isRttCall()) {
3127 logi("callSessionRttModifyRequestReceived:: ignoring request, requested profile " +
3128 "is not RTT.");
3129 return;
3130 }
3131
3132 if (listener != null) {
3133 try {
3134 listener.onRttModifyRequestReceived(ImsCall.this);
3135 } catch (Throwable t) {
3136 loge("callSessionRttModifyRequestReceived:: ", t);
3137 }
3138 }
3139 }
3140
3141 @Override
3142 public void callSessionRttModifyResponseReceived(int status) {
3143 ImsCall.Listener listener;
3144
Hall Liub77d8972018-01-22 19:15:05 -08003145 logi("callSessionRttModifyResponseReceived");
Hall Liubd3f3772017-03-22 15:38:23 -07003146 synchronized(ImsCall.this) {
3147 listener = mListener;
3148 }
3149
3150 if (listener != null) {
3151 try {
3152 listener.onRttModifyResponseReceived(ImsCall.this, status);
3153 } catch (Throwable t) {
3154 loge("callSessionRttModifyResponseReceived:: ", t);
3155 }
3156 }
3157 }
3158
3159 @Override
3160 public void callSessionRttMessageReceived(String rttMessage) {
3161 ImsCall.Listener listener;
3162
3163 synchronized(ImsCall.this) {
3164 listener = mListener;
3165 }
3166
3167 if (listener != null) {
3168 try {
3169 listener.onRttMessageReceived(ImsCall.this, rttMessage);
3170 } catch (Throwable t) {
3171 loge("callSessionRttModifyResponseReceived:: ", t);
3172 }
3173 }
3174 }
Wink Savilleef36ef62014-06-11 08:39:38 -07003175 }
Tyler Gunn938116f2014-10-27 09:15:23 -07003176
3177 /**
3178 * Report a new conference state to the current {@link ImsCall} and inform listeners of the
3179 * change. Marked as {@code VisibleForTesting} so that the
3180 * {@code com.android.internal.telephony.TelephonyTester} class can inject a test conference
3181 * event package into a regular ongoing IMS call.
3182 *
3183 * @param state The {@link ImsConferenceState}.
3184 */
3185 @VisibleForTesting
3186 public void conferenceStateUpdated(ImsConferenceState state) {
3187 Listener listener;
3188
3189 synchronized(this) {
3190 notifyConferenceStateUpdated(state);
3191 listener = mListener;
3192 }
3193
3194 if (listener != null) {
3195 try {
3196 listener.onCallConferenceStateUpdated(this, state);
3197 } catch (Throwable t) {
3198 loge("callSessionConferenceStateUpdated :: ", t);
3199 }
3200 }
3201 }
Tyler Gunn168c6342014-11-18 08:40:49 -08003202
3203 /**
3204 * Provides a human-readable string representation of an update request.
3205 *
3206 * @param updateRequest The update request.
3207 * @return The string representation.
3208 */
3209 private String updateRequestToString(int updateRequest) {
3210 switch (updateRequest) {
3211 case UPDATE_NONE:
3212 return "NONE";
3213 case UPDATE_HOLD:
3214 return "HOLD";
3215 case UPDATE_HOLD_MERGE:
3216 return "HOLD_MERGE";
3217 case UPDATE_RESUME:
3218 return "RESUME";
3219 case UPDATE_MERGE:
3220 return "MERGE";
3221 case UPDATE_EXTEND_TO_CONFERENCE:
3222 return "EXTEND_TO_CONFERENCE";
3223 case UPDATE_UNSPECIFIED:
3224 return "UNSPECIFIED";
3225 default:
3226 return "UNKNOWN";
3227 }
3228 }
3229
3230 /**
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08003231 * Clears the merge peer for this call, ensuring that the peer's connection to this call is also
3232 * severed at the same time.
3233 */
Anthony Leec479f662015-02-11 17:04:35 -08003234 private void clearMergeInfo() {
Anthony Lee68048512015-03-18 15:04:18 -07003235 if (CONF_DBG) {
3236 logi("clearMergeInfo :: clearing all merge info");
Anthony Leec479f662015-02-11 17:04:35 -08003237 }
Tyler Gunn047d8102015-01-30 15:21:11 -08003238
Anthony Leec479f662015-02-11 17:04:35 -08003239 // First clear out the merge partner then clear ourselves out.
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08003240 if (mMergeHost != null) {
3241 mMergeHost.mMergePeer = null;
Libin.Tang@motorola.com165aed52015-02-05 22:12:09 -06003242 mMergeHost.mUpdateRequest = UPDATE_NONE;
Anthony Leec479f662015-02-11 17:04:35 -08003243 mMergeHost.mCallSessionMergePending = false;
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08003244 }
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08003245 if (mMergePeer != null) {
3246 mMergePeer.mMergeHost = null;
Libin.Tang@motorola.com165aed52015-02-05 22:12:09 -06003247 mMergePeer.mUpdateRequest = UPDATE_NONE;
Anthony Leec479f662015-02-11 17:04:35 -08003248 mMergePeer.mCallSessionMergePending = false;
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08003249 }
Anthony Leec479f662015-02-11 17:04:35 -08003250 mMergeHost = null;
3251 mMergePeer = null;
3252 mUpdateRequest = UPDATE_NONE;
3253 mCallSessionMergePending = false;
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08003254 }
3255
3256 /**
3257 * Sets the merge peer for the current call. The merge peer is the background call that will be
3258 * merged into this call. On the merge peer, sets the merge host to be this call.
3259 *
3260 * @param mergePeer The peer call to be merged into this one.
3261 */
3262 private void setMergePeer(ImsCall mergePeer) {
3263 mMergePeer = mergePeer;
3264 mMergeHost = null;
3265
3266 mergePeer.mMergeHost = ImsCall.this;
3267 mergePeer.mMergePeer = null;
3268 }
3269
3270 /**
3271 * Sets the merge hody for the current call. The merge host is the foreground call this call
3272 * will be merged into. On the merge host, sets the merge peer to be this call.
3273 *
3274 * @param mergeHost The merge host this call will be merged into.
3275 */
3276 public void setMergeHost(ImsCall mergeHost) {
3277 mMergeHost = mergeHost;
3278 mMergePeer = null;
3279
3280 mergeHost.mMergeHost = null;
3281 mergeHost.mMergePeer = ImsCall.this;
3282 }
3283
3284 /**
3285 * Determines if the current call is in the process of merging with another call or conference.
3286 *
3287 * @return {@code true} if in the process of merging.
3288 */
3289 private boolean isMerging() {
3290 return mMergePeer != null || mMergeHost != null;
3291 }
3292
3293 /**
3294 * Determines if the current call is the host of the merge.
3295 *
3296 * @return {@code true} if the call is the merge host.
3297 */
3298 private boolean isMergeHost() {
3299 return mMergePeer != null && mMergeHost == null;
3300 }
3301
3302 /**
3303 * Determines if the current call is the peer of the merge.
3304 *
3305 * @return {@code true} if the call is the merge peer.
3306 */
3307 private boolean isMergePeer() {
3308 return mMergePeer == null && mMergeHost != null;
3309 }
3310
3311 /**
Tyler Gunn047d8102015-01-30 15:21:11 -08003312 * Determines if the call session is pending merge into a conference or not.
3313 *
3314 * @return {@code true} if a merge into a conference is pending, {@code false} otherwise.
3315 */
Tyler Gunn083efaa2017-06-20 15:50:32 -07003316 public boolean isCallSessionMergePending() {
Tyler Gunn047d8102015-01-30 15:21:11 -08003317 return mCallSessionMergePending;
3318 }
3319
3320 /**
3321 * Sets flag indicating whether the call session is pending merge into a conference or not.
3322 *
3323 * @param callSessionMergePending {@code true} if a merge into the conference is pending,
3324 * {@code false} otherwise.
3325 */
3326 private void setCallSessionMergePending(boolean callSessionMergePending) {
3327 mCallSessionMergePending = callSessionMergePending;
3328 }
3329
3330 /**
3331 * Determines if there is a conference merge in process. If there is a merge in process,
3332 * determines if both the merge host and peer sessions have completed the merge process. This
3333 * means that we have received terminate or hold signals for the sessions, indicating that they
3334 * are no longer in the process of being merged into the conference.
3335 * <p>
Anthony Leec479f662015-02-11 17:04:35 -08003336 * The sessions are considered to have merged if: both calls still have merge peer/host
3337 * relationships configured, both sessions are not waiting to be merged into the conference,
3338 * and the transient conference session is alive in the case of an initial conference.
Tyler Gunn047d8102015-01-30 15:21:11 -08003339 *
3340 * @return {@code true} where the host and peer sessions have finished merging into the
3341 * conference, {@code false} if the merge has not yet completed, and {@code false} if there
3342 * is no conference merge in progress.
3343 */
Anthony Leec479f662015-02-11 17:04:35 -08003344 private boolean shouldProcessConferenceResult() {
3345 boolean areMergeTriggersDone = false;
Tyler Gunn047d8102015-01-30 15:21:11 -08003346
Anthony Leec479f662015-02-11 17:04:35 -08003347 synchronized (ImsCall.this) {
3348 // if there is a merge going on, then the merge host/peer relationships should have been
3349 // set up. This works for both the initial conference or merging a call into an
3350 // existing conference.
3351 if (!isMergeHost() && !isMergePeer()) {
Anthony Lee68048512015-03-18 15:04:18 -07003352 if (CONF_DBG) {
3353 loge("shouldProcessConferenceResult :: no merge in progress");
Anthony Leec479f662015-02-11 17:04:35 -08003354 }
3355 return false;
3356 }
3357
3358 // There is a merge in progress, so check the sessions to ensure:
3359 // 1. Both calls have completed being merged (or failing to merge) into the conference.
3360 // 2. The transient conference session is alive.
3361 if (isMergeHost()) {
Anthony Lee68048512015-03-18 15:04:18 -07003362 if (CONF_DBG) {
3363 logi("shouldProcessConferenceResult :: We are a merge host");
3364 logi("shouldProcessConferenceResult :: Here is the merge peer=" + mMergePeer);
Anthony Leec479f662015-02-11 17:04:35 -08003365 }
3366 areMergeTriggersDone = !isCallSessionMergePending() &&
3367 !mMergePeer.isCallSessionMergePending();
3368 if (!isMultiparty()) {
3369 // Only check the transient session when there is no existing conference
3370 areMergeTriggersDone &= isSessionAlive(mTransientConferenceSession);
3371 }
3372 } else if (isMergePeer()) {
Anthony Lee68048512015-03-18 15:04:18 -07003373 if (CONF_DBG) {
3374 logi("shouldProcessConferenceResult :: We are a merge peer");
3375 logi("shouldProcessConferenceResult :: Here is the merge host=" + mMergeHost);
Anthony Leec479f662015-02-11 17:04:35 -08003376 }
3377 areMergeTriggersDone = !isCallSessionMergePending() &&
3378 !mMergeHost.isCallSessionMergePending();
3379 if (!mMergeHost.isMultiparty()) {
3380 // Only check the transient session when there is no existing conference
3381 areMergeTriggersDone &= isSessionAlive(mMergeHost.mTransientConferenceSession);
3382 } else {
3383 // This else block is a special case for Verizon to handle these steps
3384 // 1. Establish a conference call.
3385 // 2. Add a new call (conference in in BG)
3386 // 3. Swap (conference active on FG)
3387 // 4. Merge
3388 // What happens here is that the BG call gets a terminated callback
3389 // because it was added to the conference. I've seen where
3390 // the FG gets no callback at all because its already active.
3391 // So if we continue to wait for it to set its isCallSessionMerging
3392 // flag to false...we'll be waiting forever.
3393 areMergeTriggersDone = !isCallSessionMergePending();
3394 }
3395 } else {
3396 // Realistically this shouldn't happen, but best to be safe.
3397 loge("shouldProcessConferenceResult : merge in progress but call is neither" +
Anthony Lee68048512015-03-18 15:04:18 -07003398 " host nor peer.");
Anthony Leec479f662015-02-11 17:04:35 -08003399 }
Anthony Lee68048512015-03-18 15:04:18 -07003400 if (CONF_DBG) {
3401 logi("shouldProcessConferenceResult :: returning:" +
Anthony Leec479f662015-02-11 17:04:35 -08003402 (areMergeTriggersDone ? "true" : "false"));
3403 }
Tyler Gunn047d8102015-01-30 15:21:11 -08003404 }
Anthony Leec479f662015-02-11 17:04:35 -08003405 return areMergeTriggersDone;
Tyler Gunn047d8102015-01-30 15:21:11 -08003406 }
3407
3408 /**
Tyler Gunn168c6342014-11-18 08:40:49 -08003409 * Provides a string representation of the {@link ImsCall}. Primarily intended for use in log
3410 * statements.
3411 *
3412 * @return String representation of call.
3413 */
3414 @Override
3415 public String toString() {
3416 StringBuilder sb = new StringBuilder();
3417 sb.append("[ImsCall objId:");
3418 sb.append(System.identityHashCode(this));
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08003419 sb.append(" onHold:");
3420 sb.append(isOnHold() ? "Y" : "N");
3421 sb.append(" mute:");
3422 sb.append(isMuted() ? "Y" : "N");
Pavel Zhamaitsiak50b03e92016-02-29 14:47:39 -08003423 if (mCallProfile != null) {
Jack Yu9f71a242016-08-08 11:59:18 -07003424 sb.append(" mCallProfile:" + mCallProfile);
Pavel Zhamaitsiak50b03e92016-02-29 14:47:39 -08003425 sb.append(" tech:");
3426 sb.append(mCallProfile.getCallExtra(ImsCallProfile.EXTRA_CALL_RAT_TYPE));
3427 }
Tyler Gunn168c6342014-11-18 08:40:49 -08003428 sb.append(" updateRequest:");
3429 sb.append(updateRequestToString(mUpdateRequest));
Tyler Gunn87466c52014-12-08 09:56:17 -08003430 sb.append(" merging:");
3431 sb.append(isMerging() ? "Y" : "N");
3432 if (isMerging()) {
3433 if (isMergePeer()) {
3434 sb.append("P");
3435 } else {
3436 sb.append("H");
3437 }
3438 }
Anthony Leec479f662015-02-11 17:04:35 -08003439 sb.append(" merge action pending:");
3440 sb.append(isCallSessionMergePending() ? "Y" : "N");
Tyler Gunn87466c52014-12-08 09:56:17 -08003441 sb.append(" merged:");
3442 sb.append(isMerged() ? "Y" : "N");
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08003443 sb.append(" multiParty:");
3444 sb.append(isMultiparty() ? "Y" : "N");
Tyler Gunn25394092015-04-01 09:40:02 -07003445 sb.append(" confHost:");
3446 sb.append(isConferenceHost() ? "Y" : "N");
Anthony Leec479f662015-02-11 17:04:35 -08003447 sb.append(" buried term:");
3448 sb.append(mSessionEndDuringMerge ? "Y" : "N");
Jack Yu9f71a242016-08-08 11:59:18 -07003449 sb.append(" isVideo: ");
3450 sb.append(isVideoCall() ? "Y" : "N");
Tyler Gunn1ac24852016-06-22 10:04:23 -07003451 sb.append(" wasVideo: ");
3452 sb.append(mWasVideoCall ? "Y" : "N");
Jack Yu9f71a242016-08-08 11:59:18 -07003453 sb.append(" isWifi: ");
3454 sb.append(isWifiCall() ? "Y" : "N");
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08003455 sb.append(" session:");
3456 sb.append(mSession);
Tyler Gunn168c6342014-11-18 08:40:49 -08003457 sb.append(" transientSession:");
3458 sb.append(mTransientConferenceSession);
3459 sb.append("]");
3460 return sb.toString();
3461 }
Anthony Lee68048512015-03-18 15:04:18 -07003462
3463 private void throwImsException(Throwable t, int code) throws ImsException {
3464 if (t instanceof ImsException) {
3465 throw (ImsException) t;
3466 } else {
3467 throw new ImsException(String.valueOf(code), t, code);
3468 }
3469 }
3470
3471 /**
3472 * Append the ImsCall information to the provided string. Usefull for as a logging helper.
3473 * @param s The original string
3474 * @return The original string with {@code ImsCall} information appended to it.
3475 */
3476 private String appendImsCallInfoToString(String s) {
3477 StringBuilder sb = new StringBuilder();
3478 sb.append(s);
3479 sb.append(" ImsCall=");
3480 sb.append(ImsCall.this);
3481 return sb.toString();
3482 }
3483
3484 /**
Tyler Gunn1ac24852016-06-22 10:04:23 -07003485 * Updates {@link #mWasVideoCall} based on the current {@link ImsCallProfile} for the call.
3486 *
3487 * @param profile The current {@link ImsCallProfile} for the call.
3488 */
3489 private void trackVideoStateHistory(ImsCallProfile profile) {
3490 mWasVideoCall = mWasVideoCall || profile.isVideoCall();
3491 }
3492
3493 /**
3494 * @return {@code true} if this call was a video call at some point in its life span,
3495 * {@code false} otherwise.
3496 */
3497 public boolean wasVideoCall() {
3498 return mWasVideoCall;
3499 }
3500
3501 /**
3502 * @return {@code true} if this call is a video call, {@code false} otherwise.
3503 */
3504 public boolean isVideoCall() {
3505 synchronized(mLockObj) {
3506 return mCallProfile != null && mCallProfile.isVideoCall();
3507 }
3508 }
3509
3510 /**
3511 * Determines if the current call radio access technology is over WIFI.
3512 * Note: This depends on the RIL exposing the {@link ImsCallProfile#EXTRA_CALL_RAT_TYPE} extra.
3513 * This method is primarily intended to be used when checking if answering an incoming audio
3514 * call should cause a wifi video call to drop (e.g.
3515 * {@link android.telephony.CarrierConfigManager#
3516 * KEY_DROP_VIDEO_CALL_WHEN_ANSWERING_AUDIO_CALL_BOOL} is set).
3517 *
3518 * @return {@code true} if the call is over WIFI, {@code false} otherwise.
3519 */
3520 public boolean isWifiCall() {
3521 synchronized(mLockObj) {
3522 if (mCallProfile == null) {
3523 return false;
3524 }
Tyler Gunn730752a2017-08-17 15:55:53 -07003525 int radioTechnology = getRadioTechnology();
3526 return radioTechnology == ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN;
3527 }
3528 }
3529
3530 /**
3531 * Determines the radio access technology for the {@link ImsCall}.
3532 * @return The {@link ServiceState} {@code RIL_RADIO_TECHNOLOGY_*} code in use.
3533 */
3534 public int getRadioTechnology() {
3535 synchronized(mLockObj) {
3536 if (mCallProfile == null) {
3537 return ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN;
3538 }
Tyler Gunn1ac24852016-06-22 10:04:23 -07003539 String callType = mCallProfile.getCallExtra(ImsCallProfile.EXTRA_CALL_RAT_TYPE);
Tyler Gunn8ae5fbb2016-07-13 17:24:04 -07003540 if (callType == null || callType.isEmpty()) {
3541 callType = mCallProfile.getCallExtra(ImsCallProfile.EXTRA_CALL_RAT_TYPE_ALT);
3542 }
Tyler Gunn1ac24852016-06-22 10:04:23 -07003543
3544 // The RIL (sadly) sends us the EXTRA_CALL_RAT_TYPE as a string extra, rather than an
3545 // integer extra, so we need to parse it.
3546 int radioTechnology = ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN;
3547 try {
3548 radioTechnology = Integer.parseInt(callType);
3549 } catch (NumberFormatException nfe) {
3550 radioTechnology = ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN;
3551 }
3552
Tyler Gunn730752a2017-08-17 15:55:53 -07003553 return radioTechnology;
Tyler Gunn1ac24852016-06-22 10:04:23 -07003554 }
3555 }
3556
3557 /**
Anthony Lee68048512015-03-18 15:04:18 -07003558 * Log a string to the radio buffer at the info level.
3559 * @param s The message to log
3560 */
3561 private void logi(String s) {
3562 Log.i(TAG, appendImsCallInfoToString(s));
3563 }
3564
3565 /**
3566 * Log a string to the radio buffer at the debug level.
3567 * @param s The message to log
3568 */
3569 private void logd(String s) {
3570 Log.d(TAG, appendImsCallInfoToString(s));
3571 }
3572
3573 /**
3574 * Log a string to the radio buffer at the verbose level.
3575 * @param s The message to log
3576 */
3577 private void logv(String s) {
3578 Log.v(TAG, appendImsCallInfoToString(s));
3579 }
3580
3581 /**
3582 * Log a string to the radio buffer at the error level.
3583 * @param s The message to log
3584 */
3585 private void loge(String s) {
3586 Log.e(TAG, appendImsCallInfoToString(s));
3587 }
3588
3589 /**
3590 * Log a string to the radio buffer at the error level with a throwable
3591 * @param s The message to log
3592 * @param t The associated throwable
3593 */
3594 private void loge(String s, Throwable t) {
3595 Log.e(TAG, appendImsCallInfoToString(s), t);
3596 }
Wink Savilleef36ef62014-06-11 08:39:38 -07003597}