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