ImsCall.java revision a6fbae9f8c793118e008a98d3576df316bf0364a
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            if ((!isMultiparty() && !bgCall.isMultiparty()) || isMultiparty()) {
1143                // If neither call is multiparty, the current call is the merge host and the bg call
1144                // is the merge peer (ie we're starting a new conference).
1145                // OR
1146                // If this call is multiparty, it is the merge host and the other call is the merge
1147                // peer.
1148                setMergePeer(bgCall);
1149            } else {
1150                // If the bg call is multiparty, it is the merge host.
1151                setMergeHost(bgCall);
1152            }
1153        }
1154        merge();
1155    }
1156
1157    /**
1158     * Updates the current call's properties (ex. call mode change: video upgrade / downgrade).
1159     */
1160    public void update(int callType, ImsStreamMediaProfile mediaProfile) throws ImsException {
1161        if (VDBG) {
1162            logv("update ::");
1163        }
1164
1165        if (isOnHold()) {
1166            if (DBG) {
1167                log("update :: call is on hold");
1168            }
1169            throw new ImsException("Not in a call to update call",
1170                    ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
1171        }
1172
1173        synchronized(mLockObj) {
1174            if (mUpdateRequest != UPDATE_NONE) {
1175                if (DBG) {
1176                    log("update :: update is in progress; request=" +
1177                            updateRequestToString(mUpdateRequest));
1178                }
1179                throw new ImsException("Call update is in progress",
1180                        ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
1181            }
1182
1183            if (mSession == null) {
1184                loge("update :: ");
1185                throw new ImsException("No call session",
1186                        ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1187            }
1188
1189            mSession.update(callType, mediaProfile);
1190            mUpdateRequest = UPDATE_UNSPECIFIED;
1191        }
1192    }
1193
1194    /**
1195     * Extends this call (1-to-1 call) to the conference call
1196     * inviting the specified participants to.
1197     *
1198     */
1199    public void extendToConference(String[] participants) throws ImsException {
1200        if (VDBG) {
1201            logv("extendToConference ::");
1202        }
1203
1204        if (isOnHold()) {
1205            if (DBG) {
1206                log("extendToConference :: call is on hold");
1207            }
1208            throw new ImsException("Not in a call to extend a call to conference",
1209                    ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
1210        }
1211
1212        synchronized(mLockObj) {
1213            if (mUpdateRequest != UPDATE_NONE) {
1214                if (DBG) {
1215                    log("extendToConference :: update is in progress; request=" +
1216                            updateRequestToString(mUpdateRequest));
1217                }
1218                throw new ImsException("Call update is in progress",
1219                        ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
1220            }
1221
1222            if (mSession == null) {
1223                loge("extendToConference :: ");
1224                throw new ImsException("No call session",
1225                        ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1226            }
1227
1228            mSession.extendToConference(participants);
1229            mUpdateRequest = UPDATE_EXTEND_TO_CONFERENCE;
1230        }
1231    }
1232
1233    /**
1234     * Requests the conference server to invite an additional participants to the conference.
1235     *
1236     */
1237    public void inviteParticipants(String[] participants) throws ImsException {
1238        if (VDBG) {
1239            logv("inviteParticipants ::");
1240        }
1241
1242        synchronized(mLockObj) {
1243            if (mSession == null) {
1244                loge("inviteParticipants :: ");
1245                throw new ImsException("No call session",
1246                        ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1247            }
1248
1249            mSession.inviteParticipants(participants);
1250        }
1251    }
1252
1253    /**
1254     * Requests the conference server to remove the specified participants from the conference.
1255     *
1256     */
1257    public void removeParticipants(String[] participants) throws ImsException {
1258        if (DBG) {
1259            log("removeParticipants ::");
1260        }
1261
1262        synchronized(mLockObj) {
1263            if (mSession == null) {
1264                loge("removeParticipants :: ");
1265                throw new ImsException("No call session",
1266                        ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1267            }
1268
1269            mSession.removeParticipants(participants);
1270        }
1271    }
1272
1273
1274    /**
1275     * Sends a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2833</a>,
1276     * event 0 ~ 9 maps to decimal value 0 ~ 9, '*' to 10, '#' to 11, event 'A' ~ 'D' to 12 ~ 15,
1277     * and event flash to 16. Currently, event flash is not supported.
1278     *
1279     * @param char that represents the DTMF digit to send.
1280     */
1281    public void sendDtmf(char c) {
1282        sendDtmf(c, null);
1283    }
1284
1285    /**
1286     * Sends a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2833</a>,
1287     * event 0 ~ 9 maps to decimal value 0 ~ 9, '*' to 10, '#' to 11, event 'A' ~ 'D' to 12 ~ 15,
1288     * and event flash to 16. Currently, event flash is not supported.
1289     *
1290     * @param c that represents the DTMF to send. '0' ~ '9', 'A' ~ 'D', '*', '#' are valid inputs.
1291     * @param result the result message to send when done.
1292     */
1293    public void sendDtmf(char c, Message result) {
1294        if (VDBG) {
1295            logv("sendDtmf :: code=" + c);
1296        }
1297
1298        synchronized(mLockObj) {
1299            if (mSession != null) {
1300                mSession.sendDtmf(c);
1301            }
1302        }
1303
1304        if (result != null) {
1305            result.sendToTarget();
1306        }
1307    }
1308
1309    /**
1310     * Start a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2833</a>,
1311     * event 0 ~ 9 maps to decimal value 0 ~ 9, '*' to 10, '#' to 11, event 'A' ~ 'D' to 12 ~ 15,
1312     * and event flash to 16. Currently, event flash is not supported.
1313     *
1314     * @param c that represents the DTMF to send. '0' ~ '9', 'A' ~ 'D', '*', '#' are valid inputs.
1315     */
1316    public void startDtmf(char c) {
1317        if (DBG) {
1318            log("startDtmf :: session=" + mSession + ", code=" + c);
1319        }
1320
1321        synchronized(mLockObj) {
1322            if (mSession != null) {
1323                mSession.startDtmf(c);
1324            }
1325        }
1326    }
1327
1328    /**
1329     * Stop a DTMF code.
1330     */
1331    public void stopDtmf() {
1332        if (DBG) {
1333            log("stopDtmf :: session=" + mSession);
1334        }
1335
1336        synchronized(mLockObj) {
1337            if (mSession != null) {
1338                mSession.stopDtmf();
1339            }
1340        }
1341    }
1342
1343    /**
1344     * Sends an USSD message.
1345     *
1346     * @param ussdMessage USSD message to send
1347     */
1348    public void sendUssd(String ussdMessage) throws ImsException {
1349        if (VDBG) {
1350            logv("sendUssd :: ussdMessage=" + ussdMessage);
1351        }
1352
1353        synchronized(mLockObj) {
1354            if (mSession == null) {
1355                loge("sendUssd :: ");
1356                throw new ImsException("No call session",
1357                        ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1358            }
1359
1360            mSession.sendUssd(ussdMessage);
1361        }
1362    }
1363
1364    private void clear(ImsReasonInfo lastReasonInfo) {
1365        mInCall = false;
1366        mHold = false;
1367        mUpdateRequest = UPDATE_NONE;
1368        mLastReasonInfo = lastReasonInfo;
1369    }
1370
1371    /**
1372     * Creates an IMS call session listener.
1373     */
1374    private ImsCallSession.Listener createCallSessionListener() {
1375        return new ImsCallSessionListenerProxy();
1376    }
1377
1378    private ImsCall createNewCall(ImsCallSession session, ImsCallProfile profile) {
1379        ImsCall call = new ImsCall(mContext, profile);
1380
1381        try {
1382            call.attachSession(session);
1383        } catch (ImsException e) {
1384            if (call != null) {
1385                call.close();
1386                call = null;
1387            }
1388        }
1389
1390        // Do additional operations...
1391
1392        return call;
1393    }
1394
1395    private ImsStreamMediaProfile createHoldMediaProfile() {
1396        ImsStreamMediaProfile mediaProfile = new ImsStreamMediaProfile();
1397
1398        if (mCallProfile == null) {
1399            return mediaProfile;
1400        }
1401
1402        mediaProfile.mAudioQuality = mCallProfile.mMediaProfile.mAudioQuality;
1403        mediaProfile.mVideoQuality = mCallProfile.mMediaProfile.mVideoQuality;
1404        mediaProfile.mAudioDirection = ImsStreamMediaProfile.DIRECTION_SEND;
1405
1406        if (mediaProfile.mVideoQuality != ImsStreamMediaProfile.VIDEO_QUALITY_NONE) {
1407            mediaProfile.mVideoDirection = ImsStreamMediaProfile.DIRECTION_SEND;
1408        }
1409
1410        return mediaProfile;
1411    }
1412
1413    private ImsStreamMediaProfile createResumeMediaProfile() {
1414        ImsStreamMediaProfile mediaProfile = new ImsStreamMediaProfile();
1415
1416        if (mCallProfile == null) {
1417            return mediaProfile;
1418        }
1419
1420        mediaProfile.mAudioQuality = mCallProfile.mMediaProfile.mAudioQuality;
1421        mediaProfile.mVideoQuality = mCallProfile.mMediaProfile.mVideoQuality;
1422        mediaProfile.mAudioDirection = ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE;
1423
1424        if (mediaProfile.mVideoQuality != ImsStreamMediaProfile.VIDEO_QUALITY_NONE) {
1425            mediaProfile.mVideoDirection = ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE;
1426        }
1427
1428        return mediaProfile;
1429    }
1430
1431    private void enforceConversationMode() {
1432        if (mInCall) {
1433            mHold = false;
1434            mUpdateRequest = UPDATE_NONE;
1435        }
1436    }
1437
1438    private void mergeInternal() {
1439        if (VDBG) {
1440            logv("mergeInternal ::");
1441        }
1442
1443        mSession.merge();
1444        mUpdateRequest = UPDATE_MERGE;
1445    }
1446
1447    private void notifyConferenceSessionTerminated(ImsReasonInfo reasonInfo) {
1448        ImsCall.Listener listener = mListener;
1449        clear(reasonInfo);
1450
1451        if (listener != null) {
1452            try {
1453                listener.onCallTerminated(this, reasonInfo);
1454            } catch (Throwable t) {
1455                loge("notifyConferenceSessionTerminated :: ", t);
1456            }
1457        }
1458    }
1459
1460    private void notifyConferenceStateUpdated(ImsConferenceState state) {
1461        Set<Entry<String, Bundle>> participants = state.mParticipants.entrySet();
1462
1463        if (participants == null) {
1464            return;
1465        }
1466
1467        Iterator<Entry<String, Bundle>> iterator = participants.iterator();
1468        List<ConferenceParticipant> conferenceParticipants = new ArrayList<>(participants.size());
1469        while (iterator.hasNext()) {
1470            Entry<String, Bundle> entry = iterator.next();
1471
1472            String key = entry.getKey();
1473            Bundle confInfo = entry.getValue();
1474            String status = confInfo.getString(ImsConferenceState.STATUS);
1475            String user = confInfo.getString(ImsConferenceState.USER);
1476            String displayName = confInfo.getString(ImsConferenceState.DISPLAY_TEXT);
1477            String endpoint = confInfo.getString(ImsConferenceState.ENDPOINT);
1478
1479            if (DBG) {
1480                log("notifyConferenceStateUpdated :: key=" + key +
1481                        ", status=" + status +
1482                        ", user=" + user +
1483                        ", displayName= " + displayName +
1484                        ", endpoint=" + endpoint);
1485            }
1486
1487            Uri handle = Uri.parse(user);
1488            Uri endpointUri = Uri.parse(endpoint);
1489            int connectionState = ImsConferenceState.getConnectionStateForStatus(status);
1490
1491            ConferenceParticipant conferenceParticipant = new ConferenceParticipant(handle,
1492                    displayName, endpointUri, connectionState);
1493            conferenceParticipants.add(conferenceParticipant);
1494        }
1495
1496        if (!conferenceParticipants.isEmpty() && mListener != null) {
1497            try {
1498                mListener.onConferenceParticipantsStateChanged(this, conferenceParticipants);
1499            } catch (Throwable t) {
1500                loge("notifyConferenceStateUpdated :: ", t);
1501            }
1502        }
1503    }
1504
1505    /**
1506     * Perform all cleanup and notification around the termination of a session.
1507     * Note that there are 2 distinct modes of operation.  The first is when
1508     * we receive a session termination on the primary session when we are
1509     * in the processing of merging.  The second is when we are not merging anything
1510     * and the call is terminated.
1511     *
1512     * @param reasonInfo The reason for the session termination
1513     */
1514    private void processCallTerminated(ImsReasonInfo reasonInfo) {
1515        if (VDBG) {
1516            String reasonString = reasonInfo != null ? reasonInfo.toString() : "null";
1517            logv("processCallTerminated :: reason=" + reasonString);
1518        }
1519
1520        ImsCall.Listener listener = null;
1521
1522        synchronized(ImsCall.this) {
1523            // Handle the case where the original pre-merge session is terminated due to the other
1524            // party disconnecting (or some other failure).
1525            if (mUpdateRequest == UPDATE_MERGE) {
1526                // Since we are in the process of a merge, this trigger means something
1527                // else because it is probably due to the merge happening vs. the
1528                // session is really terminated. Let's flag this and revisit if
1529                // the merge() ends up failing because we will need to take action on the
1530                // mSession in that case since the termination was not due to the merge
1531                // succeeding.
1532                if (DBG) {
1533                    log("processCallTerminated :: burying termination during ongoing merge.");
1534                }
1535                mSessionEndDuringMerge = true;
1536                mSessionEndDuringMergeReasonInfo = reasonInfo;
1537                return;
1538            }
1539
1540            // If in the middle of a merge and call is not the merge host, then we are being
1541            // disconnected due to the merge and should suppress the disconnect tone.
1542            if (isMerging()) {
1543                if (isMergePeer()) {
1544                    setIsMerged(true);
1545                } else {
1546                    // We are the host and are being legitimately disconnected.  Ensure neither call
1547                    // will suppress the disconnect tone.
1548                    mMergePeer.setIsMerged(false);
1549                }
1550                clearMergePeer();
1551            }
1552
1553            // If we are terminating the conference call, notify using conference listeners.
1554            if (isMultiparty()) {
1555                notifyConferenceSessionTerminated(reasonInfo);
1556                return;
1557            } else {
1558                listener = mListener;
1559                clear(reasonInfo);
1560            }
1561        }
1562
1563        if (listener != null) {
1564            try {
1565                listener.onCallTerminated(ImsCall.this, reasonInfo);
1566            } catch (Throwable t) {
1567                loge("callSessionTerminated :: ", t);
1568            }
1569        }
1570    }
1571
1572    /**
1573     * This function determines if the ImsCallSession is our actual ImsCallSession or if is
1574     * the transient session used in the process of creating a conference. This function should only
1575     * be called within  callbacks that are not directly related to conference merging but might
1576     * potentially still be called on the transient ImsCallSession sent to us from
1577     * callSessionMergeStarted() when we don't really care. In those situations, we probably don't
1578     * want to take any action so we need to know that we can return early.
1579     *
1580     * @param session - The {@link ImsCallSession} that the function needs to analyze
1581     * @return true if this is the transient {@link ImsCallSession}, false otherwise.
1582     */
1583    private boolean isTransientConferenceSession(ImsCallSession session) {
1584        if (session != null && session != mSession && session == mTransientConferenceSession) {
1585            return true;
1586        }
1587        return false;
1588    }
1589
1590    /**
1591     * We received a callback from ImsCallSession that a merge was complete. Clean up all
1592     * internal state to represent this state change. This function will be called when
1593     * the transient conference session goes active or we get an explicit merge complete
1594     * callback on the transient session.
1595     *
1596     */
1597    private void processMergeComplete() {
1598        if (VDBG) {
1599            logv("processMergeComplete ::");
1600        }
1601
1602        ImsCall.Listener listener;
1603        synchronized(ImsCall.this) {
1604            listener = mListener;
1605            if (mTransientConferenceSession == null) {
1606                // This is an interesting state that needs to be logged since we
1607                // should only be going through this workflow for new conference calls
1608                // and not merges into existing conferences (which a null transient
1609                // session would imply)
1610                log("processMergeComplete :: ERROR no transient session");
1611                return;
1612            }
1613
1614            // Swap out the underlying sessions after shutting down the existing session.
1615            mSession.setListener(null);
1616            mSession = mTransientConferenceSession;
1617            mTransientConferenceSession = null;
1618            listener = mListener;
1619
1620            // Mark the merge peer call as merged so that when it terminates, the disconnect tone is
1621            // suppressed.
1622            if (mMergePeer != null) {
1623                mMergePeer.setIsMerged(true);
1624            }
1625            clearMergePeer();
1626
1627            // Clear some flags.  If the merge eventually worked, we can safely
1628            // ignore the call terminated message for the old session since we closed it already.
1629            mSessionEndDuringMerge = false;
1630            mSessionEndDuringMergeReasonInfo = null;
1631            mUpdateRequest = UPDATE_NONE;
1632        }
1633        if (listener != null) {
1634            try {
1635                listener.onCallMerged(ImsCall.this);
1636            } catch (Throwable t) {
1637                loge("processMergeComplete :: ", t);
1638            }
1639        }
1640
1641        return;
1642    }
1643
1644    /**
1645     * We received a callback from ImsCallSession that a merge failed. Clean up all
1646     * internal state to represent this state change.
1647     *
1648     * @param reasonInfo The {@link ImsReasonInfo} why the merge failed.
1649     */
1650    private void processMergeFailed(ImsReasonInfo reasonInfo) {
1651        if (VDBG) {
1652            String reasonString = reasonInfo != null ? reasonInfo.toString() : "null";
1653            logv("processMergeFailed :: reason=" + reasonString);
1654        }
1655
1656        ImsCall.Listener listener;
1657        boolean notifyFailure = false;
1658        ImsReasonInfo notifyFailureReasonInfo = null;
1659
1660        synchronized(ImsCall.this) {
1661            listener = mListener;
1662            if (mTransientConferenceSession != null) {
1663                // Clean up any work that we performed on the transient session.
1664                mTransientConferenceSession.setListener(null);
1665                mTransientConferenceSession = null;
1666                listener = mListener;
1667                if (mSessionEndDuringMerge) {
1668                    // Set some local variables that will send out a notification about a
1669                    // previously buried termination callback for our primary session now that
1670                    // we know that this is not due to the conference call merging succesfully.
1671                    if (DBG) {
1672                        log("processMergeFailed :: following up on a terminate during the merge");
1673                    }
1674                    notifyFailure = true;
1675                    notifyFailureReasonInfo = mSessionEndDuringMergeReasonInfo;
1676                }
1677            } else {
1678                // This is an interesting state that needs to be logged since we
1679                // should only be going through this workflow for new conference calls
1680                // and not merges into existing conferences (which a null transient
1681                // session would imply)
1682                log("processMergeFailed - ERROR no transient session");
1683            }
1684            mSessionEndDuringMerge = false;
1685            mSessionEndDuringMergeReasonInfo = null;
1686            mUpdateRequest = UPDATE_NONE;
1687
1688            // Ensure the call being conferenced into the conference has isMerged = false.
1689            if (isMergeHost()) {
1690                mMergePeer.setIsMerged(false);
1691            } else {
1692                setIsMerged(false);
1693            }
1694            // Unlink the two calls since they are no longer being involved in an attempted merge.
1695            clearMergePeer();
1696        }
1697        if (listener != null) {
1698            try {
1699                // TODO: are both of these callbacks necessary?
1700                listener.onCallMergeFailed(ImsCall.this, reasonInfo);
1701                if (notifyFailure) {
1702                    processCallTerminated(notifyFailureReasonInfo);
1703                }
1704            } catch (Throwable t) {
1705                loge("processMergeFailed :: ", t);
1706            }
1707        }
1708        return;
1709    }
1710
1711    private void notifyError(int reason, int statusCode, String message) {
1712    }
1713
1714    private void throwImsException(Throwable t, int code) throws ImsException {
1715        if (t instanceof ImsException) {
1716            throw (ImsException) t;
1717        } else {
1718            throw new ImsException(String.valueOf(code), t, code);
1719        }
1720    }
1721
1722    private void log(String s) {
1723        Rlog.d(TAG, s);
1724    }
1725
1726    /**
1727     * Logs the specified message, as well as the current instance of {@link ImsCall}.
1728     *
1729     * @param s The message to log.
1730     */
1731    private void logv(String s) {
1732        StringBuilder sb = new StringBuilder();
1733        sb.append(s);
1734        sb.append(" imsCall=");
1735        sb.append(ImsCall.this);
1736        Rlog.v(TAG, sb.toString());
1737    }
1738
1739    private void loge(String s) {
1740        Rlog.e(TAG, s);
1741    }
1742
1743    private void loge(String s, Throwable t) {
1744        Rlog.e(TAG, s, t);
1745    }
1746
1747    private class ImsCallSessionListenerProxy extends ImsCallSession.Listener {
1748        @Override
1749        public void callSessionProgressing(ImsCallSession session, ImsStreamMediaProfile profile) {
1750            if (isTransientConferenceSession(session)) {
1751                log("callSessionProgressing :: not supported for transient conference session=" +
1752                        session);
1753                return;
1754            }
1755
1756            if (VDBG) {
1757                logv("callSessionProgressing :: profile=" + profile);
1758            }
1759
1760            ImsCall.Listener listener;
1761
1762            synchronized(ImsCall.this) {
1763                listener = mListener;
1764                mCallProfile.mMediaProfile.copyFrom(profile);
1765            }
1766
1767            if (listener != null) {
1768                try {
1769                    listener.onCallProgressing(ImsCall.this);
1770                } catch (Throwable t) {
1771                    loge("callSessionProgressing :: ", t);
1772                }
1773            }
1774        }
1775
1776        @Override
1777        public void callSessionStarted(ImsCallSession session, ImsCallProfile profile) {
1778            if (VDBG) {
1779                logv("callSessionStarted :: profile=" + profile);
1780            }
1781
1782            if (isTransientConferenceSession(session)) {
1783                log("callSessionStarted :: transient conference session resumed session=" +
1784                        session);
1785                // If we get a resume on the transient session, this means that the merge
1786                // was completed, let's process it are skip the rest of the processing in
1787                // this callback.
1788                processMergeComplete();
1789                return;
1790            } else if (isMerging() && isMergeHost()) {
1791                // Ensure peer is set to suppress the disconnect tone.
1792                mMergePeer.setIsMerged(true);
1793            }
1794
1795            ImsCall.Listener listener;
1796
1797            synchronized(ImsCall.this) {
1798                listener = mListener;
1799                mCallProfile = profile;
1800            }
1801
1802            if (listener != null) {
1803                try {
1804                    listener.onCallStarted(ImsCall.this);
1805                } catch (Throwable t) {
1806                    loge("callSessionStarted :: ", t);
1807                }
1808            }
1809        }
1810
1811        @Override
1812        public void callSessionStartFailed(ImsCallSession session, ImsReasonInfo reasonInfo) {
1813            if (isTransientConferenceSession(session)) {
1814                log("callSessionStartFailed :: not supported for transient conference session=" +
1815                        session);
1816                return;
1817            }
1818
1819            if (VDBG) {
1820                logv("callSessionStartFailed :: reasonInfo=" + reasonInfo);
1821            }
1822
1823            ImsCall.Listener listener;
1824
1825            synchronized(ImsCall.this) {
1826                listener = mListener;
1827                mLastReasonInfo = reasonInfo;
1828            }
1829
1830            if (listener != null) {
1831                try {
1832                    listener.onCallStartFailed(ImsCall.this, reasonInfo);
1833                } catch (Throwable t) {
1834                    loge("callSessionStarted :: ", t);
1835                }
1836            }
1837        }
1838
1839        @Override
1840        public void callSessionTerminated(ImsCallSession session, ImsReasonInfo reasonInfo) {
1841            if (mSession != session) {
1842                log("callSessionTerminated :: not supported for conference session=" + session);
1843                return;
1844            }
1845
1846            if (VDBG) {
1847                logv("callSessionTerminated :: reasonInfo=" + reasonInfo);
1848            }
1849
1850            processCallTerminated(reasonInfo);
1851        }
1852
1853        @Override
1854        public void callSessionHeld(ImsCallSession session, ImsCallProfile profile) {
1855            if (isTransientConferenceSession(session)) {
1856                log("callSessionHeld :: not supported for transient conference session=" + session);
1857                return;
1858            }
1859
1860            if (VDBG) {
1861                logv("callSessionHeld :: profile=" + profile);
1862            }
1863
1864            ImsCall.Listener listener;
1865
1866            synchronized(ImsCall.this) {
1867                mCallProfile = profile;
1868
1869                if (mUpdateRequest == UPDATE_HOLD_MERGE) {
1870                    mergeInternal();
1871                    return;
1872                }
1873
1874                mUpdateRequest = UPDATE_NONE;
1875                listener = mListener;
1876            }
1877
1878            if (listener != null) {
1879                try {
1880                    listener.onCallHeld(ImsCall.this);
1881                } catch (Throwable t) {
1882                    loge("callSessionHeld :: ", t);
1883                }
1884            }
1885        }
1886
1887        @Override
1888        public void callSessionHoldFailed(ImsCallSession session, ImsReasonInfo reasonInfo) {
1889            if (isTransientConferenceSession(session)) {
1890                log("callSessionHoldFailed :: not supported for transient conference session=" +
1891                        session);
1892                return;
1893            }
1894
1895            if (VDBG) {
1896                logv("callSessionHoldFailed :: reasonInfo=" + reasonInfo);
1897            }
1898
1899            boolean isHoldForMerge = false;
1900            ImsCall.Listener listener;
1901
1902            synchronized(ImsCall.this) {
1903                if (mUpdateRequest == UPDATE_HOLD_MERGE) {
1904                    isHoldForMerge = true;
1905                }
1906
1907                mUpdateRequest = UPDATE_NONE;
1908                listener = mListener;
1909            }
1910
1911            if (isHoldForMerge) {
1912                // Is hold for merge implemented/supported? If so we need to take a close look
1913                // at this workflow to make sure that we handle the case where
1914                // callSessionMergeFailed() does the right thing because we have not actually
1915                // started the merge yet.
1916                callSessionMergeFailed(session, reasonInfo);
1917                return;
1918            }
1919
1920            if (listener != null) {
1921                try {
1922                    listener.onCallHoldFailed(ImsCall.this, reasonInfo);
1923                } catch (Throwable t) {
1924                    loge("callSessionHoldFailed :: ", t);
1925                }
1926            }
1927        }
1928
1929        @Override
1930        public void callSessionHoldReceived(ImsCallSession session, ImsCallProfile profile) {
1931            if (isTransientConferenceSession(session)) {
1932                log("callSessionHoldReceived :: not supported for transient conference session=" +
1933                        session);
1934                return;
1935            }
1936
1937            if (VDBG) {
1938                logv("callSessionHoldReceived :: profile=" + profile);
1939            }
1940
1941            ImsCall.Listener listener;
1942
1943            synchronized(ImsCall.this) {
1944                listener = mListener;
1945                mCallProfile = profile;
1946            }
1947
1948            if (listener != null) {
1949                try {
1950                    listener.onCallHoldReceived(ImsCall.this);
1951                } catch (Throwable t) {
1952                    loge("callSessionHoldReceived :: ", t);
1953                }
1954            }
1955        }
1956
1957        @Override
1958        public void callSessionResumed(ImsCallSession session, ImsCallProfile profile) {
1959            if (isTransientConferenceSession(session)) {
1960                log("callSessionResumed :: not supported for transient conference session=" +
1961                        session);
1962                return;
1963            }
1964
1965            if (VDBG) {
1966                logv("callSessionResumed :: profile=" + profile);
1967            }
1968
1969            ImsCall.Listener listener;
1970
1971            synchronized(ImsCall.this) {
1972                listener = mListener;
1973                mCallProfile = profile;
1974                mUpdateRequest = UPDATE_NONE;
1975                mHold = false;
1976            }
1977
1978            if (listener != null) {
1979                try {
1980                    listener.onCallResumed(ImsCall.this);
1981                } catch (Throwable t) {
1982                    loge("callSessionResumed :: ", t);
1983                }
1984            }
1985        }
1986
1987        @Override
1988        public void callSessionResumeFailed(ImsCallSession session, ImsReasonInfo reasonInfo) {
1989            if (isTransientConferenceSession(session)) {
1990                log("callSessionResumeFailed :: not supported for transient conference session=" +
1991                        session);
1992                return;
1993            }
1994
1995            if (VDBG) {
1996                logv("callSessionResumeFailed :: reasonInfo=" + reasonInfo);
1997            }
1998
1999            ImsCall.Listener listener;
2000
2001            synchronized(ImsCall.this) {
2002                listener = mListener;
2003                mUpdateRequest = UPDATE_NONE;
2004            }
2005
2006            if (listener != null) {
2007                try {
2008                    listener.onCallResumeFailed(ImsCall.this, reasonInfo);
2009                } catch (Throwable t) {
2010                    loge("callSessionResumeFailed :: ", t);
2011                }
2012            }
2013        }
2014
2015        @Override
2016        public void callSessionResumeReceived(ImsCallSession session, ImsCallProfile profile) {
2017            if (isTransientConferenceSession(session)) {
2018                log("callSessionResumeReceived :: not supported for transient conference session=" +
2019                        session);
2020                return;
2021            }
2022
2023            if (VDBG) {
2024                logv("callSessionResumeReceived :: profile=" + profile);
2025            }
2026
2027            ImsCall.Listener listener;
2028
2029            synchronized(ImsCall.this) {
2030                listener = mListener;
2031                mCallProfile = profile;
2032            }
2033
2034            if (listener != null) {
2035                try {
2036                    listener.onCallResumeReceived(ImsCall.this);
2037                } catch (Throwable t) {
2038                    loge("callSessionResumeReceived :: ", t);
2039                }
2040            }
2041        }
2042
2043        @Override
2044        public void callSessionMergeStarted(ImsCallSession session,
2045                ImsCallSession newSession, ImsCallProfile profile) {
2046            if (VDBG) {
2047                String newSessionString = newSession == null ? "null" : newSession.toString();
2048                logv("callSessionMergeStarted :: newSession=" + newSessionString +
2049                        ", profile=" + profile);
2050            }
2051
2052            if (mUpdateRequest != UPDATE_MERGE) {
2053                // Odd, we are not in the midst of merging anything.
2054                log("callSessionMergeStarted :: no merge in progress.");
2055                return;
2056            }
2057
2058            // There are 2 ways that we can go here.  If the session that supplied the params
2059            // is not null, then it is the new session that represents the new conference
2060            // if the merge succeeds. If it is null, the merge is happening on our current
2061            // ImsCallSession.
2062            if (session == null) {
2063                // Everything is already set up and we just need to make sure
2064                // that we properly respond to all the future callbacks about
2065                // this merge.
2066                if (DBG) {
2067                    log("callSessionMergeStarted :: merging into existing ImsCallSession");
2068                }
2069                return;
2070            }
2071
2072            if (DBG) {
2073                log("callSessionMergeStarted ::  setting our transient ImsCallSession");
2074            }
2075
2076            // If we are here, this means that we are creating a new conference and
2077            // we need to do some extra work around managing a new ImsCallSession that
2078            // could represent our new ImsCallSession if the merge succeeds.
2079            synchronized(ImsCall.this) {
2080                // Keep track of this session for future callbacks to indicate success
2081                // or failure of this merge.
2082                mTransientConferenceSession = newSession;
2083                mTransientConferenceSession.setListener(createCallSessionListener());
2084            }
2085
2086            return;
2087        }
2088
2089        @Override
2090        public void callSessionMergeComplete(ImsCallSession session) {
2091            if (VDBG) {
2092                logv("callSessionMergeComplete ::");
2093            }
2094            if (mUpdateRequest != UPDATE_MERGE) {
2095                // Odd, we are not in the midst of merging anything.
2096                log("callSessionMergeComplete :: no merge in progress.");
2097                return;
2098            }
2099            // Let's let our parent ImsCall now that we received notification that
2100            // the merge was completed so we can set up our internal state properly
2101            processMergeComplete();
2102        }
2103
2104        @Override
2105        public void callSessionMergeFailed(ImsCallSession session, ImsReasonInfo reasonInfo) {
2106            if (VDBG) {
2107                String reasonInfoString = reasonInfo == null ? "null" : reasonInfo.toString();
2108                logv("callSessionMergeFailed :: reasonInfo=" + reasonInfoString);
2109            }
2110            if (mUpdateRequest != UPDATE_MERGE && !isMerging()) {
2111                // Odd, we are not in the midst of merging anything.
2112                log("callSessionMergeFailed :: no merge in progress.");
2113                return;
2114            }
2115            // Let's tell our parent ImsCall that the merge has failed and we need to clean
2116            // up any temporary, transient state.
2117            processMergeFailed(reasonInfo);
2118        }
2119
2120        @Override
2121        public void callSessionUpdated(ImsCallSession session, ImsCallProfile profile) {
2122            if (isTransientConferenceSession(session)) {
2123                log("callSessionUpdated :: not supported for transient conference session=" +
2124                        session);
2125                return;
2126            }
2127
2128            if (VDBG) {
2129                logv("callSessionUpdated :: profile=" + profile);
2130            }
2131
2132            ImsCall.Listener listener;
2133
2134            synchronized(ImsCall.this) {
2135                listener = mListener;
2136                mCallProfile = profile;
2137                mUpdateRequest = UPDATE_NONE;
2138            }
2139
2140            if (listener != null) {
2141                try {
2142                    listener.onCallUpdated(ImsCall.this);
2143                } catch (Throwable t) {
2144                    loge("callSessionUpdated :: ", t);
2145                }
2146            }
2147        }
2148
2149        @Override
2150        public void callSessionUpdateFailed(ImsCallSession session, ImsReasonInfo reasonInfo) {
2151            if (isTransientConferenceSession(session)) {
2152                log("callSessionUpdateFailed :: not supported for transient conference session=" +
2153                        session);
2154                return;
2155            }
2156
2157            if (VDBG) {
2158                logv("callSessionUpdateFailed :: reasonInfo=" + reasonInfo);
2159            }
2160
2161            ImsCall.Listener listener;
2162
2163            synchronized(ImsCall.this) {
2164                listener = mListener;
2165                mUpdateRequest = UPDATE_NONE;
2166            }
2167
2168            if (listener != null) {
2169                try {
2170                    listener.onCallUpdateFailed(ImsCall.this, reasonInfo);
2171                } catch (Throwable t) {
2172                    loge("callSessionUpdateFailed :: ", t);
2173                }
2174            }
2175        }
2176
2177        @Override
2178        public void callSessionUpdateReceived(ImsCallSession session, ImsCallProfile profile) {
2179            if (isTransientConferenceSession(session)) {
2180                log("callSessionUpdateReceived :: not supported for transient conference " +
2181                        "session=" + session);
2182                return;
2183            }
2184
2185            if (VDBG) {
2186                logv("callSessionUpdateReceived :: profile=" + profile);
2187            }
2188
2189            ImsCall.Listener listener;
2190
2191            synchronized(ImsCall.this) {
2192                listener = mListener;
2193                mProposedCallProfile = profile;
2194                mUpdateRequest = UPDATE_UNSPECIFIED;
2195            }
2196
2197            if (listener != null) {
2198                try {
2199                    listener.onCallUpdateReceived(ImsCall.this);
2200                } catch (Throwable t) {
2201                    loge("callSessionUpdateReceived :: ", t);
2202                }
2203            }
2204        }
2205
2206        @Override
2207        public void callSessionConferenceExtended(ImsCallSession session, ImsCallSession newSession,
2208                ImsCallProfile profile) {
2209            if (isTransientConferenceSession(session)) {
2210                log("callSessionConferenceExtended :: not supported for transient conference " +
2211                        "session=" + session);
2212                return;
2213            }
2214
2215            if (VDBG) {
2216                logv("callSessionConferenceExtended :: newSession=" + newSession + ", profile="
2217                        + profile);
2218            }
2219
2220            ImsCall newCall = createNewCall(newSession, profile);
2221
2222            if (newCall == null) {
2223                callSessionConferenceExtendFailed(session, new ImsReasonInfo());
2224                return;
2225            }
2226
2227            ImsCall.Listener listener;
2228
2229            synchronized(ImsCall.this) {
2230                listener = mListener;
2231                mUpdateRequest = UPDATE_NONE;
2232            }
2233
2234            if (listener != null) {
2235                try {
2236                    listener.onCallConferenceExtended(ImsCall.this, newCall);
2237                } catch (Throwable t) {
2238                    loge("callSessionConferenceExtended :: ", t);
2239                }
2240            }
2241        }
2242
2243        @Override
2244        public void callSessionConferenceExtendFailed(ImsCallSession session,
2245                ImsReasonInfo reasonInfo) {
2246            if (isTransientConferenceSession(session)) {
2247                log("callSessionConferenceExtendFailed :: not supported for transient " +
2248                        "conference session=" + session);
2249                return;
2250            }
2251
2252            if (DBG) {
2253                log("callSessionConferenceExtendFailed :: imsCall=" + ImsCall.this +
2254                        ", reasonInfo=" + reasonInfo);
2255            }
2256
2257            ImsCall.Listener listener;
2258
2259            synchronized(ImsCall.this) {
2260                listener = mListener;
2261                mUpdateRequest = UPDATE_NONE;
2262            }
2263
2264            if (listener != null) {
2265                try {
2266                    listener.onCallConferenceExtendFailed(ImsCall.this, reasonInfo);
2267                } catch (Throwable t) {
2268                    loge("callSessionConferenceExtendFailed :: ", t);
2269                }
2270            }
2271        }
2272
2273        @Override
2274        public void callSessionConferenceExtendReceived(ImsCallSession session,
2275                ImsCallSession newSession, ImsCallProfile profile) {
2276            if (isTransientConferenceSession(session)) {
2277                log("callSessionConferenceExtendReceived :: not supported for transient " +
2278                        "conference session" + session);
2279                return;
2280            }
2281
2282            if (VDBG) {
2283                logv("callSessionConferenceExtendReceived :: newSession=" + newSession +
2284                        ", profile=" + profile);
2285            }
2286
2287            ImsCall newCall = createNewCall(newSession, profile);
2288
2289            if (newCall == null) {
2290                // Should all the calls be terminated...???
2291                return;
2292            }
2293
2294            ImsCall.Listener listener;
2295
2296            synchronized(ImsCall.this) {
2297                listener = mListener;
2298            }
2299
2300            if (listener != null) {
2301                try {
2302                    listener.onCallConferenceExtendReceived(ImsCall.this, newCall);
2303                } catch (Throwable t) {
2304                    loge("callSessionConferenceExtendReceived :: ", t);
2305                }
2306            }
2307        }
2308
2309        @Override
2310        public void callSessionInviteParticipantsRequestDelivered(ImsCallSession session) {
2311            if (isTransientConferenceSession(session)) {
2312                log("callSessionInviteParticipantsRequestDelivered :: not supported for " +
2313                        "conference session=" + session);
2314                return;
2315            }
2316
2317            if (VDBG) {
2318                logv("callSessionInviteParticipantsRequestDelivered ::");
2319            }
2320
2321            ImsCall.Listener listener;
2322
2323            synchronized(ImsCall.this) {
2324                listener = mListener;
2325            }
2326
2327            if (listener != null) {
2328                try {
2329                    listener.onCallInviteParticipantsRequestDelivered(ImsCall.this);
2330                } catch (Throwable t) {
2331                    loge("callSessionInviteParticipantsRequestDelivered :: ", t);
2332                }
2333            }
2334        }
2335
2336        @Override
2337        public void callSessionInviteParticipantsRequestFailed(ImsCallSession session,
2338                ImsReasonInfo reasonInfo) {
2339            if (isTransientConferenceSession(session)) {
2340                log("callSessionInviteParticipantsRequestFailed :: not supported for " +
2341                        "conference session=" + session);
2342                return;
2343            }
2344
2345            if (VDBG) {
2346                logv("callSessionInviteParticipantsRequestFailed :: reasonInfo=" + reasonInfo);
2347            }
2348
2349            ImsCall.Listener listener;
2350
2351            synchronized(ImsCall.this) {
2352                listener = mListener;
2353            }
2354
2355            if (listener != null) {
2356                try {
2357                    listener.onCallInviteParticipantsRequestFailed(ImsCall.this, reasonInfo);
2358                } catch (Throwable t) {
2359                    loge("callSessionInviteParticipantsRequestFailed :: ", t);
2360                }
2361            }
2362        }
2363
2364        @Override
2365        public void callSessionRemoveParticipantsRequestDelivered(ImsCallSession session) {
2366            if (isTransientConferenceSession(session)) {
2367                log("callSessionRemoveParticipantsRequestDelivered :: not supported for " +
2368                        "conference session=" + session);
2369                return;
2370            }
2371
2372            if (VDBG) {
2373                logv("callSessionRemoveParticipantsRequestDelivered ::");
2374            }
2375
2376            ImsCall.Listener listener;
2377
2378            synchronized(ImsCall.this) {
2379                listener = mListener;
2380            }
2381
2382            if (listener != null) {
2383                try {
2384                    listener.onCallRemoveParticipantsRequestDelivered(ImsCall.this);
2385                } catch (Throwable t) {
2386                    loge("callSessionRemoveParticipantsRequestDelivered :: ", t);
2387                }
2388            }
2389        }
2390
2391        @Override
2392        public void callSessionRemoveParticipantsRequestFailed(ImsCallSession session,
2393                ImsReasonInfo reasonInfo) {
2394            if (isTransientConferenceSession(session)) {
2395                log("callSessionRemoveParticipantsRequestFailed :: not supported for " +
2396                        "conference session=" + session);
2397                return;
2398            }
2399
2400            if (VDBG) {
2401                logv("callSessionRemoveParticipantsRequestFailed :: reasonInfo=" + reasonInfo);
2402            }
2403
2404            ImsCall.Listener listener;
2405
2406            synchronized(ImsCall.this) {
2407                listener = mListener;
2408            }
2409
2410            if (listener != null) {
2411                try {
2412                    listener.onCallRemoveParticipantsRequestFailed(ImsCall.this, reasonInfo);
2413                } catch (Throwable t) {
2414                    loge("callSessionRemoveParticipantsRequestFailed :: ", t);
2415                }
2416            }
2417        }
2418
2419        @Override
2420        public void callSessionConferenceStateUpdated(ImsCallSession session,
2421                ImsConferenceState state) {
2422            if (isTransientConferenceSession(session)) {
2423                log("callSessionConferenceStateUpdated :: not supported for transient " +
2424                        "conference session=" + session);
2425                return;
2426            }
2427
2428            if (VDBG) {
2429                logv("callSessionConferenceStateUpdated :: state=" + state);
2430            }
2431
2432            conferenceStateUpdated(state);
2433        }
2434
2435        @Override
2436        public void callSessionUssdMessageReceived(ImsCallSession session, int mode,
2437                String ussdMessage) {
2438            if (isTransientConferenceSession(session)) {
2439                log("callSessionUssdMessageReceived :: not supported for transient " +
2440                        "conference session=" + session);
2441                return;
2442            }
2443
2444            if (VDBG) {
2445                logv("callSessionUssdMessageReceived :: mode=" + mode + ", ussdMessage=" +
2446                        ussdMessage);
2447            }
2448
2449            ImsCall.Listener listener;
2450
2451            synchronized(ImsCall.this) {
2452                listener = mListener;
2453            }
2454
2455            if (listener != null) {
2456                try {
2457                    listener.onCallUssdMessageReceived(ImsCall.this, mode, ussdMessage);
2458                } catch (Throwable t) {
2459                    loge("callSessionUssdMessageReceived :: ", t);
2460                }
2461            }
2462        }
2463
2464        @Override
2465        public void callSessionTtyModeReceived(ImsCallSession session, int mode) {
2466            if (VDBG) {
2467                logv("callSessionTtyModeReceived :: mode=" + mode);
2468            }
2469
2470            int settingsTtyMode = Settings.Secure.getInt(
2471                    mContext.getContentResolver(),
2472                    Settings.Secure.PREFERRED_TTY_MODE,
2473                    TelecomManager.TTY_MODE_OFF);
2474            if (settingsTtyMode == TelecomManager.TTY_MODE_OFF) {
2475                // Notify the user that TTY mode changed in the far device
2476                int resId = 0;
2477                switch (mode) {
2478                    case TelecomManager.TTY_MODE_FULL:
2479                        resId = com.android.internal.R.string.peerTtyModeFull;
2480                        break;
2481                    case TelecomManager.TTY_MODE_HCO:
2482                        resId = com.android.internal.R.string.peerTtyModeHco;
2483                        break;
2484                    case TelecomManager.TTY_MODE_VCO:
2485                        resId = com.android.internal.R.string.peerTtyModeVco;
2486                        break;
2487                    case TelecomManager.TTY_MODE_OFF:
2488                        resId = com.android.internal.R.string.peerTtyModeOff;
2489                        break;
2490                }
2491                if (resId != 0) {
2492                    Toast.makeText(mContext, resId, Toast.LENGTH_SHORT).show();
2493                }
2494            }
2495        }
2496    }
2497
2498    /**
2499     * Report a new conference state to the current {@link ImsCall} and inform listeners of the
2500     * change.  Marked as {@code VisibleForTesting} so that the
2501     * {@code com.android.internal.telephony.TelephonyTester} class can inject a test conference
2502     * event package into a regular ongoing IMS call.
2503     *
2504     * @param state The {@link ImsConferenceState}.
2505     */
2506    @VisibleForTesting
2507    public void conferenceStateUpdated(ImsConferenceState state) {
2508        Listener listener;
2509
2510        synchronized(this) {
2511            notifyConferenceStateUpdated(state);
2512            listener = mListener;
2513        }
2514
2515        if (listener != null) {
2516            try {
2517                listener.onCallConferenceStateUpdated(this, state);
2518            } catch (Throwable t) {
2519                loge("callSessionConferenceStateUpdated :: ", t);
2520            }
2521        }
2522    }
2523
2524    /**
2525     * Provides a human-readable string representation of an update request.
2526     *
2527     * @param updateRequest The update request.
2528     * @return The string representation.
2529     */
2530    private String updateRequestToString(int updateRequest) {
2531        switch (updateRequest) {
2532            case UPDATE_NONE:
2533                return "NONE";
2534            case UPDATE_HOLD:
2535                return "HOLD";
2536            case UPDATE_HOLD_MERGE:
2537                return "HOLD_MERGE";
2538            case UPDATE_RESUME:
2539                return "RESUME";
2540            case UPDATE_MERGE:
2541                return "MERGE";
2542            case UPDATE_EXTEND_TO_CONFERENCE:
2543                return "EXTEND_TO_CONFERENCE";
2544            case UPDATE_UNSPECIFIED:
2545                return "UNSPECIFIED";
2546            default:
2547                return "UNKNOWN";
2548        }
2549    }
2550
2551    /**
2552     * Clears the merge peer for this call, ensuring that the peer's connection to this call is also
2553     * severed at the same time.
2554     */
2555    private void clearMergePeer() {
2556        if (mMergeHost != null) {
2557            mMergeHost.mMergePeer = null;
2558            mMergeHost = null;
2559        }
2560
2561        if (mMergePeer != null) {
2562            mMergePeer.mMergeHost = null;
2563            mMergePeer = null;
2564        }
2565    }
2566
2567    /**
2568     * Sets the merge peer for the current call.  The merge peer is the background call that will be
2569     * merged into this call.  On the merge peer, sets the merge host to be this call.
2570     *
2571     * @param mergePeer The peer call to be merged into this one.
2572     */
2573    private void setMergePeer(ImsCall mergePeer) {
2574        mMergePeer = mergePeer;
2575        mMergeHost = null;
2576
2577        mergePeer.mMergeHost = ImsCall.this;
2578        mergePeer.mMergePeer = null;
2579    }
2580
2581    /**
2582     * Sets the merge hody for the current call.  The merge host is the foreground call this call
2583     * will be merged into.  On the merge host, sets the merge peer to be this call.
2584     *
2585     * @param mergeHost The merge host this call will be merged into.
2586     */
2587    public void setMergeHost(ImsCall mergeHost) {
2588        mMergeHost = mergeHost;
2589        mMergePeer = null;
2590
2591        mergeHost.mMergeHost = null;
2592        mergeHost.mMergePeer = ImsCall.this;
2593    }
2594
2595    /**
2596     * Determines if the current call is in the process of merging with another call or conference.
2597     *
2598     * @return {@code true} if in the process of merging.
2599     */
2600    private boolean isMerging() {
2601        return mMergePeer != null || mMergeHost != null;
2602    }
2603
2604    /**
2605     * Determines if the current call is the host of the merge.
2606     *
2607     * @return {@code true} if the call is the merge host.
2608     */
2609    private boolean isMergeHost() {
2610        return mMergePeer != null && mMergeHost == null;
2611    }
2612
2613    /**
2614     * Determines if the current call is the peer of the merge.
2615     *
2616     * @return {@code true} if the call is the merge peer.
2617     */
2618    private boolean isMergePeer() {
2619        return mMergePeer == null && mMergeHost != null;
2620    }
2621
2622    /**
2623     * Provides a string representation of the {@link ImsCall}.  Primarily intended for use in log
2624     * statements.
2625     *
2626     * @return String representation of call.
2627     */
2628    @Override
2629    public String toString() {
2630        StringBuilder sb = new StringBuilder();
2631        sb.append("[ImsCall objId:");
2632        sb.append(System.identityHashCode(this));
2633        sb.append(" onHold:");
2634        sb.append(isOnHold() ? "Y" : "N");
2635        sb.append(" mute:");
2636        sb.append(isMuted() ? "Y" : "N");
2637        sb.append(" updateRequest:");
2638        sb.append(updateRequestToString(mUpdateRequest));
2639        sb.append(" merging:");
2640        sb.append(isMerging() ? "Y" : "N");
2641        if (isMerging()) {
2642            if (isMergePeer()) {
2643                sb.append("P");
2644            } else {
2645                sb.append("H");
2646            }
2647        }
2648        sb.append(" merged:");
2649        sb.append(isMerged() ? "Y" : "N");
2650        sb.append(" multiParty:");
2651        sb.append(isMultiparty() ? "Y" : "N");
2652        sb.append(" session:");
2653        sb.append(mSession);
2654        sb.append(" transientSession:");
2655        sb.append(mTransientConferenceSession);
2656        sb.append("]");
2657        return sb.toString();
2658    }
2659}
2660