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