ImsCall.java revision 095839a66699a128c76cd5002459fcba7c3a9a0d
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                mHold = false;
1932            }
1933
1934            if (listener != null) {
1935                try {
1936                    listener.onCallResumed(ImsCall.this);
1937                } catch (Throwable t) {
1938                    loge("callSessionResumed :: ", t);
1939                }
1940            }
1941        }
1942
1943        @Override
1944        public void callSessionResumeFailed(ImsCallSession session, ImsReasonInfo reasonInfo) {
1945            if (isTransientConferenceSession(session)) {
1946                log("callSessionResumeFailed :: not supported for transient conference session=" +
1947                        session);
1948                return;
1949            }
1950
1951            if (VDBG) {
1952                logv("callSessionResumeFailed :: reasonInfo=" + reasonInfo);
1953            }
1954
1955            ImsCall.Listener listener;
1956
1957            synchronized(ImsCall.this) {
1958                listener = mListener;
1959                mUpdateRequest = UPDATE_NONE;
1960            }
1961
1962            if (listener != null) {
1963                try {
1964                    listener.onCallResumeFailed(ImsCall.this, reasonInfo);
1965                } catch (Throwable t) {
1966                    loge("callSessionResumeFailed :: ", t);
1967                }
1968            }
1969        }
1970
1971        @Override
1972        public void callSessionResumeReceived(ImsCallSession session, ImsCallProfile profile) {
1973            if (isTransientConferenceSession(session)) {
1974                log("callSessionResumeReceived :: not supported for transient conference session=" +
1975                        session);
1976                return;
1977            }
1978
1979            if (VDBG) {
1980                logv("callSessionResumeReceived :: profile=" + profile);
1981            }
1982
1983            ImsCall.Listener listener;
1984
1985            synchronized(ImsCall.this) {
1986                listener = mListener;
1987                mCallProfile = profile;
1988            }
1989
1990            if (listener != null) {
1991                try {
1992                    listener.onCallResumeReceived(ImsCall.this);
1993                } catch (Throwable t) {
1994                    loge("callSessionResumeReceived :: ", t);
1995                }
1996            }
1997        }
1998
1999        @Override
2000        public void callSessionMergeStarted(ImsCallSession session,
2001                ImsCallSession newSession, ImsCallProfile profile) {
2002            if (VDBG) {
2003                String newSessionString = newSession == null ? "null" : newSession.toString();
2004                logv("callSessionMergeStarted :: newSession=" + newSessionString +
2005                        ", profile=" + profile);
2006            }
2007
2008            if (mUpdateRequest != UPDATE_MERGE) {
2009                // Odd, we are not in the midst of merging anything.
2010                log("callSessionMergeStarted :: no merge in progress.");
2011                return;
2012            }
2013
2014            // There are 2 ways that we can go here.  If the session that supplied the params
2015            // is not null, then it is the new session that represents the new conference
2016            // if the merge succeeds. If it is null, the merge is happening on our current
2017            // ImsCallSession.
2018            if (session == null) {
2019                // Everything is already set up and we just need to make sure
2020                // that we properly respond to all the future callbacks about
2021                // this merge.
2022                if (DBG) {
2023                    log("callSessionMergeStarted :: merging into existing ImsCallSession");
2024                }
2025                return;
2026            }
2027
2028            if (DBG) {
2029                log("callSessionMergeStarted ::  setting our transient ImsCallSession");
2030            }
2031
2032            // If we are here, this means that we are creating a new conference and
2033            // we need to do some extra work around managing a new ImsCallSession that
2034            // could represent our new ImsCallSession if the merge succeeds.
2035            synchronized(ImsCall.this) {
2036                // Keep track of this session for future callbacks to indicate success
2037                // or failure of this merge.
2038                mTransientConferenceSession = newSession;
2039                mTransientConferenceSession.setListener(createCallSessionListener());
2040            }
2041
2042            return;
2043        }
2044
2045        @Override
2046        public void callSessionMergeComplete(ImsCallSession session) {
2047            if (VDBG) {
2048                logv("callSessionMergeComplete ::");
2049            }
2050            if (mUpdateRequest != UPDATE_MERGE) {
2051                // Odd, we are not in the midst of merging anything.
2052                log("callSessionMergeComplete :: no merge in progress.");
2053                return;
2054            }
2055            // Let's let our parent ImsCall now that we received notification that
2056            // the merge was completed so we can set up our internal state properly
2057            processMergeComplete();
2058        }
2059
2060        @Override
2061        public void callSessionMergeFailed(ImsCallSession session, ImsReasonInfo reasonInfo) {
2062            if (VDBG) {
2063                String reasonInfoString = reasonInfo == null ? "null" : reasonInfo.toString();
2064                logv("callSessionMergeFailed :: reasonInfo=" + reasonInfoString);
2065            }
2066            if (mUpdateRequest != UPDATE_MERGE && !isMerging()) {
2067                // Odd, we are not in the midst of merging anything.
2068                log("callSessionMergeFailed :: no merge in progress.");
2069                return;
2070            }
2071            // Let's tell our parent ImsCall that the merge has failed and we need to clean
2072            // up any temporary, transient state.
2073            processMergeFailed(reasonInfo);
2074        }
2075
2076        @Override
2077        public void callSessionUpdated(ImsCallSession session, ImsCallProfile profile) {
2078            if (isTransientConferenceSession(session)) {
2079                log("callSessionUpdated :: not supported for transient conference session=" +
2080                        session);
2081                return;
2082            }
2083
2084            if (VDBG) {
2085                logv("callSessionUpdated :: profile=" + profile);
2086            }
2087
2088            ImsCall.Listener listener;
2089
2090            synchronized(ImsCall.this) {
2091                listener = mListener;
2092                mCallProfile = profile;
2093                mUpdateRequest = UPDATE_NONE;
2094            }
2095
2096            if (listener != null) {
2097                try {
2098                    listener.onCallUpdated(ImsCall.this);
2099                } catch (Throwable t) {
2100                    loge("callSessionUpdated :: ", t);
2101                }
2102            }
2103        }
2104
2105        @Override
2106        public void callSessionUpdateFailed(ImsCallSession session, ImsReasonInfo reasonInfo) {
2107            if (isTransientConferenceSession(session)) {
2108                log("callSessionUpdateFailed :: not supported for transient conference session=" +
2109                        session);
2110                return;
2111            }
2112
2113            if (VDBG) {
2114                logv("callSessionUpdateFailed :: reasonInfo=" + reasonInfo);
2115            }
2116
2117            ImsCall.Listener listener;
2118
2119            synchronized(ImsCall.this) {
2120                listener = mListener;
2121                mUpdateRequest = UPDATE_NONE;
2122            }
2123
2124            if (listener != null) {
2125                try {
2126                    listener.onCallUpdateFailed(ImsCall.this, reasonInfo);
2127                } catch (Throwable t) {
2128                    loge("callSessionUpdateFailed :: ", t);
2129                }
2130            }
2131        }
2132
2133        @Override
2134        public void callSessionUpdateReceived(ImsCallSession session, ImsCallProfile profile) {
2135            if (isTransientConferenceSession(session)) {
2136                log("callSessionUpdateReceived :: not supported for transient conference " +
2137                        "session=" + session);
2138                return;
2139            }
2140
2141            if (VDBG) {
2142                logv("callSessionUpdateReceived :: profile=" + profile);
2143            }
2144
2145            ImsCall.Listener listener;
2146
2147            synchronized(ImsCall.this) {
2148                listener = mListener;
2149                mProposedCallProfile = profile;
2150                mUpdateRequest = UPDATE_UNSPECIFIED;
2151            }
2152
2153            if (listener != null) {
2154                try {
2155                    listener.onCallUpdateReceived(ImsCall.this);
2156                } catch (Throwable t) {
2157                    loge("callSessionUpdateReceived :: ", t);
2158                }
2159            }
2160        }
2161
2162        @Override
2163        public void callSessionConferenceExtended(ImsCallSession session, ImsCallSession newSession,
2164                ImsCallProfile profile) {
2165            if (isTransientConferenceSession(session)) {
2166                log("callSessionConferenceExtended :: not supported for transient conference " +
2167                        "session=" + session);
2168                return;
2169            }
2170
2171            if (VDBG) {
2172                logv("callSessionConferenceExtended :: newSession=" + newSession + ", profile="
2173                        + profile);
2174            }
2175
2176            ImsCall newCall = createNewCall(newSession, profile);
2177
2178            if (newCall == null) {
2179                callSessionConferenceExtendFailed(session, new ImsReasonInfo());
2180                return;
2181            }
2182
2183            ImsCall.Listener listener;
2184
2185            synchronized(ImsCall.this) {
2186                listener = mListener;
2187                mUpdateRequest = UPDATE_NONE;
2188            }
2189
2190            if (listener != null) {
2191                try {
2192                    listener.onCallConferenceExtended(ImsCall.this, newCall);
2193                } catch (Throwable t) {
2194                    loge("callSessionConferenceExtended :: ", t);
2195                }
2196            }
2197        }
2198
2199        @Override
2200        public void callSessionConferenceExtendFailed(ImsCallSession session,
2201                ImsReasonInfo reasonInfo) {
2202            if (isTransientConferenceSession(session)) {
2203                log("callSessionConferenceExtendFailed :: not supported for transient " +
2204                        "conference session=" + session);
2205                return;
2206            }
2207
2208            if (DBG) {
2209                log("callSessionConferenceExtendFailed :: imsCall=" + ImsCall.this +
2210                        ", reasonInfo=" + reasonInfo);
2211            }
2212
2213            ImsCall.Listener listener;
2214
2215            synchronized(ImsCall.this) {
2216                listener = mListener;
2217                mUpdateRequest = UPDATE_NONE;
2218            }
2219
2220            if (listener != null) {
2221                try {
2222                    listener.onCallConferenceExtendFailed(ImsCall.this, reasonInfo);
2223                } catch (Throwable t) {
2224                    loge("callSessionConferenceExtendFailed :: ", t);
2225                }
2226            }
2227        }
2228
2229        @Override
2230        public void callSessionConferenceExtendReceived(ImsCallSession session,
2231                ImsCallSession newSession, ImsCallProfile profile) {
2232            if (isTransientConferenceSession(session)) {
2233                log("callSessionConferenceExtendReceived :: not supported for transient " +
2234                        "conference session" + session);
2235                return;
2236            }
2237
2238            if (VDBG) {
2239                logv("callSessionConferenceExtendReceived :: newSession=" + newSession +
2240                        ", profile=" + profile);
2241            }
2242
2243            ImsCall newCall = createNewCall(newSession, profile);
2244
2245            if (newCall == null) {
2246                // Should all the calls be terminated...???
2247                return;
2248            }
2249
2250            ImsCall.Listener listener;
2251
2252            synchronized(ImsCall.this) {
2253                listener = mListener;
2254            }
2255
2256            if (listener != null) {
2257                try {
2258                    listener.onCallConferenceExtendReceived(ImsCall.this, newCall);
2259                } catch (Throwable t) {
2260                    loge("callSessionConferenceExtendReceived :: ", t);
2261                }
2262            }
2263        }
2264
2265        @Override
2266        public void callSessionInviteParticipantsRequestDelivered(ImsCallSession session) {
2267            if (isTransientConferenceSession(session)) {
2268                log("callSessionInviteParticipantsRequestDelivered :: not supported for " +
2269                        "conference session=" + session);
2270                return;
2271            }
2272
2273            if (VDBG) {
2274                logv("callSessionInviteParticipantsRequestDelivered ::");
2275            }
2276
2277            ImsCall.Listener listener;
2278
2279            synchronized(ImsCall.this) {
2280                listener = mListener;
2281            }
2282
2283            if (listener != null) {
2284                try {
2285                    listener.onCallInviteParticipantsRequestDelivered(ImsCall.this);
2286                } catch (Throwable t) {
2287                    loge("callSessionInviteParticipantsRequestDelivered :: ", t);
2288                }
2289            }
2290        }
2291
2292        @Override
2293        public void callSessionInviteParticipantsRequestFailed(ImsCallSession session,
2294                ImsReasonInfo reasonInfo) {
2295            if (isTransientConferenceSession(session)) {
2296                log("callSessionInviteParticipantsRequestFailed :: not supported for " +
2297                        "conference session=" + session);
2298                return;
2299            }
2300
2301            if (VDBG) {
2302                logv("callSessionInviteParticipantsRequestFailed :: reasonInfo=" + reasonInfo);
2303            }
2304
2305            ImsCall.Listener listener;
2306
2307            synchronized(ImsCall.this) {
2308                listener = mListener;
2309            }
2310
2311            if (listener != null) {
2312                try {
2313                    listener.onCallInviteParticipantsRequestFailed(ImsCall.this, reasonInfo);
2314                } catch (Throwable t) {
2315                    loge("callSessionInviteParticipantsRequestFailed :: ", t);
2316                }
2317            }
2318        }
2319
2320        @Override
2321        public void callSessionRemoveParticipantsRequestDelivered(ImsCallSession session) {
2322            if (isTransientConferenceSession(session)) {
2323                log("callSessionRemoveParticipantsRequestDelivered :: not supported for " +
2324                        "conference session=" + session);
2325                return;
2326            }
2327
2328            if (VDBG) {
2329                logv("callSessionRemoveParticipantsRequestDelivered ::");
2330            }
2331
2332            ImsCall.Listener listener;
2333
2334            synchronized(ImsCall.this) {
2335                listener = mListener;
2336            }
2337
2338            if (listener != null) {
2339                try {
2340                    listener.onCallRemoveParticipantsRequestDelivered(ImsCall.this);
2341                } catch (Throwable t) {
2342                    loge("callSessionRemoveParticipantsRequestDelivered :: ", t);
2343                }
2344            }
2345        }
2346
2347        @Override
2348        public void callSessionRemoveParticipantsRequestFailed(ImsCallSession session,
2349                ImsReasonInfo reasonInfo) {
2350            if (isTransientConferenceSession(session)) {
2351                log("callSessionRemoveParticipantsRequestFailed :: not supported for " +
2352                        "conference session=" + session);
2353                return;
2354            }
2355
2356            if (VDBG) {
2357                logv("callSessionRemoveParticipantsRequestFailed :: reasonInfo=" + reasonInfo);
2358            }
2359
2360            ImsCall.Listener listener;
2361
2362            synchronized(ImsCall.this) {
2363                listener = mListener;
2364            }
2365
2366            if (listener != null) {
2367                try {
2368                    listener.onCallRemoveParticipantsRequestFailed(ImsCall.this, reasonInfo);
2369                } catch (Throwable t) {
2370                    loge("callSessionRemoveParticipantsRequestFailed :: ", t);
2371                }
2372            }
2373        }
2374
2375        @Override
2376        public void callSessionConferenceStateUpdated(ImsCallSession session,
2377                ImsConferenceState state) {
2378            if (isTransientConferenceSession(session)) {
2379                log("callSessionConferenceStateUpdated :: not supported for transient " +
2380                        "conference session=" + session);
2381                return;
2382            }
2383
2384            if (VDBG) {
2385                logv("callSessionConferenceStateUpdated :: state=" + state);
2386            }
2387
2388            conferenceStateUpdated(state);
2389        }
2390
2391        @Override
2392        public void callSessionUssdMessageReceived(ImsCallSession session, int mode,
2393                String ussdMessage) {
2394            if (isTransientConferenceSession(session)) {
2395                log("callSessionUssdMessageReceived :: not supported for transient " +
2396                        "conference session=" + session);
2397                return;
2398            }
2399
2400            if (VDBG) {
2401                logv("callSessionUssdMessageReceived :: mode=" + mode + ", ussdMessage=" +
2402                        ussdMessage);
2403            }
2404
2405            ImsCall.Listener listener;
2406
2407            synchronized(ImsCall.this) {
2408                listener = mListener;
2409            }
2410
2411            if (listener != null) {
2412                try {
2413                    listener.onCallUssdMessageReceived(ImsCall.this, mode, ussdMessage);
2414                } catch (Throwable t) {
2415                    loge("callSessionUssdMessageReceived :: ", t);
2416                }
2417            }
2418        }
2419
2420        @Override
2421        public void callSessionTtyModeReceived(ImsCallSession session, int mode) {
2422            if (VDBG) {
2423                logv("callSessionTtyModeReceived :: mode=" + mode);
2424            }
2425
2426            int settingsTtyMode = Settings.Secure.getInt(
2427                    mContext.getContentResolver(),
2428                    Settings.Secure.PREFERRED_TTY_MODE,
2429                    TelecomManager.TTY_MODE_OFF);
2430            if (settingsTtyMode == TelecomManager.TTY_MODE_OFF) {
2431                // Notify the user that TTY mode changed in the far device
2432                int resId = 0;
2433                switch (mode) {
2434                    case TelecomManager.TTY_MODE_FULL:
2435                        resId = com.android.internal.R.string.peerTtyModeFull;
2436                        break;
2437                    case TelecomManager.TTY_MODE_HCO:
2438                        resId = com.android.internal.R.string.peerTtyModeHco;
2439                        break;
2440                    case TelecomManager.TTY_MODE_VCO:
2441                        resId = com.android.internal.R.string.peerTtyModeVco;
2442                        break;
2443                    case TelecomManager.TTY_MODE_OFF:
2444                        resId = com.android.internal.R.string.peerTtyModeOff;
2445                        break;
2446                }
2447                if (resId != 0) {
2448                    Toast.makeText(mContext, resId, Toast.LENGTH_SHORT).show();
2449                }
2450            }
2451        }
2452    }
2453
2454    /**
2455     * Report a new conference state to the current {@link ImsCall} and inform listeners of the
2456     * change.  Marked as {@code VisibleForTesting} so that the
2457     * {@code com.android.internal.telephony.TelephonyTester} class can inject a test conference
2458     * event package into a regular ongoing IMS call.
2459     *
2460     * @param state The {@link ImsConferenceState}.
2461     */
2462    @VisibleForTesting
2463    public void conferenceStateUpdated(ImsConferenceState state) {
2464        Listener listener;
2465
2466        synchronized(this) {
2467            notifyConferenceStateUpdated(state);
2468            listener = mListener;
2469        }
2470
2471        if (listener != null) {
2472            try {
2473                listener.onCallConferenceStateUpdated(this, state);
2474            } catch (Throwable t) {
2475                loge("callSessionConferenceStateUpdated :: ", t);
2476            }
2477        }
2478    }
2479
2480    /**
2481     * Provides a human-readable string representation of an update request.
2482     *
2483     * @param updateRequest The update request.
2484     * @return The string representation.
2485     */
2486    private String updateRequestToString(int updateRequest) {
2487        switch (updateRequest) {
2488            case UPDATE_NONE:
2489                return "NONE";
2490            case UPDATE_HOLD:
2491                return "HOLD";
2492            case UPDATE_HOLD_MERGE:
2493                return "HOLD_MERGE";
2494            case UPDATE_RESUME:
2495                return "RESUME";
2496            case UPDATE_MERGE:
2497                return "MERGE";
2498            case UPDATE_EXTEND_TO_CONFERENCE:
2499                return "EXTEND_TO_CONFERENCE";
2500            case UPDATE_UNSPECIFIED:
2501                return "UNSPECIFIED";
2502            default:
2503                return "UNKNOWN";
2504        }
2505    }
2506
2507    /**
2508     * Clears the merge peer for this call, ensuring that the peer's connection to this call is also
2509     * severed at the same time.
2510     */
2511    private void clearMergePeer() {
2512        if (mMergeHost != null) {
2513            mMergeHost.mMergePeer = null;
2514            mMergeHost = null;
2515        }
2516
2517        if (mMergePeer != null) {
2518            mMergePeer.mMergeHost = null;
2519            mMergePeer = null;
2520        }
2521    }
2522
2523    /**
2524     * Sets the merge peer for the current call.  The merge peer is the background call that will be
2525     * merged into this call.  On the merge peer, sets the merge host to be this call.
2526     *
2527     * @param mergePeer The peer call to be merged into this one.
2528     */
2529    private void setMergePeer(ImsCall mergePeer) {
2530        mMergePeer = mergePeer;
2531        mMergeHost = null;
2532
2533        mergePeer.mMergeHost = ImsCall.this;
2534        mergePeer.mMergePeer = null;
2535    }
2536
2537    /**
2538     * Sets the merge hody for the current call.  The merge host is the foreground call this call
2539     * will be merged into.  On the merge host, sets the merge peer to be this call.
2540     *
2541     * @param mergeHost The merge host this call will be merged into.
2542     */
2543    public void setMergeHost(ImsCall mergeHost) {
2544        mMergeHost = mergeHost;
2545        mMergePeer = null;
2546
2547        mergeHost.mMergeHost = null;
2548        mergeHost.mMergePeer = ImsCall.this;
2549    }
2550
2551    /**
2552     * Determines if the current call is in the process of merging with another call or conference.
2553     *
2554     * @return {@code true} if in the process of merging.
2555     */
2556    private boolean isMerging() {
2557        return mMergePeer != null || mMergeHost != null;
2558    }
2559
2560    /**
2561     * Determines if the current call is the host of the merge.
2562     *
2563     * @return {@code true} if the call is the merge host.
2564     */
2565    private boolean isMergeHost() {
2566        return mMergePeer != null && mMergeHost == null;
2567    }
2568
2569    /**
2570     * Determines if the current call is the peer of the merge.
2571     *
2572     * @return {@code true} if the call is the merge peer.
2573     */
2574    private boolean isMergePeer() {
2575        return mMergePeer == null && mMergeHost != null;
2576    }
2577
2578    /**
2579     * Provides a string representation of the {@link ImsCall}.  Primarily intended for use in log
2580     * statements.
2581     *
2582     * @return String representation of call.
2583     */
2584    @Override
2585    public String toString() {
2586        StringBuilder sb = new StringBuilder();
2587        sb.append("[ImsCall objId:");
2588        sb.append(System.identityHashCode(this));
2589        sb.append(" onHold:");
2590        sb.append(isOnHold() ? "Y" : "N");
2591        sb.append(" mute:");
2592        sb.append(isMuted() ? "Y" : "N");
2593        sb.append(" updateRequest:");
2594        sb.append(updateRequestToString(mUpdateRequest));
2595        sb.append(" multiParty:");
2596        sb.append(isMultiparty() ? "Y" : "N");
2597        sb.append(" session:");
2598        sb.append(mSession);
2599        sb.append(" transientSession:");
2600        sb.append(mTransientConferenceSession);
2601        sb.append("]");
2602        return sb.toString();
2603    }
2604}
2605