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