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