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