ImsCall.java revision d43b5302d8edf4df90a28055f043786f542df219
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.HashMap;
23import java.util.Iterator;
24import java.util.Map.Entry;
25import java.util.Set;
26
27import android.content.Context;
28import android.os.Bundle;
29import android.os.Message;
30import android.telephony.Rlog;
31
32import com.android.ims.internal.CallGroup;
33import com.android.ims.internal.CallGroupManager;
34import com.android.ims.internal.ICall;
35import com.android.ims.internal.ImsCallSession;
36import com.android.ims.internal.ImsStreamMediaSession;
37
38/**
39 * Handles an IMS voice / video call over LTE. You can instantiate this class with
40 * {@link ImsManager}.
41 *
42 * @hide
43 */
44public class ImsCall implements ICall {
45    public static final int CALL_STATE_ACTIVE_TO_HOLD = 1;
46    public static final int CALL_STATE_HOLD_TO_ACTIVE = 2;
47
48    // Mode of USSD message
49    public static final int USSD_MODE_NOTIFY = 0;
50    public static final int USSD_MODE_REQUEST = 1;
51
52    private static final String TAG = "ImsCall";
53    private static final boolean DBG = true;
54
55    /**
56     * Listener for events relating to an IMS call, such as when a call is being
57     * recieved ("on ringing") or a call is outgoing ("on calling").
58     * <p>Many of these events are also received by {@link ImsCallSession.Listener}.</p>
59     */
60    public static class Listener {
61        /**
62         * Called when a request is sent out to initiate a new call
63         * and 1xx response is received from the network.
64         * The default implementation calls {@link #onCallStateChanged}.
65         *
66         * @param call the call object that carries out the IMS call
67         */
68        public void onCallProgressing(ImsCall call) {
69            onCallStateChanged(call);
70        }
71
72        /**
73         * Called when the call is established.
74         * The default implementation calls {@link #onCallStateChanged}.
75         *
76         * @param call the call object that carries out the IMS call
77         */
78        public void onCallStarted(ImsCall call) {
79            onCallStateChanged(call);
80        }
81
82        /**
83         * Called when the call setup is failed.
84         * The default implementation calls {@link #onCallError}.
85         *
86         * @param call the call object that carries out the IMS call
87         * @param reasonInfo detailed reason of the call setup failure
88         */
89        public void onCallStartFailed(ImsCall call, ImsReasonInfo reasonInfo) {
90            onCallError(call, reasonInfo);
91        }
92
93        /**
94         * Called when the call is terminated.
95         * The default implementation calls {@link #onCallStateChanged}.
96         *
97         * @param call the call object that carries out the IMS call
98         * @param reasonInfo detailed reason of the call termination
99         */
100        public void onCallTerminated(ImsCall call, ImsReasonInfo reasonInfo) {
101            // Store the call termination reason
102
103            onCallStateChanged(call);
104        }
105
106        /**
107         * Called when the call is in hold.
108         * The default implementation calls {@link #onCallStateChanged}.
109         *
110         * @param call the call object that carries out the IMS call
111         */
112        public void onCallHeld(ImsCall call) {
113            onCallStateChanged(call);
114        }
115
116        /**
117         * Called when the call hold is failed.
118         * The default implementation calls {@link #onCallError}.
119         *
120         * @param call the call object that carries out the IMS call
121         * @param reasonInfo detailed reason of the call hold failure
122         */
123        public void onCallHoldFailed(ImsCall call, ImsReasonInfo reasonInfo) {
124            onCallError(call, reasonInfo);
125        }
126
127        /**
128         * Called when the call hold is received from the remote user.
129         * The default implementation calls {@link #onCallStateChanged}.
130         *
131         * @param call the call object that carries out the IMS call
132         */
133        public void onCallHoldReceived(ImsCall call) {
134            onCallStateChanged(call);
135        }
136
137        /**
138         * Called when the call is in call.
139         * The default implementation calls {@link #onCallStateChanged}.
140         *
141         * @param call the call object that carries out the IMS call
142         */
143        public void onCallResumed(ImsCall call) {
144            onCallStateChanged(call);
145        }
146
147        /**
148         * Called when the call resume is failed.
149         * The default implementation calls {@link #onCallError}.
150         *
151         * @param call the call object that carries out the IMS call
152         * @param reasonInfo detailed reason of the call resume failure
153         */
154        public void onCallResumeFailed(ImsCall call, ImsReasonInfo reasonInfo) {
155            onCallError(call, reasonInfo);
156        }
157
158        /**
159         * Called when the call resume is received from the remote user.
160         * The default implementation calls {@link #onCallStateChanged}.
161         *
162         * @param call the call object that carries out the IMS call
163         */
164        public void onCallResumeReceived(ImsCall call) {
165            onCallStateChanged(call);
166        }
167
168        /**
169         * Called when the call is in call.
170         * The default implementation calls {@link #onCallStateChanged}.
171         *
172         * @param call the call object that carries out the IMS call
173         * @param newCall the call object that is merged with an active & hold call
174         */
175        public void onCallMerged(ImsCall call, ImsCall newCall) {
176            onCallStateChanged(call, newCall);
177        }
178
179        /**
180         * Called when the call merge is failed.
181         * The default implementation calls {@link #onCallError}.
182         *
183         * @param call the call object that carries out the IMS call
184         * @param reasonInfo detailed reason of the call merge failure
185         */
186        public void onCallMergeFailed(ImsCall call, ImsReasonInfo reasonInfo) {
187            onCallError(call, reasonInfo);
188        }
189
190        /**
191         * Called when the call is updated (except for hold/unhold).
192         * The default implementation calls {@link #onCallStateChanged}.
193         *
194         * @param call the call object that carries out the IMS call
195         */
196        public void onCallUpdated(ImsCall call) {
197            onCallStateChanged(call);
198        }
199
200        /**
201         * Called when the call update is failed.
202         * The default implementation calls {@link #onCallError}.
203         *
204         * @param call the call object that carries out the IMS call
205         * @param reasonInfo detailed reason of the call update failure
206         */
207        public void onCallUpdateFailed(ImsCall call, ImsReasonInfo reasonInfo) {
208            onCallError(call, reasonInfo);
209        }
210
211        /**
212         * Called when the call update is received from the remote user.
213         *
214         * @param call the call object that carries out the IMS call
215         */
216        public void onCallUpdateReceived(ImsCall call) {
217            // no-op
218        }
219
220        /**
221         * Called when the call is extended to the conference call.
222         * The default implementation calls {@link #onCallStateChanged}.
223         *
224         * @param call the call object that carries out the IMS call
225         * @param newCall the call object that is extended to the conference from the active call
226         */
227        public void onCallConferenceExtended(ImsCall call, ImsCall newCall) {
228            onCallStateChanged(call, newCall);
229        }
230
231        /**
232         * Called when the conference extension is failed.
233         * The default implementation calls {@link #onCallError}.
234         *
235         * @param call the call object that carries out the IMS call
236         * @param reasonInfo detailed reason of the conference extension failure
237         */
238        public void onCallConferenceExtendFailed(ImsCall call,
239                ImsReasonInfo reasonInfo) {
240            onCallError(call, reasonInfo);
241        }
242
243        /**
244         * Called when the conference extension is received from the remote user.
245         *
246         * @param call the call object that carries out the IMS call
247         * @param newCall the call object that is extended to the conference from the active call
248         */
249        public void onCallConferenceExtendReceived(ImsCall call, ImsCall newCall) {
250            onCallStateChanged(call, newCall);
251        }
252
253        /**
254         * Called when the invitation request of the participants is delivered to
255         * the conference server.
256         *
257         * @param call the call object that carries out the IMS call
258         */
259        public void onCallInviteParticipantsRequestDelivered(ImsCall call) {
260            // no-op
261        }
262
263        /**
264         * Called when the invitation request of the participants is failed.
265         *
266         * @param call the call object that carries out the IMS call
267         * @param reasonInfo detailed reason of the conference invitation failure
268         */
269        public void onCallInviteParticipantsRequestFailed(ImsCall call,
270                ImsReasonInfo reasonInfo) {
271            // no-op
272        }
273
274        /**
275         * Called when the removal request of the participants is delivered to
276         * the conference server.
277         *
278         * @param call the call object that carries out the IMS call
279         */
280        public void onCallRemoveParticipantsRequestDelivered(ImsCall call) {
281            // no-op
282        }
283
284        /**
285         * Called when the removal request of the participants is failed.
286         *
287         * @param call the call object that carries out the IMS call
288         * @param reasonInfo detailed reason of the conference removal failure
289         */
290        public void onCallRemoveParticipantsRequestFailed(ImsCall call,
291                ImsReasonInfo reasonInfo) {
292            // no-op
293        }
294
295        /**
296         * Called when the conference state is updated.
297         *
298         * @param call the call object that carries out the IMS call
299         * @param state state of the participant who is participated in the conference call
300         */
301        public void onCallConferenceStateUpdated(ImsCall call, ImsConferenceState state) {
302            // no-op
303        }
304
305        /**
306         * Called when the USSD message is received from the network.
307         *
308         * @param mode mode of the USSD message (REQUEST / NOTIFY)
309         * @param ussdMessage USSD message
310         */
311        public void onCallUssdMessageReceived(ImsCall call,
312                int mode, String ussdMessage) {
313            // no-op
314        }
315
316        /**
317         * Called when an error occurs. The default implementation is no op.
318         * overridden. The default implementation is no op. Error events are
319         * not re-directed to this callback and are handled in {@link #onCallError}.
320         *
321         * @param call the call object that carries out the IMS call
322         * @param reasonInfo detailed reason of this error
323         * @see ImsReasonInfo
324         */
325        public void onCallError(ImsCall call, ImsReasonInfo reasonInfo) {
326            // no-op
327        }
328
329        /**
330         * Called when an event occurs and the corresponding callback is not
331         * overridden. The default implementation is no op. Error events are
332         * not re-directed to this callback and are handled in {@link #onCallError}.
333         *
334         * @param call the call object that carries out the IMS call
335         */
336        public void onCallStateChanged(ImsCall call) {
337            // no-op
338        }
339
340        /**
341         * Called when an event occurs and the corresponding callback is not
342         * overridden. The default implementation is no op. Error events are
343         * not re-directed to this callback and are handled in {@link #onCallError}.
344         *
345         * @param call the call object that carries out the IMS call
346         * @param newCall the call object that will be replaced by the previous call
347         */
348        public void onCallStateChanged(ImsCall call, ImsCall newCall) {
349            // no-op
350        }
351
352        /**
353         * Called when the call moves the hold state to the conversation state.
354         * For example, when merging the active & hold call, the state of all the hold call
355         * will be changed from hold state to conversation state.
356         * This callback method can be invoked even though the application does not trigger
357         * any operations.
358         *
359         * @param call the call object that carries out the IMS call
360         * @param state the detailed state of call state changes;
361         *      Refer to CALL_STATE_* in {@link ImsCall}
362         */
363        public void onCallStateChanged(ImsCall call, int state) {
364            // no-op
365        }
366    }
367
368
369
370    // List of update operation for IMS call control
371    private static final int UPDATE_NONE = 0;
372    private static final int UPDATE_HOLD = 1;
373    private static final int UPDATE_HOLD_MERGE = 2;
374    private static final int UPDATE_RESUME = 3;
375    private static final int UPDATE_MERGE = 4;
376    private static final int UPDATE_EXTEND_TO_CONFERENCE = 5;
377    private static final int UPDATE_UNSPECIFIED = 6;
378
379    // For synchronization of private variables
380    private Object mLockObj = new Object();
381    private Context mContext;
382
383    // true if the call is established & in the conversation state
384    private boolean mInCall = false;
385    // true if the call is on hold
386    // If it is triggered by the local, mute the call. Otherwise, play local hold tone
387    // or network generated media.
388    private boolean mHold = false;
389    // true if the call is on mute
390    private boolean mMute = false;
391    // It contains the exclusive call update request. Refer to UPDATE_*.
392    private int mUpdateRequest = UPDATE_NONE;
393
394    private ImsCall.Listener mListener = null;
395    // It is for managing the multiple calls
396    // when the multiparty call is extended to the conference.
397    private CallGroup mCallGroup = null;
398
399    // Wrapper call session to interworking the IMS service (server).
400    private ImsCallSession mSession = null;
401    // Call profile of the current session.
402    // It can be changed at anytime when the call is updated.
403    private ImsCallProfile mCallProfile = null;
404    // Call profile to be updated after the application's action (accept/reject)
405    // to the call update. After the application's action (accept/reject) is done,
406    // it will be set to null.
407    private ImsCallProfile mProposedCallProfile = null;
408    private ImsReasonInfo mLastReasonInfo = null;
409
410    // Media session to control media (audio/video) operations for an IMS call
411    private ImsStreamMediaSession mMediaSession = null;
412
413    /**
414     * Create an IMS call object.
415     *
416     * @param context the context for accessing system services
417     * @param profile the call profile to make/take a call
418     */
419    public ImsCall(Context context, ImsCallProfile profile) {
420        mContext = context;
421        mCallProfile = profile;
422    }
423
424    /**
425     * Closes this object. This object is not usable after being closed.
426     */
427    @Override
428    public void close() {
429        synchronized(mLockObj) {
430            destroyCallGroup();
431
432            if (mSession != null) {
433                mSession.close();
434                mSession = null;
435            }
436
437            mCallProfile = null;
438            mProposedCallProfile = null;
439            mLastReasonInfo = null;
440            mMediaSession = null;
441        }
442    }
443
444    /**
445     * Checks if the call has a same remote user identity or not.
446     *
447     * @param userId the remote user identity
448     * @return true if the remote user identity is equal; otherwise, false
449     */
450    @Override
451    public boolean checkIfRemoteUserIsSame(String userId) {
452        if (userId == null) {
453            return false;
454        }
455
456        return userId.equals(mCallProfile.getCallExtra(ImsCallProfile.EXTRA_REMOTE_URI, ""));
457    }
458
459    /**
460     * Checks if the call is equal or not.
461     *
462     * @param call the call to be compared
463     * @return true if the call is equal; otherwise, false
464     */
465    @Override
466    public boolean equalsTo(ICall call) {
467        if (call == null) {
468            return false;
469        }
470
471        if (call instanceof ImsCall) {
472            return this.equals((ImsCall)call);
473        }
474
475        return false;
476    }
477
478    /**
479     * Gets the negotiated (local & remote) call profile.
480     *
481     * @return a {@link ImsCallProfile} object that has the negotiated call profile
482     */
483    public ImsCallProfile getCallProfile() {
484        synchronized(mLockObj) {
485            return mCallProfile;
486        }
487    }
488
489    /**
490     * Gets the local call profile (local capabilities).
491     *
492     * @return a {@link ImsCallProfile} object that has the local call profile
493     */
494    public ImsCallProfile getLocalCallProfile() throws ImsException {
495        synchronized(mLockObj) {
496            if (mSession == null) {
497                throw new ImsException("No call session",
498                        ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
499            }
500
501            try {
502                return mSession.getLocalCallProfile();
503            } catch (Throwable t) {
504                loge("getLocalCallProfile :: ", t);
505                throw new ImsException("getLocalCallProfile()", t, 0);
506            }
507        }
508    }
509
510    /**
511     * Gets the call profile proposed by the local/remote user.
512     *
513     * @return a {@link ImsCallProfile} object that has the proposed call profile
514     */
515    public ImsCallProfile getProposedCallProfile() {
516        synchronized(mLockObj) {
517            if (!isInCall()) {
518                return null;
519            }
520
521            return mProposedCallProfile;
522        }
523    }
524
525    /**
526     * Gets the state of the {@link ImsCallSession} that carries this call.
527     * The value returned must be one of the states in {@link ImsCallSession#State}.
528     *
529     * @return the session state
530     */
531    public int getState() {
532        synchronized(mLockObj) {
533            if (mSession == null) {
534                return ImsCallSession.State.IDLE;
535            }
536
537            return mSession.getState();
538        }
539    }
540
541    /**
542     * Gets the {@link ImsCallSession} that carries this call.
543     *
544     * @return the session object that carries this call
545     * @hide
546     */
547    public ImsCallSession getCallSession() {
548        synchronized(mLockObj) {
549            return mSession;
550        }
551    }
552
553    /**
554     * Gets the {@link ImsStreamMediaSession} that handles the media operation of this call.
555     * Almost interface APIs are for the VT (Video Telephony).
556     *
557     * @return the media session object that handles the media operation of this call
558     * @hide
559     */
560    public ImsStreamMediaSession getMediaSession() {
561        synchronized(mLockObj) {
562            return mMediaSession;
563        }
564    }
565
566    /**
567     * Gets the specified property of this call.
568     *
569     * @param name key to get the extra call information defined in {@link ImsCallProfile}
570     * @return the extra call information as string
571     */
572    public String getCallExtra(String name) throws ImsException {
573        // Lookup the cache
574
575        synchronized(mLockObj) {
576            // If not found, try to get the property from the remote
577            if (mSession == null) {
578                throw new ImsException("No call session",
579                        ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
580            }
581
582            try {
583                return mSession.getProperty(name);
584            } catch (Throwable t) {
585                loge("getCallExtra :: ", t);
586                throw new ImsException("getCallExtra()", t, 0);
587            }
588        }
589    }
590
591    /**
592     * Gets the last reason information when the call is not established, cancelled or terminated.
593     *
594     * @return the last reason information
595     */
596    public ImsReasonInfo getLastReasonInfo() {
597        synchronized(mLockObj) {
598            return mLastReasonInfo;
599        }
600    }
601
602    /**
603     * Checks if the call has a pending update operation.
604     *
605     * @return true if the call has a pending update operation
606     */
607    public boolean hasPendingUpdate() {
608        synchronized(mLockObj) {
609            return (mUpdateRequest != UPDATE_NONE);
610        }
611    }
612
613    /**
614     * Checks if the call is established.
615     *
616     * @return true if the call is established
617     */
618    public boolean isInCall() {
619        synchronized(mLockObj) {
620            return mInCall;
621        }
622    }
623
624    /**
625     * Checks if the call is muted.
626     *
627     * @return true if the call is muted
628     */
629    public boolean isMuted() {
630        synchronized(mLockObj) {
631            return mMute;
632        }
633    }
634
635    /**
636     * Checks if the call is on hold.
637     *
638     * @return true if the call is on hold
639     */
640    public boolean isOnHold() {
641        synchronized(mLockObj) {
642            return mHold;
643        }
644    }
645
646    /**
647     * Sets the listener to listen to the IMS call events.
648     * The method calls {@link #setListener setListener(listener, false)}.
649     *
650     * @param listener to listen to the IMS call events of this object; null to remove listener
651     * @see #setListener(Listener, boolean)
652     */
653    public void setListener(ImsCall.Listener listener) {
654        setListener(listener, false);
655    }
656
657    /**
658     * Sets the listener to listen to the IMS call events.
659     * A {@link ImsCall} can only hold one listener at a time. Subsequent calls
660     * to this method override the previous listener.
661     *
662     * @param listener to listen to the IMS call events of this object; null to remove listener
663     * @param callbackImmediately set to true if the caller wants to be called
664     *        back immediately on the current state
665     */
666    public void setListener(ImsCall.Listener listener, boolean callbackImmediately) {
667        boolean inCall;
668        boolean onHold;
669        int state;
670        ImsReasonInfo lastReasonInfo;
671
672        synchronized(mLockObj) {
673            mListener = listener;
674
675            if ((listener == null) || !callbackImmediately) {
676                return;
677            }
678
679            inCall = mInCall;
680            onHold = mHold;
681            state = getState();
682            lastReasonInfo = mLastReasonInfo;
683        }
684
685        try {
686            if (lastReasonInfo != null) {
687                listener.onCallError(this, lastReasonInfo);
688            } else if (inCall) {
689                if (onHold) {
690                    listener.onCallHeld(this);
691                } else {
692                    listener.onCallStarted(this);
693                }
694            } else {
695                switch (state) {
696                    case ImsCallSession.State.ESTABLISHING:
697                        listener.onCallProgressing(this);
698                        break;
699                    case ImsCallSession.State.TERMINATED:
700                        listener.onCallTerminated(this, lastReasonInfo);
701                        break;
702                    default:
703                        // Ignore it. There is no action in the other state.
704                        break;
705                }
706            }
707        } catch (Throwable t) {
708            loge("setListener()", t);
709        }
710    }
711
712    /**
713     * Mutes or unmutes the mic for the active call.
714     *
715     * @param muted true if the call is muted, false otherwise
716     */
717    public void setMute(boolean muted) throws ImsException {
718        synchronized(mLockObj) {
719            if (mMute != muted) {
720                mMute = muted;
721
722                try {
723                    mSession.setMute(muted);
724                } catch (Throwable t) {
725                    loge("setMute :: ", t);
726                    throwImsException(t, 0);
727                }
728            }
729        }
730    }
731
732     /**
733      * Attaches an incoming call to this call object.
734      *
735      * @param session the session that receives the incoming call
736      * @throws ImsException if the IMS service fails to attach this object to the session
737      */
738     public void attachSession(ImsCallSession session) throws ImsException {
739         if (DBG) {
740             log("attachSession :: session=" + session);
741         }
742
743         synchronized(mLockObj) {
744             mSession = session;
745
746             try {
747                 mSession.setListener(createCallSessionListener());
748             } catch (Throwable t) {
749                 loge("attachSession :: ", t);
750                 throwImsException(t, 0);
751             }
752         }
753     }
754
755    /**
756     * Initiates an IMS call with the call profile which is provided
757     * when creating a {@link ImsCall}.
758     *
759     * @param session the {@link ImsCallSession} for carrying out the call
760     * @param callee callee information to initiate an IMS call
761     * @throws ImsException if the IMS service fails to initiate the call
762     */
763    public void start(ImsCallSession session, String callee)
764            throws ImsException {
765        if (DBG) {
766            log("start(1) :: session=" + session + ", callee=" + callee);
767        }
768
769        synchronized(mLockObj) {
770            mSession = session;
771
772            try {
773                session.setListener(createCallSessionListener());
774                session.start(callee, mCallProfile);
775            } catch (Throwable t) {
776                loge("start(1) :: ", t);
777                throw new ImsException("start(1)", t, 0);
778            }
779        }
780    }
781
782    /**
783     * Initiates an IMS conferenca call with the call profile which is provided
784     * when creating a {@link ImsCall}.
785     *
786     * @param session the {@link ImsCallSession} for carrying out the call
787     * @param participants participant list to initiate an IMS conference call
788     * @throws ImsException if the IMS service fails to initiate the call
789     */
790    public void start(ImsCallSession session, String[] participants)
791            throws ImsException {
792        if (DBG) {
793            log("start(n) :: session=" + session + ", callee=" + participants);
794        }
795
796        synchronized(mLockObj) {
797            mSession = session;
798
799            try {
800                session.setListener(createCallSessionListener());
801                session.start(participants, mCallProfile);
802            } catch (Throwable t) {
803                loge("start(n) :: ", t);
804                throw new ImsException("start(n)", t, 0);
805            }
806        }
807    }
808
809    /**
810     * Accepts a call.
811     *
812     * @see Listener#onCallStarted
813     *
814     * @param callType The call type the user agreed to for accepting the call.
815     * @throws ImsException if the IMS service fails to accept the call
816     */
817    public void accept(int callType) throws ImsException {
818        if (DBG) {
819            log("accept :: session=" + mSession);
820        }
821
822        accept(callType, new ImsStreamMediaProfile());
823    }
824
825    /**
826     * Accepts a call.
827     *
828     * @param callType call type to be answered in {@link ImsCallProfile}
829     * @param profile a media profile to be answered (audio/audio & video, direction, ...)
830     * @see Listener#onCallStarted
831     * @throws ImsException if the IMS service fails to accept the call
832     */
833    public void accept(int callType, ImsStreamMediaProfile profile) throws ImsException {
834        if (DBG) {
835            log("accept :: session=" + mSession
836                    + ", callType=" + callType + ", profile=" + profile);
837        }
838
839        synchronized(mLockObj) {
840            if (mSession == null) {
841                throw new ImsException("No call to answer",
842                        ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
843            }
844
845            try {
846                mSession.accept(callType, profile);
847            } catch (Throwable t) {
848                loge("accept :: ", t);
849                throw new ImsException("accept()", t, 0);
850            }
851
852            if (mInCall && (mProposedCallProfile != null)) {
853                if (DBG) {
854                    log("accept :: call profile will be updated");
855                }
856
857                mCallProfile = mProposedCallProfile;
858                mProposedCallProfile = null;
859            }
860
861            // Other call update received
862            if (mInCall && (mUpdateRequest == UPDATE_UNSPECIFIED)) {
863                mUpdateRequest = UPDATE_NONE;
864            }
865        }
866    }
867
868    /**
869     * Rejects a call.
870     *
871     * @param reason reason code to reject an incoming call
872     * @see Listener#onCallStartFailed
873     * @throws ImsException if the IMS service fails to accept the call
874     */
875    public void reject(int reason) throws ImsException {
876        if (DBG) {
877            log("reject :: session=" + mSession + ", reason=" + reason);
878        }
879
880        synchronized(mLockObj) {
881            if (mSession != null) {
882                mSession.reject(reason);
883            }
884
885            if (mInCall && (mProposedCallProfile != null)) {
886                if (DBG) {
887                    log("reject :: call profile is not updated; destroy it...");
888                }
889
890                mProposedCallProfile = null;
891            }
892
893            // Other call update received
894            if (mInCall && (mUpdateRequest == UPDATE_UNSPECIFIED)) {
895                mUpdateRequest = UPDATE_NONE;
896            }
897        }
898    }
899
900    /**
901     * Terminates an IMS call.
902     *
903     * @param reason reason code to terminate a call
904     * @throws ImsException if the IMS service fails to terminate the call
905     */
906    public void terminate(int reason) throws ImsException {
907        if (DBG) {
908            log("terminate :: session=" + mSession + ", reason=" + reason);
909        }
910
911        synchronized(mLockObj) {
912            mHold = false;
913            mInCall = false;
914            CallGroup callGroup = getCallGroup();
915
916            if (mSession != null) {
917                if (callGroup != null && !callGroup.isOwner(ImsCall.this)) {
918                    log("terminate owner of the call group");
919                    ImsCall owner = (ImsCall) callGroup.getOwner();
920                    if (owner != null) {
921                        owner.terminate(reason);
922                        return;
923                    }
924                }
925                mSession.terminate(reason);
926            }
927        }
928    }
929
930
931    /**
932     * Puts a call on hold. When succeeds, {@link Listener#onCallHeld} is called.
933     *
934     * @see Listener#onCallHeld, Listener#onCallHoldFailed
935     * @throws ImsException if the IMS service fails to hold the call
936     */
937    public void hold() throws ImsException {
938        if (DBG) {
939            log("hold :: session=" + mSession);
940        }
941
942        if (isOnHold()) {
943            if (DBG) {
944                log("hold :: call is already on hold");
945            }
946            return;
947        }
948
949        synchronized(mLockObj) {
950            if (mUpdateRequest != UPDATE_NONE) {
951                loge("hold :: update is in progress; request=" + mUpdateRequest);
952                throw new ImsException("Call update is in progress",
953                        ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
954            }
955
956            if (mSession == null) {
957                loge("hold :: ");
958                throw new ImsException("No call session",
959                        ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
960            }
961
962            mSession.hold(createHoldMediaProfile());
963            // FIXME: update the state on the callback?
964            mHold = true;
965            mUpdateRequest = UPDATE_HOLD;
966        }
967    }
968
969    /**
970     * Continues a call that's on hold. When succeeds, {@link Listener#onCallResumed} is called.
971     *
972     * @see Listener#onCallResumed, Listener#onCallResumeFailed
973     * @throws ImsException if the IMS service fails to resume the call
974     */
975    public void resume() throws ImsException {
976        if (DBG) {
977            log("resume :: session=" + mSession);
978        }
979
980        if (!isOnHold()) {
981            if (DBG) {
982                log("resume :: call is in conversation");
983            }
984            return;
985        }
986
987        synchronized(mLockObj) {
988            if (mUpdateRequest != UPDATE_NONE) {
989                loge("resume :: update is in progress; request=" + mUpdateRequest);
990                throw new ImsException("Call update is in progress",
991                        ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
992            }
993
994            if (mSession == null) {
995                loge("resume :: ");
996                throw new ImsException("No call session",
997                        ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
998            }
999
1000            mSession.resume(createResumeMediaProfile());
1001            // FIXME: update the state on the callback?
1002            mHold = false;
1003            mUpdateRequest = UPDATE_RESUME;
1004        }
1005    }
1006
1007    /**
1008     * Merges the active & hold call.
1009     *
1010     * @see Listener#onCallMerged, Listener#onCallMergeFailed
1011     * @throws ImsException if the IMS service fails to merge the call
1012     */
1013    public void merge() throws ImsException {
1014        if (DBG) {
1015            log("merge :: session=" + mSession);
1016        }
1017
1018        synchronized(mLockObj) {
1019            if (mUpdateRequest != UPDATE_NONE) {
1020                loge("merge :: update is in progress; request=" + mUpdateRequest);
1021                throw new ImsException("Call update is in progress",
1022                        ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
1023            }
1024
1025            if (mSession == null) {
1026                loge("merge :: ");
1027                throw new ImsException("No call session",
1028                        ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1029            }
1030
1031            // if skipHoldBeforeMerge = true, IMS service implementation will
1032            // merge without explicitly holding the call.
1033            if (mHold || (mContext.getResources().getBoolean(
1034                    com.android.internal.R.bool.skipHoldBeforeMerge))) {
1035                mSession.merge();
1036                mUpdateRequest = UPDATE_MERGE;
1037            } else {
1038                mSession.hold(createHoldMediaProfile());
1039                // FIXME: ?
1040                mHold = true;
1041                mUpdateRequest = UPDATE_HOLD_MERGE;
1042            }
1043        }
1044    }
1045
1046    /**
1047     * Merges the active & hold call.
1048     *
1049     * @param bgCall the background (holding) call
1050     * @see Listener#onCallMerged, Listener#onCallMergeFailed
1051     * @throws ImsException if the IMS service fails to merge the call
1052     */
1053    public void merge(ImsCall bgCall) throws ImsException {
1054        if (DBG) {
1055            log("merge(1) :: session=" + mSession);
1056        }
1057
1058        if (bgCall == null) {
1059            throw new ImsException("No background call",
1060                    ImsReasonInfo.CODE_LOCAL_ILLEGAL_ARGUMENT);
1061        }
1062
1063        synchronized(mLockObj) {
1064            createCallGroup(bgCall);
1065        }
1066
1067        merge();
1068    }
1069
1070    /**
1071     * Updates the current call's properties (ex. call mode change: video upgrade / downgrade).
1072     */
1073    public void update(int callType, ImsStreamMediaProfile mediaProfile) throws ImsException {
1074        if (DBG) {
1075            log("update :: session=" + mSession);
1076        }
1077
1078        if (isOnHold()) {
1079            if (DBG) {
1080                log("update :: call is on hold");
1081            }
1082            throw new ImsException("Not in a call to update call",
1083                    ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
1084        }
1085
1086        synchronized(mLockObj) {
1087            if (mUpdateRequest != UPDATE_NONE) {
1088                if (DBG) {
1089                    log("update :: update is in progress; request=" + mUpdateRequest);
1090                }
1091                throw new ImsException("Call update is in progress",
1092                        ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
1093            }
1094
1095            if (mSession == null) {
1096                loge("update :: ");
1097                throw new ImsException("No call session",
1098                        ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1099            }
1100
1101            mSession.update(callType, mediaProfile);
1102            mUpdateRequest = UPDATE_UNSPECIFIED;
1103        }
1104    }
1105
1106    /**
1107     * Extends this call (1-to-1 call) to the conference call
1108     * inviting the specified participants to.
1109     *
1110     */
1111    public void extendToConference(String[] participants) throws ImsException {
1112        if (DBG) {
1113            log("extendToConference :: session=" + mSession);
1114        }
1115
1116        if (isOnHold()) {
1117            if (DBG) {
1118                log("extendToConference :: call is on hold");
1119            }
1120            throw new ImsException("Not in a call to extend a call to conference",
1121                    ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
1122        }
1123
1124        synchronized(mLockObj) {
1125            if (mUpdateRequest != UPDATE_NONE) {
1126                if (DBG) {
1127                    log("extendToConference :: update is in progress; request=" + mUpdateRequest);
1128                }
1129                throw new ImsException("Call update is in progress",
1130                        ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
1131            }
1132
1133            if (mSession == null) {
1134                loge("extendToConference :: ");
1135                throw new ImsException("No call session",
1136                        ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1137            }
1138
1139            mSession.extendToConference(participants);
1140            mUpdateRequest = UPDATE_EXTEND_TO_CONFERENCE;
1141        }
1142    }
1143
1144    /**
1145     * Requests the conference server to invite an additional participants to the conference.
1146     *
1147     */
1148    public void inviteParticipants(String[] participants) throws ImsException {
1149        if (DBG) {
1150            log("inviteParticipants :: session=" + mSession);
1151        }
1152
1153        synchronized(mLockObj) {
1154            if (mSession == null) {
1155                loge("inviteParticipants :: ");
1156                throw new ImsException("No call session",
1157                        ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1158            }
1159
1160            mSession.inviteParticipants(participants);
1161        }
1162    }
1163
1164    /**
1165     * Requests the conference server to remove the specified participants from the conference.
1166     *
1167     */
1168    public void removeParticipants(String[] participants) throws ImsException {
1169        if (DBG) {
1170            log("removeParticipants :: session=" + mSession);
1171        }
1172
1173        synchronized(mLockObj) {
1174            if (mSession == null) {
1175                loge("removeParticipants :: ");
1176                throw new ImsException("No call session",
1177                        ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1178            }
1179
1180            mSession.removeParticipants(participants);
1181        }
1182    }
1183
1184
1185    /**
1186     * Sends a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2833</a>,
1187     * event 0 ~ 9 maps to decimal value 0 ~ 9, '*' to 10, '#' to 11, event 'A' ~ 'D' to 12 ~ 15,
1188     * and event flash to 16. Currently, event flash is not supported.
1189     *
1190     * @param char that represents the DTMF digit to send.
1191     */
1192    public void sendDtmf(char c) {
1193        sendDtmf(c, null);
1194    }
1195
1196    /**
1197     * Sends a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2833</a>,
1198     * event 0 ~ 9 maps to decimal value 0 ~ 9, '*' to 10, '#' to 11, event 'A' ~ 'D' to 12 ~ 15,
1199     * and event flash to 16. Currently, event flash is not supported.
1200     *
1201     * @param c that represents the DTMF to send. '0' ~ '9', 'A' ~ 'D', '*', '#' are valid inputs.
1202     * @param result the result message to send when done.
1203     */
1204    public void sendDtmf(char c, Message result) {
1205        if (DBG) {
1206            log("sendDtmf :: session=" + mSession + ", code=" + c);
1207        }
1208
1209        synchronized(mLockObj) {
1210            if (mSession != null) {
1211                mSession.sendDtmf(c);
1212            }
1213        }
1214
1215        if (result != null) {
1216            result.sendToTarget();
1217        }
1218    }
1219
1220    /**
1221     * Sends an USSD message.
1222     *
1223     * @param ussdMessage USSD message to send
1224     */
1225    public void sendUssd(String ussdMessage) throws ImsException {
1226        if (DBG) {
1227            log("sendUssd :: session=" + mSession + ", ussdMessage=" + ussdMessage);
1228        }
1229
1230        synchronized(mLockObj) {
1231            if (mSession == null) {
1232                loge("sendUssd :: ");
1233                throw new ImsException("No call session",
1234                        ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1235            }
1236
1237            mSession.sendUssd(ussdMessage);
1238        }
1239    }
1240
1241    private void clear(ImsReasonInfo lastReasonInfo) {
1242        mInCall = false;
1243        mHold = false;
1244        mUpdateRequest = UPDATE_NONE;
1245        mLastReasonInfo = lastReasonInfo;
1246        destroyCallGroup();
1247    }
1248
1249    private void createCallGroup(ImsCall neutralReferrer) {
1250        CallGroup referrerCallGroup = neutralReferrer.getCallGroup();
1251
1252        if (mCallGroup == null) {
1253            if (referrerCallGroup == null) {
1254                mCallGroup = CallGroupManager.getInstance().createCallGroup(new ImsCallGroup());
1255            } else {
1256                mCallGroup = referrerCallGroup;
1257            }
1258
1259            if (mCallGroup != null) {
1260                mCallGroup.setNeutralReferrer(neutralReferrer);
1261            }
1262        } else {
1263            mCallGroup.setNeutralReferrer(neutralReferrer);
1264
1265            if ((referrerCallGroup != null)
1266                    && (mCallGroup != referrerCallGroup)) {
1267                loge("fatal :: call group is mismatched; call is corrupted...");
1268            }
1269        }
1270    }
1271
1272    private void updateCallGroup(ImsCall owner) {
1273        if (mCallGroup == null) {
1274            return;
1275        }
1276
1277        ImsCall neutralReferrer = (ImsCall)mCallGroup.getNeutralReferrer();
1278
1279        if (owner == null) {
1280            // Maintain the call group if the current call has been merged in the past.
1281            if (!mCallGroup.hasReferrer()) {
1282                CallGroupManager.getInstance().destroyCallGroup(mCallGroup);
1283                mCallGroup = null;
1284            }
1285        } else {
1286            mCallGroup.addReferrer(this);
1287
1288            if (neutralReferrer != null) {
1289                if (neutralReferrer.getCallGroup() == null) {
1290                    neutralReferrer.setCallGroup(mCallGroup);
1291                    mCallGroup.addReferrer(neutralReferrer);
1292                }
1293
1294                neutralReferrer.enforceConversationMode();
1295            }
1296
1297            // Close the existing owner call if present
1298            ImsCall exOwner = (ImsCall)mCallGroup.getOwner();
1299
1300            mCallGroup.setOwner(owner);
1301
1302            if (exOwner != null) {
1303                exOwner.close();
1304            }
1305        }
1306    }
1307
1308    private void destroyCallGroup() {
1309        if (mCallGroup == null) {
1310            return;
1311        }
1312
1313        mCallGroup.removeReferrer(this);
1314
1315        if (!mCallGroup.hasReferrer()) {
1316            CallGroupManager.getInstance().destroyCallGroup(mCallGroup);
1317        }
1318
1319        mCallGroup = null;
1320    }
1321
1322    private CallGroup getCallGroup() {
1323        synchronized(mLockObj) {
1324            return mCallGroup;
1325        }
1326    }
1327
1328    private void setCallGroup(CallGroup callGroup) {
1329        synchronized(mLockObj) {
1330            mCallGroup = callGroup;
1331        }
1332    }
1333
1334    /**
1335     * Creates an IMS call session listener.
1336     */
1337    private ImsCallSession.Listener createCallSessionListener() {
1338        return new ImsCallSessionListenerProxy();
1339    }
1340
1341    private ImsCall createNewCall(ImsCallSession session, ImsCallProfile profile) {
1342        ImsCall call = new ImsCall(mContext, profile);
1343
1344        try {
1345            call.attachSession(session);
1346        } catch (ImsException e) {
1347            if (call != null) {
1348                call.close();
1349                call = null;
1350            }
1351        }
1352
1353        // Do additional operations...
1354
1355        return call;
1356    }
1357
1358    private ImsStreamMediaProfile createHoldMediaProfile() {
1359        ImsStreamMediaProfile mediaProfile = new ImsStreamMediaProfile();
1360
1361        if (mCallProfile == null) {
1362            return mediaProfile;
1363        }
1364
1365        mediaProfile.mAudioQuality = mCallProfile.mMediaProfile.mAudioQuality;
1366        mediaProfile.mVideoQuality = mCallProfile.mMediaProfile.mVideoQuality;
1367        mediaProfile.mAudioDirection = ImsStreamMediaProfile.DIRECTION_SEND;
1368
1369        if (mediaProfile.mVideoQuality != ImsStreamMediaProfile.VIDEO_QUALITY_NONE) {
1370            mediaProfile.mVideoDirection = ImsStreamMediaProfile.DIRECTION_SEND;
1371        }
1372
1373        return mediaProfile;
1374    }
1375
1376    private ImsStreamMediaProfile createResumeMediaProfile() {
1377        ImsStreamMediaProfile mediaProfile = new ImsStreamMediaProfile();
1378
1379        if (mCallProfile == null) {
1380            return mediaProfile;
1381        }
1382
1383        mediaProfile.mAudioQuality = mCallProfile.mMediaProfile.mAudioQuality;
1384        mediaProfile.mVideoQuality = mCallProfile.mMediaProfile.mVideoQuality;
1385        mediaProfile.mAudioDirection = ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE;
1386
1387        if (mediaProfile.mVideoQuality != ImsStreamMediaProfile.VIDEO_QUALITY_NONE) {
1388            mediaProfile.mVideoDirection = ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE;
1389        }
1390
1391        return mediaProfile;
1392    }
1393
1394    private void enforceConversationMode() {
1395        if (mInCall) {
1396            mHold = false;
1397            mUpdateRequest = UPDATE_NONE;
1398        }
1399    }
1400
1401    private void mergeInternal() {
1402        if (DBG) {
1403            log("mergeInternal :: session=" + mSession);
1404        }
1405
1406        mSession.merge();
1407        mUpdateRequest = UPDATE_MERGE;
1408    }
1409
1410    private void notifyCallStateChanged() {
1411        int state = 0;
1412
1413        if (mInCall && (mUpdateRequest == UPDATE_HOLD_MERGE)) {
1414            state = CALL_STATE_ACTIVE_TO_HOLD;
1415            mHold = true;
1416        } else if (mInCall && ((mUpdateRequest == UPDATE_MERGE)
1417                || (mUpdateRequest == UPDATE_EXTEND_TO_CONFERENCE))) {
1418            state = CALL_STATE_HOLD_TO_ACTIVE;
1419            mHold = false;
1420            mMute = false;
1421        }
1422
1423        if (state != 0) {
1424            if (mListener != null) {
1425                try {
1426                    mListener.onCallStateChanged(ImsCall.this, state);
1427                } catch (Throwable t) {
1428                    loge("notifyCallStateChanged :: ", t);
1429                }
1430            }
1431        }
1432    }
1433
1434    private void notifyConferenceSessionTerminated(ImsReasonInfo reasonInfo) {
1435        ImsCall.Listener listener;
1436
1437        if (mCallGroup.isOwner(ImsCall.this)) {
1438            loge("Group Owner! Size of referrers list = " + mCallGroup.getReferrers().size());
1439            while (mCallGroup.hasReferrer()) {
1440                ImsCall call = (ImsCall) mCallGroup.getReferrers().get(0);
1441                loge("onCallTerminated to be called for the call:: " + call);
1442
1443                if (call == null) {
1444                    continue;
1445                }
1446
1447                listener = call.mListener;
1448                call.clear(reasonInfo);
1449
1450                if (listener != null) {
1451                    try {
1452                        listener.onCallTerminated(call, reasonInfo);
1453                    } catch (Throwable t) {
1454                        loge("notifyConferenceSessionTerminated :: ", t);
1455                    }
1456                }
1457            }
1458        } else if (!mCallGroup.isReferrer(ImsCall.this)) {
1459            return;
1460        }
1461
1462        listener = mListener;
1463        clear(reasonInfo);
1464
1465        if (listener != null) {
1466            try {
1467                listener.onCallTerminated(this, reasonInfo);
1468            } catch (Throwable t) {
1469                loge("notifyConferenceSessionTerminated :: ", t);
1470            }
1471        }
1472    }
1473
1474    private void notifyConferenceStateUpdated(ImsConferenceState state) {
1475        Set<Entry<String, Bundle>> paticipants = state.mParticipants.entrySet();
1476
1477        if (paticipants == null) {
1478            return;
1479        }
1480
1481        Iterator<Entry<String, Bundle>> iterator = paticipants.iterator();
1482
1483        while (iterator.hasNext()) {
1484            Entry<String, Bundle> entry = iterator.next();
1485
1486            String key = entry.getKey();
1487            Bundle confInfo = entry.getValue();
1488            String status = confInfo.getString(ImsConferenceState.STATUS);
1489            String user = confInfo.getString(ImsConferenceState.USER);
1490            String endpoint = confInfo.getString(ImsConferenceState.ENDPOINT);
1491
1492            if (DBG) {
1493                log("notifyConferenceStateUpdated :: key=" + key +
1494                        ", status=" + status +
1495                        ", user=" + user +
1496                        ", endpoint=" + endpoint);
1497            }
1498
1499            if ((mCallGroup != null) && (!mCallGroup.isOwner(ImsCall.this))) {
1500                continue;
1501            }
1502
1503            ImsCall referrer = (ImsCall)mCallGroup.getReferrer(endpoint);
1504
1505            if (referrer == null) {
1506                continue;
1507            }
1508
1509            if (referrer.mListener == null) {
1510                continue;
1511            }
1512
1513            try {
1514                if (status.equals(ImsConferenceState.STATUS_ALERTING)) {
1515                    referrer.mListener.onCallProgressing(referrer);
1516                }
1517                else if (status.equals(ImsConferenceState.STATUS_CONNECT_FAIL)) {
1518                    referrer.mListener.onCallStartFailed(referrer, new ImsReasonInfo());
1519                }
1520                else if (status.equals(ImsConferenceState.STATUS_ON_HOLD)) {
1521                    referrer.mListener.onCallHoldReceived(referrer);
1522                }
1523                else if (status.equals(ImsConferenceState.STATUS_CONNECTED)) {
1524                    referrer.mListener.onCallStarted(referrer);
1525                }
1526                else if (status.equals(ImsConferenceState.STATUS_DISCONNECTED)) {
1527                    referrer.clear(new ImsReasonInfo());
1528                    referrer.mListener.onCallTerminated(referrer, referrer.mLastReasonInfo);
1529                }
1530            } catch (Throwable t) {
1531                loge("notifyConferenceStateUpdated :: ", t);
1532            }
1533        }
1534    }
1535
1536    private void notifyError(int reason, int statusCode, String message) {
1537    }
1538
1539    private void throwImsException(Throwable t, int code) throws ImsException {
1540        if (t instanceof ImsException) {
1541            throw (ImsException) t;
1542        } else {
1543            throw new ImsException(String.valueOf(code), t, code);
1544        }
1545    }
1546
1547    private void log(String s) {
1548        Rlog.d(TAG, s);
1549    }
1550
1551    private void loge(String s) {
1552        Rlog.e(TAG, s);
1553    }
1554
1555    private void loge(String s, Throwable t) {
1556        Rlog.e(TAG, s, t);
1557    }
1558
1559    private class ImsCallSessionListenerProxy extends ImsCallSession.Listener {
1560        @Override
1561        public void callSessionProgressing(ImsCallSession session,
1562                ImsStreamMediaProfile profile) {
1563            if (DBG) {
1564                log("callSessionProgressing :: session=" + session + ", profile=" + profile);
1565            }
1566
1567            ImsCall.Listener listener;
1568
1569            synchronized(ImsCall.this) {
1570                listener = mListener;
1571                mCallProfile.mMediaProfile.copyFrom(profile);
1572            }
1573
1574            if (listener != null) {
1575                try {
1576                    listener.onCallProgressing(ImsCall.this);
1577                } catch (Throwable t) {
1578                    loge("callSessionProgressing :: ", t);
1579                }
1580            }
1581        }
1582
1583        @Override
1584        public void callSessionStarted(ImsCallSession session,
1585                ImsCallProfile profile) {
1586            if (DBG) {
1587                log("callSessionStarted :: session=" + session + ", profile=" + profile);
1588            }
1589
1590            ImsCall.Listener listener;
1591
1592            synchronized(ImsCall.this) {
1593                listener = mListener;
1594                mCallProfile = profile;
1595            }
1596
1597            if (listener != null) {
1598                try {
1599                    listener.onCallStarted(ImsCall.this);
1600                } catch (Throwable t) {
1601                    loge("callSessionStarted :: ", t);
1602                }
1603            }
1604        }
1605
1606        @Override
1607        public void callSessionStartFailed(ImsCallSession session,
1608                ImsReasonInfo reasonInfo) {
1609            if (DBG) {
1610                log("callSessionStartFailed :: session=" + session +
1611                        ", reasonInfo=" + reasonInfo);
1612            }
1613
1614            ImsCall.Listener listener;
1615
1616            synchronized(ImsCall.this) {
1617                listener = mListener;
1618                mLastReasonInfo = reasonInfo;
1619            }
1620
1621            if (listener != null) {
1622                try {
1623                    listener.onCallStartFailed(ImsCall.this, reasonInfo);
1624                } catch (Throwable t) {
1625                    loge("callSessionStarted :: ", t);
1626                }
1627            }
1628        }
1629
1630        @Override
1631        public void callSessionTerminated(ImsCallSession session,
1632                ImsReasonInfo reasonInfo) {
1633            if (DBG) {
1634                log("callSessionTerminated :: session=" + session +
1635                        ", reasonInfo=" + reasonInfo);
1636            }
1637
1638            ImsCall.Listener listener = null;
1639
1640            synchronized(ImsCall.this) {
1641                if (mCallGroup != null) {
1642                    notifyConferenceSessionTerminated(reasonInfo);
1643                } else {
1644                    listener = mListener;
1645                    clear(reasonInfo);
1646                }
1647            }
1648
1649            if (listener != null) {
1650                try {
1651                    listener.onCallTerminated(ImsCall.this, reasonInfo);
1652                } catch (Throwable t) {
1653                    loge("callSessionTerminated :: ", t);
1654                }
1655            }
1656        }
1657
1658        @Override
1659        public void callSessionHeld(ImsCallSession session,
1660                ImsCallProfile profile) {
1661            if (DBG) {
1662                log("callSessionHeld :: session=" + session + ", profile=" + profile);
1663            }
1664
1665            ImsCall.Listener listener;
1666
1667            synchronized(ImsCall.this) {
1668                mCallProfile = profile;
1669
1670                if (mUpdateRequest == UPDATE_HOLD_MERGE) {
1671                    mergeInternal();
1672                    return;
1673                }
1674
1675                mUpdateRequest = UPDATE_NONE;
1676                listener = mListener;
1677            }
1678
1679            if (listener != null) {
1680                try {
1681                    listener.onCallHeld(ImsCall.this);
1682                } catch (Throwable t) {
1683                    loge("callSessionHeld :: ", t);
1684                }
1685            }
1686        }
1687
1688        @Override
1689        public void callSessionHoldFailed(ImsCallSession session,
1690                ImsReasonInfo reasonInfo) {
1691            if (DBG) {
1692                log("callSessionHoldFailed :: session=" + session +
1693                        ", reasonInfo=" + reasonInfo);
1694            }
1695
1696            boolean isHoldForMerge = false;
1697            ImsCall.Listener listener;
1698
1699            synchronized(ImsCall.this) {
1700                if (mUpdateRequest == UPDATE_HOLD_MERGE) {
1701                    isHoldForMerge = true;
1702                }
1703
1704                mUpdateRequest = UPDATE_NONE;
1705                listener = mListener;
1706            }
1707
1708            if (isHoldForMerge) {
1709                callSessionMergeFailed(session, reasonInfo);
1710                return;
1711            }
1712
1713            if (listener != null) {
1714                try {
1715                    listener.onCallHoldFailed(ImsCall.this, reasonInfo);
1716                } catch (Throwable t) {
1717                    loge("callSessionHoldFailed :: ", t);
1718                }
1719            }
1720        }
1721
1722        @Override
1723        public void callSessionHoldReceived(ImsCallSession session,
1724                ImsCallProfile profile) {
1725            if (DBG) {
1726                log("callSessionHoldReceived :: session=" + session + ", profile=" + profile);
1727            }
1728
1729            ImsCall.Listener listener;
1730
1731            synchronized(ImsCall.this) {
1732                listener = mListener;
1733                mCallProfile = profile;
1734            }
1735
1736            if (listener != null) {
1737                try {
1738                    listener.onCallHoldReceived(ImsCall.this);
1739                } catch (Throwable t) {
1740                    loge("callSessionHoldReceived :: ", t);
1741                }
1742            }
1743        }
1744
1745        @Override
1746        public void callSessionResumed(ImsCallSession session,
1747                ImsCallProfile profile) {
1748            if (DBG) {
1749                log("callSessionResumed :: session=" + session + ", profile=" + profile);
1750            }
1751
1752            ImsCall.Listener listener;
1753
1754            synchronized(ImsCall.this) {
1755                listener = mListener;
1756                mCallProfile = profile;
1757                mUpdateRequest = UPDATE_NONE;
1758            }
1759
1760            if (listener != null) {
1761                try {
1762                    listener.onCallResumed(ImsCall.this);
1763                } catch (Throwable t) {
1764                    loge("callSessionResumed :: ", t);
1765                }
1766            }
1767        }
1768
1769        @Override
1770        public void callSessionResumeFailed(ImsCallSession session,
1771                ImsReasonInfo reasonInfo) {
1772            if (DBG) {
1773                log("callSessionResumeFailed :: session=" + session +
1774                        ", reasonInfo=" + reasonInfo);
1775            }
1776
1777            ImsCall.Listener listener;
1778
1779            synchronized(ImsCall.this) {
1780                listener = mListener;
1781                mUpdateRequest = UPDATE_NONE;
1782            }
1783
1784            if (listener != null) {
1785                try {
1786                    listener.onCallResumeFailed(ImsCall.this, reasonInfo);
1787                } catch (Throwable t) {
1788                    loge("callSessionResumeFailed :: ", t);
1789                }
1790            }
1791        }
1792
1793        @Override
1794        public void callSessionResumeReceived(ImsCallSession session,
1795                ImsCallProfile profile) {
1796            if (DBG) {
1797                log("callSessionResumeReceived :: session=" + session +
1798                        ", profile=" + profile);
1799            }
1800
1801            ImsCall.Listener listener;
1802
1803            synchronized(ImsCall.this) {
1804                listener = mListener;
1805                mCallProfile = profile;
1806            }
1807
1808            if (listener != null) {
1809                try {
1810                    listener.onCallResumeReceived(ImsCall.this);
1811                } catch (Throwable t) {
1812                    loge("callSessionResumeReceived :: ", t);
1813                }
1814            }
1815        }
1816
1817        @Override
1818        public void callSessionMerged(ImsCallSession session,
1819                ImsCallSession newSession, ImsCallProfile profile) {
1820            if (DBG) {
1821                log("callSessionMerged :: session=" + session
1822                        + ", newSession=" + newSession + ", profile=" + profile);
1823            }
1824
1825            ImsCall newCall = createNewCall(newSession, profile);
1826
1827            if (newCall == null) {
1828                callSessionMergeFailed(session, new ImsReasonInfo());
1829                return;
1830            }
1831
1832            ImsCall.Listener listener;
1833
1834            synchronized(ImsCall.this) {
1835                listener = mListener;
1836                updateCallGroup(newCall);
1837                newCall.setListener(mListener);
1838                newCall.setCallGroup(mCallGroup);
1839                mUpdateRequest = UPDATE_NONE;
1840            }
1841
1842            if (listener != null) {
1843                try {
1844                    listener.onCallMerged(ImsCall.this, newCall);
1845                } catch (Throwable t) {
1846                    loge("callSessionMerged :: ", t);
1847                }
1848            }
1849        }
1850
1851        @Override
1852        public void callSessionMergeFailed(ImsCallSession session,
1853                ImsReasonInfo reasonInfo) {
1854            if (DBG) {
1855                log("callSessionMergeFailed :: session=" + session +
1856                        ", reasonInfo=" + reasonInfo);
1857            }
1858
1859            ImsCall.Listener listener;
1860
1861            synchronized(ImsCall.this) {
1862                listener = mListener;
1863                updateCallGroup(null);
1864                mUpdateRequest = UPDATE_NONE;
1865            }
1866
1867            if (listener != null) {
1868                try {
1869                    listener.onCallMergeFailed(ImsCall.this, reasonInfo);
1870                } catch (Throwable t) {
1871                    loge("callSessionMergeFailed :: ", t);
1872                }
1873            }
1874        }
1875
1876        @Override
1877        public void callSessionUpdated(ImsCallSession session,
1878                ImsCallProfile profile) {
1879            if (DBG) {
1880                log("callSessionUpdated :: session=" + session + ", profile=" + profile);
1881            }
1882
1883            ImsCall.Listener listener;
1884
1885            synchronized(ImsCall.this) {
1886                listener = mListener;
1887                mCallProfile = profile;
1888                mUpdateRequest = UPDATE_NONE;
1889            }
1890
1891            if (listener != null) {
1892                try {
1893                    listener.onCallUpdated(ImsCall.this);
1894                } catch (Throwable t) {
1895                    loge("callSessionUpdated :: ", t);
1896                }
1897            }
1898        }
1899
1900        @Override
1901        public void callSessionUpdateFailed(ImsCallSession session,
1902                ImsReasonInfo reasonInfo) {
1903            if (DBG) {
1904                log("callSessionUpdateFailed :: session=" + session +
1905                        ", reasonInfo=" + reasonInfo);
1906            }
1907
1908            ImsCall.Listener listener;
1909
1910            synchronized(ImsCall.this) {
1911                listener = mListener;
1912                mUpdateRequest = UPDATE_NONE;
1913            }
1914
1915            if (listener != null) {
1916                try {
1917                    listener.onCallUpdateFailed(ImsCall.this, reasonInfo);
1918                } catch (Throwable t) {
1919                    loge("callSessionUpdateFailed :: ", t);
1920                }
1921            }
1922        }
1923
1924        @Override
1925        public void callSessionUpdateReceived(ImsCallSession session,
1926                ImsCallProfile profile) {
1927            if (DBG) {
1928                log("callSessionUpdateReceived :: session=" + session +
1929                        ", profile=" + profile);
1930            }
1931
1932            ImsCall.Listener listener;
1933
1934            synchronized(ImsCall.this) {
1935                listener = mListener;
1936                mProposedCallProfile = profile;
1937                mUpdateRequest = UPDATE_UNSPECIFIED;
1938            }
1939
1940            if (listener != null) {
1941                try {
1942                    listener.onCallUpdateReceived(ImsCall.this);
1943                } catch (Throwable t) {
1944                    loge("callSessionUpdateReceived :: ", t);
1945                }
1946            }
1947        }
1948
1949        @Override
1950        public void callSessionConferenceExtended(ImsCallSession session,
1951                ImsCallSession newSession, ImsCallProfile profile) {
1952            if (DBG) {
1953                log("callSessionConferenceExtended :: session=" + session
1954                        + ", newSession=" + newSession + ", profile=" + profile);
1955            }
1956
1957            ImsCall newCall = createNewCall(newSession, profile);
1958
1959            if (newCall == null) {
1960                callSessionConferenceExtendFailed(session, new ImsReasonInfo());
1961                return;
1962            }
1963
1964            ImsCall.Listener listener;
1965
1966            synchronized(ImsCall.this) {
1967                listener = mListener;
1968                mUpdateRequest = UPDATE_NONE;
1969            }
1970
1971            if (listener != null) {
1972                try {
1973                    listener.onCallConferenceExtended(ImsCall.this, newCall);
1974                } catch (Throwable t) {
1975                    loge("callSessionConferenceExtended :: ", t);
1976                }
1977            }
1978        }
1979
1980        @Override
1981        public void callSessionConferenceExtendFailed(ImsCallSession session,
1982                ImsReasonInfo reasonInfo) {
1983            if (DBG) {
1984                log("callSessionConferenceExtendFailed :: session=" + session +
1985                        ", reasonInfo=" + reasonInfo);
1986            }
1987
1988            ImsCall.Listener listener;
1989
1990            synchronized(ImsCall.this) {
1991                listener = mListener;
1992                mUpdateRequest = UPDATE_NONE;
1993            }
1994
1995            if (listener != null) {
1996                try {
1997                    listener.onCallConferenceExtendFailed(ImsCall.this, reasonInfo);
1998                } catch (Throwable t) {
1999                    loge("callSessionConferenceExtendFailed :: ", t);
2000                }
2001            }
2002        }
2003
2004        @Override
2005        public void callSessionConferenceExtendReceived(ImsCallSession session,
2006                ImsCallSession newSession, ImsCallProfile profile) {
2007            if (DBG) {
2008                log("callSessionConferenceExtendReceived :: session=" + session
2009                        + ", newSession=" + newSession + ", profile=" + profile);
2010            }
2011
2012            ImsCall newCall = createNewCall(newSession, profile);
2013
2014            if (newCall == null) {
2015                // Should all the calls be terminated...???
2016                return;
2017            }
2018
2019            ImsCall.Listener listener;
2020
2021            synchronized(ImsCall.this) {
2022                listener = mListener;
2023            }
2024
2025            if (listener != null) {
2026                try {
2027                    listener.onCallConferenceExtendReceived(ImsCall.this, newCall);
2028                } catch (Throwable t) {
2029                    loge("callSessionConferenceExtendReceived :: ", t);
2030                }
2031            }
2032        }
2033
2034        @Override
2035        public void callSessionInviteParticipantsRequestDelivered(ImsCallSession session) {
2036            if (DBG) {
2037                log("callSessionInviteParticipantsRequestDelivered :: session=" + session);
2038            }
2039
2040            ImsCall.Listener listener;
2041
2042            synchronized(ImsCall.this) {
2043                listener = mListener;
2044            }
2045
2046            if (listener != null) {
2047                try {
2048                    listener.onCallInviteParticipantsRequestDelivered(ImsCall.this);
2049                } catch (Throwable t) {
2050                    loge("callSessionInviteParticipantsRequestDelivered :: ", t);
2051                }
2052            }
2053        }
2054
2055        @Override
2056        public void callSessionInviteParticipantsRequestFailed(ImsCallSession session,
2057                ImsReasonInfo reasonInfo) {
2058            if (DBG) {
2059                log("callSessionInviteParticipantsRequestFailed :: session=" + session
2060                        + ", reasonInfo=" + reasonInfo);
2061            }
2062
2063            ImsCall.Listener listener;
2064
2065            synchronized(ImsCall.this) {
2066                listener = mListener;
2067            }
2068
2069            if (listener != null) {
2070                try {
2071                    listener.onCallInviteParticipantsRequestFailed(ImsCall.this, reasonInfo);
2072                } catch (Throwable t) {
2073                    loge("callSessionInviteParticipantsRequestFailed :: ", t);
2074                }
2075            }
2076        }
2077
2078        @Override
2079        public void callSessionRemoveParticipantsRequestDelivered(ImsCallSession session) {
2080            if (DBG) {
2081                log("callSessionRemoveParticipantsRequestDelivered :: session=" + session);
2082            }
2083
2084            ImsCall.Listener listener;
2085
2086            synchronized(ImsCall.this) {
2087                listener = mListener;
2088            }
2089
2090            if (listener != null) {
2091                try {
2092                    listener.onCallRemoveParticipantsRequestDelivered(ImsCall.this);
2093                } catch (Throwable t) {
2094                    loge("callSessionRemoveParticipantsRequestDelivered :: ", t);
2095                }
2096            }
2097        }
2098
2099        @Override
2100        public void callSessionRemoveParticipantsRequestFailed(ImsCallSession session,
2101                ImsReasonInfo reasonInfo) {
2102            if (DBG) {
2103                log("callSessionRemoveParticipantsRequestFailed :: session=" + session
2104                        + ", reasonInfo=" + reasonInfo);
2105            }
2106
2107            ImsCall.Listener listener;
2108
2109            synchronized(ImsCall.this) {
2110                listener = mListener;
2111            }
2112
2113            if (listener != null) {
2114                try {
2115                    listener.onCallRemoveParticipantsRequestFailed(ImsCall.this, reasonInfo);
2116                } catch (Throwable t) {
2117                    loge("callSessionRemoveParticipantsRequestFailed :: ", t);
2118                }
2119            }
2120        }
2121
2122        @Override
2123        public void callSessionConferenceStateUpdated(ImsCallSession session,
2124                ImsConferenceState state) {
2125            if (DBG) {
2126                log("callSessionConferenceStateUpdated :: session=" + session
2127                        + ", state=" + state);
2128            }
2129
2130            ImsCall.Listener listener;
2131
2132            synchronized(ImsCall.this) {
2133                notifyConferenceStateUpdated(state);
2134                listener = mListener;
2135            }
2136
2137            if (listener != null) {
2138                try {
2139                    listener.onCallConferenceStateUpdated(ImsCall.this, state);
2140                } catch (Throwable t) {
2141                    loge("callSessionConferenceStateUpdated :: ", t);
2142                }
2143            }
2144        }
2145
2146        @Override
2147        public void callSessionUssdMessageReceived(ImsCallSession session,
2148                int mode, String ussdMessage) {
2149            if (DBG) {
2150                log("callSessionUssdMessageReceived :: session=" + session
2151                        + ", mode=" + mode + ", ussdMessage=" + ussdMessage);
2152            }
2153
2154            ImsCall.Listener listener;
2155
2156            synchronized(ImsCall.this) {
2157                listener = mListener;
2158            }
2159
2160            if (listener != null) {
2161                try {
2162                    listener.onCallUssdMessageReceived(ImsCall.this, mode, ussdMessage);
2163                } catch (Throwable t) {
2164                    loge("callSessionUssdMessageReceived :: ", t);
2165                }
2166            }
2167        }
2168    }
2169}
2170