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