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