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