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