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