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