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