ImsCall.java revision 111eecc9845826e985c48133b754e453fb3aca84
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        // perform operation on owner before doing any local checks: local
943        // call may not have its status updated
944        synchronized (mLockObj) {
945            CallGroup callGroup = mCallGroup;
946            if (callGroup != null && !callGroup.isOwner(ImsCall.this)) {
947                log("hold owner of the call group");
948                ImsCall owner = (ImsCall) callGroup.getOwner();
949                if (owner != null) {
950                    owner.hold();
951                    return;
952                }
953            }
954        }
955
956        if (isOnHold()) {
957            if (DBG) {
958                log("hold :: call is already on hold");
959            }
960            return;
961        }
962
963        synchronized(mLockObj) {
964            if (mUpdateRequest != UPDATE_NONE) {
965                loge("hold :: update is in progress; request=" + mUpdateRequest);
966                throw new ImsException("Call update is in progress",
967                        ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
968            }
969
970            if (mSession == null) {
971                loge("hold :: ");
972                throw new ImsException("No call session",
973                        ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
974            }
975
976            mSession.hold(createHoldMediaProfile());
977            // FIXME: update the state on the callback?
978            mHold = true;
979            mUpdateRequest = UPDATE_HOLD;
980        }
981    }
982
983    /**
984     * Continues a call that's on hold. When succeeds, {@link Listener#onCallResumed} is called.
985     *
986     * @see Listener#onCallResumed, Listener#onCallResumeFailed
987     * @throws ImsException if the IMS service fails to resume the call
988     */
989    public void resume() throws ImsException {
990        if (DBG) {
991            log("resume :: session=" + mSession);
992        }
993
994        // perform operation on owner before doing any local checks: local
995        // call may not have its status updated
996        synchronized (mLockObj) {
997            CallGroup callGroup = mCallGroup;
998            if (callGroup != null && !callGroup.isOwner(ImsCall.this)) {
999                log("resume owner of the call group");
1000                ImsCall owner = (ImsCall) callGroup.getOwner();
1001                if (owner != null) {
1002                    owner.resume();
1003                    return;
1004                }
1005            }
1006        }
1007
1008        if (!isOnHold()) {
1009            if (DBG) {
1010                log("resume :: call is in conversation");
1011            }
1012            return;
1013        }
1014
1015        synchronized(mLockObj) {
1016            if (mUpdateRequest != UPDATE_NONE) {
1017                loge("resume :: update is in progress; request=" + mUpdateRequest);
1018                throw new ImsException("Call update is in progress",
1019                        ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
1020            }
1021
1022            if (mSession == null) {
1023                loge("resume :: ");
1024                throw new ImsException("No call session",
1025                        ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1026            }
1027
1028            mSession.resume(createResumeMediaProfile());
1029            // FIXME: update the state on the callback?
1030            mHold = false;
1031            mUpdateRequest = UPDATE_RESUME;
1032        }
1033    }
1034
1035    /**
1036     * Merges the active & hold call.
1037     *
1038     * @see Listener#onCallMerged, Listener#onCallMergeFailed
1039     * @throws ImsException if the IMS service fails to merge the call
1040     */
1041    public void merge() throws ImsException {
1042        if (DBG) {
1043            log("merge :: session=" + mSession);
1044        }
1045
1046        synchronized(mLockObj) {
1047            if (mUpdateRequest != UPDATE_NONE) {
1048                loge("merge :: update is in progress; request=" + mUpdateRequest);
1049                throw new ImsException("Call update is in progress",
1050                        ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
1051            }
1052
1053            if (mSession == null) {
1054                loge("merge :: ");
1055                throw new ImsException("No call session",
1056                        ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1057            }
1058
1059            // if skipHoldBeforeMerge = true, IMS service implementation will
1060            // merge without explicitly holding the call.
1061            if (mHold || (mContext.getResources().getBoolean(
1062                    com.android.internal.R.bool.skipHoldBeforeMerge))) {
1063                mSession.merge();
1064                mUpdateRequest = UPDATE_MERGE;
1065            } else {
1066                mSession.hold(createHoldMediaProfile());
1067                // FIXME: ?
1068                mHold = true;
1069                mUpdateRequest = UPDATE_HOLD_MERGE;
1070            }
1071        }
1072    }
1073
1074    /**
1075     * Merges the active & hold call.
1076     *
1077     * @param bgCall the background (holding) call
1078     * @see Listener#onCallMerged, Listener#onCallMergeFailed
1079     * @throws ImsException if the IMS service fails to merge the call
1080     */
1081    public void merge(ImsCall bgCall) throws ImsException {
1082        if (DBG) {
1083            log("merge(1) :: session=" + mSession);
1084        }
1085
1086        if (bgCall == null) {
1087            throw new ImsException("No background call",
1088                    ImsReasonInfo.CODE_LOCAL_ILLEGAL_ARGUMENT);
1089        }
1090
1091        synchronized(mLockObj) {
1092            createCallGroup(bgCall);
1093        }
1094
1095        merge();
1096    }
1097
1098    /**
1099     * Updates the current call's properties (ex. call mode change: video upgrade / downgrade).
1100     */
1101    public void update(int callType, ImsStreamMediaProfile mediaProfile) throws ImsException {
1102        if (DBG) {
1103            log("update :: session=" + mSession);
1104        }
1105
1106        if (isOnHold()) {
1107            if (DBG) {
1108                log("update :: call is on hold");
1109            }
1110            throw new ImsException("Not in a call to update call",
1111                    ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
1112        }
1113
1114        synchronized(mLockObj) {
1115            if (mUpdateRequest != UPDATE_NONE) {
1116                if (DBG) {
1117                    log("update :: update is in progress; request=" + mUpdateRequest);
1118                }
1119                throw new ImsException("Call update is in progress",
1120                        ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
1121            }
1122
1123            if (mSession == null) {
1124                loge("update :: ");
1125                throw new ImsException("No call session",
1126                        ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1127            }
1128
1129            mSession.update(callType, mediaProfile);
1130            mUpdateRequest = UPDATE_UNSPECIFIED;
1131        }
1132    }
1133
1134    /**
1135     * Extends this call (1-to-1 call) to the conference call
1136     * inviting the specified participants to.
1137     *
1138     */
1139    public void extendToConference(String[] participants) throws ImsException {
1140        if (DBG) {
1141            log("extendToConference :: session=" + mSession);
1142        }
1143
1144        if (isOnHold()) {
1145            if (DBG) {
1146                log("extendToConference :: call is on hold");
1147            }
1148            throw new ImsException("Not in a call to extend a call to conference",
1149                    ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
1150        }
1151
1152        synchronized(mLockObj) {
1153            if (mUpdateRequest != UPDATE_NONE) {
1154                if (DBG) {
1155                    log("extendToConference :: update is in progress; request=" + mUpdateRequest);
1156                }
1157                throw new ImsException("Call update is in progress",
1158                        ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
1159            }
1160
1161            if (mSession == null) {
1162                loge("extendToConference :: ");
1163                throw new ImsException("No call session",
1164                        ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1165            }
1166
1167            mSession.extendToConference(participants);
1168            mUpdateRequest = UPDATE_EXTEND_TO_CONFERENCE;
1169        }
1170    }
1171
1172    /**
1173     * Requests the conference server to invite an additional participants to the conference.
1174     *
1175     */
1176    public void inviteParticipants(String[] participants) throws ImsException {
1177        if (DBG) {
1178            log("inviteParticipants :: session=" + mSession);
1179        }
1180
1181        synchronized(mLockObj) {
1182            if (mSession == null) {
1183                loge("inviteParticipants :: ");
1184                throw new ImsException("No call session",
1185                        ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1186            }
1187
1188            mSession.inviteParticipants(participants);
1189        }
1190    }
1191
1192    /**
1193     * Requests the conference server to remove the specified participants from the conference.
1194     *
1195     */
1196    public void removeParticipants(String[] participants) throws ImsException {
1197        if (DBG) {
1198            log("removeParticipants :: session=" + mSession);
1199        }
1200
1201        synchronized(mLockObj) {
1202            if (mSession == null) {
1203                loge("removeParticipants :: ");
1204                throw new ImsException("No call session",
1205                        ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1206            }
1207
1208            mSession.removeParticipants(participants);
1209        }
1210    }
1211
1212
1213    /**
1214     * Sends a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2833</a>,
1215     * event 0 ~ 9 maps to decimal value 0 ~ 9, '*' to 10, '#' to 11, event 'A' ~ 'D' to 12 ~ 15,
1216     * and event flash to 16. Currently, event flash is not supported.
1217     *
1218     * @param char that represents the DTMF digit to send.
1219     */
1220    public void sendDtmf(char c) {
1221        sendDtmf(c, null);
1222    }
1223
1224    /**
1225     * Sends a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2833</a>,
1226     * event 0 ~ 9 maps to decimal value 0 ~ 9, '*' to 10, '#' to 11, event 'A' ~ 'D' to 12 ~ 15,
1227     * and event flash to 16. Currently, event flash is not supported.
1228     *
1229     * @param c that represents the DTMF to send. '0' ~ '9', 'A' ~ 'D', '*', '#' are valid inputs.
1230     * @param result the result message to send when done.
1231     */
1232    public void sendDtmf(char c, Message result) {
1233        if (DBG) {
1234            log("sendDtmf :: session=" + mSession + ", code=" + c);
1235        }
1236
1237        synchronized(mLockObj) {
1238            if (mSession != null) {
1239                mSession.sendDtmf(c);
1240            }
1241        }
1242
1243        if (result != null) {
1244            result.sendToTarget();
1245        }
1246    }
1247
1248    /**
1249     * Sends an USSD message.
1250     *
1251     * @param ussdMessage USSD message to send
1252     */
1253    public void sendUssd(String ussdMessage) throws ImsException {
1254        if (DBG) {
1255            log("sendUssd :: session=" + mSession + ", ussdMessage=" + ussdMessage);
1256        }
1257
1258        synchronized(mLockObj) {
1259            if (mSession == null) {
1260                loge("sendUssd :: ");
1261                throw new ImsException("No call session",
1262                        ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1263            }
1264
1265            mSession.sendUssd(ussdMessage);
1266        }
1267    }
1268
1269    private void clear(ImsReasonInfo lastReasonInfo) {
1270        mInCall = false;
1271        mHold = false;
1272        mUpdateRequest = UPDATE_NONE;
1273        mLastReasonInfo = lastReasonInfo;
1274        destroyCallGroup();
1275    }
1276
1277    private void createCallGroup(ImsCall neutralReferrer) {
1278        CallGroup referrerCallGroup = neutralReferrer.getCallGroup();
1279
1280        if (mCallGroup == null) {
1281            if (referrerCallGroup == null) {
1282                mCallGroup = CallGroupManager.getInstance().createCallGroup(new ImsCallGroup());
1283            } else {
1284                mCallGroup = referrerCallGroup;
1285            }
1286
1287            if (mCallGroup != null) {
1288                mCallGroup.setNeutralReferrer(neutralReferrer);
1289            }
1290        } else {
1291            mCallGroup.setNeutralReferrer(neutralReferrer);
1292
1293            if ((referrerCallGroup != null)
1294                    && (mCallGroup != referrerCallGroup)) {
1295                loge("fatal :: call group is mismatched; call is corrupted...");
1296            }
1297        }
1298    }
1299
1300    private void updateCallGroup(ImsCall owner) {
1301        if (mCallGroup == null) {
1302            return;
1303        }
1304
1305        ImsCall neutralReferrer = (ImsCall)mCallGroup.getNeutralReferrer();
1306
1307        if (owner == null) {
1308            // Maintain the call group if the current call has been merged in the past.
1309            if (!mCallGroup.hasReferrer()) {
1310                CallGroupManager.getInstance().destroyCallGroup(mCallGroup);
1311                mCallGroup = null;
1312            }
1313        } else {
1314            mCallGroup.addReferrer(this);
1315
1316            if (neutralReferrer != null) {
1317                if (neutralReferrer.getCallGroup() == null) {
1318                    neutralReferrer.setCallGroup(mCallGroup);
1319                    mCallGroup.addReferrer(neutralReferrer);
1320                }
1321
1322                neutralReferrer.enforceConversationMode();
1323            }
1324
1325            // Close the existing owner call if present
1326            ImsCall exOwner = (ImsCall)mCallGroup.getOwner();
1327
1328            mCallGroup.setOwner(owner);
1329
1330            if (exOwner != null) {
1331                exOwner.close();
1332            }
1333        }
1334    }
1335
1336    private void destroyCallGroup() {
1337        if (mCallGroup == null) {
1338            return;
1339        }
1340
1341        mCallGroup.removeReferrer(this);
1342
1343        if (!mCallGroup.hasReferrer()) {
1344            CallGroupManager.getInstance().destroyCallGroup(mCallGroup);
1345        }
1346
1347        mCallGroup = null;
1348    }
1349
1350    private CallGroup getCallGroup() {
1351        synchronized(mLockObj) {
1352            return mCallGroup;
1353        }
1354    }
1355
1356    private void setCallGroup(CallGroup callGroup) {
1357        synchronized(mLockObj) {
1358            mCallGroup = callGroup;
1359        }
1360    }
1361
1362    /**
1363     * Creates an IMS call session listener.
1364     */
1365    private ImsCallSession.Listener createCallSessionListener() {
1366        return new ImsCallSessionListenerProxy();
1367    }
1368
1369    private ImsCall createNewCall(ImsCallSession session, ImsCallProfile profile) {
1370        ImsCall call = new ImsCall(mContext, profile);
1371
1372        try {
1373            call.attachSession(session);
1374        } catch (ImsException e) {
1375            if (call != null) {
1376                call.close();
1377                call = null;
1378            }
1379        }
1380
1381        // Do additional operations...
1382
1383        return call;
1384    }
1385
1386    private ImsStreamMediaProfile createHoldMediaProfile() {
1387        ImsStreamMediaProfile mediaProfile = new ImsStreamMediaProfile();
1388
1389        if (mCallProfile == null) {
1390            return mediaProfile;
1391        }
1392
1393        mediaProfile.mAudioQuality = mCallProfile.mMediaProfile.mAudioQuality;
1394        mediaProfile.mVideoQuality = mCallProfile.mMediaProfile.mVideoQuality;
1395        mediaProfile.mAudioDirection = ImsStreamMediaProfile.DIRECTION_SEND;
1396
1397        if (mediaProfile.mVideoQuality != ImsStreamMediaProfile.VIDEO_QUALITY_NONE) {
1398            mediaProfile.mVideoDirection = ImsStreamMediaProfile.DIRECTION_SEND;
1399        }
1400
1401        return mediaProfile;
1402    }
1403
1404    private ImsStreamMediaProfile createResumeMediaProfile() {
1405        ImsStreamMediaProfile mediaProfile = new ImsStreamMediaProfile();
1406
1407        if (mCallProfile == null) {
1408            return mediaProfile;
1409        }
1410
1411        mediaProfile.mAudioQuality = mCallProfile.mMediaProfile.mAudioQuality;
1412        mediaProfile.mVideoQuality = mCallProfile.mMediaProfile.mVideoQuality;
1413        mediaProfile.mAudioDirection = ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE;
1414
1415        if (mediaProfile.mVideoQuality != ImsStreamMediaProfile.VIDEO_QUALITY_NONE) {
1416            mediaProfile.mVideoDirection = ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE;
1417        }
1418
1419        return mediaProfile;
1420    }
1421
1422    private void enforceConversationMode() {
1423        if (mInCall) {
1424            mHold = false;
1425            mUpdateRequest = UPDATE_NONE;
1426        }
1427    }
1428
1429    private void mergeInternal() {
1430        if (DBG) {
1431            log("mergeInternal :: session=" + mSession);
1432        }
1433
1434        mSession.merge();
1435        mUpdateRequest = UPDATE_MERGE;
1436    }
1437
1438    private void notifyCallStateChanged() {
1439        int state = 0;
1440
1441        if (mInCall && (mUpdateRequest == UPDATE_HOLD_MERGE)) {
1442            state = CALL_STATE_ACTIVE_TO_HOLD;
1443            mHold = true;
1444        } else if (mInCall && ((mUpdateRequest == UPDATE_MERGE)
1445                || (mUpdateRequest == UPDATE_EXTEND_TO_CONFERENCE))) {
1446            state = CALL_STATE_HOLD_TO_ACTIVE;
1447            mHold = false;
1448            mMute = false;
1449        }
1450
1451        if (state != 0) {
1452            if (mListener != null) {
1453                try {
1454                    mListener.onCallStateChanged(ImsCall.this, state);
1455                } catch (Throwable t) {
1456                    loge("notifyCallStateChanged :: ", t);
1457                }
1458            }
1459        }
1460    }
1461
1462    private void notifyConferenceSessionTerminated(ImsReasonInfo reasonInfo) {
1463        ImsCall.Listener listener;
1464
1465        if (mCallGroup.isOwner(ImsCall.this)) {
1466            log("Group Owner! Size of referrers list = " + mCallGroup.getReferrers().size());
1467            while (mCallGroup.hasReferrer()) {
1468                ImsCall call = (ImsCall) mCallGroup.getReferrers().get(0);
1469                log("onCallTerminated to be called for the call:: " + call);
1470
1471                if (call == null) {
1472                    continue;
1473                }
1474
1475                listener = call.mListener;
1476                call.clear(reasonInfo);
1477
1478                if (listener != null) {
1479                    try {
1480                        listener.onCallTerminated(call, reasonInfo);
1481                    } catch (Throwable t) {
1482                        loge("notifyConferenceSessionTerminated :: ", t);
1483                    }
1484                }
1485            }
1486        } else if (!mCallGroup.isReferrer(ImsCall.this)) {
1487            return;
1488        }
1489
1490        listener = mListener;
1491        clear(reasonInfo);
1492
1493        if (listener != null) {
1494            try {
1495                listener.onCallTerminated(this, reasonInfo);
1496            } catch (Throwable t) {
1497                loge("notifyConferenceSessionTerminated :: ", t);
1498            }
1499        }
1500    }
1501
1502    private void notifyConferenceStateUpdatedThroughGroupOwner(int update) {
1503        ImsCall.Listener listener;
1504
1505        if (mCallGroup.isOwner(ImsCall.this)) {
1506            log("Group Owner! Size of referrers list = " + mCallGroup.getReferrers().size());
1507            for (ICall icall : mCallGroup.getReferrers()) {
1508                ImsCall call = (ImsCall) icall;
1509                log("notifyConferenceStateUpdatedThroughGroupOwner to be called for the call:: " + call);
1510
1511                if (call == null) {
1512                    continue;
1513                }
1514
1515                listener = call.mListener;
1516
1517                if (listener != null) {
1518                    try {
1519                        switch (update) {
1520                            case UPDATE_HOLD:
1521                                listener.onCallHeld(call);
1522                                break;
1523                            case UPDATE_RESUME:
1524                                listener.onCallResumed(call);
1525                                break;
1526                            default:
1527                                loge("notifyConferenceStateUpdatedThroughGroupOwner :: not handled update "
1528                                        + update);
1529                        }
1530                    } catch (Throwable t) {
1531                        loge("notifyConferenceStateUpdatedThroughGroupOwner :: ", t);
1532                    }
1533                }
1534            }
1535        }
1536    }
1537
1538    private void notifyConferenceStateUpdated(ImsConferenceState state) {
1539        Set<Entry<String, Bundle>> paticipants = state.mParticipants.entrySet();
1540
1541        if (paticipants == null) {
1542            return;
1543        }
1544
1545        Iterator<Entry<String, Bundle>> iterator = paticipants.iterator();
1546
1547        while (iterator.hasNext()) {
1548            Entry<String, Bundle> entry = iterator.next();
1549
1550            String key = entry.getKey();
1551            Bundle confInfo = entry.getValue();
1552            String status = confInfo.getString(ImsConferenceState.STATUS);
1553            String user = confInfo.getString(ImsConferenceState.USER);
1554            String endpoint = confInfo.getString(ImsConferenceState.ENDPOINT);
1555
1556            if (DBG) {
1557                log("notifyConferenceStateUpdated :: key=" + key +
1558                        ", status=" + status +
1559                        ", user=" + user +
1560                        ", endpoint=" + endpoint);
1561            }
1562
1563            if ((mCallGroup != null) && (!mCallGroup.isOwner(ImsCall.this))) {
1564                continue;
1565            }
1566
1567            ImsCall referrer = (ImsCall)mCallGroup.getReferrer(endpoint);
1568
1569            if (referrer == null) {
1570                continue;
1571            }
1572
1573            if (referrer.mListener == null) {
1574                continue;
1575            }
1576
1577            try {
1578                if (status.equals(ImsConferenceState.STATUS_ALERTING)) {
1579                    referrer.mListener.onCallProgressing(referrer);
1580                }
1581                else if (status.equals(ImsConferenceState.STATUS_CONNECT_FAIL)) {
1582                    referrer.mListener.onCallStartFailed(referrer, new ImsReasonInfo());
1583                }
1584                else if (status.equals(ImsConferenceState.STATUS_ON_HOLD)) {
1585                    referrer.mListener.onCallHoldReceived(referrer);
1586                }
1587                else if (status.equals(ImsConferenceState.STATUS_CONNECTED)) {
1588                    referrer.mListener.onCallStarted(referrer);
1589                }
1590                else if (status.equals(ImsConferenceState.STATUS_DISCONNECTED)) {
1591                    referrer.clear(new ImsReasonInfo());
1592                    referrer.mListener.onCallTerminated(referrer, referrer.mLastReasonInfo);
1593                }
1594            } catch (Throwable t) {
1595                loge("notifyConferenceStateUpdated :: ", t);
1596            }
1597        }
1598    }
1599
1600    private void notifyError(int reason, int statusCode, String message) {
1601    }
1602
1603    private void throwImsException(Throwable t, int code) throws ImsException {
1604        if (t instanceof ImsException) {
1605            throw (ImsException) t;
1606        } else {
1607            throw new ImsException(String.valueOf(code), t, code);
1608        }
1609    }
1610
1611    private void log(String s) {
1612        Rlog.d(TAG, s);
1613    }
1614
1615    private void loge(String s) {
1616        Rlog.e(TAG, s);
1617    }
1618
1619    private void loge(String s, Throwable t) {
1620        Rlog.e(TAG, s, t);
1621    }
1622
1623    private class ImsCallSessionListenerProxy extends ImsCallSession.Listener {
1624        @Override
1625        public void callSessionProgressing(ImsCallSession session,
1626                ImsStreamMediaProfile profile) {
1627            if (DBG) {
1628                log("callSessionProgressing :: session=" + session + ", profile=" + profile);
1629            }
1630
1631            ImsCall.Listener listener;
1632
1633            synchronized(ImsCall.this) {
1634                listener = mListener;
1635                mCallProfile.mMediaProfile.copyFrom(profile);
1636            }
1637
1638            if (listener != null) {
1639                try {
1640                    listener.onCallProgressing(ImsCall.this);
1641                } catch (Throwable t) {
1642                    loge("callSessionProgressing :: ", t);
1643                }
1644            }
1645        }
1646
1647        @Override
1648        public void callSessionStarted(ImsCallSession session,
1649                ImsCallProfile profile) {
1650            if (DBG) {
1651                log("callSessionStarted :: session=" + session + ", profile=" + profile);
1652            }
1653
1654            ImsCall.Listener listener;
1655
1656            synchronized(ImsCall.this) {
1657                listener = mListener;
1658                mCallProfile = profile;
1659            }
1660
1661            if (listener != null) {
1662                try {
1663                    listener.onCallStarted(ImsCall.this);
1664                } catch (Throwable t) {
1665                    loge("callSessionStarted :: ", t);
1666                }
1667            }
1668        }
1669
1670        @Override
1671        public void callSessionStartFailed(ImsCallSession session,
1672                ImsReasonInfo reasonInfo) {
1673            if (DBG) {
1674                log("callSessionStartFailed :: session=" + session +
1675                        ", reasonInfo=" + reasonInfo);
1676            }
1677
1678            ImsCall.Listener listener;
1679
1680            synchronized(ImsCall.this) {
1681                listener = mListener;
1682                mLastReasonInfo = reasonInfo;
1683            }
1684
1685            if (listener != null) {
1686                try {
1687                    listener.onCallStartFailed(ImsCall.this, reasonInfo);
1688                } catch (Throwable t) {
1689                    loge("callSessionStarted :: ", t);
1690                }
1691            }
1692        }
1693
1694        @Override
1695        public void callSessionTerminated(ImsCallSession session,
1696                ImsReasonInfo reasonInfo) {
1697            if (DBG) {
1698                log("callSessionTerminated :: session=" + session +
1699                        ", reasonInfo=" + reasonInfo);
1700            }
1701
1702            ImsCall.Listener listener = null;
1703
1704            synchronized(ImsCall.this) {
1705                if (mCallGroup != null) {
1706                    notifyConferenceSessionTerminated(reasonInfo);
1707                } else {
1708                    listener = mListener;
1709                    clear(reasonInfo);
1710                }
1711            }
1712
1713            if (listener != null) {
1714                try {
1715                    listener.onCallTerminated(ImsCall.this, reasonInfo);
1716                } catch (Throwable t) {
1717                    loge("callSessionTerminated :: ", t);
1718                }
1719            }
1720        }
1721
1722        @Override
1723        public void callSessionHeld(ImsCallSession session,
1724                ImsCallProfile profile) {
1725            if (DBG) {
1726                log("callSessionHeld :: session=" + session + ", profile=" + profile);
1727            }
1728
1729            ImsCall.Listener listener;
1730
1731            synchronized(ImsCall.this) {
1732                mCallProfile = profile;
1733
1734                if (mUpdateRequest == UPDATE_HOLD_MERGE) {
1735                    mergeInternal();
1736                    return;
1737                }
1738
1739                mUpdateRequest = UPDATE_NONE;
1740                listener = mListener;
1741            }
1742
1743            if (listener != null) {
1744                try {
1745                    listener.onCallHeld(ImsCall.this);
1746                } catch (Throwable t) {
1747                    loge("callSessionHeld :: ", t);
1748                }
1749            }
1750
1751            if (mCallGroup != null) {
1752                notifyConferenceStateUpdatedThroughGroupOwner(UPDATE_HOLD);
1753            }
1754        }
1755
1756        @Override
1757        public void callSessionHoldFailed(ImsCallSession session,
1758                ImsReasonInfo reasonInfo) {
1759            if (DBG) {
1760                log("callSessionHoldFailed :: session=" + session +
1761                        ", reasonInfo=" + reasonInfo);
1762            }
1763
1764            boolean isHoldForMerge = false;
1765            ImsCall.Listener listener;
1766
1767            synchronized(ImsCall.this) {
1768                if (mUpdateRequest == UPDATE_HOLD_MERGE) {
1769                    isHoldForMerge = true;
1770                }
1771
1772                mUpdateRequest = UPDATE_NONE;
1773                listener = mListener;
1774            }
1775
1776            if (isHoldForMerge) {
1777                callSessionMergeFailed(session, reasonInfo);
1778                return;
1779            }
1780
1781            if (listener != null) {
1782                try {
1783                    listener.onCallHoldFailed(ImsCall.this, reasonInfo);
1784                } catch (Throwable t) {
1785                    loge("callSessionHoldFailed :: ", t);
1786                }
1787            }
1788        }
1789
1790        @Override
1791        public void callSessionHoldReceived(ImsCallSession session,
1792                ImsCallProfile profile) {
1793            if (DBG) {
1794                log("callSessionHoldReceived :: session=" + session + ", profile=" + profile);
1795            }
1796
1797            ImsCall.Listener listener;
1798
1799            synchronized(ImsCall.this) {
1800                listener = mListener;
1801                mCallProfile = profile;
1802            }
1803
1804            if (listener != null) {
1805                try {
1806                    listener.onCallHoldReceived(ImsCall.this);
1807                } catch (Throwable t) {
1808                    loge("callSessionHoldReceived :: ", t);
1809                }
1810            }
1811        }
1812
1813        @Override
1814        public void callSessionResumed(ImsCallSession session,
1815                ImsCallProfile profile) {
1816            if (DBG) {
1817                log("callSessionResumed :: session=" + session + ", profile=" + profile);
1818            }
1819
1820            ImsCall.Listener listener;
1821
1822            synchronized(ImsCall.this) {
1823                listener = mListener;
1824                mCallProfile = profile;
1825                mUpdateRequest = UPDATE_NONE;
1826            }
1827
1828            if (listener != null) {
1829                try {
1830                    listener.onCallResumed(ImsCall.this);
1831                } catch (Throwable t) {
1832                    loge("callSessionResumed :: ", t);
1833                }
1834            }
1835
1836            if (mCallGroup != null) {
1837                notifyConferenceStateUpdatedThroughGroupOwner(UPDATE_RESUME);
1838            }
1839        }
1840
1841        @Override
1842        public void callSessionResumeFailed(ImsCallSession session,
1843                ImsReasonInfo reasonInfo) {
1844            if (DBG) {
1845                log("callSessionResumeFailed :: session=" + session +
1846                        ", reasonInfo=" + reasonInfo);
1847            }
1848
1849            ImsCall.Listener listener;
1850
1851            synchronized(ImsCall.this) {
1852                listener = mListener;
1853                mUpdateRequest = UPDATE_NONE;
1854            }
1855
1856            if (listener != null) {
1857                try {
1858                    listener.onCallResumeFailed(ImsCall.this, reasonInfo);
1859                } catch (Throwable t) {
1860                    loge("callSessionResumeFailed :: ", t);
1861                }
1862            }
1863        }
1864
1865        @Override
1866        public void callSessionResumeReceived(ImsCallSession session,
1867                ImsCallProfile profile) {
1868            if (DBG) {
1869                log("callSessionResumeReceived :: session=" + session +
1870                        ", profile=" + profile);
1871            }
1872
1873            ImsCall.Listener listener;
1874
1875            synchronized(ImsCall.this) {
1876                listener = mListener;
1877                mCallProfile = profile;
1878            }
1879
1880            if (listener != null) {
1881                try {
1882                    listener.onCallResumeReceived(ImsCall.this);
1883                } catch (Throwable t) {
1884                    loge("callSessionResumeReceived :: ", t);
1885                }
1886            }
1887        }
1888
1889        @Override
1890        public void callSessionMerged(ImsCallSession session,
1891                ImsCallSession newSession, ImsCallProfile profile) {
1892            if (DBG) {
1893                log("callSessionMerged :: session=" + session
1894                        + ", newSession=" + newSession + ", profile=" + profile);
1895            }
1896
1897            ImsCall newCall = createNewCall(newSession, profile);
1898
1899            if (newCall == null) {
1900                callSessionMergeFailed(session, new ImsReasonInfo());
1901                return;
1902            }
1903
1904            ImsCall.Listener listener;
1905
1906            synchronized(ImsCall.this) {
1907                listener = mListener;
1908                updateCallGroup(newCall);
1909                newCall.setListener(mListener);
1910                newCall.setCallGroup(mCallGroup);
1911                mUpdateRequest = UPDATE_NONE;
1912            }
1913
1914            if (listener != null) {
1915                try {
1916                    listener.onCallMerged(ImsCall.this, newCall);
1917                } catch (Throwable t) {
1918                    loge("callSessionMerged :: ", t);
1919                }
1920            }
1921        }
1922
1923        @Override
1924        public void callSessionMergeFailed(ImsCallSession session,
1925                ImsReasonInfo reasonInfo) {
1926            if (DBG) {
1927                log("callSessionMergeFailed :: session=" + session +
1928                        ", reasonInfo=" + reasonInfo);
1929            }
1930
1931            ImsCall.Listener listener;
1932
1933            synchronized(ImsCall.this) {
1934                listener = mListener;
1935                updateCallGroup(null);
1936                mUpdateRequest = UPDATE_NONE;
1937            }
1938
1939            if (listener != null) {
1940                try {
1941                    listener.onCallMergeFailed(ImsCall.this, reasonInfo);
1942                } catch (Throwable t) {
1943                    loge("callSessionMergeFailed :: ", t);
1944                }
1945            }
1946        }
1947
1948        @Override
1949        public void callSessionUpdated(ImsCallSession session,
1950                ImsCallProfile profile) {
1951            if (DBG) {
1952                log("callSessionUpdated :: session=" + session + ", profile=" + profile);
1953            }
1954
1955            ImsCall.Listener listener;
1956
1957            synchronized(ImsCall.this) {
1958                listener = mListener;
1959                mCallProfile = profile;
1960                mUpdateRequest = UPDATE_NONE;
1961            }
1962
1963            if (listener != null) {
1964                try {
1965                    listener.onCallUpdated(ImsCall.this);
1966                } catch (Throwable t) {
1967                    loge("callSessionUpdated :: ", t);
1968                }
1969            }
1970        }
1971
1972        @Override
1973        public void callSessionUpdateFailed(ImsCallSession session,
1974                ImsReasonInfo reasonInfo) {
1975            if (DBG) {
1976                log("callSessionUpdateFailed :: session=" + session +
1977                        ", reasonInfo=" + reasonInfo);
1978            }
1979
1980            ImsCall.Listener listener;
1981
1982            synchronized(ImsCall.this) {
1983                listener = mListener;
1984                mUpdateRequest = UPDATE_NONE;
1985            }
1986
1987            if (listener != null) {
1988                try {
1989                    listener.onCallUpdateFailed(ImsCall.this, reasonInfo);
1990                } catch (Throwable t) {
1991                    loge("callSessionUpdateFailed :: ", t);
1992                }
1993            }
1994        }
1995
1996        @Override
1997        public void callSessionUpdateReceived(ImsCallSession session,
1998                ImsCallProfile profile) {
1999            if (DBG) {
2000                log("callSessionUpdateReceived :: session=" + session +
2001                        ", profile=" + profile);
2002            }
2003
2004            ImsCall.Listener listener;
2005
2006            synchronized(ImsCall.this) {
2007                listener = mListener;
2008                mProposedCallProfile = profile;
2009                mUpdateRequest = UPDATE_UNSPECIFIED;
2010            }
2011
2012            if (listener != null) {
2013                try {
2014                    listener.onCallUpdateReceived(ImsCall.this);
2015                } catch (Throwable t) {
2016                    loge("callSessionUpdateReceived :: ", t);
2017                }
2018            }
2019        }
2020
2021        @Override
2022        public void callSessionConferenceExtended(ImsCallSession session,
2023                ImsCallSession newSession, ImsCallProfile profile) {
2024            if (DBG) {
2025                log("callSessionConferenceExtended :: session=" + session
2026                        + ", newSession=" + newSession + ", profile=" + profile);
2027            }
2028
2029            ImsCall newCall = createNewCall(newSession, profile);
2030
2031            if (newCall == null) {
2032                callSessionConferenceExtendFailed(session, new ImsReasonInfo());
2033                return;
2034            }
2035
2036            ImsCall.Listener listener;
2037
2038            synchronized(ImsCall.this) {
2039                listener = mListener;
2040                mUpdateRequest = UPDATE_NONE;
2041            }
2042
2043            if (listener != null) {
2044                try {
2045                    listener.onCallConferenceExtended(ImsCall.this, newCall);
2046                } catch (Throwable t) {
2047                    loge("callSessionConferenceExtended :: ", t);
2048                }
2049            }
2050        }
2051
2052        @Override
2053        public void callSessionConferenceExtendFailed(ImsCallSession session,
2054                ImsReasonInfo reasonInfo) {
2055            if (DBG) {
2056                log("callSessionConferenceExtendFailed :: session=" + session +
2057                        ", reasonInfo=" + reasonInfo);
2058            }
2059
2060            ImsCall.Listener listener;
2061
2062            synchronized(ImsCall.this) {
2063                listener = mListener;
2064                mUpdateRequest = UPDATE_NONE;
2065            }
2066
2067            if (listener != null) {
2068                try {
2069                    listener.onCallConferenceExtendFailed(ImsCall.this, reasonInfo);
2070                } catch (Throwable t) {
2071                    loge("callSessionConferenceExtendFailed :: ", t);
2072                }
2073            }
2074        }
2075
2076        @Override
2077        public void callSessionConferenceExtendReceived(ImsCallSession session,
2078                ImsCallSession newSession, ImsCallProfile profile) {
2079            if (DBG) {
2080                log("callSessionConferenceExtendReceived :: session=" + session
2081                        + ", newSession=" + newSession + ", profile=" + profile);
2082            }
2083
2084            ImsCall newCall = createNewCall(newSession, profile);
2085
2086            if (newCall == null) {
2087                // Should all the calls be terminated...???
2088                return;
2089            }
2090
2091            ImsCall.Listener listener;
2092
2093            synchronized(ImsCall.this) {
2094                listener = mListener;
2095            }
2096
2097            if (listener != null) {
2098                try {
2099                    listener.onCallConferenceExtendReceived(ImsCall.this, newCall);
2100                } catch (Throwable t) {
2101                    loge("callSessionConferenceExtendReceived :: ", t);
2102                }
2103            }
2104        }
2105
2106        @Override
2107        public void callSessionInviteParticipantsRequestDelivered(ImsCallSession session) {
2108            if (DBG) {
2109                log("callSessionInviteParticipantsRequestDelivered :: session=" + session);
2110            }
2111
2112            ImsCall.Listener listener;
2113
2114            synchronized(ImsCall.this) {
2115                listener = mListener;
2116            }
2117
2118            if (listener != null) {
2119                try {
2120                    listener.onCallInviteParticipantsRequestDelivered(ImsCall.this);
2121                } catch (Throwable t) {
2122                    loge("callSessionInviteParticipantsRequestDelivered :: ", t);
2123                }
2124            }
2125        }
2126
2127        @Override
2128        public void callSessionInviteParticipantsRequestFailed(ImsCallSession session,
2129                ImsReasonInfo reasonInfo) {
2130            if (DBG) {
2131                log("callSessionInviteParticipantsRequestFailed :: session=" + session
2132                        + ", reasonInfo=" + reasonInfo);
2133            }
2134
2135            ImsCall.Listener listener;
2136
2137            synchronized(ImsCall.this) {
2138                listener = mListener;
2139            }
2140
2141            if (listener != null) {
2142                try {
2143                    listener.onCallInviteParticipantsRequestFailed(ImsCall.this, reasonInfo);
2144                } catch (Throwable t) {
2145                    loge("callSessionInviteParticipantsRequestFailed :: ", t);
2146                }
2147            }
2148        }
2149
2150        @Override
2151        public void callSessionRemoveParticipantsRequestDelivered(ImsCallSession session) {
2152            if (DBG) {
2153                log("callSessionRemoveParticipantsRequestDelivered :: session=" + session);
2154            }
2155
2156            ImsCall.Listener listener;
2157
2158            synchronized(ImsCall.this) {
2159                listener = mListener;
2160            }
2161
2162            if (listener != null) {
2163                try {
2164                    listener.onCallRemoveParticipantsRequestDelivered(ImsCall.this);
2165                } catch (Throwable t) {
2166                    loge("callSessionRemoveParticipantsRequestDelivered :: ", t);
2167                }
2168            }
2169        }
2170
2171        @Override
2172        public void callSessionRemoveParticipantsRequestFailed(ImsCallSession session,
2173                ImsReasonInfo reasonInfo) {
2174            if (DBG) {
2175                log("callSessionRemoveParticipantsRequestFailed :: session=" + session
2176                        + ", reasonInfo=" + reasonInfo);
2177            }
2178
2179            ImsCall.Listener listener;
2180
2181            synchronized(ImsCall.this) {
2182                listener = mListener;
2183            }
2184
2185            if (listener != null) {
2186                try {
2187                    listener.onCallRemoveParticipantsRequestFailed(ImsCall.this, reasonInfo);
2188                } catch (Throwable t) {
2189                    loge("callSessionRemoveParticipantsRequestFailed :: ", t);
2190                }
2191            }
2192        }
2193
2194        @Override
2195        public void callSessionConferenceStateUpdated(ImsCallSession session,
2196                ImsConferenceState state) {
2197            if (DBG) {
2198                log("callSessionConferenceStateUpdated :: session=" + session
2199                        + ", state=" + state);
2200            }
2201
2202            ImsCall.Listener listener;
2203
2204            synchronized(ImsCall.this) {
2205                notifyConferenceStateUpdated(state);
2206                listener = mListener;
2207            }
2208
2209            if (listener != null) {
2210                try {
2211                    listener.onCallConferenceStateUpdated(ImsCall.this, state);
2212                } catch (Throwable t) {
2213                    loge("callSessionConferenceStateUpdated :: ", t);
2214                }
2215            }
2216        }
2217
2218        @Override
2219        public void callSessionUssdMessageReceived(ImsCallSession session,
2220                int mode, String ussdMessage) {
2221            if (DBG) {
2222                log("callSessionUssdMessageReceived :: session=" + session
2223                        + ", mode=" + mode + ", ussdMessage=" + ussdMessage);
2224            }
2225
2226            ImsCall.Listener listener;
2227
2228            synchronized(ImsCall.this) {
2229                listener = mListener;
2230            }
2231
2232            if (listener != null) {
2233                try {
2234                    listener.onCallUssdMessageReceived(ImsCall.this, mode, ussdMessage);
2235                } catch (Throwable t) {
2236                    loge("callSessionUssdMessageReceived :: ", t);
2237                }
2238            }
2239        }
2240    }
2241}
2242