Add prototype of terminate() which supports override terminate reason code.
[android/platform/frameworks/opt/net/ims.git] / src / java / com / android / ims / ImsCall.java
1 /*
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
17 package com.android.ims;
18
19 import com.android.internal.R;
20
21 import java.util.ArrayList;
22 import java.util.Iterator;
23 import java.util.List;
24 import java.util.Map.Entry;
25 import java.util.Set;
26
27 import android.content.Context;
28 import android.net.Uri;
29 import android.os.Bundle;
30 import android.os.Message;
31 import android.telecom.ConferenceParticipant;
32 import android.telecom.Connection;
33 import java.util.Objects;
34 import java.util.concurrent.atomic.AtomicInteger;
35
36 import android.telephony.ServiceState;
37 import android.util.Log;
38
39 import com.android.ims.internal.ICall;
40 import com.android.ims.internal.ImsCallSession;
41 import com.android.ims.internal.ImsStreamMediaSession;
42 import com.android.internal.annotations.VisibleForTesting;
43
44 /**
45  * Handles an IMS voice / video call over LTE. You can instantiate this class with
46  * {@link ImsManager}.
47  *
48  * @hide
49  */
50 public class ImsCall implements ICall {
51     // Mode of USSD message
52     public static final int USSD_MODE_NOTIFY = 0;
53     public static final int USSD_MODE_REQUEST = 1;
54
55     private static final String TAG = "ImsCall";
56
57     // This flag is meant to be used as a debugging tool to quickly see all logs
58     // regardless of the actual log level set on this component.
59     private static final boolean FORCE_DEBUG = false; /* STOPSHIP if true */
60
61     // We will log messages guarded by these flags at the info level. If logging is required
62     // to occur at (and only at) a particular log level, please use the logd, logv and loge
63     // functions as those will not be affected by the value of FORCE_DEBUG at all.
64     // Otherwise, anything guarded by these flags will be logged at the info level since that
65     // level allows those statements ot be logged by default which supports the workflow of
66     // setting FORCE_DEBUG and knowing these logs will show up regardless of the actual log
67     // level of this component.
68     private static final boolean DBG = FORCE_DEBUG || Log.isLoggable(TAG, Log.DEBUG);
69     private static final boolean VDBG = FORCE_DEBUG || Log.isLoggable(TAG, Log.VERBOSE);
70     // This is a special flag that is used only to highlight specific log around bringing
71     // up and tearing down conference calls. At times, these errors are transient and hard to
72     // reproduce so we need to capture this information the first time.
73     // TODO: Set this flag to FORCE_DEBUG once the new conference call logic gets more mileage
74     // across different IMS implementations.
75     private static final boolean CONF_DBG = true;
76
77     private List<ConferenceParticipant> mConferenceParticipants;
78     /**
79      * Listener for events relating to an IMS call, such as when a call is being
80      * received ("on ringing") or a call is outgoing ("on calling").
81      * <p>Many of these events are also received by {@link ImsCallSession.Listener}.</p>
82      */
83     public static class Listener {
84         /**
85          * Called when a request is sent out to initiate a new call
86          * and 1xx response is received from the network.
87          * The default implementation calls {@link #onCallStateChanged}.
88          *
89          * @param call the call object that carries out the IMS call
90          */
91         public void onCallProgressing(ImsCall call) {
92             onCallStateChanged(call);
93         }
94
95         /**
96          * Called when the call is established.
97          * The default implementation calls {@link #onCallStateChanged}.
98          *
99          * @param call the call object that carries out the IMS call
100          */
101         public void onCallStarted(ImsCall call) {
102             onCallStateChanged(call);
103         }
104
105         /**
106          * Called when the call setup is failed.
107          * The default implementation calls {@link #onCallError}.
108          *
109          * @param call the call object that carries out the IMS call
110          * @param reasonInfo detailed reason of the call setup failure
111          */
112         public void onCallStartFailed(ImsCall call, ImsReasonInfo reasonInfo) {
113             onCallError(call, reasonInfo);
114         }
115
116         /**
117          * Called when the call is terminated.
118          * The default implementation calls {@link #onCallStateChanged}.
119          *
120          * @param call the call object that carries out the IMS call
121          * @param reasonInfo detailed reason of the call termination
122          */
123         public void onCallTerminated(ImsCall call, ImsReasonInfo reasonInfo) {
124             // Store the call termination reason
125
126             onCallStateChanged(call);
127         }
128
129         /**
130          * Called when the call is in hold.
131          * The default implementation calls {@link #onCallStateChanged}.
132          *
133          * @param call the call object that carries out the IMS call
134          */
135         public void onCallHeld(ImsCall call) {
136             onCallStateChanged(call);
137         }
138
139         /**
140          * Called when the call hold is failed.
141          * The default implementation calls {@link #onCallError}.
142          *
143          * @param call the call object that carries out the IMS call
144          * @param reasonInfo detailed reason of the call hold failure
145          */
146         public void onCallHoldFailed(ImsCall call, ImsReasonInfo reasonInfo) {
147             onCallError(call, reasonInfo);
148         }
149
150         /**
151          * Called when the call hold is received from the remote user.
152          * The default implementation calls {@link #onCallStateChanged}.
153          *
154          * @param call the call object that carries out the IMS call
155          */
156         public void onCallHoldReceived(ImsCall call) {
157             onCallStateChanged(call);
158         }
159
160         /**
161          * Called when the call is in call.
162          * The default implementation calls {@link #onCallStateChanged}.
163          *
164          * @param call the call object that carries out the IMS call
165          */
166         public void onCallResumed(ImsCall call) {
167             onCallStateChanged(call);
168         }
169
170         /**
171          * Called when the call resume is failed.
172          * The default implementation calls {@link #onCallError}.
173          *
174          * @param call the call object that carries out the IMS call
175          * @param reasonInfo detailed reason of the call resume failure
176          */
177         public void onCallResumeFailed(ImsCall call, ImsReasonInfo reasonInfo) {
178             onCallError(call, reasonInfo);
179         }
180
181         /**
182          * Called when the call resume is received from the remote user.
183          * The default implementation calls {@link #onCallStateChanged}.
184          *
185          * @param call the call object that carries out the IMS call
186          */
187         public void onCallResumeReceived(ImsCall call) {
188             onCallStateChanged(call);
189         }
190
191         /**
192          * Called when the call is in call.
193          * The default implementation calls {@link #onCallStateChanged}.
194          *
195          * @param call the call object that carries out the active IMS call
196          * @param peerCall the call object that carries out the held IMS call
197          * @param swapCalls {@code true} if the foreground and background calls should be swapped
198          *                              now that the merge has completed.
199          */
200         public void onCallMerged(ImsCall call, ImsCall peerCall, boolean swapCalls) {
201             onCallStateChanged(call);
202         }
203
204         /**
205          * Called when the call merge is failed.
206          * The default implementation calls {@link #onCallError}.
207          *
208          * @param call the call object that carries out the IMS call
209          * @param reasonInfo detailed reason of the call merge failure
210          */
211         public void onCallMergeFailed(ImsCall call, ImsReasonInfo reasonInfo) {
212             onCallError(call, reasonInfo);
213         }
214
215         /**
216          * Called when the call is updated (except for hold/unhold).
217          * The default implementation calls {@link #onCallStateChanged}.
218          *
219          * @param call the call object that carries out the IMS call
220          */
221         public void onCallUpdated(ImsCall call) {
222             onCallStateChanged(call);
223         }
224
225         /**
226          * Called when the call update is failed.
227          * The default implementation calls {@link #onCallError}.
228          *
229          * @param call the call object that carries out the IMS call
230          * @param reasonInfo detailed reason of the call update failure
231          */
232         public void onCallUpdateFailed(ImsCall call, ImsReasonInfo reasonInfo) {
233             onCallError(call, reasonInfo);
234         }
235
236         /**
237          * Called when the call update is received from the remote user.
238          *
239          * @param call the call object that carries out the IMS call
240          */
241         public void onCallUpdateReceived(ImsCall call) {
242             // no-op
243         }
244
245         /**
246          * Called when the call is extended to the conference call.
247          * The default implementation calls {@link #onCallStateChanged}.
248          *
249          * @param call the call object that carries out the IMS call
250          * @param newCall the call object that is extended to the conference from the active call
251          */
252         public void onCallConferenceExtended(ImsCall call, ImsCall newCall) {
253             onCallStateChanged(call);
254         }
255
256         /**
257          * Called when the conference extension is failed.
258          * The default implementation calls {@link #onCallError}.
259          *
260          * @param call the call object that carries out the IMS call
261          * @param reasonInfo detailed reason of the conference extension failure
262          */
263         public void onCallConferenceExtendFailed(ImsCall call,
264                 ImsReasonInfo reasonInfo) {
265             onCallError(call, reasonInfo);
266         }
267
268         /**
269          * Called when the conference extension is received from the remote user.
270          *
271          * @param call the call object that carries out the IMS call
272          * @param newCall the call object that is extended to the conference from the active call
273          */
274         public void onCallConferenceExtendReceived(ImsCall call, ImsCall newCall) {
275             onCallStateChanged(call);
276         }
277
278         /**
279          * Called when the invitation request of the participants is delivered to
280          * the conference server.
281          *
282          * @param call the call object that carries out the IMS call
283          */
284         public void onCallInviteParticipantsRequestDelivered(ImsCall call) {
285             // no-op
286         }
287
288         /**
289          * Called when the invitation request of the participants is failed.
290          *
291          * @param call the call object that carries out the IMS call
292          * @param reasonInfo detailed reason of the conference invitation failure
293          */
294         public void onCallInviteParticipantsRequestFailed(ImsCall call,
295                 ImsReasonInfo reasonInfo) {
296             // no-op
297         }
298
299         /**
300          * Called when the removal request of the participants is delivered to
301          * the conference server.
302          *
303          * @param call the call object that carries out the IMS call
304          */
305         public void onCallRemoveParticipantsRequestDelivered(ImsCall call) {
306             // no-op
307         }
308
309         /**
310          * Called when the removal request of the participants is failed.
311          *
312          * @param call the call object that carries out the IMS call
313          * @param reasonInfo detailed reason of the conference removal failure
314          */
315         public void onCallRemoveParticipantsRequestFailed(ImsCall call,
316                 ImsReasonInfo reasonInfo) {
317             // no-op
318         }
319
320         /**
321          * Called when the conference state is updated.
322          *
323          * @param call the call object that carries out the IMS call
324          * @param state state of the participant who is participated in the conference call
325          */
326         public void onCallConferenceStateUpdated(ImsCall call, ImsConferenceState state) {
327             // no-op
328         }
329
330         /**
331          * Called when the state of IMS conference participant(s) has changed.
332          *
333          * @param call the call object that carries out the IMS call.
334          * @param participants the participant(s) and their new state information.
335          */
336         public void onConferenceParticipantsStateChanged(ImsCall call,
337                 List<ConferenceParticipant> participants) {
338             // no-op
339         }
340
341         /**
342          * Called when the USSD message is received from the network.
343          *
344          * @param mode mode of the USSD message (REQUEST / NOTIFY)
345          * @param ussdMessage USSD message
346          */
347         public void onCallUssdMessageReceived(ImsCall call,
348                 int mode, String ussdMessage) {
349             // no-op
350         }
351
352         /**
353          * Called when an error occurs. The default implementation is no op.
354          * overridden. The default implementation is no op. Error events are
355          * not re-directed to this callback and are handled in {@link #onCallError}.
356          *
357          * @param call the call object that carries out the IMS call
358          * @param reasonInfo detailed reason of this error
359          * @see ImsReasonInfo
360          */
361         public void onCallError(ImsCall call, ImsReasonInfo reasonInfo) {
362             // no-op
363         }
364
365         /**
366          * Called when an event occurs and the corresponding callback is not
367          * overridden. The default implementation is no op. Error events are
368          * not re-directed to this callback and are handled in {@link #onCallError}.
369          *
370          * @param call the call object that carries out the IMS call
371          */
372         public void onCallStateChanged(ImsCall call) {
373             // no-op
374         }
375
376         /**
377          * Called when the call moves the hold state to the conversation state.
378          * For example, when merging the active & hold call, the state of all the hold call
379          * will be changed from hold state to conversation state.
380          * This callback method can be invoked even though the application does not trigger
381          * any operations.
382          *
383          * @param call the call object that carries out the IMS call
384          * @param state the detailed state of call state changes;
385          *      Refer to CALL_STATE_* in {@link ImsCall}
386          */
387         public void onCallStateChanged(ImsCall call, int state) {
388             // no-op
389         }
390
391         /**
392          * Called when the call supp service is received
393          * The default implementation calls {@link #onCallStateChanged}.
394          *
395          * @param call the call object that carries out the IMS call
396          */
397         public void onCallSuppServiceReceived(ImsCall call,
398             ImsSuppServiceNotification suppServiceInfo) {
399         }
400
401         /**
402          * Called when TTY mode of remote party changed
403          *
404          * @param call the call object that carries out the IMS call
405          * @param mode TTY mode of remote party
406          */
407         public void onCallSessionTtyModeReceived(ImsCall call, int mode) {
408             // no-op
409         }
410
411         /**
412          * Called when handover occurs from one access technology to another.
413          *
414          * @param imsCall ImsCall object
415          * @param srcAccessTech original access technology
416          * @param targetAccessTech new access technology
417          * @param reasonInfo
418          */
419         public void onCallHandover(ImsCall imsCall, int srcAccessTech, int targetAccessTech,
420             ImsReasonInfo reasonInfo) {
421         }
422
423         /**
424          * Called when handover from one access technology to another fails.
425          *
426          * @param imsCall call that failed the handover.
427          * @param srcAccessTech original access technology
428          * @param targetAccessTech new access technology
429          * @param reasonInfo
430          */
431         public void onCallHandoverFailed(ImsCall imsCall, int srcAccessTech, int targetAccessTech,
432             ImsReasonInfo reasonInfo) {
433         }
434
435         /**
436          * Notifies of a change to the multiparty state for this {@code ImsCall}.
437          *
438          * @param imsCall The IMS call.
439          * @param isMultiParty {@code true} if the call became multiparty, {@code false}
440          *      otherwise.
441          */
442         public void onMultipartyStateChanged(ImsCall imsCall, boolean isMultiParty) {
443         }
444     }
445
446     // List of update operation for IMS call control
447     private static final int UPDATE_NONE = 0;
448     private static final int UPDATE_HOLD = 1;
449     private static final int UPDATE_HOLD_MERGE = 2;
450     private static final int UPDATE_RESUME = 3;
451     private static final int UPDATE_MERGE = 4;
452     private static final int UPDATE_EXTEND_TO_CONFERENCE = 5;
453     private static final int UPDATE_UNSPECIFIED = 6;
454
455     // For synchronization of private variables
456     private Object mLockObj = new Object();
457     private Context mContext;
458
459     // true if the call is established & in the conversation state
460     private boolean mInCall = false;
461     // true if the call is on hold
462     // If it is triggered by the local, mute the call. Otherwise, play local hold tone
463     // or network generated media.
464     private boolean mHold = false;
465     // true if the call is on mute
466     private boolean mMute = false;
467     // It contains the exclusive call update request. Refer to UPDATE_*.
468     private int mUpdateRequest = UPDATE_NONE;
469
470     private ImsCall.Listener mListener = null;
471
472     // When merging two calls together, the "peer" call that will merge into this call.
473     private ImsCall mMergePeer = null;
474     // When merging two calls together, the "host" call we are merging into.
475     private ImsCall mMergeHost = null;
476
477     // True if Conference request was initiated by
478     // Foreground Conference call else it will be false
479     private boolean mMergeRequestedByConference = false;
480     // Wrapper call session to interworking the IMS service (server).
481     private ImsCallSession mSession = null;
482     // Call profile of the current session.
483     // It can be changed at anytime when the call is updated.
484     private ImsCallProfile mCallProfile = null;
485     // Call profile to be updated after the application's action (accept/reject)
486     // to the call update. After the application's action (accept/reject) is done,
487     // it will be set to null.
488     private ImsCallProfile mProposedCallProfile = null;
489     private ImsReasonInfo mLastReasonInfo = null;
490
491     // Media session to control media (audio/video) operations for an IMS call
492     private ImsStreamMediaSession mMediaSession = null;
493
494     // The temporary ImsCallSession that could represent the merged call once
495     // we receive notification that the merge was successful.
496     private ImsCallSession mTransientConferenceSession = null;
497     // While a merge is progressing, we bury any session termination requests
498     // made on the original ImsCallSession until we have closure on the merge request
499     // If the request ultimately fails, we need to act on the termination request
500     // that we buried temporarily. We do this because we feel that timing issues could
501     // cause the termination request to occur just because the merge is succeeding.
502     private boolean mSessionEndDuringMerge = false;
503     // Just like mSessionEndDuringMerge, we need to keep track of the reason why the
504     // termination request was made on the original session in case we need to act
505     // on it in the case of a merge failure.
506     private ImsReasonInfo mSessionEndDuringMergeReasonInfo = null;
507     // This flag is used to indicate if this ImsCall was merged into a conference
508     // or not.  It is used primarily to determine if a disconnect sound should
509     // be heard when the call is terminated.
510     private boolean mIsMerged = false;
511     // If true, this flag means that this ImsCall is in the process of merging
512     // into a conference but it does not yet have closure on if it was
513     // actually added to the conference or not. false implies that it either
514     // is not part of a merging conference or already knows if it was
515     // successfully added.
516     private boolean mCallSessionMergePending = false;
517
518     /**
519      * If {@code true}, this flag indicates that a request to terminate the call was made by
520      * Telephony (could be from the user or some internal telephony logic)
521      * and that when we receive a {@link #processCallTerminated(ImsReasonInfo)} callback from the
522      * radio indicating that the call was terminated, we should override any burying of the
523      * termination due to an ongoing conference merge.
524      */
525     private boolean mTerminationRequestPending = false;
526
527     /**
528      * For multi-party IMS calls (e.g. conferences), determines if this {@link ImsCall} is the one
529      * hosting the call.  This is used to distinguish between a situation where an {@link ImsCall}
530      * is {@link #isMultiparty()} because calls were merged on the device, and a situation where
531      * an {@link ImsCall} is {@link #isMultiparty()} because it is a member of a conference started
532      * on another device.
533      * <p>
534      * When {@code true}, this {@link ImsCall} is is the origin of the conference call.
535      * When {@code false}, this {@link ImsCall} is a member of a conference started on another
536      * device.
537      */
538     private boolean mIsConferenceHost = false;
539
540     /**
541      * Tracks whether this {@link ImsCall} has been a video call at any point in its lifetime.
542      * Some examples of calls which are/were video calls:
543      * 1. A call which has been a video call for its duration.
544      * 2. An audio call upgraded to video (and potentially downgraded to audio later).
545      * 3. A call answered as video which was downgraded to audio.
546      */
547     private boolean mWasVideoCall = false;
548
549     /**
550      * Unique id generator used to generate call id.
551      */
552     private static final AtomicInteger sUniqueIdGenerator = new AtomicInteger();
553
554     /**
555      * Unique identifier.
556      */
557     public final int uniqueId;
558
559     /**
560      * The current ImsCallSessionListenerProxy.
561      */
562     private ImsCallSessionListenerProxy mImsCallSessionListenerProxy;
563
564     /**
565      * When calling {@link #terminate(int, int)}, an override for the termination reason which the
566      * modem returns.
567      *
568      * Necessary because passing in an unexpected {@link ImsReasonInfo} reason code to
569      * {@link #terminate(int)} will cause the modem to ignore the terminate request.
570      */
571     private int mOverrideReason = ImsReasonInfo.CODE_UNSPECIFIED;
572
573     /**
574      * Create an IMS call object.
575      *
576      * @param context the context for accessing system services
577      * @param profile the call profile to make/take a call
578      */
579     public ImsCall(Context context, ImsCallProfile profile) {
580         mContext = context;
581         setCallProfile(profile);
582         uniqueId = sUniqueIdGenerator.getAndIncrement();
583     }
584
585     /**
586      * Closes this object. This object is not usable after being closed.
587      */
588     @Override
589     public void close() {
590         synchronized(mLockObj) {
591             if (mSession != null) {
592                 mSession.close();
593                 mSession = null;
594             } else {
595                 logi("close :: Cannot close Null call session!");
596             }
597
598             mCallProfile = null;
599             mProposedCallProfile = null;
600             mLastReasonInfo = null;
601             mMediaSession = null;
602         }
603     }
604
605     /**
606      * Checks if the call has a same remote user identity or not.
607      *
608      * @param userId the remote user identity
609      * @return true if the remote user identity is equal; otherwise, false
610      */
611     @Override
612     public boolean checkIfRemoteUserIsSame(String userId) {
613         if (userId == null) {
614             return false;
615         }
616
617         return userId.equals(mCallProfile.getCallExtra(ImsCallProfile.EXTRA_REMOTE_URI, ""));
618     }
619
620     /**
621      * Checks if the call is equal or not.
622      *
623      * @param call the call to be compared
624      * @return true if the call is equal; otherwise, false
625      */
626     @Override
627     public boolean equalsTo(ICall call) {
628         if (call == null) {
629             return false;
630         }
631
632         if (call instanceof ImsCall) {
633             return this.equals(call);
634         }
635
636         return false;
637     }
638
639     public static boolean isSessionAlive(ImsCallSession session) {
640         return session != null && session.isAlive();
641     }
642
643     /**
644      * Gets the negotiated (local & remote) call profile.
645      *
646      * @return a {@link ImsCallProfile} object that has the negotiated call profile
647      */
648     public ImsCallProfile getCallProfile() {
649         synchronized(mLockObj) {
650             return mCallProfile;
651         }
652     }
653
654     /**
655      * Replaces the current call profile with a new one, tracking whethere this was previously a
656      * video call or not.
657      *
658      * @param profile The new call profile.
659      */
660     private void setCallProfile(ImsCallProfile profile) {
661         synchronized(mLockObj) {
662             mCallProfile = profile;
663             trackVideoStateHistory(mCallProfile);
664         }
665     }
666
667     /**
668      * Gets the local call profile (local capabilities).
669      *
670      * @return a {@link ImsCallProfile} object that has the local call profile
671      */
672     public ImsCallProfile getLocalCallProfile() throws ImsException {
673         synchronized(mLockObj) {
674             if (mSession == null) {
675                 throw new ImsException("No call session",
676                         ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
677             }
678
679             try {
680                 return mSession.getLocalCallProfile();
681             } catch (Throwable t) {
682                 loge("getLocalCallProfile :: ", t);
683                 throw new ImsException("getLocalCallProfile()", t, 0);
684             }
685         }
686     }
687
688     /**
689      * Gets the remote call profile (remote capabilities).
690      *
691      * @return a {@link ImsCallProfile} object that has the remote call profile
692      */
693     public ImsCallProfile getRemoteCallProfile() throws ImsException {
694         synchronized(mLockObj) {
695             if (mSession == null) {
696                 throw new ImsException("No call session",
697                         ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
698             }
699
700             try {
701                 return mSession.getRemoteCallProfile();
702             } catch (Throwable t) {
703                 loge("getRemoteCallProfile :: ", t);
704                 throw new ImsException("getRemoteCallProfile()", t, 0);
705             }
706         }
707     }
708
709     /**
710      * Gets the call profile proposed by the local/remote user.
711      *
712      * @return a {@link ImsCallProfile} object that has the proposed call profile
713      */
714     public ImsCallProfile getProposedCallProfile() {
715         synchronized(mLockObj) {
716             if (!isInCall()) {
717                 return null;
718             }
719
720             return mProposedCallProfile;
721         }
722     }
723
724     /**
725      * Gets the list of conference participants currently
726      * associated with this call.
727      *
728      * @return The list of conference participants.
729      */
730     public List<ConferenceParticipant> getConferenceParticipants() {
731         synchronized(mLockObj) {
732             logi("getConferenceParticipants :: mConferenceParticipants"
733                     + mConferenceParticipants);
734             return mConferenceParticipants;
735         }
736     }
737
738     /**
739      * Gets the state of the {@link ImsCallSession} that carries this call.
740      * The value returned must be one of the states in {@link ImsCallSession#State}.
741      *
742      * @return the session state
743      */
744     public int getState() {
745         synchronized(mLockObj) {
746             if (mSession == null) {
747                 return ImsCallSession.State.IDLE;
748             }
749
750             return mSession.getState();
751         }
752     }
753
754     /**
755      * Gets the {@link ImsCallSession} that carries this call.
756      *
757      * @return the session object that carries this call
758      * @hide
759      */
760     public ImsCallSession getCallSession() {
761         synchronized(mLockObj) {
762             return mSession;
763         }
764     }
765
766     /**
767      * Gets the {@link ImsStreamMediaSession} that handles the media operation of this call.
768      * Almost interface APIs are for the VT (Video Telephony).
769      *
770      * @return the media session object that handles the media operation of this call
771      * @hide
772      */
773     public ImsStreamMediaSession getMediaSession() {
774         synchronized(mLockObj) {
775             return mMediaSession;
776         }
777     }
778
779     /**
780      * Gets the specified property of this call.
781      *
782      * @param name key to get the extra call information defined in {@link ImsCallProfile}
783      * @return the extra call information as string
784      */
785     public String getCallExtra(String name) throws ImsException {
786         // Lookup the cache
787
788         synchronized(mLockObj) {
789             // If not found, try to get the property from the remote
790             if (mSession == null) {
791                 throw new ImsException("No call session",
792                         ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
793             }
794
795             try {
796                 return mSession.getProperty(name);
797             } catch (Throwable t) {
798                 loge("getCallExtra :: ", t);
799                 throw new ImsException("getCallExtra()", t, 0);
800             }
801         }
802     }
803
804     /**
805      * Gets the last reason information when the call is not established, cancelled or terminated.
806      *
807      * @return the last reason information
808      */
809     public ImsReasonInfo getLastReasonInfo() {
810         synchronized(mLockObj) {
811             return mLastReasonInfo;
812         }
813     }
814
815     /**
816      * Checks if the call has a pending update operation.
817      *
818      * @return true if the call has a pending update operation
819      */
820     public boolean hasPendingUpdate() {
821         synchronized(mLockObj) {
822             return (mUpdateRequest != UPDATE_NONE);
823         }
824     }
825
826     /**
827      * Checks if the call is pending a hold operation.
828      *
829      * @return true if the call is pending a hold operation.
830      */
831     public boolean isPendingHold() {
832         synchronized(mLockObj) {
833             return (mUpdateRequest == UPDATE_HOLD);
834         }
835     }
836
837     /**
838      * Checks if the call is established.
839      *
840      * @return true if the call is established
841      */
842     public boolean isInCall() {
843         synchronized(mLockObj) {
844             return mInCall;
845         }
846     }
847
848     /**
849      * Checks if the call is muted.
850      *
851      * @return true if the call is muted
852      */
853     public boolean isMuted() {
854         synchronized(mLockObj) {
855             return mMute;
856         }
857     }
858
859     /**
860      * Checks if the call is on hold.
861      *
862      * @return true if the call is on hold
863      */
864     public boolean isOnHold() {
865         synchronized(mLockObj) {
866             return mHold;
867         }
868     }
869
870     /**
871      * Determines if the call is a multiparty call.
872      *
873      * @return {@code True} if the call is a multiparty call.
874      */
875     public boolean isMultiparty() {
876         synchronized(mLockObj) {
877             if (mSession == null) {
878                 return false;
879             }
880
881             return mSession.isMultiparty();
882         }
883     }
884
885     /**
886      * Where {@link #isMultiparty()} is {@code true}, determines if this {@link ImsCall} is the
887      * origin of the conference call (i.e. {@code #isConferenceHost()} is {@code true}), or if this
888      * {@link ImsCall} is a member of a conference hosted on another device.
889      *
890      * @return {@code true} if this call is the origin of the conference call it is a member of,
891      *      {@code false} otherwise.
892      */
893     public boolean isConferenceHost() {
894         synchronized(mLockObj) {
895             return isMultiparty() && mIsConferenceHost;
896         }
897     }
898
899     /**
900      * Marks whether an IMS call is merged. This should be set {@code true} when the call merges
901      * into a conference.
902      *
903      * @param isMerged Whether the call is merged.
904      */
905     public void setIsMerged(boolean isMerged) {
906         mIsMerged = isMerged;
907     }
908
909     /**
910      * @return {@code true} if the call recently merged into a conference call.
911      */
912     public boolean isMerged() {
913         return mIsMerged;
914     }
915
916     /**
917      * Sets the listener to listen to the IMS call events.
918      * The method calls {@link #setListener setListener(listener, false)}.
919      *
920      * @param listener to listen to the IMS call events of this object; null to remove listener
921      * @see #setListener(Listener, boolean)
922      */
923     public void setListener(ImsCall.Listener listener) {
924         setListener(listener, false);
925     }
926
927     /**
928      * Sets the listener to listen to the IMS call events.
929      * A {@link ImsCall} can only hold one listener at a time. Subsequent calls
930      * to this method override the previous listener.
931      *
932      * @param listener to listen to the IMS call events of this object; null to remove listener
933      * @param callbackImmediately set to true if the caller wants to be called
934      *        back immediately on the current state
935      */
936     public void setListener(ImsCall.Listener listener, boolean callbackImmediately) {
937         boolean inCall;
938         boolean onHold;
939         int state;
940         ImsReasonInfo lastReasonInfo;
941
942         synchronized(mLockObj) {
943             mListener = listener;
944
945             if ((listener == null) || !callbackImmediately) {
946                 return;
947             }
948
949             inCall = mInCall;
950             onHold = mHold;
951             state = getState();
952             lastReasonInfo = mLastReasonInfo;
953         }
954
955         try {
956             if (lastReasonInfo != null) {
957                 listener.onCallError(this, lastReasonInfo);
958             } else if (inCall) {
959                 if (onHold) {
960                     listener.onCallHeld(this);
961                 } else {
962                     listener.onCallStarted(this);
963                 }
964             } else {
965                 switch (state) {
966                     case ImsCallSession.State.ESTABLISHING:
967                         listener.onCallProgressing(this);
968                         break;
969                     case ImsCallSession.State.TERMINATED:
970                         listener.onCallTerminated(this, lastReasonInfo);
971                         break;
972                     default:
973                         // Ignore it. There is no action in the other state.
974                         break;
975                 }
976             }
977         } catch (Throwable t) {
978             loge("setListener() :: ", t);
979         }
980     }
981
982     /**
983      * Mutes or unmutes the mic for the active call.
984      *
985      * @param muted true if the call is muted, false otherwise
986      */
987     public void setMute(boolean muted) throws ImsException {
988         synchronized(mLockObj) {
989             if (mMute != muted) {
990                 logi("setMute :: turning mute " + (muted ? "on" : "off"));
991                 mMute = muted;
992
993                 try {
994                     mSession.setMute(muted);
995                 } catch (Throwable t) {
996                     loge("setMute :: ", t);
997                     throwImsException(t, 0);
998                 }
999             }
1000         }
1001     }
1002
1003      /**
1004       * Attaches an incoming call to this call object.
1005       *
1006       * @param session the session that receives the incoming call
1007       * @throws ImsException if the IMS service fails to attach this object to the session
1008       */
1009      public void attachSession(ImsCallSession session) throws ImsException {
1010          logi("attachSession :: session=" + session);
1011
1012          synchronized(mLockObj) {
1013              mSession = session;
1014
1015              try {
1016                  mSession.setListener(createCallSessionListener());
1017              } catch (Throwable t) {
1018                  loge("attachSession :: ", t);
1019                  throwImsException(t, 0);
1020              }
1021          }
1022      }
1023
1024     /**
1025      * Initiates an IMS call with the call profile which is provided
1026      * when creating a {@link ImsCall}.
1027      *
1028      * @param session the {@link ImsCallSession} for carrying out the call
1029      * @param callee callee information to initiate an IMS call
1030      * @throws ImsException if the IMS service fails to initiate the call
1031      */
1032     public void start(ImsCallSession session, String callee)
1033             throws ImsException {
1034         logi("start(1) :: session=" + session);
1035
1036         synchronized(mLockObj) {
1037             mSession = session;
1038
1039             try {
1040                 session.setListener(createCallSessionListener());
1041                 session.start(callee, mCallProfile);
1042             } catch (Throwable t) {
1043                 loge("start(1) :: ", t);
1044                 throw new ImsException("start(1)", t, 0);
1045             }
1046         }
1047     }
1048
1049     /**
1050      * Initiates an IMS conferenca call with the call profile which is provided
1051      * when creating a {@link ImsCall}.
1052      *
1053      * @param session the {@link ImsCallSession} for carrying out the call
1054      * @param participants participant list to initiate an IMS conference call
1055      * @throws ImsException if the IMS service fails to initiate the call
1056      */
1057     public void start(ImsCallSession session, String[] participants)
1058             throws ImsException {
1059         logi("start(n) :: session=" + session);
1060
1061         synchronized(mLockObj) {
1062             mSession = session;
1063
1064             try {
1065                 session.setListener(createCallSessionListener());
1066                 session.start(participants, mCallProfile);
1067             } catch (Throwable t) {
1068                 loge("start(n) :: ", t);
1069                 throw new ImsException("start(n)", t, 0);
1070             }
1071         }
1072     }
1073
1074     /**
1075      * Accepts a call.
1076      *
1077      * @see Listener#onCallStarted
1078      *
1079      * @param callType The call type the user agreed to for accepting the call.
1080      * @throws ImsException if the IMS service fails to accept the call
1081      */
1082     public void accept(int callType) throws ImsException {
1083         accept(callType, new ImsStreamMediaProfile());
1084     }
1085
1086     /**
1087      * Accepts a call.
1088      *
1089      * @param callType call type to be answered in {@link ImsCallProfile}
1090      * @param profile a media profile to be answered (audio/audio & video, direction, ...)
1091      * @see Listener#onCallStarted
1092      * @throws ImsException if the IMS service fails to accept the call
1093      */
1094     public void accept(int callType, ImsStreamMediaProfile profile) throws ImsException {
1095         logi("accept :: callType=" + callType + ", profile=" + profile);
1096
1097         synchronized(mLockObj) {
1098             if (mSession == null) {
1099                 throw new ImsException("No call to answer",
1100                         ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1101             }
1102
1103             try {
1104                 mSession.accept(callType, profile);
1105             } catch (Throwable t) {
1106                 loge("accept :: ", t);
1107                 throw new ImsException("accept()", t, 0);
1108             }
1109
1110             if (mInCall && (mProposedCallProfile != null)) {
1111                 if (DBG) {
1112                     logi("accept :: call profile will be updated");
1113                 }
1114
1115                 mCallProfile = mProposedCallProfile;
1116                 trackVideoStateHistory(mCallProfile);
1117                 mProposedCallProfile = null;
1118             }
1119
1120             // Other call update received
1121             if (mInCall && (mUpdateRequest == UPDATE_UNSPECIFIED)) {
1122                 mUpdateRequest = UPDATE_NONE;
1123             }
1124         }
1125     }
1126
1127     /**
1128      * Rejects a call.
1129      *
1130      * @param reason reason code to reject an incoming call
1131      * @see Listener#onCallStartFailed
1132      * @throws ImsException if the IMS service fails to reject the call
1133      */
1134     public void reject(int reason) throws ImsException {
1135         logi("reject :: reason=" + reason);
1136
1137         synchronized(mLockObj) {
1138             if (mSession != null) {
1139                 mSession.reject(reason);
1140             }
1141
1142             if (mInCall && (mProposedCallProfile != null)) {
1143                 if (DBG) {
1144                     logi("reject :: call profile is not updated; destroy it...");
1145                 }
1146
1147                 mProposedCallProfile = null;
1148             }
1149
1150             // Other call update received
1151             if (mInCall && (mUpdateRequest == UPDATE_UNSPECIFIED)) {
1152                 mUpdateRequest = UPDATE_NONE;
1153             }
1154         }
1155     }
1156
1157     public void terminate(int reason, int overrideReason) throws ImsException {
1158         logi("terminate :: reason=" + reason + " ; overrideReadon=" + overrideReason);
1159         mOverrideReason = overrideReason;
1160         terminate(reason);
1161     }
1162
1163     /**
1164      * Terminates an IMS call (e.g. user initiated).
1165      *
1166      * @param reason reason code to terminate a call
1167      * @throws ImsException if the IMS service fails to terminate the call
1168      */
1169     public void terminate(int reason) throws ImsException {
1170         logi("terminate :: reason=" + reason);
1171
1172         synchronized(mLockObj) {
1173             mHold = false;
1174             mInCall = false;
1175             mTerminationRequestPending = true;
1176
1177             if (mSession != null) {
1178                 // TODO: Fix the fact that user invoked call terminations during
1179                 // the process of establishing a conference call needs to be handled
1180                 // as a special case.
1181                 // Currently, any terminations (both invoked by the user or
1182                 // by the network results in a callSessionTerminated() callback
1183                 // from the network.  When establishing a conference call we bury
1184                 // these callbacks until we get closure on all participants of the
1185                 // conference. In some situations, we will throw away the callback
1186                 // (when the underlying session of the host of the new conference
1187                 // is terminated) or will will unbury it when the conference has been
1188                 // established, like when the peer of the new conference goes away
1189                 // after the conference has been created.  The UI relies on the callback
1190                 // to reflect the fact that the call is gone.
1191                 // So if a user decides to terminated a call while it is merging, it
1192                 // could take a long time to reflect in the UI due to the conference
1193                 // processing but we should probably cancel that and just terminate
1194                 // the call immediately and clean up.  This is not a huge issue right
1195                 // now because we have not seen instances where establishing a
1196                 // conference takes a long time (more than a second or two).
1197                 mSession.terminate(reason);
1198             }
1199         }
1200     }
1201
1202
1203     /**
1204      * Puts a call on hold. When succeeds, {@link Listener#onCallHeld} is called.
1205      *
1206      * @see Listener#onCallHeld, Listener#onCallHoldFailed
1207      * @throws ImsException if the IMS service fails to hold the call
1208      */
1209     public void hold() throws ImsException {
1210         logi("hold :: ");
1211
1212         if (isOnHold()) {
1213             if (DBG) {
1214                 logi("hold :: call is already on hold");
1215             }
1216             return;
1217         }
1218
1219         synchronized(mLockObj) {
1220             if (mUpdateRequest != UPDATE_NONE) {
1221                 loge("hold :: update is in progress; request=" +
1222                         updateRequestToString(mUpdateRequest));
1223                 throw new ImsException("Call update is in progress",
1224                         ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
1225             }
1226
1227             if (mSession == null) {
1228                 throw new ImsException("No call session",
1229                         ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1230             }
1231
1232             mSession.hold(createHoldMediaProfile());
1233             // FIXME: We should update the state on the callback because that is where
1234             // we can confirm that the hold request was successful or not.
1235             mHold = true;
1236             mUpdateRequest = UPDATE_HOLD;
1237         }
1238     }
1239
1240     /**
1241      * Continues a call that's on hold. When succeeds, {@link Listener#onCallResumed} is called.
1242      *
1243      * @see Listener#onCallResumed, Listener#onCallResumeFailed
1244      * @throws ImsException if the IMS service fails to resume the call
1245      */
1246     public void resume() throws ImsException {
1247         logi("resume :: ");
1248
1249         if (!isOnHold()) {
1250             if (DBG) {
1251                 logi("resume :: call is not being held");
1252             }
1253             return;
1254         }
1255
1256         synchronized(mLockObj) {
1257             if (mUpdateRequest != UPDATE_NONE) {
1258                 loge("resume :: update is in progress; request=" +
1259                         updateRequestToString(mUpdateRequest));
1260                 throw new ImsException("Call update is in progress",
1261                         ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
1262             }
1263
1264             if (mSession == null) {
1265                 loge("resume :: ");
1266                 throw new ImsException("No call session",
1267                         ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1268             }
1269
1270             // mHold is set to false in confirmation callback that the
1271             // ImsCall was resumed.
1272             mUpdateRequest = UPDATE_RESUME;
1273             mSession.resume(createResumeMediaProfile());
1274         }
1275     }
1276
1277     /**
1278      * Merges the active & hold call.
1279      *
1280      * @see Listener#onCallMerged, Listener#onCallMergeFailed
1281      * @throws ImsException if the IMS service fails to merge the call
1282      */
1283     private void merge() throws ImsException {
1284         logi("merge :: ");
1285
1286         synchronized(mLockObj) {
1287             if (mUpdateRequest != UPDATE_NONE) {
1288                 loge("merge :: update is in progress; request=" +
1289                         updateRequestToString(mUpdateRequest));
1290                 throw new ImsException("Call update is in progress",
1291                         ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
1292             }
1293
1294             if (mSession == null) {
1295                 loge("merge :: no call session");
1296                 throw new ImsException("No call session",
1297                         ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1298             }
1299
1300             // if skipHoldBeforeMerge = true, IMS service implementation will
1301             // merge without explicitly holding the call.
1302             if (mHold || (mContext.getResources().getBoolean(
1303                     com.android.internal.R.bool.skipHoldBeforeMerge))) {
1304
1305                 if (mMergePeer != null && !mMergePeer.isMultiparty() && !isMultiparty()) {
1306                     // We only set UPDATE_MERGE when we are adding the first
1307                     // calls to the Conference.  If there is already a conference
1308                     // no special handling is needed. The existing conference
1309                     // session will just go active and any other sessions will be terminated
1310                     // if needed.  There will be no merge failed callback.
1311                     // Mark both the host and peer UPDATE_MERGE to ensure both are aware that a
1312                     // merge is pending.
1313                     mUpdateRequest = UPDATE_MERGE;
1314                     mMergePeer.mUpdateRequest = UPDATE_MERGE;
1315                 }
1316
1317                 mSession.merge();
1318             } else {
1319                 // This code basically says, we need to explicitly hold before requesting a merge
1320                 // when we get the callback that the hold was successful (or failed), we should
1321                 // automatically request a merge.
1322                 mSession.hold(createHoldMediaProfile());
1323                 mHold = true;
1324                 mUpdateRequest = UPDATE_HOLD_MERGE;
1325             }
1326         }
1327     }
1328
1329     /**
1330      * Merges the active & hold call.
1331      *
1332      * @param bgCall the background (holding) call
1333      * @see Listener#onCallMerged, Listener#onCallMergeFailed
1334      * @throws ImsException if the IMS service fails to merge the call
1335      */
1336     public void merge(ImsCall bgCall) throws ImsException {
1337         logi("merge(1) :: bgImsCall=" + bgCall);
1338
1339         if (bgCall == null) {
1340             throw new ImsException("No background call",
1341                     ImsReasonInfo.CODE_LOCAL_ILLEGAL_ARGUMENT);
1342         }
1343
1344         synchronized(mLockObj) {
1345             // Mark both sessions as pending merge.
1346             this.setCallSessionMergePending(true);
1347             bgCall.setCallSessionMergePending(true);
1348
1349             if ((!isMultiparty() && !bgCall.isMultiparty()) || isMultiparty()) {
1350                 // If neither call is multiparty, the current call is the merge host and the bg call
1351                 // is the merge peer (ie we're starting a new conference).
1352                 // OR
1353                 // If this call is multiparty, it is the merge host and the other call is the merge
1354                 // peer.
1355                 setMergePeer(bgCall);
1356             } else {
1357                 // If the bg call is multiparty, it is the merge host.
1358                 setMergeHost(bgCall);
1359             }
1360         }
1361
1362         if (isMultiparty()) {
1363             mMergeRequestedByConference = true;
1364         } else {
1365             logi("merge : mMergeRequestedByConference not set");
1366         }
1367         merge();
1368     }
1369
1370     /**
1371      * Updates the current call's properties (ex. call mode change: video upgrade / downgrade).
1372      */
1373     public void update(int callType, ImsStreamMediaProfile mediaProfile) throws ImsException {
1374         logi("update :: callType=" + callType + ", mediaProfile=" + mediaProfile);
1375
1376         if (isOnHold()) {
1377             if (DBG) {
1378                 logi("update :: call is on hold");
1379             }
1380             throw new ImsException("Not in a call to update call",
1381                     ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
1382         }
1383
1384         synchronized(mLockObj) {
1385             if (mUpdateRequest != UPDATE_NONE) {
1386                 if (DBG) {
1387                     logi("update :: update is in progress; request=" +
1388                             updateRequestToString(mUpdateRequest));
1389                 }
1390                 throw new ImsException("Call update is in progress",
1391                         ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
1392             }
1393
1394             if (mSession == null) {
1395                 loge("update :: ");
1396                 throw new ImsException("No call session",
1397                         ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1398             }
1399
1400             mSession.update(callType, mediaProfile);
1401             mUpdateRequest = UPDATE_UNSPECIFIED;
1402         }
1403     }
1404
1405     /**
1406      * Extends this call (1-to-1 call) to the conference call
1407      * inviting the specified participants to.
1408      *
1409      */
1410     public void extendToConference(String[] participants) throws ImsException {
1411         logi("extendToConference ::");
1412
1413         if (isOnHold()) {
1414             if (DBG) {
1415                 logi("extendToConference :: call is on hold");
1416             }
1417             throw new ImsException("Not in a call to extend a call to conference",
1418                     ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
1419         }
1420
1421         synchronized(mLockObj) {
1422             if (mUpdateRequest != UPDATE_NONE) {
1423                 if (CONF_DBG) {
1424                     logi("extendToConference :: update is in progress; request=" +
1425                             updateRequestToString(mUpdateRequest));
1426                 }
1427                 throw new ImsException("Call update is in progress",
1428                         ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
1429             }
1430
1431             if (mSession == null) {
1432                 loge("extendToConference :: ");
1433                 throw new ImsException("No call session",
1434                         ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1435             }
1436
1437             mSession.extendToConference(participants);
1438             mUpdateRequest = UPDATE_EXTEND_TO_CONFERENCE;
1439         }
1440     }
1441
1442     /**
1443      * Requests the conference server to invite an additional participants to the conference.
1444      *
1445      */
1446     public void inviteParticipants(String[] participants) throws ImsException {
1447         logi("inviteParticipants ::");
1448
1449         synchronized(mLockObj) {
1450             if (mSession == null) {
1451                 loge("inviteParticipants :: ");
1452                 throw new ImsException("No call session",
1453                         ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1454             }
1455
1456             mSession.inviteParticipants(participants);
1457         }
1458     }
1459
1460     /**
1461      * Requests the conference server to remove the specified participants from the conference.
1462      *
1463      */
1464     public void removeParticipants(String[] participants) throws ImsException {
1465         logi("removeParticipants :: session=" + mSession);
1466         synchronized(mLockObj) {
1467             if (mSession == null) {
1468                 loge("removeParticipants :: ");
1469                 throw new ImsException("No call session",
1470                         ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1471             }
1472
1473             mSession.removeParticipants(participants);
1474
1475         }
1476     }
1477
1478     /**
1479      * Sends a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2833</a>,
1480      * event 0 ~ 9 maps to decimal value 0 ~ 9, '*' to 10, '#' to 11, event 'A' ~ 'D' to 12 ~ 15,
1481      * and event flash to 16. Currently, event flash is not supported.
1482      *
1483      * @param c that represents the DTMF to send. '0' ~ '9', 'A' ~ 'D', '*', '#' are valid inputs.
1484      * @param result the result message to send when done.
1485      */
1486     public void sendDtmf(char c, Message result) {
1487         logi("sendDtmf :: code=" + c);
1488
1489         synchronized(mLockObj) {
1490             if (mSession != null) {
1491                 mSession.sendDtmf(c, result);
1492             }
1493         }
1494     }
1495
1496     /**
1497      * Start a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2833</a>,
1498      * event 0 ~ 9 maps to decimal value 0 ~ 9, '*' to 10, '#' to 11, event 'A' ~ 'D' to 12 ~ 15,
1499      * and event flash to 16. Currently, event flash is not supported.
1500      *
1501      * @param c that represents the DTMF to send. '0' ~ '9', 'A' ~ 'D', '*', '#' are valid inputs.
1502      */
1503     public void startDtmf(char c) {
1504         logi("startDtmf :: code=" + c);
1505
1506         synchronized(mLockObj) {
1507             if (mSession != null) {
1508                 mSession.startDtmf(c);
1509             }
1510         }
1511     }
1512
1513     /**
1514      * Stop a DTMF code.
1515      */
1516     public void stopDtmf() {
1517         logi("stopDtmf :: ");
1518
1519         synchronized(mLockObj) {
1520             if (mSession != null) {
1521                 mSession.stopDtmf();
1522             }
1523         }
1524     }
1525
1526     /**
1527      * Sends an USSD message.
1528      *
1529      * @param ussdMessage USSD message to send
1530      */
1531     public void sendUssd(String ussdMessage) throws ImsException {
1532         logi("sendUssd :: ussdMessage=" + ussdMessage);
1533
1534         synchronized(mLockObj) {
1535             if (mSession == null) {
1536                 loge("sendUssd :: ");
1537                 throw new ImsException("No call session",
1538                         ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1539             }
1540
1541             mSession.sendUssd(ussdMessage);
1542         }
1543     }
1544
1545     private void clear(ImsReasonInfo lastReasonInfo) {
1546         mInCall = false;
1547         mHold = false;
1548         mUpdateRequest = UPDATE_NONE;
1549         mLastReasonInfo = lastReasonInfo;
1550     }
1551
1552     /**
1553      * Creates an IMS call session listener.
1554      */
1555     private ImsCallSession.Listener createCallSessionListener() {
1556         mImsCallSessionListenerProxy = new ImsCallSessionListenerProxy();
1557         return mImsCallSessionListenerProxy;
1558     }
1559
1560     /**
1561      * @return the current ImsCallSessionListenerProxy.  NOTE: ONLY FOR USE WITH TESTING.
1562      */
1563     @VisibleForTesting
1564     public ImsCallSessionListenerProxy getImsCallSessionListenerProxy() {
1565         return mImsCallSessionListenerProxy;
1566     }
1567
1568     private ImsCall createNewCall(ImsCallSession session, ImsCallProfile profile) {
1569         ImsCall call = new ImsCall(mContext, profile);
1570
1571         try {
1572             call.attachSession(session);
1573         } catch (ImsException e) {
1574             if (call != null) {
1575                 call.close();
1576                 call = null;
1577             }
1578         }
1579
1580         // Do additional operations...
1581
1582         return call;
1583     }
1584
1585     private ImsStreamMediaProfile createHoldMediaProfile() {
1586         ImsStreamMediaProfile mediaProfile = new ImsStreamMediaProfile();
1587
1588         if (mCallProfile == null) {
1589             return mediaProfile;
1590         }
1591
1592         mediaProfile.mAudioQuality = mCallProfile.mMediaProfile.mAudioQuality;
1593         mediaProfile.mVideoQuality = mCallProfile.mMediaProfile.mVideoQuality;
1594         mediaProfile.mAudioDirection = ImsStreamMediaProfile.DIRECTION_SEND;
1595
1596         if (mediaProfile.mVideoQuality != ImsStreamMediaProfile.VIDEO_QUALITY_NONE) {
1597             mediaProfile.mVideoDirection = ImsStreamMediaProfile.DIRECTION_SEND;
1598         }
1599
1600         return mediaProfile;
1601     }
1602
1603     private ImsStreamMediaProfile createResumeMediaProfile() {
1604         ImsStreamMediaProfile mediaProfile = new ImsStreamMediaProfile();
1605
1606         if (mCallProfile == null) {
1607             return mediaProfile;
1608         }
1609
1610         mediaProfile.mAudioQuality = mCallProfile.mMediaProfile.mAudioQuality;
1611         mediaProfile.mVideoQuality = mCallProfile.mMediaProfile.mVideoQuality;
1612         mediaProfile.mAudioDirection = ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE;
1613
1614         if (mediaProfile.mVideoQuality != ImsStreamMediaProfile.VIDEO_QUALITY_NONE) {
1615             mediaProfile.mVideoDirection = ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE;
1616         }
1617
1618         return mediaProfile;
1619     }
1620
1621     private void enforceConversationMode() {
1622         if (mInCall) {
1623             mHold = false;
1624             mUpdateRequest = UPDATE_NONE;
1625         }
1626     }
1627
1628     private void mergeInternal() {
1629         if (CONF_DBG) {
1630             logi("mergeInternal :: ");
1631         }
1632
1633         mSession.merge();
1634         mUpdateRequest = UPDATE_MERGE;
1635     }
1636
1637     private void notifyConferenceSessionTerminated(ImsReasonInfo reasonInfo) {
1638         ImsCall.Listener listener = mListener;
1639         clear(reasonInfo);
1640
1641         if (listener != null) {
1642             try {
1643                 listener.onCallTerminated(this, reasonInfo);
1644             } catch (Throwable t) {
1645                 loge("notifyConferenceSessionTerminated :: ", t);
1646             }
1647         }
1648     }
1649
1650     private void notifyConferenceStateUpdated(ImsConferenceState state) {
1651         if (state == null || state.mParticipants == null) {
1652             return;
1653         }
1654
1655         Set<Entry<String, Bundle>> participants = state.mParticipants.entrySet();
1656
1657         if (participants == null) {
1658             return;
1659         }
1660
1661         Iterator<Entry<String, Bundle>> iterator = participants.iterator();
1662         mConferenceParticipants = new ArrayList<>(participants.size());
1663         while (iterator.hasNext()) {
1664             Entry<String, Bundle> entry = iterator.next();
1665
1666             String key = entry.getKey();
1667             Bundle confInfo = entry.getValue();
1668             String status = confInfo.getString(ImsConferenceState.STATUS);
1669             String user = confInfo.getString(ImsConferenceState.USER);
1670             String displayName = confInfo.getString(ImsConferenceState.DISPLAY_TEXT);
1671             String endpoint = confInfo.getString(ImsConferenceState.ENDPOINT);
1672
1673             if (CONF_DBG) {
1674                 logi("notifyConferenceStateUpdated :: key=" + key +
1675                         ", status=" + status +
1676                         ", user=" + user +
1677                         ", displayName= " + displayName +
1678                         ", endpoint=" + endpoint);
1679             }
1680
1681             Uri handle = Uri.parse(user);
1682             if (endpoint == null) {
1683                 endpoint = "";
1684             }
1685             Uri endpointUri = Uri.parse(endpoint);
1686             int connectionState = ImsConferenceState.getConnectionStateForStatus(status);
1687
1688             if (connectionState != Connection.STATE_DISCONNECTED) {
1689                 ConferenceParticipant conferenceParticipant = new ConferenceParticipant(handle,
1690                         displayName, endpointUri, connectionState);
1691                 mConferenceParticipants.add(conferenceParticipant);
1692             }
1693         }
1694
1695         if (mConferenceParticipants != null && mListener != null) {
1696             try {
1697                 mListener.onConferenceParticipantsStateChanged(this, mConferenceParticipants);
1698             } catch (Throwable t) {
1699                 loge("notifyConferenceStateUpdated :: ", t);
1700             }
1701         }
1702     }
1703
1704     /**
1705      * Perform all cleanup and notification around the termination of a session.
1706      * Note that there are 2 distinct modes of operation.  The first is when
1707      * we receive a session termination on the primary session when we are
1708      * in the processing of merging.  The second is when we are not merging anything
1709      * and the call is terminated.
1710      *
1711      * @param reasonInfo The reason for the session termination
1712      */
1713     private void processCallTerminated(ImsReasonInfo reasonInfo) {
1714         logi("processCallTerminated :: reason=" + reasonInfo + " userInitiated = " +
1715                 mTerminationRequestPending);
1716
1717         ImsCall.Listener listener = null;
1718         synchronized(ImsCall.this) {
1719             // If we are in the midst of establishing a conference, we will bury the termination
1720             // until the merge has completed.  If necessary we can surface the termination at
1721             // this point.
1722             // We will also NOT bury the termination if a termination was initiated locally.
1723             if (isCallSessionMergePending() && !mTerminationRequestPending) {
1724                 // Since we are in the process of a merge, this trigger means something
1725                 // else because it is probably due to the merge happening vs. the
1726                 // session is really terminated. Let's flag this and revisit if
1727                 // the merge() ends up failing because we will need to take action on the
1728                 // mSession in that case since the termination was not due to the merge
1729                 // succeeding.
1730                 if (CONF_DBG) {
1731                     logi("processCallTerminated :: burying termination during ongoing merge.");
1732                 }
1733                 mSessionEndDuringMerge = true;
1734                 mSessionEndDuringMergeReasonInfo = reasonInfo;
1735                 return;
1736             }
1737
1738             // If we are terminating the conference call, notify using conference listeners.
1739             if (isMultiparty()) {
1740                 notifyConferenceSessionTerminated(reasonInfo);
1741                 return;
1742             } else {
1743                 listener = mListener;
1744                 clear(reasonInfo);
1745             }
1746         }
1747
1748         if (listener != null) {
1749             try {
1750                 listener.onCallTerminated(ImsCall.this, reasonInfo);
1751             } catch (Throwable t) {
1752                 loge("processCallTerminated :: ", t);
1753             }
1754         }
1755     }
1756
1757     /**
1758      * This function determines if the ImsCallSession is our actual ImsCallSession or if is
1759      * the transient session used in the process of creating a conference. This function should only
1760      * be called within  callbacks that are not directly related to conference merging but might
1761      * potentially still be called on the transient ImsCallSession sent to us from
1762      * callSessionMergeStarted() when we don't really care. In those situations, we probably don't
1763      * want to take any action so we need to know that we can return early.
1764      *
1765      * @param session - The {@link ImsCallSession} that the function needs to analyze
1766      * @return true if this is the transient {@link ImsCallSession}, false otherwise.
1767      */
1768     private boolean isTransientConferenceSession(ImsCallSession session) {
1769         if (session != null && session != mSession && session == mTransientConferenceSession) {
1770             return true;
1771         }
1772         return false;
1773     }
1774
1775     private void setTransientSessionAsPrimary(ImsCallSession transientSession) {
1776         synchronized (ImsCall.this) {
1777             mSession.setListener(null);
1778             mSession = transientSession;
1779             mSession.setListener(createCallSessionListener());
1780         }
1781     }
1782
1783     private void markCallAsMerged(boolean playDisconnectTone) {
1784         if (!isSessionAlive(mSession)) {
1785             // If the peer is dead, let's not play a disconnect sound for it when we
1786             // unbury the termination callback.
1787             logi("markCallAsMerged");
1788             setIsMerged(playDisconnectTone);
1789             mSessionEndDuringMerge = true;
1790             String reasonInfo;
1791             if (playDisconnectTone) {
1792                 reasonInfo = "Call ended by network";
1793             } else {
1794                 reasonInfo = "Call ended during conference merge process.";
1795             }
1796             mSessionEndDuringMergeReasonInfo = new ImsReasonInfo(
1797                     ImsReasonInfo.CODE_UNSPECIFIED, 0, reasonInfo);
1798         }
1799     }
1800
1801     /**
1802      * Checks if the merge was requested by foreground conference call
1803      *
1804      * @return true if the merge was requested by foreground conference call
1805      */
1806     public boolean isMergeRequestedByConf() {
1807         synchronized(mLockObj) {
1808             return mMergeRequestedByConference;
1809         }
1810     }
1811
1812     /**
1813      * Resets the flag which indicates merge request was sent by
1814      * foreground conference call
1815      */
1816     public void resetIsMergeRequestedByConf(boolean value) {
1817         synchronized(mLockObj) {
1818             mMergeRequestedByConference = value;
1819         }
1820     }
1821
1822     /**
1823      * Returns current ImsCallSession
1824      *
1825      * @return current session
1826      */
1827     public ImsCallSession getSession() {
1828         synchronized(mLockObj) {
1829             return mSession;
1830         }
1831     }
1832
1833     /**
1834      * We have detected that a initial conference call has been fully configured. The internal
1835      * state of both {@code ImsCall} objects need to be cleaned up to reflect the new state.
1836      * This function should only be called in the context of the merge host to simplify logic
1837      *
1838      */
1839     private void processMergeComplete() {
1840         logi("processMergeComplete :: ");
1841
1842         // The logic simplifies if we can assume that this function is only called on
1843         // the merge host.
1844         if (!isMergeHost()) {
1845             loge("processMergeComplete :: We are not the merge host!");
1846             return;
1847         }
1848
1849         ImsCall.Listener listener;
1850         boolean swapRequired = false;
1851
1852         ImsCall finalHostCall;
1853         ImsCall finalPeerCall;
1854
1855         synchronized(ImsCall.this) {
1856             if (isMultiparty()) {
1857                 setIsMerged(false);
1858                 // if case handles Case 4 explained in callSessionMergeComplete
1859                 // otherwise it is case 5
1860                 if (!mMergeRequestedByConference) {
1861                     // single call in fg, conference call in bg.
1862                     // Finally conf call becomes active after conference
1863                     this.mHold = false;
1864                     swapRequired = true;
1865                 }
1866                 mMergePeer.markCallAsMerged(false);
1867                 finalHostCall = this;
1868                 finalPeerCall = mMergePeer;
1869             } else {
1870                 // If we are here, we are not trying to merge a new call into an existing
1871                 // conference.  That means that there is a transient session on the merge
1872                 // host that represents the future conference once all the parties
1873                 // have been added to it.  So make sure that it exists or else something
1874                 // very wrong is going on.
1875                 if (mTransientConferenceSession == null) {
1876                     loge("processMergeComplete :: No transient session!");
1877                     return;
1878                 }
1879                 if (mMergePeer == null) {
1880                     loge("processMergeComplete :: No merge peer!");
1881                     return;
1882                 }
1883
1884                 // Since we are the host, we have the transient session attached to us. Let's detach
1885                 // it and figure out where we need to set it for the final conference configuration.
1886                 ImsCallSession transientConferenceSession = mTransientConferenceSession;
1887                 mTransientConferenceSession = null;
1888
1889                 // Clear the listener for this transient session, we'll create a new listener
1890                 // when it is attached to the final ImsCall that it should live on.
1891                 transientConferenceSession.setListener(null);
1892
1893                 // Determine which call the transient session should be moved to.  If the current
1894                 // call session is still alive and the merge peer's session is not, we have a
1895                 // situation where the current call failed to merge into the conference but the
1896                 // merge peer did merge in to the conference.  In this type of scenario the current
1897                 // call will continue as a single party call, yet the background call will become
1898                 // the conference.
1899
1900                 // handles Case 3 explained in callSessionMergeComplete
1901                 if (isSessionAlive(mSession) && !isSessionAlive(mMergePeer.getCallSession())) {
1902                     // I'm the host but we are moving the transient session to the peer since its
1903                     // session was disconnected and my session is still alive.  This signifies that
1904                     // their session was properly added to the conference but mine was not because
1905                     // it is probably in the held state as opposed to part of the final conference.
1906                     // In this case, we need to set isMerged to false on both calls so the
1907                     // disconnect sound is called when either call disconnects.
1908                     // Note that this case is only valid if this is an initial conference being
1909                     // brought up.
1910                     mMergePeer.mHold = false;
1911                     this.mHold = true;
1912                     if (mConferenceParticipants != null && !mConferenceParticipants.isEmpty()) {
1913                         mMergePeer.mConferenceParticipants = mConferenceParticipants;
1914                     }
1915                     // At this point both host & peer will have participant information.
1916                     // Peer will transition to host & the participant information
1917                     // from that will be used
1918                     // HostCall that failed to merge will remain as a single call with
1919                     // mConferenceParticipants, which should not be used.
1920                     // Expectation is that if this call becomes part of a conference call in future,
1921                     // mConferenceParticipants will be overriten with new CEP that is received.
1922                     finalHostCall = mMergePeer;
1923                     finalPeerCall = this;
1924                     swapRequired = true;
1925                     setIsMerged(false);
1926                     mMergePeer.setIsMerged(false);
1927                     if (CONF_DBG) {
1928                         logi("processMergeComplete :: transient will transfer to merge peer");
1929                     }
1930                 } else if (!isSessionAlive(mSession) &&
1931                                 isSessionAlive(mMergePeer.getCallSession())) {
1932                     // Handles case 2 explained in callSessionMergeComplete
1933                     // The transient session stays with us and the disconnect sound should be played
1934                     // when the merge peer eventually disconnects since it was not actually added to
1935                     // the conference and is probably sitting in the held state.
1936                     finalHostCall = this;
1937                     finalPeerCall = mMergePeer;
1938                     swapRequired = false;
1939                     setIsMerged(false);
1940                     mMergePeer.setIsMerged(false); // Play the disconnect sound
1941                     if (CONF_DBG) {
1942                         logi("processMergeComplete :: transient will stay with the merge host");
1943                     }
1944                 } else {
1945                     // Handles case 1 explained in callSessionMergeComplete
1946                     // The transient session stays with us and the disconnect sound should not be
1947                     // played when we ripple up the disconnect for the merge peer because it was
1948                     // only disconnected to be added to the conference.
1949                     finalHostCall = this;
1950                     finalPeerCall = mMergePeer;
1951                     mMergePeer.markCallAsMerged(false);
1952                     swapRequired = false;
1953                     setIsMerged(false);
1954                     mMergePeer.setIsMerged(true);
1955                     if (CONF_DBG) {
1956                         logi("processMergeComplete :: transient will stay with us (I'm the host).");
1957                     }
1958                 }
1959
1960                 if (CONF_DBG) {
1961                     logi("processMergeComplete :: call=" + finalHostCall + " is the final host");
1962                 }
1963
1964                 // Add the transient session to the ImsCall that ended up being the host for the
1965                 // conference.
1966                 finalHostCall.setTransientSessionAsPrimary(transientConferenceSession);
1967             }
1968
1969             listener = finalHostCall.mListener;
1970
1971             updateCallProfile(finalPeerCall);
1972             updateCallProfile(finalHostCall);
1973
1974             // Clear all the merge related flags.
1975             clearMergeInfo();
1976
1977             // For the final peer...let's bubble up any possible disconnects that we had
1978             // during the merge process
1979             finalPeerCall.notifySessionTerminatedDuringMerge();
1980             // For the final host, let's just bury the disconnects that we my have received
1981             // during the merge process since we are now the host of the conference call.
1982             finalHostCall.clearSessionTerminationFlags();
1983
1984             // Keep track of the fact that merge host is the origin of a conference call in
1985             // progress.  This is important so that we can later determine if a multiparty ImsCall
1986             // is multiparty because it was the origin of a conference call, or because it is a
1987             // member of a conference on another device.
1988             finalHostCall.mIsConferenceHost = true;
1989         }
1990         if (listener != null) {
1991             try {
1992                 // finalPeerCall will have the participant that was not merged and
1993                 // it will be held state
1994                 // if peer was merged successfully, finalPeerCall will be null
1995                 listener.onCallMerged(finalHostCall, finalPeerCall, swapRequired);
1996             } catch (Throwable t) {
1997                 loge("processMergeComplete :: ", t);
1998             }
1999             if (mConferenceParticipants != null && !mConferenceParticipants.isEmpty()) {
2000                 try {
2001                     listener.onConferenceParticipantsStateChanged(finalHostCall,
2002                             mConferenceParticipants);
2003                 } catch (Throwable t) {
2004                     loge("processMergeComplete :: ", t);
2005                 }
2006             }
2007         }
2008         return;
2009     }
2010
2011     private static void updateCallProfile(ImsCall call) {
2012         if (call != null) {
2013             call.updateCallProfile();
2014         }
2015     }
2016
2017     private void updateCallProfile() {
2018         synchronized (mLockObj) {
2019             if (mSession != null) {
2020                 setCallProfile(mSession.getCallProfile());
2021             }
2022         }
2023     }
2024
2025     /**
2026      * Handles the case where the session has ended during a merge by reporting the termination
2027      * reason to listeners.
2028      */
2029     private void notifySessionTerminatedDuringMerge() {
2030         ImsCall.Listener listener;
2031         boolean notifyFailure = false;
2032         ImsReasonInfo notifyFailureReasonInfo = null;
2033
2034         synchronized(ImsCall.this) {
2035             listener = mListener;
2036             if (mSessionEndDuringMerge) {
2037                 // Set some local variables that will send out a notification about a
2038                 // previously buried termination callback for our primary session now that
2039                 // we know that this is not due to the conference call merging successfully.
2040                 if (CONF_DBG) {
2041                     logi("notifySessionTerminatedDuringMerge ::reporting terminate during merge");
2042                 }
2043                 notifyFailure = true;
2044                 notifyFailureReasonInfo = mSessionEndDuringMergeReasonInfo;
2045             }
2046             clearSessionTerminationFlags();
2047         }
2048
2049         if (listener != null && notifyFailure) {
2050             try {
2051                 processCallTerminated(notifyFailureReasonInfo);
2052             } catch (Throwable t) {
2053                 loge("notifySessionTerminatedDuringMerge :: ", t);
2054             }
2055         }
2056     }
2057
2058     private void clearSessionTerminationFlags() {
2059         mSessionEndDuringMerge = false;
2060         mSessionEndDuringMergeReasonInfo = null;
2061     }
2062
2063    /**
2064      * We received a callback from ImsCallSession that a merge failed. Clean up all
2065      * internal state to represent this state change.  The calling function is a callback
2066      * and should have been called on the session that was in the foreground
2067      * when merge() was originally called.  It is assumed that this function will be called
2068      * on the merge host.
2069      *
2070      * @param reasonInfo The {@link ImsReasonInfo} why the merge failed.
2071      */
2072     private void processMergeFailed(ImsReasonInfo reasonInfo) {
2073         logi("processMergeFailed :: reason=" + reasonInfo);
2074
2075         ImsCall.Listener listener;
2076         synchronized(ImsCall.this) {
2077             // The logic simplifies if we can assume that this function is only called on
2078             // the merge host.
2079             if (!isMergeHost()) {
2080                 loge("processMergeFailed :: We are not the merge host!");
2081                 return;
2082             }
2083
2084             // Try to clean up the transient session if it exists.
2085             if (mTransientConferenceSession != null) {
2086                 mTransientConferenceSession.setListener(null);
2087                 mTransientConferenceSession = null;
2088             }
2089
2090             listener = mListener;
2091
2092             // Ensure the calls being conferenced into the conference has isMerged = false.
2093             // Ensure any terminations are surfaced from this session.
2094             markCallAsMerged(true);
2095             setCallSessionMergePending(false);
2096             notifySessionTerminatedDuringMerge();
2097
2098             // Perform the same cleanup on the merge peer if it exists.
2099             if (mMergePeer != null) {
2100                 mMergePeer.markCallAsMerged(true);
2101                 mMergePeer.setCallSessionMergePending(false);
2102                 mMergePeer.notifySessionTerminatedDuringMerge();
2103             } else {
2104                 loge("processMergeFailed :: No merge peer!");
2105             }
2106
2107             // Clear all the various flags around coordinating this merge.
2108             clearMergeInfo();
2109         }
2110         if (listener != null) {
2111             try {
2112                 listener.onCallMergeFailed(ImsCall.this, reasonInfo);
2113             } catch (Throwable t) {
2114                 loge("processMergeFailed :: ", t);
2115             }
2116         }
2117
2118         return;
2119     }
2120
2121     @VisibleForTesting
2122     public class ImsCallSessionListenerProxy extends ImsCallSession.Listener {
2123         @Override
2124         public void callSessionProgressing(ImsCallSession session, ImsStreamMediaProfile profile) {
2125             logi("callSessionProgressing :: session=" + session + " profile=" + profile);
2126
2127             if (isTransientConferenceSession(session)) {
2128                 // If it is a transient (conference) session, there is no action for this signal.
2129                 logi("callSessionProgressing :: not supported for transient conference session=" +
2130                         session);
2131                 return;
2132             }
2133
2134             ImsCall.Listener listener;
2135
2136             synchronized(ImsCall.this) {
2137                 listener = mListener;
2138                 mCallProfile.mMediaProfile.copyFrom(profile);
2139             }
2140
2141             if (listener != null) {
2142                 try {
2143                     listener.onCallProgressing(ImsCall.this);
2144                 } catch (Throwable t) {
2145                     loge("callSessionProgressing :: ", t);
2146                 }
2147             }
2148         }
2149
2150         @Override
2151         public void callSessionStarted(ImsCallSession session, ImsCallProfile profile) {
2152             logi("callSessionStarted :: session=" + session + " profile=" + profile);
2153
2154             if (!isTransientConferenceSession(session)) {
2155                 // In the case that we are in the middle of a merge (either host or peer), we have
2156                 // closure as far as this call's primary session is concerned.  If we are not
2157                 // merging...its a NOOP.
2158                 setCallSessionMergePending(false);
2159             } else {
2160                 logi("callSessionStarted :: on transient session=" + session);
2161                 return;
2162             }
2163
2164             if (isTransientConferenceSession(session)) {
2165                 // No further processing is needed if this is the transient session.
2166                 return;
2167             }
2168
2169             ImsCall.Listener listener;
2170
2171             synchronized(ImsCall.this) {
2172                 listener = mListener;
2173                 setCallProfile(profile);
2174             }
2175
2176             if (listener != null) {
2177                 try {
2178                     listener.onCallStarted(ImsCall.this);
2179                 } catch (Throwable t) {
2180                     loge("callSessionStarted :: ", t);
2181                 }
2182             }
2183         }
2184
2185         @Override
2186         public void callSessionStartFailed(ImsCallSession session, ImsReasonInfo reasonInfo) {
2187             loge("callSessionStartFailed :: session=" + session + " reasonInfo=" + reasonInfo);
2188
2189             if (isTransientConferenceSession(session)) {
2190                 // We should not get this callback for a transient session.
2191                 logi("callSessionStartFailed :: not supported for transient conference session=" +
2192                         session);
2193                 return;
2194             }
2195
2196             ImsCall.Listener listener;
2197
2198             synchronized(ImsCall.this) {
2199                 listener = mListener;
2200                 mLastReasonInfo = reasonInfo;
2201             }
2202
2203             if (listener != null) {
2204                 try {
2205                     listener.onCallStartFailed(ImsCall.this, reasonInfo);
2206                 } catch (Throwable t) {
2207                     loge("callSessionStarted :: ", t);
2208                 }
2209             }
2210         }
2211
2212         @Override
2213         public void callSessionTerminated(ImsCallSession session, ImsReasonInfo reasonInfo) {
2214             logi("callSessionTerminated :: session=" + session + " reasonInfo=" + reasonInfo);
2215
2216             if (isTransientConferenceSession(session)) {
2217                 logi("callSessionTerminated :: on transient session=" + session);
2218                 // This is bad, it should be treated much a callSessionMergeFailed since the
2219                 // transient session only exists when in the process of a merge and the
2220                 // termination of this session is effectively the end of the merge.
2221                 processMergeFailed(reasonInfo);
2222                 return;
2223             }
2224
2225             if (mOverrideReason != ImsReasonInfo.CODE_UNSPECIFIED) {
2226                 logi("callSessionTerminated :: overrideReasonInfo=" + mOverrideReason);
2227                 reasonInfo = new ImsReasonInfo(mOverrideReason, reasonInfo.getExtraCode(),
2228                         reasonInfo.getExtraMessage());
2229             }
2230
2231             // Process the termination first.  If we are in the midst of establishing a conference
2232             // call, we may bury this callback until we are done.  If there so no conference
2233             // call, the code after this function will be a NOOP.
2234             processCallTerminated(reasonInfo);
2235
2236             // If session has terminated, it is no longer pending merge.
2237             setCallSessionMergePending(false);
2238
2239         }
2240
2241         @Override
2242         public void callSessionHeld(ImsCallSession session, ImsCallProfile profile) {
2243             logi("callSessionHeld :: session=" + session + "profile=" + profile);
2244             ImsCall.Listener listener;
2245
2246             synchronized(ImsCall.this) {
2247                 // If the session was held, it is no longer pending a merge -- this means it could
2248                 // not be merged into the conference and was held instead.
2249                 setCallSessionMergePending(false);
2250
2251                 setCallProfile(profile);
2252
2253                 if (mUpdateRequest == UPDATE_HOLD_MERGE) {
2254                     // This hold request was made to set the stage for a merge.
2255                     mergeInternal();
2256                     return;
2257                 }
2258
2259                 mUpdateRequest = UPDATE_NONE;
2260                 listener = mListener;
2261             }
2262
2263             if (listener != null) {
2264                 try {
2265                     listener.onCallHeld(ImsCall.this);
2266                 } catch (Throwable t) {
2267                     loge("callSessionHeld :: ", t);
2268                 }
2269             }
2270         }
2271
2272         @Override
2273         public void callSessionHoldFailed(ImsCallSession session, ImsReasonInfo reasonInfo) {
2274             loge("callSessionHoldFailed :: session" + session + "reasonInfo=" + reasonInfo);
2275
2276             if (isTransientConferenceSession(session)) {
2277                 // We should not get this callback for a transient session.
2278                 logi("callSessionHoldFailed :: not supported for transient conference session=" +
2279                         session);
2280                 return;
2281             }
2282
2283             logi("callSessionHoldFailed :: session=" + session +
2284                     ", reasonInfo=" + reasonInfo);
2285
2286             synchronized (mLockObj) {
2287                 mHold = false;
2288             }
2289
2290             boolean isHoldForMerge = false;
2291             ImsCall.Listener listener;
2292
2293             synchronized(ImsCall.this) {
2294                 if (mUpdateRequest == UPDATE_HOLD_MERGE) {
2295                     isHoldForMerge = true;
2296                 }
2297
2298                 mUpdateRequest = UPDATE_NONE;
2299                 listener = mListener;
2300             }
2301
2302             if (listener != null) {
2303                 try {
2304                     listener.onCallHoldFailed(ImsCall.this, reasonInfo);
2305                 } catch (Throwable t) {
2306                     loge("callSessionHoldFailed :: ", t);
2307                 }
2308             }
2309         }
2310
2311         @Override
2312         public void callSessionHoldReceived(ImsCallSession session, ImsCallProfile profile) {
2313             logi("callSessionHoldReceived :: session=" + session + "profile=" + profile);
2314
2315             if (isTransientConferenceSession(session)) {
2316                 // We should not get this callback for a transient session.
2317                 logi("callSessionHoldReceived :: not supported for transient conference session=" +
2318                         session);
2319                 return;
2320             }
2321
2322             ImsCall.Listener listener;
2323
2324             synchronized(ImsCall.this) {
2325                 listener = mListener;
2326                 setCallProfile(profile);
2327             }
2328
2329             if (listener != null) {
2330                 try {
2331                     listener.onCallHoldReceived(ImsCall.this);
2332                 } catch (Throwable t) {
2333                     loge("callSessionHoldReceived :: ", t);
2334                 }
2335             }
2336         }
2337
2338         @Override
2339         public void callSessionResumed(ImsCallSession session, ImsCallProfile profile) {
2340             logi("callSessionResumed :: session=" + session + "profile=" + profile);
2341
2342             if (isTransientConferenceSession(session)) {
2343                 logi("callSessionResumed :: not supported for transient conference session=" +
2344                         session);
2345                 return;
2346             }
2347
2348             // If this call was pending a merge, it is not anymore. This is the case when we
2349             // are merging in a new call into an existing conference.
2350             setCallSessionMergePending(false);
2351
2352             // TOOD: When we are merging a new call into an existing conference we are waiting
2353             // for 2 triggers to let us know that the conference has been established, the first
2354             // is a termination for the new calls (since it is added to the conference) the second
2355             // would be a resume on the existing conference.  If the resume comes first, then
2356             // we will make the onCallResumed() callback and its unclear how this will behave if
2357             // the termination has not come yet.
2358
2359             ImsCall.Listener listener;
2360             synchronized(ImsCall.this) {
2361                 listener = mListener;
2362                 setCallProfile(profile);
2363                 mUpdateRequest = UPDATE_NONE;
2364                 mHold = false;
2365             }
2366
2367             if (listener != null) {
2368                 try {
2369                     listener.onCallResumed(ImsCall.this);
2370                 } catch (Throwable t) {
2371                     loge("callSessionResumed :: ", t);
2372                 }
2373             }
2374         }
2375
2376         @Override
2377         public void callSessionResumeFailed(ImsCallSession session, ImsReasonInfo reasonInfo) {
2378             loge("callSessionResumeFailed :: session=" + session + "reasonInfo=" + reasonInfo);
2379
2380             if (isTransientConferenceSession(session)) {
2381                 logi("callSessionResumeFailed :: not supported for transient conference session=" +
2382                         session);
2383                 return;
2384             }
2385
2386             synchronized(mLockObj) {
2387                 mHold = true;
2388             }
2389
2390             ImsCall.Listener listener;
2391
2392             synchronized(ImsCall.this) {
2393                 listener = mListener;
2394                 mUpdateRequest = UPDATE_NONE;
2395             }
2396
2397             if (listener != null) {
2398                 try {
2399                     listener.onCallResumeFailed(ImsCall.this, reasonInfo);
2400                 } catch (Throwable t) {
2401                     loge("callSessionResumeFailed :: ", t);
2402                 }
2403             }
2404         }
2405
2406         @Override
2407         public void callSessionResumeReceived(ImsCallSession session, ImsCallProfile profile) {
2408             logi("callSessionResumeReceived :: session=" + session + "profile=" + profile);
2409
2410             if (isTransientConferenceSession(session)) {
2411                 logi("callSessionResumeReceived :: not supported for transient conference session=" +
2412                         session);
2413                 return;
2414             }
2415
2416             ImsCall.Listener listener;
2417
2418             synchronized(ImsCall.this) {
2419                 listener = mListener;
2420                 setCallProfile(profile);
2421             }
2422
2423             if (listener != null) {
2424                 try {
2425                     listener.onCallResumeReceived(ImsCall.this);
2426                 } catch (Throwable t) {
2427                     loge("callSessionResumeReceived :: ", t);
2428                 }
2429             }
2430         }
2431
2432         @Override
2433         public void callSessionMergeStarted(ImsCallSession session,
2434                 ImsCallSession newSession, ImsCallProfile profile) {
2435             logi("callSessionMergeStarted :: session=" + session + " newSession=" + newSession +
2436                     ", profile=" + profile);
2437
2438             return;
2439         }
2440
2441         /*
2442          * This method check if session exists as a session on the current
2443          * ImsCall or its counterpart if it is in the process of a conference
2444          */
2445         private boolean doesCallSessionExistsInMerge(ImsCallSession cs) {
2446             String callId = cs.getCallId();
2447             return ((isMergeHost() && Objects.equals(mMergePeer.mSession.getCallId(), callId)) ||
2448                     (isMergePeer() && Objects.equals(mMergeHost.mSession.getCallId(), callId)) ||
2449                     Objects.equals(mSession.getCallId(), callId));
2450         }
2451
2452         /**
2453          * We received a callback from ImsCallSession that merge completed.
2454          * @param newSession - this session can have 2 values based on the below scenarios
2455          *
2456          * Conference Scenarios :
2457          * Case 1 - 3 way success case
2458          * Case 2 - 3 way success case but held call fails to merge
2459          * Case 3 - 3 way success case but active call fails to merge
2460          * case 4 - 4 way success case, where merge is initiated on the foreground single-party
2461          *          call and the conference (mergeHost) is the background call.
2462          * case 5 - 4 way success case, where merge is initiated on the foreground conference
2463          *          call (mergeHost) and the single party call is in the background.
2464          *
2465          * Conference Result:
2466          * session : new session after conference
2467          * newSession = new session for case 1, 2, 3.
2468          *              Should be considered as mTransientConferencession
2469          * newSession = Active conference session for case 5 will be null
2470          *              mergehost was foreground call
2471          *              mTransientConferencession will be null
2472          * newSession = Active conference session for case 4 will be null
2473          *              mergeHost was background call
2474          *              mTransientConferencession will be null
2475          */
2476         @Override
2477         public void callSessionMergeComplete(ImsCallSession newSession) {
2478             logi("callSessionMergeComplete :: newSession =" + newSession);
2479             if (!isMergeHost()) {
2480                 // Handles case 4
2481                 mMergeHost.processMergeComplete();
2482             } else {
2483                 // Handles case 1, 2, 3
2484                 if (newSession != null) {
2485                     mTransientConferenceSession = doesCallSessionExistsInMerge(newSession) ?
2486                             null: newSession;
2487                 }
2488                 // Handles case 5
2489                 processMergeComplete();
2490             }
2491         }
2492
2493         @Override
2494         public void callSessionMergeFailed(ImsCallSession session, ImsReasonInfo reasonInfo) {
2495             loge("callSessionMergeFailed :: session=" + session + "reasonInfo=" + reasonInfo);
2496
2497             // Its possible that there could be threading issues with the other thread handling
2498             // the other call. This could affect our state.
2499             synchronized (ImsCall.this) {
2500                 // Let's tell our parent ImsCall that the merge has failed and we need to clean
2501                 // up any temporary, transient state.  Note this only gets called for an initial
2502                 // conference.  If a merge into an existing conference fails, the two sessions will
2503                 // just go back to their original state (ACTIVE or HELD).
2504                 if (isMergeHost()) {
2505                     processMergeFailed(reasonInfo);
2506                 } else if (mMergeHost != null) {
2507                     mMergeHost.processMergeFailed(reasonInfo);
2508                 } else {
2509                     loge("callSessionMergeFailed :: No merge host for this conference!");
2510                 }
2511             }
2512         }
2513
2514         @Override
2515         public void callSessionUpdated(ImsCallSession session, ImsCallProfile profile) {
2516             logi("callSessionUpdated :: session=" + session + " profile=" + profile);
2517
2518             if (isTransientConferenceSession(session)) {
2519                 logi("callSessionUpdated :: not supported for transient conference session=" +
2520                         session);
2521                 return;
2522             }
2523
2524             ImsCall.Listener listener;
2525
2526             synchronized(ImsCall.this) {
2527                 listener = mListener;
2528                 setCallProfile(profile);
2529             }
2530
2531             if (listener != null) {
2532                 try {
2533                     listener.onCallUpdated(ImsCall.this);
2534                 } catch (Throwable t) {
2535                     loge("callSessionUpdated :: ", t);
2536                 }
2537             }
2538         }
2539
2540         @Override
2541         public void callSessionUpdateFailed(ImsCallSession session, ImsReasonInfo reasonInfo) {
2542             loge("callSessionUpdateFailed :: session=" + session + " reasonInfo=" + reasonInfo);
2543
2544             if (isTransientConferenceSession(session)) {
2545                 logi("callSessionUpdateFailed :: not supported for transient conference session=" +
2546                         session);
2547                 return;
2548             }
2549
2550             ImsCall.Listener listener;
2551
2552             synchronized(ImsCall.this) {
2553                 listener = mListener;
2554                 mUpdateRequest = UPDATE_NONE;
2555             }
2556
2557             if (listener != null) {
2558                 try {
2559                     listener.onCallUpdateFailed(ImsCall.this, reasonInfo);
2560                 } catch (Throwable t) {
2561                     loge("callSessionUpdateFailed :: ", t);
2562                 }
2563             }
2564         }
2565
2566         @Override
2567         public void callSessionUpdateReceived(ImsCallSession session, ImsCallProfile profile) {
2568             logi("callSessionUpdateReceived :: session=" + session + " profile=" + profile);
2569
2570             if (isTransientConferenceSession(session)) {
2571                 logi("callSessionUpdateReceived :: not supported for transient conference " +
2572                         "session=" + session);
2573                 return;
2574             }
2575
2576             ImsCall.Listener listener;
2577
2578             synchronized(ImsCall.this) {
2579                 listener = mListener;
2580                 mProposedCallProfile = profile;
2581                 mUpdateRequest = UPDATE_UNSPECIFIED;
2582             }
2583
2584             if (listener != null) {
2585                 try {
2586                     listener.onCallUpdateReceived(ImsCall.this);
2587                 } catch (Throwable t) {
2588                     loge("callSessionUpdateReceived :: ", t);
2589                 }
2590             }
2591         }
2592
2593         @Override
2594         public void callSessionConferenceExtended(ImsCallSession session, ImsCallSession newSession,
2595                 ImsCallProfile profile) {
2596             logi("callSessionConferenceExtended :: session=" + session  + " newSession=" +
2597                     newSession + ", profile=" + profile);
2598
2599             if (isTransientConferenceSession(session)) {
2600                 logi("callSessionConferenceExtended :: not supported for transient conference " +
2601                         "session=" + session);
2602                 return;
2603             }
2604
2605             ImsCall newCall = createNewCall(newSession, profile);
2606
2607             if (newCall == null) {
2608                 callSessionConferenceExtendFailed(session, new ImsReasonInfo());
2609                 return;
2610             }
2611
2612             ImsCall.Listener listener;
2613
2614             synchronized(ImsCall.this) {
2615                 listener = mListener;
2616                 mUpdateRequest = UPDATE_NONE;
2617             }
2618
2619             if (listener != null) {
2620                 try {
2621                     listener.onCallConferenceExtended(ImsCall.this, newCall);
2622                 } catch (Throwable t) {
2623                     loge("callSessionConferenceExtended :: ", t);
2624                 }
2625             }
2626         }
2627
2628         @Override
2629         public void callSessionConferenceExtendFailed(ImsCallSession session,
2630                 ImsReasonInfo reasonInfo) {
2631             loge("callSessionConferenceExtendFailed :: reasonInfo=" + reasonInfo);
2632
2633             if (isTransientConferenceSession(session)) {
2634                 logi("callSessionConferenceExtendFailed :: not supported for transient " +
2635                         "conference session=" + session);
2636                 return;
2637             }
2638
2639             ImsCall.Listener listener;
2640
2641             synchronized(ImsCall.this) {
2642                 listener = mListener;
2643                 mUpdateRequest = UPDATE_NONE;
2644             }
2645
2646             if (listener != null) {
2647                 try {
2648                     listener.onCallConferenceExtendFailed(ImsCall.this, reasonInfo);
2649                 } catch (Throwable t) {
2650                     loge("callSessionConferenceExtendFailed :: ", t);
2651                 }
2652             }
2653         }
2654
2655         @Override
2656         public void callSessionConferenceExtendReceived(ImsCallSession session,
2657                 ImsCallSession newSession, ImsCallProfile profile) {
2658             logi("callSessionConferenceExtendReceived :: newSession=" + newSession +
2659                     ", profile=" + profile);
2660
2661             if (isTransientConferenceSession(session)) {
2662                 logi("callSessionConferenceExtendReceived :: not supported for transient " +
2663                         "conference session" + session);
2664                 return;
2665             }
2666
2667             ImsCall newCall = createNewCall(newSession, profile);
2668
2669             if (newCall == null) {
2670                 // Should all the calls be terminated...???
2671                 return;
2672             }
2673
2674             ImsCall.Listener listener;
2675
2676             synchronized(ImsCall.this) {
2677                 listener = mListener;
2678             }
2679
2680             if (listener != null) {
2681                 try {
2682                     listener.onCallConferenceExtendReceived(ImsCall.this, newCall);
2683                 } catch (Throwable t) {
2684                     loge("callSessionConferenceExtendReceived :: ", t);
2685                 }
2686             }
2687         }
2688
2689         @Override
2690         public void callSessionInviteParticipantsRequestDelivered(ImsCallSession session) {
2691             logi("callSessionInviteParticipantsRequestDelivered ::");
2692
2693             if (isTransientConferenceSession(session)) {
2694                 logi("callSessionInviteParticipantsRequestDelivered :: not supported for " +
2695                         "conference session=" + session);
2696                 return;
2697             }
2698
2699             ImsCall.Listener listener;
2700
2701             synchronized(ImsCall.this) {
2702                 listener = mListener;
2703             }
2704
2705             if (listener != null) {
2706                 try {
2707                     listener.onCallInviteParticipantsRequestDelivered(ImsCall.this);
2708                 } catch (Throwable t) {
2709                     loge("callSessionInviteParticipantsRequestDelivered :: ", t);
2710                 }
2711             }
2712         }
2713
2714         @Override
2715         public void callSessionInviteParticipantsRequestFailed(ImsCallSession session,
2716                 ImsReasonInfo reasonInfo) {
2717             loge("callSessionInviteParticipantsRequestFailed :: reasonInfo=" + reasonInfo);
2718
2719             if (isTransientConferenceSession(session)) {
2720                 logi("callSessionInviteParticipantsRequestFailed :: not supported for " +
2721                         "conference session=" + session);
2722                 return;
2723             }
2724
2725             ImsCall.Listener listener;
2726
2727             synchronized(ImsCall.this) {
2728                 listener = mListener;
2729             }
2730
2731             if (listener != null) {
2732                 try {
2733                     listener.onCallInviteParticipantsRequestFailed(ImsCall.this, reasonInfo);
2734                 } catch (Throwable t) {
2735                     loge("callSessionInviteParticipantsRequestFailed :: ", t);
2736                 }
2737             }
2738         }
2739
2740         @Override
2741         public void callSessionRemoveParticipantsRequestDelivered(ImsCallSession session) {
2742             logi("callSessionRemoveParticipantsRequestDelivered ::");
2743
2744             if (isTransientConferenceSession(session)) {
2745                 logi("callSessionRemoveParticipantsRequestDelivered :: not supported for " +
2746                         "conference session=" + session);
2747                 return;
2748             }
2749
2750             ImsCall.Listener listener;
2751
2752             synchronized(ImsCall.this) {
2753                 listener = mListener;
2754             }
2755
2756             if (listener != null) {
2757                 try {
2758                     listener.onCallRemoveParticipantsRequestDelivered(ImsCall.this);
2759                 } catch (Throwable t) {
2760                     loge("callSessionRemoveParticipantsRequestDelivered :: ", t);
2761                 }
2762             }
2763         }
2764
2765         @Override
2766         public void callSessionRemoveParticipantsRequestFailed(ImsCallSession session,
2767                 ImsReasonInfo reasonInfo) {
2768             loge("callSessionRemoveParticipantsRequestFailed :: reasonInfo=" + reasonInfo);
2769
2770             if (isTransientConferenceSession(session)) {
2771                 logi("callSessionRemoveParticipantsRequestFailed :: not supported for " +
2772                         "conference session=" + session);
2773                 return;
2774             }
2775
2776             ImsCall.Listener listener;
2777
2778             synchronized(ImsCall.this) {
2779                 listener = mListener;
2780             }
2781
2782             if (listener != null) {
2783                 try {
2784                     listener.onCallRemoveParticipantsRequestFailed(ImsCall.this, reasonInfo);
2785                 } catch (Throwable t) {
2786                     loge("callSessionRemoveParticipantsRequestFailed :: ", t);
2787                 }
2788             }
2789         }
2790
2791         @Override
2792         public void callSessionConferenceStateUpdated(ImsCallSession session,
2793                 ImsConferenceState state) {
2794             logi("callSessionConferenceStateUpdated :: state=" + state);
2795
2796             conferenceStateUpdated(state);
2797         }
2798
2799         @Override
2800         public void callSessionUssdMessageReceived(ImsCallSession session, int mode,
2801                 String ussdMessage) {
2802             logi("callSessionUssdMessageReceived :: mode=" + mode + ", ussdMessage=" +
2803                     ussdMessage);
2804
2805             if (isTransientConferenceSession(session)) {
2806                 logi("callSessionUssdMessageReceived :: not supported for transient " +
2807                         "conference session=" + session);
2808                 return;
2809             }
2810
2811             ImsCall.Listener listener;
2812
2813             synchronized(ImsCall.this) {
2814                 listener = mListener;
2815             }
2816
2817             if (listener != null) {
2818                 try {
2819                     listener.onCallUssdMessageReceived(ImsCall.this, mode, ussdMessage);
2820                 } catch (Throwable t) {
2821                     loge("callSessionUssdMessageReceived :: ", t);
2822                 }
2823             }
2824         }
2825
2826         @Override
2827         public void callSessionTtyModeReceived(ImsCallSession session, int mode) {
2828             logi("callSessionTtyModeReceived :: mode=" + mode);
2829
2830             ImsCall.Listener listener;
2831
2832             synchronized(ImsCall.this) {
2833                 listener = mListener;
2834             }
2835
2836             if (listener != null) {
2837                 try {
2838                     listener.onCallSessionTtyModeReceived(ImsCall.this, mode);
2839                 } catch (Throwable t) {
2840                     loge("callSessionTtyModeReceived :: ", t);
2841                 }
2842             }
2843         }
2844
2845         /**
2846          * Notifies of a change to the multiparty state for this {@code ImsCallSession}.
2847          *
2848          * @param session The call session.
2849          * @param isMultiParty {@code true} if the session became multiparty, {@code false}
2850          *      otherwise.
2851          */
2852         @Override
2853         public void callSessionMultipartyStateChanged(ImsCallSession session,
2854                 boolean isMultiParty) {
2855             if (VDBG) {
2856                 logi("callSessionMultipartyStateChanged isMultiParty: " + (isMultiParty ? "Y"
2857                         : "N"));
2858             }
2859
2860             ImsCall.Listener listener;
2861
2862             synchronized(ImsCall.this) {
2863                 listener = mListener;
2864             }
2865
2866             if (listener != null) {
2867                 try {
2868                     listener.onMultipartyStateChanged(ImsCall.this, isMultiParty);
2869                 } catch (Throwable t) {
2870                     loge("callSessionMultipartyStateChanged :: ", t);
2871                 }
2872             }
2873         }
2874
2875         public void callSessionHandover(ImsCallSession session, int srcAccessTech,
2876             int targetAccessTech, ImsReasonInfo reasonInfo) {
2877             logi("callSessionHandover :: session=" + session + ", srcAccessTech=" +
2878                 srcAccessTech + ", targetAccessTech=" + targetAccessTech + ", reasonInfo=" +
2879                 reasonInfo);
2880
2881             ImsCall.Listener listener;
2882
2883             synchronized(ImsCall.this) {
2884                 listener = mListener;
2885             }
2886
2887             if (listener != null) {
2888                 try {
2889                     listener.onCallHandover(ImsCall.this, srcAccessTech, targetAccessTech,
2890                         reasonInfo);
2891                 } catch (Throwable t) {
2892                     loge("callSessionHandover :: ", t);
2893                 }
2894             }
2895         }
2896
2897         @Override
2898         public void callSessionHandoverFailed(ImsCallSession session, int srcAccessTech,
2899             int targetAccessTech, ImsReasonInfo reasonInfo) {
2900             loge("callSessionHandoverFailed :: session=" + session + ", srcAccessTech=" +
2901                 srcAccessTech + ", targetAccessTech=" + targetAccessTech + ", reasonInfo=" +
2902                 reasonInfo);
2903
2904             ImsCall.Listener listener;
2905
2906             synchronized(ImsCall.this) {
2907                 listener = mListener;
2908             }
2909
2910             if (listener != null) {
2911                 try {
2912                     listener.onCallHandoverFailed(ImsCall.this, srcAccessTech, targetAccessTech,
2913                         reasonInfo);
2914                 } catch (Throwable t) {
2915                     loge("callSessionHandoverFailed :: ", t);
2916                 }
2917             }
2918         }
2919
2920         @Override
2921         public void callSessionSuppServiceReceived(ImsCallSession session,
2922                 ImsSuppServiceNotification suppServiceInfo ) {
2923             if (isTransientConferenceSession(session)) {
2924                 logi("callSessionSuppServiceReceived :: not supported for transient conference"
2925                         + " session=" + session);
2926                 return;
2927             }
2928
2929             logi("callSessionSuppServiceReceived :: session=" + session +
2930                      ", suppServiceInfo" + suppServiceInfo);
2931
2932             ImsCall.Listener listener;
2933
2934             synchronized(ImsCall.this) {
2935                 listener = mListener;
2936             }
2937
2938             if (listener != null) {
2939                 try {
2940                     listener.onCallSuppServiceReceived(ImsCall.this, suppServiceInfo);
2941                 } catch (Throwable t) {
2942                     loge("callSessionSuppServiceReceived :: ", t);
2943                 }
2944             }
2945         }
2946     }
2947
2948     /**
2949      * Report a new conference state to the current {@link ImsCall} and inform listeners of the
2950      * change.  Marked as {@code VisibleForTesting} so that the
2951      * {@code com.android.internal.telephony.TelephonyTester} class can inject a test conference
2952      * event package into a regular ongoing IMS call.
2953      *
2954      * @param state The {@link ImsConferenceState}.
2955      */
2956     @VisibleForTesting
2957     public void conferenceStateUpdated(ImsConferenceState state) {
2958         Listener listener;
2959
2960         synchronized(this) {
2961             notifyConferenceStateUpdated(state);
2962             listener = mListener;
2963         }
2964
2965         if (listener != null) {
2966             try {
2967                 listener.onCallConferenceStateUpdated(this, state);
2968             } catch (Throwable t) {
2969                 loge("callSessionConferenceStateUpdated :: ", t);
2970             }
2971         }
2972     }
2973
2974     /**
2975      * Provides a human-readable string representation of an update request.
2976      *
2977      * @param updateRequest The update request.
2978      * @return The string representation.
2979      */
2980     private String updateRequestToString(int updateRequest) {
2981         switch (updateRequest) {
2982             case UPDATE_NONE:
2983                 return "NONE";
2984             case UPDATE_HOLD:
2985                 return "HOLD";
2986             case UPDATE_HOLD_MERGE:
2987                 return "HOLD_MERGE";
2988             case UPDATE_RESUME:
2989                 return "RESUME";
2990             case UPDATE_MERGE:
2991                 return "MERGE";
2992             case UPDATE_EXTEND_TO_CONFERENCE:
2993                 return "EXTEND_TO_CONFERENCE";
2994             case UPDATE_UNSPECIFIED:
2995                 return "UNSPECIFIED";
2996             default:
2997                 return "UNKNOWN";
2998         }
2999     }
3000
3001     /**
3002      * Clears the merge peer for this call, ensuring that the peer's connection to this call is also
3003      * severed at the same time.
3004      */
3005     private void clearMergeInfo() {
3006         if (CONF_DBG) {
3007             logi("clearMergeInfo :: clearing all merge info");
3008         }
3009
3010         // First clear out the merge partner then clear ourselves out.
3011         if (mMergeHost != null) {
3012             mMergeHost.mMergePeer = null;
3013             mMergeHost.mUpdateRequest = UPDATE_NONE;
3014             mMergeHost.mCallSessionMergePending = false;
3015         }
3016         if (mMergePeer != null) {
3017             mMergePeer.mMergeHost = null;
3018             mMergePeer.mUpdateRequest = UPDATE_NONE;
3019             mMergePeer.mCallSessionMergePending = false;
3020         }
3021         mMergeHost = null;
3022         mMergePeer = null;
3023         mUpdateRequest = UPDATE_NONE;
3024         mCallSessionMergePending = false;
3025     }
3026
3027     /**
3028      * Sets the merge peer for the current call.  The merge peer is the background call that will be
3029      * merged into this call.  On the merge peer, sets the merge host to be this call.
3030      *
3031      * @param mergePeer The peer call to be merged into this one.
3032      */
3033     private void setMergePeer(ImsCall mergePeer) {
3034         mMergePeer = mergePeer;
3035         mMergeHost = null;
3036
3037         mergePeer.mMergeHost = ImsCall.this;
3038         mergePeer.mMergePeer = null;
3039     }
3040
3041     /**
3042      * Sets the merge hody for the current call.  The merge host is the foreground call this call
3043      * will be merged into.  On the merge host, sets the merge peer to be this call.
3044      *
3045      * @param mergeHost The merge host this call will be merged into.
3046      */
3047     public void setMergeHost(ImsCall mergeHost) {
3048         mMergeHost = mergeHost;
3049         mMergePeer = null;
3050
3051         mergeHost.mMergeHost = null;
3052         mergeHost.mMergePeer = ImsCall.this;
3053     }
3054
3055     /**
3056      * Determines if the current call is in the process of merging with another call or conference.
3057      *
3058      * @return {@code true} if in the process of merging.
3059      */
3060     private boolean isMerging() {
3061         return mMergePeer != null || mMergeHost != null;
3062     }
3063
3064     /**
3065      * Determines if the current call is the host of the merge.
3066      *
3067      * @return {@code true} if the call is the merge host.
3068      */
3069     private boolean isMergeHost() {
3070         return mMergePeer != null && mMergeHost == null;
3071     }
3072
3073     /**
3074      * Determines if the current call is the peer of the merge.
3075      *
3076      * @return {@code true} if the call is the merge peer.
3077      */
3078     private boolean isMergePeer() {
3079         return mMergePeer == null && mMergeHost != null;
3080     }
3081
3082     /**
3083      * Determines if the call session is pending merge into a conference or not.
3084      *
3085      * @return {@code true} if a merge into a conference is pending, {@code false} otherwise.
3086      */
3087     private boolean isCallSessionMergePending() {
3088         return mCallSessionMergePending;
3089     }
3090
3091     /**
3092      * Sets flag indicating whether the call session is pending merge into a conference or not.
3093      *
3094      * @param callSessionMergePending {@code true} if a merge into the conference is pending,
3095      *      {@code false} otherwise.
3096      */
3097     private void setCallSessionMergePending(boolean callSessionMergePending) {
3098         mCallSessionMergePending = callSessionMergePending;
3099     }
3100
3101     /**
3102      * Determines if there is a conference merge in process.  If there is a merge in process,
3103      * determines if both the merge host and peer sessions have completed the merge process.  This
3104      * means that we have received terminate or hold signals for the sessions, indicating that they
3105      * are no longer in the process of being merged into the conference.
3106      * <p>
3107      * The sessions are considered to have merged if: both calls still have merge peer/host
3108      * relationships configured,  both sessions are not waiting to be merged into the conference,
3109      * and the transient conference session is alive in the case of an initial conference.
3110      *
3111      * @return {@code true} where the host and peer sessions have finished merging into the
3112      *      conference, {@code false} if the merge has not yet completed, and {@code false} if there
3113      *      is no conference merge in progress.
3114      */
3115     private boolean shouldProcessConferenceResult() {
3116         boolean areMergeTriggersDone = false;
3117
3118         synchronized (ImsCall.this) {
3119             // if there is a merge going on, then the merge host/peer relationships should have been
3120             // set up.  This works for both the initial conference or merging a call into an
3121             // existing conference.
3122             if (!isMergeHost() && !isMergePeer()) {
3123                 if (CONF_DBG) {
3124                     loge("shouldProcessConferenceResult :: no merge in progress");
3125                 }
3126                 return false;
3127             }
3128
3129             // There is a merge in progress, so check the sessions to ensure:
3130             // 1. Both calls have completed being merged (or failing to merge) into the conference.
3131             // 2. The transient conference session is alive.
3132             if (isMergeHost()) {
3133                 if (CONF_DBG) {
3134                     logi("shouldProcessConferenceResult :: We are a merge host");
3135                     logi("shouldProcessConferenceResult :: Here is the merge peer=" + mMergePeer);
3136                 }
3137                 areMergeTriggersDone = !isCallSessionMergePending() &&
3138                         !mMergePeer.isCallSessionMergePending();
3139                 if (!isMultiparty()) {
3140                     // Only check the transient session when there is no existing conference
3141                     areMergeTriggersDone &= isSessionAlive(mTransientConferenceSession);
3142                 }
3143             } else if (isMergePeer()) {
3144                 if (CONF_DBG) {
3145                     logi("shouldProcessConferenceResult :: We are a merge peer");
3146                     logi("shouldProcessConferenceResult :: Here is the merge host=" + mMergeHost);
3147                 }
3148                 areMergeTriggersDone = !isCallSessionMergePending() &&
3149                         !mMergeHost.isCallSessionMergePending();
3150                 if (!mMergeHost.isMultiparty()) {
3151                     // Only check the transient session when there is no existing conference
3152                     areMergeTriggersDone &= isSessionAlive(mMergeHost.mTransientConferenceSession);
3153                 } else {
3154                     // This else block is a special case for Verizon to handle these steps
3155                     // 1. Establish a conference call.
3156                     // 2. Add a new call (conference in in BG)
3157                     // 3. Swap (conference active on FG)
3158                     // 4. Merge
3159                     // What happens here is that the BG call gets a terminated callback
3160                     // because it was added to the conference. I've seen where
3161                     // the FG gets no callback at all because its already active.
3162                     // So if we continue to wait for it to set its isCallSessionMerging
3163                     // flag to false...we'll be waiting forever.
3164                     areMergeTriggersDone = !isCallSessionMergePending();
3165                 }
3166             } else {
3167                 // Realistically this shouldn't happen, but best to be safe.
3168                 loge("shouldProcessConferenceResult : merge in progress but call is neither" +
3169                         " host nor peer.");
3170             }
3171             if (CONF_DBG) {
3172                 logi("shouldProcessConferenceResult :: returning:" +
3173                         (areMergeTriggersDone ? "true" : "false"));
3174             }
3175         }
3176         return areMergeTriggersDone;
3177     }
3178
3179     /**
3180      * Provides a string representation of the {@link ImsCall}.  Primarily intended for use in log
3181      * statements.
3182      *
3183      * @return String representation of call.
3184      */
3185     @Override
3186     public String toString() {
3187         StringBuilder sb = new StringBuilder();
3188         sb.append("[ImsCall objId:");
3189         sb.append(System.identityHashCode(this));
3190         sb.append(" onHold:");
3191         sb.append(isOnHold() ? "Y" : "N");
3192         sb.append(" mute:");
3193         sb.append(isMuted() ? "Y" : "N");
3194         if (mCallProfile != null) {
3195             sb.append(" tech:");
3196             sb.append(mCallProfile.getCallExtra(ImsCallProfile.EXTRA_CALL_RAT_TYPE));
3197         }
3198         sb.append(" updateRequest:");
3199         sb.append(updateRequestToString(mUpdateRequest));
3200         sb.append(" merging:");
3201         sb.append(isMerging() ? "Y" : "N");
3202         if (isMerging()) {
3203             if (isMergePeer()) {
3204                 sb.append("P");
3205             } else {
3206                 sb.append("H");
3207             }
3208         }
3209         sb.append(" merge action pending:");
3210         sb.append(isCallSessionMergePending() ? "Y" : "N");
3211         sb.append(" merged:");
3212         sb.append(isMerged() ? "Y" : "N");
3213         sb.append(" multiParty:");
3214         sb.append(isMultiparty() ? "Y" : "N");
3215         sb.append(" confHost:");
3216         sb.append(isConferenceHost() ? "Y" : "N");
3217         sb.append(" buried term:");
3218         sb.append(mSessionEndDuringMerge ? "Y" : "N");
3219         sb.append(" wasVideo: ");
3220         sb.append(mWasVideoCall ? "Y" : "N");
3221         sb.append(" session:");
3222         sb.append(mSession);
3223         sb.append(" transientSession:");
3224         sb.append(mTransientConferenceSession);
3225         sb.append("]");
3226         return sb.toString();
3227     }
3228
3229     private void throwImsException(Throwable t, int code) throws ImsException {
3230         if (t instanceof ImsException) {
3231             throw (ImsException) t;
3232         } else {
3233             throw new ImsException(String.valueOf(code), t, code);
3234         }
3235     }
3236
3237     /**
3238      * Append the ImsCall information to the provided string. Usefull for as a logging helper.
3239      * @param s The original string
3240      * @return The original string with {@code ImsCall} information appended to it.
3241      */
3242     private String appendImsCallInfoToString(String s) {
3243         StringBuilder sb = new StringBuilder();
3244         sb.append(s);
3245         sb.append(" ImsCall=");
3246         sb.append(ImsCall.this);
3247         return sb.toString();
3248     }
3249
3250     /**
3251      * Updates {@link #mWasVideoCall} based on the current {@link ImsCallProfile} for the call.
3252      *
3253      * @param profile The current {@link ImsCallProfile} for the call.
3254      */
3255     private void trackVideoStateHistory(ImsCallProfile profile) {
3256         mWasVideoCall = mWasVideoCall || profile.isVideoCall();
3257     }
3258
3259     /**
3260      * @return {@code true} if this call was a video call at some point in its life span,
3261      *      {@code false} otherwise.
3262      */
3263     public boolean wasVideoCall() {
3264         return mWasVideoCall;
3265     }
3266
3267     /**
3268      * @return {@code true} if this call is a video call, {@code false} otherwise.
3269      */
3270     public boolean isVideoCall() {
3271         synchronized(mLockObj) {
3272             return mCallProfile != null && mCallProfile.isVideoCall();
3273         }
3274     }
3275
3276     /**
3277      * Determines if the current call radio access technology is over WIFI.
3278      * Note: This depends on the RIL exposing the {@link ImsCallProfile#EXTRA_CALL_RAT_TYPE} extra.
3279      * This method is primarily intended to be used when checking if answering an incoming audio
3280      * call should cause a wifi video call to drop (e.g.
3281      * {@link android.telephony.CarrierConfigManager#
3282      * KEY_DROP_VIDEO_CALL_WHEN_ANSWERING_AUDIO_CALL_BOOL} is set).
3283      *
3284      * @return {@code true} if the call is over WIFI, {@code false} otherwise.
3285      */
3286     public boolean isWifiCall() {
3287         synchronized(mLockObj) {
3288             if (mCallProfile == null) {
3289                 return false;
3290             }
3291             String callType = mCallProfile.getCallExtra(ImsCallProfile.EXTRA_CALL_RAT_TYPE);
3292             if (callType == null || callType.isEmpty()) {
3293                 callType = mCallProfile.getCallExtra(ImsCallProfile.EXTRA_CALL_RAT_TYPE_ALT);
3294             }
3295
3296             // The RIL (sadly) sends us the EXTRA_CALL_RAT_TYPE as a string extra, rather than an
3297             // integer extra, so we need to parse it.
3298             int radioTechnology = ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN;
3299             try {
3300                 radioTechnology = Integer.parseInt(callType);
3301             } catch (NumberFormatException nfe) {
3302                 radioTechnology = ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN;
3303             }
3304
3305             return radioTechnology == ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN;
3306         }
3307     }
3308
3309     /**
3310      * Log a string to the radio buffer at the info level.
3311      * @param s The message to log
3312      */
3313     private void logi(String s) {
3314         Log.i(TAG, appendImsCallInfoToString(s));
3315     }
3316
3317     /**
3318      * Log a string to the radio buffer at the debug level.
3319      * @param s The message to log
3320      */
3321     private void logd(String s) {
3322         Log.d(TAG, appendImsCallInfoToString(s));
3323     }
3324
3325     /**
3326      * Log a string to the radio buffer at the verbose level.
3327      * @param s The message to log
3328      */
3329     private void logv(String s) {
3330         Log.v(TAG, appendImsCallInfoToString(s));
3331     }
3332
3333     /**
3334      * Log a string to the radio buffer at the error level.
3335      * @param s The message to log
3336      */
3337     private void loge(String s) {
3338         Log.e(TAG, appendImsCallInfoToString(s));
3339     }
3340
3341     /**
3342      * Log a string to the radio buffer at the error level with a throwable
3343      * @param s The message to log
3344      * @param t The associated throwable
3345      */
3346     private void loge(String s, Throwable t) {
3347         Log.e(TAG, appendImsCallInfoToString(s), t);
3348     }
3349 }