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