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