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