blob: 450e5b70dcd1ecc9e739b6b97bbe76d3e7043c36 [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;
Tyler Gunn59656142014-10-28 13:52:11 -070031import android.telecom.ConferenceParticipant;
Rekha Kumar5aec2e92015-03-24 11:00:34 -070032import android.telecom.Connection;
Uma Maheswari Ramalingam95be7c62015-05-06 23:08:17 -070033import java.util.Objects;
Tyler Gunn9bd5ca52014-12-02 09:21:01 -080034import android.util.Log;
Wink Savilleef36ef62014-06-11 08:39:38 -070035
Wink Savilleef36ef62014-06-11 08:39:38 -070036import com.android.ims.internal.ICall;
37import com.android.ims.internal.ImsCallSession;
38import com.android.ims.internal.ImsStreamMediaSession;
Tyler Gunn938116f2014-10-27 09:15:23 -070039import com.android.internal.annotations.VisibleForTesting;
Wink Savilleef36ef62014-06-11 08:39:38 -070040
41/**
42 * Handles an IMS voice / video call over LTE. You can instantiate this class with
43 * {@link ImsManager}.
44 *
45 * @hide
46 */
47public class ImsCall implements ICall {
Wink Savilleef36ef62014-06-11 08:39:38 -070048 // Mode of USSD message
49 public static final int USSD_MODE_NOTIFY = 0;
50 public static final int USSD_MODE_REQUEST = 1;
51
52 private static final String TAG = "ImsCall";
Anthony Lee68048512015-03-18 15:04:18 -070053
54 // This flag is meant to be used as a debugging tool to quickly see all logs
55 // regardless of the actual log level set on this component.
Tyler Gunn6cb99be2014-12-02 14:16:30 -080056 private static final boolean FORCE_DEBUG = false; /* STOPSHIP if true */
Anthony Lee68048512015-03-18 15:04:18 -070057
58 // We will log messages guarded by these flags at the info level. If logging is required
59 // to occur at (and only at) a particular log level, please use the logd, logv and loge
60 // functions as those will not be affected by the value of FORCE_DEBUG at all.
61 // Otherwise, anything guarded by these flags will be logged at the info level since that
62 // level allows those statements ot be logged by default which supports the workflow of
63 // setting FORCE_DEBUG and knowing these logs will show up regardless of the actual log
64 // level of this component.
65 private static final boolean DBG = FORCE_DEBUG || Log.isLoggable(TAG, Log.DEBUG);
66 private static final boolean VDBG = FORCE_DEBUG || Log.isLoggable(TAG, Log.VERBOSE);
67 // This is a special flag that is used only to highlight specific log around bringing
68 // up and tearing down conference calls. At times, these errors are transient and hard to
69 // reproduce so we need to capture this information the first time.
70 // TODO: Set this flag to FORCE_DEBUG once the new conference call logic gets more mileage
71 // across different IMS implementations.
72 private static final boolean CONF_DBG = true;
Wink Savilleef36ef62014-06-11 08:39:38 -070073
Uma Maheswari Ramalingama1ed7b02015-05-20 14:26:42 -070074 private List<ConferenceParticipant> mConferenceParticipants;
Wink Savilleef36ef62014-06-11 08:39:38 -070075 /**
76 * Listener for events relating to an IMS call, such as when a call is being
Anthony Leec479f662015-02-11 17:04:35 -080077 * received ("on ringing") or a call is outgoing ("on calling").
Wink Savilleef36ef62014-06-11 08:39:38 -070078 * <p>Many of these events are also received by {@link ImsCallSession.Listener}.</p>
79 */
80 public static class Listener {
81 /**
82 * Called when a request is sent out to initiate a new call
83 * and 1xx response is received from the network.
84 * The default implementation calls {@link #onCallStateChanged}.
85 *
86 * @param call the call object that carries out the IMS call
87 */
88 public void onCallProgressing(ImsCall call) {
89 onCallStateChanged(call);
90 }
91
92 /**
93 * Called when the call is established.
94 * The default implementation calls {@link #onCallStateChanged}.
95 *
96 * @param call the call object that carries out the IMS call
97 */
98 public void onCallStarted(ImsCall call) {
99 onCallStateChanged(call);
100 }
101
102 /**
103 * Called when the call setup is failed.
104 * The default implementation calls {@link #onCallError}.
105 *
106 * @param call the call object that carries out the IMS call
107 * @param reasonInfo detailed reason of the call setup failure
108 */
109 public void onCallStartFailed(ImsCall call, ImsReasonInfo reasonInfo) {
110 onCallError(call, reasonInfo);
111 }
112
113 /**
114 * Called when the call is terminated.
115 * The default implementation calls {@link #onCallStateChanged}.
116 *
117 * @param call the call object that carries out the IMS call
118 * @param reasonInfo detailed reason of the call termination
119 */
120 public void onCallTerminated(ImsCall call, ImsReasonInfo reasonInfo) {
121 // Store the call termination reason
122
123 onCallStateChanged(call);
124 }
125
126 /**
127 * Called when the call is in hold.
128 * The default implementation calls {@link #onCallStateChanged}.
129 *
130 * @param call the call object that carries out the IMS call
131 */
132 public void onCallHeld(ImsCall call) {
133 onCallStateChanged(call);
134 }
135
136 /**
137 * Called when the call hold is failed.
138 * The default implementation calls {@link #onCallError}.
139 *
140 * @param call the call object that carries out the IMS call
141 * @param reasonInfo detailed reason of the call hold failure
142 */
143 public void onCallHoldFailed(ImsCall call, ImsReasonInfo reasonInfo) {
144 onCallError(call, reasonInfo);
145 }
146
147 /**
148 * Called when the call hold is received from the remote user.
149 * The default implementation calls {@link #onCallStateChanged}.
150 *
151 * @param call the call object that carries out the IMS call
152 */
153 public void onCallHoldReceived(ImsCall call) {
154 onCallStateChanged(call);
155 }
156
157 /**
158 * Called when the call is in call.
159 * The default implementation calls {@link #onCallStateChanged}.
160 *
161 * @param call the call object that carries out the IMS call
162 */
163 public void onCallResumed(ImsCall call) {
164 onCallStateChanged(call);
165 }
166
167 /**
168 * Called when the call resume is failed.
169 * The default implementation calls {@link #onCallError}.
170 *
171 * @param call the call object that carries out the IMS call
172 * @param reasonInfo detailed reason of the call resume failure
173 */
174 public void onCallResumeFailed(ImsCall call, ImsReasonInfo reasonInfo) {
175 onCallError(call, reasonInfo);
176 }
177
178 /**
179 * Called when the call resume is received from the remote user.
180 * The default implementation calls {@link #onCallStateChanged}.
181 *
182 * @param call the call object that carries out the IMS call
183 */
184 public void onCallResumeReceived(ImsCall call) {
185 onCallStateChanged(call);
186 }
187
188 /**
189 * Called when the call is in call.
190 * The default implementation calls {@link #onCallStateChanged}.
191 *
Uma Maheswari Ramalingam95be7c62015-05-06 23:08:17 -0700192 * @param call the call object that carries out the active IMS call
193 * @param peerCall the call object that carries out the held IMS call
Tyler Gunn047d8102015-01-30 15:21:11 -0800194 * @param swapCalls {@code true} if the foreground and background calls should be swapped
195 * now that the merge has completed.
Wink Savilleef36ef62014-06-11 08:39:38 -0700196 */
Uma Maheswari Ramalingam95be7c62015-05-06 23:08:17 -0700197 public void onCallMerged(ImsCall call, ImsCall peerCall, boolean swapCalls) {
Anthony Lee71382692014-10-30 10:50:10 -0700198 onCallStateChanged(call);
Wink Savilleef36ef62014-06-11 08:39:38 -0700199 }
200
201 /**
202 * Called when the call merge is failed.
203 * The default implementation calls {@link #onCallError}.
204 *
205 * @param call the call object that carries out the IMS call
206 * @param reasonInfo detailed reason of the call merge failure
207 */
208 public void onCallMergeFailed(ImsCall call, ImsReasonInfo reasonInfo) {
209 onCallError(call, reasonInfo);
210 }
211
212 /**
213 * Called when the call is updated (except for hold/unhold).
214 * The default implementation calls {@link #onCallStateChanged}.
215 *
216 * @param call the call object that carries out the IMS call
217 */
218 public void onCallUpdated(ImsCall call) {
219 onCallStateChanged(call);
220 }
221
222 /**
223 * Called when the call update is failed.
224 * The default implementation calls {@link #onCallError}.
225 *
226 * @param call the call object that carries out the IMS call
227 * @param reasonInfo detailed reason of the call update failure
228 */
229 public void onCallUpdateFailed(ImsCall call, ImsReasonInfo reasonInfo) {
230 onCallError(call, reasonInfo);
231 }
232
233 /**
234 * Called when the call update is received from the remote user.
235 *
236 * @param call the call object that carries out the IMS call
237 */
238 public void onCallUpdateReceived(ImsCall call) {
239 // no-op
240 }
241
242 /**
243 * Called when the call is extended to the conference call.
244 * The default implementation calls {@link #onCallStateChanged}.
245 *
246 * @param call the call object that carries out the IMS call
247 * @param newCall the call object that is extended to the conference from the active call
248 */
249 public void onCallConferenceExtended(ImsCall call, ImsCall newCall) {
Anthony Lee71382692014-10-30 10:50:10 -0700250 onCallStateChanged(call);
Wink Savilleef36ef62014-06-11 08:39:38 -0700251 }
252
253 /**
254 * Called when the conference extension is failed.
255 * The default implementation calls {@link #onCallError}.
256 *
257 * @param call the call object that carries out the IMS call
258 * @param reasonInfo detailed reason of the conference extension failure
259 */
260 public void onCallConferenceExtendFailed(ImsCall call,
261 ImsReasonInfo reasonInfo) {
262 onCallError(call, reasonInfo);
263 }
264
265 /**
266 * Called when the conference extension is received from the remote user.
267 *
268 * @param call the call object that carries out the IMS call
269 * @param newCall the call object that is extended to the conference from the active call
270 */
271 public void onCallConferenceExtendReceived(ImsCall call, ImsCall newCall) {
Anthony Lee71382692014-10-30 10:50:10 -0700272 onCallStateChanged(call);
Wink Savilleef36ef62014-06-11 08:39:38 -0700273 }
274
275 /**
276 * Called when the invitation request of the participants is delivered to
277 * the conference server.
278 *
279 * @param call the call object that carries out the IMS call
280 */
281 public void onCallInviteParticipantsRequestDelivered(ImsCall call) {
282 // no-op
283 }
284
285 /**
286 * Called when the invitation request of the participants is failed.
287 *
288 * @param call the call object that carries out the IMS call
289 * @param reasonInfo detailed reason of the conference invitation failure
290 */
291 public void onCallInviteParticipantsRequestFailed(ImsCall call,
292 ImsReasonInfo reasonInfo) {
293 // no-op
294 }
295
296 /**
297 * Called when the removal request of the participants is delivered to
298 * the conference server.
299 *
300 * @param call the call object that carries out the IMS call
301 */
302 public void onCallRemoveParticipantsRequestDelivered(ImsCall call) {
303 // no-op
304 }
305
306 /**
307 * Called when the removal request of the participants is failed.
308 *
309 * @param call the call object that carries out the IMS call
310 * @param reasonInfo detailed reason of the conference removal failure
311 */
312 public void onCallRemoveParticipantsRequestFailed(ImsCall call,
313 ImsReasonInfo reasonInfo) {
314 // no-op
315 }
316
317 /**
318 * Called when the conference state is updated.
319 *
320 * @param call the call object that carries out the IMS call
321 * @param state state of the participant who is participated in the conference call
322 */
323 public void onCallConferenceStateUpdated(ImsCall call, ImsConferenceState state) {
324 // no-op
325 }
326
327 /**
Tyler Gunn1c467602014-11-04 14:51:52 -0800328 * Called when the state of IMS conference participant(s) has changed.
Tyler Gunn59656142014-10-28 13:52:11 -0700329 *
330 * @param call the call object that carries out the IMS call.
Tyler Gunn1c467602014-11-04 14:51:52 -0800331 * @param participants the participant(s) and their new state information.
Tyler Gunn59656142014-10-28 13:52:11 -0700332 */
Tyler Gunn1c467602014-11-04 14:51:52 -0800333 public void onConferenceParticipantsStateChanged(ImsCall call,
334 List<ConferenceParticipant> participants) {
Tyler Gunn59656142014-10-28 13:52:11 -0700335 // no-op
336 }
337
338 /**
Wink Savilleef36ef62014-06-11 08:39:38 -0700339 * Called when the USSD message is received from the network.
340 *
341 * @param mode mode of the USSD message (REQUEST / NOTIFY)
342 * @param ussdMessage USSD message
343 */
344 public void onCallUssdMessageReceived(ImsCall call,
345 int mode, String ussdMessage) {
346 // no-op
347 }
348
349 /**
350 * Called when an error occurs. The default implementation is no op.
351 * overridden. The default implementation is no op. Error events are
352 * not re-directed to this callback and are handled in {@link #onCallError}.
353 *
354 * @param call the call object that carries out the IMS call
355 * @param reasonInfo detailed reason of this error
356 * @see ImsReasonInfo
357 */
358 public void onCallError(ImsCall call, ImsReasonInfo reasonInfo) {
359 // no-op
360 }
361
362 /**
363 * Called when an event occurs and the corresponding callback is not
364 * overridden. The default implementation is no op. Error events are
365 * not re-directed to this callback and are handled in {@link #onCallError}.
366 *
367 * @param call the call object that carries out the IMS call
368 */
369 public void onCallStateChanged(ImsCall call) {
370 // no-op
371 }
372
373 /**
Wink Savilleef36ef62014-06-11 08:39:38 -0700374 * Called when the call moves the hold state to the conversation state.
375 * For example, when merging the active & hold call, the state of all the hold call
376 * will be changed from hold state to conversation state.
377 * This callback method can be invoked even though the application does not trigger
378 * any operations.
379 *
380 * @param call the call object that carries out the IMS call
381 * @param state the detailed state of call state changes;
382 * Refer to CALL_STATE_* in {@link ImsCall}
383 */
384 public void onCallStateChanged(ImsCall call, int state) {
385 // no-op
386 }
Pavel Zhamaitsiaka6cae362014-12-10 17:31:33 -0800387
388 /**
Shriram Ganeshd3adfad2015-05-31 10:06:15 -0700389 * Called when the call supp service is received
390 * The default implementation calls {@link #onCallStateChanged}.
391 *
392 * @param call the call object that carries out the IMS call
393 */
394 public void onCallSuppServiceReceived(ImsCall call,
395 ImsSuppServiceNotification suppServiceInfo) {
396 }
397
398 /**
Pavel Zhamaitsiaka6cae362014-12-10 17:31:33 -0800399 * Called when TTY mode of remote party changed
400 *
401 * @param call the call object that carries out the IMS call
402 * @param mode TTY mode of remote party
403 */
404 public void onCallSessionTtyModeReceived(ImsCall call, int mode) {
405 // no-op
406 }
Rekha Kumar14631742015-02-04 10:47:00 -0800407
408 /**
409 * Called when handover occurs from one access technology to another.
410 *
Anthony Lee68048512015-03-18 15:04:18 -0700411 * @param imsCall ImsCall object
Rekha Kumar14631742015-02-04 10:47:00 -0800412 * @param srcAccessTech original access technology
413 * @param targetAccessTech new access technology
414 * @param reasonInfo
415 */
416 public void onCallHandover(ImsCall imsCall, int srcAccessTech, int targetAccessTech,
417 ImsReasonInfo reasonInfo) {
418 }
419
420 /**
421 * Called when handover from one access technology to another fails.
422 *
Anthony Lee68048512015-03-18 15:04:18 -0700423 * @param imsCall call that failed the handover.
Rekha Kumar14631742015-02-04 10:47:00 -0800424 * @param srcAccessTech original access technology
425 * @param targetAccessTech new access technology
426 * @param reasonInfo
427 */
428 public void onCallHandoverFailed(ImsCall imsCall, int srcAccessTech, int targetAccessTech,
429 ImsReasonInfo reasonInfo) {
430 }
Tyler Gunn25394092015-04-01 09:40:02 -0700431
432 /**
433 * Notifies of a change to the multiparty state for this {@code ImsCall}.
434 *
435 * @param imsCall The IMS call.
436 * @param isMultiParty {@code true} if the call became multiparty, {@code false}
437 * otherwise.
438 */
439 public void onMultipartyStateChanged(ImsCall imsCall, boolean isMultiParty) {
440 }
Wink Savilleef36ef62014-06-11 08:39:38 -0700441 }
442
Wink Savilleef36ef62014-06-11 08:39:38 -0700443 // List of update operation for IMS call control
444 private static final int UPDATE_NONE = 0;
445 private static final int UPDATE_HOLD = 1;
446 private static final int UPDATE_HOLD_MERGE = 2;
447 private static final int UPDATE_RESUME = 3;
448 private static final int UPDATE_MERGE = 4;
449 private static final int UPDATE_EXTEND_TO_CONFERENCE = 5;
450 private static final int UPDATE_UNSPECIFIED = 6;
451
452 // For synchronization of private variables
453 private Object mLockObj = new Object();
454 private Context mContext;
455
456 // true if the call is established & in the conversation state
457 private boolean mInCall = false;
458 // true if the call is on hold
459 // If it is triggered by the local, mute the call. Otherwise, play local hold tone
460 // or network generated media.
461 private boolean mHold = false;
462 // true if the call is on mute
463 private boolean mMute = false;
464 // It contains the exclusive call update request. Refer to UPDATE_*.
465 private int mUpdateRequest = UPDATE_NONE;
466
467 private ImsCall.Listener mListener = null;
Tyler Gunn9bd5ca52014-12-02 09:21:01 -0800468
469 // When merging two calls together, the "peer" call that will merge into this call.
470 private ImsCall mMergePeer = null;
471 // When merging two calls together, the "host" call we are merging into.
472 private ImsCall mMergeHost = null;
Wink Savilleef36ef62014-06-11 08:39:38 -0700473
Uma Maheswari Ramalingam95be7c62015-05-06 23:08:17 -0700474 // True if Conference request was initiated by
475 // Foreground Conference call else it will be false
476 private boolean mMergeRequestedByConference = false;
Wink Savilleef36ef62014-06-11 08:39:38 -0700477 // Wrapper call session to interworking the IMS service (server).
478 private ImsCallSession mSession = null;
479 // Call profile of the current session.
480 // It can be changed at anytime when the call is updated.
481 private ImsCallProfile mCallProfile = null;
482 // Call profile to be updated after the application's action (accept/reject)
483 // to the call update. After the application's action (accept/reject) is done,
484 // it will be set to null.
485 private ImsCallProfile mProposedCallProfile = null;
486 private ImsReasonInfo mLastReasonInfo = null;
487
488 // Media session to control media (audio/video) operations for an IMS call
489 private ImsStreamMediaSession mMediaSession = null;
490
Anthony Leeb8799fe2014-11-03 15:13:47 -0800491 // The temporary ImsCallSession that could represent the merged call once
492 // we receive notification that the merge was successful.
Anthony Lee71382692014-10-30 10:50:10 -0700493 private ImsCallSession mTransientConferenceSession = null;
Anthony Leeb8799fe2014-11-03 15:13:47 -0800494 // While a merge is progressing, we bury any session termination requests
495 // made on the original ImsCallSession until we have closure on the merge request
496 // If the request ultimately fails, we need to act on the termination request
497 // that we buried temporarily. We do this because we feel that timing issues could
498 // cause the termination request to occur just because the merge is succeeding.
499 private boolean mSessionEndDuringMerge = false;
500 // Just like mSessionEndDuringMerge, we need to keep track of the reason why the
501 // termination request was made on the original session in case we need to act
502 // on it in the case of a merge failure.
503 private ImsReasonInfo mSessionEndDuringMergeReasonInfo = null;
Anthony Leec479f662015-02-11 17:04:35 -0800504 // This flag is used to indicate if this ImsCall was merged into a conference
505 // or not. It is used primarily to determine if a disconnect sound should
506 // be heard when the call is terminated.
Andrew Lee8ae59492014-11-17 17:03:02 -0800507 private boolean mIsMerged = false;
Anthony Leec479f662015-02-11 17:04:35 -0800508 // If true, this flag means that this ImsCall is in the process of merging
509 // into a conference but it does not yet have closure on if it was
510 // actually added to the conference or not. false implies that it either
511 // is not part of a merging conference or already knows if it was
512 // successfully added.
Tyler Gunn047d8102015-01-30 15:21:11 -0800513 private boolean mCallSessionMergePending = false;
514
Wink Savilleef36ef62014-06-11 08:39:38 -0700515 /**
Tyler Gunn95d563e2015-07-23 09:28:58 -0700516 * If {@code true}, this flag indicates that a request to terminate the call was made by
517 * Telephony (could be from the user or some internal telephony logic)
518 * and that when we receive a {@link #processCallTerminated(ImsReasonInfo)} callback from the
519 * radio indicating that the call was terminated, we should override any burying of the
520 * termination due to an ongoing conference merge.
521 */
522 private boolean mTerminationRequestPending = false;
523
524 /**
Tyler Gunn25394092015-04-01 09:40:02 -0700525 * For multi-party IMS calls (e.g. conferences), determines if this {@link ImsCall} is the one
526 * hosting the call. This is used to distinguish between a situation where an {@link ImsCall}
527 * is {@link #isMultiparty()} because calls were merged on the device, and a situation where
528 * an {@link ImsCall} is {@link #isMultiparty()} because it is a member of a conference started
529 * on another device.
530 * <p>
531 * When {@code true}, this {@link ImsCall} is is the origin of the conference call.
532 * When {@code false}, this {@link ImsCall} is a member of a conference started on another
533 * device.
534 */
535 private boolean mIsConferenceHost = false;
536
537 /**
Wink Savilleef36ef62014-06-11 08:39:38 -0700538 * Create an IMS call object.
539 *
540 * @param context the context for accessing system services
541 * @param profile the call profile to make/take a call
542 */
543 public ImsCall(Context context, ImsCallProfile profile) {
544 mContext = context;
545 mCallProfile = profile;
546 }
547
548 /**
549 * Closes this object. This object is not usable after being closed.
550 */
551 @Override
552 public void close() {
553 synchronized(mLockObj) {
Wink Savilleef36ef62014-06-11 08:39:38 -0700554 if (mSession != null) {
555 mSession.close();
556 mSession = null;
Uma Maheswari Ramalingam95be7c62015-05-06 23:08:17 -0700557 } else {
558 logi("close :: Cannot close Null call session!");
Wink Savilleef36ef62014-06-11 08:39:38 -0700559 }
560
561 mCallProfile = null;
562 mProposedCallProfile = null;
563 mLastReasonInfo = null;
564 mMediaSession = null;
565 }
566 }
567
568 /**
569 * Checks if the call has a same remote user identity or not.
570 *
571 * @param userId the remote user identity
572 * @return true if the remote user identity is equal; otherwise, false
573 */
574 @Override
575 public boolean checkIfRemoteUserIsSame(String userId) {
576 if (userId == null) {
577 return false;
578 }
579
580 return userId.equals(mCallProfile.getCallExtra(ImsCallProfile.EXTRA_REMOTE_URI, ""));
581 }
582
583 /**
584 * Checks if the call is equal or not.
585 *
586 * @param call the call to be compared
587 * @return true if the call is equal; otherwise, false
588 */
589 @Override
590 public boolean equalsTo(ICall call) {
591 if (call == null) {
592 return false;
593 }
594
595 if (call instanceof ImsCall) {
Etan Cohen16b3b362014-10-24 11:10:58 -0700596 return this.equals(call);
Wink Savilleef36ef62014-06-11 08:39:38 -0700597 }
598
599 return false;
600 }
601
Anthony Leec479f662015-02-11 17:04:35 -0800602 public static boolean isSessionAlive(ImsCallSession session) {
603 return session != null && session.isAlive();
604 }
605
Wink Savilleef36ef62014-06-11 08:39:38 -0700606 /**
607 * Gets the negotiated (local & remote) call profile.
608 *
609 * @return a {@link ImsCallProfile} object that has the negotiated call profile
610 */
611 public ImsCallProfile getCallProfile() {
612 synchronized(mLockObj) {
613 return mCallProfile;
614 }
615 }
616
617 /**
618 * Gets the local call profile (local capabilities).
619 *
620 * @return a {@link ImsCallProfile} object that has the local call profile
621 */
622 public ImsCallProfile getLocalCallProfile() throws ImsException {
623 synchronized(mLockObj) {
624 if (mSession == null) {
625 throw new ImsException("No call session",
626 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
627 }
628
629 try {
630 return mSession.getLocalCallProfile();
631 } catch (Throwable t) {
632 loge("getLocalCallProfile :: ", t);
633 throw new ImsException("getLocalCallProfile()", t, 0);
634 }
635 }
636 }
637
638 /**
Shriram Ganeshe8719892014-10-13 18:28:34 -0700639 * Gets the remote call profile (remote capabilities).
640 *
641 * @return a {@link ImsCallProfile} object that has the remote call profile
642 */
643 public ImsCallProfile getRemoteCallProfile() throws ImsException {
644 synchronized(mLockObj) {
645 if (mSession == null) {
646 throw new ImsException("No call session",
647 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
648 }
649
650 try {
651 return mSession.getRemoteCallProfile();
652 } catch (Throwable t) {
653 loge("getRemoteCallProfile :: ", t);
654 throw new ImsException("getRemoteCallProfile()", t, 0);
655 }
656 }
657 }
658
659 /**
Wink Savilleef36ef62014-06-11 08:39:38 -0700660 * Gets the call profile proposed by the local/remote user.
661 *
662 * @return a {@link ImsCallProfile} object that has the proposed call profile
663 */
664 public ImsCallProfile getProposedCallProfile() {
665 synchronized(mLockObj) {
666 if (!isInCall()) {
667 return null;
668 }
669
670 return mProposedCallProfile;
671 }
672 }
673
674 /**
Uma Maheswari Ramalingama1ed7b02015-05-20 14:26:42 -0700675 * Gets the list of conference participants currently
676 * associated with this call.
677 *
678 * @return The list of conference participants.
679 */
680 public List<ConferenceParticipant> getConferenceParticipants() {
681 synchronized(mLockObj) {
682 logi("getConferenceParticipants :: mConferenceParticipants"
683 + mConferenceParticipants);
684 return mConferenceParticipants;
685 }
686 }
687
688 /**
Wink Savilleef36ef62014-06-11 08:39:38 -0700689 * Gets the state of the {@link ImsCallSession} that carries this call.
690 * The value returned must be one of the states in {@link ImsCallSession#State}.
691 *
692 * @return the session state
693 */
694 public int getState() {
695 synchronized(mLockObj) {
696 if (mSession == null) {
697 return ImsCallSession.State.IDLE;
698 }
699
700 return mSession.getState();
701 }
702 }
703
704 /**
705 * Gets the {@link ImsCallSession} that carries this call.
706 *
707 * @return the session object that carries this call
708 * @hide
709 */
710 public ImsCallSession getCallSession() {
711 synchronized(mLockObj) {
712 return mSession;
713 }
714 }
715
716 /**
717 * Gets the {@link ImsStreamMediaSession} that handles the media operation of this call.
718 * Almost interface APIs are for the VT (Video Telephony).
719 *
720 * @return the media session object that handles the media operation of this call
721 * @hide
722 */
723 public ImsStreamMediaSession getMediaSession() {
724 synchronized(mLockObj) {
725 return mMediaSession;
726 }
727 }
728
729 /**
730 * Gets the specified property of this call.
731 *
732 * @param name key to get the extra call information defined in {@link ImsCallProfile}
733 * @return the extra call information as string
734 */
735 public String getCallExtra(String name) throws ImsException {
736 // Lookup the cache
737
738 synchronized(mLockObj) {
739 // If not found, try to get the property from the remote
740 if (mSession == null) {
741 throw new ImsException("No call session",
742 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
743 }
744
745 try {
746 return mSession.getProperty(name);
747 } catch (Throwable t) {
748 loge("getCallExtra :: ", t);
749 throw new ImsException("getCallExtra()", t, 0);
750 }
751 }
752 }
753
754 /**
755 * Gets the last reason information when the call is not established, cancelled or terminated.
756 *
757 * @return the last reason information
758 */
759 public ImsReasonInfo getLastReasonInfo() {
760 synchronized(mLockObj) {
761 return mLastReasonInfo;
762 }
763 }
764
765 /**
766 * Checks if the call has a pending update operation.
767 *
768 * @return true if the call has a pending update operation
769 */
770 public boolean hasPendingUpdate() {
771 synchronized(mLockObj) {
772 return (mUpdateRequest != UPDATE_NONE);
773 }
774 }
775
776 /**
Tyler Gunn6c0b0d02015-07-01 16:39:43 -0700777 * Checks if the call is pending a hold operation.
778 *
779 * @return true if the call is pending a hold operation.
780 */
781 public boolean isPendingHold() {
782 synchronized(mLockObj) {
783 return (mUpdateRequest == UPDATE_HOLD);
784 }
785 }
786
787 /**
Wink Savilleef36ef62014-06-11 08:39:38 -0700788 * Checks if the call is established.
789 *
790 * @return true if the call is established
791 */
792 public boolean isInCall() {
793 synchronized(mLockObj) {
794 return mInCall;
795 }
796 }
797
798 /**
799 * Checks if the call is muted.
800 *
801 * @return true if the call is muted
802 */
803 public boolean isMuted() {
804 synchronized(mLockObj) {
805 return mMute;
806 }
807 }
808
809 /**
810 * Checks if the call is on hold.
811 *
812 * @return true if the call is on hold
813 */
814 public boolean isOnHold() {
815 synchronized(mLockObj) {
816 return mHold;
817 }
818 }
819
820 /**
Tyler Gunn725ad372014-10-22 11:28:47 -0700821 * Determines if the call is a multiparty call.
822 *
823 * @return {@code True} if the call is a multiparty call.
824 */
825 public boolean isMultiparty() {
Etan Cohen16b3b362014-10-24 11:10:58 -0700826 synchronized(mLockObj) {
827 if (mSession == null) {
828 return false;
829 }
830
Anthony Lee71382692014-10-30 10:50:10 -0700831 return mSession.isMultiparty();
Etan Cohen16b3b362014-10-24 11:10:58 -0700832 }
Tyler Gunn725ad372014-10-22 11:28:47 -0700833 }
834
835 /**
Tyler Gunn25394092015-04-01 09:40:02 -0700836 * Where {@link #isMultiparty()} is {@code true}, determines if this {@link ImsCall} is the
837 * origin of the conference call (i.e. {@code #isConferenceHost()} is {@code true}), or if this
838 * {@link ImsCall} is a member of a conference hosted on another device.
839 *
840 * @return {@code true} if this call is the origin of the conference call it is a member of,
841 * {@code false} otherwise.
842 */
843 public boolean isConferenceHost() {
844 synchronized(mLockObj) {
845 return isMultiparty() && mIsConferenceHost;
846 }
847 }
848
849 /**
Tyler Gunn9bd5ca52014-12-02 09:21:01 -0800850 * Marks whether an IMS call is merged. This should be set {@code true} when the call merges
851 * into a conference.
Andrew Lee8ae59492014-11-17 17:03:02 -0800852 *
853 * @param isMerged Whether the call is merged.
854 */
855 public void setIsMerged(boolean isMerged) {
856 mIsMerged = isMerged;
857 }
858
859 /**
Tyler Gunn9bd5ca52014-12-02 09:21:01 -0800860 * @return {@code true} if the call recently merged into a conference call.
Andrew Lee8ae59492014-11-17 17:03:02 -0800861 */
862 public boolean isMerged() {
863 return mIsMerged;
864 }
865
866 /**
Wink Savilleef36ef62014-06-11 08:39:38 -0700867 * Sets the listener to listen to the IMS call events.
868 * The method calls {@link #setListener setListener(listener, false)}.
869 *
870 * @param listener to listen to the IMS call events of this object; null to remove listener
871 * @see #setListener(Listener, boolean)
872 */
873 public void setListener(ImsCall.Listener listener) {
874 setListener(listener, false);
875 }
876
877 /**
878 * Sets the listener to listen to the IMS call events.
879 * A {@link ImsCall} can only hold one listener at a time. Subsequent calls
880 * to this method override the previous listener.
881 *
882 * @param listener to listen to the IMS call events of this object; null to remove listener
883 * @param callbackImmediately set to true if the caller wants to be called
884 * back immediately on the current state
885 */
886 public void setListener(ImsCall.Listener listener, boolean callbackImmediately) {
887 boolean inCall;
888 boolean onHold;
889 int state;
890 ImsReasonInfo lastReasonInfo;
891
892 synchronized(mLockObj) {
893 mListener = listener;
894
895 if ((listener == null) || !callbackImmediately) {
896 return;
897 }
898
899 inCall = mInCall;
900 onHold = mHold;
901 state = getState();
902 lastReasonInfo = mLastReasonInfo;
903 }
904
905 try {
906 if (lastReasonInfo != null) {
907 listener.onCallError(this, lastReasonInfo);
908 } else if (inCall) {
909 if (onHold) {
910 listener.onCallHeld(this);
911 } else {
912 listener.onCallStarted(this);
913 }
914 } else {
915 switch (state) {
916 case ImsCallSession.State.ESTABLISHING:
917 listener.onCallProgressing(this);
918 break;
919 case ImsCallSession.State.TERMINATED:
920 listener.onCallTerminated(this, lastReasonInfo);
921 break;
922 default:
923 // Ignore it. There is no action in the other state.
924 break;
925 }
926 }
927 } catch (Throwable t) {
Anthony Lee68048512015-03-18 15:04:18 -0700928 loge("setListener() :: ", t);
Wink Savilleef36ef62014-06-11 08:39:38 -0700929 }
930 }
931
932 /**
933 * Mutes or unmutes the mic for the active call.
934 *
935 * @param muted true if the call is muted, false otherwise
936 */
937 public void setMute(boolean muted) throws ImsException {
938 synchronized(mLockObj) {
939 if (mMute != muted) {
Anthony Lee68048512015-03-18 15:04:18 -0700940 logi("setMute :: turning mute " + (muted ? "on" : "off"));
Wink Savilleef36ef62014-06-11 08:39:38 -0700941 mMute = muted;
942
943 try {
944 mSession.setMute(muted);
945 } catch (Throwable t) {
946 loge("setMute :: ", t);
947 throwImsException(t, 0);
948 }
949 }
950 }
951 }
952
953 /**
954 * Attaches an incoming call to this call object.
955 *
956 * @param session the session that receives the incoming call
957 * @throws ImsException if the IMS service fails to attach this object to the session
958 */
959 public void attachSession(ImsCallSession session) throws ImsException {
Anthony Lee68048512015-03-18 15:04:18 -0700960 logi("attachSession :: session=" + session);
Wink Savilleef36ef62014-06-11 08:39:38 -0700961
962 synchronized(mLockObj) {
963 mSession = session;
964
965 try {
966 mSession.setListener(createCallSessionListener());
967 } catch (Throwable t) {
968 loge("attachSession :: ", t);
969 throwImsException(t, 0);
970 }
971 }
972 }
973
974 /**
975 * Initiates an IMS call with the call profile which is provided
976 * when creating a {@link ImsCall}.
977 *
978 * @param session the {@link ImsCallSession} for carrying out the call
979 * @param callee callee information to initiate an IMS call
980 * @throws ImsException if the IMS service fails to initiate the call
981 */
982 public void start(ImsCallSession session, String callee)
983 throws ImsException {
Anthony Lee68048512015-03-18 15:04:18 -0700984 logi("start(1) :: session=" + session + ", callee=" + callee);
Wink Savilleef36ef62014-06-11 08:39:38 -0700985
986 synchronized(mLockObj) {
987 mSession = session;
988
989 try {
990 session.setListener(createCallSessionListener());
991 session.start(callee, mCallProfile);
992 } catch (Throwable t) {
993 loge("start(1) :: ", t);
994 throw new ImsException("start(1)", t, 0);
995 }
996 }
997 }
998
999 /**
1000 * Initiates an IMS conferenca call with the call profile which is provided
1001 * when creating a {@link ImsCall}.
1002 *
1003 * @param session the {@link ImsCallSession} for carrying out the call
1004 * @param participants participant list to initiate an IMS conference call
1005 * @throws ImsException if the IMS service fails to initiate the call
1006 */
1007 public void start(ImsCallSession session, String[] participants)
1008 throws ImsException {
Anthony Lee68048512015-03-18 15:04:18 -07001009 logi("start(n) :: session=" + session + ", callee=" + participants);
Wink Savilleef36ef62014-06-11 08:39:38 -07001010
1011 synchronized(mLockObj) {
1012 mSession = session;
1013
1014 try {
1015 session.setListener(createCallSessionListener());
1016 session.start(participants, mCallProfile);
1017 } catch (Throwable t) {
1018 loge("start(n) :: ", t);
1019 throw new ImsException("start(n)", t, 0);
1020 }
1021 }
1022 }
1023
1024 /**
1025 * Accepts a call.
1026 *
1027 * @see Listener#onCallStarted
Tyler Gunnd1edfd82014-07-18 13:44:18 -07001028 *
1029 * @param callType The call type the user agreed to for accepting the call.
Wink Savilleef36ef62014-06-11 08:39:38 -07001030 * @throws ImsException if the IMS service fails to accept the call
1031 */
Tyler Gunnd1edfd82014-07-18 13:44:18 -07001032 public void accept(int callType) throws ImsException {
Tyler Gunnd1edfd82014-07-18 13:44:18 -07001033 accept(callType, new ImsStreamMediaProfile());
Wink Savilleef36ef62014-06-11 08:39:38 -07001034 }
1035
1036 /**
1037 * Accepts a call.
1038 *
1039 * @param callType call type to be answered in {@link ImsCallProfile}
1040 * @param profile a media profile to be answered (audio/audio & video, direction, ...)
1041 * @see Listener#onCallStarted
1042 * @throws ImsException if the IMS service fails to accept the call
1043 */
1044 public void accept(int callType, ImsStreamMediaProfile profile) throws ImsException {
Anthony Lee68048512015-03-18 15:04:18 -07001045 logi("accept :: callType=" + callType + ", profile=" + profile);
Wink Savilleef36ef62014-06-11 08:39:38 -07001046
1047 synchronized(mLockObj) {
1048 if (mSession == null) {
1049 throw new ImsException("No call to answer",
1050 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1051 }
1052
1053 try {
1054 mSession.accept(callType, profile);
1055 } catch (Throwable t) {
1056 loge("accept :: ", t);
1057 throw new ImsException("accept()", t, 0);
1058 }
1059
1060 if (mInCall && (mProposedCallProfile != null)) {
1061 if (DBG) {
Anthony Lee68048512015-03-18 15:04:18 -07001062 logi("accept :: call profile will be updated");
Wink Savilleef36ef62014-06-11 08:39:38 -07001063 }
1064
1065 mCallProfile = mProposedCallProfile;
1066 mProposedCallProfile = null;
1067 }
1068
1069 // Other call update received
1070 if (mInCall && (mUpdateRequest == UPDATE_UNSPECIFIED)) {
1071 mUpdateRequest = UPDATE_NONE;
1072 }
1073 }
1074 }
1075
1076 /**
1077 * Rejects a call.
1078 *
1079 * @param reason reason code to reject an incoming call
1080 * @see Listener#onCallStartFailed
Anthony Lee68048512015-03-18 15:04:18 -07001081 * @throws ImsException if the IMS service fails to reject the call
Wink Savilleef36ef62014-06-11 08:39:38 -07001082 */
1083 public void reject(int reason) throws ImsException {
Anthony Lee68048512015-03-18 15:04:18 -07001084 logi("reject :: reason=" + reason);
Wink Savilleef36ef62014-06-11 08:39:38 -07001085
1086 synchronized(mLockObj) {
1087 if (mSession != null) {
1088 mSession.reject(reason);
1089 }
1090
1091 if (mInCall && (mProposedCallProfile != null)) {
1092 if (DBG) {
Anthony Lee68048512015-03-18 15:04:18 -07001093 logi("reject :: call profile is not updated; destroy it...");
Wink Savilleef36ef62014-06-11 08:39:38 -07001094 }
1095
1096 mProposedCallProfile = null;
1097 }
1098
1099 // Other call update received
1100 if (mInCall && (mUpdateRequest == UPDATE_UNSPECIFIED)) {
1101 mUpdateRequest = UPDATE_NONE;
1102 }
1103 }
1104 }
1105
1106 /**
Tyler Gunn95d563e2015-07-23 09:28:58 -07001107 * Terminates an IMS call (e.g. user initiated).
Wink Savilleef36ef62014-06-11 08:39:38 -07001108 *
1109 * @param reason reason code to terminate a call
1110 * @throws ImsException if the IMS service fails to terminate the call
1111 */
1112 public void terminate(int reason) throws ImsException {
Anthony Lee68048512015-03-18 15:04:18 -07001113 logi("terminate :: reason=" + reason);
Wink Savilleef36ef62014-06-11 08:39:38 -07001114
1115 synchronized(mLockObj) {
1116 mHold = false;
1117 mInCall = false;
Tyler Gunn95d563e2015-07-23 09:28:58 -07001118 mTerminationRequestPending = true;
Wink Savilleef36ef62014-06-11 08:39:38 -07001119
1120 if (mSession != null) {
Anthony Leec479f662015-02-11 17:04:35 -08001121 // TODO: Fix the fact that user invoked call terminations during
1122 // the process of establishing a conference call needs to be handled
1123 // as a special case.
1124 // Currently, any terminations (both invoked by the user or
1125 // by the network results in a callSessionTerminated() callback
1126 // from the network. When establishing a conference call we bury
1127 // these callbacks until we get closure on all participants of the
1128 // conference. In some situations, we will throw away the callback
1129 // (when the underlying session of the host of the new conference
1130 // is terminated) or will will unbury it when the conference has been
1131 // established, like when the peer of the new conference goes away
1132 // after the conference has been created. The UI relies on the callback
1133 // to reflect the fact that the call is gone.
1134 // So if a user decides to terminated a call while it is merging, it
1135 // could take a long time to reflect in the UI due to the conference
1136 // processing but we should probably cancel that and just terminate
1137 // the call immediately and clean up. This is not a huge issue right
1138 // now because we have not seen instances where establishing a
1139 // conference takes a long time (more than a second or two).
Wink Savilleef36ef62014-06-11 08:39:38 -07001140 mSession.terminate(reason);
1141 }
1142 }
1143 }
1144
Uma Maheswari Ramalingamd43b5302014-08-29 14:13:17 -07001145
Wink Savilleef36ef62014-06-11 08:39:38 -07001146 /**
1147 * Puts a call on hold. When succeeds, {@link Listener#onCallHeld} is called.
1148 *
1149 * @see Listener#onCallHeld, Listener#onCallHoldFailed
1150 * @throws ImsException if the IMS service fails to hold the call
1151 */
1152 public void hold() throws ImsException {
Anthony Lee68048512015-03-18 15:04:18 -07001153 logi("hold :: ");
Etan Cohen111eecc2014-09-10 17:18:12 -07001154
Wink Savilleef36ef62014-06-11 08:39:38 -07001155 if (isOnHold()) {
1156 if (DBG) {
Anthony Lee68048512015-03-18 15:04:18 -07001157 logi("hold :: call is already on hold");
Wink Savilleef36ef62014-06-11 08:39:38 -07001158 }
1159 return;
1160 }
1161
1162 synchronized(mLockObj) {
1163 if (mUpdateRequest != UPDATE_NONE) {
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08001164 loge("hold :: update is in progress; request=" +
1165 updateRequestToString(mUpdateRequest));
Wink Savilleef36ef62014-06-11 08:39:38 -07001166 throw new ImsException("Call update is in progress",
1167 ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
1168 }
1169
1170 if (mSession == null) {
Wink Savilleef36ef62014-06-11 08:39:38 -07001171 throw new ImsException("No call session",
1172 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1173 }
1174
1175 mSession.hold(createHoldMediaProfile());
Anthony Lee68048512015-03-18 15:04:18 -07001176 // FIXME: We should update the state on the callback because that is where
1177 // we can confirm that the hold request was successful or not.
Wink Savilleef36ef62014-06-11 08:39:38 -07001178 mHold = true;
1179 mUpdateRequest = UPDATE_HOLD;
1180 }
1181 }
1182
1183 /**
1184 * Continues a call that's on hold. When succeeds, {@link Listener#onCallResumed} is called.
1185 *
1186 * @see Listener#onCallResumed, Listener#onCallResumeFailed
1187 * @throws ImsException if the IMS service fails to resume the call
1188 */
1189 public void resume() throws ImsException {
Anthony Lee68048512015-03-18 15:04:18 -07001190 logi("resume :: ");
Etan Cohen111eecc2014-09-10 17:18:12 -07001191
Wink Savilleef36ef62014-06-11 08:39:38 -07001192 if (!isOnHold()) {
1193 if (DBG) {
Anthony Lee68048512015-03-18 15:04:18 -07001194 logi("resume :: call is not being held");
Wink Savilleef36ef62014-06-11 08:39:38 -07001195 }
1196 return;
1197 }
1198
1199 synchronized(mLockObj) {
1200 if (mUpdateRequest != UPDATE_NONE) {
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08001201 loge("resume :: update is in progress; request=" +
1202 updateRequestToString(mUpdateRequest));
Wink Savilleef36ef62014-06-11 08:39:38 -07001203 throw new ImsException("Call update is in progress",
1204 ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
1205 }
1206
1207 if (mSession == null) {
1208 loge("resume :: ");
1209 throw new ImsException("No call session",
1210 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1211 }
1212
Anthony Leec479f662015-02-11 17:04:35 -08001213 // mHold is set to false in confirmation callback that the
1214 // ImsCall was resumed.
Wink Savilleef36ef62014-06-11 08:39:38 -07001215 mUpdateRequest = UPDATE_RESUME;
Anthony Leec479f662015-02-11 17:04:35 -08001216 mSession.resume(createResumeMediaProfile());
Wink Savilleef36ef62014-06-11 08:39:38 -07001217 }
1218 }
1219
1220 /**
1221 * Merges the active & hold call.
1222 *
1223 * @see Listener#onCallMerged, Listener#onCallMergeFailed
1224 * @throws ImsException if the IMS service fails to merge the call
1225 */
Tyler Gunn047d8102015-01-30 15:21:11 -08001226 private void merge() throws ImsException {
Anthony Lee68048512015-03-18 15:04:18 -07001227 logi("merge :: ");
Wink Savilleef36ef62014-06-11 08:39:38 -07001228
1229 synchronized(mLockObj) {
1230 if (mUpdateRequest != UPDATE_NONE) {
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08001231 loge("merge :: update is in progress; request=" +
1232 updateRequestToString(mUpdateRequest));
Wink Savilleef36ef62014-06-11 08:39:38 -07001233 throw new ImsException("Call update is in progress",
1234 ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
1235 }
1236
1237 if (mSession == null) {
Anthony Leec479f662015-02-11 17:04:35 -08001238 loge("merge :: no call session");
Wink Savilleef36ef62014-06-11 08:39:38 -07001239 throw new ImsException("No call session",
1240 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1241 }
1242
Uma Maheswari Ramalingamca45f582014-05-19 12:32:20 -07001243 // if skipHoldBeforeMerge = true, IMS service implementation will
1244 // merge without explicitly holding the call.
1245 if (mHold || (mContext.getResources().getBoolean(
1246 com.android.internal.R.bool.skipHoldBeforeMerge))) {
Anthony Leeb8799fe2014-11-03 15:13:47 -08001247
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08001248 if (mMergePeer != null && !mMergePeer.isMultiparty() && !isMultiparty()) {
1249 // We only set UPDATE_MERGE when we are adding the first
1250 // calls to the Conference. If there is already a conference
Tyler Gunn047d8102015-01-30 15:21:11 -08001251 // no special handling is needed. The existing conference
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08001252 // session will just go active and any other sessions will be terminated
1253 // if needed. There will be no merge failed callback.
Tyler Gunn047d8102015-01-30 15:21:11 -08001254 // Mark both the host and peer UPDATE_MERGE to ensure both are aware that a
1255 // merge is pending.
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08001256 mUpdateRequest = UPDATE_MERGE;
Tyler Gunn047d8102015-01-30 15:21:11 -08001257 mMergePeer.mUpdateRequest = UPDATE_MERGE;
Anthony Leeb8799fe2014-11-03 15:13:47 -08001258 }
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08001259
1260 mSession.merge();
Wink Savilleef36ef62014-06-11 08:39:38 -07001261 } else {
Anthony Lee71382692014-10-30 10:50:10 -07001262 // This code basically says, we need to explicitly hold before requesting a merge
1263 // when we get the callback that the hold was successful (or failed), we should
1264 // automatically request a merge.
Wink Savilleef36ef62014-06-11 08:39:38 -07001265 mSession.hold(createHoldMediaProfile());
Wink Savilleef36ef62014-06-11 08:39:38 -07001266 mHold = true;
1267 mUpdateRequest = UPDATE_HOLD_MERGE;
1268 }
1269 }
1270 }
1271
1272 /**
1273 * Merges the active & hold call.
1274 *
1275 * @param bgCall the background (holding) call
1276 * @see Listener#onCallMerged, Listener#onCallMergeFailed
1277 * @throws ImsException if the IMS service fails to merge the call
1278 */
1279 public void merge(ImsCall bgCall) throws ImsException {
Anthony Lee68048512015-03-18 15:04:18 -07001280 logi("merge(1) :: bgImsCall=" + bgCall);
Wink Savilleef36ef62014-06-11 08:39:38 -07001281
1282 if (bgCall == null) {
1283 throw new ImsException("No background call",
1284 ImsReasonInfo.CODE_LOCAL_ILLEGAL_ARGUMENT);
1285 }
1286
1287 synchronized(mLockObj) {
Tyler Gunn047d8102015-01-30 15:21:11 -08001288 // Mark both sessions as pending merge.
1289 this.setCallSessionMergePending(true);
1290 bgCall.setCallSessionMergePending(true);
1291
Tyler Gunn87466c52014-12-08 09:56:17 -08001292 if ((!isMultiparty() && !bgCall.isMultiparty()) || isMultiparty()) {
1293 // If neither call is multiparty, the current call is the merge host and the bg call
1294 // is the merge peer (ie we're starting a new conference).
1295 // OR
1296 // If this call is multiparty, it is the merge host and the other call is the merge
1297 // peer.
1298 setMergePeer(bgCall);
1299 } else {
1300 // If the bg call is multiparty, it is the merge host.
1301 setMergeHost(bgCall);
1302 }
Wink Savilleef36ef62014-06-11 08:39:38 -07001303 }
Uma Maheswari Ramalingama1ed7b02015-05-20 14:26:42 -07001304
Uma Maheswari Ramalingam95be7c62015-05-06 23:08:17 -07001305 if (isMultiparty()) {
1306 mMergeRequestedByConference = true;
1307 } else {
1308 logi("merge : mMergeRequestedByConference not set");
1309 }
Wink Savilleef36ef62014-06-11 08:39:38 -07001310 merge();
1311 }
1312
1313 /**
1314 * Updates the current call's properties (ex. call mode change: video upgrade / downgrade).
1315 */
1316 public void update(int callType, ImsStreamMediaProfile mediaProfile) throws ImsException {
Anthony Lee68048512015-03-18 15:04:18 -07001317 logi("update :: callType=" + callType + ", mediaProfile=" + mediaProfile);
Wink Savilleef36ef62014-06-11 08:39:38 -07001318
1319 if (isOnHold()) {
1320 if (DBG) {
Anthony Lee68048512015-03-18 15:04:18 -07001321 logi("update :: call is on hold");
Wink Savilleef36ef62014-06-11 08:39:38 -07001322 }
1323 throw new ImsException("Not in a call to update call",
1324 ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
1325 }
1326
1327 synchronized(mLockObj) {
1328 if (mUpdateRequest != UPDATE_NONE) {
1329 if (DBG) {
Anthony Lee68048512015-03-18 15:04:18 -07001330 logi("update :: update is in progress; request=" +
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08001331 updateRequestToString(mUpdateRequest));
Wink Savilleef36ef62014-06-11 08:39:38 -07001332 }
1333 throw new ImsException("Call update is in progress",
1334 ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
1335 }
1336
1337 if (mSession == null) {
1338 loge("update :: ");
1339 throw new ImsException("No call session",
1340 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1341 }
1342
1343 mSession.update(callType, mediaProfile);
1344 mUpdateRequest = UPDATE_UNSPECIFIED;
1345 }
1346 }
1347
1348 /**
1349 * Extends this call (1-to-1 call) to the conference call
1350 * inviting the specified participants to.
1351 *
1352 */
1353 public void extendToConference(String[] participants) throws ImsException {
Anthony Lee68048512015-03-18 15:04:18 -07001354 logi("extendToConference ::");
Wink Savilleef36ef62014-06-11 08:39:38 -07001355
1356 if (isOnHold()) {
1357 if (DBG) {
Anthony Lee68048512015-03-18 15:04:18 -07001358 logi("extendToConference :: call is on hold");
Wink Savilleef36ef62014-06-11 08:39:38 -07001359 }
1360 throw new ImsException("Not in a call to extend a call to conference",
1361 ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
1362 }
1363
1364 synchronized(mLockObj) {
1365 if (mUpdateRequest != UPDATE_NONE) {
Anthony Lee68048512015-03-18 15:04:18 -07001366 if (CONF_DBG) {
1367 logi("extendToConference :: update is in progress; request=" +
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08001368 updateRequestToString(mUpdateRequest));
Wink Savilleef36ef62014-06-11 08:39:38 -07001369 }
1370 throw new ImsException("Call update is in progress",
1371 ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
1372 }
1373
1374 if (mSession == null) {
1375 loge("extendToConference :: ");
1376 throw new ImsException("No call session",
1377 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1378 }
1379
1380 mSession.extendToConference(participants);
1381 mUpdateRequest = UPDATE_EXTEND_TO_CONFERENCE;
1382 }
1383 }
1384
1385 /**
1386 * Requests the conference server to invite an additional participants to the conference.
1387 *
1388 */
1389 public void inviteParticipants(String[] participants) throws ImsException {
Anthony Lee68048512015-03-18 15:04:18 -07001390 logi("inviteParticipants ::");
Wink Savilleef36ef62014-06-11 08:39:38 -07001391
1392 synchronized(mLockObj) {
1393 if (mSession == null) {
1394 loge("inviteParticipants :: ");
1395 throw new ImsException("No call session",
1396 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1397 }
1398
1399 mSession.inviteParticipants(participants);
1400 }
1401 }
1402
1403 /**
1404 * Requests the conference server to remove the specified participants from the conference.
1405 *
1406 */
1407 public void removeParticipants(String[] participants) throws ImsException {
Uma Maheswari Ramalingama1ed7b02015-05-20 14:26:42 -07001408 logi("removeParticipants :: session=" + mSession);
Wink Savilleef36ef62014-06-11 08:39:38 -07001409 synchronized(mLockObj) {
1410 if (mSession == null) {
1411 loge("removeParticipants :: ");
1412 throw new ImsException("No call session",
1413 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1414 }
1415
1416 mSession.removeParticipants(participants);
Uma Maheswari Ramalingama1ed7b02015-05-20 14:26:42 -07001417
Wink Savilleef36ef62014-06-11 08:39:38 -07001418 }
1419 }
1420
Wink Savilleef36ef62014-06-11 08:39:38 -07001421 /**
1422 * Sends a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2833</a>,
1423 * event 0 ~ 9 maps to decimal value 0 ~ 9, '*' to 10, '#' to 11, event 'A' ~ 'D' to 12 ~ 15,
1424 * and event flash to 16. Currently, event flash is not supported.
1425 *
Libin.Tang@motorola.com2f92daf2014-08-23 18:08:31 -05001426 * @param c that represents the DTMF to send. '0' ~ '9', 'A' ~ 'D', '*', '#' are valid inputs.
1427 * @param result the result message to send when done.
Wink Savilleef36ef62014-06-11 08:39:38 -07001428 */
Libin.Tang@motorola.com2f92daf2014-08-23 18:08:31 -05001429 public void sendDtmf(char c, Message result) {
Anthony Lee68048512015-03-18 15:04:18 -07001430 logi("sendDtmf :: code=" + c);
Wink Savilleef36ef62014-06-11 08:39:38 -07001431
1432 synchronized(mLockObj) {
1433 if (mSession != null) {
Andrew Leea4710d52014-12-09 14:51:53 -08001434 mSession.sendDtmf(c, result);
Wink Savilleef36ef62014-06-11 08:39:38 -07001435 }
1436 }
Wink Savilleef36ef62014-06-11 08:39:38 -07001437 }
1438
1439 /**
Uma Maheswari Ramalingama6fbae92014-12-05 16:40:46 -08001440 * Start a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2833</a>,
1441 * event 0 ~ 9 maps to decimal value 0 ~ 9, '*' to 10, '#' to 11, event 'A' ~ 'D' to 12 ~ 15,
1442 * and event flash to 16. Currently, event flash is not supported.
1443 *
1444 * @param c that represents the DTMF to send. '0' ~ '9', 'A' ~ 'D', '*', '#' are valid inputs.
1445 */
1446 public void startDtmf(char c) {
Anthony Lee68048512015-03-18 15:04:18 -07001447 logi("startDtmf :: code=" + c);
Uma Maheswari Ramalingama6fbae92014-12-05 16:40:46 -08001448
1449 synchronized(mLockObj) {
1450 if (mSession != null) {
1451 mSession.startDtmf(c);
1452 }
1453 }
1454 }
1455
1456 /**
1457 * Stop a DTMF code.
1458 */
1459 public void stopDtmf() {
Anthony Lee68048512015-03-18 15:04:18 -07001460 logi("stopDtmf :: ");
Uma Maheswari Ramalingama6fbae92014-12-05 16:40:46 -08001461
1462 synchronized(mLockObj) {
1463 if (mSession != null) {
1464 mSession.stopDtmf();
1465 }
1466 }
1467 }
1468
1469 /**
Wink Savilleef36ef62014-06-11 08:39:38 -07001470 * Sends an USSD message.
1471 *
1472 * @param ussdMessage USSD message to send
1473 */
1474 public void sendUssd(String ussdMessage) throws ImsException {
Anthony Lee68048512015-03-18 15:04:18 -07001475 logi("sendUssd :: ussdMessage=" + ussdMessage);
Wink Savilleef36ef62014-06-11 08:39:38 -07001476
1477 synchronized(mLockObj) {
1478 if (mSession == null) {
1479 loge("sendUssd :: ");
1480 throw new ImsException("No call session",
1481 ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1482 }
1483
1484 mSession.sendUssd(ussdMessage);
1485 }
1486 }
1487
1488 private void clear(ImsReasonInfo lastReasonInfo) {
1489 mInCall = false;
1490 mHold = false;
1491 mUpdateRequest = UPDATE_NONE;
1492 mLastReasonInfo = lastReasonInfo;
Wink Savilleef36ef62014-06-11 08:39:38 -07001493 }
1494
1495 /**
1496 * Creates an IMS call session listener.
1497 */
1498 private ImsCallSession.Listener createCallSessionListener() {
1499 return new ImsCallSessionListenerProxy();
1500 }
1501
1502 private ImsCall createNewCall(ImsCallSession session, ImsCallProfile profile) {
1503 ImsCall call = new ImsCall(mContext, profile);
1504
1505 try {
1506 call.attachSession(session);
1507 } catch (ImsException e) {
1508 if (call != null) {
1509 call.close();
1510 call = null;
1511 }
1512 }
1513
1514 // Do additional operations...
1515
1516 return call;
1517 }
1518
1519 private ImsStreamMediaProfile createHoldMediaProfile() {
1520 ImsStreamMediaProfile mediaProfile = new ImsStreamMediaProfile();
1521
1522 if (mCallProfile == null) {
1523 return mediaProfile;
1524 }
1525
1526 mediaProfile.mAudioQuality = mCallProfile.mMediaProfile.mAudioQuality;
1527 mediaProfile.mVideoQuality = mCallProfile.mMediaProfile.mVideoQuality;
1528 mediaProfile.mAudioDirection = ImsStreamMediaProfile.DIRECTION_SEND;
1529
1530 if (mediaProfile.mVideoQuality != ImsStreamMediaProfile.VIDEO_QUALITY_NONE) {
1531 mediaProfile.mVideoDirection = ImsStreamMediaProfile.DIRECTION_SEND;
1532 }
1533
1534 return mediaProfile;
1535 }
1536
1537 private ImsStreamMediaProfile createResumeMediaProfile() {
1538 ImsStreamMediaProfile mediaProfile = new ImsStreamMediaProfile();
1539
1540 if (mCallProfile == null) {
1541 return mediaProfile;
1542 }
1543
1544 mediaProfile.mAudioQuality = mCallProfile.mMediaProfile.mAudioQuality;
1545 mediaProfile.mVideoQuality = mCallProfile.mMediaProfile.mVideoQuality;
1546 mediaProfile.mAudioDirection = ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE;
1547
1548 if (mediaProfile.mVideoQuality != ImsStreamMediaProfile.VIDEO_QUALITY_NONE) {
1549 mediaProfile.mVideoDirection = ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE;
1550 }
1551
1552 return mediaProfile;
1553 }
1554
1555 private void enforceConversationMode() {
1556 if (mInCall) {
1557 mHold = false;
1558 mUpdateRequest = UPDATE_NONE;
1559 }
1560 }
1561
1562 private void mergeInternal() {
Anthony Lee68048512015-03-18 15:04:18 -07001563 if (CONF_DBG) {
1564 logi("mergeInternal :: ");
Wink Savilleef36ef62014-06-11 08:39:38 -07001565 }
1566
1567 mSession.merge();
1568 mUpdateRequest = UPDATE_MERGE;
1569 }
1570
Wink Savilleef36ef62014-06-11 08:39:38 -07001571 private void notifyConferenceSessionTerminated(ImsReasonInfo reasonInfo) {
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08001572 ImsCall.Listener listener = mListener;
Wink Savilleef36ef62014-06-11 08:39:38 -07001573 clear(reasonInfo);
1574
1575 if (listener != null) {
1576 try {
1577 listener.onCallTerminated(this, reasonInfo);
1578 } catch (Throwable t) {
1579 loge("notifyConferenceSessionTerminated :: ", t);
1580 }
1581 }
1582 }
1583
1584 private void notifyConferenceStateUpdated(ImsConferenceState state) {
Tyler Gunn9e3452a2015-09-08 13:10:14 -07001585 if (state == null || state.mParticipants == null) {
1586 return;
1587 }
1588
Tyler Gunn1c467602014-11-04 14:51:52 -08001589 Set<Entry<String, Bundle>> participants = state.mParticipants.entrySet();
Wink Savilleef36ef62014-06-11 08:39:38 -07001590
Tyler Gunn1c467602014-11-04 14:51:52 -08001591 if (participants == null) {
Wink Savilleef36ef62014-06-11 08:39:38 -07001592 return;
1593 }
1594
Tyler Gunn1c467602014-11-04 14:51:52 -08001595 Iterator<Entry<String, Bundle>> iterator = participants.iterator();
Uma Maheswari Ramalingama1ed7b02015-05-20 14:26:42 -07001596 mConferenceParticipants = new ArrayList<>(participants.size());
Wink Savilleef36ef62014-06-11 08:39:38 -07001597 while (iterator.hasNext()) {
1598 Entry<String, Bundle> entry = iterator.next();
1599
1600 String key = entry.getKey();
1601 Bundle confInfo = entry.getValue();
1602 String status = confInfo.getString(ImsConferenceState.STATUS);
1603 String user = confInfo.getString(ImsConferenceState.USER);
Tyler Gunn59656142014-10-28 13:52:11 -07001604 String displayName = confInfo.getString(ImsConferenceState.DISPLAY_TEXT);
Wink Savilleef36ef62014-06-11 08:39:38 -07001605 String endpoint = confInfo.getString(ImsConferenceState.ENDPOINT);
1606
Anthony Lee68048512015-03-18 15:04:18 -07001607 if (CONF_DBG) {
1608 logi("notifyConferenceStateUpdated :: key=" + key +
Wink Savilleef36ef62014-06-11 08:39:38 -07001609 ", status=" + status +
1610 ", user=" + user +
Tyler Gunn59656142014-10-28 13:52:11 -07001611 ", displayName= " + displayName +
Wink Savilleef36ef62014-06-11 08:39:38 -07001612 ", endpoint=" + endpoint);
1613 }
1614
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08001615 Uri handle = Uri.parse(user);
Uma Maheswari Ramalingama1ed7b02015-05-20 14:26:42 -07001616 if (endpoint == null) {
1617 endpoint = "";
1618 }
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08001619 Uri endpointUri = Uri.parse(endpoint);
1620 int connectionState = ImsConferenceState.getConnectionStateForStatus(status);
Wink Savilleef36ef62014-06-11 08:39:38 -07001621
Uma Maheswari Ramalingama1ed7b02015-05-20 14:26:42 -07001622 if (connectionState != Connection.STATE_DISCONNECTED) {
1623 ConferenceParticipant conferenceParticipant = new ConferenceParticipant(handle,
1624 displayName, endpointUri, connectionState);
1625 mConferenceParticipants.add(conferenceParticipant);
1626 }
Wink Savilleef36ef62014-06-11 08:39:38 -07001627 }
Tyler Gunn1c467602014-11-04 14:51:52 -08001628
Anju Mathapati0f108032015-10-09 15:34:28 -07001629 if (mConferenceParticipants != null && mListener != null) {
Tyler Gunn1c467602014-11-04 14:51:52 -08001630 try {
Uma Maheswari Ramalingama1ed7b02015-05-20 14:26:42 -07001631 mListener.onConferenceParticipantsStateChanged(this, mConferenceParticipants);
Tyler Gunn1c467602014-11-04 14:51:52 -08001632 } catch (Throwable t) {
1633 loge("notifyConferenceStateUpdated :: ", t);
1634 }
1635 }
Wink Savilleef36ef62014-06-11 08:39:38 -07001636 }
1637
Anthony Leeb8799fe2014-11-03 15:13:47 -08001638 /**
1639 * Perform all cleanup and notification around the termination of a session.
1640 * Note that there are 2 distinct modes of operation. The first is when
1641 * we receive a session termination on the primary session when we are
1642 * in the processing of merging. The second is when we are not merging anything
1643 * and the call is terminated.
1644 *
1645 * @param reasonInfo The reason for the session termination
1646 */
1647 private void processCallTerminated(ImsReasonInfo reasonInfo) {
Tyler Gunn95d563e2015-07-23 09:28:58 -07001648 logi("processCallTerminated :: reason=" + reasonInfo + " userInitiated = " +
1649 mTerminationRequestPending);
Anthony Leeb8799fe2014-11-03 15:13:47 -08001650
1651 ImsCall.Listener listener = null;
Anthony Leeb8799fe2014-11-03 15:13:47 -08001652 synchronized(ImsCall.this) {
Tyler Gunn047d8102015-01-30 15:21:11 -08001653 // If we are in the midst of establishing a conference, we will bury the termination
Tyler Gunn95d563e2015-07-23 09:28:58 -07001654 // until the merge has completed. If necessary we can surface the termination at
1655 // this point.
1656 // We will also NOT bury the termination if a termination was initiated locally.
1657 if (isCallSessionMergePending() && !mTerminationRequestPending) {
Anthony Leeb8799fe2014-11-03 15:13:47 -08001658 // Since we are in the process of a merge, this trigger means something
1659 // else because it is probably due to the merge happening vs. the
1660 // session is really terminated. Let's flag this and revisit if
1661 // the merge() ends up failing because we will need to take action on the
1662 // mSession in that case since the termination was not due to the merge
1663 // succeeding.
Anthony Lee68048512015-03-18 15:04:18 -07001664 if (CONF_DBG) {
1665 logi("processCallTerminated :: burying termination during ongoing merge.");
Anthony Leeb8799fe2014-11-03 15:13:47 -08001666 }
1667 mSessionEndDuringMerge = true;
1668 mSessionEndDuringMergeReasonInfo = reasonInfo;
1669 return;
1670 }
1671
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08001672 // If we are terminating the conference call, notify using conference listeners.
1673 if (isMultiparty()) {
Anthony Leeb8799fe2014-11-03 15:13:47 -08001674 notifyConferenceSessionTerminated(reasonInfo);
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08001675 return;
Anthony Leeb8799fe2014-11-03 15:13:47 -08001676 } else {
1677 listener = mListener;
1678 clear(reasonInfo);
1679 }
1680 }
1681
1682 if (listener != null) {
1683 try {
1684 listener.onCallTerminated(ImsCall.this, reasonInfo);
1685 } catch (Throwable t) {
Anthony Leec479f662015-02-11 17:04:35 -08001686 loge("processCallTerminated :: ", t);
Anthony Leeb8799fe2014-11-03 15:13:47 -08001687 }
1688 }
1689 }
Anthony Lee71382692014-10-30 10:50:10 -07001690
1691 /**
1692 * This function determines if the ImsCallSession is our actual ImsCallSession or if is
1693 * the transient session used in the process of creating a conference. This function should only
1694 * be called within callbacks that are not directly related to conference merging but might
1695 * potentially still be called on the transient ImsCallSession sent to us from
1696 * callSessionMergeStarted() when we don't really care. In those situations, we probably don't
1697 * want to take any action so we need to know that we can return early.
1698 *
1699 * @param session - The {@link ImsCallSession} that the function needs to analyze
1700 * @return true if this is the transient {@link ImsCallSession}, false otherwise.
1701 */
1702 private boolean isTransientConferenceSession(ImsCallSession session) {
1703 if (session != null && session != mSession && session == mTransientConferenceSession) {
1704 return true;
1705 }
1706 return false;
1707 }
1708
Anthony Leec479f662015-02-11 17:04:35 -08001709 private void setTransientSessionAsPrimary(ImsCallSession transientSession) {
1710 synchronized (ImsCall.this) {
1711 mSession.setListener(null);
1712 mSession = transientSession;
1713 mSession.setListener(createCallSessionListener());
1714 }
Tyler Gunn047d8102015-01-30 15:21:11 -08001715 }
1716
Anju Mathapati818c09d2015-08-31 14:30:47 -07001717 private void markCallAsMerged(boolean playDisconnectTone) {
1718 if (!isSessionAlive(mSession)) {
Uma Maheswari Ramalingam95be7c62015-05-06 23:08:17 -07001719 // If the peer is dead, let's not play a disconnect sound for it when we
1720 // unbury the termination callback.
Anju Mathapati818c09d2015-08-31 14:30:47 -07001721 logi("markCallAsMerged");
1722 setIsMerged(playDisconnectTone);
1723 mSessionEndDuringMerge = true;
1724 String reasonInfo;
1725 if (playDisconnectTone) {
1726 reasonInfo = "Call ended by network";
1727 } else {
1728 reasonInfo = "Call ended during conference merge process.";
1729 }
1730 mSessionEndDuringMergeReasonInfo = new ImsReasonInfo(
1731 ImsReasonInfo.CODE_UNSPECIFIED, 0, reasonInfo);
Uma Maheswari Ramalingam95be7c62015-05-06 23:08:17 -07001732 }
1733 }
1734
Tyler Gunn047d8102015-01-30 15:21:11 -08001735 /**
Uma Maheswari Ramalingam95be7c62015-05-06 23:08:17 -07001736 * Checks if the merge was requested by foreground conference call
1737 *
1738 * @return true if the merge was requested by foreground conference call
Anthony Leec479f662015-02-11 17:04:35 -08001739 */
Uma Maheswari Ramalingam95be7c62015-05-06 23:08:17 -07001740 public boolean isMergeRequestedByConf() {
1741 synchronized(mLockObj) {
1742 return mMergeRequestedByConference;
1743 }
1744 }
1745
1746 /**
1747 * Resets the flag which indicates merge request was sent by
1748 * foreground conference call
1749 */
1750 public void resetIsMergeRequestedByConf(boolean value) {
1751 synchronized(mLockObj) {
1752 mMergeRequestedByConference = value;
1753 }
1754 }
1755
1756 /**
1757 * Returns current ImsCallSession
1758 *
1759 * @return current session
1760 */
1761 public ImsCallSession getSession() {
1762 synchronized(mLockObj) {
1763 return mSession;
Anthony Leec479f662015-02-11 17:04:35 -08001764 }
1765 }
1766
1767 /**
1768 * We have detected that a initial conference call has been fully configured. The internal
1769 * state of both {@code ImsCall} objects need to be cleaned up to reflect the new state.
1770 * This function should only be called in the context of the merge host to simplify logic
Anthony Lee71382692014-10-30 10:50:10 -07001771 *
Anthony Lee71382692014-10-30 10:50:10 -07001772 */
Anthony Leeb8799fe2014-11-03 15:13:47 -08001773 private void processMergeComplete() {
Anthony Lee68048512015-03-18 15:04:18 -07001774 logi("processMergeComplete :: ");
Anthony Leec479f662015-02-11 17:04:35 -08001775
1776 // The logic simplifies if we can assume that this function is only called on
1777 // the merge host.
1778 if (!isMergeHost()) {
1779 loge("processMergeComplete :: We are not the merge host!");
1780 return;
Anthony Leeb8799fe2014-11-03 15:13:47 -08001781 }
1782
Anthony Lee71382692014-10-30 10:50:10 -07001783 ImsCall.Listener listener;
Tyler Gunn047d8102015-01-30 15:21:11 -08001784 boolean swapRequired = false;
Tyler Gunn047d8102015-01-30 15:21:11 -08001785
Uma Maheswari Ramalingam95be7c62015-05-06 23:08:17 -07001786 ImsCall finalHostCall;
1787 ImsCall finalPeerCall;
1788
1789 synchronized(ImsCall.this) {
Anthony Leec479f662015-02-11 17:04:35 -08001790 if (isMultiparty()) {
Anthony Leec479f662015-02-11 17:04:35 -08001791 setIsMerged(false);
Uma Maheswari Ramalingam95be7c62015-05-06 23:08:17 -07001792 // if case handles Case 4 explained in callSessionMergeComplete
1793 // otherwise it is case 5
1794 if (!mMergeRequestedByConference) {
1795 // single call in fg, conference call in bg.
1796 // Finally conf call becomes active after conference
1797 this.mHold = false;
1798 swapRequired = true;
Tyler Gunn047d8102015-01-30 15:21:11 -08001799 }
Anju Mathapati818c09d2015-08-31 14:30:47 -07001800 mMergePeer.markCallAsMerged(false);
Uma Maheswari Ramalingam95be7c62015-05-06 23:08:17 -07001801 finalHostCall = this;
1802 finalPeerCall = mMergePeer;
Tyler Gunn047d8102015-01-30 15:21:11 -08001803 } else {
Anthony Leec479f662015-02-11 17:04:35 -08001804 // If we are here, we are not trying to merge a new call into an existing
1805 // conference. That means that there is a transient session on the merge
1806 // host that represents the future conference once all the parties
1807 // have been added to it. So make sure that it exists or else something
1808 // very wrong is going on.
1809 if (mTransientConferenceSession == null) {
1810 loge("processMergeComplete :: No transient session!");
1811 return;
Tyler Gunn047d8102015-01-30 15:21:11 -08001812 }
Anthony Leec479f662015-02-11 17:04:35 -08001813 if (mMergePeer == null) {
1814 loge("processMergeComplete :: No merge peer!");
1815 return;
Tyler Gunn047d8102015-01-30 15:21:11 -08001816 }
Tyler Gunn047d8102015-01-30 15:21:11 -08001817
Anthony Leec479f662015-02-11 17:04:35 -08001818 // Since we are the host, we have the transient session attached to us. Let's detach
1819 // it and figure out where we need to set it for the final conference configuration.
1820 ImsCallSession transientConferenceSession = mTransientConferenceSession;
1821 mTransientConferenceSession = null;
1822
1823 // Clear the listener for this transient session, we'll create a new listener
1824 // when it is attached to the final ImsCall that it should live on.
1825 transientConferenceSession.setListener(null);
1826
1827 // Determine which call the transient session should be moved to. If the current
1828 // call session is still alive and the merge peer's session is not, we have a
1829 // situation where the current call failed to merge into the conference but the
1830 // merge peer did merge in to the conference. In this type of scenario the current
1831 // call will continue as a single party call, yet the background call will become
1832 // the conference.
1833
Uma Maheswari Ramalingam95be7c62015-05-06 23:08:17 -07001834 // handles Case 3 explained in callSessionMergeComplete
Anthony Leec479f662015-02-11 17:04:35 -08001835 if (isSessionAlive(mSession) && !isSessionAlive(mMergePeer.getCallSession())) {
1836 // I'm the host but we are moving the transient session to the peer since its
1837 // session was disconnected and my session is still alive. This signifies that
1838 // their session was properly added to the conference but mine was not because
1839 // it is probably in the held state as opposed to part of the final conference.
1840 // In this case, we need to set isMerged to false on both calls so the
1841 // disconnect sound is called when either call disconnects.
1842 // Note that this case is only valid if this is an initial conference being
1843 // brought up.
Uma Maheswari Ramalingam95be7c62015-05-06 23:08:17 -07001844 mMergePeer.mHold = false;
1845 this.mHold = true;
Anju Mathapatid0fb6642015-07-22 13:55:57 -07001846 if (mConferenceParticipants != null && !mConferenceParticipants.isEmpty()) {
1847 mMergePeer.mConferenceParticipants = mConferenceParticipants;
1848 }
1849 // At this point both host & peer will have participant information.
1850 // Peer will transition to host & the participant information
1851 // from that will be used
1852 // HostCall that failed to merge will remain as a single call with
1853 // mConferenceParticipants, which should not be used.
1854 // Expectation is that if this call becomes part of a conference call in future,
1855 // mConferenceParticipants will be overriten with new CEP that is received.
Anthony Leec479f662015-02-11 17:04:35 -08001856 finalHostCall = mMergePeer;
1857 finalPeerCall = this;
1858 swapRequired = true;
1859 setIsMerged(false);
1860 mMergePeer.setIsMerged(false);
Anthony Lee68048512015-03-18 15:04:18 -07001861 if (CONF_DBG) {
1862 logi("processMergeComplete :: transient will transfer to merge peer");
Tyler Gunn047d8102015-01-30 15:21:11 -08001863 }
Anthony Lee68048512015-03-18 15:04:18 -07001864 } else if (!isSessionAlive(mSession) &&
1865 isSessionAlive(mMergePeer.getCallSession())) {
Uma Maheswari Ramalingam95be7c62015-05-06 23:08:17 -07001866 // Handles case 2 explained in callSessionMergeComplete
Anthony Leec479f662015-02-11 17:04:35 -08001867 // The transient session stays with us and the disconnect sound should be played
1868 // when the merge peer eventually disconnects since it was not actually added to
1869 // the conference and is probably sitting in the held state.
1870 finalHostCall = this;
1871 finalPeerCall = mMergePeer;
1872 swapRequired = false;
1873 setIsMerged(false);
1874 mMergePeer.setIsMerged(false); // Play the disconnect sound
Anthony Lee68048512015-03-18 15:04:18 -07001875 if (CONF_DBG) {
1876 logi("processMergeComplete :: transient will stay with the merge host");
Tyler Gunn047d8102015-01-30 15:21:11 -08001877 }
Anthony Leec479f662015-02-11 17:04:35 -08001878 } else {
Uma Maheswari Ramalingam95be7c62015-05-06 23:08:17 -07001879 // Handles case 1 explained in callSessionMergeComplete
Anthony Leec479f662015-02-11 17:04:35 -08001880 // The transient session stays with us and the disconnect sound should not be
1881 // played when we ripple up the disconnect for the merge peer because it was
1882 // only disconnected to be added to the conference.
1883 finalHostCall = this;
1884 finalPeerCall = mMergePeer;
Anju Mathapati818c09d2015-08-31 14:30:47 -07001885 mMergePeer.markCallAsMerged(false);
Anthony Leec479f662015-02-11 17:04:35 -08001886 swapRequired = false;
1887 setIsMerged(false);
1888 mMergePeer.setIsMerged(true);
Anthony Lee68048512015-03-18 15:04:18 -07001889 if (CONF_DBG) {
1890 logi("processMergeComplete :: transient will stay with us (I'm the host).");
Anthony Leec479f662015-02-11 17:04:35 -08001891 }
Tyler Gunn047d8102015-01-30 15:21:11 -08001892 }
Anthony Leec479f662015-02-11 17:04:35 -08001893
Anthony Lee68048512015-03-18 15:04:18 -07001894 if (CONF_DBG) {
1895 logi("processMergeComplete :: call=" + finalHostCall + " is the final host");
Anthony Leec479f662015-02-11 17:04:35 -08001896 }
1897
1898 // Add the transient session to the ImsCall that ended up being the host for the
1899 // conference.
1900 finalHostCall.setTransientSessionAsPrimary(transientConferenceSession);
Tyler Gunn047d8102015-01-30 15:21:11 -08001901 }
1902
Anthony Leec479f662015-02-11 17:04:35 -08001903 listener = finalHostCall.mListener;
Tyler Gunn047d8102015-01-30 15:21:11 -08001904
Anju Mathapati1c108882015-11-17 18:59:24 +05301905 updateCallProfile(finalPeerCall);
1906 updateCallProfile(finalHostCall);
1907
Anthony Leec479f662015-02-11 17:04:35 -08001908 // Clear all the merge related flags.
1909 clearMergeInfo();
1910
1911 // For the final peer...let's bubble up any possible disconnects that we had
1912 // during the merge process
1913 finalPeerCall.notifySessionTerminatedDuringMerge();
1914 // For the final host, let's just bury the disconnects that we my have received
1915 // during the merge process since we are now the host of the conference call.
1916 finalHostCall.clearSessionTerminationFlags();
Tyler Gunn25394092015-04-01 09:40:02 -07001917
1918 // Keep track of the fact that merge host is the origin of a conference call in
1919 // progress. This is important so that we can later determine if a multiparty ImsCall
1920 // is multiparty because it was the origin of a conference call, or because it is a
1921 // member of a conference on another device.
1922 finalHostCall.mIsConferenceHost = true;
Anthony Lee71382692014-10-30 10:50:10 -07001923 }
1924 if (listener != null) {
1925 try {
Uma Maheswari Ramalingam95be7c62015-05-06 23:08:17 -07001926 // finalPeerCall will have the participant that was not merged and
1927 // it will be held state
1928 // if peer was merged successfully, finalPeerCall will be null
1929 listener.onCallMerged(finalHostCall, finalPeerCall, swapRequired);
Anthony Lee71382692014-10-30 10:50:10 -07001930 } catch (Throwable t) {
Anthony Leeb8799fe2014-11-03 15:13:47 -08001931 loge("processMergeComplete :: ", t);
Anthony Lee71382692014-10-30 10:50:10 -07001932 }
Tyler Gunn6c0b0d02015-07-01 16:39:43 -07001933 if (mConferenceParticipants != null && !mConferenceParticipants.isEmpty()) {
Uma Maheswari Ramalingama1ed7b02015-05-20 14:26:42 -07001934 try {
Anju Mathapatid0fb6642015-07-22 13:55:57 -07001935 listener.onConferenceParticipantsStateChanged(finalHostCall,
1936 mConferenceParticipants);
Uma Maheswari Ramalingama1ed7b02015-05-20 14:26:42 -07001937 } catch (Throwable t) {
1938 loge("processMergeComplete :: ", t);
1939 }
1940 }
Anthony Lee71382692014-10-30 10:50:10 -07001941 }
Anthony Lee71382692014-10-30 10:50:10 -07001942 return;
1943 }
1944
Anju Mathapati1c108882015-11-17 18:59:24 +05301945 private static void updateCallProfile(ImsCall call) {
1946 if (call != null) {
1947 call.updateCallProfile();
1948 }
1949 }
1950
1951 private void updateCallProfile() {
1952 synchronized (mLockObj) {
1953 if (mSession != null) {
1954 mCallProfile = mSession.getCallProfile();
1955 }
1956 }
1957 }
1958
Anthony Lee71382692014-10-30 10:50:10 -07001959 /**
Tyler Gunn047d8102015-01-30 15:21:11 -08001960 * Handles the case where the session has ended during a merge by reporting the termination
1961 * reason to listeners.
1962 */
1963 private void notifySessionTerminatedDuringMerge() {
1964 ImsCall.Listener listener;
1965 boolean notifyFailure = false;
1966 ImsReasonInfo notifyFailureReasonInfo = null;
1967
1968 synchronized(ImsCall.this) {
1969 listener = mListener;
1970 if (mSessionEndDuringMerge) {
1971 // Set some local variables that will send out a notification about a
1972 // previously buried termination callback for our primary session now that
1973 // we know that this is not due to the conference call merging successfully.
Anthony Lee68048512015-03-18 15:04:18 -07001974 if (CONF_DBG) {
1975 logi("notifySessionTerminatedDuringMerge ::reporting terminate during merge");
Tyler Gunn047d8102015-01-30 15:21:11 -08001976 }
1977 notifyFailure = true;
1978 notifyFailureReasonInfo = mSessionEndDuringMergeReasonInfo;
1979 }
Anthony Leec479f662015-02-11 17:04:35 -08001980 clearSessionTerminationFlags();
Tyler Gunn047d8102015-01-30 15:21:11 -08001981 }
1982
1983 if (listener != null && notifyFailure) {
1984 try {
1985 processCallTerminated(notifyFailureReasonInfo);
1986 } catch (Throwable t) {
1987 loge("notifySessionTerminatedDuringMerge :: ", t);
1988 }
1989 }
1990 }
1991
Anthony Leec479f662015-02-11 17:04:35 -08001992 private void clearSessionTerminationFlags() {
1993 mSessionEndDuringMerge = false;
1994 mSessionEndDuringMergeReasonInfo = null;
1995 }
1996
Uma Maheswari Ramalingama1ed7b02015-05-20 14:26:42 -07001997 /**
Anthony Lee71382692014-10-30 10:50:10 -07001998 * We received a callback from ImsCallSession that a merge failed. Clean up all
Anthony Leec479f662015-02-11 17:04:35 -08001999 * internal state to represent this state change. The calling function is a callback
2000 * and should have been called on the session that was in the foreground
2001 * when merge() was originally called. It is assumed that this function will be called
2002 * on the merge host.
Anthony Lee71382692014-10-30 10:50:10 -07002003 *
Anthony Lee71382692014-10-30 10:50:10 -07002004 * @param reasonInfo The {@link ImsReasonInfo} why the merge failed.
2005 */
Anthony Leeb8799fe2014-11-03 15:13:47 -08002006 private void processMergeFailed(ImsReasonInfo reasonInfo) {
Anthony Lee68048512015-03-18 15:04:18 -07002007 logi("processMergeFailed :: reason=" + reasonInfo);
Tyler Gunn047d8102015-01-30 15:21:11 -08002008
2009 ImsCall.Listener listener;
Anthony Lee71382692014-10-30 10:50:10 -07002010 synchronized(ImsCall.this) {
Anthony Leec479f662015-02-11 17:04:35 -08002011 // The logic simplifies if we can assume that this function is only called on
2012 // the merge host.
2013 if (!isMergeHost()) {
2014 loge("processMergeFailed :: We are not the merge host!");
2015 return;
2016 }
2017
Anthony Lee61d41c12015-04-02 09:40:01 -07002018 // Try to clean up the transient session if it exists.
2019 if (mTransientConferenceSession != null) {
Anthony Lee71382692014-10-30 10:50:10 -07002020 mTransientConferenceSession.setListener(null);
2021 mTransientConferenceSession = null;
2022 }
Tyler Gunn047d8102015-01-30 15:21:11 -08002023
Anthony Leec479f662015-02-11 17:04:35 -08002024 listener = mListener;
2025
Anthony Lee61d41c12015-04-02 09:40:01 -07002026 // Ensure the calls being conferenced into the conference has isMerged = false.
Anthony Leec479f662015-02-11 17:04:35 -08002027 // Ensure any terminations are surfaced from this session.
Anju Mathapati818c09d2015-08-31 14:30:47 -07002028 markCallAsMerged(true);
2029 setCallSessionMergePending(false);
Anthony Leec479f662015-02-11 17:04:35 -08002030 notifySessionTerminatedDuringMerge();
Anthony Lee61d41c12015-04-02 09:40:01 -07002031
Anju Mathapati818c09d2015-08-31 14:30:47 -07002032 // Perform the same cleanup on the merge peer if it exists.
Anthony Lee61d41c12015-04-02 09:40:01 -07002033 if (mMergePeer != null) {
Anju Mathapati818c09d2015-08-31 14:30:47 -07002034 mMergePeer.markCallAsMerged(true);
2035 mMergePeer.setCallSessionMergePending(false);
Anthony Lee61d41c12015-04-02 09:40:01 -07002036 mMergePeer.notifySessionTerminatedDuringMerge();
2037 } else {
2038 loge("processMergeFailed :: No merge peer!");
2039 }
Anthony Leec479f662015-02-11 17:04:35 -08002040
2041 // Clear all the various flags around coordinating this merge.
2042 clearMergeInfo();
Anthony Lee71382692014-10-30 10:50:10 -07002043 }
2044 if (listener != null) {
2045 try {
2046 listener.onCallMergeFailed(ImsCall.this, reasonInfo);
2047 } catch (Throwable t) {
Anthony Leeb8799fe2014-11-03 15:13:47 -08002048 loge("processMergeFailed :: ", t);
Anthony Lee71382692014-10-30 10:50:10 -07002049 }
2050 }
Anthony Leec479f662015-02-11 17:04:35 -08002051
Anthony Lee71382692014-10-30 10:50:10 -07002052 return;
2053 }
2054
Wink Savilleef36ef62014-06-11 08:39:38 -07002055 private class ImsCallSessionListenerProxy extends ImsCallSession.Listener {
2056 @Override
Anthony Lee71382692014-10-30 10:50:10 -07002057 public void callSessionProgressing(ImsCallSession session, ImsStreamMediaProfile profile) {
Anthony Lee68048512015-03-18 15:04:18 -07002058 logi("callSessionProgressing :: session=" + session + " profile=" + profile);
2059
Anthony Lee71382692014-10-30 10:50:10 -07002060 if (isTransientConferenceSession(session)) {
Anthony Leec479f662015-02-11 17:04:35 -08002061 // If it is a transient (conference) session, there is no action for this signal.
Anthony Lee68048512015-03-18 15:04:18 -07002062 logi("callSessionProgressing :: not supported for transient conference session=" +
Anthony Leeb8799fe2014-11-03 15:13:47 -08002063 session);
Anthony Lee71382692014-10-30 10:50:10 -07002064 return;
2065 }
2066
Wink Savilleef36ef62014-06-11 08:39:38 -07002067 ImsCall.Listener listener;
2068
2069 synchronized(ImsCall.this) {
2070 listener = mListener;
2071 mCallProfile.mMediaProfile.copyFrom(profile);
2072 }
2073
2074 if (listener != null) {
2075 try {
2076 listener.onCallProgressing(ImsCall.this);
2077 } catch (Throwable t) {
2078 loge("callSessionProgressing :: ", t);
2079 }
2080 }
2081 }
2082
2083 @Override
Anthony Lee71382692014-10-30 10:50:10 -07002084 public void callSessionStarted(ImsCallSession session, ImsCallProfile profile) {
Anthony Lee68048512015-03-18 15:04:18 -07002085 logi("callSessionStarted :: session=" + session + " profile=" + profile);
Anthony Leec479f662015-02-11 17:04:35 -08002086
2087 if (!isTransientConferenceSession(session)) {
2088 // In the case that we are in the middle of a merge (either host or peer), we have
2089 // closure as far as this call's primary session is concerned. If we are not
2090 // merging...its a NOOP.
2091 setCallSessionMergePending(false);
2092 } else {
Anthony Lee68048512015-03-18 15:04:18 -07002093 logi("callSessionStarted :: on transient session=" + session);
Anthony Leec479f662015-02-11 17:04:35 -08002094 return;
Wink Savilleef36ef62014-06-11 08:39:38 -07002095 }
2096
Anthony Leec479f662015-02-11 17:04:35 -08002097 if (isTransientConferenceSession(session)) {
2098 // No further processing is needed if this is the transient session.
Anthony Leeb8799fe2014-11-03 15:13:47 -08002099 return;
2100 }
2101
Wink Savilleef36ef62014-06-11 08:39:38 -07002102 ImsCall.Listener listener;
2103
2104 synchronized(ImsCall.this) {
2105 listener = mListener;
2106 mCallProfile = profile;
2107 }
2108
2109 if (listener != null) {
2110 try {
2111 listener.onCallStarted(ImsCall.this);
2112 } catch (Throwable t) {
2113 loge("callSessionStarted :: ", t);
2114 }
2115 }
2116 }
2117
2118 @Override
Anthony Lee71382692014-10-30 10:50:10 -07002119 public void callSessionStartFailed(ImsCallSession session, ImsReasonInfo reasonInfo) {
Anthony Lee68048512015-03-18 15:04:18 -07002120 loge("callSessionStartFailed :: session=" + session + " reasonInfo=" + reasonInfo);
2121
Anthony Lee71382692014-10-30 10:50:10 -07002122 if (isTransientConferenceSession(session)) {
Anthony Leec479f662015-02-11 17:04:35 -08002123 // We should not get this callback for a transient session.
Anthony Lee68048512015-03-18 15:04:18 -07002124 logi("callSessionStartFailed :: not supported for transient conference session=" +
Anthony Leeb8799fe2014-11-03 15:13:47 -08002125 session);
Anthony Lee71382692014-10-30 10:50:10 -07002126 return;
2127 }
2128
Wink Savilleef36ef62014-06-11 08:39:38 -07002129 ImsCall.Listener listener;
2130
2131 synchronized(ImsCall.this) {
2132 listener = mListener;
2133 mLastReasonInfo = reasonInfo;
2134 }
2135
2136 if (listener != null) {
2137 try {
2138 listener.onCallStartFailed(ImsCall.this, reasonInfo);
2139 } catch (Throwable t) {
2140 loge("callSessionStarted :: ", t);
2141 }
2142 }
2143 }
2144
2145 @Override
Anthony Lee71382692014-10-30 10:50:10 -07002146 public void callSessionTerminated(ImsCallSession session, ImsReasonInfo reasonInfo) {
Anthony Lee68048512015-03-18 15:04:18 -07002147 logi("callSessionTerminated :: session=" + session + " reasonInfo=" + reasonInfo);
2148
Anthony Leec479f662015-02-11 17:04:35 -08002149 if (isTransientConferenceSession(session)) {
Anthony Lee68048512015-03-18 15:04:18 -07002150 logi("callSessionTerminated :: on transient session=" + session);
Anthony Leec479f662015-02-11 17:04:35 -08002151 // This is bad, it should be treated much a callSessionMergeFailed since the
2152 // transient session only exists when in the process of a merge and the
2153 // termination of this session is effectively the end of the merge.
2154 processMergeFailed(reasonInfo);
Anthony Lee71382692014-10-30 10:50:10 -07002155 return;
2156 }
2157
Anthony Leec479f662015-02-11 17:04:35 -08002158 // Process the termination first. If we are in the midst of establishing a conference
2159 // call, we may bury this callback until we are done. If there so no conference
2160 // call, the code after this function will be a NOOP.
2161 processCallTerminated(reasonInfo);
2162
Tyler Gunn047d8102015-01-30 15:21:11 -08002163 // If session has terminated, it is no longer pending merge.
2164 setCallSessionMergePending(false);
2165
Wink Savilleef36ef62014-06-11 08:39:38 -07002166 }
2167
2168 @Override
Anthony Lee71382692014-10-30 10:50:10 -07002169 public void callSessionHeld(ImsCallSession session, ImsCallProfile profile) {
Anthony Lee68048512015-03-18 15:04:18 -07002170 logi("callSessionHeld :: session=" + session + "profile=" + profile);
Wink Savilleef36ef62014-06-11 08:39:38 -07002171 ImsCall.Listener listener;
2172
2173 synchronized(ImsCall.this) {
Tyler Gunn047d8102015-01-30 15:21:11 -08002174 // If the session was held, it is no longer pending a merge -- this means it could
2175 // not be merged into the conference and was held instead.
2176 setCallSessionMergePending(false);
2177
Wink Savilleef36ef62014-06-11 08:39:38 -07002178 mCallProfile = profile;
2179
2180 if (mUpdateRequest == UPDATE_HOLD_MERGE) {
Anthony Leec479f662015-02-11 17:04:35 -08002181 // This hold request was made to set the stage for a merge.
Wink Savilleef36ef62014-06-11 08:39:38 -07002182 mergeInternal();
2183 return;
2184 }
2185
2186 mUpdateRequest = UPDATE_NONE;
2187 listener = mListener;
2188 }
2189
2190 if (listener != null) {
2191 try {
2192 listener.onCallHeld(ImsCall.this);
2193 } catch (Throwable t) {
2194 loge("callSessionHeld :: ", t);
2195 }
2196 }
2197 }
2198
2199 @Override
Anthony Lee71382692014-10-30 10:50:10 -07002200 public void callSessionHoldFailed(ImsCallSession session, ImsReasonInfo reasonInfo) {
Anthony Lee68048512015-03-18 15:04:18 -07002201 loge("callSessionHoldFailed :: session" + session + "reasonInfo=" + reasonInfo);
2202
Anthony Lee71382692014-10-30 10:50:10 -07002203 if (isTransientConferenceSession(session)) {
Anthony Leec479f662015-02-11 17:04:35 -08002204 // We should not get this callback for a transient session.
Anthony Lee68048512015-03-18 15:04:18 -07002205 logi("callSessionHoldFailed :: not supported for transient conference session=" +
Anthony Leeb8799fe2014-11-03 15:13:47 -08002206 session);
Anthony Lee71382692014-10-30 10:50:10 -07002207 return;
2208 }
2209
Shriram Ganeshd3adfad2015-05-31 10:06:15 -07002210 logi("callSessionHoldFailed :: session=" + session +
2211 ", reasonInfo=" + reasonInfo);
2212
2213 synchronized (mLockObj) {
Uma Maheswari Ramalingama1ed7b02015-05-20 14:26:42 -07002214 mHold = false;
2215 }
2216
Wink Savilleef36ef62014-06-11 08:39:38 -07002217 boolean isHoldForMerge = false;
2218 ImsCall.Listener listener;
2219
2220 synchronized(ImsCall.this) {
2221 if (mUpdateRequest == UPDATE_HOLD_MERGE) {
2222 isHoldForMerge = true;
2223 }
2224
2225 mUpdateRequest = UPDATE_NONE;
2226 listener = mListener;
2227 }
2228
Wink Savilleef36ef62014-06-11 08:39:38 -07002229 if (listener != null) {
2230 try {
2231 listener.onCallHoldFailed(ImsCall.this, reasonInfo);
2232 } catch (Throwable t) {
2233 loge("callSessionHoldFailed :: ", t);
2234 }
2235 }
2236 }
2237
2238 @Override
Anthony Lee71382692014-10-30 10:50:10 -07002239 public void callSessionHoldReceived(ImsCallSession session, ImsCallProfile profile) {
Anthony Lee68048512015-03-18 15:04:18 -07002240 logi("callSessionHoldReceived :: session=" + session + "profile=" + profile);
2241
Anthony Lee71382692014-10-30 10:50:10 -07002242 if (isTransientConferenceSession(session)) {
Anthony Leec479f662015-02-11 17:04:35 -08002243 // We should not get this callback for a transient session.
Anthony Lee68048512015-03-18 15:04:18 -07002244 logi("callSessionHoldReceived :: not supported for transient conference session=" +
Anthony Leeb8799fe2014-11-03 15:13:47 -08002245 session);
Anthony Lee71382692014-10-30 10:50:10 -07002246 return;
2247 }
2248
Wink Savilleef36ef62014-06-11 08:39:38 -07002249 ImsCall.Listener listener;
2250
2251 synchronized(ImsCall.this) {
2252 listener = mListener;
2253 mCallProfile = profile;
2254 }
2255
2256 if (listener != null) {
2257 try {
2258 listener.onCallHoldReceived(ImsCall.this);
2259 } catch (Throwable t) {
2260 loge("callSessionHoldReceived :: ", t);
2261 }
2262 }
2263 }
2264
2265 @Override
Anthony Lee71382692014-10-30 10:50:10 -07002266 public void callSessionResumed(ImsCallSession session, ImsCallProfile profile) {
Anthony Lee68048512015-03-18 15:04:18 -07002267 logi("callSessionResumed :: session=" + session + "profile=" + profile);
2268
Anthony Lee71382692014-10-30 10:50:10 -07002269 if (isTransientConferenceSession(session)) {
Anthony Lee68048512015-03-18 15:04:18 -07002270 logi("callSessionResumed :: not supported for transient conference session=" +
Anthony Leeb8799fe2014-11-03 15:13:47 -08002271 session);
Anthony Lee71382692014-10-30 10:50:10 -07002272 return;
2273 }
2274
Anthony Leec479f662015-02-11 17:04:35 -08002275 // If this call was pending a merge, it is not anymore. This is the case when we
2276 // are merging in a new call into an existing conference.
2277 setCallSessionMergePending(false);
Wink Savilleef36ef62014-06-11 08:39:38 -07002278
Anthony Leec479f662015-02-11 17:04:35 -08002279 // TOOD: When we are merging a new call into an existing conference we are waiting
2280 // for 2 triggers to let us know that the conference has been established, the first
2281 // is a termination for the new calls (since it is added to the conference) the second
2282 // would be a resume on the existing conference. If the resume comes first, then
2283 // we will make the onCallResumed() callback and its unclear how this will behave if
2284 // the termination has not come yet.
2285
2286 ImsCall.Listener listener;
Wink Savilleef36ef62014-06-11 08:39:38 -07002287 synchronized(ImsCall.this) {
2288 listener = mListener;
2289 mCallProfile = profile;
2290 mUpdateRequest = UPDATE_NONE;
Uma Maheswari Ramalingam095839a2014-12-03 14:47:15 -08002291 mHold = false;
Wink Savilleef36ef62014-06-11 08:39:38 -07002292 }
2293
2294 if (listener != null) {
2295 try {
2296 listener.onCallResumed(ImsCall.this);
2297 } catch (Throwable t) {
2298 loge("callSessionResumed :: ", t);
2299 }
2300 }
2301 }
2302
2303 @Override
Anthony Lee71382692014-10-30 10:50:10 -07002304 public void callSessionResumeFailed(ImsCallSession session, ImsReasonInfo reasonInfo) {
Anthony Lee68048512015-03-18 15:04:18 -07002305 loge("callSessionResumeFailed :: session=" + session + "reasonInfo=" + reasonInfo);
2306
Anthony Lee71382692014-10-30 10:50:10 -07002307 if (isTransientConferenceSession(session)) {
Anthony Lee68048512015-03-18 15:04:18 -07002308 logi("callSessionResumeFailed :: not supported for transient conference session=" +
Anthony Leeb8799fe2014-11-03 15:13:47 -08002309 session);
Anthony Lee71382692014-10-30 10:50:10 -07002310 return;
2311 }
2312
Uma Maheswari Ramalingama1ed7b02015-05-20 14:26:42 -07002313 synchronized(mLockObj) {
2314 mHold = true;
2315 }
2316
Wink Savilleef36ef62014-06-11 08:39:38 -07002317 ImsCall.Listener listener;
2318
2319 synchronized(ImsCall.this) {
2320 listener = mListener;
2321 mUpdateRequest = UPDATE_NONE;
2322 }
2323
2324 if (listener != null) {
2325 try {
2326 listener.onCallResumeFailed(ImsCall.this, reasonInfo);
2327 } catch (Throwable t) {
2328 loge("callSessionResumeFailed :: ", t);
2329 }
2330 }
2331 }
2332
2333 @Override
Anthony Lee71382692014-10-30 10:50:10 -07002334 public void callSessionResumeReceived(ImsCallSession session, ImsCallProfile profile) {
Anthony Lee68048512015-03-18 15:04:18 -07002335 logi("callSessionResumeReceived :: session=" + session + "profile=" + profile);
2336
Anthony Lee71382692014-10-30 10:50:10 -07002337 if (isTransientConferenceSession(session)) {
Anthony Lee68048512015-03-18 15:04:18 -07002338 logi("callSessionResumeReceived :: not supported for transient conference session=" +
Anthony Leeb8799fe2014-11-03 15:13:47 -08002339 session);
Anthony Lee71382692014-10-30 10:50:10 -07002340 return;
2341 }
2342
Wink Savilleef36ef62014-06-11 08:39:38 -07002343 ImsCall.Listener listener;
2344
2345 synchronized(ImsCall.this) {
2346 listener = mListener;
2347 mCallProfile = profile;
2348 }
2349
2350 if (listener != null) {
2351 try {
2352 listener.onCallResumeReceived(ImsCall.this);
2353 } catch (Throwable t) {
2354 loge("callSessionResumeReceived :: ", t);
2355 }
2356 }
2357 }
2358
2359 @Override
Tyler Gunn3f2b0aa2014-10-24 10:02:19 -07002360 public void callSessionMergeStarted(ImsCallSession session,
Wink Savilleef36ef62014-06-11 08:39:38 -07002361 ImsCallSession newSession, ImsCallProfile profile) {
Anthony Lee68048512015-03-18 15:04:18 -07002362 logi("callSessionMergeStarted :: session=" + session + " newSession=" + newSession +
2363 ", profile=" + profile);
Wink Savilleef36ef62014-06-11 08:39:38 -07002364
Anthony Lee71382692014-10-30 10:50:10 -07002365 return;
Wink Savilleef36ef62014-06-11 08:39:38 -07002366 }
2367
Uma Maheswari Ramalingam95be7c62015-05-06 23:08:17 -07002368 /*
2369 * This method check if session exists as a session on the current
2370 * ImsCall or its counterpart if it is in the process of a conference
2371 */
2372 private boolean doesCallSessionExistsInMerge(ImsCallSession cs) {
2373 String callId = cs.getCallId();
2374 return ((isMergeHost() && Objects.equals(mMergePeer.mSession.getCallId(), callId)) ||
2375 (isMergePeer() && Objects.equals(mMergeHost.mSession.getCallId(), callId)) ||
2376 Objects.equals(mSession.getCallId(), callId));
2377 }
2378
2379 /**
2380 * We received a callback from ImsCallSession that merge completed.
Anju Mathapatid9ed8742015-07-17 14:54:10 -07002381 * @param newSession - this session can have 2 values based on the below scenarios
Uma Maheswari Ramalingam95be7c62015-05-06 23:08:17 -07002382 *
2383 * Conference Scenarios :
2384 * Case 1 - 3 way success case
2385 * Case 2 - 3 way success case but held call fails to merge
2386 * Case 3 - 3 way success case but active call fails to merge
2387 * case 4 - 4 way success case, where merge is initiated on the foreground single-party
2388 * call and the conference (mergeHost) is the background call.
2389 * case 5 - 4 way success case, where merge is initiated on the foreground conference
2390 * call (mergeHost) and the single party call is in the background.
2391 *
2392 * Conference Result:
Anju Mathapatid9ed8742015-07-17 14:54:10 -07002393 * session : new session after conference
2394 * newSession = new session for case 1, 2, 3.
2395 * Should be considered as mTransientConferencession
2396 * newSession = Active conference session for case 5 will be null
2397 * mergehost was foreground call
2398 * mTransientConferencession will be null
2399 * newSession = Active conference session for case 4 will be null
2400 * mergeHost was background call
2401 * mTransientConferencession will be null
Uma Maheswari Ramalingam95be7c62015-05-06 23:08:17 -07002402 */
Wink Savilleef36ef62014-06-11 08:39:38 -07002403 @Override
Anju Mathapatid9ed8742015-07-17 14:54:10 -07002404 public void callSessionMergeComplete(ImsCallSession newSession) {
2405 logi("callSessionMergeComplete :: newSession =" + newSession);
Uma Maheswari Ramalingam95be7c62015-05-06 23:08:17 -07002406 if (!isMergeHost()) {
2407 // Handles case 4
2408 mMergeHost.processMergeComplete();
2409 } else {
Anju Mathapatid9ed8742015-07-17 14:54:10 -07002410 // Handles case 1, 2, 3
2411 if (newSession != null) {
2412 mTransientConferenceSession = doesCallSessionExistsInMerge(newSession) ?
2413 null: newSession;
2414 }
2415 // Handles case 5
Uma Maheswari Ramalingam95be7c62015-05-06 23:08:17 -07002416 processMergeComplete();
2417 }
Wink Savilleef36ef62014-06-11 08:39:38 -07002418 }
2419
2420 @Override
Anthony Lee71382692014-10-30 10:50:10 -07002421 public void callSessionMergeFailed(ImsCallSession session, ImsReasonInfo reasonInfo) {
Anthony Lee68048512015-03-18 15:04:18 -07002422 loge("callSessionMergeFailed :: session=" + session + "reasonInfo=" + reasonInfo);
Anthony Leec479f662015-02-11 17:04:35 -08002423
2424 // Its possible that there could be threading issues with the other thread handling
2425 // the other call. This could affect our state.
2426 synchronized (ImsCall.this) {
Anthony Leec479f662015-02-11 17:04:35 -08002427 // Let's tell our parent ImsCall that the merge has failed and we need to clean
2428 // up any temporary, transient state. Note this only gets called for an initial
2429 // conference. If a merge into an existing conference fails, the two sessions will
2430 // just go back to their original state (ACTIVE or HELD).
2431 if (isMergeHost()) {
2432 processMergeFailed(reasonInfo);
2433 } else if (mMergeHost != null) {
2434 mMergeHost.processMergeFailed(reasonInfo);
2435 } else {
2436 loge("callSessionMergeFailed :: No merge host for this conference!");
2437 }
Anthony Lee71382692014-10-30 10:50:10 -07002438 }
Anthony Lee71382692014-10-30 10:50:10 -07002439 }
2440
2441 @Override
2442 public void callSessionUpdated(ImsCallSession session, ImsCallProfile profile) {
Anthony Lee68048512015-03-18 15:04:18 -07002443 logi("callSessionUpdated :: session=" + session + " profile=" + profile);
2444
Anthony Lee71382692014-10-30 10:50:10 -07002445 if (isTransientConferenceSession(session)) {
Anthony Lee68048512015-03-18 15:04:18 -07002446 logi("callSessionUpdated :: not supported for transient conference session=" +
Anthony Leeb8799fe2014-11-03 15:13:47 -08002447 session);
Anthony Lee71382692014-10-30 10:50:10 -07002448 return;
2449 }
2450
Wink Savilleef36ef62014-06-11 08:39:38 -07002451 ImsCall.Listener listener;
2452
2453 synchronized(ImsCall.this) {
2454 listener = mListener;
2455 mCallProfile = profile;
Wink Savilleef36ef62014-06-11 08:39:38 -07002456 }
2457
2458 if (listener != null) {
2459 try {
2460 listener.onCallUpdated(ImsCall.this);
2461 } catch (Throwable t) {
2462 loge("callSessionUpdated :: ", t);
2463 }
2464 }
2465 }
2466
2467 @Override
Anthony Lee71382692014-10-30 10:50:10 -07002468 public void callSessionUpdateFailed(ImsCallSession session, ImsReasonInfo reasonInfo) {
Anthony Lee68048512015-03-18 15:04:18 -07002469 loge("callSessionUpdateFailed :: session=" + session + " reasonInfo=" + reasonInfo);
2470
Anthony Lee71382692014-10-30 10:50:10 -07002471 if (isTransientConferenceSession(session)) {
Anthony Lee68048512015-03-18 15:04:18 -07002472 logi("callSessionUpdateFailed :: not supported for transient conference session=" +
Anthony Leeb8799fe2014-11-03 15:13:47 -08002473 session);
Anthony Lee71382692014-10-30 10:50:10 -07002474 return;
2475 }
2476
Wink Savilleef36ef62014-06-11 08:39:38 -07002477 ImsCall.Listener listener;
2478
2479 synchronized(ImsCall.this) {
2480 listener = mListener;
2481 mUpdateRequest = UPDATE_NONE;
2482 }
2483
2484 if (listener != null) {
2485 try {
2486 listener.onCallUpdateFailed(ImsCall.this, reasonInfo);
2487 } catch (Throwable t) {
2488 loge("callSessionUpdateFailed :: ", t);
2489 }
2490 }
2491 }
2492
2493 @Override
Anthony Lee71382692014-10-30 10:50:10 -07002494 public void callSessionUpdateReceived(ImsCallSession session, ImsCallProfile profile) {
Anthony Lee68048512015-03-18 15:04:18 -07002495 logi("callSessionUpdateReceived :: session=" + session + " profile=" + profile);
2496
Anthony Lee71382692014-10-30 10:50:10 -07002497 if (isTransientConferenceSession(session)) {
Anthony Lee68048512015-03-18 15:04:18 -07002498 logi("callSessionUpdateReceived :: not supported for transient conference " +
Anthony Leeb8799fe2014-11-03 15:13:47 -08002499 "session=" + session);
Anthony Lee71382692014-10-30 10:50:10 -07002500 return;
2501 }
2502
Wink Savilleef36ef62014-06-11 08:39:38 -07002503 ImsCall.Listener listener;
2504
2505 synchronized(ImsCall.this) {
2506 listener = mListener;
2507 mProposedCallProfile = profile;
2508 mUpdateRequest = UPDATE_UNSPECIFIED;
2509 }
2510
2511 if (listener != null) {
2512 try {
2513 listener.onCallUpdateReceived(ImsCall.this);
2514 } catch (Throwable t) {
2515 loge("callSessionUpdateReceived :: ", t);
2516 }
2517 }
2518 }
2519
2520 @Override
Anthony Lee71382692014-10-30 10:50:10 -07002521 public void callSessionConferenceExtended(ImsCallSession session, ImsCallSession newSession,
2522 ImsCallProfile profile) {
Anthony Lee68048512015-03-18 15:04:18 -07002523 logi("callSessionConferenceExtended :: session=" + session + " newSession=" +
2524 newSession + ", profile=" + profile);
2525
Anthony Lee71382692014-10-30 10:50:10 -07002526 if (isTransientConferenceSession(session)) {
Anthony Lee68048512015-03-18 15:04:18 -07002527 logi("callSessionConferenceExtended :: not supported for transient conference " +
Anthony Leeb8799fe2014-11-03 15:13:47 -08002528 "session=" + session);
Anthony Lee71382692014-10-30 10:50:10 -07002529 return;
2530 }
2531
Wink Savilleef36ef62014-06-11 08:39:38 -07002532 ImsCall newCall = createNewCall(newSession, profile);
2533
2534 if (newCall == null) {
2535 callSessionConferenceExtendFailed(session, new ImsReasonInfo());
2536 return;
2537 }
2538
2539 ImsCall.Listener listener;
2540
2541 synchronized(ImsCall.this) {
2542 listener = mListener;
2543 mUpdateRequest = UPDATE_NONE;
2544 }
2545
2546 if (listener != null) {
2547 try {
2548 listener.onCallConferenceExtended(ImsCall.this, newCall);
2549 } catch (Throwable t) {
2550 loge("callSessionConferenceExtended :: ", t);
2551 }
2552 }
2553 }
2554
2555 @Override
2556 public void callSessionConferenceExtendFailed(ImsCallSession session,
2557 ImsReasonInfo reasonInfo) {
Anthony Lee68048512015-03-18 15:04:18 -07002558 loge("callSessionConferenceExtendFailed :: reasonInfo=" + reasonInfo);
2559
Anthony Lee71382692014-10-30 10:50:10 -07002560 if (isTransientConferenceSession(session)) {
Anthony Lee68048512015-03-18 15:04:18 -07002561 logi("callSessionConferenceExtendFailed :: not supported for transient " +
Anthony Leeb8799fe2014-11-03 15:13:47 -08002562 "conference session=" + session);
Anthony Lee71382692014-10-30 10:50:10 -07002563 return;
2564 }
2565
Wink Savilleef36ef62014-06-11 08:39:38 -07002566 ImsCall.Listener listener;
2567
2568 synchronized(ImsCall.this) {
2569 listener = mListener;
2570 mUpdateRequest = UPDATE_NONE;
2571 }
2572
2573 if (listener != null) {
2574 try {
2575 listener.onCallConferenceExtendFailed(ImsCall.this, reasonInfo);
2576 } catch (Throwable t) {
2577 loge("callSessionConferenceExtendFailed :: ", t);
2578 }
2579 }
2580 }
2581
2582 @Override
2583 public void callSessionConferenceExtendReceived(ImsCallSession session,
2584 ImsCallSession newSession, ImsCallProfile profile) {
Anthony Lee68048512015-03-18 15:04:18 -07002585 logi("callSessionConferenceExtendReceived :: newSession=" + newSession +
2586 ", profile=" + profile);
2587
Anthony Lee71382692014-10-30 10:50:10 -07002588 if (isTransientConferenceSession(session)) {
Anthony Lee68048512015-03-18 15:04:18 -07002589 logi("callSessionConferenceExtendReceived :: not supported for transient " +
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08002590 "conference session" + session);
Anthony Lee71382692014-10-30 10:50:10 -07002591 return;
2592 }
2593
Wink Savilleef36ef62014-06-11 08:39:38 -07002594 ImsCall newCall = createNewCall(newSession, profile);
2595
2596 if (newCall == null) {
2597 // Should all the calls be terminated...???
2598 return;
2599 }
2600
2601 ImsCall.Listener listener;
2602
2603 synchronized(ImsCall.this) {
2604 listener = mListener;
2605 }
2606
2607 if (listener != null) {
2608 try {
2609 listener.onCallConferenceExtendReceived(ImsCall.this, newCall);
2610 } catch (Throwable t) {
2611 loge("callSessionConferenceExtendReceived :: ", t);
2612 }
2613 }
2614 }
2615
2616 @Override
2617 public void callSessionInviteParticipantsRequestDelivered(ImsCallSession session) {
Anthony Lee68048512015-03-18 15:04:18 -07002618 logi("callSessionInviteParticipantsRequestDelivered ::");
2619
Anthony Lee71382692014-10-30 10:50:10 -07002620 if (isTransientConferenceSession(session)) {
Anthony Lee68048512015-03-18 15:04:18 -07002621 logi("callSessionInviteParticipantsRequestDelivered :: not supported for " +
Anthony Lee71382692014-10-30 10:50:10 -07002622 "conference session=" + session);
2623 return;
2624 }
2625
Wink Savilleef36ef62014-06-11 08:39:38 -07002626 ImsCall.Listener listener;
2627
2628 synchronized(ImsCall.this) {
2629 listener = mListener;
2630 }
2631
2632 if (listener != null) {
2633 try {
2634 listener.onCallInviteParticipantsRequestDelivered(ImsCall.this);
2635 } catch (Throwable t) {
2636 loge("callSessionInviteParticipantsRequestDelivered :: ", t);
2637 }
2638 }
2639 }
2640
2641 @Override
2642 public void callSessionInviteParticipantsRequestFailed(ImsCallSession session,
2643 ImsReasonInfo reasonInfo) {
Anthony Lee68048512015-03-18 15:04:18 -07002644 loge("callSessionInviteParticipantsRequestFailed :: reasonInfo=" + reasonInfo);
2645
Anthony Lee71382692014-10-30 10:50:10 -07002646 if (isTransientConferenceSession(session)) {
Anthony Lee68048512015-03-18 15:04:18 -07002647 logi("callSessionInviteParticipantsRequestFailed :: not supported for " +
Anthony Lee71382692014-10-30 10:50:10 -07002648 "conference session=" + session);
2649 return;
2650 }
2651
Wink Savilleef36ef62014-06-11 08:39:38 -07002652 ImsCall.Listener listener;
2653
2654 synchronized(ImsCall.this) {
2655 listener = mListener;
2656 }
2657
2658 if (listener != null) {
2659 try {
2660 listener.onCallInviteParticipantsRequestFailed(ImsCall.this, reasonInfo);
2661 } catch (Throwable t) {
2662 loge("callSessionInviteParticipantsRequestFailed :: ", t);
2663 }
2664 }
2665 }
2666
2667 @Override
2668 public void callSessionRemoveParticipantsRequestDelivered(ImsCallSession session) {
Anthony Lee68048512015-03-18 15:04:18 -07002669 logi("callSessionRemoveParticipantsRequestDelivered ::");
2670
Anthony Lee71382692014-10-30 10:50:10 -07002671 if (isTransientConferenceSession(session)) {
Anthony Lee68048512015-03-18 15:04:18 -07002672 logi("callSessionRemoveParticipantsRequestDelivered :: not supported for " +
Anthony Lee71382692014-10-30 10:50:10 -07002673 "conference session=" + session);
2674 return;
2675 }
2676
Wink Savilleef36ef62014-06-11 08:39:38 -07002677 ImsCall.Listener listener;
2678
2679 synchronized(ImsCall.this) {
2680 listener = mListener;
2681 }
2682
2683 if (listener != null) {
2684 try {
2685 listener.onCallRemoveParticipantsRequestDelivered(ImsCall.this);
2686 } catch (Throwable t) {
2687 loge("callSessionRemoveParticipantsRequestDelivered :: ", t);
2688 }
2689 }
2690 }
2691
2692 @Override
2693 public void callSessionRemoveParticipantsRequestFailed(ImsCallSession session,
2694 ImsReasonInfo reasonInfo) {
Anthony Lee68048512015-03-18 15:04:18 -07002695 loge("callSessionRemoveParticipantsRequestFailed :: reasonInfo=" + reasonInfo);
2696
Anthony Lee71382692014-10-30 10:50:10 -07002697 if (isTransientConferenceSession(session)) {
Anthony Lee68048512015-03-18 15:04:18 -07002698 logi("callSessionRemoveParticipantsRequestFailed :: not supported for " +
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08002699 "conference session=" + session);
Anthony Lee71382692014-10-30 10:50:10 -07002700 return;
2701 }
2702
Wink Savilleef36ef62014-06-11 08:39:38 -07002703 ImsCall.Listener listener;
2704
2705 synchronized(ImsCall.this) {
2706 listener = mListener;
2707 }
2708
2709 if (listener != null) {
2710 try {
2711 listener.onCallRemoveParticipantsRequestFailed(ImsCall.this, reasonInfo);
2712 } catch (Throwable t) {
2713 loge("callSessionRemoveParticipantsRequestFailed :: ", t);
2714 }
2715 }
2716 }
2717
2718 @Override
2719 public void callSessionConferenceStateUpdated(ImsCallSession session,
2720 ImsConferenceState state) {
Anthony Lee68048512015-03-18 15:04:18 -07002721 logi("callSessionConferenceStateUpdated :: state=" + state);
2722
Tyler Gunn938116f2014-10-27 09:15:23 -07002723 conferenceStateUpdated(state);
Wink Savilleef36ef62014-06-11 08:39:38 -07002724 }
2725
2726 @Override
Anthony Lee71382692014-10-30 10:50:10 -07002727 public void callSessionUssdMessageReceived(ImsCallSession session, int mode,
2728 String ussdMessage) {
Anthony Lee68048512015-03-18 15:04:18 -07002729 logi("callSessionUssdMessageReceived :: mode=" + mode + ", ussdMessage=" +
2730 ussdMessage);
2731
Anthony Lee71382692014-10-30 10:50:10 -07002732 if (isTransientConferenceSession(session)) {
Anthony Lee68048512015-03-18 15:04:18 -07002733 logi("callSessionUssdMessageReceived :: not supported for transient " +
Anthony Leeb8799fe2014-11-03 15:13:47 -08002734 "conference session=" + session);
Anthony Lee71382692014-10-30 10:50:10 -07002735 return;
2736 }
2737
Wink Savilleef36ef62014-06-11 08:39:38 -07002738 ImsCall.Listener listener;
2739
2740 synchronized(ImsCall.this) {
2741 listener = mListener;
2742 }
2743
2744 if (listener != null) {
2745 try {
2746 listener.onCallUssdMessageReceived(ImsCall.this, mode, ussdMessage);
2747 } catch (Throwable t) {
2748 loge("callSessionUssdMessageReceived :: ", t);
2749 }
2750 }
2751 }
Pavel Zhamaitsiak987bab82014-11-16 15:29:09 -08002752
2753 @Override
2754 public void callSessionTtyModeReceived(ImsCallSession session, int mode) {
Anthony Lee68048512015-03-18 15:04:18 -07002755 logi("callSessionTtyModeReceived :: mode=" + mode);
Pavel Zhamaitsiak987bab82014-11-16 15:29:09 -08002756
Pavel Zhamaitsiaka6cae362014-12-10 17:31:33 -08002757 ImsCall.Listener listener;
2758
2759 synchronized(ImsCall.this) {
2760 listener = mListener;
2761 }
2762
2763 if (listener != null) {
2764 try {
2765 listener.onCallSessionTtyModeReceived(ImsCall.this, mode);
2766 } catch (Throwable t) {
2767 loge("callSessionTtyModeReceived :: ", t);
Pavel Zhamaitsiak987bab82014-11-16 15:29:09 -08002768 }
2769 }
2770 }
Rekha Kumar14631742015-02-04 10:47:00 -08002771
Tyler Gunn25394092015-04-01 09:40:02 -07002772 /**
2773 * Notifies of a change to the multiparty state for this {@code ImsCallSession}.
2774 *
2775 * @param session The call session.
2776 * @param isMultiParty {@code true} if the session became multiparty, {@code false}
2777 * otherwise.
2778 */
2779 @Override
2780 public void callSessionMultipartyStateChanged(ImsCallSession session,
2781 boolean isMultiParty) {
2782 if (VDBG) {
Pavel Zhamaitsiak691a1cc2015-04-09 10:14:55 -07002783 logi("callSessionMultipartyStateChanged isMultiParty: " + (isMultiParty ? "Y"
Tyler Gunn25394092015-04-01 09:40:02 -07002784 : "N"));
2785 }
2786
2787 ImsCall.Listener listener;
2788
2789 synchronized(ImsCall.this) {
2790 listener = mListener;
2791 }
2792
2793 if (listener != null) {
2794 try {
2795 listener.onMultipartyStateChanged(ImsCall.this, isMultiParty);
2796 } catch (Throwable t) {
2797 loge("callSessionMultipartyStateChanged :: ", t);
2798 }
2799 }
2800 }
2801
Rekha Kumar14631742015-02-04 10:47:00 -08002802 public void callSessionHandover(ImsCallSession session, int srcAccessTech,
2803 int targetAccessTech, ImsReasonInfo reasonInfo) {
Anthony Lee68048512015-03-18 15:04:18 -07002804 logi("callSessionHandover :: session=" + session + ", srcAccessTech=" +
2805 srcAccessTech + ", targetAccessTech=" + targetAccessTech + ", reasonInfo=" +
2806 reasonInfo);
Rekha Kumar14631742015-02-04 10:47:00 -08002807
2808 ImsCall.Listener listener;
2809
2810 synchronized(ImsCall.this) {
2811 listener = mListener;
2812 }
2813
2814 if (listener != null) {
2815 try {
2816 listener.onCallHandover(ImsCall.this, srcAccessTech, targetAccessTech,
2817 reasonInfo);
2818 } catch (Throwable t) {
2819 loge("callSessionHandover :: ", t);
2820 }
2821 }
2822 }
2823
2824 @Override
2825 public void callSessionHandoverFailed(ImsCallSession session, int srcAccessTech,
2826 int targetAccessTech, ImsReasonInfo reasonInfo) {
Anthony Lee68048512015-03-18 15:04:18 -07002827 loge("callSessionHandoverFailed :: session=" + session + ", srcAccessTech=" +
2828 srcAccessTech + ", targetAccessTech=" + targetAccessTech + ", reasonInfo=" +
2829 reasonInfo);
Rekha Kumar14631742015-02-04 10:47:00 -08002830
2831 ImsCall.Listener listener;
2832
2833 synchronized(ImsCall.this) {
2834 listener = mListener;
2835 }
2836
2837 if (listener != null) {
2838 try {
2839 listener.onCallHandoverFailed(ImsCall.this, srcAccessTech, targetAccessTech,
2840 reasonInfo);
2841 } catch (Throwable t) {
2842 loge("callSessionHandoverFailed :: ", t);
2843 }
2844 }
2845 }
Shriram Ganeshd3adfad2015-05-31 10:06:15 -07002846
2847 @Override
2848 public void callSessionSuppServiceReceived(ImsCallSession session,
2849 ImsSuppServiceNotification suppServiceInfo ) {
2850 if (isTransientConferenceSession(session)) {
2851 logi("callSessionSuppServiceReceived :: not supported for transient conference"
2852 + " session=" + session);
2853 return;
2854 }
2855
2856 logi("callSessionSuppServiceReceived :: session=" + session +
2857 ", suppServiceInfo" + suppServiceInfo);
2858
2859 ImsCall.Listener listener;
2860
2861 synchronized(ImsCall.this) {
2862 listener = mListener;
2863 }
2864
2865 if (listener != null) {
2866 try {
2867 listener.onCallSuppServiceReceived(ImsCall.this, suppServiceInfo);
2868 } catch (Throwable t) {
2869 loge("callSessionSuppServiceReceived :: ", t);
2870 }
2871 }
2872 }
Wink Savilleef36ef62014-06-11 08:39:38 -07002873 }
Tyler Gunn938116f2014-10-27 09:15:23 -07002874
2875 /**
2876 * Report a new conference state to the current {@link ImsCall} and inform listeners of the
2877 * change. Marked as {@code VisibleForTesting} so that the
2878 * {@code com.android.internal.telephony.TelephonyTester} class can inject a test conference
2879 * event package into a regular ongoing IMS call.
2880 *
2881 * @param state The {@link ImsConferenceState}.
2882 */
2883 @VisibleForTesting
2884 public void conferenceStateUpdated(ImsConferenceState state) {
2885 Listener listener;
2886
2887 synchronized(this) {
2888 notifyConferenceStateUpdated(state);
2889 listener = mListener;
2890 }
2891
2892 if (listener != null) {
2893 try {
2894 listener.onCallConferenceStateUpdated(this, state);
2895 } catch (Throwable t) {
2896 loge("callSessionConferenceStateUpdated :: ", t);
2897 }
2898 }
2899 }
Tyler Gunn168c6342014-11-18 08:40:49 -08002900
2901 /**
2902 * Provides a human-readable string representation of an update request.
2903 *
2904 * @param updateRequest The update request.
2905 * @return The string representation.
2906 */
2907 private String updateRequestToString(int updateRequest) {
2908 switch (updateRequest) {
2909 case UPDATE_NONE:
2910 return "NONE";
2911 case UPDATE_HOLD:
2912 return "HOLD";
2913 case UPDATE_HOLD_MERGE:
2914 return "HOLD_MERGE";
2915 case UPDATE_RESUME:
2916 return "RESUME";
2917 case UPDATE_MERGE:
2918 return "MERGE";
2919 case UPDATE_EXTEND_TO_CONFERENCE:
2920 return "EXTEND_TO_CONFERENCE";
2921 case UPDATE_UNSPECIFIED:
2922 return "UNSPECIFIED";
2923 default:
2924 return "UNKNOWN";
2925 }
2926 }
2927
2928 /**
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08002929 * Clears the merge peer for this call, ensuring that the peer's connection to this call is also
2930 * severed at the same time.
2931 */
Anthony Leec479f662015-02-11 17:04:35 -08002932 private void clearMergeInfo() {
Anthony Lee68048512015-03-18 15:04:18 -07002933 if (CONF_DBG) {
2934 logi("clearMergeInfo :: clearing all merge info");
Anthony Leec479f662015-02-11 17:04:35 -08002935 }
Tyler Gunn047d8102015-01-30 15:21:11 -08002936
Anthony Leec479f662015-02-11 17:04:35 -08002937 // First clear out the merge partner then clear ourselves out.
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08002938 if (mMergeHost != null) {
2939 mMergeHost.mMergePeer = null;
Libin.Tang@motorola.com165aed52015-02-05 22:12:09 -06002940 mMergeHost.mUpdateRequest = UPDATE_NONE;
Anthony Leec479f662015-02-11 17:04:35 -08002941 mMergeHost.mCallSessionMergePending = false;
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08002942 }
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08002943 if (mMergePeer != null) {
2944 mMergePeer.mMergeHost = null;
Libin.Tang@motorola.com165aed52015-02-05 22:12:09 -06002945 mMergePeer.mUpdateRequest = UPDATE_NONE;
Anthony Leec479f662015-02-11 17:04:35 -08002946 mMergePeer.mCallSessionMergePending = false;
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08002947 }
Anthony Leec479f662015-02-11 17:04:35 -08002948 mMergeHost = null;
2949 mMergePeer = null;
2950 mUpdateRequest = UPDATE_NONE;
2951 mCallSessionMergePending = false;
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08002952 }
2953
2954 /**
2955 * Sets the merge peer for the current call. The merge peer is the background call that will be
2956 * merged into this call. On the merge peer, sets the merge host to be this call.
2957 *
2958 * @param mergePeer The peer call to be merged into this one.
2959 */
2960 private void setMergePeer(ImsCall mergePeer) {
2961 mMergePeer = mergePeer;
2962 mMergeHost = null;
2963
2964 mergePeer.mMergeHost = ImsCall.this;
2965 mergePeer.mMergePeer = null;
2966 }
2967
2968 /**
2969 * Sets the merge hody for the current call. The merge host is the foreground call this call
2970 * will be merged into. On the merge host, sets the merge peer to be this call.
2971 *
2972 * @param mergeHost The merge host this call will be merged into.
2973 */
2974 public void setMergeHost(ImsCall mergeHost) {
2975 mMergeHost = mergeHost;
2976 mMergePeer = null;
2977
2978 mergeHost.mMergeHost = null;
2979 mergeHost.mMergePeer = ImsCall.this;
2980 }
2981
2982 /**
2983 * Determines if the current call is in the process of merging with another call or conference.
2984 *
2985 * @return {@code true} if in the process of merging.
2986 */
2987 private boolean isMerging() {
2988 return mMergePeer != null || mMergeHost != null;
2989 }
2990
2991 /**
2992 * Determines if the current call is the host of the merge.
2993 *
2994 * @return {@code true} if the call is the merge host.
2995 */
2996 private boolean isMergeHost() {
2997 return mMergePeer != null && mMergeHost == null;
2998 }
2999
3000 /**
3001 * Determines if the current call is the peer of the merge.
3002 *
3003 * @return {@code true} if the call is the merge peer.
3004 */
3005 private boolean isMergePeer() {
3006 return mMergePeer == null && mMergeHost != null;
3007 }
3008
3009 /**
Tyler Gunn047d8102015-01-30 15:21:11 -08003010 * Determines if the call session is pending merge into a conference or not.
3011 *
3012 * @return {@code true} if a merge into a conference is pending, {@code false} otherwise.
3013 */
3014 private boolean isCallSessionMergePending() {
3015 return mCallSessionMergePending;
3016 }
3017
3018 /**
3019 * Sets flag indicating whether the call session is pending merge into a conference or not.
3020 *
3021 * @param callSessionMergePending {@code true} if a merge into the conference is pending,
3022 * {@code false} otherwise.
3023 */
3024 private void setCallSessionMergePending(boolean callSessionMergePending) {
3025 mCallSessionMergePending = callSessionMergePending;
3026 }
3027
3028 /**
3029 * Determines if there is a conference merge in process. If there is a merge in process,
3030 * determines if both the merge host and peer sessions have completed the merge process. This
3031 * means that we have received terminate or hold signals for the sessions, indicating that they
3032 * are no longer in the process of being merged into the conference.
3033 * <p>
Anthony Leec479f662015-02-11 17:04:35 -08003034 * The sessions are considered to have merged if: both calls still have merge peer/host
3035 * relationships configured, both sessions are not waiting to be merged into the conference,
3036 * and the transient conference session is alive in the case of an initial conference.
Tyler Gunn047d8102015-01-30 15:21:11 -08003037 *
3038 * @return {@code true} where the host and peer sessions have finished merging into the
3039 * conference, {@code false} if the merge has not yet completed, and {@code false} if there
3040 * is no conference merge in progress.
3041 */
Anthony Leec479f662015-02-11 17:04:35 -08003042 private boolean shouldProcessConferenceResult() {
3043 boolean areMergeTriggersDone = false;
Tyler Gunn047d8102015-01-30 15:21:11 -08003044
Anthony Leec479f662015-02-11 17:04:35 -08003045 synchronized (ImsCall.this) {
3046 // if there is a merge going on, then the merge host/peer relationships should have been
3047 // set up. This works for both the initial conference or merging a call into an
3048 // existing conference.
3049 if (!isMergeHost() && !isMergePeer()) {
Anthony Lee68048512015-03-18 15:04:18 -07003050 if (CONF_DBG) {
3051 loge("shouldProcessConferenceResult :: no merge in progress");
Anthony Leec479f662015-02-11 17:04:35 -08003052 }
3053 return false;
3054 }
3055
3056 // There is a merge in progress, so check the sessions to ensure:
3057 // 1. Both calls have completed being merged (or failing to merge) into the conference.
3058 // 2. The transient conference session is alive.
3059 if (isMergeHost()) {
Anthony Lee68048512015-03-18 15:04:18 -07003060 if (CONF_DBG) {
3061 logi("shouldProcessConferenceResult :: We are a merge host");
3062 logi("shouldProcessConferenceResult :: Here is the merge peer=" + mMergePeer);
Anthony Leec479f662015-02-11 17:04:35 -08003063 }
3064 areMergeTriggersDone = !isCallSessionMergePending() &&
3065 !mMergePeer.isCallSessionMergePending();
3066 if (!isMultiparty()) {
3067 // Only check the transient session when there is no existing conference
3068 areMergeTriggersDone &= isSessionAlive(mTransientConferenceSession);
3069 }
3070 } else if (isMergePeer()) {
Anthony Lee68048512015-03-18 15:04:18 -07003071 if (CONF_DBG) {
3072 logi("shouldProcessConferenceResult :: We are a merge peer");
3073 logi("shouldProcessConferenceResult :: Here is the merge host=" + mMergeHost);
Anthony Leec479f662015-02-11 17:04:35 -08003074 }
3075 areMergeTriggersDone = !isCallSessionMergePending() &&
3076 !mMergeHost.isCallSessionMergePending();
3077 if (!mMergeHost.isMultiparty()) {
3078 // Only check the transient session when there is no existing conference
3079 areMergeTriggersDone &= isSessionAlive(mMergeHost.mTransientConferenceSession);
3080 } else {
3081 // This else block is a special case for Verizon to handle these steps
3082 // 1. Establish a conference call.
3083 // 2. Add a new call (conference in in BG)
3084 // 3. Swap (conference active on FG)
3085 // 4. Merge
3086 // What happens here is that the BG call gets a terminated callback
3087 // because it was added to the conference. I've seen where
3088 // the FG gets no callback at all because its already active.
3089 // So if we continue to wait for it to set its isCallSessionMerging
3090 // flag to false...we'll be waiting forever.
3091 areMergeTriggersDone = !isCallSessionMergePending();
3092 }
3093 } else {
3094 // Realistically this shouldn't happen, but best to be safe.
3095 loge("shouldProcessConferenceResult : merge in progress but call is neither" +
Anthony Lee68048512015-03-18 15:04:18 -07003096 " host nor peer.");
Anthony Leec479f662015-02-11 17:04:35 -08003097 }
Anthony Lee68048512015-03-18 15:04:18 -07003098 if (CONF_DBG) {
3099 logi("shouldProcessConferenceResult :: returning:" +
Anthony Leec479f662015-02-11 17:04:35 -08003100 (areMergeTriggersDone ? "true" : "false"));
3101 }
Tyler Gunn047d8102015-01-30 15:21:11 -08003102 }
Anthony Leec479f662015-02-11 17:04:35 -08003103 return areMergeTriggersDone;
Tyler Gunn047d8102015-01-30 15:21:11 -08003104 }
3105
3106 /**
Tyler Gunn168c6342014-11-18 08:40:49 -08003107 * Provides a string representation of the {@link ImsCall}. Primarily intended for use in log
3108 * statements.
3109 *
3110 * @return String representation of call.
3111 */
3112 @Override
3113 public String toString() {
3114 StringBuilder sb = new StringBuilder();
3115 sb.append("[ImsCall objId:");
3116 sb.append(System.identityHashCode(this));
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08003117 sb.append(" onHold:");
3118 sb.append(isOnHold() ? "Y" : "N");
3119 sb.append(" mute:");
3120 sb.append(isMuted() ? "Y" : "N");
Pavel Zhamaitsiak50b03e92016-02-29 14:47:39 -08003121 if (mCallProfile != null) {
3122 sb.append(" tech:");
3123 sb.append(mCallProfile.getCallExtra(ImsCallProfile.EXTRA_CALL_RAT_TYPE));
3124 }
Tyler Gunn168c6342014-11-18 08:40:49 -08003125 sb.append(" updateRequest:");
3126 sb.append(updateRequestToString(mUpdateRequest));
Tyler Gunn87466c52014-12-08 09:56:17 -08003127 sb.append(" merging:");
3128 sb.append(isMerging() ? "Y" : "N");
3129 if (isMerging()) {
3130 if (isMergePeer()) {
3131 sb.append("P");
3132 } else {
3133 sb.append("H");
3134 }
3135 }
Anthony Leec479f662015-02-11 17:04:35 -08003136 sb.append(" merge action pending:");
3137 sb.append(isCallSessionMergePending() ? "Y" : "N");
Tyler Gunn87466c52014-12-08 09:56:17 -08003138 sb.append(" merged:");
3139 sb.append(isMerged() ? "Y" : "N");
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08003140 sb.append(" multiParty:");
3141 sb.append(isMultiparty() ? "Y" : "N");
Tyler Gunn25394092015-04-01 09:40:02 -07003142 sb.append(" confHost:");
3143 sb.append(isConferenceHost() ? "Y" : "N");
Anthony Leec479f662015-02-11 17:04:35 -08003144 sb.append(" buried term:");
3145 sb.append(mSessionEndDuringMerge ? "Y" : "N");
Tyler Gunn9bd5ca52014-12-02 09:21:01 -08003146 sb.append(" session:");
3147 sb.append(mSession);
Tyler Gunn168c6342014-11-18 08:40:49 -08003148 sb.append(" transientSession:");
3149 sb.append(mTransientConferenceSession);
3150 sb.append("]");
3151 return sb.toString();
3152 }
Anthony Lee68048512015-03-18 15:04:18 -07003153
3154 private void throwImsException(Throwable t, int code) throws ImsException {
3155 if (t instanceof ImsException) {
3156 throw (ImsException) t;
3157 } else {
3158 throw new ImsException(String.valueOf(code), t, code);
3159 }
3160 }
3161
3162 /**
3163 * Append the ImsCall information to the provided string. Usefull for as a logging helper.
3164 * @param s The original string
3165 * @return The original string with {@code ImsCall} information appended to it.
3166 */
3167 private String appendImsCallInfoToString(String s) {
3168 StringBuilder sb = new StringBuilder();
3169 sb.append(s);
3170 sb.append(" ImsCall=");
3171 sb.append(ImsCall.this);
3172 return sb.toString();
3173 }
3174
3175 /**
3176 * Log a string to the radio buffer at the info level.
3177 * @param s The message to log
3178 */
3179 private void logi(String s) {
3180 Log.i(TAG, appendImsCallInfoToString(s));
3181 }
3182
3183 /**
3184 * Log a string to the radio buffer at the debug level.
3185 * @param s The message to log
3186 */
3187 private void logd(String s) {
3188 Log.d(TAG, appendImsCallInfoToString(s));
3189 }
3190
3191 /**
3192 * Log a string to the radio buffer at the verbose level.
3193 * @param s The message to log
3194 */
3195 private void logv(String s) {
3196 Log.v(TAG, appendImsCallInfoToString(s));
3197 }
3198
3199 /**
3200 * Log a string to the radio buffer at the error level.
3201 * @param s The message to log
3202 */
3203 private void loge(String s) {
3204 Log.e(TAG, appendImsCallInfoToString(s));
3205 }
3206
3207 /**
3208 * Log a string to the radio buffer at the error level with a throwable
3209 * @param s The message to log
3210 * @param t The associated throwable
3211 */
3212 private void loge(String s, Throwable t) {
3213 Log.e(TAG, appendImsCallInfoToString(s), t);
3214 }
3215
Wink Savilleef36ef62014-06-11 08:39:38 -07003216}