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