ImsCall.java revision 165aed55d289ae2a5cdf34de4164c2395f46b8c1
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            // Clear some flags.  If the merge eventually worked, we can safely
1715            // ignore the call terminated message for the old session since we closed it already.
1716            clearSessionTerminationInfo();
1717            if (isMergeHost()) {
1718                mMergePeer.clearSessionTerminationInfo();
1719            } else if (isMergePeer()) {
1720                mMergeHost.clearSessionTerminationInfo();
1721            }
1722
1723            clearMergePeer();
1724        }
1725        if (listener != null) {
1726            try {
1727                listener.onCallMerged(ImsCall.this, swapRequired);
1728            } catch (Throwable t) {
1729                loge("processMergeComplete :: ", t);
1730            }
1731        }
1732
1733        return;
1734    }
1735
1736    /**
1737     * Handles the case where the session has ended during a merge by reporting the termination
1738     * reason to listeners.
1739     */
1740    private void notifySessionTerminatedDuringMerge() {
1741        ImsCall.Listener listener;
1742        boolean notifyFailure = false;
1743        ImsReasonInfo notifyFailureReasonInfo = null;
1744
1745        synchronized(ImsCall.this) {
1746            listener = mListener;
1747            if (mSessionEndDuringMerge) {
1748                // Set some local variables that will send out a notification about a
1749                // previously buried termination callback for our primary session now that
1750                // we know that this is not due to the conference call merging successfully.
1751                if (DBG) {
1752                    log("notifySessionTerminatedDuringMerge ::reporting terminate during merge");
1753                }
1754                notifyFailure = true;
1755                notifyFailureReasonInfo = mSessionEndDuringMergeReasonInfo;
1756            }
1757
1758            mSessionEndDuringMerge = false;
1759            mSessionEndDuringMergeReasonInfo = null;
1760        }
1761
1762        if (listener != null && notifyFailure) {
1763            try {
1764                processCallTerminated(notifyFailureReasonInfo);
1765            } catch (Throwable t) {
1766                loge("notifySessionTerminatedDuringMerge :: ", t);
1767            }
1768        }
1769    }
1770
1771    /**
1772     * We received a callback from ImsCallSession that a merge failed. Clean up all
1773     * internal state to represent this state change.
1774     *
1775     * @param reasonInfo The {@link ImsReasonInfo} why the merge failed.
1776     */
1777    private void processMergeFailed(ImsReasonInfo reasonInfo) {
1778        if (VDBG) {
1779            String reasonString = reasonInfo != null ? reasonInfo.toString() : "null";
1780            logv("processMergeFailed :: reason=" + reasonString);
1781        }
1782
1783        // Ensure any terminations are surfaced from this session.
1784        notifySessionTerminatedDuringMerge();
1785
1786        // Ensure any terminations are surface from the other session.
1787        if (isMergeHost()) {
1788            mMergePeer.notifySessionTerminatedDuringMerge();
1789        } else if (isMergePeer()) {
1790            mMergeHost.notifySessionTerminatedDuringMerge();
1791        }
1792
1793        ImsCall.Listener listener;
1794        synchronized(ImsCall.this) {
1795            listener = mListener;
1796            if (mTransientConferenceSession != null) {
1797                // Clean up any work that we performed on the transient session.
1798                mTransientConferenceSession.setListener(null);
1799                mTransientConferenceSession = null;
1800            }
1801
1802            mSessionEndDuringMerge = false;
1803            mSessionEndDuringMergeReasonInfo = null;
1804            mUpdateRequest = UPDATE_NONE;
1805
1806            // Ensure the call being conferenced into the conference has isMerged = false.
1807            if (isMergeHost()) {
1808                mMergePeer.setIsMerged(false);
1809            } else {
1810                setIsMerged(false);
1811            }
1812            // Unlink the two calls since they are no longer being involved in an attempted merge.
1813            clearMergePeer();
1814        }
1815        if (listener != null) {
1816            try {
1817                listener.onCallMergeFailed(ImsCall.this, reasonInfo);
1818            } catch (Throwable t) {
1819                loge("processMergeFailed :: ", t);
1820            }
1821        }
1822        return;
1823    }
1824
1825    private void notifyError(int reason, int statusCode, String message) {
1826    }
1827
1828    private void throwImsException(Throwable t, int code) throws ImsException {
1829        if (t instanceof ImsException) {
1830            throw (ImsException) t;
1831        } else {
1832            throw new ImsException(String.valueOf(code), t, code);
1833        }
1834    }
1835
1836    private void log(String s) {
1837        Rlog.d(TAG, s);
1838    }
1839
1840    /**
1841     * Logs the specified message, as well as the current instance of {@link ImsCall}.
1842     *
1843     * @param s The message to log.
1844     */
1845    private void logv(String s) {
1846        StringBuilder sb = new StringBuilder();
1847        sb.append(s);
1848        sb.append(" imsCall=");
1849        sb.append(ImsCall.this);
1850        Rlog.v(TAG, sb.toString());
1851    }
1852
1853    private void loge(String s) {
1854        Rlog.e(TAG, s);
1855    }
1856
1857    private void loge(String s, Throwable t) {
1858        Rlog.e(TAG, s, t);
1859    }
1860
1861    private class ImsCallSessionListenerProxy extends ImsCallSession.Listener {
1862        @Override
1863        public void callSessionProgressing(ImsCallSession session, ImsStreamMediaProfile profile) {
1864            if (isTransientConferenceSession(session)) {
1865                log("callSessionProgressing :: not supported for transient conference session=" +
1866                        session);
1867                return;
1868            }
1869
1870            if (VDBG) {
1871                logv("callSessionProgressing :: profile=" + profile);
1872            }
1873
1874            ImsCall.Listener listener;
1875
1876            synchronized(ImsCall.this) {
1877                listener = mListener;
1878                mCallProfile.mMediaProfile.copyFrom(profile);
1879            }
1880
1881            if (listener != null) {
1882                try {
1883                    listener.onCallProgressing(ImsCall.this);
1884                } catch (Throwable t) {
1885                    loge("callSessionProgressing :: ", t);
1886                }
1887            }
1888        }
1889
1890        @Override
1891        public void callSessionStarted(ImsCallSession session, ImsCallProfile profile) {
1892            if (VDBG) {
1893                logv("callSessionStarted :: profile=" + profile);
1894            }
1895
1896            // Check if there is an ongoing conference merge which has completed.  If there is
1897            // we can process the merge completion now.
1898            if (hasCallSessionMergeCompleted()) {
1899                processMergeComplete();
1900                return;
1901            }
1902
1903            ImsCall.Listener listener;
1904
1905            synchronized(ImsCall.this) {
1906                listener = mListener;
1907                mCallProfile = profile;
1908            }
1909
1910            if (listener != null) {
1911                try {
1912                    listener.onCallStarted(ImsCall.this);
1913                } catch (Throwable t) {
1914                    loge("callSessionStarted :: ", t);
1915                }
1916            }
1917        }
1918
1919        @Override
1920        public void callSessionStartFailed(ImsCallSession session, ImsReasonInfo reasonInfo) {
1921            if (isTransientConferenceSession(session)) {
1922                log("callSessionStartFailed :: not supported for transient conference session=" +
1923                        session);
1924                return;
1925            }
1926
1927            if (VDBG) {
1928                logv("callSessionStartFailed :: reasonInfo=" + reasonInfo);
1929            }
1930
1931            ImsCall.Listener listener;
1932
1933            synchronized(ImsCall.this) {
1934                listener = mListener;
1935                mLastReasonInfo = reasonInfo;
1936            }
1937
1938            if (listener != null) {
1939                try {
1940                    listener.onCallStartFailed(ImsCall.this, reasonInfo);
1941                } catch (Throwable t) {
1942                    loge("callSessionStarted :: ", t);
1943                }
1944            }
1945        }
1946
1947        @Override
1948        public void callSessionTerminated(ImsCallSession session, ImsReasonInfo reasonInfo) {
1949            if (mSession != session) {
1950                log("callSessionTerminated :: not supported for conference session=" + session);
1951                return;
1952            }
1953
1954            // If session has terminated, it is no longer pending merge.
1955            setCallSessionMergePending(false);
1956
1957            // Check if there is an ongoing conference merge which has completed.  If there is
1958            // we can process the merge completion now.
1959            if (hasCallSessionMergeCompleted()) {
1960                processMergeComplete();
1961                return;
1962            }
1963
1964            if (VDBG) {
1965                logv("callSessionTerminated :: reasonInfo=" + reasonInfo);
1966            }
1967
1968            processCallTerminated(reasonInfo);
1969        }
1970
1971        @Override
1972        public void callSessionHeld(ImsCallSession session, ImsCallProfile profile) {
1973            if (isTransientConferenceSession(session)) {
1974                log("callSessionHeld :: not supported for transient conference session=" + session);
1975                return;
1976            }
1977
1978            if (VDBG) {
1979                logv("callSessionHeld :: profile=" + profile);
1980            }
1981
1982            ImsCall.Listener listener;
1983
1984            synchronized(ImsCall.this) {
1985                // If the session was held, it is no longer pending a merge -- this means it could
1986                // not be merged into the conference and was held instead.
1987                setCallSessionMergePending(false);
1988
1989                mCallProfile = profile;
1990
1991                if (mUpdateRequest == UPDATE_HOLD_MERGE) {
1992                    mergeInternal();
1993                    return;
1994                }
1995
1996                // Check if there is an ongoing conference merge which has completed.  If there is
1997                // we can process the merge completion now.
1998                if (hasCallSessionMergeCompleted()) {
1999                    processMergeComplete();
2000                }
2001
2002                mUpdateRequest = UPDATE_NONE;
2003                listener = mListener;
2004            }
2005
2006            if (listener != null) {
2007                try {
2008                    listener.onCallHeld(ImsCall.this);
2009                } catch (Throwable t) {
2010                    loge("callSessionHeld :: ", t);
2011                }
2012            }
2013        }
2014
2015        @Override
2016        public void callSessionHoldFailed(ImsCallSession session, ImsReasonInfo reasonInfo) {
2017            if (isTransientConferenceSession(session)) {
2018                log("callSessionHoldFailed :: not supported for transient conference session=" +
2019                        session);
2020                return;
2021            }
2022
2023            if (VDBG) {
2024                logv("callSessionHoldFailed :: reasonInfo=" + reasonInfo);
2025            }
2026
2027            boolean isHoldForMerge = false;
2028            ImsCall.Listener listener;
2029
2030            synchronized(ImsCall.this) {
2031                if (mUpdateRequest == UPDATE_HOLD_MERGE) {
2032                    isHoldForMerge = true;
2033                }
2034
2035                mUpdateRequest = UPDATE_NONE;
2036                listener = mListener;
2037            }
2038
2039            if (isHoldForMerge) {
2040                // Is hold for merge implemented/supported? If so we need to take a close look
2041                // at this workflow to make sure that we handle the case where
2042                // callSessionMergeFailed() does the right thing because we have not actually
2043                // started the merge yet.
2044                callSessionMergeFailed(session, reasonInfo);
2045                return;
2046            }
2047
2048            if (listener != null) {
2049                try {
2050                    listener.onCallHoldFailed(ImsCall.this, reasonInfo);
2051                } catch (Throwable t) {
2052                    loge("callSessionHoldFailed :: ", t);
2053                }
2054            }
2055        }
2056
2057        @Override
2058        public void callSessionHoldReceived(ImsCallSession session, ImsCallProfile profile) {
2059            if (isTransientConferenceSession(session)) {
2060                log("callSessionHoldReceived :: not supported for transient conference session=" +
2061                        session);
2062                return;
2063            }
2064
2065            if (VDBG) {
2066                logv("callSessionHoldReceived :: profile=" + profile);
2067            }
2068
2069            ImsCall.Listener listener;
2070
2071            synchronized(ImsCall.this) {
2072                listener = mListener;
2073                mCallProfile = profile;
2074            }
2075
2076            if (listener != null) {
2077                try {
2078                    listener.onCallHoldReceived(ImsCall.this);
2079                } catch (Throwable t) {
2080                    loge("callSessionHoldReceived :: ", t);
2081                }
2082            }
2083        }
2084
2085        @Override
2086        public void callSessionResumed(ImsCallSession session, ImsCallProfile profile) {
2087            if (isTransientConferenceSession(session)) {
2088                log("callSessionResumed :: not supported for transient conference session=" +
2089                        session);
2090                return;
2091            }
2092
2093            if (VDBG) {
2094                logv("callSessionResumed :: profile=" + profile);
2095            }
2096
2097            ImsCall.Listener listener;
2098
2099            synchronized(ImsCall.this) {
2100                listener = mListener;
2101                mCallProfile = profile;
2102                mUpdateRequest = UPDATE_NONE;
2103                mHold = false;
2104            }
2105
2106            if (listener != null) {
2107                try {
2108                    listener.onCallResumed(ImsCall.this);
2109                } catch (Throwable t) {
2110                    loge("callSessionResumed :: ", t);
2111                }
2112            }
2113        }
2114
2115        @Override
2116        public void callSessionResumeFailed(ImsCallSession session, ImsReasonInfo reasonInfo) {
2117            if (isTransientConferenceSession(session)) {
2118                log("callSessionResumeFailed :: not supported for transient conference session=" +
2119                        session);
2120                return;
2121            }
2122
2123            if (VDBG) {
2124                logv("callSessionResumeFailed :: reasonInfo=" + reasonInfo);
2125            }
2126
2127            ImsCall.Listener listener;
2128
2129            synchronized(ImsCall.this) {
2130                listener = mListener;
2131                mUpdateRequest = UPDATE_NONE;
2132            }
2133
2134            if (listener != null) {
2135                try {
2136                    listener.onCallResumeFailed(ImsCall.this, reasonInfo);
2137                } catch (Throwable t) {
2138                    loge("callSessionResumeFailed :: ", t);
2139                }
2140            }
2141        }
2142
2143        @Override
2144        public void callSessionResumeReceived(ImsCallSession session, ImsCallProfile profile) {
2145            if (isTransientConferenceSession(session)) {
2146                log("callSessionResumeReceived :: not supported for transient conference session=" +
2147                        session);
2148                return;
2149            }
2150
2151            if (VDBG) {
2152                logv("callSessionResumeReceived :: profile=" + profile);
2153            }
2154
2155            ImsCall.Listener listener;
2156
2157            synchronized(ImsCall.this) {
2158                listener = mListener;
2159                mCallProfile = profile;
2160            }
2161
2162            if (listener != null) {
2163                try {
2164                    listener.onCallResumeReceived(ImsCall.this);
2165                } catch (Throwable t) {
2166                    loge("callSessionResumeReceived :: ", t);
2167                }
2168            }
2169        }
2170
2171        @Override
2172        public void callSessionMergeStarted(ImsCallSession session,
2173                ImsCallSession newSession, ImsCallProfile profile) {
2174            if (VDBG) {
2175                String newSessionString = newSession == null ? "null" : newSession.toString();
2176                logv("callSessionMergeStarted :: newSession=" + newSessionString +
2177                        ", profile=" + profile);
2178            }
2179
2180            if (mUpdateRequest != UPDATE_MERGE) {
2181                // Odd, we are not in the midst of merging anything.
2182                log("callSessionMergeStarted :: no merge in progress.");
2183                return;
2184            }
2185
2186            // There are 2 ways that we can go here.  If the session that supplied the params
2187            // is not null, then it is the new session that represents the new conference
2188            // if the merge succeeds. If it is null, the merge is happening on our current
2189            // ImsCallSession.
2190            if (session == null) {
2191                // Everything is already set up and we just need to make sure
2192                // that we properly respond to all the future callbacks about
2193                // this merge.
2194                if (DBG) {
2195                    log("callSessionMergeStarted :: merging into existing ImsCallSession");
2196                }
2197                return;
2198            }
2199
2200            if (DBG) {
2201                log("callSessionMergeStarted ::  setting our transient ImsCallSession");
2202            }
2203
2204            // If we are here, this means that we are creating a new conference and
2205            // we need to do some extra work around managing a new ImsCallSession that
2206            // could represent our new ImsCallSession if the merge succeeds.
2207            synchronized(ImsCall.this) {
2208                // Keep track of this session for future callbacks to indicate success
2209                // or failure of this merge.
2210                mTransientConferenceSession = newSession;
2211                mTransientConferenceSession.setListener(createCallSessionListener());
2212            }
2213
2214            return;
2215        }
2216
2217        @Override
2218        public void callSessionMergeComplete(ImsCallSession session) {
2219            if (VDBG) {
2220                logv("callSessionMergeComplete ::");
2221            }
2222
2223            // Check if there is an ongoing conference merge which has completed.  If there is
2224            // we can process the merge completion now.
2225            if (hasCallSessionMergeCompleted()) {
2226                processMergeComplete();
2227            }
2228        }
2229
2230        @Override
2231        public void callSessionMergeFailed(ImsCallSession session, ImsReasonInfo reasonInfo) {
2232            if (VDBG) {
2233                String reasonInfoString = reasonInfo == null ? "null" : reasonInfo.toString();
2234                logv("callSessionMergeFailed :: reasonInfo=" + reasonInfoString);
2235            }
2236            if (mUpdateRequest != UPDATE_MERGE && !isMerging()) {
2237                // Odd, we are not in the midst of merging anything.
2238                log("callSessionMergeFailed :: no merge in progress.");
2239                return;
2240            }
2241            // Let's tell our parent ImsCall that the merge has failed and we need to clean
2242            // up any temporary, transient state.
2243            processMergeFailed(reasonInfo);
2244        }
2245
2246        @Override
2247        public void callSessionUpdated(ImsCallSession session, ImsCallProfile profile) {
2248            if (isTransientConferenceSession(session)) {
2249                log("callSessionUpdated :: not supported for transient conference session=" +
2250                        session);
2251                return;
2252            }
2253
2254            if (VDBG) {
2255                logv("callSessionUpdated :: profile=" + profile);
2256            }
2257
2258            ImsCall.Listener listener;
2259
2260            synchronized(ImsCall.this) {
2261                listener = mListener;
2262                mCallProfile = profile;
2263                mUpdateRequest = UPDATE_NONE;
2264            }
2265
2266            if (listener != null) {
2267                try {
2268                    listener.onCallUpdated(ImsCall.this);
2269                } catch (Throwable t) {
2270                    loge("callSessionUpdated :: ", t);
2271                }
2272            }
2273        }
2274
2275        @Override
2276        public void callSessionUpdateFailed(ImsCallSession session, ImsReasonInfo reasonInfo) {
2277            if (isTransientConferenceSession(session)) {
2278                log("callSessionUpdateFailed :: not supported for transient conference session=" +
2279                        session);
2280                return;
2281            }
2282
2283            if (VDBG) {
2284                logv("callSessionUpdateFailed :: reasonInfo=" + reasonInfo);
2285            }
2286
2287            ImsCall.Listener listener;
2288
2289            synchronized(ImsCall.this) {
2290                listener = mListener;
2291                mUpdateRequest = UPDATE_NONE;
2292            }
2293
2294            if (listener != null) {
2295                try {
2296                    listener.onCallUpdateFailed(ImsCall.this, reasonInfo);
2297                } catch (Throwable t) {
2298                    loge("callSessionUpdateFailed :: ", t);
2299                }
2300            }
2301        }
2302
2303        @Override
2304        public void callSessionUpdateReceived(ImsCallSession session, ImsCallProfile profile) {
2305            if (isTransientConferenceSession(session)) {
2306                log("callSessionUpdateReceived :: not supported for transient conference " +
2307                        "session=" + session);
2308                return;
2309            }
2310
2311            if (VDBG) {
2312                logv("callSessionUpdateReceived :: profile=" + profile);
2313            }
2314
2315            ImsCall.Listener listener;
2316
2317            synchronized(ImsCall.this) {
2318                listener = mListener;
2319                mProposedCallProfile = profile;
2320                mUpdateRequest = UPDATE_UNSPECIFIED;
2321            }
2322
2323            if (listener != null) {
2324                try {
2325                    listener.onCallUpdateReceived(ImsCall.this);
2326                } catch (Throwable t) {
2327                    loge("callSessionUpdateReceived :: ", t);
2328                }
2329            }
2330        }
2331
2332        @Override
2333        public void callSessionConferenceExtended(ImsCallSession session, ImsCallSession newSession,
2334                ImsCallProfile profile) {
2335            if (isTransientConferenceSession(session)) {
2336                log("callSessionConferenceExtended :: not supported for transient conference " +
2337                        "session=" + session);
2338                return;
2339            }
2340
2341            if (VDBG) {
2342                logv("callSessionConferenceExtended :: newSession=" + newSession + ", profile="
2343                        + profile);
2344            }
2345
2346            ImsCall newCall = createNewCall(newSession, profile);
2347
2348            if (newCall == null) {
2349                callSessionConferenceExtendFailed(session, new ImsReasonInfo());
2350                return;
2351            }
2352
2353            ImsCall.Listener listener;
2354
2355            synchronized(ImsCall.this) {
2356                listener = mListener;
2357                mUpdateRequest = UPDATE_NONE;
2358            }
2359
2360            if (listener != null) {
2361                try {
2362                    listener.onCallConferenceExtended(ImsCall.this, newCall);
2363                } catch (Throwable t) {
2364                    loge("callSessionConferenceExtended :: ", t);
2365                }
2366            }
2367        }
2368
2369        @Override
2370        public void callSessionConferenceExtendFailed(ImsCallSession session,
2371                ImsReasonInfo reasonInfo) {
2372            if (isTransientConferenceSession(session)) {
2373                log("callSessionConferenceExtendFailed :: not supported for transient " +
2374                        "conference session=" + session);
2375                return;
2376            }
2377
2378            if (DBG) {
2379                log("callSessionConferenceExtendFailed :: imsCall=" + ImsCall.this +
2380                        ", reasonInfo=" + reasonInfo);
2381            }
2382
2383            ImsCall.Listener listener;
2384
2385            synchronized(ImsCall.this) {
2386                listener = mListener;
2387                mUpdateRequest = UPDATE_NONE;
2388            }
2389
2390            if (listener != null) {
2391                try {
2392                    listener.onCallConferenceExtendFailed(ImsCall.this, reasonInfo);
2393                } catch (Throwable t) {
2394                    loge("callSessionConferenceExtendFailed :: ", t);
2395                }
2396            }
2397        }
2398
2399        @Override
2400        public void callSessionConferenceExtendReceived(ImsCallSession session,
2401                ImsCallSession newSession, ImsCallProfile profile) {
2402            if (isTransientConferenceSession(session)) {
2403                log("callSessionConferenceExtendReceived :: not supported for transient " +
2404                        "conference session" + session);
2405                return;
2406            }
2407
2408            if (VDBG) {
2409                logv("callSessionConferenceExtendReceived :: newSession=" + newSession +
2410                        ", profile=" + profile);
2411            }
2412
2413            ImsCall newCall = createNewCall(newSession, profile);
2414
2415            if (newCall == null) {
2416                // Should all the calls be terminated...???
2417                return;
2418            }
2419
2420            ImsCall.Listener listener;
2421
2422            synchronized(ImsCall.this) {
2423                listener = mListener;
2424            }
2425
2426            if (listener != null) {
2427                try {
2428                    listener.onCallConferenceExtendReceived(ImsCall.this, newCall);
2429                } catch (Throwable t) {
2430                    loge("callSessionConferenceExtendReceived :: ", t);
2431                }
2432            }
2433        }
2434
2435        @Override
2436        public void callSessionInviteParticipantsRequestDelivered(ImsCallSession session) {
2437            if (isTransientConferenceSession(session)) {
2438                log("callSessionInviteParticipantsRequestDelivered :: not supported for " +
2439                        "conference session=" + session);
2440                return;
2441            }
2442
2443            if (VDBG) {
2444                logv("callSessionInviteParticipantsRequestDelivered ::");
2445            }
2446
2447            ImsCall.Listener listener;
2448
2449            synchronized(ImsCall.this) {
2450                listener = mListener;
2451            }
2452
2453            if (listener != null) {
2454                try {
2455                    listener.onCallInviteParticipantsRequestDelivered(ImsCall.this);
2456                } catch (Throwable t) {
2457                    loge("callSessionInviteParticipantsRequestDelivered :: ", t);
2458                }
2459            }
2460        }
2461
2462        @Override
2463        public void callSessionInviteParticipantsRequestFailed(ImsCallSession session,
2464                ImsReasonInfo reasonInfo) {
2465            if (isTransientConferenceSession(session)) {
2466                log("callSessionInviteParticipantsRequestFailed :: not supported for " +
2467                        "conference session=" + session);
2468                return;
2469            }
2470
2471            if (VDBG) {
2472                logv("callSessionInviteParticipantsRequestFailed :: reasonInfo=" + reasonInfo);
2473            }
2474
2475            ImsCall.Listener listener;
2476
2477            synchronized(ImsCall.this) {
2478                listener = mListener;
2479            }
2480
2481            if (listener != null) {
2482                try {
2483                    listener.onCallInviteParticipantsRequestFailed(ImsCall.this, reasonInfo);
2484                } catch (Throwable t) {
2485                    loge("callSessionInviteParticipantsRequestFailed :: ", t);
2486                }
2487            }
2488        }
2489
2490        @Override
2491        public void callSessionRemoveParticipantsRequestDelivered(ImsCallSession session) {
2492            if (isTransientConferenceSession(session)) {
2493                log("callSessionRemoveParticipantsRequestDelivered :: not supported for " +
2494                        "conference session=" + session);
2495                return;
2496            }
2497
2498            if (VDBG) {
2499                logv("callSessionRemoveParticipantsRequestDelivered ::");
2500            }
2501
2502            ImsCall.Listener listener;
2503
2504            synchronized(ImsCall.this) {
2505                listener = mListener;
2506            }
2507
2508            if (listener != null) {
2509                try {
2510                    listener.onCallRemoveParticipantsRequestDelivered(ImsCall.this);
2511                } catch (Throwable t) {
2512                    loge("callSessionRemoveParticipantsRequestDelivered :: ", t);
2513                }
2514            }
2515        }
2516
2517        @Override
2518        public void callSessionRemoveParticipantsRequestFailed(ImsCallSession session,
2519                ImsReasonInfo reasonInfo) {
2520            if (isTransientConferenceSession(session)) {
2521                log("callSessionRemoveParticipantsRequestFailed :: not supported for " +
2522                        "conference session=" + session);
2523                return;
2524            }
2525
2526            if (VDBG) {
2527                logv("callSessionRemoveParticipantsRequestFailed :: reasonInfo=" + reasonInfo);
2528            }
2529
2530            ImsCall.Listener listener;
2531
2532            synchronized(ImsCall.this) {
2533                listener = mListener;
2534            }
2535
2536            if (listener != null) {
2537                try {
2538                    listener.onCallRemoveParticipantsRequestFailed(ImsCall.this, reasonInfo);
2539                } catch (Throwable t) {
2540                    loge("callSessionRemoveParticipantsRequestFailed :: ", t);
2541                }
2542            }
2543        }
2544
2545        @Override
2546        public void callSessionConferenceStateUpdated(ImsCallSession session,
2547                ImsConferenceState state) {
2548            if (isTransientConferenceSession(session)) {
2549                log("callSessionConferenceStateUpdated :: not supported for transient " +
2550                        "conference session=" + session);
2551                return;
2552            }
2553
2554            if (VDBG) {
2555                logv("callSessionConferenceStateUpdated :: state=" + state);
2556            }
2557
2558            conferenceStateUpdated(state);
2559        }
2560
2561        @Override
2562        public void callSessionUssdMessageReceived(ImsCallSession session, int mode,
2563                String ussdMessage) {
2564            if (isTransientConferenceSession(session)) {
2565                log("callSessionUssdMessageReceived :: not supported for transient " +
2566                        "conference session=" + session);
2567                return;
2568            }
2569
2570            if (VDBG) {
2571                logv("callSessionUssdMessageReceived :: mode=" + mode + ", ussdMessage=" +
2572                        ussdMessage);
2573            }
2574
2575            ImsCall.Listener listener;
2576
2577            synchronized(ImsCall.this) {
2578                listener = mListener;
2579            }
2580
2581            if (listener != null) {
2582                try {
2583                    listener.onCallUssdMessageReceived(ImsCall.this, mode, ussdMessage);
2584                } catch (Throwable t) {
2585                    loge("callSessionUssdMessageReceived :: ", t);
2586                }
2587            }
2588        }
2589
2590        @Override
2591        public void callSessionTtyModeReceived(ImsCallSession session, int mode) {
2592            if (VDBG) {
2593                logv("callSessionTtyModeReceived :: mode=" + mode);
2594            }
2595
2596            ImsCall.Listener listener;
2597
2598            synchronized(ImsCall.this) {
2599                listener = mListener;
2600            }
2601
2602            if (listener != null) {
2603                try {
2604                    listener.onCallSessionTtyModeReceived(ImsCall.this, mode);
2605                } catch (Throwable t) {
2606                    loge("callSessionTtyModeReceived :: ", t);
2607                }
2608            }
2609        }
2610    }
2611
2612    /**
2613     * Report a new conference state to the current {@link ImsCall} and inform listeners of the
2614     * change.  Marked as {@code VisibleForTesting} so that the
2615     * {@code com.android.internal.telephony.TelephonyTester} class can inject a test conference
2616     * event package into a regular ongoing IMS call.
2617     *
2618     * @param state The {@link ImsConferenceState}.
2619     */
2620    @VisibleForTesting
2621    public void conferenceStateUpdated(ImsConferenceState state) {
2622        Listener listener;
2623
2624        synchronized(this) {
2625            notifyConferenceStateUpdated(state);
2626            listener = mListener;
2627        }
2628
2629        if (listener != null) {
2630            try {
2631                listener.onCallConferenceStateUpdated(this, state);
2632            } catch (Throwable t) {
2633                loge("callSessionConferenceStateUpdated :: ", t);
2634            }
2635        }
2636    }
2637
2638    /**
2639     * Provides a human-readable string representation of an update request.
2640     *
2641     * @param updateRequest The update request.
2642     * @return The string representation.
2643     */
2644    private String updateRequestToString(int updateRequest) {
2645        switch (updateRequest) {
2646            case UPDATE_NONE:
2647                return "NONE";
2648            case UPDATE_HOLD:
2649                return "HOLD";
2650            case UPDATE_HOLD_MERGE:
2651                return "HOLD_MERGE";
2652            case UPDATE_RESUME:
2653                return "RESUME";
2654            case UPDATE_MERGE:
2655                return "MERGE";
2656            case UPDATE_EXTEND_TO_CONFERENCE:
2657                return "EXTEND_TO_CONFERENCE";
2658            case UPDATE_UNSPECIFIED:
2659                return "UNSPECIFIED";
2660            default:
2661                return "UNKNOWN";
2662        }
2663    }
2664
2665    /**
2666     * Clears the merge peer for this call, ensuring that the peer's connection to this call is also
2667     * severed at the same time.
2668     */
2669    private void clearMergePeer() {
2670        mUpdateRequest = UPDATE_NONE;
2671
2672        if (mMergeHost != null) {
2673            mMergeHost.mMergePeer = null;
2674            mMergeHost.mUpdateRequest = UPDATE_NONE;
2675            mMergeHost = null;
2676        }
2677
2678        if (mMergePeer != null) {
2679            mMergePeer.mMergeHost = null;
2680            mMergePeer.mUpdateRequest = UPDATE_NONE;
2681            mMergePeer = null;
2682        }
2683    }
2684
2685    /**
2686     * Sets the merge peer for the current call.  The merge peer is the background call that will be
2687     * merged into this call.  On the merge peer, sets the merge host to be this call.
2688     *
2689     * @param mergePeer The peer call to be merged into this one.
2690     */
2691    private void setMergePeer(ImsCall mergePeer) {
2692        mMergePeer = mergePeer;
2693        mMergeHost = null;
2694
2695        mergePeer.mMergeHost = ImsCall.this;
2696        mergePeer.mMergePeer = null;
2697    }
2698
2699    /**
2700     * Sets the merge hody for the current call.  The merge host is the foreground call this call
2701     * will be merged into.  On the merge host, sets the merge peer to be this call.
2702     *
2703     * @param mergeHost The merge host this call will be merged into.
2704     */
2705    public void setMergeHost(ImsCall mergeHost) {
2706        mMergeHost = mergeHost;
2707        mMergePeer = null;
2708
2709        mergeHost.mMergeHost = null;
2710        mergeHost.mMergePeer = ImsCall.this;
2711    }
2712
2713    /**
2714     * Determines if the current call is in the process of merging with another call or conference.
2715     *
2716     * @return {@code true} if in the process of merging.
2717     */
2718    private boolean isMerging() {
2719        return mMergePeer != null || mMergeHost != null;
2720    }
2721
2722    /**
2723     * Determines if the current call is the host of the merge.
2724     *
2725     * @return {@code true} if the call is the merge host.
2726     */
2727    private boolean isMergeHost() {
2728        return mMergePeer != null && mMergeHost == null;
2729    }
2730
2731    /**
2732     * Determines if the current call is the peer of the merge.
2733     *
2734     * @return {@code true} if the call is the merge peer.
2735     */
2736    private boolean isMergePeer() {
2737        return mMergePeer == null && mMergeHost != null;
2738    }
2739
2740    /**
2741     * Determines if the call session is pending merge into a conference or not.
2742     *
2743     * @return {@code true} if a merge into a conference is pending, {@code false} otherwise.
2744     */
2745    private boolean isCallSessionMergePending() {
2746        return mCallSessionMergePending;
2747    }
2748
2749    /**
2750     * Sets flag indicating whether the call session is pending merge into a conference or not.
2751     *
2752     * @param callSessionMergePending {@code true} if a merge into the conference is pending,
2753     *      {@code false} otherwise.
2754     */
2755    private void setCallSessionMergePending(boolean callSessionMergePending) {
2756        mCallSessionMergePending = callSessionMergePending;
2757    }
2758
2759    /**
2760     * Determines if there is a conference merge in process.  If there is a merge in process,
2761     * determines if both the merge host and peer sessions have completed the merge process.  This
2762     * means that we have received terminate or hold signals for the sessions, indicating that they
2763     * are no longer in the process of being merged into the conference.
2764     * <p>
2765     * The sessions are considered to have merged if: both calls are still in {@link #UPDATE_MERGE}
2766     * state, both sessions are not waiting to be merged into the conference, and the transient
2767     * conference session is alive.
2768     *
2769     * @return {@code true} where the host and peer sessions have finished merging into the
2770     *      conference, {@code false} if the merge has not yet completed, and {@code false} if there
2771     *      is no conference merge in progress.
2772     */
2773    private boolean hasCallSessionMergeCompleted() {
2774        // First, determine if there is even a merge in progress by checking if this call and its
2775        // peer/host are set to UPDATE_MERGE.
2776        boolean isMergeInProgress = mUpdateRequest == UPDATE_MERGE ||
2777                (isMergeHost() && mMergePeer.mUpdateRequest == UPDATE_MERGE) ||
2778                (isMergePeer() && mMergeHost.mUpdateRequest == UPDATE_MERGE);
2779
2780        if (!isMergeInProgress) {
2781            return false;
2782        }
2783
2784        // There is a merge in progress, so check the sessions to ensure:
2785        // 1. Both calls have completed being merged (or failing to merge) into the conference.
2786        // 2. The transient conference session is alive.
2787        if (isMergeHost()) {
2788            return !isCallSessionMergePending() &&
2789                    !mMergePeer.isCallSessionMergePending() &&
2790                    mTransientConferenceSession != null && mTransientConferenceSession.isAlive();
2791        } else if (isMergePeer()) {
2792            return !isCallSessionMergePending() &&
2793                    !mMergeHost.isCallSessionMergePending() &&
2794                    mMergeHost.mTransientConferenceSession != null &&
2795                    mMergeHost.mTransientConferenceSession.isAlive();
2796        }
2797
2798        // Realistically this shouldn't happen, but best to be safe.
2799        loge("hasCallSessionMergeCompleted : merge in progress but call is neither host nor peer.");
2800        return false;
2801    }
2802
2803    /**
2804     * Provides a string representation of the {@link ImsCall}.  Primarily intended for use in log
2805     * statements.
2806     *
2807     * @return String representation of call.
2808     */
2809    @Override
2810    public String toString() {
2811        StringBuilder sb = new StringBuilder();
2812        sb.append("[ImsCall objId:");
2813        sb.append(System.identityHashCode(this));
2814        sb.append(" onHold:");
2815        sb.append(isOnHold() ? "Y" : "N");
2816        sb.append(" mute:");
2817        sb.append(isMuted() ? "Y" : "N");
2818        sb.append(" updateRequest:");
2819        sb.append(updateRequestToString(mUpdateRequest));
2820        sb.append(" merging:");
2821        sb.append(isMerging() ? "Y" : "N");
2822        if (isMerging()) {
2823            if (isMergePeer()) {
2824                sb.append("P");
2825            } else {
2826                sb.append("H");
2827            }
2828        }
2829        sb.append(" merged:");
2830        sb.append(isMerged() ? "Y" : "N");
2831        sb.append(" multiParty:");
2832        sb.append(isMultiparty() ? "Y" : "N");
2833        sb.append(" session:");
2834        sb.append(mSession);
2835        sb.append(" transientSession:");
2836        sb.append(mTransientConferenceSession);
2837        sb.append("]");
2838        return sb.toString();
2839    }
2840}
2841