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